agent-embassy 1.9.4 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +28 -21
- package/README.zh-CN.md +26 -20
- package/dist/src/gateway/claude-helper-protocol.d.ts +1 -1
- package/dist/src/gateway/claude-helper-protocol.js +3 -2
- package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
- package/dist/src/gateway/claude-helper-supervisor.d.ts +1 -1
- package/dist/src/gateway/claude-helper-supervisor.js +1 -1
- package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
- package/dist/src/gateway/claude-helper.js +12 -4
- package/dist/src/gateway/claude-helper.js.map +1 -1
- package/dist/src/gateway/cli-copy.d.ts +1 -1
- package/dist/src/gateway/cli-copy.en.d.ts +9 -4
- package/dist/src/gateway/cli-copy.en.js +10 -6
- package/dist/src/gateway/cli-copy.en.js.map +1 -1
- package/dist/src/gateway/cli-copy.js +5 -0
- package/dist/src/gateway/cli-copy.js.map +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +9 -4
- package/dist/src/gateway/cli-copy.zh-CN.js +10 -6
- package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/cli.d.ts +3 -9
- package/dist/src/gateway/cli.js +56 -63
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/codex-local-transport.d.ts +1 -1
- package/dist/src/gateway/codex-local-transport.js +2 -2
- package/dist/src/gateway/codex-local-transport.js.map +1 -1
- package/dist/src/gateway/config.d.ts +4 -3
- package/dist/src/gateway/config.js +5 -11
- package/dist/src/gateway/config.js.map +1 -1
- package/dist/src/gateway/control.d.ts +20 -41
- package/dist/src/gateway/control.js +54 -96
- package/dist/src/gateway/control.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.en.js +2 -2
- package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.zh-CN.js +2 -2
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/federation-nodes.d.ts +1 -2
- package/dist/src/gateway/federation-nodes.js +29 -14
- package/dist/src/gateway/federation-nodes.js.map +1 -1
- package/dist/src/gateway/live-dashboard-app/app.js +2 -2
- package/dist/src/gateway/live-dashboard-command.d.ts +3 -1
- package/dist/src/gateway/live-dashboard-command.js +1 -1
- package/dist/src/gateway/live-dashboard-command.js.map +1 -1
- package/dist/src/gateway/peer-protocol.d.ts +1 -0
- package/dist/src/gateway/peer-protocol.js +5 -0
- package/dist/src/gateway/peer-protocol.js.map +1 -1
- package/dist/src/gateway/providers.d.ts +3 -3
- package/dist/src/gateway/providers.js +7 -10
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +1 -2
- package/dist/src/gateway/server.js +6 -13
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +3 -1
- package/dist/src/gateway/service.js +93 -39
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/store.d.ts +1 -1
- package/dist/src/gateway/store.js +30 -29
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +4 -4
- package/docs/CONFIGURATION.md +27 -21
- package/docs/CONFIGURATION.zh-CN.md +19 -15
- package/docs/DASHBOARD.md +1 -1
- package/docs/DASHBOARD.zh-CN.md +1 -1
- package/docs/DELIVERY.md +2 -2
- package/docs/DELIVERY.zh-CN.md +2 -2
- package/docs/GATEWAY-ARCHITECTURE.md +48 -58
- package/package.json +1 -1
- package/skills/embassy-peer/SKILL.md +20 -15
- package/dist/src/gateway/state-v2-to-v3.d.ts +0 -24
- package/dist/src/gateway/state-v2-to-v3.js +0 -1034
- package/dist/src/gateway/state-v2-to-v3.js.map +0 -1
|
@@ -5,7 +5,7 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { BridgeError } from "../errors.js";
|
|
7
7
|
import { KeyedMutex } from "../mutex.js";
|
|
8
|
-
import { peerEdgeRef } from "./peer-protocol.js";
|
|
8
|
+
import { peerEdgeRef, peerRouteRef } from "./peer-protocol.js";
|
|
9
9
|
import { deliveryStates, directionId, gatewayActivityActions, gatewayActivityKinds, gatewayProviders, gatewayPublicSnapshotLimits, gatewayRegistrationIngressPrefixes, parseDirection, projectGatewayPublicSnapshot, routeRegistrationModes } from "./types.js";
|
|
10
10
|
const STATE_MARKER = ".agent-embassy-state";
|
|
11
11
|
const STATE_MARKER_CONTENT = "agent-embassy-state-v1\n";
|
|
@@ -18,15 +18,14 @@ const DEFAULT_RETAINED_BODY_BYTES = 1 * 1024 * 1024;
|
|
|
18
18
|
const ALIAS_PATTERN = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
|
|
19
19
|
const HOST_PATTERN = /^[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
|
|
20
20
|
const PRIVATE_TOKEN_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
|
|
21
|
+
const REGISTRATION_ID_PATTERN = /^reg_[A-Za-z0-9_-]{1,252}$/;
|
|
21
22
|
const PEER_ROUTE_HANDLE_PATTERN = /^peer:[0-9a-f]{64}$/;
|
|
22
23
|
const SAFE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
23
24
|
const MESSAGE_ID_PATTERN = /^msg_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24
25
|
const MESSAGE_SUFFIX_PATTERN = /^[0-9a-f]{8}$/;
|
|
25
26
|
const CONVERSATION_SUFFIX_PATTERN = /^[A-Za-z0-9_-]{8}$/;
|
|
26
27
|
const FINGERPRINT_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
27
|
-
|
|
28
|
-
// bound keeps pre-release v3 artifacts written before that correction bootable.
|
|
29
|
-
const DELIVERY_TOKEN_PATTERN = /^dlv_[A-Za-z0-9_-]{24,128}$/;
|
|
28
|
+
const DELIVERY_TOKEN_PATTERN = /^dlv_[A-Za-z0-9_-]{24}$/;
|
|
30
29
|
const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
31
30
|
const PROVIDERS = new Set(gatewayProviders);
|
|
32
31
|
const REGISTRATION_MODES = new Set(routeRegistrationModes);
|
|
@@ -103,12 +102,13 @@ function isRoute(value) {
|
|
|
103
102
|
typeof value.alias === "string" &&
|
|
104
103
|
ALIAS_PATTERN.test(value.alias) &&
|
|
105
104
|
isLogicalBinding(value.binding) &&
|
|
105
|
+
REGISTRATION_ID_PATTERN.test(value.binding.registrationId) &&
|
|
106
106
|
typeof value.registrationMode === "string" &&
|
|
107
107
|
REGISTRATION_MODES.has(value.registrationMode) &&
|
|
108
108
|
(value.binding.provider !== "peer" || value.registrationMode === "federated_peer" ||
|
|
109
109
|
PEER_ROUTE_HANDLE_PATTERN.test(value.binding.routeHandle)) &&
|
|
110
110
|
typeof value.enabled === "boolean" &&
|
|
111
|
-
|
|
111
|
+
value.busyPolicy === "queue" &&
|
|
112
112
|
isIsoTimestamp(value.registeredAt) &&
|
|
113
113
|
isIsoTimestamp(value.updatedAt) &&
|
|
114
114
|
isCounters(value.counters));
|
|
@@ -396,17 +396,17 @@ function isRuntimeActivity(value) {
|
|
|
396
396
|
typeof event.operatorAction === "boolean" &&
|
|
397
397
|
(event.safeErrorCode === undefined || isSafeCode(event.safeErrorCode)));
|
|
398
398
|
}
|
|
399
|
-
function
|
|
399
|
+
function isMessageActivity(value) {
|
|
400
400
|
return (isObject(value) &&
|
|
401
401
|
hasOnlyKeys(value, ["type", "event"]) &&
|
|
402
|
-
value.type === "
|
|
402
|
+
value.type === "message_activity" &&
|
|
403
403
|
isNormalizedEvent(value.event));
|
|
404
404
|
}
|
|
405
|
-
export function
|
|
405
|
+
export function isGatewayPersistedStateV4(value) {
|
|
406
406
|
if (!isObject(value) ||
|
|
407
407
|
!hasOnlyKeys(value, ["schemaVersion", "commit", "createdAt", "updatedAt", "eventSequence",
|
|
408
408
|
"routes", "consentEdges", "messages", "dedupe", "rateBuckets", "activity", "accounting"]) ||
|
|
409
|
-
value.schemaVersion !==
|
|
409
|
+
value.schemaVersion !== 4 ||
|
|
410
410
|
!isObject(value.commit) ||
|
|
411
411
|
!hasOnlyKeys(value.commit, ["sequence", "id"]) ||
|
|
412
412
|
!isNonNegativeInteger(value.commit.sequence) ||
|
|
@@ -428,7 +428,7 @@ export function isGatewayPersistedStateV3(value) {
|
|
|
428
428
|
typeof bucket.sourceAlias === "string" && ALIAS_PATTERN.test(bucket.sourceAlias) &&
|
|
429
429
|
isIsoTimestamp(bucket.windowStartedAt) && isNonNegativeInteger(bucket.count)) ||
|
|
430
430
|
!Array.isArray(value.activity) ||
|
|
431
|
-
!value.activity.every((entry) =>
|
|
431
|
+
!value.activity.every((entry) => isMessageActivity(entry) || isRuntimeActivity(entry)) ||
|
|
432
432
|
!isAccounting(value.accounting)) {
|
|
433
433
|
return false;
|
|
434
434
|
}
|
|
@@ -690,7 +690,7 @@ export class GatewayStore {
|
|
|
690
690
|
const now = this.now();
|
|
691
691
|
const loaded = await this.loadStateFile();
|
|
692
692
|
this.state = loaded ?? {
|
|
693
|
-
schemaVersion:
|
|
693
|
+
schemaVersion: 4,
|
|
694
694
|
commit: { sequence: 0, id: this.randomId() },
|
|
695
695
|
createdAt: now.toISOString(), updatedAt: now.toISOString(),
|
|
696
696
|
eventSequence: 0, routes: [],
|
|
@@ -760,7 +760,7 @@ export class GatewayStore {
|
|
|
760
760
|
}
|
|
761
761
|
async reconcilePeerCatalog(peerHost, catalog) {
|
|
762
762
|
return this.mutate((state, now) => {
|
|
763
|
-
const localHost = this.config.hostId
|
|
763
|
+
const localHost = this.config.hostId;
|
|
764
764
|
if (!this.config.allowedHosts.includes(peerHost) || peerHost === localHost ||
|
|
765
765
|
!catalog.complete || catalog.truncated || catalog.routes.some((route) => route.host !== peerHost) ||
|
|
766
766
|
catalog.consentEdges.some((edge) => edge.ownerHost !== peerHost)) {
|
|
@@ -807,7 +807,7 @@ export class GatewayStore {
|
|
|
807
807
|
(endpoint.host === peerHost ? route.registrationMode === "federated_peer" && route.binding.hostId === peerHost &&
|
|
808
808
|
route.binding.routeHandle === endpoint.routeRef : endpoint.host === localHost &&
|
|
809
809
|
route.registrationMode !== "federated_peer" && route.binding.hostId === localHost &&
|
|
810
|
-
route.binding.registrationId === endpoint.routeRef)));
|
|
810
|
+
peerRouteRef(localHost, route.binding.registrationId) === endpoint.routeRef)));
|
|
811
811
|
if (resolved[0] === undefined || resolved[1] === undefined || edge.ref !== peerEdgeRef(edge.endpoints))
|
|
812
812
|
throw new BridgeError("INVALID_PEER_CATALOG", "A peer consent edge does not match current endpoint authority.");
|
|
813
813
|
desiredEdges.push(canonicalConsentEndpoints(resolved[0], resolved[1]));
|
|
@@ -831,7 +831,7 @@ export class GatewayStore {
|
|
|
831
831
|
}
|
|
832
832
|
async enqueuePeerHandoff(peerHost, handoff) {
|
|
833
833
|
return this.mutate((state, now) => {
|
|
834
|
-
const localHost = this.config.hostId
|
|
834
|
+
const localHost = this.config.hostId;
|
|
835
835
|
if (!this.config.allowedHosts.includes(peerHost) || peerHost === localHost || handoff.source.host !== peerHost ||
|
|
836
836
|
handoff.target.host !== localHost || handoff.edgeOwnerHost !== [peerHost, localHost].sort()[0] ||
|
|
837
837
|
handoff.edgeRef !== peerEdgeRef([handoff.source, handoff.target]))
|
|
@@ -840,7 +840,7 @@ export class GatewayStore {
|
|
|
840
840
|
route.alias === handoff.source.alias && route.binding.provider === handoff.source.provider && route.binding.routeHandle === handoff.source.routeRef);
|
|
841
841
|
const target = state.routes.find((route) => route.registrationMode !== "federated_peer" && route.binding.hostId === localHost &&
|
|
842
842
|
route.alias === handoff.target.alias && route.binding.provider === handoff.target.provider &&
|
|
843
|
-
route.binding.registrationId === handoff.target.routeRef && route.enabled);
|
|
843
|
+
peerRouteRef(localHost, route.binding.registrationId) === handoff.target.routeRef && route.enabled);
|
|
844
844
|
if (source === undefined || target === undefined)
|
|
845
845
|
throw new BridgeError("ROUTE_UNREGISTERED", "The peer handoff endpoint is no longer current.");
|
|
846
846
|
const endpoints = canonicalConsentEndpoints(source, target);
|
|
@@ -1093,7 +1093,7 @@ export class GatewayStore {
|
|
|
1093
1093
|
throw new BridgeError("ROUTE_UNREGISTERED", "The native ingress target registration is no longer current.");
|
|
1094
1094
|
}
|
|
1095
1095
|
const nativeSourceHost = target.registrationMode === "federated_peer"
|
|
1096
|
-
?
|
|
1096
|
+
? this.config.hostId : target.binding.hostId;
|
|
1097
1097
|
if (input.source.binding.provider !== "claude" ||
|
|
1098
1098
|
input.source.binding.hostId !== nativeSourceHost ||
|
|
1099
1099
|
input.source.alias.endsWith(`@${nativeSourceHost}`) === false) {
|
|
@@ -1448,16 +1448,16 @@ export class GatewayStore {
|
|
|
1448
1448
|
},
|
|
1449
1449
|
],
|
|
1450
1450
|
host: edgeOwnerHost(edge.endpoints), counters: { ...edge.counters },
|
|
1451
|
-
mutable: edgeOwnerHost(edge.endpoints) ===
|
|
1451
|
+
mutable: edgeOwnerHost(edge.endpoints) === this.config.hostId,
|
|
1452
1452
|
}));
|
|
1453
1453
|
const currentEvents = state.messages.map((message) => this.projectMessageEvent(message));
|
|
1454
|
-
const
|
|
1455
|
-
.filter((entry) => entry.type === "
|
|
1454
|
+
const messageEvents = state.activity
|
|
1455
|
+
.filter((entry) => entry.type === "message_activity")
|
|
1456
1456
|
.map((entry) => structuredClone(entry.event));
|
|
1457
1457
|
const activityEvents = state.activity
|
|
1458
1458
|
.filter((entry) => entry.type === "activity")
|
|
1459
1459
|
.map((entry) => structuredClone(entry.event));
|
|
1460
|
-
const messages = [...
|
|
1460
|
+
const messages = [...messageEvents, ...currentEvents]
|
|
1461
1461
|
.sort((left, right) => left.sequence - right.sequence)
|
|
1462
1462
|
.slice(-gatewayPublicSnapshotLimits.messages);
|
|
1463
1463
|
const pressureBuckets = [
|
|
@@ -1499,7 +1499,7 @@ export class GatewayStore {
|
|
|
1499
1499
|
connectors: 0, availablePeers: 0,
|
|
1500
1500
|
routes: 0, consentEdges: 0,
|
|
1501
1501
|
activityEvents: 0,
|
|
1502
|
-
messages: Math.max(0,
|
|
1502
|
+
messages: Math.max(0, messageEvents.length + currentEvents.length - messages.length),
|
|
1503
1503
|
alerts: 0,
|
|
1504
1504
|
},
|
|
1505
1505
|
});
|
|
@@ -1545,10 +1545,11 @@ export class GatewayStore {
|
|
|
1545
1545
|
};
|
|
1546
1546
|
}
|
|
1547
1547
|
validateRouteInput(input) {
|
|
1548
|
-
const localHost = this.config.hostId
|
|
1548
|
+
const localHost = this.config.hostId;
|
|
1549
1549
|
if (!isObject(input) ||
|
|
1550
1550
|
!ALIAS_PATTERN.test(input.alias) ||
|
|
1551
1551
|
!isLogicalBinding(input.binding) ||
|
|
1552
|
+
!REGISTRATION_ID_PATTERN.test(input.binding.registrationId) ||
|
|
1552
1553
|
!REGISTRATION_MODES.has(input.registrationMode) ||
|
|
1553
1554
|
(input.binding.provider === "peer" && input.registrationMode !== "federated_peer" &&
|
|
1554
1555
|
!PEER_ROUTE_HANDLE_PATTERN.test(input.binding.routeHandle)) ||
|
|
@@ -1816,7 +1817,7 @@ export class GatewayStore {
|
|
|
1816
1817
|
return;
|
|
1817
1818
|
state.eventSequence += 1;
|
|
1818
1819
|
state.activity.push({
|
|
1819
|
-
type: "
|
|
1820
|
+
type: "message_activity",
|
|
1820
1821
|
event: {
|
|
1821
1822
|
sequence: state.eventSequence, timestamp: now.toISOString(),
|
|
1822
1823
|
messageIdSuffix: suffix,
|
|
@@ -2051,7 +2052,7 @@ export class GatewayStore {
|
|
|
2051
2052
|
assertConfiguredBounds(state) {
|
|
2052
2053
|
const active = state.messages.filter((message) => message.state.phase !== "terminal");
|
|
2053
2054
|
if (state.routes.some((route) => !this.config.allowedHosts.includes(route.binding.hostId) ||
|
|
2054
|
-
!routeModeMatchesHost(route, this.config.hostId
|
|
2055
|
+
!routeModeMatchesHost(route, this.config.hostId)) ||
|
|
2055
2056
|
state.routes.length > this.config.limits.maxRoutes ||
|
|
2056
2057
|
state.consentEdges.length > this.config.limits.maxConsentEdges ||
|
|
2057
2058
|
active.length > this.config.limits.maxQueueMessages ||
|
|
@@ -2171,7 +2172,7 @@ export class GatewayStore {
|
|
|
2171
2172
|
}
|
|
2172
2173
|
if (!markerExists) {
|
|
2173
2174
|
const entries = await readdir(root);
|
|
2174
|
-
if (existed && entries.
|
|
2175
|
+
if (existed && entries.some((entry) => entry !== "nodes.json")) {
|
|
2175
2176
|
throw new BridgeError("GATEWAY_STATE_DIRECTORY_NOT_OWNED", "The existing state directory is non-empty and lacks the ownership marker.");
|
|
2176
2177
|
}
|
|
2177
2178
|
const marker = await open(markerPath, constants.O_CREAT |
|
|
@@ -2338,11 +2339,11 @@ export class GatewayStore {
|
|
|
2338
2339
|
catch {
|
|
2339
2340
|
throw new BridgeError("CORRUPT_GATEWAY_STATE", "The gateway controller state is not valid JSON.");
|
|
2340
2341
|
}
|
|
2341
|
-
if (isObject(parsed) && parsed.schemaVersion
|
|
2342
|
-
throw new BridgeError("
|
|
2342
|
+
if (isObject(parsed) && Object.hasOwn(parsed, "schemaVersion") && parsed.schemaVersion !== 4) {
|
|
2343
|
+
throw new BridgeError("GATEWAY_STATE_SCHEMA_UNSUPPORTED", "The gateway state schema is unsupported. Stop Embassy, move gateway-state.json aside, keep nodes.json, then restart and re-register, select, and pair routes.");
|
|
2343
2344
|
}
|
|
2344
|
-
if (!
|
|
2345
|
-
throw new BridgeError("CORRUPT_GATEWAY_STATE", "The gateway controller state failed strict
|
|
2345
|
+
if (!isGatewayPersistedStateV4(parsed)) {
|
|
2346
|
+
throw new BridgeError("CORRUPT_GATEWAY_STATE", "The gateway controller state failed strict v4 schema validation.");
|
|
2346
2347
|
}
|
|
2347
2348
|
this.assertConfiguredBounds(parsed);
|
|
2348
2349
|
return parsed;
|