@camstack/system 1.2.83 → 1.2.85
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +32 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +32 -2
- package/dist/builtins/device-manager/device-meta-actions.d.ts +12 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +66 -11
- package/dist/builtins/snapshot/index.mjs +66 -11
- package/dist/builtins/snapshot/snapshot.addon.d.ts +39 -3
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +30 -10
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +31 -11
- package/dist/builtins/sqlite-storage/path-guard.d.ts +8 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/index.d.ts +5 -6
- package/dist/builtins/storage-orchestrator/index.mjs +1 -1
- package/dist/builtins/storage-orchestrator/provider-discovery.d.ts +1 -1
- package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1252 -1191
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1253 -1192
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-EAvNJD24.mjs → dist-DWuNENzg.mjs} +527 -11
- package/dist/{dist-CqiMPPs0.js → dist-DiScsb8j.js} +527 -11
- package/dist/index.js +83 -18
- package/dist/index.mjs +84 -19
- package/dist/kernel/addon-installer.d.ts +58 -6
- package/dist/kernel/moleculer/event-bus-core.d.ts +47 -1
- package/dist/kernel/transport/child-cap-protocol.d.ts +32 -0
- package/dist/kernel/transport/hub-cap-forward.d.ts +8 -0
- package/dist/kernel/transport/parent-unowned-call.d.ts +19 -0
- package/dist/{manifest-python-deps-DG_LLcZF.js → manifest-python-deps-C0zkl1Ae.js} +133 -53
- package/dist/{manifest-python-deps-C1DMs1J9.mjs → manifest-python-deps-fWzdEasI.mjs} +133 -53
- package/package.json +6 -1
|
@@ -3871,14 +3871,69 @@ function matchesPattern(pattern, category) {
|
|
|
3871
3871
|
if (pattern.endsWith(".*")) return category.startsWith(pattern.slice(0, -2));
|
|
3872
3872
|
return pattern === category;
|
|
3873
3873
|
}
|
|
3874
|
-
|
|
3875
|
-
|
|
3874
|
+
/**
|
|
3875
|
+
* EVERY category pattern a filter asks for — the registration key set.
|
|
3876
|
+
*
|
|
3877
|
+
* `EventFilter.category` has always accepted an array, and until 2026-08-12
|
|
3878
|
+
* the delivery path kept only `cat[0]`. Inside a forked runner that was not
|
|
3879
|
+
* merely a local filter: `createUdsEventBus` reports its handler-map keys to
|
|
3880
|
+
* the parent as this child's forwarding interest, so the categories that never
|
|
3881
|
+
* became keys were never forwarded across the process boundary at all. A
|
|
3882
|
+
* ten-category subscribe received one category, silently.
|
|
3883
|
+
*
|
|
3884
|
+
* Duplicates are collapsed so a filter that names the same category twice does
|
|
3885
|
+
* not put the entry in one key twice.
|
|
3886
|
+
*/
|
|
3887
|
+
function extractCategoryPatterns(filter) {
|
|
3888
|
+
if (typeof filter === "string") return [filter];
|
|
3876
3889
|
if (filter && typeof filter === "object" && "category" in filter) {
|
|
3877
3890
|
const cat = filter.category;
|
|
3878
|
-
if (typeof cat === "string") return cat;
|
|
3879
|
-
if (Array.isArray(cat) && cat.length > 0) return String(
|
|
3891
|
+
if (typeof cat === "string") return [cat];
|
|
3892
|
+
if (Array.isArray(cat) && cat.length > 0) return [...new Set(cat.map((c) => String(c)))];
|
|
3893
|
+
}
|
|
3894
|
+
return ["*"];
|
|
3895
|
+
}
|
|
3896
|
+
/**
|
|
3897
|
+
* Register one handler under every category pattern its filter names, and
|
|
3898
|
+
* return the unsubscribe that removes it from all of them.
|
|
3899
|
+
*
|
|
3900
|
+
* The ONE place a subscriber enters `state.handlers` — both bus
|
|
3901
|
+
* implementations (broker and UDS) call it, so "which keys does this
|
|
3902
|
+
* subscriber occupy" has a single answer. An emptied key is DELETED rather
|
|
3903
|
+
* than left as an empty `Set`: on a child bus the key set is what the parent
|
|
3904
|
+
* is told to forward, and an empty set left behind pins the parent to forward
|
|
3905
|
+
* a category nobody wants.
|
|
3906
|
+
*
|
|
3907
|
+
* @param onKeysChanged - Called after the key set may have changed (both on
|
|
3908
|
+
* registration and on unsubscribe), for buses that mirror their key set
|
|
3909
|
+
* somewhere — the UDS child's parent report, the broker's local-interest
|
|
3910
|
+
* surface. Receives the patterns this call touched.
|
|
3911
|
+
*/
|
|
3912
|
+
function registerSubscriber(state, filter, handler, onKeysChanged) {
|
|
3913
|
+
const patterns = extractCategoryPatterns(filter);
|
|
3914
|
+
const entry = {
|
|
3915
|
+
filter,
|
|
3916
|
+
categoryPattern: patterns[0] ?? "*",
|
|
3917
|
+
handler
|
|
3918
|
+
};
|
|
3919
|
+
for (const pattern of patterns) {
|
|
3920
|
+
const set = state.handlers.get(pattern) ?? /* @__PURE__ */ new Set();
|
|
3921
|
+
set.add(entry);
|
|
3922
|
+
state.handlers.set(pattern, set);
|
|
3880
3923
|
}
|
|
3881
|
-
|
|
3924
|
+
onKeysChanged?.(patterns, true);
|
|
3925
|
+
let released = false;
|
|
3926
|
+
return () => {
|
|
3927
|
+
if (released) return;
|
|
3928
|
+
released = true;
|
|
3929
|
+
for (const pattern of patterns) {
|
|
3930
|
+
const current = state.handlers.get(pattern);
|
|
3931
|
+
if (current === void 0) continue;
|
|
3932
|
+
current.delete(entry);
|
|
3933
|
+
if (current.size === 0) state.handlers.delete(pattern);
|
|
3934
|
+
}
|
|
3935
|
+
onKeysChanged?.(patterns, false);
|
|
3936
|
+
};
|
|
3882
3937
|
}
|
|
3883
3938
|
/**
|
|
3884
3939
|
* Match an event against the full `EventFilter`. The Map-keyed subscribe path
|
|
@@ -3991,10 +4046,13 @@ function deliverShared(state, event) {
|
|
|
3991
4046
|
state.recent.push(event);
|
|
3992
4047
|
if (state.recent.length > 1e3) state.recent.splice(0, state.recent.length - MAX_RECENT_EVENTS);
|
|
3993
4048
|
}
|
|
4049
|
+
const delivered = /* @__PURE__ */ new Set();
|
|
3994
4050
|
for (const [pattern, set] of state.handlers) {
|
|
3995
4051
|
if (!matchesPattern(pattern, event.category)) continue;
|
|
3996
4052
|
for (const entry of set) {
|
|
4053
|
+
if (delivered.has(entry)) continue;
|
|
3997
4054
|
if (!matchesEventFilter(event, entry.filter)) continue;
|
|
4055
|
+
delivered.add(entry);
|
|
3998
4056
|
try {
|
|
3999
4057
|
entry.handler(event);
|
|
4000
4058
|
} catch (err) {}
|
|
@@ -4842,13 +4900,14 @@ var LocalChildRegistry = class {
|
|
|
4842
4900
|
method: out.method,
|
|
4843
4901
|
args: out.args,
|
|
4844
4902
|
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {},
|
|
4845
|
-
...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {}
|
|
4903
|
+
...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {},
|
|
4904
|
+
...out.native === true ? { native: true } : {}
|
|
4846
4905
|
};
|
|
4847
4906
|
const pinnedNodeId = out.nodeId ?? extractNodeId(out.args);
|
|
4848
4907
|
const pinTargetsThisNode = pinnedNodeId !== void 0 && this.ownNodeId !== void 0 && pinnedNodeId === this.ownNodeId;
|
|
4849
4908
|
const aggregated = pinnedNodeId === void 0 && this.isAggregatedCollectionMethod?.(out.capName, out.method) === true;
|
|
4850
4909
|
const addonPinned = pinnedNodeId === void 0 && this.isAddonPinnedCall?.(out.capName, out.method, out.args) === true;
|
|
4851
|
-
if ((!aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
|
|
4910
|
+
if ((!(out.native === true) && !aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
|
|
4852
4911
|
if (!this.egressRoutedCaps.has(out.capName)) {
|
|
4853
4912
|
this.egressRoutedCaps.add(out.capName);
|
|
4854
4913
|
this.logger?.info("routed child egress over UDS", { capName: out.capName });
|
|
@@ -5154,7 +5213,8 @@ var LocalChildClient = class {
|
|
|
5154
5213
|
method: input.method,
|
|
5155
5214
|
args: input.args,
|
|
5156
5215
|
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
5157
|
-
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {}
|
|
5216
|
+
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {},
|
|
5217
|
+
...input.native === true ? { native: true } : {}
|
|
5158
5218
|
};
|
|
5159
5219
|
return this.channel.request(msg);
|
|
5160
5220
|
}
|
|
@@ -5305,23 +5365,7 @@ function createUdsEventBus(client, addonId) {
|
|
|
5305
5365
|
client.emitEvent(enriched);
|
|
5306
5366
|
},
|
|
5307
5367
|
subscribe(filter, handler) {
|
|
5308
|
-
|
|
5309
|
-
const entry = {
|
|
5310
|
-
filter,
|
|
5311
|
-
categoryPattern: pattern,
|
|
5312
|
-
handler
|
|
5313
|
-
};
|
|
5314
|
-
const set = state.handlers.get(pattern) ?? /* @__PURE__ */ new Set();
|
|
5315
|
-
set.add(entry);
|
|
5316
|
-
state.handlers.set(pattern, set);
|
|
5317
|
-
reportPatterns();
|
|
5318
|
-
return () => {
|
|
5319
|
-
const current = state.handlers.get(pattern);
|
|
5320
|
-
if (current === void 0) return;
|
|
5321
|
-
current.delete(entry);
|
|
5322
|
-
if (current.size === 0) state.handlers.delete(pattern);
|
|
5323
|
-
reportPatterns();
|
|
5324
|
-
};
|
|
5368
|
+
return registerSubscriber(state, filter, handler, () => reportPatterns());
|
|
5325
5369
|
},
|
|
5326
5370
|
getRecent(filter, limit) {
|
|
5327
5371
|
const filtered = filter ? state.recent.filter((e) => {
|
|
@@ -5420,6 +5464,26 @@ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
5420
5464
|
function createParentUnownedCallHandler(deps) {
|
|
5421
5465
|
return async (input) => {
|
|
5422
5466
|
const deviceId = input.deviceId ?? extractDeviceId(input.args);
|
|
5467
|
+
if (input.native === true) {
|
|
5468
|
+
if (deviceId === void 0) throw new Error(`no native provider for capability "${input.capName}" — the call carried no deviceId`);
|
|
5469
|
+
const provider = deps.resolveNative?.(input.capName, deviceId) ?? null;
|
|
5470
|
+
if (provider !== null) {
|
|
5471
|
+
const fn = provider[input.method];
|
|
5472
|
+
if (typeof fn !== "function") throw new Error(`no native provider for capability "${input.capName}" implementing "${input.method}" on device ${String(deviceId)}`);
|
|
5473
|
+
return await fn(typeof input.args === "object" && input.args !== null && !Array.isArray(input.args) ? {
|
|
5474
|
+
...input.args,
|
|
5475
|
+
deviceId
|
|
5476
|
+
} : { deviceId });
|
|
5477
|
+
}
|
|
5478
|
+
if (deps.forwardToHub !== void 0) return await deps.forwardToHub({
|
|
5479
|
+
capName: input.capName,
|
|
5480
|
+
method: input.method,
|
|
5481
|
+
args: input.args,
|
|
5482
|
+
deviceId,
|
|
5483
|
+
native: true
|
|
5484
|
+
});
|
|
5485
|
+
throw new Error(`no native provider for capability "${input.capName}" on device ${String(deviceId)}`);
|
|
5486
|
+
}
|
|
5423
5487
|
const nodeId = deps.isDataNodeIdCap?.(input.capName) === true ? void 0 : input.nodeId ?? extractNodeId(input.args);
|
|
5424
5488
|
const pinnedToOtherNode = nodeId !== void 0 && nodeId !== deps.broker.nodeID;
|
|
5425
5489
|
if (nodeId === void 0) {
|
|
@@ -5525,12 +5589,14 @@ function narrowParams(raw) {
|
|
|
5525
5589
|
if (typeof capName !== "string" || typeof method !== "string") throw new Error("$hub-cap-fwd.forward: invalid params — capName and method are required strings");
|
|
5526
5590
|
const deviceId = Reflect.get(raw, "deviceId");
|
|
5527
5591
|
const nodeId = Reflect.get(raw, "nodeId");
|
|
5592
|
+
const native = Reflect.get(raw, "native");
|
|
5528
5593
|
return {
|
|
5529
5594
|
capName,
|
|
5530
5595
|
method,
|
|
5531
5596
|
args: Reflect.get(raw, "args"),
|
|
5532
5597
|
deviceId: typeof deviceId === "number" ? deviceId : void 0,
|
|
5533
|
-
nodeId: typeof nodeId === "string" && nodeId.length > 0 ? nodeId : void 0
|
|
5598
|
+
nodeId: typeof nodeId === "string" && nodeId.length > 0 ? nodeId : void 0,
|
|
5599
|
+
native: native === true ? true : void 0
|
|
5534
5600
|
};
|
|
5535
5601
|
}
|
|
5536
5602
|
/**
|
|
@@ -5546,13 +5612,14 @@ function createHubCapForwardService(onUnownedCall) {
|
|
|
5546
5612
|
return {
|
|
5547
5613
|
name: HUB_CAP_FWD_SERVICE,
|
|
5548
5614
|
actions: { forward: { handler: async (ctx) => {
|
|
5549
|
-
const { capName, method, args, deviceId, nodeId } = narrowParams(ctx.params);
|
|
5615
|
+
const { capName, method, args, deviceId, nodeId, native } = narrowParams(ctx.params);
|
|
5550
5616
|
return onUnownedCall({
|
|
5551
5617
|
capName,
|
|
5552
5618
|
method,
|
|
5553
5619
|
args,
|
|
5554
5620
|
...deviceId !== void 0 ? { deviceId } : {},
|
|
5555
|
-
...nodeId !== void 0 ? { nodeId } : {}
|
|
5621
|
+
...nodeId !== void 0 ? { nodeId } : {},
|
|
5622
|
+
...native === true ? { native: true } : {}
|
|
5556
5623
|
});
|
|
5557
5624
|
} } }
|
|
5558
5625
|
};
|
|
@@ -6486,18 +6553,7 @@ function localSubscriberPatterns(broker) {
|
|
|
6486
6553
|
* Returns an unsubscribe function.
|
|
6487
6554
|
*/
|
|
6488
6555
|
function subscribePassthrough(broker, handler) {
|
|
6489
|
-
|
|
6490
|
-
const entry = {
|
|
6491
|
-
filter: {},
|
|
6492
|
-
categoryPattern: "*",
|
|
6493
|
-
handler
|
|
6494
|
-
};
|
|
6495
|
-
const set = state.handlers.get("*") ?? /* @__PURE__ */ new Set();
|
|
6496
|
-
set.add(entry);
|
|
6497
|
-
state.handlers.set("*", set);
|
|
6498
|
-
return () => {
|
|
6499
|
-
state.handlers.get("*")?.delete(entry);
|
|
6500
|
-
};
|
|
6556
|
+
return registerSubscriber(getSharedBusState(broker), {}, handler);
|
|
6501
6557
|
}
|
|
6502
6558
|
/** Per-broker cross-node inbound counters (delivered / would-suppress). */
|
|
6503
6559
|
var brokerCrossNodeStats = /* @__PURE__ */ new WeakMap();
|
|
@@ -6672,19 +6728,10 @@ function getBrokerEventBus(broker, options) {
|
|
|
6672
6728
|
}
|
|
6673
6729
|
},
|
|
6674
6730
|
subscribe(filter, handler) {
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
handler
|
|
6680
|
-
};
|
|
6681
|
-
const set = state.handlers.get(pattern) ?? /* @__PURE__ */ new Set();
|
|
6682
|
-
set.add(entry);
|
|
6683
|
-
state.handlers.set(pattern, set);
|
|
6684
|
-
bumpLocalInterest(broker, pattern);
|
|
6685
|
-
return () => {
|
|
6686
|
-
if (state.handlers.get(pattern)?.delete(entry) === true) dropLocalInterest(broker, pattern);
|
|
6687
|
-
};
|
|
6731
|
+
return registerSubscriber(state, filter, handler, (patterns, added) => {
|
|
6732
|
+
for (const pattern of patterns) if (added) bumpLocalInterest(broker, pattern);
|
|
6733
|
+
else dropLocalInterest(broker, pattern);
|
|
6734
|
+
});
|
|
6688
6735
|
},
|
|
6689
6736
|
getRecent(filter, limit) {
|
|
6690
6737
|
const filtered = filter ? state.recent.filter((e) => {
|
|
@@ -7227,6 +7274,29 @@ function kebabToCamel(s) {
|
|
|
7227
7274
|
return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
7228
7275
|
}
|
|
7229
7276
|
/**
|
|
7277
|
+
* Native-provider proxy for a UDS runner: every cap method dispatches
|
|
7278
|
+
* `native: true` over the child→parent channel, carrying `deviceId` both
|
|
7279
|
+
* out-of-band (for routing) and inside the args (where the provider reads it).
|
|
7280
|
+
*
|
|
7281
|
+
* Built from `cap.methods` rather than as a `Proxy` so an unknown method name
|
|
7282
|
+
* is a local `undefined` — a wrapper that probes for an optional method sees
|
|
7283
|
+
* the same shape it sees on the hub instead of a round-trip that fails.
|
|
7284
|
+
*
|
|
7285
|
+
* Irreducible bridge: the returned object is assembled by name at runtime, so
|
|
7286
|
+
* it is validated structurally against `InferProvider<TCap>` at this one seam
|
|
7287
|
+
* (same boundary `buildRemoteNativeProvider` casts at).
|
|
7288
|
+
*/
|
|
7289
|
+
function buildUdsNativeProvider(callOut, cap, deviceId) {
|
|
7290
|
+
const provider = {};
|
|
7291
|
+
for (const methodName of Object.keys(cap.methods)) provider[methodName] = async (input) => {
|
|
7292
|
+
return await callOut(methodName, typeof input === "object" && input !== null && !Array.isArray(input) ? {
|
|
7293
|
+
...input,
|
|
7294
|
+
deviceId
|
|
7295
|
+
} : { deviceId });
|
|
7296
|
+
};
|
|
7297
|
+
return provider;
|
|
7298
|
+
}
|
|
7299
|
+
/**
|
|
7230
7300
|
* Broker-mode entry point — UNCHANGED public signature. The hub
|
|
7231
7301
|
* (`addon-registry.service.ts`), the agent (`agent-bootstrap.ts`), and
|
|
7232
7302
|
* every broker-mode test call this with a positional `ServiceBroker`.
|
|
@@ -7530,6 +7600,16 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
|
|
|
7530
7600
|
},
|
|
7531
7601
|
getNativeProvider(cap, deviceId) {
|
|
7532
7602
|
if (options?.capabilityRegistry) return options.capabilityRegistry.getNativeProvider(cap.name, deviceId);
|
|
7603
|
+
if (runtime.mode === "uds") {
|
|
7604
|
+
const udsClient = runtime.client;
|
|
7605
|
+
return buildUdsNativeProvider((method, args) => udsClient.callOut({
|
|
7606
|
+
capName: cap.name,
|
|
7607
|
+
method,
|
|
7608
|
+
args,
|
|
7609
|
+
deviceId,
|
|
7610
|
+
native: true
|
|
7611
|
+
}), cap, deviceId);
|
|
7612
|
+
}
|
|
7533
7613
|
return buildRemoteNativeProvider(api, cap, {
|
|
7534
7614
|
nodeId: "hub",
|
|
7535
7615
|
bypassWrapper: true
|
|
@@ -3869,14 +3869,69 @@ function matchesPattern(pattern, category) {
|
|
|
3869
3869
|
if (pattern.endsWith(".*")) return category.startsWith(pattern.slice(0, -2));
|
|
3870
3870
|
return pattern === category;
|
|
3871
3871
|
}
|
|
3872
|
-
|
|
3873
|
-
|
|
3872
|
+
/**
|
|
3873
|
+
* EVERY category pattern a filter asks for — the registration key set.
|
|
3874
|
+
*
|
|
3875
|
+
* `EventFilter.category` has always accepted an array, and until 2026-08-12
|
|
3876
|
+
* the delivery path kept only `cat[0]`. Inside a forked runner that was not
|
|
3877
|
+
* merely a local filter: `createUdsEventBus` reports its handler-map keys to
|
|
3878
|
+
* the parent as this child's forwarding interest, so the categories that never
|
|
3879
|
+
* became keys were never forwarded across the process boundary at all. A
|
|
3880
|
+
* ten-category subscribe received one category, silently.
|
|
3881
|
+
*
|
|
3882
|
+
* Duplicates are collapsed so a filter that names the same category twice does
|
|
3883
|
+
* not put the entry in one key twice.
|
|
3884
|
+
*/
|
|
3885
|
+
function extractCategoryPatterns(filter) {
|
|
3886
|
+
if (typeof filter === "string") return [filter];
|
|
3874
3887
|
if (filter && typeof filter === "object" && "category" in filter) {
|
|
3875
3888
|
const cat = filter.category;
|
|
3876
|
-
if (typeof cat === "string") return cat;
|
|
3877
|
-
if (Array.isArray(cat) && cat.length > 0) return String(
|
|
3889
|
+
if (typeof cat === "string") return [cat];
|
|
3890
|
+
if (Array.isArray(cat) && cat.length > 0) return [...new Set(cat.map((c) => String(c)))];
|
|
3891
|
+
}
|
|
3892
|
+
return ["*"];
|
|
3893
|
+
}
|
|
3894
|
+
/**
|
|
3895
|
+
* Register one handler under every category pattern its filter names, and
|
|
3896
|
+
* return the unsubscribe that removes it from all of them.
|
|
3897
|
+
*
|
|
3898
|
+
* The ONE place a subscriber enters `state.handlers` — both bus
|
|
3899
|
+
* implementations (broker and UDS) call it, so "which keys does this
|
|
3900
|
+
* subscriber occupy" has a single answer. An emptied key is DELETED rather
|
|
3901
|
+
* than left as an empty `Set`: on a child bus the key set is what the parent
|
|
3902
|
+
* is told to forward, and an empty set left behind pins the parent to forward
|
|
3903
|
+
* a category nobody wants.
|
|
3904
|
+
*
|
|
3905
|
+
* @param onKeysChanged - Called after the key set may have changed (both on
|
|
3906
|
+
* registration and on unsubscribe), for buses that mirror their key set
|
|
3907
|
+
* somewhere — the UDS child's parent report, the broker's local-interest
|
|
3908
|
+
* surface. Receives the patterns this call touched.
|
|
3909
|
+
*/
|
|
3910
|
+
function registerSubscriber(state, filter, handler, onKeysChanged) {
|
|
3911
|
+
const patterns = extractCategoryPatterns(filter);
|
|
3912
|
+
const entry = {
|
|
3913
|
+
filter,
|
|
3914
|
+
categoryPattern: patterns[0] ?? "*",
|
|
3915
|
+
handler
|
|
3916
|
+
};
|
|
3917
|
+
for (const pattern of patterns) {
|
|
3918
|
+
const set = state.handlers.get(pattern) ?? /* @__PURE__ */ new Set();
|
|
3919
|
+
set.add(entry);
|
|
3920
|
+
state.handlers.set(pattern, set);
|
|
3878
3921
|
}
|
|
3879
|
-
|
|
3922
|
+
onKeysChanged?.(patterns, true);
|
|
3923
|
+
let released = false;
|
|
3924
|
+
return () => {
|
|
3925
|
+
if (released) return;
|
|
3926
|
+
released = true;
|
|
3927
|
+
for (const pattern of patterns) {
|
|
3928
|
+
const current = state.handlers.get(pattern);
|
|
3929
|
+
if (current === void 0) continue;
|
|
3930
|
+
current.delete(entry);
|
|
3931
|
+
if (current.size === 0) state.handlers.delete(pattern);
|
|
3932
|
+
}
|
|
3933
|
+
onKeysChanged?.(patterns, false);
|
|
3934
|
+
};
|
|
3880
3935
|
}
|
|
3881
3936
|
/**
|
|
3882
3937
|
* Match an event against the full `EventFilter`. The Map-keyed subscribe path
|
|
@@ -3989,10 +4044,13 @@ function deliverShared(state, event) {
|
|
|
3989
4044
|
state.recent.push(event);
|
|
3990
4045
|
if (state.recent.length > 1e3) state.recent.splice(0, state.recent.length - MAX_RECENT_EVENTS);
|
|
3991
4046
|
}
|
|
4047
|
+
const delivered = /* @__PURE__ */ new Set();
|
|
3992
4048
|
for (const [pattern, set] of state.handlers) {
|
|
3993
4049
|
if (!matchesPattern(pattern, event.category)) continue;
|
|
3994
4050
|
for (const entry of set) {
|
|
4051
|
+
if (delivered.has(entry)) continue;
|
|
3995
4052
|
if (!matchesEventFilter(event, entry.filter)) continue;
|
|
4053
|
+
delivered.add(entry);
|
|
3996
4054
|
try {
|
|
3997
4055
|
entry.handler(event);
|
|
3998
4056
|
} catch (err) {}
|
|
@@ -4840,13 +4898,14 @@ var LocalChildRegistry = class {
|
|
|
4840
4898
|
method: out.method,
|
|
4841
4899
|
args: out.args,
|
|
4842
4900
|
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {},
|
|
4843
|
-
...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {}
|
|
4901
|
+
...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {},
|
|
4902
|
+
...out.native === true ? { native: true } : {}
|
|
4844
4903
|
};
|
|
4845
4904
|
const pinnedNodeId = out.nodeId ?? extractNodeId(out.args);
|
|
4846
4905
|
const pinTargetsThisNode = pinnedNodeId !== void 0 && this.ownNodeId !== void 0 && pinnedNodeId === this.ownNodeId;
|
|
4847
4906
|
const aggregated = pinnedNodeId === void 0 && this.isAggregatedCollectionMethod?.(out.capName, out.method) === true;
|
|
4848
4907
|
const addonPinned = pinnedNodeId === void 0 && this.isAddonPinnedCall?.(out.capName, out.method, out.args) === true;
|
|
4849
|
-
if ((!aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
|
|
4908
|
+
if ((!(out.native === true) && !aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
|
|
4850
4909
|
if (!this.egressRoutedCaps.has(out.capName)) {
|
|
4851
4910
|
this.egressRoutedCaps.add(out.capName);
|
|
4852
4911
|
this.logger?.info("routed child egress over UDS", { capName: out.capName });
|
|
@@ -5152,7 +5211,8 @@ var LocalChildClient = class {
|
|
|
5152
5211
|
method: input.method,
|
|
5153
5212
|
args: input.args,
|
|
5154
5213
|
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
5155
|
-
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {}
|
|
5214
|
+
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {},
|
|
5215
|
+
...input.native === true ? { native: true } : {}
|
|
5156
5216
|
};
|
|
5157
5217
|
return this.channel.request(msg);
|
|
5158
5218
|
}
|
|
@@ -5303,23 +5363,7 @@ function createUdsEventBus(client, addonId) {
|
|
|
5303
5363
|
client.emitEvent(enriched);
|
|
5304
5364
|
},
|
|
5305
5365
|
subscribe(filter, handler) {
|
|
5306
|
-
|
|
5307
|
-
const entry = {
|
|
5308
|
-
filter,
|
|
5309
|
-
categoryPattern: pattern,
|
|
5310
|
-
handler
|
|
5311
|
-
};
|
|
5312
|
-
const set = state.handlers.get(pattern) ?? /* @__PURE__ */ new Set();
|
|
5313
|
-
set.add(entry);
|
|
5314
|
-
state.handlers.set(pattern, set);
|
|
5315
|
-
reportPatterns();
|
|
5316
|
-
return () => {
|
|
5317
|
-
const current = state.handlers.get(pattern);
|
|
5318
|
-
if (current === void 0) return;
|
|
5319
|
-
current.delete(entry);
|
|
5320
|
-
if (current.size === 0) state.handlers.delete(pattern);
|
|
5321
|
-
reportPatterns();
|
|
5322
|
-
};
|
|
5366
|
+
return registerSubscriber(state, filter, handler, () => reportPatterns());
|
|
5323
5367
|
},
|
|
5324
5368
|
getRecent(filter, limit) {
|
|
5325
5369
|
const filtered = filter ? state.recent.filter((e) => {
|
|
@@ -5418,6 +5462,26 @@ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
5418
5462
|
function createParentUnownedCallHandler(deps) {
|
|
5419
5463
|
return async (input) => {
|
|
5420
5464
|
const deviceId = input.deviceId ?? extractDeviceId(input.args);
|
|
5465
|
+
if (input.native === true) {
|
|
5466
|
+
if (deviceId === void 0) throw new Error(`no native provider for capability "${input.capName}" — the call carried no deviceId`);
|
|
5467
|
+
const provider = deps.resolveNative?.(input.capName, deviceId) ?? null;
|
|
5468
|
+
if (provider !== null) {
|
|
5469
|
+
const fn = provider[input.method];
|
|
5470
|
+
if (typeof fn !== "function") throw new Error(`no native provider for capability "${input.capName}" implementing "${input.method}" on device ${String(deviceId)}`);
|
|
5471
|
+
return await fn(typeof input.args === "object" && input.args !== null && !Array.isArray(input.args) ? {
|
|
5472
|
+
...input.args,
|
|
5473
|
+
deviceId
|
|
5474
|
+
} : { deviceId });
|
|
5475
|
+
}
|
|
5476
|
+
if (deps.forwardToHub !== void 0) return await deps.forwardToHub({
|
|
5477
|
+
capName: input.capName,
|
|
5478
|
+
method: input.method,
|
|
5479
|
+
args: input.args,
|
|
5480
|
+
deviceId,
|
|
5481
|
+
native: true
|
|
5482
|
+
});
|
|
5483
|
+
throw new Error(`no native provider for capability "${input.capName}" on device ${String(deviceId)}`);
|
|
5484
|
+
}
|
|
5421
5485
|
const nodeId = deps.isDataNodeIdCap?.(input.capName) === true ? void 0 : input.nodeId ?? extractNodeId(input.args);
|
|
5422
5486
|
const pinnedToOtherNode = nodeId !== void 0 && nodeId !== deps.broker.nodeID;
|
|
5423
5487
|
if (nodeId === void 0) {
|
|
@@ -5523,12 +5587,14 @@ function narrowParams(raw) {
|
|
|
5523
5587
|
if (typeof capName !== "string" || typeof method !== "string") throw new Error("$hub-cap-fwd.forward: invalid params — capName and method are required strings");
|
|
5524
5588
|
const deviceId = Reflect.get(raw, "deviceId");
|
|
5525
5589
|
const nodeId = Reflect.get(raw, "nodeId");
|
|
5590
|
+
const native = Reflect.get(raw, "native");
|
|
5526
5591
|
return {
|
|
5527
5592
|
capName,
|
|
5528
5593
|
method,
|
|
5529
5594
|
args: Reflect.get(raw, "args"),
|
|
5530
5595
|
deviceId: typeof deviceId === "number" ? deviceId : void 0,
|
|
5531
|
-
nodeId: typeof nodeId === "string" && nodeId.length > 0 ? nodeId : void 0
|
|
5596
|
+
nodeId: typeof nodeId === "string" && nodeId.length > 0 ? nodeId : void 0,
|
|
5597
|
+
native: native === true ? true : void 0
|
|
5532
5598
|
};
|
|
5533
5599
|
}
|
|
5534
5600
|
/**
|
|
@@ -5544,13 +5610,14 @@ function createHubCapForwardService(onUnownedCall) {
|
|
|
5544
5610
|
return {
|
|
5545
5611
|
name: HUB_CAP_FWD_SERVICE,
|
|
5546
5612
|
actions: { forward: { handler: async (ctx) => {
|
|
5547
|
-
const { capName, method, args, deviceId, nodeId } = narrowParams(ctx.params);
|
|
5613
|
+
const { capName, method, args, deviceId, nodeId, native } = narrowParams(ctx.params);
|
|
5548
5614
|
return onUnownedCall({
|
|
5549
5615
|
capName,
|
|
5550
5616
|
method,
|
|
5551
5617
|
args,
|
|
5552
5618
|
...deviceId !== void 0 ? { deviceId } : {},
|
|
5553
|
-
...nodeId !== void 0 ? { nodeId } : {}
|
|
5619
|
+
...nodeId !== void 0 ? { nodeId } : {},
|
|
5620
|
+
...native === true ? { native: true } : {}
|
|
5554
5621
|
});
|
|
5555
5622
|
} } }
|
|
5556
5623
|
};
|
|
@@ -6484,18 +6551,7 @@ function localSubscriberPatterns(broker) {
|
|
|
6484
6551
|
* Returns an unsubscribe function.
|
|
6485
6552
|
*/
|
|
6486
6553
|
function subscribePassthrough(broker, handler) {
|
|
6487
|
-
|
|
6488
|
-
const entry = {
|
|
6489
|
-
filter: {},
|
|
6490
|
-
categoryPattern: "*",
|
|
6491
|
-
handler
|
|
6492
|
-
};
|
|
6493
|
-
const set = state.handlers.get("*") ?? /* @__PURE__ */ new Set();
|
|
6494
|
-
set.add(entry);
|
|
6495
|
-
state.handlers.set("*", set);
|
|
6496
|
-
return () => {
|
|
6497
|
-
state.handlers.get("*")?.delete(entry);
|
|
6498
|
-
};
|
|
6554
|
+
return registerSubscriber(getSharedBusState(broker), {}, handler);
|
|
6499
6555
|
}
|
|
6500
6556
|
/** Per-broker cross-node inbound counters (delivered / would-suppress). */
|
|
6501
6557
|
var brokerCrossNodeStats = /* @__PURE__ */ new WeakMap();
|
|
@@ -6670,19 +6726,10 @@ function getBrokerEventBus(broker, options) {
|
|
|
6670
6726
|
}
|
|
6671
6727
|
},
|
|
6672
6728
|
subscribe(filter, handler) {
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
handler
|
|
6678
|
-
};
|
|
6679
|
-
const set = state.handlers.get(pattern) ?? /* @__PURE__ */ new Set();
|
|
6680
|
-
set.add(entry);
|
|
6681
|
-
state.handlers.set(pattern, set);
|
|
6682
|
-
bumpLocalInterest(broker, pattern);
|
|
6683
|
-
return () => {
|
|
6684
|
-
if (state.handlers.get(pattern)?.delete(entry) === true) dropLocalInterest(broker, pattern);
|
|
6685
|
-
};
|
|
6729
|
+
return registerSubscriber(state, filter, handler, (patterns, added) => {
|
|
6730
|
+
for (const pattern of patterns) if (added) bumpLocalInterest(broker, pattern);
|
|
6731
|
+
else dropLocalInterest(broker, pattern);
|
|
6732
|
+
});
|
|
6686
6733
|
},
|
|
6687
6734
|
getRecent(filter, limit) {
|
|
6688
6735
|
const filtered = filter ? state.recent.filter((e) => {
|
|
@@ -7225,6 +7272,29 @@ function kebabToCamel(s) {
|
|
|
7225
7272
|
return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
7226
7273
|
}
|
|
7227
7274
|
/**
|
|
7275
|
+
* Native-provider proxy for a UDS runner: every cap method dispatches
|
|
7276
|
+
* `native: true` over the child→parent channel, carrying `deviceId` both
|
|
7277
|
+
* out-of-band (for routing) and inside the args (where the provider reads it).
|
|
7278
|
+
*
|
|
7279
|
+
* Built from `cap.methods` rather than as a `Proxy` so an unknown method name
|
|
7280
|
+
* is a local `undefined` — a wrapper that probes for an optional method sees
|
|
7281
|
+
* the same shape it sees on the hub instead of a round-trip that fails.
|
|
7282
|
+
*
|
|
7283
|
+
* Irreducible bridge: the returned object is assembled by name at runtime, so
|
|
7284
|
+
* it is validated structurally against `InferProvider<TCap>` at this one seam
|
|
7285
|
+
* (same boundary `buildRemoteNativeProvider` casts at).
|
|
7286
|
+
*/
|
|
7287
|
+
function buildUdsNativeProvider(callOut, cap, deviceId) {
|
|
7288
|
+
const provider = {};
|
|
7289
|
+
for (const methodName of Object.keys(cap.methods)) provider[methodName] = async (input) => {
|
|
7290
|
+
return await callOut(methodName, typeof input === "object" && input !== null && !Array.isArray(input) ? {
|
|
7291
|
+
...input,
|
|
7292
|
+
deviceId
|
|
7293
|
+
} : { deviceId });
|
|
7294
|
+
};
|
|
7295
|
+
return provider;
|
|
7296
|
+
}
|
|
7297
|
+
/**
|
|
7228
7298
|
* Broker-mode entry point — UNCHANGED public signature. The hub
|
|
7229
7299
|
* (`addon-registry.service.ts`), the agent (`agent-bootstrap.ts`), and
|
|
7230
7300
|
* every broker-mode test call this with a positional `ServiceBroker`.
|
|
@@ -7528,6 +7598,16 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
|
|
|
7528
7598
|
},
|
|
7529
7599
|
getNativeProvider(cap, deviceId) {
|
|
7530
7600
|
if (options?.capabilityRegistry) return options.capabilityRegistry.getNativeProvider(cap.name, deviceId);
|
|
7601
|
+
if (runtime.mode === "uds") {
|
|
7602
|
+
const udsClient = runtime.client;
|
|
7603
|
+
return buildUdsNativeProvider((method, args) => udsClient.callOut({
|
|
7604
|
+
capName: cap.name,
|
|
7605
|
+
method,
|
|
7606
|
+
args,
|
|
7607
|
+
deviceId,
|
|
7608
|
+
native: true
|
|
7609
|
+
}), cap, deviceId);
|
|
7610
|
+
}
|
|
7531
7611
|
return buildRemoteNativeProvider(api, cap, {
|
|
7532
7612
|
nodeId: "hub",
|
|
7533
7613
|
bypassWrapper: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/system",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.85",
|
|
4
4
|
"description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -168,7 +168,12 @@
|
|
|
168
168
|
"id": "snapshot",
|
|
169
169
|
"category": "system",
|
|
170
170
|
"name": "Snapshot",
|
|
171
|
+
"description": "Owns every camera's latest frame: the capture ladder (vendor native → ffmpeg keyframe grab off the stream-broker restream), the sharp resize, the courtesy frame for a camera that cannot answer, and the two HTTP planes the clients actually fetch bytes from. Runs in its OWN process (`execution.isolate`) — capture, transcode and byte caches do not belong on the event loop that serves the API.",
|
|
171
172
|
"entry": "./dist/builtins/snapshot/index.js",
|
|
173
|
+
"execution": {
|
|
174
|
+
"isolate": true,
|
|
175
|
+
"heapProfile": "heavy"
|
|
176
|
+
},
|
|
172
177
|
"capabilities": [
|
|
173
178
|
{
|
|
174
179
|
"name": "snapshot"
|