@masons/runtime-broker 0.2.7 → 0.2.8

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";
@@ -74,6 +75,10 @@ export interface RunningBroker {
74
75
  networkPresenceChangedQueueSize(): number;
75
76
  runtimeInboundRoutedQueueSize(): number;
76
77
  }
78
+ export declare class BrokerAlreadyRunningError extends Error {
79
+ readonly discovery: DiscoveryRecord;
80
+ constructor(discovery: DiscoveryRecord);
81
+ }
77
82
  export declare function startBrokerDaemon(opts: BrokerDaemonOptions): Promise<RunningBroker>;
78
83
  export declare function readPluginPidHeader(req: IncomingMessage): number | null;
79
84
  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";
@@ -59,6 +59,41 @@ function derivePublicDisplayLabel(body) {
59
59
  ? parts.join(" · ")
60
60
  : `${body.kind}-${String(body.plugin_pid).slice(-4)}`;
61
61
  }
62
+ export class BrokerAlreadyRunningError extends Error {
63
+ discovery;
64
+ constructor(discovery) {
65
+ super(`another healthy runtime broker already owns discovery at ${discovery.ipcUrl}`);
66
+ this.name = "BrokerAlreadyRunningError";
67
+ this.discovery = discovery;
68
+ }
69
+ }
70
+ async function findHealthyDiscoveryOwner(discoveryFile) {
71
+ const existing = readDiscoveryFile(discoveryFile);
72
+ if (!existing)
73
+ return null;
74
+ if (existing.pid === process.pid)
75
+ return null;
76
+ if (!(await probeDiscoveryHealth(existing)))
77
+ return null;
78
+ return existing;
79
+ }
80
+ async function probeDiscoveryHealth(record) {
81
+ const ctrl = new AbortController();
82
+ const timer = setTimeout(() => ctrl.abort(), 2_000);
83
+ try {
84
+ const res = await fetch(`${record.ipcUrl}/health`, {
85
+ headers: { Authorization: `Bearer ${record.bearerToken}` },
86
+ signal: ctrl.signal,
87
+ });
88
+ return res.ok;
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ finally {
94
+ clearTimeout(timer);
95
+ }
96
+ }
62
97
  export async function startBrokerDaemon(opts) {
63
98
  const { paths, asNodeId: _asNodeIdReservedForA3, connector, apiPort, logger, closedEndpointLookup, livenessProbe = defaultIsPluginAlive, spawnDriverRegistry, spawnFn = spawnChild, servicesEventClientFactory, } = opts;
64
99
  const graceMs = opts.graceMs ?? DEFAULT_GRACE_MS;
@@ -69,6 +104,14 @@ export async function startBrokerDaemon(opts) {
69
104
  (async () => {
70
105
  });
71
106
  void _asNodeIdReservedForA3;
107
+ const existingOwner = await findHealthyDiscoveryOwner(paths.discoveryFile);
108
+ if (existingOwner) {
109
+ logger.info("broker_already_running", {
110
+ pid: existingOwner.pid,
111
+ ipcUrl: existingOwner.ipcUrl,
112
+ });
113
+ throw new BrokerAlreadyRunningError(existingOwner);
114
+ }
72
115
  const persistedDeliveryCursor = readDeliveryCursorFile(paths.deliveryCursorFile) ?? 0;
73
116
  connector.setDeliveryCursor(persistedDeliveryCursor);
74
117
  logger.info("delivery_cursor_loaded", {
@@ -1569,6 +1612,24 @@ export async function startBrokerDaemon(opts) {
1569
1612
  }
1570
1613
  },
1571
1614
  });
1615
+ const ownerAfterBind = await findHealthyDiscoveryOwner(paths.discoveryFile);
1616
+ if (ownerAfterBind) {
1617
+ logger.info("broker_already_running_after_ipc_bind", {
1618
+ pid: ownerAfterBind.pid,
1619
+ ipcUrl: ownerAfterBind.ipcUrl,
1620
+ });
1621
+ try {
1622
+ await ipc.close();
1623
+ }
1624
+ catch {
1625
+ }
1626
+ try {
1627
+ connector.disconnect();
1628
+ }
1629
+ catch {
1630
+ }
1631
+ throw new BrokerAlreadyRunningError(ownerAfterBind);
1632
+ }
1572
1633
  const inboundProcessing = new Set();
1573
1634
  const blockedInboundSeqs = new Set();
1574
1635
  const asyncAcceptedInboundSeqs = new Set();
@@ -1857,13 +1918,14 @@ export async function startBrokerDaemon(opts) {
1857
1918
  deferredDeliveryPendingUpTo = undefined;
1858
1919
  connector.ackDelivery(payload.upTo);
1859
1920
  });
1860
- writeDiscoveryFile(paths.discoveryFile, {
1921
+ const ownDiscovery = {
1861
1922
  version: 1,
1862
1923
  pid: process.pid,
1863
1924
  startedAt: new Date().toISOString(),
1864
1925
  ipcUrl: ipc.ipcUrl,
1865
1926
  bearerToken,
1866
- });
1927
+ };
1928
+ writeDiscoveryFile(paths.discoveryFile, ownDiscovery);
1867
1929
  logger.info("discovery_written", { path: paths.discoveryFile });
1868
1930
  let servicesEventClient;
1869
1931
  if (servicesEventClientFactory) {
@@ -1937,7 +1999,13 @@ export async function startBrokerDaemon(opts) {
1937
1999
  catch {
1938
2000
  }
1939
2001
  }));
1940
- deleteDiscoveryFile(paths.discoveryFile);
2002
+ const discoveryDeleted = deleteDiscoveryFileIfOwned(paths.discoveryFile, ownDiscovery);
2003
+ if (!discoveryDeleted) {
2004
+ logger.warn("discovery_delete_skipped_not_owner", {
2005
+ path: paths.discoveryFile,
2006
+ pid: process.pid,
2007
+ });
2008
+ }
1941
2009
  try {
1942
2010
  await ipc.close();
1943
2011
  }
@@ -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;
@@ -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;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"}
@@ -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";
@@ -273,6 +273,9 @@ export async function main() {
273
273
  const argvUrl = process.argv[1] ? pathToFileURL(process.argv[1]).href : "";
274
274
  if (import.meta.url === argvUrl) {
275
275
  main().catch((err) => {
276
+ if (err instanceof BrokerAlreadyRunningError) {
277
+ process.exit(0);
278
+ }
276
279
  console.error("broker_entry_fatal", err);
277
280
  process.exit(1);
278
281
  });
@@ -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.8";
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.8";
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.8",
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)",