@camstack/addon-provider-dreo 0.2.102 → 0.2.104

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 +355 -14
  2. package/dist/addon.mjs +355 -14
  3. package/package.json +2 -2
package/dist/addon.js CHANGED
@@ -5396,7 +5396,7 @@ var ZodIssueCode = {
5396
5396
  var ZodFirstPartyTypeKind;
5397
5397
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5398
5398
  //#endregion
5399
- //#region ../types/dist/sleep-DEuj7E3j.mjs
5399
+ //#region ../types/dist/sleep-BDR76Ykr.mjs
5400
5400
  /**
5401
5401
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5402
5402
  * window to float samples (D455).
@@ -11598,6 +11598,21 @@ var streamSignalsCapability = {
11598
11598
  mode: "singleton",
11599
11599
  deviceTypes: Object.values(DeviceType),
11600
11600
  runtimeState: StreamSignalsStatusSchema,
11601
+ /**
11602
+ * Runtime-state durability: **session** — mirrored in RAM, never written.
11603
+ *
11604
+ * The slice holds what the DEVICE says it can emit. That is a probed fact,
11605
+ * not an operator choice: the provider re-declares it on every registration,
11606
+ * so losing it loses nothing and persisting it would freeze an answer the
11607
+ * camera is entitled to change. Measured the same day on the sibling case —
11608
+ * `native-object-detection.supportedClasses` was persisted, and a firmware
11609
+ * class the camera really detected stayed missing for the life of the row
11610
+ * because the fix could not reach it.
11611
+ *
11612
+ * See `RuntimeStateDurability`. Enforced by
11613
+ * `scripts/check-runtime-state-durability.ts`.
11614
+ */
11615
+ durability: "session",
11601
11616
  methods: {
11602
11617
  /**
11603
11618
  * What this device can emit. Empty is a valid and common answer — most
@@ -18229,6 +18244,48 @@ var NcAlarmConfigSchema = object({
18229
18244
  settings: NcAlarmSettingsSchema,
18230
18245
  coverage: array(NcAlarmModeCoverageSchema)
18231
18246
  });
18247
+ /**
18248
+ * ONE rule's demand on ONE camera's clip ring.
18249
+ *
18250
+ * A rule, not a camera: the broker takes the MAX over the asks that reach a
18251
+ * camera, so the answer stays a faithful description of the rules and the
18252
+ * bounding (the byte budget, the broker's own ceiling) stays where the cost
18253
+ * lives. A camera that appears in no ask is being told **nothing** — which is
18254
+ * a different thing from never having been told, and only the envelope
18255
+ * (`rulesLoaded`) can tell those apart.
18256
+ */
18257
+ var NcClipRetentionAskSchema = object({
18258
+ /**
18259
+ * The camera this ask is about. **Absent = every camera**, which is what a
18260
+ * rule with no `conditions.devices` means: it can fire anywhere, so it is
18261
+ * asking everywhere.
18262
+ */
18263
+ deviceId: number().int().nonnegative().optional(),
18264
+ /** Seconds of history the rule needs held for it. Never zero — a rule that
18265
+ * needs nothing produces no ask at all. */
18266
+ seconds: number().min(0).max(60),
18267
+ /** The profile the rule cuts from, when it named one. Absent = the cheapest
18268
+ * assigned, which is what the cut defaults to. */
18269
+ profile: CamProfileSchema.optional(),
18270
+ /** Who is asking — so a retention the operator did not expect names a rule. */
18271
+ ruleId: string(),
18272
+ ruleName: string(),
18273
+ /** Why this many seconds: the rule's own window, or the occupancy ceiling. */
18274
+ reason: _enum(["window", "occupancy"])
18275
+ });
18276
+ /**
18277
+ * The whole answer, with the one bit that keeps absence from reading as zero.
18278
+ *
18279
+ * `rulesLoaded` is false until the rule ledger has completed a load at least
18280
+ * once. A centre that is up but has not read its rules yet answers with an
18281
+ * empty ask list that means nothing at all — applying it would drop every
18282
+ * camera's ring to zero for the first events after a restart, which is the
18283
+ * footage this mechanism exists to keep.
18284
+ */
18285
+ var NcClipRetentionPlanSchema = object({
18286
+ rulesLoaded: boolean(),
18287
+ asks: array(NcClipRetentionAskSchema).readonly()
18288
+ });
18232
18289
  method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), method(object({ ruleId: string() }), object({ rule: NcRuleSchema.nullable() }), { auth: "admin" }), method(object({ rule: NcRuleInputSchema }), object({ rule: NcRuleSchema }), {
18233
18290
  kind: "mutation",
18234
18291
  auth: "admin",
@@ -18249,7 +18306,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
18249
18306
  }), object({ success: literal(true) }), {
18250
18307
  kind: "mutation",
18251
18308
  auth: "admin"
18252
- }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18309
+ }), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18253
18310
  deviceId: number().int(),
18254
18311
  muted: boolean()
18255
18312
  }), object({ success: literal(true) }), {
@@ -19434,13 +19491,26 @@ var TrackAudioLabelSchema = object({
19434
19491
  * - `audio` — an audio event on the camera itself that was anomalous for
19435
19492
  * THAT camera, loud, and heard while nothing visual was happening (D62).
19436
19493
  *
19494
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
19495
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
19496
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
19497
+ * detector.
19498
+ *
19499
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
19500
+ * a persisted onboard track failed `safeParse` on read and came back with NO
19501
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
19502
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
19503
+ * could not name.
19504
+ *
19437
19505
  * The spatial subsystems (tracker association, occupancy count, re-id /
19438
19506
  * embedding, resurrection) MUST skip every synthetic source. Test for that
19439
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
19440
- * check silently readmits every source added after it was written.
19507
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
19508
+ * `!== 'sensor'` check silently readmits every source added after it was
19509
+ * written.
19441
19510
  */
19442
19511
  var TrackSourceSchema = _enum([
19443
19512
  "pipeline",
19513
+ "onboard",
19444
19514
  "sensor",
19445
19515
  "audio"
19446
19516
  ]);
@@ -20823,6 +20893,33 @@ var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
20823
20893
  * solid is this track", cheaper than re-deriving it from a trajectory. */
20824
20894
  framesMatched: number().int()
20825
20895
  })).readonly() });
