@camstack/system 1.1.18 → 1.1.20
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/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +57 -51
- package/dist/index.mjs +57 -51
- package/dist/kernel/moleculer/event-bus-core.d.ts +41 -4
- package/dist/kernel/moleculer/event-bus.d.ts +11 -2
- package/dist/kernel/moleculer/process-service.d.ts +1 -1
- package/dist/kernel/transport/child-cap-protocol.d.ts +25 -4
- package/dist/kernel/transport/local-child-client.d.ts +34 -2
- package/dist/kernel/transport/local-child-registry.d.ts +57 -2
- package/dist/kernel/transport/uds-event-bridge.d.ts +10 -0
- package/dist/kernel/transport/uds-event-bus.d.ts +6 -0
- package/dist/{manifest-python-deps-lZd5B4FS.js → manifest-python-deps-PbdHKDpj.js} +315 -117
- package/dist/{manifest-python-deps-DOAh19rY.mjs → manifest-python-deps-X00KVYHa.mjs} +315 -117
- package/dist/notification/notification-service.d.ts +29 -23
- package/package.json +1 -1
package/dist/addon-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
2
|
+
const require_manifest_python_deps = require("./manifest-python-deps-PbdHKDpj.js");
|
|
3
3
|
const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
|
|
4
4
|
let node_fs = require("node:fs");
|
|
5
5
|
node_fs = require_chunk.__toESM(node_fs);
|
package/dist/addon-runner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as getWorkerNativeCapProvider, N as createUdsLoggerWithControl, P as LocalChildClient, _t as resolveAddonClass, et as getWorkerNativeCapSnapshot, gt as installManifestNativeDeps, i as createUdsAddonContext, it as validateProviderRegistrations, nt as setWorkerNativeCapsChangeListener, t as installManifestPythonDeps } from "./manifest-python-deps-
|
|
1
|
+
import { $ as getWorkerNativeCapProvider, N as createUdsLoggerWithControl, P as LocalChildClient, _t as resolveAddonClass, et as getWorkerNativeCapSnapshot, gt as installManifestNativeDeps, i as createUdsAddonContext, it as validateProviderRegistrations, nt as setWorkerNativeCapsChangeListener, t as installManifestPythonDeps } from "./manifest-python-deps-X00KVYHa.mjs";
|
|
2
2
|
import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
|
|
3
3
|
import { register } from "node:module";
|
|
4
4
|
import * as fs from "node:fs";
|
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,7 @@ export type { UserRecord, UserStorageAccess, UserConfigReader } from './auth/use
|
|
|
72
72
|
export { ScopedTokenManager } from './auth/scoped-token-manager.js';
|
|
73
73
|
export { scopesAllowDeviceCap } from './auth/scope-matcher.js';
|
|
74
74
|
export { NotificationService } from './notification/notification-service.js';
|
|
75
|
+
export type { NotificationTargetRef } from './notification/notification-service.js';
|
|
75
76
|
export { ToastService } from './notification/toast-service.js';
|
|
76
77
|
export type { Unsubscribe } from './notification/toast-service.js';
|
|
77
78
|
export { AddonRouteRegistry } from './addon-routes/addon-route-registry.js';
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const require_builtins_local_auth_local_auth_addon = require("./builtins/local-a
|
|
|
20
20
|
require("./builtins/local-auth/index.js");
|
|
21
21
|
const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
|
|
22
22
|
require("./builtins/device-manager/index.js");
|
|
23
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
23
|
+
const require_manifest_python_deps = require("./manifest-python-deps-PbdHKDpj.js");
|
|
24
24
|
const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
|
|
25
25
|
let _camstack_types_node = require("@camstack/types/node");
|
|
26
26
|
let node_http = require("node:http");
|
|
@@ -1493,16 +1493,17 @@ function scopesAllowDeviceCap(scopes, access) {
|
|
|
1493
1493
|
//#endregion
|
|
1494
1494
|
//#region src/notification/notification-service.ts
|
|
1495
1495
|
/**
|
|
1496
|
-
* Central notification service that routes notifications to
|
|
1497
|
-
* Framework-agnostic —
|
|
1496
|
+
* Central notification service that routes canonical notifications to
|
|
1497
|
+
* configured `(addonId, targetId)` targets. Framework-agnostic —
|
|
1498
|
+
* dependencies injected via constructor.
|
|
1498
1499
|
*
|
|
1499
|
-
*
|
|
1500
|
-
*
|
|
1501
|
-
*
|
|
1500
|
+
* Providers are resolved from the ICapabilityRegistry by `addonId` on each
|
|
1501
|
+
* call (proxy pattern). Falls back to a local map when no registry is
|
|
1502
|
+
* provided (tests / backward compat).
|
|
1502
1503
|
*/
|
|
1503
1504
|
var NotificationService = class {
|
|
1504
1505
|
logger;
|
|
1505
|
-
|
|
1506
|
+
localProviders = /* @__PURE__ */ new Map();
|
|
1506
1507
|
routing = /* @__PURE__ */ new Map();
|
|
1507
1508
|
rateLimits = /* @__PURE__ */ new Map();
|
|
1508
1509
|
lastSent = /* @__PURE__ */ new Map();
|
|
@@ -1510,35 +1511,28 @@ var NotificationService = class {
|
|
|
1510
1511
|
constructor(logger) {
|
|
1511
1512
|
this.logger = logger;
|
|
1512
1513
|
}
|
|
1513
|
-
/** Set the registry for live
|
|
1514
|
+
/** Set the registry for live provider lookup. Called once during boot. */
|
|
1514
1515
|
setRegistry(registry) {
|
|
1515
1516
|
this.registry = registry;
|
|
1516
1517
|
}
|
|
1517
|
-
/** Resolve
|
|
1518
|
-
|
|
1519
|
-
if (this.registry)
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
}
|
|
1527
|
-
/** Register an output in the local fallback map (used when no registry is set). */
|
|
1528
|
-
registerLocalOutput(output) {
|
|
1529
|
-
this.localOutputs.set(output.id, output);
|
|
1530
|
-
this.logger.info("Notification output added", { meta: {
|
|
1531
|
-
name: output.name,
|
|
1532
|
-
outputId: output.id
|
|
1533
|
-
} });
|
|
1518
|
+
/** Resolve the collection provider for an addon — registry first, then local map. */
|
|
1519
|
+
resolveProvider(addonId) {
|
|
1520
|
+
if (this.registry) return this.registry.getProviderByAddon("notification-output", addonId) ?? void 0;
|
|
1521
|
+
return this.localProviders.get(addonId);
|
|
1522
|
+
}
|
|
1523
|
+
/** Register a provider in the local fallback map (used when no registry is set). */
|
|
1524
|
+
registerLocalProvider(addonId, provider) {
|
|
1525
|
+
this.localProviders.set(addonId, provider);
|
|
1526
|
+
this.logger.info("Notification provider added", { meta: { addonId } });
|
|
1534
1527
|
}
|
|
1535
|
-
/** Remove
|
|
1536
|
-
|
|
1537
|
-
this.
|
|
1538
|
-
this.logger.info("Notification
|
|
1528
|
+
/** Remove a provider from the local fallback map. */
|
|
1529
|
+
unregisterLocalProvider(addonId) {
|
|
1530
|
+
this.localProviders.delete(addonId);
|
|
1531
|
+
this.logger.info("Notification provider removed", { meta: { addonId } });
|
|
1539
1532
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1533
|
+
/** Route a category to a set of `(addonId, targetId)` targets. */
|
|
1534
|
+
setRouting(category, targets) {
|
|
1535
|
+
this.routing.set(category, [...targets]);
|
|
1542
1536
|
}
|
|
1543
1537
|
setRateLimit(category, minIntervalMs) {
|
|
1544
1538
|
this.rateLimits.set(category, minIntervalMs);
|
|
@@ -1554,38 +1548,36 @@ var NotificationService = class {
|
|
|
1554
1548
|
return;
|
|
1555
1549
|
}
|
|
1556
1550
|
}
|
|
1557
|
-
const
|
|
1558
|
-
if (
|
|
1551
|
+
const targets = this.routing.get(category) ?? this.routing.get("*") ?? [];
|
|
1552
|
+
if (targets.length === 0) {
|
|
1559
1553
|
this.logger.debug("No routing configured for category", { meta: { category } });
|
|
1560
1554
|
return;
|
|
1561
1555
|
}
|
|
1562
|
-
const currentOutputs = this.outputs;
|
|
1563
1556
|
this.lastSent.set(rateLimitKey, notification.timestamp);
|
|
1564
|
-
|
|
1557
|
+
const { category: _category, timestamp: _timestamp, ...canonical } = notification;
|
|
1558
|
+
await Promise.allSettled(targets.map(async (ref) => {
|
|
1559
|
+
const provider = this.resolveProvider(ref.addonId);
|
|
1560
|
+
if (!provider) {
|
|
1561
|
+
this.logger.debug("No provider for target", { meta: { addonId: ref.addonId } });
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1565
1564
|
try {
|
|
1566
|
-
await
|
|
1565
|
+
await provider.send({
|
|
1566
|
+
targetId: ref.targetId,
|
|
1567
|
+
notification: canonical
|
|
1568
|
+
});
|
|
1567
1569
|
} catch (err) {
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
error:
|
|
1570
|
+
this.logger.error("Notification target failed", { meta: {
|
|
1571
|
+
addonId: ref.addonId,
|
|
1572
|
+
targetId: ref.targetId,
|
|
1573
|
+
error: (0, _camstack_types.errMsg)(err)
|
|
1572
1574
|
} });
|
|
1573
1575
|
}
|
|
1574
1576
|
}));
|
|
1575
1577
|
}
|
|
1576
|
-
getOutputs() {
|
|
1577
|
-
return Array.from(this.outputs.values()).map(({ id, name, icon }) => ({
|
|
1578
|
-
id,
|
|
1579
|
-
name,
|
|
1580
|
-
icon
|
|
1581
|
-
}));
|
|
1582
|
-
}
|
|
1583
1578
|
getRouting() {
|
|
1584
1579
|
return this.routing;
|
|
1585
1580
|
}
|
|
1586
|
-
getOutput(id) {
|
|
1587
|
-
return this.outputs.get(id);
|
|
1588
|
-
}
|
|
1589
1581
|
};
|
|
1590
1582
|
//#endregion
|
|
1591
1583
|
//#region src/notification/toast-service.ts
|
|
@@ -92366,6 +92358,16 @@ function readAddonHeapProfile(spec) {
|
|
|
92366
92358
|
heapProfileCache.set(cacheKey, profile);
|
|
92367
92359
|
return profile;
|
|
92368
92360
|
}
|
|
92361
|
+
/**
|
|
92362
|
+
* Drop the cached heap profile for each addon so the NEXT `readAddonHeapProfile`
|
|
92363
|
+
* re-reads the manifest from disk. Called on operator-driven spawns / restarts
|
|
92364
|
+
* (a deploy may have changed the addon's `execution.heapProfile`); the
|
|
92365
|
+
* crash-respawn fast path deliberately does NOT invalidate — a crashing runner
|
|
92366
|
+
* has the same on-disk manifest, so re-reading it is pure overhead.
|
|
92367
|
+
*/
|
|
92368
|
+
function invalidateHeapProfiles(addons) {
|
|
92369
|
+
for (const spec of addons) heapProfileCache.delete(`${spec.addonDir}::${spec.addonId}`);
|
|
92370
|
+
}
|
|
92369
92371
|
function manifestHasAddon(parsed, addonId) {
|
|
92370
92372
|
return readManifestAddons(parsed).some((a) => a.id === addonId);
|
|
92371
92373
|
}
|
|
@@ -92486,12 +92488,13 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92486
92488
|
CAMSTACK_RUNNER_ADDONS: JSON.stringify(addons),
|
|
92487
92489
|
CAMSTACK_PARENT_NODE_ID: parentNodeId,
|
|
92488
92490
|
CAMSTACK_DATA_DIR: dataDir,
|
|
92489
|
-
CAMSTACK_LOG_LEVEL: "
|
|
92491
|
+
CAMSTACK_LOG_LEVEL: process.env["CAMSTACK_LOG_LEVEL"] ?? "info",
|
|
92490
92492
|
...parentTcpPort !== void 0 ? { CAMSTACK_PARENT_TCP_PORT: String(parentTcpPort) } : {},
|
|
92491
92493
|
...parentUdsPath !== void 0 ? { CAMSTACK_PARENT_UDS_PATH: parentUdsPath } : {},
|
|
92492
92494
|
...env
|
|
92493
92495
|
};
|
|
92494
92496
|
const heapFlags = runnerHeapFlags(addons);
|
|
92497
|
+
capturedBroker?.logger.info(`[${runnerId}] heap profile: ${isHeavyRunner(addons) ? "heavy" : "light"} flags=[${heapFlags.join(" ")}]`);
|
|
92495
92498
|
const child = spawnFn(process.execPath, [...heapFlags, runnerPath], {
|
|
92496
92499
|
env: childEnv,
|
|
92497
92500
|
stdio: [
|
|
@@ -92566,6 +92569,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92566
92569
|
const { runnerId, addons, env } = ctx.params;
|
|
92567
92570
|
if (processes.has(runnerId)) throw new Error(`Runner "${runnerId}" already running`);
|
|
92568
92571
|
if (!Array.isArray(addons) || addons.length === 0) throw new Error(`spawnRunner requires non-empty addons array`);
|
|
92572
|
+
invalidateHeapProfiles(addons);
|
|
92569
92573
|
const entry = spawnRunner(runnerId, addons, env);
|
|
92570
92574
|
return {
|
|
92571
92575
|
pid: entry.pid,
|
|
@@ -92640,6 +92644,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92640
92644
|
processes.delete(name);
|
|
92641
92645
|
crashSupervisor.reset(name);
|
|
92642
92646
|
forceMoleculerDisconnect(ctx.broker, deadNodeId);
|
|
92647
|
+
invalidateHeapProfiles(runnerAddons);
|
|
92643
92648
|
const restarted = spawnRunner(name, runnerAddons);
|
|
92644
92649
|
restarted.restartCount = prevRestartCount + 1;
|
|
92645
92650
|
return {
|
|
@@ -92697,6 +92702,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92697
92702
|
}
|
|
92698
92703
|
processes.delete(name);
|
|
92699
92704
|
crashSupervisor.reset(name);
|
|
92705
|
+
invalidateHeapProfiles(runnerAddons);
|
|
92700
92706
|
const respawned = spawnRunner(name, runnerAddons);
|
|
92701
92707
|
respawned.restartCount = prevRestartCount + 1;
|
|
92702
92708
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { LocalAuthAddon, a as require_ms, c as __esmMin, d as __toCommonJS, f as
|
|
|
18
18
|
import "./builtins/local-auth/index.mjs";
|
|
19
19
|
import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
|
|
20
20
|
import "./builtins/device-manager/index.mjs";
|
|
21
|
-
import { A as createUdsEventBus, B as CapRouteError, C as ipcParentLink, D as createHubCapForwardService, E as HUB_CAP_FWD_SERVICE, F as LocalChildRegistry, G as UdsLocalTransportServer, H as callWithServiceDiscovery, I as UDS_NO_ROUTE_PREFIX, J as FrameDecoder, K as SocketChannel, L as AGENT_CAP_FWD_ACTION, M as createUdsLogger, N as createUdsLoggerWithControl, O as createParentUnownedCallHandler, P as LocalChildClient, Q as createBrokerDeviceManagerApi, R as AGENT_CAP_FWD_SERVICE, S as ipcChildLink, T as HUB_CAP_FWD_ACTION, U as createLocalTransport, V as classifyCapRoute, W as UdsLocalTransportClient, X as buildNativeCapProxy, Y as encodeFrame, Z as buildUdsNativeCapProxy, _ as __resetCapUsageRegistryForTests, _t as resolveAddonClass, a as getWorkerDeviceRegistry, at as NATIVE_PROVIDER_SERVICE_INFIX, b as brokerTransportLink, c as setHubConnected, ct as capBareAction, d as registerEventBusService, dt as deserializeTypedArrays, f as AddonDepsManager, ft as serializeTypedArrays, g as CapUsageRegistry, gt as installManifestNativeDeps, h as createHwAccelService, ht as CapabilityUnavailableError, i as createUdsAddonContext, it as validateProviderRegistrations, j as udsChildLogToWorkerEntry, k as createUdsEventBridge, l as EVENT_TOPIC_PREFIX, lt as capServiceName, m as resolveHwAccel, mt as CapabilityHandle, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as capActionName, p as createKernelHwAccel, pt as DeviceRegistry, q as localEndpointPath, r as createAddonContext, rt as createAddonService, s as getOrInitReadinessRegistryForClient, st as capActionSuffix, t as installManifestPythonDeps, tt as mountNativeCapService, u as getBrokerEventBus, ut as parseCapAction, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as CapRouteResolver } from "./manifest-python-deps-
|
|
21
|
+
import { A as createUdsEventBus, B as CapRouteError, C as ipcParentLink, D as createHubCapForwardService, E as HUB_CAP_FWD_SERVICE, F as LocalChildRegistry, G as UdsLocalTransportServer, H as callWithServiceDiscovery, I as UDS_NO_ROUTE_PREFIX, J as FrameDecoder, K as SocketChannel, L as AGENT_CAP_FWD_ACTION, M as createUdsLogger, N as createUdsLoggerWithControl, O as createParentUnownedCallHandler, P as LocalChildClient, Q as createBrokerDeviceManagerApi, R as AGENT_CAP_FWD_SERVICE, S as ipcChildLink, T as HUB_CAP_FWD_ACTION, U as createLocalTransport, V as classifyCapRoute, W as UdsLocalTransportClient, X as buildNativeCapProxy, Y as encodeFrame, Z as buildUdsNativeCapProxy, _ as __resetCapUsageRegistryForTests, _t as resolveAddonClass, a as getWorkerDeviceRegistry, at as NATIVE_PROVIDER_SERVICE_INFIX, b as brokerTransportLink, c as setHubConnected, ct as capBareAction, d as registerEventBusService, dt as deserializeTypedArrays, f as AddonDepsManager, ft as serializeTypedArrays, g as CapUsageRegistry, gt as installManifestNativeDeps, h as createHwAccelService, ht as CapabilityUnavailableError, i as createUdsAddonContext, it as validateProviderRegistrations, j as udsChildLogToWorkerEntry, k as createUdsEventBridge, l as EVENT_TOPIC_PREFIX, lt as capServiceName, m as resolveHwAccel, mt as CapabilityHandle, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as capActionName, p as createKernelHwAccel, pt as DeviceRegistry, q as localEndpointPath, r as createAddonContext, rt as createAddonService, s as getOrInitReadinessRegistryForClient, st as capActionSuffix, t as installManifestPythonDeps, tt as mountNativeCapService, u as getBrokerEventBus, ut as parseCapAction, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as CapRouteResolver } from "./manifest-python-deps-X00KVYHa.mjs";
|
|
22
22
|
import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
|
|
23
23
|
import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
|
|
24
24
|
import { request } from "node:http";
|
|
@@ -1485,16 +1485,17 @@ function scopesAllowDeviceCap(scopes, access) {
|
|
|
1485
1485
|
//#endregion
|
|
1486
1486
|
//#region src/notification/notification-service.ts
|
|
1487
1487
|
/**
|
|
1488
|
-
* Central notification service that routes notifications to
|
|
1489
|
-
* Framework-agnostic —
|
|
1488
|
+
* Central notification service that routes canonical notifications to
|
|
1489
|
+
* configured `(addonId, targetId)` targets. Framework-agnostic —
|
|
1490
|
+
* dependencies injected via constructor.
|
|
1490
1491
|
*
|
|
1491
|
-
*
|
|
1492
|
-
*
|
|
1493
|
-
*
|
|
1492
|
+
* Providers are resolved from the ICapabilityRegistry by `addonId` on each
|
|
1493
|
+
* call (proxy pattern). Falls back to a local map when no registry is
|
|
1494
|
+
* provided (tests / backward compat).
|
|
1494
1495
|
*/
|
|
1495
1496
|
var NotificationService = class {
|
|
1496
1497
|
logger;
|
|
1497
|
-
|
|
1498
|
+
localProviders = /* @__PURE__ */ new Map();
|
|
1498
1499
|
routing = /* @__PURE__ */ new Map();
|
|
1499
1500
|
rateLimits = /* @__PURE__ */ new Map();
|
|
1500
1501
|
lastSent = /* @__PURE__ */ new Map();
|
|
@@ -1502,35 +1503,28 @@ var NotificationService = class {
|
|
|
1502
1503
|
constructor(logger) {
|
|
1503
1504
|
this.logger = logger;
|
|
1504
1505
|
}
|
|
1505
|
-
/** Set the registry for live
|
|
1506
|
+
/** Set the registry for live provider lookup. Called once during boot. */
|
|
1506
1507
|
setRegistry(registry) {
|
|
1507
1508
|
this.registry = registry;
|
|
1508
1509
|
}
|
|
1509
|
-
/** Resolve
|
|
1510
|
-
|
|
1511
|
-
if (this.registry)
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
}
|
|
1519
|
-
/** Register an output in the local fallback map (used when no registry is set). */
|
|
1520
|
-
registerLocalOutput(output) {
|
|
1521
|
-
this.localOutputs.set(output.id, output);
|
|
1522
|
-
this.logger.info("Notification output added", { meta: {
|
|
1523
|
-
name: output.name,
|
|
1524
|
-
outputId: output.id
|
|
1525
|
-
} });
|
|
1510
|
+
/** Resolve the collection provider for an addon — registry first, then local map. */
|
|
1511
|
+
resolveProvider(addonId) {
|
|
1512
|
+
if (this.registry) return this.registry.getProviderByAddon("notification-output", addonId) ?? void 0;
|
|
1513
|
+
return this.localProviders.get(addonId);
|
|
1514
|
+
}
|
|
1515
|
+
/** Register a provider in the local fallback map (used when no registry is set). */
|
|
1516
|
+
registerLocalProvider(addonId, provider) {
|
|
1517
|
+
this.localProviders.set(addonId, provider);
|
|
1518
|
+
this.logger.info("Notification provider added", { meta: { addonId } });
|
|
1526
1519
|
}
|
|
1527
|
-
/** Remove
|
|
1528
|
-
|
|
1529
|
-
this.
|
|
1530
|
-
this.logger.info("Notification
|
|
1520
|
+
/** Remove a provider from the local fallback map. */
|
|
1521
|
+
unregisterLocalProvider(addonId) {
|
|
1522
|
+
this.localProviders.delete(addonId);
|
|
1523
|
+
this.logger.info("Notification provider removed", { meta: { addonId } });
|
|
1531
1524
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1525
|
+
/** Route a category to a set of `(addonId, targetId)` targets. */
|
|
1526
|
+
setRouting(category, targets) {
|
|
1527
|
+
this.routing.set(category, [...targets]);
|
|
1534
1528
|
}
|
|
1535
1529
|
setRateLimit(category, minIntervalMs) {
|
|
1536
1530
|
this.rateLimits.set(category, minIntervalMs);
|
|
@@ -1546,38 +1540,36 @@ var NotificationService = class {
|
|
|
1546
1540
|
return;
|
|
1547
1541
|
}
|
|
1548
1542
|
}
|
|
1549
|
-
const
|
|
1550
|
-
if (
|
|
1543
|
+
const targets = this.routing.get(category) ?? this.routing.get("*") ?? [];
|
|
1544
|
+
if (targets.length === 0) {
|
|
1551
1545
|
this.logger.debug("No routing configured for category", { meta: { category } });
|
|
1552
1546
|
return;
|
|
1553
1547
|
}
|
|
1554
|
-
const currentOutputs = this.outputs;
|
|
1555
1548
|
this.lastSent.set(rateLimitKey, notification.timestamp);
|
|
1556
|
-
|
|
1549
|
+
const { category: _category, timestamp: _timestamp, ...canonical } = notification;
|
|
1550
|
+
await Promise.allSettled(targets.map(async (ref) => {
|
|
1551
|
+
const provider = this.resolveProvider(ref.addonId);
|
|
1552
|
+
if (!provider) {
|
|
1553
|
+
this.logger.debug("No provider for target", { meta: { addonId: ref.addonId } });
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1557
1556
|
try {
|
|
1558
|
-
await
|
|
1557
|
+
await provider.send({
|
|
1558
|
+
targetId: ref.targetId,
|
|
1559
|
+
notification: canonical
|
|
1560
|
+
});
|
|
1559
1561
|
} catch (err) {
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
error:
|
|
1562
|
+
this.logger.error("Notification target failed", { meta: {
|
|
1563
|
+
addonId: ref.addonId,
|
|
1564
|
+
targetId: ref.targetId,
|
|
1565
|
+
error: errMsg(err)
|
|
1564
1566
|
} });
|
|
1565
1567
|
}
|
|
1566
1568
|
}));
|
|
1567
1569
|
}
|
|
1568
|
-
getOutputs() {
|
|
1569
|
-
return Array.from(this.outputs.values()).map(({ id, name, icon }) => ({
|
|
1570
|
-
id,
|
|
1571
|
-
name,
|
|
1572
|
-
icon
|
|
1573
|
-
}));
|
|
1574
|
-
}
|
|
1575
1570
|
getRouting() {
|
|
1576
1571
|
return this.routing;
|
|
1577
1572
|
}
|
|
1578
|
-
getOutput(id) {
|
|
1579
|
-
return this.outputs.get(id);
|
|
1580
|
-
}
|
|
1581
1573
|
};
|
|
1582
1574
|
//#endregion
|
|
1583
1575
|
//#region src/notification/toast-service.ts
|
|
@@ -92358,6 +92350,16 @@ function readAddonHeapProfile(spec) {
|
|
|
92358
92350
|
heapProfileCache.set(cacheKey, profile);
|
|
92359
92351
|
return profile;
|
|
92360
92352
|
}
|
|
92353
|
+
/**
|
|
92354
|
+
* Drop the cached heap profile for each addon so the NEXT `readAddonHeapProfile`
|
|
92355
|
+
* re-reads the manifest from disk. Called on operator-driven spawns / restarts
|
|
92356
|
+
* (a deploy may have changed the addon's `execution.heapProfile`); the
|
|
92357
|
+
* crash-respawn fast path deliberately does NOT invalidate — a crashing runner
|
|
92358
|
+
* has the same on-disk manifest, so re-reading it is pure overhead.
|
|
92359
|
+
*/
|
|
92360
|
+
function invalidateHeapProfiles(addons) {
|
|
92361
|
+
for (const spec of addons) heapProfileCache.delete(`${spec.addonDir}::${spec.addonId}`);
|
|
92362
|
+
}
|
|
92361
92363
|
function manifestHasAddon(parsed, addonId) {
|
|
92362
92364
|
return readManifestAddons(parsed).some((a) => a.id === addonId);
|
|
92363
92365
|
}
|
|
@@ -92478,12 +92480,13 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92478
92480
|
CAMSTACK_RUNNER_ADDONS: JSON.stringify(addons),
|
|
92479
92481
|
CAMSTACK_PARENT_NODE_ID: parentNodeId,
|
|
92480
92482
|
CAMSTACK_DATA_DIR: dataDir,
|
|
92481
|
-
CAMSTACK_LOG_LEVEL: "
|
|
92483
|
+
CAMSTACK_LOG_LEVEL: process.env["CAMSTACK_LOG_LEVEL"] ?? "info",
|
|
92482
92484
|
...parentTcpPort !== void 0 ? { CAMSTACK_PARENT_TCP_PORT: String(parentTcpPort) } : {},
|
|
92483
92485
|
...parentUdsPath !== void 0 ? { CAMSTACK_PARENT_UDS_PATH: parentUdsPath } : {},
|
|
92484
92486
|
...env
|
|
92485
92487
|
};
|
|
92486
92488
|
const heapFlags = runnerHeapFlags(addons);
|
|
92489
|
+
capturedBroker?.logger.info(`[${runnerId}] heap profile: ${isHeavyRunner(addons) ? "heavy" : "light"} flags=[${heapFlags.join(" ")}]`);
|
|
92487
92490
|
const child = spawnFn(process.execPath, [...heapFlags, runnerPath], {
|
|
92488
92491
|
env: childEnv,
|
|
92489
92492
|
stdio: [
|
|
@@ -92558,6 +92561,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92558
92561
|
const { runnerId, addons, env } = ctx.params;
|
|
92559
92562
|
if (processes.has(runnerId)) throw new Error(`Runner "${runnerId}" already running`);
|
|
92560
92563
|
if (!Array.isArray(addons) || addons.length === 0) throw new Error(`spawnRunner requires non-empty addons array`);
|
|
92564
|
+
invalidateHeapProfiles(addons);
|
|
92561
92565
|
const entry = spawnRunner(runnerId, addons, env);
|
|
92562
92566
|
return {
|
|
92563
92567
|
pid: entry.pid,
|
|
@@ -92632,6 +92636,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92632
92636
|
processes.delete(name);
|
|
92633
92637
|
crashSupervisor.reset(name);
|
|
92634
92638
|
forceMoleculerDisconnect(ctx.broker, deadNodeId);
|
|
92639
|
+
invalidateHeapProfiles(runnerAddons);
|
|
92635
92640
|
const restarted = spawnRunner(name, runnerAddons);
|
|
92636
92641
|
restarted.restartCount = prevRestartCount + 1;
|
|
92637
92642
|
return {
|
|
@@ -92689,6 +92694,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
92689
92694
|
}
|
|
92690
92695
|
processes.delete(name);
|
|
92691
92696
|
crashSupervisor.reset(name);
|
|
92697
|
+
invalidateHeapProfiles(runnerAddons);
|
|
92692
92698
|
const respawned = spawnRunner(name, runnerAddons);
|
|
92693
92699
|
respawned.restartCount = prevRestartCount + 1;
|
|
92694
92700
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EventFilter, SystemEvent } from '@camstack/types';
|
|
2
2
|
export interface SubscriberEntry {
|
|
3
3
|
/** Original filter — kept whole so non-category dimensions are honoured per-event. */
|
|
4
4
|
readonly filter: EventFilter | string | undefined;
|
|
@@ -18,11 +18,34 @@ export interface SharedBusState {
|
|
|
18
18
|
* high-frequency categories so the buffer only accumulates audit-grade
|
|
19
19
|
* lifecycle events.
|
|
20
20
|
*
|
|
21
|
+
* ONLY populated when `retainRecent` is true (the hub main process). On every
|
|
22
|
+
* other bus this array stays empty for the process's whole lifetime.
|
|
23
|
+
*
|
|
21
24
|
* Cleared on server restart (intentional — events are volatile by design).
|
|
22
25
|
*/
|
|
23
26
|
readonly recent: SystemEvent[];
|
|
27
|
+
/**
|
|
28
|
+
* Whether this bus retains events in `recent[]` to back the
|
|
29
|
+
* `getRecent`/audit capability.
|
|
30
|
+
*
|
|
31
|
+
* Events are one-shot (fire-and-forget): ONLY the hub main process — the
|
|
32
|
+
* single process that serves `getRecent` — needs the registry, so ONLY its
|
|
33
|
+
* broker bus sets this true. Child addon runners and remote agents leave it
|
|
34
|
+
* false: they fan every event out to their local subscribers but retain
|
|
35
|
+
* nothing, so their heap can never accumulate an unbounded recent[] backlog.
|
|
36
|
+
*
|
|
37
|
+
* Mutable so the authoritative opt-in (the hub-main `getBrokerEventBus`
|
|
38
|
+
* caller) can upgrade a bus that a non-retaining caller happened to create
|
|
39
|
+
* first — see `getSharedBusState`. Never downgraded from true → false.
|
|
40
|
+
*/
|
|
41
|
+
retainRecent: boolean;
|
|
24
42
|
}
|
|
25
|
-
|
|
43
|
+
/**
|
|
44
|
+
* @param retainRecent - `true` ONLY for the hub main broker bus (it serves
|
|
45
|
+
* `getRecent`). Defaults to `false` — the safe one-shot default for child
|
|
46
|
+
* runners + agents, which retain nothing.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createSharedBusState(retainRecent?: boolean): SharedBusState;
|
|
26
49
|
export declare function matchesPattern(pattern: string, category: string): boolean;
|
|
27
50
|
export declare function extractCategoryPattern(filter: EventFilter | string): string;
|
|
28
51
|
/**
|
|
@@ -45,9 +68,23 @@ export declare function matchesEventFilter(event: SystemEvent, filter: EventFilt
|
|
|
45
68
|
* being displaced by per-frame noise.
|
|
46
69
|
*/
|
|
47
70
|
export declare const RING_BUFFER_DENY_PATTERNS: readonly string[];
|
|
71
|
+
/**
|
|
72
|
+
* Hard cap on the `recent[]` buffer size. Even audit-grade categories that
|
|
73
|
+
* are NOT on the deny-list (e.g. `device.state-changed`) must not accumulate
|
|
74
|
+
* without bound — this is the last-N window, not a durable log. Without this
|
|
75
|
+
* cap the buffer grew forever (~26 MB/h/process across every broker, agent,
|
|
76
|
+
* and UDS-child bus), filling the hub to 16 GB.
|
|
77
|
+
*/
|
|
78
|
+
export declare const MAX_RECENT_EVENTS = 1000;
|
|
48
79
|
export declare function isHighFrequencyCategory(category: string): boolean;
|
|
49
80
|
/**
|
|
50
|
-
* Deliver `event` to all matching local subscribers and —
|
|
51
|
-
*
|
|
81
|
+
* Deliver `event` to all matching local subscribers and — ONLY on a bus that
|
|
82
|
+
* opted into retention (`state.retainRecent`, i.e. the hub main process) and
|
|
83
|
+
* unless the event is high-frequency — append it to the bounded `recent[]`
|
|
84
|
+
* ring.
|
|
85
|
+
*
|
|
86
|
+
* On a non-retaining bus (child runners, agents) the retention block is skipped
|
|
87
|
+
* entirely: events are one-shot / fire-and-forget, still fanned out to local
|
|
88
|
+
* subscribers but never accumulated. `getRecent` on such a bus returns `[]`.
|
|
52
89
|
*/
|
|
53
90
|
export declare function deliverShared(state: SharedBusState, event: SystemEvent): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ServiceBroker } from 'moleculer';
|
|
2
1
|
import { IEventBus } from '@camstack/types';
|
|
2
|
+
import { ServiceBroker } from 'moleculer';
|
|
3
3
|
/**
|
|
4
4
|
* Narrow interface for the Moleculer ServiceBroker surface used in this file.
|
|
5
5
|
* moleculer's index.d.ts chains through eventemitter2 whose package.json has
|
|
@@ -49,5 +49,14 @@ export declare function clusterEventTopic(category: string): string;
|
|
|
49
49
|
* - forkable process / group runner, before `broker.start()`
|
|
50
50
|
*/
|
|
51
51
|
export declare function registerEventBusService(broker: ServiceBroker): void;
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* @param options.retainRecent - Pass `true` ONLY from the hub main process
|
|
54
|
+
* (`EventBusService.attachBroker`), the single process that serves the
|
|
55
|
+
* `getRecent`/audit capability. Every other caller (agents, child runners,
|
|
56
|
+
* the per-addon wrapper, the `$event-bus` handler) omits it → the bus retains
|
|
57
|
+
* nothing and `getRecent` returns `[]`. Events are one-shot everywhere else.
|
|
58
|
+
*/
|
|
59
|
+
export declare function getBrokerEventBus(broker: ServiceBroker, options?: {
|
|
60
|
+
retainRecent?: boolean;
|
|
61
|
+
}): IEventBus;
|
|
53
62
|
export declare function createBrokerEventBus(broker: ServiceBroker, addonId: string): IEventBus;
|
|
@@ -68,5 +68,5 @@ interface ProcessServiceDeps {
|
|
|
68
68
|
readonly spawnFn?: SpawnFn;
|
|
69
69
|
}
|
|
70
70
|
declare function createProcessService(parentNodeId: string, dataDir: string, deps?: ProcessServiceDeps, parentTcpPort?: number, parentUdsPath?: string): ServiceSchema;
|
|
71
|
-
export { createProcessService };
|
|
72
71
|
export type { ProcessInfo, SpawnedProcess };
|
|
72
|
+
export { createProcessService };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IReadinessRegistryRecord, LogLevel, LogTags, SystemEvent } from '@camstack/types';
|
|
2
2
|
/**
|
|
3
3
|
* Routing descriptor a child sends so the parent can route cap calls to it.
|
|
4
4
|
* The provider implementation never crosses the wire — only these keys do.
|
|
@@ -15,6 +15,27 @@ export interface RegisterMessage {
|
|
|
15
15
|
readonly kind: 'register';
|
|
16
16
|
readonly childId: string;
|
|
17
17
|
readonly caps: readonly ChildCapDescriptor[];
|
|
18
|
+
/**
|
|
19
|
+
* Snapshot of the child's live category-pattern subscriptions (the keys of
|
|
20
|
+
* its local `SharedBusState.handlers` map). Carried IN the register frame so
|
|
21
|
+
* the parent learns the child's subscriptions race-free at boot — no separate
|
|
22
|
+
* post-connect sync can be missed. OPTIONAL: a legacy child bundling an older
|
|
23
|
+
* `@camstack/system` omits it; the parent treats an absent field as
|
|
24
|
+
* "undeclared" and fails OPEN (broadcast every event to that child). See
|
|
25
|
+
* `EventSubUpdateMessage` for post-boot updates.
|
|
26
|
+
*/
|
|
27
|
+
readonly eventPatterns?: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Child → parent, fire-and-forget: the child's category-pattern subscription
|
|
31
|
+
* set changed after registration. FULL-SET REPLACE semantics — `patterns` is
|
|
32
|
+
* the complete current set, not a delta. Idempotent and order-safe on the
|
|
33
|
+
* in-order UDS socket (last write wins is correct). Sent via `channel.emit`
|
|
34
|
+
* like {@link ChildEventMessage}; carries no response.
|
|
35
|
+
*/
|
|
36
|
+
export interface EventSubUpdateMessage {
|
|
37
|
+
readonly kind: 'event-sub';
|
|
38
|
+
readonly patterns: readonly string[];
|
|
18
39
|
}
|
|
19
40
|
/** Child → parent outbound cap invocation — the child asks the parent to route a cap call it does not own. */
|
|
20
41
|
export interface CapCallOutMessage {
|
|
@@ -56,10 +77,10 @@ export interface ReadinessSnapshotRequest {
|
|
|
56
77
|
* Child → parent message bodies.
|
|
57
78
|
* `RegisterMessage`, `CapCallOutMessage`, and `ReadinessSnapshotRequest` travel
|
|
58
79
|
* as correlated request frames (`channel.request`) and carry a response.
|
|
59
|
-
* `ChildEventMessage` and `
|
|
60
|
-
* `channel.emit()` and do NOT carry a response.
|
|
80
|
+
* `ChildEventMessage`, `ChildLogMessage`, and `EventSubUpdateMessage` are sent
|
|
81
|
+
* fire-and-forget via `channel.emit()` and do NOT carry a response.
|
|
61
82
|
*/
|
|
62
|
-
export type ChildToParentRequest = RegisterMessage | CapCallOutMessage | ChildEventMessage | ChildLogMessage | ReadinessSnapshotRequest;
|
|
83
|
+
export type ChildToParentRequest = RegisterMessage | CapCallOutMessage | ChildEventMessage | ChildLogMessage | EventSubUpdateMessage | ReadinessSnapshotRequest;
|
|
63
84
|
/** Parent → child cap invocation. */
|
|
64
85
|
export interface CapCallMessage {
|
|
65
86
|
readonly kind: 'cap-call';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { IReadinessRegistryRecord, SystemEvent } from '@camstack/types';
|
|
2
|
+
import { AddonCallInput, CapCallInput, ChildCapDescriptor, ChildLogMessage } from './child-cap-protocol.js';
|
|
3
3
|
export interface LocalChildClientOptions {
|
|
4
4
|
/** Parent node id — selects the UDS endpoint this child connects to. */
|
|
5
5
|
readonly nodeId: string;
|
|
@@ -38,6 +38,24 @@ export declare class LocalChildClient {
|
|
|
38
38
|
* the latest set) instead of being lost or throwing.
|
|
39
39
|
*/
|
|
40
40
|
private latestCaps;
|
|
41
|
+
/**
|
|
42
|
+
* Per-owner (addonId) category-pattern subscription sets, and their union.
|
|
43
|
+
* The union is declared to the parent (in `RegisterMessage.eventPatterns`
|
|
44
|
+
* pre/at-connect, or via an `event-sub` emit post-connect) so the parent can
|
|
45
|
+
* subscription-filter its event fan-out. Per-owner keying keeps the union
|
|
46
|
+
* correct if multiple event buses ever share one client (group-runner case).
|
|
47
|
+
*/
|
|
48
|
+
private readonly patternsByOwner;
|
|
49
|
+
private latestEventPatterns;
|
|
50
|
+
/**
|
|
51
|
+
* Whether `updateEventPatterns` has ever been called. A client that never
|
|
52
|
+
* declared a subscription set (no event bus wired — e.g. a pure cap-call
|
|
53
|
+
* runner) omits `eventPatterns` from its register frame entirely, so the
|
|
54
|
+
* parent treats it as UNDECLARED and fails OPEN (broadcast-all). Once any
|
|
55
|
+
* owner declares (in production every addon context's framework
|
|
56
|
+
* subscriptions do), the register carries the real union — even if empty.
|
|
57
|
+
*/
|
|
58
|
+
private hasDeclaredEventPatterns;
|
|
41
59
|
/** Events and logs queued while the channel is not yet open. */
|
|
42
60
|
private readonly pendingEmits;
|
|
43
61
|
/** Handler for parent→child events. Registered via `onEvent`. */
|
|
@@ -60,6 +78,20 @@ export declare class LocalChildClient {
|
|
|
60
78
|
/** Callbacks registered via `onConnected`. Fired on every (re)connect. */
|
|
61
79
|
private readonly connectedHandlers;
|
|
62
80
|
constructor(options: LocalChildClientOptions);
|
|
81
|
+
/**
|
|
82
|
+
* Declare an owner's live category-pattern subscription set. Stores it
|
|
83
|
+
* per-owner (keyed by `ownerId` = addonId), recomputes the union, and — only
|
|
84
|
+
* if the union changed — declares it to the parent:
|
|
85
|
+
* - pre-connect: buffered only; `start()`'s register frame carries the set.
|
|
86
|
+
* - post-connect: emitted as a fire-and-forget `event-sub` (full-set
|
|
87
|
+
* replace).
|
|
88
|
+
* Idempotent on an unchanged union (no redundant `event-sub` frames).
|
|
89
|
+
*/
|
|
90
|
+
updateEventPatterns(ownerId: string, patterns: readonly string[]): void;
|
|
91
|
+
/** Sorted, de-duplicated union across all owners' pattern sets. */
|
|
92
|
+
private computePatternUnion;
|
|
93
|
+
/** Order-insensitive equality — both inputs are already sorted unions here. */
|
|
94
|
+
private patternsEqual;
|
|
63
95
|
/**
|
|
64
96
|
* Register a callback that fires each time the client successfully connects
|
|
65
97
|
* (or reconnects) to its parent. Multiple handlers may be registered; all
|