@camstack/system 1.1.18 → 1.1.19

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.
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-lZd5B4FS.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-Dt5XNSyd.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { $ as getWorkerNativeCapProvider, N as createUdsLoggerWithControl, P as LocalChildClient, _t as resolveAddonClass, et as getWorkerNativeCapSnapshot, gt as installManifestNativeDeps, i as createUdsAddonContext, it as validateProviderRegistrations, nt as setWorkerNativeCapsChangeListener, t as installManifestPythonDeps } from "./manifest-python-deps-DOAh19rY.mjs";
1
+ import { $ as getWorkerNativeCapProvider, N as createUdsLoggerWithControl, P as LocalChildClient, _t as resolveAddonClass, et as getWorkerNativeCapSnapshot, gt as installManifestNativeDeps, i as createUdsAddonContext, it as validateProviderRegistrations, nt as setWorkerNativeCapsChangeListener, t as installManifestPythonDeps } from "./manifest-python-deps-C1Dd9MSr.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
3
3
  import { register } from "node:module";
4
4
  import * as fs from "node:fs";
package/dist/index.d.ts CHANGED
@@ -72,6 +72,7 @@ export type { UserRecord, UserStorageAccess, UserConfigReader } from './auth/use
72
72
  export { ScopedTokenManager } from './auth/scoped-token-manager.js';
73
73
  export { scopesAllowDeviceCap } from './auth/scope-matcher.js';
74
74
  export { NotificationService } from './notification/notification-service.js';
75
+ export type { NotificationTargetRef } from './notification/notification-service.js';
75
76
  export { ToastService } from './notification/toast-service.js';
76
77
  export type { Unsubscribe } from './notification/toast-service.js';
