@camstack/system 1.2.69 → 1.2.71

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.
Files changed (70) hide show
  1. package/dist/addon-runner.js +6 -6
  2. package/dist/addon-runner.mjs +3 -3
  3. package/dist/addon-utils.js +11 -11
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/auth/auth-manager.d.ts +13 -29
  6. package/dist/auth/scope-matcher.d.ts +6 -5
  7. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +3 -3
  8. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +2 -2
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +3 -3
  10. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +2 -2
  11. package/dist/builtins/alerts/alerts.addon.js +1 -1
  12. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  13. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +8 -8
  14. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +6 -6
  15. package/dist/builtins/console-logging/index.js +1 -1
  16. package/dist/builtins/console-logging/index.mjs +1 -1
  17. package/dist/builtins/core-blocks/core-blocks.addon.js +3 -3
  18. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  19. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  20. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  21. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  22. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  23. package/dist/builtins/hub-forwarder/index.js +1 -1
  24. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  25. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  26. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  27. package/dist/builtins/local-auth/index.d.ts +0 -1
  28. package/dist/builtins/local-auth/index.js +0 -1
  29. package/dist/builtins/local-auth/index.mjs +2 -2
  30. package/dist/builtins/local-auth/local-auth.addon.js +4878 -4883
  31. package/dist/builtins/local-auth/local-auth.addon.mjs +4879 -4883
  32. package/dist/builtins/local-auth/oauth-grants.d.ts +15 -0
  33. package/dist/builtins/local-network/local-network.addon.js +1 -1
  34. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  35. package/dist/builtins/loki-logging/index.js +1 -1
  36. package/dist/builtins/loki-logging/index.mjs +1 -1
  37. package/dist/builtins/native-metrics/native-metrics.addon.js +5 -5
  38. package/dist/builtins/native-metrics/native-metrics.addon.mjs +4 -4
  39. package/dist/builtins/platform-probe/index.js +4 -4
  40. package/dist/builtins/platform-probe/index.mjs +3 -3
  41. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  42. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  43. package/dist/builtins/snapshot/index.js +2 -2
  44. package/dist/builtins/snapshot/index.mjs +2 -2
  45. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +4 -4
  46. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +3 -3
  47. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  48. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  49. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +3 -3
  50. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -2
  51. package/dist/builtins/system-config/system-config.addon.js +1 -1
  52. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  53. package/dist/builtins/winston-logging/index.js +1 -1
  54. package/dist/builtins/winston-logging/index.mjs +1 -1
  55. package/dist/{dist-C-QzFJ8v.mjs → dist-Daxx9CB0.mjs} +139 -5
  56. package/dist/{dist-D7wC-S4p.js → dist-I0WgqQQ-.js} +150 -10
  57. package/dist/{model-download-service-CTBHzORJ.mjs → file-data-plane-CuE_hBli.mjs} +848 -848
  58. package/dist/index.d.ts +73 -81
  59. package/dist/index.js +1302 -1278
  60. package/dist/index.mjs +1283 -1259
  61. package/dist/kernel/capability-registry.d.ts +26 -0
  62. package/dist/kernel/config-schema.d.ts +1 -1
  63. package/dist/kernel/transport/parent-unowned-call.d.ts +27 -0
  64. package/dist/{manifest-python-deps-Dz1ryHIN.mjs → manifest-python-deps-BdWxKNvu.mjs} +49 -47
  65. package/dist/{manifest-python-deps-BE6l8Y4W.js → manifest-python-deps-BqIpg0C7.js} +49 -47
  66. package/package.json +1 -4
  67. package/dist/builtins/local-auth/homeassistant-integration.d.ts +0 -7
  68. package/dist/{model-download-service-Caj1kaSi.js → file-data-plane-DUHPHa-Y.js} +847 -847
  69. package/dist/{resource-monitor-BkP504Vq.mjs → resource-monitor-BWmQ5i-o.mjs} +1 -1
  70. package/dist/{resource-monitor-DNNomR-i.js → resource-monitor-CdnzxBLP.js} +1 -1
