@masons/agent-network 0.5.28 → 0.5.29
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/dist/broker/broker-daemon.d.ts +24 -0
- package/dist/broker/broker-daemon.d.ts.map +1 -0
- package/dist/broker/broker-daemon.js +229 -0
- package/dist/broker/connector-ws.d.ts +47 -0
- package/dist/broker/connector-ws.d.ts.map +1 -0
- package/dist/broker/connector-ws.js +60 -0
- package/dist/broker/correlation-ring.d.ts +10 -0
- package/dist/broker/correlation-ring.d.ts.map +1 -0
- package/dist/broker/correlation-ring.js +32 -0
- package/dist/broker/discovery-file.d.ts +12 -0
- package/dist/broker/discovery-file.d.ts.map +1 -0
- package/dist/broker/discovery-file.js +77 -0
- package/dist/broker/endpoint-registry.d.ts +37 -0
- package/dist/broker/endpoint-registry.d.ts.map +1 -0
- package/dist/broker/endpoint-registry.js +44 -0
- package/dist/broker/entry.d.ts +2 -0
- package/dist/broker/entry.d.ts.map +1 -0
- package/dist/broker/entry.js +85 -0
- package/dist/broker/ipc-server.d.ts +65 -0
- package/dist/broker/ipc-server.d.ts.map +1 -0
- package/dist/broker/ipc-server.js +212 -0
- package/dist/broker/logger.d.ts +10 -0
- package/dist/broker/logger.d.ts.map +1 -0
- package/dist/broker/logger.js +34 -0
- package/dist/broker/paths.d.ts +10 -0
- package/dist/broker/paths.d.ts.map +1 -0
- package/dist/broker/paths.js +28 -0
- package/dist/broker/routing-table.d.ts +22 -0
- package/dist/broker/routing-table.d.ts.map +1 -0
- package/dist/broker/routing-table.js +35 -0
- package/dist/broker/runtime-endpoint-port.d.ts +7 -0
- package/dist/broker/runtime-endpoint-port.d.ts.map +1 -0
- package/dist/broker/runtime-endpoint-port.js +1 -0
- package/dist/broker/version-handshake.d.ts +30 -0
- package/dist/broker/version-handshake.d.ts.map +1 -0
- package/dist/broker/version-handshake.js +47 -0
- package/dist/broker-client/broker-client.d.ts +64 -0
- package/dist/broker-client/broker-client.d.ts.map +1 -0
- package/dist/broker-client/broker-client.js +164 -0
- package/dist/broker-client/lazy-spawn.d.ts +18 -0
- package/dist/broker-client/lazy-spawn.d.ts.map +1 -0
- package/dist/broker-client/lazy-spawn.js +61 -0
- package/dist/runtime-endpoint-client.d.ts +2 -2
- package/dist/runtime-endpoint-client.d.ts.map +1 -1
- package/dist/runtime-endpoint-client.js +4 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +11 -2
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
import type { WebSocket } from "ws";
|
|
3
|
+
import type { ConnectorWS } from "./connector-ws.js";
|
|
4
|
+
import type { BrokerLogger } from "./logger.js";
|
|
5
|
+
import type { BrokerPaths } from "./paths.js";
|
|
6
|
+
import type { RuntimeEndpointPort } from "./runtime-endpoint-port.js";
|
|
7
|
+
export interface BrokerDaemonOptions {
|
|
8
|
+
paths: BrokerPaths;
|
|
9
|
+
asNodeId: string;
|
|
10
|
+
connector: ConnectorWS;
|
|
11
|
+
apiPort: RuntimeEndpointPort;
|
|
12
|
+
logger: BrokerLogger;
|
|
13
|
+
}
|
|
14
|
+
export interface RunningBroker {
|
|
15
|
+
bearerToken: string;
|
|
16
|
+
ipcUrl: string;
|
|
17
|
+
shutdown(reason: string): Promise<void>;
|
|
18
|
+
registrySize(): number;
|
|
19
|
+
_bindPluginChannel(plugin_pid: number, ws: WebSocket): void;
|
|
20
|
+
_unbindPluginChannel(plugin_pid: number): void;
|
|
21
|
+
}
|
|
22
|
+
export declare function startBrokerDaemon(opts: BrokerDaemonOptions): Promise<RunningBroker>;
|
|
23
|
+
export declare function readPluginPidHeader(req: IncomingMessage): number | null;
|
|
24
|
+
//# sourceMappingURL=broker-daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker-daemon.d.ts","sourceRoot":"","sources":["../../src/broker/broker-daemon.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAiBrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,CAAC;IACvB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,MAAM,EAAE,YAAY,CAAC;CAEtB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC,YAAY,IAAI,MAAM,CAAC;IAEvB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5D,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAChD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,aAAa,CAAC,CA8TxB;AAGD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAMvE"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { deleteDiscoveryFile, mintBearerToken, writeDiscoveryFile, } from "./discovery-file.js";
|
|
3
|
+
import { EndpointRegistry } from "./endpoint-registry.js";
|
|
4
|
+
import { BrokerHttpError, pushToPlugin, startIPCServer, } from "./ipc-server.js";
|
|
5
|
+
import { RoutingTable } from "./routing-table.js";
|
|
6
|
+
export async function startBrokerDaemon(opts) {
|
|
7
|
+
const { paths, connector, apiPort, logger } = opts;
|
|
8
|
+
logger.info("connecting_to_connector");
|
|
9
|
+
await connector.connect();
|
|
10
|
+
logger.info("connector_connected");
|
|
11
|
+
const registry = new EndpointRegistry();
|
|
12
|
+
const router = new RoutingTable(registry, {
|
|
13
|
+
onCollision: (correlation_id, count) => {
|
|
14
|
+
logger.warn("correlation_id_collision", { correlation_id, count });
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
const channels = new Map();
|
|
18
|
+
const handlers = {
|
|
19
|
+
async registerEndpoint(body, _ipcWs) {
|
|
20
|
+
const boundWs = channels.get(body.plugin_pid);
|
|
21
|
+
if (!boundWs) {
|
|
22
|
+
throw new Error(`no open IPC channel for plugin_pid=${body.plugin_pid}; ` +
|
|
23
|
+
"open WS /v1/stream with x-plugin-pid header first");
|
|
24
|
+
}
|
|
25
|
+
const apiResp = await apiPort.register({
|
|
26
|
+
runtime_kind: body.kind,
|
|
27
|
+
endpoint_nonce: randomUUID(),
|
|
28
|
+
display_label: body.session_name ??
|
|
29
|
+
`${body.kind}-${String(body.plugin_pid).slice(-4)}`,
|
|
30
|
+
workspace: body.workspace
|
|
31
|
+
? { root: body.workspace, cwd: body.workspace }
|
|
32
|
+
: undefined,
|
|
33
|
+
tracking_ref: body.tracking_ref,
|
|
34
|
+
});
|
|
35
|
+
registry.register({
|
|
36
|
+
endpoint_id: apiResp.endpoint_id,
|
|
37
|
+
agent_id: body.agent_id,
|
|
38
|
+
plugin_pid: body.plugin_pid,
|
|
39
|
+
ipc_ws: boundWs,
|
|
40
|
+
display_metadata: {
|
|
41
|
+
session_name: body.session_name,
|
|
42
|
+
kind: body.kind,
|
|
43
|
+
started_at: body.started_at ?? new Date().toISOString(),
|
|
44
|
+
workspace: body.workspace,
|
|
45
|
+
tracking_ref: body.tracking_ref,
|
|
46
|
+
task_hint: body.task_hint,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
logger.info("endpoint_registered", {
|
|
50
|
+
endpoint_id: apiResp.endpoint_id,
|
|
51
|
+
plugin_pid: body.plugin_pid,
|
|
52
|
+
kind: body.kind,
|
|
53
|
+
});
|
|
54
|
+
return { endpoint_id: apiResp.endpoint_id };
|
|
55
|
+
},
|
|
56
|
+
async heartbeatEndpoint(endpoint_id) {
|
|
57
|
+
const entry = registry.get(endpoint_id);
|
|
58
|
+
if (!entry) {
|
|
59
|
+
throw new Error(`unknown endpoint_id: ${endpoint_id}`);
|
|
60
|
+
}
|
|
61
|
+
await apiPort.heartbeat(endpoint_id);
|
|
62
|
+
},
|
|
63
|
+
async unregisterEndpoint(endpoint_id) {
|
|
64
|
+
const entry = registry.get(endpoint_id);
|
|
65
|
+
if (!entry)
|
|
66
|
+
return;
|
|
67
|
+
registry.unregister(endpoint_id);
|
|
68
|
+
try {
|
|
69
|
+
await apiPort.unregister(endpoint_id);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
logger.warn("unregister_api_failed", {
|
|
73
|
+
endpoint_id,
|
|
74
|
+
err: err instanceof Error ? err.message : String(err),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
async send(body) {
|
|
79
|
+
const entry = registry.get(body.endpoint_id);
|
|
80
|
+
if (!entry) {
|
|
81
|
+
throw new BrokerHttpError(404, "unknown_endpoint", `unknown endpoint_id: ${body.endpoint_id}`);
|
|
82
|
+
}
|
|
83
|
+
const incoming = body.metadata ?? {};
|
|
84
|
+
if ("correlation_id" in incoming) {
|
|
85
|
+
throw new BrokerHttpError(400, "correlation_id_forbidden", "broker is the sole issuer of correlation_id; do not pass it from the Plugin");
|
|
86
|
+
}
|
|
87
|
+
if ("target_endpoint_id" in incoming) {
|
|
88
|
+
throw new BrokerHttpError(400, "target_endpoint_id_forbidden", "target_endpoint_id is a reply-routing key, not an outbound input");
|
|
89
|
+
}
|
|
90
|
+
if ("source_endpoint_id" in incoming &&
|
|
91
|
+
incoming.source_endpoint_id !== body.endpoint_id) {
|
|
92
|
+
throw new BrokerHttpError(403, "source_endpoint_id_mismatch", "metadata.source_endpoint_id must equal body.endpoint_id");
|
|
93
|
+
}
|
|
94
|
+
const correlationId = randomUUID();
|
|
95
|
+
entry.correlation_ring.add(correlationId);
|
|
96
|
+
const metadata = {
|
|
97
|
+
...incoming,
|
|
98
|
+
correlation_id: correlationId,
|
|
99
|
+
source_endpoint_id: body.endpoint_id,
|
|
100
|
+
require_live: body.require_live ?? false,
|
|
101
|
+
};
|
|
102
|
+
const ack = await connector.send(body.to, body.content, body.contentType ?? "text", metadata);
|
|
103
|
+
return {
|
|
104
|
+
messageId: ack.messageId,
|
|
105
|
+
status: ack.status,
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
async reattachEndpoint(endpoint_id, plugin_pid, ipcWs) {
|
|
109
|
+
const entry = registry.preconditionForReattach(endpoint_id, plugin_pid);
|
|
110
|
+
if (!entry)
|
|
111
|
+
return false;
|
|
112
|
+
entry.ipc_ws = ipcWs;
|
|
113
|
+
return true;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const bearerToken = mintBearerToken();
|
|
117
|
+
const ipc = await startIPCServer({
|
|
118
|
+
bearerToken,
|
|
119
|
+
handlers,
|
|
120
|
+
logger,
|
|
121
|
+
onChannelOpened: (plugin_pid, ws) => {
|
|
122
|
+
const prior = channels.get(plugin_pid);
|
|
123
|
+
if (prior && prior !== ws) {
|
|
124
|
+
try {
|
|
125
|
+
prior.close(1000, "replaced");
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
channels.set(plugin_pid, ws);
|
|
131
|
+
},
|
|
132
|
+
onChannelClosed: (plugin_pid, ws) => {
|
|
133
|
+
if (channels.get(plugin_pid) === ws) {
|
|
134
|
+
channels.delete(plugin_pid);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
connector.on("message_received", ({ payload }) => {
|
|
139
|
+
const meta = payload.metadata ?? {};
|
|
140
|
+
const decision = router.route({
|
|
141
|
+
target_endpoint_id: typeof meta.target_endpoint_id === "string"
|
|
142
|
+
? meta.target_endpoint_id
|
|
143
|
+
: undefined,
|
|
144
|
+
correlation_id: typeof meta.correlation_id === "string"
|
|
145
|
+
? meta.correlation_id
|
|
146
|
+
: undefined,
|
|
147
|
+
});
|
|
148
|
+
if (decision.kind === "endpoint") {
|
|
149
|
+
pushToPlugin(decision.entry.ipc_ws, {
|
|
150
|
+
event: "message_received",
|
|
151
|
+
from: payload.from,
|
|
152
|
+
content: payload.content,
|
|
153
|
+
contentType: payload.contentType,
|
|
154
|
+
metadata: meta,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
logger.info("inbound_no_match", { from: payload.from });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
connector.on("delivery_pending", ({ payload }) => {
|
|
162
|
+
connector.ackDelivery(payload.upTo);
|
|
163
|
+
});
|
|
164
|
+
writeDiscoveryFile(paths.discoveryFile, {
|
|
165
|
+
version: 1,
|
|
166
|
+
pid: process.pid,
|
|
167
|
+
startedAt: new Date().toISOString(),
|
|
168
|
+
ipcUrl: ipc.ipcUrl,
|
|
169
|
+
bearerToken,
|
|
170
|
+
});
|
|
171
|
+
logger.info("discovery_written", { path: paths.discoveryFile });
|
|
172
|
+
let shutdownStarted = false;
|
|
173
|
+
const shutdown = async (reason) => {
|
|
174
|
+
if (shutdownStarted)
|
|
175
|
+
return;
|
|
176
|
+
shutdownStarted = true;
|
|
177
|
+
logger.info("shutdown_begin", { reason });
|
|
178
|
+
const endpoints = registry.list();
|
|
179
|
+
await Promise.all(endpoints.map(async (entry) => {
|
|
180
|
+
try {
|
|
181
|
+
await apiPort.unregister(entry.endpoint_id);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
}
|
|
185
|
+
}));
|
|
186
|
+
deleteDiscoveryFile(paths.discoveryFile);
|
|
187
|
+
try {
|
|
188
|
+
await ipc.close();
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
logger.warn("ipc_close_error", {
|
|
192
|
+
err: err instanceof Error ? err.message : String(err),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
connector.disconnect();
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
bearerToken,
|
|
203
|
+
ipcUrl: ipc.ipcUrl,
|
|
204
|
+
shutdown,
|
|
205
|
+
registrySize: () => registry.size(),
|
|
206
|
+
_bindPluginChannel: (plugin_pid, ws) => {
|
|
207
|
+
const prior = channels.get(plugin_pid);
|
|
208
|
+
if (prior) {
|
|
209
|
+
try {
|
|
210
|
+
prior.close(1000, "replaced");
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
channels.set(plugin_pid, ws);
|
|
216
|
+
},
|
|
217
|
+
_unbindPluginChannel: (plugin_pid) => {
|
|
218
|
+
channels.delete(plugin_pid);
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
export function readPluginPidHeader(req) {
|
|
223
|
+
const raw = req.headers["x-plugin-pid"];
|
|
224
|
+
const str = Array.isArray(raw) ? raw[0] : raw;
|
|
225
|
+
if (!str)
|
|
226
|
+
return null;
|
|
227
|
+
const n = Number.parseInt(str, 10);
|
|
228
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
229
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ConnectorError } from "../connector-client.js";
|
|
2
|
+
import type { AddressedMessageEvent, DeliveryPendingEvent, DeliveryStatusEvent, SendAckEvent, StructuredErrorEvent } from "../types.js";
|
|
3
|
+
export type ConnectorWSEvent = {
|
|
4
|
+
event: "message_received";
|
|
5
|
+
payload: AddressedMessageEvent;
|
|
6
|
+
} | {
|
|
7
|
+
event: "send_ack";
|
|
8
|
+
payload: SendAckEvent;
|
|
9
|
+
} | {
|
|
10
|
+
event: "delivery_pending";
|
|
11
|
+
payload: DeliveryPendingEvent;
|
|
12
|
+
} | {
|
|
13
|
+
event: "delivery_status";
|
|
14
|
+
payload: DeliveryStatusEvent;
|
|
15
|
+
} | {
|
|
16
|
+
event: "structured_error";
|
|
17
|
+
payload: StructuredErrorEvent;
|
|
18
|
+
} | {
|
|
19
|
+
event: "connected";
|
|
20
|
+
} | {
|
|
21
|
+
event: "disconnected";
|
|
22
|
+
} | {
|
|
23
|
+
event: "error";
|
|
24
|
+
payload: Error;
|
|
25
|
+
};
|
|
26
|
+
type ListenerFor<E extends ConnectorWSEvent["event"]> = (e: Extract<ConnectorWSEvent, {
|
|
27
|
+
event: E;
|
|
28
|
+
}>) => void;
|
|
29
|
+
export interface ConnectorWSOptions {
|
|
30
|
+
url: string;
|
|
31
|
+
token: string;
|
|
32
|
+
}
|
|
33
|
+
export declare class ConnectorWS {
|
|
34
|
+
private readonly client;
|
|
35
|
+
private readonly bus;
|
|
36
|
+
constructor(opts: ConnectorWSOptions);
|
|
37
|
+
connect(): Promise<void>;
|
|
38
|
+
disconnect(): void;
|
|
39
|
+
send(to: string, content: string, contentType?: string, metadata?: Record<string, unknown>): Promise<SendAckEvent>;
|
|
40
|
+
ackDelivery(upTo: string): boolean;
|
|
41
|
+
on<E extends ConnectorWSEvent["event"]>(event: E, listener: ListenerFor<E>): this;
|
|
42
|
+
off<E extends ConnectorWSEvent["event"]>(event: E, listener: ListenerFor<E>): this;
|
|
43
|
+
_emitForTesting(e: ConnectorWSEvent): void;
|
|
44
|
+
private wireForwarders;
|
|
45
|
+
}
|
|
46
|
+
export { ConnectorError };
|
|
47
|
+
//# sourceMappingURL=connector-ws.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector-ws.d.ts","sourceRoot":"","sources":["../../src/broker/connector-ws.ts"],"names":[],"mappings":"AAaA,OAAO,EAAmB,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EACrB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,gBAAgB,GACxB;IAAE,KAAK,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,qBAAqB,CAAA;CAAE,GAC7D;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC5C;IAAE,KAAK,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,GAC5D;IAAE,KAAK,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,GAC5D;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,cAAc,CAAA;CAAE,GACzB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,KAAK,CAAA;CAAE,CAAC;AAEvC,KAAK,WAAW,CAAC,CAAC,SAAS,gBAAgB,CAAC,OAAO,CAAC,IAAI,CACtD,CAAC,EAAE,OAAO,CAAC,gBAAgB,EAAE;IAAE,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC,KACvC,IAAI,CAAC;AAEV,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAsB;gBAE9B,IAAI,EAAE,kBAAkB;IAMpC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAKxB,UAAU,IAAI,IAAI;IASlB,IAAI,CACF,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,WAAW,SAAS,EACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,YAAY,CAAC;IAKxB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIlC,EAAE,CAAC,CAAC,SAAS,gBAAgB,CAAC,OAAO,CAAC,EACpC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,GACvB,IAAI;IAKP,GAAG,CAAC,CAAC,SAAS,gBAAgB,CAAC,OAAO,CAAC,EACrC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,GACvB,IAAI;IAMS,eAAe,CAAC,CAAC,EAAE,gBAAgB,GAAG,IAAI;IAI1D,OAAO,CAAC,cAAc;CA0BvB;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { ConnectorClient, ConnectorError } from "../connector-client.js";
|
|
3
|
+
export class ConnectorWS {
|
|
4
|
+
client;
|
|
5
|
+
bus = new EventEmitter();
|
|
6
|
+
constructor(opts) {
|
|
7
|
+
this.client = new ConnectorClient(opts.url, opts.token);
|
|
8
|
+
this.wireForwarders();
|
|
9
|
+
}
|
|
10
|
+
connect() {
|
|
11
|
+
return this.client.connect();
|
|
12
|
+
}
|
|
13
|
+
disconnect() {
|
|
14
|
+
this.client.disconnect();
|
|
15
|
+
}
|
|
16
|
+
send(to, content, contentType = "text", metadata) {
|
|
17
|
+
return this.client.send(to, content, contentType, metadata);
|
|
18
|
+
}
|
|
19
|
+
ackDelivery(upTo) {
|
|
20
|
+
return this.client.ackDelivery(upTo);
|
|
21
|
+
}
|
|
22
|
+
on(event, listener) {
|
|
23
|
+
this.bus.on(event, listener);
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
off(event, listener) {
|
|
27
|
+
this.bus.off(event, listener);
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
_emitForTesting(e) {
|
|
31
|
+
this.bus.emit(e.event, e);
|
|
32
|
+
}
|
|
33
|
+
wireForwarders() {
|
|
34
|
+
this.client.on("message_received", (payload) => {
|
|
35
|
+
this.bus.emit("message_received", { event: "message_received", payload });
|
|
36
|
+
});
|
|
37
|
+
this.client.on("send_ack", (payload) => {
|
|
38
|
+
this.bus.emit("send_ack", { event: "send_ack", payload });
|
|
39
|
+
});
|
|
40
|
+
this.client.on("delivery_pending", (payload) => {
|
|
41
|
+
this.bus.emit("delivery_pending", { event: "delivery_pending", payload });
|
|
42
|
+
});
|
|
43
|
+
this.client.on("delivery_status", (payload) => {
|
|
44
|
+
this.bus.emit("delivery_status", { event: "delivery_status", payload });
|
|
45
|
+
});
|
|
46
|
+
this.client.on("structured_error", (payload) => {
|
|
47
|
+
this.bus.emit("structured_error", { event: "structured_error", payload });
|
|
48
|
+
});
|
|
49
|
+
this.client.on("connected", () => {
|
|
50
|
+
this.bus.emit("connected", { event: "connected" });
|
|
51
|
+
});
|
|
52
|
+
this.client.on("disconnected", () => {
|
|
53
|
+
this.bus.emit("disconnected", { event: "disconnected" });
|
|
54
|
+
});
|
|
55
|
+
this.client.on("error", (err) => {
|
|
56
|
+
this.bus.emit("error", { event: "error", payload: err });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export { ConnectorError };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class CorrelationRingBuffer {
|
|
2
|
+
private readonly capacity;
|
|
3
|
+
private readonly set;
|
|
4
|
+
private readonly order;
|
|
5
|
+
constructor(capacity?: number);
|
|
6
|
+
add(correlation_id: string): void;
|
|
7
|
+
has(correlation_id: string): boolean;
|
|
8
|
+
size(): number;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=correlation-ring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlation-ring.d.ts","sourceRoot":"","sources":["../../src/broker/correlation-ring.ts"],"names":[],"mappings":"AAaA,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;gBAErB,QAAQ,GAAE,MAAoB;IAU1C,GAAG,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAYjC,GAAG,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAIpC,IAAI,IAAI,MAAM;CAGf"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const DEFAULT_CAP = 1024;
|
|
2
|
+
export class CorrelationRingBuffer {
|
|
3
|
+
capacity;
|
|
4
|
+
set;
|
|
5
|
+
order;
|
|
6
|
+
constructor(capacity = DEFAULT_CAP) {
|
|
7
|
+
if (capacity <= 0) {
|
|
8
|
+
throw new Error(`capacity must be positive: ${capacity}`);
|
|
9
|
+
}
|
|
10
|
+
this.capacity = capacity;
|
|
11
|
+
this.set = new Set();
|
|
12
|
+
this.order = [];
|
|
13
|
+
}
|
|
14
|
+
add(correlation_id) {
|
|
15
|
+
if (this.set.has(correlation_id)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (this.order.length >= this.capacity) {
|
|
19
|
+
const oldest = this.order.shift();
|
|
20
|
+
if (oldest !== undefined)
|
|
21
|
+
this.set.delete(oldest);
|
|
22
|
+
}
|
|
23
|
+
this.set.add(correlation_id);
|
|
24
|
+
this.order.push(correlation_id);
|
|
25
|
+
}
|
|
26
|
+
has(correlation_id) {
|
|
27
|
+
return this.set.has(correlation_id);
|
|
28
|
+
}
|
|
29
|
+
size() {
|
|
30
|
+
return this.set.size;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface DiscoveryRecord {
|
|
2
|
+
version: 1;
|
|
3
|
+
pid: number;
|
|
4
|
+
startedAt: string;
|
|
5
|
+
ipcUrl: string;
|
|
6
|
+
bearerToken: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function mintBearerToken(): string;
|
|
9
|
+
export declare function writeDiscoveryFile(path: string, record: DiscoveryRecord): void;
|
|
10
|
+
export declare function readDiscoveryFile(path: string): DiscoveryRecord | null;
|
|
11
|
+
export declare function deleteDiscoveryFile(path: string): void;
|
|
12
|
+
//# sourceMappingURL=discovery-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-file.d.ts","sourceRoot":"","sources":["../../src/broker/discovery-file.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,CAAC,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAGD,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAMD,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,eAAe,GACtB,IAAI,CA4BN;AAMD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAetE;AAGD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOtD"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
export function mintBearerToken() {
|
|
5
|
+
return randomBytes(32).toString("base64url");
|
|
6
|
+
}
|
|
7
|
+
export function writeDiscoveryFile(path, record) {
|
|
8
|
+
if (record.version !== 1) {
|
|
9
|
+
throw new Error(`unsupported discovery record version: ${record.version}`);
|
|
10
|
+
}
|
|
11
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
12
|
+
const tmp = `${path}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
13
|
+
const payload = JSON.stringify(record, null, 2);
|
|
14
|
+
writeFileSync(tmp, payload, { mode: 0o600, encoding: "utf8" });
|
|
15
|
+
try {
|
|
16
|
+
chmodSync(tmp, 0o600);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
renameSync(tmp, path);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
try {
|
|
25
|
+
unlinkSync(tmp);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
}
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function readDiscoveryFile(path) {
|
|
33
|
+
let raw;
|
|
34
|
+
try {
|
|
35
|
+
raw = readFileSync(path, "utf8");
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (isNodeErr(err, "ENOENT"))
|
|
39
|
+
return null;
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(raw);
|
|
44
|
+
if (!isDiscoveryRecord(parsed))
|
|
45
|
+
return null;
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function deleteDiscoveryFile(path) {
|
|
53
|
+
try {
|
|
54
|
+
unlinkSync(path);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (isNodeErr(err, "ENOENT"))
|
|
58
|
+
return;
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function isDiscoveryRecord(value) {
|
|
63
|
+
if (typeof value !== "object" || value === null)
|
|
64
|
+
return false;
|
|
65
|
+
const v = value;
|
|
66
|
+
return (v.version === 1 &&
|
|
67
|
+
typeof v.pid === "number" &&
|
|
68
|
+
typeof v.startedAt === "string" &&
|
|
69
|
+
typeof v.ipcUrl === "string" &&
|
|
70
|
+
typeof v.bearerToken === "string");
|
|
71
|
+
}
|
|
72
|
+
function isNodeErr(err, code) {
|
|
73
|
+
return (typeof err === "object" &&
|
|
74
|
+
err !== null &&
|
|
75
|
+
"code" in err &&
|
|
76
|
+
err.code === code);
|
|
77
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type WebSocket from "ws";
|
|
2
|
+
import { CorrelationRingBuffer } from "./correlation-ring.js";
|
|
3
|
+
export interface DisplayMetadata {
|
|
4
|
+
session_name?: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
started_at: string;
|
|
7
|
+
workspace?: string;
|
|
8
|
+
tracking_ref?: string;
|
|
9
|
+
task_hint?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface BrokerEndpointEntry {
|
|
12
|
+
endpoint_id: string;
|
|
13
|
+
agent_id: string;
|
|
14
|
+
plugin_pid: number;
|
|
15
|
+
ipc_ws: WebSocket;
|
|
16
|
+
state: "active";
|
|
17
|
+
state_since: Date;
|
|
18
|
+
display_metadata: DisplayMetadata;
|
|
19
|
+
correlation_ring: CorrelationRingBuffer;
|
|
20
|
+
}
|
|
21
|
+
export interface RegisterArgs {
|
|
22
|
+
endpoint_id: string;
|
|
23
|
+
agent_id: string;
|
|
24
|
+
plugin_pid: number;
|
|
25
|
+
ipc_ws: WebSocket;
|
|
26
|
+
display_metadata: DisplayMetadata;
|
|
27
|
+
}
|
|
28
|
+
export declare class EndpointRegistry {
|
|
29
|
+
private readonly byId;
|
|
30
|
+
register(args: RegisterArgs): BrokerEndpointEntry;
|
|
31
|
+
get(endpoint_id: string): BrokerEndpointEntry | undefined;
|
|
32
|
+
unregister(endpoint_id: string): BrokerEndpointEntry | undefined;
|
|
33
|
+
preconditionForReattach(endpoint_id: string, plugin_pid: number): BrokerEndpointEntry | undefined;
|
|
34
|
+
list(): BrokerEndpointEntry[];
|
|
35
|
+
size(): number;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=endpoint-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-registry.d.ts","sourceRoot":"","sources":["../../src/broker/endpoint-registry.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,WAAW,EAAE,IAAI,CAAC;IAClB,gBAAgB,EAAE,eAAe,CAAC;IAClC,gBAAgB,EAAE,qBAAqB,CAAC;CACzC;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,gBAAgB,EAAE,eAAe,CAAC;CACnC;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0C;IAG/D,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,mBAAmB;IAkBjD,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAKzD,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAchE,uBAAuB,CACrB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GACjB,mBAAmB,GAAG,SAAS;IAQlC,IAAI,IAAI,mBAAmB,EAAE;IAK7B,IAAI,IAAI,MAAM;CAGf"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CorrelationRingBuffer } from "./correlation-ring.js";
|
|
2
|
+
export class EndpointRegistry {
|
|
3
|
+
byId = new Map();
|
|
4
|
+
register(args) {
|
|
5
|
+
if (this.byId.has(args.endpoint_id)) {
|
|
6
|
+
throw new Error(`endpoint_id already registered: ${args.endpoint_id}`);
|
|
7
|
+
}
|
|
8
|
+
const entry = {
|
|
9
|
+
endpoint_id: args.endpoint_id,
|
|
10
|
+
agent_id: args.agent_id,
|
|
11
|
+
plugin_pid: args.plugin_pid,
|
|
12
|
+
ipc_ws: args.ipc_ws,
|
|
13
|
+
state: "active",
|
|
14
|
+
state_since: new Date(),
|
|
15
|
+
display_metadata: args.display_metadata,
|
|
16
|
+
correlation_ring: new CorrelationRingBuffer(),
|
|
17
|
+
};
|
|
18
|
+
this.byId.set(args.endpoint_id, entry);
|
|
19
|
+
return entry;
|
|
20
|
+
}
|
|
21
|
+
get(endpoint_id) {
|
|
22
|
+
return this.byId.get(endpoint_id);
|
|
23
|
+
}
|
|
24
|
+
unregister(endpoint_id) {
|
|
25
|
+
const entry = this.byId.get(endpoint_id);
|
|
26
|
+
if (entry)
|
|
27
|
+
this.byId.delete(endpoint_id);
|
|
28
|
+
return entry;
|
|
29
|
+
}
|
|
30
|
+
preconditionForReattach(endpoint_id, plugin_pid) {
|
|
31
|
+
const entry = this.byId.get(endpoint_id);
|
|
32
|
+
if (!entry)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (entry.plugin_pid !== plugin_pid)
|
|
35
|
+
return undefined;
|
|
36
|
+
return entry;
|
|
37
|
+
}
|
|
38
|
+
list() {
|
|
39
|
+
return Array.from(this.byId.values());
|
|
40
|
+
}
|
|
41
|
+
size() {
|
|
42
|
+
return this.byId.size;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/broker/entry.ts"],"names":[],"mappings":"AAmGA,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAgD1C"}
|