@bridge_gpt/mcp-server 0.2.68 → 0.2.70

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.
Files changed (166) hide show
  1. package/README.md +226 -320
  2. package/build/agent-launchers/claude-executor-adapter.js +17 -4
  3. package/build/agents.generated.js +4 -4
  4. package/build/artifact-diff.js +110 -0
  5. package/build/bin-entrypoint.js +36 -0
  6. package/build/bridge-permissions.js +650 -98
  7. package/build/check-update-cli.js +74 -0
  8. package/build/claude-review-workflow-drift.js +3 -3
  9. package/build/claude-settings.js +84 -2
  10. package/build/cli-help.js +7 -1
  11. package/build/codex-bridge-profile.js +1 -1
  12. package/build/codex-home.js +9 -1
  13. package/build/codex-launcher-pin.js +598 -0
  14. package/build/commands.generated.js +8 -8
  15. package/build/conductor/bridge-api-client.js +598 -19
  16. package/build/conductor/bring-up-facts.js +9 -9
  17. package/build/conductor/doctor.js +466 -18
  18. package/build/conductor/epic-child-continuation.js +54 -3
  19. package/build/conductor/epic-implementer-cli.js +132 -34
  20. package/build/conductor/epic-runtime.js +2 -2
  21. package/build/conductor/epic-state.js +1 -1
  22. package/build/conductor/hook-runtime-qualification.js +1 -1
  23. package/build/conductor/install-doctor.js +4 -2
  24. package/build/conductor/launch-need-probes.js +337 -0
  25. package/build/conductor/local-merge.js +241 -56
  26. package/build/conductor/merge-credential.js +172 -0
  27. package/build/conductor/merge-execution-eligibility.js +1 -1
  28. package/build/conductor/merged-child-check.js +95 -0
  29. package/build/conductor/plan-trim.js +30 -0
  30. package/build/conductor/pr-ci-producer.js +1 -1
  31. package/build/conductor/readiness-cli.js +11 -6
  32. package/build/conductor/readiness-prerequisites.js +41 -0
  33. package/build/conductor/readiness-sections.js +345 -55
  34. package/build/conductor/readiness.js +99 -87
  35. package/build/conductor/recovery-cli.js +606 -21
  36. package/build/conductor/recovery-operations.js +306 -7
  37. package/build/conductor/repair-contract.js +47 -2
  38. package/build/conductor/repository-workflow-remedy.js +8 -6
  39. package/build/conductor/stored-merge-credential.js +76 -0
  40. package/build/conductor/stored-merge-token-probe.js +28 -0
  41. package/build/conductor/temporary-plan.js +50 -0
  42. package/build/conductor/wait-run-cli.js +759 -0
  43. package/build/conductor/worker-database-doctor-inspector.js +49 -0
  44. package/build/conductor-bin.js +2 -2
  45. package/build/conductor-claude-hook-bin.js +2 -2
  46. package/build/conductor-claude-hook-removed-stub-bin.js +2 -5
  47. package/build/conductor-removed-stub-bin.js +2 -5
  48. package/build/connect-github-api.js +33 -14
  49. package/build/credential-store.js +356 -13
  50. package/build/docs.generated.js +27 -14
  51. package/build/doctor.js +203 -38
  52. package/build/drive-epic-help.js +212 -0
  53. package/build/drive-epic.js +1840 -373
  54. package/build/epic-ci-workflow-drift.js +77 -0
  55. package/build/epic-ci-workflow-install.js +183 -0
  56. package/build/epic-ci-workflow.js +157 -0
  57. package/build/epic-contract-context.js +326 -0
  58. package/build/epic-implementer-bin.js +131 -120
  59. package/build/epic-implementer-claude-hook-bin.js +2 -2
  60. package/build/escalation-reachability.js +134 -0
  61. package/build/executor/cleanup-report.js +99 -0
  62. package/build/executor/cli.js +12 -1
  63. package/build/executor/completion-contract.js +68 -11
  64. package/build/executor/completion-supervision.js +99 -0
  65. package/build/executor/delivery-outcome.js +179 -0
  66. package/build/executor/deps.js +3 -0
  67. package/build/executor/env.js +71 -0
  68. package/build/executor/failure-reason-codes.js +126 -0
  69. package/build/executor/heartbeat.js +38 -0
  70. package/build/executor/http-client.js +115 -4
  71. package/build/executor/integration-job.js +298 -5
  72. package/build/executor/job-errors.js +36 -6
  73. package/build/executor/job-runner.js +720 -36
  74. package/build/executor/likely-tests.js +36 -2
  75. package/build/executor/merge-job.js +145 -2
  76. package/build/executor/observation.js +73 -4
  77. package/build/executor/permissions.js +193 -97
  78. package/build/executor/preflight.js +14 -3
  79. package/build/executor/process-containment.js +28 -2
  80. package/build/executor/prompt-spec.js +50 -5
  81. package/build/executor/rebase-finalization.js +38 -3
  82. package/build/executor/reconcile-overlap-job.js +35 -7
  83. package/build/executor/required-work-ledger.js +236 -81
  84. package/build/executor/resume-pre-spawn.js +38 -1
  85. package/build/executor/runner.js +91 -3
  86. package/build/executor/test-clock.js +8 -0
  87. package/build/executor/types.js +37 -2
  88. package/build/executor/worker-cli-launcher.js +259 -0
  89. package/build/executor/worker-command.js +41 -4
  90. package/build/executor/worker-database-target.js +91 -0
  91. package/build/executor/worker-database.js +908 -0
  92. package/build/executor/worker-finalization.js +106 -0
  93. package/build/executor/worker-guard-hook.js +203 -0
  94. package/build/executor/worker-resources.js +47 -0
  95. package/build/executor/worker-verification-guidance.js +15 -2
  96. package/build/executor/worktree.js +303 -15
  97. package/build/executor-launch.js +11 -1
  98. package/build/generated-assets-doctor.js +9 -1
  99. package/build/generated-assets-health.js +13 -4
  100. package/build/generated-assets-legacy.js +39 -0
  101. package/build/generated-assets-paths.js +12 -1
  102. package/build/generated-assets-repair.js +4 -1
  103. package/build/generated-assets-test-support.js +12 -0
  104. package/build/github-remote.js +33 -0
  105. package/build/github-sign-in.js +62 -0
  106. package/build/index.js +1214 -465
  107. package/build/init.js +39 -1
  108. package/build/install-bridge-conductor-gate.js +78 -0
  109. package/build/install-bridge-conductor-setup.js +413 -0
  110. package/build/install-bridge-conductor.js +1226 -267
  111. package/build/install-bridge.js +629 -350
  112. package/build/launcher-config-inspection.js +184 -19
  113. package/build/mcp-host-targets.js +4 -0
  114. package/build/mcp-identity.js +7 -0
  115. package/build/mcp-install-state.js +8 -2
  116. package/build/merge-pull-request.js +6 -0
  117. package/build/merge-workflow-install.js +763 -345
  118. package/build/phase-result-artifacts.js +75 -20
  119. package/build/pipeline-orchestrator.js +247 -4
  120. package/build/pipelines.generated.js +35 -35
  121. package/build/plan-discovery.js +15 -2
  122. package/build/plan-execution-ledger.js +306 -17
  123. package/build/plan-phase-routing.js +48 -13
  124. package/build/plane/cli.js +105 -51
  125. package/build/plane/defaults.js +3 -0
  126. package/build/plane/plane-freshness.js +294 -0
  127. package/build/plane/schema-compatibility.js +137 -0
  128. package/build/plane/shutdown-deps.js +38 -0
  129. package/build/plane/shutdown.js +83 -8
  130. package/build/plane/terminal-run-cleanup.js +303 -0
  131. package/build/printed-remedy-commands.js +26 -4
  132. package/build/readiness-check.js +6 -2
  133. package/build/readiness-prerequisites.generated.js +532 -0
  134. package/build/readme.generated.js +1 -1
  135. package/build/session-upgrade-notice.js +86 -0
  136. package/build/setup-epic.js +400 -30
  137. package/build/shared-resources.generated.js +5 -0
  138. package/build/start-tickets.js +43 -15
  139. package/build/test-command-detection.js +247 -0
  140. package/build/test-command-validation.js +73 -0
  141. package/build/testing-config-client.js +150 -0
  142. package/build/ticket-delivery-artifacts.js +258 -0
  143. package/build/ticket-delivery-enrollment.js +306 -0
  144. package/build/ticket-delivery.js +205 -0
  145. package/build/ticket-review-summary.js +14 -0
  146. package/build/tool-call-advisory.js +162 -0
  147. package/build/tool-surface-gating.js +28 -89
  148. package/build/update-check.js +97 -16
  149. package/build/update-status.js +61 -71
  150. package/build/upgrade-advice.js +15 -11
  151. package/build/upgrade-cli.js +360 -20
  152. package/build/v2-readiness-requirements.js +552 -0
  153. package/build/version.generated.js +3 -3
  154. package/build/worker-guard-hook-bin.js +2 -2
  155. package/build/worktree-core.js +22 -7
  156. package/docs/BRIDGE-DIRECTORY-README.md +48 -0
  157. package/docs/EPIC-IMPLEMENTER-INSTALL.md +12 -867
  158. package/docs/EPIC-IMPLEMENTER.md +104 -63
  159. package/docs/epic-implementer/getting-started.md +131 -0
  160. package/docs/epic-implementer/self-hosting.md +133 -0
  161. package/package.json +3 -3
  162. package/pipelines/idea-to-ticket.json +1 -1
  163. package/pipelines/implement-ticket.json +8 -8
  164. package/pipelines/review-ticket.json +2 -2
  165. package/design-assets/favicon/favicon-32x32.png +0 -0
  166. package/design-assets/just-logo-rough-draft.png +0 -0
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * Update check logic — extracted for testability.
3
- * The main index.ts imports and calls checkForUpdate().
3
+ * `update-status.ts` (the background manager) and `doctor.ts` call checkForUpdate();
4
+ * the synchronous notice paths use `readCachedLatestVersion` and never touch the
5
+ * network.
4
6
  */
