@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.
- package/dist/broker/broker-daemon.d.ts +6 -0
- package/dist/broker/broker-daemon.d.ts.map +1 -1
- package/dist/broker/broker-daemon.js +196 -17
- 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 +1 -0
- package/dist/broker/entry.d.ts.map +1 -1
- package/dist/broker/entry.js +46 -2
- package/dist/broker/ipc-server.d.ts +6 -1
- package/dist/broker/ipc-server.d.ts.map +1 -1
- package/dist/broker/ipc-server.js +26 -2
- package/dist/broker/version-handshake.d.ts +2 -1
- package/dist/broker/version-handshake.d.ts.map +1 -1
- package/dist/broker/version-handshake.js +2 -1
- package/dist/broker-client/broker-client.d.ts +13 -0
- package/dist/broker-client/broker-client.d.ts.map +1 -1
- package/dist/broker-client/broker-client.js +92 -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";
|
|
@@ -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":"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";
|
|
@@ -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 (!
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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;
|
package/dist/broker/entry.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
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";
|
|
@@ -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
|
-
|
|
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":"AAyBA,OAAO,EAAE,KAAK,OAAO,EAAE,SAAS,EAAmB,MAAM,IAAI,CAAC;AAC9D,OAAO,KAAK,EACV,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EACV,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,2CAA2C,CAAC;
|
|
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.
|
|
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;
|
|
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.
|
|
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":"AAiBA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,KAAK,EACV,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EACL,KAAK,sBAAsB,EAE3B,KAAK,eAAe,EACrB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EACV,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,+BAA+B,CAAC;AAcvC,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,EAAG,uBAAuB,CAAU;IACjD,QAAQ,CAAC,SAAS,EAAG,IAAI,CAAU;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAOD,qBAAa,gCAAiC,SAAQ,KAAK;IACzD,QAAQ,CAAC,IAAI,EAAG,gCAAgC,CAAU;IAC1D,QAAQ,CAAC,SAAS,EAAG,KAAK,CAAU;gBACxB,OAAO,EAAE,MAAM;CAI5B;AAgBD,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IAErB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;
|
|
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:
|
|
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":"
|
|
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.9";
|
|
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.9";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@masons/runtime-broker",
|
|
3
|
-
"version": "0.2.
|
|
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)",
|