@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.
- package/dist/broker/broker-daemon.d.ts +5 -0
- package/dist/broker/broker-daemon.d.ts.map +1 -1
- package/dist/broker/broker-daemon.js +72 -4
- package/dist/broker/discovery-file.d.ts +3 -0
- package/dist/broker/discovery-file.d.ts.map +1 -1
- package/dist/broker/discovery-file.js +14 -0
- package/dist/broker/entry.d.ts.map +1 -1
- package/dist/broker/entry.js +4 -1
- package/dist/broker-client/lazy-spawn.d.ts.map +1 -1
- package/dist/broker-client/lazy-spawn.js +136 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -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":"AA2BA,OAAO,EAAqB,KAAK,IAAI,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAQjD,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;
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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":"
|
|
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"}
|
package/dist/broker/entry.js
CHANGED
|
@@ -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":"
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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)",
|