agent-embassy 2.0.1 → 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.
Files changed (147) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/CONTRIBUTING.md +23 -36
  3. package/README.md +123 -222
  4. package/SECURITY.md +72 -93
  5. package/dist/src/errors.d.ts +10 -1
  6. package/dist/src/errors.js +3 -1
  7. package/dist/src/errors.js.map +1 -1
  8. package/dist/src/gateway/claude-helper-protocol.d.ts +8 -11
  9. package/dist/src/gateway/claude-helper-protocol.js +12 -11
  10. package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
  11. package/dist/src/gateway/claude-helper-supervisor.d.ts +1 -5
  12. package/dist/src/gateway/claude-helper-supervisor.js +45 -17
  13. package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
  14. package/dist/src/gateway/claude-helper.js +6 -6
  15. package/dist/src/gateway/claude-helper.js.map +1 -1
  16. package/dist/src/gateway/claude-peer.d.ts +0 -3
  17. package/dist/src/gateway/claude-peer.js +7 -19
  18. package/dist/src/gateway/claude-peer.js.map +1 -1
  19. package/dist/src/gateway/cli.d.ts +17 -7
  20. package/dist/src/gateway/cli.js +945 -265
  21. package/dist/src/gateway/cli.js.map +1 -1
  22. package/dist/src/gateway/codex-socket-holder.d.ts +26 -0
  23. package/dist/src/gateway/codex-socket-holder.js +76 -0
  24. package/dist/src/gateway/codex-socket-holder.js.map +1 -0
  25. package/dist/src/gateway/codex-stateless-transport.js +1 -1
  26. package/dist/src/gateway/codex-stateless-transport.js.map +1 -1
  27. package/dist/src/gateway/config.d.ts +1 -10
  28. package/dist/src/gateway/config.js +4 -10
  29. package/dist/src/gateway/config.js.map +1 -1
  30. package/dist/src/gateway/control.d.ts +66 -80
  31. package/dist/src/gateway/control.js +92 -147
  32. package/dist/src/gateway/control.js.map +1 -1
  33. package/dist/src/gateway/federation-nodes.d.ts +29 -2
  34. package/dist/src/gateway/federation-nodes.js +177 -7
  35. package/dist/src/gateway/federation-nodes.js.map +1 -1
  36. package/dist/src/gateway/peer-client.d.ts +4 -3
  37. package/dist/src/gateway/peer-client.js +22 -13
  38. package/dist/src/gateway/peer-client.js.map +1 -1
  39. package/dist/src/gateway/peer-protocol.d.ts +13 -11
  40. package/dist/src/gateway/peer-protocol.js +14 -12
  41. package/dist/src/gateway/peer-protocol.js.map +1 -1
  42. package/dist/src/gateway/provenance-envelope.d.ts +0 -1
  43. package/dist/src/gateway/provenance-envelope.js +4 -19
  44. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  45. package/dist/src/gateway/providers.d.ts +11 -7
  46. package/dist/src/gateway/providers.js +49 -50
  47. package/dist/src/gateway/providers.js.map +1 -1
  48. package/dist/src/gateway/server.d.ts +4 -14
  49. package/dist/src/gateway/server.js +32 -54
  50. package/dist/src/gateway/server.js.map +1 -1
  51. package/dist/src/gateway/service-agent.d.ts +187 -0
  52. package/dist/src/gateway/service-agent.js +758 -0
  53. package/dist/src/gateway/service-agent.js.map +1 -0
  54. package/dist/src/gateway/service.d.ts +119 -38
  55. package/dist/src/gateway/service.js +754 -798
  56. package/dist/src/gateway/service.js.map +1 -1
  57. package/dist/src/gateway/status-view.d.ts +119 -0
  58. package/dist/src/gateway/status-view.js +484 -0
  59. package/dist/src/gateway/status-view.js.map +1 -0
  60. package/dist/src/gateway/store.d.ts +106 -21
  61. package/dist/src/gateway/store.js +463 -537
  62. package/dist/src/gateway/store.js.map +1 -1
  63. package/dist/src/gateway/types.d.ts +64 -102
  64. package/dist/src/gateway/types.js +40 -86
  65. package/dist/src/gateway/types.js.map +1 -1
  66. package/docs/CONFIGURATION.md +174 -45
  67. package/docs/DELIVERY.md +11 -11
  68. package/docs/GATEWAY-ARCHITECTURE.md +292 -384
  69. package/package.json +4 -12
  70. package/skills/embassy-peer/SKILL.md +69 -92
  71. package/skills/embassy-peer/agents/openai.yaml +1 -1
  72. package/README.zh-CN.md +0 -275
  73. package/assets/live-dashboard/app.css +0 -1619
  74. package/assets/vendor/react/LICENSE +0 -21
  75. package/assets/vendor/react/react-dom.production.min.js +0 -267
  76. package/assets/vendor/react/react.production.min.js +0 -31
  77. package/dist/src/gateway/acp-client.d.ts +0 -110
  78. package/dist/src/gateway/acp-client.js +0 -407
  79. package/dist/src/gateway/acp-client.js.map +0 -1
  80. package/dist/src/gateway/acp-provider.d.ts +0 -66
  81. package/dist/src/gateway/acp-provider.js +0 -275
  82. package/dist/src/gateway/acp-provider.js.map +0 -1
  83. package/dist/src/gateway/claude-helper-client.d.ts +0 -2
  84. package/dist/src/gateway/claude-helper-client.js +0 -2
  85. package/dist/src/gateway/claude-helper-client.js.map +0 -1
  86. package/dist/src/gateway/cli-copy.d.ts +0 -8
  87. package/dist/src/gateway/cli-copy.en.d.ts +0 -22
  88. package/dist/src/gateway/cli-copy.en.js +0 -62
  89. package/dist/src/gateway/cli-copy.en.js.map +0 -1
  90. package/dist/src/gateway/cli-copy.js +0 -27
  91. package/dist/src/gateway/cli-copy.js.map +0 -1
  92. package/dist/src/gateway/cli-copy.zh-CN.d.ts +0 -22
  93. package/dist/src/gateway/cli-copy.zh-CN.js +0 -62
  94. package/dist/src/gateway/cli-copy.zh-CN.js.map +0 -1
  95. package/dist/src/gateway/codex-doctor.d.ts +0 -36
  96. package/dist/src/gateway/codex-doctor.js +0 -127
  97. package/dist/src/gateway/codex-doctor.js.map +0 -1
  98. package/dist/src/gateway/dashboard-copy.d.ts +0 -7
  99. package/dist/src/gateway/dashboard-copy.en.d.ts +0 -504
  100. package/dist/src/gateway/dashboard-copy.en.js +0 -505
  101. package/dist/src/gateway/dashboard-copy.en.js.map +0 -1
  102. package/dist/src/gateway/dashboard-copy.js +0 -514
  103. package/dist/src/gateway/dashboard-copy.js.map +0 -1
  104. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +0 -504
  105. package/dist/src/gateway/dashboard-copy.zh-CN.js +0 -505
  106. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +0 -1
  107. package/dist/src/gateway/dashboard-model.d.ts +0 -343
  108. package/dist/src/gateway/dashboard-model.js +0 -1061
  109. package/dist/src/gateway/dashboard-model.js.map +0 -1
  110. package/dist/src/gateway/dashboard.d.ts +0 -20
  111. package/dist/src/gateway/dashboard.js +0 -874
  112. package/dist/src/gateway/dashboard.js.map +0 -1
  113. package/dist/src/gateway/deepseek-detect.d.ts +0 -14
  114. package/dist/src/gateway/deepseek-detect.js +0 -41
  115. package/dist/src/gateway/deepseek-detect.js.map +0 -1
  116. package/dist/src/gateway/live-dashboard-app/app.js +0 -2385
  117. package/dist/src/gateway/live-dashboard-assets.d.ts +0 -10
  118. package/dist/src/gateway/live-dashboard-assets.js +0 -74
  119. package/dist/src/gateway/live-dashboard-assets.js.map +0 -1
  120. package/dist/src/gateway/live-dashboard-command.d.ts +0 -60
  121. package/dist/src/gateway/live-dashboard-command.js +0 -334
  122. package/dist/src/gateway/live-dashboard-command.js.map +0 -1
  123. package/dist/src/gateway/live-dashboard-http.d.ts +0 -39
  124. package/dist/src/gateway/live-dashboard-http.js +0 -383
  125. package/dist/src/gateway/live-dashboard-http.js.map +0 -1
  126. package/dist/src/gateway/live-dashboard-protocol.d.ts +0 -34
  127. package/dist/src/gateway/live-dashboard-protocol.js +0 -114
  128. package/dist/src/gateway/live-dashboard-protocol.js.map +0 -1
  129. package/dist/src/gateway/live-dashboard-server.d.ts +0 -33
  130. package/dist/src/gateway/live-dashboard-server.js +0 -144
  131. package/dist/src/gateway/live-dashboard-server.js.map +0 -1
  132. package/dist/src/gateway/live-dashboard-stream.d.ts +0 -46
  133. package/dist/src/gateway/live-dashboard-stream.js +0 -234
  134. package/dist/src/gateway/live-dashboard-stream.js.map +0 -1
  135. package/dist/src/gateway/live-dashboard.d.ts +0 -28
  136. package/dist/src/gateway/live-dashboard.js +0 -154
  137. package/dist/src/gateway/live-dashboard.js.map +0 -1
  138. package/dist/src/gateway/locale.d.ts +0 -4
  139. package/dist/src/gateway/locale.js +0 -10
  140. package/dist/src/gateway/locale.js.map +0 -1
  141. package/dist/src/gateway/progress-watch-machine.d.ts +0 -45
  142. package/dist/src/gateway/progress-watch-machine.js +0 -70
  143. package/dist/src/gateway/progress-watch-machine.js.map +0 -1
  144. package/docs/CONFIGURATION.zh-CN.md +0 -97
  145. package/docs/DASHBOARD.md +0 -98
  146. package/docs/DASHBOARD.zh-CN.md +0 -49
  147. package/docs/DELIVERY.zh-CN.md +0 -55
