@camstack/server 1.1.19 → 1.1.21

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,21 +1,38 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createEventBusProxyRouter = createEventBusProxyRouter;
4
2
  /**
5
- * EventBus proxy router — allows forked workers to emit events to the
6
- * hub's EventBus via tRPC.
3
+ * EventBus proxy router — allows forked workers to emit events to, and
4
+ * read the recent-events registry of, the hub's EventBus via tRPC.
7
5
  *
8
6
  * Workers call `trpc.eventBusProxy.emit.mutate(event)` from their
9
7
  * `context.eventBus.emit()` implementation. The hub-side router
10
8
  * deserializes the event and emits it on the real EventBus.
11
9
  *
12
- * Subscribe/getRecent are routed through the existing `live.onEvent`
13
- * subscription and `events` query routers no duplication needed here.
10
+ * `getRecent` exposes the hub's retained recent-events registry to forked
11
+ * addon runners. Post the `retainRecent` change (f5a93332) ONLY the hub
12
+ * main process keeps the `recent[]` buffer — a child runner's own
13
+ * `ctx.eventBus.getRecent()` now returns `[]`. Addons that need event
14
+ * HISTORY (e.g. advanced-notifier's `testRule`) must read it from the hub
15
+ * over this proxy instead of their local, non-retaining bus. This router
16
+ * is in `CORE_NAMESPACES` (`core-cap-bridge.ts`), so the call reaches the
17
+ * hub's `services.eventBus` from any hub-local child over the UDS/core-cap
18
+ * path — exactly like `capabilities.*` / `system.*`.
19
+ *
20
+ * `subscribe` is routed through the existing `live.onEvent` subscription
21
+ * and `systemEvents.subscribe` routers — no duplication needed here.
14
22
  *
15
23
  * Introduced in session 7 (EventBus wiring for forked addons).
16
24
  */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.createEventBusProxyRouter = createEventBusProxyRouter;
17
27
  const zod_1 = require("zod");
18
28
  const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
29
+ /**
30
+ * Upper bound on the `getRecent` limit — kept in step with the hub's
31
+ * `recent[]` ring-buffer size (`MAX_RECENT_EVENTS` in `event-bus-core.ts`).
32
+ * Duplicated as a local literal rather than imported so this router does
33
+ * not depend on the event-bus core internals.
34
+ */
35
+ const MAX_RECENT_EVENTS = 1000;
19
36
  const SystemEventInputSchema = zod_1.z.object({
20
37
  id: zod_1.z.string(),
21
38
  timestamp: zod_1.z.string(), // ISO 8601 — converted to Date on emit
@@ -26,6 +43,50 @@ const SystemEventInputSchema = zod_1.z.object({
26
43
  category: zod_1.z.string(),
27
44
  data: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
28
45
  });
46
+ /**
47
+ * `getRecent` input — mirrors `systemEvents.getRecent`'s category/limit
48
+ * semantics. `category` accepts a single string or an array so a caller
49
+ * can whitelist the categories it cares about server-side; `limit` is
50
+ * capped at the hub's `recent[]` buffer size (`MAX_RECENT_EVENTS`).
51
+ */
52
+ const GetRecentInputSchema = zod_1.z.object({
53
+ category: zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).optional(),
54
+ limit: zod_1.z.number().int().min(1).max(MAX_RECENT_EVENTS).optional(),
55
+ });
56
+ /**
57
+ * Serialized recent event — `timestamp` is an ISO-8601 string so the
58
+ * shape survives every transport (UDS/MsgPack, Moleculer) unchanged.
59
+ * Callers reconstruct the `Date` on receipt. Mirrors the serialization
60
+ * used by `systemEvents.getRecent`.
61
+ */
62
+ const RecentEventOutputSchema = zod_1.z.object({
63
+ id: zod_1.z.string(),
64
+ timestamp: zod_1.z.string(),
65
+ source: zod_1.z.object({
66
+ type: zod_1.z.string(),
67
+ id: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]),
68
+ nodeId: zod_1.z.string().optional(),
69
+ addonId: zod_1.z.string().optional(),
70
+ deviceId: zod_1.z.number().optional(),
71
+ }),
72
+ category: zod_1.z.string(),
73
+ data: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
74
+ });
75
+ function serializeRecentEvent(e) {
76
+ return {
77
+ id: e.id,
78
+ timestamp: new Date(e.timestamp).toISOString(),
79
+ source: {
80
+ type: e.source.type,
81
+ id: e.source.id,
82
+ ...(e.source.nodeId ? { nodeId: e.source.nodeId } : {}),
83
+ ...(e.source.addonId ? { addonId: e.source.addonId } : {}),
84
+ ...(e.source.deviceId !== undefined ? { deviceId: e.source.deviceId } : {}),
85
+ },
86
+ category: e.category,
87
+ data: e.data,
88
+ };
89
+ }
29
90
  function createEventBusProxyRouter(eventBus) {
30
91
  return (0, trpc_middleware_js_1.trpcRouter)({
31
92
  emit: trpc_middleware_js_1.protectedProcedure
@@ -41,5 +102,13 @@ function createEventBusProxyRouter(eventBus) {
41
102
  });
42
103
  return { ok: true };
43
104
  }),
