agent-embassy 1.6.1 → 1.7.1

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 (105) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +9 -9
  3. package/README.zh-CN.md +9 -9
  4. package/SECURITY.md +34 -77
  5. package/dist/src/gateway/acp-client.d.ts +117 -0
  6. package/dist/src/gateway/acp-client.js +382 -0
  7. package/dist/src/gateway/acp-client.js.map +1 -0
  8. package/dist/src/gateway/acp-provider.d.ts +65 -0
  9. package/dist/src/gateway/acp-provider.js +163 -0
  10. package/dist/src/gateway/acp-provider.js.map +1 -0
  11. package/dist/src/gateway/claude-helper-protocol.d.ts +3 -1
  12. package/dist/src/gateway/claude-helper-protocol.js +15 -5
  13. package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
  14. package/dist/src/gateway/claude-helper-supervisor.d.ts +3 -1
  15. package/dist/src/gateway/claude-helper-supervisor.js +16 -2
  16. package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
  17. package/dist/src/gateway/claude-helper.js +4 -3
  18. package/dist/src/gateway/claude-helper.js.map +1 -1
  19. package/dist/src/gateway/claude-peer.d.ts +1 -1
  20. package/dist/src/gateway/claude-peer.js +11 -13
  21. package/dist/src/gateway/claude-peer.js.map +1 -1
  22. package/dist/src/gateway/claude-runtime.d.ts +0 -20
  23. package/dist/src/gateway/claude-runtime.js +6 -127
  24. package/dist/src/gateway/claude-runtime.js.map +1 -1
  25. package/dist/src/gateway/cli-copy.d.ts +1 -1
  26. package/dist/src/gateway/cli-copy.en.d.ts +3 -1
  27. package/dist/src/gateway/cli-copy.en.js +5 -2
  28. package/dist/src/gateway/cli-copy.en.js.map +1 -1
  29. package/dist/src/gateway/cli-copy.js +2 -0
  30. package/dist/src/gateway/cli-copy.js.map +1 -1
  31. package/dist/src/gateway/cli-copy.zh-CN.d.ts +3 -1
  32. package/dist/src/gateway/cli-copy.zh-CN.js +5 -2
  33. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
  34. package/dist/src/gateway/cli.d.ts +2 -2
  35. package/dist/src/gateway/cli.js +73 -6
  36. package/dist/src/gateway/cli.js.map +1 -1
  37. package/dist/src/gateway/codex-app-server.d.ts +5 -74
  38. package/dist/src/gateway/codex-app-server.js +9 -599
  39. package/dist/src/gateway/codex-app-server.js.map +1 -1
  40. package/dist/src/gateway/codex-doctor.d.ts +33 -0
  41. package/dist/src/gateway/codex-doctor.js +148 -0
  42. package/dist/src/gateway/codex-doctor.js.map +1 -0
  43. package/dist/src/gateway/codex-local-transport.d.ts +13 -26
  44. package/dist/src/gateway/codex-local-transport.js +31 -76
  45. package/dist/src/gateway/codex-local-transport.js.map +1 -1
  46. package/dist/src/gateway/compatibility.d.ts +6 -66
  47. package/dist/src/gateway/compatibility.js +14 -296
  48. package/dist/src/gateway/compatibility.js.map +1 -1
  49. package/dist/src/gateway/config.d.ts +9 -0
  50. package/dist/src/gateway/config.js +6 -2
  51. package/dist/src/gateway/config.js.map +1 -1
  52. package/dist/src/gateway/control.d.ts +16 -1
  53. package/dist/src/gateway/control.js +128 -59
  54. package/dist/src/gateway/control.js.map +1 -1
  55. package/dist/src/gateway/dashboard-copy.d.ts +1 -1
  56. package/dist/src/gateway/dashboard-copy.en.d.ts +19 -44
  57. package/dist/src/gateway/dashboard-copy.en.js +42 -67
  58. package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
  59. package/dist/src/gateway/dashboard-copy.js +19 -44
  60. package/dist/src/gateway/dashboard-copy.js.map +1 -1
  61. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +19 -44
  62. package/dist/src/gateway/dashboard-copy.zh-CN.js +42 -67
  63. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
  64. package/dist/src/gateway/dashboard-model.d.ts +41 -76
  65. package/dist/src/gateway/dashboard-model.js +167 -257
  66. package/dist/src/gateway/dashboard-model.js.map +1 -1
  67. package/dist/src/gateway/dashboard.d.ts +0 -3
  68. package/dist/src/gateway/dashboard.js +77 -76
  69. package/dist/src/gateway/dashboard.js.map +1 -1
  70. package/dist/src/gateway/deepseek-detect.d.ts +7 -6
  71. package/dist/src/gateway/deepseek-detect.js +31 -115
  72. package/dist/src/gateway/deepseek-detect.js.map +1 -1
  73. package/dist/src/gateway/live-dashboard-app/app.js +175 -621
  74. package/dist/src/gateway/live-dashboard-command.js +2 -4
  75. package/dist/src/gateway/live-dashboard-command.js.map +1 -1
  76. package/dist/src/gateway/live-dashboard-http.d.ts +1 -2
  77. package/dist/src/gateway/live-dashboard-http.js +14 -15
  78. package/dist/src/gateway/live-dashboard-http.js.map +1 -1
  79. package/dist/src/gateway/progress-watch-machine.d.ts +1 -4
  80. package/dist/src/gateway/progress-watch-machine.js.map +1 -1
  81. package/dist/src/gateway/provenance-envelope.d.ts +3 -2
  82. package/dist/src/gateway/provenance-envelope.js +44 -11
  83. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  84. package/dist/src/gateway/providers.d.ts +19 -85
  85. package/dist/src/gateway/providers.js +132 -835
  86. package/dist/src/gateway/providers.js.map +1 -1
  87. package/dist/src/gateway/server.d.ts +8 -9
  88. package/dist/src/gateway/server.js +63 -99
  89. package/dist/src/gateway/server.js.map +1 -1
  90. package/dist/src/gateway/service.d.ts +48 -51
  91. package/dist/src/gateway/service.js +748 -540
  92. package/dist/src/gateway/service.js.map +1 -1
  93. package/dist/src/gateway/store.d.ts +20 -29
  94. package/dist/src/gateway/store.js +306 -838
  95. package/dist/src/gateway/store.js.map +1 -1
  96. package/dist/src/gateway/types.d.ts +63 -54
  97. package/dist/src/gateway/types.js +51 -91
  98. package/dist/src/gateway/types.js.map +1 -1
  99. package/docs/CONFIGURATION.md +11 -10
  100. package/docs/CONFIGURATION.zh-CN.md +10 -9
  101. package/docs/DASHBOARD.md +4 -38
  102. package/docs/DASHBOARD.zh-CN.md +4 -2
  103. package/docs/GATEWAY-ARCHITECTURE.md +61 -119
  104. package/package.json +1 -1
  105. package/skills/embassy-peer/SKILL.md +6 -6
@@ -1,15 +1,14 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
2
  import { BridgeError } from "../errors.js";
3
3
  import { KeyedMutex } from "../mutex.js";
4
- import { certifiedCompatibilityVersions, compatibilityCoversWrites, compatibilitySurfaceDefinitions, evaluateCompatibilityAttestation, isCompatibilityAttestation, } from "./compatibility.js";
5
- import { createGatewayConversationId, isGatewaySnapshot, startGatewayControlServer, } from "./control.js";
4
+ import { createGatewayConversationId, isGatewaySnapshot, startGatewayControlServer, pairParamAliases, pairParamThreadAttestation, } from "./control.js";
6
5
  import { publishGatewayDashboard } from "./dashboard.js";
7
6
  import { createDeliveryMachine, isTerminalDeliveryMachine, projectDelivery, projectDeliveryWakeups, transitionDelivery, } from "./delivery-machine.js";
8
7
  import { createCodexRegistrationSuccession, transitionCodexRegistrationSuccession, } from "./codex-registration-succession.js";
9
8
  import { createCodexRegistrationGeneration, isCodexRegistrationGeneration, } from "./codex-registration-generation.js";
10
9
  import { PROGRESS_WATCH_DEFAULT_IDLE_MS } from "./progress-watch-machine.js";
11
10
  import { GatewayStore, } from "./store.js";
12
- import { GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET, gatewayPublicSnapshotLimits, arePublicAvailablePeerSnapshots, projectPublicCompatibilityCheck, projectGatewayPublicSnapshot, } from "./types.js";
11
+ import { GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET, gatewayPublicSnapshotLimits, gatewayRegistrationIngressPrefixes, arePublicAvailablePeerSnapshots, directionId, parseDirection, projectGatewayPublicSnapshot, CONNECTOR_OBSERVATION_STALE_AFTER_MS, } from "./types.js";
13
12
  const PUBLIC_ALIAS = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
14
13
  const CLAUDE_SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
15
14
  const SAFE_CODE = /^[A-Z][A-Z0-9_]{0,63}$/;
@@ -22,6 +21,8 @@ const NATIVE_HELD_THRESHOLD_MS = 1_000;
22
21
  const RUNTIME_ACTIVITY_CAPACITY = 256;
23
22
  const DELIVERY_SETTLEMENT_RETRY_MS = 250;
24
23
  const DELIVERY_DASHBOARD_REFRESH_MS = 15_000;