20896
+ /**
20897
+ * The long-term series a camera produces. A closed union so a typo cannot
20898
+ * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
20899
+ * is an alias of this, not a second list.
20900
+ */
20901
+ var AnalyticsLtsSeriesSchema = _enum([
20902
+ "motion",
20903
+ "audio-dbfs",
20904
+ "battery",
20905
+ "occupancy"
20906
+ ]);
20907
+ /**
20908
+ * One closed 5-minute bucket of a long-term series.
20909
+ *
20910
+ * `samples` is how many readings the bucket folded — it is the ACTIVITY for a
20911
+ * series like `motion`, and the confidence for one like `occupancy`. A bucket
20912
+ * that exists at all was measured; a bucket that is absent was not, which is a
20913
+ * different claim from zero and the reason this is a sparse series.
20914
+ */
20915
+ var LtsBucketSchema = object({
20916
+ scope: string(),
20917
+ bucketStart: number().int(),
20918
+ samples: number().int().nonnegative(),
20919
+ sum: number(),
20920
+ min: number(),
20921
+ max: number()
20922
+ });
20826
20923
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
20827
20924
  deviceId: number(),
20828
20925
  trackId: string()
@@ -20869,6 +20966,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20869
20966
  kinds: array(string()).optional(),
20870
20967
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20871
20968
  }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