105
+ getRecent: trpc_middleware_js_1.protectedProcedure
106
+ .input(GetRecentInputSchema)
107
+ .output(zod_1.z.array(RecentEventOutputSchema))
108
+ .query(({ input }) => {
109
+ return eventBus
110
+ .getRecent(input.category !== undefined ? { category: input.category } : undefined, input.limit)
111
+ .map(serializeRecentEvent);
112
+ }),
44
113
  });
45
114
  }
@@ -4,64 +4,44 @@ exports.createNotificationsRouter = createNotificationsRouter;
4
4
  /**
5
5
  * Notifications router — fixed core API (not a capability).
6
6
  *
7
- * Routes events by category to registered outputs. The NotificationService
8
- * is a singleton from @camstack/system; individual outputs are still provided
9
- * by addons via the `notification-output` capability collection.
7
+ * Routes events by category to `(addonId, targetId)` notification targets.
8
+ * The NotificationService is a singleton from @camstack/system; the targets
9
+ * themselves are provided by addons via the `notification-output` capability
10
+ * collection (see the notifiers addon / HA addon). Target management + the
11
+ * test panel live on the cap surface (`api.notificationOutput.*`); this
12
+ * legacy router only exposes category→target routing.
10
13
  */
11
14
  const zod_1 = require("zod");
12
15
  const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