77
78
  export { AddonRouteRegistry } from './addon-routes/addon-route-registry.js';
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ const require_builtins_local_auth_local_auth_addon = require("./builtins/local-a
20
20
  require("./builtins/local-auth/index.js");
21
21
  const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
22
22
  require("./builtins/device-manager/index.js");
23
- const require_manifest_python_deps = require("./manifest-python-deps-lZd5B4FS.js");
23
+ const require_manifest_python_deps = require("./manifest-python-deps-Dt5XNSyd.js");
24
24
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
25
25
  let _camstack_types_node = require("@camstack/types/node");
26
26
  let node_http = require("node:http");
@@ -1493,16 +1493,17 @@ function scopesAllowDeviceCap(scopes, access) {
1493
1493
  //#endregion
1494
1494
  //#region src/notification/notification-service.ts
1495
1495
  /**
1496
- * Central notification service that routes notifications to configured outputs.
1497
- * Framework-agnostic — dependencies injected via constructor.
1496
+ * Central notification service that routes canonical notifications to
1497
+ * configured `(addonId, targetId)` targets. Framework-agnostic —
1498
+ * dependencies injected via constructor.
1498
1499
  *
1499
- * Outputs are resolved from the ICapabilityRegistry's 'notification-output'
1500
- * collection on each call (proxy pattern). Falls back to a local map
1501
- * when no registry is provided (backward compat).
1500
+ * Providers are resolved from the ICapabilityRegistry by `addonId` on each
1501
+ * call (proxy pattern). Falls back to a local map when no registry is
1502
+ * provided (tests / backward compat).
1502
1503
  */
1503
1504
  var NotificationService = class {
1504
1505
  logger;
1505
- localOutputs = /* @__PURE__ */ new Map();
1506
+ localProviders = /* @__PURE__ */ new Map();
1506
1507
  routing = /* @__PURE__ */ new Map();
1507
1508
  rateLimits = /* @__PURE__ */ new Map();
1508
1509
  lastSent = /* @__PURE__ */ new Map();
@@ -1510,35 +1511,28 @@ var NotificationService = class {
1510
1511
  constructor(logger) {
1511
1512
  this.logger = logger;
1512
1513
  }
1513
- /** Set the registry for live output lookup. Called once during boot. */
1514
+ /** Set the registry for live provider lookup. Called once during boot. */
1514
1515
  setRegistry(registry) {
1515
1516
  this.registry = registry;
1516
1517
  }
1517
- /** Resolve all outputsprefers registry, falls back to local map */
1518
- get outputs() {
1519
- if (this.registry) {
1520
- const collection = this.registry.getCollection("notification-output");
1521
- const map = /* @__PURE__ */ new Map();
1522
- for (const output of collection) map.set(output.id, output);
1523
- return map;
1524
- }
1525
- return this.localOutputs;
1526
- }
1527
- /** Register an output in the local fallback map (used when no registry is set). */
1528
- registerLocalOutput(output) {
1529
- this.localOutputs.set(output.id, output);
1530
- this.logger.info("Notification output added", { meta: {
1531
- name: output.name,
1532
- outputId: output.id
1533
- } });
1518
+ /** Resolve the collection provider for an addon registry first, then local map. */
1519
+ resolveProvider(addonId) {
1520
+ if (this.registry) return this.registry.getProviderByAddon("notification-output", addonId) ?? void 0;
1521
+ return this.localProviders.get(addonId);
1522
+ }
1523
+ /** Register a provider in the local fallback map (used when no registry is set). */
1524
+ registerLocalProvider(addonId, provider) {
1525
+ this.localProviders.set(addonId, provider);
1526
+ this.logger.info("Notification provider added", { meta: { addonId } });
1534
1527
  }
1535
- /** Remove an output from the local fallback map. */
1536
- unregisterLocalOutput(id) {
1537
- this.localOutputs.delete(id);
1538
- this.logger.info("Notification output removed", { meta: { outputId: id } });
1528
+ /** Remove a provider from the local fallback map. */
1529
+ unregisterLocalProvider(addonId) {
1530
+ this.localProviders.delete(addonId);
1531
+ this.logger.info("Notification provider removed", { meta: { addonId } });
1539
1532
  }
1540
- setRouting(category, outputIds) {
1541
- this.routing.set(category, [...outputIds]);
1533
+ /** Route a category to a set of `(addonId, targetId)` targets. */
1534
+ setRouting(category, targets) {
1535
+ this.routing.set(category, [...targets]);
1542
1536
  }
1543
1537
  setRateLimit(category, minIntervalMs) {
1544
1538
  this.rateLimits.set(category, minIntervalMs);
@@ -1554,38 +1548,36 @@ var NotificationService = class {
1554
1548
  return;
1555
1549
  }
1556
1550
  }
1557
- const targetIds = this.routing.get(category) ?? this.routing.get("*") ?? [];
1558
- if (targetIds.length === 0) {
1551
+ const targets = this.routing.get(category) ?? this.routing.get("*") ?? [];
1552
+ if (targets.length === 0) {
1559
1553
  this.logger.debug("No routing configured for category", { meta: { category } });
1560
1554
  return;
1561
1555
  }
1562
- const currentOutputs = this.outputs;
1563
1556
  this.lastSent.set(rateLimitKey, notification.timestamp);
1564
- await Promise.allSettled(targetIds.map((id) => currentOutputs.get(id)).filter((output) => output !== void 0).map(async (output) => {
1557
+ const { category: _category, timestamp: _timestamp, ...canonical } = notification;
1558
+ await Promise.allSettled(targets.map(async (ref) => {
1559
+ const provider = this.resolveProvider(ref.addonId);
1560
+ if (!provider) {
1561
+ this.logger.debug("No provider for target", { meta: { addonId: ref.addonId } });
1562
+ return;
1563
+ }
1565
1564
  try {
1566
- await output.send(notification);
1565
+ await provider.send({
1566
+ targetId: ref.targetId,
1567
+ notification: canonical
1568
+ });
1567
1569
  } catch (err) {
1568
- const msg = (0, _camstack_types.errMsg)(err);
1569
- this.logger.error("Notification output failed", { meta: {
1570
- outputId: output.id,
1571
- error: msg
1570
+ this.logger.error("Notification target failed", { meta: {
1571
+ addonId: ref.addonId,
1572
+ targetId: ref.targetId,
1573
+ error: (0, _camstack_types.errMsg)(err)
1572
1574
  } });
1573
1575
  }
1574
1576
  }));
1575
1577
  }
1576
- getOutputs() {
1577
- return Array.from(this.outputs.values()).map(({ id, name, icon }) => ({
1578
- id,
1579
- name,
1580
- icon
1581
- }));
1582
- }
1583
1578
  getRouting() {
1584
1579
  return this.routing;
1585
1580
  }
1586
- getOutput(id) {
1587
- return this.outputs.get(id);
1588
- }
1589
1581
  };
1590
1582
  //#endregion
1591
1583
  //#region src/notification/toast-service.ts
package/dist/index.mjs CHANGED
@@ -18,7 +18,7 @@ import { LocalAuthAddon, a as require_ms, c as __esmMin, d as __toCommonJS, f as
18
18
  import "./builtins/local-auth/index.mjs";
19
19
  import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
20
20
  import "./builtins/device-manager/index.mjs";
21
- import { A as createUdsEventBus, B as CapRouteError, C as ipcParentLink, D as createHubCapForwardService, E as HUB_CAP_FWD_SERVICE, F as LocalChildRegistry, G as UdsLocalTransportServer, H as callWithServiceDiscovery, I as UDS_NO_ROUTE_PREFIX, J as FrameDecoder, K as SocketChannel, L as AGENT_CAP_FWD_ACTION, M as createUdsLogger, N as createUdsLoggerWithControl, O as createParentUnownedCallHandler, P as LocalChildClient, Q as createBrokerDeviceManagerApi, R as AGENT_CAP_FWD_SERVICE, S as ipcChildLink, T as HUB_CAP_FWD_ACTION, U as createLocalTransport, V as classifyCapRoute, W as UdsLocalTransportClient, X as buildNativeCapProxy, Y as encodeFrame, Z as buildUdsNativeCapProxy, _ as __resetCapUsageRegistryForTests, _t as resolveAddonClass, a as getWorkerDeviceRegistry, at as NATIVE_PROVIDER_SERVICE_INFIX, b as brokerTransportLink, c as setHubConnected, ct as capBareAction, d as registerEventBusService, dt as deserializeTypedArrays, f as AddonDepsManager, ft as serializeTypedArrays, g as CapUsageRegistry, gt as installManifestNativeDeps, h as createHwAccelService, ht as CapabilityUnavailableError, i as createUdsAddonContext, it as validateProviderRegistrations, j as udsChildLogToWorkerEntry, k as createUdsEventBridge, l as EVENT_TOPIC_PREFIX, lt as capServiceName, m as resolveHwAccel, mt as CapabilityHandle, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as capActionName, p as createKernelHwAccel, pt as DeviceRegistry, q as localEndpointPath, r as createAddonContext, rt as createAddonService, s as getOrInitReadinessRegistryForClient, st as capActionSuffix, t as installManifestPythonDeps, tt as mountNativeCapService, u as getBrokerEventBus, ut as parseCapAction, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as CapRouteResolver } from "./manifest-python-deps-DOAh19rY.mjs";
21
+ import { A as createUdsEventBus, B as CapRouteError, C as ipcParentLink, D as createHubCapForwardService, E as HUB_CAP_FWD_SERVICE, F as LocalChildRegistry, G as UdsLocalTransportServer, H as callWithServiceDiscovery, I as UDS_NO_ROUTE_PREFIX, J as FrameDecoder, K as SocketChannel, L as AGENT_CAP_FWD_ACTION, M as createUdsLogger, N as createUdsLoggerWithControl, O as createParentUnownedCallHandler, P as LocalChildClient, Q as createBrokerDeviceManagerApi, R as AGENT_CAP_FWD_SERVICE, S as ipcChildLink, T as HUB_CAP_FWD_ACTION, U as createLocalTransport, V as classifyCapRoute, W as UdsLocalTransportClient, X as buildNativeCapProxy, Y as encodeFrame, Z as buildUdsNativeCapProxy, _ as __resetCapUsageRegistryForTests, _t as resolveAddonClass, a as getWorkerDeviceRegistry, at as NATIVE_PROVIDER_SERVICE_INFIX, b as brokerTransportLink, c as setHubConnected, ct as capBareAction, d as registerEventBusService, dt as deserializeTypedArrays, f as AddonDepsManager, ft as serializeTypedArrays, g as CapUsageRegistry, gt as installManifestNativeDeps, h as createHwAccelService, ht as CapabilityUnavailableError, i as createUdsAddonContext, it as validateProviderRegistrations, j as udsChildLogToWorkerEntry, k as createUdsEventBridge, l as EVENT_TOPIC_PREFIX, lt as capServiceName, m as resolveHwAccel, mt as CapabilityHandle, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as capActionName, p as createKernelHwAccel, pt as DeviceRegistry, q as localEndpointPath, r as createAddonContext, rt as createAddonService, s as getOrInitReadinessRegistryForClient, st as capActionSuffix, t as installManifestPythonDeps, tt as mountNativeCapService, u as getBrokerEventBus, ut as parseCapAction, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as CapRouteResolver } from "./manifest-python-deps-C1Dd9MSr.mjs";
22
22
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
23
23
  import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
24
24
  import { request } from "node:http";
@@ -1485,16 +1485,17 @@ function scopesAllowDeviceCap(scopes, access) {
1485
1485
  //#endregion
1486
1486
  //#region src/notification/notification-service.ts
1487
1487
  /**
1488
- * Central notification service that routes notifications to configured outputs.
1489
- * Framework-agnostic — dependencies injected via constructor.
1488
+ * Central notification service that routes canonical notifications to
1489
+ * configured `(addonId, targetId)` targets. Framework-agnostic —
1490
+ * dependencies injected via constructor.
1490
1491
  *
1491
- * Outputs are resolved from the ICapabilityRegistry's 'notification-output'
1492
- * collection on each call (proxy pattern). Falls back to a local map
1493
- * when no registry is provided (backward compat).
1492
+ * Providers are resolved from the ICapabilityRegistry by `addonId` on each
1493
+ * call (proxy pattern). Falls back to a local map when no registry is
1494
+ * provided (tests / backward compat).
1494
1495
  */
1495
1496
  var NotificationService = class {
1496
1497
  logger;
1497
- localOutputs = /* @__PURE__ */ new Map();
1498
+ localProviders = /* @__PURE__ */ new Map();
1498
1499
  routing = /* @__PURE__ */ new Map();
1499
1500
  rateLimits = /* @__PURE__ */ new Map();
1500
1501
  lastSent = /* @__PURE__ */ new Map();
@@ -1502,35 +1503,28 @@ var NotificationService = class {
1502
1503
  constructor(logger) {
1503
1504
  this.logger = logger;
1504
1505
  }
1505
- /** Set the registry for live output lookup. Called once during boot. */
1506
+ /** Set the registry for live provider lookup. Called once during boot. */
1506
1507
  setRegistry(registry) {
1507
1508
  this.registry = registry;
1508
1509
  }
1509
- /** Resolve all outputsprefers registry, falls back to local map */
1510
- get outputs() {
1511
- if (this.registry) {
1512
- const collection = this.registry.getCollection("notification-output");
1513
- const map = /* @__PURE__ */ new Map();
1514
- for (const output of collection) map.set(output.id, output);
1515
- return map;
1516
- }
1517
- return this.localOutputs;
1518
- }
1519
- /** Register an output in the local fallback map (used when no registry is set). */
1520
- registerLocalOutput(output) {
1521
- this.localOutputs.set(output.id, output);
1522
- this.logger.info("Notification output added", { meta: {
1523
- name: output.name,
1524
- outputId: output.id
1525
- } });
1510
+ /** Resolve the collection provider for an addon registry first, then local map. */
1511
+ resolveProvider(addonId) {
1512
+ if (this.registry) return this.registry.getProviderByAddon("notification-output", addonId) ?? void 0;
1513
+ return this.localProviders.get(addonId);
1514
+ }
1515
+ /** Register a provider in the local fallback map (used when no registry is set). */
1516
+ registerLocalProvider(addonId, provider) {
1517
+ this.localProviders.set(addonId, provider);
1518
+ this.logger.info("Notification provider added", { meta: { addonId } });
1526
1519
  }
1527
- /** Remove an output from the local fallback map. */
1528
- unregisterLocalOutput(id) {
1529
- this.localOutputs.delete(id);
1530
- this.logger.info("Notification output removed", { meta: { outputId: id } });
1520
+ /** Remove a provider from the local fallback map. */
1521
+ unregisterLocalProvider(addonId) {
1522
+ this.localProviders.delete(addonId);
1523
+ this.logger.info("Notification provider removed", { meta: { addonId } });
1531
1524
  }
1532
- setRouting(category, outputIds) {
1533
- this.routing.set(category, [...outputIds]);
1525
+ /** Route a category to a set of `(addonId, targetId)` targets. */
1526
+ setRouting(category, targets) {
1527
+ this.routing.set(category, [...targets]);
1534
1528
  }
1535
1529
  setRateLimit(category, minIntervalMs) {
1536
1530
  this.rateLimits.set(category, minIntervalMs);
@@ -1546,38 +1540,36 @@ var NotificationService = class {
1546
1540
  return;
1547
1541
  }
1548
1542
  }
1549
- const targetIds = this.routing.get(category) ?? this.routing.get("*") ?? [];
1550
- if (targetIds.length === 0) {
1543
+ const targets = this.routing.get(category) ?? this.routing.get("*") ?? [];
1544
+ if (targets.length === 0) {
1551
1545
  this.logger.debug("No routing configured for category", { meta: { category } });
1552
1546
  return;
1553
1547
  }
1554
- const currentOutputs = this.outputs;
1555
1548
  this.lastSent.set(rateLimitKey, notification.timestamp);
1556
- await Promise.allSettled(targetIds.map((id) => currentOutputs.get(id)).filter((output) => output !== void 0).map(async (output) => {
1549
+ const { category: _category, timestamp: _timestamp, ...canonical } = notification;
1550
+ await Promise.allSettled(targets.map(async (ref) => {
1551
+ const provider = this.resolveProvider(ref.addonId);
1552
+ if (!provider) {
1553
+ this.logger.debug("No provider for target", { meta: { addonId: ref.addonId } });
1554
+ return;
1555
+ }
1557
1556
  try {
1558
- await output.send(notification);
1557
+ await provider.send({
1558
+ targetId: ref.targetId,
1559
+ notification: canonical
1560
+ });
1559
1561
  } catch (err) {
1560
- const msg = errMsg(err);
1561
- this.logger.error("Notification output failed", { meta: {
1562
- outputId: output.id,
1563
- error: msg
1562
+ this.logger.error("Notification target failed", { meta: {
1563
+ addonId: ref.addonId,
1564
+ targetId: ref.targetId,
1565
+ error: errMsg(err)
1564
1566
  } });
1565
1567
  }
1566
1568
  }));
1567
1569
  }
1568
- getOutputs() {
1569
- return Array.from(this.outputs.values()).map(({ id, name, icon }) => ({
1570
- id,
1571
- name,
1572
- icon
1573
- }));
1574
- }
1575
1570
  getRouting() {
1576
1571
  return this.routing;
1577
1572
  }
1578
- getOutput(id) {
1579
- return this.outputs.get(id);
1580
- }
1581
1573
  };
1582
1574
  //#endregion
1583
1575
  //#region src/notification/toast-service.ts
@@ -38,6 +38,16 @@ export interface LocalChildRegistryOptions {
38
38
  * preference is set or the cap is not a singleton.
39
39
  */
40
40
  readonly getActiveSingletonAddonId?: (capName: string) => string | null;
41
+ /**
42
+ * Resolve an addonId to its runner/child id — the addon's
43
+ * `execution.group` when it declares one, else the addon id itself
44
+ * (`resolveRunnerId`). Needed so the active-singleton preference (which
45
+ * is expressed as an addonId) can be matched against the registry's
46
+ * candidate CHILD ids: a grouped addon's child id is the group name, not
47
+ * its own id. Omitted → identity (addonId === childId), preserving the
48
+ * solo-runner behaviour for every ungrouped addon.
49
+ */
50
+ readonly resolveChildIdForAddon?: (addonId: string) => string;
41
51
  }
42
52
  /**
43
53
  * Parent-side authority for local addon-runners reachable over a
@@ -56,6 +66,7 @@ export declare class LocalChildRegistry {
56
66
  private readonly onUnownedCall?;
57
67
  private readonly logger?;
58
68
  private readonly getActiveSingletonAddonId?;
69
+ private readonly resolveChildIdForAddon?;
59
70
  /** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
60
71
  private readonly egressRoutedCaps;
61
72
  /**
@@ -3776,6 +3776,7 @@ var LocalChildRegistry = class {
3776
3776
  onUnownedCall;
3777
3777
  logger;
3778
3778
  getActiveSingletonAddonId;
3779
+ resolveChildIdForAddon;
3779
3780
  /** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
3780
3781
  egressRoutedCaps = /* @__PURE__ */ new Set();
3781
3782
  /**
@@ -3796,6 +3797,7 @@ var LocalChildRegistry = class {
3796
3797
  this.onUnownedCall = opts.onUnownedCall;
3797
3798
  this.logger = opts.logger;
3798
3799
  this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
3800
+ this.resolveChildIdForAddon = opts.resolveChildIdForAddon;
3799
3801
  } else {
3800
3802
  this.server = serverOrOptions;
3801
3803
  this.onUnownedCall = onUnownedCallArg;
@@ -3829,8 +3831,11 @@ var LocalChildRegistry = class {
3829
3831
  const candidates = this.findAllChildIds((cap) => cap.capName === capName && cap.deviceId === void 0);
3830
3832
  if (candidates.length === 0) return null;
3831
3833
  if (candidates.length === 1) return candidates[0];
3832
- const preferred = this.getActiveSingletonAddonId?.(capName) ?? null;
3833
- if (preferred !== null && candidates.includes(preferred)) return preferred;
3834
+ const preferredAddonId = this.getActiveSingletonAddonId?.(capName) ?? null;
3835
+ if (preferredAddonId !== null) {
3836
+ const preferredChildId = this.resolveChildIdForAddon?.(preferredAddonId) ?? preferredAddonId;
3837
+ if (candidates.includes(preferredChildId)) return preferredChildId;
3838
+ }
3834
3839
  return candidates[0];
3835
3840
  }
3836
3841
  /** First child whose cap manifest contains a descriptor matching `predicate`. */
@@ -3778,6 +3778,7 @@ var LocalChildRegistry = class {
3778
3778
  onUnownedCall;
3779
3779
  logger;
3780
3780
  getActiveSingletonAddonId;
3781
+ resolveChildIdForAddon;
3781
3782
  /** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
3782
3783
  egressRoutedCaps = /* @__PURE__ */ new Set();
3783
3784
  /**
@@ -3798,6 +3799,7 @@ var LocalChildRegistry = class {
3798
3799
  this.onUnownedCall = opts.onUnownedCall;
3799
3800
  this.logger = opts.logger;
3800
3801
  this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
3802
+ this.resolveChildIdForAddon = opts.resolveChildIdForAddon;
3801
3803
  } else {
3802
3804
  this.server = serverOrOptions;
3803
3805
  this.onUnownedCall = onUnownedCallArg;
@@ -3831,8 +3833,11 @@ var LocalChildRegistry = class {
3831
3833
  const candidates = this.findAllChildIds((cap) => cap.capName === capName && cap.deviceId === void 0);
3832
3834
  if (candidates.length === 0) return null;
3833
3835
  if (candidates.length === 1) return candidates[0];
3834
- const preferred = this.getActiveSingletonAddonId?.(capName) ?? null;
3835
- if (preferred !== null && candidates.includes(preferred)) return preferred;
3836
+ const preferredAddonId = this.getActiveSingletonAddonId?.(capName) ?? null;
3837
+ if (preferredAddonId !== null) {
3838
+ const preferredChildId = this.resolveChildIdForAddon?.(preferredAddonId) ?? preferredAddonId;
3839
+ if (candidates.includes(preferredChildId)) return preferredChildId;
3840
+ }
3836
3841
  return candidates[0];
3837
3842
  }
3838
3843
  /** First child whose cap manifest contains a descriptor matching `predicate`. */
@@ -1,37 +1,43 @@
1
- import { INotificationOutput, Notification, ICapabilityRegistry } from '@camstack/types';
1
+ import { INotificationOutputProvider, RoutedNotification, ICapabilityRegistry } from '@camstack/types';
2
2
  import { IScopedLogger } from '../logging/scoped-logger.js';
3
3
  /**
4
- * Central notification service that routes notifications to configured outputs.
5
- * Framework-agnostic dependencies injected via constructor.
4
+ * A routing destination: one target (`targetId`) owned by one provider
5
+ * (`addonId`). The notifiers addon and the HA addon each register a
6
+ * `notification-output` collection provider; the service resolves the
7
+ * provider by `addonId` and dispatches to the specific target.
8
+ */
9
+ export interface NotificationTargetRef {
10
+ readonly addonId: string;
11
+ readonly targetId: string;
12
+ }
13
+ /**
14
+ * Central notification service that routes canonical notifications to
15
+ * configured `(addonId, targetId)` targets. Framework-agnostic —
16
+ * dependencies injected via constructor.
6
17
  *
7
- * Outputs are resolved from the ICapabilityRegistry's 'notification-output'
8
- * collection on each call (proxy pattern). Falls back to a local map
9
- * when no registry is provided (backward compat).
18
+ * Providers are resolved from the ICapabilityRegistry by `addonId` on each
19
+ * call (proxy pattern). Falls back to a local map when no registry is
20
+ * provided (tests / backward compat).
10
21
  */
11
22
  export declare class NotificationService {
12
23
  private readonly logger;
13
- private readonly localOutputs;
24
+ private readonly localProviders;
14
25
  private readonly routing;
15
26
  private readonly rateLimits;
16
27
  private readonly lastSent;
17
28
  private registry;
18
29
  constructor(logger: IScopedLogger);
19
- /** Set the registry for live output lookup. Called once during boot. */
30
+ /** Set the registry for live provider lookup. Called once during boot. */
20
31
  setRegistry(registry: ICapabilityRegistry): void;
21
- /** Resolve all outputsprefers registry, falls back to local map */
22
- private get outputs();
23
- /** Register an output in the local fallback map (used when no registry is set). */
24
- registerLocalOutput(output: INotificationOutput): void;
25
- /** Remove an output from the local fallback map. */
26
- unregisterLocalOutput(id: string): void;
27
- setRouting(category: string, outputIds: string[]): void;
32
+ /** Resolve the collection provider for an addon registry first, then local map. */
33
+ private resolveProvider;
34
+ /** Register a provider in the local fallback map (used when no registry is set). */
35
+ registerLocalProvider(addonId: string, provider: INotificationOutputProvider): void;
36
+ /** Remove a provider from the local fallback map. */
37
+ unregisterLocalProvider(addonId: string): void;
38
+ /** Route a category to a set of `(addonId, targetId)` targets. */
39
+ setRouting(category: string, targets: readonly NotificationTargetRef[]): void;
28
40
  setRateLimit(category: string, minIntervalMs: number): void;
29
- notify(notification: Notification): Promise<void>;
30
- getOutputs(): ReadonlyArray<{
31
- id: string;
32
- name: string;
33
- icon: string;
34
- }>;
35
- getRouting(): ReadonlyMap<string, string[]>;
36
- getOutput(id: string): INotificationOutput | undefined;
41
+ notify(notification: RoutedNotification): Promise<void>;
42
+ getRouting(): ReadonlyMap<string, NotificationTargetRef[]>;
37
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",