@camstack/server 1.1.11 → 1.1.13

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.
@@ -787,6 +787,19 @@ function buildIntegrationsProvider(ar, eb, loggingService, capabilityRegistry) {
787
787
  const supportsLocationImport = kind === 'device-adoption'
788
788
  ? (d?.supportsLocationImport ?? m.supportsLocationImport ?? false)
789
789
  : false;
790
+ // Integration wizard `mode` (LOCKED MODEL — supersedes `instanceMode`
791
+ // for wizard routing). Explicit manifest `mode` wins; otherwise derive:
792
+ // 1. broker cap AND a brokerKind → 'broker' (HA/MQTT shared session)
793
+ // 2. device-adoption (and NOT broker) → 'account'
794
+ // 3. device-provider only / old `instanceMode: unique` → 'standalone'
795
+ const explicitMode = d?.mode ?? m.mode;
796
+ const declaresBroker = capNames.includes('broker');
797
+ const mode = explicitMode ??
798
+ (declaresBroker && brokerKind
799
+ ? 'broker'
800
+ : kind === 'device-adoption'
801
+ ? 'account'
802
+ : 'standalone');
790
803
  return {
791
804
  addonId: m.id,
792
805
  name: m.name ?? m.id,
@@ -794,6 +807,7 @@ function buildIntegrationsProvider(ar, eb, loggingService, capabilityRegistry) {
794
807
  iconUrl: icon ? `/api/addon-assets/${m.id}/${icon}` : null,
795
808
  color,
796
809
  instanceMode,
810
+ mode,
797
811
  discoveryMode,
798
812
  kind,
799
813
  brokerKind,
@@ -1,10 +1,4 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.requireSingleton = requireSingleton;
4
- exports.requireDeviceScoped = requireDeviceScoped;
5
- exports.concatCollection = concatCollection;
6
- exports.firstSupported = firstSupported;
7
- exports.anySupports = anySupports;
8
2
  /**
9
3
  * Small helpers for wiring capability routers to the `CapabilityRegistry`
10
4
  * in `trpc.router.ts`. These functions don't generate code — they just
@@ -25,6 +19,12 @@ exports.anySupports = anySupports;
25
19
  * provider responsible for a given `streamId`) are NOT covered here —
26
20
  * they have app-specific routing logic that belongs in the mount.
27
21
  */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.requireSingleton = requireSingleton;
24
+ exports.requireDeviceScoped = requireDeviceScoped;
25
+ exports.concatCollection = concatCollection;
26
+ exports.firstSupported = firstSupported;
27
+ exports.anySupports = anySupports;
28
28
  const server_1 = require("@trpc/server");
29
29
  /**
30
30
  * Fetch the currently active singleton provider for a capability.
@@ -34,27 +34,6 @@ const server_1 = require("@trpc/server");
34
34
  function requireSingleton(registry, capName) {
35
35
  return registry?.getSingleton(capName) ?? null;
36
36
  }
37
- /**
38
- * Build a per-device dispatcher that satisfies the singleton-provider
39
- * shape but resolves the actual implementation lazily via
40
- * `registry.getNativeProvider(capName, deviceId)` on every method call.
41
- *
42
- * Use for device-scoped caps that have NO system-level wrapper (PTZ,
43
- * reboot, doorbell, brightness, motion-trigger, switch, …) — drivers
44
- * register per-device native providers via
45
- * `DeviceContext.registerNativeCap`, and this helper bridges the cap-
46
- * router's "fetch a singleton then call methods on it" flow into a
47
- * "resolve native by deviceId per call" flow.
48
- *
49
- * Method input MUST carry `deviceId: number`. Methods without that
50
- * field (auto-injected `getStatus({deviceId})` for caps with a status
51
- * block, every business method that follows the cap-definition
52
- * convention) work transparently.
53
- *
54
- * Throws PRECONDITION_FAILED with a device-specific message when no
55
- * native provider exists for the requested deviceId — much friendlier
56
- * than the singleton fallthrough's "no provider" generic error.
57
- */
58
37
  function requireDeviceScoped(registry, capName) {
59
38
  if (!registry)
60
39
  return null;
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ /**
3
+ * Server-side glue for the runtime cap-router builder (BIG PLAN 2).
4
+ *
5
+ * `@camstack/system` `buildCapRouters(primitives, services)` owns the
6
+ * per-cap router CONSTRUCTION (procedure shapes + node routing), but it
7
+ * holds no tRPC primitives and no backend services. This module supplies
8
+ * both:
9
+ *
10
+ * - `primitives` — the `t`-half: `trpcRouter`, the auth procedures, and
11
+ * `iterableSubscription`, taken from `trpc.middleware`.
12
+ * - `services` — the provider-resolution half: `getLocalProvider`
13
+ * (mirrors the codegen `generate-cap-mounts.ts` decision per mount
14
+ * kind), the cross-node `remoteProxy`, and the canonical tRPC error
15
+ * raisers (the server owns `TRPCError`).
16
+ *
17
+ * The result is spread into the AppRouter BEFORE the hand-written core
18
+ * routers + the `custom`-mount overrides (snapshot-provider, webrtc-
19
+ * session), so override-by-spread precedence is preserved exactly as it
20
+ * was with the old static `mountAllCaps()` spread.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.createCapRouterPrimitives = createCapRouterPrimitives;
24
+ exports.createCapRouterServices = createCapRouterServices;
25
+ exports.buildRuntimeCapRouters = buildRuntimeCapRouters;
26
+ const system_1 = require("@camstack/system");
27
+ const types_1 = require("@camstack/types");
28
+ const server_1 = require("@trpc/server");
29
+ const cap_mount_helpers_js_1 = require("./cap-mount-helpers.js");
30
+ const trpc_middleware_js_1 = require("./trpc.middleware.js");
31
+ /**
32
+ * Pre-computed set of capName → array-output method names for collection
33
+ * caps, derived once from the cap defs at module init. Mirrors
34
+ * `generate-cap-mounts.ts` `isArrayOutputSchema` so the runtime collection
35
+ * resolver fans the SAME methods the codegen used to.
36
+ */
37
+ const COLLECTION_ARRAY_METHODS = computeArrayMethods();
38
+ function computeArrayMethods() {
39
+ const out = new Map();
40
+ for (const def of types_1.ALL_CAPABILITY_DEFINITIONS) {
41
+ if (def.mode !== 'collection')
42
+ continue;
43
+ const names = [];
44
+ for (const [name, schema] of Object.entries(def.methods)) {
45
+ const m = schema;
46
+ if (m.kind === 'subscription')
47
+ continue;
48
+ if (isArrayOutputSchema(m.output))
49
+ names.push(name);
50
+ }
51
+ if (names.length > 0)
52
+ out.set(def.name, names);
53
+ }
54
+ return out;
55
+ }
56
+ /**
57
+ * Structural "is this Zod schema an array output" check — bottoms out at
58
+ * `ZodArray` through the canonical Zod 4 wrappers. Identical logic to
59
+ * `scripts/generate-cap-mounts.ts`.
60
+ */
61
+ function isArrayOutputSchema(schema) {
62
+ let cur = schema;
63
+ for (let depth = 0; depth < 8 && cur != null; depth += 1) {
64
+ const def = cur._def;
65
+ const ctor = cur.constructor?.name;
66
+ if (ctor === 'ZodArray' || def?.type === 'array')
67
+ return true;
68
+ const wraps = ctor === 'ZodReadonly' ||
69
+ def?.type === 'readonly' ||
70
+ ctor === 'ZodOptional' ||
71
+ def?.type === 'optional' ||
72
+ ctor === 'ZodNullable' ||
73
+ def?.type === 'nullable' ||
74
+ ctor === 'ZodDefault' ||
75
+ def?.type === 'default';
76
+ if (!wraps || def?.innerType == null)
77
+ return false;
78
+ cur = def.innerType;
79
+ }
80
+ return false;
81
+ }
82
+ /**
83
+ * Resolve a collection cap's provider exactly as the codegen-emitted
84
+ * mount did:
85
+ * - `addonId` set → that addon's provider directly (bypasses the
86
+ * enabled/disabled filter — an explicit addonId must always hit that
87
+ * exact provider).
88
+ * - else, for caps with array-output methods → an AGGREGATE provider
89
+ * whose array methods are `concatCollection`-fanned across every
90
+ * ENABLED provider; non-array methods fall through to the first.
91
+ * - else → the first enabled provider.
92
+ */
93
+ function resolveCollectionProvider(reg, capName, addonId) {
94
+ if (!reg)
95
+ return null;
96
+ if (addonId !== undefined) {
97
+ return reg.getProviderByAddonId(capName, addonId);
98
+ }
99
+ const entries = reg.getCollectionEntries(capName);
100
+ if (entries.length === 0)
101
+ return null;
102
+ const providers = entries.map(([, p]) => p);
103
+ const first = providers[0];
104
+ if (first === undefined)
105
+ return null;
106
+ const arrayMethods = COLLECTION_ARRAY_METHODS.get(capName);
107
+ if (!arrayMethods || arrayMethods.length === 0)
108
+ return first;
109
+ const aggregate = { ...first };
110
+ for (const method of arrayMethods) {
111
+ aggregate[method] = concatArrayMethod(providers, method);
112
+ }
113
+ return aggregate;
114
+ }
115
+ /**
116
+ * Fan an array-returning method across every provider and flatten the
117
+ * results — the runtime equivalent of `concatCollection` for the
118
+ * structural `ResolvedProvider` shape (which a string method key can't
119
+ * satisfy `concatCollection`'s `ArrayReturningMethodKey<T>` constraint).
120
+ * Behaviour matches `concatCollection`: non-function / non-array results
121
+ * contribute nothing.
122
+ */
123
+ function concatArrayMethod(providers, method) {
124
+ return async (input) => {
125
+ const results = await Promise.all(providers.map(async (p) => {
126
+ const fn = p[method];
127
+ if (typeof fn !== 'function')
128
+ return [];
129
+ const out = await fn(input);
130
+ return Array.isArray(out) ? out : [];
131
+ }));
132
+ return results.flat();
133
+ };
134
+ }
135
+ /** Build the `primitives` half of `buildCapRouters(primitives, services)`. */
136
+ function createCapRouterPrimitives() {
137
+ const procedures = {
138
+ public: trpc_middleware_js_1.publicProcedure,
139
+ protected: trpc_middleware_js_1.protectedProcedure,
140
+ admin: trpc_middleware_js_1.adminProcedure,
141
+ };
142
+ return {
143
+ // Documented type boundary: the builder hands a record of REAL tRPC
144
+ // procedures typed structurally as `unknown` (it can't see the
145
+ // server's procedure types). `trpcRouter` (`t.router`) expects
146
+ // `CreateRouterOptions`; the runtime values satisfy it. Narrow at this
147
+ // single seam so the rest of the builder stays free of `@trpc/server`
148
+ // internal types — mirrors the existing `remoteCapProxy` cast pattern.
149
+ router: (procs) => (0, trpc_middleware_js_1.trpcRouter)(procs),
150
+ procedures,
151
+ iterableSubscription: trpc_middleware_js_1.iterableSubscription,
152
+ };
153
+ }
154
+ /** Build the `services` half of `buildCapRouters(primitives, services)`. */
155
+ function createCapRouterServices(deps) {
156
+ const reg = deps.capabilityRegistry;
157
+ const remoteProxy = (capName, nodeId) => deps.moleculer.createCapabilityProxy(capName, nodeId);
158
+ const getLocalProvider = (capName, mountKind, ctx, addonId) => {
159
+ switch (mountKind) {
160
+ case 'service-backed': {
161
+ const factory = deps.serviceProviders[capName];
162
+ return factory ? factory(ctx) : null;
163
+ }
164
+ case 'device-native':
165
+ return (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, capName);
166
+ case 'collection':
167
+ return resolveCollectionProvider(reg, capName, addonId);
168
+ // singleton / per-node / hub-only resolve the active singleton.
169
+ default:
170
+ return reg?.getSingleton(capName) ?? null;
171
+ }
172
+ };
173
+ return {
174
+ getLocalProvider,
175
+ remoteProxy,
176
+ noProvider: (capName, nodeId) => {
177
+ if (nodeId !== undefined && nodeId !== 'hub') {
178
+ throw new server_1.TRPCError({
179
+ code: 'PRECONDITION_FAILED',
180
+ message: `Capability "${capName}" not available on node "${nodeId}"`,
181
+ });
182
+ }
183
+ throw new server_1.TRPCError({
184
+ code: 'PRECONDITION_FAILED',
185
+ message: `Capability "${capName}" provider not available`,
186
+ });
187
+ },
188
+ noNodeRouting: (capName) => {
189
+ throw new server_1.TRPCError({
190
+ code: 'BAD_REQUEST',
191
+ message: `Node routing not available for "${capName}"`,
192
+ });
193
+ },
194
+ badRequest: (message) => {
195
+ throw new server_1.TRPCError({ code: 'BAD_REQUEST', message });
196
+ },
197
+ };
198
+ }
199
+ /**
200
+ * Build the runtime cap-router map (the dynamic value). Returns
201
+ * `Record<string, unknown>` — the caller assigns it to the static
202
+ * `ReturnType<typeof mountAllCaps>` type at the single documented
203
+ * boundary so the AppRouter TYPE stays codegen-derived while the VALUE is
204
+ * built at runtime from `ALL_CAPABILITY_DEFINITIONS`.
205
+ */
206
+ function buildRuntimeCapRouters(deps) {
207
+ return (0, system_1.buildCapRouters)(createCapRouterPrimitives(), createCapRouterServices(deps));
208
+ }
@@ -1669,6 +1669,24 @@ function createCapRouter_climateControl(getProvider, createRemoteProxy) {
1669
1669
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
1670
1670
  return p.setTargetHumidity(methodInput);
1671
1671
  }),
1672
+ setSwingVertical: trpc_middleware_js_1.adminProcedure
1673
+ .input(types_28.climateControlCapability.methods.setSwingVertical.input.loose())
1674
+ .output(types_28.climateControlCapability.methods.setSwingVertical.output)
1675
+ .mutation(async ({ input, ctx }) => {
1676
+ const { nodeId, ...methodInput } = input;
1677
+ const p = resolveProvider('climate-control', nodeId, () => getProvider(ctx), createRemoteProxy);
1678
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
1679
+ return p.setSwingVertical(methodInput);
1680
+ }),
1681
+ setSwingHorizontal: trpc_middleware_js_1.adminProcedure
1682
+ .input(types_28.climateControlCapability.methods.setSwingHorizontal.input.loose())
1683
+ .output(types_28.climateControlCapability.methods.setSwingHorizontal.output)
1684
+ .mutation(async ({ input, ctx }) => {
1685
+ const { nodeId, ...methodInput } = input;
1686
+ const p = resolveProvider('climate-control', nodeId, () => getProvider(ctx), createRemoteProxy);
1687
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
1688
+ return p.setSwingHorizontal(methodInput);
1689
+ }),
1672
1690
  });
1673
1691
  }