@@ -4,10 +4,10 @@ import { chmod, lstat, mkdtemp, realpath, rename, rmdir, unlink } from "node:fs/
4
4
  import net from "node:net";
5
5
  import path from "node:path";
6
6
  import { TextDecoder } from "node:util";
7
- import { CONNECTOR_OBSERVATION_STALE_AFTER_MS, GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET, gatewayProviders, gatewayPublicSnapshotLimits, isGatewayProvider, isMessageDirection, isPublicRegistryObservationSnapshot, } from "./types.js";
8
- import { decodePeerParams, decodePeerResult } from "./peer-protocol.js";
7
+ import { CONNECTOR_OBSERVATION_STALE_AFTER_MS, GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET, gatewayPublicSnapshotLimits, isGatewayProvider, isMessageDirection, isPublicRegistryObservationSnapshot, } from "./types.js";
8
+ import { decodePeerParams, decodePeerResult, isPeerHandoffRefusal } from "./peer-protocol.js";
9
9
  import { isPeerMailboxAwaitResult } from "./peer-mailbox.js";
10
- export const GATEWAY_CONTROL_PROTOCOL_VERSION = 2;
10
+ export const GATEWAY_CONTROL_PROTOCOL_VERSION = 4;
11
11
  export const GATEWAY_CONTROL_MAX_FRAME_BYTES = 32 * 1024;
