@masons/agent-network 0.5.31 → 0.5.321
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 +23 -0
- package/dist/broker/broker-daemon.d.ts.map +1 -1
- package/dist/broker/broker-daemon.js +291 -3
- package/dist/broker/claude-code-spawn-driver.d.ts +14 -0
- package/dist/broker/claude-code-spawn-driver.d.ts.map +1 -0
- package/dist/broker/claude-code-spawn-driver.js +39 -0
- package/dist/broker/codex-spawn-driver-stub.d.ts +7 -0
- package/dist/broker/codex-spawn-driver-stub.d.ts.map +1 -0
- package/dist/broker/codex-spawn-driver-stub.js +13 -0
- package/dist/broker/control-event-dispatcher.d.ts +1 -0
- package/dist/broker/control-event-dispatcher.d.ts.map +1 -1
- package/dist/broker/control-event-types.d.ts +11 -13
- package/dist/broker/control-event-types.d.ts.map +1 -1
- package/dist/broker/entry.d.ts.map +1 -1
- package/dist/broker/entry.js +41 -0
- package/dist/broker/ipc-server.d.ts +7 -0
- package/dist/broker/ipc-server.d.ts.map +1 -1
- package/dist/broker/ipc-server.js +20 -0
- package/dist/broker/network-presence.d.ts +31 -0
- package/dist/broker/network-presence.d.ts.map +1 -0
- package/dist/broker/network-presence.js +109 -0
- package/dist/broker/received-message-correlation-cache.d.ts +23 -0
- package/dist/broker/received-message-correlation-cache.d.ts.map +1 -0
- package/dist/broker/received-message-correlation-cache.js +114 -0
- package/dist/broker/services-event-client.d.ts +21 -0
- package/dist/broker/services-event-client.d.ts.map +1 -0
- package/dist/broker/services-event-client.js +221 -0
- package/dist/broker/spawn-correlation.d.ts +28 -0
- package/dist/broker/spawn-correlation.d.ts.map +1 -0
- package/dist/broker/spawn-correlation.js +77 -0
- package/dist/broker/spawn-driver.d.ts +27 -0
- package/dist/broker/spawn-driver.d.ts.map +1 -0
- package/dist/broker/spawn-driver.js +15 -0
- package/dist/broker/task-hint-handler.d.ts +21 -0
- package/dist/broker/task-hint-handler.d.ts.map +1 -0
- package/dist/broker/task-hint-handler.js +33 -0
- package/dist/broker/transition-state-retry-queue.d.ts +20 -0
- package/dist/broker/transition-state-retry-queue.d.ts.map +1 -0
- package/dist/broker/transition-state-retry-queue.js +48 -0
- package/dist/broker-client/broker-client.d.ts +1 -0
- package/dist/broker-client/broker-client.d.ts.map +1 -1
- package/dist/broker-client/broker-client.js +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
import { spawn as spawnChild } from "node:child_process";
|
|
1
2
|
import type { IncomingMessage } from "node:http";
|
|
2
3
|
import type { ClosedEndpointLookup } from "./closed-endpoint-lookup.js";
|
|
3
4
|
import type { ConnectorWS } from "./connector-ws.js";
|
|
4
5
|
import { type ControlEventDispatcher } from "./control-event-dispatcher.js";
|
|
6
|
+
import type { ControlAck } from "./control-event-types.js";
|
|
5
7
|
import { type EndpointState } from "./endpoint-state-machine.js";
|
|
6
8
|
import type { BrokerLogger } from "./logger.js";
|
|
9
|
+
import { type NetworkPresence } from "./network-presence.js";
|
|
7
10
|
import type { BrokerPaths } from "./paths.js";
|
|
8
11
|
import { type BufferedMessage } from "./reconnecting-buffer.js";
|
|
9
12
|
import type { RuntimeEndpointPort } from "./runtime-endpoint-port.js";
|
|
13
|
+
import type { ServicesEventClient } from "./services-event-client.js";
|
|
14
|
+
import type { SpawnDriverRegistry } from "./spawn-driver.js";
|
|
10
15
|
import { type UndispatchedInbox } from "./undispatched-inbox.js";
|
|
11
16
|
export interface BrokerDaemonOptions {
|
|
12
17
|
paths: BrokerPaths;
|
|
@@ -18,6 +23,20 @@ export interface BrokerDaemonOptions {
|
|
|
18
23
|
inboxCapacity?: number;
|
|
19
24
|
graceMs?: number;
|
|
20
25
|
livenessProbe?: (plugin_pid: number) => boolean;
|
|
26
|
+
spawnDriverRegistry?: SpawnDriverRegistry;
|
|
27
|
+
spawnFn?: typeof spawnChild;
|
|
28
|
+
spawnRateLimitMaxInWindow?: number;
|
|
29
|
+
spawnRateLimitWindowMs?: number;
|
|
30
|
+
spawnCorrelationTimeoutMs?: number;
|
|
31
|
+
presenceGraceMs?: number;
|
|
32
|
+
retryQueueCapacity?: number;
|
|
33
|
+
servicesEventClientFactory?: (deps: {
|
|
34
|
+
dispatcher: ControlEventDispatcher;
|
|
35
|
+
logger: BrokerLogger;
|
|
36
|
+
}) => ServicesEventClient;
|
|
37
|
+
postControlAck?: (ack: ControlAck) => Promise<void>;
|
|
38
|
+
replyCorrelationTtlMs?: number;
|
|
39
|
+
replyCorrelationCapPerEndpoint?: number;
|
|
21
40
|
}
|
|
22
41
|
export interface RunningBroker {
|
|
23
42
|
bearerToken: string;
|
|
@@ -27,6 +46,10 @@ export interface RunningBroker {
|
|
|
27
46
|
state(endpoint_id: string): EndpointState | undefined;
|
|
28
47
|
undispatchedInbox: UndispatchedInbox;
|
|
29
48
|
controlEventDispatcher: ControlEventDispatcher;
|
|
49
|
+
networkPresence(): NetworkPresence;
|
|
50
|
+
pendingSpawnCount(): number;
|
|
51
|
+
retryQueueSize(): number;
|
|
52
|
+
replyCorrelationCacheSize(endpoint_id: string): number;
|
|
30
53
|
}
|
|
31
54
|
export declare function startBrokerDaemon(opts: BrokerDaemonOptions): Promise<RunningBroker>;
|
|
32
55
|
export declare function readPluginPidHeader(req: IncomingMessage): number | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker-daemon.d.ts","sourceRoot":"","sources":["../../src/broker/broker-daemon.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"broker-daemon.d.ts","sourceRoot":"","sources":["../../src/broker/broker-daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn as spawnChild } from "node:child_process";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { createControlEventDispatcher, } from "./control-event-dispatcher.js";
|
|
3
4
|
import { deleteDiscoveryFile, mintBearerToken, writeDiscoveryFile, } from "./discovery-file.js";
|
|
@@ -5,13 +6,22 @@ import { EndpointRegistry } from "./endpoint-registry.js";
|
|
|
5
6
|
import { DEFAULT_GRACE_MS, transition, } from "./endpoint-state-machine.js";
|
|
6
7
|
import { createGraceTimerManager } from "./grace-timer.js";
|
|
7
8
|
import { BrokerHttpError, pushToPlugin, startIPCServer, } from "./ipc-server.js";
|
|
9
|
+
import { transitionPresence, } from "./network-presence.js";
|
|
8
10
|
import { isPluginAlive as defaultIsPluginAlive } from "./plugin-liveness.js";
|
|
11
|
+
import { createReceivedMessageCorrelationCache, DEFAULT_TTL_MS as DEFAULT_REPLY_CORRELATION_TTL_MS, } from "./received-message-correlation-cache.js";
|
|
9
12
|
import { createReconnectingBufferManager, } from "./reconnecting-buffer.js";
|
|
10
13
|
import { RoutingTable } from "./routing-table.js";
|
|
14
|
+
import { createSpawnCorrelationManager, createSpawnRateLimiter, } from "./spawn-correlation.js";
|
|
15
|
+
import { TASK_HINT_MAX_LENGTH, updateTaskHint } from "./task-hint-handler.js";
|
|
16
|
+
import { createTransitionStateRetryQueue } from "./transition-state-retry-queue.js";
|
|
11
17
|
import { createUndispatchedInbox, } from "./undispatched-inbox.js";
|
|
12
18
|
export async function startBrokerDaemon(opts) {
|
|
13
|
-
const { paths, asNodeId: _asNodeIdReservedForA3, connector, apiPort, logger, closedEndpointLookup, livenessProbe = defaultIsPluginAlive, } = opts;
|
|
19
|
+
const { paths, asNodeId: _asNodeIdReservedForA3, connector, apiPort, logger, closedEndpointLookup, livenessProbe = defaultIsPluginAlive, spawnDriverRegistry, spawnFn = spawnChild, servicesEventClientFactory, } = opts;
|
|
14
20
|
const graceMs = opts.graceMs ?? DEFAULT_GRACE_MS;
|
|
21
|
+
const presenceGraceMs = opts.presenceGraceMs;
|
|
22
|
+
const postControlAck = opts.postControlAck ??
|
|
23
|
+
(async () => {
|
|
24
|
+
});
|
|
15
25
|
void _asNodeIdReservedForA3;
|
|
16
26
|
logger.info("connecting_to_connector");
|
|
17
27
|
await connector.connect();
|
|
@@ -28,14 +38,112 @@ export async function startBrokerDaemon(opts) {
|
|
|
28
38
|
const reconnectingBuffers = createReconnectingBufferManager(paths.buffersDir);
|
|
29
39
|
const graceTimers = createGraceTimerManager();
|
|
30
40
|
const controlEventDispatcher = createControlEventDispatcher();
|
|
41
|
+
const spawnCorrelation = createSpawnCorrelationManager({
|
|
42
|
+
timeoutMs: opts.spawnCorrelationTimeoutMs,
|
|
43
|
+
});
|
|
44
|
+
const spawnRateLimiter = createSpawnRateLimiter({
|
|
45
|
+
maxInWindow: opts.spawnRateLimitMaxInWindow,
|
|
46
|
+
windowMs: opts.spawnRateLimitWindowMs,
|
|
47
|
+
});
|
|
48
|
+
const retryQueue = createTransitionStateRetryQueue({
|
|
49
|
+
capacity: opts.retryQueueCapacity,
|
|
50
|
+
onDrop: (dropped) => {
|
|
51
|
+
logger.warn("transition_state_retry_queue_overflow", {
|
|
52
|
+
endpoint_id: dropped.endpoint_id,
|
|
53
|
+
state: dropped.params.state,
|
|
54
|
+
reason: dropped.params.reason,
|
|
55
|
+
enqueued_at: dropped.enqueued_at,
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const replyCorrelationCache = createReceivedMessageCorrelationCache({
|
|
60
|
+
ttlMs: opts.replyCorrelationTtlMs,
|
|
61
|
+
capPerEndpoint: opts.replyCorrelationCapPerEndpoint,
|
|
62
|
+
});
|
|
63
|
+
const replyCorrelationTtl = opts.replyCorrelationTtlMs ?? DEFAULT_REPLY_CORRELATION_TTL_MS;
|
|
64
|
+
const replyCorrelationSweepInterval = Math.max(Math.floor(replyCorrelationTtl / 4), 60_000);
|
|
65
|
+
const replyCorrelationSweepTimer = setInterval(() => {
|
|
66
|
+
const evicted = replyCorrelationCache.sweep();
|
|
67
|
+
if (evicted > 0) {
|
|
68
|
+
logger.info("reply_correlation_cache_sweep", { evicted });
|
|
69
|
+
}
|
|
70
|
+
}, replyCorrelationSweepInterval);
|
|
71
|
+
replyCorrelationSweepTimer.unref?.();
|
|
72
|
+
const recordInboundCorrelation = (endpoint_id, metadata) => {
|
|
73
|
+
const c = metadata && typeof metadata.correlation_id === "string"
|
|
74
|
+
? metadata.correlation_id
|
|
75
|
+
: undefined;
|
|
76
|
+
if (!c)
|
|
77
|
+
return;
|
|
78
|
+
replyCorrelationCache.record(endpoint_id, c);
|
|
79
|
+
};
|
|
80
|
+
let networkPresence = "offline";
|
|
81
|
+
let presenceGraceTimer;
|
|
82
|
+
const applyPresenceTransition = (event) => {
|
|
83
|
+
const result = transitionPresence(networkPresence, event, {
|
|
84
|
+
graceMs: presenceGraceMs,
|
|
85
|
+
});
|
|
86
|
+
if (result.ignored)
|
|
87
|
+
return;
|
|
88
|
+
networkPresence = result.next;
|
|
89
|
+
for (const effect of result.effects) {
|
|
90
|
+
switch (effect.type) {
|
|
91
|
+
case "start_grace_timer":
|
|
92
|
+
if (presenceGraceTimer)
|
|
93
|
+
clearTimeout(presenceGraceTimer);
|
|
94
|
+
presenceGraceTimer = setTimeout(() => {
|
|
95
|
+
applyPresenceTransition({ type: "presence_grace_expired" });
|
|
96
|
+
}, effect.deadline_ms);
|
|
97
|
+
presenceGraceTimer.unref?.();
|
|
98
|
+
break;
|
|
99
|
+
case "cancel_grace_timer":
|
|
100
|
+
if (presenceGraceTimer) {
|
|
101
|
+
clearTimeout(presenceGraceTimer);
|
|
102
|
+
presenceGraceTimer = undefined;
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case "emit_presence":
|
|
106
|
+
logger.info("network_presence_changed", {
|
|
107
|
+
presence: effect.presence,
|
|
108
|
+
reason: effect.reason,
|
|
109
|
+
});
|
|
110
|
+
if (effect.presence === "online") {
|
|
111
|
+
void retryQueue.flush(async (entry) => {
|
|
112
|
+
await apiPort.transitionState(entry.endpoint_id, entry.params);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
applyPresenceTransition({ type: "connector_connected" });
|
|
120
|
+
connector.on("connected", () => {
|
|
121
|
+
applyPresenceTransition({ type: "connector_connected" });
|
|
122
|
+
});
|
|
123
|
+
connector.on("disconnected", () => {
|
|
124
|
+
applyPresenceTransition({ type: "connector_disconnected" });
|
|
125
|
+
});
|
|
31
126
|
const channels = new Map();
|
|
32
127
|
const wsEndpoints = new WeakMap();
|
|
33
128
|
const displacedWs = new WeakSet();
|
|
34
129
|
const emitTransition = (endpoint_id, state, reason) => {
|
|
35
|
-
|
|
130
|
+
const params = {
|
|
36
131
|
state,
|
|
37
132
|
reason,
|
|
38
133
|
ts: new Date().toISOString(),
|
|
134
|
+
};
|
|
135
|
+
if (networkPresence === "online") {
|
|
136
|
+
void apiPort.transitionState(endpoint_id, params);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const accepted = retryQueue.enqueue(endpoint_id, params);
|
|
140
|
+
logger.info("transition_state_deferred", {
|
|
141
|
+
endpoint_id,
|
|
142
|
+
state,
|
|
143
|
+
reason,
|
|
144
|
+
presence: networkPresence,
|
|
145
|
+
queued: accepted,
|
|
146
|
+
retry_queue_size: retryQueue.size(),
|
|
39
147
|
});
|
|
40
148
|
};
|
|
41
149
|
const handleTransition = (endpoint_id, event) => {
|
|
@@ -89,6 +197,7 @@ export async function startBrokerDaemon(opts) {
|
|
|
89
197
|
break;
|
|
90
198
|
case "remove_from_registry":
|
|
91
199
|
registry.unregister(endpoint_id);
|
|
200
|
+
replyCorrelationCache.forgetEndpoint(endpoint_id);
|
|
92
201
|
void apiPort.unregister(endpoint_id).catch(() => {
|
|
93
202
|
});
|
|
94
203
|
break;
|
|
@@ -130,6 +239,7 @@ export async function startBrokerDaemon(opts) {
|
|
|
130
239
|
dispatched_by: "passport",
|
|
131
240
|
},
|
|
132
241
|
};
|
|
242
|
+
recordInboundCorrelation(target.endpoint_id, stamped.metadata);
|
|
133
243
|
if (target.state === "active") {
|
|
134
244
|
pushToPlugin(target.ipc_ws, stamped);
|
|
135
245
|
}
|
|
@@ -153,6 +263,84 @@ export async function startBrokerDaemon(opts) {
|
|
|
153
263
|
});
|
|
154
264
|
return { ok: true };
|
|
155
265
|
});
|
|
266
|
+
controlEventDispatcher.on("spawn_request", async (event) => {
|
|
267
|
+
if (!spawnRateLimiter.tryConsume()) {
|
|
268
|
+
return {
|
|
269
|
+
ok: false,
|
|
270
|
+
detail: "spawn_rate_limited",
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
if (!spawnDriverRegistry) {
|
|
274
|
+
return {
|
|
275
|
+
ok: false,
|
|
276
|
+
detail: "spawn_driver_registry_not_configured",
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
const driver = spawnDriverRegistry.lookup(event.runtime_kind);
|
|
280
|
+
if (!driver) {
|
|
281
|
+
return {
|
|
282
|
+
ok: false,
|
|
283
|
+
detail: `runtime_kind_unsupported: ${event.runtime_kind}`,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const avail = await driver.isAvailable();
|
|
287
|
+
if (!avail.available) {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
detail: avail.reason ?? "driver_unavailable",
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const cmd = driver.buildSpawnCommand({
|
|
294
|
+
prompt: event.prompt,
|
|
295
|
+
spawn_token: event.idempotency_key,
|
|
296
|
+
});
|
|
297
|
+
let child;
|
|
298
|
+
try {
|
|
299
|
+
child = spawnFn(cmd.binary, cmd.args, {
|
|
300
|
+
detached: true,
|
|
301
|
+
stdio: "ignore",
|
|
302
|
+
windowsHide: true,
|
|
303
|
+
env: { ...process.env, ...(cmd.env ?? {}) },
|
|
304
|
+
cwd: cmd.cwd,
|
|
305
|
+
});
|
|
306
|
+
child.unref();
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
return {
|
|
310
|
+
ok: false,
|
|
311
|
+
detail: `spawn_failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
spawnCorrelation.track(event.idempotency_key, () => {
|
|
315
|
+
logger.warn("spawn_correlation_timeout", {
|
|
316
|
+
spawn_token: event.idempotency_key,
|
|
317
|
+
runtime_kind: event.runtime_kind,
|
|
318
|
+
});
|
|
319
|
+
void postControlAck({
|
|
320
|
+
idempotency_key: event.idempotency_key,
|
|
321
|
+
status: "failed",
|
|
322
|
+
detail: "spawn_correlation_timeout",
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
logger.info("spawn_request_initiated", {
|
|
326
|
+
spawn_token: event.idempotency_key,
|
|
327
|
+
runtime_kind: event.runtime_kind,
|
|
328
|
+
pid: child.pid,
|
|
329
|
+
});
|
|
330
|
+
return { ok: true, ack_hint: "received" };
|
|
331
|
+
});
|
|
332
|
+
controlEventDispatcher.on("force_unregister", async (event) => {
|
|
333
|
+
const entry = registry.get(event.endpoint_id);
|
|
334
|
+
if (!entry) {
|
|
335
|
+
return { ok: true };
|
|
336
|
+
}
|
|
337
|
+
logger.info("force_unregister_received", {
|
|
338
|
+
endpoint_id: event.endpoint_id,
|
|
339
|
+
reason: event.reason,
|
|
340
|
+
});
|
|
341
|
+
handleTransition(event.endpoint_id, { type: "explicit_unregister" });
|
|
342
|
+
return { ok: true };
|
|
343
|
+
});
|
|
156
344
|
const handlers = {
|
|
157
345
|
async registerEndpoint(body, _ipcWs) {
|
|
158
346
|
const boundWs = channels.get(body.plugin_pid);
|
|
@@ -191,6 +379,25 @@ export async function startBrokerDaemon(opts) {
|
|
|
191
379
|
}
|
|
192
380
|
bound.add(apiResp.endpoint_id);
|
|
193
381
|
emitTransition(apiResp.endpoint_id, "active", "register");
|
|
382
|
+
if (typeof body.spawn_token === "string") {
|
|
383
|
+
const pending = spawnCorrelation.consume(body.spawn_token);
|
|
384
|
+
if (pending) {
|
|
385
|
+
logger.info("endpoint_spawned_correlation_resolved", {
|
|
386
|
+
endpoint_id: apiResp.endpoint_id,
|
|
387
|
+
spawn_token: body.spawn_token,
|
|
388
|
+
});
|
|
389
|
+
void postControlAck({
|
|
390
|
+
idempotency_key: pending.spawn_token,
|
|
391
|
+
status: "applied",
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
logger.warn("endpoint_register_unknown_spawn_token", {
|
|
396
|
+
endpoint_id: apiResp.endpoint_id,
|
|
397
|
+
spawn_token: body.spawn_token,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
194
401
|
logger.info("endpoint_registered", {
|
|
195
402
|
endpoint_id: apiResp.endpoint_id,
|
|
196
403
|
plugin_pid: body.plugin_pid,
|
|
@@ -230,7 +437,27 @@ export async function startBrokerDaemon(opts) {
|
|
|
230
437
|
incoming.source_endpoint_id !== body.endpoint_id) {
|
|
231
438
|
throw new BrokerHttpError(403, "source_endpoint_id_mismatch", "metadata.source_endpoint_id must equal body.endpoint_id");
|
|
232
439
|
}
|
|
233
|
-
const
|
|
440
|
+
const inReplyTo = typeof incoming.in_reply_to === "string"
|
|
441
|
+
? incoming.in_reply_to
|
|
442
|
+
: undefined;
|
|
443
|
+
let correlationId;
|
|
444
|
+
if (inReplyTo) {
|
|
445
|
+
const resolved = replyCorrelationCache.resolve(body.endpoint_id, inReplyTo);
|
|
446
|
+
if (resolved.ok) {
|
|
447
|
+
correlationId = inReplyTo;
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
logger.warn("reply_aware_fallback", {
|
|
451
|
+
endpoint_id: body.endpoint_id,
|
|
452
|
+
in_reply_to: inReplyTo,
|
|
453
|
+
reason: resolved.reason,
|
|
454
|
+
});
|
|
455
|
+
correlationId = randomUUID();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
correlationId = randomUUID();
|
|
460
|
+
}
|
|
234
461
|
entry.correlation_ring.add(correlationId);
|
|
235
462
|
const metadata = {
|
|
236
463
|
...incoming,
|
|
@@ -291,6 +518,7 @@ export async function startBrokerDaemon(opts) {
|
|
|
291
518
|
type: "dispatch_undispatched",
|
|
292
519
|
version: 1,
|
|
293
520
|
idempotency_key: `local-${randomUUID()}`,
|
|
521
|
+
emitted_at: Date.now(),
|
|
294
522
|
undispatched_id,
|
|
295
523
|
target_endpoint_id,
|
|
296
524
|
});
|
|
@@ -298,6 +526,27 @@ export async function startBrokerDaemon(opts) {
|
|
|
298
526
|
throw new BrokerHttpError(400, "dispatch_failed", result.detail ?? "unknown");
|
|
299
527
|
}
|
|
300
528
|
},
|
|
529
|
+
async setTaskHint(body) {
|
|
530
|
+
const result = updateTaskHint({ registry }, {
|
|
531
|
+
endpoint_id: body.endpoint_id,
|
|
532
|
+
plugin_pid: body.plugin_pid,
|
|
533
|
+
task_hint: body.task_hint,
|
|
534
|
+
});
|
|
535
|
+
if (!result.ok) {
|
|
536
|
+
const status = result.code === "endpoint_unknown"
|
|
537
|
+
? 404
|
|
538
|
+
: result.code === "ownership_mismatch"
|
|
539
|
+
? 403
|
|
540
|
+
: 400;
|
|
541
|
+
throw new BrokerHttpError(status, result.code, result.message);
|
|
542
|
+
}
|
|
543
|
+
logger.info("task_hint_updated", {
|
|
544
|
+
endpoint_id: body.endpoint_id,
|
|
545
|
+
truncated: result.truncated,
|
|
546
|
+
length: result.effective.length,
|
|
547
|
+
max: TASK_HINT_MAX_LENGTH,
|
|
548
|
+
});
|
|
549
|
+
},
|
|
301
550
|
};
|
|
302
551
|
const bearerToken = mintBearerToken();
|
|
303
552
|
const ipc = await startIPCServer({
|
|
@@ -342,6 +591,7 @@ export async function startBrokerDaemon(opts) {
|
|
|
342
591
|
const correlation_id = typeof meta.correlation_id === "string" ? meta.correlation_id : undefined;
|
|
343
592
|
const decision = router.route({ target_endpoint_id, correlation_id });
|
|
344
593
|
if (decision.kind === "endpoint") {
|
|
594
|
+
recordInboundCorrelation(decision.entry.endpoint_id, meta);
|
|
345
595
|
if (decision.entry.state === "active") {
|
|
346
596
|
pushToPlugin(decision.entry.ipc_ws, {
|
|
347
597
|
event: "message_received",
|
|
@@ -421,13 +671,47 @@ export async function startBrokerDaemon(opts) {
|
|
|
421
671
|
bearerToken,
|
|
422
672
|
});
|
|
423
673
|
logger.info("discovery_written", { path: paths.discoveryFile });
|
|
674
|
+
let servicesEventClient;
|
|
675
|
+
if (servicesEventClientFactory) {
|
|
676
|
+
servicesEventClient = servicesEventClientFactory({
|
|
677
|
+
dispatcher: controlEventDispatcher,
|
|
678
|
+
logger,
|
|
679
|
+
});
|
|
680
|
+
try {
|
|
681
|
+
await servicesEventClient.start();
|
|
682
|
+
}
|
|
683
|
+
catch (err) {
|
|
684
|
+
logger.warn("services_event_client_start_failed", {
|
|
685
|
+
err: err instanceof Error ? err.message : String(err),
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
424
689
|
let shutdownStarted = false;
|
|
425
690
|
const shutdown = async (reason) => {
|
|
426
691
|
if (shutdownStarted)
|
|
427
692
|
return;
|
|
428
693
|
shutdownStarted = true;
|
|
429
694
|
logger.info("shutdown_begin", { reason });
|
|
695
|
+
if (servicesEventClient) {
|
|
696
|
+
try {
|
|
697
|
+
await servicesEventClient.stop();
|
|
698
|
+
}
|
|
699
|
+
catch {
|
|
700
|
+
}
|
|
701
|
+
}
|
|
430
702
|
graceTimers.cancelAll();
|
|
703
|
+
spawnCorrelation.cancelAll();
|
|
704
|
+
if (presenceGraceTimer) {
|
|
705
|
+
clearTimeout(presenceGraceTimer);
|
|
706
|
+
presenceGraceTimer = undefined;
|
|
707
|
+
}
|
|
708
|
+
clearInterval(replyCorrelationSweepTimer);
|
|
709
|
+
if (retryQueue.size() > 0) {
|
|
710
|
+
logger.warn("retry_queue_dropped_on_shutdown", {
|
|
711
|
+
size: retryQueue.size(),
|
|
712
|
+
reason,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
431
715
|
const endpoints = registry.list();
|
|
432
716
|
await Promise.all(endpoints.map(async (entry) => {
|
|
433
717
|
try {
|
|
@@ -459,6 +743,10 @@ export async function startBrokerDaemon(opts) {
|
|
|
459
743
|
state: (endpoint_id) => registry.get(endpoint_id)?.state,
|
|
460
744
|
undispatchedInbox,
|
|
461
745
|
controlEventDispatcher,
|
|
746
|
+
networkPresence: () => networkPresence,
|
|
747
|
+
pendingSpawnCount: () => spawnCorrelation.size(),
|
|
748
|
+
retryQueueSize: () => retryQueue.size(),
|
|
749
|
+
replyCorrelationCacheSize: (endpoint_id) => replyCorrelationCache.sizeForEndpoint(endpoint_id),
|
|
462
750
|
};
|
|
463
751
|
}
|
|
464
752
|
export function readPluginPidHeader(req) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DriverAvailability, SpawnCommand, SpawnContext, SpawnDriver } from "./spawn-driver.js";
|
|
2
|
+
export interface ClaudeCodeSpawnDriverOptions {
|
|
3
|
+
binary?: string;
|
|
4
|
+
isOnPath?: (binary: string) => boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class ClaudeCodeSpawnDriver implements SpawnDriver {
|
|
7
|
+
readonly runtime_kind = "claude-code";
|
|
8
|
+
private readonly binary;
|
|
9
|
+
private readonly isOnPath;
|
|
10
|
+
constructor(opts?: ClaudeCodeSpawnDriverOptions);
|
|
11
|
+
buildSpawnCommand(ctx: SpawnContext): SpawnCommand;
|
|
12
|
+
isAvailable(): Promise<DriverAvailability>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=claude-code-spawn-driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code-spawn-driver.d.ts","sourceRoot":"","sources":["../../src/broker/claude-code-spawn-driver.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAG3B,MAAM,WAAW,4BAA4B;IAE3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAMhB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;CACxC;AAED,qBAAa,qBAAsB,YAAW,WAAW;IACvD,QAAQ,CAAC,YAAY,iBAAiB;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;gBAE3C,IAAI,GAAE,4BAAiC;IAKnD,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,YAAY;IAS5C,WAAW,IAAI,OAAO,CAAC,kBAAkB,CAAC;CASjD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
export class ClaudeCodeSpawnDriver {
|
|
3
|
+
runtime_kind = "claude-code";
|
|
4
|
+
binary;
|
|
5
|
+
isOnPath;
|
|
6
|
+
constructor(opts = {}) {
|
|
7
|
+
this.binary = opts.binary ?? "claude";
|
|
8
|
+
this.isOnPath = opts.isOnPath ?? defaultIsOnPath;
|
|
9
|
+
}
|
|
10
|
+
buildSpawnCommand(ctx) {
|
|
11
|
+
return {
|
|
12
|
+
binary: this.binary,
|
|
13
|
+
args: ["--bg", ctx.prompt],
|
|
14
|
+
env: { MASONS_SPAWN_TOKEN: ctx.spawn_token },
|
|
15
|
+
cwd: ctx.cwd,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async isAvailable() {
|
|
19
|
+
if (this.isOnPath(this.binary)) {
|
|
20
|
+
return { available: true };
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
available: false,
|
|
24
|
+
reason: `\`${this.binary}\` CLI not found in PATH. Install Claude Code or check $PATH.`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function defaultIsOnPath(binary) {
|
|
29
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(binary))
|
|
30
|
+
return false;
|
|
31
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
32
|
+
try {
|
|
33
|
+
execFileSync(probe, [binary], { stdio: "ignore" });
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DriverAvailability, SpawnCommand, SpawnContext, SpawnDriver } from "./spawn-driver.js";
|
|
2
|
+
export declare class CodexSpawnDriverStub implements SpawnDriver {
|
|
3
|
+
readonly runtime_kind = "codex";
|
|
4
|
+
buildSpawnCommand(_ctx: SpawnContext): SpawnCommand;
|
|
5
|
+
isAvailable(): Promise<DriverAvailability>;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=codex-spawn-driver-stub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-spawn-driver-stub.d.ts","sourceRoot":"","sources":["../../src/broker/codex-spawn-driver-stub.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,oBAAqB,YAAW,WAAW;IACtD,QAAQ,CAAC,YAAY,WAAW;IAEhC,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,YAAY;IAW7C,WAAW,IAAI,OAAO,CAAC,kBAAkB,CAAC;CAYjD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class CodexSpawnDriverStub {
|
|
2
|
+
runtime_kind = "codex";
|
|
3
|
+
buildSpawnCommand(_ctx) {
|
|
4
|
+
throw new Error("CodexSpawnDriverStub.buildSpawnCommand called — broker logic bug; " +
|
|
5
|
+
"isAvailable() should have short-circuited the spawn before this point");
|
|
6
|
+
}
|
|
7
|
+
async isAvailable() {
|
|
8
|
+
return {
|
|
9
|
+
available: false,
|
|
10
|
+
reason: "codex driver not registered on this broker — Codex distribution will ship a SpawnDriver in a future release (broker restart required after install)",
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-event-dispatcher.d.ts","sourceRoot":"","sources":["../../src/broker/control-event-dispatcher.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,eAAO,MAAM,6BAA6B,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"control-event-dispatcher.d.ts","sourceRoot":"","sources":["../../src/broker/control-event-dispatcher.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAYlD,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,UAAU,CAAA;CAAE,GACnC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,IAAI,CACvE,KAAK,EAAE,CAAC,KACL,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAElC,MAAM,WAAW,sBAAsB;IAErC,EAAE,CAAC,CAAC,SAAS,YAAY,EACvB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EACf,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC9B,IAAI,CAAC;IAKR,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAE5D,QAAQ,IAAI,MAAM,CAAC;IAEnB,OAAO,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3C;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,GAAE,6BAAkC,GACvC,sBAAsB,CAqDxB"}
|
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
export
|
|
2
|
-
export interface DispatchUndispatchedEvent {
|
|
3
|
-
type: "dispatch_undispatched";
|
|
1
|
+
export interface ControlEventBase {
|
|
4
2
|
version: 1;
|
|
5
3
|
idempotency_key: string;
|
|
4
|
+
emitted_at: number;
|
|
5
|
+
}
|
|
6
|
+
export type ControlEvent = DispatchUndispatchedEvent | SpawnRequestEvent | ForceUnregisterEvent;
|
|
7
|
+
export interface DispatchUndispatchedEvent extends ControlEventBase {
|
|
8
|
+
type: "dispatch_undispatched";
|
|
6
9
|
undispatched_id: string;
|
|
7
10
|
target_endpoint_id: string;
|
|
8
11
|
}
|
|
9
|
-
export interface SpawnRequestEvent {
|
|
12
|
+
export interface SpawnRequestEvent extends ControlEventBase {
|
|
10
13
|
type: "spawn_request";
|
|
11
|
-
version: 1;
|
|
12
|
-
idempotency_key: string;
|
|
13
|
-
request_id: string;
|
|
14
14
|
prompt: string;
|
|
15
|
-
runtime_kind:
|
|
15
|
+
runtime_kind: "claude-code" | "openclaw" | "codex" | "custom";
|
|
16
16
|
origin: "passport-spawn";
|
|
17
17
|
}
|
|
18
|
-
export interface ForceUnregisterEvent {
|
|
18
|
+
export interface ForceUnregisterEvent extends ControlEventBase {
|
|
19
19
|
type: "force_unregister";
|
|
20
|
-
version: 1;
|
|
21
|
-
idempotency_key: string;
|
|
22
20
|
endpoint_id: string;
|
|
23
21
|
reason: string;
|
|
24
22
|
}
|
|
25
23
|
export interface ControlAck {
|
|
26
|
-
|
|
27
|
-
status: "
|
|
24
|
+
idempotency_key: string;
|
|
25
|
+
status: "received" | "applied" | "failed";
|
|
28
26
|
detail?: string;
|
|
29
27
|
}
|
|
30
28
|
//# sourceMappingURL=control-event-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-event-types.d.ts","sourceRoot":"","sources":["../../src/broker/control-event-types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"control-event-types.d.ts","sourceRoot":"","sources":["../../src/broker/control-event-types.ts"],"names":[],"mappings":"AAuCA,MAAM,WAAW,gBAAgB;IAI/B,OAAO,EAAE,CAAC,CAAC;IAWX,eAAe,EAAE,MAAM,CAAC;IAKxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,MAAM,YAAY,GACpB,yBAAyB,GACzB,iBAAiB,GACjB,oBAAoB,CAAC;AAGzB,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE,IAAI,EAAE,uBAAuB,CAAC;IAE9B,eAAe,EAAE,MAAM,CAAC;IAExB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAGD,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,IAAI,EAAE,eAAe,CAAC;IAEtB,MAAM,EAAE,MAAM,CAAC;IAMf,YAAY,EAAE,aAAa,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;IAG9D,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAGD,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC5D,IAAI,EAAE,kBAAkB,CAAC;IAEzB,WAAW,EAAE,MAAM,CAAC;IAEpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAwBD,MAAM,WAAW,UAAU;IAEzB,eAAe,EAAE,MAAM,CAAC;IAExB,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAE1C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/broker/entry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/broker/entry.ts"],"names":[],"mappings":"AA6EA,OAAO,EAAE,KAAK,YAAY,EAAsB,MAAM,aAAa,CAAC;AAEpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAMtE,UAAU,mBAAmB;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAUD,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CA8F9B;AAyDD,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,YAAY,GACnB,mBAAmB,CAkErB;AAYD,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA8F1C"}
|