@camstack/server 1.0.0 → 1.0.1

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 (234) hide show
  1. package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
  2. package/dist/api/addon-upload.js +441 -0
  3. package/dist/api/addons-custom.router.js +91 -0
  4. package/dist/api/auth-whoami.js +55 -0
  5. package/dist/api/bridge-addons.router.js +109 -0
  6. package/dist/api/capabilities.router.js +229 -0
  7. package/dist/api/core/addon-settings.router.js +117 -0
  8. package/dist/api/core/agents.router.js +73 -0
  9. package/dist/api/core/auth.router.js +286 -0
  10. package/dist/api/core/bulk-update-coordinator.js +229 -0
  11. package/dist/api/core/cap-providers.js +1124 -0
  12. package/dist/api/core/capabilities.router.js +138 -0
  13. package/dist/api/core/collection-preference.js +17 -0
  14. package/dist/api/core/event-bus-proxy.router.js +45 -0
  15. package/dist/api/core/hwaccel.router.js +91 -0
  16. package/dist/api/core/live-events.router.js +61 -0
  17. package/dist/api/core/logs.router.js +172 -0
  18. package/dist/api/core/notifications.router.js +67 -0
  19. package/dist/api/core/repl.router.js +35 -0
  20. package/dist/api/core/settings-backend.router.js +121 -0
  21. package/dist/api/core/stream-probe.router.js +58 -0
  22. package/dist/api/core/system-events.router.js +100 -0
  23. package/dist/api/health/health.routes.js +68 -0
  24. package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
  25. package/dist/api/oauth2/oauth2-routes.js +219 -0
  26. package/dist/api/trpc/cap-mount-helpers.js +194 -0
  27. package/dist/api/trpc/cap-route-error-formatter.js +133 -0
  28. package/dist/api/trpc/client-ip.js +147 -0
  29. package/dist/api/trpc/core-cap-bridge.js +115 -0
  30. package/dist/api/trpc/generated-cap-mounts.js +388 -0
  31. package/dist/api/trpc/generated-cap-routers.js +7635 -0
  32. package/dist/api/trpc/scope-access.js +93 -0
  33. package/dist/api/trpc/trpc.context.js +184 -0
  34. package/dist/api/trpc/trpc.middleware.js +139 -0
  35. package/dist/api/trpc/trpc.router.js +188 -0
  36. package/dist/auth/session-cookie.js +47 -0
  37. package/dist/boot/boot-config.js +241 -0
  38. package/dist/boot/integration-id-backfill.js +76 -0
  39. package/dist/boot/post-boot.service.js +85 -0
  40. package/dist/core/addon/addon-call-gateway.js +99 -0
  41. package/dist/core/addon/addon-package.service.js +1560 -0
  42. package/dist/core/addon/addon-registry.service.js +2739 -0
  43. package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
  44. package/dist/core/addon/addon-search.service.js +62 -0
  45. package/dist/core/addon/addon-settings-provider.js +102 -0
  46. package/dist/core/addon/addon.tokens.js +5 -0
  47. package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
  48. package/dist/core/addon-pages/addon-pages.service.js +107 -0
  49. package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
  50. package/dist/core/agent/agent-registry.service.js +477 -0
  51. package/dist/core/auth/auth.service.js +10 -0
  52. package/dist/core/capability/capability.service.js +58 -0
  53. package/dist/core/config/config.schema.js +7 -0
  54. package/dist/core/config/config.service.js +10 -0
  55. package/dist/core/events/event-bus.service.js +83 -0
  56. package/dist/core/feature/feature.service.js +10 -0
  57. package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
  58. package/dist/core/logging/log-ring-buffer.js +6 -0
  59. package/dist/core/logging/logging.service.js +130 -0
  60. package/dist/core/logging/scoped-logger.js +6 -0
  61. package/dist/core/moleculer/cap-call-fn.js +50 -0
  62. package/dist/core/moleculer/cap-route-authority.js +122 -0
  63. package/dist/core/moleculer/moleculer.service.js +898 -0
  64. package/dist/core/network/network-quality.service.js +7 -0
  65. package/dist/core/notification/notification-wrapper.service.js +33 -0
  66. package/dist/core/notification/toast-wrapper.service.js +25 -0
  67. package/dist/core/provider/provider.tokens.js +4 -0
  68. package/dist/core/repl/repl-engine.service.js +140 -0
  69. package/dist/core/storage/fs-storage-backend.js +6 -0
  70. package/dist/core/storage/storage-location-manager.js +6 -0
  71. package/dist/core/storage/storage.service.js +7 -0
  72. package/dist/core/streaming/stream-probe.service.js +209 -0
  73. package/dist/core/topology/topology-emitter.service.js +106 -0
  74. package/dist/launcher.js +325 -0
  75. package/dist/main.js +1098 -0
  76. package/dist/manual-boot.js +227 -0
  77. package/package.json +5 -1
  78. package/src/__tests__/addon-install-e2e.test.ts +0 -74
  79. package/src/__tests__/addon-pages-e2e.test.ts +0 -200
  80. package/src/__tests__/addon-route-session.test.ts +0 -17
  81. package/src/__tests__/addon-settings-router.spec.ts +0 -67
  82. package/src/__tests__/addon-upload.spec.ts +0 -475
  83. package/src/__tests__/agent-registry.spec.ts +0 -179
  84. package/src/__tests__/agent-status-page.spec.ts +0 -82
  85. package/src/__tests__/auth-session-cookie.test.ts +0 -48
  86. package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
  87. package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
  88. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
  89. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
  90. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
  91. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
  92. package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
  93. package/src/__tests__/cap-route-adapter.spec.ts +0 -302
  94. package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
  95. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
  96. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
  97. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
  98. package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
  99. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
  100. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
  101. package/src/__tests__/cap-routers/harness.ts +0 -163
  102. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
  103. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
  104. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
  105. package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
  106. package/src/__tests__/capability-e2e.test.ts +0 -384
  107. package/src/__tests__/cli-e2e.test.ts +0 -150
  108. package/src/__tests__/core-cap-bridge.spec.ts +0 -91
  109. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
  110. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
  111. package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
  112. package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
  113. package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
  114. package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
  115. package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
  116. package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
  117. package/src/__tests__/framework-allowlist.spec.ts +0 -96
  118. package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
  119. package/src/__tests__/https-e2e.test.ts +0 -124
  120. package/src/__tests__/lifecycle-e2e.test.ts +0 -189
  121. package/src/__tests__/live-events-subscription.spec.ts +0 -149
  122. package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
  123. package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
  124. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
  125. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
  126. package/src/__tests__/native-cap-route.spec.ts +0 -427
  127. package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
  128. package/src/__tests__/post-boot-restart.spec.ts +0 -161
  129. package/src/__tests__/singleton-contention.test.ts +0 -499
  130. package/src/__tests__/streaming-diagnostic.test.ts +0 -615
  131. package/src/__tests__/streaming-scale.test.ts +0 -314
  132. package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
  133. package/src/__tests__/uds-log-ingest.spec.ts +0 -183
  134. package/src/api/__tests__/addons-custom.spec.ts +0 -148
  135. package/src/api/__tests__/capabilities.router.test.ts +0 -56
  136. package/src/api/addon-upload.ts +0 -529
  137. package/src/api/addons-custom.router.ts +0 -101
  138. package/src/api/auth-whoami.ts +0 -101
  139. package/src/api/bridge-addons.router.ts +0 -122
  140. package/src/api/capabilities.router.ts +0 -265
  141. package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
  142. package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
  143. package/src/api/core/addon-settings.router.ts +0 -127
  144. package/src/api/core/agents.router.ts +0 -86
  145. package/src/api/core/auth.router.ts +0 -322
  146. package/src/api/core/bulk-update-coordinator.ts +0 -305
  147. package/src/api/core/cap-providers.ts +0 -1339
  148. package/src/api/core/capabilities.router.ts +0 -149
  149. package/src/api/core/collection-preference.ts +0 -40
  150. package/src/api/core/event-bus-proxy.router.ts +0 -45
  151. package/src/api/core/hwaccel.router.ts +0 -108
  152. package/src/api/core/live-events.router.ts +0 -67
  153. package/src/api/core/logs.router.ts +0 -195
  154. package/src/api/core/notifications.router.ts +0 -66
  155. package/src/api/core/repl.router.ts +0 -39
  156. package/src/api/core/settings-backend.router.ts +0 -140
  157. package/src/api/core/stream-probe.router.ts +0 -57
  158. package/src/api/core/system-events.router.ts +0 -125
  159. package/src/api/health/health.routes.ts +0 -117
  160. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
  161. package/src/api/oauth2/oauth2-routes.ts +0 -281
  162. package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
  163. package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
  164. package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
  165. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
  166. package/src/api/trpc/cap-mount-helpers.ts +0 -245
  167. package/src/api/trpc/cap-route-error-formatter.ts +0 -171
  168. package/src/api/trpc/client-ip.ts +0 -147
  169. package/src/api/trpc/core-cap-bridge.ts +0 -154
  170. package/src/api/trpc/generated-cap-mounts.ts +0 -1240
  171. package/src/api/trpc/generated-cap-routers.ts +0 -11523
  172. package/src/api/trpc/scope-access.ts +0 -110
  173. package/src/api/trpc/trpc.context.ts +0 -258
  174. package/src/api/trpc/trpc.middleware.ts +0 -146
  175. package/src/api/trpc/trpc.router.ts +0 -389
  176. package/src/auth/session-cookie.ts +0 -54
  177. package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
  178. package/src/boot/boot-config.ts +0 -259
  179. package/src/boot/integration-id-backfill.ts +0 -109
  180. package/src/boot/post-boot.service.ts +0 -105
  181. package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
  182. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
  183. package/src/core/addon/addon-call-gateway.ts +0 -171
  184. package/src/core/addon/addon-package.service.ts +0 -1787
  185. package/src/core/addon/addon-registry.service.ts +0 -3130
  186. package/src/core/addon/addon-search.service.ts +0 -91
  187. package/src/core/addon/addon-settings-provider.ts +0 -220
  188. package/src/core/addon/addon.tokens.ts +0 -2
  189. package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
  190. package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
  191. package/src/core/addon-pages/addon-pages.service.ts +0 -82
  192. package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
  193. package/src/core/agent/agent-registry.service.ts +0 -529
  194. package/src/core/auth/auth.service.spec.ts +0 -86
  195. package/src/core/auth/auth.service.ts +0 -8
  196. package/src/core/capability/capability.service.ts +0 -66
  197. package/src/core/config/config.schema.ts +0 -3
  198. package/src/core/config/config.service.spec.ts +0 -175
  199. package/src/core/config/config.service.ts +0 -7
  200. package/src/core/events/event-bus.service.spec.ts +0 -235
  201. package/src/core/events/event-bus.service.ts +0 -89
  202. package/src/core/feature/feature.service.spec.ts +0 -99
  203. package/src/core/feature/feature.service.ts +0 -8
  204. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
  205. package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
  206. package/src/core/logging/log-ring-buffer.ts +0 -3
  207. package/src/core/logging/logging.service.spec.ts +0 -287
  208. package/src/core/logging/logging.service.ts +0 -143
  209. package/src/core/logging/scoped-logger.ts +0 -3
  210. package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
  211. package/src/core/moleculer/cap-call-fn.ts +0 -107
  212. package/src/core/moleculer/cap-route-authority.ts +0 -194
  213. package/src/core/moleculer/moleculer.service.ts +0 -1072
  214. package/src/core/network/network-quality.service.spec.ts +0 -53
  215. package/src/core/network/network-quality.service.ts +0 -5
  216. package/src/core/notification/notification-wrapper.service.ts +0 -34
  217. package/src/core/notification/toast-wrapper.service.ts +0 -27
  218. package/src/core/provider/provider.tokens.ts +0 -1
  219. package/src/core/repl/repl-engine.service.spec.ts +0 -444
  220. package/src/core/repl/repl-engine.service.ts +0 -155
  221. package/src/core/storage/fs-storage-backend.spec.ts +0 -70
  222. package/src/core/storage/fs-storage-backend.ts +0 -3
  223. package/src/core/storage/storage-location-manager.spec.ts +0 -130
  224. package/src/core/storage/storage-location-manager.ts +0 -3
  225. package/src/core/storage/storage.service.spec.ts +0 -73
  226. package/src/core/storage/storage.service.ts +0 -3
  227. package/src/core/streaming/stream-probe.service.ts +0 -221
  228. package/src/core/topology/topology-emitter.service.ts +0 -105
  229. package/src/launcher.ts +0 -314
  230. package/src/main.ts +0 -1245
  231. package/src/manual-boot.ts +0 -301
  232. package/tsconfig.build.json +0 -8
  233. package/tsconfig.json +0 -33
  234. package/vitest.config.ts +0 -26
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBridgeAddonsRouter = createBridgeAddonsRouter;
4
+ const zod_1 = require("zod");
5
+ const server_1 = require("@trpc/server");
6
+ const trpc_middleware_1 = require("./trpc/trpc.middleware");
7
+ function createBridgeAddonsRouter(bridge, addonSearch, addonRegistry, toastService) {
8
+ return (0, trpc_middleware_1.trpcRouter)({
9
+ /** List all addon packages installed in the addons directory */
10
+ listPackages: trpc_middleware_1.protectedProcedure.query(() => {
11
+ const installer = bridge.getInstaller();
12
+ if (!installer)
13
+ return [];
14
+ return installer.listInstalled();
15
+ }),
16
+ /** List all available addons across all loaded packages */
17
+ listAddons: trpc_middleware_1.protectedProcedure.query(() => {
18
+ return bridge.listAvailableAddons().map((id) => {
19
+ const addon = bridge.getLoader().getAddon(id);
20
+ return {
21
+ id,
22
+ packageName: addon?.packageName ?? 'unknown',
23
+ slot: addon?.declaration.slot ?? null,
24
+ };
25
+ });
26
+ }),
27
+ /** Install a community addon package from npm */
28
+ installPackage: trpc_middleware_1.adminProcedure
29
+ .input(zod_1.z.object({ packageName: zod_1.z.string(), version: zod_1.z.string().optional() }))
30
+ .mutation(async ({ input }) => {
31
+ const installer = bridge.getInstaller();
32
+ if (!installer) {
33
+ throw new server_1.TRPCError({
34
+ code: 'PRECONDITION_FAILED',
35
+ message: 'Addon installer not available — bridge may have failed to initialize',
36
+ });
37
+ }
38
+ await installer.install(input.packageName, input.version);
39
+ await bridge.reloadPackages();
40
+ const result = addonRegistry
41
+ ? await addonRegistry.loadNewAddons()
42
+ : { loaded: [], failed: [] };
43
+ toastService?.broadcast({
44
+ title: 'Addon Installed',
45
+ message: `${input.packageName} installed successfully${result.loaded.length ? ` (${result.loaded.join(', ')})` : ''}`,
46
+ severity: result.failed.length ? 'warning' : 'info',
47
+ });
48
+ return { success: true, loaded: result.loaded, failed: result.failed };
49
+ }),
50
+ /** Uninstall a community addon package */
51
+ uninstallPackage: trpc_middleware_1.adminProcedure
52
+ .input(zod_1.z.object({ packageName: zod_1.z.string() }))
53
+ .mutation(async ({ input }) => {
54
+ // Server-side guard: prevent uninstalling required packages.
55
+ // After Phase D bundle merge, the pipeline-related packages
56
+ // (stream-broker, detection-pipeline, motion-wasm, decoders,
57
+ // audio) all live in @camstack/addon-pipeline.
58
+ const REQUIRED = new Set([
59
+ '@camstack/core',
60
+ '@camstack/addon-pipeline',
61
+ '@camstack/addon-pipeline-orchestrator',
62
+ '@camstack/addon-post-analysis',
63
+ '@camstack/addon-admin-ui',
64
+ ]);
65
+ if (REQUIRED.has(input.packageName)) {
66
+ throw new server_1.TRPCError({
67
+ code: 'FORBIDDEN',
68
+ message: `Package ${input.packageName} is required and cannot be uninstalled`,
69
+ });
70
+ }
71
+ const installer = bridge.getInstaller();
72
+ if (!installer) {
73
+ throw new server_1.TRPCError({
74
+ code: 'PRECONDITION_FAILED',
75
+ message: 'Addon installer not available — bridge may have failed to initialize',
76
+ });
77
+ }
78
+ await installer.uninstall(input.packageName);
79
+ await bridge.reloadPackages();
80
+ if (addonRegistry)
81
+ await addonRegistry.loadNewAddons();
82
+ toastService?.broadcast({
83
+ title: 'Addon Uninstalled',
84
+ message: `${input.packageName} has been removed`,
85
+ severity: 'info',
86
+ });
87
+ return { success: true };
88
+ }),
89
+ /** Force reload all addon packages (re-scan directories, re-import modules) */
90
+ reloadPackages: trpc_middleware_1.adminProcedure.mutation(async () => {
91
+ await bridge.reloadPackages();
92
+ return { success: true, message: 'Addon packages reloaded' };
93
+ }),
94
+ /** Search npm for available CamStack addons */
95
+ searchAvailable: trpc_middleware_1.protectedProcedure
96
+ .input(zod_1.z.object({ query: zod_1.z.string().optional() }).optional())
97
+ .query(async ({ input }) => {
98
+ const results = await addonSearch.searchAddons(input?.query);
99
+ // Enrich with install status from locally installed packages
100
+ const installed = bridge.getInstaller()?.listInstalled() ?? [];
101
+ const installedMap = new Map(installed.map((p) => [p.name, p.version]));
102
+ return results.map((r) => ({
103
+ ...r,
104
+ installed: installedMap.has(r.name),
105
+ installedVersion: installedMap.get(r.name),
106
+ }));
107
+ }),
108
+ });
109
+ }
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCapabilitiesRouter = createCapabilitiesRouter;
4
+ const zod_1 = require("zod");
5
+ const server_1 = require("@trpc/server");
6
+ const trpc_middleware_1 = require("./trpc/trpc.middleware");
7
+ const kernel_1 = require("@camstack/kernel");
8
+ const collection_preference_1 = require("./core/collection-preference");
9
+ // ─── Zod Schemas ───────────────────────────────────────────────────────────
10
+ const setPreferenceInput = zod_1.z.discriminatedUnion('mode', [
11
+ zod_1.z.object({
12
+ mode: zod_1.z.literal('singleton'),
13
+ capability: zod_1.z.string(),
14
+ addonId: zod_1.z.string(),
15
+ }),
16
+ zod_1.z.object({
17
+ mode: zod_1.z.literal('collection'),
18
+ capability: zod_1.z.string(),
19
+ addonId: zod_1.z.string(),
20
+ enabled: zod_1.z.boolean(),
21
+ }),
22
+ ]);
23
+ // ─── Router ────────────────────────────────────────────────────────────────
24
+ function createCapabilitiesRouter(addonRegistry, configService, loggingService) {
25
+ const logger = loggingService.createLogger('CapabilitiesRouter');
26
+ /** Build enriched provider details from addon metadata */
27
+ function getProviderDetails(addonIds) {
28
+ const allAddons = addonRegistry.listAllAddons();
29
+ return addonIds.map((addonId) => {
30
+ const addon = allAddons.find((a) => a.manifest.id === addonId);
31
+ return {
32
+ addonId,
33
+ displayName: addon?.manifest.name ?? addonId,
34
+ packageName: addon?.manifest.packageName ?? addonId,
35
+ };
36
+ });
37
+ }
38
+ return (0, trpc_middleware_1.trpcRouter)({
39
+ // ─── List all capabilities with enriched metadata ──────────────────
40
+ list: trpc_middleware_1.adminProcedure.query(() => {
41
+ const registry = addonRegistry.getCapabilityRegistry();
42
+ const caps = registry.listCapabilities();
43
+ return caps.map((cap) => ({
44
+ ...cap,
45
+ providerDetails: getProviderDetails(cap.providers),
46
+ isInfra: (0, kernel_1.isInfraCapability)(cap.name),
47
+ }));
48
+ }),
49
+ // ─── Get current preference for a capability ──────────────────────
50
+ getPreference: trpc_middleware_1.adminProcedure.input(zod_1.z.object({ capability: zod_1.z.string() })).query(({ input }) => {
51
+ const registry = addonRegistry.getCapabilityRegistry();
52
+ const mode = registry.getMode(input.capability);
53
+ if (!mode)
54
+ return null;
55
+ if (mode === 'singleton') {
56
+ const addonId = configService.get(`capabilities.singleton.${input.capability}`);
57
+ return {
58
+ capability: input.capability,
59
+ mode: mode,
60
+ preference: addonId ? { addonId } : null,
61
+ };
62
+ }
63
+ // collection
64
+ const raw = configService.get((0, collection_preference_1.collectionPreferenceKey)(input.capability));
65
+ let disabled = [];
66
+ if (raw) {
67
+ try {
68
+ const parsed = JSON.parse(raw);
69
+ disabled = Array.isArray(parsed.disabled) ? parsed.disabled : [];
70
+ }
71
+ catch {
72
+ /* ignore malformed */
73
+ }
74
+ }
75
+ return {
76
+ capability: input.capability,
77
+ mode: mode,
78
+ preference: { disabled },
79
+ };
80
+ }),
81
+ // ─── Set preference (singleton switch or collection toggle) ───────
82
+ setPreference: trpc_middleware_1.adminProcedure.input(setPreferenceInput).mutation(async ({ input }) => {
83
+ const registry = addonRegistry.getCapabilityRegistry();
84
+ if (input.mode === 'singleton') {
85
+ const { capability, addonId } = input;
86
+ const caps = registry.listCapabilities();
87
+ const cap = caps.find((c) => c.name === capability);
88
+ if (!cap)
89
+ throw new server_1.TRPCError({ code: 'NOT_FOUND', message: `Unknown capability: ${capability}` });
90
+ if (cap.mode !== 'singleton')
91
+ throw new server_1.TRPCError({
92
+ code: 'BAD_REQUEST',
93
+ message: `"${capability}" is not a singleton`,
94
+ });
95
+ if (!cap.providers.includes(addonId)) {
96
+ throw new server_1.TRPCError({
97
+ code: 'BAD_REQUEST',
98
+ message: `Provider "${addonId}" is not registered for "${capability}"`,
99
+ });
100
+ }
101
+ const requiresRestart = (0, kernel_1.isInfraCapability)(capability);
102
+ if (!requiresRestart) {
103
+ // Hot-swap at runtime
104
+ await registry.setActiveSingleton(capability, addonId);
105
+ }
106
+ // Persist preference
107
+ configService.set(`capabilities.singleton.${capability}`, addonId);
108
+ logger.info('Singleton preference set', {
109
+ tags: { addonId },
110
+ meta: { capability, requiresRestart },
111
+ });
112
+ return { success: true, requiresRestart };
113
+ }
114
+ // collection toggle
115
+ const { capability, addonId, enabled } = input;
116
+ const caps = registry.listCapabilities();
117
+ const cap = caps.find((c) => c.name === capability);
118
+ if (!cap)
119
+ throw new server_1.TRPCError({ code: 'NOT_FOUND', message: `Unknown capability: ${capability}` });
120
+ if (cap.mode !== 'collection')
121
+ throw new server_1.TRPCError({ code: 'BAD_REQUEST', message: `"${capability}" is not a collection` });
122
+ if (!cap.providers.includes(addonId)) {
123
+ throw new server_1.TRPCError({
124
+ code: 'BAD_REQUEST',
125
+ message: `Provider "${addonId}" is not registered for "${capability}"`,
126
+ });
127
+ }
128
+ if (enabled) {
129
+ registry.enableCollectionProvider(capability, addonId);
130
+ }
131
+ else {
132
+ registry.disableCollectionProvider(capability, addonId);
133
+ }
134
+ // Persist disabled list via the shared canonical writer.
135
+ const updatedCap = registry.listCapabilities().find((c) => c.name === capability);
136
+ (0, collection_preference_1.persistCollectionDisabled)(configService, capability, updatedCap?.disabledProviders ?? []);
137
+ logger.info('Collection provider toggled', {
138
+ tags: { addonId },
139
+ meta: { capability, enabled },
140
+ });
141
+ return { success: true, requiresRestart: false };
142
+ }),
143
+ // ─── Reset preference to default ──────────────────────────────────
144
+ resetPreference: trpc_middleware_1.adminProcedure
145
+ .input(zod_1.z.object({ capability: zod_1.z.string() }))
146
+ .mutation(({ input }) => {
147
+ const registry = addonRegistry.getCapabilityRegistry();
148
+ const mode = registry.getMode(input.capability);
149
+ if (!mode)
150
+ throw new server_1.TRPCError({
151
+ code: 'NOT_FOUND',
152
+ message: `Unknown capability: ${input.capability}`,
153
+ });
154
+ if (mode === 'singleton') {
155
+ configService.set(`capabilities.singleton.${input.capability}`, null);
156
+ logger.info('Singleton preference reset (takes effect on restart)', {
157
+ meta: { capability: input.capability },
158
+ });
159
+ return { success: true, requiresRestart: true };
160
+ }
161
+ // collection: re-enable all disabled providers
162
+ const caps = registry.listCapabilities();
163
+ const cap = caps.find((c) => c.name === input.capability);
164
+ if (cap) {
165
+ for (const addonId of cap.disabledProviders) {
166
+ registry.enableCollectionProvider(input.capability, addonId);
167
+ }
168
+ }
169
+ configService.set(`capabilities.collection.${input.capability}`, null);
170
+ logger.info('Collection preference reset (all providers re-enabled)', {
171
+ meta: { capability: input.capability },
172
+ });
173
+ return { success: true, requiresRestart: false };
174
+ }),
175
+ // ─── Per-device overrides (existing, unchanged) ───────────────────
176
+ listCapabilities: trpc_middleware_1.adminProcedure.query(() => {
177
+ const registry = addonRegistry.getCapabilityRegistry();
178
+ return registry.listCapabilities();
179
+ }),
180
+ setDeviceCapability: trpc_middleware_1.adminProcedure
181
+ .input(zod_1.z.object({ deviceId: zod_1.z.string(), capability: zod_1.z.string(), addonId: zod_1.z.string() }))
182
+ .mutation(({ input }) => {
183
+ const registry = addonRegistry.getCapabilityRegistry();
184
+ registry.setDeviceOverride(input.deviceId, input.capability, input.addonId);
185
+ logger.info('Device capability override set', {
186
+ tags: { deviceId: Number(input.deviceId), addonId: input.addonId },
187
+ meta: { capability: input.capability },
188
+ });
189
+ }),
190
+ clearDeviceCapability: trpc_middleware_1.adminProcedure
191
+ .input(zod_1.z.object({ deviceId: zod_1.z.string(), capability: zod_1.z.string() }))
192
+ .mutation(({ input }) => {
193
+ const registry = addonRegistry.getCapabilityRegistry();
194
+ registry.clearDeviceOverride(input.deviceId, input.capability);
195
+ logger.info('Device capability override cleared', {
196
+ tags: { deviceId: Number(input.deviceId) },
197
+ meta: { capability: input.capability },
198
+ });
199
+ }),
200
+ getDeviceCapabilities: trpc_middleware_1.adminProcedure
201
+ .input(zod_1.z.object({ deviceId: zod_1.z.string() }))
202
+ .output(zod_1.z.record(zod_1.z.string(), zod_1.z.string()))
203
+ .query(({ input }) => {
204
+ const registry = addonRegistry.getCapabilityRegistry();
205
+ const overrides = registry.getDeviceOverrides(input.deviceId);
206
+ return Object.fromEntries(overrides);
207
+ }),
208
+ setDeviceCollectionFilter: trpc_middleware_1.adminProcedure
209
+ .input(zod_1.z.object({ deviceId: zod_1.z.string(), capability: zod_1.z.string(), addonIds: zod_1.z.array(zod_1.z.string()) }))
210
+ .mutation(({ input }) => {
211
+ const registry = addonRegistry.getCapabilityRegistry();
212
+ registry.setDeviceCollectionFilter(input.deviceId, input.capability, input.addonIds);
213
+ logger.info('Device collection filter set', {
214
+ tags: { deviceId: Number(input.deviceId) },
215
+ meta: { capability: input.capability, addonIds: input.addonIds },
216
+ });
217
+ }),
218
+ clearDeviceCollectionFilter: trpc_middleware_1.adminProcedure
219
+ .input(zod_1.z.object({ deviceId: zod_1.z.string(), capability: zod_1.z.string() }))
220
+ .mutation(({ input }) => {
221
+ const registry = addonRegistry.getCapabilityRegistry();
222
+ registry.clearDeviceCollectionFilter(input.deviceId, input.capability);
223
+ logger.info('Device collection filter cleared', {
224
+ tags: { deviceId: Number(input.deviceId) },
225
+ meta: { capability: input.capability },
226
+ });
227
+ }),
228
+ });
229
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAddonSettingsRouter = createAddonSettingsRouter;
4
+ /**
5
+ * Addon settings router — raw DB proxy for the common settings API.
6
+ *
7
+ * Exposes four protected procedures consumed by:
8
+ * 1. Forked addons (via the tRPC WSS client in `WorkerBootstrapService`)
9
+ * to read/write their 3-level settings chain from the worker process.
10
+ * 2. Future UI flows that want to inspect/mutate addon settings through
11
+ * a single well-typed endpoint.
12
+ *
13
+ * The router is deliberately thin — it does NOT perform schema-based
14
+ * resolver merging (defaults → global → per-device). That happens on the
15
+ * consumer side where the addon's `ConfigUISchema` is available:
16
+ * - In-process addons: handled by `SettingsResolverService.createView()`
17
+ * wired into `AddonContext.settings` during `createAddonContext()`.
18
+ * - Forked addons: handled by the `AddonSettingsView` constructed inside
19
+ * `WorkerBootstrapService`, which has access to the worker's local
20
+ * addon schema.
21
+ *
22
+ * Introduced in session 5 Sprint 3a (worker-bootstrap cap-aware wiring).
23
+ */
24
+ const zod_1 = require("zod");
25
+ const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
26
+ const AddonSettingsRecordSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.unknown());
27
+ const AddonIdInputSchema = zod_1.z.object({
28
+ addonId: zod_1.z.string(),
29
+ });
30
+ const AddonDeviceInputSchema = zod_1.z.object({
31
+ addonId: zod_1.z.string(),
32
+ deviceId: zod_1.z.string(),
33
+ });
34
+ const UpdateGlobalInputSchema = zod_1.z.object({
35
+ addonId: zod_1.z.string(),
36
+ field: zod_1.z.string(),
37
+ value: zod_1.z.unknown(),
38
+ });
39
+ const UpdateDeviceInputSchema = zod_1.z.object({
40
+ addonId: zod_1.z.string(),
41
+ deviceId: zod_1.z.string(),
42
+ field: zod_1.z.string(),
43
+ value: zod_1.z.unknown(),
44
+ });
45
+ const SuccessSchema = zod_1.z.object({ success: zod_1.z.literal(true) });
46
+ const ReplaceGlobalInputSchema = zod_1.z.object({
47
+ addonId: zod_1.z.string(),
48
+ config: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
49
+ });
50
+ function createAddonSettingsRouter(cfg) {
51
+ return (0, trpc_middleware_js_1.trpcRouter)({
52
+ /**
53
+ * Read the addon-global settings record for the given addon.
54
+ * Returns the raw stored values (no defaults, no device overrides).
55
+ */
56
+ getGlobal: trpc_middleware_js_1.protectedProcedure
57
+ .input(AddonIdInputSchema)
58
+ .output(AddonSettingsRecordSchema)
59
+ .query(({ input }) => cfg.getAddonConfig(input.addonId)),
60
+ /**
61
+ * Read the per-device override record for the given addon × device.
62
+ * Returns the raw stored values (schema filtering happens on the
63
+ * consumer side at merge time).
64
+ */
65
+ getDeviceOverrides: trpc_middleware_js_1.protectedProcedure
66
+ .input(AddonDeviceInputSchema)
67
+ .output(AddonSettingsRecordSchema)
68
+ .query(({ input }) => cfg.getAddonDevice(input.addonId, input.deviceId)),
69
+ /**
70
+ * Update a single field in the addon-global settings record.
71
+ * Reads the current record, merges the new value, and writes back
72
+ * via `setAddonConfig` (bulk replace). Intended for small per-field
73
+ * writes from addon code; bulk updates should use a dedicated admin
74
+ * endpoint (not exposed here).
75
+ */
76
+ updateGlobal: trpc_middleware_js_1.protectedProcedure
77
+ .input(UpdateGlobalInputSchema)
78
+ .output(SuccessSchema)
79
+ .mutation(({ input }) => {
80
+ const current = cfg.getAddonConfig(input.addonId);
81
+ cfg.setAddonConfig(input.addonId, { ...current, [input.field]: input.value });
82
+ return { success: true };
83
+ }),
84
+ /**
85
+ * Update a single field in the per-device override record for the
86
+ * given addon × device. Merges with the existing overrides and
87
+ * writes back via `setAddonDevice`. Scope enforcement (dropping
88
+ * fields not declared as `scope: 'device'`) is the consumer's
89
+ * responsibility — we preserve the raw shape at this layer so the
90
+ * resolver contract remains symmetric with `getDeviceOverrides`.
91
+ */
92
+ updateDevice: trpc_middleware_js_1.protectedProcedure
93
+ .input(UpdateDeviceInputSchema)
94
+ .output(SuccessSchema)
95
+ .mutation(({ input }) => {
96
+ const current = cfg.getAddonDevice(input.addonId, input.deviceId);
97
+ cfg.setAddonDevice(input.addonId, input.deviceId, {
98
+ ...current,
99
+ [input.field]: input.value,
100
+ });
101
+ return { success: true };
102
+ }),
103
+ /**
104
+ * Replace the entire addon-global settings record in one call.
105
+ * Used by forked workers for `context.config.setAll()`. Unlike
106
+ * `updateGlobal` (single-field merge), this overwrites the full record.
107
+ * Admin-level write: only workers with valid hub tokens can call this.
108
+ */
109
+ replaceGlobal: trpc_middleware_js_1.protectedProcedure
110
+ .input(ReplaceGlobalInputSchema)
111
+ .output(SuccessSchema)
112
+ .mutation(({ input }) => {
113
+ cfg.setAddonConfig(input.addonId, input.config);
114
+ return { success: true };
115
+ }),
116
+ });
117
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAgentsRouter = createAgentsRouter;
4
+ /**
5
+ * Agents router — fixed core API (not a capability).
6
+ *
7
+ * Thin binding over AgentRegistryService which now delegates to Moleculer
8
+ * for node discovery and health. Only role-assignment management and
9
+ * node listing remain; protocol endpoints (register, heartbeat, task
10
+ * dispatch) are handled natively by the Moleculer service mesh.
11
+ */
12
+ const zod_1 = require("zod");
13
+ const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
14
+ const AgentRoleSchema = zod_1.z.enum(['decoder', 'transcoder', 'detector', 'recorder']);
15
+ function createAgentsRouter(ar, moleculer) {
16
+ return (0, trpc_middleware_js_1.trpcRouter)({
17
+ // ── Node listing (replaces listAgents / listConnected) ────────────
18
+ listNodes: trpc_middleware_js_1.adminProcedure.input(zod_1.z.void()).query(async () => {
19
+ const items = await ar.listNodes();
20
+ // Spread to mutable copies: AgentListItem uses readonly arrays; Zod output schema uses mutable.
21
+ return items.map((a) => ({
22
+ ...a,
23
+ info: {
24
+ ...a.info,
25
+ capabilities: [...a.info.capabilities],
26
+ },
27
+ status: {
28
+ ...a.status,
29
+ fps: { ...a.status.fps },
30
+ errors: [...a.status.errors],
31
+ },
32
+ subProcesses: [...a.subProcesses],
33
+ }));
34
+ }),
35
+ // ── Capability discovery (via Moleculer service list) ─────────────
36
+ getAgentCapabilities: trpc_middleware_js_1.adminProcedure.input(zod_1.z.void()).query(async () => {
37
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access -- moleculer.broker.call typing chain unresolvable; runtime shape validated by the cast below
38
+ const services = (await moleculer.broker.call('$node.services', {}));
39
+ const capSet = new Set();
40
+ for (const svc of services) {
41
+ if (svc.name.startsWith('$'))
42
+ continue;
43
+ capSet.add(svc.name);
44
+ }
45
+ return [...capSet].toSorted();
46
+ }),
47
+ // ── Role assignments ──────────────────────────────────────────────
48
+ getAssignments: trpc_middleware_js_1.adminProcedure
49
+ .input(zod_1.z.object({ cameraId: zod_1.z.number().optional() }))
50
+ .query(({ input }) => ar.getAssignments(input.cameraId)),
51
+ setAssignment: trpc_middleware_js_1.adminProcedure
52
+ .input(zod_1.z.object({
53
+ cameraId: zod_1.z.number(),
54
+ role: AgentRoleSchema,
55
+ agentId: zod_1.z.string(),
56
+ priority: zod_1.z.enum(['primary', 'backup', 'overflow']),
57
+ rtspUrl: zod_1.z.string().optional(),
58
+ }))
59
+ .mutation(({ input }) => ar.setAssignment(input)),
60
+ removeAssignment: trpc_middleware_js_1.adminProcedure
61
+ .input(zod_1.z.object({
62
+ cameraId: zod_1.z.number(),
63
+ role: AgentRoleSchema,
64
+ }))
65
+ .mutation(({ input }) => ar.removeAssignment(input.cameraId, input.role)),
66
+ activateBackup: trpc_middleware_js_1.adminProcedure
67
+ .input(zod_1.z.object({
68
+ cameraId: zod_1.z.number(),
69
+ role: AgentRoleSchema,
70
+ }))
71
+ .mutation(({ input }) => ar.activateBackup(input.cameraId, input.role)),
72
+ });
73
+ }