agent-embassy 1.8.2 → 1.9.2
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 +18 -0
- package/README.md +23 -3
- package/README.zh-CN.md +23 -3
- package/SECURITY.md +30 -6
- package/dist/src/gateway/cli-copy.en.d.ts +1 -1
- package/dist/src/gateway/cli-copy.en.js +8 -0
- package/dist/src/gateway/cli-copy.en.js.map +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.js +8 -0
- package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/cli.d.ts +8 -3
- package/dist/src/gateway/cli.js +191 -19
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/config.d.ts +2 -0
- package/dist/src/gateway/config.js +1 -1
- package/dist/src/gateway/config.js.map +1 -1
- package/dist/src/gateway/control.d.ts +79 -25
- package/dist/src/gateway/control.js +98 -18
- package/dist/src/gateway/control.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.d.ts +1 -1
- package/dist/src/gateway/dashboard-copy.en.d.ts +2 -0
- package/dist/src/gateway/dashboard-copy.en.js +2 -0
- package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.js +2 -0
- package/dist/src/gateway/dashboard-copy.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +2 -0
- package/dist/src/gateway/dashboard-copy.zh-CN.js +2 -0
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/dashboard-model.d.ts +3 -0
- package/dist/src/gateway/dashboard-model.js +3 -0
- package/dist/src/gateway/dashboard-model.js.map +1 -1
- package/dist/src/gateway/federation-nodes.d.ts +18 -0
- package/dist/src/gateway/federation-nodes.js +142 -0
- package/dist/src/gateway/federation-nodes.js.map +1 -0
- package/dist/src/gateway/live-dashboard-app/app.js +5 -4
- package/dist/src/gateway/peer-client.d.ts +57 -0
- package/dist/src/gateway/peer-client.js +208 -0
- package/dist/src/gateway/peer-client.js.map +1 -0
- package/dist/src/gateway/peer-mailbox.d.ts +78 -0
- package/dist/src/gateway/peer-mailbox.js +168 -0
- package/dist/src/gateway/peer-mailbox.js.map +1 -0
- package/dist/src/gateway/peer-protocol.d.ts +107 -0
- package/dist/src/gateway/peer-protocol.js +72 -0
- package/dist/src/gateway/peer-protocol.js.map +1 -0
- package/dist/src/gateway/peer-stdio.d.ts +23 -0
- package/dist/src/gateway/peer-stdio.js +100 -0
- package/dist/src/gateway/peer-stdio.js.map +1 -0
- package/dist/src/gateway/provenance-envelope.js +6 -1
- package/dist/src/gateway/provenance-envelope.js.map +1 -1
- package/dist/src/gateway/providers.d.ts +5 -2
- package/dist/src/gateway/providers.js +10 -7
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +4 -1
- package/dist/src/gateway/server.js +21 -8
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +19 -0
- package/dist/src/gateway/service.js +437 -37
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/state-v2-to-v3.js +1 -0
- package/dist/src/gateway/state-v2-to-v3.js.map +1 -1
- package/dist/src/gateway/store.d.ts +9 -1
- package/dist/src/gateway/store.js +183 -43
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +7 -6
- package/dist/src/gateway/types.js +5 -10
- package/dist/src/gateway/types.js.map +1 -1
- package/docs/CONFIGURATION.md +23 -8
- package/docs/CONFIGURATION.zh-CN.md +12 -4
- package/docs/DASHBOARD.md +2 -2
- package/docs/DASHBOARD.zh-CN.md +2 -2
- package/docs/DELIVERY.md +3 -1
- package/docs/DELIVERY.zh-CN.md +3 -1
- package/docs/GATEWAY-ARCHITECTURE.md +6 -4
- package/package.json +1 -1
- package/skills/embassy-peer/SKILL.md +32 -4
- package/skills/embassy-peer/agents/openai.yaml +2 -2
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { GatewayPublicSnapshot } from "./types.js";
|
|
2
|
+
import { type PeerCatalogResult, type PeerHandoffParams, type PeerHandoffResult } from "./peer-protocol.js";
|
|
3
|
+
import { type PeerMailboxAwaitResult } from "./peer-mailbox.js";
|
|
2
4
|
export declare const GATEWAY_CONTROL_PROTOCOL_VERSION: 1;
|
|
3
5
|
export declare const GATEWAY_CONTROL_MAX_FRAME_BYTES: number;
|
|
4
6
|
export declare const GATEWAY_CONTROL_MAX_RESPONSE_BYTES: number;
|
|
5
7
|
export declare const GATEWAY_CONTROL_MAX_MESSAGE_BYTES: number;
|
|
6
8
|
export declare const GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS = 3000;
|
|
7
9
|
export declare const GATEWAY_CONTROL_MAX_CONNECTIONS = 32;
|
|
8
|
-
export declare const gatewayControlMethods: readonly ["health", "register_codex", "unregister_codex", "remove_codex_registration", "select_claude", "unselect_claude", "pair", "unpair", "list_snapshot", "observe_snapshot", "delivery_status", "untrack", "send_to_claude", "send_to_codex", "reply", "refresh_dashboard"];
|
|
10
|
+
export declare const gatewayControlMethods: readonly ["health", "register_codex", "unregister_codex", "remove_codex_registration", "select_claude", "unselect_claude", "pair", "unpair", "list_snapshot", "observe_snapshot", "delivery_status", "untrack", "send_to_claude", "send_to_codex", "reply", "refresh_dashboard", "peer_catalog", "peer_handoff", "register_peer", "unregister_peer", "await_peer", "peer_receipt"];
|
|
9
11
|
export type GatewayControlMethod = (typeof gatewayControlMethods)[number];
|
|
10
12
|
export type GatewayBusyPolicy = "queue";
|
|
11
13
|
export type RegisterCodexParams = {
|
|
@@ -51,38 +53,44 @@ export declare function pairParamThreadAttestation(params: Readonly<PairParams>)
|
|
|
51
53
|
alias: string;
|
|
52
54
|
threadId: string;
|
|
53
55
|
}> | undefined;
|
|
54
|
-
|
|
56
|
+
type SendBase = {
|
|
55
57
|
fromAlias: string;
|
|
56
|
-
threadId: string;
|
|
57
58
|
toAlias: string;
|
|
58
59
|
text: string;
|
|
59
60
|
expectsReply?: boolean;
|
|
60
61
|
trackIdleMinutes?: number;
|
|
61
62
|
};
|
|
62
|
-
|
|
63
|
-
fromAlias: string;
|
|
64
|
-
threadId: string;
|
|
65
|
-
toAlias: string;
|
|
66
|
-
text: string;
|
|
63
|
+
type ValidatedSendBase = Omit<SendBase, "expectsReply"> & {
|
|
67
64
|
expectsReply: boolean;
|
|
68
|
-
trackIdleMinutes?: number;
|
|
69
|
-
};
|
|
70
|
-
export type SendToCodexParams = {
|
|
71
|
-
fromAlias: string;
|
|
72
|
-
toAlias: string;
|
|
73
|
-
text: string;
|
|
74
|
-
replyAddress?: string;
|
|
75
|
-
expectsReply?: boolean;
|
|
76
|
-
trackIdleMinutes?: number;
|
|
77
|
-
};
|
|
78
|
-
export type ValidatedSendToCodexParams = {
|
|
79
|
-
fromAlias: string;
|
|
80
|
-
toAlias: string;
|
|
81
|
-
text: string;
|
|
82
|
-
replyAddress?: string;
|
|
83
|
-
expectsReply: boolean;
|
|
84
|
-
trackIdleMinutes?: number;
|
|
85
65
|
};
|
|
66
|
+
export type SendToClaudeParams = SendBase & ({
|
|
67
|
+
threadId: string;
|
|
68
|
+
peerToken?: never;
|
|
69
|
+
} | {
|
|
70
|
+
peerToken: string;
|
|
71
|
+
threadId?: never;
|
|
72
|
+
});
|
|
73
|
+
export type ValidatedSendToClaudeParams = ValidatedSendBase & ({
|
|
74
|
+
threadId: string;
|
|
75
|
+
peerToken?: never;
|
|
76
|
+
} | {
|
|
77
|
+
peerToken: string;
|
|
78
|
+
threadId?: never;
|
|
79
|
+
});
|
|
80
|
+
export type SendToCodexParams = SendBase & ({
|
|
81
|
+
replyAddress: string;
|
|
82
|
+
peerToken?: never;
|
|
83
|
+
} | {
|
|
84
|
+
peerToken: string;
|
|
85
|
+
replyAddress?: never;
|
|
86
|
+
});
|
|
87
|
+
export type ValidatedSendToCodexParams = ValidatedSendBase & ({
|
|
88
|
+
replyAddress: string;
|
|
89
|
+
peerToken?: never;
|
|
90
|
+
} | {
|
|
91
|
+
peerToken: string;
|
|
92
|
+
replyAddress?: never;
|
|
93
|
+
});
|
|
86
94
|
export type GatewayReplyCaller = {
|
|
87
95
|
kind: "codex";
|
|
88
96
|
alias: string;
|
|
@@ -91,6 +99,10 @@ export type GatewayReplyCaller = {
|
|
|
91
99
|
kind: "claude";
|
|
92
100
|
alias: string;
|
|
93
101
|
replyAddress?: string;
|
|
102
|
+
} | {
|
|
103
|
+
kind: "peer";
|
|
104
|
+
alias: string;
|
|
105
|
+
token: string;
|
|
94
106
|
};
|
|
95
107
|
export type ReplyParams = {
|
|
96
108
|
conversationId: string;
|
|
@@ -104,6 +116,24 @@ export type UntrackParams = {
|
|
|
104
116
|
export type DeliveryStatusParams = {
|
|
105
117
|
token: string;
|
|
106
118
|
};
|
|
119
|
+
export type PeerCatalogParams = {
|
|
120
|
+
peerHost: string;
|
|
121
|
+
};
|
|
122
|
+
export type PeerHandoffControlParams = {
|
|
123
|
+
peerHost: string;
|
|
124
|
+
handoff: PeerHandoffParams;
|
|
125
|
+
};
|
|
126
|
+
export type RegisterPeerParams = {
|
|
127
|
+
alias: string;
|
|
128
|
+
token?: string;
|
|
129
|
+
};
|
|
130
|
+
export type PeerPrincipalParams = {
|
|
131
|
+
alias: string;
|
|
132
|
+
token: string;
|
|
133
|
+
};
|
|
134
|
+
export type PeerReceiptParams = PeerPrincipalParams & {
|
|
135
|
+
receipt: string;
|
|
136
|
+
};
|
|
107
137
|
type RequestParams = {
|
|
108
138
|
health: Record<string, never>;
|
|
109
139
|
register_codex: RegisterCodexParams;
|
|
@@ -121,6 +151,12 @@ type RequestParams = {
|
|
|
121
151
|
send_to_codex: SendToCodexParams;
|
|
122
152
|
reply: ReplyParams;
|
|
123
153
|
refresh_dashboard: Record<string, never>;
|
|
154
|
+
peer_catalog: PeerCatalogParams;
|
|
155
|
+
peer_handoff: PeerHandoffControlParams;
|
|
156
|
+
register_peer: RegisterPeerParams;
|
|
157
|
+
unregister_peer: PeerPrincipalParams;
|
|
158
|
+
await_peer: PeerPrincipalParams;
|
|
159
|
+
peer_receipt: PeerReceiptParams;
|
|
124
160
|
};
|
|
125
161
|
export type GatewayControlRequest = {
|
|
126
162
|
[M in GatewayControlMethod]: {
|
|
@@ -136,6 +172,7 @@ export type GatewayDecision = {
|
|
|
136
172
|
} | {
|
|
137
173
|
accepted: false;
|
|
138
174
|
code: Exclude<GatewayDecisionCode, "ok">;
|
|
175
|
+
ownerHost?: string;
|
|
139
176
|
};
|
|
140
177
|
export type GatewaySendResult = {
|
|
141
178
|
accepted: true;
|
|
@@ -170,6 +207,11 @@ export type GatewaySnapshotObservation = {
|
|
|
170
207
|
snapshotRevision: number;
|
|
171
208
|
snapshot: GatewaySnapshot;
|
|
172
209
|
};
|
|
210
|
+
export type GatewayRegisterPeerResult = GatewayDecision | {
|
|
211
|
+
accepted: true;
|
|
212
|
+
code: "ok";
|
|
213
|
+
token: string;
|
|
214
|
+
};
|
|
173
215
|
type ResultByMethod = {
|
|
174
216
|
health: GatewayHealthResult;
|
|
175
217
|
register_codex: GatewayDecision;
|
|
@@ -187,6 +229,12 @@ type ResultByMethod = {
|
|
|
187
229
|
send_to_codex: GatewaySendResult;
|
|
188
230
|
reply: GatewaySendResult;
|
|
189
231
|
refresh_dashboard: GatewayRefreshResult;
|
|
232
|
+
peer_catalog: PeerCatalogResult;
|
|
233
|
+
peer_handoff: PeerHandoffResult;
|
|
234
|
+
register_peer: GatewayRegisterPeerResult;
|
|
235
|
+
unregister_peer: GatewayDecision;
|
|
236
|
+
await_peer: PeerMailboxAwaitResult;
|
|
237
|
+
peer_receipt: GatewayDecision;
|
|
190
238
|
};
|
|
191
239
|
type MaybePromise<T> = T | Promise<T>;
|
|
192
240
|
export type GatewayControlHandlers = {
|
|
@@ -206,6 +254,12 @@ export type GatewayControlHandlers = {
|
|
|
206
254
|
sendToCodex: (params: Readonly<ValidatedSendToCodexParams>) => MaybePromise<GatewaySendResult>;
|
|
207
255
|
reply: (params: Readonly<ReplyParams>) => MaybePromise<GatewaySendResult>;
|
|
208
256
|
refreshDashboard: () => MaybePromise<GatewayRefreshResult>;
|
|
257
|
+
peerCatalog?: (params: Readonly<PeerCatalogParams>) => MaybePromise<PeerCatalogResult>;
|
|
258
|
+
peerHandoff?: (params: Readonly<PeerHandoffControlParams>) => MaybePromise<PeerHandoffResult>;
|
|
259
|
+
registerPeer: (params: Readonly<RegisterPeerParams>) => MaybePromise<GatewayRegisterPeerResult>;
|
|
260
|
+
unregisterPeer: (params: Readonly<PeerPrincipalParams>) => MaybePromise<GatewayDecision>;
|
|
261
|
+
awaitPeer: (params: Readonly<PeerPrincipalParams>) => MaybePromise<PeerMailboxAwaitResult>;
|
|
262
|
+
peerReceipt: (params: Readonly<PeerReceiptParams>) => MaybePromise<GatewayDecision>;
|
|
209
263
|
};
|
|
210
264
|
export type GatewayWireErrorCode = "INVALID_JSON" | "FRAME_TOO_LARGE" | "INVALID_REQUEST" | "UNSUPPORTED_VERSION" | "UNKNOWN_METHOD" | "MULTIPLE_FRAMES" | "SERVER_BUSY" | "REQUEST_TIMEOUT" | "HANDLER_FAILURE" | "INVALID_HANDLER_RESPONSE" | "RESPONSE_TOO_LARGE";
|
|
211
265
|
export type GatewayControlErrorResponse = {
|
|
@@ -5,6 +5,8 @@ import net from "node:net";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { TextDecoder } from "node:util";
|
|
7
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";
|
|
9
|
+
import { isPeerMailboxAwaitResult } from "./peer-mailbox.js";
|
|
8
10
|
export const GATEWAY_CONTROL_PROTOCOL_VERSION = 1;
|
|
9
11
|
export const GATEWAY_CONTROL_MAX_FRAME_BYTES = 32 * 1024;
|
|
10
12
|
export const GATEWAY_CONTROL_MAX_RESPONSE_BYTES = 256 * 1024;
|
|
@@ -19,6 +21,8 @@ const ALIAS_PATTERN = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9
|
|
|
19
21
|
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;
|
|
20
22
|
const CONVERSATION_ID_PATTERN = /^conv_[A-Za-z0-9_-]{16,64}$/;
|
|
21
23
|
const DELIVERY_TOKEN_PATTERN = /^dlv_[A-Za-z0-9_-]{24}$/;
|
|
24
|
+
const PEER_TOKEN_PATTERN = /^peer_[A-Za-z0-9_-]{32}$/;
|
|
25
|
+
const PEER_RECEIPT_PATTERN = /^prc_[A-Za-z0-9_-]{24}$/;
|
|
22
26
|
const HOST_PATTERN = /^[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
|
|
23
27
|
const SAFE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
24
28
|
const PROTOCOL_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
@@ -30,7 +34,8 @@ export const gatewayControlMethods = [
|
|
|
30
34
|
"health", "register_codex", "unregister_codex", "remove_codex_registration",
|
|
31
35
|
"select_claude", "unselect_claude", "pair", "unpair", "list_snapshot",
|
|
32
36
|
"observe_snapshot", "delivery_status", "untrack", "send_to_claude",
|
|
33
|
-
"send_to_codex", "reply", "refresh_dashboard",
|
|
37
|
+
"send_to_codex", "reply", "refresh_dashboard", "peer_catalog", "peer_handoff",
|
|
38
|
+
"register_peer", "unregister_peer", "await_peer", "peer_receipt",
|
|
34
39
|
];
|
|
35
40
|
export function pairParamAliases(params) {
|
|
36
41
|
return "aliases" in params ? params.aliases : [params.claudeAlias, params.codexAlias];
|
|
@@ -178,11 +183,62 @@ function arrayOf(value, maximum, check) {
|
|
|
178
183
|
function positive(value) { return nonNegative(value) && value > 0; }
|
|
179
184
|
function safeCode(value) { return typeof value === "string" && SAFE_CODE_PATTERN.test(value); }
|
|
180
185
|
const messageText = isMessageText;
|
|
186
|
+
const peerToken = (value) => typeof value === "string" && PEER_TOKEN_PATTERN.test(value);
|
|
187
|
+
const peerAlias = (value) => alias(value) && value.startsWith("peer-");
|
|
181
188
|
function emptyParams(value) {
|
|
182
189
|
if (!isRecord(value) || !exact(value, []))
|
|
183
190
|
invalid();
|
|
184
191
|
return {};
|
|
185
192
|
}
|
|
193
|
+
function decodePeerCatalog(value) {
|
|
194
|
+
if (!shape(value, { peerHost: host }))
|
|
195
|
+
invalid();
|
|
196
|
+
return { peerHost: value.peerHost };
|
|
197
|
+
}
|
|
198
|
+
function decodePeerHandoff(value) {
|
|
199
|
+
if (!isRecord(value) || !exact(value, ["peerHost", "handoff"]) || !host(value.peerHost))
|
|
200
|
+
invalid();
|
|
201
|
+
try {
|
|
202
|
+
return { peerHost: value.peerHost, handoff: decodePeerParams("handoff", value.handoff) };
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return invalid();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function decodeRegisterPeer(value) {
|
|
209
|
+
if (!shape(value, { alias: peerAlias }, { token: peerToken }))
|
|
210
|
+
invalid();
|
|
211
|
+
return { alias: value.alias, ...(value.token === undefined ? {} : { token: value.token }) };
|
|
212
|
+
}
|
|
213
|
+
function decodePeerPrincipal(value) {
|
|
214
|
+
if (!shape(value, { alias: peerAlias, token: peerToken }))
|
|
215
|
+
invalid();
|
|
216
|
+
return { alias: value.alias, token: value.token };
|
|
217
|
+
}
|
|
218
|
+
function decodePeerReceipt(value) {
|
|
219
|
+
if (!shape(value, { alias: peerAlias, token: peerToken,
|
|
220
|
+
receipt: (item) => typeof item === "string" && PEER_RECEIPT_PATTERN.test(item) }))
|
|
221
|
+
invalid();
|
|
222
|
+
return { alias: value.alias, token: value.token, receipt: value.receipt };
|
|
223
|
+
}
|
|
224
|
+
const isPeerCatalog = (value) => {
|
|
225
|
+
try {
|
|
226
|
+
decodePeerResult("catalog/get", value);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const isPeerHandoffResult = (value) => {
|
|
234
|
+
try {
|
|
235
|
+
decodePeerResult("handoff", value);
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
186
242
|
function decodeRegister(value) {
|
|
187
243
|
if (!isRecord(value) || !exact(value, ["alias", "threadId"], ["hostId", "busyPolicy", "succeedsAlias"]) ||
|
|
188
244
|
!alias(value.alias) || !value.alias.startsWith("codex-") || !uuid(value.threadId) ||
|
|
@@ -222,9 +278,7 @@ function decodePair(value) {
|
|
|
222
278
|
if (Object.hasOwn(value, "aliases")) {
|
|
223
279
|
if (!exact(value, ["aliases"], ["threadAttestation"]) || !Array.isArray(value.aliases) ||
|
|
224
280
|
value.aliases.length !== 2 || !alias(value.aliases[0]) || !alias(value.aliases[1]) ||
|
|
225
|
-
value.aliases[0] === value.aliases[1]
|
|
226
|
-
value.aliases[0].slice(value.aliases[0].lastIndexOf("@") + 1) !==
|
|
227
|
-
value.aliases[1].slice(value.aliases[1].lastIndexOf("@") + 1))
|
|
281
|
+
value.aliases[0] === value.aliases[1])
|
|
228
282
|
invalid();
|
|
229
283
|
let threadAttestation;
|
|
230
284
|
if (value.threadAttestation !== undefined) {
|
|
@@ -258,24 +312,29 @@ function commonSend(value, required, optional) {
|
|
|
258
312
|
return value;
|
|
259
313
|
}
|
|
260
314
|
function decodeSendClaude(value) {
|
|
261
|
-
if (!isRecord(value) || !exact(value, ["fromAlias", "
|
|
262
|
-
!alias(value.fromAlias) ||
|
|
315
|
+
if (!isRecord(value) || !exact(value, ["fromAlias", "toAlias", "text"], ["threadId", "peerToken", "expectsReply", "trackIdleMinutes"]) ||
|
|
316
|
+
!alias(value.fromAlias) || (value.threadId === undefined) === (value.peerToken === undefined) ||
|
|
317
|
+
(value.threadId !== undefined && !uuid(value.threadId)) ||
|
|
318
|
+
(value.peerToken !== undefined && !peerToken(value.peerToken)) || typeof value.toAlias !== "string" ||
|
|
263
319
|
!isClaudeSessionSelector(value.toAlias) || !messageText(value.text) ||
|
|
264
320
|
(value.expectsReply !== undefined && typeof value.expectsReply !== "boolean") ||
|
|
265
321
|
(value.trackIdleMinutes !== undefined && !trackMinutes(value.trackIdleMinutes)))
|
|
266
322
|
invalid();
|
|
267
|
-
return { fromAlias: value.fromAlias,
|
|
323
|
+
return { fromAlias: value.fromAlias,
|
|
268
324
|
toAlias: UUID_PATTERN.test(value.toAlias) ? value.toAlias.toLowerCase() : value.toAlias,
|
|
269
325
|
text: value.text, expectsReply: value.expectsReply ?? false,
|
|
326
|
+
...(typeof value.threadId === "string" ? { threadId: value.threadId.toLowerCase() } : { peerToken: value.peerToken }),
|
|
270
327
|
...(value.trackIdleMinutes === undefined ? {} : { trackIdleMinutes: value.trackIdleMinutes }) };
|
|
271
328
|
}
|
|
272
329
|
function decodeSendCodex(value) {
|
|
273
|
-
const row = commonSend(value, ["fromAlias", "toAlias", "text"], ["replyAddress", "expectsReply", "trackIdleMinutes"]);
|
|
274
|
-
if (row.replyAddress
|
|
275
|
-
(typeof row.replyAddress !== "string" || !isGatewayReplyAddress(row.replyAddress)))
|
|
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)))
|
|
276
334
|
invalid();
|
|
335
|
+
const replyAddress = row.replyAddress !== undefined;
|
|
277
336
|
return { fromAlias: row.fromAlias, toAlias: row.toAlias, text: row.text,
|
|
278
|
-
...(
|
|
337
|
+
...(replyAddress ? { replyAddress: row.replyAddress } : { peerToken: row.peerToken }),
|
|
279
338
|
expectsReply: row.expectsReply === true,
|
|
280
339
|
...(row.trackIdleMinutes === undefined ? {} : { trackIdleMinutes: row.trackIdleMinutes }) };
|
|
281
340
|
}
|
|
@@ -290,6 +349,8 @@ function normalizeReplyCaller(value) {
|
|
|
290
349
|
(typeof value.replyAddress === "string" && isGatewayReplyAddress(value.replyAddress))))
|
|
291
350
|
return { kind: "claude", alias: value.alias,
|
|
292
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 };
|
|
293
354
|
return invalid();
|
|
294
355
|
}
|
|
295
356
|
function decodeReply(value) {
|
|
@@ -312,7 +373,12 @@ function decodeUntrack(value) {
|
|
|
312
373
|
return { conversationId: value.conversationId };
|
|
313
374
|
}
|
|
314
375
|
function isDecision(value) {
|
|
315
|
-
return shape(value, { accepted: (item) => typeof item === "boolean", code: oneOf("ok", "not_found", "conflict", "watch_owner_conflict", "route_mismatch", "busy", "unavailable", "rejected") }) && (value.accepted === true
|
|
376
|
+
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
|
|
377
|
+
? value.code === "ok" && value.ownerHost === undefined
|
|
378
|
+
: value.code !== "ok" && (value.ownerHost === undefined || value.code === "conflict"));
|
|
379
|
+
}
|
|
380
|
+
function isRegisterPeerResult(value) {
|
|
381
|
+
return isDecision(value) || shape(value, { accepted: oneOf(true), code: oneOf("ok"), token: peerToken });
|
|
316
382
|
}
|
|
317
383
|
function isHealthResult(value) {
|
|
318
384
|
return shape(value, { status: oneOf("ok", "degraded"), revision: nonNegative });
|
|
@@ -356,7 +422,7 @@ function isConnectorSnapshot(value) {
|
|
|
356
422
|
function isRouteSnapshot(value) {
|
|
357
423
|
if (!shape(value, { alias, provider: isGatewayProvider, host, enabled: (item) => typeof item === "boolean",
|
|
358
424
|
state: oneOf("stale", "idle", "busy", "awaiting_approval", "offline", "disabled"),
|
|
359
|
-
busyPolicy: oneOf("queue"), queueDepth: nonNegative, counters: isRouteCounters }, { lastSeenAt: iso, oldestQueuedAt: iso, safeErrorCode: safeCode }))
|
|
425
|
+
busyPolicy: oneOf("queue"), queueDepth: nonNegative, counters: isRouteCounters }, { lastSeenAt: iso, oldestQueuedAt: iso, safeErrorCode: safeCode, mutable: (item) => typeof item === "boolean" }))
|
|
360
426
|
return false;
|
|
361
427
|
const row = value;
|
|
362
428
|
return row.alias.endsWith(`@${row.host}`) &&
|
|
@@ -369,11 +435,12 @@ function compareConsentEndpoints(left, right) {
|
|
|
369
435
|
function isConsentEdgeSnapshot(value) {
|
|
370
436
|
const endpoint = (item) => shape(item, { alias, provider: isGatewayProvider });
|
|
371
437
|
if (!shape(value, { endpoints: (rows) => Array.isArray(rows) && rows.length === 2 && rows.every(endpoint),
|
|
372
|
-
host, counters: isRouteCounters }))
|
|
438
|
+
host, counters: isRouteCounters }, { mutable: (item) => typeof item === "boolean" }))
|
|
373
439
|
return false;
|
|
374
440
|
const [left, right] = value.endpoints;
|
|
375
|
-
|
|
376
|
-
|
|
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];
|
|
377
444
|
}
|
|
378
445
|
function isAvailablePeerSnapshot(value) {
|
|
379
446
|
return shape(value, { alias, provider: oneOf("claude"), host,
|
|
@@ -500,6 +567,12 @@ const descriptors = {
|
|
|
500
567
|
delivery_status: { handler: "deliveryStatus", decode: decodeDeliveryStatus, result: isDeliveryStatusResult, mutation: false }, untrack: { handler: "untrack", decode: decodeUntrack, result: isDecision, mutation: false },
|
|
501
568
|
send_to_claude: { handler: "sendToClaude", decode: decodeSendClaude, result: isSendResult, mutation: true }, send_to_codex: { handler: "sendToCodex", decode: decodeSendCodex, result: isSendResult, mutation: true },
|
|
502
569
|
reply: { handler: "reply", decode: decodeReply, result: isSendResult, mutation: true }, refresh_dashboard: { handler: "refreshDashboard", decode: emptyParams, result: isRefreshResult, mutation: false },
|
|
570
|
+
peer_catalog: { handler: "peerCatalog", decode: decodePeerCatalog, result: isPeerCatalog, mutation: false },
|
|
571
|
+
peer_handoff: { handler: "peerHandoff", decode: decodePeerHandoff, result: isPeerHandoffResult, mutation: true },
|
|
572
|
+
register_peer: { handler: "registerPeer", decode: decodeRegisterPeer, result: isRegisterPeerResult, mutation: true },
|
|
573
|
+
unregister_peer: { handler: "unregisterPeer", decode: decodePeerPrincipal, result: isDecision, mutation: true },
|
|
574
|
+
await_peer: { handler: "awaitPeer", decode: decodePeerPrincipal, result: isPeerMailboxAwaitResult, mutation: false },
|
|
575
|
+
peer_receipt: { handler: "peerReceipt", decode: decodePeerReceipt, result: isDecision, mutation: true },
|
|
503
576
|
};
|
|
504
577
|
function parseRequestObject(value) {
|
|
505
578
|
if (!isRecord(value) || !exact(value, ["protocolVersion", "method", "params"]))
|
|
@@ -887,7 +960,7 @@ export async function startGatewayControlServer(options) {
|
|
|
887
960
|
close,
|
|
888
961
|
};
|
|
889
962
|
}
|
|
890
|
-
function decodeResponse(method, value) {
|
|
963
|
+
function decodeResponse(method, params, value) {
|
|
891
964
|
if (!isRecord(value) || value.protocolVersion !== 1)
|
|
892
965
|
throw controlTransportError("CONTROL_INVALID_RESPONSE");
|
|
893
966
|
if (value.ok === false) {
|
|
@@ -899,6 +972,13 @@ function decodeResponse(method, value) {
|
|
|
899
972
|
}
|
|
900
973
|
if (!shape(value, { protocolVersion: oneOf(1), ok: oneOf(true), result: descriptors[method].result }))
|
|
901
974
|
throw controlTransportError("CONTROL_INVALID_RESPONSE");
|
|
975
|
+
if (method === "register_peer" && isRecord(value.result) && value.result.accepted === true &&
|
|
976
|
+
Object.hasOwn(params, "token") === Object.hasOwn(value.result, "token"))
|
|
977
|
+
throw controlTransportError("CONTROL_INVALID_RESPONSE");
|
|
978
|
+
if (method === "await_peer" && isRecord(value.result) && value.result.state === "message" &&
|
|
979
|
+
JSON.parse(value.result.frame).result.toAlias !==
|
|
980
|
+
params.alias)
|
|
981
|
+
throw controlTransportError("CONTROL_INVALID_RESPONSE");
|
|
902
982
|
return value;
|
|
903
983
|
}
|
|
904
984
|
export async function sendGatewayControlRequest(options) {
|
|
@@ -928,7 +1008,7 @@ export async function sendGatewayControlRequest(options) {
|
|
|
928
1008
|
socket.once("connect", () => { writeStarted = true; socket.write(frame); });
|
|
929
1009
|
void readOneFrame(socket, maximum).then((responseFrame) => {
|
|
930
1010
|
try {
|
|
931
|
-
const response = decodeResponse(options.request.method, decodeJson(responseFrame, "INVALID_JSON"));
|
|
1011
|
+
const response = decodeResponse(options.request.method, request.params, decodeJson(responseFrame, "INVALID_JSON"));
|
|
932
1012
|
if (!settled) {
|
|
933
1013
|
settled = true;
|
|
934
1014
|
socket.destroy();
|