@camstack/addon-provider-dreame 0.2.27 → 0.2.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +184 -34
  2. package/dist/addon.mjs +184 -34
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -56306,6 +56306,13 @@ var BaseAddon = class {
56306
56306
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
56307
56307
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
56308
56308
  _registeredCapNames = [];
56309
+ /**
56310
+ * True only after `readAddonStore` actually answered. Constructor
56311
+ * defaults look like stored config when the store is down — a forked
56312
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
56313
+ * mode, 2026-08-25) is not "the operator chose this".
56314
+ */
56315
+ settingsStoreReady = false;
56309
56316
  /** Default config values. Provided via constructor. */
56310
56317
  defaults;
56311
56318
  constructor(defaults) {
@@ -56706,7 +56713,9 @@ var BaseAddon = class {
56706
56713
  ];
56707
56714
  let lastErr;
56708
56715
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
56709
- return await settings.readAddonStore() ?? {};
56716
+ const stored = await settings.readAddonStore() ?? {};
56717
+ this.settingsStoreReady = true;
56718
+ return stored;
56710
56719
  } catch (err) {
56711
56720
  lastErr = err;
56712
56721
  const msg = err instanceof Error ? err.message : String(err);
@@ -56714,6 +56723,7 @@ var BaseAddon = class {
56714
56723
  if (attempt === delaysMs.length) break;
56715
56724
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
56716
56725
  }
56726
+ this.settingsStoreReady = false;
56717
56727
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
56718
56728
  return {};
56719
56729
  }
@@ -58617,6 +58627,12 @@ var ModelVariantGroupSchema = object({
58617
58627
  */
58618
58628
  resolution: number().int().positive().optional()
58619
58629
  });
58630
+ var ModelProviderIdSchema = _enum([
58631
+ "camstack",
58632
+ "frigate",
58633
+ "scrypted",
58634
+ "custom"
58635
+ ]);
58620
58636
  var ModelCatalogEntrySchema = object({
58621
58637
  id: string(),
58622
58638
  name: string(),
@@ -58714,6 +58730,12 @@ var ModelCatalogEntrySchema = object({
58714
58730
  */
58715
58731
  group: ModelVariantGroupSchema.optional(),
58716
58732
  /**
58733
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
58734
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
58735
+ * persisted before this field existed (`inferModelProvider` fills those).
58736
+ */
58737
+ provider: ModelProviderIdSchema.optional(),
58738
+ /**
58717
58739
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
58718
58740
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
58719
58741
  * labels already ARE the CamStack macros (Scrypted identity map).
@@ -62522,6 +62544,27 @@ var LinkedDeviceSchema = object({
62522
62544
  features: array(string()),
62523
62545
  producesTrackedEvents: boolean().optional()
62524
62546
  });
62547
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
62548
+ * The batch answer needs the tag; the single-device answer already has it
62549
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
62550
+ var LinkedDevicesForDeviceSchema = object({
62551
+ deviceId: number(),
62552
+ mode: LinkedDevicesModeSchema,
62553
+ devices: array(LinkedDeviceSchema)
62554
+ });
62555
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
62556
+ * `getAllBindings` all answer in. Declared once: three copies of the same
62557
+ * object literal is exactly how the three drift apart. */
62558
+ var DeviceBindingsForDeviceSchema = object({
62559
+ deviceId: number(),
62560
+ entries: array(object({
62561
+ capName: string(),
62562
+ kind: _enum(["native", "wrapped"]),
62563
+ providerAddonId: string(),
62564
+ providerNodeId: string(),
62565
+ nativeAddonId: string()
62566
+ }))
62567
+ });
62525
62568
  var SavedDeviceRowSchema = object({
62526
62569
  /** Numeric id reserved at allocateDeviceId time. */
62527
62570
  id: number(),
@@ -62747,11 +62790,25 @@ method(object({
62747
62790
  projection: _enum(["full", "slim"]).optional(),
62748
62791
  /** Return only camera devices. Filtering server-side instead of
62749
62792
  * shipping 293 rows to find 12. */
62750
- isCamera: boolean().optional()
62793
+ isCamera: boolean().optional(),
62794
+ /**
62795
+ * Return only these device ids. For the caller that already KNOWS the
62796
+ * handful it wants and needs a field the id-bearing answer does not
62797
+ * carry — the viewer's linked-devices panel joins `type` and `online`
62798
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
62799
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
62800
+ * refetches on the reconcile interval, on a phone.
62801
+ *
62802
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
62803
+ * keys rather than rejecting them (verified against the live hub
62804
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
62805
+ * it answers today and the caller filters as it already does.
62806
+ */
62807
+ deviceIds: array(number()).optional()
62751
62808
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
62752
62809
  mode: LinkedDevicesModeSchema,
62753
62810
  devices: array(LinkedDeviceSchema)
62754
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
62811
+ })), 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({
62755
62812
  deviceId: number(),
62756
62813
  values: record(string(), unknown())
62757
62814
  }), object({ success: literal(true) }), {
@@ -62778,25 +62835,7 @@ method(object({
62778
62835
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
62779
62836
  kind: "mutation",
62780
62837
  auth: "admin"
62781
- }), method(object({ deviceId: number() }), object({
62782
- deviceId: number(),
62783
- entries: array(object({
62784
- capName: string(),
62785
- kind: _enum(["native", "wrapped"]),
62786
- providerAddonId: string(),
62787
- providerNodeId: string(),
62788
- nativeAddonId: string()
62789
- }))
62790
- })), method(object({}), array(object({
62791
- deviceId: number(),
62792
- entries: array(object({
62793
- capName: string(),
62794
- kind: _enum(["native", "wrapped"]),
62795
- providerAddonId: string(),
62796
- providerNodeId: string(),
62797
- nativeAddonId: string()
62798
- }))
62799
- }))), method(object({
62838
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
62800
62839
  deviceId: number(),
62801
62840
  capName: string(),
62802
62841
  wrapperAddonId: string(),
@@ -65206,12 +65245,15 @@ var NcOccupancyConditionSchema = object({
65206
65245
  * there is no second switch that can disagree with the first and every rule
65207
65246
  * authored before the decision migrates for free (`audioModeOf`):
65208
65247
  *
65209
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
65210
- * classifier labels with one of them. No window, no percentage:
65211
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
65212
- * `throttle` cooldown is the only brake. The per-label confidence floor is
65213
- * the analyzer's (`classificationMinScore`, per device) a label only
65214
- * reaches this condition if the classifier was already confident enough.
65248
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
65249
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
65250
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
65251
+ * frames is the wrong question for a classifier that labels 1–3 frames
65252
+ * per episode. The count window is the brake that drops a single-frame
65253
+ * false positive; the rule's own `throttle` cooldown is the other. The
65254
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
65255
+ * per device) — a label only reaches this condition if the classifier was
65256
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
65215
65257
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
65216
65258
  * the condition: at least `hitPercent`% of the samples over
65217
65259
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -65238,14 +65280,22 @@ var NcOccupancyConditionSchema = object({
65238
65280
  * an operator who typed `dog` mean the same thing.
65239
65281
  */
65240
65282
  var NcAudioConditionSchema = object({
65241
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
65283
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
65242
65284
  labels: array(string().min(1)).min(1).optional(),
65243
65285
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
65244
65286
  dbThreshold: number().min(-96).max(0).optional(),
65245
65287
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
65246
65288
  hitPercent: number().int().min(1).max(100).default(60),
65247
65289
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
65248
- samplingSeconds: number().int().min(1).max(300).default(10)
65290
+ samplingSeconds: number().int().min(1).max(300).default(10),
65291
+ /**
65292
+ * LABEL MODE: how many labelled frames must land inside
65293
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
65294
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
65295
+ */
65296
+ confirmHits: number().int().min(1).max(20).optional(),
65297
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
65298
+ confirmWindowSec: number().int().min(1).max(60).optional()
65249
65299
  });
65250
65300
  /**
65251
65301
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -67619,6 +67669,46 @@ var RecentTracksPageSchema = object({
67619
67669
  /** Cursor for the next page, or null when this page is the last. */
67620
67670
  nextCursor: string().nullable()
67621
67671
  });
67672
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
67673
+ var LIST_GROUPS_MAX_LIMIT = 100;
67674
+ var AnalyticsGroupRecordSchema = object({
67675
+ id: string(),
67676
+ deviceId: number().int(),
67677
+ openedAt: number().int(),
67678
+ closedAt: number().int(),
67679
+ timestamp: number().int(),
67680
+ memberCount: number().int(),
67681
+ memberTrackIds: array(string()).readonly(),
67682
+ className: string(),
67683
+ classes: array(string()).readonly(),
67684
+ /** Relative event-media path, or null when the group has no picture yet. */
67685
+ mediaUrl: string().nullable(),
67686
+ singleton: boolean()
67687
+ });
67688
+ var AnalyticsGroupMemberSchema = object({
67689
+ trackId: string(),
67690
+ deviceId: number().int(),
67691
+ className: string(),
67692
+ firstSeen: number().int(),
67693
+ lastSeen: number().int(),
67694
+ mediaUrl: string().nullable()
67695
+ });
67696
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
67697
+ var ListGroupsQueryInput = object({
67698
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
67699
+ deviceIds: array(number()),
67700
+ /** Window lower bound on `closedAt` (inclusive). */
67701
+ since: number().optional(),
67702
+ /** Window upper bound on `openedAt` (inclusive). */
67703
+ until: number().optional(),
67704
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
67705
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
67706
+ cursor: string().optional()
67707
+ });
67708
+ var ListGroupsPageSchema = object({
67709
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
67710
+ nextCursor: string().nullable()
67711
+ });
67622
67712
  var KeyEventQueryInput = object({
67623
67713
  deviceId: number(),
67624
67714
  /** Window lower bound (track firstSeen ≥ since). */
@@ -67694,7 +67784,9 @@ var TrackCascadeCountsSchema = object({
67694
67784
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
67695
67785
  plates: number().int(),
67696
67786
  /** Per-track CLIP search vectors removed (best-effort). */
67697
- embeddings: number().int()
67787
+ embeddings: number().int(),
67788
+ /** Group membership + group rows removed with their last member (best-effort). */
67789
+ groups: number().int()
67698
67790
  });
67699
67791
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
67700
67792
  var DiskReconcileCountsSchema = object({
@@ -67840,7 +67932,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
67840
67932
  * stationary registry). Default false: the timeline lists passages,
67841
67933
  * not parking records (operator decision, 2026-08-15). */
67842
67934
  includeStationary: boolean().optional()
67843
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
67935
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
67936
+ deviceId: number(),
67937
+ groupId: string().min(1)
67938
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
67844
67939
  kind: "mutation",
67845
67940
  auth: "admin"
67846
67941
  }), 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({
@@ -68150,7 +68245,8 @@ var PipelineModelOptionSchema = object({
68150
68245
  sizeMB: number()
68151
68246
  })),
68152
68247
  group: ModelVariantGroupSchema.optional(),
68153
- legacy: boolean().optional()
68248
+ legacy: boolean().optional(),
68249
+ provider: ModelProviderIdSchema.optional()
68154
68250
  });
68155
68251
  var ConfigFieldBridge = custom();
68156
68252
  var PipelineAddonSchemaSchema = object({
@@ -76272,7 +76368,12 @@ var PlateInfoSchema = object({
76272
76368
  plateBbox: BoundingBoxSchema.optional(),
76273
76369
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
76274
76370
  keyFrameMediaKey: string().optional(),
76275
- base64: string().optional()
76371
+ base64: string().optional(),
76372
+ /**
76373
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
76374
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
76375
+ */
76376
+ cropUrl: string().optional()
76276
76377
  });
76277
76378
  var MediaFileLiteSchema = object({
76278
76379
  key: string(),
@@ -81796,6 +81897,12 @@ Object.freeze({
81796
81897
  addonId: null,
81797
81898
  access: "view"
81798
81899
  },
81900
+ "deviceManager.getBindingsBatch": {
81901
+ capName: "device-manager",
81902
+ capScope: "system",
81903
+ addonId: null,
81904
+ access: "view"
81905
+ },
81799
81906
  "deviceManager.getChildren": {
81800
81907
  capName: "device-manager",
81801
81908
  capScope: "system",
@@ -81856,6 +81963,12 @@ Object.freeze({
81856
81963
  addonId: null,
81857
81964
  access: "view"
81858
81965
  },
81966
+ "deviceManager.getLinkedDevicesBatch": {
81967
+ capName: "device-manager",
81968
+ capScope: "system",
81969
+ addonId: null,
81970
+ access: "view"
81971
+ },
81859
81972
  "deviceManager.getRoleDisplayDefaults": {
81860
81973
  capName: "device-manager",
81861
81974
  capScope: "system",
@@ -83626,6 +83739,12 @@ Object.freeze({
83626
83739
  addonId: null,
83627
83740
  access: "view"
83628
83741
  },
83742
+ "pipelineAnalytics.getGroup": {
83743
+ capName: "pipeline-analytics",
83744
+ capScope: "device",
83745
+ addonId: null,
83746
+ access: "view"
83747
+ },
83629
83748
  "pipelineAnalytics.getKeyEvents": {
83630
83749
  capName: "pipeline-analytics",
83631
83750
  capScope: "device",
@@ -83710,6 +83829,12 @@ Object.freeze({
83710
83829
  addonId: null,
83711
83830
  access: "view"
83712
83831
  },
83832
+ "pipelineAnalytics.listGroups": {
83833
+ capName: "pipeline-analytics",
83834
+ capScope: "device",
83835
+ addonId: null,
83836
+ access: "view"
83837
+ },
83713
83838
  "pipelineAnalytics.listOpsLog": {
83714
83839
  capName: "pipeline-analytics",
83715
83840
  capScope: "device",
@@ -86491,6 +86616,11 @@ Object.freeze({
86491
86616
  form: "single",
86492
86617
  optional: false
86493
86618
  }],
86619
+ "deviceManager.getBindingsBatch": [{
86620
+ name: "deviceIds",
86621
+ form: "array",
86622
+ optional: false
86623
+ }],
86494
86624
  "deviceManager.getChildren": [{
86495
86625
  name: "parentDeviceId",
86496
86626
  form: "single",
@@ -86536,6 +86666,11 @@ Object.freeze({
86536
86666
  form: "single",
86537
86667
  optional: false
86538
86668
  }],
86669
+ "deviceManager.getLinkedDevicesBatch": [{
86670
+ name: "deviceIds",
86671
+ form: "array",
86672
+ optional: false
86673
+ }],
86539
86674
  "deviceManager.getSettingsSchema": [{
86540
86675
  name: "deviceId",
86541
86676
  form: "single",
@@ -86556,6 +86691,11 @@ Object.freeze({
86556
86691
  form: "single",
86557
86692
  optional: false
86558
86693
  }],
86694
+ "deviceManager.listAll": [{
86695
+ name: "deviceIds",
86696
+ form: "array",
86697
+ optional: true
86698
+ }],
86559
86699
  "deviceManager.loadConfig": [{
86560
86700
  name: "deviceId",
86561
86701
  form: "single",
@@ -87129,6 +87269,11 @@ Object.freeze({
87129
87269
  form: "single",
87130
87270
  optional: false
87131
87271
  }],
87272
+ "pipelineAnalytics.getGroup": [{
87273
+ name: "deviceId",
87274
+ form: "single",
87275
+ optional: false
87276
+ }],
87132
87277
  "pipelineAnalytics.getKeyEvents": [{
87133
87278
  name: "deviceId",
87134
87279
  form: "single",
@@ -87184,6 +87329,11 @@ Object.freeze({
87184
87329
  form: "array",
87185
87330
  optional: false
87186
87331
  }],
87332
+ "pipelineAnalytics.listGroups": [{
87333
+ name: "deviceIds",
87334
+ form: "array",
87335
+ optional: false
87336
+ }],
87187
87337
  "pipelineAnalytics.listOpsLog": [{
87188
87338
  name: "deviceId",
87189
87339
  form: "single",
package/dist/addon.mjs CHANGED
@@ -56306,6 +56306,13 @@ var BaseAddon = class {
56306
56306
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
56307
56307
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
56308
56308
  _registeredCapNames = [];
56309
+ /**
56310
+ * True only after `readAddonStore` actually answered. Constructor
56311
+ * defaults look like stored config when the store is down — a forked
56312
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
56313
+ * mode, 2026-08-25) is not "the operator chose this".
56314
+ */
56315
+ settingsStoreReady = false;
56309
56316
  /** Default config values. Provided via constructor. */
56310
56317
  defaults;
56311
56318
  constructor(defaults) {
@@ -56706,7 +56713,9 @@ var BaseAddon = class {
56706
56713
  ];
56707
56714
  let lastErr;
56708
56715
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
56709
- return await settings.readAddonStore() ?? {};
56716
+ const stored = await settings.readAddonStore() ?? {};
56717
+ this.settingsStoreReady = true;
56718
+ return stored;
56710
56719
  } catch (err) {
56711
56720
  lastErr = err;
56712
56721
  const msg = err instanceof Error ? err.message : String(err);
@@ -56714,6 +56723,7 @@ var BaseAddon = class {
56714
56723
  if (attempt === delaysMs.length) break;
56715
56724
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
56716
56725
  }
56726
+ this.settingsStoreReady = false;
56717
56727
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
56718
56728
  return {};
56719
56729
  }
@@ -58617,6 +58627,12 @@ var ModelVariantGroupSchema = object({
58617
58627
  */
58618
58628
  resolution: number().int().positive().optional()
58619
58629
  });
58630
+ var ModelProviderIdSchema = _enum([
58631
+ "camstack",
58632
+ "frigate",
58633
+ "scrypted",
58634
+ "custom"
58635
+ ]);
58620
58636
  var ModelCatalogEntrySchema = object({
58621
58637
  id: string(),
58622
58638
  name: string(),
@@ -58714,6 +58730,12 @@ var ModelCatalogEntrySchema = object({
58714
58730
  */
58715
58731
  group: ModelVariantGroupSchema.optional(),
58716
58732
  /**
58733
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
58734
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
58735
+ * persisted before this field existed (`inferModelProvider` fills those).
58736
+ */
58737
+ provider: ModelProviderIdSchema.optional(),
58738
+ /**
58717
58739
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
58718
58740
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
58719
58741
  * labels already ARE the CamStack macros (Scrypted identity map).
@@ -62522,6 +62544,27 @@ var LinkedDeviceSchema = object({
62522
62544
  features: array(string()),
62523
62545
  producesTrackedEvents: boolean().optional()
62524
62546
  });
62547
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
62548
+ * The batch answer needs the tag; the single-device answer already has it
62549
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
62550
+ var LinkedDevicesForDeviceSchema = object({
62551
+ deviceId: number(),
62552
+ mode: LinkedDevicesModeSchema,
62553
+ devices: array(LinkedDeviceSchema)
62554
+ });
62555
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
62556
+ * `getAllBindings` all answer in. Declared once: three copies of the same
62557
+ * object literal is exactly how the three drift apart. */
62558
+ var DeviceBindingsForDeviceSchema = object({
62559
+ deviceId: number(),
62560
+ entries: array(object({
62561
+ capName: string(),
62562
+ kind: _enum(["native", "wrapped"]),
62563
+ providerAddonId: string(),
62564
+ providerNodeId: string(),
62565
+ nativeAddonId: string()
62566
+ }))
62567
+ });
62525
62568
  var SavedDeviceRowSchema = object({
62526
62569
  /** Numeric id reserved at allocateDeviceId time. */
62527
62570
  id: number(),
@@ -62747,11 +62790,25 @@ method(object({
62747
62790
  projection: _enum(["full", "slim"]).optional(),
62748
62791
  /** Return only camera devices. Filtering server-side instead of
62749
62792
  * shipping 293 rows to find 12. */
62750
- isCamera: boolean().optional()
62793
+ isCamera: boolean().optional(),
62794
+ /**
62795
+ * Return only these device ids. For the caller that already KNOWS the
62796
+ * handful it wants and needs a field the id-bearing answer does not
62797
+ * carry — the viewer's linked-devices panel joins `type` and `online`
62798
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
62799
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
62800
+ * refetches on the reconcile interval, on a phone.
62801
+ *
62802
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
62803
+ * keys rather than rejecting them (verified against the live hub
62804
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
62805
+ * it answers today and the caller filters as it already does.
62806
+ */
62807
+ deviceIds: array(number()).optional()
62751
62808
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
62752
62809
  mode: LinkedDevicesModeSchema,
62753
62810
  devices: array(LinkedDeviceSchema)
62754
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
62811
+ })), 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({
62755
62812
  deviceId: number(),
62756
62813
  values: record(string(), unknown())
62757
62814
  }), object({ success: literal(true) }), {
@@ -62778,25 +62835,7 @@ method(object({
62778
62835
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
62779
62836
  kind: "mutation",
62780
62837
  auth: "admin"
62781
- }), method(object({ deviceId: number() }), object({
62782
- deviceId: number(),
62783
- entries: array(object({
62784
- capName: string(),
62785
- kind: _enum(["native", "wrapped"]),
62786
- providerAddonId: string(),
62787
- providerNodeId: string(),
62788
- nativeAddonId: string()
62789
- }))
62790
- })), method(object({}), array(object({
62791
- deviceId: number(),
62792
- entries: array(object({
62793
- capName: string(),
62794
- kind: _enum(["native", "wrapped"]),
62795
- providerAddonId: string(),
62796
- providerNodeId: string(),
62797
- nativeAddonId: string()
62798
- }))
62799
- }))), method(object({
62838
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
62800
62839
  deviceId: number(),
62801
62840
  capName: string(),
62802
62841
  wrapperAddonId: string(),
@@ -65206,12 +65245,15 @@ var NcOccupancyConditionSchema = object({
65206
65245
  * there is no second switch that can disagree with the first and every rule
65207
65246
  * authored before the decision migrates for free (`audioModeOf`):
65208
65247
  *
65209
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
65210
- * classifier labels with one of them. No window, no percentage:
65211
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
65212
- * `throttle` cooldown is the only brake. The per-label confidence floor is
65213
- * the analyzer's (`classificationMinScore`, per device) a label only
65214
- * reaches this condition if the classifier was already confident enough.
65248
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
65249
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
65250
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
65251
+ * frames is the wrong question for a classifier that labels 1–3 frames
65252
+ * per episode. The count window is the brake that drops a single-frame
65253
+ * false positive; the rule's own `throttle` cooldown is the other. The
65254
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
65255
+ * per device) — a label only reaches this condition if the classifier was
65256
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
65215
65257
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
65216
65258
  * the condition: at least `hitPercent`% of the samples over
65217
65259
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -65238,14 +65280,22 @@ var NcOccupancyConditionSchema = object({
65238
65280
  * an operator who typed `dog` mean the same thing.
65239
65281
  */
65240
65282
  var NcAudioConditionSchema = object({
65241
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
65283
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
65242
65284
  labels: array(string().min(1)).min(1).optional(),
65243
65285
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
65244
65286
  dbThreshold: number().min(-96).max(0).optional(),
65245
65287
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
65246
65288
  hitPercent: number().int().min(1).max(100).default(60),
65247
65289
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
65248
- samplingSeconds: number().int().min(1).max(300).default(10)
65290
+ samplingSeconds: number().int().min(1).max(300).default(10),
65291
+ /**
65292
+ * LABEL MODE: how many labelled frames must land inside
65293
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
65294
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
65295
+ */
65296
+ confirmHits: number().int().min(1).max(20).optional(),
65297
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
65298
+ confirmWindowSec: number().int().min(1).max(60).optional()
65249
65299
  });
65250
65300
  /**
65251
65301
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -67619,6 +67669,46 @@ var RecentTracksPageSchema = object({
67619
67669
  /** Cursor for the next page, or null when this page is the last. */
67620
67670
  nextCursor: string().nullable()
67621
67671
  });
67672
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
67673
+ var LIST_GROUPS_MAX_LIMIT = 100;
67674
+ var AnalyticsGroupRecordSchema = object({
67675
+ id: string(),
67676
+ deviceId: number().int(),
67677
+ openedAt: number().int(),
67678
+ closedAt: number().int(),
67679
+ timestamp: number().int(),
67680
+ memberCount: number().int(),
67681
+ memberTrackIds: array(string()).readonly(),
67682
+ className: string(),
67683
+ classes: array(string()).readonly(),
67684
+ /** Relative event-media path, or null when the group has no picture yet. */
67685
+ mediaUrl: string().nullable(),
67686
+ singleton: boolean()
67687
+ });
67688
+ var AnalyticsGroupMemberSchema = object({
67689
+ trackId: string(),
67690
+ deviceId: number().int(),
67691
+ className: string(),
67692
+ firstSeen: number().int(),
67693
+ lastSeen: number().int(),
67694
+ mediaUrl: string().nullable()
67695
+ });
67696
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
67697
+ var ListGroupsQueryInput = object({
67698
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
67699
+ deviceIds: array(number()),
67700
+ /** Window lower bound on `closedAt` (inclusive). */
67701
+ since: number().optional(),
67702
+ /** Window upper bound on `openedAt` (inclusive). */
67703
+ until: number().optional(),
67704
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
67705
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
67706
+ cursor: string().optional()
67707
+ });
67708
+ var ListGroupsPageSchema = object({
67709
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
67710
+ nextCursor: string().nullable()
67711
+ });
67622
67712
  var KeyEventQueryInput = object({
67623
67713
  deviceId: number(),
67624
67714
  /** Window lower bound (track firstSeen ≥ since). */
@@ -67694,7 +67784,9 @@ var TrackCascadeCountsSchema = object({
67694
67784
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
67695
67785
  plates: number().int(),
67696
67786
  /** Per-track CLIP search vectors removed (best-effort). */
67697
- embeddings: number().int()
67787
+ embeddings: number().int(),
67788
+ /** Group membership + group rows removed with their last member (best-effort). */
67789
+ groups: number().int()
67698
67790
  });
67699
67791
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
67700
67792
  var DiskReconcileCountsSchema = object({
@@ -67840,7 +67932,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
67840
67932
  * stationary registry). Default false: the timeline lists passages,
67841
67933
  * not parking records (operator decision, 2026-08-15). */
67842
67934
  includeStationary: boolean().optional()
67843
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
67935
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
67936
+ deviceId: number(),
67937
+ groupId: string().min(1)
67938
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
67844
67939
  kind: "mutation",
67845
67940
  auth: "admin"
67846
67941
  }), 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({
@@ -68150,7 +68245,8 @@ var PipelineModelOptionSchema = object({
68150
68245
  sizeMB: number()
68151
68246
  })),
68152
68247
  group: ModelVariantGroupSchema.optional(),
68153
- legacy: boolean().optional()
68248
+ legacy: boolean().optional(),
68249
+ provider: ModelProviderIdSchema.optional()
68154
68250
  });
68155
68251
  var ConfigFieldBridge = custom();
68156
68252
  var PipelineAddonSchemaSchema = object({
@@ -76272,7 +76368,12 @@ var PlateInfoSchema = object({
76272
76368
  plateBbox: BoundingBoxSchema.optional(),
76273
76369
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
76274
76370
  keyFrameMediaKey: string().optional(),
76275
- base64: string().optional()
76371
+ base64: string().optional(),
76372
+ /**
76373
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
76374
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
76375
+ */
76376
+ cropUrl: string().optional()
76276
76377
  });
76277
76378
  var MediaFileLiteSchema = object({
76278
76379
  key: string(),
@@ -81796,6 +81897,12 @@ Object.freeze({
81796
81897
  addonId: null,
81797
81898
  access: "view"
81798
81899
  },
81900
+ "deviceManager.getBindingsBatch": {
81901
+ capName: "device-manager",
81902
+ capScope: "system",
81903
+ addonId: null,
81904
+ access: "view"
81905
+ },
81799
81906
  "deviceManager.getChildren": {
81800
81907
  capName: "device-manager",
81801
81908
  capScope: "system",
@@ -81856,6 +81963,12 @@ Object.freeze({
81856
81963
  addonId: null,
81857
81964
  access: "view"
81858
81965
  },
81966
+ "deviceManager.getLinkedDevicesBatch": {
81967
+ capName: "device-manager",
81968
+ capScope: "system",
81969
+ addonId: null,
81970
+ access: "view"
81971
+ },
81859
81972
  "deviceManager.getRoleDisplayDefaults": {
81860
81973
  capName: "device-manager",
81861
81974
  capScope: "system",
@@ -83626,6 +83739,12 @@ Object.freeze({
83626
83739
  addonId: null,
83627
83740
  access: "view"
83628
83741
  },
83742
+ "pipelineAnalytics.getGroup": {
83743
+ capName: "pipeline-analytics",
83744
+ capScope: "device",
83745
+ addonId: null,
83746
+ access: "view"
83747
+ },
83629
83748
  "pipelineAnalytics.getKeyEvents": {
83630
83749
  capName: "pipeline-analytics",
83631
83750
  capScope: "device",
@@ -83710,6 +83829,12 @@ Object.freeze({
83710
83829
  addonId: null,
83711
83830
  access: "view"
83712
83831
  },
83832
+ "pipelineAnalytics.listGroups": {
83833
+ capName: "pipeline-analytics",
83834
+ capScope: "device",
83835
+ addonId: null,
83836
+ access: "view"
83837
+ },
83713
83838
  "pipelineAnalytics.listOpsLog": {
83714
83839
  capName: "pipeline-analytics",
83715
83840
  capScope: "device",
@@ -86491,6 +86616,11 @@ Object.freeze({
86491
86616
  form: "single",
86492
86617
  optional: false
86493
86618
  }],
86619
+ "deviceManager.getBindingsBatch": [{
86620
+ name: "deviceIds",
86621
+ form: "array",
86622
+ optional: false
86623
+ }],
86494
86624
  "deviceManager.getChildren": [{
86495
86625
  name: "parentDeviceId",
86496
86626
  form: "single",
@@ -86536,6 +86666,11 @@ Object.freeze({
86536
86666
  form: "single",
86537
86667
  optional: false
86538
86668
  }],
86669
+ "deviceManager.getLinkedDevicesBatch": [{
86670
+ name: "deviceIds",
86671
+ form: "array",
86672
+ optional: false
86673
+ }],
86539
86674
  "deviceManager.getSettingsSchema": [{
86540
86675
  name: "deviceId",
86541
86676
  form: "single",
@@ -86556,6 +86691,11 @@ Object.freeze({
86556
86691
  form: "single",
86557
86692
  optional: false
86558
86693
  }],
86694
+ "deviceManager.listAll": [{
86695
+ name: "deviceIds",
86696
+ form: "array",
86697
+ optional: true
86698
+ }],
86559
86699
  "deviceManager.loadConfig": [{
86560
86700
  name: "deviceId",
86561
86701
  form: "single",
@@ -87129,6 +87269,11 @@ Object.freeze({
87129
87269
  form: "single",
87130
87270
  optional: false
87131
87271
  }],
87272
+ "pipelineAnalytics.getGroup": [{
87273
+ name: "deviceId",
87274
+ form: "single",
87275
+ optional: false
87276
+ }],
87132
87277
  "pipelineAnalytics.getKeyEvents": [{
87133
87278
  name: "deviceId",
87134
87279
  form: "single",
@@ -87184,6 +87329,11 @@ Object.freeze({
87184
87329
  form: "array",
87185
87330
  optional: false
87186
87331
  }],
87332
+ "pipelineAnalytics.listGroups": [{
87333
+ name: "deviceIds",
87334
+ form: "array",
87335
+ optional: false
87336
+ }],
87187
87337
  "pipelineAnalytics.listOpsLog": [{
87188
87338
  name: "deviceId",
87189
87339
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreame",
3
- "version": "0.2.27",
3
+ "version": "0.2.28",
4
4
  "description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
5
5
  "keywords": [
6
6
  "camstack",