13
- const NotificationOutputSchema = zod_1.z.object({
14
- id: zod_1.z.string(),
15
- name: zod_1.z.string(),
16
- icon: zod_1.z.string(),
17
- });
18
- const SendTestResultSchema = zod_1.z.object({
19
- success: zod_1.z.boolean(),
20
- error: zod_1.z.string().optional(),
16
+ const NotificationTargetRefSchema = zod_1.z.object({
17
+ addonId: zod_1.z.string(),
18
+ targetId: zod_1.z.string(),
21
19
  });
22
20
  function createNotificationsRouter(ns) {
23
21
  return (0, trpc_middleware_js_1.trpcRouter)({
24
- listOutputs: trpc_middleware_js_1.protectedProcedure
25
- .input(zod_1.z.void())
26
- .output(zod_1.z.array(NotificationOutputSchema).readonly())
27
- .query(() => {
28
- if (!ns)
29
- return [];
30
- return ns.getOutputs();
31
- }),
32
22
  getRouting: trpc_middleware_js_1.protectedProcedure
33
23
  .input(zod_1.z.void())
34
- .output(zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())))
24
+ .output(zod_1.z.record(zod_1.z.string(), zod_1.z.array(NotificationTargetRefSchema)))
35
25
  .query(() => {
36
26
  if (!ns)
37
27
  return {};
38
28
  const routing = ns.getRouting();
39
29
  const result = {};
40
- for (const [category, outputIds] of routing) {
41
- result[category] = [...outputIds];
30
+ for (const [category, targets] of routing) {
31
+ result[category] = [...targets];
42
32
  }
43
33
  return result;
44
34
  }),
45
35
  setRouting: trpc_middleware_js_1.adminProcedure
46
- .input(zod_1.z.object({ category: zod_1.z.string(), outputIds: zod_1.z.array(zod_1.z.string()) }))
36
+ .input(zod_1.z.object({
37
+ category: zod_1.z.string(),
38
+ targets: zod_1.z.array(NotificationTargetRefSchema),
39
+ }))
47
40
  .output(zod_1.z.void())
48
41
  .mutation(({ input }) => {
49
42
  if (!ns)
50
43
  throw new Error('Notification service unavailable');
51
- ns.setRouting(input.category, input.outputIds);
52
- }),
53
- sendTest: trpc_middleware_js_1.adminProcedure
54
- .input(zod_1.z.object({ outputId: zod_1.z.string() }))
55
- .output(SendTestResultSchema)
56
- .mutation(async ({ input }) => {
57
- if (!ns)
58
- return { success: false, error: 'Notification service unavailable' };
59
- const output = ns.getOutput(input.outputId);
60
- if (!output)
61
- return { success: false, error: `Output "${input.outputId}" not found` };
62
- if (!output.sendTest)
63
- return { success: false, error: 'Output does not support test notifications' };
64
- return output.sendTest();
44
+ ns.setRouting(input.category, input.targets);
65
45
  }),
66
46
  });
67
47
  }
@@ -271,7 +271,16 @@ function mountAllCaps(services) {
271
271
  return reg.getProviderByAddonId('notification-output', addonId);
272
272
  }
273
273
  const entries = reg.getCollectionEntries('notification-output');
274
- return entries[0]?.[1] ?? null;
274
+ if (entries.length === 0)
275
+ return null;
276
+ const providers = entries.map(([, p]) => p);
277
+ const first = providers[0];
278
+ return {
279
+ ...first,
280
+ listTargetKinds: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'listTargetKinds'),
281
+ listTargets: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'listTargets'),
282
+ discoverTargets: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'discoverTargets'),
283
+ };
275
284
  }, remoteCapProxy),
276
285
  notifier: (0, generated_cap_routers_js_1.createCapRouter_notifier)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'notifier'), remoteCapProxy),
277
286
  numericSensor: (0, generated_cap_routers_js_1.createCapRouter_numericSensor)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'numeric-sensor'), remoteCapProxy),
@@ -4469,6 +4469,33 @@ function createCapRouter_nodes(getProvider, _createRemoteProxy) {
4469
4469
  }
