@camstack/types 1.2.18 → 1.2.20

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/dist/addon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-DETsT9rJ.js");
2
+ const require_sleep = require("./sleep-mkNQLVPE.js");
3
3
  const require_event_category = require("./event-category-39bpf1r_.js");
4
4
  const require_err_msg = require("./err-msg-COpsHMw2.js");
5
5
  //#region src/generated/collection-array-methods.ts
package/dist/addon.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as parseJsonUnknown, Ct as DisposerChain, D as asString, F as ReadinessRegistry, I as ReadinessTimeoutError, M as nodePin, N as readNodePin, P as DATAPLANE_SECRET_HEADER, S as DeviceType, T as asJsonObject, a as viewerUiCapability, dt as BaseAddon, ft as normalizeAddonInitResult, ht as emitReadiness, i as deviceOpsCapability, m as expandCapMethods, o as adminUiCapability, s as createDeviceProxy, t as sleep, z as scopeKey } from "./sleep-B_Uaqbai.mjs";
1
+ import { A as parseJsonUnknown, Ct as DisposerChain, D as asString, F as ReadinessRegistry, I as ReadinessTimeoutError, M as nodePin, N as readNodePin, P as DATAPLANE_SECRET_HEADER, S as DeviceType, T as asJsonObject, a as viewerUiCapability, dt as BaseAddon, ft as normalizeAddonInitResult, ht as emitReadiness, i as deviceOpsCapability, m as expandCapMethods, o as adminUiCapability, s as createDeviceProxy, t as sleep, z as scopeKey } from "./sleep-BYyv9gUM.mjs";
2
2
  import { t as EventCategory } from "./event-category-Bz24uP1U.mjs";
3
3
  import { t as errMsg } from "./err-msg-IQTHeDzc.mjs";
4
4
  //#region src/generated/collection-array-methods.ts
@@ -634,8 +634,8 @@ export declare const addonsCapability: {
634
634
  finishedAtMs: z.ZodNullable<z.ZodNumber>;
635
635
  }, z.core.$strip>>;