24
+ const CLAUDE_DISCOVERY_REFRESH_MS = 30_000;
25
+ const CODEX_DOCTOR_REFRESH_MS = 30_000;
25
26
  const BOOT_REACTIVATION_TRUST_FAILURE_CODES = new Set([
26
27
  "HOME_INVALID",
27
28
  "MANAGED_CODEX_INVALID",
@@ -77,39 +78,13 @@ function bindingKey(binding) {
77
78
  ].join("\0");
78
79
  }
79
80
  function endpointRefreshCallbackKey(event) {
80
- const attestation = {
81
- schemaVersion: event.attestation.schemaVersion,
82
- surface: event.attestation.surface,
83
- version: event.attestation.version,
84
- tier: event.attestation.tier,
85
- checkedAt: event.attestation.checkedAt,
86
- probes: event.attestation.probes.map((probe) => ({
87
- name: probe.name,
88
- outcome: probe.outcome,
89
- ...(probe.safeErrorCode === undefined
90
- ? {}
91
- : { safeErrorCode: probe.safeErrorCode }),
92
- })),
93
- ...(event.attestation.safeErrorCode === undefined
94
- ? {}
95
- : { safeErrorCode: event.attestation.safeErrorCode }),
96
- };
97
- return event.outcome === "compatible"
98
- ? JSON.stringify({
99
- outcome: event.outcome,
100
- previous: event.previous,
101
- current: event.current,
102
- attestation,
103
- routes: [...event.routes]
104
- .map((route) => ({ ...route }))
105
- .sort((left, right) => left.routeHandle.localeCompare(right.routeHandle)),
106
- })
107
- : JSON.stringify({
108
- outcome: event.outcome,
109
- previous: event.previous,
110
- candidate: event.candidate,
111
- attestation,
112
- });
81
+ return JSON.stringify({
82
+ previous: event.previous,
83
+ current: event.current,
84
+ routes: [...event.routes]
85
+ .map((route) => ({ ...route }))
86
+ .sort((left, right) => left.routeHandle.localeCompare(right.routeHandle)),
87
+ });
113
88
  }
114
89
  function stableLease(provider, value) {
115
90
  return `lease_${createHash("sha256").update(provider).update("\0").update(value).digest("base64url")}`;
@@ -149,13 +124,12 @@ export class GatewayService {
149
124
  config;
150
125
  store;
151
126
  adapters;
152
- compatibilityObservers;
153
- compatibilitySurfaceDefinitions;
154
127
  publishDashboard;
155
128
  now;
156
129
  timers;
157
130
  nativePeerCwd;
158
131
  successionGeneration;
132
+ codexDoctor;
159
133
  mutex = new KeyedMutex();
160
134
  routeBindings = new Map();
161
135
  routeStates = new Map();
@@ -176,9 +150,6 @@ export class GatewayService {
176
150
  deliveryTokens = new Map();
177
151
  runtimeAlerts = [];
178
152
  activityEvents = [];
179
- /** Startup probe failures that must preflight cross-provider mutations. */
180
- incompatibleProviderSurfaces = new Map();
181
- activeCodexIncompatibility;
182
153
  activitySequence = 0;
183
154
  detachedReceiptWrites = new Set();
184
155
  nativeIngressByConversation = new Map();
@@ -202,8 +173,6 @@ export class GatewayService {
202
173
  durablyAppliedEndpointRefreshes = new Map();
203
174
  /** Selector transitions staged until their exact registration owns a helper. */
204
175
  pendingSelectorEndpointActivations = new Map();
205
- /** Last incompatible transition already projected per allowed Codex host. */
206
- handledIncompatibleEndpointRefreshes = new Map();
207
176
  /** Process-local dedupe for automatic public activity rows across retries. */
208
177
  endpointRefreshActivityKeys = new Set();
209
178
  callbackWorker;
@@ -211,8 +180,17 @@ export class GatewayService {
211
180
  callbackCapacity;
212
181
  deliveryCallbackReserve;
213
182
  deliveryTokenCapacity;
183
+ providerDispatchInFlightMessageIds = new Set();
184
+ connectorFreshnessDeadlineAt = new Map();
214
185
  lifecycleTimer;
186
+ lifecyclePublishPending = false;
215
187
  nextDashboardRefreshAt;
188
+ nextClaudeDiscoveryAt;
189
+ claudeDiscoveryScheduled = false;
190
+ claudeDiscoveryInFlight;
191
+ codexDoctorTimer;
192
+ codexDoctorInFlight;
193
+ codexDoctorResult;
216
194
  nextProgressWatchAt;
217
195
  control;
218
196
  /** Coarse controller mutation revision exposed by `health`. */
@@ -247,9 +225,6 @@ export class GatewayService {
247
225
  constructor(options) {
248
226
  this.config = options.config;
249
227
  this.adapters = [...(options.adapters ?? [])];
250
- this.compatibilityObservers = [...(options.compatibilityObservers ?? [])];
251
- this.compatibilitySurfaceDefinitions =
252
- options.compatibilitySurfaceDefinitions ?? compatibilitySurfaceDefinitions;
253
228
  this.publishDashboard = options.publishDashboard ?? publishGatewayDashboard;
254
229
  this.now = options.now ?? (() => new Date());
255
230
  this.store =
@@ -262,6 +237,7 @@ export class GatewayService {
262
237
  this.successionGeneration =
263
238
  options.successionGeneration ??
264
239
  createCodexRegistrationGeneration;
240
+ this.codexDoctor = options.codexDoctor;
265
241
  this.callbackCapacity = Math.max(64, options.config.limits.maxRoutes +
266
242
  options.config.limits.maxInFlightMessages * 8);
267
243
  // A callback flood must never crowd out write evidence. One selected route
@@ -297,28 +273,50 @@ export class GatewayService {
297
273
  }
298
274
  seen.add(key);
299
275
  }
300
- if (this.adapters.length > 0) {
301
- await this.runAutomaticCompatibilityProbesLocked();
302
- assertStartActive();
303
- }
304
276
  for (const adapter of this.adapters) {
305
277
  const observation = await adapter.initialize(this.callbacksFor(adapter));
306
278
  assertStartActive();
279
+ this.captureRegistryObservation(adapter, adapter.latestRegistryObservation?.());
307
280
  await this.store.observeConnector({
308
281
  identity: adapter.identity,
309
282
  health: observation.health,
310
- compatibility: observation.compatibility,
311
283
  protocol: adapter.protocol,
312
284
  protocolVersion: adapter.protocolVersion,
313
285
  ...(observation.safeErrorCode === undefined
314
286
  ? {}
315
287
  : { safeErrorCode: safeCode(observation.safeErrorCode, "ADAPTER_DEGRADED") }),
316
288
  });
289
+ this.recordConnectorFreshness(adapter.identity, observation.health);
290
+ if (observation.ownedRoute !== undefined) {
291
+ const route = observation.ownedRoute;
292
+ const prefix = gatewayRegistrationIngressPrefixes[adapter.identity.provider];
293
+ if (prefix === undefined || !route.alias.startsWith(prefix) ||
294
+ !route.alias.endsWith(`@${adapter.identity.hostId}`) ||
295
+ !PUBLIC_ALIAS.test(route.alias) || !PRIVATE_HANDLE.test(route.routeHandle) ||
296
+ route.state !== "idle") {
297
+ throw new BridgeError("GATEWAY_OWNED_ROUTE_INVALID", "A config-declared provider route has invalid identity evidence.");
298
+ }
299
+ const binding = {
300
+ ...adapter.identity,
301
+ routeHandle: route.routeHandle,
302
+ ownerLease: stableLease(adapter.identity.provider, `${adapter.identity.hostId}\0${route.alias}`),
303
+ };
304
+ await this.registerOrRebind(route.alias, binding, "endpoint_reobserved", route.state, binding.ownerLease, "explicit_opt_in");
305
+ this.rememberBinding(route.alias, binding, route.state);
306
+ bootReactivatedIdleAliases.push(route.alias);
307
+ }
317
308
  assertStartActive();
318
309
  }
310
+ await this.refreshClaudeDiscovery({ incrementRevision: false }).catch(() => false);
311
+ await this.refreshCodexDoctor();
312
+ for (const [alias, binding] of this.routeBindings) {
313
+ if (binding.provider === "claude" &&
314
+ this.routeStates.get(alias) === "idle") {
315
+ bootReactivatedIdleAliases.push(alias);
316
+ }
317
+ }
319
318
  await this.recoverCodexSuccessionAfterRestartLocked();
320
- bootReactivatedIdleAliases =
321
- await this.reactivateRetainedCodexRoutesAfterRestartLocked();
319
+ bootReactivatedIdleAliases.push(...(await this.reactivateRetainedCodexRoutesAfterRestartLocked()));
322
320
  assertStartActive();
323
321
  const control = await startGatewayControlServer({
324
322
  stateDir: this.store.rootDir,
@@ -337,8 +335,11 @@ export class GatewayService {
337
335
  this.control = control;
338
336
  this.running = true;
339
337
  this.nextDashboardRefreshAt = undefined;
338
+ this.nextClaudeDiscoveryAt =
339
+ this.now().getTime() + CLAUDE_DISCOVERY_REFRESH_MS;
340
340
  await this.refreshNextProgressWatchWakeLocked();
341
341
  this.scheduleLifecycleWakeLocked();
342
+ this.scheduleCodexDoctor();
342
343
  for (const alias of bootReactivatedIdleAliases) {
343
344
  this.scheduleDispatch(alias);
344
345
  }
@@ -367,6 +368,12 @@ export class GatewayService {
367
368
  const control = this.control;
368
369
  this.control = undefined;
369
370
  let closeFailed = false;
371
+ if (this.codexDoctorTimer !== undefined) {
372
+ this.timers.clearTimeout(this.codexDoctorTimer);
373
+ this.codexDoctorTimer = undefined;
374
+ }
375
+ await this.codexDoctorInFlight?.catch(() => undefined);
376
+ await this.claudeDiscoveryInFlight?.catch(() => undefined);
370
377
  if (control !== undefined) {
371
378
  try {
372
379
  await control.close();
@@ -383,6 +390,9 @@ export class GatewayService {
383
390
  this.timers.clearTimeout(this.lifecycleTimer);
384
391
  this.lifecycleTimer = undefined;
385
392
  }
393
+ this.nextClaudeDiscoveryAt = undefined;
394
+ this.lifecyclePublishPending = false;
395
+ this.connectorFreshnessDeadlineAt.clear();
386
396
  const quiesceResults = await Promise.allSettled(this.adapters.map(async (adapter) => await adapter.quiesceNativeInbound?.()));
387
397
  if (quiesceResults.some((result) => result.status === "rejected")) {
388
398
  closeFailed = true;
@@ -395,6 +405,13 @@ export class GatewayService {
395
405
  for (const messageId of [...this.messageContexts.keys()]) {
396
406
  if (retainedQueuedMessageIds.has(messageId))
397
407
  continue;
408
+ if (this.providerDispatchInFlightMessageIds.has(messageId)) {
409
+ await this.advanceDeliveryLocked(messageId, {
410
+ type: "dispatch_ambiguous",
411
+ at: this.now().getTime(),
412
+ safeErrorCode: "DISPATCH_OUTCOME_AMBIGUOUS",
413
+ });
414
+ }
398
415
  const queued = await this.store.settleQueuedMessage({
399
416
  messageId,
400
417
  state: "cancelled",
@@ -452,6 +469,7 @@ export class GatewayService {
452
469
  this.activeDispatchByTarget.clear();
453
470
  this.scheduledDispatchTargets.clear();
454
471
  this.dispatchRunnerTargets.clear();
472
+ this.providerDispatchInFlightMessageIds.clear();
455
473
  for (const timer of this.heldRedispatchTimers.values()) {
456
474
  this.timers.clearTimeout(timer);
457
475
  }
@@ -460,7 +478,6 @@ export class GatewayService {
460
478
  this.deliveryTrackers.clear();
461
479
  this.deliveryTokens.clear();
462
480
  this.runtimeAlerts.length = 0;
463
- this.incompatibleProviderSurfaces.clear();
464
481
  this.registryEvidenceByConnector.clear();
465
482
  this.registryAdaptersWithParseableRecords.clear();
466
483
  this.detachedReceiptWrites.clear();
@@ -476,7 +493,6 @@ export class GatewayService {
476
493
  this.activatedEndpointRefreshes.clear();
477
494
  this.durablyAppliedEndpointRefreshes.clear();
478
495
  this.pendingSelectorEndpointActivations.clear();
479
- this.handledIncompatibleEndpointRefreshes.clear();
480
496
  this.endpointRefreshActivityKeys.clear();
481
497
  this.codexOrphanCleanupPending.clear();
482
498
  this.candidates.clear();
@@ -536,12 +552,12 @@ export class GatewayService {
536
552
  pair: async (params) => await this.exclusiveDecision(async () => this.pairRoutes(params), {
537
553
  kind: "pairing",
538
554
  action: "routes_paired",
539
- aliases: [params.claudeAlias, params.codexAlias],
555
+ aliases: [...pairParamAliases(params)],
540
556
  }),
541
557
  unpair: async (params) => await this.exclusiveDecision(async () => this.unpairRoutes(params), {
542
558
  kind: "pairing",
543
559
  action: "routes_unpaired",
544
- aliases: [params.claudeAlias, params.codexAlias],
560
+ aliases: [...pairParamAliases(params)],
545
561
  }),
546
562
  listSnapshot: async () => (await this.observeSnapshot()).snapshot,
547
563
  observeSnapshot: async () => await this.observeSnapshot(),
@@ -572,91 +588,6 @@ export class GatewayService {
572
588
  async snapshot() {
573
589
  return (await this.observeSnapshot()).snapshot;
574
590
  }
575
- async runAutomaticCompatibilityProbesLocked() {
576
- const entries = [
577
- ...this.adapters.map((adapter) => {
578
- if (adapter.compatibilitySurface === undefined ||
579
- adapter.runCompatibilityProbes === undefined) {
580
- throw new BridgeError("COMPAT_PROVIDER_UNAVAILABLE", "Every local provider must expose the bounded compatibility probe contract.");
581
- }
582
- return {
583
- observer: adapter,
584
- adapter,
585
- };
586
- }),
587
- ...this.compatibilityObservers.map((observer) => ({ observer })),
588
- ];
589
- const observedEntries = entries.map((entry) => ({
590
- ...entry,
591
- observation: entry.observer.compatibilitySurface(),
592
- }));
593
- const bySurface = new Map(observedEntries.map((entry) => [entry.observation.surface, entry]));
594
- if (bySurface.size !== entries.length) {
595
- throw new BridgeError("COMPAT_PROVIDER_UNAVAILABLE", "Each compatibility observer must expose one distinct surface.");
596
- }
597
- const declared = new Set(this.compatibilitySurfaceDefinitions.map(({ surface }) => surface));
598
- if ([...bySurface.keys()].some((surface) => !declared.has(surface))) {
599
- throw new BridgeError("COMPAT_SURFACE_UNDECLARED", "Every compatibility observer must use a declared surface.");
600
- }
601
- const forbiddenCodexThreadIds = (await this.store.inspectPrivateCodexRoutes()).map((route) => route.binding.routeHandle);
602
- const probeContext = {
603
- stateRoot: this.store.rootDir,
604
- forbiddenCodexThreadIds,
605
- };
606
- const attestations = [];
607
- for (const definition of this.compatibilitySurfaceDefinitions) {
608
- const { surface } = definition;
609
- const entry = bySurface.get(surface);
610
- if (entry === undefined) {
611
- if (!definition.required)
612
- continue;
613
- throw new BridgeError("COMPAT_PROVIDER_UNAVAILABLE", "A required compatibility surface is unavailable.");
614
- }
615
- const probes = entry.adapter === undefined
616
- ? await entry.observer.runCompatibilityProbes()
617
- : await entry.adapter.runCompatibilityProbes(probeContext);
618
- let writeProbePassed = false;
619
- if (entry.adapter !== undefined) {
620
- const writeProbe = entry.adapter.latestWriteCompatibilityProbeObservation?.(entry.adapter.identity.endpointGeneration);
621
- writeProbePassed = writeProbe?.outcome === "pass";
622
- if (writeProbe?.outcome === "fail") {
623
- this.addRuntimeAlert(writeProbe.safeErrorCode, writeProbe.safeErrorCode === "CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED" ||
624
- writeProbe.safeErrorCode === "CODEX_WRITE_PROBE_CLEANUP_UNCONFIRMED"
625
- ? "error"
626
- : "warning", { provider: "codex", host: entry.adapter.identity.hostId });
627
- }
628
- this.captureRegistryObservation(entry.adapter, entry.adapter.latestRegistryObservation?.());
629
- }
630
- const attestation = evaluateCompatibilityAttestation({
631
- surface,
632
- version: entry.observation.version,
633
- checkedAt: this.now().toISOString(),
634
- certifiedVersions: certifiedCompatibilityVersions[surface],
635
- probes,
636
- });
637
- await this.store.recordCompatibilityAttestation(attestation);
638
- entry.observer.acceptCompatibilityAttestation?.(attestation);
639
- if (attestation.tier === "incompatible" && entry.adapter !== undefined) {
640
- const code = attestation.safeErrorCode ?? "COMPATIBILITY_CHECK_FAILED";
641
- this.incompatibleProviderSurfaces.set(this.providerSurfaceKey(entry.adapter.identity.provider, entry.adapter.identity.hostId), code);
642
- if (surface === "codex") {
643
- this.activeCodexIncompatibility = structuredClone(attestation);
644
- }
645
- }
646
- else if (surface === "codex" &&
647
- attestation.tier === "schema_attested" &&
648
- !(compatibilityCoversWrites(attestation) && writeProbePassed) &&
649
- entry.adapter !== undefined) {
650
- // A live but untested App Server schema is useful for observation only.
651
- // Fence retained boot reactivation before it can trigger endpoint
652
- // refresh churn; an explicit registration may still re-probe a later,
653
- // certified generation through the provider's bounded refresh path.
654
- this.incompatibleProviderSurfaces.set(this.providerSurfaceKey("codex", entry.adapter.identity.hostId), "CODEX_MONITOR_ONLY");
655
- }
656
- attestations.push(attestation);
657
- }
658
- return attestations;
659
- }
660
591
  captureRegistryObservation(adapter, observation) {
661
592
  if (adapter.identity.provider !== "claude" ||
662
593
  observation === undefined ||
@@ -703,6 +634,14 @@ export class GatewayService {
703
634
  const registry = this.registryEvidenceByConnector.get(`${connector.provider}\0${connector.host}`);
704
635
  return {
705
636
  ...connector,
637
+ ...(connector.provider === "codex" &&
638
+ this.codexDoctorResult !== undefined
639
+ ? {
640
+ codexDoctor: {
641
+ conditions: this.codexDoctorConditions(connector),
642
+ },
643
+ }
644
+ : {}),
706
645
  ...(connector.provider !== "claude" || registry === undefined
707
646
  ? {}
708
647
  : {
@@ -714,6 +653,70 @@ export class GatewayService {
714
653
  };
715
654
  });
716
655
  }
656
+ recordConnectorFreshness(identity, health) {
657
+ const key = `${identity.provider}\0${identity.hostId}`;
658
+ if (health === "healthy") {
659
+ this.connectorFreshnessDeadlineAt.set(key, this.now().getTime() + CONNECTOR_OBSERVATION_STALE_AFTER_MS + 1);
660
+ }
661
+ else {
662
+ this.connectorFreshnessDeadlineAt.delete(key);
663
+ }
664
+ }
665
+ codexDoctorConditions(connector) {
666
+ const attachment = this.codexDoctorResult?.conditions[0] ?? "unknown";
667
+ const stale = connector.observationAgeMs === undefined ||
668
+ connector.observationAgeMs > CONNECTOR_OBSERVATION_STALE_AFTER_MS;
669
+ if (attachment === "split_brain" || attachment === "orphaned") {
670
+ return stale ? [attachment, "observation_stale"] : [attachment];
671
+ }
672
+ if (stale)
673
+ return ["observation_stale", attachment];
674
+ return [attachment];
675
+ }
676
+ async refreshCodexDoctor() {
677
+ if (this.codexDoctor === undefined)
678
+ return false;
679
+ const next = await this.codexDoctor().catch(() => ({ conditions: ["unknown"] }));
680
+ const changed = JSON.stringify(next.conditions) !==
681
+ JSON.stringify(this.codexDoctorResult?.conditions);
682
+ this.codexDoctorResult = next;
683
+ return changed;
684
+ }
685
+ scheduleCodexDoctor() {
686
+ if (!this.running ||
687
+ this.closing ||
688
+ this.codexDoctor === undefined ||
689
+ this.codexDoctorTimer !== undefined ||
690
+ this.codexDoctorInFlight !== undefined) {
691
+ return;
692
+ }
693
+ const timer = this.timers.setTimeout(() => {
694
+ if (this.codexDoctorTimer !== timer)
695
+ return;
696
+ this.codexDoctorTimer = undefined;
697
+ const operation = this.refreshCodexDoctor()
698
+ .then(async (changed) => {
699
+ if (!changed || this.closing)
700
+ return;
701
+ await this.mutex.run("service", async () => {
702
+ this.revision += 1;
703
+ await this.publish();
704
+ });
705
+ })
706
+ .catch(() => {
707
+ this.dashboardHealthy = false;
708
+ });
709
+ this.codexDoctorInFlight = operation;
710
+ void operation.finally(() => {
711
+ if (this.codexDoctorInFlight === operation) {
712
+ this.codexDoctorInFlight = undefined;
713
+ }
714
+ this.scheduleCodexDoctor();
715
+ });
716
+ }, CODEX_DOCTOR_REFRESH_MS);
717
+ this.codexDoctorTimer = timer;
718
+ timer.unref?.();
719
+ }
717
720
  async observeSnapshot() {
718
721
  return await this.mutex.run("service", async () => {
719
722
  const changed = await this.processLifecycleLocked();
@@ -746,7 +749,11 @@ export class GatewayService {
746
749
  fingerprintSnapshot(snapshot) {
747
750
  // Closed snapshot validation bounds every array and field before this
748
751
  // serialization. Omitting generatedAt is the sole semantic exception.
749
- const { generatedAt: _generatedAt, ...semanticSnapshot } = snapshot;
752
+ const { generatedAt: _generatedAt, connectors, ...snapshotWithoutGeneratedAt } = snapshot;
753
+ const semanticSnapshot = {
754
+ ...snapshotWithoutGeneratedAt,
755
+ connectors: connectors.map(({ observationAgeMs: _observationAgeMs, ...connector }) => connector),
756
+ };
750
757
  const encoded = JSON.stringify(semanticSnapshot);
751
758
  if (Buffer.byteLength(encoded, "utf8") >
752
759
  GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET) {
@@ -884,50 +891,30 @@ export class GatewayService {
884
891
  });
885
892
  },
886
893
  onEndpointRefresh: (event) => {
894
+ if (!Array.isArray(event.routes))
895
+ return;
896
+ const routeHandles = event.routes.map((route) => route.routeHandle);
887
897
  if (!this.running ||
888
898
  adapter.identity.provider !== "codex" ||
889
899
  event.previous.provider !== "codex" ||
890
900
  event.previous.hostId !== adapter.identity.hostId ||
891
- !isCompatibilityAttestation(event.attestation) ||
892
- event.attestation.surface !== "codex") {
893
- return;
894
- }
895
- if (event.outcome === "compatible") {
896
- const routeHandles = event.routes.map((route) => route.routeHandle);
897
- if (event.current.provider !== "codex" ||
898
- event.current.hostId !== event.previous.hostId ||
899
- event.current.endpointGeneration !==
900
- adapter.identity.endpointGeneration ||
901
- event.attestation.version !== adapter.protocolVersion ||
902
- event.attestation.tier === "incompatible" ||
903
- routeHandles.length > this.config.limits.maxRoutes ||
904
- new Set(routeHandles).size !== routeHandles.length ||
905
- routeHandles.some((routeHandle) => !PRIVATE_HANDLE.test(routeHandle))) {
906
- return;
907
- }
908
- }
909
- else if (event.candidate.provider !== "codex" ||
910
- event.candidate.hostId !== event.previous.hostId ||
911
- event.previous.endpointGeneration !==
901
+ event.current.provider !== "codex" ||
902
+ event.current.hostId !== event.previous.hostId ||
903
+ event.current.endpointGeneration !==
912
904
  adapter.identity.endpointGeneration ||
913
- event.attestation.tier !== "incompatible") {
905
+ routeHandles.length > this.config.limits.maxRoutes ||
906
+ new Set(routeHandles).size !== routeHandles.length ||
907
+ routeHandles.some((routeHandle) => !PRIVATE_HANDLE.test(routeHandle))) {
914
908
  return;
915
909
  }
916
910
  this.enqueueCallback({
917
911
  type: "endpoint_refresh",
918
912
  adapter,
919
- value: event.outcome === "compatible"
920
- ? {
921
- ...event,
922
- previous: { ...event.previous },
923
- current: { ...event.current },
924
- routes: event.routes.map((route) => ({ ...route })),
925
- }
926
- : {
927
- ...event,
928
- previous: { ...event.previous },
929
- candidate: { ...event.candidate },
930
- },
913
+ value: {
914
+ previous: { ...event.previous },
915
+ current: { ...event.current },
916
+ routes: event.routes.map((route) => ({ ...route })),
917
+ },
931
918
  });
932
919
  },
933
920
  };
@@ -1032,10 +1019,10 @@ export class GatewayService {
1032
1019
  this.endpointRefreshConflictPending);
1033
1020
  }
1034
1021
  enqueueCallbackWorkerOnly() {
1035
- if (this.callbackScheduled)
1022
+ if (this.callbackScheduled || this.callbackWorker !== undefined)
1036
1023
  return;
1037
1024
  this.callbackScheduled = true;
1038
- setImmediate(() => {
1025
+ queueMicrotask(() => {
1039
1026
  this.callbackScheduled = false;
1040
1027
  const worker = this.mutex.run("service", async () => {
1041
1028
  await this.drainCallbackQueueLocked();
@@ -1074,7 +1061,7 @@ export class GatewayService {
1074
1061
  await this.onDelivery(event.source, event.value, event.receivedAt);
1075
1062
  }
1076
1063
  else if (event.type === "route") {
1077
- if (pendingRefresh?.outcome === "compatible" &&
1064
+ if (pendingRefresh !== undefined &&
1078
1065
  event.source.provider === "codex" &&
1079
1066
  event.source.hostId === pendingRefresh.current.hostId &&
1080
1067
  event.source.endpointGeneration ===
@@ -1204,12 +1191,7 @@ export class GatewayService {
1204
1191
  await this.drainDetachedReceiptWritesLocked();
1205
1192
  }
1206
1193
  async failClosedEndpointRefreshConflict(boundary) {
1207
- const identities = [
1208
- boundary.value.previous,
1209
- ...(boundary.value.outcome === "compatible"
1210
- ? [boundary.value.current]
1211
- : []),
1212
- ];
1194
+ const identities = [boundary.value.previous, boundary.value.current];
1213
1195
  for (const identity of identities) {
1214
1196
  const routes = (await this.store.inspectPrivateCodexRoutes()).filter((route) => route.binding.hostId === identity.hostId &&
1215
1197
  route.binding.endpointGeneration === identity.endpointGeneration);
@@ -1311,23 +1293,6 @@ export class GatewayService {
1311
1293
  throw new BridgeError("PROVIDER_UNAVAILABLE", "The selected provider is unavailable.", true);
1312
1294
  return adapter;
1313
1295
  }
1314
- providerSurfaceKey(provider, host) {
1315
- return `${provider}@${host}`;
1316
- }
1317
- incompatibleProviderCode(provider, host) {
1318
- return this.incompatibleProviderSurfaces.get(this.providerSurfaceKey(provider, host));
1319
- }
1320
- /** Reject before either half of a cross-provider mutation can acquire state. */
1321
- assertCrossProviderMutationCompatible(host, options = { allowCodexRefresh: false }) {
1322
- const claudeCode = this.incompatibleProviderCode("claude", host);
1323
- if (claudeCode !== undefined) {
1324
- throw new BridgeError(claudeCode, "The Claude provider is observation-only until Embassy restarts with a compatible runtime.");
1325
- }
1326
- const codexCode = this.incompatibleProviderCode("codex", host);
1327
- if (codexCode !== undefined && !options.allowCodexRefresh) {
1328
- throw new BridgeError(codexCode, "The Codex provider is observation-only until a compatible endpoint is re-observed.");
1329
- }
1330
- }
1331
1296
  async assertClaudeWorkspaceDisjoint(adapter, routeHandle) {
1332
1297
  if (adapter.identity.provider !== "claude") {
1333
1298
  throw new BridgeError("CLAUDE_PROVIDER_MISMATCH", "The selected workspace guard does not belong to Claude.");
@@ -1385,7 +1350,6 @@ export class GatewayService {
1385
1350
  for (const route of retained) {
1386
1351
  if (!route.enabled ||
1387
1352
  route.state !== "stale" ||
1388
- route.compatibility !== "expired" ||
1389
1353
  this.pendingCodexSuccessionRecovery?.journal.new.alias === route.alias ||
1390
1354
  this.codexSuccessionPoisoned.has(route.alias)) {
1391
1355
  continue;
@@ -1398,13 +1362,6 @@ export class GatewayService {
1398
1362
  this.addRuntimeAlert("CODEX_BOOT_REACTIVATION_SKIPPED", "warning", { provider: "codex", host: route.binding.hostId, alias: route.alias });
1399
1363
  continue;
1400
1364
  }
1401
- if (this.incompatibleProviderCode("codex", route.binding.hostId) !==
1402
- undefined ||
1403
- this.incompatibleProviderCode("claude", route.binding.hostId) !==
1404
- undefined) {
1405
- this.addRuntimeAlert("CODEX_BOOT_REACTIVATION_SKIPPED", "warning", { provider: "codex", host: route.binding.hostId, alias: route.alias });
1406
- continue;
1407
- }
1408
1365
  let routeSelected = false;
1409
1366
  let advertiseAttempted = false;
1410
1367
  let storeReactivated = false;
@@ -1442,19 +1399,20 @@ export class GatewayService {
1442
1399
  storeReactivated =
1443
1400
  afterStaging !== undefined &&
1444
1401
  afterStaging.enabled &&
1445
- afterStaging.compatibility === "compatible" &&
1402
+ afterStaging.state !== "stale" &&
1446
1403
  bindingKey(afterStaging.binding) === bindingKey(binding);
1447
1404
  }
1448
1405
  advertiseAttempted = true;
1449
- const advertise = claudeAdapter.advertiseNativeCodexPeer;
1406
+ const advertise = claudeAdapter.advertiseNativeSourcePeer;
1450
1407
  if (advertise === undefined) {
1451
1408
  throw new BridgeError("CODEX_BOOT_REACTIVATION_UNAVAILABLE", "The retained Codex task cannot rebuild its native advertisement.");
1452
1409
  }
1453
1410
  await advertise.call(claudeAdapter, {
1454
1411
  alias: route.alias,
1412
+ sourceProvider: "codex",
1455
1413
  cwd: this.nativePeerCwd,
1456
1414
  });
1457
- await claudeAdapter.updateNativeCodexPeerStatus?.(route.alias, selected.state === "idle"
1415
+ await claudeAdapter.updateNativeSourcePeerStatus?.(route.alias, selected.state === "idle"
1458
1416
  ? "idle"
1459
1417
  : selected.state === "awaiting_approval"
1460
1418
  ? "waiting"
@@ -1498,7 +1456,7 @@ export class GatewayService {
1498
1456
  let cleanupFailed = false;
1499
1457
  if (advertiseAttempted) {
1500
1458
  await claudeAdapter
1501
- .unadvertiseNativeCodexPeer?.(route.alias)
1459
+ .unadvertiseNativeSourcePeer?.(route.alias)
1502
1460
  .catch(() => {
1503
1461
  cleanupFailed = true;
1504
1462
  });
@@ -1630,7 +1588,7 @@ export class GatewayService {
1630
1588
  oldInspection.binding.ownerLease !==
1631
1589
  stableLease("codex", `${lock.hostId}\0${lock.threadId}`) ||
1632
1590
  !oldInspection.enabled ||
1633
- oldInspection.compatibility !== "compatible") {
1591
+ oldInspection.state === "stale") {
1634
1592
  throw new BridgeError("CODEX_SUCCESSION_OWNER_MISMATCH", "The active Codex registration is no longer exact and compatible.");
1635
1593
  }
1636
1594
  const claudeAdapter = this.adapter("claude", params.hostId);
@@ -1939,7 +1897,7 @@ export class GatewayService {
1939
1897
  this.rememberBinding(effect.registration.alias, execution.newBinding, state);
1940
1898
  this.codexRegistrationLocks.delete(this.successionOldRegistration(execution).alias);
1941
1899
  this.codexRegistrationLocks.set(effect.registration.alias, Object.freeze({ ...effect.registration }));
1942
- await claudeAdapter.updateNativeCodexPeerStatus?.(effect.registration.alias, state === "idle"
1900
+ await claudeAdapter.updateNativeSourcePeerStatus?.(effect.registration.alias, state === "idle"
1943
1901
  ? "idle"
1944
1902
  : state === "awaiting_approval"
1945
1903
  ? "waiting"
@@ -2084,8 +2042,8 @@ export class GatewayService {
2084
2042
  throw new BridgeError("CODEX_SUCCESSION_DRAIN_INCOMPLETE", "Interrupted generations cannot close before pending work and receipts are drained. Restart the gateway with embassy serve.");
2085
2043
  }
2086
2044
  const results = await Promise.allSettled([
2087
- claudeAdapter.unadvertiseNativeCodexPeer?.(execution.oldRegistration.alias),
2088
- claudeAdapter.unadvertiseNativeCodexPeer?.(execution.newRegistration.alias),
2045
+ claudeAdapter.unadvertiseNativeSourcePeer?.(execution.oldRegistration.alias),
2046
+ claudeAdapter.unadvertiseNativeSourcePeer?.(execution.newRegistration.alias),
2089
2047
  codexAdapter.releaseRoute?.(execution.oldRegistration.threadId),
2090
2048
  codexAdapter.releaseRoute?.(execution.newRegistration.threadId),
2091
2049
  ]);
@@ -2310,9 +2268,6 @@ export class GatewayService {
2310
2268
  if (!params.alias.startsWith("codex-")) {
2311
2269
  throw new BridgeError("INVALID_CODEX_PEER_ALIAS", "A registered Codex alias must start with codex-.");
2312
2270
  }
2313
- this.assertCrossProviderMutationCompatible(params.hostId, {
2314
- allowCodexRefresh: params.succeedsAlias === undefined,
2315
- });
2316
2271
  if (this.codexSuccessionPoisoned.has(params.alias) ||
2317
2272
  this.codexOrphanCleanupPending.has(params.alias) ||
2318
2273
  (params.succeedsAlias !== undefined &&
@@ -2383,7 +2338,7 @@ export class GatewayService {
2383
2338
  const afterRefresh = await this.store.inspectPrivateRoute(params.alias);
2384
2339
  const alreadyInstalledExactly = afterRefresh !== undefined &&
2385
2340
  afterRefresh.enabled &&
2386
- afterRefresh.compatibility === "compatible" &&
2341
+ afterRefresh.state !== "stale" &&
2387
2342
  bindingKey(afterRefresh.binding) === bindingKey(binding);
2388
2343
  if (!alreadyInstalledExactly || !deferredEndpointRefresh) {
2389
2344
  await this.registerOrRebind(params.alias, binding, "endpoint_reobserved", registered.state);
@@ -2398,15 +2353,16 @@ export class GatewayService {
2398
2353
  try {
2399
2354
  this.rememberBinding(params.alias, binding, registered.state);
2400
2355
  const claudeAdapter = this.adapter("claude", params.hostId);
2401
- const advertise = claudeAdapter.advertiseNativeCodexPeer;
2356
+ const advertise = claudeAdapter.advertiseNativeSourcePeer;
2402
2357
  if (advertise !== undefined) {
2403
2358
  advertiseAttempted = true;
2404
2359
  await advertise.call(claudeAdapter, {
2405
2360
  alias: params.alias,
2361
+ sourceProvider: "codex",
2406
2362
  cwd: this.nativePeerCwd,
2407
2363
  });
2408
2364
  }
2409
- await claudeAdapter.updateNativeCodexPeerStatus?.(params.alias, registered.state === "idle"
2365
+ await claudeAdapter.updateNativeSourcePeerStatus?.(params.alias, registered.state === "idle"
2410
2366
  ? "idle"
2411
2367
  : registered.state === "awaiting_approval"
2412
2368
  ? "waiting"
@@ -2428,8 +2384,7 @@ export class GatewayService {
2428
2384
  }
2429
2385
  catch (error) {
2430
2386
  if (routeBeforeRegistration !== undefined) {
2431
- if (deferredEndpointRefresh &&
2432
- registered.endpointRefresh?.outcome === "compatible") {
2387
+ if (deferredEndpointRefresh && registered.endpointRefresh !== undefined) {
2433
2388
  try {
2434
2389
  await this.store.markConnectorOffline(registered.endpointRefresh.current, "CODEX_ENDPOINT_ACTIVATION_FAILED", [], { preserveQueued: true });
2435
2390
  this.routeStates.set(params.alias, "stale");
@@ -2511,7 +2466,7 @@ export class GatewayService {
2511
2466
  });
2512
2467
  if (advertiseAttempted) {
2513
2468
  await this.adapter("claude", params.hostId)
2514
- .unadvertiseNativeCodexPeer?.(params.alias)
2469
+ .unadvertiseNativeSourcePeer?.(params.alias)
2515
2470
  .catch(() => {
2516
2471
  cleanupFailed = true;
2517
2472
  });
@@ -2538,7 +2493,7 @@ export class GatewayService {
2538
2493
  }
2539
2494
  this.forgetBinding(params.alias);
2540
2495
  await this.adapter("claude", host)
2541
- .unadvertiseNativeCodexPeer?.(params.alias)
2496
+ .unadvertiseNativeSourcePeer?.(params.alias)
2542
2497
  .catch(() => {
2543
2498
  this.dashboardHealthy = false;
2544
2499
  });
@@ -2709,7 +2664,7 @@ export class GatewayService {
2709
2664
  }
2710
2665
  await this.requireUnchangedStaleCodexOrphan(alias, binding);
2711
2666
  if (nativeGeneration !== undefined) {
2712
- const unadvertise = claudeAdapter.unadvertiseNativeCodexPeer;
2667
+ const unadvertise = claudeAdapter.unadvertiseNativeSourcePeer;
2713
2668
  if (unadvertise === undefined) {
2714
2669
  throw new BridgeError("CODEX_ORPHAN_NATIVE_IDENTITY_UNPROVEN", "The native Codex listener cannot be removed exactly.");
2715
2670
  }
@@ -2773,7 +2728,7 @@ export class GatewayService {
2773
2728
  return JSON.stringify({
2774
2729
  inboundMode: snapshot.inboundMode,
2775
2730
  health: snapshot.health,
2776
- connectors: snapshot.connectors.map(({ lastSeenAt: _lastSeenAt, ...connector }) => connector),
2731
+ connectors: snapshot.connectors.map(({ lastSeenAt: _lastSeenAt, observationAgeMs: _observationAgeMs, ...connector }) => connector),
2777
2732
  availablePeers: snapshot.availablePeers.map(({ lastSeenAt: _lastSeenAt, ...peer }) => peer),
2778
2733
  routes: snapshot.routes.map(({ lastSeenAt: _lastSeenAt, ...route }) => route),
2779
2734
  messages: snapshot.messages,
@@ -2782,7 +2737,116 @@ export class GatewayService {
2782
2737
  truncation: snapshot.truncation,
2783
2738
  });
2784
2739
  }
2785
- async refreshClaudeDiscovery() {
2740
+ schedulePeriodicClaudeDiscovery() {
2741
+ if (this.closing ||
2742
+ this.claudeDiscoveryScheduled ||
2743
+ this.claudeDiscoveryInFlight !== undefined) {
2744
+ return;
2745
+ }
2746
+ this.claudeDiscoveryScheduled = true;
2747
+ queueMicrotask(() => {
2748
+ this.claudeDiscoveryScheduled = false;
2749
+ if (this.closing || !this.running)
2750
+ return;
2751
+ const operation = this.refreshClaudeDiscoveryPeriodically().catch(() => {
2752
+ this.dashboardHealthy = false;
2753
+ });
2754
+ this.claudeDiscoveryInFlight = operation;
2755
+ void operation.finally(() => {
2756
+ if (this.claudeDiscoveryInFlight === operation) {
2757
+ this.claudeDiscoveryInFlight = undefined;
2758
+ }
2759
+ });
2760
+ });
2761
+ }
2762
+ async refreshClaudeDiscoveryPeriodically() {
2763
+ const observations = (await Promise.all(this.adapters
2764
+ .filter((adapter) => adapter.identity.provider === "claude")
2765
+ .map(async (adapter) => {
2766
+ try {
2767
+ const snapshot = await adapter.discoverClaudePeers?.();
2768
+ return snapshot === undefined ? undefined : { adapter, snapshot };
2769
+ }
2770
+ catch {
2771
+ return undefined;
2772
+ }
2773
+ }))).filter((item) => item !== undefined);
2774
+ if (this.closing)
2775
+ return;
2776
+ const preparedRebinds = [];
2777
+ const persisted = await this.store.inspectPrivateClaudeRoutes();
2778
+ for (const route of persisted) {
2779
+ if (!route.enabled || route.state !== "stale")
2780
+ continue;
2781
+ const observation = observations.find(({ adapter, snapshot }) => snapshot.complete &&
2782
+ adapter.identity.hostId === route.binding.hostId);
2783
+ if (observation === undefined)
2784
+ continue;
2785
+ const peers = observation.snapshot.peers.filter((peer) => peer.kind === "interactive" &&
2786
+ PUBLIC_ALIAS.test(peer.alias) &&
2787
+ peer.alias.endsWith(`@${observation.adapter.identity.hostId}`));
2788
+ const candidates = peers.filter((peer) => peer.routeHandle === route.binding.routeHandle);
2789
+ const peer = candidates[0];
2790
+ if (candidates.length !== 1 ||
2791
+ peer === undefined ||
2792
+ peers.filter((item) => item.alias === peer.alias).length !== 1) {
2793
+ continue;
2794
+ }
2795
+ const candidate = { ...peer, adapter: observation.adapter };
2796
+ try {
2797
+ this.validatePersistedClaudeCandidate(candidate, route);
2798
+ await this.assertClaudeWorkspaceDisjoint(candidate.adapter, candidate.routeHandle);
2799
+ const selected = await candidate.adapter.selectRoute({
2800
+ alias: candidate.alias,
2801
+ routeHandle: candidate.routeHandle,
2802
+ });
2803
+ if (selected.routeHandle !== candidate.routeHandle) {
2804
+ throw new BridgeError("ROUTE_MISMATCH", "The peer identity changed during selection.");
2805
+ }
2806
+ preparedRebinds.push({
2807
+ candidate,
2808
+ persisted: route,
2809
+ selectedState: selected.state,
2810
+ });
2811
+ }
2812
+ catch {
2813
+ await candidate.adapter.releaseRoute?.(candidate.routeHandle).catch(() => undefined);
2814
+ }
2815
+ }
2816
+ const consumed = new Set();
2817
+ try {
2818
+ await this.mutex.run("service", async () => {
2819
+ if (this.closing || !this.running)
2820
+ return;
2821
+ const changed = await this.refreshClaudeDiscovery({
2822
+ incrementRevision: false,
2823
+ observations,
2824
+ preparedRebinds,
2825
+ consumedPreparedRebinds: consumed,
2826
+ });
2827
+ if (changed) {
2828
+ this.revision += 1;
2829
+ await this.publish();
2830
+ }
2831
+ this.scheduleLifecycleWakeLocked();
2832
+ });
2833
+ }
2834
+ finally {
2835
+ const releasable = await this.mutex.run("service", async () => preparedRebinds.filter((prepared) => {
2836
+ if (consumed.has(prepared))
2837
+ return false;
2838
+ const binding = {
2839
+ ...prepared.candidate.adapter.identity,
2840
+ routeHandle: prepared.candidate.routeHandle,
2841
+ ownerLease: stableLease("claude", prepared.candidate.routeHandle),
2842
+ };
2843
+ const alias = this.bindingAliases.get(bindingKey(binding));
2844
+ return alias === undefined || this.routeStates.get(alias) === "stale";
2845
+ }));
2846
+ await Promise.allSettled(releasable.map(async (prepared) => await prepared.candidate.adapter.releaseRoute?.(prepared.candidate.routeHandle)));
2847
+ }
2848
+ }
2849
+ async refreshClaudeDiscovery(options = {}) {
2786
2850
  const publicBefore = await this.discoveryPublicFingerprint();
2787
2851
  this.candidates.clear();
2788
2852
  const rowsByAlias = new Map();
@@ -2794,14 +2858,33 @@ export class GatewayService {
2794
2858
  peers: [],
2795
2859
  complete: false,
2796
2860
  };
2861
+ let discoverySucceeded = false;
2797
2862
  try {
2798
- discovered = (await adapter.discoverClaudePeers?.()) ?? discovered;
2863
+ const observed = options.observations?.find((item) => item.adapter === adapter);
2864
+ if (observed !== undefined) {
2865
+ discovered = observed.snapshot;
2866
+ discoverySucceeded = true;
2867
+ }
2868
+ else if (options.observations === undefined &&
2869
+ adapter.discoverClaudePeers !== undefined) {
2870
+ discovered = await adapter.discoverClaudePeers();
2871
+ discoverySucceeded = true;
2872
+ }
2799
2873
  }
2800
2874
  catch {
2801
2875
  // Registry observation is dashboard evidence, not Claude mailbox
2802
2876
  // authority. The provider still revalidates the exact target before
2803
2877
  // any write.
2804
2878
  }
2879
+ if (discoverySucceeded && discovered.complete) {
2880
+ await this.store.observeConnector({
2881
+ identity: adapter.identity,
2882
+ health: "healthy",
2883
+ protocol: adapter.protocol,
2884
+ protocolVersion: adapter.protocolVersion,
2885
+ });
2886
+ this.recordConnectorFreshness(adapter.identity, "healthy");
2887
+ }
2805
2888
  this.captureRegistryObservation(adapter, discovered.registry ?? adapter.latestRegistryObservation?.());
2806
2889
  const grouped = new Map();
2807
2890
  const byHandle = new Map();
@@ -2899,7 +2982,6 @@ export class GatewayService {
2899
2982
  for (const route of persisted) {
2900
2983
  if (!route.enabled ||
2901
2984
  route.state !== "stale" ||
2902
- route.compatibility !== "expired" ||
2903
2985
  !CLAUDE_SESSION_ID.test(route.binding.routeHandle) ||
2904
2986
  route.binding.ownerLease !==
2905
2987
  stableLease("claude", route.binding.routeHandle)) {
@@ -2913,7 +2995,21 @@ export class GatewayService {
2913
2995
  const candidate = matches[0];
2914
2996
  if (candidate === undefined)
2915
2997
  continue;
2916
- await this.activatePersistedClaudeCandidate(candidate, route, "peer_identity_reobserved").catch(() => undefined);
2998
+ const prepared = options.preparedRebinds?.find((item) => item.candidate.adapter === candidate.adapter &&
2999
+ item.candidate.routeHandle === candidate.routeHandle &&
3000
+ item.persisted.binding.ownerLease === route.binding.ownerLease);
3001
+ const activated = prepared === undefined
3002
+ ? options.observations === undefined &&
3003
+ await this.activatePersistedClaudeCandidate(candidate, route, "peer_identity_reobserved").then(() => true, () => false)
3004
+ : await this.commitPreparedClaudeRebind(prepared, "peer_identity_reobserved").then(() => {
3005
+ options.consumedPreparedRebinds?.add(prepared);
3006
+ return true;
3007
+ }, () => false);
3008
+ if (activated &&
3009
+ this.running &&
3010
+ this.routeStates.get(candidate.alias) === "idle") {
3011
+ this.scheduleDispatch(candidate.alias);
3012
+ }
2917
3013
  }
2918
3014
  const rows = [];
2919
3015
  for (const [alias, row] of rowsByAlias) {
@@ -2923,8 +3019,7 @@ export class GatewayService {
2923
3019
  alias,
2924
3020
  provider: "claude",
2925
3021
  host: row.adapter.identity.hostId,
2926
- state: "incompatible",
2927
- compatibility: "incompatible",
3022
+ state: "offline",
2928
3023
  validated: false,
2929
3024
  selected: false,
2930
3025
  safeErrorCode: row.safeErrorCode,
@@ -2942,7 +3037,6 @@ export class GatewayService {
2942
3037
  provider: "claude",
2943
3038
  host: row.adapter.identity.hostId,
2944
3039
  state: row.peer.state,
2945
- compatibility: row.peer.compatibility,
2946
3040
  validated: true,
2947
3041
  selected,
2948
3042
  });
@@ -2951,7 +3045,6 @@ export class GatewayService {
2951
3045
  await this.store.observeRoute({
2952
3046
  binding: selectedBinding,
2953
3047
  state: row.peer.state,
2954
- compatibility: "compatible",
2955
3048
  });
2956
3049
  }
2957
3050
  }
@@ -2966,7 +3059,7 @@ export class GatewayService {
2966
3059
  const inspection = await this.store.inspectPrivateRoute(alias);
2967
3060
  if (inspection !== undefined &&
2968
3061
  inspection.enabled &&
2969
- inspection.compatibility === "compatible") {
3062
+ inspection.state !== "stale") {
2970
3063
  await this.drainPreDeadlineDeliveryCallbacksLocked();
2971
3064
  const inFlightSettlements = await this.planRouteInFlightSettlementsLocked(alias, {
2972
3065
  unwrittenOutcome: "failed",
@@ -2986,7 +3079,7 @@ export class GatewayService {
2986
3079
  }
2987
3080
  this.availablePeers = rows.sort((left, right) => left.alias.localeCompare(right.alias));
2988
3081
  const changed = publicBefore !== (await this.discoveryPublicFingerprint());
2989
- if (changed)
3082
+ if (changed && options.incrementRevision !== false)
2990
3083
  this.revision += 1;
2991
3084
  return changed;
2992
3085
  }
@@ -3040,17 +3133,7 @@ export class GatewayService {
3040
3133
  return this.candidates.get(selector);
3041
3134
  }
3042
3135
  async activatePersistedClaudeCandidate(candidate, persisted, reason) {
3043
- const expectedLease = stableLease("claude", candidate.routeHandle);
3044
- if (!persisted.enabled ||
3045
- persisted.state !== "stale" ||
3046
- persisted.compatibility !== "expired" ||
3047
- persisted.binding.provider !== "claude" ||
3048
- candidate.adapter.identity.provider !== "claude" ||
3049
- persisted.binding.hostId !== candidate.adapter.identity.hostId ||
3050
- persisted.binding.routeHandle !== candidate.routeHandle ||
3051
- persisted.binding.ownerLease !== expectedLease) {
3052
- throw new BridgeError("ROUTE_REBIND_IDENTITY_MISMATCH", "The persisted Claude selection does not match the exact live session identity.");
3053
- }
3136
+ this.validatePersistedClaudeCandidate(candidate, persisted);
3054
3137
  await this.assertClaudeWorkspaceDisjoint(candidate.adapter, candidate.routeHandle);
3055
3138
  try {
3056
3139
  const selected = await candidate.adapter.selectRoute({
@@ -3060,23 +3143,7 @@ export class GatewayService {
3060
3143
  if (selected.routeHandle !== candidate.routeHandle) {
3061
3144
  throw new BridgeError("ROUTE_MISMATCH", "The peer identity changed during selection.");
3062
3145
  }
3063
- const binding = {
3064
- ...candidate.adapter.identity,
3065
- routeHandle: candidate.routeHandle,
3066
- ownerLease: expectedLease,
3067
- };
3068
- await this.store.rebindStaleRoute({
3069
- alias: persisted.alias,
3070
- newAlias: candidate.alias,
3071
- currentOwnerLease: persisted.binding.ownerLease,
3072
- newBinding: binding,
3073
- reason,
3074
- state: selected.state,
3075
- });
3076
- if (persisted.alias !== candidate.alias) {
3077
- this.forgetBinding(persisted.alias);
3078
- }
3079
- this.rememberBinding(candidate.alias, binding, selected.state);
3146
+ await this.commitPreparedClaudeRebind({ candidate, persisted, selectedState: selected.state }, reason);
3080
3147
  }
3081
3148
  catch (error) {
3082
3149
  await candidate.adapter.releaseRoute?.(candidate.routeHandle).catch(() => {
@@ -3085,6 +3152,44 @@ export class GatewayService {
3085
3152
  throw error;
3086
3153
  }
3087
3154
  }
3155
+ validatePersistedClaudeCandidate(candidate, persisted) {
3156
+ const expectedLease = stableLease("claude", candidate.routeHandle);
3157
+ if (!persisted.enabled ||
3158
+ persisted.state !== "stale" ||
3159
+ persisted.binding.provider !== "claude" ||
3160
+ candidate.adapter.identity.provider !== "claude" ||
3161
+ persisted.binding.hostId !== candidate.adapter.identity.hostId ||
3162
+ persisted.binding.routeHandle !== candidate.routeHandle ||
3163
+ persisted.binding.ownerLease !== expectedLease) {
3164
+ throw new BridgeError("ROUTE_REBIND_IDENTITY_MISMATCH", "The persisted Claude selection does not match the exact live session identity.");
3165
+ }
3166
+ }
3167
+ async commitPreparedClaudeRebind(prepared, reason) {
3168
+ const { candidate, selectedState } = prepared;
3169
+ const persisted = (await this.store.inspectPrivateRoute(prepared.persisted.alias)) ??
3170
+ (await this.store.inspectPrivateRoute(candidate.alias));
3171
+ if (persisted === undefined) {
3172
+ throw new BridgeError("ROUTE_REBIND_IDENTITY_MISMATCH", "The persisted Claude selection changed before re-observation committed.");
3173
+ }
3174
+ this.validatePersistedClaudeCandidate(candidate, persisted);
3175
+ const binding = {
3176
+ ...candidate.adapter.identity,
3177
+ routeHandle: candidate.routeHandle,
3178
+ ownerLease: stableLease("claude", candidate.routeHandle),
3179
+ };
3180
+ await this.store.rebindStaleRoute({
3181
+ alias: persisted.alias,
3182
+ newAlias: candidate.alias,
3183
+ currentOwnerLease: persisted.binding.ownerLease,
3184
+ newBinding: binding,
3185
+ reason,
3186
+ state: selectedState,
3187
+ });
3188
+ if (persisted.alias !== candidate.alias) {
3189
+ this.forgetBinding(persisted.alias);
3190
+ }
3191
+ this.rememberBinding(candidate.alias, binding, selectedState);
3192
+ }
3088
3193
  async selectClaudeCandidate(candidate, persisted) {
3089
3194
  if (persisted !== undefined) {
3090
3195
  await this.activatePersistedClaudeCandidate(candidate, persisted, "peer_explicitly_reselected");
@@ -3109,7 +3214,6 @@ export class GatewayService {
3109
3214
  binding,
3110
3215
  registrationMode: "selected_live_peer",
3111
3216
  state: selected.state,
3112
- compatibility: "compatible",
3113
3217
  });
3114
3218
  this.rememberBinding(candidate.alias, binding, selected.state);
3115
3219
  }
@@ -3161,7 +3265,6 @@ export class GatewayService {
3161
3265
  binding,
3162
3266
  registrationMode: "selected_live_peer",
3163
3267
  state: selectedState,
3164
- compatibility: "compatible",
3165
3268
  },
3166
3269
  inFlightSettlements,
3167
3270
  });
@@ -3234,47 +3337,83 @@ export class GatewayService {
3234
3337
  }
3235
3338
  return candidate.alias;
3236
3339
  }
3237
- assertCodexPairCaller(params) {
3238
- const binding = this.routeBindings.get(params.codexAlias);
3340
+ assertPairThreadAttestation(params) {
3341
+ const attestation = pairParamThreadAttestation(params);
3342
+ const legacyAlias = "codexAlias" in params ? params.codexAlias : undefined;
3343
+ if (attestation === undefined && legacyAlias === undefined)
3344
+ return;
3345
+ const binding = this.routeBindings.get(attestation?.alias ?? legacyAlias);
3239
3346
  if (binding?.provider !== "codex" ||
3240
- (params.codexThreadId !== undefined &&
3241
- binding.routeHandle !== params.codexThreadId)) {
3347
+ (attestation !== undefined && binding.routeHandle !== attestation.threadId)) {
3242
3348
  throw new BridgeError("CODEX_CALLER_MISMATCH", "The inherited Codex task does not own the requested pair endpoint.");
3243
3349
  }
3244
3350
  }
3245
3351
  async pairRoutes(params) {
3246
- this.assertCodexPairCaller(params);
3247
- await this.selectAndPairClaude(params.claudeAlias, params.codexAlias);
3352
+ this.assertPairThreadAttestation(params);
3353
+ if ("claudeAlias" in params) {
3354
+ await this.selectAndPairClaude(params.claudeAlias, params.codexAlias);
3355
+ return;
3356
+ }
3357
+ await this.assertLiveConsentEndpoints(params.aliases);
3358
+ const added = await this.store.addConsentEdge({ aliases: params.aliases });
3359
+ if (added.created)
3360
+ await this.changed();
3361
+ else
3362
+ await this.publish();
3248
3363
  }
3249
3364
  async unpairRoutes(params) {
3250
- this.assertCodexPairCaller(params);
3251
- const selected = await this.selectedClaudeRoute(params.claudeAlias);
3252
- await this.unpairClaudeRoute(selected, params.codexAlias);
3365
+ this.assertPairThreadAttestation(params);
3366
+ const aliases = pairParamAliases(params);
3367
+ if (!("claudeAlias" in params))
3368
+ await this.assertLiveConsentEndpoints(aliases);
3369
+ await this.removeConsentEdge(aliases);
3370
+ }
3371
+ async assertLiveConsentEndpoints(aliases) {
3372
+ const routes = await Promise.all(aliases.map((alias) => this.store.inspectPrivateRoute(alias)));
3373
+ if (routes[0] === undefined || routes[1] === undefined) {
3374
+ throw new BridgeError("CONSENT_EDGE_ROUTE_NOT_FOUND", "Both consent endpoints must be registered.");
3375
+ }
3376
+ for (const route of routes) {
3377
+ const live = this.routeBindings.get(route.alias);
3378
+ if (!route.enabled ||
3379
+ route.state === "stale" ||
3380
+ live === undefined ||
3381
+ bindingKey(live) !== bindingKey(route.binding) ||
3382
+ this.adapter(route.binding.provider, route.binding.hostId).identity.endpointGeneration !==
3383
+ route.binding.endpointGeneration) {
3384
+ throw new BridgeError("ROUTE_UNAVAILABLE", "Both consent endpoints must be exact positively observed live routes.");
3385
+ }
3386
+ }
3387
+ if (routes[0].binding.provider === routes[1].binding.provider || routes[0].binding.hostId !== routes[1].binding.hostId) {
3388
+ throw new BridgeError("INVALID_CONSENT_EDGE", "A consent edge must connect distinct providers on the same host.");
3389
+ }
3390
+ return routes;
3253
3391
  }
3254
- async unpairClaudeRoute(selected, codexAlias) {
3392
+ async removeConsentEdge(aliases) {
3255
3393
  await this.drainPreDeadlineDeliveryCallbacksLocked();
3256
- const pair = {
3257
- claudeAlias: selected.alias,
3258
- codexAlias,
3259
- };
3260
- const inFlightSettlements = await this.planPairInFlightSettlementsLocked(pair, {
3394
+ const routes = await Promise.all(aliases.map((alias) => this.store.inspectPrivateRoute(alias)));
3395
+ const edge = { aliases };
3396
+ const inFlightSettlements = await this.planConsentEdgeInFlightSettlementsLocked(edge, {
3261
3397
  unwrittenOutcome: "cancelled",
3262
3398
  safeErrorCode: "PAIR_REMOVED",
3263
3399
  });
3264
- const result = await this.store.unpairRoutes({
3265
- ...pair,
3400
+ const result = await this.store.removeConsentEdge({
3401
+ ...edge,
3266
3402
  inFlightSettlements,
3267
3403
  });
3268
3404
  for (const settlement of result.settlements) {
3269
3405
  await this.applyTerminalSettlementLocked(settlement);
3270
3406
  }
3271
- this.purgePairCapabilitiesLocked(pair);
3272
- if (result.claudeRouteUnreferenced) {
3273
- const settlements = await this.store.unregisterRoute(selected.alias, selected.binding.ownerLease);
3407
+ this.purgeConsentEdgeCapabilitiesLocked(aliases);
3408
+ for (const alias of result.unreferencedAliases) {
3409
+ const selected = routes.find((route) => route?.alias === alias);
3410
+ if (selected?.binding.provider !== "claude" || selected.registrationMode !== "selected_live_peer")
3411
+ continue;
3412
+ const settlements = await this.store.unregisterRoute(alias, selected.binding.ownerLease);
3274
3413
  for (const settlement of settlements) {
3275
3414
  await this.applyTerminalSettlementLocked(settlement);
3276
3415
  }
3277
- this.forgetBinding(selected.alias);
3416
+ this.forgetBinding(alias);
3278
3417
  await this.adapters
3279
3418
  .find((adapter) => adapter.identity.provider === "claude" &&
3280
3419
  adapter.identity.hostId === selected.binding.hostId)
@@ -3282,7 +3421,7 @@ export class GatewayService {
3282
3421
  .catch(() => {
3283
3422
  this.dashboardHealthy = false;
3284
3423
  });
3285
- this.availablePeers = this.availablePeers.map((peer) => peer.alias === selected.alias ? { ...peer, selected: false } : peer);
3424
+ this.availablePeers = this.availablePeers.map((peer) => peer.alias === alias ? { ...peer, selected: false } : peer);
3286
3425
  }
3287
3426
  await this.changed();
3288
3427
  }
@@ -3291,17 +3430,6 @@ export class GatewayService {
3291
3430
  }
3292
3431
  async selectAndPairClaude(selector, codexAlias) {
3293
3432
  const codexBinding = this.routeBindings.get(codexAlias);
3294
- const retainedCodexRoute = codexBinding?.provider === "codex"
3295
- ? undefined
3296
- : await this.store.inspectPrivateRoute(codexAlias);
3297
- const codexHost = codexBinding?.provider === "codex"
3298
- ? codexBinding.hostId
3299
- : retainedCodexRoute?.binding.provider === "codex"
3300
- ? retainedCodexRoute.binding.hostId
3301
- : undefined;
3302
- if (codexHost !== undefined) {
3303
- this.assertCrossProviderMutationCompatible(codexHost);
3304
- }
3305
3433
  const codexAdapter = codexBinding?.provider === "codex"
3306
3434
  ? this.adapters.find((adapter) => adapter.identity.provider === "codex" &&
3307
3435
  adapter.identity.hostId === codexBinding.hostId)
@@ -3341,10 +3469,7 @@ export class GatewayService {
3341
3469
  }
3342
3470
  let paired;
3343
3471
  try {
3344
- paired = await this.store.pairRoutes({
3345
- claudeAlias: candidate.alias,
3346
- codexAlias,
3347
- });
3472
+ paired = await this.store.addConsentEdge({ aliases: [candidate.alias, codexAlias] });
3348
3473
  }
3349
3474
  catch (error) {
3350
3475
  if (!alreadyLive &&
@@ -3384,7 +3509,7 @@ export class GatewayService {
3384
3509
  async unselectClaude(params) {
3385
3510
  const codexAlias = await this.inferCodexAlias(params.codexThreadId);
3386
3511
  const selected = await this.selectedClaudeRoute(params.alias);
3387
- await this.unpairClaudeRoute(selected, codexAlias);
3512
+ await this.removeConsentEdge([selected.alias, codexAlias]);
3388
3513
  }
3389
3514
  async selectedClaudeRoute(selector) {
3390
3515
  const persisted = await this.store.inspectPrivateClaudeRoutes();
@@ -3396,14 +3521,13 @@ export class GatewayService {
3396
3521
  throw new BridgeError("PEER_NOT_FOUND", "No selected Claude session matches that selector.");
3397
3522
  return selected;
3398
3523
  }
3399
- async registerOrRebind(alias, binding, reason, state, currentOwnerLease = binding.ownerLease) {
3524
+ async registerOrRebind(alias, binding, reason, state, currentOwnerLease = binding.ownerLease, registrationMode = binding.provider === "codex" ? "explicit_opt_in" : "selected_live_peer") {
3400
3525
  try {
3401
3526
  await this.store.registerRoute({
3402
3527
  alias,
3403
3528
  binding,
3404
- registrationMode: binding.provider === "codex" ? "explicit_opt_in" : "selected_live_peer",
3529
+ registrationMode,
3405
3530
  state,
3406
- compatibility: "compatible",
3407
3531
  });
3408
3532
  }
3409
3533
  catch (error) {
@@ -3473,14 +3597,13 @@ export class GatewayService {
3473
3597
  await this.store.observeConnector({
3474
3598
  identity: adapter.identity,
3475
3599
  health: "healthy",
3476
- compatibility: "compatible",
3477
3600
  protocol: adapter.protocol,
3478
3601
  protocolVersion: adapter.protocolVersion,
3479
3602
  });
3603
+ this.recordConnectorFreshness(adapter.identity, "healthy");
3480
3604
  await this.store.observeRoute({
3481
3605
  binding,
3482
3606
  state,
3483
- compatibility: "compatible",
3484
3607
  });
3485
3608
  return binding;
3486
3609
  }
@@ -3783,7 +3906,15 @@ export class GatewayService {
3783
3906
  unwrittenOutcome: input.unwrittenOutcome,
3784
3907
  safeErrorCode: input.safeErrorCode,
3785
3908
  };
3786
- const transition = transitionDelivery(tracker.machine, event);
3909
+ const stateAtTermination = tracker.pendingTerminalEvent === undefined &&
3910
+ this.providerDispatchInFlightMessageIds.has(messageId)
3911
+ ? transitionDelivery(tracker.machine, {
3912
+ type: "dispatch_ambiguous",
3913
+ at: observedAt,
3914
+ safeErrorCode: "DISPATCH_OUTCOME_AMBIGUOUS",
3915
+ }).state
3916
+ : tracker.machine;
3917
+ const transition = transitionDelivery(stateAtTermination, event);
3787
3918
  const settlement = transition.effects.find((effect) => effect.type === "settle_delivery");
3788
3919
  if (settlement === undefined) {
3789
3920
  throw new BridgeError("ROUTE_TERMINATION_STATE_MISMATCH", "An affected in-flight delivery could not produce an exact terminal route settlement.", true);
@@ -3796,8 +3927,8 @@ export class GatewayService {
3796
3927
  };
3797
3928
  });
3798
3929
  }
3799
- async planPairInFlightSettlementsLocked(pair, input) {
3800
- const affected = await this.store.inspectAffectedPairInFlightMessages(pair);
3930
+ async planConsentEdgeInFlightSettlementsLocked(edge, input) {
3931
+ const affected = await this.store.inspectAffectedConsentEdgeInFlightMessages(edge);
3801
3932
  const observedAt = this.now().getTime();
3802
3933
  return affected.map(({ messageId }) => {
3803
3934
  const tracker = this.deliveryTrackers.get(messageId);
@@ -3815,7 +3946,15 @@ export class GatewayService {
3815
3946
  unwrittenOutcome: input.unwrittenOutcome,
3816
3947
  safeErrorCode: input.safeErrorCode,
3817
3948
  };
3818
- const transition = transitionDelivery(tracker.machine, event);
3949
+ const stateAtTermination = tracker.pendingTerminalEvent === undefined &&
3950
+ this.providerDispatchInFlightMessageIds.has(messageId)
3951
+ ? transitionDelivery(tracker.machine, {
3952
+ type: "dispatch_ambiguous",
3953
+ at: observedAt,
3954
+ safeErrorCode: "DISPATCH_OUTCOME_AMBIGUOUS",
3955
+ }).state
3956
+ : tracker.machine;
3957
+ const transition = transitionDelivery(stateAtTermination, event);
3819
3958
  const settlement = transition.effects.find((effect) => effect.type === "settle_delivery");
3820
3959
  if (settlement === undefined) {
3821
3960
  throw new BridgeError("PAIR_TERMINATION_STATE_MISMATCH", "An affected in-flight delivery could not produce an exact terminal pair settlement.", true);
@@ -3828,9 +3967,8 @@ export class GatewayService {
3828
3967
  };
3829
3968
  });
3830
3969
  }
3831
- purgePairCapabilitiesLocked(pair) {
3832
- const matches = (sourceAlias, targetAlias) => (sourceAlias === pair.claudeAlias && targetAlias === pair.codexAlias) ||
3833
- (sourceAlias === pair.codexAlias && targetAlias === pair.claudeAlias);
3970
+ purgeConsentEdgeCapabilitiesLocked(aliases) {
3971
+ const matches = (sourceAlias, targetAlias) => aliases.includes(sourceAlias) && aliases.includes(targetAlias);
3834
3972
  const conversationIds = new Set();
3835
3973
  for (const [conversationId, conversation] of this.conversations) {
3836
3974
  if (conversation.pair !== true ||
@@ -4181,6 +4319,17 @@ export class GatewayService {
4181
4319
  async processLifecycleLocked() {
4182
4320
  const now = this.now().getTime();
4183
4321
  let changed = false;
4322
+ if (this.nextClaudeDiscoveryAt !== undefined &&
4323
+ this.nextClaudeDiscoveryAt <= now) {
4324
+ this.nextClaudeDiscoveryAt = now + CLAUDE_DISCOVERY_REFRESH_MS;
4325
+ this.schedulePeriodicClaudeDiscovery();
4326
+ }
4327
+ for (const [key, deadlineAt] of this.connectorFreshnessDeadlineAt) {
4328
+ if (deadlineAt > now)
4329
+ continue;
4330
+ this.connectorFreshnessDeadlineAt.delete(key);
4331
+ changed = true;
4332
+ }
4184
4333
  if (await this.processProgressWatchesLocked(now))
4185
4334
  changed = true;
4186
4335
  await this.drainPreDeadlineDeliveryCallbacksLocked();
@@ -4318,6 +4467,14 @@ export class GatewayService {
4318
4467
  if (this.nextProgressWatchAt !== undefined) {
4319
4468
  consider(this.nextProgressWatchAt);
4320
4469
  }
4470
+ if (this.nextClaudeDiscoveryAt !== undefined) {
4471
+ consider(this.nextClaudeDiscoveryAt);
4472
+ }
4473
+ for (const deadlineAt of this.connectorFreshnessDeadlineAt.values()) {
4474
+ consider(deadlineAt);
4475
+ }
4476
+ if (this.lifecyclePublishPending)
4477
+ consider(now);
4321
4478
  if (hasNonterminal) {
4322
4479
  this.nextDashboardRefreshAt ??= now + DELIVERY_DASHBOARD_REFRESH_MS;
4323
4480
  consider(this.nextDashboardRefreshAt);
@@ -4333,10 +4490,21 @@ export class GatewayService {
4333
4490
  this.lifecycleTimer = undefined;
4334
4491
  void this.mutex
4335
4492
  .run("service", async () => {
4336
- const changed = await this.processLifecycleLocked();
4337
- if (changed) {
4338
- this.revision += 1;
4339
- await this.publish();
4493
+ try {
4494
+ const changed = await this.processLifecycleLocked();
4495
+ if (changed) {
4496
+ this.revision += 1;
4497
+ this.lifecyclePublishPending = true;
4498
+ }
4499
+ if (this.lifecyclePublishPending) {
4500
+ await this.publish();
4501
+ this.lifecyclePublishPending = false;
4502
+ }
4503
+ this.scheduleLifecycleWakeLocked();
4504
+ }
4505
+ catch {
4506
+ this.dashboardHealthy = false;
4507
+ this.scheduleLifecycleRetryLocked();
4340
4508
  }
4341
4509
  })
4342
4510
  .catch(() => {
@@ -4346,6 +4514,23 @@ export class GatewayService {
4346
4514
  this.lifecycleTimer = timer;
4347
4515
  timer.unref?.();
4348
4516
  }
4517
+ scheduleLifecycleRetryLocked() {
4518
+ if (!this.running || this.closing || this.lifecycleTimer !== undefined) {
4519
+ return;
4520
+ }
4521
+ const timer = this.timers.setTimeout(() => {
4522
+ if (this.lifecycleTimer !== timer)
4523
+ return;
4524
+ this.lifecycleTimer = undefined;
4525
+ void this.mutex.run("service", async () => {
4526
+ this.scheduleLifecycleWakeLocked();
4527
+ }).catch(() => {
4528
+ this.dashboardHealthy = false;
4529
+ });
4530
+ }, DELIVERY_SETTLEMENT_RETRY_MS);
4531
+ this.lifecycleTimer = timer;
4532
+ timer.unref?.();
4533
+ }
4349
4534
  async deliveryStatus(token) {
4350
4535
  return await this.mutex.run("service", async () => {
4351
4536
  if (!DELIVERY_TOKEN.test(token))
@@ -4614,12 +4799,13 @@ export class GatewayService {
4614
4799
  * native-ingress boundary; this preserves the exact connector generation
4615
4800
  * without recreating route authority or accepting a new unsolicited send.
4616
4801
  */
4617
- async enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, text) {
4802
+ async enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, expectedTargetBinding, text) {
4618
4803
  const target = await this.store.resolveRoute(conversation.sourceAlias);
4619
4804
  if (sourceBinding.provider !== "claude" ||
4620
- target.provider !== "codex" ||
4805
+ target.provider === "claude" ||
4806
+ target.provider !== expectedTargetBinding.provider || target.ownerLease !== expectedTargetBinding.ownerLease ||
4621
4807
  sourceBinding.hostId !== target.hostId) {
4622
- throw new BridgeError("RECOVERED_REPLY_ROUTE_MISMATCH", "The retained provider reply no longer matches an exact same-host Codex target.");
4808
+ throw new BridgeError("RECOVERED_REPLY_ROUTE_MISMATCH", "The retained provider reply no longer matches an exact same-host non-Claude target.");
4623
4809
  }
4624
4810
  const sequence = conversation.nextSequence;
4625
4811
  const enqueuedAt = this.now().getTime();
@@ -4650,6 +4836,7 @@ export class GatewayService {
4650
4836
  expectsReply: false,
4651
4837
  sequence,
4652
4838
  targetBindingKey: bindingKey(target),
4839
+ sourceBinding: { ...sourceBinding },
4653
4840
  authorization: "selected_route",
4654
4841
  targetAlias: conversation.sourceAlias,
4655
4842
  deadlineAt,
@@ -4662,7 +4849,7 @@ export class GatewayService {
4662
4849
  deadlineAt,
4663
4850
  });
4664
4851
  await this.changed();
4665
- await this.setNativeCodexStatus(conversation.sourceAlias, "waiting");
4852
+ await this.setNativeSourceStatus(conversation.sourceAlias, "waiting");
4666
4853
  this.scheduleDispatch(conversation.sourceAlias);
4667
4854
  }
4668
4855
  pruneTransient() {
@@ -4703,7 +4890,7 @@ export class GatewayService {
4703
4890
  break;
4704
4891
  }
4705
4892
  }
4706
- async awaitDispatchWithNativeHeldLocked(tracker, operation) {
4893
+ async awaitDispatchWithNativeHeld(messageId, operation) {
4707
4894
  const outcome = operation.then((value) => ({ kind: "result", value }), (error) => ({ kind: "error", error }));
4708
4895
  const unwrap = (settled) => {
4709
4896
  if (settled.kind === "error")
@@ -4711,7 +4898,7 @@ export class GatewayService {
4711
4898
  return settled.value;
4712
4899
  };
4713
4900
  while (true) {
4714
- const current = this.deliveryTrackers.get(tracker.messageId);
4901
+ const current = this.deliveryTrackers.get(messageId);
4715
4902
  if (current === undefined ||
4716
4903
  current.nativeReceipt === undefined ||
4717
4904
  current.nativeHeldSent ||
@@ -4736,14 +4923,15 @@ export class GatewayService {
4736
4923
  }
4737
4924
  // A terminal callback may have arrived before the provider's dispatch
4738
4925
  // promise resolved. Let that exact evidence win before emitting held.
4739
- await this.drainDeliveryCallbacksForMessageLocked(tracker.messageId);
4740
- const afterCallbacks = this.deliveryTrackers.get(tracker.messageId);
4741
- if (afterCallbacks === undefined ||
4742
- isTerminalDeliveryMachine(afterCallbacks.machine) ||
4743
- this.now().getTime() >= afterCallbacks.deadlineAt) {
4744
- return unwrap(await outcome);
4745
- }
4746
- await this.publishNativeHeldLocked(afterCallbacks);
4926
+ await this.mutex.run("service", async () => {
4927
+ await this.drainDeliveryCallbacksForMessageLocked(messageId);
4928
+ const afterCallbacks = this.deliveryTrackers.get(messageId);
4929
+ if (afterCallbacks !== undefined &&
4930
+ !isTerminalDeliveryMachine(afterCallbacks.machine) &&
4931
+ this.now().getTime() < afterCallbacks.deadlineAt) {
4932
+ await this.publishNativeHeldLocked(afterCallbacks);
4933
+ }
4934
+ });
4747
4935
  }
4748
4936
  }
4749
4937
  restoreQueuedMessageContextLocked(item, binding, canonicalConversationId) {
@@ -4784,7 +4972,7 @@ export class GatewayService {
4784
4972
  });
4785
4973
  return context;
4786
4974
  }
4787
- async dispatchOne(targetAlias) {
4975
+ async prepareDispatchLocked(targetAlias) {
4788
4976
  if (!this.running ||
4789
4977
  this.closing ||
4790
4978
  this.codexSuccessionDispatchFrozen.has(targetAlias) ||
@@ -4882,7 +5070,6 @@ export class GatewayService {
4882
5070
  !dispatchable ||
4883
5071
  inspection === undefined ||
4884
5072
  !inspection.enabled ||
4885
- inspection.compatibility !== "compatible" ||
4886
5073
  !storedDispatchable ||
4887
5074
  bindingKey(inspection.binding) !== bindingKey(binding) ||
4888
5075
  bindingKey(selectedBinding) !== bindingKey(binding)) {
@@ -4972,13 +5159,35 @@ export class GatewayService {
4972
5159
  })).markerActive
4973
5160
  : false;
4974
5161
  }
4975
- let result;
4976
- try {
4977
- result = await this.awaitDispatchWithNativeHeldLocked(tracker, this.adapter(binding.provider, binding.hostId).dispatch({
5162
+ const sourceBinding = context.sourceBinding ??
5163
+ (await this.store.inspectPrivateRoute(item.sourceAlias))?.binding;
5164
+ const parsedDirection = parseDirection(item.direction);
5165
+ if (sourceBinding === undefined ||
5166
+ parsedDirection?.sourceProvider !== sourceBinding.provider ||
5167
+ parsedDirection.targetProvider !== binding.provider ||
5168
+ item.direction !== directionId(sourceBinding.provider, binding.provider)) {
5169
+ await this.finishDelivery({
5170
+ messageId: item.messageId,
5171
+ state: "failed",
5172
+ safeErrorCode: "MESSAGE_DIRECTION_BINDING_MISMATCH",
5173
+ });
5174
+ return;
5175
+ }
5176
+ context.sourceBinding ??= { ...sourceBinding };
5177
+ this.scheduleLifecycleWakeLocked();
5178
+ return {
5179
+ targetAlias,
5180
+ messageId: item.messageId,
5181
+ body: item.body,
5182
+ binding: { ...binding },
5183
+ authorization: context.authorization,
5184
+ steeringWhileBusy,
5185
+ input: {
4978
5186
  sourceAlias: item.sourceAlias,
5187
+ sourceProvider: sourceBinding.provider,
4979
5188
  targetAlias: item.targetAlias,
4980
5189
  conversationId: context.conversationId,
4981
- binding,
5190
+ binding: { ...binding },
4982
5191
  authorization: context.authorization,
4983
5192
  messageId: item.messageId,
4984
5193
  text: item.body,
@@ -4988,21 +5197,43 @@ export class GatewayService {
4988
5197
  ...(item.steer === true && item.queuedAhead !== undefined
4989
5198
  ? { queuedAhead: item.queuedAhead }
4990
5199
  : {}),
4991
- ...(progressWatchActive
4992
- ? { progressWatchActive: true }
4993
- : {}),
4994
- }));
5200
+ ...(progressWatchActive ? { progressWatchActive: true } : {}),
5201
+ },
5202
+ };
5203
+ }
5204
+ async commitDispatchLocked(prepared, outcome) {
5205
+ const { targetAlias, messageId, body, binding, authorization, steeringWhileBusy, } = prepared;
5206
+ await this.drainDeliveryCallbacksForMessageLocked(messageId);
5207
+ if (await this.processLifecycleLocked()) {
5208
+ await this.changed();
4995
5209
  }
4996
- catch {
4997
- await this.drainDeliveryCallbacksForMessageLocked(item.messageId);
4998
- if (await this.processLifecycleLocked()) {
4999
- await this.changed();
5000
- }
5001
- if (!this.messageContexts.has(item.messageId)) {
5002
- this.scheduleDispatch(this.bindingAliases.get(bindingKey(binding)) ?? targetAlias);
5003
- return;
5004
- }
5005
- await this.advanceDeliveryLocked(item.messageId, {
5210
+ const context = this.messageContexts.get(messageId);
5211
+ const tracker = this.deliveryTrackers.get(messageId);
5212
+ if (context === undefined || tracker === undefined) {
5213
+ this.scheduleDispatch(this.bindingAliases.get(bindingKey(binding)) ?? targetAlias);
5214
+ return;
5215
+ }
5216
+ const currentTargetAlias = this.bindingAliases.get(context.targetBindingKey) ??
5217
+ this.bindingAliases.get(bindingKey(binding)) ??
5218
+ targetAlias;
5219
+ const currentSelectedBinding = authorization === "selected_route"
5220
+ ? this.routeBindings.get(currentTargetAlias)
5221
+ : undefined;
5222
+ const currentSelectedRoute = authorization === "selected_route"
5223
+ ? await this.store.inspectPrivateRoute(currentTargetAlias)
5224
+ : undefined;
5225
+ const authorityStillCurrent = !(context.authorization !== authorization ||
5226
+ context.targetBindingKey !== bindingKey(binding) ||
5227
+ (authorization === "selected_route" &&
5228
+ (currentSelectedBinding === undefined ||
5229
+ bindingKey(currentSelectedBinding) !== bindingKey(binding) ||
5230
+ currentSelectedRoute === undefined ||
5231
+ !currentSelectedRoute.enabled ||
5232
+ currentSelectedRoute.state === "stale" ||
5233
+ bindingKey(currentSelectedRoute.binding) !== bindingKey(binding) ||
5234
+ this.routeStates.get(currentTargetAlias) === "stale")));
5235
+ if (outcome.kind === "error") {
5236
+ await this.advanceDeliveryLocked(messageId, {
5006
5237
  type: "dispatch_ambiguous",
5007
5238
  at: this.now().getTime(),
5008
5239
  safeErrorCode: "DISPATCH_OUTCOME_AMBIGUOUS",
@@ -5011,16 +5242,8 @@ export class GatewayService {
5011
5242
  this.scheduleDispatch(this.bindingAliases.get(bindingKey(binding)) ?? targetAlias);
5012
5243
  return;
5013
5244
  }
5014
- await this.drainDeliveryCallbacksForMessageLocked(item.messageId);
5015
- if (await this.processLifecycleLocked()) {
5016
- await this.changed();
5017
- }
5018
- if (!this.messageContexts.has(item.messageId)) {
5019
- this.scheduleDispatch(this.bindingAliases.get(bindingKey(binding)) ?? targetAlias);
5020
- return;
5021
- }
5245
+ const result = outcome.result;
5022
5246
  if (result.state === "deferred") {
5023
- const currentTargetAlias = this.bindingAliases.get(bindingKey(binding)) ?? targetAlias;
5024
5247
  const now = this.now().getTime();
5025
5248
  const deferredSafeErrorCode = safeCode(result.safeErrorCode, "PROVIDER_DISPATCH_DEFERRED");
5026
5249
  const retryDelayMs = binding.provider === "claude"
@@ -5032,14 +5255,14 @@ export class GatewayService {
5032
5255
  const retryAt = binding.provider === "claude"
5033
5256
  ? Math.min(now + retryDelayMs, tracker.deadlineAt - 1)
5034
5257
  : now + retryDelayMs;
5035
- await this.advanceDeliveryLocked(item.messageId, {
5258
+ await this.advanceDeliveryLocked(messageId, {
5036
5259
  type: "dispatch_clean_prewrite_failed",
5037
5260
  at: now,
5038
5261
  retryAt,
5039
5262
  safeErrorCode: deferredSafeErrorCode,
5040
5263
  });
5041
5264
  tracker.deliverySafeErrorCode = deferredSafeErrorCode;
5042
- const postDeferred = this.deliveryTrackers.get(item.messageId)?.machine;
5265
+ const postDeferred = this.deliveryTrackers.get(messageId)?.machine;
5043
5266
  if (postDeferred === undefined ||
5044
5267
  isTerminalDeliveryMachine(postDeferred)) {
5045
5268
  await this.changed();
@@ -5050,32 +5273,33 @@ export class GatewayService {
5050
5273
  // evidence. Only the reducer's explicit queued retry phase may return
5051
5274
  // the body to the store queue.
5052
5275
  if (postDeferred.phase !== "queued") {
5053
- await this.advanceDeliveryLocked(item.messageId, {
5276
+ await this.advanceDeliveryLocked(messageId, {
5054
5277
  type: "await_terminal",
5055
5278
  at: this.now().getTime(),
5056
5279
  });
5057
5280
  await this.changed();
5058
5281
  return;
5059
5282
  }
5060
- const requeued = await this.store.requeueInFlightMessage(item.messageId, item.body, deferredSafeErrorCode);
5061
- if (this.activeDispatchByTarget.get(currentTargetAlias) === item.messageId) {
5283
+ const requeued = await this.store.requeueInFlightMessage(messageId, body, deferredSafeErrorCode);
5284
+ if (this.activeDispatchByTarget.get(currentTargetAlias) === messageId) {
5062
5285
  this.activeDispatchByTarget.delete(currentTargetAlias);
5063
5286
  }
5064
5287
  if (requeued.status === "requeued") {
5065
- if (context.authorization === "selected_route") {
5288
+ if (context.authorization === "selected_route" &&
5289
+ authorityStillCurrent) {
5066
5290
  if (binding.provider === "codex" && !steeringWhileBusy) {
5067
5291
  this.routeStates.set(currentTargetAlias, "idle");
5068
5292
  await this.store.observeRoute({
5069
5293
  binding,
5070
5294
  state: "idle",
5071
- compatibility: "compatible",
5072
5295
  });
5073
5296
  }
5074
5297
  else if (binding.provider === "codex") {
5075
5298
  this.scheduleHeldRedispatch(currentTargetAlias);
5076
5299
  }
5077
- await this.setNativeCodexStatus(currentTargetAlias, "waiting");
5300
+ await this.setNativeSourceStatus(currentTargetAlias, "waiting");
5078
5301
  }
5302
+ await this.publishNativeHeldLocked(tracker);
5079
5303
  }
5080
5304
  else if (requeued.status === "settled") {
5081
5305
  await this.applyTerminalSettlementLocked(requeued.settlement);
@@ -5083,20 +5307,30 @@ export class GatewayService {
5083
5307
  await this.changed();
5084
5308
  return;
5085
5309
  }
5310
+ if (!authorityStillCurrent) {
5311
+ await this.advanceDeliveryLocked(messageId, {
5312
+ type: "dispatch_ambiguous",
5313
+ at: this.now().getTime(),
5314
+ safeErrorCode: "DISPATCH_OUTCOME_AMBIGUOUS",
5315
+ });
5316
+ await this.changed();
5317
+ this.scheduleDispatch(currentTargetAlias);
5318
+ return;
5319
+ }
5086
5320
  if (result.state === "pending" || result.state === "accepted") {
5087
5321
  if (result.state === "accepted" &&
5088
- (await this.acceptProviderDeliveryLocked(item.messageId))) {
5322
+ (await this.acceptProviderDeliveryLocked(messageId))) {
5089
5323
  await this.changed();
5090
5324
  }
5091
5325
  else if (result.state === "pending") {
5092
- await this.advanceDeliveryLocked(item.messageId, {
5326
+ await this.advanceDeliveryLocked(messageId, {
5093
5327
  type: "await_terminal",
5094
5328
  at: this.now().getTime(),
5095
5329
  });
5096
5330
  }
5097
5331
  }
5098
5332
  else {
5099
- await this.finishDelivery({ messageId: item.messageId, state: result.state, ...(result.safeErrorCode === undefined ? {} : { safeErrorCode: result.safeErrorCode }), ...(result.replyText === undefined ? {} : { replyText: result.replyText }) });
5333
+ await this.finishDelivery({ messageId, state: result.state, ...(result.safeErrorCode === undefined ? {} : { safeErrorCode: result.safeErrorCode }), ...(result.replyText === undefined ? {} : { replyText: result.replyText }) });
5100
5334
  }
5101
5335
  this.scheduleDispatch(this.bindingAliases.get(bindingKey(binding)) ?? targetAlias);
5102
5336
  }
@@ -5110,27 +5344,100 @@ export class GatewayService {
5110
5344
  return;
5111
5345
  this.scheduledDispatchTargets.add(targetAlias);
5112
5346
  setImmediate(() => {
5113
- this.mutex
5114
- .run("service", async () => {
5347
+ void this.runDispatch(targetAlias).catch(() => {
5348
+ this.dashboardHealthy = false;
5349
+ });
5350
+ });
5351
+ }
5352
+ async runDispatch(targetAlias) {
5353
+ let runnerStarted = false;
5354
+ let initialScheduleConsumed = false;
5355
+ let preparedMessageId;
5356
+ let commitCompleted = false;
5357
+ try {
5358
+ const prepared = await this.mutex.run("service", async () => {
5115
5359
  this.scheduledDispatchTargets.delete(targetAlias);
5360
+ initialScheduleConsumed = true;
5116
5361
  if (this.closing ||
5117
5362
  this.codexSuccessionDispatchFrozen.has(targetAlias) ||
5118
5363
  this.codexSuccessionPoisoned.has(targetAlias)) {
5119
5364
  return;
5120
5365
  }
5121
5366
  this.dispatchRunnerTargets.add(targetAlias);
5122
- try {
5123
- await this.dispatchOne(targetAlias);
5124
- }
5125
- finally {
5367
+ runnerStarted = true;
5368
+ const candidate = await this.prepareDispatchLocked(targetAlias);
5369
+ if (candidate === undefined) {
5126
5370
  this.dispatchRunnerTargets.delete(targetAlias);
5127
5371
  this.scheduleLifecycleWakeLocked();
5372
+ runnerStarted = false;
5128
5373
  }
5129
- })
5130
- .catch(() => {
5131
- this.dashboardHealthy = false;
5374
+ else {
5375
+ preparedMessageId = candidate.messageId;
5376
+ this.providerDispatchInFlightMessageIds.add(candidate.messageId);
5377
+ }
5378
+ return candidate;
5132
5379
  });
5133
- });
5380
+ if (prepared === undefined)
5381
+ return;
5382
+ let outcome;
5383
+ try {
5384
+ const adapter = this.adapter(prepared.binding.provider, prepared.binding.hostId);
5385
+ outcome = {
5386
+ kind: "result",
5387
+ result: await this.awaitDispatchWithNativeHeld(prepared.messageId, adapter.dispatch(prepared.input)),
5388
+ };
5389
+ }
5390
+ catch {
5391
+ outcome = { kind: "error" };
5392
+ }
5393
+ await this.mutex.run("service", async () => {
5394
+ await this.commitDispatchLocked(prepared, outcome);
5395
+ commitCompleted = true;
5396
+ this.providerDispatchInFlightMessageIds.delete(prepared.messageId);
5397
+ preparedMessageId = undefined;
5398
+ this.dispatchRunnerTargets.delete(targetAlias);
5399
+ runnerStarted = false;
5400
+ this.scheduleLifecycleWakeLocked();
5401
+ });
5402
+ }
5403
+ finally {
5404
+ await this.mutex.run("service", async () => {
5405
+ if (preparedMessageId !== undefined) {
5406
+ let releaseUncertaintyMarker = commitCompleted;
5407
+ if (!commitCompleted) {
5408
+ const tracker = this.deliveryTrackers.get(preparedMessageId);
5409
+ if (tracker !== undefined &&
5410
+ !isTerminalDeliveryMachine(tracker.machine)) {
5411
+ try {
5412
+ await this.advanceDeliveryLocked(preparedMessageId, {
5413
+ type: "dispatch_ambiguous",
5414
+ at: this.now().getTime(),
5415
+ safeErrorCode: "DISPATCH_OUTCOME_AMBIGUOUS",
5416
+ });
5417
+ await this.changed();
5418
+ releaseUncertaintyMarker = true;
5419
+ }
5420
+ catch {
5421
+ this.dashboardHealthy = false;
5422
+ }
5423
+ }
5424
+ else {
5425
+ releaseUncertaintyMarker = true;
5426
+ }
5427
+ }
5428
+ if (releaseUncertaintyMarker) {
5429
+ this.providerDispatchInFlightMessageIds.delete(preparedMessageId);
5430
+ }
5431
+ }
5432
+ if (runnerStarted) {
5433
+ this.dispatchRunnerTargets.delete(targetAlias);
5434
+ }
5435
+ if (!initialScheduleConsumed) {
5436
+ this.scheduledDispatchTargets.delete(targetAlias);
5437
+ }
5438
+ this.scheduleLifecycleWakeLocked();
5439
+ });
5440
+ }
5134
5441
  }
5135
5442
  scheduleHeldRedispatch(targetAlias) {
5136
5443
  if (this.closing || this.heldRedispatchTimers.has(targetAlias))
@@ -5285,13 +5592,12 @@ export class GatewayService {
5285
5592
  .then(() => true)
5286
5593
  .catch(() => false);
5287
5594
  const pairStillActive = conversation.pair !== true ||
5288
- (await this.store.hasPair({
5289
- claudeAlias: conversation.targetAlias,
5290
- codexAlias: conversation.sourceAlias,
5595
+ (await this.store.hasConsentEdge({
5596
+ aliases: [conversation.targetAlias, conversation.sourceAlias],
5291
5597
  }));
5292
5598
  if ((!sourceStillRoutable || !pairStillActive) &&
5293
- sourceBinding?.provider === "claude") {
5294
- await this.enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, replyText);
5599
+ sourceBinding?.provider === "claude" && context.sourceBinding !== undefined) {
5600
+ await this.enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, context.sourceBinding, replyText);
5295
5601
  }
5296
5602
  else {
5297
5603
  await this.enqueue(conversation.targetAlias, conversation.sourceAlias, replyText, false, {
@@ -5384,7 +5690,7 @@ export class GatewayService {
5384
5690
  };
5385
5691
  const collision = this.routeBindings.get(event.sourceAlias);
5386
5692
  if (target === undefined ||
5387
- target.provider !== "codex" ||
5693
+ target.provider === "claude" ||
5388
5694
  target.hostId !== event.endpoint.hostId ||
5389
5695
  (collision !== undefined &&
5390
5696
  bindingKey(collision) !== bindingKey(sourceBinding))) {
@@ -5426,6 +5732,7 @@ export class GatewayService {
5426
5732
  expectsReply: !steer,
5427
5733
  sequence: 0,
5428
5734
  targetBindingKey: bindingKey(target),
5735
+ sourceBinding: { ...sourceBinding },
5429
5736
  authorization: "selected_route",
5430
5737
  targetAlias: event.targetAlias,
5431
5738
  deadlineAt,
@@ -5451,13 +5758,17 @@ export class GatewayService {
5451
5758
  deadlineAt,
5452
5759
  });
5453
5760
  await this.changed();
5454
- await this.setNativeCodexStatus(event.targetAlias, "waiting");
5455
- // Native ingress gets one immediate dispatch opportunity. Only an
5456
- // actual queued/waiting result earns a `held` acknowledgement; an idle
5457
- // fast path can therefore settle with one terminal acknowledgement.
5458
- await this.dispatchOne(event.targetAlias);
5761
+ await this.setNativeSourceStatus(event.targetAlias, "waiting");
5762
+ // Provider I/O runs outside the service mutex. The lifecycle timer emits
5763
+ // `held` only if this exact body remains nonterminal at its prompt
5764
+ // boundary; a fast provider result therefore still produces one
5765
+ // terminal acknowledgement and never freezes control-plane callbacks.
5766
+ this.scheduleDispatch(event.targetAlias);
5459
5767
  const tracker = this.deliveryTrackers.get(queued.messageId);
5460
- if (tracker?.machine.phase === "queued") {
5768
+ const routeState = this.routeStates.get(event.targetAlias);
5769
+ if (tracker?.machine.phase === "queued" &&
5770
+ routeState !== "idle" &&
5771
+ (!steer || routeState !== "busy")) {
5461
5772
  await this.publishNativeHeldLocked(tracker);
5462
5773
  }
5463
5774
  }
@@ -5521,7 +5832,7 @@ export class GatewayService {
5521
5832
  return activationDeferred;
5522
5833
  }
5523
5834
  finally {
5524
- if (!succeeded && event.outcome === "compatible") {
5835
+ if (!succeeded) {
5525
5836
  adapter.releaseEndpointRefreshSelectorClaim?.(event.current.endpointGeneration);
5526
5837
  }
5527
5838
  if (ownsActiveTransition &&
@@ -5540,56 +5851,29 @@ export class GatewayService {
5540
5851
  }
5541
5852
  }
5542
5853
  async applyCodexEndpointRefresh(adapter, event, transitionKey, options) {
5854
+ const routeHandles = Array.isArray(event.routes)
5855
+ ? event.routes.map((route) => route.routeHandle)
5856
+ : [];
5543
5857
  if (this.closing ||
5544
- (event.outcome !== "compatible" && event.outcome !== "incompatible") ||
5545
- !isCompatibilityAttestation(event.attestation) ||
5546
- event.attestation.surface !== "codex" ||
5547
5858
  event.previous.provider !== "codex" ||
5548
5859
  !PRIVATE_HANDLE.test(event.previous.endpointGeneration) ||
5549
5860
  adapter.identity.provider !== "codex" ||
5550
5861
  adapter.identity.hostId !== event.previous.hostId ||
5551
- !this.config.allowedHosts.includes(event.previous.hostId)) {
5862
+ !this.config.allowedHosts.includes(event.previous.hostId) ||
5863
+ event.current.provider !== "codex" ||
5864
+ event.current.hostId !== event.previous.hostId ||
5865
+ !PRIVATE_HANDLE.test(event.current.endpointGeneration) ||
5866
+ event.current.endpointGeneration === event.previous.endpointGeneration ||
5867
+ adapter.identity.endpointGeneration !== event.current.endpointGeneration ||
5868
+ !Array.isArray(event.routes) ||
5869
+ event.routes.length > this.config.limits.maxRoutes ||
5870
+ new Set(routeHandles).size !== routeHandles.length ||
5871
+ event.routes.some((route) => !PRIVATE_HANDLE.test(route.routeHandle) ||
5872
+ !["idle", "busy", "awaiting_approval"].includes(route.state))) {
5552
5873
  throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "The Codex endpoint transition failed closed runtime validation.");
5553
5874
  }
5554
- if (event.outcome === "compatible") {
5555
- const routeHandles = Array.isArray(event.routes)
5556
- ? event.routes.map((route) => route.routeHandle)
5557
- : [];
5558
- if (event.current.provider !== "codex" ||
5559
- event.current.hostId !== event.previous.hostId ||
5560
- !PRIVATE_HANDLE.test(event.current.endpointGeneration) ||
5561
- event.current.endpointGeneration ===
5562
- event.previous.endpointGeneration ||
5563
- adapter.identity.endpointGeneration !==
5564
- event.current.endpointGeneration ||
5565
- event.attestation.version !== adapter.protocolVersion ||
5566
- event.attestation.tier === "incompatible" ||
5567
- !Array.isArray(event.routes) ||
5568
- event.routes.length > this.config.limits.maxRoutes ||
5569
- new Set(routeHandles).size !== routeHandles.length ||
5570
- event.routes.some((route) => !PRIVATE_HANDLE.test(route.routeHandle) ||
5571
- !["idle", "busy", "awaiting_approval"].includes(route.state))) {
5572
- throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "The refreshed Codex provider identity changed before activation.");
5573
- }
5574
- if (this.activatedEndpointRefreshes.get(event.current.hostId) ===
5575
- transitionKey) {
5576
- return false;
5577
- }
5578
- }
5579
- else {
5580
- if (event.candidate.provider !== "codex" ||
5581
- event.candidate.hostId !== event.previous.hostId ||
5582
- !PRIVATE_HANDLE.test(event.candidate.endpointGeneration) ||
5583
- event.candidate.endpointGeneration ===
5584
- event.previous.endpointGeneration ||
5585
- adapter.identity.endpointGeneration !==
5586
- event.previous.endpointGeneration ||
5587
- event.attestation.tier !== "incompatible") {
5588
- throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "The incompatible Codex candidate no longer matches the active endpoint.");
5589
- }
5590
- if (this.handledIncompatibleEndpointRefreshes.get(event.previous.hostId) === transitionKey) {
5591
- return false;
5592
- }
5875
+ if (this.activatedEndpointRefreshes.get(event.current.hostId) === transitionKey) {
5876
+ return false;
5593
5877
  }
5594
5878
  let inventory = await this.store.inspectPrivateCodexRoutes();
5595
5879
  const previousRoutes = inventory.filter((route) => route.binding.hostId === event.previous.hostId &&
@@ -5607,11 +5891,10 @@ export class GatewayService {
5607
5891
  settlements = await this.store.markConnectorOffline(event.previous, "ENDPOINT_GENERATION_CHANGED", inFlightSettlements, { preserveQueued: true });
5608
5892
  }
5609
5893
  catch (error) {
5610
- const alreadyInvalidatedCompatibleTransition = event.outcome === "compatible" &&
5611
- error instanceof BridgeError &&
5894
+ const alreadyInvalidatedTransition = error instanceof BridgeError &&
5612
5895
  error.code === "CONNECTOR_NOT_FOUND" &&
5613
- previousRoutes.every((route) => route.state === "stale" && route.compatibility === "expired");
5614
- if (!alreadyInvalidatedCompatibleTransition)
5896
+ previousRoutes.every((route) => route.state === "stale");
5897
+ if (!alreadyInvalidatedTransition)
5615
5898
  throw error;
5616
5899
  }
5617
5900
  for (const settlement of settlements) {
@@ -5620,50 +5903,13 @@ export class GatewayService {
5620
5903
  for (const route of previousRoutes) {
5621
5904
  this.routeStates.set(route.alias, "stale");
5622
5905
  }
5623
- await this.store.recordCompatibilityAttestation(event.attestation);
5624
- if (event.outcome === "incompatible") {
5625
- await this.requireEndpointRefreshUnconflicted();
5626
- this.activeCodexIncompatibility = structuredClone(event.attestation);
5627
- this.incompatibleProviderSurfaces.set(this.providerSurfaceKey("codex", event.previous.hostId), event.attestation.safeErrorCode ?? "CODEX_ENDPOINT_INCOMPATIBLE");
5628
- this.addRuntimeAlert(event.attestation.safeErrorCode ?? "CODEX_ENDPOINT_INCOMPATIBLE", "error", { provider: "codex", host: event.previous.hostId });
5629
- this.handledIncompatibleEndpointRefreshes.set(event.previous.hostId, transitionKey);
5630
- await this.changed();
5631
- return false;
5632
- }
5633
- if (event.attestation.tier === "schema_attested" &&
5634
- !(compatibilityCoversWrites(event.attestation) &&
5635
- adapter.latestWriteCompatibilityProbeObservation?.(event.current.endpointGeneration)?.outcome === "pass")) {
5636
- // A passing untested-version schema is useful observation, but it is not
5637
- // durable route or write authority. Settle the provider's pending probe
5638
- // so a later generation can be examined, while leaving every G1 route
5639
- // stale and refusing selector/registration activation.
5640
- await this.requireEndpointRefreshUnconflicted();
5641
- await this.store.observeConnector({
5642
- identity: event.current,
5643
- health: "degraded",
5644
- compatibility: "unknown",
5645
- protocol: adapter.protocol,
5646
- protocolVersion: adapter.protocolVersion,
5647
- safeErrorCode: "CODEX_MONITOR_ONLY",
5648
- });
5649
- adapter.acceptCompatibilityAttestation(event.attestation);
5650
- this.activeCodexIncompatibility = undefined;
5651
- this.incompatibleProviderSurfaces.set(this.providerSurfaceKey("codex", event.current.hostId), "CODEX_MONITOR_ONLY");
5652
- this.handledIncompatibleEndpointRefreshes.delete(event.current.hostId);
5653
- await this.changed();
5654
- if (options.deferActivation === true ||
5655
- options.requiredRoute !== undefined) {
5656
- throw new BridgeError("CODEX_MONITOR_ONLY", "This Codex App Server passed read-only schema probes but cannot authorize a task route or message write.");
5657
- }
5658
- return false;
5659
- }
5660
5906
  await this.store.observeConnector({
5661
5907
  identity: event.current,
5662
5908
  health: "healthy",
5663
- compatibility: "compatible",
5664
5909
  protocol: adapter.protocol,
5665
5910
  protocolVersion: adapter.protocolVersion,
5666
5911
  });
5912
+ this.recordConnectorFreshness(event.current, "healthy");
5667
5913
  const refreshedByHandle = new Map(event.routes.map((route) => [route.routeHandle, route.state]));
5668
5914
  const pendingReanchors = previousRoutes.flatMap((route) => {
5669
5915
  const state = refreshedByHandle.get(route.binding.routeHandle);
@@ -5715,7 +5961,7 @@ export class GatewayService {
5715
5961
  route.binding.routeHandle === expected.threadId &&
5716
5962
  route.binding.ownerLease === expected.ownerLease &&
5717
5963
  route.enabled &&
5718
- route.compatibility === "compatible");
5964
+ route.state !== "stale");
5719
5965
  return matches.length === 1;
5720
5966
  });
5721
5967
  if (committedExactly) {
@@ -5748,8 +5994,7 @@ export class GatewayService {
5748
5994
  route.binding.hostId !== event.current.hostId ||
5749
5995
  route.binding.endpointGeneration !==
5750
5996
  event.current.endpointGeneration ||
5751
- route.state !== "stale" ||
5752
- route.compatibility !== "expired") {
5997
+ route.state !== "stale") {
5753
5998
  continue;
5754
5999
  }
5755
6000
  await this.registerOrRebind(route.alias, route.binding, "endpoint_reobserved", state, route.binding.ownerLease);
@@ -5764,7 +6009,7 @@ export class GatewayService {
5764
6009
  event.current.endpointGeneration &&
5765
6010
  route.binding.routeHandle === observed.routeHandle &&
5766
6011
  route.enabled &&
5767
- route.compatibility === "compatible");
6012
+ route.state !== "stale");
5768
6013
  if (matches.length > 1) {
5769
6014
  throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "A refreshed Codex task has multiple durable owners.");
5770
6015
  }
@@ -5804,7 +6049,7 @@ export class GatewayService {
5804
6049
  event.current.endpointGeneration &&
5805
6050
  route.binding.routeHandle === requiredRoute.threadId &&
5806
6051
  route.enabled &&
5807
- route.compatibility === "compatible");
6052
+ route.state !== "stale");
5808
6053
  const match = matches[0];
5809
6054
  if (matches.length !== 1 || match === undefined) {
5810
6055
  throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "The registering Codex task was not durably installed exactly.");
@@ -5858,7 +6103,7 @@ export class GatewayService {
5858
6103
  }
5859
6104
  for (const route of activatedRoutes) {
5860
6105
  requireNativeContinuity(route);
5861
- await this.setNativeCodexStatus(route.alias, route.state === "idle"
6106
+ await this.setNativeSourceStatus(route.alias, route.state === "idle"
5862
6107
  ? "idle"
5863
6108
  : route.state === "awaiting_approval"
5864
6109
  ? "waiting"
@@ -5898,10 +6143,7 @@ export class GatewayService {
5898
6143
  for (const route of activatedRoutes) {
5899
6144
  requireNativeContinuity(route);
5900
6145
  }
5901
- // Provider writes remain closed until every durable and native transition
5902
- // above is complete. This acknowledgement is deliberately the final
5903
- // fallible activation step.
5904
- adapter.acceptCompatibilityAttestation(event.attestation);
6146
+ adapter.activateEndpointGeneration(event.current.endpointGeneration);
5905
6147
  }
5906
6148
  catch (error) {
5907
6149
  try {
@@ -5921,9 +6163,6 @@ export class GatewayService {
5921
6163
  ?.transitionKey === transitionKey) {
5922
6164
  this.pendingSelectorEndpointActivations.delete(event.current.hostId);
5923
6165
  }
5924
- this.handledIncompatibleEndpointRefreshes.delete(event.current.hostId);
5925
- this.activeCodexIncompatibility = undefined;
5926
- this.incompatibleProviderSurfaces.delete(this.providerSurfaceKey("codex", event.current.hostId));
5927
6166
  return false;
5928
6167
  }
5929
6168
  async onRouteState(source, event) {
@@ -5945,7 +6184,6 @@ export class GatewayService {
5945
6184
  await this.store.observeConnector({
5946
6185
  identity: source,
5947
6186
  health: event.state === "stale" ? "degraded" : "healthy",
5948
- compatibility: event.state === "stale" ? "expired" : "compatible",
5949
6187
  protocol: adapter.protocol,
5950
6188
  protocolVersion: adapter.protocolVersion,
5951
6189
  ...(event.safeErrorCode === undefined
@@ -5954,6 +6192,7 @@ export class GatewayService {
5954
6192
  safeErrorCode: safeCode(event.safeErrorCode, "CONNECTOR_DEGRADED"),
5955
6193
  }),
5956
6194
  });
6195
+ this.recordConnectorFreshness(source, event.state === "stale" ? "degraded" : "healthy");
5957
6196
  if (event.state === "stale") {
5958
6197
  if (previousState !== "stale") {
5959
6198
  this.addRuntimeAlert(staleSafeCode, "error", { provider: binding.provider, host: binding.hostId, alias });
@@ -5961,7 +6200,6 @@ export class GatewayService {
5961
6200
  await this.store.observeRoute({
5962
6201
  binding,
5963
6202
  state: "stale",
5964
- compatibility: "expired",
5965
6203
  safeErrorCode: staleSafeCode,
5966
6204
  });
5967
6205
  }
@@ -5969,7 +6207,6 @@ export class GatewayService {
5969
6207
  await this.store.observeRoute({
5970
6208
  binding,
5971
6209
  state: event.state,
5972
- compatibility: "compatible",
5973
6210
  ...(event.safeErrorCode === undefined
5974
6211
  ? {}
5975
6212
  : {
@@ -5979,15 +6216,15 @@ export class GatewayService {
5979
6216
  }
5980
6217
  await this.store.touchProgressWatchesForAlias(alias);
5981
6218
  await this.changed();
5982
- if (binding.provider === "codex") {
5983
- await this.setNativeCodexStatus(alias, event.state === "idle"
6219
+ if (binding.provider !== "claude") {
6220
+ await this.setNativeSourceStatus(alias, event.state === "idle"
5984
6221
  ? "idle"
5985
6222
  : event.state === "awaiting_approval"
5986
6223
  ? "waiting"
5987
6224
  : "busy");
5988
- if (previousState === "stale" && event.state !== "stale") {
5989
- adapter.rearmEndpointRefreshActivation?.(binding.endpointGeneration);
5990
- }
6225
+ }
6226
+ if (binding.provider === "codex" && previousState === "stale" && event.state !== "stale") {
6227
+ adapter.rearmEndpointRefreshActivation?.(binding.endpointGeneration);
5991
6228
  }
5992
6229
  if (event.state === "idle" || event.state === "stale") {
5993
6230
  const heldTimer = this.heldRedispatchTimers.get(alias);
@@ -5998,12 +6235,12 @@ export class GatewayService {
5998
6235
  this.scheduleDispatch(alias);
5999
6236
  }
6000
6237
  }
6001
- async setNativeCodexStatus(alias, status, requireSuccess = false) {
6238
+ async setNativeSourceStatus(alias, status, requireSuccess = false) {
6002
6239
  const binding = this.routeBindings.get(alias);
6003
- if (binding?.provider !== "codex")
6240
+ if (binding === undefined || binding.provider === "claude")
6004
6241
  return;
6005
6242
  const adapter = this.adapter("claude", binding.hostId);
6006
- const update = adapter.updateNativeCodexPeerStatus;
6243
+ const update = adapter.updateNativeSourcePeerStatus;
6007
6244
  if (update === undefined) {
6008
6245
  if (requireSuccess) {
6009
6246
  throw new BridgeError("CODEX_ENDPOINT_REFRESH_NATIVE_STATUS_FAILED", "The native listener does not support refreshed Codex status activation.", true);
@@ -6102,30 +6339,6 @@ export class GatewayService {
6102
6339
  }
6103
6340
  async publicSnapshotLocked() {
6104
6341
  const base = await this.store.publicSnapshot();
6105
- const compatibilityChecks = [];
6106
- for (const { surface } of this.compatibilitySurfaceDefinitions) {
6107
- if (surface === "codex" && this.activeCodexIncompatibility !== undefined) {
6108
- compatibilityChecks.push(structuredClone(this.activeCodexIncompatibility));
6109
- continue;
6110
- }
6111
- const currentVersions = [
6112
- ...new Set([...this.adapters, ...this.compatibilityObservers].flatMap((observer) => {
6113
- const observation = observer.compatibilitySurface?.();
6114
- return observation?.surface === surface ? [observation.version] : [];
6115
- })),
6116
- ].sort((left, right) => left.localeCompare(right));
6117
- const currentAttestations = (await Promise.all(currentVersions.map(async (version) => await this.store.inspectCompatibilityAttestation(surface, version))))
6118
- .filter((attestation) => attestation !== undefined)
6119
- .sort((left, right) => {
6120
- const checkedAt = right.checkedAt.localeCompare(left.checkedAt);
6121
- if (checkedAt !== 0)
6122
- return checkedAt;
6123
- return left.version.localeCompare(right.version);
6124
- });
6125
- const selected = currentAttestations[0];
6126
- if (selected !== undefined)
6127
- compatibilityChecks.push(selected);
6128
- }
6129
6342
  const peers = this.availablePeers.map((peer) => ({ ...peer }));
6130
6343
  if (!arePublicAvailablePeerSnapshots(peers)) {
6131
6344
  throw new BridgeError("INVALID_TRANSIENT_INVENTORY", "The transient peer inventory is unsafe.");
@@ -6154,11 +6367,6 @@ export class GatewayService {
6154
6367
  return projectGatewayPublicSnapshot({
6155
6368
  ...base,
6156
6369
  connectors: this.connectorsWithRegistry(base.connectors),
6157
- ...(compatibilityChecks.length === 0
6158
- ? {}
6159
- : {
6160
- compatibilityChecks: compatibilityChecks.map(projectPublicCompatibilityCheck),
6161
- }),
6162
6370
  availablePeers: peers,
6163
6371
  activityEvents: this.activityEvents.map((event) => ({
6164
6372
  ...event,