@camstack/addon-provider-reolink 1.2.70 → 1.2.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -8308,6 +8308,20 @@ var RelocateJobSchema = object({
8308
8308
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8309
8309
  */
8310
8310
  rowsReconciled: number().int().nonnegative().optional(),
8311
+ /**
8312
+ * Rows this run FORGOT because the file they name is not on disk.
8313
+ *
8314
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8315
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8316
+ * and the durable row is dropped through the same channel eviction uses. It
8317
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8318
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8319
+ * the same failure as one that quietly skips them (D295).
8320
+ *
8321
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8322
+ * ledger claimed 5.65 GB of footage that no longer existed.
8323
+ */
8324
+ rowsForgotten: number().int().nonnegative().optional(),
8311
8325
  startedAt: number(),
8312
8326
  finishedAt: number().nullable(),
8313
8327
  error: string().nullable()
@@ -8671,6 +8685,91 @@ var RelocateResidueSchema = object({
8671
8685
  segments: number().int().nonnegative(),
8672
8686
  bytes: number().int().nonnegative()
8673
8687
  }).nullable();
8688
+ /**
8689
+ * Ask one location whether its durable hour rows describe the disk — the walk
8690
+ * (D319).
8691
+ *
8692
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8693
+ * missing tool is the question, and the dry run is how they sanity-check the
8694
+ * destructive run before authorising it.
8695
+ */
8696
+ var LedgerWalkInputSchema = object({
8697
+ locationId: string().min(1),
8698
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8699
+ apply: boolean().optional(),
8700
+ /** Narrow to one camera. */
8701
+ deviceId: number().int().positive().optional(),
8702
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8703
+ profiles: array(string().min(1)).optional()
8704
+ });
8705
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8706
+ var LedgerWalkRefusalSchema = _enum([
8707
+ "location-unknown",
8708
+ "source-writable",
8709
+ "no-ledger",
8710
+ "archive-unreadable",
8711
+ "anchor-absent",
8712
+ "anchor-unreadable",
8713
+ "anchor-moved"
8714
+ ]);
8715
+ _enum([
8716
+ "live-tail",
8717
+ "listing-error",
8718
+ "path-mismatch",
8719
+ "durable-refused"
8720
+ ]);
8721
+ /** Every skip reason, always present, always a number — so a reason that never
8722
+ * fired reports as zero rather than absent and the report shape is constant
8723
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8724
+ var LedgerWalkSkipCountsSchema = object({
8725
+ "live-tail": number().int().nonnegative(),
8726
+ "listing-error": number().int().nonnegative(),
8727
+ "path-mismatch": number().int().nonnegative(),
8728
+ "durable-refused": number().int().nonnegative()
8729
+ });
8730
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8731
+ var LedgerWalkDeviceReportSchema = object({
8732
+ deviceId: number().int(),
8733
+ hoursWalked: number().int().nonnegative(),
8734
+ hoursMissing: number().int().nonnegative(),
8735
+ ghostSegments: number().int().nonnegative(),
8736
+ ghostBytes: number().int().nonnegative(),
8737
+ forgottenSegments: number().int().nonnegative(),
8738
+ orphanFiles: number().int().nonnegative()
8739
+ });
8740
+ /**
8741
+ * What one walk claimed, listed, found and (only when armed) forgot.
8742
+ *
8743
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8744
+ * walk that saw a fraction of the location is visible in its own report rather
8745
+ * than in the absence of one.
8746
+ */
8747
+ var LedgerWalkReportSchema = object({
8748
+ locationId: string(),
8749
+ applied: boolean(),
8750
+ refused: LedgerWalkRefusalSchema.nullable(),
8751
+ archiveSegments: number().int().nonnegative().nullable(),
8752
+ archiveBytes: number().int().nonnegative().nullable(),
8753
+ hoursClaimed: number().int().nonnegative(),
8754
+ hoursWalked: number().int().nonnegative(),
8755
+ hoursMissing: number().int().nonnegative(),
8756
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8757
+ listings: number().int().nonnegative(),
8758
+ segmentsClaimed: number().int().nonnegative(),
8759
+ ghostSegments: number().int().nonnegative(),
8760
+ ghostBytes: number().int().nonnegative(),
8761
+ ghostHoursWhole: number().int().nonnegative(),
8762
+ forgottenSegments: number().int().nonnegative(),
8763
+ forgottenBytes: number().int().nonnegative(),
8764
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8765
+ orphanFiles: number().int().nonnegative(),
8766
+ orphanSample: array(string()).readonly(),
8767
+ hoursSkipped: number().int().nonnegative(),
8768
+ skippedByReason: LedgerWalkSkipCountsSchema,
8769
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8770
+ bounded: boolean(),
8771
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8772
+ });
8674
8773
  /** How many rows a media pass would still act on against a given target — the
8675
8774
  * media lane's denominator AND its residue, from ONE derivation so the two can
8676
8775
  * never disagree. `null` = the count could not be taken. */
@@ -19619,13 +19718,15 @@ var ListGroupsPageSchema = object({
19619
19718
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19620
19719
  nextCursor: string().nullable()
19621
19720
  });
19721
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19722
+ var KEY_EVENTS_MAX_LIMIT = 200;
19622
19723
  var KeyEventQueryInput = object({
19623
19724
  deviceId: number(),
19624
19725
  /** Window lower bound (track firstSeen ≥ since). */
19625
19726
  since: number(),
19626
19727
  /** Window upper bound (track firstSeen ≤ until). */
19627
19728
  until: number(),
19628
- limit: number().int().min(1).max(200).default(50),
19729
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19629
19730
  /** Drop tracks scoring below this importance. */
19630
19731
  minImportance: number().min(0).max(1).optional(),
19631
19732
  /** Restrict to a single class (e.g. 'person'). */
@@ -19647,6 +19748,32 @@ var KeyEventSchema = object({
19647
19748
  ...TrackFlagFields,
19648
19749
  ...TrackRetrainFields
19649
19750
  });
19751
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19752
+ var KeyEventBatchQueryInput = object({
19753
+ deviceIds: array(number()).min(1).max(200),
19754
+ since: number(),
19755
+ until: number(),
19756
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19757
+ * across the set, which would let a busy camera starve a quiet one of its
19758
+ * rows and change what the merged feed contains. */
19759
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19760
+ minImportance: number().min(0).max(1).optional(),
19761
+ classFilter: string().optional()
19762
+ });
19763
+ /**
19764
+ * One camera's key events in a batch answer.
19765
+ *
19766
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19767
+ * error rather than throwing, so a camera whose store read failed and one with
19768
+ * no events in the window were ALREADY indistinguishable per camera — the
19769
+ * batch does not make that worse, and the row keeps the deviceId the single
19770
+ * method's output never carried (the caller used to stamp it from the fan-out
19771
+ * key, which only worked because there was one query per camera).
19772
+ */
19773
+ var KeyEventsForDeviceSchema = object({
19774
+ deviceId: number(),
19775
+ events: array(KeyEventSchema).readonly()
19776
+ });
19650
19777
  object({
19651
19778
  trackId: string(),
19652
19779
  className: string(),
@@ -19718,6 +19845,50 @@ var EventStoreFootprintSchema = object({
19718
19845
  totalBytes: number().int(),
19719
19846
  devices: array(EventStoreDeviceFootprintSchema).readonly()
19720
19847
  });
19848
+ /** Event-media footprint for one {@link MediaFileKind}. */
19849
+ var EventMediaKindFootprintSchema = object({
19850
+ kind: MediaFileKindEnum,
19851
+ /** Media rows of this kind. */
19852
+ rows: number().int(),
19853
+ /** Bytes on disk held by those rows. */
19854
+ bytes: number().int()
19855
+ });
19856
+ /**
19857
+ * The media footprint broken down by KIND — the axis a deletion decision
19858
+ * actually turns on.
19859
+ *
19860
+ * A byte total says how much there is; it cannot say what is safe to remove.
19861
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19862
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19863
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19864
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19865
+ * nothing else, so sizing a deletion means summing per kind.
19866
+ *
19867
+ * ## Why `unaccounted*` exists
19868
+ *
19869
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19870
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19871
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19872
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19873
+ * retired code path, or by a version that knew a kind this one does not) would
19874
+ * otherwise vanish from the total silently, and an operator would delete
19875
+ * against a denominator smaller than the disk.
19876
+ *
19877
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19878
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19879
+ */
19880
+ var EventMediaKindBreakdownSchema = object({
19881
+ /** Every media row in scope, from one unfiltered aggregate. */
19882
+ totalRows: number().int(),
19883
+ /** Every media byte in scope, from that same aggregate. */
19884
+ totalBytes: number().int(),
19885
+ /** Per-kind footprint, ordered by bytes descending. */
19886
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19887
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19888
+ unaccountedRows: number().int(),
19889
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19890
+ unaccountedBytes: number().int()
19891
+ });
19721
19892
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
19722
19893
  var EventPruneCountsSchema = object({
19723
19894
  motion: number().int(),
@@ -19872,7 +20043,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19872
20043
  until: number().optional(),
19873
20044
  kinds: array(string()).optional(),
19874
20045
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19875
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
20046
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19876
20047
  deviceId: number(),
19877
20048
  since: number(),
19878
20049
  until: number(),
@@ -19921,6 +20092,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19921
20092
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19922
20093
  kind: "query",
19923
20094
  auth: "admin"
20095
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
20096
+ kind: "query",
20097
+ auth: "admin"
19924
20098
  }), method(object({
19925
20099
  olderThanMs: number(),
19926
20100
  reason: OpsLogReasonSchema.optional()
@@ -21976,6 +22150,20 @@ method(object({
21976
22150
  error: string().optional()
21977
22151
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21978
22152
  providerId: string(),
22153
+ /**
22154
+ * The location this config is an UNSAVED edit of, when there is one.
22155
+ *
22156
+ * `listLocations` replaces every declared secret with the redaction
22157
+ * sentinel, so the edit modal's form state holds the sentinel for any
22158
+ * credential the operator did not retype — and posting that here
22159
+ * without a way to resolve it makes the provider try to authenticate
22160
+ * as `__camstack_redacted__` and report the operator's own working
22161
+ * password as wrong. Given this id, the orchestrator restores each
22162
+ * sentinel from the stored config (same rule as `upsertLocation`)
22163
+ * before dispatching. Omitted by the "Add location" wizard, where
22164
+ * every value was typed just now and nothing is stored yet.
22165
+ */
22166
+ locationId: string().optional(),
21979
22167
  config: record(string(), unknown())
21980
22168
  }), object({
21981
22169
  ok: boolean(),
@@ -29413,6 +29601,9 @@ method(object({
29413
29601
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29414
29602
  kind: "query",
29415
29603
  auth: "admin"
29604
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29605
+ kind: "mutation",
29606
+ auth: "admin"
29416
29607
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29417
29608
  kind: "mutation",
29418
29609
  auth: "admin"
@@ -29804,6 +29995,25 @@ var SceneMonitorStatusSchema = object({
29804
29995
  monitors: array(SceneMonitorSchema),
29805
29996
  lastFetchedAt: number()
29806
29997
  });
29998
+ /**
29999
+ * One camera's row in a `listScenesBatch` answer.
30000
+ *
30001
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
30002
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
30003
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
30004
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
30005
+ * configured. Fanned out per camera the difference was visible — one query
30006
+ * errored while the others resolved — and a batch that returned only the rows
30007
+ * it managed would have destroyed it, silently, by making an unreachable camera
30008
+ * indistinguishable from one that answered `monitors: []`.
30009
+ *
30010
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
30011
+ * could not be read"; `status.monitors: []` means "read, and it has none".
30012
+ */
30013
+ var SceneMonitorStatusForDeviceSchema = object({
30014
+ deviceId: number(),
30015
+ status: SceneMonitorStatusSchema.nullable()
30016
+ });
29807
30017
  var sceneMonitorCapability = {
29808
30018
  name: "scene-monitor",
29809
30019
  scope: "device",
@@ -29813,6 +30023,22 @@ var sceneMonitorCapability = {
29813
30023
  deviceTypes: [DeviceType.Camera],
29814
30024
  methods: {
29815
30025
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
30026
+ /**
30027
+ * The same answer, for a SET of cameras, in one round trip.
30028
+ *
30029
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
30030
+ * poll behind the push slice. Fanned out client-side that was one query
30031
+ * per camera — 29 round trips through the browser, the hub and the
30032
+ * post-analysis runner every 30 seconds to read an in-memory map the
30033
+ * owner had already merged. The work is unchanged (`statusFor` per
30034
+ * device, all in-process at the owner); what collapses is the transport.
30035
+ *
30036
+ * A camera that cannot answer still gets a row, with `status: null` —
30037
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
30038
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
30039
+ * tell which two are missing, or that any are.
30040
+ */
30041
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29816
30042
  createScene: method(object({
29817
30043
  deviceId: number(),
29818
30044
  label: string(),
@@ -31846,6 +32072,27 @@ var CameraOccupancySnapshotSchema = object({
31846
32072
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31847
32073
  });
31848
32074
  /**
32075
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
32076
+ *
32077
+ * THREE outcomes, and the single-camera method could only express two of them
32078
+ * because `snapshot: null` was already spoken for:
32079
+ *
32080
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
32081
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
32082
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
32083
+ * - `read: 'unreadable'` — the owner could not answer for this
32084
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
32085
+ *
32086
+ * Collapsing the last two is the failure this field exists to prevent: a
32087
+ * hydration that threw would otherwise render as an empty Stationary section,
32088
+ * which is a definite claim about a camera nobody could read.
32089
+ */
32090
+ var CameraOccupancySnapshotForDeviceSchema = object({
32091
+ deviceId: number(),
32092
+ read: _enum(["read", "unreadable"]),
32093
+ snapshot: CameraOccupancySnapshotSchema.nullable()
32094
+ });
32095
+ /**
31849
32096
  * Time-series resolution. The history methods return one bucket per
31850
32097
  * step over the requested range. Smaller resolutions cost more
31851
32098
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31902,6 +32149,20 @@ var zoneAnalyticsCapability = {
31902
32149
  * (no inference result emitted since boot or since binding was
31903
32150
  * activated). */
31904
32151
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
32152
+ /**
32153
+ * The same snapshot, for a SET of cameras, in one round trip.
32154
+ *
32155
+ * The Events page's Stationary section polls this every 15s for every
32156
+ * selected camera. Fanned out client-side that is one query per camera to
32157
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
32158
+ * costs everything. Batched, N transports become one and the per-device
32159
+ * work is unchanged.
32160
+ *
32161
+ * Every requested deviceId gets a row, tagged `read` — see
32162
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
32163
+ * not answer for is `'unreadable'`, never an empty reading.
32164
+ */
32165
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31905
32166
  /** Time-series object count inside one zone. `className` optional —
31906
32167
  * omit to count every class in the zone. */
31907
32168
  getZoneHistory: method(object({
@@ -36466,6 +36727,12 @@ Object.freeze({
36466
36727
  addonId: null,
36467
36728
  access: "view"
36468
36729
  },
36730
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
36731
+ capName: "pipeline-analytics",
36732
+ capScope: "device",
36733
+ addonId: null,
36734
+ access: "view"
36735
+ },
36469
36736
  "pipelineAnalytics.getEventStoreFootprint": {
36470
36737
  capName: "pipeline-analytics",
36471
36738
  capScope: "device",
@@ -36484,6 +36751,12 @@ Object.freeze({
36484
36751
  addonId: null,
36485
36752
  access: "view"
36486
36753
  },
36754
+ "pipelineAnalytics.getKeyEventsBatch": {
36755
+ capName: "pipeline-analytics",
36756
+ capScope: "device",
36757
+ addonId: null,
36758
+ access: "view"
36759
+ },
36487
36760
  "pipelineAnalytics.getMotionEvents": {
36488
36761
  capName: "pipeline-analytics",
36489
36762
  capScope: "device",
@@ -37660,6 +37933,12 @@ Object.freeze({
37660
37933
  addonId: null,
37661
37934
  access: "view"
37662
37935
  },
37936
+ "recording.reconcileLedgerAgainstDisk": {
37937
+ capName: "recording",
37938
+ capScope: "system",
37939
+ addonId: null,
37940
+ access: "create"
37941
+ },
37663
37942
  "recording.refreshStorageLocationsForMigration": {
37664
37943
  capName: "recording",
37665
37944
  capScope: "system",
@@ -37786,6 +38065,12 @@ Object.freeze({
37786
38065
  addonId: null,
37787
38066
  access: "view"
37788
38067
  },
38068
+ "sceneMonitor.listScenesBatch": {
38069
+ capName: "scene-monitor",
38070
+ capScope: "device",
38071
+ addonId: null,
38072
+ access: "view"
38073
+ },
37789
38074
  "sceneMonitor.recheckNow": {
37790
38075
  capName: "scene-monitor",
37791
38076
  capScope: "device",
@@ -39142,6 +39427,12 @@ Object.freeze({
39142
39427
  addonId: null,
39143
39428
  access: "view"
39144
39429
  },
39430
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39431
+ capName: "zone-analytics",
39432
+ capScope: "device",
39433
+ addonId: null,
39434
+ access: "view"
39435
+ },
39145
39436
  "zoneAnalytics.getUnzonedHistory": {
39146
39437
  capName: "zone-analytics",
39147
39438
  capScope: "device",
@@ -40131,6 +40422,11 @@ Object.freeze({
40131
40422
  form: "single",
40132
40423
  optional: false
40133
40424
  }],
40425
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
40426
+ name: "deviceId",
40427
+ form: "single",
40428
+ optional: true
40429
+ }],
40134
40430
  "pipelineAnalytics.getGroup": [{
40135
40431
  name: "deviceId",
40136
40432
  form: "single",
@@ -40141,6 +40437,11 @@ Object.freeze({
40141
40437
  form: "single",
40142
40438
  optional: false
40143
40439
  }],
40440
+ "pipelineAnalytics.getKeyEventsBatch": [{
40441
+ name: "deviceIds",
40442
+ form: "array",
40443
+ optional: false
40444
+ }],
40144
40445
  "pipelineAnalytics.getMotionEvents": [{
40145
40446
  name: "deviceId",
40146
40447
  form: "single",
@@ -40581,6 +40882,11 @@ Object.freeze({
40581
40882
  form: "single",
40582
40883
  optional: false
40583
40884
  }],
40885
+ "recording.reconcileLedgerAgainstDisk": [{
40886
+ name: "deviceId",
40887
+ form: "single",
40888
+ optional: true
40889
+ }],
40584
40890
  "recording.relocateFootage": [{
40585
40891
  name: "deviceId",
40586
40892
  form: "single",
@@ -40646,6 +40952,11 @@ Object.freeze({
40646
40952
  form: "single",
40647
40953
  optional: false
40648
40954
  }],
40955
+ "sceneMonitor.listScenesBatch": [{
40956
+ name: "deviceIds",
40957
+ form: "array",
40958
+ optional: false
40959
+ }],
40649
40960
  "sceneMonitor.recheckNow": [{
40650
40961
  name: "deviceId",
40651
40962
  form: "single",
@@ -40907,6 +41218,11 @@ Object.freeze({
40907
41218
  form: "single",
40908
41219
  optional: false
40909
41220
  }],
41221
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41222
+ name: "deviceIds",
41223
+ form: "array",
41224
+ optional: false
41225
+ }],
40910
41226
  "zoneAnalytics.getUnzonedHistory": [{
40911
41227
  name: "deviceId",
40912
41228
  form: "single",
package/dist/addon.mjs CHANGED
@@ -8303,6 +8303,20 @@ var RelocateJobSchema = object({
8303
8303
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8304
8304
  */
8305
8305
  rowsReconciled: number().int().nonnegative().optional(),
8306
+ /**
8307
+ * Rows this run FORGOT because the file they name is not on disk.
8308
+ *
8309
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8310
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8311
+ * and the durable row is dropped through the same channel eviction uses. It
8312
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8313
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8314
+ * the same failure as one that quietly skips them (D295).
8315
+ *
8316
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8317
+ * ledger claimed 5.65 GB of footage that no longer existed.
8318
+ */
8319
+ rowsForgotten: number().int().nonnegative().optional(),
8306
8320
  startedAt: number(),
8307
8321
  finishedAt: number().nullable(),
8308
8322
  error: string().nullable()
@@ -8666,6 +8680,91 @@ var RelocateResidueSchema = object({
8666
8680
  segments: number().int().nonnegative(),
8667
8681
  bytes: number().int().nonnegative()
8668
8682
  }).nullable();
8683
+ /**
8684
+ * Ask one location whether its durable hour rows describe the disk — the walk
8685
+ * (D319).
8686
+ *
8687
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8688
+ * missing tool is the question, and the dry run is how they sanity-check the
8689
+ * destructive run before authorising it.
8690
+ */
8691
+ var LedgerWalkInputSchema = object({
8692
+ locationId: string().min(1),
8693
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8694
+ apply: boolean().optional(),
8695
+ /** Narrow to one camera. */
8696
+ deviceId: number().int().positive().optional(),
8697
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8698
+ profiles: array(string().min(1)).optional()
8699
+ });
8700
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8701
+ var LedgerWalkRefusalSchema = _enum([
8702
+ "location-unknown",
8703
+ "source-writable",
8704
+ "no-ledger",
8705
+ "archive-unreadable",
8706
+ "anchor-absent",
8707
+ "anchor-unreadable",
8708
+ "anchor-moved"
8709
+ ]);
8710
+ _enum([
8711
+ "live-tail",
8712
+ "listing-error",
8713
+ "path-mismatch",
8714
+ "durable-refused"
8715
+ ]);
8716
+ /** Every skip reason, always present, always a number — so a reason that never
8717
+ * fired reports as zero rather than absent and the report shape is constant
8718
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8719
+ var LedgerWalkSkipCountsSchema = object({
8720
+ "live-tail": number().int().nonnegative(),
8721
+ "listing-error": number().int().nonnegative(),
8722
+ "path-mismatch": number().int().nonnegative(),
8723
+ "durable-refused": number().int().nonnegative()
8724
+ });
8725
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8726
+ var LedgerWalkDeviceReportSchema = object({
8727
+ deviceId: number().int(),
8728
+ hoursWalked: number().int().nonnegative(),
8729
+ hoursMissing: number().int().nonnegative(),
8730
+ ghostSegments: number().int().nonnegative(),
8731
+ ghostBytes: number().int().nonnegative(),
8732
+ forgottenSegments: number().int().nonnegative(),
8733
+ orphanFiles: number().int().nonnegative()
8734
+ });
8735
+ /**
8736
+ * What one walk claimed, listed, found and (only when armed) forgot.
8737
+ *
8738
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8739
+ * walk that saw a fraction of the location is visible in its own report rather
8740
+ * than in the absence of one.
8741
+ */
8742
+ var LedgerWalkReportSchema = object({
8743
+ locationId: string(),
8744
+ applied: boolean(),
8745
+ refused: LedgerWalkRefusalSchema.nullable(),
8746
+ archiveSegments: number().int().nonnegative().nullable(),
8747
+ archiveBytes: number().int().nonnegative().nullable(),
8748
+ hoursClaimed: number().int().nonnegative(),
8749
+ hoursWalked: number().int().nonnegative(),
8750
+ hoursMissing: number().int().nonnegative(),
8751
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8752
+ listings: number().int().nonnegative(),
8753
+ segmentsClaimed: number().int().nonnegative(),
8754
+ ghostSegments: number().int().nonnegative(),
8755
+ ghostBytes: number().int().nonnegative(),
8756
+ ghostHoursWhole: number().int().nonnegative(),
8757
+ forgottenSegments: number().int().nonnegative(),
8758
+ forgottenBytes: number().int().nonnegative(),
8759
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8760
+ orphanFiles: number().int().nonnegative(),
8761
+ orphanSample: array(string()).readonly(),
8762
+ hoursSkipped: number().int().nonnegative(),
8763
+ skippedByReason: LedgerWalkSkipCountsSchema,
8764
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8765
+ bounded: boolean(),
8766
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8767
+ });
8669
8768
  /** How many rows a media pass would still act on against a given target — the
8670
8769
  * media lane's denominator AND its residue, from ONE derivation so the two can
8671
8770
  * never disagree. `null` = the count could not be taken. */
@@ -19614,13 +19713,15 @@ var ListGroupsPageSchema = object({
19614
19713
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19615
19714
  nextCursor: string().nullable()
19616
19715
  });
19716
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19717
+ var KEY_EVENTS_MAX_LIMIT = 200;
19617
19718
  var KeyEventQueryInput = object({
19618
19719
  deviceId: number(),
19619
19720
  /** Window lower bound (track firstSeen ≥ since). */
19620
19721
  since: number(),
19621
19722
  /** Window upper bound (track firstSeen ≤ until). */
19622
19723
  until: number(),
19623
- limit: number().int().min(1).max(200).default(50),
19724
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19624
19725
  /** Drop tracks scoring below this importance. */
19625
19726
  minImportance: number().min(0).max(1).optional(),
19626
19727
  /** Restrict to a single class (e.g. 'person'). */
@@ -19642,6 +19743,32 @@ var KeyEventSchema = object({
19642
19743
  ...TrackFlagFields,
19643
19744
  ...TrackRetrainFields
19644
19745
  });
19746
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19747
+ var KeyEventBatchQueryInput = object({
19748
+ deviceIds: array(number()).min(1).max(200),
19749
+ since: number(),
19750
+ until: number(),
19751
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19752
+ * across the set, which would let a busy camera starve a quiet one of its
19753
+ * rows and change what the merged feed contains. */
19754
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19755
+ minImportance: number().min(0).max(1).optional(),
19756
+ classFilter: string().optional()
19757
+ });
19758
+ /**
19759
+ * One camera's key events in a batch answer.
19760
+ *
19761
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19762
+ * error rather than throwing, so a camera whose store read failed and one with
19763
+ * no events in the window were ALREADY indistinguishable per camera — the
19764
+ * batch does not make that worse, and the row keeps the deviceId the single
19765
+ * method's output never carried (the caller used to stamp it from the fan-out
19766
+ * key, which only worked because there was one query per camera).
19767
+ */
19768
+ var KeyEventsForDeviceSchema = object({
19769
+ deviceId: number(),
19770
+ events: array(KeyEventSchema).readonly()
19771
+ });
19645
19772
  object({
19646
19773
  trackId: string(),
19647
19774
  className: string(),
@@ -19713,6 +19840,50 @@ var EventStoreFootprintSchema = object({
19713
19840
  totalBytes: number().int(),
19714
19841
  devices: array(EventStoreDeviceFootprintSchema).readonly()
19715
19842
  });
19843
+ /** Event-media footprint for one {@link MediaFileKind}. */
19844
+ var EventMediaKindFootprintSchema = object({
19845
+ kind: MediaFileKindEnum,
19846
+ /** Media rows of this kind. */
19847
+ rows: number().int(),
19848
+ /** Bytes on disk held by those rows. */
19849
+ bytes: number().int()
19850
+ });
19851
+ /**
19852
+ * The media footprint broken down by KIND — the axis a deletion decision
19853
+ * actually turns on.
19854
+ *
19855
+ * A byte total says how much there is; it cannot say what is safe to remove.
19856
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19857
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19858
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19859
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19860
+ * nothing else, so sizing a deletion means summing per kind.
19861
+ *
19862
+ * ## Why `unaccounted*` exists
19863
+ *
19864
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19865
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19866
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19867
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19868
+ * retired code path, or by a version that knew a kind this one does not) would
19869
+ * otherwise vanish from the total silently, and an operator would delete
19870
+ * against a denominator smaller than the disk.
19871
+ *
19872
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19873
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19874
+ */
19875
+ var EventMediaKindBreakdownSchema = object({
19876
+ /** Every media row in scope, from one unfiltered aggregate. */
19877
+ totalRows: number().int(),
19878
+ /** Every media byte in scope, from that same aggregate. */
19879
+ totalBytes: number().int(),
19880
+ /** Per-kind footprint, ordered by bytes descending. */
19881
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19882
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19883
+ unaccountedRows: number().int(),
19884
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19885
+ unaccountedBytes: number().int()
19886
+ });
19716
19887
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
19717
19888
  var EventPruneCountsSchema = object({
19718
19889
  motion: number().int(),
@@ -19867,7 +20038,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19867
20038
  until: number().optional(),
19868
20039
  kinds: array(string()).optional(),
19869
20040
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19870
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
20041
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19871
20042
  deviceId: number(),
19872
20043
  since: number(),
19873
20044
  until: number(),
@@ -19916,6 +20087,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19916
20087
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19917
20088
  kind: "query",
19918
20089
  auth: "admin"
20090
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
20091
+ kind: "query",
20092
+ auth: "admin"
19919
20093
  }), method(object({
19920
20094
  olderThanMs: number(),
19921
20095
  reason: OpsLogReasonSchema.optional()
@@ -21971,6 +22145,20 @@ method(object({
21971
22145
  error: string().optional()
21972
22146
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21973
22147
  providerId: string(),
22148
+ /**
22149
+ * The location this config is an UNSAVED edit of, when there is one.
22150
+ *
22151
+ * `listLocations` replaces every declared secret with the redaction
22152
+ * sentinel, so the edit modal's form state holds the sentinel for any
22153
+ * credential the operator did not retype — and posting that here
22154
+ * without a way to resolve it makes the provider try to authenticate
22155
+ * as `__camstack_redacted__` and report the operator's own working
22156
+ * password as wrong. Given this id, the orchestrator restores each
22157
+ * sentinel from the stored config (same rule as `upsertLocation`)
22158
+ * before dispatching. Omitted by the "Add location" wizard, where
22159
+ * every value was typed just now and nothing is stored yet.
22160
+ */
22161
+ locationId: string().optional(),
21974
22162
  config: record(string(), unknown())
21975
22163
  }), object({
21976
22164
  ok: boolean(),
@@ -29408,6 +29596,9 @@ method(object({
29408
29596
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29409
29597
  kind: "query",
29410
29598
  auth: "admin"
29599
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29600
+ kind: "mutation",
29601
+ auth: "admin"
29411
29602
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29412
29603
  kind: "mutation",
29413
29604
  auth: "admin"
@@ -29799,6 +29990,25 @@ var SceneMonitorStatusSchema = object({
29799
29990
  monitors: array(SceneMonitorSchema),
29800
29991
  lastFetchedAt: number()
29801
29992
  });
29993
+ /**
29994
+ * One camera's row in a `listScenesBatch` answer.
29995
+ *
29996
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29997
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29998
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29999
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
30000
+ * configured. Fanned out per camera the difference was visible — one query
30001
+ * errored while the others resolved — and a batch that returned only the rows
30002
+ * it managed would have destroyed it, silently, by making an unreachable camera
30003
+ * indistinguishable from one that answered `monitors: []`.
30004
+ *
30005
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
30006
+ * could not be read"; `status.monitors: []` means "read, and it has none".
30007
+ */
30008
+ var SceneMonitorStatusForDeviceSchema = object({
30009
+ deviceId: number(),
30010
+ status: SceneMonitorStatusSchema.nullable()
30011
+ });
29802
30012
  var sceneMonitorCapability = {
29803
30013
  name: "scene-monitor",
29804
30014
  scope: "device",
@@ -29808,6 +30018,22 @@ var sceneMonitorCapability = {
29808
30018
  deviceTypes: [DeviceType.Camera],
29809
30019
  methods: {
29810
30020
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
30021
+ /**
30022
+ * The same answer, for a SET of cameras, in one round trip.
30023
+ *
30024
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
30025
+ * poll behind the push slice. Fanned out client-side that was one query
30026
+ * per camera — 29 round trips through the browser, the hub and the
30027
+ * post-analysis runner every 30 seconds to read an in-memory map the
30028
+ * owner had already merged. The work is unchanged (`statusFor` per
30029
+ * device, all in-process at the owner); what collapses is the transport.
30030
+ *
30031
+ * A camera that cannot answer still gets a row, with `status: null` —
30032
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
30033
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
30034
+ * tell which two are missing, or that any are.
30035
+ */
30036
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29811
30037
  createScene: method(object({
29812
30038
  deviceId: number(),
29813
30039
  label: string(),
@@ -31841,6 +32067,27 @@ var CameraOccupancySnapshotSchema = object({
31841
32067
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31842
32068
  });
31843
32069
  /**
32070
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
32071
+ *
32072
+ * THREE outcomes, and the single-camera method could only express two of them
32073
+ * because `snapshot: null` was already spoken for:
32074
+ *
32075
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
32076
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
32077
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
32078
+ * - `read: 'unreadable'` — the owner could not answer for this
32079
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
32080
+ *
32081
+ * Collapsing the last two is the failure this field exists to prevent: a
32082
+ * hydration that threw would otherwise render as an empty Stationary section,
32083
+ * which is a definite claim about a camera nobody could read.
32084
+ */
32085
+ var CameraOccupancySnapshotForDeviceSchema = object({
32086
+ deviceId: number(),
32087
+ read: _enum(["read", "unreadable"]),
32088
+ snapshot: CameraOccupancySnapshotSchema.nullable()
32089
+ });
32090
+ /**
31844
32091
  * Time-series resolution. The history methods return one bucket per
31845
32092
  * step over the requested range. Smaller resolutions cost more
31846
32093
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31897,6 +32144,20 @@ var zoneAnalyticsCapability = {
31897
32144
  * (no inference result emitted since boot or since binding was
31898
32145
  * activated). */
31899
32146
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
32147
+ /**
32148
+ * The same snapshot, for a SET of cameras, in one round trip.
32149
+ *
32150
+ * The Events page's Stationary section polls this every 15s for every
32151
+ * selected camera. Fanned out client-side that is one query per camera to
32152
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
32153
+ * costs everything. Batched, N transports become one and the per-device
32154
+ * work is unchanged.
32155
+ *
32156
+ * Every requested deviceId gets a row, tagged `read` — see
32157
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
32158
+ * not answer for is `'unreadable'`, never an empty reading.
32159
+ */
32160
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31900
32161
  /** Time-series object count inside one zone. `className` optional —
31901
32162
  * omit to count every class in the zone. */
31902
32163
  getZoneHistory: method(object({
@@ -36461,6 +36722,12 @@ Object.freeze({
36461
36722
  addonId: null,
36462
36723
  access: "view"
36463
36724
  },
36725
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
36726
+ capName: "pipeline-analytics",
36727
+ capScope: "device",
36728
+ addonId: null,
36729
+ access: "view"
36730
+ },
36464
36731
  "pipelineAnalytics.getEventStoreFootprint": {
36465
36732
  capName: "pipeline-analytics",
36466
36733
  capScope: "device",
@@ -36479,6 +36746,12 @@ Object.freeze({
36479
36746
  addonId: null,
36480
36747
  access: "view"
36481
36748
  },
36749
+ "pipelineAnalytics.getKeyEventsBatch": {
36750
+ capName: "pipeline-analytics",
36751
+ capScope: "device",
36752
+ addonId: null,
36753
+ access: "view"
36754
+ },
36482
36755
  "pipelineAnalytics.getMotionEvents": {
36483
36756
  capName: "pipeline-analytics",
36484
36757
  capScope: "device",
@@ -37655,6 +37928,12 @@ Object.freeze({
37655
37928
  addonId: null,
37656
37929
  access: "view"
37657
37930
  },
37931
+ "recording.reconcileLedgerAgainstDisk": {
37932
+ capName: "recording",
37933
+ capScope: "system",
37934
+ addonId: null,
37935
+ access: "create"
37936
+ },
37658
37937
  "recording.refreshStorageLocationsForMigration": {
37659
37938
  capName: "recording",
37660
37939
  capScope: "system",
@@ -37781,6 +38060,12 @@ Object.freeze({
37781
38060
  addonId: null,
37782
38061
  access: "view"
37783
38062
  },
38063
+ "sceneMonitor.listScenesBatch": {
38064
+ capName: "scene-monitor",
38065
+ capScope: "device",
38066
+ addonId: null,
38067
+ access: "view"
38068
+ },
37784
38069
  "sceneMonitor.recheckNow": {
37785
38070
  capName: "scene-monitor",
37786
38071
  capScope: "device",
@@ -39137,6 +39422,12 @@ Object.freeze({
39137
39422
  addonId: null,
39138
39423
  access: "view"
39139
39424
  },
39425
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39426
+ capName: "zone-analytics",
39427
+ capScope: "device",
39428
+ addonId: null,
39429
+ access: "view"
39430
+ },
39140
39431
  "zoneAnalytics.getUnzonedHistory": {
39141
39432
  capName: "zone-analytics",
39142
39433
  capScope: "device",
@@ -40126,6 +40417,11 @@ Object.freeze({
40126
40417
  form: "single",
40127
40418
  optional: false
40128
40419
  }],
40420
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
40421
+ name: "deviceId",
40422
+ form: "single",
40423
+ optional: true
40424
+ }],
40129
40425
  "pipelineAnalytics.getGroup": [{
40130
40426
  name: "deviceId",
40131
40427
  form: "single",
@@ -40136,6 +40432,11 @@ Object.freeze({
40136
40432
  form: "single",
40137
40433
  optional: false
40138
40434
  }],
40435
+ "pipelineAnalytics.getKeyEventsBatch": [{
40436
+ name: "deviceIds",
40437
+ form: "array",
40438
+ optional: false
40439
+ }],
40139
40440
  "pipelineAnalytics.getMotionEvents": [{
40140
40441
  name: "deviceId",
40141
40442
  form: "single",
@@ -40576,6 +40877,11 @@ Object.freeze({
40576
40877
  form: "single",
40577
40878
  optional: false
40578
40879
  }],
40880
+ "recording.reconcileLedgerAgainstDisk": [{
40881
+ name: "deviceId",
40882
+ form: "single",
40883
+ optional: true
40884
+ }],
40579
40885
  "recording.relocateFootage": [{
40580
40886
  name: "deviceId",
40581
40887
  form: "single",
@@ -40641,6 +40947,11 @@ Object.freeze({
40641
40947
  form: "single",
40642
40948
  optional: false
40643
40949
  }],
40950
+ "sceneMonitor.listScenesBatch": [{
40951
+ name: "deviceIds",
40952
+ form: "array",
40953
+ optional: false
40954
+ }],
40644
40955
  "sceneMonitor.recheckNow": [{
40645
40956
  name: "deviceId",
40646
40957
  form: "single",
@@ -40902,6 +41213,11 @@ Object.freeze({
40902
41213
  form: "single",
40903
41214
  optional: false
40904
41215
  }],
41216
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41217
+ name: "deviceIds",
41218
+ form: "array",
41219
+ optional: false
41220
+ }],
40905
41221
  "zoneAnalytics.getUnzonedHistory": [{
40906
41222
  name: "deviceId",
40907
41223
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.70",
3
+ "version": "1.2.72",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",