12
12
  export const GATEWAY_CONTROL_MAX_RESPONSE_BYTES = 256 * 1024;
13
13
  export const GATEWAY_CONTROL_MAX_MESSAGE_BYTES = 16 * 1024;
@@ -30,12 +30,16 @@ const MESSAGE_SUFFIX_PATTERN = /^[0-9a-f]{8}$/;
30
30
  const CONVERSATION_SUFFIX_PATTERN = /^[A-Za-z0-9_-]{8}$/;
31
31
  const ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
32
32
  export const gatewayControlMethods = [
33
- "health", "register_codex", "unregister_codex", "remove_codex_registration",
34
- "select_claude", "unselect_claude", "pair", "unpair", "list_snapshot",
35
- "observe_snapshot", "delivery_status", "untrack", "send", "reply",
36
- "refresh_dashboard", "peer_catalog", "peer_handoff",
33
+ "health", "register_codex", "unregister_codex", "list_snapshot",
34
+ "observe_snapshot", "delivery_status", "send",
35
+ "refresh_discovery", "peer_catalog", "peer_handoff",
37
36
  "register_peer", "unregister_peer", "await_peer", "peer_receipt",
37
+ "retire_route",
38
38
  ];
39
+ /** Bounds on an ephemeral peer registration's lifetime, in milliseconds. */
40
+ export const GATEWAY_EPHEMERAL_PEER_TTL_DEFAULT_MS = 300_000;
41
+ export const GATEWAY_EPHEMERAL_PEER_TTL_MIN_MS = 1_000;
42
+ export const GATEWAY_EPHEMERAL_PEER_TTL_MAX_MS = 3_600_000;
39
43
  const WIRE_ERROR_MESSAGES = {
40
44
  INVALID_JSON: "The control frame is not valid JSON.", FRAME_TOO_LARGE: "The control frame exceeds the size limit.",
41
45
  INVALID_REQUEST: "The control request is invalid.", UNSUPPORTED_VERSION: "The control protocol version is unsupported.",
@@ -129,12 +133,6 @@ function isNonNegativeInteger(value) {
129
133
  value >= 0 &&
130
134
  value <= MAX_REVISION);
131
135
  }
