agent-embassy 3.0.0 → 3.1.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 +21 -0
- package/CONTRIBUTING.md +4 -2
- package/README.md +26 -7
- package/SECURITY.md +17 -4
- package/dist/src/gateway/claude-helper-supervisor.js +36 -9
- package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
- package/dist/src/gateway/claude-helper.js.map +1 -1
- package/dist/src/gateway/claude-peer.js +1 -1
- package/dist/src/gateway/claude-peer.js.map +1 -1
- package/dist/src/gateway/cli.d.ts +2 -3
- package/dist/src/gateway/cli.js +135 -106
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/config.js +1 -1
- package/dist/src/gateway/config.js.map +1 -1
- package/dist/src/gateway/control.d.ts +23 -5
- package/dist/src/gateway/control.js +41 -11
- package/dist/src/gateway/control.js.map +1 -1
- package/dist/src/gateway/peer-protocol.d.ts +5 -0
- package/dist/src/gateway/peer-protocol.js +9 -0
- package/dist/src/gateway/peer-protocol.js.map +1 -1
- package/dist/src/gateway/providers.d.ts +4 -3
- package/dist/src/gateway/providers.js +33 -30
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +1 -2
- package/dist/src/gateway/server.js +1 -4
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +6 -8
- package/dist/src/gateway/service.js +313 -252
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/status-view.d.ts +2 -50
- package/dist/src/gateway/status-view.js +66 -70
- package/dist/src/gateway/status-view.js.map +1 -1
- package/dist/src/gateway/store.d.ts +4 -1
- package/dist/src/gateway/store.js +12 -11
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +17 -4
- package/dist/src/gateway/types.js +27 -36
- package/dist/src/gateway/types.js.map +1 -1
- package/docs/CONFIGURATION.md +3 -3
- package/docs/GATEWAY-ARCHITECTURE.md +27 -21
- package/package.json +1 -1
- package/skills/embassy-peer/SKILL.md +7 -5
- package/dist/src/gateway/claude-helper-client.d.ts +0 -2
- package/dist/src/gateway/claude-helper-client.js +0 -2
- package/dist/src/gateway/claude-helper-client.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { BridgeError } from "../errors.js";
|
|
3
3
|
import { createGatewayConversationId, startGatewayControlServer, GATEWAY_EPHEMERAL_PEER_TTL_DEFAULT_MS, } from "./control.js";
|
|
4
|
-
import { PeerConnectionLostError, spawnPeerClient } from "./peer-client.js";
|
|
5
|
-
import { peerRouteRef } from "./peer-protocol.js";
|
|
4
|
+
import { PeerConnectionLostError, PeerRequestError, spawnPeerClient } from "./peer-client.js";
|
|
5
|
+
import { isPeerHandoffRefusal, peerRouteRef } from "./peer-protocol.js";
|
|
6
6
|
import { GatewayStore } from "./store.js";
|
|
7
|
-
import { CONNECTOR_OBSERVATION_STALE_AFTER_MS, directionId, GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET, gatewayPublicSnapshotLimits,
|
|
7
|
+
import { CONNECTOR_OBSERVATION_STALE_AFTER_MS, gatewayHealthWord, directionId, GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET, gatewayPublicSnapshotLimits, parseDirection, projectGatewayPublicSnapshot, } from "./types.js";
|
|
8
8
|
const SAFE_CODE = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
9
9
|
const PRIVATE_HANDLE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
|
|
10
10
|
const PUBLIC_ALIAS = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
|
|
@@ -121,9 +121,6 @@ function conversationIdForSuffix(suffix) {
|
|
|
121
121
|
const fresh = createGatewayConversationId();
|
|
122
122
|
return suffix === undefined ? fresh : `${fresh.slice(0, -8)}${suffix}`;
|
|
123
123
|
}
|
|
124
|
-
function bodyHash(body) {
|
|
125
|
-
return createHash("sha256").update(body, "utf8").digest("hex");
|
|
126
|
-
}
|
|
127
124
|
function peerCatalogAuthorityChanged(prior, current) {
|
|
128
125
|
const authority = (catalog) => JSON.stringify(catalog.routes.map(({ state: _state, queueDepth: _depth, lastSeenAt: _seen, safeErrorCode: _code, ...route }) => route).sort((a, b) => a.ref.localeCompare(b.ref)));
|
|
129
126
|
return prior === undefined || authority(prior) !== authority(current);
|
|
@@ -151,7 +148,7 @@ export class GatewayService {
|
|
|
151
148
|
spawnPeer;
|
|
152
149
|
peerClients = new Map();
|
|
153
150
|
peerCatalogs = new Map();
|
|
154
|
-
|
|
151
|
+
routeCleanupAliases = new Set();
|
|
155
152
|
/** View-only freshness; never consulted by routing or write authorization. */
|
|
156
153
|
peerRouteViews = new Map();
|
|
157
154
|
connectors = new Map();
|
|
@@ -237,9 +234,6 @@ export class GatewayService {
|
|
|
237
234
|
if (registry !== undefined)
|
|
238
235
|
runtime.registry = publicRegistry(registry, false);
|
|
239
236
|
this.connectors.set(key, runtime);
|
|
240
|
-
if (started.ownedRoute !== undefined) {
|
|
241
|
-
await this.installOwnedRoute(adapter, started.ownedRoute);
|
|
242
|
-
}
|
|
243
237
|
}
|
|
244
238
|
await this.refreshClaudeDiscovery().catch(() => undefined);
|
|
245
239
|
await this.refreshManagedCodexSocket().catch(() => undefined);
|
|
@@ -335,7 +329,9 @@ export class GatewayService {
|
|
|
335
329
|
const decide = async (operation, peerPrincipal = false) => {
|
|
336
330
|
try {
|
|
337
331
|
this.assertWritable();
|
|
338
|
-
await operation();
|
|
332
|
+
const decision = await operation();
|
|
333
|
+
if (decision !== undefined)
|
|
334
|
+
return decision;
|
|
339
335
|
this.revision += 1;
|
|
340
336
|
return { accepted: true, code: "ok" };
|
|
341
337
|
}
|
|
@@ -360,6 +356,14 @@ export class GatewayService {
|
|
|
360
356
|
unregisterPeer: (params) => decide(async () => this.unregisterPeer(params), true),
|
|
361
357
|
awaitPeer: (params) => this.awaitPeer(params),
|
|
362
358
|
peerReceipt: (params) => decide(async () => this.peerReceipt(params), true),
|
|
359
|
+
retireRoute: async ({ alias }) => {
|
|
360
|
+
try {
|
|
361
|
+
return await this.retireRoute(alias);
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
return sendRefusal(error);
|
|
365
|
+
}
|
|
366
|
+
},
|
|
363
367
|
unregisterCodex: (params) => decide(async () => this.unregisterCodex(params)),
|
|
364
368
|
listSnapshot: () => this.snapshot(),
|
|
365
369
|
observeSnapshot: () => this.observeSnapshot(),
|
|
@@ -418,7 +422,17 @@ export class GatewayService {
|
|
|
418
422
|
async receivePeerHandoff(peerHost, handoff) {
|
|
419
423
|
if (!this.config.peerNodes.includes(peerHost))
|
|
420
424
|
throw new BridgeError("PEER_NOT_CONFIGURED", "The sending peer host is not configured.");
|
|
421
|
-
|
|
425
|
+
let enqueued;
|
|
426
|
+
try {
|
|
427
|
+
enqueued = await this.store.enqueuePeerHandoff(peerHost, handoff);
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
const refusal = { accepted: false, reason: error instanceof BridgeError ? error.code : undefined };
|
|
431
|
+
if (isPeerHandoffRefusal(refusal))
|
|
432
|
+
return refusal;
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
// Post-admission bookkeeping is deliberately outside the refusal catch.
|
|
422
436
|
if (enqueued.messageId !== undefined) {
|
|
423
437
|
const source = await this.store.inspectPrivateRoute(handoff.source.alias);
|
|
424
438
|
const target = await this.store.inspectPrivateRoute(handoff.target.alias);
|
|
@@ -460,26 +474,8 @@ export class GatewayService {
|
|
|
460
474
|
...(persisted.safeErrorCode === undefined ? {} : { safeErrorCode: persisted.safeErrorCode }),
|
|
461
475
|
};
|
|
462
476
|
});
|
|
463
|
-
const connectors =
|
|
464
|
-
|
|
465
|
-
const age = observedAt === undefined ? undefined : Math.max(0, now.getTime() - Date.parse(observedAt));
|
|
466
|
-
const stale = runtime.health === "healthy" &&
|
|
467
|
-
(age === undefined || age > CONNECTOR_OBSERVATION_STALE_AFTER_MS);
|
|
468
|
-
const socketHeld = runtime.adapter.identity.provider === "codex" && this.managedCodexSocketHeldOutside;
|
|
469
|
-
const connectorCode = stale ? "CONNECTOR_OBSERVATION_STALE" : runtime.safeErrorCode ??
|
|
470
|
-
(socketHeld ? "MANAGED_CODEX_UNAVAILABLE" : undefined);
|
|
471
|
-
return {
|
|
472
|
-
provider: runtime.adapter.identity.provider,
|
|
473
|
-
host: runtime.adapter.identity.hostId,
|
|
474
|
-
health: stale || socketHeld ? "degraded" : runtime.health,
|
|
475
|
-
protocol: runtime.adapter.protocol,
|
|
476
|
-
protocolVersion: runtime.adapter.protocolVersion,
|
|
477
|
-
...(observedAt === undefined ? {} : { lastSeenAt: observedAt }),
|
|
478
|
-
...(age === undefined ? {} : { observationAgeMs: age }),
|
|
479
|
-
...(runtime.registry === undefined ? {} : { registry: runtime.registry }),
|
|
480
|
-
...(connectorCode === undefined ? {} : { safeErrorCode: connectorCode }),
|
|
481
|
-
};
|
|
482
|
-
});
|
|
477
|
+
const connectors = this.publicConnectors(now);
|
|
478
|
+
const word = gatewayHealthWord(connectors);
|
|
483
479
|
const availablePeers = [...this.candidates.values()]
|
|
484
480
|
.filter((candidate) => !this.collidingClaudeAliases.has(candidate.alias))
|
|
485
481
|
.slice(0, gatewayPublicSnapshotLimits.availablePeers)
|
|
@@ -495,11 +491,7 @@ export class GatewayService {
|
|
|
495
491
|
return projectGatewayPublicSnapshot({
|
|
496
492
|
...base,
|
|
497
493
|
generatedAt: now.toISOString(),
|
|
498
|
-
health:
|
|
499
|
-
? "degraded"
|
|
500
|
-
: connectors.length === 0
|
|
501
|
-
? "offline"
|
|
502
|
-
: "healthy",
|
|
494
|
+
health: word === "ok" || word === "stale" ? "healthy" : word,
|
|
503
495
|
connectors,
|
|
504
496
|
availablePeers,
|
|
505
497
|
routes,
|
|
@@ -517,30 +509,29 @@ export class GatewayService {
|
|
|
517
509
|
return { snapshotRevision: this.snapshotRevision, snapshot };
|
|
518
510
|
}
|
|
519
511
|
health() {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
registrationMode: "explicit_opt_in",
|
|
512
|
+
const word = gatewayHealthWord(this.publicConnectors(this.now()));
|
|
513
|
+
return word === "ok" || word === "stale" ? "ok" : "degraded";
|
|
514
|
+
}
|
|
515
|
+
publicConnectors(now) {
|
|
516
|
+
return [...this.connectors.values()].map((runtime) => {
|
|
517
|
+
const observedAt = runtime.observedAt;
|
|
518
|
+
const age = observedAt === undefined ? undefined : Math.max(0, now.getTime() - Date.parse(observedAt));
|
|
519
|
+
const stale = runtime.health === "healthy" &&
|
|
520
|
+
(age === undefined || age > CONNECTOR_OBSERVATION_STALE_AFTER_MS);
|
|
521
|
+
const socketHeld = runtime.adapter.identity.provider === "codex" && this.managedCodexSocketHeldOutside;
|
|
522
|
+
const connectorCode = runtime.safeErrorCode ??
|
|
523
|
+
(socketHeld ? "MANAGED_CODEX_UNAVAILABLE" : stale ? "CONNECTOR_OBSERVATION_STALE" : undefined);
|
|
524
|
+
return {
|
|
525
|
+
provider: runtime.adapter.identity.provider,
|
|
526
|
+
host: runtime.adapter.identity.hostId,
|
|
527
|
+
health: stale || socketHeld ? "degraded" : runtime.health,
|
|
528
|
+
protocol: runtime.adapter.protocol,
|
|
529
|
+
protocolVersion: runtime.adapter.protocolVersion,
|
|
530
|
+
...(observedAt === undefined ? {} : { lastSeenAt: observedAt }),
|
|
531
|
+
...(age === undefined ? {} : { observationAgeMs: age }),
|
|
532
|
+
...(runtime.registry === undefined ? {} : { registry: runtime.registry }),
|
|
533
|
+
...(connectorCode === undefined ? {} : { safeErrorCode: connectorCode }),
|
|
534
|
+
};
|
|
544
535
|
});
|
|
545
536
|
}
|
|
546
537
|
async observeLoadedRoutes() {
|
|
@@ -584,18 +575,18 @@ export class GatewayService {
|
|
|
584
575
|
return this.adapterFor({ provider: "claude", hostId });
|
|
585
576
|
}
|
|
586
577
|
async advertise(route) {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
await this.claudeAdapter(hostId)?.advertiseNativeSourcePeer?.({
|
|
578
|
+
if (route.registrationMode === "federated_peer")
|
|
579
|
+
return;
|
|
580
|
+
await this.claudeAdapter(route.binding.hostId)?.advertiseNativeSourcePeer?.({
|
|
590
581
|
alias: route.alias,
|
|
591
582
|
sourceProvider: route.binding.provider,
|
|
592
583
|
cwd: this.nativePeerCwd,
|
|
593
584
|
});
|
|
594
585
|
}
|
|
595
586
|
async unadvertise(route) {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
await this.claudeAdapter(hostId)?.unadvertiseNativeSourcePeer?.(route.alias);
|
|
587
|
+
if (route.registrationMode === "federated_peer")
|
|
588
|
+
return;
|
|
589
|
+
await this.claudeAdapter(route.binding.hostId)?.unadvertiseNativeSourcePeer?.(route.alias);
|
|
599
590
|
}
|
|
600
591
|
reconcileAdvertisement(route) {
|
|
601
592
|
void this.advertise(route).catch((error) => this.alert("NATIVE_ADVERTISEMENT_FAILED", route, error));
|
|
@@ -603,6 +594,66 @@ export class GatewayService {
|
|
|
603
594
|
async reconcileUnadvertisement(route) {
|
|
604
595
|
await this.unadvertise(route).catch((error) => this.alert("NATIVE_UNADVERTISEMENT_FAILED", route, error));
|
|
605
596
|
}
|
|
597
|
+
assertRouteNotCleaning(alias) {
|
|
598
|
+
if (this.routeCleanupAliases.has(alias))
|
|
599
|
+
throw new BridgeError("ROUTE_BUSY", "The route is still completing cleanup.", true);
|
|
600
|
+
}
|
|
601
|
+
async retireRoute(alias) {
|
|
602
|
+
this.assertWritable();
|
|
603
|
+
const route = await this.store.inspectPrivateRoute(alias);
|
|
604
|
+
if (!route)
|
|
605
|
+
return { accepted: false, code: "not_found" };
|
|
606
|
+
if (route.registrationMode === "federated_peer" || route.binding.hostId !== this.config.hostId) {
|
|
607
|
+
throw new BridgeError("FEDERATED_ROUTE_READ_ONLY", "A remote route is retired by its owner, not this broker.");
|
|
608
|
+
}
|
|
609
|
+
this.assertRouteNotCleaning(alias);
|
|
610
|
+
this.routeCleanupAliases.add(alias);
|
|
611
|
+
const expiry = this.ephemeralPeerExpiries.get(alias);
|
|
612
|
+
this.ephemeralPeerExpiries.delete(alias);
|
|
613
|
+
this.ephemeralExpiryFailures.delete(alias);
|
|
614
|
+
let removed = false;
|
|
615
|
+
try {
|
|
616
|
+
const result = await this.store.removeOwnedRouteAtomic({ alias, binding: route.binding,
|
|
617
|
+
activity: { operatorAction: true, action: "route_retired" } });
|
|
618
|
+
if (!result.removed)
|
|
619
|
+
return { accepted: false, code: "not_found" };
|
|
620
|
+
removed = true;
|
|
621
|
+
await this.finishSettlements(result.settlements);
|
|
622
|
+
const incident = (row) => row.sourceBinding?.registrationId === route.binding.registrationId || row.targetBinding?.registrationId === route.binding.registrationId;
|
|
623
|
+
for (const [id, conversation] of this.conversations)
|
|
624
|
+
if (incident(conversation))
|
|
625
|
+
this.conversations.delete(id);
|
|
626
|
+
for (const [id, rows] of this.pendingClaudeReplies) {
|
|
627
|
+
const retained = rows.filter((row) => !incident(row));
|
|
628
|
+
if (retained.length)
|
|
629
|
+
this.pendingClaudeReplies.set(id, retained);
|
|
630
|
+
else
|
|
631
|
+
this.pendingClaudeReplies.delete(id);
|
|
632
|
+
}
|
|
633
|
+
this.forgetRoute(route);
|
|
634
|
+
if (route.binding.provider === "claude") {
|
|
635
|
+
try {
|
|
636
|
+
await this.adapterFor(route.binding)?.releaseRoute?.(route.binding.routeHandle);
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
this.alert("ROUTE_RELEASE_FAILED", route, error);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
else
|
|
643
|
+
await this.reconcileUnadvertisement(route);
|
|
644
|
+
this.revision += 1;
|
|
645
|
+
return { accepted: true, code: "ok", settlements: {
|
|
646
|
+
cancelled: result.settlements.filter((row) => row.state === "cancelled").length,
|
|
647
|
+
ambiguous: result.settlements.filter((row) => row.state === "ambiguous").length,
|
|
648
|
+
unconfirmed: result.settlements.filter((row) => row.state === "unconfirmed").length,
|
|
649
|
+
} };
|
|
650
|
+
}
|
|
651
|
+
finally {
|
|
652
|
+
if (!removed && expiry !== undefined)
|
|
653
|
+
this.ephemeralPeerExpiries.set(alias, expiry);
|
|
654
|
+
this.routeCleanupAliases.delete(alias);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
606
657
|
async removeOwnedRoute(route) {
|
|
607
658
|
this.assertWritable();
|
|
608
659
|
const result = await this.store.removeOwnedRouteAtomic({
|
|
@@ -627,6 +678,7 @@ export class GatewayService {
|
|
|
627
678
|
}
|
|
628
679
|
async registerCodex(params) {
|
|
629
680
|
const existing = await this.store.inspectPrivateRoute(params.alias);
|
|
681
|
+
this.assertRouteNotCleaning(params.alias);
|
|
630
682
|
if (existing !== undefined && params.succeedsAlias === undefined) {
|
|
631
683
|
if (existing.binding.provider !== "codex" || existing.binding.routeHandle !== params.threadId) {
|
|
632
684
|
throw new BridgeError("ROUTE_ALIAS_ALREADY_REGISTERED", "The alias belongs to another registration.");
|
|
@@ -647,6 +699,7 @@ export class GatewayService {
|
|
|
647
699
|
};
|
|
648
700
|
if (params.succeedsAlias === undefined) {
|
|
649
701
|
this.assertWritable();
|
|
702
|
+
this.assertRouteNotCleaning(params.alias);
|
|
650
703
|
await this.store.registerRoute(replacement);
|
|
651
704
|
const installed = (await this.store.inspectPrivateRoute(params.alias));
|
|
652
705
|
await this.recordActivity("registration", "codex_registered", [installed.alias], true);
|
|
@@ -668,6 +721,7 @@ export class GatewayService {
|
|
|
668
721
|
throw new BridgeError("CODEX_SUCCESSION_OWNER_MISMATCH", "The succeeded registration is absent.");
|
|
669
722
|
}
|
|
670
723
|
this.assertWritable();
|
|
724
|
+
this.assertRouteNotCleaning(params.alias);
|
|
671
725
|
const result = await this.store.replaceCodexRegistrationAtomic({
|
|
672
726
|
oldAlias: params.succeedsAlias,
|
|
673
727
|
expectedOldRegistrationId: previous.binding.registrationId,
|
|
@@ -685,7 +739,7 @@ export class GatewayService {
|
|
|
685
739
|
this.kick(params.alias);
|
|
686
740
|
}
|
|
687
741
|
async registerPeer(params) {
|
|
688
|
-
if (this.
|
|
742
|
+
if (this.routeCleanupAliases.has(params.alias)) {
|
|
689
743
|
throw new BridgeError("ROUTE_BUSY", "The route is still completing cleanup.", true);
|
|
690
744
|
}
|
|
691
745
|
const existing = await this.store.inspectPrivateRoute(params.alias);
|
|
@@ -702,6 +756,7 @@ export class GatewayService {
|
|
|
702
756
|
throw new BridgeError("ROUTE_UNREGISTERED", "The route binding does not match.");
|
|
703
757
|
}
|
|
704
758
|
const token = `peer_${randomBytes(24).toString("base64url")}`;
|
|
759
|
+
this.assertRouteNotCleaning(params.alias);
|
|
705
760
|
await this.store.registerRoute({ alias: params.alias, binding: {
|
|
706
761
|
provider: "peer", hostId: this.config.hostId,
|
|
707
762
|
routeHandle: peerHandle(process.getuid(), params.alias, token), registrationId: registrationId(),
|
|
@@ -729,9 +784,9 @@ export class GatewayService {
|
|
|
729
784
|
}
|
|
730
785
|
async unregisterPeer(params) {
|
|
731
786
|
const route = await this.assertPeer(params);
|
|
732
|
-
if (this.
|
|
787
|
+
if (this.routeCleanupAliases.has(route.alias))
|
|
733
788
|
throw new BridgeError("ROUTE_BUSY", "The route is still completing cleanup.", true);
|
|
734
|
-
this.
|
|
789
|
+
this.routeCleanupAliases.add(route.alias);
|
|
735
790
|
// The expiry leaves the map before the removal, under the same cleanup
|
|
736
791
|
// guard, so a clock that fires mid-removal has nothing to act on; a
|
|
737
792
|
// removal that does not happen puts it back, so a refused unregister
|
|
@@ -752,7 +807,7 @@ export class GatewayService {
|
|
|
752
807
|
finally {
|
|
753
808
|
if (!removed && expiry !== undefined)
|
|
754
809
|
this.ephemeralPeerExpiries.set(route.alias, expiry);
|
|
755
|
-
this.
|
|
810
|
+
this.routeCleanupAliases.delete(route.alias);
|
|
756
811
|
}
|
|
757
812
|
}
|
|
758
813
|
/**
|
|
@@ -774,7 +829,7 @@ export class GatewayService {
|
|
|
774
829
|
if (expiresAt > now)
|
|
775
830
|
continue;
|
|
776
831
|
// Another owner is mid-removal; the entry is theirs to clear or restore.
|
|
777
|
-
if (this.
|
|
832
|
+
if (this.routeCleanupAliases.has(alias))
|
|
778
833
|
continue;
|
|
779
834
|
const route = await this.store.inspectPrivateRoute(alias);
|
|
780
835
|
if (route === undefined || !this.store.isEphemeralRegistration(route.binding.registrationId)) {
|
|
@@ -782,7 +837,7 @@ export class GatewayService {
|
|
|
782
837
|
this.ephemeralExpiryFailures.delete(alias);
|
|
783
838
|
continue;
|
|
784
839
|
}
|
|
785
|
-
this.
|
|
840
|
+
this.routeCleanupAliases.add(alias);
|
|
786
841
|
try {
|
|
787
842
|
const result = await this.store.removeOwnedRouteAtomic({ alias, binding: route.binding });
|
|
788
843
|
if (result.removed) {
|
|
@@ -808,7 +863,7 @@ export class GatewayService {
|
|
|
808
863
|
}
|
|
809
864
|
}
|
|
810
865
|
finally {
|
|
811
|
-
this.
|
|
866
|
+
this.routeCleanupAliases.delete(alias);
|
|
812
867
|
}
|
|
813
868
|
}
|
|
814
869
|
}
|
|
@@ -826,7 +881,7 @@ export class GatewayService {
|
|
|
826
881
|
const route = await this.assertPeer(params);
|
|
827
882
|
const adapter = this.adapterFor(route.binding);
|
|
828
883
|
if (adapter?.acknowledgeReceipt({ alias: route.alias, ...route.binding, receipt: params.receipt }) === "rejected") {
|
|
829
|
-
|
|
884
|
+
return { accepted: false, code: "not_found" };
|
|
830
885
|
}
|
|
831
886
|
}
|
|
832
887
|
async unregisterCodex(params) {
|
|
@@ -894,6 +949,7 @@ export class GatewayService {
|
|
|
894
949
|
},
|
|
895
950
|
registrationMode: "selected_live_peer",
|
|
896
951
|
};
|
|
952
|
+
this.assertRouteNotCleaning(alias);
|
|
897
953
|
const result = await this.installWithConcurrentFirstSend(input, adapter.identity.hostId, chosen.routeHandle);
|
|
898
954
|
await this.finishSettlements(result.settlements);
|
|
899
955
|
const after = (await this.store.listLogicalRoutes()).filter((route) => route.binding.provider === "claude");
|
|
@@ -1014,9 +1070,6 @@ export class GatewayService {
|
|
|
1014
1070
|
return routed;
|
|
1015
1071
|
return await this.installClaudeRoute(candidate);
|
|
1016
1072
|
}
|
|
1017
|
-
async resolveClaudeRoute(selector, hostId) {
|
|
1018
|
-
return await this.materializeClaudeRoute(await this.lookUpClaudeRoute(selector, hostId));
|
|
1019
|
-
}
|
|
1020
1073
|
async assertThread(alias, threadId) {
|
|
1021
1074
|
const route = await this.store.inspectPrivateRoute(alias);
|
|
1022
1075
|
// No registration at all is its own condition — the state every Codex
|
|
@@ -1468,190 +1521,198 @@ export class GatewayService {
|
|
|
1468
1521
|
await this.resolvePrewrite(attempt.messageId, attempt.attemptId, "failed", "ROUTE_UNREGISTERED");
|
|
1469
1522
|
return false;
|
|
1470
1523
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1524
|
+
return target.registrationMode === "federated_peer"
|
|
1525
|
+
? this.runPeerAttempt(attempt, source, target, conversation)
|
|
1526
|
+
: this.runProviderAttempt(attempt, source, target, conversation, messageContext);
|
|
1527
|
+
}
|
|
1528
|
+
return false;
|
|
1529
|
+
}
|
|
1530
|
+
async runPeerAttempt(attempt, source, target, conversation) {
|
|
1531
|
+
const peer = this.peerClients.get(target.binding.hostId);
|
|
1532
|
+
if (peer === undefined || source.registrationMode === "federated_peer") {
|
|
1533
|
+
await this.resolvePrewrite(attempt.messageId, attempt.attemptId, "requeue", "PEER_TUNNEL_UNAVAILABLE");
|
|
1534
|
+
return false;
|
|
1535
|
+
}
|
|
1536
|
+
const sourceEndpoint = { alias: source.alias, provider: source.binding.provider,
|
|
1537
|
+
host: source.binding.hostId, routeRef: peerRouteRef(source.binding.hostId, source.binding.registrationId) };
|
|
1538
|
+
const targetEndpoint = { alias: target.alias, provider: target.binding.provider,
|
|
1539
|
+
host: target.binding.hostId, routeRef: target.binding.routeHandle };
|
|
1540
|
+
const params = {
|
|
1541
|
+
originAttemptId: attempt.attemptId, originMessageId: attempt.messageId,
|
|
1542
|
+
source: sourceEndpoint, target: targetEndpoint,
|
|
1543
|
+
deadlineAt: attempt.deadlineAt, expectsReply: conversation?.expectsReply ?? true,
|
|
1544
|
+
body: attempt.body,
|
|
1545
|
+
...(attempt.steer === true ? { steer: true } : {}),
|
|
1546
|
+
...(attempt.conversationIdSuffix === undefined ? {} : { conversationCorrelation: attempt.conversationIdSuffix }),
|
|
1547
|
+
};
|
|
1548
|
+
let armed = false, authorizationUncertain = false, acceptanceObserved = false, result;
|
|
1549
|
+
try {
|
|
1550
|
+
const prepared = peer.prepareHandoff(params);
|
|
1551
|
+
let authorized;
|
|
1552
|
+
try {
|
|
1553
|
+
authorized = await this.store.authorizeMessage({ messageId: attempt.messageId,
|
|
1554
|
+
attemptId: attempt.attemptId, sourceRegistrationId: attempt.sourceRegistrationId,
|
|
1555
|
+
targetRegistrationId: attempt.targetRegistrationId,
|
|
1556
|
+
prepared: { kind: "peer_handoff", bodyBytes: prepared.bodyBytes,
|
|
1557
|
+
bodySha256: prepared.bodySha256, frameBytes: prepared.frameBytes, sha256: prepared.sha256 } });
|
|
1558
|
+
}
|
|
1559
|
+
catch (error) {
|
|
1560
|
+
authorizationUncertain = true;
|
|
1561
|
+
throw error;
|
|
1562
|
+
}
|
|
1563
|
+
if (authorized.status !== "authorized") {
|
|
1564
|
+
prepared.cancel();
|
|
1565
|
+
if (authorized.status === "terminal")
|
|
1566
|
+
await this.finishSettlement(authorized.settlement);
|
|
1567
|
+
this.activeAttempts.delete(attempt.messageId);
|
|
1568
|
+
return false;
|
|
1569
|
+
}
|
|
1570
|
+
armed = true;
|
|
1571
|
+
await prepared.perform();
|
|
1572
|
+
acceptanceObserved = true;
|
|
1573
|
+
const accepted = await this.store.acceptMessage({ messageId: attempt.messageId,
|
|
1574
|
+
attemptId: attempt.attemptId, lossOutcome: "unconfirmed" });
|
|
1575
|
+
if (accepted.status !== "accepted")
|
|
1576
|
+
throw new BridgeError("ACCEPTANCE_UNCONFIRMED", "The peer acceptance fence is stale.");
|
|
1577
|
+
result = { state: "delivered", safeErrorCode: "PEER_HANDOFF_CONFIRMED" };
|
|
1578
|
+
}
|
|
1579
|
+
catch (error) {
|
|
1580
|
+
if (!armed && !authorizationUncertain) {
|
|
1581
|
+
peer.close();
|
|
1582
|
+
this.peerClients.delete(target.binding.hostId);
|
|
1583
|
+
await this.resolvePrewrite(attempt.messageId, attempt.attemptId, "requeue", "PEER_TUNNEL_UNAVAILABLE");
|
|
1584
|
+
return false;
|
|
1585
|
+
}
|
|
1586
|
+
result = armed && !authorizationUncertain && !acceptanceObserved &&
|
|
1587
|
+
error instanceof PeerRequestError && error.detail.code === -32000 && isPeerHandoffRefusal(error.detail.data)
|
|
1588
|
+
? { state: "failed", safeErrorCode: error.detail.data.reason }
|
|
1589
|
+
: acceptanceObserved
|
|
1590
|
+
? { state: "unconfirmed", safeErrorCode: "PEER_HANDOFF_ACCEPTANCE_UNCONFIRMED" }
|
|
1591
|
+
: { state: "ambiguous", safeErrorCode: authorizationUncertain ? "WRITE_AUTHORIZATION_UNCERTAIN" : "PEER_HANDOFF_OUTCOME_UNKNOWN" };
|
|
1592
|
+
}
|
|
1593
|
+
const requeued = await this.applyDispatchResult(attempt.messageId, attempt.attemptId, result, armed, conversation, source.alias, target.alias);
|
|
1594
|
+
this.activeAttempts.delete(attempt.messageId);
|
|
1595
|
+
return requeued;
|
|
1596
|
+
}
|
|
1597
|
+
async runProviderAttempt(attempt, source, target, conversation, messageContext) {
|
|
1598
|
+
const adapter = this.adapterFor(target.binding);
|
|
1599
|
+
if (adapter === undefined) {
|
|
1600
|
+
await this.resolvePrewrite(attempt.messageId, attempt.attemptId, "failed", "PROVIDER_UNAVAILABLE");
|
|
1601
|
+
return false;
|
|
1602
|
+
}
|
|
1603
|
+
const active = this.activeAttempts.get(attempt.messageId);
|
|
1604
|
+
let authorizationUncertain = false;
|
|
1605
|
+
let armed = false;
|
|
1606
|
+
let accepted = false;
|
|
1607
|
+
const conversationId = messageContext?.conversationId ??
|
|
1608
|
+
conversationIdForSuffix(attempt.conversationIdSuffix);
|
|
1609
|
+
const parsed = parseDirection(attempt.direction);
|
|
1610
|
+
if (target.binding.provider === "claude" &&
|
|
1611
|
+
messageContext?.expectsReply === true &&
|
|
1612
|
+
![...this.pendingClaudeReplies.values()].some((rows) => rows.some((row) => row.messageId === attempt.messageId)) &&
|
|
1613
|
+
[...this.pendingClaudeReplies.values()].reduce((count, rows) => count + rows.length, 0) >= MAX_PENDING_CLAUDE_REPLIES) {
|
|
1614
|
+
await this.resolvePrewrite(attempt.messageId, attempt.attemptId, "requeue", "ROUTE_BUSY");
|
|
1615
|
+
return true;
|
|
1616
|
+
}
|
|
1617
|
+
let result;
|
|
1618
|
+
try {
|
|
1619
|
+
result = await adapter.dispatch({
|
|
1620
|
+
attemptId: attempt.attemptId,
|
|
1621
|
+
sourceAlias: source.alias,
|
|
1622
|
+
sourceProvider: parsed.sourceProvider,
|
|
1623
|
+
// The route's CURRENT alias, not the one captured at reserve. The
|
|
1624
|
+
// provider adapters compare this against the alias they hold for the
|
|
1625
|
+
// binding, so a session renamed between reserve and dispatch would
|
|
1626
|
+
// otherwise fail its own delivery over a stale display name.
|
|
1627
|
+
targetAlias: target.alias,
|
|
1628
|
+
conversationId,
|
|
1629
|
+
binding: target.binding,
|
|
1630
|
+
authorization: "selected_route",
|
|
1631
|
+
messageId: attempt.messageId,
|
|
1632
|
+
text: attempt.body,
|
|
1633
|
+
expectsReply: conversation?.expectsReply ?? true,
|
|
1634
|
+
deadlineAt: attempt.deadlineAt,
|
|
1635
|
+
...(attempt.steer === true ? { steer: true, queuedAhead: 0 } : {}),
|
|
1636
|
+
authorizeWrite: async (evidence) => {
|
|
1637
|
+
if (evidence.attemptId !== attempt.attemptId)
|
|
1638
|
+
return false;
|
|
1493
1639
|
try {
|
|
1494
|
-
authorized = await this.store.authorizeMessage({
|
|
1495
|
-
|
|
1640
|
+
const authorized = await this.store.authorizeMessage({
|
|
1641
|
+
messageId: attempt.messageId,
|
|
1642
|
+
attemptId: attempt.attemptId,
|
|
1643
|
+
sourceRegistrationId: attempt.sourceRegistrationId,
|
|
1496
1644
|
targetRegistrationId: attempt.targetRegistrationId,
|
|
1497
|
-
prepared: {
|
|
1498
|
-
|
|
1645
|
+
prepared: {
|
|
1646
|
+
kind: evidence.kind,
|
|
1647
|
+
bodyBytes: evidence.bodyBytes,
|
|
1648
|
+
bodySha256: evidence.bodySha256,
|
|
1649
|
+
frameBytes: evidence.frameBytes,
|
|
1650
|
+
sha256: evidence.sha256,
|
|
1651
|
+
},
|
|
1652
|
+
});
|
|
1653
|
+
if (authorized.status === "terminal") {
|
|
1654
|
+
await this.finishSettlement(authorized.settlement);
|
|
1655
|
+
}
|
|
1656
|
+
if (authorized.status === "authorized") {
|
|
1657
|
+
armed = true;
|
|
1658
|
+
if (target.binding.provider === "claude" &&
|
|
1659
|
+
messageContext?.expectsReply === true) {
|
|
1660
|
+
this.installPendingClaudeReply({
|
|
1661
|
+
messageId: attempt.messageId,
|
|
1662
|
+
conversationId,
|
|
1663
|
+
sourceAlias: source.alias,
|
|
1664
|
+
targetAlias: target.alias,
|
|
1665
|
+
sourceBinding: source.binding,
|
|
1666
|
+
targetBinding: target.binding,
|
|
1667
|
+
deadlineAt: attempt.deadlineAt,
|
|
1668
|
+
state: "armed",
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
return authorized.status === "authorized";
|
|
1499
1673
|
}
|
|
1500
1674
|
catch (error) {
|
|
1501
1675
|
authorizationUncertain = true;
|
|
1502
1676
|
throw error;
|
|
1503
1677
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
return false;
|
|
1678
|
+
},
|
|
1679
|
+
onAccepted: async (evidence) => {
|
|
1680
|
+
if ((target.binding.provider !== "codex" && target.binding.provider !== "peer") ||
|
|
1681
|
+
evidence.attemptId !== attempt.attemptId) {
|
|
1682
|
+
throw new BridgeError("ACCEPTANCE_UNCONFIRMED", "The provider accepted another attempt.");
|
|
1510
1683
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
if (
|
|
1517
|
-
throw new BridgeError("ACCEPTANCE_UNCONFIRMED", "The
|
|
1518
|
-
result = { state: "delivered", safeErrorCode: "PEER_HANDOFF_CONFIRMED" };
|
|
1519
|
-
}
|
|
1520
|
-
catch {
|
|
1521
|
-
if (!armed && !authorizationUncertain) {
|
|
1522
|
-
peer.close();
|
|
1523
|
-
this.peerClients.delete(target.binding.hostId);
|
|
1524
|
-
await this.resolvePrewrite(attempt.messageId, attempt.attemptId, "requeue", "PEER_TUNNEL_UNAVAILABLE");
|
|
1525
|
-
return false;
|
|
1684
|
+
const result = await this.store.acceptMessage({
|
|
1685
|
+
messageId: attempt.messageId,
|
|
1686
|
+
attemptId: attempt.attemptId,
|
|
1687
|
+
lossOutcome: attempt.steer === true ? "ambiguous" : "unconfirmed",
|
|
1688
|
+
});
|
|
1689
|
+
if (result.status !== "accepted") {
|
|
1690
|
+
throw new BridgeError("ACCEPTANCE_UNCONFIRMED", "The durable acceptance fence is stale.");
|
|
1526
1691
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
}
|
|
1531
|
-
const requeued = await this.applyDispatchResult(attempt.messageId, attempt.attemptId, result, armed, conversation, attempt.sourceAlias, attempt.targetAlias);
|
|
1532
|
-
this.activeAttempts.delete(attempt.messageId);
|
|
1533
|
-
return requeued;
|
|
1534
|
-
}
|
|
1535
|
-
const adapter = this.adapterFor(target.binding);
|
|
1536
|
-
if (adapter === undefined) {
|
|
1537
|
-
await this.resolvePrewrite(attempt.messageId, attempt.attemptId, "failed", "PROVIDER_UNAVAILABLE");
|
|
1538
|
-
return false;
|
|
1539
|
-
}
|
|
1540
|
-
const active = this.activeAttempts.get(attempt.messageId);
|
|
1541
|
-
let authorizationUncertain = false;
|
|
1542
|
-
let armed = false;
|
|
1543
|
-
let accepted = false;
|
|
1544
|
-
const conversationId = messageContext?.conversationId ??
|
|
1545
|
-
conversationIdForSuffix(attempt.conversationIdSuffix);
|
|
1546
|
-
const parsed = parseDirection(attempt.direction);
|
|
1547
|
-
if (target.binding.provider === "claude" &&
|
|
1548
|
-
messageContext?.expectsReply === true &&
|
|
1549
|
-
![...this.pendingClaudeReplies.values()].some((rows) => rows.some((row) => row.messageId === attempt.messageId)) &&
|
|
1550
|
-
[...this.pendingClaudeReplies.values()].reduce((count, rows) => count + rows.length, 0) >= MAX_PENDING_CLAUDE_REPLIES) {
|
|
1551
|
-
await this.resolvePrewrite(attempt.messageId, attempt.attemptId, "requeue", "ROUTE_BUSY");
|
|
1552
|
-
return true;
|
|
1553
|
-
}
|
|
1554
|
-
let result;
|
|
1555
|
-
try {
|
|
1556
|
-
result = await adapter.dispatch({
|
|
1557
|
-
attemptId: attempt.attemptId,
|
|
1558
|
-
sourceAlias: attempt.sourceAlias,
|
|
1559
|
-
sourceProvider: parsed.sourceProvider,
|
|
1560
|
-
// The route's CURRENT alias, not the one captured at reserve. The
|
|
1561
|
-
// provider adapters compare this against the alias they hold for the
|
|
1562
|
-
// binding, so a session renamed between reserve and dispatch would
|
|
1563
|
-
// otherwise fail its own delivery over a stale display name.
|
|
1564
|
-
targetAlias: target.alias,
|
|
1565
|
-
conversationId,
|
|
1566
|
-
binding: target.binding,
|
|
1567
|
-
authorization: "selected_route",
|
|
1568
|
-
messageId: attempt.messageId,
|
|
1569
|
-
text: attempt.body,
|
|
1570
|
-
expectsReply: conversation?.expectsReply ?? true,
|
|
1571
|
-
deadlineAt: attempt.deadlineAt,
|
|
1572
|
-
...(attempt.steer === true ? { steer: true, queuedAhead: 0 } : {}),
|
|
1573
|
-
authorizeWrite: async (evidence) => {
|
|
1574
|
-
if (evidence.attemptId !== attempt.attemptId)
|
|
1575
|
-
return false;
|
|
1576
|
-
try {
|
|
1577
|
-
const authorized = await this.store.authorizeMessage({
|
|
1578
|
-
messageId: attempt.messageId,
|
|
1579
|
-
attemptId: attempt.attemptId,
|
|
1580
|
-
sourceRegistrationId: attempt.sourceRegistrationId,
|
|
1581
|
-
targetRegistrationId: attempt.targetRegistrationId,
|
|
1582
|
-
prepared: {
|
|
1583
|
-
kind: evidence.kind,
|
|
1584
|
-
bodyBytes: evidence.bodyBytes,
|
|
1585
|
-
bodySha256: evidence.bodySha256,
|
|
1586
|
-
frameBytes: evidence.frameBytes,
|
|
1587
|
-
sha256: evidence.sha256,
|
|
1588
|
-
},
|
|
1589
|
-
});
|
|
1590
|
-
if (authorized.status === "terminal") {
|
|
1591
|
-
await this.finishSettlement(authorized.settlement);
|
|
1592
|
-
}
|
|
1593
|
-
if (authorized.status === "authorized") {
|
|
1594
|
-
armed = true;
|
|
1595
|
-
if (target.binding.provider === "claude" &&
|
|
1596
|
-
messageContext?.expectsReply === true) {
|
|
1597
|
-
this.installPendingClaudeReply({
|
|
1598
|
-
messageId: attempt.messageId,
|
|
1599
|
-
conversationId,
|
|
1600
|
-
sourceAlias: attempt.sourceAlias,
|
|
1601
|
-
targetAlias: attempt.targetAlias,
|
|
1602
|
-
sourceBinding: source.binding,
|
|
1603
|
-
targetBinding: target.binding,
|
|
1604
|
-
deadlineAt: attempt.deadlineAt,
|
|
1605
|
-
state: "armed",
|
|
1606
|
-
});
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
return authorized.status === "authorized";
|
|
1610
|
-
}
|
|
1611
|
-
catch (error) {
|
|
1612
|
-
authorizationUncertain = true;
|
|
1613
|
-
throw error;
|
|
1614
|
-
}
|
|
1615
|
-
},
|
|
1616
|
-
onAccepted: async (evidence) => {
|
|
1617
|
-
if ((target.binding.provider !== "codex" && target.binding.provider !== "peer") ||
|
|
1618
|
-
evidence.attemptId !== attempt.attemptId) {
|
|
1619
|
-
throw new BridgeError("ACCEPTANCE_UNCONFIRMED", "The provider accepted another attempt.");
|
|
1620
|
-
}
|
|
1621
|
-
const result = await this.store.acceptMessage({
|
|
1622
|
-
messageId: attempt.messageId,
|
|
1623
|
-
attemptId: attempt.attemptId,
|
|
1624
|
-
lossOutcome: attempt.steer === true ? "ambiguous" : "unconfirmed",
|
|
1625
|
-
});
|
|
1626
|
-
if (result.status !== "accepted") {
|
|
1627
|
-
throw new BridgeError("ACCEPTANCE_UNCONFIRMED", "The durable acceptance fence is stale.");
|
|
1628
|
-
}
|
|
1629
|
-
accepted = true;
|
|
1630
|
-
},
|
|
1631
|
-
});
|
|
1632
|
-
}
|
|
1633
|
-
catch {
|
|
1634
|
-
result = {
|
|
1635
|
-
state: accepted
|
|
1636
|
-
? attempt.steer === true
|
|
1637
|
-
? "ambiguous"
|
|
1638
|
-
: "unconfirmed"
|
|
1639
|
-
: armed || authorizationUncertain
|
|
1640
|
-
? "ambiguous"
|
|
1641
|
-
: "failed",
|
|
1642
|
-
safeErrorCode: accepted
|
|
1643
|
-
? "DELIVERY_UNCONFIRMED"
|
|
1644
|
-
: authorizationUncertain || armed
|
|
1645
|
-
? "WRITE_AUTHORIZATION_UNCERTAIN"
|
|
1646
|
-
: "PROVIDER_DISPATCH_FAILED",
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
const requeued = await this.applyDispatchResult(attempt.messageId, attempt.attemptId, result, armed, conversation, attempt.sourceAlias, attempt.targetAlias);
|
|
1650
|
-
if (this.activeAttempts.get(attempt.messageId) === active)
|
|
1651
|
-
this.activeAttempts.delete(attempt.messageId);
|
|
1652
|
-
return requeued;
|
|
1692
|
+
accepted = true;
|
|
1693
|
+
},
|
|
1694
|
+
});
|
|
1653
1695
|
}
|
|
1654
|
-
|
|
1696
|
+
catch {
|
|
1697
|
+
result = {
|
|
1698
|
+
state: accepted
|
|
1699
|
+
? attempt.steer === true
|
|
1700
|
+
? "ambiguous"
|
|
1701
|
+
: "unconfirmed"
|
|
1702
|
+
: armed || authorizationUncertain
|
|
1703
|
+
? "ambiguous"
|
|
1704
|
+
: "failed",
|
|
1705
|
+
safeErrorCode: accepted
|
|
1706
|
+
? "DELIVERY_UNCONFIRMED"
|
|
1707
|
+
: authorizationUncertain || armed
|
|
1708
|
+
? "WRITE_AUTHORIZATION_UNCERTAIN"
|
|
1709
|
+
: "PROVIDER_DISPATCH_FAILED",
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
const requeued = await this.applyDispatchResult(attempt.messageId, attempt.attemptId, result, armed, conversation, source.alias, target.alias);
|
|
1713
|
+
if (this.activeAttempts.get(attempt.messageId) === active)
|
|
1714
|
+
this.activeAttempts.delete(attempt.messageId);
|
|
1715
|
+
return requeued;
|
|
1655
1716
|
}
|
|
1656
1717
|
async settleAttemptForShutdown(messageId, attemptId) {
|
|
1657
1718
|
const result = await this.store.settleAttemptForShutdown({ messageId, attemptId });
|