@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.
Files changed (46) hide show
  1. package/dist/broker/broker-daemon.d.ts +23 -0
  2. package/dist/broker/broker-daemon.d.ts.map +1 -1
  3. package/dist/broker/broker-daemon.js +291 -3
  4. package/dist/broker/claude-code-spawn-driver.d.ts +14 -0
  5. package/dist/broker/claude-code-spawn-driver.d.ts.map +1 -0
  6. package/dist/broker/claude-code-spawn-driver.js +39 -0
  7. package/dist/broker/codex-spawn-driver-stub.d.ts +7 -0
  8. package/dist/broker/codex-spawn-driver-stub.d.ts.map +1 -0
  9. package/dist/broker/codex-spawn-driver-stub.js +13 -0
  10. package/dist/broker/control-event-dispatcher.d.ts +1 -0
  11. package/dist/broker/control-event-dispatcher.d.ts.map +1 -1
  12. package/dist/broker/control-event-types.d.ts +11 -13
  13. package/dist/broker/control-event-types.d.ts.map +1 -1
  14. package/dist/broker/entry.d.ts.map +1 -1
  15. package/dist/broker/entry.js +41 -0
  16. package/dist/broker/ipc-server.d.ts +7 -0
  17. package/dist/broker/ipc-server.d.ts.map +1 -1
  18. package/dist/broker/ipc-server.js +20 -0
  19. package/dist/broker/network-presence.d.ts +31 -0
  20. package/dist/broker/network-presence.d.ts.map +1 -0
  21. package/dist/broker/network-presence.js +109 -0
  22. package/dist/broker/received-message-correlation-cache.d.ts +23 -0
  23. package/dist/broker/received-message-correlation-cache.d.ts.map +1 -0
  24. package/dist/broker/received-message-correlation-cache.js +114 -0
  25. package/dist/broker/services-event-client.d.ts +21 -0
  26. package/dist/broker/services-event-client.d.ts.map +1 -0
  27. package/dist/broker/services-event-client.js +221 -0
  28. package/dist/broker/spawn-correlation.d.ts +28 -0
  29. package/dist/broker/spawn-correlation.d.ts.map +1 -0
  30. package/dist/broker/spawn-correlation.js +77 -0
  31. package/dist/broker/spawn-driver.d.ts +27 -0
  32. package/dist/broker/spawn-driver.d.ts.map +1 -0
  33. package/dist/broker/spawn-driver.js +15 -0
  34. package/dist/broker/task-hint-handler.d.ts +21 -0
  35. package/dist/broker/task-hint-handler.d.ts.map +1 -0
  36. package/dist/broker/task-hint-handler.js +33 -0
  37. package/dist/broker/transition-state-retry-queue.d.ts +20 -0
  38. package/dist/broker/transition-state-retry-queue.d.ts.map +1 -0
  39. package/dist/broker/transition-state-retry-queue.js +48 -0
  40. package/dist/broker-client/broker-client.d.ts +1 -0
  41. package/dist/broker-client/broker-client.d.ts.map +1 -1
  42. package/dist/broker-client/broker-client.js +1 -0
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.d.ts.map +1 -1
  45. package/dist/version.js +1 -1
  46. 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":"AAqBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,KAAK,sBAAsB,EAE5B,MAAM,+BAA+B,CAAC;AAQvC,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,6BAA6B,CAAC;AAarC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EACL,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,yBAAyB,CAAC;AAEjC,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;IAGrB,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAE5C,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;CACjD;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;IAGvB,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;IAEtD,iBAAiB,EAAE,iBAAiB,CAAC;IAErC,sBAAsB,EAAE,sBAAsB,CAAC;CAChD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,aAAa,CAAC,CAqnBxB;AAGD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAMvE;AAED,YAAY,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"broker-daemon.d.ts","sourceRoot":"","sources":["../../src/broker/broker-daemon.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAqB,KAAK,IAAI,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,KAAK,sBAAsB,EAE5B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EACV,UAAU,EAIX,MAAM,0BAA0B,CAAC;AAOlC,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,6BAA6B,CAAC;AAcrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,KAAK,eAAe,EAErB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAO9C,OAAO,EACL,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAKtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,yBAAyB,CAAC;AAEjC,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;IAGrB,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAE5C,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IAKhD,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAG1C,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC;IAG5B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAGhC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAGnC,eAAe,CAAC,EAAE,MAAM,CAAC;IAGzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAK5B,0BAA0B,CAAC,EAAE,CAAC,IAAI,EAAE;QAClC,UAAU,EAAE,sBAAsB,CAAC;QACnC,MAAM,EAAE,YAAY,CAAC;KACtB,KAAK,mBAAmB,CAAC;IAK1B,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAQpD,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAI/B,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACzC;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;IAGvB,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;IAEtD,iBAAiB,EAAE,iBAAiB,CAAC;IAErC,sBAAsB,EAAE,sBAAsB,CAAC;IAE/C,eAAe,IAAI,eAAe,CAAC;IAEnC,iBAAiB,IAAI,MAAM,CAAC;IAE5B,cAAc,IAAI,MAAM,CAAC;IAGzB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;CACxD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,aAAa,CAAC,CAoiCxB;AAGD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAMvE;AAED,YAAY,EAAE,eAAe,EAAE,CAAC"}
@@ -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
- void apiPort.transitionState(endpoint_id, {
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 correlationId = randomUUID();
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
+ }
@@ -2,6 +2,7 @@ import type { ControlEvent } from "./control-event-types.js";
2
2
  export declare const DEFAULT_IDEMPOTENCY_RING_SIZE = 1024;
3
3
  export type ControlEventOutcome = {
4
4
  ok: true;
5
+ ack_hint?: "received";
5
6
  } | {
6
7
  ok: false;
7
8
  detail: string;
@@ -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;AAElD,MAAM,MAAM,mBAAmB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,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
+ {"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 type ControlEvent = DispatchUndispatchedEvent | SpawnRequestEvent | ForceUnregisterEvent;
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: string;
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
- request_id: string;
27
- status: "ok" | "error";
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":"AAgBA,MAAM,MAAM,YAAY,GACpB,yBAAyB,GACzB,iBAAiB,GACjB,oBAAoB,CAAC;AAGzB,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,OAAO,EAAE,CAAC,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAGD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,CAAC,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAGD,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,CAAC,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAGD,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
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":"AA0EA,OAAO,EAAE,KAAK,YAAY,EAAsB,MAAM,aAAa,CAAC;AAEpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAItE,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,CAgD1C"}
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"}