@camstack/addon-provider-reolink 1.2.49 → 1.2.50

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
@@ -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
  }
@@ -8326,6 +8336,12 @@ var ModelVariantGroupSchema = object({
8326
8336
  */
8327
8337
  resolution: number().int().positive().optional()
8328
8338
  });
8339
+ var ModelProviderIdSchema = _enum([
8340
+ "camstack",
8341
+ "frigate",
8342
+ "scrypted",
8343
+ "custom"
8344
+ ]);
8329
8345
  var ModelCatalogEntrySchema = object({
8330
8346
  id: string(),
8331
8347
  name: string(),
@@ -8423,6 +8439,12 @@ var ModelCatalogEntrySchema = object({
8423
8439
  */
8424
8440
  group: ModelVariantGroupSchema.optional(),
8425
8441
  /**
8442
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8443
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8444
+ * persisted before this field existed (`inferModelProvider` fills those).
8445
+ */
8446
+ provider: ModelProviderIdSchema.optional(),
8447
+ /**
8426
8448
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8427
8449
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8428
8450
  * labels already ARE the CamStack macros (Scrypted identity map).
@@ -12226,6 +12248,27 @@ var LinkedDeviceSchema = object({
12226
12248
  features: array(string()),
12227
12249
  producesTrackedEvents: boolean().optional()
12228
12250
  });
12251
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12252
+ * The batch answer needs the tag; the single-device answer already has it
12253
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12254
+ var LinkedDevicesForDeviceSchema = object({
12255
+ deviceId: number(),
12256
+ mode: LinkedDevicesModeSchema,
12257
+ devices: array(LinkedDeviceSchema)
12258
+ });
12259
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12260
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12261
+ * object literal is exactly how the three drift apart. */
12262
+ var DeviceBindingsForDeviceSchema = object({
12263
+ deviceId: number(),
12264
+ entries: array(object({
12265
+ capName: string(),
12266
+ kind: _enum(["native", "wrapped"]),
12267
+ providerAddonId: string(),
12268
+ providerNodeId: string(),
12269
+ nativeAddonId: string()
12270
+ }))
12271
+ });
12229
12272
  var SavedDeviceRowSchema = object({
12230
12273
  /** Numeric id reserved at allocateDeviceId time. */
12231
12274
  id: number(),
@@ -12451,11 +12494,25 @@ method(object({
12451
12494
  projection: _enum(["full", "slim"]).optional(),
12452
12495
  /** Return only camera devices. Filtering server-side instead of
12453
12496
  * shipping 293 rows to find 12. */
12454
- isCamera: boolean().optional()
12497
+ isCamera: boolean().optional(),
12498
+ /**
12499
+ * Return only these device ids. For the caller that already KNOWS the
12500
+ * handful it wants and needs a field the id-bearing answer does not
12501
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12502
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12503
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12504
+ * refetches on the reconcile interval, on a phone.
12505
+ *
12506
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12507
+ * keys rather than rejecting them (verified against the live hub
12508
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12509
+ * it answers today and the caller filters as it already does.
12510
+ */
12511
+ deviceIds: array(number()).optional()
12455
12512
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12456
12513
  mode: LinkedDevicesModeSchema,
12457
12514
  devices: array(LinkedDeviceSchema)
12458
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12515
+ })), 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({
12459
12516
  deviceId: number(),
12460
12517
  values: record(string(), unknown())
12461
12518
  }), object({ success: literal(true) }), {
@@ -12482,25 +12539,7 @@ method(object({
12482
12539
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12483
12540
  kind: "mutation",
12484
12541
  auth: "admin"
12485
- }), method(object({ deviceId: number() }), object({
12486
- deviceId: number(),
12487
- entries: array(object({
12488
- capName: string(),
12489
- kind: _enum(["native", "wrapped"]),
12490
- providerAddonId: string(),
12491
- providerNodeId: string(),
12492
- nativeAddonId: string()
12493
- }))
12494
- })), method(object({}), array(object({
12495
- deviceId: number(),
12496
- entries: array(object({
12497
- capName: string(),
12498
- kind: _enum(["native", "wrapped"]),
12499
- providerAddonId: string(),
12500
- providerNodeId: string(),
12501
- nativeAddonId: string()
12502
- }))
12503
- }))), method(object({
12542
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12504
12543
  deviceId: number(),
12505
12544
  capName: string(),
12506
12545
  wrapperAddonId: string(),
@@ -14910,12 +14949,15 @@ var NcOccupancyConditionSchema = object({
14910
14949
  * there is no second switch that can disagree with the first and every rule
14911
14950
  * authored before the decision migrates for free (`audioModeOf`):
14912
14951
  *
14913
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14914
- * classifier labels with one of them. No window, no percentage:
14915
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14916
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14917
- * the analyzer's (`classificationMinScore`, per device) a label only
14918
- * reaches this condition if the classifier was already confident enough.
14952
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14953
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14954
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
14955
+ * frames is the wrong question for a classifier that labels 1–3 frames
14956
+ * per episode. The count window is the brake that drops a single-frame
14957
+ * false positive; the rule's own `throttle` cooldown is the other. The
14958
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14959
+ * per device) — a label only reaches this condition if the classifier was
14960
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14919
14961
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14920
14962
  * the condition: at least `hitPercent`% of the samples over
14921
14963
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14942,14 +14984,22 @@ var NcOccupancyConditionSchema = object({
14942
14984
  * an operator who typed `dog` mean the same thing.
14943
14985
  */
14944
14986
  var NcAudioConditionSchema = object({
14945
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14987
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14946
14988
  labels: array(string().min(1)).min(1).optional(),
14947
14989
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14948
14990
  dbThreshold: number().min(-96).max(0).optional(),
14949
14991
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14950
14992
  hitPercent: number().int().min(1).max(100).default(60),
14951
14993
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14952
- samplingSeconds: number().int().min(1).max(300).default(10)
14994
+ samplingSeconds: number().int().min(1).max(300).default(10),
14995
+ /**
14996
+ * LABEL MODE: how many labelled frames must land inside
14997
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14998
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14999
+ */
15000
+ confirmHits: number().int().min(1).max(20).optional(),
15001
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
15002
+ confirmWindowSec: number().int().min(1).max(60).optional()
14953
15003
  });
14954
15004
  /**
14955
15005
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17323,6 +17373,46 @@ var RecentTracksPageSchema = object({
17323
17373
  /** Cursor for the next page, or null when this page is the last. */
17324
17374
  nextCursor: string().nullable()
17325
17375
  });
17376
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17377
+ var LIST_GROUPS_MAX_LIMIT = 100;
17378
+ var AnalyticsGroupRecordSchema = object({
17379
+ id: string(),
17380
+ deviceId: number().int(),
17381
+ openedAt: number().int(),
17382
+ closedAt: number().int(),
17383
+ timestamp: number().int(),
17384
+ memberCount: number().int(),
17385
+ memberTrackIds: array(string()).readonly(),
17386
+ className: string(),
17387
+ classes: array(string()).readonly(),
17388
+ /** Relative event-media path, or null when the group has no picture yet. */
17389
+ mediaUrl: string().nullable(),
17390
+ singleton: boolean()
17391
+ });
17392
+ var AnalyticsGroupMemberSchema = object({
17393
+ trackId: string(),
17394
+ deviceId: number().int(),
17395
+ className: string(),
17396
+ firstSeen: number().int(),
17397
+ lastSeen: number().int(),
17398
+ mediaUrl: string().nullable()
17399
+ });
17400
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17401
+ var ListGroupsQueryInput = object({
17402
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17403
+ deviceIds: array(number()),
17404
+ /** Window lower bound on `closedAt` (inclusive). */
17405
+ since: number().optional(),
17406
+ /** Window upper bound on `openedAt` (inclusive). */
17407
+ until: number().optional(),
17408
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17409
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17410
+ cursor: string().optional()
17411
+ });
17412
+ var ListGroupsPageSchema = object({
17413
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17414
+ nextCursor: string().nullable()
17415
+ });
17326
17416
  var KeyEventQueryInput = object({
17327
17417
  deviceId: number(),
17328
17418
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17398,7 +17488,9 @@ var TrackCascadeCountsSchema = object({
17398
17488
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17399
17489
  plates: number().int(),
17400
17490
  /** Per-track CLIP search vectors removed (best-effort). */
17401
- embeddings: number().int()
17491
+ embeddings: number().int(),
17492
+ /** Group membership + group rows removed with their last member (best-effort). */
17493
+ groups: number().int()
17402
17494
  });
17403
17495
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17404
17496
  var DiskReconcileCountsSchema = object({
@@ -17544,7 +17636,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17544
17636
  * stationary registry). Default false: the timeline lists passages,
17545
17637
  * not parking records (operator decision, 2026-08-15). */
17546
17638
  includeStationary: boolean().optional()
17547
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17639
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17640
+ deviceId: number(),
17641
+ groupId: string().min(1)
17642
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17548
17643
  kind: "mutation",
17549
17644
  auth: "admin"
17550
17645
  }), 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({
@@ -17854,7 +17949,8 @@ var PipelineModelOptionSchema = object({
17854
17949
  sizeMB: number()
17855
17950
  })),
17856
17951
  group: ModelVariantGroupSchema.optional(),
17857
- legacy: boolean().optional()
17952
+ legacy: boolean().optional(),
17953
+ provider: ModelProviderIdSchema.optional()
17858
17954
  });
17859
17955
  var ConfigFieldBridge = custom$2();
17860
17956
  var PipelineAddonSchemaSchema = object({
@@ -26055,7 +26151,12 @@ var PlateInfoSchema = object({
26055
26151
  plateBbox: BoundingBoxSchema.optional(),
26056
26152
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
26057
26153
  keyFrameMediaKey: string().optional(),
26058
- base64: string().optional()
26154
+ base64: string().optional(),
26155
+ /**
26156
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
26157
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
26158
+ */
26159
+ cropUrl: string().optional()
26059
26160
  });
26060
26161
  var MediaFileLiteSchema = object({
26061
26162
  key: string(),
@@ -32031,6 +32132,12 @@ Object.freeze({
32031
32132
  addonId: null,
32032
32133
  access: "view"
32033
32134
  },
32135
+ "deviceManager.getBindingsBatch": {
32136
+ capName: "device-manager",
32137
+ capScope: "system",
32138
+ addonId: null,
32139
+ access: "view"
32140
+ },
32034
32141
  "deviceManager.getChildren": {
32035
32142
  capName: "device-manager",
32036
32143
  capScope: "system",
@@ -32091,6 +32198,12 @@ Object.freeze({
32091
32198
  addonId: null,
32092
32199
  access: "view"
32093
32200
  },
32201
+ "deviceManager.getLinkedDevicesBatch": {
32202
+ capName: "device-manager",
32203
+ capScope: "system",
32204
+ addonId: null,
32205
+ access: "view"
32206
+ },
32094
32207
  "deviceManager.getRoleDisplayDefaults": {
32095
32208
  capName: "device-manager",
32096
32209
  capScope: "system",
@@ -33861,6 +33974,12 @@ Object.freeze({
33861
33974
  addonId: null,
33862
33975
  access: "view"
33863
33976
  },
33977
+ "pipelineAnalytics.getGroup": {
33978
+ capName: "pipeline-analytics",
33979
+ capScope: "device",
33980
+ addonId: null,
33981
+ access: "view"
33982
+ },
33864
33983
  "pipelineAnalytics.getKeyEvents": {
33865
33984
  capName: "pipeline-analytics",
33866
33985
  capScope: "device",
@@ -33945,6 +34064,12 @@ Object.freeze({
33945
34064
  addonId: null,
33946
34065
  access: "view"
33947
34066
  },
34067
+ "pipelineAnalytics.listGroups": {
34068
+ capName: "pipeline-analytics",
34069
+ capScope: "device",
34070
+ addonId: null,
34071
+ access: "view"
34072
+ },
33948
34073
  "pipelineAnalytics.listOpsLog": {
33949
34074
  capName: "pipeline-analytics",
33950
34075
  capScope: "device",
@@ -36726,6 +36851,11 @@ Object.freeze({
36726
36851
  form: "single",
36727
36852
  optional: false
36728
36853
  }],
36854
+ "deviceManager.getBindingsBatch": [{
36855
+ name: "deviceIds",
36856
+ form: "array",
36857
+ optional: false
36858
+ }],
36729
36859
  "deviceManager.getChildren": [{
36730
36860
  name: "parentDeviceId",
36731
36861
  form: "single",
@@ -36771,6 +36901,11 @@ Object.freeze({
36771
36901
  form: "single",
36772
36902
  optional: false
36773
36903
  }],
36904
+ "deviceManager.getLinkedDevicesBatch": [{
36905
+ name: "deviceIds",
36906
+ form: "array",
36907
+ optional: false
36908
+ }],
36774
36909
  "deviceManager.getSettingsSchema": [{
36775
36910
  name: "deviceId",
36776
36911
  form: "single",
@@ -36791,6 +36926,11 @@ Object.freeze({
36791
36926
  form: "single",
36792
36927
  optional: false
36793
36928
  }],
36929
+ "deviceManager.listAll": [{
36930
+ name: "deviceIds",
36931
+ form: "array",
36932
+ optional: true
36933
+ }],
36794
36934
  "deviceManager.loadConfig": [{
36795
36935
  name: "deviceId",
36796
36936
  form: "single",
@@ -37364,6 +37504,11 @@ Object.freeze({
37364
37504
  form: "single",
37365
37505
  optional: false
37366
37506
  }],
37507
+ "pipelineAnalytics.getGroup": [{
37508
+ name: "deviceId",
37509
+ form: "single",
37510
+ optional: false
37511
+ }],
37367
37512
  "pipelineAnalytics.getKeyEvents": [{
37368
37513
  name: "deviceId",
37369
37514
  form: "single",
@@ -37419,6 +37564,11 @@ Object.freeze({
37419
37564
  form: "array",
37420
37565
  optional: false
37421
37566
  }],
37567
+ "pipelineAnalytics.listGroups": [{
37568
+ name: "deviceIds",
37569
+ form: "array",
37570
+ optional: false
37571
+ }],
37422
37572
  "pipelineAnalytics.listOpsLog": [{
37423
37573
  name: "deviceId",
37424
37574
  form: "single",
package/dist/addon.mjs CHANGED
@@ -5823,6 +5823,13 @@ var BaseAddon = class {
5823
5823
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5824
5824
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5825
5825
  _registeredCapNames = [];
5826
+ /**
5827
+ * True only after `readAddonStore` actually answered. Constructor
5828
+ * defaults look like stored config when the store is down — a forked
5829
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5830
+ * mode, 2026-08-25) is not "the operator chose this".
5831
+ */
5832
+ settingsStoreReady = false;
5826
5833
  /** Default config values. Provided via constructor. */
5827
5834
  defaults;
5828
5835
  constructor(defaults) {
@@ -6223,7 +6230,9 @@ var BaseAddon = class {
6223
6230
  ];
6224
6231
  let lastErr;
6225
6232
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6226
- return await settings.readAddonStore() ?? {};
6233
+ const stored = await settings.readAddonStore() ?? {};
6234
+ this.settingsStoreReady = true;
6235
+ return stored;
6227
6236
  } catch (err) {
6228
6237
  lastErr = err;
6229
6238
  const msg = err instanceof Error ? err.message : String(err);
@@ -6231,6 +6240,7 @@ var BaseAddon = class {
6231
6240
  if (attempt === delaysMs.length) break;
6232
6241
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6233
6242
  }
6243
+ this.settingsStoreReady = false;
6234
6244
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6235
6245
  return {};
6236
6246
  }
@@ -8321,6 +8331,12 @@ var ModelVariantGroupSchema = object({
8321
8331
  */
8322
8332
  resolution: number().int().positive().optional()
8323
8333
  });
8334
+ var ModelProviderIdSchema = _enum([
8335
+ "camstack",
8336
+ "frigate",
8337
+ "scrypted",
8338
+ "custom"
8339
+ ]);
8324
8340
  var ModelCatalogEntrySchema = object({
8325
8341
  id: string(),
8326
8342
  name: string(),
@@ -8418,6 +8434,12 @@ var ModelCatalogEntrySchema = object({
8418
8434
  */
8419
8435
  group: ModelVariantGroupSchema.optional(),
8420
8436
  /**
8437
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8438
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8439
+ * persisted before this field existed (`inferModelProvider` fills those).
8440
+ */
8441
+ provider: ModelProviderIdSchema.optional(),
8442
+ /**
8421
8443
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8422
8444
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8423
8445
  * labels already ARE the CamStack macros (Scrypted identity map).
@@ -12221,6 +12243,27 @@ var LinkedDeviceSchema = object({
12221
12243
  features: array(string()),
12222
12244
  producesTrackedEvents: boolean().optional()
12223
12245
  });
12246
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12247
+ * The batch answer needs the tag; the single-device answer already has it
12248
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12249
+ var LinkedDevicesForDeviceSchema = object({
12250
+ deviceId: number(),
12251
+ mode: LinkedDevicesModeSchema,
12252
+ devices: array(LinkedDeviceSchema)
12253
+ });
12254
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12255
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12256
+ * object literal is exactly how the three drift apart. */
12257
+ var DeviceBindingsForDeviceSchema = object({
12258
+ deviceId: number(),
12259
+ entries: array(object({
12260
+ capName: string(),
12261
+ kind: _enum(["native", "wrapped"]),
12262
+ providerAddonId: string(),
12263
+ providerNodeId: string(),
12264
+ nativeAddonId: string()
12265
+ }))
12266
+ });
12224
12267
  var SavedDeviceRowSchema = object({
12225
12268
  /** Numeric id reserved at allocateDeviceId time. */
12226
12269
  id: number(),
@@ -12446,11 +12489,25 @@ method(object({
12446
12489
  projection: _enum(["full", "slim"]).optional(),
12447
12490
  /** Return only camera devices. Filtering server-side instead of
12448
12491
  * shipping 293 rows to find 12. */
12449
- isCamera: boolean().optional()
12492
+ isCamera: boolean().optional(),
12493
+ /**
12494
+ * Return only these device ids. For the caller that already KNOWS the
12495
+ * handful it wants and needs a field the id-bearing answer does not
12496
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12497
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12498
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12499
+ * refetches on the reconcile interval, on a phone.
12500
+ *
12501
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12502
+ * keys rather than rejecting them (verified against the live hub
12503
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12504
+ * it answers today and the caller filters as it already does.
12505
+ */
12506
+ deviceIds: array(number()).optional()
12450
12507
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12451
12508
  mode: LinkedDevicesModeSchema,
12452
12509
  devices: array(LinkedDeviceSchema)
12453
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12510
+ })), 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({
12454
12511
  deviceId: number(),
12455
12512
  values: record(string(), unknown())
12456
12513
  }), object({ success: literal(true) }), {
@@ -12477,25 +12534,7 @@ method(object({
12477
12534
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12478
12535
  kind: "mutation",
12479
12536
  auth: "admin"
12480
- }), method(object({ deviceId: number() }), object({
12481
- deviceId: number(),
12482
- entries: array(object({
12483
- capName: string(),
12484
- kind: _enum(["native", "wrapped"]),
12485
- providerAddonId: string(),
12486
- providerNodeId: string(),
12487
- nativeAddonId: string()
12488
- }))
12489
- })), method(object({}), array(object({
12490
- deviceId: number(),
12491
- entries: array(object({
12492
- capName: string(),
12493
- kind: _enum(["native", "wrapped"]),
12494
- providerAddonId: string(),
12495
- providerNodeId: string(),
12496
- nativeAddonId: string()
12497
- }))
12498
- }))), method(object({
12537
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12499
12538
  deviceId: number(),
12500
12539
  capName: string(),
12501
12540
  wrapperAddonId: string(),
@@ -14905,12 +14944,15 @@ var NcOccupancyConditionSchema = object({
14905
14944
  * there is no second switch that can disagree with the first and every rule
14906
14945
  * authored before the decision migrates for free (`audioModeOf`):
14907
14946
  *
14908
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14909
- * classifier labels with one of them. No window, no percentage:
14910
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14911
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14912
- * the analyzer's (`classificationMinScore`, per device) a label only
14913
- * reaches this condition if the classifier was already confident enough.
14947
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14948
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14949
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
14950
+ * frames is the wrong question for a classifier that labels 1–3 frames
14951
+ * per episode. The count window is the brake that drops a single-frame
14952
+ * false positive; the rule's own `throttle` cooldown is the other. The
14953
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14954
+ * per device) — a label only reaches this condition if the classifier was
14955
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14914
14956
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14915
14957
  * the condition: at least `hitPercent`% of the samples over
14916
14958
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14937,14 +14979,22 @@ var NcOccupancyConditionSchema = object({
14937
14979
  * an operator who typed `dog` mean the same thing.
14938
14980
  */
14939
14981
  var NcAudioConditionSchema = object({
14940
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14982
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14941
14983
  labels: array(string().min(1)).min(1).optional(),
14942
14984
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14943
14985
  dbThreshold: number().min(-96).max(0).optional(),
14944
14986
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14945
14987
  hitPercent: number().int().min(1).max(100).default(60),
14946
14988
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14947
- samplingSeconds: number().int().min(1).max(300).default(10)
14989
+ samplingSeconds: number().int().min(1).max(300).default(10),
14990
+ /**
14991
+ * LABEL MODE: how many labelled frames must land inside
14992
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14993
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14994
+ */
14995
+ confirmHits: number().int().min(1).max(20).optional(),
14996
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14997
+ confirmWindowSec: number().int().min(1).max(60).optional()
14948
14998
  });
14949
14999
  /**
14950
15000
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17318,6 +17368,46 @@ var RecentTracksPageSchema = object({
17318
17368
  /** Cursor for the next page, or null when this page is the last. */
17319
17369
  nextCursor: string().nullable()
17320
17370
  });
17371
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17372
+ var LIST_GROUPS_MAX_LIMIT = 100;
17373
+ var AnalyticsGroupRecordSchema = object({
17374
+ id: string(),
17375
+ deviceId: number().int(),
17376
+ openedAt: number().int(),
17377
+ closedAt: number().int(),
17378
+ timestamp: number().int(),
17379
+ memberCount: number().int(),
17380
+ memberTrackIds: array(string()).readonly(),
17381
+ className: string(),
17382
+ classes: array(string()).readonly(),
17383
+ /** Relative event-media path, or null when the group has no picture yet. */
17384
+ mediaUrl: string().nullable(),
17385
+ singleton: boolean()
17386
+ });
17387
+ var AnalyticsGroupMemberSchema = object({
17388
+ trackId: string(),
17389
+ deviceId: number().int(),
17390
+ className: string(),
17391
+ firstSeen: number().int(),
17392
+ lastSeen: number().int(),
17393
+ mediaUrl: string().nullable()
17394
+ });
17395
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17396
+ var ListGroupsQueryInput = object({
17397
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17398
+ deviceIds: array(number()),
17399
+ /** Window lower bound on `closedAt` (inclusive). */
17400
+ since: number().optional(),
17401
+ /** Window upper bound on `openedAt` (inclusive). */
17402
+ until: number().optional(),
17403
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17404
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17405
+ cursor: string().optional()
17406
+ });
17407
+ var ListGroupsPageSchema = object({
17408
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17409
+ nextCursor: string().nullable()
17410
+ });
17321
17411
  var KeyEventQueryInput = object({
17322
17412
  deviceId: number(),
17323
17413
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17393,7 +17483,9 @@ var TrackCascadeCountsSchema = object({
17393
17483
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17394
17484
  plates: number().int(),
17395
17485
  /** Per-track CLIP search vectors removed (best-effort). */
17396
- embeddings: number().int()
17486
+ embeddings: number().int(),
17487
+ /** Group membership + group rows removed with their last member (best-effort). */
17488
+ groups: number().int()
17397
17489
  });
17398
17490
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17399
17491
  var DiskReconcileCountsSchema = object({
@@ -17539,7 +17631,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17539
17631
  * stationary registry). Default false: the timeline lists passages,
17540
17632
  * not parking records (operator decision, 2026-08-15). */
17541
17633
  includeStationary: boolean().optional()
17542
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17634
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17635
+ deviceId: number(),
17636
+ groupId: string().min(1)
17637
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17543
17638
  kind: "mutation",
17544
17639
  auth: "admin"
17545
17640
  }), 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({
@@ -17849,7 +17944,8 @@ var PipelineModelOptionSchema = object({
17849
17944
  sizeMB: number()
17850
17945
  })),
17851
17946
  group: ModelVariantGroupSchema.optional(),
17852
- legacy: boolean().optional()
17947
+ legacy: boolean().optional(),
17948
+ provider: ModelProviderIdSchema.optional()
17853
17949
  });
17854
17950
  var ConfigFieldBridge = custom$2();
17855
17951
  var PipelineAddonSchemaSchema = object({
@@ -26050,7 +26146,12 @@ var PlateInfoSchema = object({
26050
26146
  plateBbox: BoundingBoxSchema.optional(),
26051
26147
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
26052
26148
  keyFrameMediaKey: string().optional(),
26053
- base64: string().optional()
26149
+ base64: string().optional(),
26150
+ /**
26151
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
26152
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
26153
+ */
26154
+ cropUrl: string().optional()
26054
26155
  });
26055
26156
  var MediaFileLiteSchema = object({
26056
26157
  key: string(),
@@ -32026,6 +32127,12 @@ Object.freeze({
32026
32127
  addonId: null,
32027
32128
  access: "view"
32028
32129
  },
32130
+ "deviceManager.getBindingsBatch": {
32131
+ capName: "device-manager",
32132
+ capScope: "system",
32133
+ addonId: null,
32134
+ access: "view"
32135
+ },
32029
32136
  "deviceManager.getChildren": {
32030
32137
  capName: "device-manager",
32031
32138
  capScope: "system",
@@ -32086,6 +32193,12 @@ Object.freeze({
32086
32193
  addonId: null,
32087
32194
  access: "view"
32088
32195
  },
32196
+ "deviceManager.getLinkedDevicesBatch": {
32197
+ capName: "device-manager",
32198
+ capScope: "system",
32199
+ addonId: null,
32200
+ access: "view"
32201
+ },
32089
32202
  "deviceManager.getRoleDisplayDefaults": {
32090
32203
  capName: "device-manager",
32091
32204
  capScope: "system",
@@ -33856,6 +33969,12 @@ Object.freeze({
33856
33969
  addonId: null,
33857
33970
  access: "view"
33858
33971
  },
33972
+ "pipelineAnalytics.getGroup": {
33973
+ capName: "pipeline-analytics",
33974
+ capScope: "device",
33975
+ addonId: null,
33976
+ access: "view"
33977
+ },
33859
33978
  "pipelineAnalytics.getKeyEvents": {
33860
33979
  capName: "pipeline-analytics",
33861
33980
  capScope: "device",
@@ -33940,6 +34059,12 @@ Object.freeze({
33940
34059
  addonId: null,
33941
34060
  access: "view"
33942
34061
  },
34062
+ "pipelineAnalytics.listGroups": {
34063
+ capName: "pipeline-analytics",
34064
+ capScope: "device",
34065
+ addonId: null,
34066
+ access: "view"
34067
+ },
33943
34068
  "pipelineAnalytics.listOpsLog": {
33944
34069
  capName: "pipeline-analytics",
33945
34070
  capScope: "device",
@@ -36721,6 +36846,11 @@ Object.freeze({
36721
36846
  form: "single",
36722
36847
  optional: false
36723
36848
  }],
36849
+ "deviceManager.getBindingsBatch": [{
36850
+ name: "deviceIds",
36851
+ form: "array",
36852
+ optional: false
36853
+ }],
36724
36854
  "deviceManager.getChildren": [{
36725
36855
  name: "parentDeviceId",
36726
36856
  form: "single",
@@ -36766,6 +36896,11 @@ Object.freeze({
36766
36896
  form: "single",
36767
36897
  optional: false
36768
36898
  }],
36899
+ "deviceManager.getLinkedDevicesBatch": [{
36900
+ name: "deviceIds",
36901
+ form: "array",
36902
+ optional: false
36903
+ }],
36769
36904
  "deviceManager.getSettingsSchema": [{
36770
36905
  name: "deviceId",
36771
36906
  form: "single",
@@ -36786,6 +36921,11 @@ Object.freeze({
36786
36921
  form: "single",
36787
36922
  optional: false
36788
36923
  }],
36924
+ "deviceManager.listAll": [{
36925
+ name: "deviceIds",
36926
+ form: "array",
36927
+ optional: true
36928
+ }],
36789
36929
  "deviceManager.loadConfig": [{
36790
36930
  name: "deviceId",
36791
36931
  form: "single",
@@ -37359,6 +37499,11 @@ Object.freeze({
37359
37499
  form: "single",
37360
37500
  optional: false
37361
37501
  }],
37502
+ "pipelineAnalytics.getGroup": [{
37503
+ name: "deviceId",
37504
+ form: "single",
37505
+ optional: false
37506
+ }],
37362
37507
  "pipelineAnalytics.getKeyEvents": [{
37363
37508
  name: "deviceId",
37364
37509
  form: "single",
@@ -37414,6 +37559,11 @@ Object.freeze({
37414
37559
  form: "array",
37415
37560
  optional: false
37416
37561
  }],
37562
+ "pipelineAnalytics.listGroups": [{
37563
+ name: "deviceIds",
37564
+ form: "array",
37565
+ optional: false
37566
+ }],
37417
37567
  "pipelineAnalytics.listOpsLog": [{
37418
37568
  name: "deviceId",
37419
37569
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.49",
3
+ "version": "1.2.50",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",