7
+ import { readFileSync } from "fs";
5
8
  import { writeFile, mkdir, readFile } from "fs/promises";
6
9
  import path from "path";
7
10
  import os from "os";
@@ -10,13 +13,51 @@ import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
10
13
  const CACHE_TTL = 86400000; // 24 hours
11
14
  const FETCH_TIMEOUT = 3000;
12
15
  const REGISTRY_URL = `https://registry.npmjs.org/${MCP_PACKAGE_NAME}/latest`;
16
+ /** Manual-only override of the latest version, for exercising the notice paths. */
17
+ export const FAKE_LATEST_ENV = "BAPI_MCP_UPDATE_CHECK_FAKE_LATEST";
18
+ const PLAIN_RELEASE_RE = /^\d+\.\d+\.\d+$/;
13
19
  export function getCachePath() {
14
20
  return path.join(os.homedir(), ".config", ...MCP_PACKAGE_NAME.split("/"), "update-check.json");
15
21
  }
22
+ /** True only for a plain `MAJOR.MINOR.PATCH` release string. */
23
+ export function isPlainReleaseVersion(value) {
24
+ return typeof value === "string" && PLAIN_RELEASE_RE.test(value);
25
+ }
26
+ /**
27
+ * Read the manual fake-latest override. Returns the value only when it is a plain
28
+ * release string; anything else (absent, blank, a prerelease, shell text) is
29
+ * ignored and never echoed.
30
+ */
31
+ export function readFakeLatestOverride(env = process.env) {
32
+ const value = env[FAKE_LATEST_ENV];
33
+ return isPlainReleaseVersion(value) ? value : null;
34
+ }
35
+ /** Compare two digit strings numerically without converting them to Numbers. */
36
+ function compareDigitStrings(a, b) {
37
+ const x = a.replace(/^0+(?=\d)/, "");
38
+ const y = b.replace(/^0+(?=\d)/, "");
39
+ if (x.length !== y.length)
40
+ return x.length > y.length ? 1 : -1;
41
+ if (x === y)
42
+ return 0;
43
+ return x > y ? 1 : -1;
44
+ }
16
45
  /**
17
46
  * Compare two semver strings. Returns true if `latest` is strictly greater.
47
+ * Valid release strings are compared segment by segment as digit strings, so a
48
+ * segment beyond the safe-integer range keeps its ordering.
18
49
  */
