@camstack/server 1.2.25 → 1.2.27

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.
@@ -322,6 +322,11 @@ async function startAgent(configPath) {
322
322
  const agentCapabilities = [
323
323
  types_2.storageCapability,
324
324
  types_2.storageProviderCapability,
325
+ // The path browser for "add a storage location on this node" — the agent
326
+ // registers the provider since day one, but without DECLARING the cap
327
+ // here the hub's manifest never learns of it and node-routed browse
328
+ // answers "not available on node X" (2026-07-30 fix).
329
+ types_2.filesystemBrowseCapability,
325
330
  types_2.settingsStoreCapability,
326
331
  types_2.logDestinationCapability,
327
332
  types_2.metricsProviderCapability,
@@ -21,6 +21,7 @@
21
21
  */
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.COLLECTION_ARRAY_METHODS = void 0;
24
+ exports.aggregateCollectionCall = aggregateCollectionCall;
24
25
  exports.createCapRouterPrimitives = createCapRouterPrimitives;
25
26
  exports.createCapRouterServices = createCapRouterServices;
26
27
  exports.buildRuntimeCapRouters = buildRuntimeCapRouters;
@@ -86,6 +87,38 @@ function resolveCollectionProvider(reg, capName, addonId) {
86
87
  }
87
88
  return aggregate;
88
89
  }
90
+ /**
91
+ * The collection UNION for a cap call arriving OUTSIDE the tRPC router — a
92
+ * forked addon's `ctx.api.<cap>.<method>` reaching the parent's
93
+ * `onUnownedCall`. Returns `null` when `(capName, method)` is not an
94
+ * aggregated collection array-method, so the caller routes normally.
95
+ *
96
+ * Exists because routing resolves ONE provider while this router fans array
97
+ * methods across ALL of them: the two answers disagreed, and for
98
+ * `notification-output` the routed one was empty — which the Notification
99
+ * Center read as "target deleted" and turned into permanent dead-letters for
100
+ * every notification it ever produced (2026-07-30). Sharing
101
+ * {@link resolveCollectionProvider} keeps the two paths from drifting again.
102
+ *
103
+ * `null` on zero providers too: that is the empty-collection fast-path's job
104
+ * (it answers `[]` without routing), not this one's.
105
+ */
106
+ async function aggregateCollectionCall(reg, capName, method, args) {
107
+ if (!reg)
108
+ return null;
109
+ if (reg.getDefinition(capName)?.mode !== 'collection')
110
+ return null;
111
+ if (!exports.COLLECTION_ARRAY_METHODS.get(capName)?.includes(method))
112
+ return null;
113
+ if (reg.getCollectionEntries(capName).length === 0)
114
+ return null;
115
+ const aggregate = resolveCollectionProvider(reg, capName, undefined);
116
+ const fn = aggregate?.[method];
117
+ if (typeof fn !== 'function')
118
+ return null;
119
+ const out = await fn(args);
120
+ return Array.isArray(out) ? out : null;
121
+ }
89
122
  /**
90
123
  * Fan an array-returning method across every provider and flatten the
91
124
  * results — the runtime equivalent of `concatCollection` for the
@@ -4043,6 +4043,22 @@ function createCapRouter_localNetwork(getProvider, createRemoteProxy) {
4043
4043
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4044
4044
  return p.getConnectionEndpoints(methodInput);
4045
4045
  }),
4046
+ getNotificationEndpoint: trpc_middleware_js_1.protectedProcedure
4047
+ .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
4048
+ .output(types_54.localNetworkCapability.methods.getNotificationEndpoint.output)
4049
+ .query(async ({ input, ctx }) => {
4050
+ const p = resolveProvider('local-network', input?.nodeId, () => getProvider(ctx), createRemoteProxy);
4051
+ return p.getNotificationEndpoint();
4052
+ }),
4053
+ setNotificationEndpoint: trpc_middleware_js_1.protectedProcedure
4054
+ .input(types_54.localNetworkCapability.methods.setNotificationEndpoint.input.loose())
4055
+ .output(types_54.localNetworkCapability.methods.setNotificationEndpoint.output)
4056
+ .mutation(async ({ input, ctx }) => {
4057
+ const { nodeId, ...methodInput } = input;
4058
+ const p = resolveProvider('local-network', nodeId, () => getProvider(ctx), createRemoteProxy);
4059
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4060
+ return p.setNotificationEndpoint(methodInput);
4061
+ }),
4046
4062
  getAllowedAddresses: trpc_middleware_js_1.protectedProcedure
4047
4063
  .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
4048
4064
  .output(types_54.localNetworkCapability.methods.getAllowedAddresses.output)
@@ -5387,6 +5403,33 @@ function createCapRouter_pipelineAnalytics(getProvider, createRemoteProxy) {
5387
5403
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5388
5404
  return p.deleteDeviceEvents(methodInput);
5389
5405
  }),
5406
+ relocateMedia: trpc_middleware_js_1.adminProcedure
5407
+ .input(types_78.pipelineAnalyticsCapability.methods.relocateMedia.input.loose())
5408
+ .output(types_78.pipelineAnalyticsCapability.methods.relocateMedia.output)
5409
+ .mutation(async ({ input, ctx }) => {
5410
+ const { nodeId, ...methodInput } = input;
5411
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5412
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5413
+ return p.relocateMedia(methodInput);
5414
+ }),
5415
+ getMediaRelocateStatus: trpc_middleware_js_1.adminProcedure
5416
+ .input(types_78.pipelineAnalyticsCapability.methods.getMediaRelocateStatus.input.loose())
5417
+ .output(types_78.pipelineAnalyticsCapability.methods.getMediaRelocateStatus.output)
5418
+ .query(async ({ input, ctx }) => {
5419
+ const { nodeId, ...methodInput } = input;
5420
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5421
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5422
+ return p.getMediaRelocateStatus(methodInput);
5423
+ }),
5424
+ cancelMediaRelocate: trpc_middleware_js_1.adminProcedure
5425
+ .input(types_78.pipelineAnalyticsCapability.methods.cancelMediaRelocate.input.loose())
5426
+ .output(types_78.pipelineAnalyticsCapability.methods.cancelMediaRelocate.output)
5427
+ .mutation(async ({ input, ctx }) => {
5428
+ const { nodeId, ...methodInput } = input;
5429
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5430
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5431
+ return p.cancelMediaRelocate(methodInput);
5432
+ }),
5390
5433
  listOpsLog: trpc_middleware_js_1.adminProcedure
5391
5434
  .input(types_78.pipelineAnalyticsCapability.methods.listOpsLog.input.loose())
5392
5435
  .output(types_78.pipelineAnalyticsCapability.methods.listOpsLog.output)
@@ -6725,6 +6768,51 @@ function createCapRouter_recording(getProvider, createRemoteProxy) {
6725
6768
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6726
6769
  return p.listOpsLog(methodInput);
6727
6770
  }),
6771
+ renderGif: trpc_middleware_js_1.adminProcedure
6772
+ .input(types_88.recordingCapability.methods.renderGif.input.loose())
6773
+ .output(types_88.recordingCapability.methods.renderGif.output)
6774
+ .mutation(async ({ input, ctx }) => {
6775
+ const { nodeId, ...methodInput } = input;
6776
+ const p = resolveProvider('recording', nodeId, () => getProvider(ctx), createRemoteProxy);
6777
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6778
+ return p.renderGif(methodInput);
6779
+ }),
6780
+ renderClip: trpc_middleware_js_1.adminProcedure
6781
+ .input(types_88.recordingCapability.methods.renderClip.input.loose())
6782
+ .output(types_88.recordingCapability.methods.renderClip.output)
6783
+ .mutation(async ({ input, ctx }) => {
6784
+ const { nodeId, ...methodInput } = input;
6785
+ const p = resolveProvider('recording', nodeId, () => getProvider(ctx), createRemoteProxy);
6786
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6787
+ return p.renderClip(methodInput);
6788
+ }),
6789
+ relocateFootage: trpc_middleware_js_1.adminProcedure
6790
+ .input(types_88.recordingCapability.methods.relocateFootage.input.loose())
6791
+ .output(types_88.recordingCapability.methods.relocateFootage.output)
6792
+ .mutation(async ({ input, ctx }) => {
6793
+ const { nodeId, ...methodInput } = input;
6794
+ const p = resolveProvider('recording', nodeId, () => getProvider(ctx), createRemoteProxy);
6795
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6796
+ return p.relocateFootage(methodInput);
6797
+ }),
6798
+ getRelocateStatus: trpc_middleware_js_1.adminProcedure
6799
+ .input(types_88.recordingCapability.methods.getRelocateStatus.input.loose())
6800
+ .output(types_88.recordingCapability.methods.getRelocateStatus.output)
6801
+ .query(async ({ input, ctx }) => {
6802
+ const { nodeId, ...methodInput } = input;
6803
+ const p = resolveProvider('recording', nodeId, () => getProvider(ctx), createRemoteProxy);
6804
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6805
+ return p.getRelocateStatus(methodInput);
6806
+ }),
6807
+ cancelRelocate: trpc_middleware_js_1.adminProcedure
6808
+ .input(types_88.recordingCapability.methods.cancelRelocate.input.loose())
6809
+ .output(types_88.recordingCapability.methods.cancelRelocate.output)
6810
+ .mutation(async ({ input, ctx }) => {
6811
+ const { nodeId, ...methodInput } = input;
6812
+ const p = resolveProvider('recording', nodeId, () => getProvider(ctx), createRemoteProxy);
6813
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6814
+ return p.cancelRelocate(methodInput);
6815
+ }),
6728
6816
  });
6729
6817
  }
6730
6818
  function createCapRouter_recordingExport(getProvider, createRemoteProxy) {
@@ -7587,6 +7675,15 @@ function createCapRouter_streamBroker(getProvider, createRemoteProxy) {
7587
7675
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
7588
7676
  return p.unassignProfile(methodInput);
7589
7677
  }),
7678
+ renderPreBufferClip: trpc_middleware_js_1.adminProcedure
7679
+ .input(types_100.streamBrokerCapability.methods.renderPreBufferClip.input.loose())
7680
+ .output(types_100.streamBrokerCapability.methods.renderPreBufferClip.output)
7681
+ .mutation(async ({ input, ctx }) => {
7682
+ const { nodeId, ...methodInput } = input;
7683
+ const p = resolveProvider('stream-broker', nodeId, () => getProvider(ctx), createRemoteProxy);
7684
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
7685
+ return p.renderPreBufferClip(methodInput);
7686
+ }),
7590
7687
  listAllCameraStreams: trpc_middleware_js_1.protectedProcedure
7591
7688
  .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
7592
7689
  .output(types_100.streamBrokerCapability.methods.listAllCameraStreams.output)
@@ -2266,7 +2266,9 @@ class AddonRegistryService {
2266
2266
  const provider = this.capabilityRegistry.getProviderByAddon('log-destination', addonId);
2267
2267
  if (!provider)
2268
2268
  return;
2269
- this.loggingService.addDestination(provider);
2269
+ // Tagged with the providing addon so the fan-out can honour the
2270
+ // operator's Enable/Disable toggle for THIS destination.
2271
+ this.loggingService.addDestination(provider, { addonId });
2270
2272
  this.logger.info('Log destination added', { meta: { phase: 'v2' } });
2271
2273
  break;
2272
2274
  }
@@ -2711,6 +2713,9 @@ class AddonRegistryService {
2711
2713
  // deviceId index. The per-device `getBindings` resolver prefers this
2712
2714
  // over filtering the whole-cluster flat view.
2713
2715
  listClusterNativeCapsForDevice: (deviceId) => this.moleculer.listClusterNativeCapsForDevice(deviceId),
2716
+ // Node-routed cap proxy — lets hub builtins (storage-orchestrator)
2717
+ // dispatch provider work to the node that OWNS the data.
2718
+ resolveNodeCapability: (capName, nodeId) => this.moleculer.createCapabilityProxy(capName, nodeId),
2714
2719
  },
2715
2720
  registerProvider,
2716
2721
  resolveProvider: (capName) => {
@@ -290,6 +290,13 @@ class MoleculerService {
290
290
  return null;
291
291
  return [];
292
292
  },
293
+ // Collection FAN-OUT for the same child→parent path: a collection
294
+ // array-method answers with the union across every provider, exactly
295
+ // as this server's tRPC cap-router does (shared code — the two must
296
+ // never disagree again). Routing alone picks ONE provider, which is
297
+ // how the Notification Center saw an empty `notification-output`
298
+ // catalog and dead-lettered every notification (2026-07-30).
299
+ callCollectionAggregate: (capName, method, args) => (0, cap_router_runtime_js_1.aggregateCollectionCall)(this.capabilityService.getRegistry(), capName, method, args),
293
300
  // Hub-core `$`-service discriminant for the general
294
301
  // provider-not-yet-registered recovery. A resolver miss on a NON-core
295
302
  // cap (any addon-provided system/singleton cap, e.g.
@@ -322,6 +329,24 @@ class MoleculerService {
322
329
  // onUnownedCall (a pin to ANOTHER node still bypasses the sibling).
323
330
  ownNodeId: nodeId,
324
331
  onUnownedCall,
332
+ // A collection ARRAY method must reach `onUnownedCall` (which fans it
333
+ // across every provider) instead of being short-circuited to ONE local
334
+ // sibling. Same predicate the aggregate itself uses, so the two agree
335
+ // by construction.
336
+ isAggregatedCollectionMethod: (capName, method) => {
337
+ const reg = this.capabilityService.getRegistry();
338
+ if (!reg)
339
+ return false;
340
+ if (reg.getDefinition(capName)?.mode !== 'collection')
341
+ return false;
342
+ return cap_router_runtime_js_1.COLLECTION_ARRAY_METHODS.get(capName)?.includes(method) === true;
343
+ },
344
+ // Bound every UDS cap call by the cap's own declared timeout, falling
345
+ // back to the channel default. Same resolver the REMOTE plane uses
346
+ // below, so a method that survives 16 minutes across nodes survives it
347
+ // across a socket too — and one that hangs is cut off on both.
348
+ capTimeoutMs: (capName, method) => this.capabilityService.getRegistry()?.getDefinition(capName)?.methods?.[method]
349
+ ?.timeoutMs,
325
350
  logger: {
326
351
  info: (msg, meta) => logger.info(msg, meta !== null && meta !== undefined
327
352
  ? { meta: meta }
@@ -114,6 +114,11 @@ async function bootManual(opts) {
114
114
  // very first DeviceRegistered event populates the cache before any
115
115
  // log line needing `deviceName` is emitted.
116
116
  loggingService.attachDeviceNameStream(eventBusService);
117
+ // The orchestrator's per-destination Enable/Disable toggle becomes the ONE
118
+ // truth for every log destination — it used to be write-only (nothing read
119
+ // the disabled set), so console/winston could not be turned off at all while
120
+ // Loki appeared to work only through a second, private `enabled` setting.
121
+ loggingService.setDestinationEnabledCheck((addonId) => capabilityService.getRegistry()?.isProviderDisabled('log-destination', addonId) !== true);
117
122
  const featureService = new feature_service_1.FeatureService(configService);
118
123
  const authService = new auth_service_1.AuthService(configService);
119
124
  const streamProbeService = new stream_probe_service_1.StreamProbeService(loggingService);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.25",
3
+ "version": "1.2.27",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -33,19 +33,19 @@
33
33
  ]
34
34
  },
35
35
  "dependencies": {
36
- "@camstack/addon-admin-ui": "1.2.16",
36
+ "@camstack/addon-admin-ui": "1.2.17",
37
37
  "@camstack/addon-agent-ui": "1.2.6",
38
38
  "@camstack/addon-auth": "1.2.6",
39
39
  "@camstack/addon-decoder-nodeav": "1.2.6",
40
- "@camstack/addon-notifiers": "1.2.7",
41
- "@camstack/addon-pipeline": "1.2.19",
40
+ "@camstack/addon-notifiers": "1.2.8",
41
+ "@camstack/addon-pipeline": "1.2.28",
42
42
  "@camstack/addon-pipeline-orchestrator": "1.2.11",
43
- "@camstack/addon-post-analysis": "1.2.17",
43
+ "@camstack/addon-post-analysis": "1.2.18",
44
44
  "@camstack/sdk": "1.2.6",
45
45
  "@camstack/shm-ring": "1.1.6",
46
- "@camstack/system": "1.2.22",
47
- "@camstack/types": "1.2.16",
48
- "@camstack/ui-library": "1.2.12",
46
+ "@camstack/system": "1.2.24",
47
+ "@camstack/types": "1.2.17",
48
+ "@camstack/ui-library": "1.2.13",
49
49
  "@fastify/compress": "^9.0.0",
50
50
  "@fastify/cookie": "^11.0.2",
51
51
  "@fastify/cors": "^11.2.0",