4470
4470
  function createCapRouter_notificationOutput(getProvider, createRemoteProxy) {
4471
4471
  return (0, trpc_middleware_js_1.trpcRouter)({
4472
+ listTargetKinds: trpc_middleware_js_1.protectedProcedure
4473
+ .input(types_68.notificationOutputCapability.methods.listTargetKinds.input.loose())
4474
+ .output(types_68.notificationOutputCapability.methods.listTargetKinds.output)
4475
+ .query(async ({ input, ctx }) => {
4476
+ const { nodeId, addonId, ...methodInput } = input;
4477
+ const p = resolveProvider('notification-output', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
4478
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4479
+ return p.listTargetKinds(methodInput);
4480
+ }),
4481
+ listTargets: trpc_middleware_js_1.protectedProcedure
4482
+ .input(types_68.notificationOutputCapability.methods.listTargets.input.loose())
4483
+ .output(types_68.notificationOutputCapability.methods.listTargets.output)
4484
+ .query(async ({ input, ctx }) => {
4485
+ const { nodeId, addonId, ...methodInput } = input;
4486
+ const p = resolveProvider('notification-output', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
4487
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4488
+ return p.listTargets(methodInput);
4489
+ }),
4490
+ discoverTargets: trpc_middleware_js_1.protectedProcedure
4491
+ .input(types_68.notificationOutputCapability.methods.discoverTargets.input.loose())
4492
+ .output(types_68.notificationOutputCapability.methods.discoverTargets.output)
4493
+ .query(async ({ input, ctx }) => {
4494
+ const { nodeId, addonId, ...methodInput } = input;
4495
+ const p = resolveProvider('notification-output', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
4496
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4497
+ return p.discoverTargets(methodInput);
4498
+ }),
4472
4499
  send: trpc_middleware_js_1.protectedProcedure
4473
4500
  .input(types_68.notificationOutputCapability.methods.send.input.loose())
4474
4501
  .output(types_68.notificationOutputCapability.methods.send.output)
@@ -4478,12 +4505,41 @@ function createCapRouter_notificationOutput(getProvider, createRemoteProxy) {
4478
4505
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4479
4506
  return p.send(methodInput);
4480
4507
  }),
4481
- sendTest: trpc_middleware_js_1.protectedProcedure
4482
- .input(zod_1.z.object({ nodeId: zod_1.z.string().optional(), addonId: zod_1.z.string().optional() }).optional())
4483
- .output(types_68.notificationOutputCapability.methods.sendTest.output)
4508
+ testTarget: trpc_middleware_js_1.protectedProcedure
4509
+ .input(types_68.notificationOutputCapability.methods.testTarget.input.loose())
4510
+ .output(types_68.notificationOutputCapability.methods.testTarget.output)
4511
+ .mutation(async ({ input, ctx }) => {
4512
+ const { nodeId, addonId, ...methodInput } = input;
4513
+ const p = resolveProvider('notification-output', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
4514
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4515
+ return p.testTarget(methodInput);
4516
+ }),
4517
+ upsertTarget: trpc_middleware_js_1.protectedProcedure
4518
+ .input(types_68.notificationOutputCapability.methods.upsertTarget.input.loose())
4519
+ .output(types_68.notificationOutputCapability.methods.upsertTarget.output)
4520
+ .mutation(async ({ input, ctx }) => {
4521
+ const { nodeId, addonId, ...methodInput } = input;
4522
+ const p = resolveProvider('notification-output', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
4523
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4524
+ return p.upsertTarget(methodInput);
4525
+ }),
4526
+ deleteTarget: trpc_middleware_js_1.protectedProcedure
4527
+ .input(types_68.notificationOutputCapability.methods.deleteTarget.input.loose())
4528
+ .output(types_68.notificationOutputCapability.methods.deleteTarget.output)
4484
4529
  .mutation(async ({ input, ctx }) => {
4485
- const p = resolveProvider('notification-output', input?.nodeId, () => getProvider(ctx, input?.addonId), createRemoteProxy);
4486
- return p.sendTest();
4530
+ const { nodeId, addonId, ...methodInput } = input;
4531
+ const p = resolveProvider('notification-output', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
4532
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4533
+ return p.deleteTarget(methodInput);
4534
+ }),
4535
+ setTargetEnabled: trpc_middleware_js_1.protectedProcedure
4536
+ .input(types_68.notificationOutputCapability.methods.setTargetEnabled.input.loose())
4537
+ .output(types_68.notificationOutputCapability.methods.setTargetEnabled.output)
4538
+ .mutation(async ({ input, ctx }) => {
4539
+ const { nodeId, addonId, ...methodInput } = input;
4540
+ const p = resolveProvider('notification-output', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
4541
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4542
+ return p.setTargetEnabled(methodInput);
4487
4543
  }),
4488
4544
  });
4489
4545
  }
@@ -18,7 +18,11 @@ class AddonCallGateway {
18
18
  const onHub = resolved === 'hub' || resolved === this.deps.hubNodeId;
19
19
  if (onHub) {
20
20
  const childRegistry = this.deps.getChildRegistry();
21
- if (childRegistry !== null && childRegistry.isChildKnown(addonId)) {
21
+ // Resolve to the runner child id: a grouped addon's UDS child is the
22
+ // group name, not the addon id. `isChildKnown(addonId)` would be false
23
+ // for a grouped addon and misclassify it as `in-process`.
24
+ const childId = this.deps.resolveChildId(addonId);
25
+ if (childRegistry !== null && childRegistry.isChildKnown(childId)) {
22
26
  return { kind: 'hub-local-child' };
23
27
  }
24
28
  return { kind: 'in-process' };
@@ -48,7 +52,10 @@ class AddonCallGateway {
48
52
  if (childRegistry === null) {
49
53
  throw new Error(`AddonCallGateway: child registry unavailable for "${addonId}"`);
50
54
  }
51
- return childRegistry.callAddonOnChild(addonId, fullInput);
55
+ // Dispatch to the runner CHILD id (group when grouped, else addon id);
56
+ // the payload keeps the real `addonId` so the child fans out to the
57
+ // right co-located addon within the group.
58
+ return childRegistry.callAddonOnChild(this.deps.resolveChildId(addonId), fullInput);
52
59
  }
53
60
  case 'remote-agent':
54
61
  return this.callRemoteAgent(addonId, dest.baseNodeId, fullInput);
@@ -1674,8 +1674,9 @@ class AddonPackageService {
1674
1674
  this.notificationService
1675
1675
  .notify({
1676
1676
  title: 'Package Updated',
1677
- message: `${name} updated to v${version}`,
1678
- severity: 'info',
1677
+ body: `${name} updated to v${version}`,
1678
+ format: 'text',
1679
+ priority: 3,
1679
1680
  category: 'system',
1680
1681
  timestamp: Date.now(),
1681
1682
  })
@@ -359,10 +359,15 @@ class AddonRegistryService {
359
359
  // forked-vs-in-process is decided by `isChildKnown` in the gateway.
360
360
  return entry ? this.broker.nodeID : 'hub';
361
361
  },
362
+ resolveChildId: (addonId) => this.childIdForAddon(addonId),
362
363
  getChildRegistry: () => this.moleculer.childRegistry,
363
364
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- moleculer types unresolvable; see `broker` getter docstring
364
365
  broker: this.moleculer.broker,
365
366
  });
367
+ // Hand the UDS registry a live addonId → runner-id resolver so its
368
+ // active-singleton preference can select a grouped addon (whose child id
369
+ // is its `execution.group`, not its own id). Identity for ungrouped addons.
370
+ this.moleculer.setRunnerIdResolver((addonId) => this.childIdForAddon(addonId));
366
371
  const settingsProvider = (0, addon_settings_provider_js_1.createAddonSettingsProvider)({
367
372
  getAddon: (addonId) => {
368
373
  const entry = this.addonEntries.get(addonId);
@@ -869,6 +874,19 @@ class AddonRegistryService {
869
874
  * isn't a cap, but the child is UDS-reachable and has served its endpoints by
870
875
  * the time any of its caps register.
871
876
  */
877
+ /**
878
+ * The UDS runner/child id an addon is reachable under. For a solo addon
879
+ * (no `execution.group`) this is the addon id itself; for a grouped addon
880
+ * it is the shared group name (`resolveRunnerId`). The single authority for
881
+ * "which child hosts this addon" on the parent side — every gateway /
882
+ * route-mount / data-plane-mount lookup routes through here so a grouped
883
+ * addon (`execution.group`) is never misclassified as `in-process`.
884
+ * Falls back to the addon id when the declaration is not yet known.
885
+ */
886
+ childIdForAddon(addonId) {
887
+ const decl = this.addonEntries.get(addonId)?.declaration;
888
+ return decl ? (0, types_1.resolveRunnerId)(decl, addonId) : addonId;
889
+ }
872
890
  async mountAddonDataPlanes(addonId) {
873
891
  const registry = this.dataPlaneRegistry;
874
892
  if (!registry)
@@ -880,7 +898,7 @@ class AddonRegistryService {
880
898
  // data-plane yet).
881
899
  if (!entry || !this.isForkedAddonEntry(entry))
882
900
  return;
883
- if (childRegistry === null || !childRegistry.isChildKnown(addonId))
901
+ if (childRegistry === null || !childRegistry.isChildKnown(this.childIdForAddon(addonId)))
884
902
  return;
885
903
  const raw = await this.addonCallGateway.callForked(addonId, { target: 'data-planes' });
886
904
  const endpoints = parseDataPlaneEndpoints(raw);
@@ -2168,7 +2186,7 @@ class AddonRegistryService {
2168
2186
  const entry = this.addonEntries.get(addonId);
2169
2187
  const childRegistry = this.moleculer.childRegistry;
2170
2188
  if (entry && this.isForkedAddonEntry(entry)) {
2171
- if (childRegistry !== null && childRegistry.isChildKnown(addonId)) {
2189
+ if (childRegistry !== null && childRegistry.isChildKnown(this.childIdForAddon(addonId))) {
2172
2190
  await this.mountForkedAddonRoutes(addonId, routeProvider, addonRouteRegistry);
2173
2191
  return;
2174
2192
  }
@@ -36,7 +36,12 @@ class EventBusService {
36
36
  if (this.broker === broker)
37
37
  return;
38
38
  this.broker = broker;
39
- const inner = (0, system_1.getBrokerEventBus)(broker);
39
+ // The hub main process is the ONLY process that serves `getRecent` — so it
40
+ // is the only bus that opts into retaining the recent[] registry. Child
41
+ // addon runners + remote agents leave `retainRecent` false: their events
42
+ // are one-shot (fire-and-forget), fanned out to local subscribers but never
43
+ // accumulated, so their heap can't grow an unbounded recent[] backlog.
44
+ const inner = (0, system_1.getBrokerEventBus)(broker, { retainRecent: true });
40
45
  this.inner = inner;
41
46
  // Replay deferred subscriptions onto the real bus.
42
47
  for (const sub of this.deferredSubs) {
@@ -74,6 +74,23 @@ class MoleculerService {
74
74
  * handler, preventing subscriber leaks on shutdown.
75
75
  */
76
76
  udsEventBridgeDispose = null;
77
+ /**
78
+ * addonId → runner/child id resolver, injected late by
79
+ * `AddonRegistryService` (which owns the addon declarations). Lets the UDS
80
+ * `LocalChildRegistry` map the operator's active-singleton preference (an
81
+ * addonId) onto a candidate CHILD id — a grouped addon's child id is its
82
+ * `execution.group`, not its own id. Read live so a manifest change takes
83
+ * effect without rebuilding the registry. Null → identity (addonId ===
84
+ * childId), the correct default for every ungrouped addon.
85
+ */
86
+ runnerIdResolver = null;
87
+ /**
88
+ * Late-wire the addonId → runner-id resolver used by the UDS singleton
89
+ * preference. Called once by `AddonRegistryService` during boot.
90
+ */
91
+ setRunnerIdResolver(resolver) {
92
+ this.runnerIdResolver = resolver;
93
+ }
77
94
  get childRegistry() {
78
95
  return this.localChildRegistry;
79
96
  }
@@ -287,6 +304,11 @@ class MoleculerService {
287
304
  // registry on every call so a runtime swap takes effect
288
305
  // immediately without rebuilding the resolver snapshot.
289
306
  getActiveSingletonAddonId: (capName) => this.capabilityService.getRegistry()?.getSingletonAddonId(capName) ?? null,
307
+ // Map the preferred addonId to its runner CHILD id (group when the
308
+ // addon declares `execution.group`, else its own id) so a grouped
309
+ // addon stays selectable as the active singleton. Identity until
310
+ // `AddonRegistryService` injects the resolver.
311
+ resolveChildIdForAddon: (addonId) => this.runnerIdResolver?.(addonId) ?? addonId,
290
312
  });
291
313
  await registry.start();
292
314
  // E1: apply child manifest + cleanup from the UDS lifecycle (hub-local children).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.1.19",
3
+ "version": "1.1.21",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -24,6 +24,7 @@
24
24
  "dependencies": {
25
25
  "@camstack/addon-admin-ui": "*",
26
26
  "@camstack/addon-advanced-notifier": "*",
27
+ "@camstack/addon-notifiers": "*",
27
28
  "@camstack/addon-pipeline": "*",
28
29
  "@camstack/addon-pipeline-orchestrator": "*",
29
30
  "@camstack/addon-post-analysis": "*",