19
50
  export function isNewerVersion(current, latest) {
51
+ if (isPlainReleaseVersion(current) && isPlainReleaseVersion(latest)) {
52
+ const c = current.split(".");
53
+ const l = latest.split(".");
54
+ for (let i = 0; i < 3; i++) {
55
+ const order = compareDigitStrings(l[i], c[i]);
56
+ if (order !== 0)
57
+ return order > 0;
58
+ }
59
+ return false;
60
+ }
20
61
  const c = current.split(".").map(Number);
21
62
  const l = latest.split(".").map(Number);
22
63
  for (let i = 0; i < 3; i++) {
@@ -27,28 +68,66 @@ export function isNewerVersion(current, latest) {
27
68
  }
28
69
  return false;
29
70
  }
71
+ /**
72
+ * Read the latest version the server last cached, synchronously and offline.
73
+ *
74
+ * A valid override wins before the cache is opened. The cache's age is ignored on
75
+ * purpose: a cached latest can only be older than the real latest, so it is a
76
+ * lower bound. It may under-claim an update, but it never claims one that does
77
+ * not exist. Any missing, corrupt, malformed, or unreadable cache returns null.
78
+ */
79
+ export function readCachedLatestVersion(env = process.env) {
80
+ try {
81
+ const override = readFakeLatestOverride(env);
82
+ if (override)
83
+ return override;
84
+ const cache = JSON.parse(readFileSync(getCachePath(), "utf-8"));
85
+ if (cache === null || typeof cache !== "object" || Array.isArray(cache))
86
+ return null;
87
+ const { lastCheck, latestVersion } = cache;
88
+ if (typeof lastCheck !== "number" || !Number.isFinite(lastCheck))
89
+ return null;
90
+ return isPlainReleaseVersion(latestVersion) ? latestVersion : null;
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
30
96
  /**
31
97
  * Checks the npm registry for a newer version of @bridge_gpt/mcp-server.
32
98
  * Results are cached for 24 hours. Returns null if the check fails.
33
99
  */
34
- export async function checkForUpdate() {
100
+ export async function checkForUpdate(options = {}) {
35
101
  try {
102
+ const override = readFakeLatestOverride(options.env ?? process.env);
103
+ if (override) {
104
+ // An override is never cached, so it cannot outlive the manual test.
105
+ return {
106
+ updateAvailable: isNewerVersion(VERSION, override),
107
+ currentVersion: VERSION,
108
+ latestVersion: override,
109
+ };
110
+ }
111
+ const useCache = options.readCache !== false;
112
+ const saveCache = options.writeCache !== false;
36
113
  const cachePath = getCachePath();
37
114
  const cacheDir = path.dirname(cachePath);
38
115
  let latestVersion = null;
39
116
  // Try reading cache
40
- try {
41
- const raw = await readFile(cachePath, "utf-8");
42
- const cache = JSON.parse(raw);
43
- if (cache &&
44
- typeof cache.lastCheck === "number" &&
45
- typeof cache.latestVersion === "string" &&
46
- Date.now() - cache.lastCheck < CACHE_TTL) {
47
- latestVersion = cache.latestVersion;
117
+ if (useCache) {
118
+ try {
119
+ const raw = await readFile(cachePath, "utf-8");
120
+ const cache = JSON.parse(raw);
121
+ if (cache &&
122
+ typeof cache.lastCheck === "number" &&
123
+ isPlainReleaseVersion(cache.latestVersion) &&
124
+ Date.now() - cache.lastCheck < CACHE_TTL) {
125
+ latestVersion = cache.latestVersion;
126
+ }
127
+ }
128
+ catch {
129
+ // Cache missing, corrupt, or stale — will fetch fresh
48
130
  }
49
- }
50
- catch {
51
- // Cache missing, corrupt, or stale — will fetch fresh
52
131
  }
53
132
  // Fetch from registry if no valid cache
54
133
  if (!latestVersion) {
@@ -57,10 +136,12 @@ export async function checkForUpdate() {
57
136
  try {
58
137
  const res = await fetch(REGISTRY_URL, { signal: controller.signal });
59
138
  const data = await res.json();
60
- if (data.version) {
139
+ if (isPlainReleaseVersion(data.version)) {
61
140
  latestVersion = data.version;
62
- await mkdir(cacheDir, { recursive: true });
63
- await writeFile(cachePath, JSON.stringify({ lastCheck: Date.now(), latestVersion }), "utf-8");
141
+ if (saveCache) {
142
+ await mkdir(cacheDir, { recursive: true });
143
+ await writeFile(cachePath, JSON.stringify({ lastCheck: Date.now(), latestVersion }), "utf-8");
144
+ }
64
145
  }
65
146
  }
66
147
  finally {
@@ -1,69 +1,86 @@
1
1
  /**
2
- * Process-wide cached update status (BAPI-728).
2
+ * Process-wide settled update status (BAPI-728, reshaped by BAPI-1446).
3
3
  *
4
- * The staleness signal already existed — `checkForUpdate()` ran on every server
5
- * start and computed the right answer. It was emitted through
6
- * `sendLoggingMessage({ level: "notice", ... })`, an MCP log sink that no user
7
- * or model surface reads. The published 0.2.16 tarball shipped that code, so a
8
- * stale server correctly computed `updateAvailable: true` and announced it to
9
- * nobody, on every session, for twenty releases.
4
+ * The staleness signal already existed: `checkForUpdate()` ran on every server
5
+ * start and computed the right answer, then announced it to a log sink nobody
6
+ * reads. BAPI-1446 moved delivery to surfaces a session actually reads (startup
7
+ * instructions, the first completed tool result, the SessionStart hook). This
8
+ * manager is now only the source of one fact for those surfaces: what the live
9
+ * check concluded, once it has settled.
10
10
  *
11
- * This manager gives that value two readers instead of zero: one stderr warning
12
- * per process (humans), and a `tools/list` advisory (models). It is:
11
+ * The settled-status contract:
13
12
  *
14
13
  * - **single-flight** — one `checkForUpdate()` per process, cached forever;
15
- * - **non-blocking** — started at boot, never awaited before `connect`;
16
- * - **fail-open** — a null, malformed, rejected, or not-newer result yields
17
- * no advisory and no warning, and never throws.
14
+ * - **non-blocking** — `start()` returns at once and is never awaited before
15
+ * `connect`; `getStatus()` is synchronous and never waits for the lookup;
16
+ * - **known versus unknown** — `known: true` means a reliable live result
17
+ * settled, including "you are current". Initial, in-flight, disabled,
18
+ * rejected, null, and malformed outcomes are `known: false` and not stale, so
19
+ * the tool-result channel never mistakes "unknown" for "confirmed current"
20
+ * and can fall back to the local cache;
21
+ * - **fail-open** — nothing here throws, and `whenSettled()` never rejects.
18
22
  *
19
- * The manager is default-on. Passing `enabled: false` (BAPI-917) makes it
20
- * permanently `NOT_STALE` from the moment `start()` returns, without ever
21
- * invoking the checker, touching the update cache, or making a registry
22
- * request — used so a hermetic measurement capture cannot be perturbed by
23
- * runtime update state.
23
+ * `stale` is kept for existing production consumers and is only ever true for a
24
+ * reliable newer result.
25
+ *
26
+ * Passing `enabled: false` (BAPI-917) makes the manager permanently unknown from
27
+ * the moment `start()` returns, without invoking the checker, touching the update
28
+ * cache, or making a registry request — used so a hermetic measurement capture
29
+ * cannot be perturbed by runtime update state.
24
30
  */
25
- import { checkForUpdate } from "./update-check.js";
26
- import { formatUpdateAdvice, formatToolSurfaceUpdateAdvisory } from "./upgrade-advice.js";
27
- /** The not-stale conclusion, used for every fail-open branch. */
28
- const NOT_STALE = { stale: false };
31
+ import { checkForUpdate, isNewerVersion, isPlainReleaseVersion, readFakeLatestOverride, } from "./update-check.js";
32
+ import { VERSION } from "./version.generated.js";
33
+ /** The unknown conclusion, used for every fail-open branch. */
34
+ const UNKNOWN = { stale: false, known: false };
29
35
  /**
30
36
  * Build the manager. Nothing runs until `start()` is called, so importing this
31
37
  * module never performs I/O.
32
38
  */
33
39
  export function createUpdateStatusManager(options = {}) {
34
- const check = options.check ?? checkForUpdate;
35
- const warn = options.warn ?? ((message) => console.error(message));
40
+ const env = options.env ?? process.env;
41
+ const check = options.check ?? (() => checkForUpdate({ env }));
36
42
  const enabled = options.enabled !== false;
43
+ const runningVersion = options.currentVersion ?? VERSION;
37
44
  let started = false;
38
45
  let settled = null;
39
46
  let settledPromise = null;
40
- let warned = false;
41
- let listServed = false;
42
- let lateNotified = false;
43
47
  function conclude(result) {
44
- // Every uncertain shape is fail-open. A malformed payload must never produce
45
- // an advisory telling a user to upgrade to a version that may not exist.
48
+ // Every uncertain shape is unknown. A malformed payload must never produce a
49
+ // claim about a version that may not exist.
46
50
  if (!result || typeof result !== "object")
47
- return NOT_STALE;
48
- if (result.updateAvailable !== true)
49
- return NOT_STALE;
51
+ return UNKNOWN;
52
+ if (typeof result.updateAvailable !== "boolean")
53
+ return UNKNOWN;
50
54
  const { currentVersion, latestVersion } = result;
51
- if (typeof currentVersion !== "string" || currentVersion.length === 0)
52
- return NOT_STALE;
53
- if (typeof latestVersion !== "string" || latestVersion.length === 0)
54
- return NOT_STALE;
55
- return { stale: true, currentVersion, latestVersion };
55
+ if (!isPlainReleaseVersion(currentVersion))
56
+ return UNKNOWN;
57
+ if (!isPlainReleaseVersion(latestVersion))
58
+ return UNKNOWN;
59
+ return { stale: result.updateAvailable, known: true, currentVersion, latestVersion };
56
60
  }
57
61
  function start() {
58
62
  if (started)
59
63
  return;
60
64
  started = true;
61
65
  if (!enabled) {
62
- // Disabled: settle immediately, without ever invoking `check`, warning,
63
- // or scheduling a checker operation. `whenSettled()` stays usable — it
64
- // just resolves to the same already-known conclusion.
65
- settled = NOT_STALE;
66
- settledPromise = Promise.resolve(NOT_STALE);
66
+ // Disabled: settle immediately, without ever invoking `check`. `whenSettled()`
67
+ // stays usable — it just resolves to the same already-known conclusion.
68
+ settled = UNKNOWN;
69
+ settledPromise = Promise.resolve(UNKNOWN);
70
+ return;
71
+ }
72
+ // A valid fake-latest override settles at once, before any filesystem or
73
+ // registry work, exactly as `checkForUpdate` honors it.
74
+ const override = readFakeLatestOverride(env);
75
+ if (override && isPlainReleaseVersion(runningVersion)) {
76
+ const status = {
77
+ stale: isNewerVersion(runningVersion, override),
78
+ known: true,
79
+ currentVersion: runningVersion,
80
+ latestVersion: override,
81
+ };
82
+ settled = status;
83
+ settledPromise = Promise.resolve(status);
67
84
  return;
68
85
  }
69
86
  settledPromise = (async () => {
@@ -72,46 +89,19 @@ export function createUpdateStatusManager(options = {}) {
72
89
  status = conclude(await check());
73
90
  }
74
91
  catch {
75
- status = NOT_STALE; // `checkForUpdate` is contracted not to reject; belt and braces.
92
+ status = UNKNOWN; // `checkForUpdate` is contracted not to reject; belt and braces.
76
93
  }
77
94
  settled = status;
78
- if (status.stale && !warned) {
79
- warned = true;
80
- // stderr, never stdout: stdout is the JSON-RPC frame channel and any
81
- // stray write there corrupts the protocol stream.
82
- warn(formatUpdateAdvice(status.currentVersion, status.latestVersion));
83
- if (listServed && !lateNotified) {
84
- lateNotified = true;
85
- try {
86
- options.onLateStale?.();
87
- }
88
- catch {
89
- // A notification failure must never affect server operation.
90
- }
91
- }
92
- }
93
95
  return status;
94
96
  })();
95
97
  }
96
98
  return {
97
99
  start,
98
- getStatus: () => settled ?? NOT_STALE,
100
+ getStatus: () => settled ?? UNKNOWN,
99
101
  whenSettled: async () => {
100
102
  if (!started)
101
103
  start();
102
- return (await settledPromise) ?? NOT_STALE;
103
- },
104
- markListServed: () => {
105
- listServed = true;
104
+ return (await settledPromise) ?? UNKNOWN;
106
105
  },
107
106
  };
108
107
  }
109
- /**
110
- * Produce the model-visible advisory for a settled status, or null when the
111
- * status is not (yet) known-stale. Pure — safe to call on every list request.
112
- */
113
- export function updateAdvisoryFor(status) {
114
- if (!status.stale || !status.currentVersion || !status.latestVersion)
115
- return null;
116
- return formatToolSurfaceUpdateAdvisory(status.currentVersion, status.latestVersion);
117
- }
@@ -29,20 +29,24 @@ function sanitizeVersion(value) {
29
29
  return typeof value === "string" && SAFE_VERSION_RE.test(value) ? value : "unknown";
30
30
  }
31
31
  /**
32
- * Format the one-line staleness advisory shown to humans (stderr) and models
33
- * (tool discovery). Only the two version values vary; the command is constant.
32
+ * Format the human-facing staleness line (stderr, terminal, `doctor`, and the
33
+ * `ping` advice item). It names both versions and the slash command, and carries
34
+ * no instruction for a model; the model-directed text is
35
+ * {@link formatSessionUpgradeNotice}.
34
36
  */
35
37
  export function formatUpdateAdvice(currentVersion, latestVersion) {
36
- return (`Bridge API MCP server update available: running ${sanitizeVersion(currentVersion)}, ` +
37
- `latest ${sanitizeVersion(latestVersion)} — run ${CANONICAL_UPGRADE_COMMAND}`);
38
+ return (`Bridge MCP: installed ${sanitizeVersion(currentVersion)}, ` +
39
+ `latest ${sanitizeVersion(latestVersion)} — upgrade available — run /upgrade-bridge`);
38
40
  }
39
41
  /**
40
- * The compact form attached to `tools/list`, so a model can see that the tool
41
- * surface it is looking at may be missing or misnaming tools that exist in a
42
- * newer release (the BAPI-645 `council` rename is the canonical example).
42
+ * Format the one-line, plain-text notice delivered to a model session (startup
43
+ * instructions, the first completed tool result, the SessionStart hook). It tells
44
+ * the model to inform the user, offer the upgrade, and run `/upgrade-bridge`
45
+ * only if they say yes. Both versions go through `sanitizeVersion`, so no
46
+ * caller can interpolate arbitrary text.
43
47
  */
44
- export function formatToolSurfaceUpdateAdvisory(currentVersion, latestVersion) {
45
- return (`NOTE: this Bridge API MCP server is running ${sanitizeVersion(currentVersion)}, ` +
46
- `but ${sanitizeVersion(latestVersion)} is published. Some tools may be missing or ` +
47
- `renamed in this older build. To update, run: ${CANONICAL_UPGRADE_COMMAND}`);
48
+ export function formatSessionUpgradeNotice(current, latest) {
49
+ return (`Bridge MCP upgrade available: this session is running ${sanitizeVersion(current)} ` +
50
+ `and ${sanitizeVersion(latest)} is published. Tell the user an upgrade is available, ` +
51
+ "offer to run /upgrade-bridge, and run it only if they say yes.");
48
52
  }