@camstack/system 1.2.22 → 1.2.24
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.d.ts +0 -9
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -40
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -40
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- 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/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.d.ts +16 -0
- package/dist/builtins/local-network/local-network.addon.js +30 -2
- package/dist/builtins/local-network/local-network.addon.mjs +30 -2
- package/dist/builtins/loki-logging/index.js +34 -12
- package/dist/builtins/loki-logging/index.mjs +34 -12
- package/dist/builtins/loki-logging/loki-logging.addon.d.ts +19 -0
- 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 +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -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/storage-orchestrator.addon.d.ts +7 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +108 -4
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +108 -4
- package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +11 -1
- package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +5 -0
- 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-C9_XdXl5.mjs → dist-CIek6F9X.mjs} +436 -72
- package/dist/{dist-j49ewCPY.js → dist-CS0pdC8l.js} +436 -72
- package/dist/index.js +58 -8
- package/dist/index.mjs +58 -8
- package/dist/kernel/capability-registry.d.ts +9 -0
- package/dist/kernel/transport/local-child-registry.d.ts +38 -0
- package/dist/kernel/transport/local-transport.d.ts +11 -1
- package/dist/kernel/transport/parent-unowned-call.d.ts +22 -0
- package/dist/kernel/transport/socket-channel.d.ts +28 -1
- package/dist/logging/log-manager.d.ts +32 -1
- package/dist/logging/partitioned-log-buffer.d.ts +15 -3
- package/dist/{manifest-python-deps-CnRW2Utv.js → manifest-python-deps-BbOZZbVt.js} +56 -12
- package/dist/{manifest-python-deps-B30qVAuW.mjs → manifest-python-deps-C9qhvMUi.mjs} +57 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("./dist-
|
|
3
|
+
const require_dist = require("./dist-CS0pdC8l.js");
|
|
4
4
|
const require_model_download_service = require("./model-download-service-hf0ookyy.js");
|
|
5
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
5
|
+
const require_manifest_python_deps = require("./manifest-python-deps-BbOZZbVt.js");
|
|
6
6
|
const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
|
|
7
7
|
const require_builtins_sqlite_storage_filesystem_storage_addon = require("./builtins/sqlite-storage/filesystem-storage.addon.js");
|
|
8
8
|
const require_builtins_sqlite_storage_sqlite_settings_addon = require("./builtins/sqlite-storage/sqlite-settings.addon.js");
|
|
@@ -991,6 +991,18 @@ var PartitionedLogBuffer = class {
|
|
|
991
991
|
this.maxTotalEntries = options.maxTotalEntries ?? null;
|
|
992
992
|
this.pruneLevel = options.pruneLevel ?? "debug";
|
|
993
993
|
}
|
|
994
|
+
/**
|
|
995
|
+
* Apply new limits at runtime. Shrinking takes effect as entries are pushed
|
|
996
|
+
* (each partition trims to the new capacity on its next write) — existing
|
|
997
|
+
* entries are NOT dropped eagerly: losing history the operator is currently
|
|
998
|
+
* reading, the instant they lower a number, is a worse surprise than a
|
|
999
|
+
* momentarily oversized buffer.
|
|
1000
|
+
*/
|
|
1001
|
+
reconfigure(options) {
|
|
1002
|
+
if (options.perAddonCapacity !== void 0 && options.perAddonCapacity > 0) this.perAddonCapacity = options.perAddonCapacity;
|
|
1003
|
+
if (options.maxTotalEntries !== void 0) this.maxTotalEntries = options.maxTotalEntries;
|
|
1004
|
+
if (options.pruneLevel !== void 0) this.pruneLevel = options.pruneLevel;
|
|
1005
|
+
}
|
|
994
1006
|
bufferFor(key) {
|
|
995
1007
|
let buf = this.buffers.get(key);
|
|
996
1008
|
if (buf === void 0) {
|
|
@@ -1130,11 +1142,24 @@ var ScopedLogger = class ScopedLogger {
|
|
|
1130
1142
|
};
|
|
1131
1143
|
}
|
|
1132
1144
|
};
|
|
1133
|
-
//#endregion
|
|
1134
|
-
//#region src/logging/log-manager.ts
|
|
1135
1145
|
var LogManager = class {
|
|
1136
1146
|
ringBuffer;
|
|
1147
|
+
/**
|
|
1148
|
+
* Destinations, each tagged with the addon that provides it (when known) so
|
|
1149
|
+
* the fan-out can honour the operator's per-destination toggle.
|
|
1150
|
+
*/
|
|
1137
1151
|
destinations = [];
|
|
1152
|
+
/**
|
|
1153
|
+
* "Is this destination enabled?", injected by the wiring side (which owns
|
|
1154
|
+
* the capability registry). Absent ⇒ everything is enabled.
|
|
1155
|
+
*
|
|
1156
|
+
* WHY: the orchestrator's Enable/Disable toggle only added the addon to
|
|
1157
|
+
* `disabledProviders` and NOTHING consulted it for `log-destination` — so
|
|
1158
|
+
* console and winston could not be turned off at all and the badge in the UI
|
|
1159
|
+
* lied, while Loki appeared to work only because it carried a SECOND,
|
|
1160
|
+
* private `enabled` setting of its own (audit 2026-07-30).
|
|
1161
|
+
*/
|
|
1162
|
+
isDestinationEnabled;
|
|
1138
1163
|
subscribers = /* @__PURE__ */ new Map();
|
|
1139
1164
|
deviceNameLookup = null;
|
|
1140
1165
|
/**
|
|
@@ -1169,8 +1194,11 @@ var LogManager = class {
|
|
|
1169
1194
|
createLogger(scope) {
|
|
1170
1195
|
return new ScopedLogger(scope, (entry) => {
|
|
1171
1196
|
const enriched = this.enrichDeviceName(entry);
|
|
1172
|
-
this.ringBuffer.push(enriched);
|
|
1173
|
-
for (const
|
|
1197
|
+
if (this.isDestinationEnabled?.("log-buffer") !== false) this.ringBuffer.push(enriched);
|
|
1198
|
+
for (const entry of this.destinations) {
|
|
1199
|
+
if (entry.addonId !== void 0 && this.isDestinationEnabled?.(entry.addonId) === false) continue;
|
|
1200
|
+
entry.dest.write(enriched);
|
|
1201
|
+
}
|
|
1174
1202
|
for (const [, sub] of this.subscribers) if (this.matchesFilter(enriched, sub.filter)) try {
|
|
1175
1203
|
sub.callback(enriched);
|
|
1176
1204
|
} catch {}
|
|
@@ -1228,15 +1256,26 @@ var LogManager = class {
|
|
|
1228
1256
|
* traffic starts. Lets a destination added mid-boot
|
|
1229
1257
|
* still receive boot-time log entries.
|
|
1230
1258
|
*/
|
|
1259
|
+
/** Apply new in-memory buffer limits at runtime (see PartitionedLogBuffer). */
|
|
1260
|
+
reconfigureBuffer(options) {
|
|
1261
|
+
this.ringBuffer.reconfigure(options);
|
|
1262
|
+
}
|
|
1263
|
+
/** Set the per-destination enablement check (see {@link isDestinationEnabled}). */
|
|
1264
|
+
setDestinationEnabledCheck(check) {
|
|
1265
|
+
this.isDestinationEnabled = check ?? void 0;
|
|
1266
|
+
}
|
|
1231
1267
|
addDestination(dest, opts = {}) {
|
|
1232
1268
|
const replay = opts.replay ?? true;
|
|
1233
|
-
this.destinations.push(dest
|
|
1269
|
+
this.destinations.push(opts.addonId === void 0 ? { dest } : {
|
|
1270
|
+
dest,
|
|
1271
|
+
addonId: opts.addonId
|
|
1272
|
+
});
|
|
1234
1273
|
if (replay) for (const entry of this.ringBuffer.query({})) try {
|
|
1235
1274
|
dest.write(entry);
|
|
1236
1275
|
} catch {}
|
|
1237
1276
|
}
|
|
1238
1277
|
removeDestination(dest) {
|
|
1239
|
-
const index = this.destinations.
|
|
1278
|
+
const index = this.destinations.findIndex((d) => d.dest === dest);
|
|
1240
1279
|
if (index !== -1) this.destinations.splice(index, 1);
|
|
1241
1280
|
}
|
|
1242
1281
|
query(filter) {
|
|
@@ -5168,6 +5207,17 @@ var CapabilityRegistry = class CapabilityRegistry {
|
|
|
5168
5207
|
if (!state || state.definition.mode !== "collection") return [];
|
|
5169
5208
|
return [...state.providers.entries()].filter(([addonId]) => !state.disabledProviders.has(addonId));
|
|
5170
5209
|
}
|
|
5210
|
+
/**
|
|
5211
|
+
* Is `addonId` currently DISABLED as a provider of `capability`?
|
|
5212
|
+
*
|
|
5213
|
+
* Exposed because the disabled set was write-only for `log-destination`:
|
|
5214
|
+
* the orchestrator toggle added the addon here and nothing ever read it, so
|
|
5215
|
+
* console and winston could not be turned off at all while the UI badge said
|
|
5216
|
+
* they were (audit 2026-07-30).
|
|
5217
|
+
*/
|
|
5218
|
+
isProviderDisabled(capability, addonId) {
|
|
5219
|
+
return this.capabilities.get(capability)?.disabledProviders.has(addonId) === true;
|
|
5220
|
+
}
|
|
5171
5221
|
/** Get the full CapabilityDefinition for a declared capability. */
|
|
5172
5222
|
getDefinition(capability) {
|
|
5173
5223
|
return this.capabilities.get(capability)?.definition;
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { B as logLevelAtMost, Dt as readinessKey, Et as parseJsonUnknown$1, F as isVoidInput, I as kebabToCamel, L as lifecycleJobSchema, M as isArrayOutputSchema, N as isCollectionArrayMethod, Ot as resolveCapMount, P as isObjectInput, Tt as parseJsonObject, V as looseSchema, W as objectInputDeclaresAddonId, _t as asString$1, c as RUNTIME_DEFAULTS, ct as DEVICE_STATUS_METHOD, ft as ReadinessRegistry, gt as asNumber, ht as asJsonObject$1, it as errMsg$1, jt as EventCategory$1, k as extractNestedAddonId, kt as scopeKey, m as addonSettingsCapability, ot as DATAPLANE_SECRET_HEADER$1, pt as ReadinessTimeoutError, q as procedureAuthKey, s as METHOD_ACCESS_MAP, st as DEVICE_SETTINGS_CONTRIBUTION_METHODS, t as ALL_CAPABILITY_DEFINITIONS, vt as createEvent, xt as expandCapMethods, yt as emitDownForOwnedCaps } from "./dist-
|
|
2
|
+
import { B as logLevelAtMost, Dt as readinessKey, Et as parseJsonUnknown$1, F as isVoidInput, I as kebabToCamel, L as lifecycleJobSchema, M as isArrayOutputSchema, N as isCollectionArrayMethod, Ot as resolveCapMount, P as isObjectInput, Tt as parseJsonObject, V as looseSchema, W as objectInputDeclaresAddonId, _t as asString$1, c as RUNTIME_DEFAULTS, ct as DEVICE_STATUS_METHOD, ft as ReadinessRegistry, gt as asNumber, ht as asJsonObject$1, it as errMsg$1, jt as EventCategory$1, k as extractNestedAddonId, kt as scopeKey, m as addonSettingsCapability, ot as DATAPLANE_SECRET_HEADER$1, pt as ReadinessTimeoutError, q as procedureAuthKey, s as METHOD_ACCESS_MAP, st as DEVICE_SETTINGS_CONTRIBUTION_METHODS, t as ALL_CAPABILITY_DEFINITIONS, vt as createEvent, xt as expandCapMethods, yt as emitDownForOwnedCaps } from "./dist-CIek6F9X.mjs";
|
|
3
3
|
import { a as downloadModel, c as getModelFilePath, d as contentTypeFor, f as createAuthenticatedFileServer, h as resolveFilePath, i as downloadFile, l as isModelDownloaded, m as parseTokenizedUrl, n as collectModelFiles, o as ensureModel, p as parseRangeHeader, r as deleteModelFromDisk, s as fetchJson, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-Cp9f4dk6.mjs";
|
|
4
|
-
import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-
|
|
4
|
+
import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-C9qhvMUi.mjs";
|
|
5
5
|
import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BkP504Vq.mjs";
|
|
6
6
|
import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
|
|
7
7
|
import { SqliteSettingsAddon, t as SqliteSettingsBackend } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
|
|
@@ -984,6 +984,18 @@ var PartitionedLogBuffer = class {
|
|
|
984
984
|
this.maxTotalEntries = options.maxTotalEntries ?? null;
|
|
985
985
|
this.pruneLevel = options.pruneLevel ?? "debug";
|
|
986
986
|
}
|
|
987
|
+
/**
|
|
988
|
+
* Apply new limits at runtime. Shrinking takes effect as entries are pushed
|
|
989
|
+
* (each partition trims to the new capacity on its next write) — existing
|
|
990
|
+
* entries are NOT dropped eagerly: losing history the operator is currently
|
|
991
|
+
* reading, the instant they lower a number, is a worse surprise than a
|
|
992
|
+
* momentarily oversized buffer.
|
|
993
|
+
*/
|
|
994
|
+
reconfigure(options) {
|
|
995
|
+
if (options.perAddonCapacity !== void 0 && options.perAddonCapacity > 0) this.perAddonCapacity = options.perAddonCapacity;
|
|
996
|
+
if (options.maxTotalEntries !== void 0) this.maxTotalEntries = options.maxTotalEntries;
|
|
997
|
+
if (options.pruneLevel !== void 0) this.pruneLevel = options.pruneLevel;
|
|
998
|
+
}
|
|
987
999
|
bufferFor(key) {
|
|
988
1000
|
let buf = this.buffers.get(key);
|
|
989
1001
|
if (buf === void 0) {
|
|
@@ -1123,11 +1135,24 @@ var ScopedLogger = class ScopedLogger {
|
|
|
1123
1135
|
};
|
|
1124
1136
|
}
|
|
1125
1137
|
};
|
|
1126
|
-
//#endregion
|
|
1127
|
-
//#region src/logging/log-manager.ts
|
|
1128
1138
|
var LogManager = class {
|
|
1129
1139
|
ringBuffer;
|
|
1140
|
+
/**
|
|
1141
|
+
* Destinations, each tagged with the addon that provides it (when known) so
|
|
1142
|
+
* the fan-out can honour the operator's per-destination toggle.
|
|
1143
|
+
*/
|
|
1130
1144
|
destinations = [];
|
|
1145
|
+
/**
|
|
1146
|
+
* "Is this destination enabled?", injected by the wiring side (which owns
|
|
1147
|
+
* the capability registry). Absent ⇒ everything is enabled.
|
|
1148
|
+
*
|
|
1149
|
+
* WHY: the orchestrator's Enable/Disable toggle only added the addon to
|
|
1150
|
+
* `disabledProviders` and NOTHING consulted it for `log-destination` — so
|
|
1151
|
+
* console and winston could not be turned off at all and the badge in the UI
|
|
1152
|
+
* lied, while Loki appeared to work only because it carried a SECOND,
|
|
1153
|
+
* private `enabled` setting of its own (audit 2026-07-30).
|
|
1154
|
+
*/
|
|
1155
|
+
isDestinationEnabled;
|
|
1131
1156
|
subscribers = /* @__PURE__ */ new Map();
|
|
1132
1157
|
deviceNameLookup = null;
|
|
1133
1158
|
/**
|
|
@@ -1162,8 +1187,11 @@ var LogManager = class {
|
|
|
1162
1187
|
createLogger(scope) {
|
|
1163
1188
|
return new ScopedLogger(scope, (entry) => {
|
|
1164
1189
|
const enriched = this.enrichDeviceName(entry);
|
|
1165
|
-
this.ringBuffer.push(enriched);
|
|
1166
|
-
for (const
|
|
1190
|
+
if (this.isDestinationEnabled?.("log-buffer") !== false) this.ringBuffer.push(enriched);
|
|
1191
|
+
for (const entry of this.destinations) {
|
|
1192
|
+
if (entry.addonId !== void 0 && this.isDestinationEnabled?.(entry.addonId) === false) continue;
|
|
1193
|
+
entry.dest.write(enriched);
|
|
1194
|
+
}
|
|
1167
1195
|
for (const [, sub] of this.subscribers) if (this.matchesFilter(enriched, sub.filter)) try {
|
|
1168
1196
|
sub.callback(enriched);
|
|
1169
1197
|
} catch {}
|
|
@@ -1221,15 +1249,26 @@ var LogManager = class {
|
|
|
1221
1249
|
* traffic starts. Lets a destination added mid-boot
|
|
1222
1250
|
* still receive boot-time log entries.
|
|
1223
1251
|
*/
|
|
1252
|
+
/** Apply new in-memory buffer limits at runtime (see PartitionedLogBuffer). */
|
|
1253
|
+
reconfigureBuffer(options) {
|
|
1254
|
+
this.ringBuffer.reconfigure(options);
|
|
1255
|
+
}
|
|
1256
|
+
/** Set the per-destination enablement check (see {@link isDestinationEnabled}). */
|
|
1257
|
+
setDestinationEnabledCheck(check) {
|
|
1258
|
+
this.isDestinationEnabled = check ?? void 0;
|
|
1259
|
+
}
|
|
1224
1260
|
addDestination(dest, opts = {}) {
|
|
1225
1261
|
const replay = opts.replay ?? true;
|
|
1226
|
-
this.destinations.push(dest
|
|
1262
|
+
this.destinations.push(opts.addonId === void 0 ? { dest } : {
|
|
1263
|
+
dest,
|
|
1264
|
+
addonId: opts.addonId
|
|
1265
|
+
});
|
|
1227
1266
|
if (replay) for (const entry of this.ringBuffer.query({})) try {
|
|
1228
1267
|
dest.write(entry);
|
|
1229
1268
|
} catch {}
|
|
1230
1269
|
}
|
|
1231
1270
|
removeDestination(dest) {
|
|
1232
|
-
const index = this.destinations.
|
|
1271
|
+
const index = this.destinations.findIndex((d) => d.dest === dest);
|
|
1233
1272
|
if (index !== -1) this.destinations.splice(index, 1);
|
|
1234
1273
|
}
|
|
1235
1274
|
query(filter) {
|
|
@@ -5161,6 +5200,17 @@ var CapabilityRegistry = class CapabilityRegistry {
|
|
|
5161
5200
|
if (!state || state.definition.mode !== "collection") return [];
|
|
5162
5201
|
return [...state.providers.entries()].filter(([addonId]) => !state.disabledProviders.has(addonId));
|
|
5163
5202
|
}
|
|
5203
|
+
/**
|
|
5204
|
+
* Is `addonId` currently DISABLED as a provider of `capability`?
|
|
5205
|
+
*
|
|
5206
|
+
* Exposed because the disabled set was write-only for `log-destination`:
|
|
5207
|
+
* the orchestrator toggle added the addon here and nothing ever read it, so
|
|
5208
|
+
* console and winston could not be turned off at all while the UI badge said
|
|
5209
|
+
* they were (audit 2026-07-30).
|
|
5210
|
+
*/
|
|
5211
|
+
isProviderDisabled(capability, addonId) {
|
|
5212
|
+
return this.capabilities.get(capability)?.disabledProviders.has(addonId) === true;
|
|
5213
|
+
}
|
|
5164
5214
|
/** Get the full CapabilityDefinition for a declared capability. */
|
|
5165
5215
|
getDefinition(capability) {
|
|
5166
5216
|
return this.capabilities.get(capability)?.definition;
|
|
@@ -311,6 +311,15 @@ export declare class CapabilityRegistry {
|
|
|
311
311
|
*/
|
|
312
312
|
getCollectionEntries<K extends keyof CollectionCapabilityMap>(capability: K): readonly (readonly [string, CollectionCapabilityMap[K]])[];
|
|
313
313
|
getCollectionEntries<T = unknown>(capability: string): readonly (readonly [string, T])[];
|
|
314
|
+
/**
|
|
315
|
+
* Is `addonId` currently DISABLED as a provider of `capability`?
|
|
316
|
+
*
|
|
317
|
+
* Exposed because the disabled set was write-only for `log-destination`:
|
|
318
|
+
* the orchestrator toggle added the addon here and nothing ever read it, so
|
|
319
|
+
* console and winston could not be turned off at all while the UI badge said
|
|
320
|
+
* they were (audit 2026-07-30).
|
|
321
|
+
*/
|
|
322
|
+
isProviderDisabled(capability: string, addonId: string): boolean;
|
|
314
323
|
/** Get the full CapabilityDefinition for a declared capability. */
|
|
315
324
|
getDefinition(capability: string): CapabilityDefinition | undefined;
|
|
316
325
|
/** Get the mode declared for a capability. */
|
|
@@ -79,6 +79,34 @@ export interface LocalChildRegistryOptions {
|
|
|
79
79
|
* distinguish a pin-to-self from a pin-to-another-node.
|
|
80
80
|
*/
|
|
81
81
|
readonly ownNodeId?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Does `(capName, method)` name a `mode:'collection'` array method whose
|
|
84
|
+
* contract is the UNION across every provider? Such a call must NOT be
|
|
85
|
+
* short-circuited to a local sibling: `resolveChildId` picks ONE provider
|
|
86
|
+
* (first-registered, or the singleton preference — meaningless here), so the
|
|
87
|
+
* caller would see a fraction of the catalog the hub's own tRPC router
|
|
88
|
+
* returns. It goes to `onUnownedCall`, which owns the aggregate over local
|
|
89
|
+
* siblings, in-process providers and remote nodes alike.
|
|
90
|
+
*
|
|
91
|
+
* `notification-output` has three provider siblings; the Notification Center
|
|
92
|
+
* landed on one holding no targets, read the empty list as "target deleted"
|
|
93
|
+
* and permanently dead-lettered every notification it ever produced
|
|
94
|
+
* (2026-07-30). Omitted ⇒ prior behaviour exactly (sibling short-circuit for
|
|
95
|
+
* everything). The kernel has no cap registry, so the wiring side injects it.
|
|
96
|
+
*/
|
|
97
|
+
readonly isAggregatedCollectionMethod?: (capName: string, method: string) => boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Per-cap-method bound for UDS cap calls, in ms — read from the cap
|
|
100
|
+
* definition's declared `timeoutMs`. The SAME resolver the remote plane uses
|
|
101
|
+
* (`CapRouteResolver.capTimeoutMs`), so the two planes agree by construction
|
|
102
|
+
* instead of by coincidence.
|
|
103
|
+
*
|
|
104
|
+
* Without this the channel default (60 s) would apply to everything, and a
|
|
105
|
+
* legitimately long method — `applyServerUpdate` declares 16 min because it
|
|
106
|
+
* awaits a full npm install — would be cut off mid-run. Omitted ⇒ the
|
|
107
|
+
* channel default for every cap call.
|
|
108
|
+
*/
|
|
109
|
+
readonly capTimeoutMs?: (capName: string, method: string) => number | undefined;
|
|
82
110
|
}
|
|
83
111
|
/**
|
|
84
112
|
* Parent-side authority for local addon-runners reachable over a
|
|
@@ -100,6 +128,10 @@ export declare class LocalChildRegistry {
|
|
|
100
128
|
private readonly resolveChildIdForAddon?;
|
|
101
129
|
/** This node's own Moleculer nodeId (see {@link LocalChildRegistryOptions.ownNodeId}). */
|
|
102
130
|
private readonly ownNodeId?;
|
|
131
|
+
/** See {@link LocalChildRegistryOptions.isAggregatedCollectionMethod}. */
|
|
132
|
+
private readonly isAggregatedCollectionMethod?;
|
|
133
|
+
/** See {@link LocalChildRegistryOptions.capTimeoutMs}. */
|
|
134
|
+
private readonly capTimeoutMs?;
|
|
103
135
|
/** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
|
|
104
136
|
private readonly egressRoutedCaps;
|
|
105
137
|
/** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
|
|
@@ -209,6 +241,12 @@ export declare class LocalChildRegistry {
|
|
|
209
241
|
*/
|
|
210
242
|
isChildKnown(childId: string): boolean;
|
|
211
243
|
childProvides(childId: string, capName: string, deviceId?: number): boolean;
|
|
244
|
+
/**
|
|
245
|
+
* Per-request options for a cap call: the cap method's declared `timeoutMs`
|
|
246
|
+
* when one exists, else `undefined` so the channel applies its default.
|
|
247
|
+
* Mirrors `CapRouteResolver.callTimeout` for the remote plane.
|
|
248
|
+
*/
|
|
249
|
+
private capRequestOpts;
|
|
212
250
|
/** Forward a cap method call to a SPECIFIC child by id over UDS; rejects if that child is absent. */
|
|
213
251
|
callCapOnChild(childId: string, input: CapCallInput): Promise<unknown>;
|
|
214
252
|
/** Forward a cap method call to the owning child over UDS; rejects if none. */
|
|
@@ -19,9 +19,19 @@ export type Frame = {
|
|
|
19
19
|
};
|
|
20
20
|
export type RequestHandler = (body: unknown) => Promise<unknown>;
|
|
21
21
|
export type EventHandler = (body: unknown) => void;
|
|
22
|
+
/** Per-request options a {@link LocalChannel} implementation may honour. */
|
|
23
|
+
export interface LocalRequestOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Bound on this request in ms; `0` or negative disables it. Omitted ⇒ the
|
|
26
|
+
* implementation's default. Cap calls pass the cap method's declared
|
|
27
|
+
* `timeoutMs` so a legitimately long method (`applyServerUpdate`, 16 min) is
|
|
28
|
+
* not cut short.
|
|
29
|
+
*/
|
|
30
|
+
readonly timeoutMs?: number;
|
|
31
|
+
}
|
|
22
32
|
/** A bidirectional request/response + one-way event channel over one socket. */
|
|
23
33
|
export interface LocalChannel {
|
|
24
|
-
request(body: unknown): Promise<unknown>;
|
|
34
|
+
request(body: unknown, opts?: LocalRequestOptions): Promise<unknown>;
|
|
25
35
|
emit(body: unknown): void;
|
|
26
36
|
onRequest(handler: RequestHandler): void;
|
|
27
37
|
onEvent(handler: EventHandler): void;
|
|
@@ -110,6 +110,28 @@ export interface ParentUnownedCallDeps {
|
|
|
110
110
|
* `CapabilityDefinition`s and the registry) injects this.
|
|
111
111
|
*/
|
|
112
112
|
readonly resolveEmptyCollection?: (capName: string, method: string) => readonly unknown[] | null;
|
|
113
|
+
/**
|
|
114
|
+
* Optional collection FAN-OUT. Returns the union of `method` across every
|
|
115
|
+
* registered provider of a `mode:'collection'` cap, or `null` when the
|
|
116
|
+
* (cap, method) pair is not an aggregated collection array-method.
|
|
117
|
+
*
|
|
118
|
+
* Why: routing resolves ONE provider (`resolveChildId` picks the
|
|
119
|
+
* first-registered child, or the singleton preference — meaningless for a
|
|
120
|
+
* collection). The hub's tRPC cap-router instead fans array methods across
|
|
121
|
+
* every provider (`concatArrayMethod`). A forked addon calling the same cap
|
|
122
|
+
* through `ctx.api` therefore saw a DIFFERENT, smaller answer than the hub's
|
|
123
|
+
* own router — and for `notification-output`, whose three providers include
|
|
124
|
+
* two that hold no targets, it saw `[]`. The Notification Center read that
|
|
125
|
+
* empty catalog as "the target was deleted" and permanently dead-lettered
|
|
126
|
+
* every notification it ever produced (2026-07-30).
|
|
127
|
+
*
|
|
128
|
+
* Skipped when the call carries an explicit `nodeId` pin — a pin must route
|
|
129
|
+
* to its node, never be answered by a cluster-wide union. The kernel layer
|
|
130
|
+
* has no cap registry, so the wiring side injects this (same shape as
|
|
131
|
+
* {@link resolveEmptyCollection}, and it shares that path's aggregation code
|
|
132
|
+
* so the two answers cannot drift).
|
|
133
|
+
*/
|
|
134
|
+
readonly callCollectionAggregate?: (capName: string, method: string, args: unknown) => Promise<readonly unknown[] | null>;
|
|
113
135
|
/**
|
|
114
136
|
* Optional predicate — returns `true` when `capName` is a HUB-CORE `$`-service
|
|
115
137
|
* cap (`system`, `addons`, `nodes`, `capabilities`, `stream-probe`, …): served
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { Socket } from 'node:net';
|
|
2
2
|
import { LocalChannel, RequestHandler, EventHandler } from './local-transport.js';
|
|
3
|
+
/**
|
|
4
|
+
* Default bound on one UDS request, when the caller names none.
|
|
5
|
+
*
|
|
6
|
+
* Chosen to match `REMOTE_CALL_TIMEOUT_MS` in `cap-route-resolver.ts`: the SAME
|
|
7
|
+
* cap method already dies at 60 s when it resolves to a remote node, so
|
|
8
|
+
* applying the same bound locally makes the two planes consistent rather than
|
|
9
|
+
* newly restrictive. Callers with a legitimately longer method (the cap
|
|
10
|
+
* layer's declared `timeoutMs`, e.g. `applyServerUpdate` at 16 min) pass it
|
|
11
|
+
* explicitly.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_UDS_REQUEST_TIMEOUT_MS = 60000;
|
|
14
|
+
/** Per-request options for {@link SocketChannel.request}. */
|
|
15
|
+
export interface SocketRequestOptions {
|
|
16
|
+
/** Bound in ms; `0` or negative disables the timer entirely. */
|
|
17
|
+
readonly timeoutMs?: number;
|
|
18
|
+
}
|
|
3
19
|
export declare class SocketChannel implements LocalChannel {
|
|
4
20
|
private readonly socket;
|
|
5
21
|
private nextId;
|
|
@@ -11,7 +27,18 @@ export declare class SocketChannel implements LocalChannel {
|
|
|
11
27
|
private closed;
|
|
12
28
|
private closeFired;
|
|
13
29
|
constructor(socket: Socket);
|
|
14
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Send a request and wait for the peer's response.
|
|
32
|
+
*
|
|
33
|
+
* Bounded. Without a timer this settles on exactly two events — a matching
|
|
34
|
+
* `res` frame, or the socket's `close` (which drains `pending`) — so a peer
|
|
35
|
+
* that receives the request and then neither answers nor closes leaves the
|
|
36
|
+
* caller pending forever. That is a wedged handler, or a half-open socket
|
|
37
|
+
* after a child died without a clean teardown, and it is how camera 615 spent
|
|
38
|
+
* 30+ minutes with every inference permit held by a call that was never
|
|
39
|
+
* coming back.
|
|
40
|
+
*/
|
|
41
|
+
request(body: unknown, opts?: SocketRequestOptions): Promise<unknown>;
|
|
15
42
|
emit(body: unknown): void;
|
|
16
43
|
/** Replace the request handler. Only one handler is active at a time. */
|
|
17
44
|
onRequest(handler: RequestHandler): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LogEntry, LogFilter } from './log-ring-buffer.js';
|
|
1
|
+
import { LogEntry, LogFilter, LogLevel } from './log-ring-buffer.js';
|
|
2
2
|
import { PartitionedLogBufferOptions } from './partitioned-log-buffer.js';
|
|
3
3
|
import { IScopedLogger } from './scoped-logger.js';
|
|
4
4
|
export interface ILogDestination {
|
|
@@ -7,9 +7,31 @@ export interface ILogDestination {
|
|
|
7
7
|
write(entry: LogEntry): void;
|
|
8
8
|
query?(filter: LogFilter): Promise<readonly LogEntry[]>;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The reserved destination id of the in-memory buffer. Not an addon: the
|
|
12
|
+
* buffer must exist before any addon loads (boot lines) and it backs
|
|
13
|
+
* `logs.query`/`subscribe`, so it lives in the kernel — but it answers to the
|
|
14
|
+
* SAME orchestrator toggle as every other destination.
|
|
15
|
+
*/
|
|
16
|
+
export declare const LOG_BUFFER_DESTINATION_ID = "log-buffer";
|
|
10
17
|
export declare class LogManager {
|
|
11
18
|
private readonly ringBuffer;
|
|
19
|
+
/**
|
|
20
|
+
* Destinations, each tagged with the addon that provides it (when known) so
|
|
21
|
+
* the fan-out can honour the operator's per-destination toggle.
|
|
22
|
+
*/
|
|
12
23
|
private readonly destinations;
|
|
24
|
+
/**
|
|
25
|
+
* "Is this destination enabled?", injected by the wiring side (which owns
|
|
26
|
+
* the capability registry). Absent ⇒ everything is enabled.
|
|
27
|
+
*
|
|
28
|
+
* WHY: the orchestrator's Enable/Disable toggle only added the addon to
|
|
29
|
+
* `disabledProviders` and NOTHING consulted it for `log-destination` — so
|
|
30
|
+
* console and winston could not be turned off at all and the badge in the UI
|
|
31
|
+
* lied, while Loki appeared to work only because it carried a SECOND,
|
|
32
|
+
* private `enabled` setting of its own (audit 2026-07-30).
|
|
33
|
+
*/
|
|
34
|
+
private isDestinationEnabled?;
|
|
13
35
|
private readonly subscribers;
|
|
14
36
|
private deviceNameLookup;
|
|
15
37
|
/**
|
|
@@ -48,8 +70,17 @@ export declare class LogManager {
|
|
|
48
70
|
* traffic starts. Lets a destination added mid-boot
|
|
49
71
|
* still receive boot-time log entries.
|
|
50
72
|
*/
|
|
73
|
+
/** Apply new in-memory buffer limits at runtime (see PartitionedLogBuffer). */
|
|
74
|
+
reconfigureBuffer(options: {
|
|
75
|
+
readonly perAddonCapacity?: number;
|
|
76
|
+
readonly maxTotalEntries?: number | null;
|
|
77
|
+
readonly pruneLevel?: LogLevel;
|
|
78
|
+
}): void;
|
|
79
|
+
/** Set the per-destination enablement check (see {@link isDestinationEnabled}). */
|
|
80
|
+
setDestinationEnabledCheck(check: ((addonId: string) => boolean) | null): void;
|
|
51
81
|
addDestination(dest: ILogDestination, opts?: {
|
|
52
82
|
replay?: boolean;
|
|
83
|
+
addonId?: string;
|
|
53
84
|
}): void;
|
|
54
85
|
removeDestination(dest: ILogDestination): void;
|
|
55
86
|
query(filter: LogFilter): LogEntry[];
|
|
@@ -42,12 +42,24 @@ export interface PartitionedLogBufferOptions {
|
|
|
42
42
|
*/
|
|
43
43
|
export declare class PartitionedLogBuffer {
|
|
44
44
|
private readonly buffers;
|
|
45
|
-
private
|
|
46
|
-
private
|
|
47
|
-
private
|
|
45
|
+
private perAddonCapacity;
|
|
46
|
+
private maxTotalEntries;
|
|
47
|
+
private pruneLevel;
|
|
48
48
|
/** Running total, so `push` does not walk every bucket to decide. */
|
|
49
49
|
private totalEntries;
|
|
50
50
|
constructor(perAddonCapacity?: number, options?: PartitionedLogBufferOptions);
|
|
51
|
+
/**
|
|
52
|
+
* Apply new limits at runtime. Shrinking takes effect as entries are pushed
|
|
53
|
+
* (each partition trims to the new capacity on its next write) — existing
|
|
54
|
+
* entries are NOT dropped eagerly: losing history the operator is currently
|
|
55
|
+
* reading, the instant they lower a number, is a worse surprise than a
|
|
56
|
+
* momentarily oversized buffer.
|
|
57
|
+
*/
|
|
58
|
+
reconfigure(options: {
|
|
59
|
+
readonly perAddonCapacity?: number;
|
|
60
|
+
readonly maxTotalEntries?: number | null;
|
|
61
|
+
readonly pruneLevel?: LogLevel;
|
|
62
|
+
}): void;
|
|
51
63
|
private bufferFor;
|
|
52
64
|
push(entry: LogEntry): void;
|
|
53
65
|
/**
|
|
@@ -3526,8 +3526,6 @@ function localEndpointPath(nodeId) {
|
|
|
3526
3526
|
if (process.platform === "win32") return `\\\\.\\pipe\\camstack-${safe}`;
|
|
3527
3527
|
return (0, node_path.join)((0, node_os.tmpdir)(), `camstack-${safe}.sock`);
|
|
3528
3528
|
}
|
|
3529
|
-
//#endregion
|
|
3530
|
-
//#region src/kernel/transport/socket-channel.ts
|
|
3531
3529
|
var SocketChannel = class {
|
|
3532
3530
|
socket;
|
|
3533
3531
|
nextId = 1;
|
|
@@ -3546,13 +3544,34 @@ var SocketChannel = class {
|
|
|
3546
3544
|
socket.on("close", () => this.handleClose());
|
|
3547
3545
|
socket.on("error", () => {});
|
|
3548
3546
|
}
|
|
3549
|
-
|
|
3547
|
+
/**
|
|
3548
|
+
* Send a request and wait for the peer's response.
|
|
3549
|
+
*
|
|
3550
|
+
* Bounded. Without a timer this settles on exactly two events — a matching
|
|
3551
|
+
* `res` frame, or the socket's `close` (which drains `pending`) — so a peer
|
|
3552
|
+
* that receives the request and then neither answers nor closes leaves the
|
|
3553
|
+
* caller pending forever. That is a wedged handler, or a half-open socket
|
|
3554
|
+
* after a child died without a clean teardown, and it is how camera 615 spent
|
|
3555
|
+
* 30+ minutes with every inference permit held by a call that was never
|
|
3556
|
+
* coming back.
|
|
3557
|
+
*/
|
|
3558
|
+
request(body, opts) {
|
|
3550
3559
|
if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("channel closed"));
|
|
3551
3560
|
const id = this.nextId++;
|
|
3561
|
+
const timeoutMs = opts?.timeoutMs ?? 6e4;
|
|
3552
3562
|
return new Promise((resolve, reject) => {
|
|
3563
|
+
let timer = null;
|
|
3564
|
+
if (timeoutMs > 0) {
|
|
3565
|
+
timer = setTimeout(() => {
|
|
3566
|
+
this.pending.delete(id);
|
|
3567
|
+
reject(/* @__PURE__ */ new Error(`UDS request timed out after ${timeoutMs}ms`));
|
|
3568
|
+
}, timeoutMs);
|
|
3569
|
+
timer.unref?.();
|
|
3570
|
+
}
|
|
3553
3571
|
this.pending.set(id, {
|
|
3554
3572
|
resolve,
|
|
3555
|
-
reject
|
|
3573
|
+
reject,
|
|
3574
|
+
timer
|
|
3556
3575
|
});
|
|
3557
3576
|
this.send({
|
|
3558
3577
|
k: "req",
|
|
@@ -3616,6 +3635,7 @@ var SocketChannel = class {
|
|
|
3616
3635
|
const p = this.pending.get(frame.id);
|
|
3617
3636
|
if (!p) return;
|
|
3618
3637
|
this.pending.delete(frame.id);
|
|
3638
|
+
if (p.timer !== null) clearTimeout(p.timer);
|
|
3619
3639
|
if (frame.ok) p.resolve(frame.body);
|
|
3620
3640
|
else p.reject(new Error(frame.err));
|
|
3621
3641
|
return;
|
|
@@ -3626,7 +3646,10 @@ var SocketChannel = class {
|
|
|
3626
3646
|
if (this.closeFired) return;
|
|
3627
3647
|
this.closeFired = true;
|
|
3628
3648
|
this.closed = true;
|
|
3629
|
-
for (const [, p] of this.pending)
|
|
3649
|
+
for (const [, p] of this.pending) {
|
|
3650
|
+
if (p.timer !== null) clearTimeout(p.timer);
|
|
3651
|
+
p.reject(/* @__PURE__ */ new Error("channel closed"));
|
|
3652
|
+
}
|
|
3630
3653
|
this.pending.clear();
|
|
3631
3654
|
this.closeHandler();
|
|
3632
3655
|
}
|
|
@@ -4303,6 +4326,10 @@ var LocalChildRegistry = class {
|
|
|
4303
4326
|
resolveChildIdForAddon;
|
|
4304
4327
|
/** This node's own Moleculer nodeId (see {@link LocalChildRegistryOptions.ownNodeId}). */
|
|
4305
4328
|
ownNodeId;
|
|
4329
|
+
/** See {@link LocalChildRegistryOptions.isAggregatedCollectionMethod}. */
|
|
4330
|
+
isAggregatedCollectionMethod;
|
|
4331
|
+
/** See {@link LocalChildRegistryOptions.capTimeoutMs}. */
|
|
4332
|
+
capTimeoutMs;
|
|
4306
4333
|
/** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
|
|
4307
4334
|
egressRoutedCaps = /* @__PURE__ */ new Set();
|
|
4308
4335
|
/** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
|
|
@@ -4331,6 +4358,8 @@ var LocalChildRegistry = class {
|
|
|
4331
4358
|
this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
|
|
4332
4359
|
this.resolveChildIdForAddon = opts.resolveChildIdForAddon;
|
|
4333
4360
|
this.ownNodeId = opts.ownNodeId;
|
|
4361
|
+
this.isAggregatedCollectionMethod = opts.isAggregatedCollectionMethod;
|
|
4362
|
+
this.capTimeoutMs = opts.capTimeoutMs;
|
|
4334
4363
|
} else {
|
|
4335
4364
|
this.server = serverOrOptions;
|
|
4336
4365
|
this.onUnownedCall = onUnownedCallArg;
|
|
@@ -4493,11 +4522,20 @@ var LocalChildRegistry = class {
|
|
|
4493
4522
|
if (deviceId !== void 0 && entry.caps.some((cap) => cap.capName === capName && cap.deviceId === deviceId)) return true;
|
|
4494
4523
|
return entry.caps.some((cap) => cap.capName === capName && cap.deviceId === void 0);
|
|
4495
4524
|
}
|
|
4525
|
+
/**
|
|
4526
|
+
* Per-request options for a cap call: the cap method's declared `timeoutMs`
|
|
4527
|
+
* when one exists, else `undefined` so the channel applies its default.
|
|
4528
|
+
* Mirrors `CapRouteResolver.callTimeout` for the remote plane.
|
|
4529
|
+
*/
|
|
4530
|
+
capRequestOpts(input) {
|
|
4531
|
+
const declared = this.capTimeoutMs?.(input.capName, input.method);
|
|
4532
|
+
return typeof declared === "number" ? { timeoutMs: declared } : void 0;
|
|
4533
|
+
}
|
|
4496
4534
|
/** Forward a cap method call to a SPECIFIC child by id over UDS; rejects if that child is absent. */
|
|
4497
4535
|
async callCapOnChild(childId, input) {
|
|
4498
4536
|
const entry = this.children.get(childId);
|
|
4499
4537
|
if (!entry) throw new Error(`no local child "${childId}" for cap "${input.capName}"`);
|
|
4500
|
-
return entry.channel.request(this.toCapCall(input));
|
|
4538
|
+
return entry.channel.request(this.toCapCall(input), this.capRequestOpts(input));
|
|
4501
4539
|
}
|
|
4502
4540
|
/** Forward a cap method call to the owning child over UDS; rejects if none. */
|
|
4503
4541
|
async callCap(input) {
|
|
@@ -4507,7 +4545,7 @@ var LocalChildRegistry = class {
|
|
|
4507
4545
|
const where = input.deviceId === void 0 ? "" : ` for device ${input.deviceId}`;
|
|
4508
4546
|
throw new Error(`no local child provides cap "${input.capName}"${where}`);
|
|
4509
4547
|
}
|
|
4510
|
-
return entry.channel.request(this.toCapCall(input));
|
|
4548
|
+
return entry.channel.request(this.toCapCall(input), this.capRequestOpts(input));
|
|
4511
4549
|
}
|
|
4512
4550
|
/**
|
|
4513
4551
|
* Forward an ADDON-LEVEL call (routes / custom-action) to a SPECIFIC child
|
|
@@ -4694,7 +4732,7 @@ var LocalChildRegistry = class {
|
|
|
4694
4732
|
};
|
|
4695
4733
|
const pinnedNodeId = out.nodeId ?? extractNodeId(out.args);
|
|
4696
4734
|
const pinTargetsThisNode = pinnedNodeId !== void 0 && this.ownNodeId !== void 0 && pinnedNodeId === this.ownNodeId;
|
|
4697
|
-
if ((pinnedNodeId === void 0 || pinTargetsThisNode ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
|
|
4735
|
+
if ((!(pinnedNodeId === void 0 && this.isAggregatedCollectionMethod?.(out.capName, out.method) === true) && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null) !== null) {
|
|
4698
4736
|
if (!this.egressRoutedCaps.has(out.capName)) {
|
|
4699
4737
|
this.egressRoutedCaps.add(out.capName);
|
|
4700
4738
|
this.logger?.info("routed child egress over UDS", { capName: out.capName });
|
|
@@ -5250,6 +5288,8 @@ function createParentUnownedCallHandler(deps) {
|
|
|
5250
5288
|
if (nodeId === void 0) {
|
|
5251
5289
|
const empty = deps.resolveEmptyCollection?.(input.capName, input.method);
|
|
5252
5290
|
if (empty != null) return empty;
|
|
5291
|
+
const aggregate = await deps.callCollectionAggregate?.(input.capName, input.method, input.args);
|
|
5292
|
+
if (aggregate != null) return aggregate;
|
|
5253
5293
|
}
|
|
5254
5294
|
const resolver = deps.getResolver();
|
|
5255
5295
|
if (resolver !== null) try {
|
|
@@ -5417,14 +5457,18 @@ function localProviderLink(resolver, observerOpts) {
|
|
|
5417
5457
|
return ({ op, next }) => {
|
|
5418
5458
|
const parsed = parsePath(op.path);
|
|
5419
5459
|
if (!parsed) return next(op);
|
|
5460
|
+
const opWithDefaults = op.input === void 0 ? op : {
|
|
5461
|
+
...op,
|
|
5462
|
+
input: (0, _camstack_types_addon.withCapInputDefaults)(parsed.capName, parsed.method, op.input)
|
|
5463
|
+
};
|
|
5420
5464
|
if (localBase !== void 0) {
|
|
5421
5465
|
const pinnedNodeId = (0, _camstack_types_addon.readNodePin)(op.context);
|
|
5422
|
-
if (pinnedNodeId !== void 0 && pinnedNodeId !== localBase) return next(
|
|
5466
|
+
if (pinnedNodeId !== void 0 && pinnedNodeId !== localBase) return next(opWithDefaults);
|
|
5423
5467
|
}
|
|
5424
5468
|
const provider = resolver.getByName(parsed.capName);
|
|
5425
|
-
if (!provider || typeof provider !== "object") return next(
|
|
5469
|
+
if (!provider || typeof provider !== "object") return next(opWithDefaults);
|
|
5426
5470
|
const fn = Reflect.get(provider, parsed.method);
|
|
5427
|
-
if (typeof fn !== "function") return next(
|
|
5471
|
+
if (typeof fn !== "function") return next(opWithDefaults);
|
|
5428
5472
|
if (observerOpts) try {
|
|
5429
5473
|
const providerAddonId = observerOpts.providerAddonIdForCap(parsed.capName);
|
|
5430
5474
|
if (providerAddonId) observerOpts.observer({
|
|
@@ -5436,7 +5480,7 @@ function localProviderLink(resolver, observerOpts) {
|
|
|
5436
5480
|
});
|
|
5437
5481
|
} catch {}
|
|
5438
5482
|
return observable((observer) => {
|
|
5439
|
-
Promise.resolve(fn.call(provider,
|
|
5483
|
+
Promise.resolve(fn.call(provider, opWithDefaults.input)).then((data) => {
|
|
5440
5484
|
observer.next({ result: {
|
|
5441
5485
|
type: "data",
|
|
5442
5486
|
data
|