agent-embassy 1.2.0 → 1.3.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 +24 -0
- package/CONTRIBUTING.md +25 -7
- package/README.md +20 -12
- package/README.zh-CN.md +14 -13
- package/SECURITY.md +55 -41
- package/dist/src/gateway/claude-helper-supervisor.js +0 -1
- package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
- package/dist/src/gateway/claude-runtime.d.ts +0 -2
- package/dist/src/gateway/claude-runtime.js +6 -17
- package/dist/src/gateway/claude-runtime.js.map +1 -1
- package/dist/src/gateway/cli-copy.d.ts +1 -1
- package/dist/src/gateway/cli-copy.en.d.ts +3 -1
- package/dist/src/gateway/cli-copy.en.js +5 -3
- package/dist/src/gateway/cli-copy.en.js.map +1 -1
- package/dist/src/gateway/cli-copy.js +2 -0
- package/dist/src/gateway/cli-copy.js.map +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +3 -1
- package/dist/src/gateway/cli-copy.zh-CN.js +5 -3
- package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/cli.d.ts +2 -5
- package/dist/src/gateway/cli.js +30 -67
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/codex-app-server.d.ts +1 -7
- package/dist/src/gateway/codex-app-server.js +5 -52
- package/dist/src/gateway/codex-app-server.js.map +1 -1
- package/dist/src/gateway/codex-local-transport.d.ts +4 -10
- package/dist/src/gateway/codex-local-transport.js +13 -16
- package/dist/src/gateway/codex-local-transport.js.map +1 -1
- package/dist/src/gateway/compatibility.d.ts +0 -27
- package/dist/src/gateway/compatibility.js +1 -101
- package/dist/src/gateway/compatibility.js.map +1 -1
- package/dist/src/gateway/config.d.ts +0 -3
- package/dist/src/gateway/config.js +0 -10
- package/dist/src/gateway/config.js.map +1 -1
- package/dist/src/gateway/control.d.ts +1 -20
- package/dist/src/gateway/control.js +2 -33
- 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 +6 -7
- package/dist/src/gateway/dashboard-copy.en.js +15 -16
- package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.js +6 -7
- package/dist/src/gateway/dashboard-copy.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +6 -7
- package/dist/src/gateway/dashboard-copy.zh-CN.js +15 -16
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/dashboard-model.d.ts +3 -7
- package/dist/src/gateway/dashboard-model.js +41 -16
- package/dist/src/gateway/dashboard-model.js.map +1 -1
- package/dist/src/gateway/dashboard.js +5 -7
- package/dist/src/gateway/dashboard.js.map +1 -1
- package/dist/src/gateway/live-dashboard-app/app.js +17 -45
- package/dist/src/gateway/live-dashboard-command.d.ts +5 -1
- package/dist/src/gateway/live-dashboard-command.js +8 -6
- package/dist/src/gateway/live-dashboard-command.js.map +1 -1
- package/dist/src/gateway/live-dashboard-http.d.ts +0 -4
- package/dist/src/gateway/live-dashboard-http.js +19 -93
- package/dist/src/gateway/live-dashboard-http.js.map +1 -1
- package/dist/src/gateway/live-dashboard-protocol.d.ts +1 -5
- package/dist/src/gateway/live-dashboard-protocol.js +0 -42
- package/dist/src/gateway/live-dashboard-protocol.js.map +1 -1
- package/dist/src/gateway/live-dashboard-server.d.ts +2 -2
- package/dist/src/gateway/live-dashboard-server.js +18 -4
- package/dist/src/gateway/live-dashboard-server.js.map +1 -1
- package/dist/src/gateway/live-dashboard.d.ts +3 -6
- package/dist/src/gateway/live-dashboard.js +6 -42
- package/dist/src/gateway/live-dashboard.js.map +1 -1
- package/dist/src/gateway/provenance-envelope.js +1 -1
- package/dist/src/gateway/provenance-envelope.js.map +1 -1
- package/dist/src/gateway/providers.d.ts +1 -21
- package/dist/src/gateway/providers.js +15 -261
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.js +0 -3
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +3 -15
- package/dist/src/gateway/service.js +76 -257
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/store.js +76 -18
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +0 -4
- package/dist/src/gateway/types.js.map +1 -1
- package/docs/GATEWAY-ARCHITECTURE.md +75 -63
- package/package.json +1 -1
- package/skills/embassy-peer/SKILL.md +25 -10
- package/dist/src/gateway/claude-compatibility-scratch.d.ts +0 -18
- package/dist/src/gateway/claude-compatibility-scratch.js +0 -158
- package/dist/src/gateway/claude-compatibility-scratch.js.map +0 -1
- package/dist/src/gateway/live-dashboard-bootstrap.d.ts +0 -36
- package/dist/src/gateway/live-dashboard-bootstrap.js +0 -176
- package/dist/src/gateway/live-dashboard-bootstrap.js.map +0 -1
- package/docs/DESIGN.md +0 -134
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { BridgeError } from "../errors.js";
|
|
3
3
|
import { KeyedMutex } from "../mutex.js";
|
|
4
|
-
import { certifiedCompatibilityVersions, compatibilitySurfaces,
|
|
4
|
+
import { certifiedCompatibilityVersions, compatibilitySurfaces, evaluateCompatibilityAttestation, isCompatibilityAttestation, } from "./compatibility.js";
|
|
5
5
|
import { createGatewayConversationId, isGatewaySnapshot, startGatewayControlServer, } from "./control.js";
|
|
6
6
|
import { publishGatewayDashboard } from "./dashboard.js";
|
|
7
7
|
import { createDeliveryMachine, isTerminalDeliveryMachine, projectDelivery, projectDeliveryWakeups, transitionDelivery, } from "./delivery-machine.js";
|
|
@@ -22,12 +22,6 @@ const NATIVE_HELD_THRESHOLD_MS = 1_000;
|
|
|
22
22
|
const RUNTIME_ACTIVITY_CAPACITY = 256;
|
|
23
23
|
const DELIVERY_SETTLEMENT_RETRY_MS = 250;
|
|
24
24
|
const DELIVERY_DASHBOARD_REFRESH_MS = 15_000;
|
|
25
|
-
const CLAUDE_CLEAN_PREWRITE_ROUTE_CODES = new Set([
|
|
26
|
-
"CLAUDE_PEER_TARGET_UNKNOWN",
|
|
27
|
-
"CLAUDE_PEER_TARGET_STALE",
|
|
28
|
-
"CLAUDE_PEER_TARGET_CHANGED",
|
|
29
|
-
"CLAUDE_PEER_WORKSPACE_UNATTESTED",
|
|
30
|
-
]);
|
|
31
25
|
function progressWatchActivity(text, conversationId, actorAlias, explicitIdleMinutes) {
|
|
32
26
|
const prefixedOpen = text.startsWith("TRACK:");
|
|
33
27
|
const completionSignal = text.startsWith("DONE:");
|
|
@@ -69,9 +63,6 @@ function endpointRefreshCallbackKey(event) {
|
|
|
69
63
|
? {}
|
|
70
64
|
: { safeErrorCode: probe.safeErrorCode }),
|
|
71
65
|
})),
|
|
72
|
-
...(event.attestation.certification === undefined
|
|
73
|
-
? {}
|
|
74
|
-
: { certification: { ...event.attestation.certification } }),
|
|
75
66
|
...(event.attestation.safeErrorCode === undefined
|
|
76
67
|
? {}
|
|
77
68
|
: { safeErrorCode: event.attestation.safeErrorCode }),
|
|
@@ -145,8 +136,6 @@ export class GatewayService {
|
|
|
145
136
|
activeDispatchByTarget = new Map();
|
|
146
137
|
scheduledDispatchTargets = new Set();
|
|
147
138
|
dispatchRunnerTargets = new Set();
|
|
148
|
-
/** Claude sends that proved no write wait for exact provider-idle evidence. */
|
|
149
|
-
claudePrewriteIdleWaitTargets = new Set();
|
|
150
139
|
heldRedispatchTimers = new Map();
|
|
151
140
|
pendingClaudeReplies = new Map();
|
|
152
141
|
deliveryTrackers = new Map();
|
|
@@ -285,8 +274,8 @@ export class GatewayService {
|
|
|
285
274
|
if (this.adapters.length > 0 &&
|
|
286
275
|
this.adapters.every((adapter) => adapter.compatibilitySurface !== undefined &&
|
|
287
276
|
adapter.runCompatibilityProbes !== undefined)) {
|
|
288
|
-
const
|
|
289
|
-
const incompatible =
|
|
277
|
+
const attestations = await this.runAutomaticCompatibilityProbesLocked();
|
|
278
|
+
const incompatible = attestations.find((surface) => surface.tier === "incompatible");
|
|
290
279
|
if (incompatible !== undefined) {
|
|
291
280
|
throw new BridgeError(incompatible.safeErrorCode ?? "COMPATIBILITY_CHECK_FAILED", "A local provider failed bounded compatibility attestation.");
|
|
292
281
|
}
|
|
@@ -427,7 +416,6 @@ export class GatewayService {
|
|
|
427
416
|
this.activeDispatchByTarget.clear();
|
|
428
417
|
this.scheduledDispatchTargets.clear();
|
|
429
418
|
this.dispatchRunnerTargets.clear();
|
|
430
|
-
this.claudePrewriteIdleWaitTargets.clear();
|
|
431
419
|
for (const timer of this.heldRedispatchTimers.values()) {
|
|
432
420
|
this.timers.clearTimeout(timer);
|
|
433
421
|
}
|
|
@@ -518,8 +506,6 @@ export class GatewayService {
|
|
|
518
506
|
}),
|
|
519
507
|
listSnapshot: async () => (await this.observeSnapshot()).snapshot,
|
|
520
508
|
observeSnapshot: async () => await this.observeSnapshot(),
|
|
521
|
-
compatibilityCheck: async () => await this.compatibilityCheck(),
|
|
522
|
-
compatibilityCertify: async (params) => await this.compatibilityCertify(params),
|
|
523
509
|
deliveryStatus: async (params) => await this.deliveryStatus(params.token),
|
|
524
510
|
untrack: async (params) => await this.exclusiveDecision(async () => {
|
|
525
511
|
const settled = await this.store.settleProgressWatch({
|
|
@@ -550,133 +536,7 @@ export class GatewayService {
|
|
|
550
536
|
async snapshot() {
|
|
551
537
|
return (await this.observeSnapshot()).snapshot;
|
|
552
538
|
}
|
|
553
|
-
async
|
|
554
|
-
return await this.mutex.run("service", async () => await this.compatibilityCheckLocked());
|
|
555
|
-
}
|
|
556
|
-
async compatibilityCertify(params) {
|
|
557
|
-
const plan = await this.mutex.run("service", async () => {
|
|
558
|
-
if (!this.running || this.closing) {
|
|
559
|
-
throw new BridgeError("COMPAT_PROVIDER_UNAVAILABLE", "Compatibility certification requires one running broker.");
|
|
560
|
-
}
|
|
561
|
-
const report = await this.compatibilityCheckLocked();
|
|
562
|
-
const codexRoutes = (await this.store.inspectPrivateCodexRoutes()).filter((route) => route.enabled);
|
|
563
|
-
const codexRoute = params.codexAlias === undefined
|
|
564
|
-
? codexRoutes.length === 1
|
|
565
|
-
? codexRoutes[0]
|
|
566
|
-
: undefined
|
|
567
|
-
: codexRoutes.find((route) => route.alias === params.codexAlias);
|
|
568
|
-
if (codexRoute === undefined) {
|
|
569
|
-
throw new BridgeError("COMPAT_CERTIFY_CODEX_ROUTE_REQUIRED", "Choose one exact registered Codex route for live certification.");
|
|
570
|
-
}
|
|
571
|
-
const adapters = new Map(this.adapters.map((adapter) => [adapter.identity.provider, adapter]));
|
|
572
|
-
return { adapters, codexRoute, report };
|
|
573
|
-
});
|
|
574
|
-
const certifiedAt = this.now().toISOString();
|
|
575
|
-
const updated = [];
|
|
576
|
-
for (const attestation of plan.report.surfaces) {
|
|
577
|
-
const adapter = plan.adapters.get(attestation.surface);
|
|
578
|
-
let certification;
|
|
579
|
-
if (attestation.tier === "incompatible" ||
|
|
580
|
-
adapter?.runCompatibilityCertification === undefined) {
|
|
581
|
-
certification = {
|
|
582
|
-
depth: attestation.surface === "claude"
|
|
583
|
-
? "wire"
|
|
584
|
-
: params.withTurn
|
|
585
|
-
? "turn"
|
|
586
|
-
: "thread_ops",
|
|
587
|
-
outcome: "fail",
|
|
588
|
-
certifiedAt,
|
|
589
|
-
safeErrorCode: attestation.safeErrorCode ??
|
|
590
|
-
`${attestation.surface.toUpperCase()}_CERTIFICATION_UNAVAILABLE`,
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
else {
|
|
594
|
-
try {
|
|
595
|
-
certification = await adapter.runCompatibilityCertification({
|
|
596
|
-
controllerStateRoot: this.store.rootDir,
|
|
597
|
-
withTurn: params.withTurn,
|
|
598
|
-
...(attestation.surface === "codex"
|
|
599
|
-
? { routeHandle: plan.codexRoute.binding.routeHandle }
|
|
600
|
-
: {}),
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
catch {
|
|
604
|
-
certification = {
|
|
605
|
-
depth: attestation.surface === "claude"
|
|
606
|
-
? "wire"
|
|
607
|
-
: params.withTurn
|
|
608
|
-
? "turn"
|
|
609
|
-
: "thread_ops",
|
|
610
|
-
outcome: "fail",
|
|
611
|
-
certifiedAt: this.now().toISOString(),
|
|
612
|
-
safeErrorCode: `${attestation.surface.toUpperCase()}_CERTIFICATION_FAILED`,
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
const expectedDepth = attestation.surface === "claude"
|
|
617
|
-
? "wire"
|
|
618
|
-
: params.withTurn
|
|
619
|
-
? "turn"
|
|
620
|
-
: "thread_ops";
|
|
621
|
-
if (!isCompatibilityCertification(certification) ||
|
|
622
|
-
certification.depth !== expectedDepth) {
|
|
623
|
-
certification = {
|
|
624
|
-
depth: expectedDepth,
|
|
625
|
-
outcome: "fail",
|
|
626
|
-
certifiedAt: this.now().toISOString(),
|
|
627
|
-
safeErrorCode: `${attestation.surface.toUpperCase()}_CERTIFICATION_EVIDENCE_INVALID`,
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
updated.push(attachCompatibilityCertification(attestation, certification));
|
|
631
|
-
}
|
|
632
|
-
return await this.mutex.run("service", async () => {
|
|
633
|
-
const currentCodexRoute = (await this.store.inspectPrivateCodexRoutes()).find((route) => route.enabled &&
|
|
634
|
-
route.alias === plan.codexRoute.alias &&
|
|
635
|
-
bindingKey(route.binding) === bindingKey(plan.codexRoute.binding));
|
|
636
|
-
const reconciled = updated.map((attestation) => {
|
|
637
|
-
if (attestation.surface === "codex" && currentCodexRoute === undefined) {
|
|
638
|
-
return attachCompatibilityCertification(attestation, {
|
|
639
|
-
depth: params.withTurn ? "turn" : "thread_ops",
|
|
640
|
-
outcome: "fail",
|
|
641
|
-
certifiedAt: this.now().toISOString(),
|
|
642
|
-
safeErrorCode: "CODEX_CERTIFICATION_ROUTE_CHANGED",
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
const observation = plan.adapters
|
|
646
|
-
.get(attestation.surface)
|
|
647
|
-
?.compatibilitySurface?.();
|
|
648
|
-
if (observation?.surface === attestation.surface &&
|
|
649
|
-
observation.version === attestation.version) {
|
|
650
|
-
return attestation;
|
|
651
|
-
}
|
|
652
|
-
return attachCompatibilityCertification(attestation, {
|
|
653
|
-
depth: attestation.surface === "claude"
|
|
654
|
-
? "wire"
|
|
655
|
-
: params.withTurn
|
|
656
|
-
? "turn"
|
|
657
|
-
: "thread_ops",
|
|
658
|
-
outcome: "fail",
|
|
659
|
-
certifiedAt: this.now().toISOString(),
|
|
660
|
-
safeErrorCode: `${attestation.surface.toUpperCase()}_CERTIFICATION_VERSION_CHANGED`,
|
|
661
|
-
});
|
|
662
|
-
});
|
|
663
|
-
const result = {
|
|
664
|
-
policy: plan.report.policy,
|
|
665
|
-
certified: reconciled.every((attestation) => attestation.certification?.outcome === "pass"),
|
|
666
|
-
withTurn: params.withTurn,
|
|
667
|
-
surfaces: reconciled,
|
|
668
|
-
};
|
|
669
|
-
if (!isCompatibilityCertificationReport(result)) {
|
|
670
|
-
throw new BridgeError("COMPAT_CERTIFICATION_REPORT_INVALID", "Compatibility certification evidence failed its closed report schema.");
|
|
671
|
-
}
|
|
672
|
-
for (const attestation of reconciled) {
|
|
673
|
-
await this.store.recordCompatibilityAttestation(attestation);
|
|
674
|
-
}
|
|
675
|
-
await this.changed();
|
|
676
|
-
return result;
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
async compatibilityCheckLocked() {
|
|
539
|
+
async runAutomaticCompatibilityProbesLocked() {
|
|
680
540
|
const bySurface = new Map(this.adapters.map((adapter) => {
|
|
681
541
|
if (adapter.compatibilitySurface === undefined ||
|
|
682
542
|
adapter.runCompatibilityProbes === undefined) {
|
|
@@ -695,39 +555,21 @@ export class GatewayService {
|
|
|
695
555
|
if (entry === undefined) {
|
|
696
556
|
throw new BridgeError("COMPAT_PROVIDER_UNAVAILABLE", "A required compatibility surface is unavailable.");
|
|
697
557
|
}
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
const checkedAt = cached?.checkedAt ?? this.now().toISOString();
|
|
701
|
-
let attestation = evaluateCompatibilityAttestation({
|
|
558
|
+
const probes = await entry.adapter.runCompatibilityProbes();
|
|
559
|
+
const attestation = evaluateCompatibilityAttestation({
|
|
702
560
|
surface,
|
|
703
561
|
version: entry.observation.version,
|
|
704
|
-
checkedAt,
|
|
705
|
-
policy: this.config.compatibilityPolicy ?? "observed",
|
|
562
|
+
checkedAt: this.now().toISOString(),
|
|
706
563
|
certifiedVersions: certifiedCompatibilityVersions[surface],
|
|
707
564
|
probes,
|
|
708
565
|
});
|
|
709
|
-
|
|
710
|
-
attestation.tier !== "incompatible") {
|
|
711
|
-
attestation = attachCompatibilityCertification(attestation, cached.certification);
|
|
712
|
-
}
|
|
713
|
-
if (cached === undefined ||
|
|
714
|
-
JSON.stringify(cached) !== JSON.stringify(attestation)) {
|
|
715
|
-
await this.store.recordCompatibilityAttestation(attestation);
|
|
716
|
-
}
|
|
566
|
+
await this.store.recordCompatibilityAttestation(attestation);
|
|
717
567
|
if (attestation.tier !== "incompatible") {
|
|
718
568
|
entry.adapter.acceptCompatibilityAttestation?.(attestation);
|
|
719
569
|
}
|
|
720
570
|
attestations.push(attestation);
|
|
721
571
|
}
|
|
722
|
-
|
|
723
|
-
policy: this.config.compatibilityPolicy ?? "observed",
|
|
724
|
-
compatible: attestations.every((attestation) => attestation.tier !== "incompatible"),
|
|
725
|
-
surfaces: attestations,
|
|
726
|
-
};
|
|
727
|
-
if (!isCompatibilityCheckReport(report)) {
|
|
728
|
-
throw new BridgeError("COMPAT_REPORT_INVALID", "Compatibility evidence failed its closed report schema.");
|
|
729
|
-
}
|
|
730
|
-
return report;
|
|
572
|
+
return attestations;
|
|
731
573
|
}
|
|
732
574
|
async observeSnapshot() {
|
|
733
575
|
return await this.mutex.run("service", async () => {
|
|
@@ -2746,13 +2588,22 @@ export class GatewayService {
|
|
|
2746
2588
|
const publicBefore = await this.discoveryPublicFingerprint();
|
|
2747
2589
|
this.candidates.clear();
|
|
2748
2590
|
const rowsByAlias = new Map();
|
|
2591
|
+
const collisionByRouteHandle = new Map();
|
|
2749
2592
|
const discoveredCandidates = [];
|
|
2750
2593
|
const observedSelected = new Set();
|
|
2751
2594
|
for (const adapter of this.adapters.filter((item) => item.identity.provider === "claude")) {
|
|
2752
|
-
|
|
2595
|
+
let discovered = {
|
|
2753
2596
|
peers: [],
|
|
2754
2597
|
complete: false,
|
|
2755
2598
|
};
|
|
2599
|
+
try {
|
|
2600
|
+
discovered = (await adapter.discoverClaudePeers?.()) ?? discovered;
|
|
2601
|
+
}
|
|
2602
|
+
catch {
|
|
2603
|
+
// Registry observation is dashboard evidence, not Claude mailbox
|
|
2604
|
+
// authority. The provider still revalidates the exact target before
|
|
2605
|
+
// any write.
|
|
2606
|
+
}
|
|
2756
2607
|
const grouped = new Map();
|
|
2757
2608
|
const byHandle = new Map();
|
|
2758
2609
|
for (const peer of discovered.peers) {
|
|
@@ -2772,6 +2623,8 @@ export class GatewayService {
|
|
|
2772
2623
|
if (peer === undefined)
|
|
2773
2624
|
continue;
|
|
2774
2625
|
if (matches.length !== 1) {
|
|
2626
|
+
for (const match of matches)
|
|
2627
|
+
collisionByRouteHandle.set(match.routeHandle, "PEER_ALIAS_COLLISION");
|
|
2775
2628
|
rowsByAlias.set(alias, {
|
|
2776
2629
|
peer,
|
|
2777
2630
|
adapter,
|
|
@@ -2788,6 +2641,7 @@ export class GatewayService {
|
|
|
2788
2641
|
continue;
|
|
2789
2642
|
}
|
|
2790
2643
|
if ((byHandle.get(peer.routeHandle)?.length ?? 0) !== 1) {
|
|
2644
|
+
collisionByRouteHandle.set(peer.routeHandle, "PEER_SESSION_COLLISION");
|
|
2791
2645
|
rowsByAlias.set(alias, {
|
|
2792
2646
|
peer,
|
|
2793
2647
|
adapter,
|
|
@@ -2813,6 +2667,8 @@ export class GatewayService {
|
|
|
2813
2667
|
const collision = this.routeBindings.get(alias);
|
|
2814
2668
|
if (collision !== undefined &&
|
|
2815
2669
|
bindingKey(collision) !== bindingKey(existing[1])) {
|
|
2670
|
+
collisionByRouteHandle.set(routeHandle, "PEER_ALIAS_COLLISION");
|
|
2671
|
+
collisionByRouteHandle.set(collision.routeHandle, "PEER_ALIAS_COLLISION");
|
|
2816
2672
|
rowsByAlias.set(alias, {
|
|
2817
2673
|
peer: candidate,
|
|
2818
2674
|
adapter,
|
|
@@ -2827,10 +2683,16 @@ export class GatewayService {
|
|
|
2827
2683
|
catch (error) {
|
|
2828
2684
|
if (!(error instanceof BridgeError))
|
|
2829
2685
|
throw error;
|
|
2686
|
+
// Only a real alias collision may wear that receipt; every other
|
|
2687
|
+
// rename failure is an unobserved peer, not a name conflict.
|
|
2688
|
+
const safeErrorCode = error.code === "ROUTE_ALIAS_COLLISION"
|
|
2689
|
+
? "PEER_ALIAS_COLLISION"
|
|
2690
|
+
: "PEER_NOT_OBSERVED";
|
|
2691
|
+
collisionByRouteHandle.set(routeHandle, safeErrorCode);
|
|
2830
2692
|
rowsByAlias.set(alias, {
|
|
2831
2693
|
peer: candidate,
|
|
2832
2694
|
adapter,
|
|
2833
|
-
safeErrorCode
|
|
2695
|
+
safeErrorCode,
|
|
2834
2696
|
});
|
|
2835
2697
|
}
|
|
2836
2698
|
}
|
|
@@ -2899,6 +2761,9 @@ export class GatewayService {
|
|
|
2899
2761
|
observedSelected.has(bindingKey(binding))) {
|
|
2900
2762
|
continue;
|
|
2901
2763
|
}
|
|
2764
|
+
const discoveryError = collisionByRouteHandle.get(binding.routeHandle);
|
|
2765
|
+
if (discoveryError === undefined)
|
|
2766
|
+
continue;
|
|
2902
2767
|
const inspection = await this.store.inspectPrivateRoute(alias);
|
|
2903
2768
|
if (inspection !== undefined &&
|
|
2904
2769
|
inspection.enabled &&
|
|
@@ -2906,9 +2771,9 @@ export class GatewayService {
|
|
|
2906
2771
|
await this.drainPreDeadlineDeliveryCallbacksLocked();
|
|
2907
2772
|
const inFlightSettlements = await this.planRouteInFlightSettlementsLocked(alias, {
|
|
2908
2773
|
unwrittenOutcome: "failed",
|
|
2909
|
-
safeErrorCode:
|
|
2774
|
+
safeErrorCode: discoveryError,
|
|
2910
2775
|
});
|
|
2911
|
-
const settlements = await this.store.invalidateRoute(binding,
|
|
2776
|
+
const settlements = await this.store.invalidateRoute(binding, discoveryError, inFlightSettlements);
|
|
2912
2777
|
for (const settlement of settlements) {
|
|
2913
2778
|
await this.applyTerminalSettlementLocked(settlement);
|
|
2914
2779
|
}
|
|
@@ -3135,6 +3000,11 @@ export class GatewayService {
|
|
|
3135
3000
|
async resolveSelectedClaudeDestination(selector) {
|
|
3136
3001
|
const discoveryChanged = await this.refreshClaudeDiscovery();
|
|
3137
3002
|
try {
|
|
3003
|
+
const selected = [...this.routeBindings.entries()].find(([alias, binding]) => binding.provider === "claude" &&
|
|
3004
|
+
(alias === selector ||
|
|
3005
|
+
binding.routeHandle.toLowerCase() === selector.toLowerCase()));
|
|
3006
|
+
if (selected !== undefined)
|
|
3007
|
+
return { alias: selected[0], discoveryChanged };
|
|
3138
3008
|
const candidate = this.claudeCandidate(selector);
|
|
3139
3009
|
if (candidate === undefined) {
|
|
3140
3010
|
throw new BridgeError("PEER_NOT_FOUND", "No unique compatible interactive peer matches that current name or session UUID.");
|
|
@@ -3327,9 +3197,6 @@ export class GatewayService {
|
|
|
3327
3197
|
this.routeBindings.set(alias, binding);
|
|
3328
3198
|
this.routeStates.set(alias, state);
|
|
3329
3199
|
this.bindingAliases.set(bindingKey(binding), alias);
|
|
3330
|
-
if (binding.provider === "claude" && state === "idle") {
|
|
3331
|
-
this.claudePrewriteIdleWaitTargets.delete(alias);
|
|
3332
|
-
}
|
|
3333
3200
|
}
|
|
3334
3201
|
forgetBinding(alias) {
|
|
3335
3202
|
const heldTimer = this.heldRedispatchTimers.get(alias);
|
|
@@ -3344,7 +3211,6 @@ export class GatewayService {
|
|
|
3344
3211
|
}
|
|
3345
3212
|
this.routeBindings.delete(alias);
|
|
3346
3213
|
this.routeStates.delete(alias);
|
|
3347
|
-
this.claudePrewriteIdleWaitTargets.delete(alias);
|
|
3348
3214
|
for (const [messageId, continuation] of this.providerTurnContinuations) {
|
|
3349
3215
|
if (continuation.targetAlias !== alias)
|
|
3350
3216
|
continue;
|
|
@@ -3453,8 +3319,10 @@ export class GatewayService {
|
|
|
3453
3319
|
enqueuedAt: input.enqueuedAt,
|
|
3454
3320
|
stallAt,
|
|
3455
3321
|
deadlineAt,
|
|
3456
|
-
// Deferred means the provider proved no write.
|
|
3457
|
-
// attempt per 500 ms slice
|
|
3322
|
+
// Deferred means the provider proved no write. This budgets one
|
|
3323
|
+
// attempt per 500 ms slice of this message's immutable deadline; a
|
|
3324
|
+
// Codex-bound retry spends one slice each, while a Claude-bound retry
|
|
3325
|
+
// backs off exponentially and spends far fewer of them.
|
|
3458
3326
|
maxCleanPrewriteRetries: Math.max(0, Math.ceil((deadlineAt - input.enqueuedAt) / 500)),
|
|
3459
3327
|
nativeReceipt: input.nativeReceipt !== undefined,
|
|
3460
3328
|
maxNativeReceiptCleanPrewriteRetries: DELIVERY_ACK_RETRY_DELAYS_MS.length,
|
|
@@ -4071,12 +3939,10 @@ export class GatewayService {
|
|
|
4071
3939
|
`Embassy 自动活跃检查 — ${action.conversationId}]\n` +
|
|
4072
3940
|
"Reply with the result or a status. / 请回复结果或状态。";
|
|
4073
3941
|
try {
|
|
4074
|
-
await this.enqueue(action.ownerAlias, action.workerAlias, text, false,
|
|
3942
|
+
await this.enqueue(action.ownerAlias, action.workerAlias, text, false, {
|
|
4075
3943
|
existingConversationId: action.conversationId,
|
|
4076
|
-
requestedHopCount: 0,
|
|
4077
3944
|
exposeDeliveryToken: false,
|
|
4078
3945
|
skipProgressWatchActivity: true,
|
|
4079
|
-
preserveConversationHop: true,
|
|
4080
3946
|
progressWatchNudge: {
|
|
4081
3947
|
conversationId: action.conversationId,
|
|
4082
3948
|
nudgeNumber: action.nudgeNumber,
|
|
@@ -4123,8 +3989,7 @@ export class GatewayService {
|
|
|
4123
3989
|
}
|
|
4124
3990
|
if (!isTerminalDeliveryMachine(tracker.machine) &&
|
|
4125
3991
|
tracker.machine.dispatchRetryAt !== null &&
|
|
4126
|
-
tracker.machine.dispatchRetryAt <= now
|
|
4127
|
-
!this.claudePrewriteIdleWaitTargets.has(tracker.targetAlias)) {
|
|
3992
|
+
tracker.machine.dispatchRetryAt <= now) {
|
|
4128
3993
|
if (!this.dispatchRunnerTargets.has(tracker.targetAlias)) {
|
|
4129
3994
|
this.scheduleDispatch(tracker.targetAlias);
|
|
4130
3995
|
}
|
|
@@ -4213,7 +4078,6 @@ export class GatewayService {
|
|
|
4213
4078
|
if (wakeups.deadlineAt !== undefined)
|
|
4214
4079
|
consider(wakeups.deadlineAt);
|
|
4215
4080
|
if (wakeups.dispatchRetryAt !== undefined &&
|
|
4216
|
-
!this.claudePrewriteIdleWaitTargets.has(tracker.targetAlias) &&
|
|
4217
4081
|
!this.scheduledDispatchTargets.has(tracker.targetAlias) &&
|
|
4218
4082
|
!this.dispatchRunnerTargets.has(tracker.targetAlias)) {
|
|
4219
4083
|
consider(wakeups.dispatchRetryAt);
|
|
@@ -4311,7 +4175,7 @@ export class GatewayService {
|
|
|
4311
4175
|
await this.verifyCodex(params.fromAlias, params.threadId);
|
|
4312
4176
|
const resolved = await this.resolveSelectedClaudeDestination(params.toAlias);
|
|
4313
4177
|
discoveryChanged = resolved.discoveryChanged;
|
|
4314
|
-
return this.acceptedControlResult(await this.enqueue(params.fromAlias, resolved.alias, params.text, params.expectsReply,
|
|
4178
|
+
return this.acceptedControlResult(await this.enqueue(params.fromAlias, resolved.alias, params.text, params.expectsReply, params.trackIdleMinutes === undefined
|
|
4315
4179
|
? {}
|
|
4316
4180
|
: { trackIdleMinutes: params.trackIdleMinutes }));
|
|
4317
4181
|
}
|
|
@@ -4330,7 +4194,7 @@ export class GatewayService {
|
|
|
4330
4194
|
try {
|
|
4331
4195
|
await this.verifyClaude(params.fromAlias, params.replyAddress);
|
|
4332
4196
|
const steer = this.config.steeringEnabled && params.text.startsWith("STEER:");
|
|
4333
|
-
return this.acceptedControlResult(await this.enqueue(params.fromAlias, params.toAlias, params.text, steer ? false : params.expectsReply,
|
|
4197
|
+
return this.acceptedControlResult(await this.enqueue(params.fromAlias, params.toAlias, params.text, steer ? false : params.expectsReply, {
|
|
4334
4198
|
...(steer ? { steer: true } : {}),
|
|
4335
4199
|
...(params.trackIdleMinutes === undefined
|
|
4336
4200
|
? {}
|
|
@@ -4359,10 +4223,9 @@ export class GatewayService {
|
|
|
4359
4223
|
throw new BridgeError("ROUTE_MISMATCH", "The caller is not a conversation participant.");
|
|
4360
4224
|
const result = caller.kind === "codex" &&
|
|
4361
4225
|
this.nativeIngressByConversation.has(conversation.id)
|
|
4362
|
-
? await this.enqueueNativeReply(conversation, from, params.text,
|
|
4363
|
-
: await this.enqueue(from, to, params.text, false,
|
|
4226
|
+
? await this.enqueueNativeReply(conversation, from, params.text, true, params.trackIdleMinutes)
|
|
4227
|
+
: await this.enqueue(from, to, params.text, false, {
|
|
4364
4228
|
existingConversationId: conversation.id,
|
|
4365
|
-
requestedHopCount: conversation.lastHopCount + 1,
|
|
4366
4229
|
...(params.trackIdleMinutes === undefined
|
|
4367
4230
|
? {}
|
|
4368
4231
|
: { trackIdleMinutes: params.trackIdleMinutes }),
|
|
@@ -4374,7 +4237,7 @@ export class GatewayService {
|
|
|
4374
4237
|
}
|
|
4375
4238
|
});
|
|
4376
4239
|
}
|
|
4377
|
-
async enqueue(sourceAlias, targetAlias, text, expectsReply,
|
|
4240
|
+
async enqueue(sourceAlias, targetAlias, text, expectsReply, options = {}) {
|
|
4378
4241
|
if (this.codexSuccessionPoisoned.has(sourceAlias) ||
|
|
4379
4242
|
this.codexSuccessionPoisoned.has(targetAlias)) {
|
|
4380
4243
|
throw new BridgeError("CODEX_SUCCESSION_RECOVERY_REQUIRED", "No message can be accepted while Codex succession requires manual recovery.");
|
|
@@ -4397,11 +4260,8 @@ export class GatewayService {
|
|
|
4397
4260
|
targetAlias,
|
|
4398
4261
|
expectsReply,
|
|
4399
4262
|
nextSequence: 0,
|
|
4400
|
-
lastHopCount: 0,
|
|
4401
4263
|
lastActivityAt: this.now().toISOString(),
|
|
4402
4264
|
};
|
|
4403
|
-
const hopCount = options.requestedHopCount ??
|
|
4404
|
-
(isReply ? conversation.lastHopCount + 1 : 0);
|
|
4405
4265
|
const sequence = conversation.nextSequence;
|
|
4406
4266
|
const enqueuedAt = this.now().getTime();
|
|
4407
4267
|
const deliveryToken = (options.exposeDeliveryToken ?? true)
|
|
@@ -4414,7 +4274,6 @@ export class GatewayService {
|
|
|
4414
4274
|
body: text,
|
|
4415
4275
|
dedupeKey: `${conversationId}:${sequence}`,
|
|
4416
4276
|
conversationIdSuffix: conversationId.slice(-8),
|
|
4417
|
-
hopCount,
|
|
4418
4277
|
deadlineAt,
|
|
4419
4278
|
...(options.steer === true ? { steer: true } : {}),
|
|
4420
4279
|
...(options.skipProgressWatchActivity === true
|
|
@@ -4435,15 +4294,10 @@ export class GatewayService {
|
|
|
4435
4294
|
if (queued.pair === true)
|
|
4436
4295
|
conversation.pair = true;
|
|
4437
4296
|
conversation.nextSequence += 1;
|
|
4438
|
-
if (options.preserveConversationHop !== true) {
|
|
4439
|
-
conversation.lastHopCount = hopCount;
|
|
4440
|
-
}
|
|
4441
4297
|
conversation.lastActivityAt = this.now().toISOString();
|
|
4442
4298
|
this.messageContexts.set(queued.messageId, {
|
|
4443
4299
|
conversationId,
|
|
4444
|
-
isReply,
|
|
4445
4300
|
expectsReply,
|
|
4446
|
-
hopCount,
|
|
4447
4301
|
sequence,
|
|
4448
4302
|
targetBindingKey: bindingKey(target),
|
|
4449
4303
|
authorization: "selected_route",
|
|
@@ -4462,7 +4316,6 @@ export class GatewayService {
|
|
|
4462
4316
|
this.pendingClaudeReplies.set(bindingKey(target), {
|
|
4463
4317
|
conversationId,
|
|
4464
4318
|
bindingKey: bindingKey(target),
|
|
4465
|
-
hopCount,
|
|
4466
4319
|
deadlineAt,
|
|
4467
4320
|
tainted: false,
|
|
4468
4321
|
});
|
|
@@ -4476,7 +4329,7 @@ export class GatewayService {
|
|
|
4476
4329
|
...(deliveryToken === undefined ? {} : { deliveryToken }),
|
|
4477
4330
|
};
|
|
4478
4331
|
}
|
|
4479
|
-
async enqueueNativeReply(conversation, sourceAlias, text,
|
|
4332
|
+
async enqueueNativeReply(conversation, sourceAlias, text, exposeDeliveryToken = true, trackIdleMinutes) {
|
|
4480
4333
|
this.pruneTransient();
|
|
4481
4334
|
const capability = this.nativeIngressByConversation.get(conversation.id);
|
|
4482
4335
|
if (capability === undefined ||
|
|
@@ -4500,7 +4353,6 @@ export class GatewayService {
|
|
|
4500
4353
|
body: text,
|
|
4501
4354
|
dedupeKey: `${conversation.id}:${sequence}`,
|
|
4502
4355
|
conversationIdSuffix: conversation.id.slice(-8),
|
|
4503
|
-
hopCount: requestedHopCount,
|
|
4504
4356
|
deadlineAt,
|
|
4505
4357
|
...(conversation.pair === true ? { pair: true } : {}),
|
|
4506
4358
|
progressWatch: progressWatchActivity(text, conversation.id, sourceAlias, trackIdleMinutes),
|
|
@@ -4509,13 +4361,10 @@ export class GatewayService {
|
|
|
4509
4361
|
throw new BridgeError("MESSAGE_REJECTED", "The native reply was not accepted.");
|
|
4510
4362
|
}
|
|
4511
4363
|
conversation.nextSequence += 1;
|
|
4512
|
-
conversation.lastHopCount = requestedHopCount;
|
|
4513
4364
|
conversation.lastActivityAt = this.now().toISOString();
|
|
4514
4365
|
this.messageContexts.set(queued.messageId, {
|
|
4515
4366
|
conversationId: conversation.id,
|
|
4516
|
-
isReply: true,
|
|
4517
4367
|
expectsReply: false,
|
|
4518
|
-
hopCount: requestedHopCount,
|
|
4519
4368
|
sequence,
|
|
4520
4369
|
targetBindingKey: bindingKey(capability.binding),
|
|
4521
4370
|
nativeReplyBinding: { ...capability.binding },
|
|
@@ -4547,7 +4396,7 @@ export class GatewayService {
|
|
|
4547
4396
|
* native-ingress boundary; this preserves the exact connector generation
|
|
4548
4397
|
* without recreating route authority or accepting a new unsolicited send.
|
|
4549
4398
|
*/
|
|
4550
|
-
async enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, text
|
|
4399
|
+
async enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, text) {
|
|
4551
4400
|
const target = await this.store.resolveRoute(conversation.sourceAlias);
|
|
4552
4401
|
if (sourceBinding.provider !== "claude" ||
|
|
4553
4402
|
target.provider !== "codex" ||
|
|
@@ -4566,7 +4415,6 @@ export class GatewayService {
|
|
|
4566
4415
|
body: text,
|
|
4567
4416
|
dedupeKey: `${conversation.id}:${sequence}`,
|
|
4568
4417
|
conversationIdSuffix: conversation.id.slice(-8),
|
|
4569
|
-
hopCount: requestedHopCount,
|
|
4570
4418
|
deadlineAt,
|
|
4571
4419
|
authorizedPairTeardownReply: true,
|
|
4572
4420
|
progressWatch: progressWatchActivity(text, conversation.id, conversation.targetAlias),
|
|
@@ -4575,16 +4423,13 @@ export class GatewayService {
|
|
|
4575
4423
|
throw new BridgeError("MESSAGE_REJECTED", "The retained provider reply was not accepted.");
|
|
4576
4424
|
}
|
|
4577
4425
|
conversation.nextSequence += 1;
|
|
4578
|
-
conversation.lastHopCount = requestedHopCount;
|
|
4579
4426
|
conversation.lastActivityAt = this.now().toISOString();
|
|
4580
4427
|
// The old edge has already been removed. Retain only this already-observed
|
|
4581
4428
|
// reply attempt; the conversation no longer grants pair reply authority.
|
|
4582
4429
|
delete conversation.pair;
|
|
4583
4430
|
this.messageContexts.set(queued.messageId, {
|
|
4584
4431
|
conversationId: conversation.id,
|
|
4585
|
-
isReply: true,
|
|
4586
4432
|
expectsReply: false,
|
|
4587
|
-
hopCount: requestedHopCount,
|
|
4588
4433
|
sequence,
|
|
4589
4434
|
targetBindingKey: bindingKey(target),
|
|
4590
4435
|
authorization: "selected_route",
|
|
@@ -4695,15 +4540,12 @@ export class GatewayService {
|
|
|
4695
4540
|
targetAlias: item.targetAlias,
|
|
4696
4541
|
expectsReply: false,
|
|
4697
4542
|
nextSequence: 1,
|
|
4698
|
-
lastHopCount: item.hopCount,
|
|
4699
4543
|
lastActivityAt: this.now().toISOString(),
|
|
4700
4544
|
...(item.pair === true ? { pair: true } : {}),
|
|
4701
4545
|
};
|
|
4702
4546
|
const context = {
|
|
4703
4547
|
conversationId,
|
|
4704
|
-
isReply: false,
|
|
4705
4548
|
expectsReply: false,
|
|
4706
|
-
hopCount: item.hopCount,
|
|
4707
4549
|
sequence: 0,
|
|
4708
4550
|
targetBindingKey: bindingKey(binding),
|
|
4709
4551
|
authorization: "selected_route",
|
|
@@ -4726,8 +4568,7 @@ export class GatewayService {
|
|
|
4726
4568
|
if (!this.running ||
|
|
4727
4569
|
this.closing ||
|
|
4728
4570
|
this.codexSuccessionDispatchFrozen.has(targetAlias) ||
|
|
4729
|
-
this.codexSuccessionPoisoned.has(targetAlias)
|
|
4730
|
-
this.claudePrewriteIdleWaitTargets.has(targetAlias)) {
|
|
4571
|
+
this.codexSuccessionPoisoned.has(targetAlias)) {
|
|
4731
4572
|
return;
|
|
4732
4573
|
}
|
|
4733
4574
|
if (await this.processLifecycleLocked()) {
|
|
@@ -4785,8 +4626,8 @@ export class GatewayService {
|
|
|
4785
4626
|
}
|
|
4786
4627
|
if (context.authorization === "selected_route") {
|
|
4787
4628
|
const routeState = this.routeStates.get(targetAlias);
|
|
4788
|
-
const dispatchable =
|
|
4789
|
-
|
|
4629
|
+
const dispatchable = binding.provider === "claude" ||
|
|
4630
|
+
routeState === "idle" ||
|
|
4790
4631
|
(binding.provider === "codex" &&
|
|
4791
4632
|
item.steer === true &&
|
|
4792
4633
|
routeState === "busy");
|
|
@@ -4814,8 +4655,8 @@ export class GatewayService {
|
|
|
4814
4655
|
this.scheduleDispatch(targetAlias);
|
|
4815
4656
|
return;
|
|
4816
4657
|
}
|
|
4817
|
-
const storedDispatchable =
|
|
4818
|
-
|
|
4658
|
+
const storedDispatchable = binding.provider === "claude" ||
|
|
4659
|
+
inspection?.state === "idle" ||
|
|
4819
4660
|
(binding.provider === "codex" &&
|
|
4820
4661
|
item.steer === true &&
|
|
4821
4662
|
inspection?.state === "busy");
|
|
@@ -4948,10 +4789,19 @@ export class GatewayService {
|
|
|
4948
4789
|
const currentTargetAlias = this.bindingAliases.get(bindingKey(binding)) ?? targetAlias;
|
|
4949
4790
|
const now = this.now().getTime();
|
|
4950
4791
|
const deferredSafeErrorCode = safeCode(result.safeErrorCode, "PROVIDER_DISPATCH_DEFERRED");
|
|
4792
|
+
const retryDelayMs = binding.provider === "claude"
|
|
4793
|
+
? Math.min(500 * 2 ** (tracker.machine.dispatchAttempt - 1), 300_000)
|
|
4794
|
+
: 500;
|
|
4795
|
+
// Only Claude's backoff is clamped inside the deadline, so a long delay
|
|
4796
|
+
// still buys one last attempt. A Codex-bound retry keeps its exact
|
|
4797
|
+
// unclamped slice: a slice past the deadline must settle now, not wait.
|
|
4798
|
+
const retryAt = binding.provider === "claude"
|
|
4799
|
+
? Math.min(now + retryDelayMs, tracker.deadlineAt - 1)
|
|
4800
|
+
: now + retryDelayMs;
|
|
4951
4801
|
await this.advanceDeliveryLocked(item.messageId, {
|
|
4952
4802
|
type: "dispatch_clean_prewrite_failed",
|
|
4953
4803
|
at: now,
|
|
4954
|
-
retryAt
|
|
4804
|
+
retryAt,
|
|
4955
4805
|
safeErrorCode: deferredSafeErrorCode,
|
|
4956
4806
|
});
|
|
4957
4807
|
tracker.deliverySafeErrorCode = deferredSafeErrorCode;
|
|
@@ -4979,12 +4829,7 @@ export class GatewayService {
|
|
|
4979
4829
|
}
|
|
4980
4830
|
if (requeued.status === "requeued") {
|
|
4981
4831
|
if (context.authorization === "selected_route") {
|
|
4982
|
-
|
|
4983
|
-
CLAUDE_CLEAN_PREWRITE_ROUTE_CODES.has(deferredSafeErrorCode);
|
|
4984
|
-
if (waitForClaudeIdle) {
|
|
4985
|
-
this.claudePrewriteIdleWaitTargets.add(currentTargetAlias);
|
|
4986
|
-
}
|
|
4987
|
-
else if (!steeringWhileBusy) {
|
|
4832
|
+
if (binding.provider === "codex" && !steeringWhileBusy) {
|
|
4988
4833
|
this.routeStates.set(currentTargetAlias, "idle");
|
|
4989
4834
|
await this.store.observeRoute({
|
|
4990
4835
|
binding,
|
|
@@ -4992,7 +4837,7 @@ export class GatewayService {
|
|
|
4992
4837
|
compatibility: "compatible",
|
|
4993
4838
|
});
|
|
4994
4839
|
}
|
|
4995
|
-
else {
|
|
4840
|
+
else if (binding.provider === "codex") {
|
|
4996
4841
|
this.scheduleHeldRedispatch(currentTargetAlias);
|
|
4997
4842
|
}
|
|
4998
4843
|
await this.setNativeCodexStatus(currentTargetAlias, "waiting");
|
|
@@ -5024,8 +4869,7 @@ export class GatewayService {
|
|
|
5024
4869
|
scheduleDispatch(targetAlias) {
|
|
5025
4870
|
if (this.closing ||
|
|
5026
4871
|
this.codexSuccessionDispatchFrozen.has(targetAlias) ||
|
|
5027
|
-
this.codexSuccessionPoisoned.has(targetAlias)
|
|
5028
|
-
this.claudePrewriteIdleWaitTargets.has(targetAlias)) {
|
|
4872
|
+
this.codexSuccessionPoisoned.has(targetAlias)) {
|
|
5029
4873
|
return;
|
|
5030
4874
|
}
|
|
5031
4875
|
if (this.scheduledDispatchTargets.has(targetAlias))
|
|
@@ -5198,7 +5042,7 @@ export class GatewayService {
|
|
|
5198
5042
|
conversation !== undefined) {
|
|
5199
5043
|
try {
|
|
5200
5044
|
if (this.nativeIngressByConversation.has(conversation.id)) {
|
|
5201
|
-
await this.enqueueNativeReply(conversation, conversation.targetAlias, replyText,
|
|
5045
|
+
await this.enqueueNativeReply(conversation, conversation.targetAlias, replyText, false);
|
|
5202
5046
|
}
|
|
5203
5047
|
else {
|
|
5204
5048
|
const sourceBinding = this.routeBindings.get(conversation.targetAlias);
|
|
@@ -5213,12 +5057,11 @@ export class GatewayService {
|
|
|
5213
5057
|
}));
|
|
5214
5058
|
if ((!sourceStillRoutable || !pairStillActive) &&
|
|
5215
5059
|
sourceBinding?.provider === "claude") {
|
|
5216
|
-
await this.enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, replyText
|
|
5060
|
+
await this.enqueueObservedClaudeReplyAfterRouteTeardownLocked(conversation, sourceBinding, replyText);
|
|
5217
5061
|
}
|
|
5218
5062
|
else {
|
|
5219
|
-
await this.enqueue(conversation.targetAlias, conversation.sourceAlias, replyText, false,
|
|
5063
|
+
await this.enqueue(conversation.targetAlias, conversation.sourceAlias, replyText, false, {
|
|
5220
5064
|
existingConversationId: conversation.id,
|
|
5221
|
-
requestedHopCount: context.hopCount + 1,
|
|
5222
5065
|
exposeDeliveryToken: false,
|
|
5223
5066
|
});
|
|
5224
5067
|
}
|
|
@@ -5279,9 +5122,8 @@ export class GatewayService {
|
|
|
5279
5122
|
if (conversation === undefined)
|
|
5280
5123
|
return;
|
|
5281
5124
|
this.pendingClaudeReplies.delete(key);
|
|
5282
|
-
await this.enqueue(conversation.targetAlias, conversation.sourceAlias, event.text, false,
|
|
5125
|
+
await this.enqueue(conversation.targetAlias, conversation.sourceAlias, event.text, false, {
|
|
5283
5126
|
existingConversationId: conversation.id,
|
|
5284
|
-
requestedHopCount: pending.hopCount + 1,
|
|
5285
5127
|
exposeDeliveryToken: false,
|
|
5286
5128
|
});
|
|
5287
5129
|
}
|
|
@@ -5324,7 +5166,6 @@ export class GatewayService {
|
|
|
5324
5166
|
body: event.text,
|
|
5325
5167
|
dedupeKey: `${conversationId}:0`,
|
|
5326
5168
|
conversationIdSuffix: conversationId.slice(-8),
|
|
5327
|
-
hopCount: 0,
|
|
5328
5169
|
deadlineAt,
|
|
5329
5170
|
...(steer ? { steer: true } : {}),
|
|
5330
5171
|
progressWatch: progressWatchActivity(event.text, conversationId, event.sourceAlias),
|
|
@@ -5342,16 +5183,13 @@ export class GatewayService {
|
|
|
5342
5183
|
targetAlias: event.targetAlias,
|
|
5343
5184
|
expectsReply: !steer,
|
|
5344
5185
|
nextSequence: 1,
|
|
5345
|
-
lastHopCount: 0,
|
|
5346
5186
|
lastActivityAt: this.now().toISOString(),
|
|
5347
5187
|
...(queued.pair === true ? { pair: true } : {}),
|
|
5348
5188
|
};
|
|
5349
5189
|
this.conversations.set(conversationId, conversation);
|
|
5350
5190
|
this.messageContexts.set(queued.messageId, {
|
|
5351
5191
|
conversationId,
|
|
5352
|
-
isReply: false,
|
|
5353
5192
|
expectsReply: !steer,
|
|
5354
|
-
hopCount: 0,
|
|
5355
5193
|
sequence: 0,
|
|
5356
5194
|
targetBindingKey: bindingKey(target),
|
|
5357
5195
|
authorization: "selected_route",
|
|
@@ -5824,9 +5662,6 @@ export class GatewayService {
|
|
|
5824
5662
|
const [alias, binding] = entry;
|
|
5825
5663
|
const previousState = this.routeStates.get(alias);
|
|
5826
5664
|
this.routeStates.set(alias, event.state);
|
|
5827
|
-
if (binding.provider === "claude" && event.state === "idle") {
|
|
5828
|
-
this.claudePrewriteIdleWaitTargets.delete(alias);
|
|
5829
|
-
}
|
|
5830
5665
|
const staleSafeCode = binding.provider === "codex"
|
|
5831
5666
|
? "CODEX_ROUTE_STALE"
|
|
5832
5667
|
: safeCode(event.safeErrorCode, "ROUTE_STALE");
|
|
@@ -6037,21 +5872,6 @@ export class GatewayService {
|
|
|
6037
5872
|
},
|
|
6038
5873
|
];
|
|
6039
5874
|
});
|
|
6040
|
-
const certificationAlerts = compatibilityChecks.flatMap((attestation) => {
|
|
6041
|
-
const certification = attestation.certification;
|
|
6042
|
-
if (certification?.outcome !== "fail")
|
|
6043
|
-
return [];
|
|
6044
|
-
const adapter = this.adapters.find((candidate) => candidate.identity.provider === attestation.surface);
|
|
6045
|
-
return [
|
|
6046
|
-
{
|
|
6047
|
-
code: "COMPATIBILITY_CERTIFICATION_FAILED",
|
|
6048
|
-
severity: "error",
|
|
6049
|
-
timestamp: certification.certifiedAt,
|
|
6050
|
-
provider: attestation.surface,
|
|
6051
|
-
host: adapter?.identity.hostId ?? "this-mac",
|
|
6052
|
-
},
|
|
6053
|
-
];
|
|
6054
|
-
});
|
|
6055
5875
|
return projectGatewayPublicSnapshot({
|
|
6056
5876
|
...base,
|
|
6057
5877
|
...(compatibilityChecks.length === 0 ? {} : { compatibilityChecks }),
|
|
@@ -6067,7 +5887,6 @@ export class GatewayService {
|
|
|
6067
5887
|
alerts: [
|
|
6068
5888
|
...base.alerts,
|
|
6069
5889
|
...this.runtimeAlerts.map((alert) => ({ ...alert })),
|
|
6070
|
-
...certificationAlerts,
|
|
6071
5890
|
...stalledAlerts,
|
|
6072
5891
|
],
|
|
6073
5892
|
});
|