@masons/runtime-broker 0.2.7 → 0.2.9

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.
@@ -4,6 +4,7 @@ import type { ClosedEndpointLookup } from "./closed-endpoint-lookup.js";
4
4
  import type { ConnectorWS } from "./connector-ws.js";
5
5
  import { type ControlEventDispatcher } from "./control-event-dispatcher.js";
6
6
  import type { ControlAck } from "./control-event-types.js";
7
+ import { type DiscoveryRecord } from "./discovery-file.js";
7
8
  import { type EndpointState } from "./endpoint-state-machine.js";
8
9
  import type { BrokerLogger } from "./logger.js";
9
10
  import { type NetworkPresence } from "./network-presence.js";
@@ -57,6 +58,7 @@ export interface BrokerDaemonOptions {
57
58
  runtimeInboundRoutedBackoffMaxMs?: number;
58
59
  runtimeInboundRoutedMaxRetries?: number;
59
60
  runtimeAssignmentAcceptanceTimeoutMs?: number;
61
+ pushReceiptAckTimeoutMs?: number;
60
62
  }
61
63
  export interface RunningBroker {
62
64
  bearerToken: string;
@@ -74,6 +76,10 @@ export interface RunningBroker {
74
76
  networkPresenceChangedQueueSize(): number;
75
77
  runtimeInboundRoutedQueueSize(): number;
76
78
  }
79
+ export declare class BrokerAlreadyRunningError extends Error {
80
+ readonly discovery: DiscoveryRecord;
81
+ constructor(discovery: DiscoveryRecord);
82
+ }
77
83
  export declare function startBrokerDaemon(opts: BrokerDaemonOptions): Promise<RunningBroker>;
78
84
  export declare function readPluginPidHeader(req: IncomingMessage): number | null;
79
85
  export type { BufferedMessage };
@@ -1 +1 @@
1
- {"version":3,"file":"broker-daemon.d.ts","sourceRoot":"","sources":["../../src/broker/broker-daemon.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"broker-daemon.d.ts","sourceRoot":"","sources":["../../src/broker/broker-daemon.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@ import { basename } from "node:path";
4
4
  import { ConnectorClientUnavailableError, ConnectorSendAckTimeoutError, } from "../connector-client.js";
5
5
  import { createControlEventDispatcher, } from "./control-event-dispatcher.js";
6
6
  import { readDeliveryCursorFile, writeDeliveryCursorFile, } from "./delivery-cursor-file.js";
7
- import { deleteDiscoveryFile, mintBearerToken, writeDiscoveryFile, } from "./discovery-file.js";
7
+ import { deleteDiscoveryFileIfOwned, mintBearerToken, readDiscoveryFile, writeDiscoveryFile, } from "./discovery-file.js";
8
8
  import { EndpointRegistry, } from "./endpoint-registry.js";
9
9
  import { DEFAULT_GRACE_MS, transition, } from "./endpoint-state-machine.js";
10
10
  import { createGraceTimerManager } from "./grace-timer.js";
@@ -24,6 +24,7 @@ import { createUndispatchedChangedEmitter, postUndispatchedChangedViaPort, } fro
24
24
  import { createUndispatchedInbox, } from "./undispatched-inbox.js";
25
25
  const REMOTE_SPAWN_CAPABILITY = "remote_spawn_v1";
26
26
  const DEFAULT_RUNTIME_ASSIGNMENT_ACCEPTANCE_TIMEOUT_MS = 30_000;
27
+ const DEFAULT_PUSH_RECEIPT_ACK_TIMEOUT_MS = 2_000;
27
28
  const RUNTIME_ASSIGNMENT_REPLY_RECORD_TTL_MS = 24 * 60 * 60 * 1000;
28
29
  const RUNTIME_PROCESSING_RETRY_INITIAL_MS = 1_000;
29
30
  const RUNTIME_PROCESSING_RETRY_MAX_MS = 30_000;
@@ -59,16 +60,60 @@ function derivePublicDisplayLabel(body) {
59
60
  ? parts.join(" · ")
60
61
  : `${body.kind}-${String(body.plugin_pid).slice(-4)}`;
61
62
  }
63
+ export class BrokerAlreadyRunningError extends Error {
64
+ discovery;
65
+ constructor(discovery) {
66
+ super(`another healthy runtime broker already owns discovery at ${discovery.ipcUrl}`);
67
+ this.name = "BrokerAlreadyRunningError";
68
+ this.discovery = discovery;
69
+ }
70
+ }
71
+ async function findHealthyDiscoveryOwner(discoveryFile) {
72
+ const existing = readDiscoveryFile(discoveryFile);
73
+ if (!existing)
74
+ return null;
75
+ if (existing.pid === process.pid)
76
+ return null;
77
+ if (!(await probeDiscoveryHealth(existing)))
78
+ return null;
79
+ return existing;
80
+ }
81
+ async function probeDiscoveryHealth(record) {
82
+ const ctrl = new AbortController();
83
+ const timer = setTimeout(() => ctrl.abort(), 2_000);
84
+ try {
85
+ const res = await fetch(`${record.ipcUrl}/health`, {
86
+ headers: { Authorization: `Bearer ${record.bearerToken}` },
87
+ signal: ctrl.signal,
88
+ });
89
+ return res.ok;
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ finally {
95
+ clearTimeout(timer);
96
+ }
97
+ }
62
98
  export async function startBrokerDaemon(opts) {
63
99
  const { paths, asNodeId: _asNodeIdReservedForA3, connector, apiPort, logger, closedEndpointLookup, livenessProbe = defaultIsPluginAlive, spawnDriverRegistry, spawnFn = spawnChild, servicesEventClientFactory, } = opts;
64
100
  const graceMs = opts.graceMs ?? DEFAULT_GRACE_MS;
65
101
  const presenceGraceMs = opts.presenceGraceMs;
66
102
  const runtimeAssignmentAcceptanceTimeoutMs = opts.runtimeAssignmentAcceptanceTimeoutMs ??
67
103
  DEFAULT_RUNTIME_ASSIGNMENT_ACCEPTANCE_TIMEOUT_MS;
104
+ const pushReceiptAckTimeoutMs = opts.pushReceiptAckTimeoutMs ?? DEFAULT_PUSH_RECEIPT_ACK_TIMEOUT_MS;
68
105
  const postControlAck = opts.postControlAck ??
69
106
  (async () => {
70
107
  });
71
108
  void _asNodeIdReservedForA3;
109
+ const existingOwner = await findHealthyDiscoveryOwner(paths.discoveryFile);
110
+ if (existingOwner) {
111
+ logger.info("broker_already_running", {
112
+ pid: existingOwner.pid,
113
+ ipcUrl: existingOwner.ipcUrl,
114
+ });
115
+ throw new BrokerAlreadyRunningError(existingOwner);
116
+ }
72
117
  const persistedDeliveryCursor = readDeliveryCursorFile(paths.deliveryCursorFile) ?? 0;
73
118
  connector.setDeliveryCursor(persistedDeliveryCursor);
74
119
  logger.info("delivery_cursor_loaded", {
@@ -496,6 +541,65 @@ export async function startBrokerDaemon(opts) {
496
541
  }
497
542
  };
498
543
  const channels = new Map();
544
+ const channelCapabilities = new WeakMap();
545
+ const pendingPushAcks = new Map();
546
+ const settlePushAck = (pushId, ok) => {
547
+ const pending = pendingPushAcks.get(pushId);
548
+ if (!pending)
549
+ return;
550
+ pendingPushAcks.delete(pushId);
551
+ clearTimeout(pending.timer);
552
+ pending.resolve(ok);
553
+ };
554
+ const waitForPushAck = (plugin_pid, ipc_ws, pushId, event, endpoint_id) => new Promise((resolve) => {
555
+ const timer = setTimeout(() => {
556
+ logger.warn("ipc_push_ack_timeout", {
557
+ push_id: pushId,
558
+ plugin_pid,
559
+ event,
560
+ endpoint_id,
561
+ });
562
+ settlePushAck(pushId, false);
563
+ }, pushReceiptAckTimeoutMs);
564
+ timer.unref?.();
565
+ pendingPushAcks.set(pushId, {
566
+ plugin_pid,
567
+ ipc_ws,
568
+ endpoint_id,
569
+ event,
570
+ timer,
571
+ resolve,
572
+ });
573
+ });
574
+ const pushToPluginWithReceipt = async (entry, event) => {
575
+ const capabilities = channelCapabilities.get(entry.ipc_ws);
576
+ if (capabilities?.push_receipt_ack_v1 !== true) {
577
+ return pushToPlugin(entry.ipc_ws, event);
578
+ }
579
+ const pushId = `push_${randomUUID()}`;
580
+ const ack = waitForPushAck(entry.plugin_pid, entry.ipc_ws, pushId, event.event, entry.endpoint_id);
581
+ let pushed = false;
582
+ try {
583
+ pushed = pushToPlugin(entry.ipc_ws, {
584
+ ...event,
585
+ push_id: pushId,
586
+ });
587
+ }
588
+ catch (err) {
589
+ logger.warn("ipc_push_send_failed", {
590
+ push_id: pushId,
591
+ endpoint_id: entry.endpoint_id,
592
+ plugin_pid: entry.plugin_pid,
593
+ event: event.event,
594
+ err: err instanceof Error ? err.message : String(err),
595
+ });
596
+ }
597
+ if (!pushed) {
598
+ settlePushAck(pushId, false);
599
+ return false;
600
+ }
601
+ return ack;
602
+ };
499
603
  let networkPresence = "offline";
500
604
  let presenceGraceTimer;
501
605
  const applyPresenceTransition = (event) => {
@@ -875,7 +979,6 @@ export async function startBrokerDaemon(opts) {
875
979
  storedAssignment.lastStateSequence = Math.max(storedAssignment.lastStateSequence, processingEvent.state_sequence);
876
980
  runtimeAssignments.set(assignmentKey, storedAssignment);
877
981
  scheduleAcceptanceTimeout(assignmentKey);
878
- rememberRuntimeAssignmentCorrelation(target.endpoint_id, taken.metadata.correlation_id, assignmentKey);
879
982
  const stamped = {
880
983
  event: "message_received",
881
984
  from: taken.sender_address,
@@ -889,15 +992,26 @@ export async function startBrokerDaemon(opts) {
889
992
  sourceMessageId: taken.source_message_id,
890
993
  runtimeAssignment,
891
994
  };
892
- recordInboundCorrelation(target.endpoint_id, stamped.metadata);
893
995
  if (target.state === "active") {
894
- if (!pushToPlugin(target.ipc_ws, stamped)) {
895
- await restorePendingAssignmentsForEndpoint(target.endpoint_id, "ipc_push_failed");
896
- return {
897
- ok: false,
898
- detail: `target endpoint not writable: ${targetEndpointId}`,
899
- };
996
+ if (!(await pushToPluginWithReceipt(target, stamped))) {
997
+ const current = runtimeAssignments.get(assignmentKey);
998
+ if (current?.accepted) {
999
+ logger.info("ipc_push_ack_missing_after_adapter_accept", {
1000
+ assignment_id: runtimeAssignment.assignmentId,
1001
+ source_message_id: runtimeAssignment.sourceMessageId,
1002
+ target_endpoint_id: target.endpoint_id,
1003
+ });
1004
+ }
1005
+ else {
1006
+ await restorePendingAssignmentsForEndpoint(target.endpoint_id, "ipc_push_not_received");
1007
+ return {
1008
+ ok: false,
1009
+ detail: `target endpoint did not acknowledge push receipt: ${targetEndpointId}`,
1010
+ };
1011
+ }
900
1012
  }
1013
+ rememberRuntimeAssignmentCorrelation(target.endpoint_id, taken.metadata.correlation_id, assignmentKey);
1014
+ recordInboundCorrelation(target.endpoint_id, stamped.metadata);
901
1015
  }
902
1016
  else {
903
1017
  try {
@@ -1440,10 +1554,10 @@ export async function startBrokerDaemon(opts) {
1440
1554
  const buf = reconnectingBuffers.forEndpoint(endpoint_id);
1441
1555
  const drained = buf.read();
1442
1556
  graceTimers.cancel(endpoint_id);
1443
- registry.markActive(endpoint_id, ipcWs);
1557
+ const activeEntry = registry.markActive(endpoint_id, ipcWs);
1444
1558
  let allPushed = true;
1445
1559
  for (const msg of drained) {
1446
- const pushed = pushToPlugin(ipcWs, {
1560
+ const pushed = await pushToPluginWithReceipt(activeEntry, {
1447
1561
  event: "message_received",
1448
1562
  from: msg.envelope.from,
1449
1563
  content: msg.envelope.content,
@@ -1539,7 +1653,7 @@ export async function startBrokerDaemon(opts) {
1539
1653
  handlers,
1540
1654
  logger,
1541
1655
  getNetworkPresence: () => networkPresence,
1542
- onChannelOpened: (plugin_pid, ws) => {
1656
+ onChannelOpened: (plugin_pid, ws, clientCapabilities) => {
1543
1657
  const prior = channels.get(plugin_pid);
1544
1658
  if (prior && prior !== ws) {
1545
1659
  displacedWs.add(prior);
@@ -1550,11 +1664,50 @@ export async function startBrokerDaemon(opts) {
1550
1664
  }
1551
1665
  }
1552
1666
  channels.set(plugin_pid, ws);
1667
+ channelCapabilities.set(ws, clientCapabilities);
1668
+ },
1669
+ onPushAck: (plugin_pid, ws, push_id) => {
1670
+ const pending = pendingPushAcks.get(push_id);
1671
+ if (!pending) {
1672
+ logger.debug("ipc_push_ack_unknown", { plugin_pid, push_id });
1673
+ return;
1674
+ }
1675
+ if (pending.plugin_pid !== plugin_pid) {
1676
+ logger.warn("ipc_push_ack_pid_mismatch", {
1677
+ push_id,
1678
+ expected_plugin_pid: pending.plugin_pid,
1679
+ actual_plugin_pid: plugin_pid,
1680
+ event: pending.event,
1681
+ endpoint_id: pending.endpoint_id,
1682
+ });
1683
+ return;
1684
+ }
1685
+ if (pending.ipc_ws !== ws) {
1686
+ logger.warn("ipc_push_ack_ws_mismatch", {
1687
+ push_id,
1688
+ plugin_pid,
1689
+ event: pending.event,
1690
+ endpoint_id: pending.endpoint_id,
1691
+ });
1692
+ return;
1693
+ }
1694
+ settlePushAck(push_id, true);
1553
1695
  },
1554
1696
  onChannelClosed: (plugin_pid, ws) => {
1555
1697
  if (channels.get(plugin_pid) === ws) {
1556
1698
  channels.delete(plugin_pid);
1557
1699
  }
1700
+ for (const [pushId, pending] of Array.from(pendingPushAcks.entries())) {
1701
+ if (pending.plugin_pid === plugin_pid && pending.ipc_ws === ws) {
1702
+ logger.warn("ipc_push_ack_channel_closed", {
1703
+ push_id: pushId,
1704
+ plugin_pid,
1705
+ event: pending.event,
1706
+ endpoint_id: pending.endpoint_id,
1707
+ });
1708
+ settlePushAck(pushId, false);
1709
+ }
1710
+ }
1558
1711
  if (displacedWs.has(ws))
1559
1712
  return;
1560
1713
  const bound = wsEndpoints.get(ws);
@@ -1569,6 +1722,24 @@ export async function startBrokerDaemon(opts) {
1569
1722
  }
1570
1723
  },
1571
1724
  });
1725
+ const ownerAfterBind = await findHealthyDiscoveryOwner(paths.discoveryFile);
1726
+ if (ownerAfterBind) {
1727
+ logger.info("broker_already_running_after_ipc_bind", {
1728
+ pid: ownerAfterBind.pid,
1729
+ ipcUrl: ownerAfterBind.ipcUrl,
1730
+ });
1731
+ try {
1732
+ await ipc.close();
1733
+ }
1734
+ catch {
1735
+ }
1736
+ try {
1737
+ connector.disconnect();
1738
+ }
1739
+ catch {
1740
+ }
1741
+ throw new BrokerAlreadyRunningError(ownerAfterBind);
1742
+ }
1572
1743
  const inboundProcessing = new Set();
1573
1744
  const blockedInboundSeqs = new Set();
1574
1745
  const asyncAcceptedInboundSeqs = new Set();
@@ -1725,9 +1896,8 @@ export async function startBrokerDaemon(opts) {
1725
1896
  routed_at: Date.now(),
1726
1897
  };
1727
1898
  }
1728
- recordInboundCorrelation(decision.entry.endpoint_id, meta);
1729
1899
  if (decision.entry.state === "active") {
1730
- const pushed = pushToPlugin(decision.entry.ipc_ws, {
1900
+ const pushed = await pushToPluginWithReceipt(decision.entry, {
1731
1901
  event: "message_received",
1732
1902
  from: payload.from,
1733
1903
  content: payload.content,
@@ -1739,6 +1909,7 @@ export async function startBrokerDaemon(opts) {
1739
1909
  });
1740
1910
  if (!pushed)
1741
1911
  return false;
1912
+ recordInboundCorrelation(decision.entry.endpoint_id, meta);
1742
1913
  }
1743
1914
  else {
1744
1915
  const buf = reconnectingBuffers.forEndpoint(decision.entry.endpoint_id);
@@ -1764,6 +1935,7 @@ export async function startBrokerDaemon(opts) {
1764
1935
  });
1765
1936
  return false;
1766
1937
  }
1938
+ recordInboundCorrelation(decision.entry.endpoint_id, meta);
1767
1939
  logger.info("buffered_for_reconnecting_endpoint", {
1768
1940
  endpoint_id: decision.entry.endpoint_id,
1769
1941
  buffer_size: buf.size(),
@@ -1857,13 +2029,14 @@ export async function startBrokerDaemon(opts) {
1857
2029
  deferredDeliveryPendingUpTo = undefined;
1858
2030
  connector.ackDelivery(payload.upTo);
1859
2031
  });
1860
- writeDiscoveryFile(paths.discoveryFile, {
2032
+ const ownDiscovery = {
1861
2033
  version: 1,
1862
2034
  pid: process.pid,
1863
2035
  startedAt: new Date().toISOString(),
1864
2036
  ipcUrl: ipc.ipcUrl,
1865
2037
  bearerToken,
1866
- });
2038
+ };
2039
+ writeDiscoveryFile(paths.discoveryFile, ownDiscovery);
1867
2040
  logger.info("discovery_written", { path: paths.discoveryFile });
1868
2041
  let servicesEventClient;
1869
2042
  if (servicesEventClientFactory) {
@@ -1937,7 +2110,13 @@ export async function startBrokerDaemon(opts) {
1937
2110
  catch {
1938
2111
  }
1939
2112
  }));
1940
- deleteDiscoveryFile(paths.discoveryFile);
2113
+ const discoveryDeleted = deleteDiscoveryFileIfOwned(paths.discoveryFile, ownDiscovery);
2114
+ if (!discoveryDeleted) {
2115
+ logger.warn("discovery_delete_skipped_not_owner", {
2116
+ path: paths.discoveryFile,
2117
+ pid: process.pid,
2118
+ });
2119
+ }
1941
2120
  try {
1942
2121
  await ipc.close();
1943
2122
  }
@@ -5,8 +5,11 @@ export interface DiscoveryRecord {
5
5
  ipcUrl: string;
6
6
  bearerToken: string;
7
7
  }
8
+ export type DiscoveryOwner = Pick<DiscoveryRecord, "pid" | "ipcUrl" | "bearerToken">;
8
9
  export declare function mintBearerToken(): string;
9
10
  export declare function writeDiscoveryFile(path: string, record: DiscoveryRecord): void;
10
11
  export declare function readDiscoveryFile(path: string): DiscoveryRecord | null;
11
12
  export declare function deleteDiscoveryFile(path: string): void;
13
+ export declare function deleteDiscoveryFileIfOwned(path: string, owner: DiscoveryOwner): boolean;
14
+ export declare function isDiscoveryOwner(record: DiscoveryRecord, owner: DiscoveryOwner): boolean;
12
15
  //# sourceMappingURL=discovery-file.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"discovery-file.d.ts","sourceRoot":"","sources":["../../src/broker/discovery-file.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,CAAC,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAGD,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAMD,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,eAAe,GACtB,IAAI,CA4BN;AAMD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAetE;AAGD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOtD"}
1
+ {"version":3,"file":"discovery-file.d.ts","sourceRoot":"","sources":["../../src/broker/discovery-file.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,CAAC,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,IAAI,CAC/B,eAAe,EACf,KAAK,GAAG,QAAQ,GAAG,aAAa,CACjC,CAAC;AAGF,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAMD,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,eAAe,GACtB,IAAI,CA4BN;AAMD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAetE;AAGD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOtD;AAOD,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,cAAc,GACpB,OAAO,CAMT;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,cAAc,GACpB,OAAO,CAMT"}
@@ -59,6 +59,20 @@ export function deleteDiscoveryFile(path) {
59
59
  throw err;
60
60
  }
61
61
  }
62
+ export function deleteDiscoveryFileIfOwned(path, owner) {
63
+ const current = readDiscoveryFile(path);
64
+ if (!current)
65
+ return false;
66
+ if (!isDiscoveryOwner(current, owner))
67
+ return false;
68
+ deleteDiscoveryFile(path);
69
+ return true;
70
+ }
71
+ export function isDiscoveryOwner(record, owner) {
72
+ return (record.pid === owner.pid &&
73
+ record.ipcUrl === owner.ipcUrl &&
74
+ record.bearerToken === owner.bearerToken);
75
+ }
62
76
  function isDiscoveryRecord(value) {
63
77
  if (typeof value !== "object" || value === null)
64
78
  return false;
@@ -15,6 +15,7 @@ interface RuntimePrincipal {
15
15
  export declare function resolveCredentialsFromFile(filePath: string, accountId: string, envApiHost: string | undefined): Promise<ResolvedCredentials>;
16
16
  export declare function resolveRuntimePrincipal(apiHost: string, runtimeKey: string, fetchImpl?: typeof globalThis.fetch): Promise<RuntimePrincipal>;
17
17
  export declare function buildApiPort(apiHost: string, runtimeKey: string, logger: BrokerLogger): RuntimeEndpointPort;
18
+ export declare function installFatalExitHandlers(logger: BrokerLogger): void;
18
19
  export declare function main(): Promise<void>;
19
20
  export {};
20
21
  //# sourceMappingURL=entry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/broker/entry.ts"],"names":[],"mappings":"AAmFA,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;AAED,UAAU,gBAAgB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAUD,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CA8F9B;AA2DD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,OAAO,UAAU,CAAC,KAAwB,GACpD,OAAO,CAAC,gBAAgB,CAAC,CA4B3B;AAaD,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,YAAY,GACnB,mBAAmB,CAqIrB;AAYD,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAgG1C"}
1
+ {"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/broker/entry.ts"],"names":[],"mappings":"AAsFA,OAAO,EAAE,KAAK,YAAY,EAAsB,MAAM,aAAa,CAAC;AAEpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAOtE,UAAU,mBAAmB;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,gBAAgB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAUD,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CA8F9B;AA2DD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,OAAO,UAAU,CAAC,KAAwB,GACpD,OAAO,CAAC,gBAAgB,CAAC,CA4B3B;AAaD,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,YAAY,GACnB,mBAAmB,CAqIrB;AAyCD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAenE;AAED,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAiG1C"}
@@ -3,7 +3,7 @@ import { pathToFileURL } from "node:url";
3
3
  import { readConfig } from "../config-fs.js";
4
4
  import { DEFAULT_API_HOST } from "../platform-client.js";
5
5
  import { claimRuntimeInboundRoute, emitRuntimeInboundRouted, emitRuntimeNetworkPresenceChanged, emitRuntimeProcessingState, emitRuntimeUndispatchedChanged, heartbeatRuntimeEndpoint, registerRuntimeEndpoint, transitionRuntimeEndpointState, unregisterRuntimeEndpoint, updateRuntimeEndpointDisplayMetadata, } from "../runtime-endpoint-client.js";
6
- import { startBrokerDaemon } from "./broker-daemon.js";
6
+ import { BrokerAlreadyRunningError, startBrokerDaemon, } from "./broker-daemon.js";
7
7
  import { ClaudeCodeSpawnDriver } from "./claude-code-spawn-driver.js";
8
8
  import { CodexSpawnDriverStub } from "./codex-spawn-driver-stub.js";
9
9
  import { ConnectorWS } from "./connector-ws.js";
@@ -12,6 +12,7 @@ import { resolveBrokerPaths } from "./paths.js";
12
12
  import { createServicesEventClient } from "./services-event-client.js";
13
13
  import { SpawnDriverRegistry } from "./spawn-driver.js";
14
14
  const DEFAULT_ACCOUNT_ID = "default";
15
+ let entryLogger = null;
15
16
  export async function resolveCredentialsFromFile(filePath, accountId, envApiHost) {
16
17
  let raw;
17
18
  try {
@@ -191,6 +192,40 @@ function errorStatus(err) {
191
192
  function sleep(ms) {
192
193
  return new Promise((resolve) => setTimeout(resolve, ms));
193
194
  }
195
+ function serializeFatalReason(reason) {
196
+ if (reason instanceof Error) {
197
+ return {
198
+ name: reason.name,
199
+ message: reason.message,
200
+ stack: reason.stack,
201
+ };
202
+ }
203
+ return { message: String(reason) };
204
+ }
205
+ function logBrokerFatalExit(kind, reason) {
206
+ const fields = {
207
+ kind,
208
+ ...serializeFatalReason(reason),
209
+ };
210
+ if (entryLogger) {
211
+ entryLogger.error("broker_fatal_exit", fields);
212
+ return;
213
+ }
214
+ console.error("broker_fatal_exit", fields);
215
+ }
216
+ export function installFatalExitHandlers(logger) {
217
+ entryLogger = logger;
218
+ let fatalExitStarted = false;
219
+ const exitAfterLog = (kind, reason) => {
220
+ if (fatalExitStarted)
221
+ return;
222
+ fatalExitStarted = true;
223
+ logBrokerFatalExit(kind, reason);
224
+ process.exit(1);
225
+ };
226
+ process.on("uncaughtException", (err) => exitAfterLog("uncaughtException", err));
227
+ process.on("unhandledRejection", (reason) => exitAfterLog("unhandledRejection", reason));
228
+ }
194
229
  export async function main() {
195
230
  const accountId = process.env.MASONS_BROKER_ACCOUNT_ID ?? DEFAULT_ACCOUNT_ID;
196
231
  const envApiHost = process.env.MASONS_BROKER_API_HOST;
@@ -200,6 +235,7 @@ export async function main() {
200
235
  userDataDir: userDataOverride,
201
236
  });
202
237
  const logger = createBrokerLogger(paths.logDir);
238
+ installFatalExitHandlers(logger);
203
239
  const principal = await resolveRuntimePrincipal(creds.apiHost, creds.token);
204
240
  logger.info("broker_entry_starting", {
205
241
  accountId: creds.accountId,
@@ -273,7 +309,15 @@ export async function main() {
273
309
  const argvUrl = process.argv[1] ? pathToFileURL(process.argv[1]).href : "";
274
310
  if (import.meta.url === argvUrl) {
275
311
  main().catch((err) => {
276
- console.error("broker_entry_fatal", err);
312
+ if (err instanceof BrokerAlreadyRunningError) {
313
+ process.exit(0);
314
+ }
315
+ if (entryLogger) {
316
+ entryLogger.error("broker_entry_fatal", serializeFatalReason(err));
317
+ }
318
+ else {
319
+ console.error("broker_entry_fatal", err);
320
+ }
277
321
  process.exit(1);
278
322
  });
279
323
  }
@@ -3,6 +3,7 @@ import type { EndpointBackgroundExchangeMode, EndpointExecutionSurface, RuntimeE
3
3
  import type { BrokerLogger } from "./logger.js";
4
4
  import type { NetworkPresence } from "./network-presence.js";
5
5
  import type { RuntimeProcessingState, RuntimeWorkTargetRef } from "./runtime-processing-state-event-types.js";
6
+ import { type IpcCapabilities } from "./version-handshake.js";
6
7
  export interface IPCServerHandlers {
7
8
  registerEndpoint(body: RegisterEndpointBody, ipcWs: WebSocket): Promise<RegisterEndpointResponse>;
8
9
  heartbeatEndpoint(endpoint_id: string): Promise<void>;
@@ -101,8 +102,9 @@ export interface IPCServerOptions {
101
102
  bearerToken: string;
102
103
  handlers: IPCServerHandlers;
103
104
  logger: BrokerLogger;
104
- onChannelOpened?: (plugin_pid: number, ws: WebSocket) => void;
105
+ onChannelOpened?: (plugin_pid: number, ws: WebSocket, clientCapabilities: IpcCapabilities) => void;
105
106
  onChannelClosed?: (plugin_pid: number, ws: WebSocket) => void;
107
+ onPushAck?: (plugin_pid: number, ws: WebSocket, push_id: string) => void;
106
108
  getNetworkPresence: () => NetworkPresence;
107
109
  }
108
110
  export interface RunningIPCServer {
@@ -114,6 +116,7 @@ export declare function startIPCServer(opts: IPCServerOptions): Promise<RunningI
114
116
  export declare function pushToPlugin(ws: WebSocket, event: PushEvent): boolean;
115
117
  export type PushEvent = {
116
118
  event: "message_received";
119
+ push_id?: string;
117
120
  from: string;
118
121
  content: string;
119
122
  contentType: string;
@@ -122,9 +125,11 @@ export type PushEvent = {
122
125
  runtimeAssignment?: RuntimeAssignmentContext;
123
126
  } | {
124
127
  event: "ping";
128
+ push_id?: string;
125
129
  ts: string;
126
130
  } | {
127
131
  event: "presence_changed";
132
+ push_id?: string;
128
133
  presence: NetworkPresence;
129
134
  reason?: string;
130
135
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ipc-server.d.ts","sourceRoot":"","sources":["../../src/broker/ipc-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ipc-server.d.ts","sourceRoot":"","sources":["../../src/broker/ipc-server.ts"],"names":[],"mappings":"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"}
@@ -29,7 +29,7 @@ export class BrokerHttpError extends Error {
29
29
  }
30
30
  }
31
31
  export async function startIPCServer(opts) {
32
- const { bearerToken, handlers, logger, onChannelOpened, onChannelClosed, getNetworkPresence, } = opts;
32
+ const { bearerToken, handlers, logger, onChannelOpened, onChannelClosed, getNetworkPresence, onPushAck, } = opts;
33
33
  const http = createServer((req, res) => {
34
34
  void routeHttp(req, res, bearerToken, handlers, logger, getNetworkPresence);
35
35
  });
@@ -52,8 +52,17 @@ export async function startIPCServer(opts) {
52
52
  return;
53
53
  }
54
54
  wss.handleUpgrade(req, socket, head, (ws) => {
55
- onChannelOpened?.(pid, ws);
55
+ onChannelOpened?.(pid, ws, readClientCapabilities(req));
56
56
  ws.once("close", () => onChannelClosed?.(pid, ws));
57
+ ws.on("message", (data) => {
58
+ const parsed = parsePluginFrame(data);
59
+ if (parsed !== null &&
60
+ typeof parsed === "object" &&
61
+ parsed.event === "push_ack" &&
62
+ typeof parsed.push_id === "string") {
63
+ onPushAck?.(pid, ws, parsed.push_id);
64
+ }
65
+ });
57
66
  wss.emit("connection", ws, req);
58
67
  });
59
68
  });
@@ -235,6 +244,21 @@ function readPluginPid(req) {
235
244
  const n = Number.parseInt(str, 10);
236
245
  return Number.isFinite(n) && n > 0 ? n : null;
237
246
  }
247
+ function readClientCapabilities(req) {
248
+ const raw = req.headers["x-masons-ipc-capabilities"];
249
+ const str = Array.isArray(raw) ? raw.join(",") : raw;
250
+ const tokens = new Set(typeof str === "string"
251
+ ? str
252
+ .split(",")
253
+ .map((part) => part.trim())
254
+ .filter(Boolean)
255
+ : []);
256
+ return {
257
+ ...(tokens.has("push_receipt_ack_v1") && {
258
+ push_receipt_ack_v1: true,
259
+ }),
260
+ };
261
+ }
238
262
  function constantTimeEquals(a, b) {
239
263
  if (a.length !== b.length)
240
264
  return false;
@@ -1,10 +1,11 @@
1
1
  import type { NetworkPresence } from "./network-presence.js";
2
- export declare const IPC_PROTOCOL_VERSION = "1.0";
2
+ export declare const IPC_PROTOCOL_VERSION = "1.1";
3
3
  export interface IpcCapabilities {
4
4
  endpoint_metadata_v1?: boolean;
5
5
  remote_spawn_v1?: boolean;
6
6
  undispatched_inbox_v1?: boolean;
7
7
  runtime_assignment_reply_v1?: boolean;
8
+ push_receipt_ack_v1?: boolean;
8
9
  }
9
10
  export declare const SERVER_CAPABILITIES: Readonly<IpcCapabilities>;
10
11
  export interface InitializeRequestBody {
@@ -1 +1 @@
1
- {"version":3,"file":"version-handshake.d.ts","sourceRoot":"","sources":["../../src/broker/version-handshake.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,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;IAEhC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAGD,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,eAAe,CAKxD,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;IAanB,gBAAgB,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,2BAA2B,GAAG,aAAa,CAAC;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB;AAUD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,qBAAqB,EAC1B,GAAG,EAAE;IAAE,eAAe,EAAE,eAAe,CAAA;CAAE,GAEvC;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,sBAAsB,CAAA;CAAE,GAC7C;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,mBAAmB,CAAA;CAAE,CA2C7C"}
1
+ {"version":3,"file":"version-handshake.d.ts","sourceRoot":"","sources":["../../src/broker/version-handshake.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,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;IAEhC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IAMtC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAGD,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,eAAe,CAMxD,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;IAanB,gBAAgB,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,2BAA2B,GAAG,aAAa,CAAC;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB;AAUD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,qBAAqB,EAC1B,GAAG,EAAE;IAAE,eAAe,EAAE,eAAe,CAAA;CAAE,GAEvC;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,sBAAsB,CAAA;CAAE,GAC7C;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,mBAAmB,CAAA;CAAE,CA2C7C"}
@@ -1,10 +1,11 @@
1
1
  import { randomUUID } from "node:crypto";
2
- export const IPC_PROTOCOL_VERSION = "1.0";
2
+ export const IPC_PROTOCOL_VERSION = "1.1";
3
3
  export const SERVER_CAPABILITIES = Object.freeze({
4
4
  endpoint_metadata_v1: true,
5
5
  remote_spawn_v1: true,
6
6
  undispatched_inbox_v1: false,
7
7
  runtime_assignment_reply_v1: true,
8
+ push_receipt_ack_v1: true,
8
9
  });
9
10
  export function handleInitialize(req, ctx) {
10
11
  if (typeof req.client_protocol_version !== "string") {
@@ -26,6 +26,9 @@ export interface BrokerClientConnectOptions {
26
26
  pluginPid: number;
27
27
  clientKind: string;
28
28
  clientVersion: string;
29
+ heartbeatIntervalMs?: number;
30
+ heartbeatTimeoutMs?: number;
31
+ heartbeatMissesBeforeDisconnect?: number;
29
32
  }
30
33
  export interface RegisterEndpointArgs {
31
34
  agentId: string;
@@ -92,6 +95,11 @@ type Events = {
92
95
  export declare class BrokerClient extends EventEmitter {
93
96
  private readonly handle;
94
97
  private ws;
98
+ private heartbeatInterval;
99
+ private heartbeatTimeout;
100
+ private heartbeatMisses;
101
+ private heartbeatTimeoutMs;
102
+ private heartbeatMissesBeforeDisconnect;
95
103
  private pluginPid;
96
104
  private _sessionId;
97
105
  private _networkPresence;
@@ -127,6 +135,11 @@ export declare class BrokerClient extends EventEmitter {
127
135
  emit(event: string | symbol, ...args: unknown[]): boolean;
128
136
  private httpJson;
129
137
  private handleFrame;
138
+ private sendPushAck;
139
+ private startHeartbeat;
140
+ private sendHeartbeatProbe;
141
+ private markHeartbeatAlive;
142
+ private clearHeartbeat;
130
143
  }
131
144
  export {};
132
145
  //# sourceMappingURL=broker-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"broker-client.d.ts","sourceRoot":"","sources":["../../src/broker-client/broker-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"broker-client.d.ts","sourceRoot":"","sources":["../../src/broker-client/broker-client.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,11 @@ function safeJsonParse(text) {
30
30
  export class BrokerClient extends EventEmitter {
31
31
  handle;
32
32
  ws = null;
33
+ heartbeatInterval = null;
34
+ heartbeatTimeout = null;
35
+ heartbeatMisses = 0;
36
+ heartbeatTimeoutMs = 5_000;
37
+ heartbeatMissesBeforeDisconnect = 2;
33
38
  pluginPid = null;
34
39
  _sessionId = null;
35
40
  _networkPresence = null;
@@ -81,11 +86,22 @@ export class BrokerClient extends EventEmitter {
81
86
  this.pluginPid = opts.pluginPid;
82
87
  this._clientKind = opts.clientKind;
83
88
  this._clientVersion = opts.clientVersion;
89
+ this.heartbeatTimeoutMs = opts.heartbeatTimeoutMs ?? 5_000;
90
+ this.heartbeatMissesBeforeDisconnect =
91
+ opts.heartbeatMissesBeforeDisconnect ?? 2;
92
+ const clientCapabilities = {
93
+ endpoint_metadata_v1: true,
94
+ push_receipt_ack_v1: true,
95
+ };
96
+ const clientCapabilityHeader = Object.entries(clientCapabilities)
97
+ .filter(([, enabled]) => enabled === true)
98
+ .map(([name]) => name)
99
+ .join(",");
84
100
  const initRes = await this.httpJson("POST", "/v1/initialize", {
85
101
  client_protocol_version: IPC_PROTOCOL_VERSION,
86
102
  client_kind: opts.clientKind,
87
103
  client_version: opts.clientVersion,
88
- capabilities: { endpoint_metadata_v1: true },
104
+ capabilities: clientCapabilities,
89
105
  });
90
106
  this._sessionId = initRes.session_id;
91
107
  this._serverCapabilities = initRes.server_capabilities;
@@ -95,6 +111,7 @@ export class BrokerClient extends EventEmitter {
95
111
  headers: {
96
112
  Authorization: `Bearer ${this.handle.record.bearerToken}`,
97
113
  "x-plugin-pid": String(opts.pluginPid),
114
+ "x-masons-ipc-capabilities": clientCapabilityHeader,
98
115
  },
99
116
  });
100
117
  this.ws = ws;
@@ -111,11 +128,14 @@ export class BrokerClient extends EventEmitter {
111
128
  ws.once("error", onError);
112
129
  });
113
130
  ws.on("message", (data) => this.handleFrame(data.toString()));
131
+ ws.on("pong", () => this.markHeartbeatAlive());
114
132
  ws.on("close", () => {
133
+ this.clearHeartbeat();
115
134
  this.ws = null;
116
135
  this.emit("disconnected");
117
136
  });
118
137
  ws.on("error", (err) => this.emit("error", err));
138
+ this.startHeartbeat(opts.heartbeatIntervalMs ?? 30_000);
119
139
  return initRes;
120
140
  }
121
141
  async registerEndpoint(args) {
@@ -196,6 +216,7 @@ export class BrokerClient extends EventEmitter {
196
216
  this.ws.readyState === WebSocket.CONNECTING)) {
197
217
  this.ws.close(1000, "client_close");
198
218
  }
219
+ this.clearHeartbeat();
199
220
  this.ws = null;
200
221
  }
201
222
  on(event, listener) {
@@ -258,6 +279,11 @@ export class BrokerClient extends EventEmitter {
258
279
  }
259
280
  if (!parsed || typeof parsed !== "object" || !("event" in parsed))
260
281
  return;
282
+ const pushId = parsed.push_id;
283
+ if (typeof pushId === "string" &&
284
+ this._serverCapabilities.push_receipt_ack_v1 === true) {
285
+ this.sendPushAck(pushId);
286
+ }
261
287
  const event = parsed.event;
262
288
  if (event === "message_received") {
263
289
  const msg = parsed;
@@ -291,4 +317,69 @@ export class BrokerClient extends EventEmitter {
291
317
  return;
292
318
  }
293
319
  }
320
+ sendPushAck(pushId) {
321
+ const ws = this.ws;
322
+ if (!ws || ws.readyState !== WebSocket.OPEN)
323
+ return;
324
+ try {
325
+ ws.send(JSON.stringify({ event: "push_ack", push_id: pushId }));
326
+ }
327
+ catch (err) {
328
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
329
+ }
330
+ }
331
+ startHeartbeat(intervalMs) {
332
+ this.clearHeartbeat();
333
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0)
334
+ return;
335
+ const timer = setInterval(() => this.sendHeartbeatProbe(), intervalMs);
336
+ const maybeUnref = timer;
337
+ if (typeof maybeUnref.unref === "function")
338
+ maybeUnref.unref();
339
+ this.heartbeatInterval = timer;
340
+ }
341
+ sendHeartbeatProbe() {
342
+ const ws = this.ws;
343
+ if (!ws || ws.readyState !== WebSocket.OPEN)
344
+ return;
345
+ if (this.heartbeatTimeout)
346
+ return;
347
+ try {
348
+ ws.ping();
349
+ }
350
+ catch (err) {
351
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
352
+ ws.terminate();
353
+ return;
354
+ }
355
+ const timeout = setTimeout(() => {
356
+ this.heartbeatTimeout = null;
357
+ this.heartbeatMisses += 1;
358
+ if (this.heartbeatMisses >= this.heartbeatMissesBeforeDisconnect) {
359
+ ws.terminate();
360
+ }
361
+ }, this.heartbeatTimeoutMs);
362
+ const maybeUnref = timeout;
363
+ if (typeof maybeUnref.unref === "function")
364
+ maybeUnref.unref();
365
+ this.heartbeatTimeout = timeout;
366
+ }
367
+ markHeartbeatAlive() {
368
+ this.heartbeatMisses = 0;
369
+ if (this.heartbeatTimeout) {
370
+ clearTimeout(this.heartbeatTimeout);
371
+ this.heartbeatTimeout = null;
372
+ }
373
+ }
374
+ clearHeartbeat() {
375
+ if (this.heartbeatInterval) {
376
+ clearInterval(this.heartbeatInterval);
377
+ this.heartbeatInterval = null;
378
+ }
379
+ if (this.heartbeatTimeout) {
380
+ clearTimeout(this.heartbeatTimeout);
381
+ this.heartbeatTimeout = null;
382
+ }
383
+ this.heartbeatMisses = 0;
384
+ }
294
385
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lazy-spawn.d.ts","sourceRoot":"","sources":["../../src/broker-client/lazy-spawn.ts"],"names":[],"mappings":"AA0CA,OAAO,EACL,KAAK,eAAe,EAErB,MAAM,6BAA6B,CAAC;AAErC,MAAM,WAAW,gBAAgB;IAE/B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IAEvD,aAAa,EAAE,MAAM,CAAC;IAEtB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,eAAe,CAAC;IAExB,OAAO,EAAE,OAAO,CAAC;CAClB;AAMD,wBAAsB,cAAc,CAClC,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,eAAe,CAAC,CAgC1B"}
1
+ {"version":3,"file":"lazy-spawn.d.ts","sourceRoot":"","sources":["../../src/broker-client/lazy-spawn.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,KAAK,eAAe,EAErB,MAAM,6BAA6B,CAAC;AAErC,MAAM,WAAW,gBAAgB;IAE/B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IAEvD,aAAa,EAAE,MAAM,CAAC;IAEtB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,eAAe,CAAC;IAExB,OAAO,EAAE,OAAO,CAAC;CAClB;AAsBD,wBAAsB,cAAc,CAClC,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,eAAe,CAAC,CAsD1B"}
@@ -1,9 +1,12 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { chmodSync, closeSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
3
+ import { dirname } from "node:path";
2
4
  import { fileURLToPath } from "node:url";
3
5
  import { readDiscoveryFile, } from "../broker/discovery-file.js";
4
6
  const DEFAULT_POLL_INTERVAL_MS = 100;
5
7
  const DEFAULT_DISCOVER_TIMEOUT_MS = 5_000;
6
8
  const DEFAULT_HEALTH_TIMEOUT_MS = 2_000;
9
+ const SPAWN_LOCK_VERSION = 1;
7
10
  export async function discoverBroker(opts) {
8
11
  const deadline = Date.now() + (opts.timeoutMs ?? DEFAULT_DISCOVER_TIMEOUT_MS);
9
12
  const poll = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
@@ -14,17 +17,138 @@ export async function discoverBroker(opts) {
14
17
  return { record: existing, spawned: false };
15
18
  }
16
19
  }
17
- spawnDaemon(opts);
18
- while (Date.now() < deadline) {
19
- const rec = readDiscoveryFile(opts.discoveryFile);
20
- if (rec && (await probeHealth(rec.ipcUrl, rec.bearerToken))) {
21
- return { record: rec, spawned: true };
20
+ const acquired = await acquireSpawnLock({
21
+ discoveryFile: opts.discoveryFile,
22
+ deadline,
23
+ pollIntervalMs: poll,
24
+ });
25
+ if (acquired.kind === "existing") {
26
+ return { record: acquired.record, spawned: false };
27
+ }
28
+ const { lock } = acquired;
29
+ try {
30
+ const recovered = readDiscoveryFile(opts.discoveryFile);
31
+ if (recovered &&
32
+ (await probeHealth(recovered.ipcUrl, recovered.bearerToken))) {
33
+ return { record: recovered, spawned: false };
34
+ }
35
+ spawnDaemon(opts);
36
+ while (Date.now() < deadline) {
37
+ const rec = readDiscoveryFile(opts.discoveryFile);
38
+ if (rec && (await probeHealth(rec.ipcUrl, rec.bearerToken))) {
39
+ return { record: rec, spawned: true };
40
+ }
41
+ await sleep(poll);
22
42
  }
23
- await sleep(poll);
43
+ }
44
+ finally {
45
+ lock.release();
24
46
  }
25
47
  throw new Error(`broker did not become healthy within ${opts.timeoutMs ?? DEFAULT_DISCOVER_TIMEOUT_MS}ms; ` +
26
48
  `check ${opts.discoveryFile} and the daemon log`);
27
49
  }
50
+ async function acquireSpawnLock(opts) {
51
+ const lockPath = `${opts.discoveryFile}.lock`;
52
+ mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 });
53
+ while (Date.now() < opts.deadline) {
54
+ try {
55
+ const fd = openSync(lockPath, "wx", 0o600);
56
+ const record = {
57
+ version: SPAWN_LOCK_VERSION,
58
+ pid: process.pid,
59
+ startedAt: new Date().toISOString(),
60
+ };
61
+ writeFileSync(fd, JSON.stringify(record, null, 2), {
62
+ encoding: "utf8",
63
+ });
64
+ try {
65
+ chmodSync(lockPath, 0o600);
66
+ }
67
+ catch {
68
+ }
69
+ return {
70
+ kind: "lock",
71
+ lock: {
72
+ path: lockPath,
73
+ fd,
74
+ release() {
75
+ releaseSpawnLock(lockPath, fd);
76
+ },
77
+ },
78
+ };
79
+ }
80
+ catch (err) {
81
+ if (!isNodeErr(err, "EEXIST"))
82
+ throw err;
83
+ const existing = readDiscoveryFile(opts.discoveryFile);
84
+ if (existing &&
85
+ (await probeHealth(existing.ipcUrl, existing.bearerToken))) {
86
+ return { kind: "existing", record: existing };
87
+ }
88
+ if (isStaleSpawnLock(lockPath)) {
89
+ try {
90
+ unlinkSync(lockPath);
91
+ continue;
92
+ }
93
+ catch (unlinkErr) {
94
+ if (!isNodeErr(unlinkErr, "ENOENT")) {
95
+ throw unlinkErr;
96
+ }
97
+ }
98
+ }
99
+ await sleep(opts.pollIntervalMs);
100
+ }
101
+ }
102
+ throw new Error(`broker spawn lock was not acquired before discover timeout; ` +
103
+ `check ${lockPath}`);
104
+ }
105
+ function releaseSpawnLock(path, fd) {
106
+ try {
107
+ closeSync(fd);
108
+ }
109
+ catch {
110
+ }
111
+ try {
112
+ unlinkSync(path);
113
+ }
114
+ catch {
115
+ }
116
+ }
117
+ function isStaleSpawnLock(path) {
118
+ let parsed;
119
+ try {
120
+ const raw = readFileSync(path, "utf8");
121
+ const value = JSON.parse(raw);
122
+ if (!isSpawnLockRecord(value))
123
+ return false;
124
+ parsed = value;
125
+ }
126
+ catch (err) {
127
+ if (isNodeErr(err, "ENOENT"))
128
+ return false;
129
+ return false;
130
+ }
131
+ return !isProcessAlive(parsed.pid);
132
+ }
133
+ function isSpawnLockRecord(value) {
134
+ if (typeof value !== "object" || value === null)
135
+ return false;
136
+ const v = value;
137
+ return (v.version === SPAWN_LOCK_VERSION &&
138
+ typeof v.pid === "number" &&
139
+ Number.isInteger(v.pid) &&
140
+ v.pid > 0 &&
141
+ typeof v.startedAt === "string");
142
+ }
143
+ function isProcessAlive(pid) {
144
+ try {
145
+ process.kill(pid, 0);
146
+ return true;
147
+ }
148
+ catch (err) {
149
+ return isNodeErr(err, "EPERM");
150
+ }
151
+ }
28
152
  function spawnDaemon(opts) {
29
153
  const entry = opts.brokerEntry ?? resolveDefaultBrokerEntry();
30
154
  const child = spawn(opts.nodeBinary ?? process.execPath, [entry], {
@@ -59,3 +183,9 @@ async function probeHealth(ipcUrl, bearerToken) {
59
183
  function sleep(ms) {
60
184
  return new Promise((resolve) => setTimeout(resolve, ms));
61
185
  }
186
+ function isNodeErr(err, code) {
187
+ return (typeof err === "object" &&
188
+ err !== null &&
189
+ "code" in err &&
190
+ err.code === code);
191
+ }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const PLUGIN_VERSION = "0.2.7";
1
+ export declare const PLUGIN_VERSION = "0.2.9";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const PLUGIN_VERSION = "0.2.7";
1
+ export const PLUGIN_VERSION = "0.2.9";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masons/runtime-broker",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "MASONS Runtime Broker — local daemon and BrokerClient SDK for multi-session agent runtime coordination.",
5
5
  "license": "MIT",
6
6
  "author": "MASONS.ai <hello@masons.ai> (https://masons.ai)",