@@ -30,6 +30,19 @@ export declare class CapabilityRegistry {
30
30
  private _ready;
31
31
  /** Boot-time dependency graph: capability → dependsOn names (from CapabilityDeclaration) */
32
32
  private readonly capabilityDeps;
33
+ /**
34
+ * capability → addon ids whose MANIFEST declares it.
35
+ *
36
+ * Populated by {@link declareFromManifest}, which runs at addon-LOAD time —
37
+ * before a forked addon's runner has spawned, let alone registered anything.
38
+ * So this map answers a question `providers` cannot: **which addons still owe
39
+ * this capability a provider**, i.e. is an absence final or merely early.
40
+ *
41
+ * It is not a second registry and nothing waits on it: it stores an argument
42
+ * `declareFromManifest` was already being handed and dropping, and it is read
43
+ * synchronously at the moment a caller needs the answer ([D3](../../../../docs/decisions/adr-0003.md)).
44
+ */
45
+ private readonly manifestDeclarers;
33
46
  /** Per-device singleton overrides: deviceId → (capability → addonId) */
34
47
  private readonly deviceOverrides;
35
48
  /** capName → (nodeId → bare addonId) per-node singleton overrides. */
@@ -147,6 +160,19 @@ export declare class CapabilityRegistry {
147
160
  * re-register the same pair must unregister first.
148
161
  */
149
162
  hasProvider(capabilityName: string, addonId: string): boolean;
163
+ /**
164
+ * Addon ids whose manifest declares `capabilityName`, whether or not they
165
+ * have registered a provider yet.
166
+ *
167
+ * Pair it with {@link hasProvider} to separate "this capability's collection
168
+ * is still filling up" from "this is everything there will ever be" — the
169
+ * difference between a 503 a client should retry and a 400 it should not.
170
+ * Every entry is an addon INSTALLED on this node; an addon that is installed
171
+ * but disabled therefore stays listed, which is the honest answer (the
172
+ * capability is owed by something that is not going to supply it, and naming
173
+ * it is the diagnosis).
174
+ */
175
+ getManifestDeclarers(capabilityName: string): readonly string[];
150
176
  /** Unregister a provider. For singleton: clears active. For collection: removes from list. */
151
177
  unregisterProvider(capabilityName: string, addonId: string): void;
152
178
  /** Enable a previously disabled collection provider. */
@@ -5,8 +5,8 @@ export declare const DEFAULT_DATA_PATH = "camstack-data";
5
5
  * All other settings live in SQL (system_settings table). */
6
6
  export declare const bootstrapSchema: z.ZodObject<{
7
7
  mode: z.ZodDefault<z.ZodEnum<{
8
- hub: "hub";
9
8
  agent: "agent";
9
+ hub: "hub";
10
10
  }>>;
11
11
  server: z.ZodDefault<z.ZodObject<{
12
12
  port: z.ZodDefault<z.ZodNumber>;
@@ -132,6 +132,33 @@ export interface ParentUnownedCallDeps {
132
132
  * so the two answers cannot drift).
133
133
  */
134
134
  readonly callCollectionAggregate?: (capName: string, method: string, args: unknown) => Promise<readonly unknown[] | null>;
135
+ /**
136
+ * Optional `{ addonId }` PIN for a collection cap's SCALAR method — the twin
137
+ * of {@link callCollectionAggregate}, for the same reason.
138
+ *
139
+ * On a system-scoped collection cap `addonId` is not provider data: the
140
+ * codegen injects it as the `{nodeId?, addonId?}` selector on every method,
141
+ * and the hub's tRPC cap-router routes by it. This handler lifted only
142
+ * `nodeId` and `deviceId` out of a child's args, so a forked addon's pin was
143
+ * DISCARDED IN SILENCE and the call was answered by whichever provider
144
+ * registered first. `broker.getBrokerConfig({id:'ha_001', addonId:'provider-
145
+ * homeassistant'})` therefore returned Homematic's `null` — indistinguishable
146
+ * from "no such broker" — and the Home Assistant export could never open a
147
+ * link. Every write-up that "proved the pin works" had measured the hub's
148
+ * tRPC surface, never this path (2026-08-09).
149
+ *
150
+ * Returns a `{ value }` box so a legitimate `null` result is distinguishable
151
+ * from "not a pinned collection call, route normally", and THROWS when the
152
+ * pin names a non-provider — a pin that silently degrades to another
153
+ * provider's answer is worse than no pin. Array methods are never pinned
154
+ * here: their contract is the union above.
155
+ *
156
+ * The kernel layer has no cap registry, so the wiring side injects this and
157
+ * it shares the tRPC path's provider resolution — the two must not drift.
158
+ */
159
+ readonly callPinnedCollection?: (capName: string, method: string, args: unknown) => Promise<{
160
+ readonly value: unknown;
161
+ } | null>;
135
162
  /**
136
163
  * Optional predicate — returns `true` when `capName` is a HUB-CORE `$`-service
137
164
  * cap (`system`, `addons`, `nodes`, `capabilities`, `stream-probe`, …): served
@@ -1,16 +1,16 @@
1
- import { ensureBinary, ensureFfmpeg, ensurePython, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
2
1
  import * as crypto$1 from "node:crypto";
3
2
  import { randomBytes, randomUUID } from "node:crypto";
4
- import { createServer } from "node:http";
5
- import * as fs from "node:fs";
3
+ import { ensureBinary, ensureFfmpeg, ensurePython, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
4
+ import { unlink } from "node:fs/promises";
6
5
  import * as path$1 from "node:path";
7
6
  import { isAbsolute, join } from "node:path";
8
- import { promisify } from "node:util";
9
- import { DATAPLANE_SECRET_HEADER, DeviceType, DisposerChain, EventCategory, ReadinessRegistry, asJsonObject, asString, createDeviceProxy, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, readNodePin, scopeKey, sleep, withCapInputDefaults } from "@camstack/types/addon";
10
7
  import { execFile } from "node:child_process";
8
+ import { promisify } from "node:util";
11
9
  import * as os from "node:os";
12
10
  import { tmpdir } from "node:os";
13
- import { unlink } from "node:fs/promises";
11
+ import * as fs from "node:fs";
12
+ import { createServer } from "node:http";
13
+ import { DATAPLANE_SECRET_HEADER, DeviceType, DisposerChain, EventCategory, ReadinessRegistry, asJsonObject, asString, createDeviceProxy, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, readNodePin, scopeKey, sleep, withCapInputDefaults } from "@camstack/types/addon";
14
14
  import { TRPCClientError, createTRPCClient } from "@trpc/client";
15
15
  import { connect, createServer as createServer$1 } from "node:net";
16
16
  //#region src/kernel/moleculer/addon-data-plane-facility.ts
@@ -129,46 +129,6 @@ function createAddonDataPlaneFacility(args) {
129
129
  };
130
130
  }
131
131
  //#endregion
132
- //#region src/kernel/addon-class-resolver.ts
133
- function isRecord(value) {
134
- return typeof value === "object" && value !== null && !Array.isArray(value);
135
- }
136
- /**
137
- * Resolve the addon class from a dynamically imported module.
138
- *
139
- * Two compilation patterns to handle:
140
- * 1. ESM with explicit `export default class …` — `mod.default` is the class.
141
- * 2. CJS via tsup that emits only named exports (no `module.exports = …`)
142
- * — `mod.default` is undefined; we must pick the right named export.
143
- *
144
- * The CJS path is the trap: an addon bundle can also re-export device
145
- * classes (e.g. `ReolinkCamera` extends `BaseDevice`) and helper
146
- * functions. Picking `Object.keys(mod)[0]` is bundler-order-dependent —
147
- * the first named export today is `ReolinkCamera`, whose `new` requires
148
- * a `DeviceContext` and crashes on `new MyClass()`.
149
- *
150
- * The contract is: the addon class extends `BaseAddon`, which has an
151
- * `initialize` method on its prototype. Device classes (`BaseDevice`)
152
- * do not. We probe for that method to disambiguate.
153
- */
154
- function isAddonConstructor(fn) {
155
- if (typeof fn !== "function") return false;
156
- const proto = fn.prototype;
157
- if (!isRecord(proto)) return false;
158
- return typeof proto["initialize"] === "function";
159
- }
160
- function resolveAddonClass(mod) {
161
- let candidate = mod["default"];
162
- if (isRecord(candidate) && "default" in candidate) candidate = candidate["default"];
163
- if (isAddonConstructor(candidate)) return candidate;
164
- if (isRecord(candidate)) {
165
- const fn = Object.values(candidate).find(isAddonConstructor);
166
- if (fn) return fn;
167
- }
168
- const namedAddon = Object.values(mod).find(isAddonConstructor);
169
- if (namedAddon) return namedAddon;
170
- }
171
- //#endregion
172
132
  //#region src/kernel/deps/npm-command.ts
173
133
  /**
174
134
  * npm invocation resolver — makes "is npm available?" a solved problem for
@@ -298,6 +258,46 @@ async function resolveUncached(options) {
298
258
  }
299
259
  }
300
260
  //#endregion
261
+ //#region src/kernel/addon-class-resolver.ts
262
+ function isRecord(value) {
263
+ return typeof value === "object" && value !== null && !Array.isArray(value);
264
+ }
265
+ /**
266
+ * Resolve the addon class from a dynamically imported module.
267
+ *
268
+ * Two compilation patterns to handle:
269
+ * 1. ESM with explicit `export default class …` — `mod.default` is the class.
270
+ * 2. CJS via tsup that emits only named exports (no `module.exports = …`)
271
+ * — `mod.default` is undefined; we must pick the right named export.
272
+ *
273
+ * The CJS path is the trap: an addon bundle can also re-export device
274
+ * classes (e.g. `ReolinkCamera` extends `BaseDevice`) and helper
275
+ * functions. Picking `Object.keys(mod)[0]` is bundler-order-dependent —
276
+ * the first named export today is `ReolinkCamera`, whose `new` requires
277
+ * a `DeviceContext` and crashes on `new MyClass()`.
278
+ *
279
+ * The contract is: the addon class extends `BaseAddon`, which has an
280
+ * `initialize` method on its prototype. Device classes (`BaseDevice`)
281
+ * do not. We probe for that method to disambiguate.
282
+ */
283
+ function isAddonConstructor(fn) {
284
+ if (typeof fn !== "function") return false;
285
+ const proto = fn.prototype;
286
+ if (!isRecord(proto)) return false;
287
+ return typeof proto["initialize"] === "function";
288
+ }
289
+ function resolveAddonClass(mod) {
290
+ let candidate = mod["default"];
291
+ if (isRecord(candidate) && "default" in candidate) candidate = candidate["default"];
292
+ if (isAddonConstructor(candidate)) return candidate;
293
+ if (isRecord(candidate)) {
294
+ const fn = Object.values(candidate).find(isAddonConstructor);
295
+ if (fn) return fn;
296
+ }
297
+ const namedAddon = Object.values(mod).find(isAddonConstructor);
298
+ if (namedAddon) return namedAddon;
299
+ }
300
+ //#endregion
301
301
  //#region src/kernel/deps/manifest-native-deps.ts
302
302
  var execFileAsync = promisify(execFile);
303
303
  /**
@@ -5421,6 +5421,8 @@ function createParentUnownedCallHandler(deps) {
5421
5421
  if (empty != null) return empty;
5422
5422
  const aggregate = await deps.callCollectionAggregate?.(input.capName, input.method, input.args);
5423
5423
  if (aggregate != null) return aggregate;
5424
+ const pinned = await deps.callPinnedCollection?.(input.capName, input.method, input.args);
5425
+ if (pinned !== null && pinned !== void 0) return pinned.value;
5424
5426
  }
5425
5427
  const resolver = deps.getResolver();
5426
5428
  if (resolver !== null) try {
@@ -7614,4 +7616,4 @@ async function installManifestPythonDeps(declaration, addonDir, deps, logger) {
7614
7616
  await deps.installPythonRequirements(reqAbs);
7615
7617
  }
7616
7618
  //#endregion
7617
- export { buildNativeCapProxy as $, createHubCapForwardService as A, AGENT_CAP_FWD_ACTION as B, brokerTransportLink as C, resolveAddonClass as Ct, localProviderLink as D, ipcParentLink as E, createUdsLogger as F, callWithServiceDiscovery as G, CapRouteResolver as H, createUdsLoggerWithControl as I, UdsLocalTransportServer as J, createLocalTransport as K, LocalChildClient as L, createUdsEventBridge as M, createUdsEventBus as N, HUB_CAP_FWD_ACTION as O, udsChildLogToWorkerEntry as P, encodeFrame as Q, LocalChildRegistry as R, brokerCallForCap as S, runNpm as St, ipcChildLink as T, CapRouteError as U, AGENT_CAP_FWD_SERVICE as V, classifyCapRoute as W, localEndpointPath as X, SocketChannel as Y, FrameDecoder as Z, createKernelHwAccel as _, CapabilityHandle as _t, getWorkerDeviceRegistry as a, setWorkerNativeCapsChangeListener as at, __resetCapUsageRegistryForTests as b, installManifestNativeDeps as bt, setHubConnected as c, NATIVE_PROVIDER_SERVICE_INFIX as ct, getBrokerEventBus as d, capBareAction as dt, buildUdsNativeCapProxy as et, getMoleculerEventStats as f, capServiceName as ft, AddonDepsManager as g, DeviceRegistry as gt, subscribePassthrough as h, serializeTypedArrays as ht, createUdsAddonContext as i, mountNativeCapService as it, createParentUnownedCallHandler as j, HUB_CAP_FWD_SERVICE as k, EVENT_TOPIC_PREFIX as l, capActionName as lt, setNodeEventInterest as m, deserializeTypedArrays as mt, adaptBrokerToCluster as n, getWorkerNativeCapProvider as nt, getOrInitReadinessRegistry as o, createAddonService as ot, registerEventBusService as p, parseCapAction as pt, UdsLocalTransportClient as q, createAddonContext as r, getWorkerNativeCapSnapshot as rt, getOrInitReadinessRegistryForClient as s, validateProviderRegistrations as st, installManifestPythonDeps as t, createBrokerDeviceManagerApi as tt, clusterEventTopic as u, capActionSuffix as ut, resolveHwAccel as v, CapabilityUnavailableError as vt, buildLinkChain as w, createAddonDataPlaneFacility as wt, getCapUsageRegistry as x, resolveNpmInvocation as xt, CapUsageRegistry as y, copyBundledNativeModules as yt, UDS_NO_ROUTE_PREFIX as z };
7619
+ export { buildNativeCapProxy as $, createHubCapForwardService as A, AGENT_CAP_FWD_ACTION as B, brokerTransportLink as C, runNpm as Ct, localProviderLink as D, ipcParentLink as E, createUdsLogger as F, callWithServiceDiscovery as G, CapRouteResolver as H, createUdsLoggerWithControl as I, UdsLocalTransportServer as J, createLocalTransport as K, LocalChildClient as L, createUdsEventBridge as M, createUdsEventBus as N, HUB_CAP_FWD_ACTION as O, udsChildLogToWorkerEntry as P, encodeFrame as Q, LocalChildRegistry as R, brokerCallForCap as S, resolveNpmInvocation as St, ipcChildLink as T, CapRouteError as U, AGENT_CAP_FWD_SERVICE as V, classifyCapRoute as W, localEndpointPath as X, SocketChannel as Y, FrameDecoder as Z, createKernelHwAccel as _, CapabilityHandle as _t, getWorkerDeviceRegistry as a, setWorkerNativeCapsChangeListener as at, __resetCapUsageRegistryForTests as b, installManifestNativeDeps as bt, setHubConnected as c, NATIVE_PROVIDER_SERVICE_INFIX as ct, getBrokerEventBus as d, capBareAction as dt, buildUdsNativeCapProxy as et, getMoleculerEventStats as f, capServiceName as ft, AddonDepsManager as g, DeviceRegistry as gt, subscribePassthrough as h, serializeTypedArrays as ht, createUdsAddonContext as i, mountNativeCapService as it, createParentUnownedCallHandler as j, HUB_CAP_FWD_SERVICE as k, EVENT_TOPIC_PREFIX as l, capActionName as lt, setNodeEventInterest as m, deserializeTypedArrays as mt, adaptBrokerToCluster as n, getWorkerNativeCapProvider as nt, getOrInitReadinessRegistry as o, createAddonService as ot, registerEventBusService as p, parseCapAction as pt, UdsLocalTransportClient as q, createAddonContext as r, getWorkerNativeCapSnapshot as rt, getOrInitReadinessRegistryForClient as s, validateProviderRegistrations as st, installManifestPythonDeps as t, createBrokerDeviceManagerApi as tt, clusterEventTopic as u, capActionSuffix as ut, resolveHwAccel as v, CapabilityUnavailableError as vt, buildLinkChain as w, createAddonDataPlaneFacility as wt, getCapUsageRegistry as x, resolveAddonClass as xt, CapUsageRegistry as y, copyBundledNativeModules as yt, UDS_NO_ROUTE_PREFIX as z };
@@ -1,18 +1,18 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- let _camstack_types_node = require("@camstack/types/node");
3
2
  let node_crypto = require("node:crypto");
4
3
  node_crypto = require_chunk.__toESM(node_crypto);
5
- let node_http = require("node:http");
6
- let node_fs = require("node:fs");
7
- node_fs = require_chunk.__toESM(node_fs);
4
+ let _camstack_types_node = require("@camstack/types/node");
5
+ let node_fs_promises = require("node:fs/promises");
8
6
  let node_path = require("node:path");
9
7
  node_path = require_chunk.__toESM(node_path);
10
- let node_util = require("node:util");
11
- let _camstack_types_addon = require("@camstack/types/addon");
12
8
  let node_child_process = require("node:child_process");
9
+ let node_util = require("node:util");
13
10
  let node_os = require("node:os");
14
11
  node_os = require_chunk.__toESM(node_os);
15
- let node_fs_promises = require("node:fs/promises");
12
+ let node_fs = require("node:fs");
13
+ node_fs = require_chunk.__toESM(node_fs);
14
+ let node_http = require("node:http");
15
+ let _camstack_types_addon = require("@camstack/types/addon");
16
16
  let _trpc_client = require("@trpc/client");
17
17
  let node_net = require("node:net");
18
18
  //#region src/kernel/moleculer/addon-data-plane-facility.ts
@@ -131,46 +131,6 @@ function createAddonDataPlaneFacility(args) {
131
131
  };
132
132
  }
133
133
  //#endregion
134
- //#region src/kernel/addon-class-resolver.ts
135
- function isRecord(value) {
136
- return typeof value === "object" && value !== null && !Array.isArray(value);
137
- }
138
- /**
139
- * Resolve the addon class from a dynamically imported module.
140
- *
141
- * Two compilation patterns to handle:
142
- * 1. ESM with explicit `export default class …` — `mod.default` is the class.
143
- * 2. CJS via tsup that emits only named exports (no `module.exports = …`)
144
- * — `mod.default` is undefined; we must pick the right named export.
145
- *
146
- * The CJS path is the trap: an addon bundle can also re-export device
147
- * classes (e.g. `ReolinkCamera` extends `BaseDevice`) and helper
148
- * functions. Picking `Object.keys(mod)[0]` is bundler-order-dependent —
149
- * the first named export today is `ReolinkCamera`, whose `new` requires
150
- * a `DeviceContext` and crashes on `new MyClass()`.
151
- *
152
- * The contract is: the addon class extends `BaseAddon`, which has an
153
- * `initialize` method on its prototype. Device classes (`BaseDevice`)
154
- * do not. We probe for that method to disambiguate.
155
- */
156
- function isAddonConstructor(fn) {
157
- if (typeof fn !== "function") return false;
158
- const proto = fn.prototype;
159
- if (!isRecord(proto)) return false;
160
- return typeof proto["initialize"] === "function";
161
- }
162
- function resolveAddonClass(mod) {
163
- let candidate = mod["default"];
164
- if (isRecord(candidate) && "default" in candidate) candidate = candidate["default"];
165
- if (isAddonConstructor(candidate)) return candidate;
166
- if (isRecord(candidate)) {
167
- const fn = Object.values(candidate).find(isAddonConstructor);
168
- if (fn) return fn;
169
- }
170
- const namedAddon = Object.values(mod).find(isAddonConstructor);
171
- if (namedAddon) return namedAddon;
172
- }
173
- //#endregion
174
134
  //#region src/kernel/deps/npm-command.ts
175
135
  /**
176
136
  * npm invocation resolver — makes "is npm available?" a solved problem for
@@ -300,6 +260,46 @@ async function resolveUncached(options) {
300
260
  }
301
261
  }
302
262
  //#endregion
263
+ //#region src/kernel/addon-class-resolver.ts
264
+ function isRecord(value) {
265
+ return typeof value === "object" && value !== null && !Array.isArray(value);
266
+ }
267
+ /**
268
+ * Resolve the addon class from a dynamically imported module.
269
+ *
270
+ * Two compilation patterns to handle:
271
+ * 1. ESM with explicit `export default class …` — `mod.default` is the class.
272
+ * 2. CJS via tsup that emits only named exports (no `module.exports = …`)
273
+ * — `mod.default` is undefined; we must pick the right named export.
274
+ *
275
+ * The CJS path is the trap: an addon bundle can also re-export device
276
+ * classes (e.g. `ReolinkCamera` extends `BaseDevice`) and helper
277
+ * functions. Picking `Object.keys(mod)[0]` is bundler-order-dependent —
278
+ * the first named export today is `ReolinkCamera`, whose `new` requires
279
+ * a `DeviceContext` and crashes on `new MyClass()`.
280
+ *
281
+ * The contract is: the addon class extends `BaseAddon`, which has an
282
+ * `initialize` method on its prototype. Device classes (`BaseDevice`)
283
+ * do not. We probe for that method to disambiguate.
284
+ */
285
+ function isAddonConstructor(fn) {
286
+ if (typeof fn !== "function") return false;
287
+ const proto = fn.prototype;
288
+ if (!isRecord(proto)) return false;
289
+ return typeof proto["initialize"] === "function";
290
+ }
291
+ function resolveAddonClass(mod) {
292
+ let candidate = mod["default"];
293
+ if (isRecord(candidate) && "default" in candidate) candidate = candidate["default"];
294
+ if (isAddonConstructor(candidate)) return candidate;
295
+ if (isRecord(candidate)) {
296
+ const fn = Object.values(candidate).find(isAddonConstructor);
297
+ if (fn) return fn;
298
+ }
299
+ const namedAddon = Object.values(mod).find(isAddonConstructor);
300
+ if (namedAddon) return namedAddon;
301
+ }
302
+ //#endregion
303
303
  //#region src/kernel/deps/manifest-native-deps.ts
304
304
  var execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
305
305
  /**
@@ -5423,6 +5423,8 @@ function createParentUnownedCallHandler(deps) {
5423
5423
  if (empty != null) return empty;
5424
5424
  const aggregate = await deps.callCollectionAggregate?.(input.capName, input.method, input.args);
5425
5425
  if (aggregate != null) return aggregate;
5426
+ const pinned = await deps.callPinnedCollection?.(input.capName, input.method, input.args);
5427
+ if (pinned !== null && pinned !== void 0) return pinned.value;
5426
5428
  }
5427
5429
  const resolver = deps.getResolver();
5428
5430
  if (resolver !== null) try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.69",
3
+ "version": "1.2.71",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",
@@ -267,9 +267,6 @@
267
267
  },
268
268
  {
269
269
  "name": "user-management"
270
- },
271
- {
272
- "name": "oauth-integration"
273
270
  }
274
271
  ]
275
272
  },
@@ -1,7 +0,0 @@
1
- import { OauthIntegrationDescriptor } from '@camstack/types';
2
- /**
3
- * Scopes granted to a linked Home Assistant, derived from the tRPC paths the
4
- * component actually calls. `category:system [create]` was rejected: it would
5
- * hand `addons.installPackage` to a home-automation bridge.
6
- */
7
- export declare const HOME_ASSISTANT_OAUTH_INTEGRATION: OauthIntegrationDescriptor;