@camstack/server 0.2.2 → 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.
- package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
- package/dist/api/addon-upload.js +441 -0
- package/dist/api/addons-custom.router.js +91 -0
- package/dist/api/auth-whoami.js +55 -0
- package/dist/api/bridge-addons.router.js +109 -0
- package/dist/api/capabilities.router.js +229 -0
- package/dist/api/core/addon-settings.router.js +117 -0
- package/dist/api/core/agents.router.js +73 -0
- package/dist/api/core/auth.router.js +286 -0
- package/dist/api/core/bulk-update-coordinator.js +229 -0
- package/dist/api/core/cap-providers.js +1124 -0
- package/dist/api/core/capabilities.router.js +138 -0
- package/dist/api/core/collection-preference.js +17 -0
- package/dist/api/core/event-bus-proxy.router.js +45 -0
- package/dist/api/core/hwaccel.router.js +91 -0
- package/dist/api/core/live-events.router.js +61 -0
- package/dist/api/core/logs.router.js +172 -0
- package/dist/api/core/notifications.router.js +67 -0
- package/dist/api/core/repl.router.js +35 -0
- package/dist/api/core/settings-backend.router.js +121 -0
- package/dist/api/core/stream-probe.router.js +58 -0
- package/dist/api/core/system-events.router.js +100 -0
- package/dist/api/health/health.routes.js +68 -0
- package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
- package/dist/api/oauth2/oauth2-routes.js +219 -0
- package/dist/api/trpc/cap-mount-helpers.js +194 -0
- package/dist/api/trpc/cap-route-error-formatter.js +133 -0
- package/dist/api/trpc/client-ip.js +147 -0
- package/dist/api/trpc/core-cap-bridge.js +115 -0
- package/dist/api/trpc/generated-cap-mounts.js +388 -0
- package/dist/api/trpc/generated-cap-routers.js +7635 -0
- package/dist/api/trpc/scope-access.js +93 -0
- package/dist/api/trpc/trpc.context.js +184 -0
- package/dist/api/trpc/trpc.middleware.js +139 -0
- package/dist/api/trpc/trpc.router.js +188 -0
- package/dist/auth/session-cookie.js +47 -0
- package/dist/boot/boot-config.js +241 -0
- package/dist/boot/integration-id-backfill.js +76 -0
- package/dist/boot/post-boot.service.js +85 -0
- package/dist/core/addon/addon-call-gateway.js +99 -0
- package/dist/core/addon/addon-package.service.js +1560 -0
- package/dist/core/addon/addon-registry.service.js +2739 -0
- package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
- package/dist/core/addon/addon-search.service.js +62 -0
- package/dist/core/addon/addon-settings-provider.js +102 -0
- package/dist/core/addon/addon.tokens.js +5 -0
- package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
- package/dist/core/addon-pages/addon-pages.service.js +107 -0
- package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
- package/dist/core/agent/agent-registry.service.js +477 -0
- package/dist/core/auth/auth.service.js +10 -0
- package/dist/core/capability/capability.service.js +58 -0
- package/dist/core/config/config.schema.js +7 -0
- package/dist/core/config/config.service.js +10 -0
- package/dist/core/events/event-bus.service.js +83 -0
- package/dist/core/feature/feature.service.js +10 -0
- package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
- package/dist/core/logging/log-ring-buffer.js +6 -0
- package/dist/core/logging/logging.service.js +130 -0
- package/dist/core/logging/scoped-logger.js +6 -0
- package/dist/core/moleculer/cap-call-fn.js +50 -0
- package/dist/core/moleculer/cap-route-authority.js +122 -0
- package/dist/core/moleculer/moleculer.service.js +898 -0
- package/dist/core/network/network-quality.service.js +7 -0
- package/dist/core/notification/notification-wrapper.service.js +33 -0
- package/dist/core/notification/toast-wrapper.service.js +25 -0
- package/dist/core/provider/provider.tokens.js +4 -0
- package/dist/core/repl/repl-engine.service.js +140 -0
- package/dist/core/storage/fs-storage-backend.js +6 -0
- package/dist/core/storage/storage-location-manager.js +6 -0
- package/dist/core/storage/storage.service.js +7 -0
- package/dist/core/streaming/stream-probe.service.js +209 -0
- package/dist/core/topology/topology-emitter.service.js +106 -0
- package/dist/launcher.js +325 -0
- package/dist/main.js +1098 -0
- package/dist/manual-boot.js +227 -0
- package/package.json +5 -1
- package/src/__tests__/addon-install-e2e.test.ts +0 -74
- package/src/__tests__/addon-pages-e2e.test.ts +0 -200
- package/src/__tests__/addon-route-session.test.ts +0 -17
- package/src/__tests__/addon-settings-router.spec.ts +0 -67
- package/src/__tests__/addon-upload.spec.ts +0 -475
- package/src/__tests__/agent-registry.spec.ts +0 -179
- package/src/__tests__/agent-status-page.spec.ts +0 -82
- package/src/__tests__/auth-session-cookie.test.ts +0 -48
- package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
- package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
- package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
- package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
- package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
- package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
- package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
- package/src/__tests__/cap-route-adapter.spec.ts +0 -302
- package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
- package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
- package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
- package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
- package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
- package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
- package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
- package/src/__tests__/cap-routers/harness.ts +0 -163
- package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
- package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
- package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
- package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
- package/src/__tests__/capability-e2e.test.ts +0 -384
- package/src/__tests__/cli-e2e.test.ts +0 -150
- package/src/__tests__/core-cap-bridge.spec.ts +0 -91
- package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
- package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
- package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
- package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
- package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
- package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
- package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
- package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
- package/src/__tests__/framework-allowlist.spec.ts +0 -96
- package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
- package/src/__tests__/https-e2e.test.ts +0 -124
- package/src/__tests__/lifecycle-e2e.test.ts +0 -189
- package/src/__tests__/live-events-subscription.spec.ts +0 -149
- package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
- package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
- package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
- package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
- package/src/__tests__/native-cap-route.spec.ts +0 -427
- package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
- package/src/__tests__/post-boot-restart.spec.ts +0 -161
- package/src/__tests__/singleton-contention.test.ts +0 -499
- package/src/__tests__/streaming-diagnostic.test.ts +0 -615
- package/src/__tests__/streaming-scale.test.ts +0 -314
- package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
- package/src/__tests__/uds-log-ingest.spec.ts +0 -183
- package/src/api/__tests__/addons-custom.spec.ts +0 -148
- package/src/api/__tests__/capabilities.router.test.ts +0 -56
- package/src/api/addon-upload.ts +0 -529
- package/src/api/addons-custom.router.ts +0 -101
- package/src/api/auth-whoami.ts +0 -101
- package/src/api/bridge-addons.router.ts +0 -122
- package/src/api/capabilities.router.ts +0 -265
- package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
- package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
- package/src/api/core/addon-settings.router.ts +0 -127
- package/src/api/core/agents.router.ts +0 -86
- package/src/api/core/auth.router.ts +0 -322
- package/src/api/core/bulk-update-coordinator.ts +0 -305
- package/src/api/core/cap-providers.ts +0 -1339
- package/src/api/core/capabilities.router.ts +0 -149
- package/src/api/core/collection-preference.ts +0 -40
- package/src/api/core/event-bus-proxy.router.ts +0 -45
- package/src/api/core/hwaccel.router.ts +0 -108
- package/src/api/core/live-events.router.ts +0 -67
- package/src/api/core/logs.router.ts +0 -195
- package/src/api/core/notifications.router.ts +0 -66
- package/src/api/core/repl.router.ts +0 -39
- package/src/api/core/settings-backend.router.ts +0 -140
- package/src/api/core/stream-probe.router.ts +0 -57
- package/src/api/core/system-events.router.ts +0 -125
- package/src/api/health/health.routes.ts +0 -117
- package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
- package/src/api/oauth2/oauth2-routes.ts +0 -281
- package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
- package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
- package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
- package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
- package/src/api/trpc/cap-mount-helpers.ts +0 -245
- package/src/api/trpc/cap-route-error-formatter.ts +0 -171
- package/src/api/trpc/client-ip.ts +0 -147
- package/src/api/trpc/core-cap-bridge.ts +0 -154
- package/src/api/trpc/generated-cap-mounts.ts +0 -1240
- package/src/api/trpc/generated-cap-routers.ts +0 -11523
- package/src/api/trpc/scope-access.ts +0 -110
- package/src/api/trpc/trpc.context.ts +0 -258
- package/src/api/trpc/trpc.middleware.ts +0 -146
- package/src/api/trpc/trpc.router.ts +0 -389
- package/src/auth/session-cookie.ts +0 -54
- package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
- package/src/boot/boot-config.ts +0 -259
- package/src/boot/integration-id-backfill.ts +0 -109
- package/src/boot/post-boot.service.ts +0 -105
- package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
- package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
- package/src/core/addon/addon-call-gateway.ts +0 -171
- package/src/core/addon/addon-package.service.ts +0 -1787
- package/src/core/addon/addon-registry.service.ts +0 -3130
- package/src/core/addon/addon-search.service.ts +0 -91
- package/src/core/addon/addon-settings-provider.ts +0 -220
- package/src/core/addon/addon.tokens.ts +0 -2
- package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
- package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
- package/src/core/addon-pages/addon-pages.service.ts +0 -82
- package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
- package/src/core/agent/agent-registry.service.ts +0 -529
- package/src/core/auth/auth.service.spec.ts +0 -86
- package/src/core/auth/auth.service.ts +0 -8
- package/src/core/capability/capability.service.ts +0 -66
- package/src/core/config/config.schema.ts +0 -3
- package/src/core/config/config.service.spec.ts +0 -175
- package/src/core/config/config.service.ts +0 -7
- package/src/core/events/event-bus.service.spec.ts +0 -235
- package/src/core/events/event-bus.service.ts +0 -89
- package/src/core/feature/feature.service.spec.ts +0 -99
- package/src/core/feature/feature.service.ts +0 -8
- package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
- package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
- package/src/core/logging/log-ring-buffer.ts +0 -3
- package/src/core/logging/logging.service.spec.ts +0 -287
- package/src/core/logging/logging.service.ts +0 -143
- package/src/core/logging/scoped-logger.ts +0 -3
- package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
- package/src/core/moleculer/cap-call-fn.ts +0 -107
- package/src/core/moleculer/cap-route-authority.ts +0 -194
- package/src/core/moleculer/moleculer.service.ts +0 -1072
- package/src/core/network/network-quality.service.spec.ts +0 -53
- package/src/core/network/network-quality.service.ts +0 -5
- package/src/core/notification/notification-wrapper.service.ts +0 -34
- package/src/core/notification/toast-wrapper.service.ts +0 -27
- package/src/core/provider/provider.tokens.ts +0 -1
- package/src/core/repl/repl-engine.service.spec.ts +0 -444
- package/src/core/repl/repl-engine.service.ts +0 -155
- package/src/core/storage/fs-storage-backend.spec.ts +0 -70
- package/src/core/storage/fs-storage-backend.ts +0 -3
- package/src/core/storage/storage-location-manager.spec.ts +0 -130
- package/src/core/storage/storage-location-manager.ts +0 -3
- package/src/core/storage/storage.service.spec.ts +0 -73
- package/src/core/storage/storage.service.ts +0 -3
- package/src/core/streaming/stream-probe.service.ts +0 -221
- package/src/core/topology/topology-emitter.service.ts +0 -105
- package/src/launcher.ts +0 -314
- package/src/main.ts +0 -1245
- package/src/manual-boot.ts +0 -301
- package/tsconfig.build.json +0 -8
- package/tsconfig.json +0 -33
- 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
|
+
}
|