agent-embassy 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +9 -9
- package/README.zh-CN.md +9 -9
- package/SECURITY.md +34 -77
- package/dist/src/gateway/acp-client.d.ts +117 -0
- package/dist/src/gateway/acp-client.js +382 -0
- package/dist/src/gateway/acp-client.js.map +1 -0
- package/dist/src/gateway/acp-provider.d.ts +65 -0
- package/dist/src/gateway/acp-provider.js +163 -0
- package/dist/src/gateway/acp-provider.js.map +1 -0
- package/dist/src/gateway/claude-helper-protocol.d.ts +3 -1
- package/dist/src/gateway/claude-helper-protocol.js +15 -5
- package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
- package/dist/src/gateway/claude-helper-supervisor.d.ts +3 -1
- package/dist/src/gateway/claude-helper-supervisor.js +16 -2
- package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
- package/dist/src/gateway/claude-helper.js +4 -3
- package/dist/src/gateway/claude-helper.js.map +1 -1
- package/dist/src/gateway/claude-peer.d.ts +1 -1
- package/dist/src/gateway/claude-peer.js +11 -13
- package/dist/src/gateway/claude-peer.js.map +1 -1
- package/dist/src/gateway/claude-runtime.d.ts +0 -20
- package/dist/src/gateway/claude-runtime.js +6 -127
- package/dist/src/gateway/claude-runtime.js.map +1 -1
- package/dist/src/gateway/cli-copy.en.d.ts +1 -1
- package/dist/src/gateway/cli-copy.en.js +2 -2
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.js +2 -2
- package/dist/src/gateway/cli.d.ts +1 -1
- package/dist/src/gateway/cli.js +26 -5
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/codex-app-server.d.ts +5 -74
- package/dist/src/gateway/codex-app-server.js +9 -597
- package/dist/src/gateway/codex-app-server.js.map +1 -1
- package/dist/src/gateway/codex-local-transport.d.ts +8 -26
- package/dist/src/gateway/codex-local-transport.js +26 -76
- package/dist/src/gateway/codex-local-transport.js.map +1 -1
- package/dist/src/gateway/compatibility.d.ts +6 -66
- package/dist/src/gateway/compatibility.js +14 -296
- package/dist/src/gateway/compatibility.js.map +1 -1
- package/dist/src/gateway/config.d.ts +9 -0
- package/dist/src/gateway/config.js +6 -2
- package/dist/src/gateway/config.js.map +1 -1
- package/dist/src/gateway/control.d.ts +16 -1
- package/dist/src/gateway/control.js +89 -57
- package/dist/src/gateway/control.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.d.ts +1 -1
- package/dist/src/gateway/dashboard-copy.en.d.ts +14 -44
- package/dist/src/gateway/dashboard-copy.en.js +37 -67
- package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.js +14 -44
- package/dist/src/gateway/dashboard-copy.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +14 -44
- package/dist/src/gateway/dashboard-copy.zh-CN.js +37 -67
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/dashboard-model.d.ts +38 -76
- package/dist/src/gateway/dashboard-model.js +158 -256
- package/dist/src/gateway/dashboard-model.js.map +1 -1
- package/dist/src/gateway/dashboard.d.ts +0 -3
- package/dist/src/gateway/dashboard.js +75 -76
- package/dist/src/gateway/dashboard.js.map +1 -1
- package/dist/src/gateway/deepseek-detect.d.ts +7 -6
- package/dist/src/gateway/deepseek-detect.js +31 -115
- package/dist/src/gateway/deepseek-detect.js.map +1 -1
- package/dist/src/gateway/live-dashboard-app/app.js +166 -621
- package/dist/src/gateway/live-dashboard-command.js +2 -4
- package/dist/src/gateway/live-dashboard-command.js.map +1 -1
- package/dist/src/gateway/live-dashboard-http.d.ts +1 -2
- package/dist/src/gateway/live-dashboard-http.js +14 -15
- package/dist/src/gateway/live-dashboard-http.js.map +1 -1
- package/dist/src/gateway/progress-watch-machine.d.ts +1 -4
- package/dist/src/gateway/progress-watch-machine.js.map +1 -1
- package/dist/src/gateway/provenance-envelope.d.ts +3 -2
- package/dist/src/gateway/provenance-envelope.js +44 -11
- package/dist/src/gateway/provenance-envelope.js.map +1 -1
- package/dist/src/gateway/providers.d.ts +12 -84
- package/dist/src/gateway/providers.js +69 -806
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +8 -9
- package/dist/src/gateway/server.js +51 -99
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +22 -49
- package/dist/src/gateway/service.js +196 -432
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/store.d.ts +20 -29
- package/dist/src/gateway/store.js +281 -827
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +55 -55
- package/dist/src/gateway/types.js +49 -91
- package/dist/src/gateway/types.js.map +1 -1
- package/docs/CONFIGURATION.md +11 -10
- package/docs/CONFIGURATION.zh-CN.md +10 -9
- package/docs/DASHBOARD.md +4 -38
- package/docs/DASHBOARD.zh-CN.md +4 -2
- package/docs/GATEWAY-ARCHITECTURE.md +61 -119
- package/package.json +1 -1
- 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 {
|
|
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,
|
|
11
|
+
import { GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET, gatewayPublicSnapshotLimits, gatewayRegistrationIngressPrefixes, arePublicAvailablePeerSnapshots, directionId, parseDirection, projectGatewayPublicSnapshot, } 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}$/;
|
|
@@ -77,39 +76,13 @@ function bindingKey(binding) {
|
|
|
77
76
|
].join("\0");
|
|
78
77
|
}
|
|
79
78
|
function endpointRefreshCallbackKey(event) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
});
|
|
79
|
+
return JSON.stringify({
|
|
80
|
+
previous: event.previous,
|
|
81
|
+
current: event.current,
|
|
82
|
+
routes: [...event.routes]
|
|
83
|
+
.map((route) => ({ ...route }))
|
|
84
|
+
.sort((left, right) => left.routeHandle.localeCompare(right.routeHandle)),
|
|
85
|
+
});
|
|
113
86
|
}
|
|
114
87
|
function stableLease(provider, value) {
|
|
115
88
|
return `lease_${createHash("sha256").update(provider).update("\0").update(value).digest("base64url")}`;
|
|
@@ -149,8 +122,6 @@ export class GatewayService {
|
|
|
149
122
|
config;
|
|
150
123
|
store;
|
|
151
124
|
adapters;
|
|
152
|
-
compatibilityObservers;
|
|
153
|
-
compatibilitySurfaceDefinitions;
|
|
154
125
|
publishDashboard;
|
|
155
126
|
now;
|
|
156
127
|
timers;
|
|
@@ -176,9 +147,6 @@ export class GatewayService {
|
|
|
176
147
|
deliveryTokens = new Map();
|
|
177
148
|
runtimeAlerts = [];
|
|
178
149
|
activityEvents = [];
|
|
179
|
-
/** Startup probe failures that must preflight cross-provider mutations. */
|
|
180
|
-
incompatibleProviderSurfaces = new Map();
|
|
181
|
-
activeCodexIncompatibility;
|
|
182
150
|
activitySequence = 0;
|
|
183
151
|
detachedReceiptWrites = new Set();
|
|
184
152
|
nativeIngressByConversation = new Map();
|
|
@@ -202,8 +170,6 @@ export class GatewayService {
|
|
|
202
170
|
durablyAppliedEndpointRefreshes = new Map();
|
|
203
171
|
/** Selector transitions staged until their exact registration owns a helper. */
|
|
204
172
|
pendingSelectorEndpointActivations = new Map();
|
|
205
|
-
/** Last incompatible transition already projected per allowed Codex host. */
|
|
206
|
-
handledIncompatibleEndpointRefreshes = new Map();
|
|
207
173
|
/** Process-local dedupe for automatic public activity rows across retries. */
|
|
208
174
|
endpointRefreshActivityKeys = new Set();
|
|
209
175
|
callbackWorker;
|
|
@@ -247,9 +213,6 @@ export class GatewayService {
|
|
|
247
213
|
constructor(options) {
|
|
248
214
|
this.config = options.config;
|
|
249
215
|
this.adapters = [...(options.adapters ?? [])];
|
|
250
|
-
this.compatibilityObservers = [...(options.compatibilityObservers ?? [])];
|
|
251
|
-
this.compatibilitySurfaceDefinitions =
|
|
252
|
-
options.compatibilitySurfaceDefinitions ?? compatibilitySurfaceDefinitions;
|
|
253
216
|
this.publishDashboard = options.publishDashboard ?? publishGatewayDashboard;
|
|
254
217
|
this.now = options.now ?? (() => new Date());
|
|
255
218
|
this.store =
|
|
@@ -297,28 +260,41 @@ export class GatewayService {
|
|
|
297
260
|
}
|
|
298
261
|
seen.add(key);
|
|
299
262
|
}
|
|
300
|
-
if (this.adapters.length > 0) {
|
|
301
|
-
await this.runAutomaticCompatibilityProbesLocked();
|
|
302
|
-
assertStartActive();
|
|
303
|
-
}
|
|
304
263
|
for (const adapter of this.adapters) {
|
|
305
264
|
const observation = await adapter.initialize(this.callbacksFor(adapter));
|
|
306
265
|
assertStartActive();
|
|
266
|
+
this.captureRegistryObservation(adapter, adapter.latestRegistryObservation?.());
|
|
307
267
|
await this.store.observeConnector({
|
|
308
268
|
identity: adapter.identity,
|
|
309
269
|
health: observation.health,
|
|
310
|
-
compatibility: observation.compatibility,
|
|
311
270
|
protocol: adapter.protocol,
|
|
312
271
|
protocolVersion: adapter.protocolVersion,
|
|
313
272
|
...(observation.safeErrorCode === undefined
|
|
314
273
|
? {}
|
|
315
274
|
: { safeErrorCode: safeCode(observation.safeErrorCode, "ADAPTER_DEGRADED") }),
|
|
316
275
|
});
|
|
276
|
+
if (observation.ownedRoute !== undefined) {
|
|
277
|
+
const route = observation.ownedRoute;
|
|
278
|
+
const prefix = gatewayRegistrationIngressPrefixes[adapter.identity.provider];
|
|
279
|
+
if (prefix === undefined || !route.alias.startsWith(prefix) ||
|
|
280
|
+
!route.alias.endsWith(`@${adapter.identity.hostId}`) ||
|
|
281
|
+
!PUBLIC_ALIAS.test(route.alias) || !PRIVATE_HANDLE.test(route.routeHandle) ||
|
|
282
|
+
route.state !== "idle") {
|
|
283
|
+
throw new BridgeError("GATEWAY_OWNED_ROUTE_INVALID", "A config-declared provider route has invalid identity evidence.");
|
|
284
|
+
}
|
|
285
|
+
const binding = {
|
|
286
|
+
...adapter.identity,
|
|
287
|
+
routeHandle: route.routeHandle,
|
|
288
|
+
ownerLease: stableLease(adapter.identity.provider, `${adapter.identity.hostId}\0${route.alias}`),
|
|
289
|
+
};
|
|
290
|
+
await this.registerOrRebind(route.alias, binding, "endpoint_reobserved", route.state, binding.ownerLease, "explicit_opt_in");
|
|
291
|
+
this.rememberBinding(route.alias, binding, route.state);
|
|
292
|
+
bootReactivatedIdleAliases.push(route.alias);
|
|
293
|
+
}
|
|
317
294
|
assertStartActive();
|
|
318
295
|
}
|
|
319
296
|
await this.recoverCodexSuccessionAfterRestartLocked();
|
|
320
|
-
bootReactivatedIdleAliases
|
|
321
|
-
await this.reactivateRetainedCodexRoutesAfterRestartLocked();
|
|
297
|
+
bootReactivatedIdleAliases.push(...(await this.reactivateRetainedCodexRoutesAfterRestartLocked()));
|
|
322
298
|
assertStartActive();
|
|
323
299
|
const control = await startGatewayControlServer({
|
|
324
300
|
stateDir: this.store.rootDir,
|
|
@@ -460,7 +436,6 @@ export class GatewayService {
|
|
|
460
436
|
this.deliveryTrackers.clear();
|
|
461
437
|
this.deliveryTokens.clear();
|
|
462
438
|
this.runtimeAlerts.length = 0;
|
|
463
|
-
this.incompatibleProviderSurfaces.clear();
|
|
464
439
|
this.registryEvidenceByConnector.clear();
|
|
465
440
|
this.registryAdaptersWithParseableRecords.clear();
|
|
466
441
|
this.detachedReceiptWrites.clear();
|
|
@@ -476,7 +451,6 @@ export class GatewayService {
|
|
|
476
451
|
this.activatedEndpointRefreshes.clear();
|
|
477
452
|
this.durablyAppliedEndpointRefreshes.clear();
|
|
478
453
|
this.pendingSelectorEndpointActivations.clear();
|
|
479
|
-
this.handledIncompatibleEndpointRefreshes.clear();
|
|
480
454
|
this.endpointRefreshActivityKeys.clear();
|
|
481
455
|
this.codexOrphanCleanupPending.clear();
|
|
482
456
|
this.candidates.clear();
|
|
@@ -536,12 +510,12 @@ export class GatewayService {
|
|
|
536
510
|
pair: async (params) => await this.exclusiveDecision(async () => this.pairRoutes(params), {
|
|
537
511
|
kind: "pairing",
|
|
538
512
|
action: "routes_paired",
|
|
539
|
-
aliases: [params
|
|
513
|
+
aliases: [...pairParamAliases(params)],
|
|
540
514
|
}),
|
|
541
515
|
unpair: async (params) => await this.exclusiveDecision(async () => this.unpairRoutes(params), {
|
|
542
516
|
kind: "pairing",
|
|
543
517
|
action: "routes_unpaired",
|
|
544
|
-
aliases: [params
|
|
518
|
+
aliases: [...pairParamAliases(params)],
|
|
545
519
|
}),
|
|
546
520
|
listSnapshot: async () => (await this.observeSnapshot()).snapshot,
|
|
547
521
|
observeSnapshot: async () => await this.observeSnapshot(),
|
|
@@ -572,88 +546,6 @@ export class GatewayService {
|
|
|
572
546
|
async snapshot() {
|
|
573
547
|
return (await this.observeSnapshot()).snapshot;
|
|
574
548
|
}
|
|
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
|
-
if (entry.adapter !== undefined) {
|
|
619
|
-
const writeProbe = entry.adapter.latestWriteCompatibilityProbeObservation?.(entry.adapter.identity.endpointGeneration);
|
|
620
|
-
if (writeProbe?.outcome === "fail") {
|
|
621
|
-
this.addRuntimeAlert(writeProbe.safeErrorCode, writeProbe.safeErrorCode === "CODEX_WRITE_PROBE_TOOL_ACTIVITY_OBSERVED" ||
|
|
622
|
-
writeProbe.safeErrorCode === "CODEX_WRITE_PROBE_CLEANUP_UNCONFIRMED"
|
|
623
|
-
? "error"
|
|
624
|
-
: "warning", { provider: "codex", host: entry.adapter.identity.hostId });
|
|
625
|
-
}
|
|
626
|
-
this.captureRegistryObservation(entry.adapter, entry.adapter.latestRegistryObservation?.());
|
|
627
|
-
}
|
|
628
|
-
const attestation = evaluateCompatibilityAttestation({
|
|
629
|
-
surface,
|
|
630
|
-
version: entry.observation.version,
|
|
631
|
-
checkedAt: this.now().toISOString(),
|
|
632
|
-
certifiedVersions: certifiedCompatibilityVersions[surface],
|
|
633
|
-
probes,
|
|
634
|
-
});
|
|
635
|
-
await this.store.recordCompatibilityAttestation(attestation);
|
|
636
|
-
entry.observer.acceptCompatibilityAttestation?.(attestation);
|
|
637
|
-
if (attestation.tier === "incompatible" && entry.adapter !== undefined) {
|
|
638
|
-
const code = attestation.safeErrorCode ?? "COMPATIBILITY_CHECK_FAILED";
|
|
639
|
-
this.incompatibleProviderSurfaces.set(this.providerSurfaceKey(entry.adapter.identity.provider, entry.adapter.identity.hostId), code);
|
|
640
|
-
if (surface === "codex") {
|
|
641
|
-
this.activeCodexIncompatibility = structuredClone(attestation);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
else if (surface === "codex" &&
|
|
645
|
-
attestation.tier === "schema_attested" &&
|
|
646
|
-
entry.adapter !== undefined) {
|
|
647
|
-
// A live but untested App Server schema is useful for observation only.
|
|
648
|
-
// Fence retained boot reactivation before it can trigger endpoint
|
|
649
|
-
// refresh churn; an explicit registration may still re-probe a later,
|
|
650
|
-
// certified generation through the provider's bounded refresh path.
|
|
651
|
-
this.incompatibleProviderSurfaces.set(this.providerSurfaceKey("codex", entry.adapter.identity.hostId), "CODEX_MONITOR_ONLY");
|
|
652
|
-
}
|
|
653
|
-
attestations.push(attestation);
|
|
654
|
-
}
|
|
655
|
-
return attestations;
|
|
656
|
-
}
|
|
657
549
|
captureRegistryObservation(adapter, observation) {
|
|
658
550
|
if (adapter.identity.provider !== "claude" ||
|
|
659
551
|
observation === undefined ||
|
|
@@ -881,50 +773,30 @@ export class GatewayService {
|
|
|
881
773
|
});
|
|
882
774
|
},
|
|
883
775
|
onEndpointRefresh: (event) => {
|
|
776
|
+
if (!Array.isArray(event.routes))
|
|
777
|
+
return;
|
|
778
|
+
const routeHandles = event.routes.map((route) => route.routeHandle);
|
|
884
779
|
if (!this.running ||
|
|
885
780
|
adapter.identity.provider !== "codex" ||
|
|
886
781
|
event.previous.provider !== "codex" ||
|
|
887
782
|
event.previous.hostId !== adapter.identity.hostId ||
|
|
888
|
-
|
|
889
|
-
event.
|
|
890
|
-
|
|
891
|
-
}
|
|
892
|
-
if (event.outcome === "compatible") {
|
|
893
|
-
const routeHandles = event.routes.map((route) => route.routeHandle);
|
|
894
|
-
if (event.current.provider !== "codex" ||
|
|
895
|
-
event.current.hostId !== event.previous.hostId ||
|
|
896
|
-
event.current.endpointGeneration !==
|
|
897
|
-
adapter.identity.endpointGeneration ||
|
|
898
|
-
event.attestation.version !== adapter.protocolVersion ||
|
|
899
|
-
event.attestation.tier === "incompatible" ||
|
|
900
|
-
routeHandles.length > this.config.limits.maxRoutes ||
|
|
901
|
-
new Set(routeHandles).size !== routeHandles.length ||
|
|
902
|
-
routeHandles.some((routeHandle) => !PRIVATE_HANDLE.test(routeHandle))) {
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
else if (event.candidate.provider !== "codex" ||
|
|
907
|
-
event.candidate.hostId !== event.previous.hostId ||
|
|
908
|
-
event.previous.endpointGeneration !==
|
|
783
|
+
event.current.provider !== "codex" ||
|
|
784
|
+
event.current.hostId !== event.previous.hostId ||
|
|
785
|
+
event.current.endpointGeneration !==
|
|
909
786
|
adapter.identity.endpointGeneration ||
|
|
910
|
-
|
|
787
|
+
routeHandles.length > this.config.limits.maxRoutes ||
|
|
788
|
+
new Set(routeHandles).size !== routeHandles.length ||
|
|
789
|
+
routeHandles.some((routeHandle) => !PRIVATE_HANDLE.test(routeHandle))) {
|
|
911
790
|
return;
|
|
912
791
|
}
|
|
913
792
|
this.enqueueCallback({
|
|
914
793
|
type: "endpoint_refresh",
|
|
915
794
|
adapter,
|
|
916
|
-
value:
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
routes: event.routes.map((route) => ({ ...route })),
|
|
922
|
-
}
|
|
923
|
-
: {
|
|
924
|
-
...event,
|
|
925
|
-
previous: { ...event.previous },
|
|
926
|
-
candidate: { ...event.candidate },
|
|
927
|
-
},
|
|
795
|
+
value: {
|
|
796
|
+
previous: { ...event.previous },
|
|
797
|
+
current: { ...event.current },
|
|
798
|
+
routes: event.routes.map((route) => ({ ...route })),
|
|
799
|
+
},
|
|
928
800
|
});
|
|
929
801
|
},
|
|
930
802
|
};
|
|
@@ -1071,7 +943,7 @@ export class GatewayService {
|
|
|
1071
943
|
await this.onDelivery(event.source, event.value, event.receivedAt);
|
|
1072
944
|
}
|
|
1073
945
|
else if (event.type === "route") {
|
|
1074
|
-
if (pendingRefresh
|
|
946
|
+
if (pendingRefresh !== undefined &&
|
|
1075
947
|
event.source.provider === "codex" &&
|
|
1076
948
|
event.source.hostId === pendingRefresh.current.hostId &&
|
|
1077
949
|
event.source.endpointGeneration ===
|
|
@@ -1201,12 +1073,7 @@ export class GatewayService {
|
|
|
1201
1073
|
await this.drainDetachedReceiptWritesLocked();
|
|
1202
1074
|
}
|
|
1203
1075
|
async failClosedEndpointRefreshConflict(boundary) {
|
|
1204
|
-
const identities = [
|
|
1205
|
-
boundary.value.previous,
|
|
1206
|
-
...(boundary.value.outcome === "compatible"
|
|
1207
|
-
? [boundary.value.current]
|
|
1208
|
-
: []),
|
|
1209
|
-
];
|
|
1076
|
+
const identities = [boundary.value.previous, boundary.value.current];
|
|
1210
1077
|
for (const identity of identities) {
|
|
1211
1078
|
const routes = (await this.store.inspectPrivateCodexRoutes()).filter((route) => route.binding.hostId === identity.hostId &&
|
|
1212
1079
|
route.binding.endpointGeneration === identity.endpointGeneration);
|
|
@@ -1308,23 +1175,6 @@ export class GatewayService {
|
|
|
1308
1175
|
throw new BridgeError("PROVIDER_UNAVAILABLE", "The selected provider is unavailable.", true);
|
|
1309
1176
|
return adapter;
|
|
1310
1177
|
}
|
|
1311
|
-
providerSurfaceKey(provider, host) {
|
|
1312
|
-
return `${provider}@${host}`;
|
|
1313
|
-
}
|
|
1314
|
-
incompatibleProviderCode(provider, host) {
|
|
1315
|
-
return this.incompatibleProviderSurfaces.get(this.providerSurfaceKey(provider, host));
|
|
1316
|
-
}
|
|
1317
|
-
/** Reject before either half of a cross-provider mutation can acquire state. */
|
|
1318
|
-
assertCrossProviderMutationCompatible(host, options = { allowCodexRefresh: false }) {
|
|
1319
|
-
const claudeCode = this.incompatibleProviderCode("claude", host);
|
|
1320
|
-
if (claudeCode !== undefined) {
|
|
1321
|
-
throw new BridgeError(claudeCode, "The Claude provider is observation-only until Embassy restarts with a compatible runtime.");
|
|
1322
|
-
}
|
|
1323
|
-
const codexCode = this.incompatibleProviderCode("codex", host);
|
|
1324
|
-
if (codexCode !== undefined && !options.allowCodexRefresh) {
|
|
1325
|
-
throw new BridgeError(codexCode, "The Codex provider is observation-only until a compatible endpoint is re-observed.");
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
1178
|
async assertClaudeWorkspaceDisjoint(adapter, routeHandle) {
|
|
1329
1179
|
if (adapter.identity.provider !== "claude") {
|
|
1330
1180
|
throw new BridgeError("CLAUDE_PROVIDER_MISMATCH", "The selected workspace guard does not belong to Claude.");
|
|
@@ -1382,7 +1232,6 @@ export class GatewayService {
|
|
|
1382
1232
|
for (const route of retained) {
|
|
1383
1233
|
if (!route.enabled ||
|
|
1384
1234
|
route.state !== "stale" ||
|
|
1385
|
-
route.compatibility !== "expired" ||
|
|
1386
1235
|
this.pendingCodexSuccessionRecovery?.journal.new.alias === route.alias ||
|
|
1387
1236
|
this.codexSuccessionPoisoned.has(route.alias)) {
|
|
1388
1237
|
continue;
|
|
@@ -1395,13 +1244,6 @@ export class GatewayService {
|
|
|
1395
1244
|
this.addRuntimeAlert("CODEX_BOOT_REACTIVATION_SKIPPED", "warning", { provider: "codex", host: route.binding.hostId, alias: route.alias });
|
|
1396
1245
|
continue;
|
|
1397
1246
|
}
|
|
1398
|
-
if (this.incompatibleProviderCode("codex", route.binding.hostId) !==
|
|
1399
|
-
undefined ||
|
|
1400
|
-
this.incompatibleProviderCode("claude", route.binding.hostId) !==
|
|
1401
|
-
undefined) {
|
|
1402
|
-
this.addRuntimeAlert("CODEX_BOOT_REACTIVATION_SKIPPED", "warning", { provider: "codex", host: route.binding.hostId, alias: route.alias });
|
|
1403
|
-
continue;
|
|
1404
|
-
}
|
|
1405
1247
|
let routeSelected = false;
|
|
1406
1248
|
let advertiseAttempted = false;
|
|
1407
1249
|
let storeReactivated = false;
|
|
@@ -1439,19 +1281,20 @@ export class GatewayService {
|
|
|
1439
1281
|
storeReactivated =
|
|
1440
1282
|
afterStaging !== undefined &&
|
|
1441
1283
|
afterStaging.enabled &&
|
|
1442
|
-
afterStaging.
|
|
1284
|
+
afterStaging.state !== "stale" &&
|
|
1443
1285
|
bindingKey(afterStaging.binding) === bindingKey(binding);
|
|
1444
1286
|
}
|
|
1445
1287
|
advertiseAttempted = true;
|
|
1446
|
-
const advertise = claudeAdapter.
|
|
1288
|
+
const advertise = claudeAdapter.advertiseNativeSourcePeer;
|
|
1447
1289
|
if (advertise === undefined) {
|
|
1448
1290
|
throw new BridgeError("CODEX_BOOT_REACTIVATION_UNAVAILABLE", "The retained Codex task cannot rebuild its native advertisement.");
|
|
1449
1291
|
}
|
|
1450
1292
|
await advertise.call(claudeAdapter, {
|
|
1451
1293
|
alias: route.alias,
|
|
1294
|
+
sourceProvider: "codex",
|
|
1452
1295
|
cwd: this.nativePeerCwd,
|
|
1453
1296
|
});
|
|
1454
|
-
await claudeAdapter.
|
|
1297
|
+
await claudeAdapter.updateNativeSourcePeerStatus?.(route.alias, selected.state === "idle"
|
|
1455
1298
|
? "idle"
|
|
1456
1299
|
: selected.state === "awaiting_approval"
|
|
1457
1300
|
? "waiting"
|
|
@@ -1495,7 +1338,7 @@ export class GatewayService {
|
|
|
1495
1338
|
let cleanupFailed = false;
|
|
1496
1339
|
if (advertiseAttempted) {
|
|
1497
1340
|
await claudeAdapter
|
|
1498
|
-
.
|
|
1341
|
+
.unadvertiseNativeSourcePeer?.(route.alias)
|
|
1499
1342
|
.catch(() => {
|
|
1500
1343
|
cleanupFailed = true;
|
|
1501
1344
|
});
|
|
@@ -1627,7 +1470,7 @@ export class GatewayService {
|
|
|
1627
1470
|
oldInspection.binding.ownerLease !==
|
|
1628
1471
|
stableLease("codex", `${lock.hostId}\0${lock.threadId}`) ||
|
|
1629
1472
|
!oldInspection.enabled ||
|
|
1630
|
-
oldInspection.
|
|
1473
|
+
oldInspection.state === "stale") {
|
|
1631
1474
|
throw new BridgeError("CODEX_SUCCESSION_OWNER_MISMATCH", "The active Codex registration is no longer exact and compatible.");
|
|
1632
1475
|
}
|
|
1633
1476
|
const claudeAdapter = this.adapter("claude", params.hostId);
|
|
@@ -1936,7 +1779,7 @@ export class GatewayService {
|
|
|
1936
1779
|
this.rememberBinding(effect.registration.alias, execution.newBinding, state);
|
|
1937
1780
|
this.codexRegistrationLocks.delete(this.successionOldRegistration(execution).alias);
|
|
1938
1781
|
this.codexRegistrationLocks.set(effect.registration.alias, Object.freeze({ ...effect.registration }));
|
|
1939
|
-
await claudeAdapter.
|
|
1782
|
+
await claudeAdapter.updateNativeSourcePeerStatus?.(effect.registration.alias, state === "idle"
|
|
1940
1783
|
? "idle"
|
|
1941
1784
|
: state === "awaiting_approval"
|
|
1942
1785
|
? "waiting"
|
|
@@ -2081,8 +1924,8 @@ export class GatewayService {
|
|
|
2081
1924
|
throw new BridgeError("CODEX_SUCCESSION_DRAIN_INCOMPLETE", "Interrupted generations cannot close before pending work and receipts are drained. Restart the gateway with embassy serve.");
|
|
2082
1925
|
}
|
|
2083
1926
|
const results = await Promise.allSettled([
|
|
2084
|
-
claudeAdapter.
|
|
2085
|
-
claudeAdapter.
|
|
1927
|
+
claudeAdapter.unadvertiseNativeSourcePeer?.(execution.oldRegistration.alias),
|
|
1928
|
+
claudeAdapter.unadvertiseNativeSourcePeer?.(execution.newRegistration.alias),
|
|
2086
1929
|
codexAdapter.releaseRoute?.(execution.oldRegistration.threadId),
|
|
2087
1930
|
codexAdapter.releaseRoute?.(execution.newRegistration.threadId),
|
|
2088
1931
|
]);
|
|
@@ -2307,9 +2150,6 @@ export class GatewayService {
|
|
|
2307
2150
|
if (!params.alias.startsWith("codex-")) {
|
|
2308
2151
|
throw new BridgeError("INVALID_CODEX_PEER_ALIAS", "A registered Codex alias must start with codex-.");
|
|
2309
2152
|
}
|
|
2310
|
-
this.assertCrossProviderMutationCompatible(params.hostId, {
|
|
2311
|
-
allowCodexRefresh: params.succeedsAlias === undefined,
|
|
2312
|
-
});
|
|
2313
2153
|
if (this.codexSuccessionPoisoned.has(params.alias) ||
|
|
2314
2154
|
this.codexOrphanCleanupPending.has(params.alias) ||
|
|
2315
2155
|
(params.succeedsAlias !== undefined &&
|
|
@@ -2380,7 +2220,7 @@ export class GatewayService {
|
|
|
2380
2220
|
const afterRefresh = await this.store.inspectPrivateRoute(params.alias);
|
|
2381
2221
|
const alreadyInstalledExactly = afterRefresh !== undefined &&
|
|
2382
2222
|
afterRefresh.enabled &&
|
|
2383
|
-
afterRefresh.
|
|
2223
|
+
afterRefresh.state !== "stale" &&
|
|
2384
2224
|
bindingKey(afterRefresh.binding) === bindingKey(binding);
|
|
2385
2225
|
if (!alreadyInstalledExactly || !deferredEndpointRefresh) {
|
|
2386
2226
|
await this.registerOrRebind(params.alias, binding, "endpoint_reobserved", registered.state);
|
|
@@ -2395,15 +2235,16 @@ export class GatewayService {
|
|
|
2395
2235
|
try {
|
|
2396
2236
|
this.rememberBinding(params.alias, binding, registered.state);
|
|
2397
2237
|
const claudeAdapter = this.adapter("claude", params.hostId);
|
|
2398
|
-
const advertise = claudeAdapter.
|
|
2238
|
+
const advertise = claudeAdapter.advertiseNativeSourcePeer;
|
|
2399
2239
|
if (advertise !== undefined) {
|
|
2400
2240
|
advertiseAttempted = true;
|
|
2401
2241
|
await advertise.call(claudeAdapter, {
|
|
2402
2242
|
alias: params.alias,
|
|
2243
|
+
sourceProvider: "codex",
|
|
2403
2244
|
cwd: this.nativePeerCwd,
|
|
2404
2245
|
});
|
|
2405
2246
|
}
|
|
2406
|
-
await claudeAdapter.
|
|
2247
|
+
await claudeAdapter.updateNativeSourcePeerStatus?.(params.alias, registered.state === "idle"
|
|
2407
2248
|
? "idle"
|
|
2408
2249
|
: registered.state === "awaiting_approval"
|
|
2409
2250
|
? "waiting"
|
|
@@ -2425,8 +2266,7 @@ export class GatewayService {
|
|
|
2425
2266
|
}
|
|
2426
2267
|
catch (error) {
|
|
2427
2268
|
if (routeBeforeRegistration !== undefined) {
|
|
2428
|
-
if (deferredEndpointRefresh &&
|
|
2429
|
-
registered.endpointRefresh?.outcome === "compatible") {
|
|
2269
|
+
if (deferredEndpointRefresh && registered.endpointRefresh !== undefined) {
|
|
2430
2270
|
try {
|
|
2431
2271
|
await this.store.markConnectorOffline(registered.endpointRefresh.current, "CODEX_ENDPOINT_ACTIVATION_FAILED", [], { preserveQueued: true });
|
|
2432
2272
|
this.routeStates.set(params.alias, "stale");
|
|
@@ -2508,7 +2348,7 @@ export class GatewayService {
|
|
|
2508
2348
|
});
|
|
2509
2349
|
if (advertiseAttempted) {
|
|
2510
2350
|
await this.adapter("claude", params.hostId)
|
|
2511
|
-
.
|
|
2351
|
+
.unadvertiseNativeSourcePeer?.(params.alias)
|
|
2512
2352
|
.catch(() => {
|
|
2513
2353
|
cleanupFailed = true;
|
|
2514
2354
|
});
|
|
@@ -2535,7 +2375,7 @@ export class GatewayService {
|
|
|
2535
2375
|
}
|
|
2536
2376
|
this.forgetBinding(params.alias);
|
|
2537
2377
|
await this.adapter("claude", host)
|
|
2538
|
-
.
|
|
2378
|
+
.unadvertiseNativeSourcePeer?.(params.alias)
|
|
2539
2379
|
.catch(() => {
|
|
2540
2380
|
this.dashboardHealthy = false;
|
|
2541
2381
|
});
|
|
@@ -2706,7 +2546,7 @@ export class GatewayService {
|
|
|
2706
2546
|
}
|
|
2707
2547
|
await this.requireUnchangedStaleCodexOrphan(alias, binding);
|
|
2708
2548
|
if (nativeGeneration !== undefined) {
|
|
2709
|
-
const unadvertise = claudeAdapter.
|
|
2549
|
+
const unadvertise = claudeAdapter.unadvertiseNativeSourcePeer;
|
|
2710
2550
|
if (unadvertise === undefined) {
|
|
2711
2551
|
throw new BridgeError("CODEX_ORPHAN_NATIVE_IDENTITY_UNPROVEN", "The native Codex listener cannot be removed exactly.");
|
|
2712
2552
|
}
|
|
@@ -2896,7 +2736,6 @@ export class GatewayService {
|
|
|
2896
2736
|
for (const route of persisted) {
|
|
2897
2737
|
if (!route.enabled ||
|
|
2898
2738
|
route.state !== "stale" ||
|
|
2899
|
-
route.compatibility !== "expired" ||
|
|
2900
2739
|
!CLAUDE_SESSION_ID.test(route.binding.routeHandle) ||
|
|
2901
2740
|
route.binding.ownerLease !==
|
|
2902
2741
|
stableLease("claude", route.binding.routeHandle)) {
|
|
@@ -2920,8 +2759,7 @@ export class GatewayService {
|
|
|
2920
2759
|
alias,
|
|
2921
2760
|
provider: "claude",
|
|
2922
2761
|
host: row.adapter.identity.hostId,
|
|
2923
|
-
state: "
|
|
2924
|
-
compatibility: "incompatible",
|
|
2762
|
+
state: "offline",
|
|
2925
2763
|
validated: false,
|
|
2926
2764
|
selected: false,
|
|
2927
2765
|
safeErrorCode: row.safeErrorCode,
|
|
@@ -2939,7 +2777,6 @@ export class GatewayService {
|
|
|
2939
2777
|
provider: "claude",
|
|
2940
2778
|
host: row.adapter.identity.hostId,
|
|
2941
2779
|
state: row.peer.state,
|
|
2942
|
-
compatibility: row.peer.compatibility,
|
|
2943
2780
|
validated: true,
|
|
2944
2781
|
selected,
|
|
2945
2782
|
});
|
|
@@ -2948,7 +2785,6 @@ export class GatewayService {
|
|
|
2948
2785
|
await this.store.observeRoute({
|
|
2949
2786
|
binding: selectedBinding,
|
|
2950
2787
|
state: row.peer.state,
|
|
2951
|
-
compatibility: "compatible",
|
|
2952
2788
|
});
|
|
2953
2789
|
}
|
|
2954
2790
|
}
|
|
@@ -2963,7 +2799,7 @@ export class GatewayService {
|
|
|
2963
2799
|
const inspection = await this.store.inspectPrivateRoute(alias);
|
|
2964
2800
|
if (inspection !== undefined &&
|
|
2965
2801
|
inspection.enabled &&
|
|
2966
|
-
inspection.
|
|
2802
|
+
inspection.state !== "stale") {
|
|
2967
2803
|
await this.drainPreDeadlineDeliveryCallbacksLocked();
|
|
2968
2804
|
const inFlightSettlements = await this.planRouteInFlightSettlementsLocked(alias, {
|
|
2969
2805
|
unwrittenOutcome: "failed",
|
|
@@ -3040,7 +2876,6 @@ export class GatewayService {
|
|
|
3040
2876
|
const expectedLease = stableLease("claude", candidate.routeHandle);
|
|
3041
2877
|
if (!persisted.enabled ||
|
|
3042
2878
|
persisted.state !== "stale" ||
|
|
3043
|
-
persisted.compatibility !== "expired" ||
|
|
3044
2879
|
persisted.binding.provider !== "claude" ||
|
|
3045
2880
|
candidate.adapter.identity.provider !== "claude" ||
|
|
3046
2881
|
persisted.binding.hostId !== candidate.adapter.identity.hostId ||
|
|
@@ -3106,7 +2941,6 @@ export class GatewayService {
|
|
|
3106
2941
|
binding,
|
|
3107
2942
|
registrationMode: "selected_live_peer",
|
|
3108
2943
|
state: selected.state,
|
|
3109
|
-
compatibility: "compatible",
|
|
3110
2944
|
});
|
|
3111
2945
|
this.rememberBinding(candidate.alias, binding, selected.state);
|
|
3112
2946
|
}
|
|
@@ -3158,7 +2992,6 @@ export class GatewayService {
|
|
|
3158
2992
|
binding,
|
|
3159
2993
|
registrationMode: "selected_live_peer",
|
|
3160
2994
|
state: selectedState,
|
|
3161
|
-
compatibility: "compatible",
|
|
3162
2995
|
},
|
|
3163
2996
|
inFlightSettlements,
|
|
3164
2997
|
});
|
|
@@ -3231,47 +3064,83 @@ export class GatewayService {
|
|
|
3231
3064
|
}
|
|
3232
3065
|
return candidate.alias;
|
|
3233
3066
|
}
|
|
3234
|
-
|
|
3235
|
-
const
|
|
3067
|
+
assertPairThreadAttestation(params) {
|
|
3068
|
+
const attestation = pairParamThreadAttestation(params);
|
|
3069
|
+
const legacyAlias = "codexAlias" in params ? params.codexAlias : undefined;
|
|
3070
|
+
if (attestation === undefined && legacyAlias === undefined)
|
|
3071
|
+
return;
|
|
3072
|
+
const binding = this.routeBindings.get(attestation?.alias ?? legacyAlias);
|
|
3236
3073
|
if (binding?.provider !== "codex" ||
|
|
3237
|
-
(
|
|
3238
|
-
binding.routeHandle !== params.codexThreadId)) {
|
|
3074
|
+
(attestation !== undefined && binding.routeHandle !== attestation.threadId)) {
|
|
3239
3075
|
throw new BridgeError("CODEX_CALLER_MISMATCH", "The inherited Codex task does not own the requested pair endpoint.");
|
|
3240
3076
|
}
|
|
3241
3077
|
}
|
|
3242
3078
|
async pairRoutes(params) {
|
|
3243
|
-
this.
|
|
3244
|
-
|
|
3079
|
+
this.assertPairThreadAttestation(params);
|
|
3080
|
+
if ("claudeAlias" in params) {
|
|
3081
|
+
await this.selectAndPairClaude(params.claudeAlias, params.codexAlias);
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3084
|
+
await this.assertLiveConsentEndpoints(params.aliases);
|
|
3085
|
+
const added = await this.store.addConsentEdge({ aliases: params.aliases });
|
|
3086
|
+
if (added.created)
|
|
3087
|
+
await this.changed();
|
|
3088
|
+
else
|
|
3089
|
+
await this.publish();
|
|
3245
3090
|
}
|
|
3246
3091
|
async unpairRoutes(params) {
|
|
3247
|
-
this.
|
|
3248
|
-
const
|
|
3249
|
-
|
|
3092
|
+
this.assertPairThreadAttestation(params);
|
|
3093
|
+
const aliases = pairParamAliases(params);
|
|
3094
|
+
if (!("claudeAlias" in params))
|
|
3095
|
+
await this.assertLiveConsentEndpoints(aliases);
|
|
3096
|
+
await this.removeConsentEdge(aliases);
|
|
3097
|
+
}
|
|
3098
|
+
async assertLiveConsentEndpoints(aliases) {
|
|
3099
|
+
const routes = await Promise.all(aliases.map((alias) => this.store.inspectPrivateRoute(alias)));
|
|
3100
|
+
if (routes[0] === undefined || routes[1] === undefined) {
|
|
3101
|
+
throw new BridgeError("CONSENT_EDGE_ROUTE_NOT_FOUND", "Both consent endpoints must be registered.");
|
|
3102
|
+
}
|
|
3103
|
+
for (const route of routes) {
|
|
3104
|
+
const live = this.routeBindings.get(route.alias);
|
|
3105
|
+
if (!route.enabled ||
|
|
3106
|
+
route.state === "stale" ||
|
|
3107
|
+
live === undefined ||
|
|
3108
|
+
bindingKey(live) !== bindingKey(route.binding) ||
|
|
3109
|
+
this.adapter(route.binding.provider, route.binding.hostId).identity.endpointGeneration !==
|
|
3110
|
+
route.binding.endpointGeneration) {
|
|
3111
|
+
throw new BridgeError("ROUTE_UNAVAILABLE", "Both consent endpoints must be exact positively observed live routes.");
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
if (routes[0].binding.provider === routes[1].binding.provider || routes[0].binding.hostId !== routes[1].binding.hostId) {
|
|
3115
|
+
throw new BridgeError("INVALID_CONSENT_EDGE", "A consent edge must connect distinct providers on the same host.");
|
|
3116
|
+
}
|
|
3117
|
+
return routes;
|
|
3250
3118
|
}
|
|
3251
|
-
async
|
|
3119
|
+
async removeConsentEdge(aliases) {
|
|
3252
3120
|
await this.drainPreDeadlineDeliveryCallbacksLocked();
|
|
3253
|
-
const
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
};
|
|
3257
|
-
const inFlightSettlements = await this.planPairInFlightSettlementsLocked(pair, {
|
|
3121
|
+
const routes = await Promise.all(aliases.map((alias) => this.store.inspectPrivateRoute(alias)));
|
|
3122
|
+
const edge = { aliases };
|
|
3123
|
+
const inFlightSettlements = await this.planConsentEdgeInFlightSettlementsLocked(edge, {
|
|
3258
3124
|
unwrittenOutcome: "cancelled",
|
|
3259
3125
|
safeErrorCode: "PAIR_REMOVED",
|
|
3260
3126
|
});
|
|
3261
|
-
const result = await this.store.
|
|
3262
|
-
...
|
|
3127
|
+
const result = await this.store.removeConsentEdge({
|
|
3128
|
+
...edge,
|
|
3263
3129
|
inFlightSettlements,
|
|
3264
3130
|
});
|
|
3265
3131
|
for (const settlement of result.settlements) {
|
|
3266
3132
|
await this.applyTerminalSettlementLocked(settlement);
|
|
3267
3133
|
}
|
|
3268
|
-
this.
|
|
3269
|
-
|
|
3270
|
-
const
|
|
3134
|
+
this.purgeConsentEdgeCapabilitiesLocked(aliases);
|
|
3135
|
+
for (const alias of result.unreferencedAliases) {
|
|
3136
|
+
const selected = routes.find((route) => route?.alias === alias);
|
|
3137
|
+
if (selected?.binding.provider !== "claude" || selected.registrationMode !== "selected_live_peer")
|
|
3138
|
+
continue;
|
|
3139
|
+
const settlements = await this.store.unregisterRoute(alias, selected.binding.ownerLease);
|
|
3271
3140
|
for (const settlement of settlements) {
|
|
3272
3141
|
await this.applyTerminalSettlementLocked(settlement);
|
|
3273
3142
|
}
|
|
3274
|
-
this.forgetBinding(
|
|
3143
|
+
this.forgetBinding(alias);
|
|
3275
3144
|
await this.adapters
|
|
3276
3145
|
.find((adapter) => adapter.identity.provider === "claude" &&
|
|
3277
3146
|
adapter.identity.hostId === selected.binding.hostId)
|
|
@@ -3279,7 +3148,7 @@ export class GatewayService {
|
|
|
3279
3148
|
.catch(() => {
|
|
3280
3149
|
this.dashboardHealthy = false;
|
|
3281
3150
|
});
|
|
3282
|
-
this.availablePeers = this.availablePeers.map((peer) => peer.alias ===
|
|
3151
|
+
this.availablePeers = this.availablePeers.map((peer) => peer.alias === alias ? { ...peer, selected: false } : peer);
|
|
3283
3152
|
}
|
|
3284
3153
|
await this.changed();
|
|
3285
3154
|
}
|
|
@@ -3288,17 +3157,6 @@ export class GatewayService {
|
|
|
3288
3157
|
}
|
|
3289
3158
|
async selectAndPairClaude(selector, codexAlias) {
|
|
3290
3159
|
const codexBinding = this.routeBindings.get(codexAlias);
|
|
3291
|
-
const retainedCodexRoute = codexBinding?.provider === "codex"
|
|
3292
|
-
? undefined
|
|
3293
|
-
: await this.store.inspectPrivateRoute(codexAlias);
|
|
3294
|
-
const codexHost = codexBinding?.provider === "codex"
|
|
3295
|
-
? codexBinding.hostId
|
|
3296
|
-
: retainedCodexRoute?.binding.provider === "codex"
|
|
3297
|
-
? retainedCodexRoute.binding.hostId
|
|
3298
|
-
: undefined;
|
|
3299
|
-
if (codexHost !== undefined) {
|
|
3300
|
-
this.assertCrossProviderMutationCompatible(codexHost);
|
|
3301
|
-
}
|
|
3302
3160
|
const codexAdapter = codexBinding?.provider === "codex"
|
|
3303
3161
|
? this.adapters.find((adapter) => adapter.identity.provider === "codex" &&
|
|
3304
3162
|
adapter.identity.hostId === codexBinding.hostId)
|
|
@@ -3338,10 +3196,7 @@ export class GatewayService {
|
|
|
3338
3196
|
}
|
|
3339
3197
|
let paired;
|
|
3340
3198
|
try {
|
|
3341
|
-
paired = await this.store.
|
|
3342
|
-
claudeAlias: candidate.alias,
|
|
3343
|
-
codexAlias,
|
|
3344
|
-
});
|
|
3199
|
+
paired = await this.store.addConsentEdge({ aliases: [candidate.alias, codexAlias] });
|
|
3345
3200
|
}
|
|
3346
3201
|
catch (error) {
|
|
3347
3202
|
if (!alreadyLive &&
|
|
@@ -3381,7 +3236,7 @@ export class GatewayService {
|
|
|
3381
3236
|
async unselectClaude(params) {
|
|
3382
3237
|
const codexAlias = await this.inferCodexAlias(params.codexThreadId);
|
|
3383
3238
|
const selected = await this.selectedClaudeRoute(params.alias);
|
|
3384
|
-
await this.
|
|
3239
|
+
await this.removeConsentEdge([selected.alias, codexAlias]);
|
|
3385
3240
|
}
|
|
3386
3241
|
async selectedClaudeRoute(selector) {
|
|
3387
3242
|
const persisted = await this.store.inspectPrivateClaudeRoutes();
|
|
@@ -3393,14 +3248,13 @@ export class GatewayService {
|
|
|
3393
3248
|
throw new BridgeError("PEER_NOT_FOUND", "No selected Claude session matches that selector.");
|
|
3394
3249
|
return selected;
|
|
3395
3250
|
}
|
|
3396
|
-
async registerOrRebind(alias, binding, reason, state, currentOwnerLease = binding.ownerLease) {
|
|
3251
|
+
async registerOrRebind(alias, binding, reason, state, currentOwnerLease = binding.ownerLease, registrationMode = binding.provider === "codex" ? "explicit_opt_in" : "selected_live_peer") {
|
|
3397
3252
|
try {
|
|
3398
3253
|
await this.store.registerRoute({
|
|
3399
3254
|
alias,
|
|
3400
3255
|
binding,
|
|
3401
|
-
registrationMode
|
|
3256
|
+
registrationMode,
|
|
3402
3257
|
state,
|
|
3403
|
-
compatibility: "compatible",
|
|
3404
3258
|
});
|
|
3405
3259
|
}
|
|
3406
3260
|
catch (error) {
|
|
@@ -3470,14 +3324,12 @@ export class GatewayService {
|
|
|
3470
3324
|
await this.store.observeConnector({
|
|
3471
3325
|
identity: adapter.identity,
|
|
3472
3326
|
health: "healthy",
|
|
3473
|
-
compatibility: "compatible",
|
|
3474
3327
|
protocol: adapter.protocol,
|
|
3475
3328
|
protocolVersion: adapter.protocolVersion,
|
|
3476
3329
|
});
|
|
3477
3330
|
await this.store.observeRoute({
|
|
3478
3331
|
binding,
|
|
3479
3332
|
state,
|
|
3480
|
-
compatibility: "compatible",
|
|
3481
3333
|
});
|
|
3482
3334
|
return binding;
|
|
3483
3335
|
}
|
|
@@ -3793,8 +3645,8 @@ export class GatewayService {
|
|
|
3793
3645
|
};
|
|
3794
3646
|
});
|
|
3795
3647
|
}
|
|
3796
|
-
async
|
|
3797
|
-
const affected = await this.store.
|
|
3648
|
+
async planConsentEdgeInFlightSettlementsLocked(edge, input) {
|
|
3649
|
+
const affected = await this.store.inspectAffectedConsentEdgeInFlightMessages(edge);
|
|
3798
3650
|
const observedAt = this.now().getTime();
|
|
3799
3651
|
return affected.map(({ messageId }) => {
|
|
3800
3652
|
const tracker = this.deliveryTrackers.get(messageId);
|
|
@@ -3825,9 +3677,8 @@ export class GatewayService {
|
|
|
3825
3677
|
};
|
|
3826
3678
|
});
|
|
3827
3679
|
}
|
|
3828
|
-
|
|
3829
|
-
const matches = (sourceAlias, targetAlias) => (sourceAlias
|
|
3830
|
-
(sourceAlias === pair.codexAlias && targetAlias === pair.claudeAlias);
|
|
3680
|
+
purgeConsentEdgeCapabilitiesLocked(aliases) {
|
|
3681
|
+
const matches = (sourceAlias, targetAlias) => aliases.includes(sourceAlias) && aliases.includes(targetAlias);
|
|
3831
3682
|
const conversationIds = new Set();
|
|
3832
3683
|
for (const [conversationId, conversation] of this.conversations) {
|
|
3833
3684
|
if (conversation.pair !== true ||
|
|
@@ -4611,12 +4462,13 @@ export class GatewayService {
|
|
|
4611
4462
|
* native-ingress boundary; this preserves the exact connector generation
|
|
4612
4463
|
* without recreating route authority or accepting a new unsolicited send.
|
|
4613
4464
|
*/
|
|
4614
|
-
async enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, text) {
|
|
4465
|
+
async enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, expectedTargetBinding, text) {
|
|
4615
4466
|
const target = await this.store.resolveRoute(conversation.sourceAlias);
|
|
4616
4467
|
if (sourceBinding.provider !== "claude" ||
|
|
4617
|
-
target.provider
|
|
4468
|
+
target.provider === "claude" ||
|
|
4469
|
+
target.provider !== expectedTargetBinding.provider || target.ownerLease !== expectedTargetBinding.ownerLease ||
|
|
4618
4470
|
sourceBinding.hostId !== target.hostId) {
|
|
4619
|
-
throw new BridgeError("RECOVERED_REPLY_ROUTE_MISMATCH", "The retained provider reply no longer matches an exact same-host
|
|
4471
|
+
throw new BridgeError("RECOVERED_REPLY_ROUTE_MISMATCH", "The retained provider reply no longer matches an exact same-host non-Claude target.");
|
|
4620
4472
|
}
|
|
4621
4473
|
const sequence = conversation.nextSequence;
|
|
4622
4474
|
const enqueuedAt = this.now().getTime();
|
|
@@ -4647,6 +4499,7 @@ export class GatewayService {
|
|
|
4647
4499
|
expectsReply: false,
|
|
4648
4500
|
sequence,
|
|
4649
4501
|
targetBindingKey: bindingKey(target),
|
|
4502
|
+
sourceBinding: { ...sourceBinding },
|
|
4650
4503
|
authorization: "selected_route",
|
|
4651
4504
|
targetAlias: conversation.sourceAlias,
|
|
4652
4505
|
deadlineAt,
|
|
@@ -4659,7 +4512,7 @@ export class GatewayService {
|
|
|
4659
4512
|
deadlineAt,
|
|
4660
4513
|
});
|
|
4661
4514
|
await this.changed();
|
|
4662
|
-
await this.
|
|
4515
|
+
await this.setNativeSourceStatus(conversation.sourceAlias, "waiting");
|
|
4663
4516
|
this.scheduleDispatch(conversation.sourceAlias);
|
|
4664
4517
|
}
|
|
4665
4518
|
pruneTransient() {
|
|
@@ -4879,7 +4732,6 @@ export class GatewayService {
|
|
|
4879
4732
|
!dispatchable ||
|
|
4880
4733
|
inspection === undefined ||
|
|
4881
4734
|
!inspection.enabled ||
|
|
4882
|
-
inspection.compatibility !== "compatible" ||
|
|
4883
4735
|
!storedDispatchable ||
|
|
4884
4736
|
bindingKey(inspection.binding) !== bindingKey(binding) ||
|
|
4885
4737
|
bindingKey(selectedBinding) !== bindingKey(binding)) {
|
|
@@ -4969,10 +4821,26 @@ export class GatewayService {
|
|
|
4969
4821
|
})).markerActive
|
|
4970
4822
|
: false;
|
|
4971
4823
|
}
|
|
4824
|
+
const sourceBinding = context.sourceBinding ??
|
|
4825
|
+
(await this.store.inspectPrivateRoute(item.sourceAlias))?.binding;
|
|
4826
|
+
const parsedDirection = parseDirection(item.direction);
|
|
4827
|
+
if (sourceBinding === undefined ||
|
|
4828
|
+
parsedDirection?.sourceProvider !== sourceBinding.provider ||
|
|
4829
|
+
parsedDirection.targetProvider !== binding.provider ||
|
|
4830
|
+
item.direction !== directionId(sourceBinding.provider, binding.provider)) {
|
|
4831
|
+
await this.finishDelivery({
|
|
4832
|
+
messageId: item.messageId,
|
|
4833
|
+
state: "failed",
|
|
4834
|
+
safeErrorCode: "MESSAGE_DIRECTION_BINDING_MISMATCH",
|
|
4835
|
+
});
|
|
4836
|
+
return;
|
|
4837
|
+
}
|
|
4838
|
+
context.sourceBinding ??= { ...sourceBinding };
|
|
4972
4839
|
let result;
|
|
4973
4840
|
try {
|
|
4974
4841
|
result = await this.awaitDispatchWithNativeHeldLocked(tracker, this.adapter(binding.provider, binding.hostId).dispatch({
|
|
4975
4842
|
sourceAlias: item.sourceAlias,
|
|
4843
|
+
sourceProvider: sourceBinding.provider,
|
|
4976
4844
|
targetAlias: item.targetAlias,
|
|
4977
4845
|
conversationId: context.conversationId,
|
|
4978
4846
|
binding,
|
|
@@ -5065,13 +4933,12 @@ export class GatewayService {
|
|
|
5065
4933
|
await this.store.observeRoute({
|
|
5066
4934
|
binding,
|
|
5067
4935
|
state: "idle",
|
|
5068
|
-
compatibility: "compatible",
|
|
5069
4936
|
});
|
|
5070
4937
|
}
|
|
5071
4938
|
else if (binding.provider === "codex") {
|
|
5072
4939
|
this.scheduleHeldRedispatch(currentTargetAlias);
|
|
5073
4940
|
}
|
|
5074
|
-
await this.
|
|
4941
|
+
await this.setNativeSourceStatus(currentTargetAlias, "waiting");
|
|
5075
4942
|
}
|
|
5076
4943
|
}
|
|
5077
4944
|
else if (requeued.status === "settled") {
|
|
@@ -5282,13 +5149,12 @@ export class GatewayService {
|
|
|
5282
5149
|
.then(() => true)
|
|
5283
5150
|
.catch(() => false);
|
|
5284
5151
|
const pairStillActive = conversation.pair !== true ||
|
|
5285
|
-
(await this.store.
|
|
5286
|
-
|
|
5287
|
-
codexAlias: conversation.sourceAlias,
|
|
5152
|
+
(await this.store.hasConsentEdge({
|
|
5153
|
+
aliases: [conversation.targetAlias, conversation.sourceAlias],
|
|
5288
5154
|
}));
|
|
5289
5155
|
if ((!sourceStillRoutable || !pairStillActive) &&
|
|
5290
|
-
sourceBinding?.provider === "claude") {
|
|
5291
|
-
await this.enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, replyText);
|
|
5156
|
+
sourceBinding?.provider === "claude" && context.sourceBinding !== undefined) {
|
|
5157
|
+
await this.enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, context.sourceBinding, replyText);
|
|
5292
5158
|
}
|
|
5293
5159
|
else {
|
|
5294
5160
|
await this.enqueue(conversation.targetAlias, conversation.sourceAlias, replyText, false, {
|
|
@@ -5381,7 +5247,7 @@ export class GatewayService {
|
|
|
5381
5247
|
};
|
|
5382
5248
|
const collision = this.routeBindings.get(event.sourceAlias);
|
|
5383
5249
|
if (target === undefined ||
|
|
5384
|
-
target.provider
|
|
5250
|
+
target.provider === "claude" ||
|
|
5385
5251
|
target.hostId !== event.endpoint.hostId ||
|
|
5386
5252
|
(collision !== undefined &&
|
|
5387
5253
|
bindingKey(collision) !== bindingKey(sourceBinding))) {
|
|
@@ -5423,6 +5289,7 @@ export class GatewayService {
|
|
|
5423
5289
|
expectsReply: !steer,
|
|
5424
5290
|
sequence: 0,
|
|
5425
5291
|
targetBindingKey: bindingKey(target),
|
|
5292
|
+
sourceBinding: { ...sourceBinding },
|
|
5426
5293
|
authorization: "selected_route",
|
|
5427
5294
|
targetAlias: event.targetAlias,
|
|
5428
5295
|
deadlineAt,
|
|
@@ -5448,7 +5315,7 @@ export class GatewayService {
|
|
|
5448
5315
|
deadlineAt,
|
|
5449
5316
|
});
|
|
5450
5317
|
await this.changed();
|
|
5451
|
-
await this.
|
|
5318
|
+
await this.setNativeSourceStatus(event.targetAlias, "waiting");
|
|
5452
5319
|
// Native ingress gets one immediate dispatch opportunity. Only an
|
|
5453
5320
|
// actual queued/waiting result earns a `held` acknowledgement; an idle
|
|
5454
5321
|
// fast path can therefore settle with one terminal acknowledgement.
|
|
@@ -5518,7 +5385,7 @@ export class GatewayService {
|
|
|
5518
5385
|
return activationDeferred;
|
|
5519
5386
|
}
|
|
5520
5387
|
finally {
|
|
5521
|
-
if (!succeeded
|
|
5388
|
+
if (!succeeded) {
|
|
5522
5389
|
adapter.releaseEndpointRefreshSelectorClaim?.(event.current.endpointGeneration);
|
|
5523
5390
|
}
|
|
5524
5391
|
if (ownsActiveTransition &&
|
|
@@ -5537,56 +5404,29 @@ export class GatewayService {
|
|
|
5537
5404
|
}
|
|
5538
5405
|
}
|
|
5539
5406
|
async applyCodexEndpointRefresh(adapter, event, transitionKey, options) {
|
|
5407
|
+
const routeHandles = Array.isArray(event.routes)
|
|
5408
|
+
? event.routes.map((route) => route.routeHandle)
|
|
5409
|
+
: [];
|
|
5540
5410
|
if (this.closing ||
|
|
5541
|
-
(event.outcome !== "compatible" && event.outcome !== "incompatible") ||
|
|
5542
|
-
!isCompatibilityAttestation(event.attestation) ||
|
|
5543
|
-
event.attestation.surface !== "codex" ||
|
|
5544
5411
|
event.previous.provider !== "codex" ||
|
|
5545
5412
|
!PRIVATE_HANDLE.test(event.previous.endpointGeneration) ||
|
|
5546
5413
|
adapter.identity.provider !== "codex" ||
|
|
5547
5414
|
adapter.identity.hostId !== event.previous.hostId ||
|
|
5548
|
-
!this.config.allowedHosts.includes(event.previous.hostId)
|
|
5415
|
+
!this.config.allowedHosts.includes(event.previous.hostId) ||
|
|
5416
|
+
event.current.provider !== "codex" ||
|
|
5417
|
+
event.current.hostId !== event.previous.hostId ||
|
|
5418
|
+
!PRIVATE_HANDLE.test(event.current.endpointGeneration) ||
|
|
5419
|
+
event.current.endpointGeneration === event.previous.endpointGeneration ||
|
|
5420
|
+
adapter.identity.endpointGeneration !== event.current.endpointGeneration ||
|
|
5421
|
+
!Array.isArray(event.routes) ||
|
|
5422
|
+
event.routes.length > this.config.limits.maxRoutes ||
|
|
5423
|
+
new Set(routeHandles).size !== routeHandles.length ||
|
|
5424
|
+
event.routes.some((route) => !PRIVATE_HANDLE.test(route.routeHandle) ||
|
|
5425
|
+
!["idle", "busy", "awaiting_approval"].includes(route.state))) {
|
|
5549
5426
|
throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "The Codex endpoint transition failed closed runtime validation.");
|
|
5550
5427
|
}
|
|
5551
|
-
if (event.
|
|
5552
|
-
|
|
5553
|
-
? event.routes.map((route) => route.routeHandle)
|
|
5554
|
-
: [];
|
|
5555
|
-
if (event.current.provider !== "codex" ||
|
|
5556
|
-
event.current.hostId !== event.previous.hostId ||
|
|
5557
|
-
!PRIVATE_HANDLE.test(event.current.endpointGeneration) ||
|
|
5558
|
-
event.current.endpointGeneration ===
|
|
5559
|
-
event.previous.endpointGeneration ||
|
|
5560
|
-
adapter.identity.endpointGeneration !==
|
|
5561
|
-
event.current.endpointGeneration ||
|
|
5562
|
-
event.attestation.version !== adapter.protocolVersion ||
|
|
5563
|
-
event.attestation.tier === "incompatible" ||
|
|
5564
|
-
!Array.isArray(event.routes) ||
|
|
5565
|
-
event.routes.length > this.config.limits.maxRoutes ||
|
|
5566
|
-
new Set(routeHandles).size !== routeHandles.length ||
|
|
5567
|
-
event.routes.some((route) => !PRIVATE_HANDLE.test(route.routeHandle) ||
|
|
5568
|
-
!["idle", "busy", "awaiting_approval"].includes(route.state))) {
|
|
5569
|
-
throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "The refreshed Codex provider identity changed before activation.");
|
|
5570
|
-
}
|
|
5571
|
-
if (this.activatedEndpointRefreshes.get(event.current.hostId) ===
|
|
5572
|
-
transitionKey) {
|
|
5573
|
-
return false;
|
|
5574
|
-
}
|
|
5575
|
-
}
|
|
5576
|
-
else {
|
|
5577
|
-
if (event.candidate.provider !== "codex" ||
|
|
5578
|
-
event.candidate.hostId !== event.previous.hostId ||
|
|
5579
|
-
!PRIVATE_HANDLE.test(event.candidate.endpointGeneration) ||
|
|
5580
|
-
event.candidate.endpointGeneration ===
|
|
5581
|
-
event.previous.endpointGeneration ||
|
|
5582
|
-
adapter.identity.endpointGeneration !==
|
|
5583
|
-
event.previous.endpointGeneration ||
|
|
5584
|
-
event.attestation.tier !== "incompatible") {
|
|
5585
|
-
throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "The incompatible Codex candidate no longer matches the active endpoint.");
|
|
5586
|
-
}
|
|
5587
|
-
if (this.handledIncompatibleEndpointRefreshes.get(event.previous.hostId) === transitionKey) {
|
|
5588
|
-
return false;
|
|
5589
|
-
}
|
|
5428
|
+
if (this.activatedEndpointRefreshes.get(event.current.hostId) === transitionKey) {
|
|
5429
|
+
return false;
|
|
5590
5430
|
}
|
|
5591
5431
|
let inventory = await this.store.inspectPrivateCodexRoutes();
|
|
5592
5432
|
const previousRoutes = inventory.filter((route) => route.binding.hostId === event.previous.hostId &&
|
|
@@ -5604,11 +5444,10 @@ export class GatewayService {
|
|
|
5604
5444
|
settlements = await this.store.markConnectorOffline(event.previous, "ENDPOINT_GENERATION_CHANGED", inFlightSettlements, { preserveQueued: true });
|
|
5605
5445
|
}
|
|
5606
5446
|
catch (error) {
|
|
5607
|
-
const
|
|
5608
|
-
error instanceof BridgeError &&
|
|
5447
|
+
const alreadyInvalidatedTransition = error instanceof BridgeError &&
|
|
5609
5448
|
error.code === "CONNECTOR_NOT_FOUND" &&
|
|
5610
|
-
previousRoutes.every((route) => route.state === "stale"
|
|
5611
|
-
if (!
|
|
5449
|
+
previousRoutes.every((route) => route.state === "stale");
|
|
5450
|
+
if (!alreadyInvalidatedTransition)
|
|
5612
5451
|
throw error;
|
|
5613
5452
|
}
|
|
5614
5453
|
for (const settlement of settlements) {
|
|
@@ -5617,45 +5456,9 @@ export class GatewayService {
|
|
|
5617
5456
|
for (const route of previousRoutes) {
|
|
5618
5457
|
this.routeStates.set(route.alias, "stale");
|
|
5619
5458
|
}
|
|
5620
|
-
await this.store.recordCompatibilityAttestation(event.attestation);
|
|
5621
|
-
if (event.outcome === "incompatible") {
|
|
5622
|
-
await this.requireEndpointRefreshUnconflicted();
|
|
5623
|
-
this.activeCodexIncompatibility = structuredClone(event.attestation);
|
|
5624
|
-
this.incompatibleProviderSurfaces.set(this.providerSurfaceKey("codex", event.previous.hostId), event.attestation.safeErrorCode ?? "CODEX_ENDPOINT_INCOMPATIBLE");
|
|
5625
|
-
this.addRuntimeAlert(event.attestation.safeErrorCode ?? "CODEX_ENDPOINT_INCOMPATIBLE", "error", { provider: "codex", host: event.previous.hostId });
|
|
5626
|
-
this.handledIncompatibleEndpointRefreshes.set(event.previous.hostId, transitionKey);
|
|
5627
|
-
await this.changed();
|
|
5628
|
-
return false;
|
|
5629
|
-
}
|
|
5630
|
-
if (event.attestation.tier === "schema_attested") {
|
|
5631
|
-
// A passing untested-version schema is useful observation, but it is not
|
|
5632
|
-
// durable route or write authority. Settle the provider's pending probe
|
|
5633
|
-
// so a later generation can be examined, while leaving every G1 route
|
|
5634
|
-
// stale and refusing selector/registration activation.
|
|
5635
|
-
await this.requireEndpointRefreshUnconflicted();
|
|
5636
|
-
await this.store.observeConnector({
|
|
5637
|
-
identity: event.current,
|
|
5638
|
-
health: "degraded",
|
|
5639
|
-
compatibility: "unknown",
|
|
5640
|
-
protocol: adapter.protocol,
|
|
5641
|
-
protocolVersion: adapter.protocolVersion,
|
|
5642
|
-
safeErrorCode: "CODEX_MONITOR_ONLY",
|
|
5643
|
-
});
|
|
5644
|
-
adapter.acceptCompatibilityAttestation(event.attestation);
|
|
5645
|
-
this.activeCodexIncompatibility = undefined;
|
|
5646
|
-
this.incompatibleProviderSurfaces.set(this.providerSurfaceKey("codex", event.current.hostId), "CODEX_MONITOR_ONLY");
|
|
5647
|
-
this.handledIncompatibleEndpointRefreshes.delete(event.current.hostId);
|
|
5648
|
-
await this.changed();
|
|
5649
|
-
if (options.deferActivation === true ||
|
|
5650
|
-
options.requiredRoute !== undefined) {
|
|
5651
|
-
throw new BridgeError("CODEX_MONITOR_ONLY", "This Codex App Server passed read-only schema probes but cannot authorize a task route or message write.");
|
|
5652
|
-
}
|
|
5653
|
-
return false;
|
|
5654
|
-
}
|
|
5655
5459
|
await this.store.observeConnector({
|
|
5656
5460
|
identity: event.current,
|
|
5657
5461
|
health: "healthy",
|
|
5658
|
-
compatibility: "compatible",
|
|
5659
5462
|
protocol: adapter.protocol,
|
|
5660
5463
|
protocolVersion: adapter.protocolVersion,
|
|
5661
5464
|
});
|
|
@@ -5710,7 +5513,7 @@ export class GatewayService {
|
|
|
5710
5513
|
route.binding.routeHandle === expected.threadId &&
|
|
5711
5514
|
route.binding.ownerLease === expected.ownerLease &&
|
|
5712
5515
|
route.enabled &&
|
|
5713
|
-
route.
|
|
5516
|
+
route.state !== "stale");
|
|
5714
5517
|
return matches.length === 1;
|
|
5715
5518
|
});
|
|
5716
5519
|
if (committedExactly) {
|
|
@@ -5743,8 +5546,7 @@ export class GatewayService {
|
|
|
5743
5546
|
route.binding.hostId !== event.current.hostId ||
|
|
5744
5547
|
route.binding.endpointGeneration !==
|
|
5745
5548
|
event.current.endpointGeneration ||
|
|
5746
|
-
route.state !== "stale"
|
|
5747
|
-
route.compatibility !== "expired") {
|
|
5549
|
+
route.state !== "stale") {
|
|
5748
5550
|
continue;
|
|
5749
5551
|
}
|
|
5750
5552
|
await this.registerOrRebind(route.alias, route.binding, "endpoint_reobserved", state, route.binding.ownerLease);
|
|
@@ -5759,7 +5561,7 @@ export class GatewayService {
|
|
|
5759
5561
|
event.current.endpointGeneration &&
|
|
5760
5562
|
route.binding.routeHandle === observed.routeHandle &&
|
|
5761
5563
|
route.enabled &&
|
|
5762
|
-
route.
|
|
5564
|
+
route.state !== "stale");
|
|
5763
5565
|
if (matches.length > 1) {
|
|
5764
5566
|
throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "A refreshed Codex task has multiple durable owners.");
|
|
5765
5567
|
}
|
|
@@ -5799,7 +5601,7 @@ export class GatewayService {
|
|
|
5799
5601
|
event.current.endpointGeneration &&
|
|
5800
5602
|
route.binding.routeHandle === requiredRoute.threadId &&
|
|
5801
5603
|
route.enabled &&
|
|
5802
|
-
route.
|
|
5604
|
+
route.state !== "stale");
|
|
5803
5605
|
const match = matches[0];
|
|
5804
5606
|
if (matches.length !== 1 || match === undefined) {
|
|
5805
5607
|
throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "The registering Codex task was not durably installed exactly.");
|
|
@@ -5853,7 +5655,7 @@ export class GatewayService {
|
|
|
5853
5655
|
}
|
|
5854
5656
|
for (const route of activatedRoutes) {
|
|
5855
5657
|
requireNativeContinuity(route);
|
|
5856
|
-
await this.
|
|
5658
|
+
await this.setNativeSourceStatus(route.alias, route.state === "idle"
|
|
5857
5659
|
? "idle"
|
|
5858
5660
|
: route.state === "awaiting_approval"
|
|
5859
5661
|
? "waiting"
|
|
@@ -5893,10 +5695,7 @@ export class GatewayService {
|
|
|
5893
5695
|
for (const route of activatedRoutes) {
|
|
5894
5696
|
requireNativeContinuity(route);
|
|
5895
5697
|
}
|
|
5896
|
-
|
|
5897
|
-
// above is complete. This acknowledgement is deliberately the final
|
|
5898
|
-
// fallible activation step.
|
|
5899
|
-
adapter.acceptCompatibilityAttestation(event.attestation);
|
|
5698
|
+
adapter.activateEndpointGeneration(event.current.endpointGeneration);
|
|
5900
5699
|
}
|
|
5901
5700
|
catch (error) {
|
|
5902
5701
|
try {
|
|
@@ -5916,9 +5715,6 @@ export class GatewayService {
|
|
|
5916
5715
|
?.transitionKey === transitionKey) {
|
|
5917
5716
|
this.pendingSelectorEndpointActivations.delete(event.current.hostId);
|
|
5918
5717
|
}
|
|
5919
|
-
this.handledIncompatibleEndpointRefreshes.delete(event.current.hostId);
|
|
5920
|
-
this.activeCodexIncompatibility = undefined;
|
|
5921
|
-
this.incompatibleProviderSurfaces.delete(this.providerSurfaceKey("codex", event.current.hostId));
|
|
5922
5718
|
return false;
|
|
5923
5719
|
}
|
|
5924
5720
|
async onRouteState(source, event) {
|
|
@@ -5940,7 +5736,6 @@ export class GatewayService {
|
|
|
5940
5736
|
await this.store.observeConnector({
|
|
5941
5737
|
identity: source,
|
|
5942
5738
|
health: event.state === "stale" ? "degraded" : "healthy",
|
|
5943
|
-
compatibility: event.state === "stale" ? "expired" : "compatible",
|
|
5944
5739
|
protocol: adapter.protocol,
|
|
5945
5740
|
protocolVersion: adapter.protocolVersion,
|
|
5946
5741
|
...(event.safeErrorCode === undefined
|
|
@@ -5956,7 +5751,6 @@ export class GatewayService {
|
|
|
5956
5751
|
await this.store.observeRoute({
|
|
5957
5752
|
binding,
|
|
5958
5753
|
state: "stale",
|
|
5959
|
-
compatibility: "expired",
|
|
5960
5754
|
safeErrorCode: staleSafeCode,
|
|
5961
5755
|
});
|
|
5962
5756
|
}
|
|
@@ -5964,7 +5758,6 @@ export class GatewayService {
|
|
|
5964
5758
|
await this.store.observeRoute({
|
|
5965
5759
|
binding,
|
|
5966
5760
|
state: event.state,
|
|
5967
|
-
compatibility: "compatible",
|
|
5968
5761
|
...(event.safeErrorCode === undefined
|
|
5969
5762
|
? {}
|
|
5970
5763
|
: {
|
|
@@ -5974,15 +5767,15 @@ export class GatewayService {
|
|
|
5974
5767
|
}
|
|
5975
5768
|
await this.store.touchProgressWatchesForAlias(alias);
|
|
5976
5769
|
await this.changed();
|
|
5977
|
-
if (binding.provider
|
|
5978
|
-
await this.
|
|
5770
|
+
if (binding.provider !== "claude") {
|
|
5771
|
+
await this.setNativeSourceStatus(alias, event.state === "idle"
|
|
5979
5772
|
? "idle"
|
|
5980
5773
|
: event.state === "awaiting_approval"
|
|
5981
5774
|
? "waiting"
|
|
5982
5775
|
: "busy");
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5776
|
+
}
|
|
5777
|
+
if (binding.provider === "codex" && previousState === "stale" && event.state !== "stale") {
|
|
5778
|
+
adapter.rearmEndpointRefreshActivation?.(binding.endpointGeneration);
|
|
5986
5779
|
}
|
|
5987
5780
|
if (event.state === "idle" || event.state === "stale") {
|
|
5988
5781
|
const heldTimer = this.heldRedispatchTimers.get(alias);
|
|
@@ -5993,12 +5786,12 @@ export class GatewayService {
|
|
|
5993
5786
|
this.scheduleDispatch(alias);
|
|
5994
5787
|
}
|
|
5995
5788
|
}
|
|
5996
|
-
async
|
|
5789
|
+
async setNativeSourceStatus(alias, status, requireSuccess = false) {
|
|
5997
5790
|
const binding = this.routeBindings.get(alias);
|
|
5998
|
-
if (binding
|
|
5791
|
+
if (binding === undefined || binding.provider === "claude")
|
|
5999
5792
|
return;
|
|
6000
5793
|
const adapter = this.adapter("claude", binding.hostId);
|
|
6001
|
-
const update = adapter.
|
|
5794
|
+
const update = adapter.updateNativeSourcePeerStatus;
|
|
6002
5795
|
if (update === undefined) {
|
|
6003
5796
|
if (requireSuccess) {
|
|
6004
5797
|
throw new BridgeError("CODEX_ENDPOINT_REFRESH_NATIVE_STATUS_FAILED", "The native listener does not support refreshed Codex status activation.", true);
|
|
@@ -6097,30 +5890,6 @@ export class GatewayService {
|
|
|
6097
5890
|
}
|
|
6098
5891
|
async publicSnapshotLocked() {
|
|
6099
5892
|
const base = await this.store.publicSnapshot();
|
|
6100
|
-
const compatibilityChecks = [];
|
|
6101
|
-
for (const { surface } of this.compatibilitySurfaceDefinitions) {
|
|
6102
|
-
if (surface === "codex" && this.activeCodexIncompatibility !== undefined) {
|
|
6103
|
-
compatibilityChecks.push(structuredClone(this.activeCodexIncompatibility));
|
|
6104
|
-
continue;
|
|
6105
|
-
}
|
|
6106
|
-
const currentVersions = [
|
|
6107
|
-
...new Set([...this.adapters, ...this.compatibilityObservers].flatMap((observer) => {
|
|
6108
|
-
const observation = observer.compatibilitySurface?.();
|
|
6109
|
-
return observation?.surface === surface ? [observation.version] : [];
|
|
6110
|
-
})),
|
|
6111
|
-
].sort((left, right) => left.localeCompare(right));
|
|
6112
|
-
const currentAttestations = (await Promise.all(currentVersions.map(async (version) => await this.store.inspectCompatibilityAttestation(surface, version))))
|
|
6113
|
-
.filter((attestation) => attestation !== undefined)
|
|
6114
|
-
.sort((left, right) => {
|
|
6115
|
-
const checkedAt = right.checkedAt.localeCompare(left.checkedAt);
|
|
6116
|
-
if (checkedAt !== 0)
|
|
6117
|
-
return checkedAt;
|
|
6118
|
-
return left.version.localeCompare(right.version);
|
|
6119
|
-
});
|
|
6120
|
-
const selected = currentAttestations[0];
|
|
6121
|
-
if (selected !== undefined)
|
|
6122
|
-
compatibilityChecks.push(selected);
|
|
6123
|
-
}
|
|
6124
5893
|
const peers = this.availablePeers.map((peer) => ({ ...peer }));
|
|
6125
5894
|
if (!arePublicAvailablePeerSnapshots(peers)) {
|
|
6126
5895
|
throw new BridgeError("INVALID_TRANSIENT_INVENTORY", "The transient peer inventory is unsafe.");
|
|
@@ -6149,11 +5918,6 @@ export class GatewayService {
|
|
|
6149
5918
|
return projectGatewayPublicSnapshot({
|
|
6150
5919
|
...base,
|
|
6151
5920
|
connectors: this.connectorsWithRegistry(base.connectors),
|
|
6152
|
-
...(compatibilityChecks.length === 0
|
|
6153
|
-
? {}
|
|
6154
|
-
: {
|
|
6155
|
-
compatibilityChecks: compatibilityChecks.map(projectPublicCompatibilityCheck),
|
|
6156
|
-
}),
|
|
6157
5921
|
availablePeers: peers,
|
|
6158
5922
|
activityEvents: this.activityEvents.map((event) => ({
|
|
6159
5923
|
...event,
|