@bridge_gpt/mcp-server 0.2.68 → 0.2.69
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/README.md +99 -41
- package/build/bridge-permissions.js +627 -94
- package/build/check-update-cli.js +74 -0
- package/build/claude-settings.js +84 -2
- package/build/cli-help.js +7 -1
- package/build/codex-home.js +9 -1
- package/build/codex-launcher-pin.js +598 -0
- package/build/commands.generated.js +5 -5
- package/build/connect-github-api.js +33 -14
- package/build/docs.generated.js +2 -2
- package/build/doctor.js +109 -6
- package/build/epic-implementer-bin.js +80 -85
- package/build/executor/completion-contract.js +2 -0
- package/build/executor/http-client.js +43 -4
- package/build/index.js +464 -246
- package/build/install-bridge.js +260 -297
- package/build/launcher-config-inspection.js +184 -19
- package/build/mcp-host-targets.js +4 -0
- package/build/mcp-identity.js +7 -0
- package/build/pipelines.generated.js +4 -4
- package/build/readme.generated.js +1 -1
- package/build/session-upgrade-notice.js +86 -0
- package/build/start-tickets.js +15 -0
- package/build/tool-call-advisory.js +162 -0
- package/build/tool-surface-gating.js +28 -89
- package/build/update-check.js +97 -16
- package/build/update-status.js +61 -71
- package/build/upgrade-advice.js +15 -11
- package/build/upgrade-cli.js +347 -20
- package/build/version.generated.js +3 -3
- package/package.json +1 -1
package/build/upgrade-cli.js
CHANGED
|
@@ -30,9 +30,41 @@
|
|
|
30
30
|
*
|
|
31
31
|
* Every launcher claim is verified by RE-READING the configs after the write.
|
|
32
32
|
* The command never reports a transition it did not confirm on disk.
|
|
33
|
+
*
|
|
34
|
+
* ---------------------------------------------------------------------------
|
|
35
|
+
* BAPI-1445 — Codex's global config
|
|
36
|
+
* ---------------------------------------------------------------------------
|
|
37
|
+
* The three project JSON configs are not the only place a Bridge pin lives:
|
|
38
|
+
* Codex keeps its registration in one per-user TOML file
|
|
39
|
+
* (`~/.codex/config.toml`, or `$CODEX_HOME/config.toml` for an absolute
|
|
40
|
+
* override). A repin that skipped it left Codex on an old server for every
|
|
41
|
+
* project on the machine. Upgrade now inspects, repins, and verifies that file
|
|
42
|
+
* too, beside the JSON targets, with these boundaries:
|
|
43
|
+
*
|
|
44
|
+
* - Preflight refusal guarantees NO writes. An unsafe Codex file (a range, tag,
|
|
45
|
+
* or ahead pin, a duplicate or unlocatable registration, an unreadable file)
|
|
46
|
+
* stops the run in Phase 2, before local-install removal, `runInit`, any
|
|
47
|
+
* config write, or the permission write.
|
|
48
|
+
* - A failure AFTER `runInit` can leave the JSON configs upgraded. It exits
|
|
49
|
+
* through `did NOT converge` with exit code 1 — no rollback, no automatic
|
|
50
|
+
* retry, no completion line, and no reload guidance.
|
|
51
|
+
* - The Codex write is one surgical splice of the package specifier, written in
|
|
52
|
+
* place only if the file is byte-identical to what Phase 2 read. That compare
|
|
53
|
+
* is optimistic protection, not a lock.
|
|
54
|
+
* - Install is unchanged: it keeps its existing-table detection, its
|
|
55
|
+
* `manual-required` outcome, and its current guidance. Upgrade adds no install
|
|
56
|
+
* handoff and makes no repair calls. `LAUNCHER_CONFIG_TARGETS`, `runInit`,
|
|
57
|
+
* doctor, and Phases 1, 4, and 8 are untouched.
|
|
58
|
+
* - Windsurf is excluded by its `manual-instructions` write strategy: upgrade
|
|
59
|
+
* never inspects or writes its file, never prints its reload guidance, and
|
|
60
|
+
* leaves its manual setup with the operator.
|
|
61
|
+
* - Upgrade-notice delivery is a separate concern. The one thing this file does
|
|
62
|
+
* for it (BAPI-1446) is Phase 8's advisory SessionStart hook request, for a
|
|
63
|
+
* Claude Code installation only, pinned to the converged target version.
|
|
33
64
|
*/
|
|
34
65
|
import { spawn } from "child_process";
|
|
35
66
|
import { mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
67
|
+
import os from "os";
|
|
36
68
|
import path from "path";
|
|
37
69
|
import { VERSION } from "./version.generated.js";
|
|
38
70
|
import { runInit } from "./init.js";
|
|
@@ -40,15 +72,18 @@ import { InitPreflightError } from "./init.js";
|
|
|
40
72
|
import { isNewerVersion } from "./update-check.js";
|
|
41
73
|
import { fetchLatestVersion } from "./cli-release.js";
|
|
42
74
|
import { CANONICAL_UPGRADE_COMMAND } from "./upgrade-advice.js";
|
|
43
|
-
import {
|
|
75
|
+
import { applyCodexRepin, inspectCodexLauncher } from "./codex-launcher-pin.js";
|
|
76
|
+
import { describeLauncherReason, inspectGlobalLauncherConfigs, inspectLauncherConfigs, isApplicableStandardLauncher, isPrerequisiteFailure, LAUNCHER_CONFIG_TARGETS, resolveCodexConfigLocation, } from "./launcher-config-inspection.js";
|
|
77
|
+
import { allHostTargets, HOST_PLATFORM_ORDER, MCP_HOST_TARGETS, } from "./mcp-host-targets.js";
|
|
44
78
|
import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
45
|
-
import { applyProjectPermissions, BRIDGE_PERMISSIONS_MANIFEST_RELPATH, BRIDGE_PROJECT_SETTINGS_RELPATH, } from "./bridge-permissions.js";
|
|
79
|
+
import { applyProjectPermissions, BRIDGE_PERMISSIONS_MANIFEST_RELPATH, BRIDGE_PROJECT_SETTINGS_RELPATH, describeSessionStartHookOutcome, } from "./bridge-permissions.js";
|
|
80
|
+
import { readMcpInstallState } from "./mcp-install-state.js";
|
|
46
81
|
import { ensureGitignored } from "./git-ignore-utils.js";
|
|
47
82
|
import { defaultNpmEntryFileExists, resolveNpmEntry } from "./windows-npm-entry.js";
|
|
48
83
|
/** The sentinel this CLI passes to the child it re-execs from `@latest`. */
|
|
49
84
|
const INTERNAL_REEXEC_FLAG = "--internal-reexec";
|
|
50
85
|
/** Production binding of {@link UpgradeCliDeps.applyBridgePermissions}. */
|
|
51
|
-
export async function defaultApplyBridgePermissions(cwd, env) {
|
|
86
|
+
export async function defaultApplyBridgePermissions(cwd, env, sessionStartHook) {
|
|
52
87
|
const fsDeps = {
|
|
53
88
|
readFile: (filePath) => readFile(filePath, "utf-8"),
|
|
54
89
|
writeFile: (filePath, data) => writeFile(filePath, data, "utf-8"),
|
|
@@ -62,7 +97,22 @@ export async function defaultApplyBridgePermissions(cwd, env) {
|
|
|
62
97
|
// Advisory: the files are still written, and doctor reads them either way.
|
|
63
98
|
}
|
|
64
99
|
}
|
|
65
|
-
return applyProjectPermissions({ cwd, env }, fsDeps);
|
|
100
|
+
return applyProjectPermissions({ cwd, env, sessionStartHook }, fsDeps);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Is this a Claude Code installation? Answered from the install state `install`
|
|
104
|
+
* recorded, not from a bare `.mcp.json`: that file is also written for other hosts
|
|
105
|
+
* and by hand, so its presence proves nothing about Claude Code. A missing,
|
|
106
|
+
* invalid, or unreadable state is "no", so the hook is never written on a guess.
|
|
107
|
+
*/
|
|
108
|
+
async function isClaudeCodeInstallation(deps) {
|
|
109
|
+
try {
|
|
110
|
+
const state = await readMcpInstallState(deps.cwd, { readFile: deps.readFile });
|
|
111
|
+
return state.status === "valid" && state.state.selectedPlatforms.includes("claude-code");
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
66
116
|
}
|
|
67
117
|
/**
|
|
68
118
|
* The fixed, secret-free message printed when, on Windows, the latest CLI could
|
|
@@ -198,6 +248,10 @@ export function createDefaultUpgradeCliDeps(cwd) {
|
|
|
198
248
|
cwd,
|
|
199
249
|
fetchLatestVersion,
|
|
200
250
|
readFile: (filePath) => readFile(filePath, "utf-8"),
|
|
251
|
+
// In place on purpose: writing through the existing file keeps its mode, owner,
|
|
252
|
+
// and symlink behavior, none of which a rename-based write would preserve.
|
|
253
|
+
writeFile: (filePath, content) => writeFile(filePath, content, "utf-8"),
|
|
254
|
+
homedir: () => os.homedir(),
|
|
201
255
|
hasCompetingLocalInstall: defaultHasCompetingLocalInstall,
|
|
202
256
|
removeCompetingLocalInstall: (installCwd) => defaultRemoveCompetingLocalInstall(installCwd),
|
|
203
257
|
runInit,
|
|
@@ -316,6 +370,177 @@ export function isEveryLauncherAlreadyCurrent(actions) {
|
|
|
316
370
|
return applicable.every((a) => a.kind === "already-current");
|
|
317
371
|
}
|
|
318
372
|
// ---------------------------------------------------------------------------
|
|
373
|
+
// Global (Codex) config — BAPI-1445
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
/** The label that tells a reader this config is machine-wide, not per-project. */
|
|
376
|
+
const GLOBAL_SCOPE_LABEL = "global — applies to every project on this machine";
|
|
377
|
+
/** The path-plus-scope label a global config line carries before its colon. */
|
|
378
|
+
function globalConfigLabel(displayPath) {
|
|
379
|
+
return `${displayPath} (${GLOBAL_SCOPE_LABEL})`;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Project a global inspection onto the project-inspection shape so the report
|
|
383
|
+
* helpers reuse the existing transition, current, normalization, and shim wording
|
|
384
|
+
* rather than growing a second set that could drift.
|
|
385
|
+
*/
|
|
386
|
+
function asProjectInspection(found) {
|
|
387
|
+
return { ...found, relPath: globalConfigLabel(found.displayPath), topLevelKey: "mcp_servers" };
|
|
388
|
+
}
|
|
389
|
+
/** Render the dry-run preview line for the global Codex config, or null when inactive. */
|
|
390
|
+
export function formatGlobalDryRunLine(found) {
|
|
391
|
+
return formatDryRunLine(asProjectInspection(found));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* The real-run action for the global Codex config, in the form
|
|
395
|
+
* {@link formatResultLine} already renders. Called only after verification
|
|
396
|
+
* passed: a failed write is never advertised as a completed repin.
|
|
397
|
+
*/
|
|
398
|
+
function globalResultAction(initial, targetVersion) {
|
|
399
|
+
const relPath = globalConfigLabel(initial.displayPath);
|
|
400
|
+
const registrationKey = initial.registrationKey ?? undefined;
|
|
401
|
+
switch (initial.action) {
|
|
402
|
+
case "repin":
|
|
403
|
+
return {
|
|
404
|
+
relPath,
|
|
405
|
+
kind: "repinned",
|
|
406
|
+
registrationKey,
|
|
407
|
+
fromVersion: initial.currentVersion ?? undefined,
|
|
408
|
+
toVersion: targetVersion,
|
|
409
|
+
};
|
|
410
|
+
case "normalize-unpinned":
|
|
411
|
+
return {
|
|
412
|
+
relPath,
|
|
413
|
+
kind: "normalized",
|
|
414
|
+
registrationKey,
|
|
415
|
+
fromSpec: initial.spec ?? undefined,
|
|
416
|
+
toVersion: targetVersion,
|
|
417
|
+
};
|
|
418
|
+
case "already-current":
|
|
419
|
+
return { relPath, kind: "already-current", registrationKey, toVersion: targetVersion };
|
|
420
|
+
case "skip-worktree-shim":
|
|
421
|
+
return { relPath, kind: "skipped-worktree-shim", registrationKey };
|
|
422
|
+
default:
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async function snapshotCodexConfig(deps, env, targetVersion) {
|
|
427
|
+
const home = deps.homedir();
|
|
428
|
+
const location = resolveCodexConfigLocation(env, home);
|
|
429
|
+
const captured = { source: null };
|
|
430
|
+
const inspection = await inspectGlobalLauncherConfigs({
|
|
431
|
+
env,
|
|
432
|
+
homedir: home,
|
|
433
|
+
targetVersion,
|
|
434
|
+
readFile: async (filePath) => {
|
|
435
|
+
const text = await deps.readFile(filePath);
|
|
436
|
+
captured.source = text;
|
|
437
|
+
return text;
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
return { ...location, inspection, source: captured.source };
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The guarded Phase 5 Codex write. Computes the one-token splice from the Phase 2
|
|
444
|
+
* snapshot, re-reads the file, and writes ONLY if it is still byte-identical to
|
|
445
|
+
* that snapshot. The comparison is optimistic protection against a concurrent
|
|
446
|
+
* edit, not a lock. Every failure is a fixed, path-specific sentence — never the
|
|
447
|
+
* TOML, an argument, an environment value, or exception text.
|
|
448
|
+
*/
|
|
449
|
+
async function applyGuardedCodexWrite(deps, snapshot, targetVersion) {
|
|
450
|
+
const where = snapshot.displayPath;
|
|
451
|
+
let expected;
|
|
452
|
+
try {
|
|
453
|
+
if (snapshot.source === null)
|
|
454
|
+
throw new Error("no snapshot");
|
|
455
|
+
expected = applyCodexRepin(snapshot.source, inspectCodexLauncher(snapshot.source), targetVersion);
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
return { failure: `${where}: the Codex configuration could not be updated safely; it was left unchanged` };
|
|
459
|
+
}
|
|
460
|
+
let current;
|
|
461
|
+
try {
|
|
462
|
+
current = await deps.readFile(snapshot.filePath);
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return { failure: `${where}: the Codex configuration could not be re-read before writing; it was left unchanged` };
|
|
466
|
+
}
|
|
467
|
+
if (current !== snapshot.source) {
|
|
468
|
+
return { failure: `${where}: the Codex configuration changed after it was inspected; it was left unchanged` };
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
await deps.writeFile(snapshot.filePath, expected);
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
return { failure: `${where}: the Codex configuration could not be written` };
|
|
475
|
+
}
|
|
476
|
+
return { expected };
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Phase 6 for Codex: re-inspect and prove the claim. A written config must be
|
|
480
|
+
* current at the exact target under its ORIGINAL registration name AND equal the
|
|
481
|
+
* expected spliced text exactly — a correct pin beside any other difference is
|
|
482
|
+
* still a failure. Initially inactive configs are nonparticipants and are never
|
|
483
|
+
* verified, created, or repaired.
|
|
484
|
+
*/
|
|
485
|
+
async function verifyCodexConfig(deps, env, snapshot, targetVersion, expected) {
|
|
486
|
+
const initial = snapshot.inspection;
|
|
487
|
+
if (initial.action === "skip-inactive")
|
|
488
|
+
return { inspection: null, failures: [] };
|
|
489
|
+
const where = snapshot.displayPath;
|
|
490
|
+
let after;
|
|
491
|
+
try {
|
|
492
|
+
after = await snapshotCodexConfig(deps, env, targetVersion);
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
return {
|
|
496
|
+
inspection: null,
|
|
497
|
+
failures: [`${where}: could not verify the Codex configuration after writing`],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
const found = after.inspection;
|
|
501
|
+
const fail = (message) => ({ inspection: found, failures: [`${where}: ${message}`] });
|
|
502
|
+
if (!found.filePresent)
|
|
503
|
+
return fail("the Codex configuration disappeared after the write");
|
|
504
|
+
if (found.reason === "file-unreadable") {
|
|
505
|
+
return fail("the Codex configuration could not be read back after the write");
|
|
506
|
+
}
|
|
507
|
+
if (found.action === "duplicate-registration") {
|
|
508
|
+
return fail("the Codex configuration now carries two Bridge registrations");
|
|
509
|
+
}
|
|
510
|
+
if (found.reason === "unlocatable" || found.action === "skip-inactive") {
|
|
511
|
+
return fail("the Codex Bridge launcher can no longer be located after the write");
|
|
512
|
+
}
|
|
513
|
+
if (found.registrationKey !== initial.registrationKey) {
|
|
514
|
+
return fail("the Codex Bridge entry no longer lives under its original registration name");
|
|
515
|
+
}
|
|
516
|
+
if (initial.action === "skip-worktree-shim") {
|
|
517
|
+
return found.action === "skip-worktree-shim"
|
|
518
|
+
? { inspection: found, failures: [] }
|
|
519
|
+
: fail("a worktree shim was expected to be preserved but no longer looks like one");
|
|
520
|
+
}
|
|
521
|
+
if (found.action !== "already-current" || found.currentVersion !== targetVersion) {
|
|
522
|
+
return fail(`expected ${MCP_PACKAGE_NAME}@${targetVersion} after the write`);
|
|
523
|
+
}
|
|
524
|
+
if (expected !== null && after.source !== expected) {
|
|
525
|
+
return fail("the Codex configuration differs from the expected upgraded text");
|
|
526
|
+
}
|
|
527
|
+
return { inspection: found, failures: [] };
|
|
528
|
+
}
|
|
529
|
+
/** The host a project config path belongs to, derived from the registry. */
|
|
530
|
+
function hostIdForRelPath(relPath) {
|
|
531
|
+
return allHostTargets().find((t) => t.scope === "project" && t.relPath === relPath)?.id;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* One `Reload <host>: <hint>.` line per changed, automatically managed host, in
|
|
535
|
+
* registry order. Hint text lives on the registry entry; nothing here names a
|
|
536
|
+
* host or a path.
|
|
537
|
+
*/
|
|
538
|
+
export function reloadGuidanceLines(changed) {
|
|
539
|
+
return HOST_PLATFORM_ORDER.map((id) => MCP_HOST_TARGETS[id])
|
|
540
|
+
.filter((t) => changed.has(t.id) && t.writeStrategy !== "manual-instructions" && t.reloadHint)
|
|
541
|
+
.map((t) => `Reload ${t.label}: ${t.reloadHint}.`);
|
|
542
|
+
}
|
|
543
|
+
// ---------------------------------------------------------------------------
|
|
319
544
|
// The command
|
|
320
545
|
// ---------------------------------------------------------------------------
|
|
321
546
|
export async function runUpgradeCli(argv, injected) {
|
|
@@ -361,13 +586,32 @@ export async function runUpgradeCli(argv, injected) {
|
|
|
361
586
|
stderr(`Bridge API upgrade failed: could not inspect launcher configs: ${msg}`);
|
|
362
587
|
return 1;
|
|
363
588
|
}
|
|
589
|
+
// BAPI-1445: Codex's global config, inspected beside the project targets in its
|
|
590
|
+
// OWN error boundary so an unexpected failure is reported with fixed text and
|
|
591
|
+
// the safe display path, never with exception text that could carry a path.
|
|
592
|
+
const env = deps.env ?? {};
|
|
593
|
+
let codexSnapshot;
|
|
594
|
+
try {
|
|
595
|
+
codexSnapshot = await snapshotCodexConfig(deps, env, target.version);
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
const where = resolveCodexConfigLocation(env, "").displayPath;
|
|
599
|
+
stderr(`Bridge API upgrade failed: could not inspect the Codex configuration at ${where}.`);
|
|
600
|
+
stderr(" Nothing was changed.");
|
|
601
|
+
return 1;
|
|
602
|
+
}
|
|
603
|
+
const codex = codexSnapshot.inspection;
|
|
364
604
|
const blocking = inspections.filter(isPrerequisiteFailure);
|
|
365
605
|
const ahead = inspections.filter((i) => i.action === "ahead");
|
|
366
|
-
|
|
606
|
+
const codexRefused = isPrerequisiteFailure(codex) || codex.action === "ahead";
|
|
607
|
+
if (blocking.length > 0 || ahead.length > 0 || codexRefused) {
|
|
367
608
|
stderr("Bridge API upgrade aborted — these configs cannot be safely reconciled:");
|
|
368
609
|
for (const found of [...blocking, ...ahead]) {
|
|
369
610
|
stderr(` ${found.relPath}: ${describeLauncherReason(found.reason)}`);
|
|
370
611
|
}
|
|
612
|
+
if (codexRefused) {
|
|
613
|
+
stderr(` ${codex.displayPath}: ${describeLauncherReason(codex.reason)}`);
|
|
614
|
+
}
|
|
371
615
|
stderr(" Nothing was changed. Resolve these by hand, then re-run the upgrade.");
|
|
372
616
|
return 1;
|
|
373
617
|
}
|
|
@@ -381,9 +625,7 @@ export async function runUpgradeCli(argv, injected) {
|
|
|
381
625
|
}
|
|
382
626
|
stdout(`- Would detect and remove competing local install in node_modules/${MCP_PACKAGE_NAME} if present.`);
|
|
383
627
|
stdout(`- Launcher pins (target ${MCP_PACKAGE_NAME}@${target.version}):`);
|
|
384
|
-
const lines = inspections
|
|
385
|
-
.map(formatDryRunLine)
|
|
386
|
-
.filter((l) => l !== null);
|
|
628
|
+
const lines = [...inspections.map(formatDryRunLine), formatGlobalDryRunLine(codex)].filter((l) => l !== null);
|
|
387
629
|
if (lines.length === 0) {
|
|
388
630
|
stdout(" (no applicable launcher configs found)");
|
|
389
631
|
}
|
|
@@ -391,11 +633,24 @@ export async function runUpgradeCli(argv, injected) {
|
|
|
391
633
|
for (const line of lines)
|
|
392
634
|
stdout(line);
|
|
393
635
|
}
|
|
394
|
-
|
|
636
|
+
const plannedInspections = [...inspections, asProjectInspection(codex)];
|
|
637
|
+
if (isEveryLauncherAlreadyCurrent(toPlannedActions(plannedInspections))) {
|
|
395
638
|
stdout(`- Would print: Already up-to-date.`);
|
|
396
639
|
}
|
|
397
640
|
stdout(`- Would refresh scaffolded artifacts (commands, agents, pipelines).`);
|
|
398
|
-
|
|
641
|
+
// Reload guidance only for hosts whose configuration this run would change.
|
|
642
|
+
const plannedHosts = new Set();
|
|
643
|
+
for (const found of inspections) {
|
|
644
|
+
if (PLANNED_CHANGE_ACTIONS.has(found.action)) {
|
|
645
|
+
const id = hostIdForRelPath(found.relPath);
|
|
646
|
+
if (id)
|
|
647
|
+
plannedHosts.add(id);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (PLANNED_CHANGE_ACTIONS.has(codex.action))
|
|
651
|
+
plannedHosts.add(codex.hostId);
|
|
652
|
+
for (const line of reloadGuidanceLines(plannedHosts))
|
|
653
|
+
stdout(`- Would print: ${line}`);
|
|
399
654
|
return 0;
|
|
400
655
|
}
|
|
401
656
|
// ---- Phase 4: competing local install — a reconciliation, not a transition ----
|
|
@@ -434,6 +689,21 @@ export async function runUpgradeCli(argv, injected) {
|
|
|
434
689
|
stderr(`Bridge API upgrade failed: ${msg}`);
|
|
435
690
|
return 1;
|
|
436
691
|
}
|
|
692
|
+
// ---- Phase 5b (BAPI-1445): the guarded Codex write ----
|
|
693
|
+
// Only after `runInit` succeeded, and only for a pin that has to move. A failure
|
|
694
|
+
// here is recorded as a convergence failure, never thrown: the JSON configs
|
|
695
|
+
// `runInit` already upgraded stay upgraded (no rollback) and nothing retries.
|
|
696
|
+
const codexFailures = [];
|
|
697
|
+
let codexExpected = null;
|
|
698
|
+
if (codex.action === "repin" || codex.action === "normalize-unpinned") {
|
|
699
|
+
const written = await applyGuardedCodexWrite(deps, codexSnapshot, target.version);
|
|
700
|
+
if ("failure" in written) {
|
|
701
|
+
codexFailures.push(written.failure);
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
codexExpected = written.expected;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
437
707
|
// ---- Phase 6: verify every claim by re-reading the configs ----
|
|
438
708
|
let verified;
|
|
439
709
|
try {
|
|
@@ -491,10 +761,22 @@ export async function runUpgradeCli(argv, injected) {
|
|
|
491
761
|
`after the write, found ${after.registrationKey ? `\`${after.registrationKey}\`` : "no recognized registration"}`);
|
|
492
762
|
}
|
|
493
763
|
}
|
|
764
|
+
// Codex is verified independently — and only when its write did not already fail.
|
|
765
|
+
let codexVerified = null;
|
|
766
|
+
if (codexFailures.length === 0) {
|
|
767
|
+
const codexCheck = await verifyCodexConfig(deps, env, codexSnapshot, target.version, codexExpected);
|
|
768
|
+
codexVerified = codexCheck.inspection;
|
|
769
|
+
codexFailures.push(...codexCheck.failures);
|
|
770
|
+
}
|
|
771
|
+
verificationFailures.push(...codexFailures);
|
|
772
|
+
// A Codex transition is reported only once it verified.
|
|
773
|
+
const codexAction = codexFailures.length === 0 ? globalResultAction(codex, target.version) : null;
|
|
494
774
|
// ---- Phase 7: report what actually happened ----
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
.
|
|
775
|
+
const codexLine = codexAction ? formatResultLine(codexAction) : null;
|
|
776
|
+
const resultLines = [
|
|
777
|
+
...initResult.configActions.map(formatResultLine),
|
|
778
|
+
codexLine,
|
|
779
|
+
].filter((l) => l !== null);
|
|
498
780
|
if (resultLines.length > 0) {
|
|
499
781
|
stdout("Launcher pins:");
|
|
500
782
|
for (const line of resultLines)
|
|
@@ -518,16 +800,26 @@ export async function runUpgradeCli(argv, injected) {
|
|
|
518
800
|
// ---- Phase 8 (BAPI-1398): the project's /drive-epic-run permission rules ----
|
|
519
801
|
// Only after every gate above passed. Advisory: a malformed settings file or a
|
|
520
802
|
// refused launcher is reported and never changes the exit code.
|
|
803
|
+
// BAPI-1446: the same step carries the pinned SessionStart upgrade-notice hook,
|
|
804
|
+
// requested only for a Claude Code installation and pinned to the CONVERGED
|
|
805
|
+
// target version (never `VERSION`, `@latest`, or the fake-latest override).
|
|
521
806
|
if (deps.applyBridgePermissions) {
|
|
522
807
|
try {
|
|
523
|
-
|
|
808
|
+
const sessionStartHook = (await isClaudeCodeInstallation(deps))
|
|
809
|
+
? { pinnedVersion: target.version }
|
|
810
|
+
: undefined;
|
|
811
|
+
reportUpgradePermissions(await deps.applyBridgePermissions(deps.cwd, deps.env ?? {}, sessionStartHook), stdout, stderr);
|
|
524
812
|
}
|
|
525
813
|
catch {
|
|
526
814
|
stderr("Warning: could not write Bridge's permission rules (non-fatal).");
|
|
527
815
|
}
|
|
528
816
|
}
|
|
529
|
-
const applicableStandard =
|
|
530
|
-
|
|
817
|
+
const applicableStandard = [
|
|
818
|
+
...verified.filter(isApplicableStandardLauncher),
|
|
819
|
+
...(codexVerified && isApplicableStandardLauncher(codexVerified) ? [codexVerified] : []),
|
|
820
|
+
];
|
|
821
|
+
const everyAction = [...initResult.configActions, ...(codexAction ? [codexAction] : [])];
|
|
822
|
+
if (isEveryLauncherAlreadyCurrent(everyAction)) {
|
|
531
823
|
// The exact string `/upgrade-bridge` is contracted to relay verbatim.
|
|
532
824
|
stdout("\nAlready up-to-date.");
|
|
533
825
|
}
|
|
@@ -538,15 +830,50 @@ export async function runUpgradeCli(argv, injected) {
|
|
|
538
830
|
}
|
|
539
831
|
else {
|
|
540
832
|
stdout(`\nUpgrade complete: launcher pins are at ${target.version}.`);
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
|
|
833
|
+
// Running sessions still hold the OLD MCP connection — MCP config is only read
|
|
834
|
+
// at session startup, so a launcher repin does not hot-reload it. Name the
|
|
835
|
+
// concrete action for each host whose configuration actually changed, from the
|
|
836
|
+
// registry's own hints and in its order.
|
|
837
|
+
const changedHosts = new Set();
|
|
838
|
+
for (const action of initResult.configActions) {
|
|
839
|
+
if (!CHANGED_ACTION_KINDS.has(action.kind))
|
|
840
|
+
continue;
|
|
841
|
+
const id = hostIdForRelPath(action.relPath);
|
|
842
|
+
if (id)
|
|
843
|
+
changedHosts.add(id);
|
|
844
|
+
}
|
|
845
|
+
if (codexAction && CHANGED_ACTION_KINDS.has(codexAction.kind))
|
|
846
|
+
changedHosts.add(codex.hostId);
|
|
847
|
+
for (const line of reloadGuidanceLines(changedHosts))
|
|
848
|
+
stdout(line);
|
|
545
849
|
}
|
|
546
850
|
return 0;
|
|
547
851
|
}
|
|
852
|
+
/** Planned actions that change a configuration and so warrant reload guidance. */
|
|
853
|
+
const PLANNED_CHANGE_ACTIONS = new Set([
|
|
854
|
+
"repin",
|
|
855
|
+
"normalize-unpinned",
|
|
856
|
+
"add",
|
|
857
|
+
"create",
|
|
858
|
+
]);
|
|
859
|
+
/** Real-run action kinds that changed a configuration. */
|
|
860
|
+
const CHANGED_ACTION_KINDS = new Set([
|
|
861
|
+
"repinned",
|
|
862
|
+
"normalized",
|
|
863
|
+
"added",
|
|
864
|
+
"created",
|
|
865
|
+
]);
|
|
548
866
|
/** Render the project permission result in upgrade's own report style. */
|
|
549
867
|
export function reportUpgradePermissions(result, stdout, stderr) {
|
|
868
|
+
if (result.hook) {
|
|
869
|
+
// Reported on its own, so a refused launcher or a malformed hooks structure
|
|
870
|
+
// never hides, or is hidden by, the permission-rule result.
|
|
871
|
+
const described = describeSessionStartHookOutcome(result.hook);
|
|
872
|
+
for (const line of described.lines)
|
|
873
|
+
stdout(line);
|
|
874
|
+
for (const warning of described.warnings)
|
|
875
|
+
stderr(`Warning: ${warning}.`);
|
|
876
|
+
}
|
|
550
877
|
if (!result.launcher.ok) {
|
|
551
878
|
stderr(`Warning: Bridge permission rules were not written: ${result.launcher.reason}.`);
|
|
552
879
|
return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
|
|
2
|
-
export const VERSION = "0.2.
|
|
3
|
-
export const BUILD_COMMIT = "
|
|
4
|
-
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
2
|
+
export const VERSION = "0.2.69";
|
|
3
|
+
export const BUILD_COMMIT = "5279e5545a81";
|
|
4
|
+
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.69", "serve"];
|