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.
Files changed (72) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +28 -21
  4. package/README.zh-CN.md +26 -20
  5. package/dist/src/gateway/claude-helper-protocol.d.ts +1 -1
  6. package/dist/src/gateway/claude-helper-protocol.js +3 -2
  7. package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
  8. package/dist/src/gateway/claude-helper-supervisor.d.ts +1 -1
  9. package/dist/src/gateway/claude-helper-supervisor.js +1 -1
  10. package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
  11. package/dist/src/gateway/claude-helper.js +12 -4
  12. package/dist/src/gateway/claude-helper.js.map +1 -1
  13. package/dist/src/gateway/cli-copy.d.ts +1 -1
  14. package/dist/src/gateway/cli-copy.en.d.ts +9 -4
  15. package/dist/src/gateway/cli-copy.en.js +10 -6
  16. package/dist/src/gateway/cli-copy.en.js.map +1 -1
  17. package/dist/src/gateway/cli-copy.js +5 -0
  18. package/dist/src/gateway/cli-copy.js.map +1 -1
  19. package/dist/src/gateway/cli-copy.zh-CN.d.ts +9 -4
  20. package/dist/src/gateway/cli-copy.zh-CN.js +10 -6
  21. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
  22. package/dist/src/gateway/cli.d.ts +3 -9
  23. package/dist/src/gateway/cli.js +56 -63
  24. package/dist/src/gateway/cli.js.map +1 -1
  25. package/dist/src/gateway/codex-local-transport.d.ts +1 -1
  26. package/dist/src/gateway/codex-local-transport.js +2 -2
  27. package/dist/src/gateway/codex-local-transport.js.map +1 -1
  28. package/dist/src/gateway/config.d.ts +4 -3
  29. package/dist/src/gateway/config.js +5 -11
  30. package/dist/src/gateway/config.js.map +1 -1
  31. package/dist/src/gateway/control.d.ts +20 -41
  32. package/dist/src/gateway/control.js +54 -96
  33. package/dist/src/gateway/control.js.map +1 -1
  34. package/dist/src/gateway/dashboard-copy.en.js +2 -2
  35. package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
  36. package/dist/src/gateway/dashboard-copy.zh-CN.js +2 -2
  37. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
  38. package/dist/src/gateway/federation-nodes.d.ts +1 -2
  39. package/dist/src/gateway/federation-nodes.js +29 -14
  40. package/dist/src/gateway/federation-nodes.js.map +1 -1
  41. package/dist/src/gateway/live-dashboard-app/app.js +2 -2
  42. package/dist/src/gateway/live-dashboard-command.d.ts +3 -1
  43. package/dist/src/gateway/live-dashboard-command.js +1 -1
  44. package/dist/src/gateway/live-dashboard-command.js.map +1 -1
  45. package/dist/src/gateway/peer-protocol.d.ts +1 -0
  46. package/dist/src/gateway/peer-protocol.js +5 -0
  47. package/dist/src/gateway/peer-protocol.js.map +1 -1
  48. package/dist/src/gateway/providers.d.ts +3 -3
  49. package/dist/src/gateway/providers.js +7 -10
  50. package/dist/src/gateway/providers.js.map +1 -1
  51. package/dist/src/gateway/server.d.ts +1 -2
  52. package/dist/src/gateway/server.js +6 -13
  53. package/dist/src/gateway/server.js.map +1 -1
  54. package/dist/src/gateway/service.d.ts +3 -1
  55. package/dist/src/gateway/service.js +93 -39
  56. package/dist/src/gateway/service.js.map +1 -1
  57. package/dist/src/gateway/store.d.ts +1 -1
  58. package/dist/src/gateway/store.js +30 -29
  59. package/dist/src/gateway/store.js.map +1 -1
  60. package/dist/src/gateway/types.d.ts +4 -4
  61. package/docs/CONFIGURATION.md +27 -21
  62. package/docs/CONFIGURATION.zh-CN.md +19 -15
  63. package/docs/DASHBOARD.md +1 -1
  64. package/docs/DASHBOARD.zh-CN.md +1 -1
  65. package/docs/DELIVERY.md +2 -2
  66. package/docs/DELIVERY.zh-CN.md +2 -2
  67. package/docs/GATEWAY-ARCHITECTURE.md +48 -58
  68. package/package.json +1 -1
  69. package/skills/embassy-peer/SKILL.md +20 -15
  70. package/dist/src/gateway/state-v2-to-v3.d.ts +0 -24
  71. package/dist/src/gateway/state-v2-to-v3.js +0 -1034
  72. 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
- // New/public tokens are exact 24-character handles. The wider private read
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
- (value.busyPolicy === "queue" || value.busyPolicy === "refuse") &&
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 isLegacyActivity(value) {
399
+ function isMessageActivity(value) {
400
400
  return (isObject(value) &&
401
401
  hasOnlyKeys(value, ["type", "event"]) &&
402
- value.type === "legacy_message" &&
402
+ value.type === "message_activity" &&
403
403
  isNormalizedEvent(value.event));
404
404
  }
405
- export function isGatewayPersistedStateV3(value) {
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 !== 3 ||
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) => isLegacyActivity(entry) || isRuntimeActivity(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: 3,
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 ?? this.config.allowedHosts[0];
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 ?? this.config.allowedHosts[0];
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
- ? (this.config.hostId ?? this.config.allowedHosts[0]) : target.binding.hostId;
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) === (this.config.hostId ?? this.config.allowedHosts[0]),
1451
+ mutable: edgeOwnerHost(edge.endpoints) === this.config.hostId,
1452
1452
  }));
1453
1453
  const currentEvents = state.messages.map((message) => this.projectMessageEvent(message));
1454
- const legacyEvents = state.activity
1455
- .filter((entry) => entry.type === "legacy_message")
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 = [...legacyEvents, ...currentEvents]
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, legacyEvents.length + currentEvents.length - messages.length),
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 ?? this.config.allowedHosts[0];
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: "legacy_message",
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 ?? this.config.allowedHosts[0])) ||
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.length > 0) {
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 === 2) {
2342
- throw new BridgeError("GATEWAY_STATE_CONVERSION_REQUIRED", "The gateway state must be converted offline before this release can start.");
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 (!isGatewayPersistedStateV3(parsed)) {
2345
- throw new BridgeError("CORRUPT_GATEWAY_STATE", "The gateway controller state failed strict v3 schema validation.");
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;