@camstack/addon-export-alexa 1.2.33 → 1.2.34

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.
@@ -5828,6 +5828,13 @@ var BaseAddon = class {
5828
5828
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5829
5829
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5830
5830
  _registeredCapNames = [];
5831
+ /**
5832
+ * True only after `readAddonStore` actually answered. Constructor
5833
+ * defaults look like stored config when the store is down — a forked
5834
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5835
+ * mode, 2026-08-25) is not "the operator chose this".
5836
+ */
5837
+ settingsStoreReady = false;
5831
5838
  /** Default config values. Provided via constructor. */
5832
5839
  defaults;
5833
5840
  constructor(defaults) {
@@ -6228,7 +6235,9 @@ var BaseAddon = class {
6228
6235
  ];
6229
6236
  let lastErr;
6230
6237
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6231
- return await settings.readAddonStore() ?? {};
6238
+ const stored = await settings.readAddonStore() ?? {};
6239
+ this.settingsStoreReady = true;
6240
+ return stored;
6232
6241
  } catch (err) {
6233
6242
  lastErr = err;
6234
6243
  const msg = err instanceof Error ? err.message : String(err);
@@ -6236,6 +6245,7 @@ var BaseAddon = class {
6236
6245
  if (attempt === delaysMs.length) break;
6237
6246
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6238
6247
  }
6248
+ this.settingsStoreReady = false;
6239
6249
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6240
6250
  return {};
6241
6251
  }
@@ -8334,6 +8344,12 @@ var ModelVariantGroupSchema = object({
8334
8344
  */
8335
8345
  resolution: number().int().positive().optional()
8336
8346
  });
8347
+ var ModelProviderIdSchema = _enum([
8348
+ "camstack",
8349
+ "frigate",
8350
+ "scrypted",
8351
+ "custom"
8352
+ ]);
8337
8353
  var ModelCatalogEntrySchema = object({
8338
8354
  id: string(),
8339
8355
  name: string(),
@@ -8431,6 +8447,12 @@ var ModelCatalogEntrySchema = object({
8431
8447
  */
8432
8448
  group: ModelVariantGroupSchema.optional(),
8433
8449
  /**
8450
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8451
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8452
+ * persisted before this field existed (`inferModelProvider` fills those).
8453
+ */
8454
+ provider: ModelProviderIdSchema.optional(),
8455
+ /**
8434
8456
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8435
8457
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8436
8458
  * labels already ARE the CamStack macros (Scrypted identity map).
@@ -12031,6 +12053,27 @@ var LinkedDeviceSchema = object({
12031
12053
  features: array(string()),
12032
12054
  producesTrackedEvents: boolean().optional()
12033
12055
  });
12056
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12057
+ * The batch answer needs the tag; the single-device answer already has it
12058
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12059
+ var LinkedDevicesForDeviceSchema = object({
12060
+ deviceId: number(),
12061
+ mode: LinkedDevicesModeSchema,
12062
+ devices: array(LinkedDeviceSchema)
12063
+ });
12064
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12065
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12066
+ * object literal is exactly how the three drift apart. */
12067
+ var DeviceBindingsForDeviceSchema = object({
12068
+ deviceId: number(),
12069
+ entries: array(object({
12070
+ capName: string(),
12071
+ kind: _enum(["native", "wrapped"]),
12072
+ providerAddonId: string(),
12073
+ providerNodeId: string(),
12074
+ nativeAddonId: string()
12075
+ }))
12076
+ });
12034
12077
  var SavedDeviceRowSchema = object({
12035
12078
  /** Numeric id reserved at allocateDeviceId time. */
12036
12079
  id: number(),
@@ -12256,11 +12299,25 @@ method(object({
12256
12299
  projection: _enum(["full", "slim"]).optional(),
12257
12300
  /** Return only camera devices. Filtering server-side instead of
12258
12301
  * shipping 293 rows to find 12. */
12259
- isCamera: boolean().optional()
12302
+ isCamera: boolean().optional(),
12303
+ /**
12304
+ * Return only these device ids. For the caller that already KNOWS the
12305
+ * handful it wants and needs a field the id-bearing answer does not
12306
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12307
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12308
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12309
+ * refetches on the reconcile interval, on a phone.
12310
+ *
12311
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12312
+ * keys rather than rejecting them (verified against the live hub
12313
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12314
+ * it answers today and the caller filters as it already does.
12315
+ */
12316
+ deviceIds: array(number()).optional()
12260
12317
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12261
12318
  mode: LinkedDevicesModeSchema,
12262
12319
  devices: array(LinkedDeviceSchema)
12263
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12320
+ })), method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12264
12321
  deviceId: number(),
12265
12322
  values: record(string(), unknown())
12266
12323
  }), object({ success: literal(true) }), {
@@ -12287,25 +12344,7 @@ method(object({
12287
12344
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12288
12345
  kind: "mutation",
12289
12346
  auth: "admin"
12290
- }), method(object({ deviceId: number() }), object({
12291
- deviceId: number(),
12292
- entries: array(object({
12293
- capName: string(),
12294
- kind: _enum(["native", "wrapped"]),
12295
- providerAddonId: string(),
12296
- providerNodeId: string(),
12297
- nativeAddonId: string()
12298
- }))
12299
- })), method(object({}), array(object({
12300
- deviceId: number(),
12301
- entries: array(object({
12302
- capName: string(),
12303
- kind: _enum(["native", "wrapped"]),
12304
- providerAddonId: string(),
12305
- providerNodeId: string(),
12306
- nativeAddonId: string()
12307
- }))
12308
- }))), method(object({
12347
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12309
12348
  deviceId: number(),
12310
12349
  capName: string(),
12311
12350
  wrapperAddonId: string(),
@@ -14677,12 +14716,15 @@ var NcOccupancyConditionSchema = object({
14677
14716
  * there is no second switch that can disagree with the first and every rule
14678
14717
  * authored before the decision migrates for free (`audioModeOf`):
14679
14718
  *
14680
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14681
- * classifier labels with one of them. No window, no percentage:
14682
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14683
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14684
- * the analyzer's (`classificationMinScore`, per device) — a label only
14685
- * reaches this condition if the classifier was already confident enough.
14719
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14720
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14721
+ * `hitPercent` and `samplingSeconds` are still ignored — a percentage of
14722
+ * frames is the wrong question for a classifier that labels 1–3 frames
14723
+ * per episode. The count window is the brake that drops a single-frame
14724
+ * false positive; the rule's own `throttle` cooldown is the other. The
14725
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14726
+ * per device) — a label only reaches this condition if the classifier was
14727
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14686
14728
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14687
14729
  * the condition: at least `hitPercent`% of the samples over
14688
14730
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14709,14 +14751,22 @@ var NcOccupancyConditionSchema = object({
14709
14751
  * an operator who typed `dog` mean the same thing.
14710
14752
  */
14711
14753
  var NcAudioConditionSchema = object({
14712
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14754
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14713
14755
  labels: array(string().min(1)).min(1).optional(),
14714
14756
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14715
14757
  dbThreshold: number().min(-96).max(0).optional(),
14716
14758
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14717
14759
  hitPercent: number().int().min(1).max(100).default(60),
14718
14760
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14719
- samplingSeconds: number().int().min(1).max(300).default(10)
14761
+ samplingSeconds: number().int().min(1).max(300).default(10),
14762
+ /**
14763
+ * LABEL MODE: how many labelled frames must land inside
14764
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14765
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14766
+ */
14767
+ confirmHits: number().int().min(1).max(20).optional(),
14768
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14769
+ confirmWindowSec: number().int().min(1).max(60).optional()
14720
14770
  });
14721
14771
  /**
14722
14772
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17096,6 +17146,46 @@ var RecentTracksPageSchema = object({
17096
17146
  /** Cursor for the next page, or null when this page is the last. */
17097
17147
  nextCursor: string().nullable()
17098
17148
  });
17149
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17150
+ var LIST_GROUPS_MAX_LIMIT = 100;
17151
+ var AnalyticsGroupRecordSchema = object({
17152
+ id: string(),
17153
+ deviceId: number().int(),
17154
+ openedAt: number().int(),
17155
+ closedAt: number().int(),
17156
+ timestamp: number().int(),
17157
+ memberCount: number().int(),
17158
+ memberTrackIds: array(string()).readonly(),
17159
+ className: string(),
17160
+ classes: array(string()).readonly(),
17161
+ /** Relative event-media path, or null when the group has no picture yet. */
17162
+ mediaUrl: string().nullable(),
17163
+ singleton: boolean()
17164
+ });
17165
+ var AnalyticsGroupMemberSchema = object({
17166
+ trackId: string(),
17167
+ deviceId: number().int(),
17168
+ className: string(),
17169
+ firstSeen: number().int(),
17170
+ lastSeen: number().int(),
17171
+ mediaUrl: string().nullable()
17172
+ });
17173
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17174
+ var ListGroupsQueryInput = object({
17175
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17176
+ deviceIds: array(number()),
17177
+ /** Window lower bound on `closedAt` (inclusive). */
17178
+ since: number().optional(),
17179
+ /** Window upper bound on `openedAt` (inclusive). */
17180
+ until: number().optional(),
17181
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17182
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17183
+ cursor: string().optional()
17184
+ });
17185
+ var ListGroupsPageSchema = object({
17186
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17187
+ nextCursor: string().nullable()
17188
+ });
17099
17189
  var KeyEventQueryInput = object({
17100
17190
  deviceId: number(),
17101
17191
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17171,7 +17261,9 @@ var TrackCascadeCountsSchema = object({
17171
17261
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17172
17262
  plates: number().int(),
17173
17263
  /** Per-track CLIP search vectors removed (best-effort). */
17174
- embeddings: number().int()
17264
+ embeddings: number().int(),
17265
+ /** Group membership + group rows removed with their last member (best-effort). */
17266
+ groups: number().int()
17175
17267
  });
17176
17268
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17177
17269
  var DiskReconcileCountsSchema = object({
@@ -17317,7 +17409,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17317
17409
  * stationary registry). Default false: the timeline lists passages,
17318
17410
  * not parking records (operator decision, 2026-08-15). */
17319
17411
  includeStationary: boolean().optional()
17320
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17412
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17413
+ deviceId: number(),
17414
+ groupId: string().min(1)
17415
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17321
17416
  kind: "mutation",
17322
17417
  auth: "admin"
17323
17418
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -17627,7 +17722,8 @@ var PipelineModelOptionSchema = object({
17627
17722
  sizeMB: number()
17628
17723
  })),
17629
17724
  group: ModelVariantGroupSchema.optional(),
17630
- legacy: boolean().optional()
17725
+ legacy: boolean().optional(),
17726
+ provider: ModelProviderIdSchema.optional()
17631
17727
  });
17632
17728
  var ConfigFieldBridge = custom();
17633
17729
  var PipelineAddonSchemaSchema = object({
@@ -24211,7 +24307,12 @@ var PlateInfoSchema = object({
24211
24307
  plateBbox: BoundingBoxSchema.optional(),
24212
24308
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
24213
24309
  keyFrameMediaKey: string().optional(),
24214
- base64: string().optional()
24310
+ base64: string().optional(),
24311
+ /**
24312
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24313
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24314
+ */
24315
+ cropUrl: string().optional()
24215
24316
  });
24216
24317
  var MediaFileLiteSchema = object({
24217
24318
  key: string(),
@@ -27851,6 +27952,12 @@ Object.freeze({
27851
27952
  addonId: null,
27852
27953
  access: "view"
27853
27954
  },
27955
+ "deviceManager.getBindingsBatch": {
27956
+ capName: "device-manager",
27957
+ capScope: "system",
27958
+ addonId: null,
27959
+ access: "view"
27960
+ },
27854
27961
  "deviceManager.getChildren": {
27855
27962
  capName: "device-manager",
27856
27963
  capScope: "system",
@@ -27911,6 +28018,12 @@ Object.freeze({
27911
28018
  addonId: null,
27912
28019
  access: "view"
27913
28020
  },
28021
+ "deviceManager.getLinkedDevicesBatch": {
28022
+ capName: "device-manager",
28023
+ capScope: "system",
28024
+ addonId: null,
28025
+ access: "view"
28026
+ },
27914
28027
  "deviceManager.getRoleDisplayDefaults": {
27915
28028
  capName: "device-manager",
27916
28029
  capScope: "system",
@@ -29681,6 +29794,12 @@ Object.freeze({
29681
29794
  addonId: null,
29682
29795
  access: "view"
29683
29796
  },
29797
+ "pipelineAnalytics.getGroup": {
29798
+ capName: "pipeline-analytics",
29799
+ capScope: "device",
29800
+ addonId: null,
29801
+ access: "view"
29802
+ },
29684
29803
  "pipelineAnalytics.getKeyEvents": {
29685
29804
  capName: "pipeline-analytics",
29686
29805
  capScope: "device",
@@ -29765,6 +29884,12 @@ Object.freeze({
29765
29884
  addonId: null,
29766
29885
  access: "view"
29767
29886
  },
29887
+ "pipelineAnalytics.listGroups": {
29888
+ capName: "pipeline-analytics",
29889
+ capScope: "device",
29890
+ addonId: null,
29891
+ access: "view"
29892
+ },
29768
29893
  "pipelineAnalytics.listOpsLog": {
29769
29894
  capName: "pipeline-analytics",
29770
29895
  capScope: "device",
@@ -32546,6 +32671,11 @@ Object.freeze({
32546
32671
  form: "single",
32547
32672
  optional: false
32548
32673
  }],
32674
+ "deviceManager.getBindingsBatch": [{
32675
+ name: "deviceIds",
32676
+ form: "array",
32677
+ optional: false
32678
+ }],
32549
32679
  "deviceManager.getChildren": [{
32550
32680
  name: "parentDeviceId",
32551
32681
  form: "single",
@@ -32591,6 +32721,11 @@ Object.freeze({
32591
32721
  form: "single",
32592
32722
  optional: false
32593
32723
  }],
32724
+ "deviceManager.getLinkedDevicesBatch": [{
32725
+ name: "deviceIds",
32726
+ form: "array",
32727
+ optional: false
32728
+ }],
32594
32729
  "deviceManager.getSettingsSchema": [{
32595
32730
  name: "deviceId",
32596
32731
  form: "single",
@@ -32611,6 +32746,11 @@ Object.freeze({
32611
32746
  form: "single",
32612
32747
  optional: false
32613
32748
  }],
32749
+ "deviceManager.listAll": [{
32750
+ name: "deviceIds",
32751
+ form: "array",
32752
+ optional: true
32753
+ }],
32614
32754
  "deviceManager.loadConfig": [{
32615
32755
  name: "deviceId",
32616
32756
  form: "single",
@@ -33184,6 +33324,11 @@ Object.freeze({
33184
33324
  form: "single",
33185
33325
  optional: false
33186
33326
  }],
33327
+ "pipelineAnalytics.getGroup": [{
33328
+ name: "deviceId",
33329
+ form: "single",
33330
+ optional: false
33331
+ }],
33187
33332
  "pipelineAnalytics.getKeyEvents": [{
33188
33333
  name: "deviceId",
33189
33334
  form: "single",
@@ -33239,6 +33384,11 @@ Object.freeze({
33239
33384
  form: "array",
33240
33385
  optional: false
33241
33386
  }],
33387
+ "pipelineAnalytics.listGroups": [{
33388
+ name: "deviceIds",
33389
+ form: "array",
33390
+ optional: false
33391
+ }],
33242
33392
  "pipelineAnalytics.listOpsLog": [{
33243
33393
  name: "deviceId",
33244
33394
  form: "single",
@@ -5801,6 +5801,13 @@ var BaseAddon = class {
5801
5801
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5802
5802
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5803
5803
  _registeredCapNames = [];
5804
+ /**
5805
+ * True only after `readAddonStore` actually answered. Constructor
5806
+ * defaults look like stored config when the store is down — a forked
5807
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5808
+ * mode, 2026-08-25) is not "the operator chose this".
5809
+ */
5810
+ settingsStoreReady = false;
5804
5811
  /** Default config values. Provided via constructor. */
5805
5812
  defaults;
5806
5813
  constructor(defaults) {
@@ -6201,7 +6208,9 @@ var BaseAddon = class {
6201
6208
  ];
6202
6209
  let lastErr;
6203
6210
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6204
- return await settings.readAddonStore() ?? {};
6211
+ const stored = await settings.readAddonStore() ?? {};
6212
+ this.settingsStoreReady = true;
6213
+ return stored;
6205
6214
  } catch (err) {
6206
6215
  lastErr = err;
6207
6216
  const msg = err instanceof Error ? err.message : String(err);
@@ -6209,6 +6218,7 @@ var BaseAddon = class {
6209
6218
  if (attempt === delaysMs.length) break;
6210
6219
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6211
6220
  }
6221
+ this.settingsStoreReady = false;
6212
6222
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6213
6223
  return {};
6214
6224
  }
@@ -8307,6 +8317,12 @@ var ModelVariantGroupSchema = object({
8307
8317
  */
8308
8318
  resolution: number().int().positive().optional()
8309
8319
  });
8320
+ var ModelProviderIdSchema = _enum([
8321
+ "camstack",
8322
+ "frigate",
8323
+ "scrypted",
8324
+ "custom"
8325
+ ]);
8310
8326
  var ModelCatalogEntrySchema = object({
8311
8327
  id: string(),
8312
8328
  name: string(),
@@ -8404,6 +8420,12 @@ var ModelCatalogEntrySchema = object({
8404
8420
  */
8405
8421
  group: ModelVariantGroupSchema.optional(),
8406
8422
  /**
8423
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8424
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8425
+ * persisted before this field existed (`inferModelProvider` fills those).
8426
+ */
8427
+ provider: ModelProviderIdSchema.optional(),
8428
+ /**
8407
8429
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8408
8430
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8409
8431
  * labels already ARE the CamStack macros (Scrypted identity map).
@@ -12004,6 +12026,27 @@ var LinkedDeviceSchema = object({
12004
12026
  features: array(string()),
12005
12027
  producesTrackedEvents: boolean().optional()
12006
12028
  });
12029
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12030
+ * The batch answer needs the tag; the single-device answer already has it
12031
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12032
+ var LinkedDevicesForDeviceSchema = object({
12033
+ deviceId: number(),
12034
+ mode: LinkedDevicesModeSchema,
12035
+ devices: array(LinkedDeviceSchema)
12036
+ });
12037
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12038
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12039
+ * object literal is exactly how the three drift apart. */
12040
+ var DeviceBindingsForDeviceSchema = object({
12041
+ deviceId: number(),
12042
+ entries: array(object({
12043
+ capName: string(),
12044
+ kind: _enum(["native", "wrapped"]),
12045
+ providerAddonId: string(),
12046
+ providerNodeId: string(),
12047
+ nativeAddonId: string()
12048
+ }))
12049
+ });
12007
12050
  var SavedDeviceRowSchema = object({
12008
12051
  /** Numeric id reserved at allocateDeviceId time. */
12009
12052
  id: number(),
@@ -12229,11 +12272,25 @@ method(object({
12229
12272
  projection: _enum(["full", "slim"]).optional(),
12230
12273
  /** Return only camera devices. Filtering server-side instead of
12231
12274
  * shipping 293 rows to find 12. */
12232
- isCamera: boolean().optional()
12275
+ isCamera: boolean().optional(),
12276
+ /**
12277
+ * Return only these device ids. For the caller that already KNOWS the
12278
+ * handful it wants and needs a field the id-bearing answer does not
12279
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12280
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12281
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12282
+ * refetches on the reconcile interval, on a phone.
12283
+ *
12284
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12285
+ * keys rather than rejecting them (verified against the live hub
12286
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12287
+ * it answers today and the caller filters as it already does.
12288
+ */
12289
+ deviceIds: array(number()).optional()
12233
12290
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12234
12291
  mode: LinkedDevicesModeSchema,
12235
12292
  devices: array(LinkedDeviceSchema)
12236
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12293
+ })), method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12237
12294
  deviceId: number(),
12238
12295
  values: record(string(), unknown())
12239
12296
  }), object({ success: literal(true) }), {
@@ -12260,25 +12317,7 @@ method(object({
12260
12317
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12261
12318
  kind: "mutation",
12262
12319
  auth: "admin"
12263
- }), method(object({ deviceId: number() }), object({
12264
- deviceId: number(),
12265
- entries: array(object({
12266
- capName: string(),
12267
- kind: _enum(["native", "wrapped"]),
12268
- providerAddonId: string(),
12269
- providerNodeId: string(),
12270
- nativeAddonId: string()
12271
- }))
12272
- })), method(object({}), array(object({
12273
- deviceId: number(),
12274
- entries: array(object({
12275
- capName: string(),
12276
- kind: _enum(["native", "wrapped"]),
12277
- providerAddonId: string(),
12278
- providerNodeId: string(),
12279
- nativeAddonId: string()
12280
- }))
12281
- }))), method(object({
12320
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12282
12321
  deviceId: number(),
12283
12322
  capName: string(),
12284
12323
  wrapperAddonId: string(),
@@ -14650,12 +14689,15 @@ var NcOccupancyConditionSchema = object({
14650
14689
  * there is no second switch that can disagree with the first and every rule
14651
14690
  * authored before the decision migrates for free (`audioModeOf`):
14652
14691
  *
14653
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14654
- * classifier labels with one of them. No window, no percentage:
14655
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14656
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14657
- * the analyzer's (`classificationMinScore`, per device) — a label only
14658
- * reaches this condition if the classifier was already confident enough.
14692
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14693
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14694
+ * `hitPercent` and `samplingSeconds` are still ignored — a percentage of
14695
+ * frames is the wrong question for a classifier that labels 1–3 frames
14696
+ * per episode. The count window is the brake that drops a single-frame
14697
+ * false positive; the rule's own `throttle` cooldown is the other. The
14698
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14699
+ * per device) — a label only reaches this condition if the classifier was
14700
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14659
14701
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14660
14702
  * the condition: at least `hitPercent`% of the samples over
14661
14703
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14682,14 +14724,22 @@ var NcOccupancyConditionSchema = object({
14682
14724
  * an operator who typed `dog` mean the same thing.
14683
14725
  */
14684
14726
  var NcAudioConditionSchema = object({
14685
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14727
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14686
14728
  labels: array(string().min(1)).min(1).optional(),
14687
14729
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14688
14730
  dbThreshold: number().min(-96).max(0).optional(),
14689
14731
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14690
14732
  hitPercent: number().int().min(1).max(100).default(60),
14691
14733
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14692
- samplingSeconds: number().int().min(1).max(300).default(10)
14734
+ samplingSeconds: number().int().min(1).max(300).default(10),
14735
+ /**
14736
+ * LABEL MODE: how many labelled frames must land inside
14737
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14738
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14739
+ */
14740
+ confirmHits: number().int().min(1).max(20).optional(),
14741
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14742
+ confirmWindowSec: number().int().min(1).max(60).optional()
14693
14743
  });
14694
14744
  /**
14695
14745
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17069,6 +17119,46 @@ var RecentTracksPageSchema = object({
17069
17119
  /** Cursor for the next page, or null when this page is the last. */
17070
17120
  nextCursor: string().nullable()
17071
17121
  });
17122
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17123
+ var LIST_GROUPS_MAX_LIMIT = 100;
17124
+ var AnalyticsGroupRecordSchema = object({
17125
+ id: string(),
17126
+ deviceId: number().int(),
17127
+ openedAt: number().int(),
17128
+ closedAt: number().int(),
17129
+ timestamp: number().int(),
17130
+ memberCount: number().int(),
17131
+ memberTrackIds: array(string()).readonly(),
17132
+ className: string(),
17133
+ classes: array(string()).readonly(),
17134
+ /** Relative event-media path, or null when the group has no picture yet. */
17135
+ mediaUrl: string().nullable(),
17136
+ singleton: boolean()
17137
+ });
17138
+ var AnalyticsGroupMemberSchema = object({
17139
+ trackId: string(),
17140
+ deviceId: number().int(),
17141
+ className: string(),
17142
+ firstSeen: number().int(),
17143
+ lastSeen: number().int(),
17144
+ mediaUrl: string().nullable()
17145
+ });
17146
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17147
+ var ListGroupsQueryInput = object({
17148
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17149
+ deviceIds: array(number()),
17150
+ /** Window lower bound on `closedAt` (inclusive). */
17151
+ since: number().optional(),
17152
+ /** Window upper bound on `openedAt` (inclusive). */
17153
+ until: number().optional(),
17154
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17155
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17156
+ cursor: string().optional()
17157
+ });
17158
+ var ListGroupsPageSchema = object({
17159
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17160
+ nextCursor: string().nullable()
17161
+ });
17072
17162
  var KeyEventQueryInput = object({
17073
17163
  deviceId: number(),
17074
17164
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17144,7 +17234,9 @@ var TrackCascadeCountsSchema = object({
17144
17234
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17145
17235
  plates: number().int(),
17146
17236
  /** Per-track CLIP search vectors removed (best-effort). */
17147
- embeddings: number().int()
17237
+ embeddings: number().int(),
17238
+ /** Group membership + group rows removed with their last member (best-effort). */
17239
+ groups: number().int()
17148
17240
  });
17149
17241
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17150
17242
  var DiskReconcileCountsSchema = object({
@@ -17290,7 +17382,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17290
17382
  * stationary registry). Default false: the timeline lists passages,
17291
17383
  * not parking records (operator decision, 2026-08-15). */
17292
17384
  includeStationary: boolean().optional()
17293
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17385
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17386
+ deviceId: number(),
17387
+ groupId: string().min(1)
17388
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17294
17389
  kind: "mutation",
17295
17390
  auth: "admin"
17296
17391
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -17600,7 +17695,8 @@ var PipelineModelOptionSchema = object({
17600
17695
  sizeMB: number()
17601
17696
  })),
17602
17697
  group: ModelVariantGroupSchema.optional(),
17603
- legacy: boolean().optional()
17698
+ legacy: boolean().optional(),
17699
+ provider: ModelProviderIdSchema.optional()
17604
17700
  });
17605
17701
  var ConfigFieldBridge = custom();
17606
17702
  var PipelineAddonSchemaSchema = object({
@@ -24184,7 +24280,12 @@ var PlateInfoSchema = object({
24184
24280
  plateBbox: BoundingBoxSchema.optional(),
24185
24281
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
24186
24282
  keyFrameMediaKey: string().optional(),
24187
- base64: string().optional()
24283
+ base64: string().optional(),
24284
+ /**
24285
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24286
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24287
+ */
24288
+ cropUrl: string().optional()
24188
24289
  });
24189
24290
  var MediaFileLiteSchema = object({
24190
24291
  key: string(),
@@ -27824,6 +27925,12 @@ Object.freeze({
27824
27925
  addonId: null,
27825
27926
  access: "view"
27826
27927
  },
27928
+ "deviceManager.getBindingsBatch": {
27929
+ capName: "device-manager",
27930
+ capScope: "system",
27931
+ addonId: null,
27932
+ access: "view"
27933
+ },
27827
27934
  "deviceManager.getChildren": {
27828
27935
  capName: "device-manager",
27829
27936
  capScope: "system",
@@ -27884,6 +27991,12 @@ Object.freeze({
27884
27991
  addonId: null,
27885
27992
  access: "view"
27886
27993
  },
27994
+ "deviceManager.getLinkedDevicesBatch": {
27995
+ capName: "device-manager",
27996
+ capScope: "system",
27997
+ addonId: null,
27998
+ access: "view"
27999
+ },
27887
28000
  "deviceManager.getRoleDisplayDefaults": {
27888
28001
  capName: "device-manager",
27889
28002
  capScope: "system",
@@ -29654,6 +29767,12 @@ Object.freeze({
29654
29767
  addonId: null,
29655
29768
  access: "view"
29656
29769
  },
29770
+ "pipelineAnalytics.getGroup": {
29771
+ capName: "pipeline-analytics",
29772
+ capScope: "device",
29773
+ addonId: null,
29774
+ access: "view"
29775
+ },
29657
29776
  "pipelineAnalytics.getKeyEvents": {
29658
29777
  capName: "pipeline-analytics",
29659
29778
  capScope: "device",
@@ -29738,6 +29857,12 @@ Object.freeze({
29738
29857
  addonId: null,
29739
29858
  access: "view"
29740
29859
  },
29860
+ "pipelineAnalytics.listGroups": {
29861
+ capName: "pipeline-analytics",
29862
+ capScope: "device",
29863
+ addonId: null,
29864
+ access: "view"
29865
+ },
29741
29866
  "pipelineAnalytics.listOpsLog": {
29742
29867
  capName: "pipeline-analytics",
29743
29868
  capScope: "device",
@@ -32519,6 +32644,11 @@ Object.freeze({
32519
32644
  form: "single",
32520
32645
  optional: false
32521
32646
  }],
32647
+ "deviceManager.getBindingsBatch": [{
32648
+ name: "deviceIds",
32649
+ form: "array",
32650
+ optional: false
32651
+ }],
32522
32652
  "deviceManager.getChildren": [{
32523
32653
  name: "parentDeviceId",
32524
32654
  form: "single",
@@ -32564,6 +32694,11 @@ Object.freeze({
32564
32694
  form: "single",
32565
32695
  optional: false
32566
32696
  }],
32697
+ "deviceManager.getLinkedDevicesBatch": [{
32698
+ name: "deviceIds",
32699
+ form: "array",
32700
+ optional: false
32701
+ }],
32567
32702
  "deviceManager.getSettingsSchema": [{
32568
32703
  name: "deviceId",
32569
32704
  form: "single",
@@ -32584,6 +32719,11 @@ Object.freeze({
32584
32719
  form: "single",
32585
32720
  optional: false
32586
32721
  }],
32722
+ "deviceManager.listAll": [{
32723
+ name: "deviceIds",
32724
+ form: "array",
32725
+ optional: true
32726
+ }],
32587
32727
  "deviceManager.loadConfig": [{
32588
32728
  name: "deviceId",
32589
32729
  form: "single",
@@ -33157,6 +33297,11 @@ Object.freeze({
33157
33297
  form: "single",
33158
33298
  optional: false
33159
33299
  }],
33300
+ "pipelineAnalytics.getGroup": [{
33301
+ name: "deviceId",
33302
+ form: "single",
33303
+ optional: false
33304
+ }],
33160
33305
  "pipelineAnalytics.getKeyEvents": [{
33161
33306
  name: "deviceId",
33162
33307
  form: "single",
@@ -33212,6 +33357,11 @@ Object.freeze({
33212
33357
  form: "array",
33213
33358
  optional: false
33214
33359
  }],
33360
+ "pipelineAnalytics.listGroups": [{
33361
+ name: "deviceIds",
33362
+ form: "array",
33363
+ optional: false
33364
+ }],
33215
33365
  "pipelineAnalytics.listOpsLog": [{
33216
33366
  name: "deviceId",
33217
33367
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-alexa",
3
- "version": "1.2.33",
3
+ "version": "1.2.34",
4
4
  "description": "Alexa Smart Home Skill export — hub-side OAuth provider + directive handler. The companion AWS Lambda forwards Alexa directives to this addon's /addon/export-alexa/directive endpoint.",
5
5
  "keywords": [
6
6
  "camstack",