1674
1692
  function createCapRouter_color(getProvider, createRemoteProxy) {
@@ -3,24 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enrichInputWithUserAgent = enrichInputWithUserAgent;
4
4
  exports.wrapWebrtcSessionProviderWithRelay = wrapWebrtcSessionProviderWithRelay;
5
5
  exports.buildAppRouter = buildAppRouter;
6
- const trpc_middleware_1 = require("./trpc.middleware");
7
- const generated_cap_routers_1 = require("./generated-cap-routers");
8
- const generated_cap_mounts_js_1 = require("./generated-cap-mounts.js");
9
- const cap_providers_js_1 = require("../core/cap-providers.js");
10
- const auth_router_js_1 = require("../core/auth.router.js");
11
6
  const addon_settings_router_js_1 = require("../core/addon-settings.router.js");
12
- const settings_backend_router_js_1 = require("../core/settings-backend.router.js");
7
+ const auth_router_js_1 = require("../core/auth.router.js");
8
+ const cap_providers_js_1 = require("../core/cap-providers.js");
9
+ const capabilities_router_js_1 = require("../core/capabilities.router.js");
13
10
  const event_bus_proxy_router_js_1 = require("../core/event-bus-proxy.router.js");
14
- const repl_router_js_1 = require("../core/repl.router.js");
15
- const notifications_router_js_1 = require("../core/notifications.router.js");
16
- const logs_router_js_1 = require("../core/logs.router.js");
17
- const system_events_router_js_1 = require("../core/system-events.router.js");
11
+ const hwaccel_router_js_1 = require("../core/hwaccel.router.js");
18
12
  const live_events_router_js_1 = require("../core/live-events.router.js");
19
- const capabilities_router_js_1 = require("../core/capabilities.router.js");
13
+ const logs_router_js_1 = require("../core/logs.router.js");
14
+ const notifications_router_js_1 = require("../core/notifications.router.js");
15
+ const repl_router_js_1 = require("../core/repl.router.js");
16
+ const settings_backend_router_js_1 = require("../core/settings-backend.router.js");
20
17
  const stream_probe_router_js_1 = require("../core/stream-probe.router.js");
21
- const hwaccel_router_js_1 = require("../core/hwaccel.router.js");
18
+ const system_events_router_js_1 = require("../core/system-events.router.js");
22
19
  const cap_mount_helpers_js_1 = require("./cap-mount-helpers.js");
20
+ const cap_router_runtime_js_1 = require("./cap-router-runtime.js");
23
21
  const client_ip_js_1 = require("./client-ip.js");
22
+ const generated_cap_routers_1 = require("./generated-cap-routers");
23
+ const trpc_middleware_1 = require("./trpc.middleware");
24
24
  /**
25
25
  * Merge the server-read User-Agent into a signaling call's
26
26
  * `consumerAttribution`, building a NEW input object (immutable — never
@@ -66,31 +66,73 @@ function wrapWebrtcSessionProviderWithRelay(provider, ctx) {
66
66
  };
67
67
  }
68
68
  /**
69
- * Build the AppRouter. Mounts every codegen'd cap router via the auto-
70
- * mount entrypoint and overrides the handful that need service-backed
71
- * providers or custom collection dispatch. Non-cap (core) routers ride
72
- * alongside in the same root object.
69
+ * Build the service-backed provider factory map (BIG PLAN 2). Caps marked
70
+ * `mount: { kind: 'service-backed' }` have NO addon-registered provider —
71
+ * the provider is composed from backend services. The runtime builder
72
+ * resolves them through this map (keyed by cap NAME) instead of the
73
+ * registry, keeping backend composition in `@camstack/server` (the
74
+ * self-sufficiency invariant — `@camstack/system` never touches a backend
75
+ * service). Each factory receives the per-request tRPC `ctx`.
76
+ */
77
+ function buildServiceProviders(services) {
78
+ const wrap = (fn) => (ctx) => fn(ctx);
79
+ return {
80
+ 'network-quality': wrap(() => (0, cap_providers_js_1.buildNetworkQualityProvider)(services.networkQualityService)),
81
+ system: wrap(() => (0, cap_providers_js_1.buildSystemProvider)(services.featureService, services.capabilityRegistry)),
82
+ toast: wrap((ctx) => (0, cap_providers_js_1.buildToastProvider)(services.toastService, ctx)),
83
+ integrations: wrap(() => (0, cap_providers_js_1.buildIntegrationsProvider)(services.addonRegistry, services.eventBus, services.loggingService, services.capabilityRegistry)),
84
+ nodes: wrap(() => (0, cap_providers_js_1.buildNodesProvider)(services.agentRegistry, services.moleculer, services.addonRegistry)),
85
+ addons: wrap((ctx) => (0, cap_providers_js_1.buildAddonsProvider)(services.addonRegistry, services.addonPackageService, services.loggingService, services.moleculer, services.configService, ctx)),
86
+ };
87
+ }
88
+ /**
89
+ * Build the AppRouter capability track (BIG PLAN 2).
73
90
  *
74
- * Override-by-spread: spread `mountAllCaps(services)` first, then the
75
- * overrides AFTER the later property wins. The drift guard in
76
- * `scripts/codegen.ts` ensures every codegen'd `createCapRouter_X` is
77
- * present in the auto-mount inventory (or explicitly in the legacy
78
- * skip-list), so the override list below NEVER needs to add a new entry
79
- * just to mount a new cap — only to swap in a custom provider.
91
+ * The per-cap router CONSTRUCTION now lives in `@camstack/system`
92
+ * (`buildCapRouters`), driven at runtime by `ALL_CAPABILITY_DEFINITIONS`
93
+ * + the declarative `mount` hint on each cap def. A capability update
94
+ * therefore ships via the framework plugin (`@camstack/types` +
95
+ * `@camstack/system`) with NO `@camstack/server` rebuild the value is
96
+ * dynamic.
97
+ *
98
+ * The AppRouter TYPE stays codegen-derived: the dynamic value is assigned
99
+ * to `ReturnType<typeof mountAllCaps>` (the generated per-cap router type
100
+ * map) at the single documented type boundary below, so the SDK keeps
101
+ * full procedure inference and `generate-api-types.ts` resolves the same
102
+ * expanded type as before.
103
+ *
104
+ * Override-by-spread is preserved: spread the dynamic caps first, then the
105
+ * hand-written core routers + the two `custom`-mount overrides
106
+ * (snapshot-provider's probe fan-out, webrtc-session's relay enrichment),
107
+ * which the generic builder intentionally leaves unmounted for the server
108
+ * to fill.
80
109
  */
81
110
  function buildCapabilityRouters(services) {
111
+ // ── Dynamic cap routers (value) → static cap-router map (type). ──────
112
+ // The runtime builder returns `Record<string, unknown>`; assigning it to
113
+ // `ReturnType<typeof mountAllCaps>` is the documented type boundary that
114
+ // keeps the AppRouter type codegen-derived while the value is built from
115
+ // the cap defs at runtime. `mountAllCaps` is imported for its TYPE only
116
+ // (`import type`) — its value is never called; the codegen
117
+ // `generated-cap-mounts.ts` / `generated-cap-routers.ts` remain the TYPE
118
+ // source of record.
119
+ const dynamicCaps = (0, cap_router_runtime_js_1.buildRuntimeCapRouters)({
120
+ capabilityRegistry: services.capabilityRegistry,
121
+ moleculer: services.moleculer,
122
+ serviceProviders: buildServiceProviders(services),
123
+ });
82
124
  return {
83
- // ── Auto-mount: every codegen'd cap router with a canonical
84
- // provider shape. Everything below this line OVERRIDES the
85
- // auto-mount entry for caps with service-backed providers,
86
- // custom collection routing, or a hub-only `null` remote proxy.
87
- ...(0, generated_cap_mounts_js_1.mountAllCaps)(services),
125
+ // ── Runtime-built cap routers: every cap whose mount is
126
+ // `singleton` / `collection` / `device-native` / `service-backed`
127
+ // / `hub-only`. `custom` + `skip` caps are NOT here — `skip` is
128
+ // never mounted; `custom` is filled by the overrides below. ──────
129
+ ...dynamicCaps,
88
130
  // ── Non-cap (core) routers — hand-written, single-impl ──────────
89
131
  notifications: (0, notifications_router_js_1.createNotificationsRouter)(services.notificationService),
90
132
  // Raw DB proxy for forked workers to read/write addon store.
91
133
  // Workers use ctx.api.addonSettingsRaw.getGlobal.query({...}).
92
- // NOT the three-level settings gateway — that's the codegen'd
93
- // `addonSettings` cap router (mounted via auto-mount above).
134
+ // NOT the three-level settings gateway — that's the
135
+ // `addonSettings` cap router (mounted dynamically above).
94
136
  addonSettingsRaw: (0, addon_settings_router_js_1.createAddonSettingsRouter)(services.configService),
95
137
  settingsBackend: (0, settings_backend_router_js_1.createSettingsBackendRouter)(() => services.addonRegistry.getSettingsBackend()),
96
138
  eventBusProxy: (0, event_bus_proxy_router_js_1.createEventBusProxyRouter)(services.eventBus),
@@ -106,46 +148,11 @@ function buildCapabilityRouters(services) {
106
148
  // which hardware backend each agent will use.
107
149
  hwaccel: (0, hwaccel_router_js_1.createHwAccelRouter)(services.moleculer?.broker ?? null),
108
150
  auth: (0, auth_router_js_1.createAuthRouter)(services.authService, services.capabilityRegistry),
109
- // ── Cap overrides: service-backed providers ─────────────────────
110
- // These caps don't have an addon registering a provider in the
111
- // CapabilityRegistry — the provider is built on-demand from
112
- // backend services. `mountAllCaps` would return `null` for them
113
- // (registry lookup miss), so we re-mount with `buildXProvider`.
114
- networkQuality: (0, generated_cap_routers_1.createCapRouter_networkQuality)((_ctx) => (0, cap_providers_js_1.buildNetworkQualityProvider)(services.networkQualityService)),
115
- system: (0, generated_cap_routers_1.createCapRouter_system)((_ctx) => (0, cap_providers_js_1.buildSystemProvider)(services.featureService, services.capabilityRegistry)),
116
- toast: (0, generated_cap_routers_1.createCapRouter_toast)((ctx) => (0, cap_providers_js_1.buildToastProvider)(services.toastService, ctx)),
117
- integrations: (0, generated_cap_routers_1.createCapRouter_integrations)((_ctx) => (0, cap_providers_js_1.buildIntegrationsProvider)(services.addonRegistry, services.eventBus, services.loggingService, services.capabilityRegistry)),
118
- nodes: (0, generated_cap_routers_1.createCapRouter_nodes)((_ctx) => (0, cap_providers_js_1.buildNodesProvider)(services.agentRegistry, services.moleculer, services.addonRegistry)),
119
- addons: (0, generated_cap_routers_1.createCapRouter_addons)((ctx) => (0, cap_providers_js_1.buildAddonsProvider)(services.addonRegistry, services.addonPackageService, services.loggingService, services.moleculer, services.configService, ctx)),
120
- // ── Cap overrides: cross-node remote-proxy cast ─────────────────
121
- // These caps' providers have manual interface types that pre-date
122
- // `InferProvider<typeof xCap>` — structurally identical, nominally
123
- // distinct. Casting at the override site is cheaper than reworking
124
- // the provider declarations. Auto-mount can't infer the cast.
125
- pipelineExecutor: (0, generated_cap_routers_1.createCapRouter_pipelineExecutor)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'pipeline-executor'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
126
- pipelineRunner: (0, generated_cap_routers_1.createCapRouter_pipelineRunner)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'pipeline-runner'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
127
- pipelineOrchestrator: (0, generated_cap_routers_1.createCapRouter_pipelineOrchestrator)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'pipeline-orchestrator'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
128
- audioAnalyzer: (0, generated_cap_routers_1.createCapRouter_audioAnalyzer)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'audio-analyzer'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
129
- audioCodec: (0, generated_cap_routers_1.createCapRouter_audioCodec)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'audio-codec'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
130
- decoder: (0, generated_cap_routers_1.createCapRouter_decoder)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'decoder'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
131
- modelConvert: (0, generated_cap_routers_1.createCapRouter_modelConvert)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'model-convert'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
132
- platformProbe: (0, generated_cap_routers_1.createCapRouter_platformProbe)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'platform-probe'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
133
- // ── Cap overrides: hub-only, no remote fallback ─────────────────
134
- // The cap is intentionally single-node; agents are not directly
135
- // addressable. Auto-mount would still set up a proxy factory; we
136
- // explicitly return `null` to short-circuit any cross-node attempt.
137
- localNetwork: (0, generated_cap_routers_1.createCapRouter_localNetwork)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'local-network'), (_capName, _nodeId) => null),
138
- // ── Cap overrides: collection dispatch (contribution / probe) ──
139
- // `turn-provider.getTurnServers` is now handled generically by the
140
- // auto-mount: it's an array-output method on a `collection` cap, so
141
- // `mountAllCaps` fans it across every enabled provider via
142
- // `concatCollection` when no `addonId` is supplied. No hand-written
143
- // override needed.
144
- //
151
+ // ── Cap overrides: `mount: { kind: 'custom' }` ──────────────────
145
152
  // `snapshot-provider.supportsDevice` is an OR across providers;
146
153
  // `getSnapshot` picks the first one that claims the device. The
147
- // generic first-provider resolver from the auto-mount can't model
148
- // this — we hand-write the probe + fan-out logic.
154
+ // generic builder's first-provider/concat collection resolver can't
155
+ // model this — we hand-write the probe + fan-out logic.
149
156
  snapshotProvider: (0, generated_cap_routers_1.createCapRouter_snapshotProvider)((_ctx) => {
150
157
  const reg = services.capabilityRegistry;
151
158
  if (!reg)
@@ -157,29 +164,25 @@ function buildCapabilityRouters(services) {
157
164
  const getSnapshot = (0, cap_mount_helpers_js_1.firstSupported)(providers, 'supportsDevice', 'getSnapshot');
158
165
  return { supportsDevice, getSnapshot };
159
166
  }),
160
- // ── Cap override: server-detected remote → relay-only ────────────
161
- // The broker (a forked addon) can't see the HTTP request, so it
162
- // can't tell a LAN viewer from a remote one. We override only the
163
- // `getProvider` accessor to return a per-request provider whose
164
- // `createSession` carries a server-computed `relayOnly` flag derived
165
- // from the client IP in `ctx.req`. Remote (CGNAT/4G) viewers force
166
- // TURN-relay-only ICE; LAN viewers keep the direct host/srflx path.
167
- // All other methods delegate straight through, and the cross-node
168
- // remote-proxy routing is preserved (forked/agent-hosted brokers).
167
+ // `webrtc-session` server-detected remote → relay-only. The broker
168
+ // (a forked addon) can't see the HTTP request, so it can't tell a LAN
169
+ // viewer from a remote one. We override `getProvider` to return a
170
+ // per-request provider whose `createSession` carries a server-computed
171
+ // `relayOnly` flag derived from the client IP in `ctx.req`. Remote
172
+ // (CGNAT/4G) viewers force TURN-relay-only ICE; LAN viewers keep the
173
+ // direct host/srflx path. All other methods delegate straight through,
174
+ // and the cross-node remote-proxy routing is preserved (forked/agent-
175
+ // hosted brokers).
169
176
  webrtcSession: (0, generated_cap_routers_1.createCapRouter_webrtcSession)((ctx) => {
170
177
  const provider = services.capabilityRegistry?.getSingleton('webrtc-session') ?? null;
171
178
  return provider ? wrapWebrtcSessionProviderWithRelay(provider, ctx) : null;
172
179
  }, (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
173
- // NOT MOUNTED — legacy provider shapes (positional args / sync
174
- // returns) that don't match the codegen routers' {input}-object +
175
- // Promise<T> contract. Tracked by `LEGACY_SHAPE_SKIP` in
176
- // `generated-cap-mounts.ts` until the provider refactor (task #195):
177
- // - addon-routes (IAddonRouteProvider: getRoutes sync)
178
- // - auth-provider (IAuthProvider: positional credentials)
179
- // - log-destination (ILogDestination: positional + extra lifecycle)
180
- // - restreamer (IRestreamer: registerDevice positional)
181
- // - streaming-engine (IStreamingEngine: registerStream positional)
182
- // - webrtc (IWebRtcProvider: missing hasAdaptiveBitrate)
180
+ // NOT MOUNTED — `mount: { kind: 'skip' }` legacy provider shapes
181
+ // (positional args / sync returns) that don't match the codegen
182
+ // routers' {input}-object + Promise<T> contract. The runtime builder
183
+ // skips them via `resolveCapMount` (task #195):
184
+ // - addon-routes / auth-provider / log-destination
185
+ // - restreamer / streaming-engine / webrtc
183
186
  };
184
187
  }
185
188
  function buildAppRouter(services) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",