20969
+ deviceId: number(),
20970
+ series: AnalyticsLtsSeriesSchema,
20971
+ /** Omit for EVERY scope of this series in the range. */
20972
+ scope: string().optional(),
20973
+ from: number(),
20974
+ to: number(),
20975
+ limit: number().int().positive().optional()
20976
+ }), array(LtsBucketSchema).readonly()), method(object({
20872
20977
  deviceId: number(),
20873
20978
  since: number(),
20874
20979
  until: number(),
@@ -21487,6 +21592,40 @@ var MotionSourceEnum = _enum([
21487
21592
  */
21488
21593
  var MotionSourcesSchema = array(MotionSourceEnum);
21489
21594
  /**
21595
+ * Which root detector a camera runs — EXACTLY ONE.
21596
+ *
21597
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
21598
+ * with (`DetectionSource`) rather than a second spelling of the same two
21599
+ * ideas: the value an operator picks here is the value that comes back on the
21600
+ * track, the overlay and the debug row.
21601
+ *
21602
+ * ## Why one, and why it is still an array
21603
+ *
21604
+ * Two roots on one camera is a capability nobody asked for and the operator has
21605
+ * since ruled out. It was never free: both planes feed the SAME per-device
21606
+ * stationary registry, both can promote the same parked object, and the two
21607
+ * detectors disagree about the same box by construction — which is the
21608
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
21609
+ *
21610
+ * The ARRAY shape survives because the stored settings and the attach payload
21611
+ * already speak it on every camera of every fleet, and a cap input that
21612
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
21613
+ * in the one direction that costs an operator their cameras. So the wire stays
21614
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
21615
+ * element rather than refused, and `max(1)` is what every consumer can then
21616
+ * rely on.
21617
+ *
21618
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
21619
+ * a camera an operator left with no root detector — and `planDetectionSources`
21620
+ * has always said so: inventing a root for them is how a default acts without
21621
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
21622
+ * suite caught it.
21623
+ *
21624
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
21625
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
21626
+ */
21627
+ var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
21628
+ /**
21490
21629
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
21491
21630
  * so cap-side consumers (the orchestrator forward, the runner addon's
21492
21631
  * cap implementation, tests) can reuse the type instead of redeclaring
@@ -21637,6 +21776,35 @@ var RunnerCameraConfigSchema = object({
21637
21776
  * events; the orchestrator forwards to `reportMotion`.
21638
21777
  */
21639
21778
  motionSources: MotionSourcesSchema.default(["analyzer"]),
21779
+ /**
21780
+ * WHICH root detector runs for this camera. The detection counterpart of
21781
+ * {@link motionSources}, same shape and same discipline: a per-camera list
21782
+ * of sources the system understands, vendor-agnostic, owned here.
21783
+ *
21784
+ * - `pipeline` — this runner's own root step on decoded pixels (`steps`).
21785
+ * - `onboard` — the CAMERA's own detector, admitted only when it is named
21786
+ * here. Its boxes reach post-analysis on the same
21787
+ * `PipelineInferenceResult` payload as the pipeline's, so an onboard-born
21788
+ * track gets the same tracker, the same overlay and the same detail
21789
+ * subtree (face / plate / embedding).
21790
+ *
21791
+ * `['onboard']` alone is the REPLACEMENT: this runner's root step does not
21792
+ * run, and that is where a camera with a usable onboard detector buys back
21793
+ * its share of the accelerator. It saves the root inference, not the decode
21794
+ * — the detail subtree still needs pixels to cut crops from.
21795
+ *
21796
+ * A camera whose onboard detections carry no geometry cannot serve as a root
21797
+ * step at all (there is nothing to track), and the cap that knows this says
21798
+ * so on `native-object-detection.getOptions().geometry`. On Reolink that is
21799
+ * every battery camera: the boxes ride a sub-stream a battery camera never
21800
+ * attaches, so such a camera stays on `['pipeline']` and its onboard AI is
21801
+ * worth exactly what it already is — a wake signal with a class.
21802
+ *
21803
+ * Defaults to `['pipeline']`, which is byte-for-byte today's behaviour: a
21804
+ * vendor never turns a detector on for the operator, and neither does a
21805
+ * default.
21806
+ */
21807
+ detectionSources: DetectionSourcesSchema.default(["pipeline"]),
21640
21808
  pipelineEnabled: boolean().default(true),
21641
21809
  /** Ordered tree of video steps. Absent → runner skips video detection. */
21642
21810
  steps: array(PipelineStepInputSchema).readonly().optional(),
@@ -21818,7 +21986,47 @@ var RunnerLocalMetricsSchema = object({
21818
21986
  queueDepth: number(),
21819
21987
  frameLazy: FrameLazyMetricsSchema.optional()
21820
21988
  });
21989
+ /**
21990
+ * Pipeline Runner capability — runtime detection workhorse.
21991
+ *
21992
+ * One instance per node. Receives camera assignments from
21993
+ * `addon-pipeline-orchestrator`, subscribes to the local stream-broker for
21994
+ * decoded frames, drains motion + detection queues, calls the local
21995
+ * `motion-detection` and `pipeline-executor` capabilities, and emits typed
21996
+ * `pipeline.inference-result` and `detection.motion-analysis` events on
21997
+ * the bus.
21998
+ *
21999
+ * Distinct from `pipeline-orchestrator` (the hub-side load balancer) — the
22000
+ * runner has zero knowledge of other agents, no global state, and never
22001
+ * makes assignment decisions itself.
22002
+ */
22003
+ /**
22004
+ * Why a runner declined to look at a camera out of band. Named rather than a
22005
+ * bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
22006
+ * already mid-session and one already bursting are four different facts, and a
22007
+ * caller told only "no" cannot tell a protection from a bug.
22008
+ */
22009
+ var OccupancyBurstRefusalSchema = _enum([
22010
+ "not-attached",
22011
+ "battery-asleep",
22012
+ "not-watching",
22013
+ "already-bursting"
22014
+ ]);
21821
22015
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
22016
+ deviceId: number(),
22017
+ /** How many frames to collect. The caller sizes this against what its
22018
+ * own confirmation needs; the runner clamps it to the field's range. */
22019
+ frames: number().int().positive().optional(),
22020
+ /** Why, for the log. A burst nobody can attribute is a cost nobody can
22021
+ * defend — the periodic one at least has a timer to point at. */
22022
+ reason: string()
22023
+ }), object({
22024
+ started: boolean(),
22025
+ refusedBecause: OccupancyBurstRefusalSchema.optional()
22026
+ }), {
22027
+ auth: "admin",
22028
+ kind: "mutation"
22029
+ }), method(object({
21822
22030
  handle: FrameHandleSchema,
21823
22031
  bbox: NativeCropBboxSchema,
21824
22032
  maxWidth: number().int().positive().optional(),
@@ -28656,11 +28864,46 @@ var NativeObjectDetectionStatusSchema = object({
28656
28864
  supportedClasses: array(NativeObjectClassEnum).readonly(),
28657
28865
  /**
28658
28866
  * Whether forwarding of onboard AI detections is enabled for this device.
28659
- * Default FALSE (opt-in, cold-start) — onboard AI pushes are noisy/sparse and
28660
- * churn the tracker, so forwarding stays off until the operator enables it.
28867
+ *
28868
+ * Cold-start OFF on every camera. A vendor does not decide which detector a
28869
+ * camera runs — that is the operator's choice, and it is made where every
28870
+ * other detection choice is made. `geometry` on
28871
+ * {@link NativeObjectDetectionOptionsSchema} is how the form says what the
28872
+ * choice would buy this particular camera.
28661
28873
  */
28662
28874
  enabled: boolean()
28663
28875
  });
28876
+ /**
28877
+ * WHEN a camera's onboard detections carry geometry.
28878
+ *
28879
+ * - `boxed` — always. The provider holds a standing channel for the boxes, so
28880
+ * a detection is a trackable subject whenever the camera sees one.
28881
+ * - `while-streaming` — only while some stream of this camera is already open.
28882
+ * The boxes ride the video's own side-channel rather than a feed of their
28883
+ * own, so they cost nothing and they exist only when something is pulling.
28884
+ * On Reolink this is every BATTERY camera: a persistent second feed is real
28885
+ * radio drain, but a camera that is awake is awake precisely because
28886
+ * something is in front of it, and its stream is already being pulled.
28887
+ * - `flag-only` — never. The firmware ships the class and nothing else; the
28888
+ * pipeline needs geometry to make a subject, so such a push reaches the
28889
+ * tracker as motion and no further.
28890
+ *
28891
+ * Reported and not inferred, because the operator's question in front of the
28892
+ * picker is "what do I get", and these are three different answers (D14: the
28893
+ * derived form is only as honest as `getOptions`).
28894
+ */
28895
+ var NativeObjectGeometryEnum = _enum([
28896
+ "boxed",
28897
+ "while-streaming",
28898
+ "flag-only"
28899
+ ]);
28900
+ var NativeObjectDetectionOptionsSchema = object({
28901
+ /** Classes this firmware can detect — the same list the status reports. */
28902
+ supportedClasses: array(NativeObjectClassEnum).readonly(),
28903
+ /** What a detection from this camera carries. */
28904
+ geometry: NativeObjectGeometryEnum
28905
+ });
28906
+ var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
28664
28907
  var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
28665
28908
  /** Required by createRuntimeStateBridge — epoch ms of last refresh. */
28666
28909
  lastFetchedAt: number() });
@@ -28670,13 +28913,23 @@ var nativeObjectDetectionCapability = {
28670
28913
  deviceNative: true,
28671
28914
  mode: "singleton",
28672
28915
  deviceTypes: [DeviceType.Camera],
28673
- methods: { setEnabled: method(object({
28674
- deviceId: number(),
28675
- enabled: boolean()
28676
- }), _void(), {
28677
- kind: "mutation",
28678
- auth: "admin"
28679
- }) },
28916
+ methods: {
28917
+ getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
28918
+ setSettings: method(object({
28919
+ deviceId: number(),
28920
+ settings: NativeObjectDetectionSettingsPatchSchema
28921
+ }), _void(), {
28922
+ kind: "mutation",
28923
+ auth: "admin"
28924
+ }),
28925
+ setEnabled: method(object({
28926
+ deviceId: number(),
28927
+ enabled: boolean()
28928
+ }), _void(), {
28929
+ kind: "mutation",
28930
+ auth: "admin"
28931
+ })
28932
+ },
28680
28933
  events: { onDetected: { data: object({
28681
28934
  deviceId: number(),
28682
28935
  detection: NativeDetectionSchema
@@ -33524,7 +33777,28 @@ var PerScopeBreakdownSchema = object({
33524
33777
  /** Total tracked objects in this scope (frame / zone / unzoned). */
33525
33778
  totalObjects: number().int().nonnegative(),
33526
33779
  /** Per-class count. Keys are macro class names (e.g. `person`, `car`). */
33527
- byClass: record(string(), number().int().nonnegative())
33780
+ byClass: record(string(), number().int().nonnegative()),
33781
+ /**
33782
+ * Of `totalObjects`, how many are STANDING — objects the census holds,
33783
+ * present but not going anywhere (a parked car, a bin, a statue).
33784
+ *
33785
+ * The operator's question about a scene has two halves and they answer
33786
+ * different things: "what is parked here" is stable for hours and must not
33787
+ * flap, while "who is walking through" is the thing an alert is about. One
33788
+ * number could only serve one of them, and `totalObjects` served the first
33789
+ * badly — a passer-by moved it and every occupancy rule saw a change.
33790
+ *
33791
+ * `standing + transient === totalObjects`, always. The total keeps its old
33792
+ * meaning exactly, so no rule written against it changes behaviour.
33793
+ *
33794
+ * ABSENT means UNKNOWN, never zero (D393): a snapshot from a node older than
33795
+ * this field cannot say, and a reader that folds absence in as 0 reports "no
33796
+ * parked objects here" about a camera whose lot is full.
33797
+ */
33798
+ standingObjects: number().int().nonnegative().optional(),
33799
+ /** Of `totalObjects`, how many are passing through. See `standingObjects` —
33800
+ * same absence rule. */
33801
+ transientObjects: number().int().nonnegative().optional()
33528
33802
  });
33529
33803
  var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
33530
33804
  zoneId: string(),
@@ -33647,6 +33921,23 @@ var HistoryPointSchema = object({
33647
33921
  * triple covers the three spatial scopes (per-zone, frame-wide,
33648
33922
  * outside-any-zone). Crossing them with `className?` gives the full
33649
33923
  * combinatorial coverage the operator UI requested.
33924
+ *
33925
+ * ## ⚠️ The three history methods are DEPRECATED
33926
+ *
33927
+ * They are served from a 60-MINUTE in-memory ring. It dies with the process,
33928
+ * so after a restart they report nothing about a night that did happen, and any
33929
+ * window past an hour is silently clamped — a caller cannot tell a clamp from
33930
+ * an empty scene. The presets in the occupancy chart existed to hide that
33931
+ * horizon rather than to answer a question.
33932
+ *
33933
+ * Use `pipelineAnalytics.readLongTermSeries({ series: 'occupancy', scope })`
33934
+ * instead. Those rows are already written for every camera, ~60 bytes per
33935
+ * (camera, scope, 5-minute bucket), kept for a year, and survive a restart.
33936
+ * Spell the scope with `occupancyScope(zoneId?, className?)` — the same
33937
+ * function the writer uses, so the two cannot drift.
33938
+ *
33939
+ * The ring stays until these three go, and it is the only thing left reading
33940
+ * it. Do not add a fourth caller.
33650
33941
  */
33651
33942
  var zoneAnalyticsCapability = {
33652
33943
  name: "zone-analytics",
@@ -38046,12 +38337,24 @@ Object.freeze({
38046
38337
  addonId: null,
38047
38338
  access: "create"
38048
38339
  },
38340
+ "nativeObjectDetection.getOptions": {
38341
+ capName: "native-object-detection",
38342
+ capScope: "device",
38343
+ addonId: null,
38344
+ access: "view"
38345
+ },
38049
38346
  "nativeObjectDetection.setEnabled": {
38050
38347
  capName: "native-object-detection",
38051
38348
  capScope: "device",
38052
38349
  addonId: null,
38053
38350
  access: "create"
38054
38351
  },
38352
+ "nativeObjectDetection.setSettings": {
38353
+ capName: "native-object-detection",
38354
+ capScope: "device",
38355
+ addonId: null,
38356
+ access: "create"
38357
+ },
38055
38358
  "navigation.getFeatures": {
38056
38359
  capName: "navigation",
38057
38360
  capScope: "device",
@@ -38316,6 +38619,12 @@ Object.freeze({
38316
38619
  addonId: null,
38317
38620
  access: "view"
38318
38621
  },
38622
+ "notificationRules.getClipRetentionAsks": {
38623
+ capName: "notification-rules",
38624
+ capScope: "system",
38625
+ addonId: null,
38626
+ access: "view"
38627
+ },
38319
38628
  "notificationRules.getConditionCatalog": {
38320
38629
  capName: "notification-rules",
38321
38630
  capScope: "system",
@@ -38826,6 +39135,12 @@ Object.freeze({
38826
39135
  addonId: null,
38827
39136
  access: "create"
38828
39137
  },
39138
+ "pipelineAnalytics.readLongTermSeries": {
39139
+ capName: "pipeline-analytics",
39140
+ capScope: "device",
39141
+ addonId: null,
39142
+ access: "view"
39143
+ },
38829
39144
  "pipelineAnalytics.rebuildObjectEmbeddings": {
38830
39145
  capName: "pipeline-analytics",
38831
39146
  capScope: "device",
@@ -39498,6 +39813,12 @@ Object.freeze({
39498
39813
  addonId: null,
39499
39814
  access: "create"
39500
39815
  },
39816
+ "pipelineRunner.requestOccupancyBurst": {
39817
+ capName: "pipeline-runner",
39818
+ capScope: "system",
39819
+ addonId: null,
39820
+ access: "create"
39821
+ },
39501
39822
  "pipelineRunner.runDetailSubtree": {
39502
39823
  capName: "pipeline-runner",
39503
39824
  capScope: "system",
@@ -42278,11 +42599,21 @@ Object.freeze({
42278
42599
  form: "single",
42279
42600
  optional: false
42280
42601
  }],
42602
+ "nativeObjectDetection.getOptions": [{
42603
+ name: "deviceId",
42604
+ form: "single",
42605
+ optional: false
42606
+ }],
42281
42607
  "nativeObjectDetection.setEnabled": [{
42282
42608
  name: "deviceId",
42283
42609
  form: "single",
42284
42610
  optional: false
42285
42611
  }],
42612
+ "nativeObjectDetection.setSettings": [{
42613
+ name: "deviceId",
42614
+ form: "single",
42615
+ optional: false
42616
+ }],
42286
42617
  "navigation.getFeatures": [{
42287
42618
  name: "deviceId",
42288
42619
  form: "single",
@@ -42632,6 +42963,11 @@ Object.freeze({
42632
42963
  form: "single",
42633
42964
  optional: false
42634
42965
  }],
42966
+ "pipelineAnalytics.readLongTermSeries": [{
42967
+ name: "deviceId",
42968
+ form: "single",
42969
+ optional: false
42970
+ }],
42635
42971
  "pipelineAnalytics.rebuildObjectEmbeddings": [{
42636
42972
  name: "deviceId",
42637
42973
  form: "single",
@@ -42822,6 +43158,11 @@ Object.freeze({
42822
43158
  form: "single",
42823
43159
  optional: false
42824
43160
  }],
43161
+ "pipelineRunner.requestOccupancyBurst": [{
43162
+ name: "deviceId",
43163
+ form: "single",
43164
+ optional: false
43165
+ }],
42825
43166
  "pipelineRunner.runDetailSubtree": [{
42826
43167
  name: "deviceId",
42827
43168
  form: "single",
package/dist/addon.mjs CHANGED
@@ -5397,7 +5397,7 @@ var ZodIssueCode = {
5397
5397
  var ZodFirstPartyTypeKind;
5398
5398
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5399
5399
  //#endregion
5400
- //#region ../types/dist/sleep-DEuj7E3j.mjs
5400
+ //#region ../types/dist/sleep-BDR76Ykr.mjs
5401
5401
  /**
5402
5402
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5403
5403
  * window to float samples (D455).
@@ -11599,6 +11599,21 @@ var streamSignalsCapability = {
11599
11599
  mode: "singleton",
11600
11600
  deviceTypes: Object.values(DeviceType),
11601
11601
  runtimeState: StreamSignalsStatusSchema,
11602
+ /**
11603
+ * Runtime-state durability: **session** — mirrored in RAM, never written.
11604
+ *
11605
+ * The slice holds what the DEVICE says it can emit. That is a probed fact,
11606
+ * not an operator choice: the provider re-declares it on every registration,
11607
+ * so losing it loses nothing and persisting it would freeze an answer the
11608
+ * camera is entitled to change. Measured the same day on the sibling case —
11609
+ * `native-object-detection.supportedClasses` was persisted, and a firmware
11610
+ * class the camera really detected stayed missing for the life of the row
11611
+ * because the fix could not reach it.
11612
+ *
11613
+ * See `RuntimeStateDurability`. Enforced by
11614
+ * `scripts/check-runtime-state-durability.ts`.
11615
+ */
11616
+ durability: "session",
11602
11617
  methods: {
11603
11618
  /**
11604
11619
  * What this device can emit. Empty is a valid and common answer — most
@@ -18230,6 +18245,48 @@ var NcAlarmConfigSchema = object({
18230
18245
  settings: NcAlarmSettingsSchema,
18231
18246
  coverage: array(NcAlarmModeCoverageSchema)
18232
18247
  });
18248
+ /**
18249
+ * ONE rule's demand on ONE camera's clip ring.
18250
+ *
18251
+ * A rule, not a camera: the broker takes the MAX over the asks that reach a
18252
+ * camera, so the answer stays a faithful description of the rules and the
18253
+ * bounding (the byte budget, the broker's own ceiling) stays where the cost
18254
+ * lives. A camera that appears in no ask is being told **nothing** — which is
18255
+ * a different thing from never having been told, and only the envelope
18256
+ * (`rulesLoaded`) can tell those apart.
18257
+ */
18258
+ var NcClipRetentionAskSchema = object({
18259
+ /**
18260
+ * The camera this ask is about. **Absent = every camera**, which is what a
18261
+ * rule with no `conditions.devices` means: it can fire anywhere, so it is
18262
+ * asking everywhere.
18263
+ */
18264
+ deviceId: number().int().nonnegative().optional(),
18265
+ /** Seconds of history the rule needs held for it. Never zero — a rule that
18266
+ * needs nothing produces no ask at all. */
18267
+ seconds: number().min(0).max(60),
18268
+ /** The profile the rule cuts from, when it named one. Absent = the cheapest
18269
+ * assigned, which is what the cut defaults to. */
18270
+ profile: CamProfileSchema.optional(),
18271
+ /** Who is asking — so a retention the operator did not expect names a rule. */
18272
+ ruleId: string(),
18273
+ ruleName: string(),
18274
+ /** Why this many seconds: the rule's own window, or the occupancy ceiling. */
18275
+ reason: _enum(["window", "occupancy"])
18276
+ });
18277
+ /**
18278
+ * The whole answer, with the one bit that keeps absence from reading as zero.
18279
+ *
18280
+ * `rulesLoaded` is false until the rule ledger has completed a load at least
18281
+ * once. A centre that is up but has not read its rules yet answers with an
18282
+ * empty ask list that means nothing at all — applying it would drop every
18283
+ * camera's ring to zero for the first events after a restart, which is the
18284
+ * footage this mechanism exists to keep.
18285
+ */
18286
+ var NcClipRetentionPlanSchema = object({
18287
+ rulesLoaded: boolean(),
18288
+ asks: array(NcClipRetentionAskSchema).readonly()
18289
+ });
18233
18290
  method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), method(object({ ruleId: string() }), object({ rule: NcRuleSchema.nullable() }), { auth: "admin" }), method(object({ rule: NcRuleInputSchema }), object({ rule: NcRuleSchema }), {
18234
18291
  kind: "mutation",
18235
18292
  auth: "admin",
@@ -18250,7 +18307,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
18250
18307
  }), object({ success: literal(true) }), {
18251
18308
  kind: "mutation",
18252
18309
  auth: "admin"
18253
- }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18310
+ }), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18254
18311
  deviceId: number().int(),
18255
18312
  muted: boolean()
18256
18313
  }), object({ success: literal(true) }), {
@@ -19435,13 +19492,26 @@ var TrackAudioLabelSchema = object({
19435
19492
  * - `audio` — an audio event on the camera itself that was anomalous for
19436
19493
  * THAT camera, loud, and heard while nothing visual was happening (D62).
19437
19494
  *
19495
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
19496
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
19497
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
19498
+ * detector.
19499
+ *
19500
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
19501
+ * a persisted onboard track failed `safeParse` on read and came back with NO
19502
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
19503
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
19504
+ * could not name.
19505
+ *
19438
19506
  * The spatial subsystems (tracker association, occupancy count, re-id /
19439
19507
  * embedding, resurrection) MUST skip every synthetic source. Test for that
19440
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
19441
- * check silently readmits every source added after it was written.
19508
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
19509
+ * `!== 'sensor'` check silently readmits every source added after it was
19510
+ * written.
19442
19511
  */
19443
19512
  var TrackSourceSchema = _enum([
19444
19513
  "pipeline",
19514
+ "onboard",
19445
19515
  "sensor",
19446
19516
  "audio"
19447
19517
  ]);
@@ -20824,6 +20894,33 @@ var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
20824
20894
  * solid is this track", cheaper than re-deriving it from a trajectory. */
20825
20895
  framesMatched: number().int()
20826
20896
  })).readonly() });
20897
+ /**
20898
+ * The long-term series a camera produces. A closed union so a typo cannot
20899
+ * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
20900
+ * is an alias of this, not a second list.
20901
+ */
20902
+ var AnalyticsLtsSeriesSchema = _enum([
20903
+ "motion",
20904
+ "audio-dbfs",
20905
+ "battery",
20906
+ "occupancy"
20907
+ ]);
20908
+ /**
20909
+ * One closed 5-minute bucket of a long-term series.
20910
+ *
20911
+ * `samples` is how many readings the bucket folded — it is the ACTIVITY for a
20912
+ * series like `motion`, and the confidence for one like `occupancy`. A bucket
20913
+ * that exists at all was measured; a bucket that is absent was not, which is a
20914
+ * different claim from zero and the reason this is a sparse series.
20915
+ */
20916
+ var LtsBucketSchema = object({
20917
+ scope: string(),
20918
+ bucketStart: number().int(),
20919
+ samples: number().int().nonnegative(),
20920
+ sum: number(),
20921
+ min: number(),
20922
+ max: number()
20923
+ });
20827
20924
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
20828
20925
  deviceId: number(),
20829
20926
  trackId: string()
@@ -20870,6 +20967,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20870
20967
  kinds: array(string()).optional(),
20871
20968
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20872
20969
  }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
20970
+ deviceId: number(),
20971
+ series: AnalyticsLtsSeriesSchema,
20972
+ /** Omit for EVERY scope of this series in the range. */
20973
+ scope: string().optional(),
20974
+ from: number(),
20975
+ to: number(),
20976
+ limit: number().int().positive().optional()
20977
+ }), array(LtsBucketSchema).readonly()), method(object({
20873
20978
  deviceId: number(),
20874
20979
  since: number(),
20875
20980
  until: number(),
@@ -21488,6 +21593,40 @@ var MotionSourceEnum = _enum([
21488
21593
  */
21489
21594
  var MotionSourcesSchema = array(MotionSourceEnum);
21490
21595
  /**
21596
+ * Which root detector a camera runs — EXACTLY ONE.
21597
+ *
21598
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
21599
+ * with (`DetectionSource`) rather than a second spelling of the same two
21600
+ * ideas: the value an operator picks here is the value that comes back on the
21601
+ * track, the overlay and the debug row.
21602
+ *
21603
+ * ## Why one, and why it is still an array
21604
+ *
21605
+ * Two roots on one camera is a capability nobody asked for and the operator has
21606
+ * since ruled out. It was never free: both planes feed the SAME per-device
21607
+ * stationary registry, both can promote the same parked object, and the two
21608
+ * detectors disagree about the same box by construction — which is the
21609
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
21610
+ *
21611
+ * The ARRAY shape survives because the stored settings and the attach payload
21612
+ * already speak it on every camera of every fleet, and a cap input that
21613
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
21614
+ * in the one direction that costs an operator their cameras. So the wire stays
21615
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
21616
+ * element rather than refused, and `max(1)` is what every consumer can then
21617
+ * rely on.
21618
+ *
21619
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
21620
+ * a camera an operator left with no root detector — and `planDetectionSources`
21621
+ * has always said so: inventing a root for them is how a default acts without
21622
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
21623
+ * suite caught it.
21624
+ *
21625
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
21626
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
21627
+ */
21628
+ var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
21629
+ /**
21491
21630
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
21492
21631
  * so cap-side consumers (the orchestrator forward, the runner addon's
21493
21632
  * cap implementation, tests) can reuse the type instead of redeclaring
@@ -21638,6 +21777,35 @@ var RunnerCameraConfigSchema = object({
21638
21777
  * events; the orchestrator forwards to `reportMotion`.
21639
21778
  */
21640
21779
  motionSources: MotionSourcesSchema.default(["analyzer"]),
21780
+ /**
21781
+ * WHICH root detector runs for this camera. The detection counterpart of
21782
+ * {@link motionSources}, same shape and same discipline: a per-camera list
21783
+ * of sources the system understands, vendor-agnostic, owned here.
21784
+ *
21785
+ * - `pipeline` — this runner's own root step on decoded pixels (`steps`).
21786
+ * - `onboard` — the CAMERA's own detector, admitted only when it is named
21787
+ * here. Its boxes reach post-analysis on the same
21788
+ * `PipelineInferenceResult` payload as the pipeline's, so an onboard-born
21789
+ * track gets the same tracker, the same overlay and the same detail
21790
+ * subtree (face / plate / embedding).
21791
+ *
21792
+ * `['onboard']` alone is the REPLACEMENT: this runner's root step does not
21793
+ * run, and that is where a camera with a usable onboard detector buys back
21794
+ * its share of the accelerator. It saves the root inference, not the decode
21795
+ * — the detail subtree still needs pixels to cut crops from.
21796
+ *
21797
+ * A camera whose onboard detections carry no geometry cannot serve as a root
21798
+ * step at all (there is nothing to track), and the cap that knows this says
21799
+ * so on `native-object-detection.getOptions().geometry`. On Reolink that is
21800
+ * every battery camera: the boxes ride a sub-stream a battery camera never
21801
+ * attaches, so such a camera stays on `['pipeline']` and its onboard AI is
21802
+ * worth exactly what it already is — a wake signal with a class.
21803
+ *
21804
+ * Defaults to `['pipeline']`, which is byte-for-byte today's behaviour: a
21805
+ * vendor never turns a detector on for the operator, and neither does a
21806
+ * default.
21807
+ */
21808
+ detectionSources: DetectionSourcesSchema.default(["pipeline"]),
21641
21809
  pipelineEnabled: boolean().default(true),
21642
21810
  /** Ordered tree of video steps. Absent → runner skips video detection. */
21643
21811
  steps: array(PipelineStepInputSchema).readonly().optional(),
@@ -21819,7 +21987,47 @@ var RunnerLocalMetricsSchema = object({
21819
21987
  queueDepth: number(),
21820
21988
  frameLazy: FrameLazyMetricsSchema.optional()
21821
21989
  });
21990
+ /**
21991
+ * Pipeline Runner capability — runtime detection workhorse.
21992
+ *
21993
+ * One instance per node. Receives camera assignments from
21994
+ * `addon-pipeline-orchestrator`, subscribes to the local stream-broker for
21995
+ * decoded frames, drains motion + detection queues, calls the local
21996
+ * `motion-detection` and `pipeline-executor` capabilities, and emits typed
21997
+ * `pipeline.inference-result` and `detection.motion-analysis` events on
21998
+ * the bus.
21999
+ *
22000
+ * Distinct from `pipeline-orchestrator` (the hub-side load balancer) — the
22001
+ * runner has zero knowledge of other agents, no global state, and never
22002
+ * makes assignment decisions itself.
22003
+ */
22004
+ /**
22005
+ * Why a runner declined to look at a camera out of band. Named rather than a
22006
+ * bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
22007
+ * already mid-session and one already bursting are four different facts, and a
22008
+ * caller told only "no" cannot tell a protection from a bug.
22009
+ */
22010
+ var OccupancyBurstRefusalSchema = _enum([
22011
+ "not-attached",
22012
+ "battery-asleep",
22013
+ "not-watching",
22014
+ "already-bursting"
22015
+ ]);
21822
22016
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
22017
+ deviceId: number(),
22018
+ /** How many frames to collect. The caller sizes this against what its
22019
+ * own confirmation needs; the runner clamps it to the field's range. */
22020
+ frames: number().int().positive().optional(),
22021
+ /** Why, for the log. A burst nobody can attribute is a cost nobody can
22022
+ * defend — the periodic one at least has a timer to point at. */
22023
+ reason: string()
22024
+ }), object({
22025
+ started: boolean(),
22026
+ refusedBecause: OccupancyBurstRefusalSchema.optional()
22027
+ }), {
22028
+ auth: "admin",
22029
+ kind: "mutation"
22030
+ }), method(object({
21823
22031
  handle: FrameHandleSchema,
21824
22032
  bbox: NativeCropBboxSchema,
21825
22033
  maxWidth: number().int().positive().optional(),
@@ -28657,11 +28865,46 @@ var NativeObjectDetectionStatusSchema = object({
28657
28865
  supportedClasses: array(NativeObjectClassEnum).readonly(),
28658
28866
  /**
28659
28867
  * Whether forwarding of onboard AI detections is enabled for this device.
28660
- * Default FALSE (opt-in, cold-start) — onboard AI pushes are noisy/sparse and
28661
- * churn the tracker, so forwarding stays off until the operator enables it.
28868
+ *
28869
+ * Cold-start OFF on every camera. A vendor does not decide which detector a
28870
+ * camera runs — that is the operator's choice, and it is made where every
28871
+ * other detection choice is made. `geometry` on
28872
+ * {@link NativeObjectDetectionOptionsSchema} is how the form says what the
28873
+ * choice would buy this particular camera.
28662
28874
  */
28663
28875
  enabled: boolean()
28664
28876
  });
28877
+ /**
28878
+ * WHEN a camera's onboard detections carry geometry.
28879
+ *
28880
+ * - `boxed` — always. The provider holds a standing channel for the boxes, so
28881
+ * a detection is a trackable subject whenever the camera sees one.
28882
+ * - `while-streaming` — only while some stream of this camera is already open.
28883
+ * The boxes ride the video's own side-channel rather than a feed of their
28884
+ * own, so they cost nothing and they exist only when something is pulling.
28885
+ * On Reolink this is every BATTERY camera: a persistent second feed is real
28886
+ * radio drain, but a camera that is awake is awake precisely because
28887
+ * something is in front of it, and its stream is already being pulled.
28888
+ * - `flag-only` — never. The firmware ships the class and nothing else; the
28889
+ * pipeline needs geometry to make a subject, so such a push reaches the
28890
+ * tracker as motion and no further.
28891
+ *
28892
+ * Reported and not inferred, because the operator's question in front of the
28893
+ * picker is "what do I get", and these are three different answers (D14: the
28894
+ * derived form is only as honest as `getOptions`).
28895
+ */
28896
+ var NativeObjectGeometryEnum = _enum([
28897
+ "boxed",
28898
+ "while-streaming",
28899
+ "flag-only"
28900
+ ]);
28901
+ var NativeObjectDetectionOptionsSchema = object({
28902
+ /** Classes this firmware can detect — the same list the status reports. */
28903
+ supportedClasses: array(NativeObjectClassEnum).readonly(),
28904
+ /** What a detection from this camera carries. */
28905
+ geometry: NativeObjectGeometryEnum
28906
+ });
28907
+ var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
28665
28908
  var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
28666
28909
  /** Required by createRuntimeStateBridge — epoch ms of last refresh. */
28667
28910
  lastFetchedAt: number() });
@@ -28671,13 +28914,23 @@ var nativeObjectDetectionCapability = {
28671
28914
  deviceNative: true,
28672
28915
  mode: "singleton",
28673
28916
  deviceTypes: [DeviceType.Camera],
28674
- methods: { setEnabled: method(object({
28675
- deviceId: number(),
28676
- enabled: boolean()
28677
- }), _void(), {
28678
- kind: "mutation",
28679
- auth: "admin"
28680
- }) },
28917
+ methods: {
28918
+ getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
28919
+ setSettings: method(object({
28920
+ deviceId: number(),
28921
+ settings: NativeObjectDetectionSettingsPatchSchema
28922
+ }), _void(), {
28923
+ kind: "mutation",
28924
+ auth: "admin"
28925
+ }),
28926
+ setEnabled: method(object({
28927
+ deviceId: number(),
28928
+ enabled: boolean()
28929
+ }), _void(), {
28930
+ kind: "mutation",
28931
+ auth: "admin"
28932
+ })
28933
+ },
28681
28934
  events: { onDetected: { data: object({
28682
28935
  deviceId: number(),
28683
28936
  detection: NativeDetectionSchema
@@ -33525,7 +33778,28 @@ var PerScopeBreakdownSchema = object({
33525
33778
  /** Total tracked objects in this scope (frame / zone / unzoned). */
33526
33779
  totalObjects: number().int().nonnegative(),
33527
33780
  /** Per-class count. Keys are macro class names (e.g. `person`, `car`). */
33528
- byClass: record(string(), number().int().nonnegative())
33781
+ byClass: record(string(), number().int().nonnegative()),
33782
+ /**
33783
+ * Of `totalObjects`, how many are STANDING — objects the census holds,
33784
+ * present but not going anywhere (a parked car, a bin, a statue).
33785
+ *
33786
+ * The operator's question about a scene has two halves and they answer
33787
+ * different things: "what is parked here" is stable for hours and must not
33788
+ * flap, while "who is walking through" is the thing an alert is about. One
33789
+ * number could only serve one of them, and `totalObjects` served the first
33790
+ * badly — a passer-by moved it and every occupancy rule saw a change.
33791
+ *
33792
+ * `standing + transient === totalObjects`, always. The total keeps its old
33793
+ * meaning exactly, so no rule written against it changes behaviour.
33794
+ *
33795
+ * ABSENT means UNKNOWN, never zero (D393): a snapshot from a node older than
33796
+ * this field cannot say, and a reader that folds absence in as 0 reports "no
33797
+ * parked objects here" about a camera whose lot is full.
33798
+ */
33799
+ standingObjects: number().int().nonnegative().optional(),
33800
+ /** Of `totalObjects`, how many are passing through. See `standingObjects` —
33801
+ * same absence rule. */
33802
+ transientObjects: number().int().nonnegative().optional()
33529
33803
  });
33530
33804
  var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
33531
33805
  zoneId: string(),
@@ -33648,6 +33922,23 @@ var HistoryPointSchema = object({
33648
33922
  * triple covers the three spatial scopes (per-zone, frame-wide,
33649
33923
  * outside-any-zone). Crossing them with `className?` gives the full
33650
33924
  * combinatorial coverage the operator UI requested.
33925
+ *
33926
+ * ## ⚠️ The three history methods are DEPRECATED
33927
+ *
33928
+ * They are served from a 60-MINUTE in-memory ring. It dies with the process,
33929
+ * so after a restart they report nothing about a night that did happen, and any
33930
+ * window past an hour is silently clamped — a caller cannot tell a clamp from
33931
+ * an empty scene. The presets in the occupancy chart existed to hide that
33932
+ * horizon rather than to answer a question.
33933
+ *
33934
+ * Use `pipelineAnalytics.readLongTermSeries({ series: 'occupancy', scope })`
33935
+ * instead. Those rows are already written for every camera, ~60 bytes per
33936
+ * (camera, scope, 5-minute bucket), kept for a year, and survive a restart.
33937
+ * Spell the scope with `occupancyScope(zoneId?, className?)` — the same
33938
+ * function the writer uses, so the two cannot drift.
33939
+ *
33940
+ * The ring stays until these three go, and it is the only thing left reading
33941
+ * it. Do not add a fourth caller.
33651
33942
  */
33652
33943
  var zoneAnalyticsCapability = {
33653
33944
  name: "zone-analytics",
@@ -38047,12 +38338,24 @@ Object.freeze({
38047
38338
  addonId: null,
38048
38339
  access: "create"
38049
38340
  },
38341
+ "nativeObjectDetection.getOptions": {
38342
+ capName: "native-object-detection",
38343
+ capScope: "device",
38344
+ addonId: null,
38345
+ access: "view"
38346
+ },
38050
38347
  "nativeObjectDetection.setEnabled": {
38051
38348
  capName: "native-object-detection",
38052
38349
  capScope: "device",
38053
38350
  addonId: null,
38054
38351
  access: "create"
38055
38352
  },
38353
+ "nativeObjectDetection.setSettings": {
38354
+ capName: "native-object-detection",
38355
+ capScope: "device",
38356
+ addonId: null,
38357
+ access: "create"
38358
+ },
38056
38359
  "navigation.getFeatures": {
38057
38360
  capName: "navigation",
38058
38361
  capScope: "device",
@@ -38317,6 +38620,12 @@ Object.freeze({
38317
38620
  addonId: null,
38318
38621
  access: "view"
38319
38622
  },
38623
+ "notificationRules.getClipRetentionAsks": {
38624
+ capName: "notification-rules",
38625
+ capScope: "system",
38626
+ addonId: null,
38627
+ access: "view"
38628
+ },
38320
38629
  "notificationRules.getConditionCatalog": {
38321
38630
  capName: "notification-rules",
38322
38631
  capScope: "system",
@@ -38827,6 +39136,12 @@ Object.freeze({
38827
39136
  addonId: null,
38828
39137
  access: "create"
38829
39138
  },
39139
+ "pipelineAnalytics.readLongTermSeries": {
39140
+ capName: "pipeline-analytics",
39141
+ capScope: "device",
39142
+ addonId: null,
39143
+ access: "view"
39144
+ },
38830
39145
  "pipelineAnalytics.rebuildObjectEmbeddings": {
38831
39146
  capName: "pipeline-analytics",
38832
39147
  capScope: "device",
@@ -39499,6 +39814,12 @@ Object.freeze({
39499
39814
  addonId: null,
39500
39815
  access: "create"
39501
39816
  },
39817
+ "pipelineRunner.requestOccupancyBurst": {
39818
+ capName: "pipeline-runner",
39819
+ capScope: "system",
39820
+ addonId: null,
39821
+ access: "create"
39822
+ },
39502
39823
  "pipelineRunner.runDetailSubtree": {
39503
39824
  capName: "pipeline-runner",
39504
39825
  capScope: "system",
@@ -42279,11 +42600,21 @@ Object.freeze({
42279
42600
  form: "single",
42280
42601
  optional: false
42281
42602
  }],
42603
+ "nativeObjectDetection.getOptions": [{
42604
+ name: "deviceId",
42605
+ form: "single",
42606
+ optional: false
42607
+ }],
42282
42608
  "nativeObjectDetection.setEnabled": [{
42283
42609
  name: "deviceId",
42284
42610
  form: "single",
42285
42611
  optional: false
42286
42612
  }],
42613
+ "nativeObjectDetection.setSettings": [{
42614
+ name: "deviceId",
42615
+ form: "single",
42616
+ optional: false
42617
+ }],
42287
42618
  "navigation.getFeatures": [{
42288
42619
  name: "deviceId",
42289
42620
  form: "single",
@@ -42633,6 +42964,11 @@ Object.freeze({
42633
42964
  form: "single",
42634
42965
  optional: false
42635
42966
  }],
42967
+ "pipelineAnalytics.readLongTermSeries": [{
42968
+ name: "deviceId",
42969
+ form: "single",
42970
+ optional: false
42971
+ }],
42636
42972
  "pipelineAnalytics.rebuildObjectEmbeddings": [{
42637
42973
  name: "deviceId",
42638
42974
  form: "single",
@@ -42823,6 +43159,11 @@ Object.freeze({
42823
43159
  form: "single",
42824
43160
  optional: false
42825
43161
  }],
43162
+ "pipelineRunner.requestOccupancyBurst": [{
43163
+ name: "deviceId",
43164
+ form: "single",
43165
+ optional: false
43166
+ }],
42826
43167
  "pipelineRunner.runDetailSubtree": [{
42827
43168
  name: "deviceId",
42828
43169
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreo",
3
- "version": "0.2.102",
3
+ "version": "0.2.104",
4
4
  "description": "Dreo smart-device (fan / air-circulator / purifier / heater / humidifier) device-provider addon for CamStack — wraps the @apocaliss92/nodedreo Dreo cloud client (REST + WebSocket)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -71,7 +71,7 @@
71
71
  "dev": "vite build --watch",
72
72
  "typecheck": "tsc --noEmit",
73
73
  "test": "vitest run",
74
- "publish": "npm publish --access public"
74
+ "publish:npm": "npm publish --access public"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@apocaliss92/nodedreo": "file:./vendor/apocaliss92-nodedreo-0.1.0.tgz",