@masons/runtime-broker 0.2.6 → 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 +83 -5
- 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
- package/dist/broker/background-assignment-ledger.d.ts +0 -27
- package/dist/broker/background-assignment-ledger.d.ts.map +0 -1
- package/dist/broker/background-assignment-ledger.js +0 -125
|
@@ -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,8 +4,8 @@ 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 {
|
|
8
|
-
import { EndpointRegistry } from "./endpoint-registry.js";
|
|
7
|
+
import { deleteDiscoveryFileIfOwned, mintBearerToken, readDiscoveryFile, writeDiscoveryFile, } from "./discovery-file.js";
|
|
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";
|
|
11
11
|
import { BrokerHttpError, pushToPlugin, startIPCServer, } from "./ipc-server.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", {
|
|
@@ -170,6 +213,9 @@ export async function startBrokerDaemon(opts) {
|
|
|
170
213
|
const endpointCorrelationKey = (endpointId, correlationId) => `${endpointId}\u0000${correlationId}`;
|
|
171
214
|
const isBackgroundExchangeTarget = (entry) => Boolean(entry.runtime_capabilities?.includes("background_exchange_v1") &&
|
|
172
215
|
isBackgroundExchangeMode(entry.background_exchange_mode));
|
|
216
|
+
const requiresForegroundChannel = (entry) => entry.execution_surface !== "headless";
|
|
217
|
+
const canAcceptRuntimeMailboxDispatch = (entry) => !requiresForegroundChannel(entry) ||
|
|
218
|
+
Boolean(entry.runtime_capabilities?.includes("foreground_channel_v1"));
|
|
173
219
|
const rememberRuntimeAssignmentCorrelation = (endpointId, correlationId, assignmentKey) => {
|
|
174
220
|
if (typeof correlationId !== "string" || correlationId.length === 0) {
|
|
175
221
|
return;
|
|
@@ -772,6 +818,13 @@ export async function startBrokerDaemon(opts) {
|
|
|
772
818
|
detail: `target endpoint not found: ${targetEndpointId}`,
|
|
773
819
|
};
|
|
774
820
|
}
|
|
821
|
+
if (!canAcceptRuntimeMailboxDispatch(target)) {
|
|
822
|
+
undispatchedInbox.tryAdd(taken);
|
|
823
|
+
return {
|
|
824
|
+
ok: false,
|
|
825
|
+
detail: `target endpoint is not foreground-channel capable: ${targetEndpointId}`,
|
|
826
|
+
};
|
|
827
|
+
}
|
|
775
828
|
let targetRef = event.target_ref;
|
|
776
829
|
if (!targetRef) {
|
|
777
830
|
if (target.background_exchange_mode === "resident_endpoint") {
|
|
@@ -1559,6 +1612,24 @@ export async function startBrokerDaemon(opts) {
|
|
|
1559
1612
|
}
|
|
1560
1613
|
},
|
|
1561
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
|
+
}
|
|
1562
1633
|
const inboundProcessing = new Set();
|
|
1563
1634
|
const blockedInboundSeqs = new Set();
|
|
1564
1635
|
const asyncAcceptedInboundSeqs = new Set();
|
|
@@ -1847,13 +1918,14 @@ export async function startBrokerDaemon(opts) {
|
|
|
1847
1918
|
deferredDeliveryPendingUpTo = undefined;
|
|
1848
1919
|
connector.ackDelivery(payload.upTo);
|
|
1849
1920
|
});
|
|
1850
|
-
|
|
1921
|
+
const ownDiscovery = {
|
|
1851
1922
|
version: 1,
|
|
1852
1923
|
pid: process.pid,
|
|
1853
1924
|
startedAt: new Date().toISOString(),
|
|
1854
1925
|
ipcUrl: ipc.ipcUrl,
|
|
1855
1926
|
bearerToken,
|
|
1856
|
-
}
|
|
1927
|
+
};
|
|
1928
|
+
writeDiscoveryFile(paths.discoveryFile, ownDiscovery);
|
|
1857
1929
|
logger.info("discovery_written", { path: paths.discoveryFile });
|
|
1858
1930
|
let servicesEventClient;
|
|
1859
1931
|
if (servicesEventClientFactory) {
|
|
@@ -1927,7 +1999,13 @@ export async function startBrokerDaemon(opts) {
|
|
|
1927
1999
|
catch {
|
|
1928
2000
|
}
|
|
1929
2001
|
}));
|
|
1930
|
-
|
|
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
|
+
}
|
|
1931
2009
|
try {
|
|
1932
2010
|
await ipc.close();
|
|
1933
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)",
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { UndispatchedMessage } from "./undispatched-inbox.js";
|
|
2
|
-
export type DurableBackgroundAssignmentState = "assigned" | "committing" | "committed";
|
|
3
|
-
export type DurableBackgroundAssignmentKind = "mailbox_dispatch" | "broker_route";
|
|
4
|
-
export interface DurableBackgroundAssignment {
|
|
5
|
-
kind: DurableBackgroundAssignmentKind;
|
|
6
|
-
state: DurableBackgroundAssignmentState;
|
|
7
|
-
assignmentId: string;
|
|
8
|
-
sourceMessageId: string;
|
|
9
|
-
endpointId: string;
|
|
10
|
-
message: UndispatchedMessage;
|
|
11
|
-
assignedAt: number;
|
|
12
|
-
acceptedAt?: number;
|
|
13
|
-
committedAt?: number;
|
|
14
|
-
deliverySeq?: number;
|
|
15
|
-
routeKind?: "active_endpoint" | "reconnecting_endpoint";
|
|
16
|
-
}
|
|
17
|
-
export interface BackgroundAssignmentLedger {
|
|
18
|
-
get(sourceMessageId: string): DurableBackgroundAssignment | undefined;
|
|
19
|
-
upsert(record: DurableBackgroundAssignment): boolean;
|
|
20
|
-
delete(sourceMessageId: string): boolean;
|
|
21
|
-
list(): DurableBackgroundAssignment[];
|
|
22
|
-
}
|
|
23
|
-
export interface BackgroundAssignmentLedgerOptions {
|
|
24
|
-
persistFile?: string;
|
|
25
|
-
}
|
|
26
|
-
export declare function createBackgroundAssignmentLedger(opts?: BackgroundAssignmentLedgerOptions): BackgroundAssignmentLedger;
|
|
27
|
-
//# sourceMappingURL=background-assignment-ledger.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"background-assignment-ledger.d.ts","sourceRoot":"","sources":["../../src/broker/background-assignment-ledger.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,MAAM,MAAM,gCAAgC,GACxC,UAAU,GACV,YAAY,GACZ,WAAW,CAAC;AAEhB,MAAM,MAAM,+BAA+B,GACvC,kBAAkB,GAClB,cAAc,CAAC;AAEnB,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,+BAA+B,CAAC;IACtC,KAAK,EAAE,gCAAgC,CAAC;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,iBAAiB,GAAG,uBAAuB,CAAC;CACzD;AAED,MAAM,WAAW,0BAA0B;IACzC,GAAG,CAAC,eAAe,EAAE,MAAM,GAAG,2BAA2B,GAAG,SAAS,CAAC;IACtE,MAAM,CAAC,MAAM,EAAE,2BAA2B,GAAG,OAAO,CAAC;IACrD,MAAM,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC;IACzC,IAAI,IAAI,2BAA2B,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,iCAAiC;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,gCAAgC,CAC9C,IAAI,GAAE,iCAAsC,GAC3C,0BAA0B,CAkF5B"}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { closeSync, fdatasyncSync, mkdirSync, openSync, readFileSync, renameSync, writeSync, } from "node:fs";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
export function createBackgroundAssignmentLedger(opts = {}) {
|
|
4
|
-
const persistFile = opts.persistFile;
|
|
5
|
-
const bySourceMessageId = new Map();
|
|
6
|
-
const snapshot = () => [
|
|
7
|
-
...bySourceMessageId.values(),
|
|
8
|
-
];
|
|
9
|
-
const persist = () => {
|
|
10
|
-
if (!persistFile)
|
|
11
|
-
return;
|
|
12
|
-
mkdirSync(dirname(persistFile), { recursive: true, mode: 0o700 });
|
|
13
|
-
const tmp = `${persistFile}.tmp`;
|
|
14
|
-
const fd = openSync(tmp, "w", 0o600);
|
|
15
|
-
try {
|
|
16
|
-
writeSync(fd, `${JSON.stringify({ version: 1, assignments: snapshot() })}\n`);
|
|
17
|
-
fdatasyncSync(fd);
|
|
18
|
-
}
|
|
19
|
-
finally {
|
|
20
|
-
closeSync(fd);
|
|
21
|
-
}
|
|
22
|
-
renameSync(tmp, persistFile);
|
|
23
|
-
};
|
|
24
|
-
if (persistFile) {
|
|
25
|
-
try {
|
|
26
|
-
const raw = readFileSync(persistFile, "utf8").trim();
|
|
27
|
-
if (raw.length > 0) {
|
|
28
|
-
const parsed = JSON.parse(raw);
|
|
29
|
-
if (parsed.version !== 1 || !Array.isArray(parsed.assignments)) {
|
|
30
|
-
throw new Error("invalid persisted background assignment ledger");
|
|
31
|
-
}
|
|
32
|
-
for (const assignment of parsed.assignments) {
|
|
33
|
-
if (!isDurableBackgroundAssignment(assignment)) {
|
|
34
|
-
throw new Error("invalid persisted background assignment ledger record");
|
|
35
|
-
}
|
|
36
|
-
bySourceMessageId.set(assignment.sourceMessageId, assignment);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
catch (err) {
|
|
41
|
-
if (!isNodeErr(err, "ENOENT"))
|
|
42
|
-
throw err;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
get(sourceMessageId) {
|
|
47
|
-
return bySourceMessageId.get(sourceMessageId);
|
|
48
|
-
},
|
|
49
|
-
upsert(record) {
|
|
50
|
-
const prior = bySourceMessageId.get(record.sourceMessageId);
|
|
51
|
-
bySourceMessageId.set(record.sourceMessageId, record);
|
|
52
|
-
try {
|
|
53
|
-
persist();
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
if (prior)
|
|
58
|
-
bySourceMessageId.set(record.sourceMessageId, prior);
|
|
59
|
-
else
|
|
60
|
-
bySourceMessageId.delete(record.sourceMessageId);
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
delete(sourceMessageId) {
|
|
65
|
-
const prior = bySourceMessageId.get(sourceMessageId);
|
|
66
|
-
if (!prior)
|
|
67
|
-
return true;
|
|
68
|
-
bySourceMessageId.delete(sourceMessageId);
|
|
69
|
-
try {
|
|
70
|
-
persist();
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
bySourceMessageId.set(sourceMessageId, prior);
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
list() {
|
|
79
|
-
return snapshot();
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function isDurableBackgroundAssignment(value) {
|
|
84
|
-
if (!value || typeof value !== "object")
|
|
85
|
-
return false;
|
|
86
|
-
const record = value;
|
|
87
|
-
return ((record.kind === "mailbox_dispatch" || record.kind === "broker_route") &&
|
|
88
|
-
(record.state === "assigned" ||
|
|
89
|
-
record.state === "committing" ||
|
|
90
|
-
record.state === "committed") &&
|
|
91
|
-
typeof record.assignmentId === "string" &&
|
|
92
|
-
typeof record.sourceMessageId === "string" &&
|
|
93
|
-
typeof record.endpointId === "string" &&
|
|
94
|
-
typeof record.assignedAt === "number" &&
|
|
95
|
-
isUndispatchedMessageShape(record.message) &&
|
|
96
|
-
(record.acceptedAt === undefined ||
|
|
97
|
-
typeof record.acceptedAt === "number") &&
|
|
98
|
-
(record.committedAt === undefined ||
|
|
99
|
-
typeof record.committedAt === "number") &&
|
|
100
|
-
(record.deliverySeq === undefined ||
|
|
101
|
-
typeof record.deliverySeq === "number") &&
|
|
102
|
-
(record.routeKind === undefined ||
|
|
103
|
-
record.routeKind === "active_endpoint" ||
|
|
104
|
-
record.routeKind === "reconnecting_endpoint"));
|
|
105
|
-
}
|
|
106
|
-
function isUndispatchedMessageShape(value) {
|
|
107
|
-
if (!value || typeof value !== "object")
|
|
108
|
-
return false;
|
|
109
|
-
const record = value;
|
|
110
|
-
return (typeof record.id === "string" &&
|
|
111
|
-
typeof record.arrived_at === "string" &&
|
|
112
|
-
typeof record.sender_address === "string" &&
|
|
113
|
-
typeof record.content === "string" &&
|
|
114
|
-
typeof record.content_type === "string" &&
|
|
115
|
-
!!record.metadata &&
|
|
116
|
-
typeof record.metadata === "object" &&
|
|
117
|
-
(record.reason === "unaddressed" ||
|
|
118
|
-
record.reason === "closed_session_continuation"));
|
|
119
|
-
}
|
|
120
|
-
function isNodeErr(err, code) {
|
|
121
|
-
return (typeof err === "object" &&
|
|
122
|
-
err !== null &&
|
|
123
|
-
"code" in err &&
|
|
124
|
-
err.code === code);
|
|
125
|
-
}
|