@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.
Files changed (48) hide show
  1. package/dist/broker/broker-daemon.d.ts +24 -0
  2. package/dist/broker/broker-daemon.d.ts.map +1 -0
  3. package/dist/broker/broker-daemon.js +229 -0
  4. package/dist/broker/connector-ws.d.ts +47 -0
  5. package/dist/broker/connector-ws.d.ts.map +1 -0
  6. package/dist/broker/connector-ws.js +60 -0
  7. package/dist/broker/correlation-ring.d.ts +10 -0
  8. package/dist/broker/correlation-ring.d.ts.map +1 -0
  9. package/dist/broker/correlation-ring.js +32 -0
  10. package/dist/broker/discovery-file.d.ts +12 -0
  11. package/dist/broker/discovery-file.d.ts.map +1 -0
  12. package/dist/broker/discovery-file.js +77 -0
  13. package/dist/broker/endpoint-registry.d.ts +37 -0
  14. package/dist/broker/endpoint-registry.d.ts.map +1 -0
  15. package/dist/broker/endpoint-registry.js +44 -0
  16. package/dist/broker/entry.d.ts +2 -0
  17. package/dist/broker/entry.d.ts.map +1 -0
  18. package/dist/broker/entry.js +85 -0
  19. package/dist/broker/ipc-server.d.ts +65 -0
  20. package/dist/broker/ipc-server.d.ts.map +1 -0
  21. package/dist/broker/ipc-server.js +212 -0
  22. package/dist/broker/logger.d.ts +10 -0
  23. package/dist/broker/logger.d.ts.map +1 -0
  24. package/dist/broker/logger.js +34 -0
  25. package/dist/broker/paths.d.ts +10 -0
  26. package/dist/broker/paths.d.ts.map +1 -0
  27. package/dist/broker/paths.js +28 -0
  28. package/dist/broker/routing-table.d.ts +22 -0
  29. package/dist/broker/routing-table.d.ts.map +1 -0
  30. package/dist/broker/routing-table.js +35 -0
  31. package/dist/broker/runtime-endpoint-port.d.ts +7 -0
  32. package/dist/broker/runtime-endpoint-port.d.ts.map +1 -0
  33. package/dist/broker/runtime-endpoint-port.js +1 -0
  34. package/dist/broker/version-handshake.d.ts +30 -0
  35. package/dist/broker/version-handshake.d.ts.map +1 -0
  36. package/dist/broker/version-handshake.js +47 -0
  37. package/dist/broker-client/broker-client.d.ts +64 -0
  38. package/dist/broker-client/broker-client.d.ts.map +1 -0
  39. package/dist/broker-client/broker-client.js +164 -0
  40. package/dist/broker-client/lazy-spawn.d.ts +18 -0
  41. package/dist/broker-client/lazy-spawn.d.ts.map +1 -0
  42. package/dist/broker-client/lazy-spawn.js +61 -0
  43. package/dist/runtime-endpoint-client.d.ts +2 -2
  44. package/dist/runtime-endpoint-client.d.ts.map +1 -1
  45. package/dist/runtime-endpoint-client.js +4 -2
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.js +1 -1
  48. package/package.json +11 -2
