@bridge_gpt/mcp-server 0.2.60 → 0.2.62

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.
@@ -17,7 +17,7 @@
17
17
  * never resolves or stores it, and `ANTHROPIC_API_KEY` remains absolutely denied
18
18
  * to a worker (`executor/env.ts`).
19
19
  */
20
- import { resolveBapiCredentials } from "../credential-store.js";
20
+ import { readStoredBinding, resolveBapiCredentials } from "../credential-store.js";
21
21
  import { normalizeBaseUrlCandidate } from "../base-url.js";
22
22
  /**
23
23
  * The documented production Bridge API base URL — the value an operator pastes
@@ -79,6 +79,22 @@ export async function resolveExecutorApiAccess(repoName, deps, explicitBaseUrl)
79
79
  error: `could not resolve Bridge API credentials for repo '${trimmed}' (${result.kind})`,
80
80
  };
81
81
  }
82
+ // BAPI-1260: a stored key that guided setup approved for ONE endpoint is never
83
+ // sent to another. A restarted executor re-reads the binding here, so a unit
84
+ // whose --base-url drifted from the approved target refuses before its first
85
+ // authenticated request. An unbound (legacy) entry keeps today's behavior —
86
+ // the explicit-target requirement above still applies to it.
87
+ if (result.credentials.source === "file") {
88
+ const bound = await readStoredBinding(`bapi:${trimmed}`, storeDeps);
89
+ if (bound !== null && bound !== url.baseUrl) {
90
+ return {
91
+ ok: false,
92
+ error: `the stored credential for repo '${trimmed}' is approved for a different Bridge API ` +
93
+ "endpoint than this executor's base URL; it was not sent. Re-run guided setup " +
94
+ "(install conductor) to re-bind it, or correct --base-url.",
95
+ };
96
+ }
97
+ }
82
98
  return {
83
99
  ok: true,
84
100
  apiKey: result.credentials.apiKey,
@@ -9,6 +9,30 @@
9
9
  */
10
10
  import { RECONCILER_LIVENESS_VALUES } from "./types.js";
11
11
  import { readEpicRunCompletionState, } from "../conductor/bridge-api-client.js";
12
+ /** BAPI-1258 — the server's stable `error_code` for refused capacity evidence. */
13
+ export const UNSUPPORTED_TERMINAL_CAPACITY_ERROR_CODE = "UNSUPPORTED_TERMINAL_CAPACITY";
14
+ /**
15
+ * Whether a `/fail` response refused the capacity evidence it carried: a 422
16
+ * (malformed envelope) or a 409 whose `error_code` is the stable unsupported
17
+ * code. A 409 `STALE_CLAIM` is NOT a refusal — the claim is gone, and resending
18
+ * cannot help. The body is inspected only for that one closed token.
19
+ */
20
+ function isCapacityEvidenceRefusal(status, bodyText) {
21
+ if (status === 422)
22
+ return true;
23
+ if (status !== 409)
24
+ return false;
25
+ try {
26
+ const parsed = JSON.parse(bodyText);
27
+ const detail = parsed && typeof parsed === "object" ? parsed.detail : undefined;
28
+ return (!!detail &&
29
+ typeof detail === "object" &&
30
+ detail.error_code === UNSUPPORTED_TERMINAL_CAPACITY_ERROR_CODE);
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
12
36
  /** Bound a server-provided diagnostic snippet included in error strings. */
13
37
  function boundedDetail(text) {
14
38
  const trimmed = (text ?? "").trim();
@@ -269,7 +293,7 @@ export function createExecutorHttpClient(config) {
269
293
  };
270
294
  return mutate(`/${job.id}/complete`, body, true, job.repo_name);
271
295
  },
272
- fail(job, failure) {
296
+ async fail(job, failure) {
273
297
  const body = {
274
298
  repo_name: job.repo_name,
275
299
  claim_token: job.claim_token,
@@ -280,6 +304,26 @@ export function createExecutorHttpClient(config) {
280
304
  last_commit_sha: failure.last_commit_sha,
281
305
  telemetry: failure.telemetry,
282
306
  };
307
+ if (failure.capacity_evidence === undefined) {
308
+ return mutate(`/${job.id}/fail`, body, false, job.repo_name);
309
+ }
310
+ // BAPI-1258 — send the evidence once. If the server refuses it (unsupported
311
+ // protocol, or a malformed envelope), re-send the SAME ordinary failure
312
+ // without it, exactly once, so the job still reaches a terminal state and
313
+ // the original error kind/classification are preserved. Refused evidence
314
+ // never becomes an automatic recovery.
315
+ let first;
316
+ try {
317
+ first = await post(`/${job.id}/fail`, { ...body, capacity_evidence: failure.capacity_evidence }, job.repo_name);
318
+ }
319
+ catch {
320
+ return { outcome: "fatal_http_error" };
321
+ }
322
+ if (!isCapacityEvidenceRefusal(first.status, first.text)) {
323
+ return mapMutationResponse(first.status, first.text, false);
324
+ }
325
+ (config.warn ?? ((message) => console.error(message)))(`executor: server refused terminal capacity evidence for job ${job.id} ` +
326
+ `(HTTP ${first.status}); reporting an ordinary failure without automatic recovery.`);
283
327
  return mutate(`/${job.id}/fail`, body, false, job.repo_name);
284
328
  },
285
329
  /**