agent-embassy 1.6.1 → 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 +20 -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 -599
- 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 -85
- package/dist/src/gateway/providers.js +72 -835
- 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 -437
- 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,91 +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
|
-
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
549
|
captureRegistryObservation(adapter, observation) {
|
|
661
550
|
if (adapter.identity.provider !== "claude" ||
|
|
662
551
|
observation === undefined ||
|
|
@@ -884,50 +773,30 @@ export class GatewayService {
|
|
|
884
773
|
});
|
|
885
774
|
},
|
|
886
775
|
onEndpointRefresh: (event) => {
|
|
776
|
+
if (!Array.isArray(event.routes))
|
|
777
|
+
return;
|
|
778
|
+
const routeHandles = event.routes.map((route) => route.routeHandle);
|
|
887
779
|
if (!this.running ||
|
|
888
780
|
adapter.identity.provider !== "codex" ||
|
|
889
781
|
event.previous.provider !== "codex" ||
|
|
890
782
|
event.previous.hostId !== adapter.identity.hostId ||
|
|
891
|
-
|
|
892
|
-
event.
|
|
893
|
-
|
|
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 !==
|
|
783
|
+
event.current.provider !== "codex" ||
|
|
784
|
+
event.current.hostId !== event.previous.hostId ||
|
|
785
|
+
event.current.endpointGeneration !==
|
|
912
786
|
adapter.identity.endpointGeneration ||
|
|
913
|
-
|
|
787
|
+
routeHandles.length > this.config.limits.maxRoutes ||
|
|
788
|
+
new Set(routeHandles).size !== routeHandles.length ||
|
|
789
|
+
routeHandles.some((routeHandle) => !PRIVATE_HANDLE.test(routeHandle))) {
|
|
914
790
|
return;
|
|
915
791
|
}
|
|
916
792
|
this.enqueueCallback({
|
|
917
793
|
type: "endpoint_refresh",
|
|
918
794
|
adapter,
|
|
919
|
-
value:
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
routes: event.routes.map((route) => ({ ...route })),
|
|
925
|
-
}
|
|
926
|
-
: {
|
|
927
|
-
...event,
|
|
928
|
-
previous: { ...event.previous },
|
|
929
|
-
candidate: { ...event.candidate },
|
|
930
|
-
},
|
|
795
|
+
value: {
|
|
796
|
+
previous: { ...event.previous },
|
|
797
|
+
current: { ...event.current },
|
|
798
|
+
routes: event.routes.map((route) => ({ ...route })),
|
|
799
|
+
},
|
|
931
800
|
});
|
|
932
801
|
},
|
|
933
802
|
};
|
|
@@ -1074,7 +943,7 @@ export class GatewayService {
|
|
|
1074
943
|
await this.onDelivery(event.source, event.value, event.receivedAt);
|
|
1075
944
|
}
|
|
1076
945
|
else if (event.type === "route") {
|
|
1077
|
-
if (pendingRefresh
|
|
946
|
+
if (pendingRefresh !== undefined &&
|
|
1078
947
|
event.source.provider === "codex" &&
|
|
1079
948
|
event.source.hostId === pendingRefresh.current.hostId &&
|
|
1080
949
|
event.source.endpointGeneration ===
|
|
@@ -1204,12 +1073,7 @@ export class GatewayService {
|
|
|
1204
1073
|
await this.drainDetachedReceiptWritesLocked();
|
|
1205
1074
|
}
|
|
1206
1075
|
async failClosedEndpointRefreshConflict(boundary) {
|
|
1207
|
-
const identities = [
|
|
1208
|
-
boundary.value.previous,
|
|
1209
|
-
...(boundary.value.outcome === "compatible"
|
|
1210
|
-
? [boundary.value.current]
|
|
1211
|
-
: []),
|
|
1212
|
-
];
|
|
1076
|
+
const identities = [boundary.value.previous, boundary.value.current];
|
|
1213
1077
|
for (const identity of identities) {
|
|
1214
1078
|
const routes = (await this.store.inspectPrivateCodexRoutes()).filter((route) => route.binding.hostId === identity.hostId &&
|
|
1215
1079
|
route.binding.endpointGeneration === identity.endpointGeneration);
|
|
@@ -1311,23 +1175,6 @@ export class GatewayService {
|
|
|
1311
1175
|
throw new BridgeError("PROVIDER_UNAVAILABLE", "The selected provider is unavailable.", true);
|
|
1312
1176
|
return adapter;
|
|
1313
1177
|
}
|
|
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
1178
|
async assertClaudeWorkspaceDisjoint(adapter, routeHandle) {
|
|
1332
1179
|
if (adapter.identity.provider !== "claude") {
|
|
1333
1180
|
throw new BridgeError("CLAUDE_PROVIDER_MISMATCH", "The selected workspace guard does not belong to Claude.");
|
|
@@ -1385,7 +1232,6 @@ export class GatewayService {
|
|
|
1385
1232
|
for (const route of retained) {
|
|
1386
1233
|
if (!route.enabled ||
|
|
1387
1234
|
route.state !== "stale" ||
|
|
1388
|
-
route.compatibility !== "expired" ||
|
|
1389
1235
|
this.pendingCodexSuccessionRecovery?.journal.new.alias === route.alias ||
|
|
1390
1236
|
this.codexSuccessionPoisoned.has(route.alias)) {
|
|
1391
1237
|
continue;
|
|
@@ -1398,13 +1244,6 @@ export class GatewayService {
|
|
|
1398
1244
|
this.addRuntimeAlert("CODEX_BOOT_REACTIVATION_SKIPPED", "warning", { provider: "codex", host: route.binding.hostId, alias: route.alias });
|
|
1399
1245
|
continue;
|
|
1400
1246
|
}
|
|
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
1247
|
let routeSelected = false;
|
|
1409
1248
|
let advertiseAttempted = false;
|
|
1410
1249
|
let storeReactivated = false;
|
|
@@ -1442,19 +1281,20 @@ export class GatewayService {
|
|
|
1442
1281
|
storeReactivated =
|
|
1443
1282
|
afterStaging !== undefined &&
|
|
1444
1283
|
afterStaging.enabled &&
|
|
1445
|
-
afterStaging.
|
|
1284
|
+
afterStaging.state !== "stale" &&
|
|
1446
1285
|
bindingKey(afterStaging.binding) === bindingKey(binding);
|
|
1447
1286
|
}
|
|
1448
1287
|
advertiseAttempted = true;
|
|
1449
|
-
const advertise = claudeAdapter.
|
|
1288
|
+
const advertise = claudeAdapter.advertiseNativeSourcePeer;
|
|
1450
1289
|
if (advertise === undefined) {
|
|
1451
1290
|
throw new BridgeError("CODEX_BOOT_REACTIVATION_UNAVAILABLE", "The retained Codex task cannot rebuild its native advertisement.");
|
|
1452
1291
|
}
|
|
1453
1292
|
await advertise.call(claudeAdapter, {
|
|
1454
1293
|
alias: route.alias,
|
|
1294
|
+
sourceProvider: "codex",
|
|
1455
1295
|
cwd: this.nativePeerCwd,
|
|
1456
1296
|
});
|
|
1457
|
-
await claudeAdapter.
|
|
1297
|
+
await claudeAdapter.updateNativeSourcePeerStatus?.(route.alias, selected.state === "idle"
|
|
1458
1298
|
? "idle"
|
|
1459
1299
|
: selected.state === "awaiting_approval"
|
|
1460
1300
|
? "waiting"
|
|
@@ -1498,7 +1338,7 @@ export class GatewayService {
|
|
|
1498
1338
|
let cleanupFailed = false;
|
|
1499
1339
|
if (advertiseAttempted) {
|
|
1500
1340
|
await claudeAdapter
|
|
1501
|
-
.
|
|
1341
|
+
.unadvertiseNativeSourcePeer?.(route.alias)
|
|
1502
1342
|
.catch(() => {
|
|
1503
1343
|
cleanupFailed = true;
|
|
1504
1344
|
});
|
|
@@ -1630,7 +1470,7 @@ export class GatewayService {
|
|
|
1630
1470
|
oldInspection.binding.ownerLease !==
|
|
1631
1471
|
stableLease("codex", `${lock.hostId}\0${lock.threadId}`) ||
|
|
1632
1472
|
!oldInspection.enabled ||
|
|
1633
|
-
oldInspection.
|
|
1473
|
+
oldInspection.state === "stale") {
|
|
1634
1474
|
throw new BridgeError("CODEX_SUCCESSION_OWNER_MISMATCH", "The active Codex registration is no longer exact and compatible.");
|
|
1635
1475
|
}
|
|
1636
1476
|
const claudeAdapter = this.adapter("claude", params.hostId);
|
|
@@ -1939,7 +1779,7 @@ export class GatewayService {
|
|
|
1939
1779
|
this.rememberBinding(effect.registration.alias, execution.newBinding, state);
|
|
1940
1780
|
this.codexRegistrationLocks.delete(this.successionOldRegistration(execution).alias);
|
|
1941
1781
|
this.codexRegistrationLocks.set(effect.registration.alias, Object.freeze({ ...effect.registration }));
|
|
1942
|
-
await claudeAdapter.
|
|
1782
|
+
await claudeAdapter.updateNativeSourcePeerStatus?.(effect.registration.alias, state === "idle"
|
|
1943
1783
|
? "idle"
|
|
1944
1784
|
: state === "awaiting_approval"
|
|
1945
1785
|
? "waiting"
|
|
@@ -2084,8 +1924,8 @@ export class GatewayService {
|
|
|
2084
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.");
|
|
2085
1925
|
}
|
|
2086
1926
|
const results = await Promise.allSettled([
|
|
2087
|
-
claudeAdapter.
|
|
2088
|
-
claudeAdapter.
|
|
1927
|
+
claudeAdapter.unadvertiseNativeSourcePeer?.(execution.oldRegistration.alias),
|
|
1928
|
+
claudeAdapter.unadvertiseNativeSourcePeer?.(execution.newRegistration.alias),
|
|
2089
1929
|
codexAdapter.releaseRoute?.(execution.oldRegistration.threadId),
|
|
2090
1930
|
codexAdapter.releaseRoute?.(execution.newRegistration.threadId),
|
|
2091
1931
|
]);
|
|
@@ -2310,9 +2150,6 @@ export class GatewayService {
|
|
|
2310
2150
|
if (!params.alias.startsWith("codex-")) {
|
|
2311
2151
|
throw new BridgeError("INVALID_CODEX_PEER_ALIAS", "A registered Codex alias must start with codex-.");
|
|
2312
2152
|
}
|
|
2313
|
-
this.assertCrossProviderMutationCompatible(params.hostId, {
|
|
2314
|
-
allowCodexRefresh: params.succeedsAlias === undefined,
|
|
2315
|
-
});
|
|
2316
2153
|
if (this.codexSuccessionPoisoned.has(params.alias) ||
|
|
2317
2154
|
this.codexOrphanCleanupPending.has(params.alias) ||
|
|
2318
2155
|
(params.succeedsAlias !== undefined &&
|
|
@@ -2383,7 +2220,7 @@ export class GatewayService {
|
|
|
2383
2220
|
const afterRefresh = await this.store.inspectPrivateRoute(params.alias);
|
|
2384
2221
|
const alreadyInstalledExactly = afterRefresh !== undefined &&
|
|
2385
2222
|
afterRefresh.enabled &&
|
|
2386
|
-
afterRefresh.
|
|
2223
|
+
afterRefresh.state !== "stale" &&
|
|
2387
2224
|
bindingKey(afterRefresh.binding) === bindingKey(binding);
|
|
2388
2225
|
if (!alreadyInstalledExactly || !deferredEndpointRefresh) {
|
|
2389
2226
|
await this.registerOrRebind(params.alias, binding, "endpoint_reobserved", registered.state);
|
|
@@ -2398,15 +2235,16 @@ export class GatewayService {
|
|
|
2398
2235
|
try {
|
|
2399
2236
|
this.rememberBinding(params.alias, binding, registered.state);
|
|
2400
2237
|
const claudeAdapter = this.adapter("claude", params.hostId);
|
|
2401
|
-
const advertise = claudeAdapter.
|
|
2238
|
+
const advertise = claudeAdapter.advertiseNativeSourcePeer;
|
|
2402
2239
|
if (advertise !== undefined) {
|
|
2403
2240
|
advertiseAttempted = true;
|
|
2404
2241
|
await advertise.call(claudeAdapter, {
|
|
2405
2242
|
alias: params.alias,
|
|
2243
|
+
sourceProvider: "codex",
|
|
2406
2244
|
cwd: this.nativePeerCwd,
|
|
2407
2245
|
});
|
|
2408
2246
|
}
|
|
2409
|
-
await claudeAdapter.
|
|
2247
|
+
await claudeAdapter.updateNativeSourcePeerStatus?.(params.alias, registered.state === "idle"
|
|
2410
2248
|
? "idle"
|
|
2411
2249
|
: registered.state === "awaiting_approval"
|
|
2412
2250
|
? "waiting"
|
|
@@ -2428,8 +2266,7 @@ export class GatewayService {
|
|
|
2428
2266
|
}
|
|
2429
2267
|
catch (error) {
|
|
2430
2268
|
if (routeBeforeRegistration !== undefined) {
|
|
2431
|
-
if (deferredEndpointRefresh &&
|
|
2432
|
-
registered.endpointRefresh?.outcome === "compatible") {
|
|
2269
|
+
if (deferredEndpointRefresh && registered.endpointRefresh !== undefined) {
|
|
2433
2270
|
try {
|
|
2434
2271
|
await this.store.markConnectorOffline(registered.endpointRefresh.current, "CODEX_ENDPOINT_ACTIVATION_FAILED", [], { preserveQueued: true });
|
|
2435
2272
|
this.routeStates.set(params.alias, "stale");
|
|
@@ -2511,7 +2348,7 @@ export class GatewayService {
|
|
|
2511
2348
|
});
|
|
2512
2349
|
if (advertiseAttempted) {
|
|
2513
2350
|
await this.adapter("claude", params.hostId)
|
|
2514
|
-
.
|
|
2351
|
+
.unadvertiseNativeSourcePeer?.(params.alias)
|
|
2515
2352
|
.catch(() => {
|
|
2516
2353
|
cleanupFailed = true;
|
|
2517
2354
|
});
|
|
@@ -2538,7 +2375,7 @@ export class GatewayService {
|
|
|
2538
2375
|
}
|
|
2539
2376
|
this.forgetBinding(params.alias);
|
|
2540
2377
|
await this.adapter("claude", host)
|
|
2541
|
-
.
|
|
2378
|
+
.unadvertiseNativeSourcePeer?.(params.alias)
|
|
2542
2379
|
.catch(() => {
|
|
2543
2380
|
this.dashboardHealthy = false;
|
|
2544
2381
|
});
|
|
@@ -2709,7 +2546,7 @@ export class GatewayService {
|
|
|
2709
2546
|
}
|
|
2710
2547
|
await this.requireUnchangedStaleCodexOrphan(alias, binding);
|
|
2711
2548
|
if (nativeGeneration !== undefined) {
|
|
2712
|
-
const unadvertise = claudeAdapter.
|
|
2549
|
+
const unadvertise = claudeAdapter.unadvertiseNativeSourcePeer;
|
|
2713
2550
|
if (unadvertise === undefined) {
|
|
2714
2551
|
throw new BridgeError("CODEX_ORPHAN_NATIVE_IDENTITY_UNPROVEN", "The native Codex listener cannot be removed exactly.");
|
|
2715
2552
|
}
|
|
@@ -2899,7 +2736,6 @@ export class GatewayService {
|
|
|
2899
2736
|
for (const route of persisted) {
|
|
2900
2737
|
if (!route.enabled ||
|
|
2901
2738
|
route.state !== "stale" ||
|
|
2902
|
-
route.compatibility !== "expired" ||
|
|
2903
2739
|
!CLAUDE_SESSION_ID.test(route.binding.routeHandle) ||
|
|
2904
2740
|
route.binding.ownerLease !==
|
|
2905
2741
|
stableLease("claude", route.binding.routeHandle)) {
|
|
@@ -2923,8 +2759,7 @@ export class GatewayService {
|
|
|
2923
2759
|
alias,
|
|
2924
2760
|
provider: "claude",
|
|
2925
2761
|
host: row.adapter.identity.hostId,
|
|
2926
|
-
state: "
|
|
2927
|
-
compatibility: "incompatible",
|
|
2762
|
+
state: "offline",
|
|
2928
2763
|
validated: false,
|
|
2929
2764
|
selected: false,
|
|
2930
2765
|
safeErrorCode: row.safeErrorCode,
|
|
@@ -2942,7 +2777,6 @@ export class GatewayService {
|
|
|
2942
2777
|
provider: "claude",
|
|
2943
2778
|
host: row.adapter.identity.hostId,
|
|
2944
2779
|
state: row.peer.state,
|
|
2945
|
-
compatibility: row.peer.compatibility,
|
|
2946
2780
|
validated: true,
|
|
2947
2781
|
selected,
|
|
2948
2782
|
});
|
|
@@ -2951,7 +2785,6 @@ export class GatewayService {
|
|
|
2951
2785
|
await this.store.observeRoute({
|
|
2952
2786
|
binding: selectedBinding,
|
|
2953
2787
|
state: row.peer.state,
|
|
2954
|
-
compatibility: "compatible",
|
|
2955
2788
|
});
|
|
2956
2789
|
}
|
|
2957
2790
|
}
|
|
@@ -2966,7 +2799,7 @@ export class GatewayService {
|
|
|
2966
2799
|
const inspection = await this.store.inspectPrivateRoute(alias);
|
|
2967
2800
|
if (inspection !== undefined &&
|
|
2968
2801
|
inspection.enabled &&
|
|
2969
|
-
inspection.
|
|
2802
|
+
inspection.state !== "stale") {
|
|
2970
2803
|
await this.drainPreDeadlineDeliveryCallbacksLocked();
|
|
2971
2804
|
const inFlightSettlements = await this.planRouteInFlightSettlementsLocked(alias, {
|
|
2972
2805
|
unwrittenOutcome: "failed",
|
|
@@ -3043,7 +2876,6 @@ export class GatewayService {
|
|
|
3043
2876
|
const expectedLease = stableLease("claude", candidate.routeHandle);
|
|
3044
2877
|
if (!persisted.enabled ||
|
|
3045
2878
|
persisted.state !== "stale" ||
|
|
3046
|
-
persisted.compatibility !== "expired" ||
|
|
3047
2879
|
persisted.binding.provider !== "claude" ||
|
|
3048
2880
|
candidate.adapter.identity.provider !== "claude" ||
|
|
3049
2881
|
persisted.binding.hostId !== candidate.adapter.identity.hostId ||
|
|
@@ -3109,7 +2941,6 @@ export class GatewayService {
|
|
|
3109
2941
|
binding,
|
|
3110
2942
|
registrationMode: "selected_live_peer",
|
|
3111
2943
|
state: selected.state,
|
|
3112
|
-
compatibility: "compatible",
|
|
3113
2944
|
});
|
|
3114
2945
|
this.rememberBinding(candidate.alias, binding, selected.state);
|
|
3115
2946
|
}
|
|
@@ -3161,7 +2992,6 @@ export class GatewayService {
|
|
|
3161
2992
|
binding,
|
|
3162
2993
|
registrationMode: "selected_live_peer",
|
|
3163
2994
|
state: selectedState,
|
|
3164
|
-
compatibility: "compatible",
|
|
3165
2995
|
},
|
|
3166
2996
|
inFlightSettlements,
|
|
3167
2997
|
});
|
|
@@ -3234,47 +3064,83 @@ export class GatewayService {
|
|
|
3234
3064
|
}
|
|
3235
3065
|
return candidate.alias;
|
|
3236
3066
|
}
|
|
3237
|
-
|
|
3238
|
-
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);
|
|
3239
3073
|
if (binding?.provider !== "codex" ||
|
|
3240
|
-
(
|
|
3241
|
-
binding.routeHandle !== params.codexThreadId)) {
|
|
3074
|
+
(attestation !== undefined && binding.routeHandle !== attestation.threadId)) {
|
|
3242
3075
|
throw new BridgeError("CODEX_CALLER_MISMATCH", "The inherited Codex task does not own the requested pair endpoint.");
|
|
3243
3076
|
}
|
|
3244
3077
|
}
|
|
3245
3078
|
async pairRoutes(params) {
|
|
3246
|
-
this.
|
|
3247
|
-
|
|
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();
|
|
3248
3090
|
}
|
|
3249
3091
|
async unpairRoutes(params) {
|
|
3250
|
-
this.
|
|
3251
|
-
const
|
|
3252
|
-
|
|
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;
|
|
3253
3118
|
}
|
|
3254
|
-
async
|
|
3119
|
+
async removeConsentEdge(aliases) {
|
|
3255
3120
|
await this.drainPreDeadlineDeliveryCallbacksLocked();
|
|
3256
|
-
const
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
};
|
|
3260
|
-
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, {
|
|
3261
3124
|
unwrittenOutcome: "cancelled",
|
|
3262
3125
|
safeErrorCode: "PAIR_REMOVED",
|
|
3263
3126
|
});
|
|
3264
|
-
const result = await this.store.
|
|
3265
|
-
...
|
|
3127
|
+
const result = await this.store.removeConsentEdge({
|
|
3128
|
+
...edge,
|
|
3266
3129
|
inFlightSettlements,
|
|
3267
3130
|
});
|
|
3268
3131
|
for (const settlement of result.settlements) {
|
|
3269
3132
|
await this.applyTerminalSettlementLocked(settlement);
|
|
3270
3133
|
}
|
|
3271
|
-
this.
|
|
3272
|
-
|
|
3273
|
-
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);
|
|
3274
3140
|
for (const settlement of settlements) {
|
|
3275
3141
|
await this.applyTerminalSettlementLocked(settlement);
|
|
3276
3142
|
}
|
|
3277
|
-
this.forgetBinding(
|
|
3143
|
+
this.forgetBinding(alias);
|
|
3278
3144
|
await this.adapters
|
|
3279
3145
|
.find((adapter) => adapter.identity.provider === "claude" &&
|
|
3280
3146
|
adapter.identity.hostId === selected.binding.hostId)
|
|
@@ -3282,7 +3148,7 @@ export class GatewayService {
|
|
|
3282
3148
|
.catch(() => {
|
|
3283
3149
|
this.dashboardHealthy = false;
|
|
3284
3150
|
});
|
|
3285
|
-
this.availablePeers = this.availablePeers.map((peer) => peer.alias ===
|
|
3151
|
+
this.availablePeers = this.availablePeers.map((peer) => peer.alias === alias ? { ...peer, selected: false } : peer);
|
|
3286
3152
|
}
|
|
3287
3153
|
await this.changed();
|
|
3288
3154
|
}
|
|
@@ -3291,17 +3157,6 @@ export class GatewayService {
|
|
|
3291
3157
|
}
|
|
3292
3158
|
async selectAndPairClaude(selector, codexAlias) {
|
|
3293
3159
|
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
3160
|
const codexAdapter = codexBinding?.provider === "codex"
|
|
3306
3161
|
? this.adapters.find((adapter) => adapter.identity.provider === "codex" &&
|
|
3307
3162
|
adapter.identity.hostId === codexBinding.hostId)
|
|
@@ -3341,10 +3196,7 @@ export class GatewayService {
|
|
|
3341
3196
|
}
|
|
3342
3197
|
let paired;
|
|
3343
3198
|
try {
|
|
3344
|
-
paired = await this.store.
|
|
3345
|
-
claudeAlias: candidate.alias,
|
|
3346
|
-
codexAlias,
|
|
3347
|
-
});
|
|
3199
|
+
paired = await this.store.addConsentEdge({ aliases: [candidate.alias, codexAlias] });
|
|
3348
3200
|
}
|
|
3349
3201
|
catch (error) {
|
|
3350
3202
|
if (!alreadyLive &&
|
|
@@ -3384,7 +3236,7 @@ export class GatewayService {
|
|
|
3384
3236
|
async unselectClaude(params) {
|
|
3385
3237
|
const codexAlias = await this.inferCodexAlias(params.codexThreadId);
|
|
3386
3238
|
const selected = await this.selectedClaudeRoute(params.alias);
|
|
3387
|
-
await this.
|
|
3239
|
+
await this.removeConsentEdge([selected.alias, codexAlias]);
|
|
3388
3240
|
}
|
|
3389
3241
|
async selectedClaudeRoute(selector) {
|
|
3390
3242
|
const persisted = await this.store.inspectPrivateClaudeRoutes();
|
|
@@ -3396,14 +3248,13 @@ export class GatewayService {
|
|
|
3396
3248
|
throw new BridgeError("PEER_NOT_FOUND", "No selected Claude session matches that selector.");
|
|
3397
3249
|
return selected;
|
|
3398
3250
|
}
|
|
3399
|
-
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") {
|
|
3400
3252
|
try {
|
|
3401
3253
|
await this.store.registerRoute({
|
|
3402
3254
|
alias,
|
|
3403
3255
|
binding,
|
|
3404
|
-
registrationMode
|
|
3256
|
+
registrationMode,
|
|
3405
3257
|
state,
|
|
3406
|
-
compatibility: "compatible",
|
|
3407
3258
|
});
|
|
3408
3259
|
}
|
|
3409
3260
|
catch (error) {
|
|
@@ -3473,14 +3324,12 @@ export class GatewayService {
|
|
|
3473
3324
|
await this.store.observeConnector({
|
|
3474
3325
|
identity: adapter.identity,
|
|
3475
3326
|
health: "healthy",
|
|
3476
|
-
compatibility: "compatible",
|
|
3477
3327
|
protocol: adapter.protocol,
|
|
3478
3328
|
protocolVersion: adapter.protocolVersion,
|
|
3479
3329
|
});
|
|
3480
3330
|
await this.store.observeRoute({
|
|
3481
3331
|
binding,
|
|
3482
3332
|
state,
|
|
3483
|
-
compatibility: "compatible",
|
|
3484
3333
|
});
|
|
3485
3334
|
return binding;
|
|
3486
3335
|
}
|
|
@@ -3796,8 +3645,8 @@ export class GatewayService {
|
|
|
3796
3645
|
};
|
|
3797
3646
|
});
|
|
3798
3647
|
}
|
|
3799
|
-
async
|
|
3800
|
-
const affected = await this.store.
|
|
3648
|
+
async planConsentEdgeInFlightSettlementsLocked(edge, input) {
|
|
3649
|
+
const affected = await this.store.inspectAffectedConsentEdgeInFlightMessages(edge);
|
|
3801
3650
|
const observedAt = this.now().getTime();
|
|
3802
3651
|
return affected.map(({ messageId }) => {
|
|
3803
3652
|
const tracker = this.deliveryTrackers.get(messageId);
|
|
@@ -3828,9 +3677,8 @@ export class GatewayService {
|
|
|
3828
3677
|
};
|
|
3829
3678
|
});
|
|
3830
3679
|
}
|
|
3831
|
-
|
|
3832
|
-
const matches = (sourceAlias, targetAlias) => (sourceAlias
|
|
3833
|
-
(sourceAlias === pair.codexAlias && targetAlias === pair.claudeAlias);
|
|
3680
|
+
purgeConsentEdgeCapabilitiesLocked(aliases) {
|
|
3681
|
+
const matches = (sourceAlias, targetAlias) => aliases.includes(sourceAlias) && aliases.includes(targetAlias);
|
|
3834
3682
|
const conversationIds = new Set();
|
|
3835
3683
|
for (const [conversationId, conversation] of this.conversations) {
|
|
3836
3684
|
if (conversation.pair !== true ||
|
|
@@ -4614,12 +4462,13 @@ export class GatewayService {
|
|
|
4614
4462
|
* native-ingress boundary; this preserves the exact connector generation
|
|
4615
4463
|
* without recreating route authority or accepting a new unsolicited send.
|
|
4616
4464
|
*/
|
|
4617
|
-
async enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, text) {
|
|
4465
|
+
async enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, expectedTargetBinding, text) {
|
|
4618
4466
|
const target = await this.store.resolveRoute(conversation.sourceAlias);
|
|
4619
4467
|
if (sourceBinding.provider !== "claude" ||
|
|
4620
|
-
target.provider
|
|
4468
|
+
target.provider === "claude" ||
|
|
4469
|
+
target.provider !== expectedTargetBinding.provider || target.ownerLease !== expectedTargetBinding.ownerLease ||
|
|
4621
4470
|
sourceBinding.hostId !== target.hostId) {
|
|
4622
|
-
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.");
|
|
4623
4472
|
}
|
|
4624
4473
|
const sequence = conversation.nextSequence;
|
|
4625
4474
|
const enqueuedAt = this.now().getTime();
|
|
@@ -4650,6 +4499,7 @@ export class GatewayService {
|
|
|
4650
4499
|
expectsReply: false,
|
|
4651
4500
|
sequence,
|
|
4652
4501
|
targetBindingKey: bindingKey(target),
|
|
4502
|
+
sourceBinding: { ...sourceBinding },
|
|
4653
4503
|
authorization: "selected_route",
|
|
4654
4504
|
targetAlias: conversation.sourceAlias,
|
|
4655
4505
|
deadlineAt,
|
|
@@ -4662,7 +4512,7 @@ export class GatewayService {
|
|
|
4662
4512
|
deadlineAt,
|
|
4663
4513
|
});
|
|
4664
4514
|
await this.changed();
|
|
4665
|
-
await this.
|
|
4515
|
+
await this.setNativeSourceStatus(conversation.sourceAlias, "waiting");
|
|
4666
4516
|
this.scheduleDispatch(conversation.sourceAlias);
|
|
4667
4517
|
}
|
|
4668
4518
|
pruneTransient() {
|
|
@@ -4882,7 +4732,6 @@ export class GatewayService {
|
|
|
4882
4732
|
!dispatchable ||
|
|
4883
4733
|
inspection === undefined ||
|
|
4884
4734
|
!inspection.enabled ||
|
|
4885
|
-
inspection.compatibility !== "compatible" ||
|
|
4886
4735
|
!storedDispatchable ||
|
|
4887
4736
|
bindingKey(inspection.binding) !== bindingKey(binding) ||
|
|
4888
4737
|
bindingKey(selectedBinding) !== bindingKey(binding)) {
|
|
@@ -4972,10 +4821,26 @@ export class GatewayService {
|
|
|
4972
4821
|
})).markerActive
|
|
4973
4822
|
: false;
|
|
4974
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 };
|
|
4975
4839
|
let result;
|
|
4976
4840
|
try {
|
|
4977
4841
|
result = await this.awaitDispatchWithNativeHeldLocked(tracker, this.adapter(binding.provider, binding.hostId).dispatch({
|
|
4978
4842
|
sourceAlias: item.sourceAlias,
|
|
4843
|
+
sourceProvider: sourceBinding.provider,
|
|
4979
4844
|
targetAlias: item.targetAlias,
|
|
4980
4845
|
conversationId: context.conversationId,
|
|
4981
4846
|
binding,
|
|
@@ -5068,13 +4933,12 @@ export class GatewayService {
|
|
|
5068
4933
|
await this.store.observeRoute({
|
|
5069
4934
|
binding,
|
|
5070
4935
|
state: "idle",
|
|
5071
|
-
compatibility: "compatible",
|
|
5072
4936
|
});
|
|
5073
4937
|
}
|
|
5074
4938
|
else if (binding.provider === "codex") {
|
|
5075
4939
|
this.scheduleHeldRedispatch(currentTargetAlias);
|
|
5076
4940
|
}
|
|
5077
|
-
await this.
|
|
4941
|
+
await this.setNativeSourceStatus(currentTargetAlias, "waiting");
|
|
5078
4942
|
}
|
|
5079
4943
|
}
|
|
5080
4944
|
else if (requeued.status === "settled") {
|
|
@@ -5285,13 +5149,12 @@ export class GatewayService {
|
|
|
5285
5149
|
.then(() => true)
|
|
5286
5150
|
.catch(() => false);
|
|
5287
5151
|
const pairStillActive = conversation.pair !== true ||
|
|
5288
|
-
(await this.store.
|
|
5289
|
-
|
|
5290
|
-
codexAlias: conversation.sourceAlias,
|
|
5152
|
+
(await this.store.hasConsentEdge({
|
|
5153
|
+
aliases: [conversation.targetAlias, conversation.sourceAlias],
|
|
5291
5154
|
}));
|
|
5292
5155
|
if ((!sourceStillRoutable || !pairStillActive) &&
|
|
5293
|
-
sourceBinding?.provider === "claude") {
|
|
5294
|
-
await this.enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, replyText);
|
|
5156
|
+
sourceBinding?.provider === "claude" && context.sourceBinding !== undefined) {
|
|
5157
|
+
await this.enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, context.sourceBinding, replyText);
|
|
5295
5158
|
}
|
|
5296
5159
|
else {
|
|
5297
5160
|
await this.enqueue(conversation.targetAlias, conversation.sourceAlias, replyText, false, {
|
|
@@ -5384,7 +5247,7 @@ export class GatewayService {
|
|
|
5384
5247
|
};
|
|
5385
5248
|
const collision = this.routeBindings.get(event.sourceAlias);
|
|
5386
5249
|
if (target === undefined ||
|
|
5387
|
-
target.provider
|
|
5250
|
+
target.provider === "claude" ||
|
|
5388
5251
|
target.hostId !== event.endpoint.hostId ||
|
|
5389
5252
|
(collision !== undefined &&
|
|
5390
5253
|
bindingKey(collision) !== bindingKey(sourceBinding))) {
|
|
@@ -5426,6 +5289,7 @@ export class GatewayService {
|
|
|
5426
5289
|
expectsReply: !steer,
|
|
5427
5290
|
sequence: 0,
|
|
5428
5291
|
targetBindingKey: bindingKey(target),
|
|
5292
|
+
sourceBinding: { ...sourceBinding },
|
|
5429
5293
|
authorization: "selected_route",
|
|
5430
5294
|
targetAlias: event.targetAlias,
|
|
5431
5295
|
deadlineAt,
|
|
@@ -5451,7 +5315,7 @@ export class GatewayService {
|
|
|
5451
5315
|
deadlineAt,
|
|
5452
5316
|
});
|
|
5453
5317
|
await this.changed();
|
|
5454
|
-
await this.
|
|
5318
|
+
await this.setNativeSourceStatus(event.targetAlias, "waiting");
|
|
5455
5319
|
// Native ingress gets one immediate dispatch opportunity. Only an
|
|
5456
5320
|
// actual queued/waiting result earns a `held` acknowledgement; an idle
|
|
5457
5321
|
// fast path can therefore settle with one terminal acknowledgement.
|
|
@@ -5521,7 +5385,7 @@ export class GatewayService {
|
|
|
5521
5385
|
return activationDeferred;
|
|
5522
5386
|
}
|
|
5523
5387
|
finally {
|
|
5524
|
-
if (!succeeded
|
|
5388
|
+
if (!succeeded) {
|
|
5525
5389
|
adapter.releaseEndpointRefreshSelectorClaim?.(event.current.endpointGeneration);
|
|
5526
5390
|
}
|
|
5527
5391
|
if (ownsActiveTransition &&
|
|
@@ -5540,56 +5404,29 @@ export class GatewayService {
|
|
|
5540
5404
|
}
|
|
5541
5405
|
}
|
|
5542
5406
|
async applyCodexEndpointRefresh(adapter, event, transitionKey, options) {
|
|
5407
|
+
const routeHandles = Array.isArray(event.routes)
|
|
5408
|
+
? event.routes.map((route) => route.routeHandle)
|
|
5409
|
+
: [];
|
|
5543
5410
|
if (this.closing ||
|
|
5544
|
-
(event.outcome !== "compatible" && event.outcome !== "incompatible") ||
|
|
5545
|
-
!isCompatibilityAttestation(event.attestation) ||
|
|
5546
|
-
event.attestation.surface !== "codex" ||
|
|
5547
5411
|
event.previous.provider !== "codex" ||
|
|
5548
5412
|
!PRIVATE_HANDLE.test(event.previous.endpointGeneration) ||
|
|
5549
5413
|
adapter.identity.provider !== "codex" ||
|
|
5550
5414
|
adapter.identity.hostId !== event.previous.hostId ||
|
|
5551
|
-
!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))) {
|
|
5552
5426
|
throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "The Codex endpoint transition failed closed runtime validation.");
|
|
5553
5427
|
}
|
|
5554
|
-
if (event.
|
|
5555
|
-
|
|
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
|
-
}
|
|
5428
|
+
if (this.activatedEndpointRefreshes.get(event.current.hostId) === transitionKey) {
|
|
5429
|
+
return false;
|
|
5593
5430
|
}
|
|
5594
5431
|
let inventory = await this.store.inspectPrivateCodexRoutes();
|
|
5595
5432
|
const previousRoutes = inventory.filter((route) => route.binding.hostId === event.previous.hostId &&
|
|
@@ -5607,11 +5444,10 @@ export class GatewayService {
|
|
|
5607
5444
|
settlements = await this.store.markConnectorOffline(event.previous, "ENDPOINT_GENERATION_CHANGED", inFlightSettlements, { preserveQueued: true });
|
|
5608
5445
|
}
|
|
5609
5446
|
catch (error) {
|
|
5610
|
-
const
|
|
5611
|
-
error instanceof BridgeError &&
|
|
5447
|
+
const alreadyInvalidatedTransition = error instanceof BridgeError &&
|
|
5612
5448
|
error.code === "CONNECTOR_NOT_FOUND" &&
|
|
5613
|
-
previousRoutes.every((route) => route.state === "stale"
|
|
5614
|
-
if (!
|
|
5449
|
+
previousRoutes.every((route) => route.state === "stale");
|
|
5450
|
+
if (!alreadyInvalidatedTransition)
|
|
5615
5451
|
throw error;
|
|
5616
5452
|
}
|
|
5617
5453
|
for (const settlement of settlements) {
|
|
@@ -5620,47 +5456,9 @@ export class GatewayService {
|
|
|
5620
5456
|
for (const route of previousRoutes) {
|
|
5621
5457
|
this.routeStates.set(route.alias, "stale");
|
|
5622
5458
|
}
|
|
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
5459
|
await this.store.observeConnector({
|
|
5661
5460
|
identity: event.current,
|
|
5662
5461
|
health: "healthy",
|
|
5663
|
-
compatibility: "compatible",
|
|
5664
5462
|
protocol: adapter.protocol,
|
|
5665
5463
|
protocolVersion: adapter.protocolVersion,
|
|
5666
5464
|
});
|
|
@@ -5715,7 +5513,7 @@ export class GatewayService {
|
|
|
5715
5513
|
route.binding.routeHandle === expected.threadId &&
|
|
5716
5514
|
route.binding.ownerLease === expected.ownerLease &&
|
|
5717
5515
|
route.enabled &&
|
|
5718
|
-
route.
|
|
5516
|
+
route.state !== "stale");
|
|
5719
5517
|
return matches.length === 1;
|
|
5720
5518
|
});
|
|
5721
5519
|
if (committedExactly) {
|
|
@@ -5748,8 +5546,7 @@ export class GatewayService {
|
|
|
5748
5546
|
route.binding.hostId !== event.current.hostId ||
|
|
5749
5547
|
route.binding.endpointGeneration !==
|
|
5750
5548
|
event.current.endpointGeneration ||
|
|
5751
|
-
route.state !== "stale"
|
|
5752
|
-
route.compatibility !== "expired") {
|
|
5549
|
+
route.state !== "stale") {
|
|
5753
5550
|
continue;
|
|
5754
5551
|
}
|
|
5755
5552
|
await this.registerOrRebind(route.alias, route.binding, "endpoint_reobserved", state, route.binding.ownerLease);
|
|
@@ -5764,7 +5561,7 @@ export class GatewayService {
|
|
|
5764
5561
|
event.current.endpointGeneration &&
|
|
5765
5562
|
route.binding.routeHandle === observed.routeHandle &&
|
|
5766
5563
|
route.enabled &&
|
|
5767
|
-
route.
|
|
5564
|
+
route.state !== "stale");
|
|
5768
5565
|
if (matches.length > 1) {
|
|
5769
5566
|
throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "A refreshed Codex task has multiple durable owners.");
|
|
5770
5567
|
}
|
|
@@ -5804,7 +5601,7 @@ export class GatewayService {
|
|
|
5804
5601
|
event.current.endpointGeneration &&
|
|
5805
5602
|
route.binding.routeHandle === requiredRoute.threadId &&
|
|
5806
5603
|
route.enabled &&
|
|
5807
|
-
route.
|
|
5604
|
+
route.state !== "stale");
|
|
5808
5605
|
const match = matches[0];
|
|
5809
5606
|
if (matches.length !== 1 || match === undefined) {
|
|
5810
5607
|
throw new BridgeError("CODEX_ENDPOINT_REFRESH_MISMATCH", "The registering Codex task was not durably installed exactly.");
|
|
@@ -5858,7 +5655,7 @@ export class GatewayService {
|
|
|
5858
5655
|
}
|
|
5859
5656
|
for (const route of activatedRoutes) {
|
|
5860
5657
|
requireNativeContinuity(route);
|
|
5861
|
-
await this.
|
|
5658
|
+
await this.setNativeSourceStatus(route.alias, route.state === "idle"
|
|
5862
5659
|
? "idle"
|
|
5863
5660
|
: route.state === "awaiting_approval"
|
|
5864
5661
|
? "waiting"
|
|
@@ -5898,10 +5695,7 @@ export class GatewayService {
|
|
|
5898
5695
|
for (const route of activatedRoutes) {
|
|
5899
5696
|
requireNativeContinuity(route);
|
|
5900
5697
|
}
|
|
5901
|
-
|
|
5902
|
-
// above is complete. This acknowledgement is deliberately the final
|
|
5903
|
-
// fallible activation step.
|
|
5904
|
-
adapter.acceptCompatibilityAttestation(event.attestation);
|
|
5698
|
+
adapter.activateEndpointGeneration(event.current.endpointGeneration);
|
|
5905
5699
|
}
|
|
5906
5700
|
catch (error) {
|
|
5907
5701
|
try {
|
|
@@ -5921,9 +5715,6 @@ export class GatewayService {
|
|
|
5921
5715
|
?.transitionKey === transitionKey) {
|
|
5922
5716
|
this.pendingSelectorEndpointActivations.delete(event.current.hostId);
|
|
5923
5717
|
}
|
|
5924
|
-
this.handledIncompatibleEndpointRefreshes.delete(event.current.hostId);
|
|
5925
|
-
this.activeCodexIncompatibility = undefined;
|
|
5926
|
-
this.incompatibleProviderSurfaces.delete(this.providerSurfaceKey("codex", event.current.hostId));
|
|
5927
5718
|
return false;
|
|
5928
5719
|
}
|
|
5929
5720
|
async onRouteState(source, event) {
|
|
@@ -5945,7 +5736,6 @@ export class GatewayService {
|
|
|
5945
5736
|
await this.store.observeConnector({
|
|
5946
5737
|
identity: source,
|
|
5947
5738
|
health: event.state === "stale" ? "degraded" : "healthy",
|
|
5948
|
-
compatibility: event.state === "stale" ? "expired" : "compatible",
|
|
5949
5739
|
protocol: adapter.protocol,
|
|
5950
5740
|
protocolVersion: adapter.protocolVersion,
|
|
5951
5741
|
...(event.safeErrorCode === undefined
|
|
@@ -5961,7 +5751,6 @@ export class GatewayService {
|
|
|
5961
5751
|
await this.store.observeRoute({
|
|
5962
5752
|
binding,
|
|
5963
5753
|
state: "stale",
|
|
5964
|
-
compatibility: "expired",
|
|
5965
5754
|
safeErrorCode: staleSafeCode,
|
|
5966
5755
|
});
|
|
5967
5756
|
}
|
|
@@ -5969,7 +5758,6 @@ export class GatewayService {
|
|
|
5969
5758
|
await this.store.observeRoute({
|
|
5970
5759
|
binding,
|
|
5971
5760
|
state: event.state,
|
|
5972
|
-
compatibility: "compatible",
|
|
5973
5761
|
...(event.safeErrorCode === undefined
|
|
5974
5762
|
? {}
|
|
5975
5763
|
: {
|
|
@@ -5979,15 +5767,15 @@ export class GatewayService {
|
|
|
5979
5767
|
}
|
|
5980
5768
|
await this.store.touchProgressWatchesForAlias(alias);
|
|
5981
5769
|
await this.changed();
|
|
5982
|
-
if (binding.provider
|
|
5983
|
-
await this.
|
|
5770
|
+
if (binding.provider !== "claude") {
|
|
5771
|
+
await this.setNativeSourceStatus(alias, event.state === "idle"
|
|
5984
5772
|
? "idle"
|
|
5985
5773
|
: event.state === "awaiting_approval"
|
|
5986
5774
|
? "waiting"
|
|
5987
5775
|
: "busy");
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5776
|
+
}
|
|
5777
|
+
if (binding.provider === "codex" && previousState === "stale" && event.state !== "stale") {
|
|
5778
|
+
adapter.rearmEndpointRefreshActivation?.(binding.endpointGeneration);
|
|
5991
5779
|
}
|
|
5992
5780
|
if (event.state === "idle" || event.state === "stale") {
|
|
5993
5781
|
const heldTimer = this.heldRedispatchTimers.get(alias);
|
|
@@ -5998,12 +5786,12 @@ export class GatewayService {
|
|
|
5998
5786
|
this.scheduleDispatch(alias);
|
|
5999
5787
|
}
|
|
6000
5788
|
}
|
|
6001
|
-
async
|
|
5789
|
+
async setNativeSourceStatus(alias, status, requireSuccess = false) {
|
|
6002
5790
|
const binding = this.routeBindings.get(alias);
|
|
6003
|
-
if (binding
|
|
5791
|
+
if (binding === undefined || binding.provider === "claude")
|
|
6004
5792
|
return;
|
|
6005
5793
|
const adapter = this.adapter("claude", binding.hostId);
|
|
6006
|
-
const update = adapter.
|
|
5794
|
+
const update = adapter.updateNativeSourcePeerStatus;
|
|
6007
5795
|
if (update === undefined) {
|
|
6008
5796
|
if (requireSuccess) {
|
|
6009
5797
|
throw new BridgeError("CODEX_ENDPOINT_REFRESH_NATIVE_STATUS_FAILED", "The native listener does not support refreshed Codex status activation.", true);
|
|
@@ -6102,30 +5890,6 @@ export class GatewayService {
|
|
|
6102
5890
|
}
|
|
6103
5891
|
async publicSnapshotLocked() {
|
|
6104
5892
|
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
5893
|
const peers = this.availablePeers.map((peer) => ({ ...peer }));
|
|
6130
5894
|
if (!arePublicAvailablePeerSnapshots(peers)) {
|
|
6131
5895
|
throw new BridgeError("INVALID_TRANSIENT_INVENTORY", "The transient peer inventory is unsafe.");
|
|
@@ -6154,11 +5918,6 @@ export class GatewayService {
|
|
|
6154
5918
|
return projectGatewayPublicSnapshot({
|
|
6155
5919
|
...base,
|
|
6156
5920
|
connectors: this.connectorsWithRegistry(base.connectors),
|
|
6157
|
-
...(compatibilityChecks.length === 0
|
|
6158
|
-
? {}
|
|
6159
|
-
: {
|
|
6160
|
-
compatibilityChecks: compatibilityChecks.map(projectPublicCompatibilityCheck),
|
|
6161
|
-
}),
|
|
6162
5921
|
availablePeers: peers,
|
|
6163
5922
|
activityEvents: this.activityEvents.map((event) => ({
|
|
6164
5923
|
...event,
|