@@ -0,0 +1,85 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import { readConfig } from "../config-fs.js";
3
+ import { DEFAULT_API_HOST } from "../platform-client.js";
4
+ import { heartbeatRuntimeEndpoint, registerRuntimeEndpoint, unregisterRuntimeEndpoint, } from "../runtime-endpoint-client.js";
5
+ import { startBrokerDaemon } from "./broker-daemon.js";
6
+ import { ConnectorWS } from "./connector-ws.js";
7
+ import { createBrokerLogger } from "./logger.js";
8
+ import { resolveBrokerPaths } from "./paths.js";
9
+ const DEFAULT_ACCOUNT_ID = "default";
10
+ async function resolveCredentials(accountId, envApiHost) {
11
+ const config = await readConfig();
12
+ const channels = config.channels;
13
+ const network = channels?.["agent-network"];
14
+ const apiHost = envApiHost ??
15
+ (typeof network?.apiHost === "string" ? network.apiHost : DEFAULT_API_HOST);
16
+ const accounts = network?.accounts;
17
+ const account = accounts?.[accountId];
18
+ const connectorUrl = typeof account?.connectorUrl === "string" ? account.connectorUrl : null;
19
+ const token = typeof account?.token === "string" ? account.token : null;
20
+ if (!connectorUrl || !token) {
21
+ throw new Error(`agent-network credentials missing for account "${accountId}"; ` +
22
+ "run setup before lazy-spawning the broker");
23
+ }
24
+ return { accountId, connectorUrl, token, apiHost };
25
+ }
26
+ function buildApiPort(apiHost, runtimeKey) {
27
+ return {
28
+ async register(params) {
29
+ return registerRuntimeEndpoint({ apiHost }, runtimeKey, params);
30
+ },
31
+ async heartbeat(endpointId) {
32
+ await heartbeatRuntimeEndpoint({ apiHost }, runtimeKey, endpointId);
33
+ },
34
+ async unregister(endpointId, asNodeId) {
35
+ await unregisterRuntimeEndpoint({ apiHost }, runtimeKey, endpointId, asNodeId);
36
+ },
37
+ };
38
+ }
39
+ export async function main() {
40
+ const accountId = process.env.MASONS_BROKER_ACCOUNT_ID ?? DEFAULT_ACCOUNT_ID;
41
+ const envApiHost = process.env.MASONS_BROKER_API_HOST;
42
+ const userDataOverride = process.env.MASONS_BROKER_USER_DATA;
43
+ const creds = await resolveCredentials(accountId, envApiHost);
44
+ const paths = resolveBrokerPaths(creds.accountId, creds.token, {
45
+ userDataDir: userDataOverride,
46
+ });
47
+ const logger = createBrokerLogger(paths.logDir);
48
+ logger.info("broker_entry_starting", {
49
+ accountId: creds.accountId,
50
+ apiHost: creds.apiHost,
51
+ pid: process.pid,
52
+ });
53
+ const asNodeId = creds.accountId;
54
+ const connector = new ConnectorWS({
55
+ url: creds.connectorUrl,
56
+ token: creds.token,
57
+ });
58
+ const apiPort = buildApiPort(creds.apiHost, creds.token);
59
+ const broker = await startBrokerDaemon({
60
+ paths,
61
+ asNodeId,
62
+ connector,
63
+ apiPort,
64
+ logger,
65
+ });
66
+ let shuttingDown = false;
67
+ const handleSignal = (sig) => async () => {
68
+ if (shuttingDown)
69
+ return;
70
+ shuttingDown = true;
71
+ logger.info("broker_signal", { sig });
72
+ await broker.shutdown(`signal:${sig}`);
73
+ process.exit(0);
74
+ };
75
+ process.on("SIGTERM", handleSignal("SIGTERM"));
76
+ process.on("SIGINT", handleSignal("SIGINT"));
77
+ logger.info("broker_ready", { ipcUrl: broker.ipcUrl });
78
+ }
79
+ const argvUrl = process.argv[1] ? pathToFileURL(process.argv[1]).href : "";
80
+ if (import.meta.url === argvUrl) {
81
+ main().catch((err) => {
82
+ console.error("broker_entry_fatal", err);
83
+ process.exit(1);
84
+ });
85
+ }
@@ -0,0 +1,65 @@
1
+ import { type RawData, WebSocket } from "ws";
2
+ import type { BrokerLogger } from "./logger.js";
3
+ export interface IPCServerHandlers {
4
+ registerEndpoint(body: RegisterEndpointBody, ipcWs: WebSocket): Promise<RegisterEndpointResponse>;
5
+ heartbeatEndpoint(endpoint_id: string): Promise<void>;
6
+ unregisterEndpoint(endpoint_id: string): Promise<void>;
7
+ send(body: SendBody): Promise<SendResponse>;
8
+ reattachEndpoint(endpoint_id: string, plugin_pid: number, ipcWs: WebSocket): Promise<boolean>;
9
+ }
10
+ export interface RegisterEndpointBody {
11
+ agent_id: string;
12
+ plugin_pid: number;
13
+ kind: string;
14
+ started_at?: string;
15
+ workspace?: string;
16
+ tracking_ref?: string;
17
+ session_name?: string;
18
+ task_hint?: string;
19
+ }
20
+ export interface RegisterEndpointResponse {
21
+ endpoint_id: string;
22
+ }
23
+ export interface SendBody {
24
+ endpoint_id: string;
25
+ to: string;
26
+ content: string;
27
+ contentType?: string;
28
+ metadata?: Record<string, unknown>;
29
+ require_live?: boolean;
30
+ }
31
+ export interface SendResponse {
32
+ messageId: string;
33
+ status: string;
34
+ }
35
+ export declare class BrokerHttpError extends Error {
36
+ readonly status: number;
37
+ readonly code: string;
38
+ constructor(status: number, code: string, message: string);
39
+ }
40
+ export interface IPCServerOptions {
41
+ bearerToken: string;
42
+ handlers: IPCServerHandlers;
43
+ logger: BrokerLogger;
44
+ onChannelOpened?: (plugin_pid: number, ws: WebSocket) => void;
45
+ onChannelClosed?: (plugin_pid: number, ws: WebSocket) => void;
46
+ }
47
+ export interface RunningIPCServer {
48
+ ipcUrl: string;
49
+ port: number;
50
+ close(): Promise<void>;
51
+ }
52
+ export declare function startIPCServer(opts: IPCServerOptions): Promise<RunningIPCServer>;
53
+ export declare function pushToPlugin(ws: WebSocket, event: PushEvent): void;
54
+ export type PushEvent = {
55
+ event: "message_received";
56
+ from: string;
57
+ content: string;
58
+ contentType: string;
59
+ metadata?: Record<string, unknown>;
60
+ } | {
61
+ event: "ping";
62
+ ts: string;
63
+ };
64
+ export declare function parsePluginFrame(data: RawData): unknown;
65
+ //# sourceMappingURL=ipc-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc-server.d.ts","sourceRoot":"","sources":["../../src/broker/ipc-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,212 @@
1
+ import { createServer } from "node:http";
2
+ import { WebSocket, WebSocketServer } from "ws";
3
+ import { PLUGIN_VERSION } from "../version.js";
4
+ import { handleInitialize, IPC_PROTOCOL_VERSION, SERVER_CAPABILITIES, } from "./version-handshake.js";
5
+ export class BrokerHttpError extends Error {
6
+ status;
7
+ code;
8
+ constructor(status, code, message) {
9
+ super(message);
10
+ this.name = "BrokerHttpError";
11
+ this.status = status;
12
+ this.code = code;
13
+ }
14
+ }
15
+ export async function startIPCServer(opts) {
16
+ const { bearerToken, handlers, logger, onChannelOpened, onChannelClosed } = opts;
17
+ const http = createServer((req, res) => {
18
+ void routeHttp(req, res, bearerToken, handlers, logger);
19
+ });
20
+ const wss = new WebSocketServer({ noServer: true });
21
+ http.on("upgrade", (req, socket, head) => {
22
+ if (req.url !== "/v1/stream") {
23
+ socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
24
+ socket.destroy();
25
+ return;
26
+ }
27
+ if (!authorizedRequest(req, bearerToken)) {
28
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
29
+ socket.destroy();
30
+ return;
31
+ }
32
+ const pid = readPluginPid(req);
33
+ if (pid === null) {
34
+ socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
35
+ socket.destroy();
36
+ return;
37
+ }
38
+ wss.handleUpgrade(req, socket, head, (ws) => {
39
+ onChannelOpened?.(pid, ws);
40
+ ws.once("close", () => onChannelClosed?.(pid, ws));
41
+ wss.emit("connection", ws, req);
42
+ });
43
+ });
44
+ await new Promise((resolve, reject) => {
45
+ http.once("error", reject);
46
+ http.listen(0, "127.0.0.1", () => {
47
+ http.off("error", reject);
48
+ resolve();
49
+ });
50
+ });
51
+ const addr = http.address();
52
+ const ipcUrl = `http://127.0.0.1:${addr.port}`;
53
+ logger.info("ipc_server_listening", { ipcUrl });
54
+ return {
55
+ ipcUrl,
56
+ port: addr.port,
57
+ async close() {
58
+ await closeServer(http, wss);
59
+ },
60
+ };
61
+ }
62
+ async function routeHttp(req, res, bearerToken, handlers, logger) {
63
+ const url = req.url ?? "";
64
+ const method = req.method ?? "GET";
65
+ if (method === "GET" && url === "/health") {
66
+ sendJson(res, 200, {
67
+ ok: true,
68
+ version: PLUGIN_VERSION,
69
+ protocol_version: IPC_PROTOCOL_VERSION,
70
+ capabilities: SERVER_CAPABILITIES,
71
+ });
72
+ return;
73
+ }
74
+ if (!authorizedRequest(req, bearerToken)) {
75
+ sendJson(res, 401, { error: "unauthorized" });
76
+ return;
77
+ }
78
+ try {
79
+ if (method === "POST" && url === "/v1/initialize") {
80
+ const body = await readJson(req);
81
+ const result = handleInitialize(body);
82
+ sendJson(res, result.status, result.body);
83
+ return;
84
+ }
85
+ if (method === "POST" && url === "/v1/endpoint/register") {
86
+ const body = await readJson(req);
87
+ const out = await handlers.registerEndpoint(body, null);
88
+ sendJson(res, 200, out);
89
+ return;
90
+ }
91
+ const heartbeatMatch = /^\/v1\/endpoint\/([^/]+)\/heartbeat$/.exec(url);
92
+ if (method === "POST" && heartbeatMatch && heartbeatMatch[1]) {
93
+ await handlers.heartbeatEndpoint(decodeURIComponent(heartbeatMatch[1]));
94
+ sendJson(res, 200, { ok: true });
95
+ return;
96
+ }
97
+ const unregisterMatch = /^\/v1\/endpoint\/([^/]+)$/.exec(url);
98
+ if (method === "DELETE" && unregisterMatch && unregisterMatch[1]) {
99
+ await handlers.unregisterEndpoint(decodeURIComponent(unregisterMatch[1]));
100
+ sendJson(res, 200, { ok: true });
101
+ return;
102
+ }
103
+ if (method === "POST" && url === "/v1/send") {
104
+ const body = await readJson(req);
105
+ const out = await handlers.send(body);
106
+ sendJson(res, 200, out);
107
+ return;
108
+ }
109
+ sendJson(res, 404, { error: "not_found", path: url });
110
+ }
111
+ catch (err) {
112
+ if (err instanceof BrokerHttpError) {
113
+ logger.info("ipc_route_rejected", {
114
+ url,
115
+ method,
116
+ status: err.status,
117
+ code: err.code,
118
+ message: err.message,
119
+ });
120
+ sendJson(res, err.status, { error: err.code, message: err.message });
121
+ return;
122
+ }
123
+ logger.error("ipc_route_error", {
124
+ url,
125
+ method,
126
+ err: err instanceof Error ? err.message : String(err),
127
+ });
128
+ sendJson(res, 500, {
129
+ error: "internal_error",
130
+ message: err instanceof Error ? err.message : "unknown",
131
+ });
132
+ }
133
+ }
134
+ function authorizedRequest(req, bearerToken) {
135
+ const header = req.headers.authorization;
136
+ if (typeof header !== "string")
137
+ return false;
138
+ const trimmed = header.startsWith("Bearer ")
139
+ ? header.slice("Bearer ".length)
140
+ : header;
141
+ return constantTimeEquals(trimmed, bearerToken);
142
+ }
143
+ function readPluginPid(req) {
144
+ const raw = req.headers["x-plugin-pid"];
145
+ const str = Array.isArray(raw) ? raw[0] : raw;
146
+ if (!str)
147
+ return null;
148
+ const n = Number.parseInt(str, 10);
149
+ return Number.isFinite(n) && n > 0 ? n : null;
150
+ }
151
+ function constantTimeEquals(a, b) {
152
+ if (a.length !== b.length)
153
+ return false;
154
+ let mismatch = 0;
155
+ for (let i = 0; i < a.length; i++) {
156
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
157
+ }
158
+ return mismatch === 0;
159
+ }
160
+ async function readJson(req) {
161
+ return new Promise((resolve, reject) => {
162
+ const chunks = [];
163
+ let total = 0;
164
+ const MAX = 1 * 1024 * 1024;
165
+ req.on("data", (chunk) => {
166
+ const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
167
+ total += buf.length;
168
+ if (total > MAX) {
169
+ reject(new Error("request body too large"));
170
+ req.destroy();
171
+ return;
172
+ }
173
+ chunks.push(buf);
174
+ });
175
+ req.on("end", () => {
176
+ try {
177
+ const raw = Buffer.concat(chunks).toString("utf8");
178
+ resolve(raw.length === 0 ? {} : JSON.parse(raw));
179
+ }
180
+ catch (err) {
181
+ reject(err instanceof Error ? err : new Error(String(err)));
182
+ }
183
+ });
184
+ req.on("error", reject);
185
+ });
186
+ }
187
+ function sendJson(res, status, body) {
188
+ res.statusCode = status;
189
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
190
+ res.end(JSON.stringify(body));
191
+ }
192
+ async function closeServer(http, wss) {
193
+ await new Promise((resolve) => {
194
+ wss.close(() => resolve());
195
+ });
196
+ await new Promise((resolve, reject) => {
197
+ http.close((err) => (err ? reject(err) : resolve()));
198
+ });
199
+ }
200
+ export function pushToPlugin(ws, event) {
201
+ if (ws.readyState !== WebSocket.OPEN)
202
+ return;
203
+ ws.send(JSON.stringify(event));
204
+ }
205
+ export function parsePluginFrame(data) {
206
+ try {
207
+ return JSON.parse(data.toString());
208
+ }
209
+ catch {
210
+ return null;
211
+ }
212
+ }
@@ -0,0 +1,10 @@
1
+ export type LogLevel = "debug" | "info" | "warn" | "error";
2
+ export interface BrokerLogger {
3
+ debug(msg: string, fields?: Record<string, unknown>): void;
4
+ info(msg: string, fields?: Record<string, unknown>): void;
5
+ warn(msg: string, fields?: Record<string, unknown>): void;
6
+ error(msg: string, fields?: Record<string, unknown>): void;
7
+ }
8
+ export declare function createBrokerLogger(logDir: string): BrokerLogger;
9
+ export declare function createNullLogger(): BrokerLogger;
10
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/broker/logger.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3D,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1D,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1D,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CA6B/D;AAGD,wBAAgB,gBAAgB,IAAI,YAAY,CAO/C"}
@@ -0,0 +1,34 @@
1
+ import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ export function createBrokerLogger(logDir) {
4
+ mkdirSync(logDir, { recursive: true, mode: 0o700 });
5
+ const path = join(logDir, `broker-${process.pid}.log`);
6
+ const write = (level, msg, fields) => {
7
+ const line = JSON.stringify({
8
+ ts: new Date().toISOString(),
9
+ level,
10
+ pid: process.pid,
11
+ msg,
12
+ ...fields,
13
+ });
14
+ try {
15
+ appendFileSync(path, `${line}\n`);
16
+ }
17
+ catch {
18
+ }
19
+ };
20
+ return {
21
+ debug: (msg, fields) => write("debug", msg, fields),
22
+ info: (msg, fields) => write("info", msg, fields),
23
+ warn: (msg, fields) => write("warn", msg, fields),
24
+ error: (msg, fields) => write("error", msg, fields),
25
+ };
26
+ }
27
+ export function createNullLogger() {
28
+ return {
29
+ debug: () => { },
30
+ info: () => { },
31
+ warn: () => { },
32
+ error: () => { },
33
+ };
34
+ }
@@ -0,0 +1,10 @@
1
+ export interface BrokerPathOptions {
2
+ userDataDir?: string;
3
+ }
4
+ export interface BrokerPaths {
5
+ discoveryFile: string;
6
+ discoveryDir: string;
7
+ logDir: string;
8
+ }
9
+ export declare function resolveBrokerPaths(principal: string, runtimeToken: string, opts?: BrokerPathOptions): BrokerPaths;
10
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/broker/paths.ts"],"names":[],"mappings":"AAgCA,MAAM,WAAW,iBAAiB;IAEhC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAE1B,aAAa,EAAE,MAAM,CAAC;IAEtB,YAAY,EAAE,MAAM,CAAC;IAErB,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,IAAI,GAAE,iBAAsB,GAC3B,WAAW,CA8Bb"}
@@ -0,0 +1,28 @@
1
+ import { createHash } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import envPaths from "env-paths";
4
+ const AGENT_IDENTITY_HEX_LEN = 16;
5
+ const ENV_PATHS_NAME = "masons-runtime-broker";
6
+ export function resolveBrokerPaths(principal, runtimeToken, opts = {}) {
7
+ if (!principal) {
8
+ throw new Error("principal must be a non-empty string");
9
+ }
10
+ if (!runtimeToken) {
11
+ throw new Error("runtimeToken must be a non-empty string");
12
+ }
13
+ const userData = opts.userDataDir ?? envPaths(ENV_PATHS_NAME, { suffix: "" }).data;
14
+ const agentIdentity = createHash("sha256")
15
+ .update(runtimeToken, "utf8")
16
+ .digest("hex")
17
+ .slice(0, AGENT_IDENTITY_HEX_LEN);
18
+ const discoveryDir = join(userData, "runtime-broker", sanitizeSegment(principal), agentIdentity);
19
+ const logDir = join(userData, "runtime-broker", "log");
20
+ return {
21
+ discoveryFile: join(discoveryDir, "discovery.json"),
22
+ discoveryDir,
23
+ logDir,
24
+ };
25
+ }
26
+ function sanitizeSegment(segment) {
27
+ return segment.replace(/[^a-zA-Z0-9._-]/g, "_");
28
+ }
@@ -0,0 +1,22 @@
1
+ import type { BrokerEndpointEntry, EndpointRegistry } from "./endpoint-registry.js";
2
+ export interface InboundMessageMeta {
3
+ target_endpoint_id?: string;
4
+ correlation_id?: string;
5
+ }
6
+ export type RouteResult = {
7
+ kind: "endpoint";
8
+ entry: BrokerEndpointEntry;
9
+ via: "target_endpoint_id" | "correlation_id";
10
+ } | {
11
+ kind: "no_match";
12
+ };
13
+ export interface RoutingTableOptions {
14
+ onCollision?: (correlation_id: string, count: number) => void;
15
+ }
16
+ export declare class RoutingTable {
17
+ private readonly registry;
18
+ private readonly opts;
19
+ constructor(registry: EndpointRegistry, opts?: RoutingTableOptions);
20
+ route(meta: InboundMessageMeta): RouteResult;
21
+ }
22
+ //# sourceMappingURL=routing-table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-table.d.ts","sourceRoot":"","sources":["../../src/broker/routing-table.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,WAAW,GACnB;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,GAAG,EAAE,oBAAoB,GAAG,gBAAgB,CAAC;CAC9C,GACD;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,MAAM,WAAW,mBAAmB;IAElC,WAAW,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/D;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,GAAE,mBAAwB;IASjD,KAAK,CAAC,IAAI,EAAE,kBAAkB,GAAG,WAAW;CA6B7C"}
@@ -0,0 +1,35 @@
1
+ export class RoutingTable {
2
+ registry;
3
+ opts;
4
+ constructor(registry, opts = {}) {
5
+ this.registry = registry;
6
+ this.opts = opts;
7
+ }
8
+ route(meta) {
9
+ if (meta.target_endpoint_id) {
10
+ const entry = this.registry.get(meta.target_endpoint_id);
11
+ if (entry && entry.state === "active") {
12
+ return { kind: "endpoint", entry, via: "target_endpoint_id" };
13
+ }
14
+ }
15
+ if (meta.correlation_id) {
16
+ let matched;
17
+ let collisionCount = 0;
18
+ for (const entry of this.registry.list()) {
19
+ if (entry.correlation_ring.has(meta.correlation_id)) {
20
+ collisionCount++;
21
+ if (!matched) {
22
+ matched = entry;
23
+ }
24
+ }
25
+ }
26
+ if (collisionCount > 1 && this.opts.onCollision) {
27
+ this.opts.onCollision(meta.correlation_id, collisionCount);
28
+ }
29
+ if (matched) {
30
+ return { kind: "endpoint", entry: matched, via: "correlation_id" };
31
+ }
32
+ }
33
+ return { kind: "no_match" };
34
+ }
35
+ }
@@ -0,0 +1,7 @@
1
+ import type { RegisterRuntimeEndpointParams, RegisterRuntimeEndpointResponse } from "../runtime-endpoint-client.js";
2
+ export interface RuntimeEndpointPort {
3
+ register(params: RegisterRuntimeEndpointParams): Promise<RegisterRuntimeEndpointResponse>;
4
+ heartbeat(endpointId: string): Promise<void>;
5
+ unregister(endpointId: string, asNodeId?: string): Promise<void>;
6
+ }
7
+ //# sourceMappingURL=runtime-endpoint-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-endpoint-port.d.ts","sourceRoot":"","sources":["../../src/broker/runtime-endpoint-port.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,6BAA6B,EAC7B,+BAA+B,EAChC,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CACN,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,+BAA+B,CAAC,CAAC;IAC5C,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAO7C,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ export declare const IPC_PROTOCOL_VERSION = "1.0";
2
+ export interface IpcCapabilities {
3
+ endpoint_metadata_v1?: boolean;
4
+ remote_spawn_v1?: boolean;
5
+ undispatched_inbox_v1?: boolean;
6
+ }
7
+ export declare const SERVER_CAPABILITIES: Readonly<IpcCapabilities>;
8
+ export interface InitializeRequestBody {
9
+ client_protocol_version: string;
10
+ client_kind: string;
11
+ client_version: string;
12
+ capabilities?: IpcCapabilities;
13
+ }
14
+ export interface InitializeResponseBody {
15
+ server_protocol_version: string;
16
+ server_capabilities: IpcCapabilities;
17
+ session_id: string;
18
+ }
19
+ export interface InitializeErrorBody {
20
+ error: "protocol_version_mismatch" | "bad_request";
21
+ message: string;
22
+ }
23
+ export declare function handleInitialize(req: InitializeRequestBody): {
24
+ status: 200;
25
+ body: InitializeResponseBody;
26
+ } | {
27
+ status: 400;
28
+ body: InitializeErrorBody;
29
+ };
30
+ //# sourceMappingURL=version-handshake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-handshake.d.ts","sourceRoot":"","sources":["../../src/broker/version-handshake.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAM1C,MAAM,WAAW,eAAe;IAE9B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B,eAAe,CAAC,EAAE,OAAO,CAAC;IAG1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAGD,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,eAAe,CAIxD,CAAC;AAEH,MAAM,WAAW,qBAAqB;IAEpC,uBAAuB,EAAE,MAAM,CAAC;IAEhC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,eAAe,CAAC;CAChC;AAED,MAAM,WAAW,sBAAsB;IACrC,uBAAuB,EAAE,MAAM,CAAC;IAChC,mBAAmB,EAAE,eAAe,CAAC;IAErC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,2BAA2B,GAAG,aAAa,CAAC;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,qBAAqB,GAExB;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,sBAAsB,CAAA;CAAE,GAC7C;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,mBAAmB,CAAA;CAAE,CA0C7C"}
@@ -0,0 +1,47 @@
1
+ import { randomUUID } from "node:crypto";
2
+ export const IPC_PROTOCOL_VERSION = "1.0";
3
+ export const SERVER_CAPABILITIES = Object.freeze({
4
+ endpoint_metadata_v1: true,
5
+ remote_spawn_v1: false,
6
+ undispatched_inbox_v1: false,
7
+ });
8
+ export function handleInitialize(req) {
9
+ if (typeof req.client_protocol_version !== "string") {
10
+ return {
11
+ status: 400,
12
+ body: {
13
+ error: "bad_request",
14
+ message: "client_protocol_version is required and must be a string",
15
+ },
16
+ };
17
+ }
18
+ if (typeof req.client_kind !== "string" || !req.client_kind) {
19
+ return {
20
+ status: 400,
21
+ body: {
22
+ error: "bad_request",
23
+ message: "client_kind is required and must be a non-empty string",
24
+ },
25
+ };
26
+ }
27
+ const clientMajor = req.client_protocol_version.split(".")[0];
28
+ const serverMajor = IPC_PROTOCOL_VERSION.split(".")[0];
29
+ if (clientMajor !== serverMajor) {
30
+ return {
31
+ status: 400,
32
+ body: {
33
+ error: "protocol_version_mismatch",
34
+ message: `broker IPC protocol major ${serverMajor} does not match client ` +
35
+ `protocol major ${clientMajor}`,
36
+ },
37
+ };
38
+ }
39
+ return {
40
+ status: 200,
41
+ body: {
42
+ server_protocol_version: IPC_PROTOCOL_VERSION,
43
+ server_capabilities: { ...SERVER_CAPABILITIES },
44
+ session_id: randomUUID(),
45
+ },
46
+ };
47
+ }