agent-embassy 1.9.5 → 3.0.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 +87 -0
- package/CONTRIBUTING.md +19 -34
- package/README.md +110 -221
- package/SECURITY.md +55 -89
- 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 +9 -12
- package/dist/src/gateway/claude-helper-protocol.js +15 -13
- package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
- package/dist/src/gateway/claude-helper-supervisor.d.ts +2 -6
- package/dist/src/gateway/claude-helper-supervisor.js +10 -9
- package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
- package/dist/src/gateway/claude-helper.js +18 -10
- 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 +6 -18
- package/dist/src/gateway/claude-peer.js.map +1 -1
- package/dist/src/gateway/cli.d.ts +18 -13
- package/dist/src/gateway/cli.js +942 -298
- 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/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 +5 -13
- package/dist/src/gateway/config.js +7 -19
- package/dist/src/gateway/config.js.map +1 -1
- package/dist/src/gateway/control.d.ts +55 -108
- package/dist/src/gateway/control.js +95 -222
- package/dist/src/gateway/control.js.map +1 -1
- package/dist/src/gateway/federation-nodes.d.ts +29 -3
- package/dist/src/gateway/federation-nodes.js +200 -15
- 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 +8 -11
- package/dist/src/gateway/peer-protocol.js +5 -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 +10 -7
- package/dist/src/gateway/providers.js +21 -28
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +4 -14
- package/dist/src/gateway/server.js +33 -59
- 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 +117 -32
- package/dist/src/gateway/service.js +525 -576
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/status-view.d.ts +167 -0
- package/dist/src/gateway/status-view.js +488 -0
- package/dist/src/gateway/status-view.js.map +1 -0
- package/dist/src/gateway/store.d.ts +103 -21
- package/dist/src/gateway/store.js +473 -547
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +51 -102
- package/dist/src/gateway/types.js +15 -52
- package/dist/src/gateway/types.js.map +1 -1
- package/docs/CONFIGURATION.md +194 -59
- package/docs/DELIVERY.md +11 -11
- package/docs/GATEWAY-ARCHITECTURE.md +286 -394
- package/package.json +4 -12
- package/skills/embassy-peer/SKILL.md +72 -92
- package/skills/embassy-peer/agents/openai.yaml +1 -1
- package/README.zh-CN.md +0 -269
- 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/cli-copy.d.ts +0 -8
- package/dist/src/gateway/cli-copy.en.d.ts +0 -17
- package/dist/src/gateway/cli-copy.en.js +0 -58
- package/dist/src/gateway/cli-copy.en.js.map +0 -1
- package/dist/src/gateway/cli-copy.js +0 -22
- package/dist/src/gateway/cli-copy.js.map +0 -1
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +0 -17
- package/dist/src/gateway/cli-copy.zh-CN.js +0 -58
- 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 -58
- 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/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
- package/docs/CONFIGURATION.zh-CN.md +0 -93
- 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,
|
|
7
|
+
import { CONNECTOR_OBSERVATION_STALE_AFTER_MS, GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET, gatewayPublicSnapshotLimits, isGatewayProvider, isMessageDirection, isPublicRegistryObservationSnapshot, } from "./types.js";
|
|
8
8
|
import { decodePeerParams, decodePeerResult } 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 = 3;
|
|
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;
|
|
@@ -16,7 +16,6 @@ export const GATEWAY_CONTROL_MAX_CONNECTIONS = 32;
|
|
|
16
16
|
const MAX_SOCKET_PATH_BYTES = 100;
|
|
17
17
|
const MAX_REPLY_ADDRESS_BYTES = 256;
|
|
18
18
|
const MAX_REVISION = Number.MAX_SAFE_INTEGER;
|
|
19
|
-
const DEFAULT_HOST_ID = "this-mac";
|
|
20
19
|
const ALIAS_PATTERN = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
|
|
21
20
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
22
21
|
const CONVERSATION_ID_PATTERN = /^conv_[A-Za-z0-9_-]{16,64}$/;
|
|
@@ -31,21 +30,15 @@ const MESSAGE_SUFFIX_PATTERN = /^[0-9a-f]{8}$/;
|
|
|
31
30
|
const CONVERSATION_SUFFIX_PATTERN = /^[A-Za-z0-9_-]{8}$/;
|
|
32
31
|
const ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
33
32
|
export const gatewayControlMethods = [
|
|
34
|
-
"health", "register_codex", "unregister_codex", "
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"send_to_codex", "reply", "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",
|
|
38
36
|
"register_peer", "unregister_peer", "await_peer", "peer_receipt",
|
|
39
37
|
];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export
|
|
44
|
-
if ("aliases" in params)
|
|
45
|
-
return params.threadAttestation;
|
|
46
|
-
return params.codexThreadId === undefined
|
|
47
|
-
? undefined : { alias: params.codexAlias, threadId: params.codexThreadId };
|
|
48
|
-
}
|
|
38
|
+
/** Bounds on an ephemeral peer registration's lifetime, in milliseconds. */
|
|
39
|
+
export const GATEWAY_EPHEMERAL_PEER_TTL_DEFAULT_MS = 300_000;
|
|
40
|
+
export const GATEWAY_EPHEMERAL_PEER_TTL_MIN_MS = 1_000;
|
|
41
|
+
export const GATEWAY_EPHEMERAL_PEER_TTL_MAX_MS = 3_600_000;
|
|
49
42
|
const WIRE_ERROR_MESSAGES = {
|
|
50
43
|
INVALID_JSON: "The control frame is not valid JSON.", FRAME_TOO_LARGE: "The control frame exceeds the size limit.",
|
|
51
44
|
INVALID_REQUEST: "The control request is invalid.", UNSUPPORTED_VERSION: "The control protocol version is unsupported.",
|
|
@@ -66,9 +59,13 @@ const TRANSPORT_MESSAGES = {
|
|
|
66
59
|
SOCKET_PERMISSION_FAILED: "The gateway control socket permissions could not be secured.",
|
|
67
60
|
SOCKET_CLEANUP_CONFLICT: "The gateway control socket path changed during cleanup.",
|
|
68
61
|
CONTROL_CONNECT_FAILED: "The gateway control socket could not be reached.",
|
|
62
|
+
CONTROL_CONNECT_DENIED: "The gateway control socket connection was denied by local policy.",
|
|
63
|
+
CONTROL_SOCKET_MISSING: "The gateway control socket does not exist at the configured path.",
|
|
64
|
+
CONTROL_LISTENER_UNAVAILABLE: "Nothing is listening on the gateway control socket.",
|
|
69
65
|
CONTROL_TIMEOUT: "The gateway control request timed out.",
|
|
70
66
|
CONTROL_RESPONSE_TOO_LARGE: "The gateway control response exceeds the client limit.",
|
|
71
67
|
CONTROL_INVALID_RESPONSE: "The gateway returned an invalid control response. Restart the broker, then retry.",
|
|
68
|
+
CONTROL_VERSION_MISMATCH: "The gateway control protocol version does not match this client.",
|
|
72
69
|
CONTROL_CONNECTION_CLOSED: "The gateway closed before returning a control response.",
|
|
73
70
|
CONTROL_OUTCOME_AMBIGUOUS: "The gateway may have applied the control mutation before the response was lost; do not retry automatically.",
|
|
74
71
|
};
|
|
@@ -135,12 +132,6 @@ function isNonNegativeInteger(value) {
|
|
|
135
132
|
value >= 0 &&
|
|
136
133
|
value <= MAX_REVISION);
|
|
137
134
|
}
|
|
138
|
-
function isTrackIdleMinutes(value) {
|
|
139
|
-
return (typeof value === "number" &&
|
|
140
|
-
Number.isSafeInteger(value) &&
|
|
141
|
-
value >= 1 &&
|
|
142
|
-
value <= 24 * 60);
|
|
143
|
-
}
|
|
144
135
|
function isIsoTimestamp(value) {
|
|
145
136
|
if (typeof value !== "string" || !ISO_TIMESTAMP_PATTERN.test(value)) {
|
|
146
137
|
return false;
|
|
@@ -165,7 +156,6 @@ function isMessageText(value) {
|
|
|
165
156
|
}
|
|
166
157
|
const exact = hasExactKeys;
|
|
167
158
|
const nonNegative = isNonNegativeInteger;
|
|
168
|
-
const trackMinutes = isTrackIdleMinutes;
|
|
169
159
|
const iso = isIsoTimestamp;
|
|
170
160
|
const uuid = isUuid;
|
|
171
161
|
const alias = isAlias;
|
|
@@ -206,9 +196,20 @@ function decodePeerHandoff(value) {
|
|
|
206
196
|
}
|
|
207
197
|
}
|
|
208
198
|
function decodeRegisterPeer(value) {
|
|
209
|
-
|
|
199
|
+
// `ephemeral` belongs only to a first registration: re-presenting a token
|
|
200
|
+
// proves an existing route, and an existing route's lifetime is already
|
|
201
|
+
// fixed. `ttlMs` without `ephemeral` would name a lifetime nothing honours.
|
|
202
|
+
if (!shape(value, { alias: peerAlias }, { token: peerToken, ephemeral: oneOf(true),
|
|
203
|
+
ttlMs: (item) => Number.isSafeInteger(item) &&
|
|
204
|
+
Number(item) >= GATEWAY_EPHEMERAL_PEER_TTL_MIN_MS &&
|
|
205
|
+
Number(item) <= GATEWAY_EPHEMERAL_PEER_TTL_MAX_MS }) ||
|
|
206
|
+
(value.ephemeral !== undefined && value.token !== undefined) ||
|
|
207
|
+
(value.ttlMs !== undefined && value.ephemeral === undefined))
|
|
210
208
|
invalid();
|
|
211
|
-
return { alias: value.alias,
|
|
209
|
+
return { alias: value.alias,
|
|
210
|
+
...(value.token === undefined ? {} : { token: value.token }),
|
|
211
|
+
...(value.ephemeral === undefined ? {} : { ephemeral: true }),
|
|
212
|
+
...(value.ttlMs === undefined ? {} : { ttlMs: value.ttlMs }) };
|
|
212
213
|
}
|
|
213
214
|
function decodePeerPrincipal(value) {
|
|
214
215
|
if (!shape(value, { alias: peerAlias, token: peerToken }))
|
|
@@ -240,142 +241,52 @@ const isPeerHandoffResult = (value) => {
|
|
|
240
241
|
}
|
|
241
242
|
};
|
|
242
243
|
function decodeRegister(value) {
|
|
243
|
-
if (!isRecord(value) || !exact(value, ["alias", "threadId"
|
|
244
|
+
if (!isRecord(value) || !exact(value, ["alias", "threadId", "hostId", "busyPolicy"], ["succeedsAlias"]) ||
|
|
244
245
|
!alias(value.alias) || !value.alias.startsWith("codex-") || !uuid(value.threadId) ||
|
|
245
|
-
(value.hostId
|
|
246
|
-
(value.busyPolicy !== undefined && value.busyPolicy !== "queue") ||
|
|
246
|
+
!host(value.hostId) || value.busyPolicy !== "queue" ||
|
|
247
247
|
(value.succeedsAlias !== undefined && (!alias(value.succeedsAlias) || !value.succeedsAlias.startsWith("codex-"))))
|
|
248
248
|
invalid();
|
|
249
|
-
|
|
250
|
-
if (!value.alias.endsWith(`@${hostId}`) ||
|
|
249
|
+
if (!value.alias.endsWith(`@${value.hostId}`) ||
|
|
251
250
|
(value.succeedsAlias !== undefined &&
|
|
252
|
-
(value.succeedsAlias === value.alias || !value.succeedsAlias.endsWith(`@${hostId}`))))
|
|
251
|
+
(value.succeedsAlias === value.alias || !value.succeedsAlias.endsWith(`@${value.hostId}`))))
|
|
253
252
|
invalid();
|
|
254
|
-
return { alias: value.alias, threadId: value.threadId.toLowerCase(), hostId,
|
|
255
|
-
busyPolicy:
|
|
253
|
+
return { alias: value.alias, threadId: value.threadId.toLowerCase(), hostId: value.hostId,
|
|
254
|
+
busyPolicy: value.busyPolicy, ...(value.succeedsAlias === undefined ? {} : { succeedsAlias: value.succeedsAlias }) };
|
|
256
255
|
}
|
|
257
256
|
function decodeUnregister(value) {
|
|
258
257
|
if (!shape(value, { alias, threadId: uuid }) || !value.alias.startsWith("codex-"))
|
|
259
258
|
invalid();
|
|
260
259
|
return { alias: value.alias, threadId: value.threadId.toLowerCase() };
|
|
261
260
|
}
|
|
262
|
-
function
|
|
263
|
-
if (!
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
typeof value.alias !== "string" || !isClaudeSessionSelector(value.alias) ||
|
|
270
|
-
(value.codexThreadId !== undefined && !uuid(value.codexThreadId)))
|
|
271
|
-
invalid();
|
|
272
|
-
return { alias: UUID_PATTERN.test(value.alias) ? value.alias.toLowerCase() : value.alias,
|
|
273
|
-
...(value.codexThreadId === undefined ? {} : { codexThreadId: value.codexThreadId.toLowerCase() }) };
|
|
274
|
-
}
|
|
275
|
-
function decodePair(value) {
|
|
276
|
-
if (!isRecord(value))
|
|
277
|
-
invalid();
|
|
278
|
-
if (Object.hasOwn(value, "aliases")) {
|
|
279
|
-
if (!exact(value, ["aliases"], ["threadAttestation"]) || !Array.isArray(value.aliases) ||
|
|
280
|
-
value.aliases.length !== 2 || !alias(value.aliases[0]) || !alias(value.aliases[1]) ||
|
|
281
|
-
value.aliases[0] === value.aliases[1])
|
|
282
|
-
invalid();
|
|
283
|
-
let threadAttestation;
|
|
284
|
-
if (value.threadAttestation !== undefined) {
|
|
285
|
-
if (!shape(value.threadAttestation, { alias, threadId: uuid }) ||
|
|
286
|
-
!value.aliases.includes(value.threadAttestation.alias))
|
|
287
|
-
invalid();
|
|
288
|
-
threadAttestation = { alias: value.threadAttestation.alias,
|
|
289
|
-
threadId: value.threadAttestation.threadId.toLowerCase() };
|
|
290
|
-
}
|
|
291
|
-
return { aliases: [value.aliases[0], value.aliases[1]],
|
|
292
|
-
...(threadAttestation === undefined ? {} : { threadAttestation }) };
|
|
293
|
-
}
|
|
294
|
-
if (!exact(value, ["claudeAlias", "codexAlias"], ["codexThreadId"]) ||
|
|
295
|
-
typeof value.claudeAlias !== "string" || !isClaudeSessionSelector(value.claudeAlias) ||
|
|
296
|
-
!alias(value.codexAlias) || !value.codexAlias.startsWith("codex-") ||
|
|
297
|
-
(value.codexThreadId !== undefined && !uuid(value.codexThreadId)) ||
|
|
298
|
-
value.claudeAlias === value.codexAlias ||
|
|
299
|
-
(alias(value.claudeAlias) && value.claudeAlias.slice(value.claudeAlias.lastIndexOf("@") + 1) !==
|
|
300
|
-
value.codexAlias.slice(value.codexAlias.lastIndexOf("@") + 1)))
|
|
301
|
-
invalid();
|
|
302
|
-
return { claudeAlias: UUID_PATTERN.test(value.claudeAlias) ? value.claudeAlias.toLowerCase() : value.claudeAlias,
|
|
303
|
-
codexAlias: value.codexAlias,
|
|
304
|
-
...(value.codexThreadId === undefined ? {} : { codexThreadId: value.codexThreadId.toLowerCase() }) };
|
|
305
|
-
}
|
|
306
|
-
function commonSend(value, required, optional) {
|
|
307
|
-
if (!isRecord(value) || !exact(value, required, optional) || !alias(value.fromAlias) ||
|
|
308
|
-
!alias(value.toAlias) || !messageText(value.text) ||
|
|
309
|
-
(value.expectsReply !== undefined && typeof value.expectsReply !== "boolean") ||
|
|
310
|
-
(value.trackIdleMinutes !== undefined && !trackMinutes(value.trackIdleMinutes)))
|
|
311
|
-
invalid();
|
|
312
|
-
return value;
|
|
313
|
-
}
|
|
314
|
-
function decodeSendClaude(value) {
|
|
315
|
-
if (!isRecord(value) || !exact(value, ["fromAlias", "toAlias", "text"], ["threadId", "peerToken", "expectsReply", "trackIdleMinutes"]) ||
|
|
316
|
-
!alias(value.fromAlias) || (value.threadId === undefined) === (value.peerToken === undefined) ||
|
|
261
|
+
function decodeSend(value) {
|
|
262
|
+
if (!isRecord(value) || !exact(value, ["fromAlias", "text"], ["toAlias", "conversationId", "threadId", "replyAddress", "peerToken", "expectsReply"]) ||
|
|
263
|
+
!alias(value.fromAlias) || !messageText(value.text) ||
|
|
264
|
+
[value.toAlias, value.conversationId].filter((item) => item !== undefined).length !== 1 ||
|
|
265
|
+
(value.toAlias !== undefined && (typeof value.toAlias !== "string" || !isClaudeSessionSelector(value.toAlias))) ||
|
|
266
|
+
(value.conversationId !== undefined && (typeof value.conversationId !== "string" || !isGatewayConversationId(value.conversationId))) ||
|
|
267
|
+
[value.threadId, value.replyAddress, value.peerToken].filter((item) => item !== undefined).length !== 1 ||
|
|
317
268
|
(value.threadId !== undefined && !uuid(value.threadId)) ||
|
|
318
|
-
(value.
|
|
319
|
-
|
|
320
|
-
(value.expectsReply !== undefined &&
|
|
321
|
-
|
|
322
|
-
invalid();
|
|
323
|
-
return { fromAlias: value.fromAlias,
|
|
324
|
-
toAlias: UUID_PATTERN.test(value.toAlias) ? value.toAlias.toLowerCase() : value.toAlias,
|
|
325
|
-
text: value.text, expectsReply: value.expectsReply ?? false,
|
|
326
|
-
...(typeof value.threadId === "string" ? { threadId: value.threadId.toLowerCase() } : { peerToken: value.peerToken }),
|
|
327
|
-
...(value.trackIdleMinutes === undefined ? {} : { trackIdleMinutes: value.trackIdleMinutes }) };
|
|
328
|
-
}
|
|
329
|
-
function decodeSendCodex(value) {
|
|
330
|
-
const row = commonSend(value, ["fromAlias", "toAlias", "text"], ["replyAddress", "peerToken", "expectsReply", "trackIdleMinutes"]);
|
|
331
|
-
if ((row.replyAddress === undefined) === (row.peerToken === undefined) ||
|
|
332
|
-
(row.replyAddress !== undefined && (typeof row.replyAddress !== "string" || !isGatewayReplyAddress(row.replyAddress))) ||
|
|
333
|
-
(row.peerToken !== undefined && !peerToken(row.peerToken)))
|
|
334
|
-
invalid();
|
|
335
|
-
const replyAddress = row.replyAddress !== undefined;
|
|
336
|
-
return { fromAlias: row.fromAlias, toAlias: row.toAlias, text: row.text,
|
|
337
|
-
...(replyAddress ? { replyAddress: row.replyAddress } : { peerToken: row.peerToken }),
|
|
338
|
-
expectsReply: row.expectsReply === true,
|
|
339
|
-
...(row.trackIdleMinutes === undefined ? {} : { trackIdleMinutes: row.trackIdleMinutes }) };
|
|
340
|
-
}
|
|
341
|
-
function normalizeReplyCaller(value) {
|
|
342
|
-
if (!isRecord(value))
|
|
343
|
-
invalid();
|
|
344
|
-
if (value.kind === "codex" && shape(value, { kind: oneOf("codex"), alias, threadId: uuid }))
|
|
345
|
-
return { kind: "codex", alias: value.alias,
|
|
346
|
-
threadId: value.threadId.toLowerCase() };
|
|
347
|
-
if (value.kind === "claude" && isRecord(value) && exact(value, ["kind", "alias"], ["replyAddress"]) &&
|
|
348
|
-
alias(value.alias) && (value.replyAddress === undefined ||
|
|
349
|
-
(typeof value.replyAddress === "string" && isGatewayReplyAddress(value.replyAddress))))
|
|
350
|
-
return { kind: "claude", alias: value.alias,
|
|
351
|
-
...(value.replyAddress === undefined ? {} : { replyAddress: value.replyAddress }) };
|
|
352
|
-
if (value.kind === "peer" && shape(value, { kind: oneOf("peer"), alias: peerAlias, token: peerToken }))
|
|
353
|
-
return { kind: "peer", alias: value.alias, token: value.token };
|
|
354
|
-
return invalid();
|
|
355
|
-
}
|
|
356
|
-
function decodeReply(value) {
|
|
357
|
-
if (!isRecord(value) || !exact(value, ["conversationId", "text", "caller"], ["trackIdleMinutes"]) ||
|
|
358
|
-
typeof value.conversationId !== "string" || !isGatewayConversationId(value.conversationId) ||
|
|
359
|
-
!messageText(value.text) ||
|
|
360
|
-
(value.trackIdleMinutes !== undefined && !trackMinutes(value.trackIdleMinutes)))
|
|
269
|
+
(value.replyAddress !== undefined && (typeof value.replyAddress !== "string" || !isGatewayReplyAddress(value.replyAddress))) ||
|
|
270
|
+
(value.peerToken !== undefined && !peerToken(value.peerToken)) ||
|
|
271
|
+
(value.expectsReply !== undefined &&
|
|
272
|
+
(typeof value.expectsReply !== "boolean" || value.conversationId !== undefined)))
|
|
361
273
|
invalid();
|
|
362
|
-
|
|
363
|
-
|
|
274
|
+
const authority = value.threadId !== undefined ? { threadId: value.threadId.toLowerCase() }
|
|
275
|
+
: value.replyAddress !== undefined ? { replyAddress: value.replyAddress }
|
|
276
|
+
: { peerToken: value.peerToken };
|
|
277
|
+
const target = value.conversationId !== undefined
|
|
278
|
+
? { conversationId: value.conversationId }
|
|
279
|
+
: { toAlias: UUID_PATTERN.test(value.toAlias) ? value.toAlias.toLowerCase() : value.toAlias,
|
|
280
|
+
expectsReply: value.expectsReply === true };
|
|
281
|
+
return { fromAlias: value.fromAlias, text: value.text, ...target, ...authority };
|
|
364
282
|
}
|
|
365
283
|
function decodeDeliveryStatus(value) {
|
|
366
284
|
if (!shape(value, { token: (item) => typeof item === "string" && DELIVERY_TOKEN_PATTERN.test(item) }))
|
|
367
285
|
invalid();
|
|
368
286
|
return { token: value.token };
|
|
369
287
|
}
|
|
370
|
-
function decodeUntrack(value) {
|
|
371
|
-
if (!shape(value, { conversationId: (item) => typeof item === "string" && CONVERSATION_ID_PATTERN.test(item) }))
|
|
372
|
-
invalid();
|
|
373
|
-
return { conversationId: value.conversationId };
|
|
374
|
-
}
|
|
375
288
|
function isDecision(value) {
|
|
376
|
-
return shape(value, { accepted: (item) => typeof item === "boolean", code: oneOf("ok", "not_found", "conflict", "
|
|
377
|
-
? value.code === "ok" && value.ownerHost === undefined
|
|
378
|
-
: value.code !== "ok" && (value.ownerHost === undefined || value.code === "conflict"));
|
|
289
|
+
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");
|
|
379
290
|
}
|
|
380
291
|
function isRegisterPeerResult(value) {
|
|
381
292
|
return isDecision(value) || shape(value, { accepted: oneOf(true), code: oneOf("ok"), token: peerToken });
|
|
@@ -384,14 +295,15 @@ function isHealthResult(value) {
|
|
|
384
295
|
return shape(value, { status: oneOf("ok", "degraded"), revision: nonNegative });
|
|
385
296
|
}
|
|
386
297
|
function isSendResult(value) {
|
|
387
|
-
if (!isRecord(value) || value.accepted !== true)
|
|
388
|
-
return
|
|
298
|
+
if (!isRecord(value) || value.accepted !== true) {
|
|
299
|
+
return shape(value, { accepted: oneOf(false), code: oneOf("not_found", "conflict", "route_mismatch", "busy", "unavailable", "rejected") }, { reason: safeCode });
|
|
300
|
+
}
|
|
389
301
|
return shape(value, { accepted: oneOf(true), code: oneOf("ok"),
|
|
390
302
|
conversationId: (item) => typeof item === "string" && CONVERSATION_ID_PATTERN.test(item),
|
|
391
303
|
deliveryToken: (item) => typeof item === "string" && DELIVERY_TOKEN_PATTERN.test(item) });
|
|
392
304
|
}
|
|
393
305
|
function isRefreshResult(value) {
|
|
394
|
-
return shape(value, { accepted: (item) => typeof item === "boolean", code: oneOf("ok", "not_found", "conflict", "
|
|
306
|
+
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 });
|
|
395
307
|
}
|
|
396
308
|
const TERMINAL = new Set(["delivered", "unconfirmed", "expired", "failed", "ambiguous", "cancelled"]);
|
|
397
309
|
function isDeliveryStatusResult(value) {
|
|
@@ -411,13 +323,9 @@ function isConnectorSnapshot(value) {
|
|
|
411
323
|
if (!shape(value, { provider: isGatewayProvider, host, health: isConnectorHealth,
|
|
412
324
|
protocol: (item) => typeof item === "string" && PROTOCOL_PATTERN.test(item),
|
|
413
325
|
protocolVersion: (item) => typeof item === "string" && PROTOCOL_VERSION_PATTERN.test(item) }, { lastSeenAt: iso, observationAgeMs: nonNegative, safeErrorCode: safeCode,
|
|
414
|
-
registry: isPublicRegistryObservationSnapshot
|
|
415
|
-
conditions: (rows) => Array.isArray(rows) && rows.length > 0 && rows.length <= 2 &&
|
|
416
|
-
rows.every(oneOf("split_brain", "orphaned", "attached", "observation_stale", "managed_layout_missing", "unknown")),
|
|
417
|
-
}) }))
|
|
326
|
+
registry: isPublicRegistryObservationSnapshot }))
|
|
418
327
|
return false;
|
|
419
|
-
return
|
|
420
|
-
(value.registry === undefined || value.provider === "claude");
|
|
328
|
+
return value.registry === undefined || value.provider === "claude";
|
|
421
329
|
}
|
|
422
330
|
function isRouteSnapshot(value) {
|
|
423
331
|
if (!shape(value, { alias, provider: isGatewayProvider, host, enabled: (item) => typeof item === "boolean",
|
|
@@ -428,24 +336,10 @@ function isRouteSnapshot(value) {
|
|
|
428
336
|
return row.alias.endsWith(`@${row.host}`) &&
|
|
429
337
|
(row.oldestQueuedAt === undefined ? row.queueDepth === 0 : row.queueDepth > 0);
|
|
430
338
|
}
|
|
431
|
-
function compareConsentEndpoints(left, right) {
|
|
432
|
-
return gatewayProviders.indexOf(left.provider) -
|
|
433
|
-
gatewayProviders.indexOf(right.provider) || left.alias.localeCompare(right.alias);
|
|
434
|
-
}
|
|
435
|
-
function isConsentEdgeSnapshot(value) {
|
|
436
|
-
const endpoint = (item) => shape(item, { alias, provider: isGatewayProvider });
|
|
437
|
-
if (!shape(value, { endpoints: (rows) => Array.isArray(rows) && rows.length === 2 && rows.every(endpoint),
|
|
438
|
-
host, counters: isRouteCounters }, { mutable: (item) => typeof item === "boolean" }))
|
|
439
|
-
return false;
|
|
440
|
-
const [left, right] = value.endpoints;
|
|
441
|
-
const hosts = [left.alias.slice(left.alias.lastIndexOf("@") + 1), right.alias.slice(right.alias.lastIndexOf("@") + 1)];
|
|
442
|
-
return left.alias !== right.alias && compareConsentEndpoints(left, right) < 0 &&
|
|
443
|
-
(left.provider !== right.provider || hosts[0] !== hosts[1]) && value.host === [...hosts].sort()[0];
|
|
444
|
-
}
|
|
445
339
|
function isAvailablePeerSnapshot(value) {
|
|
446
340
|
return shape(value, { alias, provider: oneOf("claude"), host,
|
|
447
341
|
state: oneOf("idle", "busy", "awaiting_approval", "offline"),
|
|
448
|
-
validated: (item) => typeof item === "boolean",
|
|
342
|
+
validated: (item) => typeof item === "boolean", routed: (item) => typeof item === "boolean" }, { lastSeenAt: iso, safeErrorCode: safeCode }) &&
|
|
449
343
|
value.alias.endsWith(`@${String(value.host)}`);
|
|
450
344
|
}
|
|
451
345
|
function isNormalizedMessageEvent(value) {
|
|
@@ -467,40 +361,17 @@ function isAccounting(value) {
|
|
|
467
361
|
function isSafeAlert(value) {
|
|
468
362
|
return shape(value, { code: safeCode, severity: oneOf("info", "warning", "error"), timestamp: iso }, { provider: isGatewayProvider, host, alias });
|
|
469
363
|
}
|
|
470
|
-
function isProgressWatchSnapshot(value) {
|
|
471
|
-
return shape(value, {
|
|
472
|
-
conversationIdSuffix: (item) => typeof item === "string" && CONVERSATION_SUFFIX_PATTERN.test(item),
|
|
473
|
-
ownerAlias: alias, workerAlias: alias, lastActivityAt: iso, nextActionAt: iso,
|
|
474
|
-
nudgeCount: oneOf(0, 1, 2),
|
|
475
|
-
}) && value.ownerAlias !== value.workerAlias;
|
|
476
|
-
}
|
|
477
|
-
function isProgressWatchEventSnapshot(value) {
|
|
478
|
-
if (!shape(value, { sequence: nonNegative, timestamp: iso,
|
|
479
|
-
conversationIdSuffix: (item) => typeof item === "string" && CONVERSATION_SUFFIX_PATTERN.test(item),
|
|
480
|
-
ownerAlias: alias, workerAlias: alias, kind: oneOf("opened", "replaced", "settled"),
|
|
481
|
-
actor: oneOf("owner", "worker", "operator", "gateway", "unknown") }, { reason: oneOf("done", "untracked", "idle_timeout", "tracking_disabled", "endpoint_retired", "pair_removed") }))
|
|
482
|
-
return false;
|
|
483
|
-
if (value.kind === "opened")
|
|
484
|
-
return value.actor === "owner" && value.reason === undefined;
|
|
485
|
-
if (value.kind === "replaced")
|
|
486
|
-
return (value.actor === "owner" || value.actor === "unknown") && value.reason === undefined;
|
|
487
|
-
return (value.reason === "done" && (value.actor === "owner" || value.actor === "worker")) ||
|
|
488
|
-
(value.reason === "untracked" && value.actor === "operator") ||
|
|
489
|
-
((value.reason === "idle_timeout" || value.reason === "tracking_disabled") && value.actor === "gateway") ||
|
|
490
|
-
(value.reason === "endpoint_retired" && (value.actor === "gateway" || value.actor === "operator")) ||
|
|
491
|
-
(value.reason === "pair_removed" && value.actor === "operator");
|
|
492
|
-
}
|
|
493
364
|
function isGatewayActivityEvent(value) {
|
|
494
365
|
if (!shape(value, { sequence: positive, timestamp: iso,
|
|
495
|
-
kind: oneOf("discovery", "
|
|
496
|
-
action: oneOf("discovery_refreshed", "
|
|
366
|
+
kind: oneOf("discovery", "registration"),
|
|
367
|
+
action: oneOf("discovery_refreshed", "codex_registered", "codex_succeeded", "codex_unregistered", "claude_route_installed", "claude_route_retired"),
|
|
497
368
|
outcome: oneOf("accepted", "rejected"), aliases: (rows) => arrayOf(rows, 2, alias),
|
|
498
369
|
operatorAction: oneOf(true) }, { safeErrorCode: safeCode }))
|
|
499
370
|
return false;
|
|
500
371
|
const allowed = {
|
|
501
|
-
discovery: ["discovery_refreshed"],
|
|
502
|
-
registration: ["codex_registered", "codex_succeeded", "codex_unregistered"
|
|
503
|
-
|
|
372
|
+
discovery: ["discovery_refreshed"],
|
|
373
|
+
registration: ["codex_registered", "codex_succeeded", "codex_unregistered",
|
|
374
|
+
"claude_route_installed", "claude_route_retired"],
|
|
504
375
|
};
|
|
505
376
|
return new Set(value.aliases).size === value.aliases.length &&
|
|
506
377
|
(allowed[value.kind]?.includes(value.action) ?? false);
|
|
@@ -518,23 +389,20 @@ function isDeadlinePressure(value) {
|
|
|
518
389
|
rows.reduce((sum, row) => sum + Number(row.expired), 0) === value.expiredEvents;
|
|
519
390
|
}
|
|
520
391
|
function isSnapshotTruncation(value) {
|
|
521
|
-
const required = ["connectors", "availablePeers", "routes", "
|
|
522
|
-
const optional = ["
|
|
392
|
+
const required = ["connectors", "availablePeers", "routes", "messages", "alerts"];
|
|
393
|
+
const optional = ["activityEvents"];
|
|
523
394
|
return isRecord(value) && exact(value, required, optional) &&
|
|
524
395
|
[...required, ...optional].every((key) => value[key] === undefined || nonNegative(value[key]));
|
|
525
396
|
}
|
|
526
397
|
export function isGatewaySnapshot(value) {
|
|
527
|
-
if (!shape(value, { schemaVersion: oneOf(2), generatedAt: iso,
|
|
398
|
+
if (!shape(value, { schemaVersion: oneOf(2), generatedAt: iso,
|
|
528
399
|
health: isConnectorHealth,
|
|
529
400
|
connectors: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.connectors, isConnectorSnapshot),
|
|
530
401
|
availablePeers: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.availablePeers, isAvailablePeerSnapshot),
|
|
531
402
|
routes: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.routes, isRouteSnapshot),
|
|
532
|
-
consentEdges: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.consentEdges, isConsentEdgeSnapshot),
|
|
533
403
|
messages: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.messages, isNormalizedMessageEvent),
|
|
534
404
|
accounting: isAccounting, alerts: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.alerts, isSafeAlert),
|
|
535
|
-
truncation: isSnapshotTruncation }, {
|
|
536
|
-
progressWatchEvents: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.progressWatchEvents, isProgressWatchEventSnapshot),
|
|
537
|
-
activityEvents: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.activityEvents, isGatewayActivityEvent),
|
|
405
|
+
truncation: isSnapshotTruncation }, { activityEvents: (rows) => arrayOf(rows, gatewayPublicSnapshotLimits.activityEvents, isGatewayActivityEvent),
|
|
538
406
|
deadlinePressure: isDeadlinePressure }))
|
|
539
407
|
return false;
|
|
540
408
|
const snapshot = value;
|
|
@@ -543,16 +411,13 @@ export function isGatewaySnapshot(value) {
|
|
|
543
411
|
const connectorKeys = snapshot.connectors.map((row) => `${row.provider}@${row.host}`);
|
|
544
412
|
const aliases = snapshot.routes.map((row) => row.alias);
|
|
545
413
|
const peers = snapshot.availablePeers.map((row) => row.alias);
|
|
546
|
-
const edgeKeys = snapshot.consentEdges.map((row) => `${row.endpoints[0].alias}\0${row.endpoints[1].alias}`);
|
|
547
|
-
const routeByAlias = new Map(snapshot.routes.map((row) => [row.alias, row]));
|
|
548
414
|
const honest = snapshot.connectors.every((row) => {
|
|
549
415
|
const seen = row.lastSeenAt === undefined ? undefined : Date.parse(row.lastSeenAt);
|
|
550
416
|
const age = seen === undefined ? undefined : Math.min(MAX_REVISION, Math.max(0, generated - seen));
|
|
551
417
|
return (row.observationAgeMs === undefined || row.observationAgeMs === age) &&
|
|
552
418
|
(row.health !== "healthy" || (age !== undefined && age <= CONNECTOR_OBSERVATION_STALE_AFTER_MS));
|
|
553
419
|
});
|
|
554
|
-
return honest && unique(connectorKeys) && unique(aliases) && unique(peers) &&
|
|
555
|
-
snapshot.consentEdges.every((edge) => edge.endpoints.every((endpoint) => routeByAlias.get(endpoint.alias)?.provider === endpoint.provider)) &&
|
|
420
|
+
return honest && unique(connectorKeys) && unique(aliases) && unique(peers) &&
|
|
556
421
|
Buffer.byteLength(JSON.stringify(snapshot), "utf8") <= GATEWAY_PUBLIC_SNAPSHOT_BYTE_BUDGET;
|
|
557
422
|
}
|
|
558
423
|
function isSnapshotObservation(value) {
|
|
@@ -560,13 +425,11 @@ function isSnapshotObservation(value) {
|
|
|
560
425
|
}
|
|
561
426
|
const descriptors = {
|
|
562
427
|
health: { handler: "health", decode: emptyParams, result: isHealthResult, mutation: false }, register_codex: { handler: "registerCodex", decode: decodeRegister, result: isDecision, mutation: true },
|
|
563
|
-
unregister_codex: { handler: "unregisterCodex", decode: decodeUnregister, result: isDecision, mutation: true },
|
|
564
|
-
select_claude: { handler: "selectClaude", decode: decodeSelection, result: isDecision, mutation: true }, unselect_claude: { handler: "unselectClaude", decode: decodeSelection, result: isDecision, mutation: true },
|
|
565
|
-
pair: { handler: "pair", decode: decodePair, result: isDecision, mutation: true }, unpair: { handler: "unpair", decode: decodePair, result: isDecision, mutation: true },
|
|
428
|
+
unregister_codex: { handler: "unregisterCodex", decode: decodeUnregister, result: isDecision, mutation: true },
|
|
566
429
|
list_snapshot: { handler: "listSnapshot", decode: emptyParams, result: isGatewaySnapshot, mutation: false }, observe_snapshot: { handler: "observeSnapshot", decode: emptyParams, result: isSnapshotObservation, mutation: false },
|
|
567
|
-
delivery_status: { handler: "deliveryStatus", decode: decodeDeliveryStatus, result: isDeliveryStatusResult, mutation: false },
|
|
568
|
-
|
|
569
|
-
|
|
430
|
+
delivery_status: { handler: "deliveryStatus", decode: decodeDeliveryStatus, result: isDeliveryStatusResult, mutation: false },
|
|
431
|
+
send: { handler: "send", decode: decodeSend, result: isSendResult, mutation: true },
|
|
432
|
+
refresh_discovery: { handler: "refreshDiscovery", decode: emptyParams, result: isRefreshResult, mutation: false },
|
|
570
433
|
peer_catalog: { handler: "peerCatalog", decode: decodePeerCatalog, result: isPeerCatalog, mutation: false },
|
|
571
434
|
peer_handoff: { handler: "peerHandoff", decode: decodePeerHandoff, result: isPeerHandoffResult, mutation: true },
|
|
572
435
|
register_peer: { handler: "registerPeer", decode: decodeRegisterPeer, result: isRegisterPeerResult, mutation: true },
|
|
@@ -577,12 +440,12 @@ const descriptors = {
|
|
|
577
440
|
function parseRequestObject(value) {
|
|
578
441
|
if (!isRecord(value) || !exact(value, ["protocolVersion", "method", "params"]))
|
|
579
442
|
invalid();
|
|
580
|
-
if (value.protocolVersion !==
|
|
443
|
+
if (value.protocolVersion !== GATEWAY_CONTROL_PROTOCOL_VERSION)
|
|
581
444
|
throw new ProtocolFault("UNSUPPORTED_VERSION");
|
|
582
445
|
if (typeof value.method !== "string" || !Object.hasOwn(descriptors, value.method))
|
|
583
446
|
throw new ProtocolFault("UNKNOWN_METHOD");
|
|
584
447
|
const method = value.method;
|
|
585
|
-
return { protocolVersion:
|
|
448
|
+
return { protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION, method, params: descriptors[method].decode(value.params) };
|
|
586
449
|
}
|
|
587
450
|
function decodeJson(frame, invalidCode) {
|
|
588
451
|
try {
|
|
@@ -593,14 +456,14 @@ function decodeJson(frame, invalidCode) {
|
|
|
593
456
|
}
|
|
594
457
|
}
|
|
595
458
|
function errorResponse(code) {
|
|
596
|
-
return { protocolVersion:
|
|
459
|
+
return { protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION, ok: false, error: { code, message: WIRE_ERROR_MESSAGES[code] } };
|
|
597
460
|
}
|
|
598
461
|
async function dispatch(request, handlers) {
|
|
599
462
|
const descriptor = descriptors[request.method];
|
|
600
463
|
const handler = handlers[descriptor.handler];
|
|
601
464
|
try {
|
|
602
465
|
const result = await (Object.keys(request.params).length === 0 ? handler() : handler(request.params));
|
|
603
|
-
return { protocolVersion:
|
|
466
|
+
return { protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION, ok: true, result };
|
|
604
467
|
}
|
|
605
468
|
catch {
|
|
606
469
|
return errorResponse("HANDLER_FAILURE");
|
|
@@ -617,9 +480,11 @@ function serializeResponse(response) {
|
|
|
617
480
|
}
|
|
618
481
|
class FrameFault extends Error {
|
|
619
482
|
kind;
|
|
620
|
-
|
|
483
|
+
systemCode;
|
|
484
|
+
constructor(kind, systemCode) {
|
|
621
485
|
super(kind);
|
|
622
486
|
this.kind = kind;
|
|
487
|
+
this.systemCode = systemCode;
|
|
623
488
|
}
|
|
624
489
|
}
|
|
625
490
|
function readOneFrame(socket, maximum) {
|
|
@@ -652,7 +517,7 @@ function readOneFrame(socket, maximum) {
|
|
|
652
517
|
socket.on("data", onData);
|
|
653
518
|
socket.once("end", onEnd);
|
|
654
519
|
socket.once("timeout", onTimeout);
|
|
655
|
-
socket.once("error", () => finish(new FrameFault("error")));
|
|
520
|
+
socket.once("error", (error) => finish(new FrameFault("error", error.code)));
|
|
656
521
|
});
|
|
657
522
|
}
|
|
658
523
|
function respond(socket, response) {
|
|
@@ -961,16 +826,18 @@ export async function startGatewayControlServer(options) {
|
|
|
961
826
|
};
|
|
962
827
|
}
|
|
963
828
|
function decodeResponse(method, params, value) {
|
|
964
|
-
if (!isRecord(value) || value.protocolVersion
|
|
829
|
+
if (!isRecord(value) || !Number.isSafeInteger(value.protocolVersion))
|
|
965
830
|
throw controlTransportError("CONTROL_INVALID_RESPONSE");
|
|
831
|
+
if (value.protocolVersion !== GATEWAY_CONTROL_PROTOCOL_VERSION)
|
|
832
|
+
throw controlTransportError("CONTROL_VERSION_MISMATCH");
|
|
966
833
|
if (value.ok === false) {
|
|
967
|
-
if (!shape(value, { protocolVersion: oneOf(
|
|
834
|
+
if (!shape(value, { protocolVersion: oneOf(GATEWAY_CONTROL_PROTOCOL_VERSION), ok: oneOf(false), error: (item) => shape(item, { code: (code) => typeof code === "string" && Object.hasOwn(WIRE_ERROR_MESSAGES, code),
|
|
968
835
|
message: (message) => typeof message === "string" }) }) ||
|
|
969
836
|
!isRecord(value.error) || value.error.message !== WIRE_ERROR_MESSAGES[value.error.code])
|
|
970
837
|
throw controlTransportError("CONTROL_INVALID_RESPONSE");
|
|
971
838
|
return value;
|
|
972
839
|
}
|
|
973
|
-
if (!shape(value, { protocolVersion: oneOf(
|
|
840
|
+
if (!shape(value, { protocolVersion: oneOf(GATEWAY_CONTROL_PROTOCOL_VERSION), ok: oneOf(true), result: descriptors[method].result }))
|
|
974
841
|
throw controlTransportError("CONTROL_INVALID_RESPONSE");
|
|
975
842
|
if (method === "register_peer" && isRecord(value.result) && value.result.accepted === true &&
|
|
976
843
|
Object.hasOwn(params, "token") === Object.hasOwn(value.result, "token"))
|
|
@@ -1015,14 +882,20 @@ export async function sendGatewayControlRequest(options) {
|
|
|
1015
882
|
resolve(response);
|
|
1016
883
|
}
|
|
1017
884
|
}
|
|
1018
|
-
catch {
|
|
1019
|
-
fail(
|
|
885
|
+
catch (error) {
|
|
886
|
+
fail(error instanceof GatewayControlTransportError
|
|
887
|
+
? error : controlTransportError("CONTROL_INVALID_RESPONSE"));
|
|
1020
888
|
}
|
|
1021
889
|
}, (error) => {
|
|
1022
890
|
const kind = error instanceof FrameFault ? error.kind : "error";
|
|
891
|
+
const systemCode = !writeStarted && error instanceof FrameFault ? error.systemCode : undefined;
|
|
1023
892
|
const code = kind === "timeout" ? "CONTROL_TIMEOUT" :
|
|
1024
893
|
kind === "too_large" ? "CONTROL_RESPONSE_TOO_LARGE" :
|
|
1025
|
-
kind === "closed" ? "CONTROL_CONNECTION_CLOSED" :
|
|
894
|
+
kind === "closed" ? "CONTROL_CONNECTION_CLOSED" :
|
|
895
|
+
systemCode === "EPERM" || systemCode === "EACCES"
|
|
896
|
+
? "CONTROL_CONNECT_DENIED" : systemCode === "ENOENT"
|
|
897
|
+
? "CONTROL_SOCKET_MISSING" : systemCode === "ECONNREFUSED"
|
|
898
|
+
? "CONTROL_LISTENER_UNAVAILABLE" : "CONTROL_CONNECT_FAILED";
|
|
1026
899
|
fail(controlTransportError(code));
|
|
1027
900
|
});
|
|
1028
901
|
});
|