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.
- package/CHANGELOG.md +77 -0
- package/CONTRIBUTING.md +23 -36
- package/README.md +123 -222
- package/SECURITY.md +72 -93
- package/dist/src/errors.d.ts +10 -1
- package/dist/src/errors.js +3 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/gateway/claude-helper-protocol.d.ts +8 -11
- package/dist/src/gateway/claude-helper-protocol.js +12 -11
- package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
- package/dist/src/gateway/claude-helper-supervisor.d.ts +1 -5
- package/dist/src/gateway/claude-helper-supervisor.js +45 -17
- package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
- package/dist/src/gateway/claude-helper.js +6 -6
- package/dist/src/gateway/claude-helper.js.map +1 -1
- package/dist/src/gateway/claude-peer.d.ts +0 -3
- package/dist/src/gateway/claude-peer.js +7 -19
- package/dist/src/gateway/claude-peer.js.map +1 -1
- package/dist/src/gateway/cli.d.ts +17 -7
- package/dist/src/gateway/cli.js +945 -265
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/codex-socket-holder.d.ts +26 -0
- package/dist/src/gateway/codex-socket-holder.js +76 -0
- package/dist/src/gateway/codex-socket-holder.js.map +1 -0
- package/dist/src/gateway/codex-stateless-transport.js +1 -1
- package/dist/src/gateway/codex-stateless-transport.js.map +1 -1
- package/dist/src/gateway/config.d.ts +1 -10
- package/dist/src/gateway/config.js +4 -10
- package/dist/src/gateway/config.js.map +1 -1
- package/dist/src/gateway/control.d.ts +66 -80
- package/dist/src/gateway/control.js +92 -147
- package/dist/src/gateway/control.js.map +1 -1
- package/dist/src/gateway/federation-nodes.d.ts +29 -2
- package/dist/src/gateway/federation-nodes.js +177 -7
- package/dist/src/gateway/federation-nodes.js.map +1 -1
- package/dist/src/gateway/peer-client.d.ts +4 -3
- package/dist/src/gateway/peer-client.js +22 -13
- package/dist/src/gateway/peer-client.js.map +1 -1
- package/dist/src/gateway/peer-protocol.d.ts +13 -11
- package/dist/src/gateway/peer-protocol.js +14 -12
- package/dist/src/gateway/peer-protocol.js.map +1 -1
- package/dist/src/gateway/provenance-envelope.d.ts +0 -1
- package/dist/src/gateway/provenance-envelope.js +4 -19
- package/dist/src/gateway/provenance-envelope.js.map +1 -1
- package/dist/src/gateway/providers.d.ts +11 -7
- package/dist/src/gateway/providers.js +49 -50
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +4 -14
- package/dist/src/gateway/server.js +32 -54
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service-agent.d.ts +187 -0
- package/dist/src/gateway/service-agent.js +758 -0
- package/dist/src/gateway/service-agent.js.map +1 -0
- package/dist/src/gateway/service.d.ts +119 -38
- package/dist/src/gateway/service.js +754 -798
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/status-view.d.ts +119 -0
- package/dist/src/gateway/status-view.js +484 -0
- package/dist/src/gateway/status-view.js.map +1 -0
- package/dist/src/gateway/store.d.ts +106 -21
- package/dist/src/gateway/store.js +463 -537
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +64 -102
- package/dist/src/gateway/types.js +40 -86
- package/dist/src/gateway/types.js.map +1 -1
- package/docs/CONFIGURATION.md +174 -45
- package/docs/DELIVERY.md +11 -11
- package/docs/GATEWAY-ARCHITECTURE.md +292 -384
- package/package.json +4 -12
- package/skills/embassy-peer/SKILL.md +69 -92
- package/skills/embassy-peer/agents/openai.yaml +1 -1
- package/README.zh-CN.md +0 -275
- package/assets/live-dashboard/app.css +0 -1619
- package/assets/vendor/react/LICENSE +0 -21
- package/assets/vendor/react/react-dom.production.min.js +0 -267
- package/assets/vendor/react/react.production.min.js +0 -31
- package/dist/src/gateway/acp-client.d.ts +0 -110
- package/dist/src/gateway/acp-client.js +0 -407
- package/dist/src/gateway/acp-client.js.map +0 -1
- package/dist/src/gateway/acp-provider.d.ts +0 -66
- package/dist/src/gateway/acp-provider.js +0 -275
- package/dist/src/gateway/acp-provider.js.map +0 -1
- 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
- package/dist/src/gateway/cli-copy.d.ts +0 -8
- package/dist/src/gateway/cli-copy.en.d.ts +0 -22
- package/dist/src/gateway/cli-copy.en.js +0 -62
- package/dist/src/gateway/cli-copy.en.js.map +0 -1
- package/dist/src/gateway/cli-copy.js +0 -27
- package/dist/src/gateway/cli-copy.js.map +0 -1
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +0 -22
- package/dist/src/gateway/cli-copy.zh-CN.js +0 -62
- package/dist/src/gateway/cli-copy.zh-CN.js.map +0 -1
- package/dist/src/gateway/codex-doctor.d.ts +0 -36
- package/dist/src/gateway/codex-doctor.js +0 -127
- package/dist/src/gateway/codex-doctor.js.map +0 -1
- package/dist/src/gateway/dashboard-copy.d.ts +0 -7
- package/dist/src/gateway/dashboard-copy.en.d.ts +0 -504
- package/dist/src/gateway/dashboard-copy.en.js +0 -505
- package/dist/src/gateway/dashboard-copy.en.js.map +0 -1
- package/dist/src/gateway/dashboard-copy.js +0 -514
- package/dist/src/gateway/dashboard-copy.js.map +0 -1
- package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +0 -504
- package/dist/src/gateway/dashboard-copy.zh-CN.js +0 -505
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +0 -1
- package/dist/src/gateway/dashboard-model.d.ts +0 -343
- package/dist/src/gateway/dashboard-model.js +0 -1061
- package/dist/src/gateway/dashboard-model.js.map +0 -1
- package/dist/src/gateway/dashboard.d.ts +0 -20
- package/dist/src/gateway/dashboard.js +0 -874
- package/dist/src/gateway/dashboard.js.map +0 -1
- package/dist/src/gateway/deepseek-detect.d.ts +0 -14
- package/dist/src/gateway/deepseek-detect.js +0 -41
- package/dist/src/gateway/deepseek-detect.js.map +0 -1
- package/dist/src/gateway/live-dashboard-app/app.js +0 -2385
- package/dist/src/gateway/live-dashboard-assets.d.ts +0 -10
- package/dist/src/gateway/live-dashboard-assets.js +0 -74
- package/dist/src/gateway/live-dashboard-assets.js.map +0 -1
- package/dist/src/gateway/live-dashboard-command.d.ts +0 -60
- package/dist/src/gateway/live-dashboard-command.js +0 -334
- package/dist/src/gateway/live-dashboard-command.js.map +0 -1
- package/dist/src/gateway/live-dashboard-http.d.ts +0 -39
- package/dist/src/gateway/live-dashboard-http.js +0 -383
- package/dist/src/gateway/live-dashboard-http.js.map +0 -1
- package/dist/src/gateway/live-dashboard-protocol.d.ts +0 -34
- package/dist/src/gateway/live-dashboard-protocol.js +0 -114
- package/dist/src/gateway/live-dashboard-protocol.js.map +0 -1
- package/dist/src/gateway/live-dashboard-server.d.ts +0 -33
- package/dist/src/gateway/live-dashboard-server.js +0 -144
- package/dist/src/gateway/live-dashboard-server.js.map +0 -1
- package/dist/src/gateway/live-dashboard-stream.d.ts +0 -46
- package/dist/src/gateway/live-dashboard-stream.js +0 -234
- package/dist/src/gateway/live-dashboard-stream.js.map +0 -1
- package/dist/src/gateway/live-dashboard.d.ts +0 -28
- package/dist/src/gateway/live-dashboard.js +0 -154
- package/dist/src/gateway/live-dashboard.js.map +0 -1
- package/dist/src/gateway/locale.d.ts +0 -4
- package/dist/src/gateway/locale.js +0 -10
- package/dist/src/gateway/locale.js.map +0 -1
- package/dist/src/gateway/progress-watch-machine.d.ts +0 -45
- package/dist/src/gateway/progress-watch-machine.js +0 -70
- package/dist/src/gateway/progress-watch-machine.js.map +0 -1
- package/docs/CONFIGURATION.zh-CN.md +0 -97
- package/docs/DASHBOARD.md +0 -98
- package/docs/DASHBOARD.zh-CN.md +0 -49
- 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,
|
|
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 =
|
|
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", "
|
|
34
|
-
"
|
|
35
|
-
"
|
|
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
|
-
|
|
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,
|
|
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", "
|
|
274
|
-
!alias(value.fromAlias) ||
|
|
275
|
-
|
|
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 &&
|
|
280
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
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
|
|
319
|
-
if (!shape(value, {
|
|
291
|
+
function decodeRetire(value) {
|
|
292
|
+
if (!shape(value, { alias }))
|
|
320
293
|
invalid();
|
|
321
|
-
return {
|
|
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", "
|
|
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
|
|
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", "
|
|
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
|
|
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
|
|
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",
|
|
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", "
|
|
444
|
-
action: oneOf("discovery_refreshed", "
|
|
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"],
|
|
450
|
-
registration: ["codex_registered", "codex_succeeded", "codex_unregistered"
|
|
451
|
-
|
|
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", "
|
|
470
|
-
const optional = ["
|
|
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,
|
|
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 }, {
|
|
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) &&
|
|
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 },
|
|
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 },
|
|
444
|
+
delivery_status: { handler: "deliveryStatus", decode: decodeDeliveryStatus, result: isDeliveryStatusResult, mutation: false },
|
|
516
445
|
send: { handler: "send", decode: decodeSend, result: isSendResult, mutation: true },
|
|
517
|
-
|
|
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
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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
|
});
|