636
636
  state: z.ZodEnum<{
637
- running: "running";
638
637
  failed: "failed";
638
+ running: "running";
639
639
  completed: "completed";
640
640
  "partially-failed": "partially-failed";
641
641
  cancelled: "cancelled";
@@ -710,8 +710,8 @@ export declare const addonsCapability: {
710
710
  finishedAtMs: z.ZodNullable<z.ZodNumber>;
711
711
  }, z.core.$strip>>;
712
712
  state: z.ZodEnum<{
713
- running: "running";
714
713
  failed: "failed";
714
+ running: "running";
715
715
  completed: "completed";
716
716
  "partially-failed": "partially-failed";
717
717
  cancelled: "cancelled";
@@ -50,9 +50,9 @@ export type ManagedRuntimeConfig = z.infer<typeof ManagedRuntimeConfigSchema>;
50
50
  export declare const LlmRuntimeStatusSchema: z.ZodObject<{
51
51
  nodeId: z.ZodString;
52
52
  state: z.ZodEnum<{
53
- ready: "ready";
54
53
  failed: "failed";
55
54
  stopped: "stopped";
55
+ ready: "ready";
56
56
  starting: "starting";
57
57
  downloading: "downloading";
58
58
  crashed: "crashed";
@@ -216,9 +216,9 @@ export declare const llmRuntimeCapability: {
216
216
  }, z.core.$strip>, z.ZodObject<{
217
217
  nodeId: z.ZodString;
218
218
  state: z.ZodEnum<{
219
- ready: "ready";
220
219
  failed: "failed";
221
220
  stopped: "stopped";
221
+ ready: "ready";
222
222
  starting: "starting";
223
223
  downloading: "downloading";
224
224
  crashed: "crashed";
@@ -237,9 +237,9 @@ export declare const llmRuntimeCapability: {
237
237
  readonly status: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{}, z.core.$strip>, z.ZodObject<{
238
238
  nodeId: z.ZodString;
239
239
  state: z.ZodEnum<{
240
- ready: "ready";
241
240
  failed: "failed";
242
241
  stopped: "stopped";
242
+ ready: "ready";
243
243
  starting: "starting";
244
244
  downloading: "downloading";
245
245
  crashed: "crashed";
@@ -139,9 +139,9 @@ export declare const LlmRuntimeNodeSchema: z.ZodObject<{
139
139
  status: z.ZodOptional<z.ZodObject<{
140
140
  nodeId: z.ZodString;
141
141
  state: z.ZodEnum<{
142
- ready: "ready";
143
142
  failed: "failed";
144
143
  stopped: "stopped";
144
+ ready: "ready";
145
145
  starting: "starting";
146
146
  downloading: "downloading";
147
147
  crashed: "crashed";
@@ -491,9 +491,9 @@ export declare const llmCapability: {
491
491
  status: z.ZodOptional<z.ZodObject<{
492
492
  nodeId: z.ZodString;
493
493
  state: z.ZodEnum<{
494
- ready: "ready";
495
494
  failed: "failed";
496
495
  stopped: "stopped";
496
+ ready: "ready";
497
497
  starting: "starting";
498
498
  downloading: "downloading";
499
499
  crashed: "crashed";
@@ -547,9 +547,9 @@ export declare const llmCapability: {
547
547
  }, z.core.$strip>, z.ZodObject<{
548
548
  nodeId: z.ZodString;
549
549
  state: z.ZodEnum<{
550
- ready: "ready";
551
550
  failed: "failed";
552
551
  stopped: "stopped";
552
+ ready: "ready";
553
553
  starting: "starting";
554
554
  downloading: "downloading";
555
555
  crashed: "crashed";
@@ -570,9 +570,9 @@ export declare const llmCapability: {
570
570
  }, z.core.$strip>, z.ZodObject<{
571
571
  nodeId: z.ZodString;
572
572
  state: z.ZodEnum<{
573
- ready: "ready";
574
573
  failed: "failed";
575
574
  stopped: "stopped";
575
+ ready: "ready";
576
576
  starting: "starting";
577
577
  downloading: "downloading";
578
578
  crashed: "crashed";
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { type InferProvider } from './capability-definition.js';
3
2
  import { DeviceType } from '../device/device-type.js';
3
+ import { type InferProvider } from './capability-definition.js';
4
4
  /**
5
5
  * Hardware / firmware motion sensor cap — binary detected state plus
6
6
  * a timestamp of the last observation. Distinct from
@@ -49,6 +49,18 @@ export declare const motionCapability: {
49
49
  readonly name: "motion";
50
50
  readonly scope: "device";
51
51
  readonly mode: "singleton";
52
+ /**
53
+ * Providers register per-device natives via `ctx.registerNativeCap`
54
+ * (Hikvision/Reolink/Amcrest/Wyze/HA/Homematic/Alexa/Matter) — there is
55
+ * NO system singleton provider. Without this flag `resolveCapMount`
56
+ * derived `{ kind: 'singleton' }`, so `motion.getStatus`/`isDetected`
57
+ * resolved via `registry.getSingleton('motion')` (always null) and every
58
+ * call 412'd "provider not available" while bindings listed a live
59
+ * `motion` native (2026-08-02). The flag routes the router through
60
+ * `requireDeviceScoped` → `getProviderForDevice`, like `motion-trigger`,
61
+ * `snapshot` and every other per-device native cap.
62
+ */
63
+ readonly deviceNative: true;
52
64
  readonly deviceTypes: readonly [DeviceType.Camera, DeviceType.Sensor];
53
65
  readonly methods: {
54
66
  /**
@@ -1296,9 +1296,9 @@ export declare const pipelineAnalyticsCapability: {
1296
1296
  readonly getMediaRelocateStatus: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{}, z.core.$strip>, z.ZodReadonly<z.ZodArray<z.ZodObject<{
1297
1297
  jobId: z.ZodString;
1298
1298
  state: z.ZodEnum<{
1299
- running: "running";
1300
1299
  done: "done";
1301
1300
  failed: "failed";
1301
+ running: "running";
1302
1302
  cancelled: "cancelled";
1303
1303
  }>;
1304
1304
  fromLocationId: z.ZodString;
@@ -381,9 +381,9 @@ declare const EngineProvisioningSchema: z.ZodObject<{
381
381
  }>>;
382
382
  device: z.ZodNullable<z.ZodString>;
383
383
  state: z.ZodEnum<{
384
- ready: "ready";
385
384
  failed: "failed";
386
385
  idle: "idle";
386
+ ready: "ready";
387
387
  installing: "installing";
388
388
  verifying: "verifying";
389
389
  }>;
@@ -530,9 +530,9 @@ export declare const pipelineExecutorCapability: {
530
530
  }>>;
531
531
  device: z.ZodNullable<z.ZodString>;
532
532
  state: z.ZodEnum<{
533
- ready: "ready";
534
533
  failed: "failed";
535
534
  idle: "idle";
535
+ ready: "ready";
536
536
  installing: "installing";
537
537
  verifying: "verifying";
538
538
  }>;
@@ -265,18 +265,18 @@ declare const CameraMotionStatusSchema: z.ZodObject<{
265
265
  }, z.core.$strip>;
266
266
  /** Detection engine provisioning state (mirrors pipeline-executor provisioning states). */
267
267
  declare const CameraDetectionProvisioningStateSchema: z.ZodEnum<{
268
- ready: "ready";
269
268
  failed: "failed";
270
269
  idle: "idle";
270
+ ready: "ready";
271
271
  installing: "installing";
272
272
  verifying: "verifying";
273
273
  }>;
274
274
  /** Detection provisioning sub-block. */
275
275
  declare const CameraDetectionProvisioningSchema: z.ZodObject<{
276
276
  state: z.ZodEnum<{
277
- ready: "ready";
278
277
  failed: "failed";
279
278
  idle: "idle";
279
+ ready: "ready";
280
280
  installing: "installing";
281
281
  verifying: "verifying";
282
282
  }>;
@@ -306,9 +306,9 @@ declare const CameraDetectionStatusSchema: z.ZodObject<{
306
306
  avgInferenceMs: z.ZodNumber;
307
307
  provisioning: z.ZodObject<{
308
308
  state: z.ZodEnum<{
309
- ready: "ready";
310
309
  failed: "failed";
311
310
  idle: "idle";
311
+ ready: "ready";
312
312
  installing: "installing";
313
313
  verifying: "verifying";
314
314
  }>;
@@ -419,9 +419,9 @@ declare const CameraStatusSchema: z.ZodObject<{
419
419
  avgInferenceMs: z.ZodNumber;
420
420
  provisioning: z.ZodObject<{
421
421
  state: z.ZodEnum<{
422
- ready: "ready";
423
422
  failed: "failed";
424
423
  idle: "idle";
424
+ ready: "ready";
425
425
  installing: "installing";
426
426
  verifying: "verifying";
427
427
  }>;
@@ -1149,9 +1149,9 @@ export declare const pipelineOrchestratorCapability: {
1149
1149
  avgInferenceMs: z.ZodNumber;
1150
1150
  provisioning: z.ZodObject<{
1151
1151
  state: z.ZodEnum<{
1152
- ready: "ready";
1153
1152
  failed: "failed";
1154
1153
  idle: "idle";
1154
+ ready: "ready";
1155
1155
  installing: "installing";
1156
1156
  verifying: "verifying";
1157
1157
  }>;
@@ -1259,9 +1259,9 @@ export declare const pipelineOrchestratorCapability: {
1259
1259
  avgInferenceMs: z.ZodNumber;
1260
1260
  provisioning: z.ZodObject<{
1261
1261
  state: z.ZodEnum<{
1262
- ready: "ready";
1263
1262
  failed: "failed";
1264
1263
  idle: "idle";
1264
+ ready: "ready";
1265
1265
  installing: "installing";
1266
1266
  verifying: "verifying";
1267
1267
  }>;
@@ -40,9 +40,9 @@ export declare const ExportOptionsSchema: z.ZodObject<{
40
40
  }, z.core.$strip>;
41
41
  export type ExportOptions = z.infer<typeof ExportOptionsSchema>;
42
42
  export declare const ExportStateSchema: z.ZodEnum<{
43
- ready: "ready";
44
43
  queued: "queued";
45
44
  failed: "failed";
45
+ ready: "ready";
46
46
  deleted: "deleted";
47
47
  rendering: "rendering";
48
48
  expired: "expired";
@@ -67,9 +67,9 @@ export declare const ExportRecordSchema: z.ZodObject<{
67
67
  title: z.ZodOptional<z.ZodString>;
68
68
  }, z.core.$strip>;
69
69
  state: z.ZodEnum<{
70
- ready: "ready";
71
70
  queued: "queued";
72
71
  failed: "failed";
72
+ ready: "ready";
73
73
  deleted: "deleted";
74
74
  rendering: "rendering";
75
75
  expired: "expired";
@@ -131,9 +131,9 @@ export declare const recordingExportCapability: {
131
131
  title: z.ZodOptional<z.ZodString>;
132
132
  }, z.core.$strip>;
133
133
  state: z.ZodEnum<{
134
- ready: "ready";
135
134
  queued: "queued";
136
135
  failed: "failed";
136
+ ready: "ready";
137
137
  deleted: "deleted";
138
138
  rendering: "rendering";
139
139
  expired: "expired";
@@ -168,9 +168,9 @@ export declare const recordingExportCapability: {
168
168
  title: z.ZodOptional<z.ZodString>;
169
169
  }, z.core.$strip>;
170
170
  state: z.ZodEnum<{
171
- ready: "ready";
172
171
  queued: "queued";
173
172
  failed: "failed";
173
+ ready: "ready";
174
174
  deleted: "deleted";
175
175
  rendering: "rendering";
176
176
  expired: "expired";
@@ -204,9 +204,9 @@ export declare const recordingExportCapability: {
204
204
  title: z.ZodOptional<z.ZodString>;
205
205
  }, z.core.$strip>;
206
206
  state: z.ZodEnum<{
207
- ready: "ready";
208
207
  queued: "queued";
209
208
  failed: "failed";
209
+ ready: "ready";
210
210
  deleted: "deleted";
211
211
  rendering: "rendering";
212
212
  expired: "expired";
@@ -241,9 +241,9 @@ export declare const recordingExportCapability: {
241
241
  title: z.ZodOptional<z.ZodString>;
242
242
  }, z.core.$strip>;
243
243
  state: z.ZodEnum<{
244
- ready: "ready";
245
244
  queued: "queued";
246
245
  failed: "failed";
246
+ ready: "ready";
247
247
  deleted: "deleted";
248
248
  rendering: "rendering";
249
249
  expired: "expired";
@@ -278,9 +278,9 @@ export declare const recordingExportCapability: {
278
278
  title: z.ZodOptional<z.ZodString>;
279
279
  }, z.core.$strip>;
280
280
  state: z.ZodEnum<{
281
- ready: "ready";
282
281
  queued: "queued";
283
282
  failed: "failed";
283
+ ready: "ready";
284
284
  deleted: "deleted";
285
285
  rendering: "rendering";
286
286
  expired: "expired";
@@ -478,9 +478,9 @@ export declare const recordingCapability: {
478
478
  readonly getRelocateStatus: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{}, z.core.$strip>, z.ZodReadonly<z.ZodArray<z.ZodObject<{
479
479
  jobId: z.ZodString;
480
480
  state: z.ZodEnum<{
481
- running: "running";
482
481
  done: "done";
483
482
  failed: "failed";
483
+ running: "running";
484
484
  cancelled: "cancelled";
485
485
  }>;
486
486
  fromLocationId: z.ZodString;
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export * from './types/config.js';
8
8
  export * from './types/entities.js';
9
9
  export * from './types/models.js';
10
10
  export * from './types/model-variant-groups.js';
11
- export type { EngineOutput, LetterboxMeta, ModelInputMeta } from './types/engine-output.js';
11
+ export type { EngineOutput } from './types/engine-output.js';
12
12
  export * from './types/pipeline.js';
13
13
  export * from './types/pipeline-step.js';
14
14
  export type { CameraPipelineConfig, PipelineTemplate as CameraPipelineTemplate, } from './types/camera-pipeline.js';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-DETsT9rJ.js");
2
+ const require_sleep = require("./sleep-mkNQLVPE.js");
3
3
  const require_event_category = require("./event-category-39bpf1r_.js");
4
4
  const require_enums = require("./enums.js");
5
5
  const require_err_msg = require("./err-msg-COpsHMw2.js");
@@ -10463,6 +10463,18 @@ var motionCapability = {
10463
10463
  name: "motion",
10464
10464
  scope: "device",
10465
10465
  mode: "singleton",
10466
+ /**
10467
+ * Providers register per-device natives via `ctx.registerNativeCap`
10468
+ * (Hikvision/Reolink/Amcrest/Wyze/HA/Homematic/Alexa/Matter) — there is
10469
+ * NO system singleton provider. Without this flag `resolveCapMount`
10470
+ * derived `{ kind: 'singleton' }`, so `motion.getStatus`/`isDetected`
10471
+ * resolved via `registry.getSingleton('motion')` (always null) and every
10472
+ * call 412'd "provider not available" while bindings listed a live
10473
+ * `motion` native (2026-08-02). The flag routes the router through
10474
+ * `requireDeviceScoped` → `getProviderForDevice`, like `motion-trigger`,
10475
+ * `snapshot` and every other per-device native cap.
10476
+ */
10477
+ deviceNative: true,
10466
10478
  deviceTypes: [require_sleep.DeviceType.Camera, require_sleep.DeviceType.Sensor],
10467
10479
  methods: {
10468
10480
  /**
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as ProfileSlotSchema, A as parseJsonUnknown, B as BrokerStatsSchema, C as asBoolean, Ct as DisposerChain, D as asString, E as asNumber, F as ReadinessRegistry, G as CamStreamResolutionSchema, H as CAM_PROFILE_ORDER, I as ReadinessTimeoutError, J as DecodedFrameSchema, K as CameraStreamSchema, L as emitDownForOwnedCaps, M as nodePin, N as readNodePin, O as parseJsonArray, P as DATAPLANE_SECRET_HEADER, Q as ProfileRtspEntrySchema, R as readinessKey, S as DeviceType, St as resolveHydratedFieldValue, T as asJsonObject, U as CamProfileSchema, V as BrokerStatusSchema, W as CamStreamKindSchema, X as FrameHandleFormatSchema, Y as EncodedPacketSchema, Z as FrameHandleSchema, _ as resolveCapMount, _t as WELL_KNOWN_TABS, a as viewerUiCapability, at as SubscribeFramesInputSchema, b as DeviceFeature, bt as collectHydratedFieldValues, c as createLazyTrpcSource, ct as makeSourceBrokerId, d as DEVICE_SETTINGS_CONTRIBUTION_METHODS, dt as BaseAddon, et as ProfileSlotStatusSchema, f as DEVICE_STATUS_METHOD, ft as normalizeAddonInitResult, g as method, gt as isEvent, h as isDeviceConfigCap, ht as emitReadiness, i as deviceOpsCapability, it as SubscribeAudioChunksResultSchema, j as CAP_NODE_PIN_CONTEXT_KEY, k as parseJsonObject, l as createMirrorSource, lt as parseProfileBrokerId, m as expandCapMethods, mt as createEvent, n as sleepCancellable, nt as StreamSourceSchema, o as adminUiCapability, ot as SubscribeFramesResultSchema, p as event, pt as createDurableState, q as DecodedAudioChunkSchema, r as RawStateResultSchema, rt as SubscribeAudioChunksInputSchema, s as createDeviceProxy, st as makeProfileBrokerId, t as sleep, tt as StreamSourceEntrySchema, u as createSliceHandle, ut as selectAssignedProfileSlots, v as systemMethod, vt as WELL_KNOWN_TAB_MAP, w as asJsonArray, x as DeviceRole, xt as hydrateSchema, y as ChargingStatus, yt as collectHydratedFieldEntries, z as scopeKey } from "./sleep-B_Uaqbai.mjs";
1
+ import { $ as ProfileSlotSchema, A as parseJsonUnknown, B as BrokerStatsSchema, C as asBoolean, Ct as DisposerChain, D as asString, E as asNumber, F as ReadinessRegistry, G as CamStreamResolutionSchema, H as CAM_PROFILE_ORDER, I as ReadinessTimeoutError, J as DecodedFrameSchema, K as CameraStreamSchema, L as emitDownForOwnedCaps, M as nodePin, N as readNodePin, O as parseJsonArray, P as DATAPLANE_SECRET_HEADER, Q as ProfileRtspEntrySchema, R as readinessKey, S as DeviceType, St as resolveHydratedFieldValue, T as asJsonObject, U as CamProfileSchema, V as BrokerStatusSchema, W as CamStreamKindSchema, X as FrameHandleFormatSchema, Y as EncodedPacketSchema, Z as FrameHandleSchema, _ as resolveCapMount, _t as WELL_KNOWN_TABS, a as viewerUiCapability, at as SubscribeFramesInputSchema, b as DeviceFeature, bt as collectHydratedFieldValues, c as createLazyTrpcSource, ct as makeSourceBrokerId, d as DEVICE_SETTINGS_CONTRIBUTION_METHODS, dt as BaseAddon, et as ProfileSlotStatusSchema, f as DEVICE_STATUS_METHOD, ft as normalizeAddonInitResult, g as method, gt as isEvent, h as isDeviceConfigCap, ht as emitReadiness, i as deviceOpsCapability, it as SubscribeAudioChunksResultSchema, j as CAP_NODE_PIN_CONTEXT_KEY, k as parseJsonObject, l as createMirrorSource, lt as parseProfileBrokerId, m as expandCapMethods, mt as createEvent, n as sleepCancellable, nt as StreamSourceSchema, o as adminUiCapability, ot as SubscribeFramesResultSchema, p as event, pt as createDurableState, q as DecodedAudioChunkSchema, r as RawStateResultSchema, rt as SubscribeAudioChunksInputSchema, s as createDeviceProxy, st as makeProfileBrokerId, t as sleep, tt as StreamSourceEntrySchema, u as createSliceHandle, ut as selectAssignedProfileSlots, v as systemMethod, vt as WELL_KNOWN_TAB_MAP, w as asJsonArray, x as DeviceRole, xt as hydrateSchema, y as ChargingStatus, yt as collectHydratedFieldEntries, z as scopeKey } from "./sleep-BYyv9gUM.mjs";
2
2
  import { t as EventCategory } from "./event-category-Bz24uP1U.mjs";
3
3
  import { EventSourceType } from "./enums.mjs";
4
4
  import { t as errMsg } from "./err-msg-IQTHeDzc.mjs";
@@ -10462,6 +10462,18 @@ var motionCapability = {
10462
10462
  name: "motion",
10463
10463
  scope: "device",
10464
10464
  mode: "singleton",
10465
+ /**
10466
+ * Providers register per-device natives via `ctx.registerNativeCap`
10467
+ * (Hikvision/Reolink/Amcrest/Wyze/HA/Homematic/Alexa/Matter) — there is
10468
+ * NO system singleton provider. Without this flag `resolveCapMount`
10469
+ * derived `{ kind: 'singleton' }`, so `motion.getStatus`/`isDetected`
10470
+ * resolved via `registry.getSingleton('motion')` (always null) and every
10471
+ * call 412'd "provider not available" while bindings listed a live
10472
+ * `motion` native (2026-08-02). The flag routes the router through
10473
+ * `requireDeviceScoped` → `getProviderForDevice`, like `motion-trigger`,
10474
+ * `snapshot` and every other per-device native cap.
10475
+ */
10476
+ deviceNative: true,
10465
10477
  deviceTypes: [DeviceType.Camera, DeviceType.Sensor],
10466
10478
  methods: {
10467
10479
  /**
@@ -851,7 +851,11 @@ export interface AddonDeclaration {
851
851
  readonly id: string;
852
852
  /** JS entry point relative to package root (e.g., "./dist/addon.js") */
853
853
  readonly entry?: string;
854
- /** Human-readable name */
854
+ /**
855
+ * Human-readable name — the label every addon list renders. Say what the
856
+ * addon IS ("Admin UI", "Recorder"); no "CamStack " prefix, it carries no
857
+ * information inside a CamStack install and eats list width.
858
+ */
855
859
  readonly name?: string;
856
860
  /** Semver version */
857
861
  readonly version?: string;
@@ -1010,7 +1014,11 @@ export interface AddonPythonRequirements {
1010
1014
  readonly requirements: string;
1011
1015
  }
1012
1016
  export interface AddonPackageManifest {
1013
- /** Human-readable package name for UI display (e.g., "CamStack Vision") */
1017
+ /**
1018
+ * Human-readable package name for UI display (e.g., "Pipeline", "Core").
1019
+ * No "CamStack " prefix — inside a CamStack install it carries no
1020
+ * information and only eats the width of every list that renders it.
1021
+ */
1014
1022
  readonly displayName?: string;
1015
1023
  /**
1016
1024
  * Optional 1-2 sentence description shown on the package card.
@@ -68,8 +68,12 @@ export interface IReadinessRegistry<TCapName extends string = string> {
68
68
  * authoritative for are skipped. For each applied entry a one-shot
69
69
  * transition is dispatched to matching subscriptions so pending
70
70
  * `awaitReady` callers unblock without waiting for a fresh delta.
71
+ *
72
+ * Returns the number of records applied (added or advanced) — `0`
73
+ * means the snapshot matched local state and callers doing periodic
74
+ * reconciles should stay silent about it.
71
75
  */
72
- hydrate(records: readonly IReadinessRegistryRecord[]): void;
76
+ hydrate(records: readonly IReadinessRegistryRecord[]): number;
73
77
  /**
74
78
  * Serializable snapshot for cross-process transport. Returned as an
75
79
  * array (MsgPack-friendly) rather than the internal Map.
@@ -10,18 +10,18 @@ import { z } from 'zod';
10
10
  * row on the owning addon's surface.
11
11
  */
12
12
  export declare const RelocateJobStateSchema: z.ZodEnum<{
13
- running: "running";
14
13
  done: "done";
15
14
  failed: "failed";
15
+ running: "running";
16
16
  cancelled: "cancelled";
17
17
  }>;
18
18
  export type RelocateJobState = z.infer<typeof RelocateJobStateSchema>;
19
19
  export declare const RelocateJobSchema: z.ZodObject<{
20
20
  jobId: z.ZodString;
21
21
  state: z.ZodEnum<{
22
- running: "running";
23
22
  done: "done";
24
23
  failed: "failed";
24
+ running: "running";
25
25
  cancelled: "cancelled";
26
26
  }>;
27
27
  fromLocationId: z.ZodString;
@@ -91,8 +91,8 @@ export declare const lifecycleTaskSchema: z.ZodObject<{
91
91
  }, z.core.$strip>;
92
92
  export type LifecycleTask = z.infer<typeof lifecycleTaskSchema>;
93
93
  export declare const lifecycleJobStateSchema: z.ZodEnum<{
94
- running: "running";
95
94
  failed: "failed";
95
+ running: "running";
96
96
  completed: "completed";
97
97
  "partially-failed": "partially-failed";
98
98
  cancelled: "cancelled";
@@ -166,8 +166,8 @@ export declare const lifecycleJobSchema: z.ZodObject<{
166
166
  finishedAtMs: z.ZodNullable<z.ZodNumber>;
167
167
  }, z.core.$strip>>;
168
168
  state: z.ZodEnum<{
169
- running: "running";
170
169
  failed: "failed";
170
+ running: "running";
171
171
  completed: "completed";
172
172
  "partially-failed": "partially-failed";
173
173
  cancelled: "cancelled";
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import type { IEventBus, ReadinessScope, ReadinessState } from '../interfaces/event-bus.js';
25
25
  import type { IScopedLogger } from '../interfaces/logging.js';
26
- import type { IReadinessRegistry, IReadinessHandler, IAwaitReadyOptions, IReadinessTransition } from '../interfaces/readiness.js';
26
+ import type { IAwaitReadyOptions, IReadinessHandler, IReadinessRegistry, IReadinessTransition } from '../interfaces/readiness.js';
27
27
  /**
28
28
  * Concrete types live here; `interfaces/readiness.ts` declares the
29
29
  * matching narrow interfaces (`IReadinessTransition`,
@@ -74,6 +74,21 @@ export interface ReadinessRegistryOptions {
74
74
  * Clock source. Injectable for tests.
75
75
  */
76
76
  readonly now?: () => number;
77
+ /**
78
+ * Opt-in per-record debug logging for DEVICE-scoped transitions.
79
+ *
80
+ * Default `false` — and that default is load-bearing. Device-scoped
81
+ * readiness is O(caps × devices) (observed live: 776 pairs for one
82
+ * producer generation), and EVERY process runs a registry whose logs
83
+ * forward to the hub `LogManager`. With per-record logging on, one hub
84
+ * container boot produced 21 577 identical-shape debug lines in a
85
+ * single minute (776 pairs × ~28 processes, 2026-08-01 15:04Z) — noise
86
+ * that buried the lines that mattered while the operator was asking
87
+ * why the viewer stalled. Node/global-scoped transitions stay logged
88
+ * per-record unconditionally: they are low-cardinality and every
89
+ * readiness investigation so far has needed them.
90
+ */
91
+ readonly verboseDeviceScopeLogs?: boolean;
77
92
  }
78
93
  export declare class ReadinessRegistry implements IReadinessRegistry {
79
94
  private readonly bus;
@@ -81,6 +96,7 @@ export declare class ReadinessRegistry implements IReadinessRegistry {
81
96
  private readonly logger;
82
97
  private readonly now;
83
98
  private readonly generation;
99
+ private readonly verboseDeviceScopeLogs;
84
100
  private readonly snapshot;
85
101
  private readonly subscriptions;
86
102
  private readonly unsubscribeBus;
@@ -124,8 +140,22 @@ export declare class ReadinessRegistry implements IReadinessRegistry {
124
140
  * Every applied entry dispatches a one-shot transition to matching
125
141
  * subscriptions so pending `awaitReady` callers unblock without having
126
142
  * to wait for a fresh event.
143
+ *
144
+ * Returns the number of records APPLIED (added or advanced). Callers
145
+ * doing periodic reconciles use this to stay silent when the snapshot
146
+ * changed nothing — the every-60s "hydrated 1446 record(s)" heartbeat
147
+ * from every child process was pure noise (≈2 800 lines/hour observed
148
+ * live on 2026-08-01).
149
+ */
150
+ hydrate(records: readonly ReadinessRecord[]): number;
151
+ /**
152
+ * Per-record debug line, volume-gated: device-scoped records are logged
153
+ * only when `verboseDeviceScopeLogs` is set (see the option's doc for
154
+ * the 21 577-lines-per-boot-minute incident that forced the gate);
155
+ * node/global records always log. Device lines carry `tags.deviceId`
156
+ * so they group per-camera (CLAUDE.md logging rule).
127
157
  */
128
- hydrate(records: readonly ReadinessRecord[]): void;
158
+ private logPerRecord;
129
159
  /**
130
160
  * True when THIS process is the origin authority for `record` — a
131
161
  * snapshot pulled from elsewhere must never overwrite it:
@@ -1513,6 +1513,7 @@ var ReadinessRegistry = class {
1513
1513
  logger;
1514
1514
  now;
1515
1515
  generation;
1516
+ verboseDeviceScopeLogs;
1516
1517
  snapshot = /* @__PURE__ */ new Map();
1517
1518
  subscriptions = /* @__PURE__ */ new Set();
1518
1519
  unsubscribeBus;
@@ -1523,6 +1524,7 @@ var ReadinessRegistry = class {
1523
1524
  this.logger = options.logger;
1524
1525
  this.now = options.now ?? (() => Date.now());
1525
1526
  this.generation = options.generation ?? randomGeneration();
1527
+ this.verboseDeviceScopeLogs = options.verboseDeviceScopeLogs ?? false;
1526
1528
  this.unsubscribeBus = this.bus.subscribe({ category: "system.ready-state" }, (event) => this.ingest(event.data));
1527
1529
  this.unsubscribeAgentOffline = this.bus.subscribe({ category: "agent.offline" }, (event) => this.synthesizeDownForNode(event.data.agentId));
1528
1530
  if (typeof this.bus.getRecent === "function") try {
@@ -1576,9 +1578,16 @@ var ReadinessRegistry = class {
1576
1578
  * Every applied entry dispatches a one-shot transition to matching
1577
1579
  * subscriptions so pending `awaitReady` callers unblock without having
1578
1580
  * to wait for a fresh event.
1581
+ *
1582
+ * Returns the number of records APPLIED (added or advanced). Callers
1583
+ * doing periodic reconciles use this to stay silent when the snapshot
1584
+ * changed nothing — the every-60s "hydrated 1446 record(s)" heartbeat
1585
+ * from every child process was pure noise (≈2 800 lines/hour observed
1586
+ * live on 2026-08-01).
1579
1587
  */
1580
1588
  hydrate(records) {
1581
1589
  const now = this.now();
1590
+ let applied = 0;
1582
1591
  for (const record of records) {
1583
1592
  const key = readinessKey(record.capName, record.scope);
1584
1593
  const prev = this.snapshot.get(key) ?? null;
@@ -1604,7 +1613,8 @@ var ReadinessRegistry = class {
1604
1613
  sourceNodeId: record.sourceNodeId
1605
1614
  };
1606
1615
  this.snapshot.set(key, hydrated);
1607
- if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated${prev !== null ? " update" : ""}, gen=${record.generation.slice(0, 6)})`);
1616
+ applied++;
1617
+ this.logPerRecord(record.scope, `readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated${prev !== null ? " update" : ""}, gen=${record.generation.slice(0, 6)})`);
1608
1618
  const transition = {
1609
1619
  capName: record.capName,
1610
1620
  scope: record.scope,
@@ -1625,6 +1635,23 @@ var ReadinessRegistry = class {
1625
1635
  }
1626
1636
  }
1627
1637
  }
1638
+ return applied;
1639
+ }
1640
+ /**
1641
+ * Per-record debug line, volume-gated: device-scoped records are logged
1642
+ * only when `verboseDeviceScopeLogs` is set (see the option's doc for
1643
+ * the 21 577-lines-per-boot-minute incident that forced the gate);
1644
+ * node/global records always log. Device lines carry `tags.deviceId`
1645
+ * so they group per-camera (CLAUDE.md logging rule).
1646
+ */
1647
+ logPerRecord(scope, message) {
1648
+ if (!this.logger) return;
1649
+ if (scope.type === "device") {
1650
+ if (!this.verboseDeviceScopeLogs) return;
1651
+ this.logger.debug(message, { tags: { deviceId: scope.deviceId } });
1652
+ return;
1653
+ }
1654
+ this.logger.debug(message);
1628
1655
  }
1629
1656
  /**
1630
1657
  * True when THIS process is the origin authority for `record` — a
@@ -1799,7 +1826,7 @@ var ReadinessRegistry = class {
1799
1826
  ts: payload.ts,
1800
1827
  durationInPrevState
1801
1828
  };
1802
- if (this.logger) this.logger.debug(`readiness: ${payload.capName} (${scopeKey(payload.scope)}) → ${payload.state} epoch=${epoch} gen=${payload.generation.slice(0, 6)} (prev ${durationInPrevState}ms)`);
1829
+ this.logPerRecord(payload.scope, `readiness: ${payload.capName} (${scopeKey(payload.scope)}) → ${payload.state} epoch=${epoch} gen=${payload.generation.slice(0, 6)} (prev ${durationInPrevState}ms)`);
1803
1830
  for (const sub of this.subscriptions) {
1804
1831
  if (sub.capName !== payload.capName) continue;
1805
1832
  if (!scopesEqual(sub.scope, payload.scope)) continue;
@@ -1844,7 +1871,7 @@ var ReadinessRegistry = class {
1844
1871
  ts: now,
1845
1872
  durationInPrevState
1846
1873
  };
1847
- if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → down (synthesized from agent.offline ${offlineNodeId})`);
1874
+ this.logPerRecord(record.scope, `readiness: ${record.capName} (${scopeKey(record.scope)}) → down (synthesized from agent.offline ${offlineNodeId})`);
1848
1875
  for (const sub of this.subscriptions) {
1849
1876
  if (sub.capName !== record.capName) continue;
1850
1877
  if (!scopesEqual(sub.scope, record.scope)) continue;
@@ -1513,6 +1513,7 @@ var ReadinessRegistry = class {
1513
1513
  logger;
1514
1514
  now;
1515
1515
  generation;
1516
+ verboseDeviceScopeLogs;
1516
1517
  snapshot = /* @__PURE__ */ new Map();
1517
1518
  subscriptions = /* @__PURE__ */ new Set();
1518
1519
  unsubscribeBus;
@@ -1523,6 +1524,7 @@ var ReadinessRegistry = class {
1523
1524
  this.logger = options.logger;
1524
1525
  this.now = options.now ?? (() => Date.now());
1525
1526
  this.generation = options.generation ?? randomGeneration();
1527
+ this.verboseDeviceScopeLogs = options.verboseDeviceScopeLogs ?? false;
1526
1528
  this.unsubscribeBus = this.bus.subscribe({ category: "system.ready-state" }, (event) => this.ingest(event.data));
1527
1529
  this.unsubscribeAgentOffline = this.bus.subscribe({ category: "agent.offline" }, (event) => this.synthesizeDownForNode(event.data.agentId));
1528
1530
  if (typeof this.bus.getRecent === "function") try {
@@ -1576,9 +1578,16 @@ var ReadinessRegistry = class {
1576
1578
  * Every applied entry dispatches a one-shot transition to matching
1577
1579
  * subscriptions so pending `awaitReady` callers unblock without having
1578
1580
  * to wait for a fresh event.
1581
+ *
1582
+ * Returns the number of records APPLIED (added or advanced). Callers
1583
+ * doing periodic reconciles use this to stay silent when the snapshot
1584
+ * changed nothing — the every-60s "hydrated 1446 record(s)" heartbeat
1585
+ * from every child process was pure noise (≈2 800 lines/hour observed
1586
+ * live on 2026-08-01).
1579
1587
  */
1580
1588
  hydrate(records) {
1581
1589
  const now = this.now();
1590
+ let applied = 0;
1582
1591
  for (const record of records) {
1583
1592
  const key = readinessKey(record.capName, record.scope);
1584
1593
  const prev = this.snapshot.get(key) ?? null;
@@ -1604,7 +1613,8 @@ var ReadinessRegistry = class {
1604
1613
  sourceNodeId: record.sourceNodeId
1605
1614
  };
1606
1615
  this.snapshot.set(key, hydrated);
1607
- if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated${prev !== null ? " update" : ""}, gen=${record.generation.slice(0, 6)})`);
1616
+ applied++;
1617
+ this.logPerRecord(record.scope, `readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated${prev !== null ? " update" : ""}, gen=${record.generation.slice(0, 6)})`);
1608
1618
  const transition = {
1609
1619
  capName: record.capName,
1610
1620
  scope: record.scope,
@@ -1625,6 +1635,23 @@ var ReadinessRegistry = class {
1625
1635
  }
1626
1636
  }
1627
1637
  }
1638
+ return applied;
1639
+ }
1640
+ /**
1641
+ * Per-record debug line, volume-gated: device-scoped records are logged
1642
+ * only when `verboseDeviceScopeLogs` is set (see the option's doc for
1643
+ * the 21 577-lines-per-boot-minute incident that forced the gate);
1644
+ * node/global records always log. Device lines carry `tags.deviceId`
1645
+ * so they group per-camera (CLAUDE.md logging rule).
1646
+ */
1647
+ logPerRecord(scope, message) {
1648
+ if (!this.logger) return;
1649
+ if (scope.type === "device") {
1650
+ if (!this.verboseDeviceScopeLogs) return;
1651
+ this.logger.debug(message, { tags: { deviceId: scope.deviceId } });
1652
+ return;
1653
+ }
1654
+ this.logger.debug(message);
1628
1655
  }
1629
1656
  /**
1630
1657
  * True when THIS process is the origin authority for `record` — a
@@ -1799,7 +1826,7 @@ var ReadinessRegistry = class {
1799
1826
  ts: payload.ts,
1800
1827
  durationInPrevState
1801
1828
  };
1802
- if (this.logger) this.logger.debug(`readiness: ${payload.capName} (${scopeKey(payload.scope)}) → ${payload.state} epoch=${epoch} gen=${payload.generation.slice(0, 6)} (prev ${durationInPrevState}ms)`);
1829
+ this.logPerRecord(payload.scope, `readiness: ${payload.capName} (${scopeKey(payload.scope)}) → ${payload.state} epoch=${epoch} gen=${payload.generation.slice(0, 6)} (prev ${durationInPrevState}ms)`);
1803
1830
  for (const sub of this.subscriptions) {
1804
1831
  if (sub.capName !== payload.capName) continue;
1805
1832
  if (!scopesEqual(sub.scope, payload.scope)) continue;
@@ -1844,7 +1871,7 @@ var ReadinessRegistry = class {
1844
1871
  ts: now,
1845
1872
  durationInPrevState
1846
1873
  };
1847
- if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → down (synthesized from agent.offline ${offlineNodeId})`);
1874
+ this.logPerRecord(record.scope, `readiness: ${record.capName} (${scopeKey(record.scope)}) → down (synthesized from agent.offline ${offlineNodeId})`);
1848
1875
  for (const sub of this.subscriptions) {
1849
1876
  if (sub.capName !== record.capName) continue;
1850
1877
  if (!scopesEqual(sub.scope, record.scope)) continue;
@@ -1,4 +1,4 @@
1
- import type { BoundingBox as EngineBoundingBox, Landmark, FrameInput } from './io.js';
1
+ import type { BoundingBox as EngineBoundingBox, FrameInput, Landmark } from './io.js';
2
2
  export interface SpatialDetection {
3
3
  readonly class: string;
4
4
  readonly originalClass: string;
@@ -248,6 +248,47 @@ export interface AudioDetection extends DetectionBase {
248
248
  * avoids an import-level collision.
249
249
  */
250
250
  export type DetectionEntry = ObjectDetection | AudioDetection;
251
+ /**
252
+ * Why a POST-NMS box was dropped between the model and the overlay. One code
253
+ * per drop site — the diagnostic overlay shows the code verbatim, so a new
254
+ * drop site MUST add its own code here rather than reuse a neighbour's:
255
+ * - `class-filter`: class not in the step's `enabledMacroClasses`, or a
256
+ * COCO class the `classMap` does not map (both are class-identity drops
257
+ * and both count toward the trace's model-saw/kept gap).
258
+ * - `below-threshold`: score under the per-macro `minConfidence<Macro>`.
259
+ * - `full-frame-guard`: the full-frame phantom guard
260
+ * (`pipeline/full-frame-guard.ts`).
261
+ * - `zone-gate`: dropped by a detection-stage zone rule AFTER the frame
262
+ * result was built (`gateDetectionsByZoneRules`).
263
+ *
264
+ * NMS suppression is NOT a reason here: every engine's NMS runs in-process in
265
+ * the Python pool (`inference_pool.py` postprocessors) and the suppressed set
266
+ * is destroyed before the boundary — the trace's model-saw count is the only
267
+ * NMS visibility. A reason that can never fire does not belong in the union.
268
+ */
269
+ export type DiscardReason = 'class-filter' | 'below-threshold' | 'full-frame-guard' | 'zone-gate';
270
+ /**
271
+ * A post-NMS box the pipeline dropped before it could reach the overlay /
272
+ * tracker / events — always carried on `FrameResult.discarded` when a frame
273
+ * drops at least one box (the discarded set is post-NMS and
274
+ * post-confidence-floor: a handful of boxes per frame, not the raw
275
+ * candidate flood, which never leaves the Python pool).
276
+ *
277
+ * `bbox` is the executor's `[x1, y1, x2, y2]` tuple in ABSOLUTE
278
+ * analysis-frame pixels (the same space the executor's drop sites hold) —
279
+ * consumers normalise against `FrameResult.width`/`height` from the same
280
+ * frame. Deliberately NOT the survivors' `{x, y, width, height}` object:
281
+ * the tuple is what every drop site already holds, and the viewer's
282
+ * `parseDiscardedEvent` accepts it as-is.
283
+ */
284
+ export interface DiscardedDetection {
285
+ /** `[x1, y1, x2, y2]` in absolute analysis-frame pixels. */
286
+ readonly bbox: readonly [number, number, number, number];
287
+ /** Macro class where the classMap resolves one, else the raw model class. */
288
+ readonly macroClass: string;
289
+ readonly score: number;
290
+ readonly reason: DiscardReason;
291
+ }
251
292
  /**
252
293
  * Per-frame pipeline result — the canonical container emitted by
253
294
  * `pipelineExecutor.runPipeline` (one-shot benchmark + runtime dispatch)
@@ -263,6 +304,13 @@ export interface FrameResult {
263
304
  readonly width: number;
264
305
  readonly height: number;
265
306
  readonly detections: readonly ObjectDetection[];
307
+ /**
308
+ * Post-NMS boxes the pipeline dropped on this frame (see
309
+ * {@link DiscardedDetection}). Always populated when at least one box was
310
+ * dropped — absent means "nothing dropped", never "empty array". A frame
311
+ * that drops nothing allocates nothing and puts no field on the wire.
312
+ */
313
+ readonly discarded?: readonly DiscardedDetection[];
266
314
  readonly debug?: FrameDebug;
267
315
  }
268
316
  /**
@@ -1,23 +1,23 @@
1
- export interface LetterboxMeta {
2
- readonly scale: number;
3
- readonly padX: number;
4
- readonly padY: number;
5
- readonly originalWidth: number;
6
- readonly originalHeight: number;
7
- }
1
+ /**
2
+ * Output envelope of an inference engine call.
3
+ *
4
+ * Every engine is a Python pool (`inference_pool.py`) and postprocessing runs
5
+ * in-process there — the pool returns a `structured` StepOutput payload. The
6
+ * former Node.js onnxruntime raw-tensor fields (`tensor` / `tensors` /
7
+ * `letterbox`) were removed with that path: the platform ships no Node ONNX
8
+ * runtime.
9
+ */
8
10
  export interface EngineOutput {
9
- readonly tensor?: Float32Array;
10
- readonly tensors?: Record<string, Float32Array>;
11
11
  readonly structured?: Record<string, unknown>;
12
- readonly letterbox?: LetterboxMeta;
13
12
  readonly inferenceMs: number;
14
- }
15
- export interface ModelInputMeta {
16
- readonly inputSize: {
17
- readonly width: number;
18
- readonly height: number;
19
- };
20
- readonly inputNormalization: 'zero-one' | 'imagenet' | 'none';
21
- readonly inputLayout: 'nchw' | 'nhwc';
22
- readonly preprocessMode: 'letterbox' | 'resize';
13
+ /**
14
+ * Overload shed: the inference pool DELIBERATELY did not run inference for
15
+ * this frame (the Python pool's per-model in-flight bound, or the TS-side
16
+ * worker in-flight cap). A shed output carries NO `structured` payload —
17
+ * consumers skip the frame the way any dropped frame is skipped. It is an
18
+ * expected flow-control outcome, never an error.
19
+ */
20
+ readonly dropped?: true;
21
+ /** Which bound shed the frame (e.g. `'worker-in-flight-cap'`). */
22
+ readonly shedReason?: string;
23
23
  }
@@ -1,7 +1,7 @@
1
- import type { ModelCatalogEntry } from './models.js';
1
+ import type { ConfigField } from '../interfaces/config-ui.js';
2
2
  import type { ClassMapDefinition } from './labels.js';
3
+ import type { ModelCatalogEntry } from './models.js';
3
4
  import type { PipelineSlot } from './pipeline.js';
4
- import type { ConfigField } from '../interfaces/config-ui.js';
5
5
  /**
6
6
  * Each pipeline step implements this interface as a class.
7
7
  * `definition` holds the static metadata; `getConfigSchema()` declares
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.2.18",
3
+ "version": "1.2.20",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "camstack": {
50
50
  "system": false,
51
- "displayName": "CamStack Types",
51
+ "displayName": "Types",
52
52
  "description": "Shared cap definitions, schemas, and runtime contracts. Required at server boot; updating restarts the hub."
53
53
  },
54
54
  "files": [