132
- function isTrackIdleMinutes(value) {
133
- return (typeof value === "number" &&
134
- Number.isSafeInteger(value) &&
135
- value >= 1 &&
136
- value <= 24 * 60);
137
- }
138
136
  function isIsoTimestamp(value) {
139
137
  if (typeof value !== "string" || !ISO_TIMESTAMP_PATTERN.test(value)) {
140
138
  return false;
@@ -159,7 +157,6 @@ function isMessageText(value) {
159
157
  }
160
158
  const exact = hasExactKeys;
161
159
  const nonNegative = isNonNegativeInteger;
162
- const trackMinutes = isTrackIdleMinutes;
163
160
  const iso = isIsoTimestamp;
164
161
  const uuid = isUuid;
165
162
  const alias = isAlias;
@@ -200,9 +197,20 @@ function decodePeerHandoff(value) {
200
197
  }
201
198
  }
202
199
  function decodeRegisterPeer(value) {
203
- if (!shape(value, { alias: peerAlias }, { token: peerToken }))
200
+ // `ephemeral` belongs only to a first registration: re-presenting a token
201
+ // proves an existing route, and an existing route's lifetime is already
202
+ // fixed. `ttlMs` without `ephemeral` would name a lifetime nothing honours.
203
+ if (!shape(value, { alias: peerAlias }, { token: peerToken, ephemeral: oneOf(true),
204
+ ttlMs: (item) => Number.isSafeInteger(item) &&
205
+ Number(item) >= GATEWAY_EPHEMERAL_PEER_TTL_MIN_MS &&
206
+ Number(item) <= GATEWAY_EPHEMERAL_PEER_TTL_MAX_MS }) ||
207
+ (value.ephemeral !== undefined && value.token !== undefined) ||
208
+ (value.ttlMs !== undefined && value.ephemeral === undefined))
204
209
  invalid();
205
- return { alias: value.alias, ...(value.token === undefined ? {} : { token: value.token }) };
210
+ return { alias: value.alias,
211
+ ...(value.token === undefined ? {} : { token: value.token }),
212
+ ...(value.ephemeral === undefined ? {} : { ephemeral: true }),
213
+ ...(value.ttlMs === undefined ? {} : { ttlMs: value.ttlMs }) };
206
214
  }
207
215
  function decodePeerPrincipal(value) {
208
216
  if (!shape(value, { alias: peerAlias, token: peerToken }))
@@ -225,6 +233,8 @@ const isPeerCatalog = (value) => {
225
233
  }
226
234
  };
227
235
  const isPeerHandoffResult = (value) => {
236
+ if (isPeerHandoffRefusal(value))
237
+ return true;
228
238
  try {
229
239
  decodePeerResult("handoff", value);
230
240
  return true;
@@ -251,79 +261,40 @@ function decodeUnregister(value) {
251
261
  invalid();
252
262
  return { alias: value.alias, threadId: value.threadId.toLowerCase() };
253
263
  }
254
- function decodeRemoval(value) {
255
- if (!shape(value, { alias }) || !value.alias.startsWith("codex-"))
256
- invalid();
257
- return { alias: value.alias };
258
- }
259
- function decodeSelection(value) {
260
- if (!isRecord(value) || !exact(value, ["alias"]) ||
261
- typeof value.alias !== "string" || !isClaudeSessionSelector(value.alias))
262
- invalid();
263
- return { alias: UUID_PATTERN.test(value.alias) ? value.alias.toLowerCase() : value.alias };
264
- }
265
- function decodePair(value) {
266
- if (!isRecord(value) || !exact(value, ["aliases"]) || !Array.isArray(value.aliases) ||
267
- value.aliases.length !== 2 || !alias(value.aliases[0]) || !alias(value.aliases[1]) ||
268
- value.aliases[0] === value.aliases[1])
269
- invalid();
270
- return { aliases: [value.aliases[0], value.aliases[1]] };
271
- }
272
264
  function decodeSend(value) {
273
- if (!isRecord(value) || !exact(value, ["fromAlias", "toAlias", "text"], ["threadId", "replyAddress", "peerToken", "expectsReply", "trackIdleMinutes"]) ||
274
- !alias(value.fromAlias) || typeof value.toAlias !== "string" || !isClaudeSessionSelector(value.toAlias) ||
275
- !messageText(value.text) || [value.threadId, value.replyAddress, value.peerToken].filter((item) => item !== undefined).length !== 1 ||
265
+ if (!isRecord(value) || !exact(value, ["fromAlias", "text"], ["toAlias", "conversationId", "threadId", "replyAddress", "peerToken", "expectsReply"]) ||
266
+ !alias(value.fromAlias) || !messageText(value.text) ||
267
+ [value.toAlias, value.conversationId].filter((item) => item !== undefined).length !== 1 ||
268
+ (value.toAlias !== undefined && (typeof value.toAlias !== "string" || !isClaudeSessionSelector(value.toAlias))) ||
269
+ (value.conversationId !== undefined && (typeof value.conversationId !== "string" || !isGatewayConversationId(value.conversationId))) ||
270
+ [value.threadId, value.replyAddress, value.peerToken].filter((item) => item !== undefined).length !== 1 ||
276
271
  (value.threadId !== undefined && !uuid(value.threadId)) ||
277
272
  (value.replyAddress !== undefined && (typeof value.replyAddress !== "string" || !isGatewayReplyAddress(value.replyAddress))) ||
278
273
  (value.peerToken !== undefined && !peerToken(value.peerToken)) ||
279
- (value.expectsReply !== undefined && typeof value.expectsReply !== "boolean") ||
280
- (value.trackIdleMinutes !== undefined && !trackMinutes(value.trackIdleMinutes)))
274
+ (value.expectsReply !== undefined &&
275
+ (typeof value.expectsReply !== "boolean" || value.conversationId !== undefined)))
281
276
  invalid();
282
277
  const authority = value.threadId !== undefined ? { threadId: value.threadId.toLowerCase() }
283
278
  : value.replyAddress !== undefined ? { replyAddress: value.replyAddress }
284
279
  : { peerToken: value.peerToken };
285
- return { fromAlias: value.fromAlias, toAlias: UUID_PATTERN.test(value.toAlias) ? value.toAlias.toLowerCase() : value.toAlias,
286
- text: value.text, ...authority, expectsReply: value.expectsReply === true,
287
- ...(value.trackIdleMinutes === undefined ? {} : { trackIdleMinutes: value.trackIdleMinutes }) };
288
- }
289
- function normalizeReplyCaller(value) {
290
- if (!isRecord(value))
291
- invalid();
292
- if (value.kind === "codex" && shape(value, { kind: oneOf("codex"), alias, threadId: uuid }))
293
- return { kind: "codex", alias: value.alias,
294
- threadId: value.threadId.toLowerCase() };
295
- if (value.kind === "claude" && isRecord(value) && exact(value, ["kind", "alias"], ["replyAddress"]) &&
296
- alias(value.alias) && (value.replyAddress === undefined ||
297
- (typeof value.replyAddress === "string" && isGatewayReplyAddress(value.replyAddress))))
298
- return { kind: "claude", alias: value.alias,
299
- ...(value.replyAddress === undefined ? {} : { replyAddress: value.replyAddress }) };
300
- if (value.kind === "peer" && shape(value, { kind: oneOf("peer"), alias: peerAlias, token: peerToken }))
301
- return { kind: "peer", alias: value.alias, token: value.token };
302
- return invalid();
303
- }
304
- function decodeReply(value) {
305
- if (!isRecord(value) || !exact(value, ["conversationId", "text", "caller"], ["trackIdleMinutes"]) ||
306
- typeof value.conversationId !== "string" || !isGatewayConversationId(value.conversationId) ||
307
- !messageText(value.text) ||
308
- (value.trackIdleMinutes !== undefined && !trackMinutes(value.trackIdleMinutes)))
309
- invalid();
310
- return { conversationId: value.conversationId, text: value.text, caller: normalizeReplyCaller(value.caller),
311
- ...(value.trackIdleMinutes === undefined ? {} : { trackIdleMinutes: value.trackIdleMinutes }) };
280
+ const target = value.conversationId !== undefined
281
+ ? { conversationId: value.conversationId }
282
+ : { toAlias: UUID_PATTERN.test(value.toAlias) ? value.toAlias.toLowerCase() : value.toAlias,
283
+ expectsReply: value.expectsReply === true };
284
+ return { fromAlias: value.fromAlias, text: value.text, ...target, ...authority };
312
285
  }
313
286
  function decodeDeliveryStatus(value) {
314
287
  if (!shape(value, { token: (item) => typeof item === "string" && DELIVERY_TOKEN_PATTERN.test(item) }))
315
288
  invalid();
316
289
  return { token: value.token };
317
290
  }
318
- function decodeUntrack(value) {
319
- if (!shape(value, { conversationId: (item) => typeof item === "string" && CONVERSATION_ID_PATTERN.test(item) }))
291
+ function decodeRetire(value) {
292
+ if (!shape(value, { alias }))
320
293
  invalid();
321
- return { conversationId: value.conversationId };
294
+ return { alias: value.alias };
322
295
  }
323
296
  function isDecision(value) {
324
- return shape(value, { accepted: (item) => typeof item === "boolean", code: oneOf("ok", "not_found", "conflict", "watch_owner_conflict", "route_mismatch", "busy", "unavailable", "rejected") }, { ownerHost: host }) && (value.accepted === true
325
- ? value.code === "ok" && value.ownerHost === undefined
326
- : value.code !== "ok" && (value.ownerHost === undefined || value.code === "conflict"));
297
+ return shape(value, { accepted: (item) => typeof item === "boolean", code: oneOf("ok", "not_found", "conflict", "route_mismatch", "busy", "unavailable", "rejected") }) && (value.accepted === true ? value.code === "ok" : value.code !== "ok");
327
298
  }
328
299
  function isRegisterPeerResult(value) {
329
300
  return isDecision(value) || shape(value, { accepted: oneOf(true), code: oneOf("ok"), token: peerToken });
@@ -332,14 +303,21 @@ function isHealthResult(value) {
332
303
  return shape(value, { status: oneOf("ok", "degraded"), revision: nonNegative });
333
304
  }
334
305
  function isSendResult(value) {
335
- if (!isRecord(value) || value.accepted !== true)
336
- return isDecision(value);
306
+ if (!isRecord(value) || value.accepted !== true) {
307
+ return shape(value, { accepted: oneOf(false), code: oneOf("not_found", "conflict", "route_mismatch", "busy", "unavailable", "rejected") }, { reason: safeCode });
308
+ }
337
309
  return shape(value, { accepted: oneOf(true), code: oneOf("ok"),
338
310
  conversationId: (item) => typeof item === "string" && CONVERSATION_ID_PATTERN.test(item),
339
311
  deliveryToken: (item) => typeof item === "string" && DELIVERY_TOKEN_PATTERN.test(item) });
340
312
  }
313
+ function isRetireResult(value) {
314
+ if (isRecord(value) && value.accepted === false)
315
+ return isSendResult(value);
316
+ return shape(value, { accepted: oneOf(true), code: oneOf("ok"),
317
+ settlements: (counts) => shape(counts, { cancelled: nonNegative, ambiguous: nonNegative, unconfirmed: nonNegative }) });
318
+ }
341
319
  function isRefreshResult(value) {
342
- return shape(value, { accepted: (item) => typeof item === "boolean", code: oneOf("ok", "not_found", "conflict", "watch_owner_conflict", "route_mismatch", "busy", "unavailable", "rejected"), revision: nonNegative }) && isDecision({ accepted: value.accepted, code: value.code });
320
+ return shape(value, { accepted: (item) => typeof item === "boolean", code: oneOf("ok", "not_found", "conflict", "route_mismatch", "busy", "unavailable", "rejected"), revision: nonNegative }) && isDecision({ accepted: value.accepted, code: value.code });
343
321
  }
344
322
  const TERMINAL = new Set(["delivered", "unconfirmed", "expired", "failed", "ambiguous", "cancelled"]);
345
323
  function isDeliveryStatusResult(value) {
@@ -359,13 +337,9 @@ function isConnectorSnapshot(value) {
359
337
  if (!shape(value, { provider: isGatewayProvider, host, health: isConnectorHealth,
360
338
  protocol: (item) => typeof item === "string" && PROTOCOL_PATTERN.test(item),
361
339
  protocolVersion: (item) => typeof item === "string" && PROTOCOL_VERSION_PATTERN.test(item) }, { lastSeenAt: iso, observationAgeMs: nonNegative, safeErrorCode: safeCode,
362
- registry: isPublicRegistryObservationSnapshot, codexDoctor: (item) => shape(item, {
363
- conditions: (rows) => Array.isArray(rows) && rows.length > 0 && rows.length <= 2 &&
364
- rows.every(oneOf("split_brain", "orphaned", "attached", "observation_stale", "managed_layout_missing", "unknown")),
365
- }) }))
340
+ registry: isPublicRegistryObservationSnapshot }))
366
341
  return false;
367
- return (value.codexDoctor === undefined || value.provider === "codex") &&
368
- (value.registry === undefined || value.provider === "claude");
342
+ return value.registry === undefined || value.provider === "claude";
369
343
  }
370
344
  function isRouteSnapshot(value) {
371
345
  if (!shape(value, { alias, provider: isGatewayProvider, host, enabled: (item) => typeof item === "boolean",
@@ -376,24 +350,10 @@ function isRouteSnapshot(value) {
376
350
  return row.alias.endsWith(`@${row.host}`) &&
377
351
  (row.oldestQueuedAt === undefined ? row.queueDepth === 0 : row.queueDepth > 0);
378
352
  }
379
- function compareConsentEndpoints(left, right) {
380
- return gatewayProviders.indexOf(left.provider) -
381
- gatewayProviders.indexOf(right.provider) || left.alias.localeCompare(right.alias);
382
- }
383
- function isConsentEdgeSnapshot(value) {
384
- const endpoint = (item) => shape(item, { alias, provider: isGatewayProvider });
385
- if (!shape(value, { endpoints: (rows) => Array.isArray(rows) && rows.length === 2 && rows.every(endpoint),
386
- host, counters: isRouteCounters }, { mutable: (item) => typeof item === "boolean" }))
387
- return false;
388
- const [left, right] = value.endpoints;
389
- const hosts = [left.alias.slice(left.alias.lastIndexOf("@") + 1), right.alias.slice(right.alias.lastIndexOf("@") + 1)];
390
- return left.alias !== right.alias && compareConsentEndpoints(left, right) < 0 &&
391
- (left.provider !== right.provider || hosts[0] !== hosts[1]) && value.host === [...hosts].sort()[0];
392
- }
393
353
  function isAvailablePeerSnapshot(value) {
394
354
  return shape(value, { alias, provider: oneOf("claude"), host,
395
355
  state: oneOf("idle", "busy", "awaiting_approval", "offline"),
396
- validated: (item) => typeof item === "boolean", selected: (item) => typeof item === "boolean" }, { lastSeenAt: iso, safeErrorCode: safeCode }) &&
356
+ validated: (item) => typeof item === "boolean", routed: (item) => typeof item === "boolean" }, { lastSeenAt: iso, safeErrorCode: safeCode }) &&
397
357
  value.alias.endsWith(`@${String(value.host)}`);
398
358
  }
399
359
  function isNormalizedMessageEvent(value) {
@@ -415,40 +375,17 @@ function isAccounting(value) {
415
375
  function isSafeAlert(value) {
416
376
  return shape(value, { code: safeCode, severity: oneOf("info", "warning", "error"), timestamp: iso }, { provider: isGatewayProvider, host, alias });
417
377
  }
418
- function isProgressWatchSnapshot(value) {
419
- return shape(value, {
420
- conversationIdSuffix: (item) => typeof item === "string" && CONVERSATION_SUFFIX_PATTERN.test(item),
421
- ownerAlias: alias, workerAlias: alias, lastActivityAt: iso, nextActionAt: iso,
422
- nudgeCount: oneOf(0, 1, 2),
423
- }) && value.ownerAlias !== value.workerAlias;
424
- }
425
- function isProgressWatchEventSnapshot(value) {
426
- if (!shape(value, { sequence: nonNegative, timestamp: iso,
427
- conversationIdSuffix: (item) => typeof item === "string" && CONVERSATION_SUFFIX_PATTERN.test(item),
428
- ownerAlias: alias, workerAlias: alias, kind: oneOf("opened", "replaced", "settled"),
429
- actor: oneOf("owner", "worker", "operator", "gateway", "unknown") }, { reason: oneOf("done", "untracked", "idle_timeout", "tracking_disabled", "endpoint_retired", "pair_removed") }))
430
- return false;
431
- if (value.kind === "opened")
432
- return value.actor === "owner" && value.reason === undefined;
433
- if (value.kind === "replaced")
434
- return (value.actor === "owner" || value.actor === "unknown") && value.reason === undefined;
435
- return (value.reason === "done" && (value.actor === "owner" || value.actor === "worker")) ||
436
- (value.reason === "untracked" && value.actor === "operator") ||
437
- ((value.reason === "idle_timeout" || value.reason === "tracking_disabled") && value.actor === "gateway") ||
438
- (value.reason === "endpoint_retired" && (value.actor === "gateway" || value.actor === "operator")) ||
439
- (value.reason === "pair_removed" && value.actor === "operator");
440
- }
441
378
  function isGatewayActivityEvent(value) {
442
379
  if (!shape(value, { sequence: positive, timestamp: iso,
443
- kind: oneOf("discovery", "selection", "registration", "pairing", "watch"),
444
- action: oneOf("discovery_refreshed", "claude_selected", "claude_unselected", "codex_registered", "codex_succeeded", "codex_unregistered", "routes_paired", "routes_unpaired", "watch_ended"),
380
+ kind: oneOf("discovery", "registration"),
381
+ action: oneOf("discovery_refreshed", "codex_registered", "codex_succeeded", "codex_unregistered", "claude_route_installed", "claude_route_retired", "route_retired"),
445
382
  outcome: oneOf("accepted", "rejected"), aliases: (rows) => arrayOf(rows, 2, alias),
446
383
  operatorAction: oneOf(true) }, { safeErrorCode: safeCode }))
447
384
  return false;
448
385
  const allowed = {
449
- discovery: ["discovery_refreshed"], selection: ["claude_selected", "claude_unselected"],
450
- registration: ["codex_registered", "codex_succeeded", "codex_unregistered"],
451
- pairing: ["routes_paired", "routes_unpaired"], watch: ["watch_ended"],
386
+ discovery: ["discovery_refreshed"],
387
+ registration: ["codex_registered", "codex_succeeded", "codex_unregistered",
388
+ "claude_route_installed", "claude_route_retired", "route_retired"],
452
389
  };
453
390
  return new Set(value.aliases).size === value.aliases.length &&
454
391
  (allowed[value.kind]?.includes(value.action) ?? false);
@@ -466,23 +403,20 @@ function isDeadlinePressure(value) {
466
403
  rows.reduce((sum, row) => sum + Number(row.expired), 0) === value.expiredEvents;
467
404
  }
468
405
  function isSnapshotTruncation(value) {
469
- const required = ["connectors", "availablePeers", "routes", "consentEdges", "messages", "alerts"];
470
- const optional = ["progressWatches", "progressWatchEvents", "activityEvents"];
406
+ const required = ["connectors", "availablePeers", "routes", "messages", "alerts"];
407
+ const optional = ["activityEvents"];
471
408
  return isRecord(value) && exact(value, required, optional) &&
472
409
  [...required, ...optional].every((key) => value[key] === undefined || nonNegative(value[key]));
473
410
  }
474
411
  export function isGatewaySnapshot(value) {
475
- if (!shape(value, { schemaVersion: oneOf(2), generatedAt: iso, inboundMode: oneOf("paired", "open"),
412
+ if (!shape(value, { schemaVersion: oneOf(2), generatedAt: iso,
476
413
  health: isConnectorHealth,
477
414
  connectors: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.connectors, isConnectorSnapshot),
478
415
  availablePeers: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.availablePeers, isAvailablePeerSnapshot),
479
416
  routes: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.routes, isRouteSnapshot),
480
- consentEdges: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.consentEdges, isConsentEdgeSnapshot),
481
417
  messages: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.messages, isNormalizedMessageEvent),
482
418
  accounting: isAccounting, alerts: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.alerts, isSafeAlert),
483
- truncation: isSnapshotTruncation }, { progressWatches: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.progressWatches, isProgressWatchSnapshot),
484
- progressWatchEvents: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.progressWatchEvents, isProgressWatchEventSnapshot),
485
- activityEvents: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.activityEvents, isGatewayActivityEvent),
419
+ truncation: isSnapshotTruncation }, { activityEvents: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.activityEvents, isGatewayActivityEvent),
486
420
  deadlinePressure: isDeadlinePressure }))
487
421
  return false;
488
422
  const snapshot = value;
@@ -491,16 +425,13 @@ export function isGatewaySnapshot(value) {
491
425
  const connectorKeys = snapshot.connectors.map((row) => `${row.provider}@${row.host}`);
492
426
  const aliases = snapshot.routes.map((row) => row.alias);
493
427
  const peers = snapshot.availablePeers.map((row) => row.alias);
494
- const edgeKeys = snapshot.consentEdges.map((row) => `${row.endpoints[0].alias}\0${row.endpoints[1].alias}`);
495
- const routeByAlias = new Map(snapshot.routes.map((row) => [row.alias, row]));
496
428
  const honest = snapshot.connectors.every((row) => {
497
429
  const seen = row.lastSeenAt === undefined ? undefined : Date.parse(row.lastSeenAt);
498
430
  const age = seen === undefined ? undefined : Math.min(MAX_REVISION, Math.max(0, generated - seen));
499
431
  return (row.observationAgeMs === undefined || row.observationAgeMs === age) &&
500
432
  (row.health !== "healthy" || (age !== undefined && age <= CONNECTOR_OBSERVATION_STALE_AFTER_MS));
501
433
  });
502
- return honest && unique(connectorKeys) && unique(aliases) && unique(peers) && unique(edgeKeys) &&
503
- snapshot.consentEdges.every((edge) => edge.endpoints.every((endpoint) => routeByAlias.get(endpoint.alias)?.provider === endpoint.provider)) &&
434
+ return honest && unique(connectorKeys) && unique(aliases) && unique(peers) &&
504
435
  Buffer.byteLength(JSON.stringify(snapshot), "utf8") <= GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET;
505
436
  }
506
437
  function isSnapshotObservation(value) {
@@ -508,19 +439,18 @@ function isSnapshotObservation(value) {
508
439
  }
509
440
  const descriptors = {
510
441
  health: { handler: "health", decode: emptyParams, result: isHealthResult, mutation: false }, register_codex: { handler: "registerCodex", decode: decodeRegister, result: isDecision, mutation: true },
511
- unregister_codex: { handler: "unregisterCodex", decode: decodeUnregister, result: isDecision, mutation: true }, remove_codex_registration: { handler: "removeCodexRegistration", decode: decodeRemoval, result: isDecision, mutation: true },
512
- select_claude: { handler: "selectClaude", decode: decodeSelection, result: isDecision, mutation: true }, unselect_claude: { handler: "unselectClaude", decode: decodeSelection, result: isDecision, mutation: true },
513
- pair: { handler: "pair", decode: decodePair, result: isDecision, mutation: true }, unpair: { handler: "unpair", decode: decodePair, result: isDecision, mutation: true },
442
+ unregister_codex: { handler: "unregisterCodex", decode: decodeUnregister, result: isDecision, mutation: true },
514
443
  list_snapshot: { handler: "listSnapshot", decode: emptyParams, result: isGatewaySnapshot, mutation: false }, observe_snapshot: { handler: "observeSnapshot", decode: emptyParams, result: isSnapshotObservation, mutation: false },
515
- delivery_status: { handler: "deliveryStatus", decode: decodeDeliveryStatus, result: isDeliveryStatusResult, mutation: false }, untrack: { handler: "untrack", decode: decodeUntrack, result: isDecision, mutation: false },
444
+ delivery_status: { handler: "deliveryStatus", decode: decodeDeliveryStatus, result: isDeliveryStatusResult, mutation: false },
516
445
  send: { handler: "send", decode: decodeSend, result: isSendResult, mutation: true },
517
- reply: { handler: "reply", decode: decodeReply, result: isSendResult, mutation: true }, refresh_dashboard: { handler: "refreshDashboard", decode: emptyParams, result: isRefreshResult, mutation: false },
446
+ refresh_discovery: { handler: "refreshDiscovery", decode: emptyParams, result: isRefreshResult, mutation: false },
518
447
  peer_catalog: { handler: "peerCatalog", decode: decodePeerCatalog, result: isPeerCatalog, mutation: false },
519
448
  peer_handoff: { handler: "peerHandoff", decode: decodePeerHandoff, result: isPeerHandoffResult, mutation: true },
520
449
  register_peer: { handler: "registerPeer", decode: decodeRegisterPeer, result: isRegisterPeerResult, mutation: true },
521
450
  unregister_peer: { handler: "unregisterPeer", decode: decodePeerPrincipal, result: isDecision, mutation: true },
522
451
  await_peer: { handler: "awaitPeer", decode: decodePeerPrincipal, result: isPeerMailboxAwaitResult, mutation: false },
523
452
  peer_receipt: { handler: "peerReceipt", decode: decodePeerReceipt, result: isDecision, mutation: true },
453
+ retire_route: { handler: "retireRoute", decode: decodeRetire, result: isRetireResult, mutation: true },
524
454
  };
525
455
  function parseRequestObject(value) {
526
456
  if (!isRecord(value) || !exact(value, ["protocolVersion", "method", "params"]))
@@ -974,13 +904,28 @@ export async function sendGatewayControlRequest(options) {
974
904
  }, (error) => {
975
905
  const kind = error instanceof FrameFault ? error.kind : "error";
976
906
  const systemCode = !writeStarted && error instanceof FrameFault ? error.systemCode : undefined;
977
- const code = kind === "timeout" ? "CONTROL_TIMEOUT" :
978
- kind === "too_large" ? "CONTROL_RESPONSE_TOO_LARGE" :
979
- kind === "closed" ? "CONTROL_CONNECTION_CLOSED" :
980
- systemCode === "EPERM" || systemCode === "EACCES"
981
- ? "CONTROL_CONNECT_DENIED" : systemCode === "ENOENT"
982
- ? "CONTROL_SOCKET_MISSING" : systemCode === "ECONNREFUSED"
983
- ? "CONTROL_LISTENER_UNAVAILABLE" : "CONTROL_CONNECT_FAILED";
907
+ let code;
908
+ if (kind === "timeout") {
909
+ code = "CONTROL_TIMEOUT";
910
+ }
911
+ else if (kind === "too_large") {
912
+ code = "CONTROL_RESPONSE_TOO_LARGE";
913
+ }
914
+ else if (kind === "closed") {
915
+ code = "CONTROL_CONNECTION_CLOSED";
916
+ }
917
+ else if (systemCode === "EPERM" || systemCode === "EACCES") {
918
+ code = "CONTROL_CONNECT_DENIED";
919
+ }
920
+ else if (systemCode === "ENOENT") {
921
+ code = "CONTROL_SOCKET_MISSING";
922
+ }
923
+ else if (systemCode === "ECONNREFUSED") {
924
+ code = "CONTROL_LISTENER_UNAVAILABLE";
925
+ }
926
+ else {
927
+ code = "CONTROL_CONNECT_FAILED";
928
+ }
984
929
  fail(controlTransportError(code));
985
930
  });
986
931
  });