@camstack/addon-provider-amcrest 0.2.104 → 0.2.106

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
@@ -5367,7 +5367,7 @@ var ZodIssueCode = {
5367
5367
  var ZodFirstPartyTypeKind;
5368
5368
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5369
5369
  //#endregion
5370
- //#region ../types/dist/sleep-DEuj7E3j.mjs
5370
+ //#region ../types/dist/sleep-BDR76Ykr.mjs
5371
5371
  /**
5372
5372
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5373
5373
  * window to float samples (D455).
@@ -11557,6 +11557,21 @@ var streamSignalsCapability = {
11557
11557
  mode: "singleton",
11558
11558
  deviceTypes: Object.values(DeviceType),
11559
11559
  runtimeState: StreamSignalsStatusSchema,
11560
+ /**
11561
+ * Runtime-state durability: **session** — mirrored in RAM, never written.
11562
+ *
11563
+ * The slice holds what the DEVICE says it can emit. That is a probed fact,
11564
+ * not an operator choice: the provider re-declares it on every registration,
11565
+ * so losing it loses nothing and persisting it would freeze an answer the
11566
+ * camera is entitled to change. Measured the same day on the sibling case —
11567
+ * `native-object-detection.supportedClasses` was persisted, and a firmware
11568
+ * class the camera really detected stayed missing for the life of the row
11569
+ * because the fix could not reach it.
11570
+ *
11571
+ * See `RuntimeStateDurability`. Enforced by
11572
+ * `scripts/check-runtime-state-durability.ts`.
11573
+ */
11574
+ durability: "session",
11560
11575
  methods: {
11561
11576
  /**
11562
11577
  * What this device can emit. Empty is a valid and common answer — most
@@ -18171,6 +18186,48 @@ var NcAlarmConfigSchema = object({
18171
18186
  settings: NcAlarmSettingsSchema,
18172
18187
  coverage: array(NcAlarmModeCoverageSchema)
18173
18188
  });
18189
+ /**
18190
+ * ONE rule's demand on ONE camera's clip ring.
18191
+ *
18192
+ * A rule, not a camera: the broker takes the MAX over the asks that reach a
18193
+ * camera, so the answer stays a faithful description of the rules and the
18194
+ * bounding (the byte budget, the broker's own ceiling) stays where the cost
18195
+ * lives. A camera that appears in no ask is being told **nothing** — which is
18196
+ * a different thing from never having been told, and only the envelope
18197
+ * (`rulesLoaded`) can tell those apart.
18198
+ */
18199
+ var NcClipRetentionAskSchema = object({
18200
+ /**
18201
+ * The camera this ask is about. **Absent = every camera**, which is what a
18202
+ * rule with no `conditions.devices` means: it can fire anywhere, so it is
18203
+ * asking everywhere.
18204
+ */
18205
+ deviceId: number().int().nonnegative().optional(),
18206
+ /** Seconds of history the rule needs held for it. Never zero — a rule that
18207
+ * needs nothing produces no ask at all. */
18208
+ seconds: number().min(0).max(60),
18209
+ /** The profile the rule cuts from, when it named one. Absent = the cheapest
18210
+ * assigned, which is what the cut defaults to. */
18211
+ profile: CamProfileSchema.optional(),
18212
+ /** Who is asking — so a retention the operator did not expect names a rule. */
18213
+ ruleId: string(),
18214
+ ruleName: string(),
18215
+ /** Why this many seconds: the rule's own window, or the occupancy ceiling. */
18216
+ reason: _enum(["window", "occupancy"])
18217
+ });
18218
+ /**
18219
+ * The whole answer, with the one bit that keeps absence from reading as zero.
18220
+ *
18221
+ * `rulesLoaded` is false until the rule ledger has completed a load at least
18222
+ * once. A centre that is up but has not read its rules yet answers with an
18223
+ * empty ask list that means nothing at all — applying it would drop every
18224
+ * camera's ring to zero for the first events after a restart, which is the
18225
+ * footage this mechanism exists to keep.
18226
+ */
18227
+ var NcClipRetentionPlanSchema = object({
18228
+ rulesLoaded: boolean(),
18229
+ asks: array(NcClipRetentionAskSchema).readonly()
18230
+ });
18174
18231
  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 }), {
18175
18232
  kind: "mutation",
18176
18233
  auth: "admin",
@@ -18191,7 +18248,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
18191
18248
  }), object({ success: literal(true) }), {
18192
18249
  kind: "mutation",
18193
18250
  auth: "admin"
18194
- }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18251
+ }), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18195
18252
  deviceId: number().int(),
18196
18253
  muted: boolean()
18197
18254
  }), object({ success: literal(true) }), {
@@ -19376,13 +19433,26 @@ var TrackAudioLabelSchema = object({
19376
19433
  * - `audio` — an audio event on the camera itself that was anomalous for
19377
19434
  * THAT camera, loud, and heard while nothing visual was happening (D62).
19378
19435
  *
19436
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
19437
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
19438
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
19439
+ * detector.
19440
+ *
19441
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
19442
+ * a persisted onboard track failed `safeParse` on read and came back with NO
19443
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
19444
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
19445
+ * could not name.
19446
+ *
19379
19447
  * The spatial subsystems (tracker association, occupancy count, re-id /
19380
19448
  * embedding, resurrection) MUST skip every synthetic source. Test for that
19381
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
19382
- * check silently readmits every source added after it was written.
19449
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
19450
+ * `!== 'sensor'` check silently readmits every source added after it was
19451
+ * written.
19383
19452
  */
19384
19453
  var TrackSourceSchema = _enum([
19385
19454
  "pipeline",
19455
+ "onboard",
19386
19456
  "sensor",
19387
19457
  "audio"
19388
19458
  ]);
@@ -20765,6 +20835,33 @@ var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
20765
20835
  * solid is this track", cheaper than re-deriving it from a trajectory. */
20766
20836
  framesMatched: number().int()
20767
20837
  })).readonly() });
20838
+ /**
20839
+ * The long-term series a camera produces. A closed union so a typo cannot
20840
+ * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
20841
+ * is an alias of this, not a second list.
20842
+ */
20843
+ var AnalyticsLtsSeriesSchema = _enum([
20844
+ "motion",
20845
+ "audio-dbfs",
20846
+ "battery",
20847
+ "occupancy"
20848
+ ]);
20849
+ /**
20850
+ * One closed 5-minute bucket of a long-term series.
20851
+ *
20852
+ * `samples` is how many readings the bucket folded — it is the ACTIVITY for a
20853
+ * series like `motion`, and the confidence for one like `occupancy`. A bucket
20854
+ * that exists at all was measured; a bucket that is absent was not, which is a
20855
+ * different claim from zero and the reason this is a sparse series.
20856
+ */
20857
+ var LtsBucketSchema = object({
20858
+ scope: string(),
20859
+ bucketStart: number().int(),
20860
+ samples: number().int().nonnegative(),
20861
+ sum: number(),
20862
+ min: number(),
20863
+ max: number()
20864
+ });
20768
20865
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
20769
20866
  deviceId: number(),
20770
20867
  trackId: string()
@@ -20811,6 +20908,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20811
20908
  kinds: array(string()).optional(),
20812
20909
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20813
20910
  }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
20911
+ deviceId: number(),
20912
+ series: AnalyticsLtsSeriesSchema,
20913
+ /** Omit for EVERY scope of this series in the range. */
20914
+ scope: string().optional(),
20915
+ from: number(),
20916
+ to: number(),
20917
+ limit: number().int().positive().optional()
20918
+ }), array(LtsBucketSchema).readonly()), method(object({
20814
20919
  deviceId: number(),
20815
20920
  since: number(),
20816
20921
  until: number(),
@@ -21429,6 +21534,40 @@ var MotionSourceEnum = _enum([
21429
21534
  */
21430
21535
  var MotionSourcesSchema = array(MotionSourceEnum);
21431
21536
  /**
21537
+ * Which root detector a camera runs — EXACTLY ONE.
21538
+ *
21539
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
21540
+ * with (`DetectionSource`) rather than a second spelling of the same two
21541
+ * ideas: the value an operator picks here is the value that comes back on the
21542
+ * track, the overlay and the debug row.
21543
+ *
21544
+ * ## Why one, and why it is still an array
21545
+ *
21546
+ * Two roots on one camera is a capability nobody asked for and the operator has
21547
+ * since ruled out. It was never free: both planes feed the SAME per-device
21548
+ * stationary registry, both can promote the same parked object, and the two
21549
+ * detectors disagree about the same box by construction — which is the
21550
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
21551
+ *
21552
+ * The ARRAY shape survives because the stored settings and the attach payload
21553
+ * already speak it on every camera of every fleet, and a cap input that
21554
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
21555
+ * in the one direction that costs an operator their cameras. So the wire stays
21556
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
21557
+ * element rather than refused, and `max(1)` is what every consumer can then
21558
+ * rely on.
21559
+ *
21560
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
21561
+ * a camera an operator left with no root detector — and `planDetectionSources`
21562
+ * has always said so: inventing a root for them is how a default acts without
21563
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
21564
+ * suite caught it.
21565
+ *
21566
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
21567
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
21568
+ */
21569
+ var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
21570
+ /**
21432
21571
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
21433
21572
  * so cap-side consumers (the orchestrator forward, the runner addon's
21434
21573
  * cap implementation, tests) can reuse the type instead of redeclaring
@@ -21579,6 +21718,35 @@ var RunnerCameraConfigSchema = object({
21579
21718
  * events; the orchestrator forwards to `reportMotion`.
21580
21719
  */
21581
21720
  motionSources: MotionSourcesSchema.default(["analyzer"]),
21721
+ /**
21722
+ * WHICH root detector runs for this camera. The detection counterpart of
21723
+ * {@link motionSources}, same shape and same discipline: a per-camera list
21724
+ * of sources the system understands, vendor-agnostic, owned here.
21725
+ *
21726
+ * - `pipeline` — this runner's own root step on decoded pixels (`steps`).
21727
+ * - `onboard` — the CAMERA's own detector, admitted only when it is named
21728
+ * here. Its boxes reach post-analysis on the same
21729
+ * `PipelineInferenceResult` payload as the pipeline's, so an onboard-born
21730
+ * track gets the same tracker, the same overlay and the same detail
21731
+ * subtree (face / plate / embedding).
21732
+ *
21733
+ * `['onboard']` alone is the REPLACEMENT: this runner's root step does not
21734
+ * run, and that is where a camera with a usable onboard detector buys back
21735
+ * its share of the accelerator. It saves the root inference, not the decode
21736
+ * — the detail subtree still needs pixels to cut crops from.
21737
+ *
21738
+ * A camera whose onboard detections carry no geometry cannot serve as a root
21739
+ * step at all (there is nothing to track), and the cap that knows this says
21740
+ * so on `native-object-detection.getOptions().geometry`. On Reolink that is
21741
+ * every battery camera: the boxes ride a sub-stream a battery camera never
21742
+ * attaches, so such a camera stays on `['pipeline']` and its onboard AI is
21743
+ * worth exactly what it already is — a wake signal with a class.
21744
+ *
21745
+ * Defaults to `['pipeline']`, which is byte-for-byte today's behaviour: a
21746
+ * vendor never turns a detector on for the operator, and neither does a
21747
+ * default.
21748
+ */
21749
+ detectionSources: DetectionSourcesSchema.default(["pipeline"]),
21582
21750
  pipelineEnabled: boolean().default(true),
21583
21751
  /** Ordered tree of video steps. Absent → runner skips video detection. */
21584
21752
  steps: array(PipelineStepInputSchema).readonly().optional(),
@@ -21760,7 +21928,47 @@ var RunnerLocalMetricsSchema = object({
21760
21928
  queueDepth: number(),
21761
21929
  frameLazy: FrameLazyMetricsSchema.optional()
21762
21930
  });
21931
+ /**
21932
+ * Pipeline Runner capability — runtime detection workhorse.
21933
+ *
21934
+ * One instance per node. Receives camera assignments from
21935
+ * `addon-pipeline-orchestrator`, subscribes to the local stream-broker for
21936
+ * decoded frames, drains motion + detection queues, calls the local
21937
+ * `motion-detection` and `pipeline-executor` capabilities, and emits typed
21938
+ * `pipeline.inference-result` and `detection.motion-analysis` events on
21939
+ * the bus.
21940
+ *
21941
+ * Distinct from `pipeline-orchestrator` (the hub-side load balancer) — the
21942
+ * runner has zero knowledge of other agents, no global state, and never
21943
+ * makes assignment decisions itself.
21944
+ */
21945
+ /**
21946
+ * Why a runner declined to look at a camera out of band. Named rather than a
21947
+ * bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
21948
+ * already mid-session and one already bursting are four different facts, and a
21949
+ * caller told only "no" cannot tell a protection from a bug.
21950
+ */
21951
+ var OccupancyBurstRefusalSchema = _enum([
21952
+ "not-attached",
21953
+ "battery-asleep",
21954
+ "not-watching",
21955
+ "already-bursting"
21956
+ ]);
21763
21957
  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({
21958
+ deviceId: number(),
21959
+ /** How many frames to collect. The caller sizes this against what its
21960
+ * own confirmation needs; the runner clamps it to the field's range. */
21961
+ frames: number().int().positive().optional(),
21962
+ /** Why, for the log. A burst nobody can attribute is a cost nobody can
21963
+ * defend — the periodic one at least has a timer to point at. */
21964
+ reason: string()
21965
+ }), object({
21966
+ started: boolean(),
21967
+ refusedBecause: OccupancyBurstRefusalSchema.optional()
21968
+ }), {
21969
+ auth: "admin",
21970
+ kind: "mutation"
21971
+ }), method(object({
21764
21972
  handle: FrameHandleSchema,
21765
21973
  bbox: NativeCropBboxSchema,
21766
21974
  maxWidth: number().int().positive().optional(),
@@ -28719,11 +28927,46 @@ var NativeObjectDetectionStatusSchema = object({
28719
28927
  supportedClasses: array(NativeObjectClassEnum).readonly(),
28720
28928
  /**
28721
28929
  * Whether forwarding of onboard AI detections is enabled for this device.
28722
- * Default FALSE (opt-in, cold-start) — onboard AI pushes are noisy/sparse and
28723
- * churn the tracker, so forwarding stays off until the operator enables it.
28930
+ *
28931
+ * Cold-start OFF on every camera. A vendor does not decide which detector a
28932
+ * camera runs — that is the operator's choice, and it is made where every
28933
+ * other detection choice is made. `geometry` on
28934
+ * {@link NativeObjectDetectionOptionsSchema} is how the form says what the
28935
+ * choice would buy this particular camera.
28724
28936
  */
28725
28937
  enabled: boolean()
28726
28938
  });
28939
+ /**
28940
+ * WHEN a camera's onboard detections carry geometry.
28941
+ *
28942
+ * - `boxed` — always. The provider holds a standing channel for the boxes, so
28943
+ * a detection is a trackable subject whenever the camera sees one.
28944
+ * - `while-streaming` — only while some stream of this camera is already open.
28945
+ * The boxes ride the video's own side-channel rather than a feed of their
28946
+ * own, so they cost nothing and they exist only when something is pulling.
28947
+ * On Reolink this is every BATTERY camera: a persistent second feed is real
28948
+ * radio drain, but a camera that is awake is awake precisely because
28949
+ * something is in front of it, and its stream is already being pulled.
28950
+ * - `flag-only` — never. The firmware ships the class and nothing else; the
28951
+ * pipeline needs geometry to make a subject, so such a push reaches the
28952
+ * tracker as motion and no further.
28953
+ *
28954
+ * Reported and not inferred, because the operator's question in front of the
28955
+ * picker is "what do I get", and these are three different answers (D14: the
28956
+ * derived form is only as honest as `getOptions`).
28957
+ */
28958
+ var NativeObjectGeometryEnum = _enum([
28959
+ "boxed",
28960
+ "while-streaming",
28961
+ "flag-only"
28962
+ ]);
28963
+ var NativeObjectDetectionOptionsSchema = object({
28964
+ /** Classes this firmware can detect — the same list the status reports. */
28965
+ supportedClasses: array(NativeObjectClassEnum).readonly(),
28966
+ /** What a detection from this camera carries. */
28967
+ geometry: NativeObjectGeometryEnum
28968
+ });
28969
+ var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
28727
28970
  var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
28728
28971
  /** Required by createRuntimeStateBridge — epoch ms of last refresh. */
28729
28972
  lastFetchedAt: number() });
@@ -28733,13 +28976,23 @@ var nativeObjectDetectionCapability = {
28733
28976
  deviceNative: true,
28734
28977
  mode: "singleton",
28735
28978
  deviceTypes: [DeviceType.Camera],
28736
- methods: { setEnabled: method(object({
28737
- deviceId: number(),
28738
- enabled: boolean()
28739
- }), _void(), {
28740
- kind: "mutation",
28741
- auth: "admin"
28742
- }) },
28979
+ methods: {
28980
+ getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
28981
+ setSettings: method(object({
28982
+ deviceId: number(),
28983
+ settings: NativeObjectDetectionSettingsPatchSchema
28984
+ }), _void(), {
28985
+ kind: "mutation",
28986
+ auth: "admin"
28987
+ }),
28988
+ setEnabled: method(object({
28989
+ deviceId: number(),
28990
+ enabled: boolean()
28991
+ }), _void(), {
28992
+ kind: "mutation",
28993
+ auth: "admin"
28994
+ })
28995
+ },
28743
28996
  events: { onDetected: { data: object({
28744
28997
  deviceId: number(),
28745
28998
  detection: NativeDetectionSchema
@@ -33893,7 +34146,28 @@ var PerScopeBreakdownSchema = object({
33893
34146
  /** Total tracked objects in this scope (frame / zone / unzoned). */
33894
34147
  totalObjects: number().int().nonnegative(),
33895
34148
  /** Per-class count. Keys are macro class names (e.g. `person`, `car`). */
33896
- byClass: record(string(), number().int().nonnegative())
34149
+ byClass: record(string(), number().int().nonnegative()),
34150
+ /**
34151
+ * Of `totalObjects`, how many are STANDING — objects the census holds,
34152
+ * present but not going anywhere (a parked car, a bin, a statue).
34153
+ *
34154
+ * The operator's question about a scene has two halves and they answer
34155
+ * different things: "what is parked here" is stable for hours and must not
34156
+ * flap, while "who is walking through" is the thing an alert is about. One
34157
+ * number could only serve one of them, and `totalObjects` served the first
34158
+ * badly — a passer-by moved it and every occupancy rule saw a change.
34159
+ *
34160
+ * `standing + transient === totalObjects`, always. The total keeps its old
34161
+ * meaning exactly, so no rule written against it changes behaviour.
34162
+ *
34163
+ * ABSENT means UNKNOWN, never zero (D393): a snapshot from a node older than
34164
+ * this field cannot say, and a reader that folds absence in as 0 reports "no
34165
+ * parked objects here" about a camera whose lot is full.
34166
+ */
34167
+ standingObjects: number().int().nonnegative().optional(),
34168
+ /** Of `totalObjects`, how many are passing through. See `standingObjects` —
34169
+ * same absence rule. */
34170
+ transientObjects: number().int().nonnegative().optional()
33897
34171
  });
33898
34172
  var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
33899
34173
  zoneId: string(),
@@ -34016,6 +34290,23 @@ var HistoryPointSchema = object({
34016
34290
  * triple covers the three spatial scopes (per-zone, frame-wide,
34017
34291
  * outside-any-zone). Crossing them with `className?` gives the full
34018
34292
  * combinatorial coverage the operator UI requested.
34293
+ *
34294
+ * ## ⚠️ The three history methods are DEPRECATED
34295
+ *
34296
+ * They are served from a 60-MINUTE in-memory ring. It dies with the process,
34297
+ * so after a restart they report nothing about a night that did happen, and any
34298
+ * window past an hour is silently clamped — a caller cannot tell a clamp from
34299
+ * an empty scene. The presets in the occupancy chart existed to hide that
34300
+ * horizon rather than to answer a question.
34301
+ *
34302
+ * Use `pipelineAnalytics.readLongTermSeries({ series: 'occupancy', scope })`
34303
+ * instead. Those rows are already written for every camera, ~60 bytes per
34304
+ * (camera, scope, 5-minute bucket), kept for a year, and survive a restart.
34305
+ * Spell the scope with `occupancyScope(zoneId?, className?)` — the same
34306
+ * function the writer uses, so the two cannot drift.
34307
+ *
34308
+ * The ring stays until these three go, and it is the only thing left reading
34309
+ * it. Do not add a fourth caller.
34019
34310
  */
34020
34311
  var zoneAnalyticsCapability = {
34021
34312
  name: "zone-analytics",
@@ -38621,12 +38912,24 @@ Object.freeze({
38621
38912
  addonId: null,
38622
38913
  access: "create"
38623
38914
  },
38915
+ "nativeObjectDetection.getOptions": {
38916
+ capName: "native-object-detection",
38917
+ capScope: "device",
38918
+ addonId: null,
38919
+ access: "view"
38920
+ },
38624
38921
  "nativeObjectDetection.setEnabled": {
38625
38922
  capName: "native-object-detection",
38626
38923
  capScope: "device",
38627
38924
  addonId: null,
38628
38925
  access: "create"
38629
38926
  },
38927
+ "nativeObjectDetection.setSettings": {
38928
+ capName: "native-object-detection",
38929
+ capScope: "device",
38930
+ addonId: null,
38931
+ access: "create"
38932
+ },
38630
38933
  "navigation.getFeatures": {
38631
38934
  capName: "navigation",
38632
38935
  capScope: "device",
@@ -38891,6 +39194,12 @@ Object.freeze({
38891
39194
  addonId: null,
38892
39195
  access: "view"
38893
39196
  },
39197
+ "notificationRules.getClipRetentionAsks": {
39198
+ capName: "notification-rules",
39199
+ capScope: "system",
39200
+ addonId: null,
39201
+ access: "view"
39202
+ },
38894
39203
  "notificationRules.getConditionCatalog": {
38895
39204
  capName: "notification-rules",
38896
39205
  capScope: "system",
@@ -39401,6 +39710,12 @@ Object.freeze({
39401
39710
  addonId: null,
39402
39711
  access: "create"
39403
39712
  },
39713
+ "pipelineAnalytics.readLongTermSeries": {
39714
+ capName: "pipeline-analytics",
39715
+ capScope: "device",
39716
+ addonId: null,
39717
+ access: "view"
39718
+ },
39404
39719
  "pipelineAnalytics.rebuildObjectEmbeddings": {
39405
39720
  capName: "pipeline-analytics",
39406
39721
  capScope: "device",
@@ -40073,6 +40388,12 @@ Object.freeze({
40073
40388
  addonId: null,
40074
40389
  access: "create"
40075
40390
  },
40391
+ "pipelineRunner.requestOccupancyBurst": {
40392
+ capName: "pipeline-runner",
40393
+ capScope: "system",
40394
+ addonId: null,
40395
+ access: "create"
40396
+ },
40076
40397
  "pipelineRunner.runDetailSubtree": {
40077
40398
  capName: "pipeline-runner",
40078
40399
  capScope: "system",
@@ -42853,11 +43174,21 @@ Object.freeze({
42853
43174
  form: "single",
42854
43175
  optional: false
42855
43176
  }],
43177
+ "nativeObjectDetection.getOptions": [{
43178
+ name: "deviceId",
43179
+ form: "single",
43180
+ optional: false
43181
+ }],
42856
43182
  "nativeObjectDetection.setEnabled": [{
42857
43183
  name: "deviceId",
42858
43184
  form: "single",
42859
43185
  optional: false
42860
43186
  }],
43187
+ "nativeObjectDetection.setSettings": [{
43188
+ name: "deviceId",
43189
+ form: "single",
43190
+ optional: false
43191
+ }],
42861
43192
  "navigation.getFeatures": [{
42862
43193
  name: "deviceId",
42863
43194
  form: "single",
@@ -43207,6 +43538,11 @@ Object.freeze({
43207
43538
  form: "single",
43208
43539
  optional: false
43209
43540
  }],
43541
+ "pipelineAnalytics.readLongTermSeries": [{
43542
+ name: "deviceId",
43543
+ form: "single",
43544
+ optional: false
43545
+ }],
43210
43546
  "pipelineAnalytics.rebuildObjectEmbeddings": [{
43211
43547
  name: "deviceId",
43212
43548
  form: "single",
@@ -43397,6 +43733,11 @@ Object.freeze({
43397
43733
  form: "single",
43398
43734
  optional: false
43399
43735
  }],
43736
+ "pipelineRunner.requestOccupancyBurst": [{
43737
+ name: "deviceId",
43738
+ form: "single",
43739
+ optional: false
43740
+ }],
43400
43741
  "pipelineRunner.runDetailSubtree": [{
43401
43742
  name: "deviceId",
43402
43743
  form: "single",
package/dist/addon.mjs CHANGED
@@ -5368,7 +5368,7 @@ var ZodIssueCode = {
5368
5368
  var ZodFirstPartyTypeKind;
5369
5369
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5370
5370
  //#endregion
5371
- //#region ../types/dist/sleep-DEuj7E3j.mjs
5371
+ //#region ../types/dist/sleep-BDR76Ykr.mjs
5372
5372
  /**
5373
5373
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5374
5374
  * window to float samples (D455).
@@ -11558,6 +11558,21 @@ var streamSignalsCapability = {
11558
11558
  mode: "singleton",
11559
11559
  deviceTypes: Object.values(DeviceType),
11560
11560
  runtimeState: StreamSignalsStatusSchema,
11561
+ /**
11562
+ * Runtime-state durability: **session** — mirrored in RAM, never written.
11563
+ *
11564
+ * The slice holds what the DEVICE says it can emit. That is a probed fact,
11565
+ * not an operator choice: the provider re-declares it on every registration,
11566
+ * so losing it loses nothing and persisting it would freeze an answer the
11567
+ * camera is entitled to change. Measured the same day on the sibling case —
11568
+ * `native-object-detection.supportedClasses` was persisted, and a firmware
11569
+ * class the camera really detected stayed missing for the life of the row
11570
+ * because the fix could not reach it.
11571
+ *
11572
+ * See `RuntimeStateDurability`. Enforced by
11573
+ * `scripts/check-runtime-state-durability.ts`.
11574
+ */
11575
+ durability: "session",
11561
11576
  methods: {
11562
11577
  /**
11563
11578
  * What this device can emit. Empty is a valid and common answer — most
@@ -18172,6 +18187,48 @@ var NcAlarmConfigSchema = object({
18172
18187
  settings: NcAlarmSettingsSchema,
18173
18188
  coverage: array(NcAlarmModeCoverageSchema)
18174
18189
  });
18190
+ /**
18191
+ * ONE rule's demand on ONE camera's clip ring.
18192
+ *
18193
+ * A rule, not a camera: the broker takes the MAX over the asks that reach a
18194
+ * camera, so the answer stays a faithful description of the rules and the
18195
+ * bounding (the byte budget, the broker's own ceiling) stays where the cost
18196
+ * lives. A camera that appears in no ask is being told **nothing** — which is
18197
+ * a different thing from never having been told, and only the envelope
18198
+ * (`rulesLoaded`) can tell those apart.
18199
+ */
18200
+ var NcClipRetentionAskSchema = object({
18201
+ /**
18202
+ * The camera this ask is about. **Absent = every camera**, which is what a
18203
+ * rule with no `conditions.devices` means: it can fire anywhere, so it is
18204
+ * asking everywhere.
18205
+ */
18206
+ deviceId: number().int().nonnegative().optional(),
18207
+ /** Seconds of history the rule needs held for it. Never zero — a rule that
18208
+ * needs nothing produces no ask at all. */
18209
+ seconds: number().min(0).max(60),
18210
+ /** The profile the rule cuts from, when it named one. Absent = the cheapest
18211
+ * assigned, which is what the cut defaults to. */
18212
+ profile: CamProfileSchema.optional(),
18213
+ /** Who is asking — so a retention the operator did not expect names a rule. */
18214
+ ruleId: string(),
18215
+ ruleName: string(),
18216
+ /** Why this many seconds: the rule's own window, or the occupancy ceiling. */
18217
+ reason: _enum(["window", "occupancy"])
18218
+ });
18219
+ /**
18220
+ * The whole answer, with the one bit that keeps absence from reading as zero.
18221
+ *
18222
+ * `rulesLoaded` is false until the rule ledger has completed a load at least
18223
+ * once. A centre that is up but has not read its rules yet answers with an
18224
+ * empty ask list that means nothing at all — applying it would drop every
18225
+ * camera's ring to zero for the first events after a restart, which is the
18226
+ * footage this mechanism exists to keep.
18227
+ */
18228
+ var NcClipRetentionPlanSchema = object({
18229
+ rulesLoaded: boolean(),
18230
+ asks: array(NcClipRetentionAskSchema).readonly()
18231
+ });
18175
18232
  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 }), {
18176
18233
  kind: "mutation",
18177
18234
  auth: "admin",
@@ -18192,7 +18249,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
18192
18249
  }), object({ success: literal(true) }), {
18193
18250
  kind: "mutation",
18194
18251
  auth: "admin"
18195
- }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18252
+ }), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18196
18253
  deviceId: number().int(),
18197
18254
  muted: boolean()
18198
18255
  }), object({ success: literal(true) }), {
@@ -19377,13 +19434,26 @@ var TrackAudioLabelSchema = object({
19377
19434
  * - `audio` — an audio event on the camera itself that was anomalous for
19378
19435
  * THAT camera, loud, and heard while nothing visual was happening (D62).
19379
19436
  *
19437
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
19438
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
19439
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
19440
+ * detector.
19441
+ *
19442
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
19443
+ * a persisted onboard track failed `safeParse` on read and came back with NO
19444
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
19445
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
19446
+ * could not name.
19447
+ *
19380
19448
  * The spatial subsystems (tracker association, occupancy count, re-id /
19381
19449
  * embedding, resurrection) MUST skip every synthetic source. Test for that
19382
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
19383
- * check silently readmits every source added after it was written.
19450
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
19451
+ * `!== 'sensor'` check silently readmits every source added after it was
19452
+ * written.
19384
19453
  */
19385
19454
  var TrackSourceSchema = _enum([
19386
19455
  "pipeline",
19456
+ "onboard",
19387
19457
  "sensor",
19388
19458
  "audio"
19389
19459
  ]);
@@ -20766,6 +20836,33 @@ var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
20766
20836
  * solid is this track", cheaper than re-deriving it from a trajectory. */
20767
20837
  framesMatched: number().int()
20768
20838
  })).readonly() });
20839
+ /**
20840
+ * The long-term series a camera produces. A closed union so a typo cannot
20841
+ * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
20842
+ * is an alias of this, not a second list.
20843
+ */
20844
+ var AnalyticsLtsSeriesSchema = _enum([
20845
+ "motion",
20846
+ "audio-dbfs",
20847
+ "battery",
20848
+ "occupancy"
20849
+ ]);
20850
+ /**
20851
+ * One closed 5-minute bucket of a long-term series.
20852
+ *
20853
+ * `samples` is how many readings the bucket folded — it is the ACTIVITY for a
20854
+ * series like `motion`, and the confidence for one like `occupancy`. A bucket
20855
+ * that exists at all was measured; a bucket that is absent was not, which is a
20856
+ * different claim from zero and the reason this is a sparse series.
20857
+ */
20858
+ var LtsBucketSchema = object({
20859
+ scope: string(),
20860
+ bucketStart: number().int(),
20861
+ samples: number().int().nonnegative(),
20862
+ sum: number(),
20863
+ min: number(),
20864
+ max: number()
20865
+ });
20769
20866
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
20770
20867
  deviceId: number(),
20771
20868
  trackId: string()
@@ -20812,6 +20909,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20812
20909
  kinds: array(string()).optional(),
20813
20910
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20814
20911
  }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
20912
+ deviceId: number(),
20913
+ series: AnalyticsLtsSeriesSchema,
20914
+ /** Omit for EVERY scope of this series in the range. */
20915
+ scope: string().optional(),
20916
+ from: number(),
20917
+ to: number(),
20918
+ limit: number().int().positive().optional()
20919
+ }), array(LtsBucketSchema).readonly()), method(object({
20815
20920
  deviceId: number(),
20816
20921
  since: number(),
20817
20922
  until: number(),
@@ -21430,6 +21535,40 @@ var MotionSourceEnum = _enum([
21430
21535
  */
21431
21536
  var MotionSourcesSchema = array(MotionSourceEnum);
21432
21537
  /**
21538
+ * Which root detector a camera runs — EXACTLY ONE.
21539
+ *
21540
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
21541
+ * with (`DetectionSource`) rather than a second spelling of the same two
21542
+ * ideas: the value an operator picks here is the value that comes back on the
21543
+ * track, the overlay and the debug row.
21544
+ *
21545
+ * ## Why one, and why it is still an array
21546
+ *
21547
+ * Two roots on one camera is a capability nobody asked for and the operator has
21548
+ * since ruled out. It was never free: both planes feed the SAME per-device
21549
+ * stationary registry, both can promote the same parked object, and the two
21550
+ * detectors disagree about the same box by construction — which is the
21551
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
21552
+ *
21553
+ * The ARRAY shape survives because the stored settings and the attach payload
21554
+ * already speak it on every camera of every fleet, and a cap input that
21555
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
21556
+ * in the one direction that costs an operator their cameras. So the wire stays
21557
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
21558
+ * element rather than refused, and `max(1)` is what every consumer can then
21559
+ * rely on.
21560
+ *
21561
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
21562
+ * a camera an operator left with no root detector — and `planDetectionSources`
21563
+ * has always said so: inventing a root for them is how a default acts without
21564
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
21565
+ * suite caught it.
21566
+ *
21567
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
21568
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
21569
+ */
21570
+ var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
21571
+ /**
21433
21572
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
21434
21573
  * so cap-side consumers (the orchestrator forward, the runner addon's
21435
21574
  * cap implementation, tests) can reuse the type instead of redeclaring
@@ -21580,6 +21719,35 @@ var RunnerCameraConfigSchema = object({
21580
21719
  * events; the orchestrator forwards to `reportMotion`.
21581
21720
  */
21582
21721
  motionSources: MotionSourcesSchema.default(["analyzer"]),
21722
+ /**
21723
+ * WHICH root detector runs for this camera. The detection counterpart of
21724
+ * {@link motionSources}, same shape and same discipline: a per-camera list
21725
+ * of sources the system understands, vendor-agnostic, owned here.
21726
+ *
21727
+ * - `pipeline` — this runner's own root step on decoded pixels (`steps`).
21728
+ * - `onboard` — the CAMERA's own detector, admitted only when it is named
21729
+ * here. Its boxes reach post-analysis on the same
21730
+ * `PipelineInferenceResult` payload as the pipeline's, so an onboard-born
21731
+ * track gets the same tracker, the same overlay and the same detail
21732
+ * subtree (face / plate / embedding).
21733
+ *
21734
+ * `['onboard']` alone is the REPLACEMENT: this runner's root step does not
21735
+ * run, and that is where a camera with a usable onboard detector buys back
21736
+ * its share of the accelerator. It saves the root inference, not the decode
21737
+ * — the detail subtree still needs pixels to cut crops from.
21738
+ *
21739
+ * A camera whose onboard detections carry no geometry cannot serve as a root
21740
+ * step at all (there is nothing to track), and the cap that knows this says
21741
+ * so on `native-object-detection.getOptions().geometry`. On Reolink that is
21742
+ * every battery camera: the boxes ride a sub-stream a battery camera never
21743
+ * attaches, so such a camera stays on `['pipeline']` and its onboard AI is
21744
+ * worth exactly what it already is — a wake signal with a class.
21745
+ *
21746
+ * Defaults to `['pipeline']`, which is byte-for-byte today's behaviour: a
21747
+ * vendor never turns a detector on for the operator, and neither does a
21748
+ * default.
21749
+ */
21750
+ detectionSources: DetectionSourcesSchema.default(["pipeline"]),
21583
21751
  pipelineEnabled: boolean().default(true),
21584
21752
  /** Ordered tree of video steps. Absent → runner skips video detection. */
21585
21753
  steps: array(PipelineStepInputSchema).readonly().optional(),
@@ -21761,7 +21929,47 @@ var RunnerLocalMetricsSchema = object({
21761
21929
  queueDepth: number(),
21762
21930
  frameLazy: FrameLazyMetricsSchema.optional()
21763
21931
  });
21932
+ /**
21933
+ * Pipeline Runner capability — runtime detection workhorse.
21934
+ *
21935
+ * One instance per node. Receives camera assignments from
21936
+ * `addon-pipeline-orchestrator`, subscribes to the local stream-broker for
21937
+ * decoded frames, drains motion + detection queues, calls the local
21938
+ * `motion-detection` and `pipeline-executor` capabilities, and emits typed
21939
+ * `pipeline.inference-result` and `detection.motion-analysis` events on
21940
+ * the bus.
21941
+ *
21942
+ * Distinct from `pipeline-orchestrator` (the hub-side load balancer) — the
21943
+ * runner has zero knowledge of other agents, no global state, and never
21944
+ * makes assignment decisions itself.
21945
+ */
21946
+ /**
21947
+ * Why a runner declined to look at a camera out of band. Named rather than a
21948
+ * bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
21949
+ * already mid-session and one already bursting are four different facts, and a
21950
+ * caller told only "no" cannot tell a protection from a bug.
21951
+ */
21952
+ var OccupancyBurstRefusalSchema = _enum([
21953
+ "not-attached",
21954
+ "battery-asleep",
21955
+ "not-watching",
21956
+ "already-bursting"
21957
+ ]);
21764
21958
  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({
21959
+ deviceId: number(),
21960
+ /** How many frames to collect. The caller sizes this against what its
21961
+ * own confirmation needs; the runner clamps it to the field's range. */
21962
+ frames: number().int().positive().optional(),
21963
+ /** Why, for the log. A burst nobody can attribute is a cost nobody can
21964
+ * defend — the periodic one at least has a timer to point at. */
21965
+ reason: string()
21966
+ }), object({
21967
+ started: boolean(),
21968
+ refusedBecause: OccupancyBurstRefusalSchema.optional()
21969
+ }), {
21970
+ auth: "admin",
21971
+ kind: "mutation"
21972
+ }), method(object({
21765
21973
  handle: FrameHandleSchema,
21766
21974
  bbox: NativeCropBboxSchema,
21767
21975
  maxWidth: number().int().positive().optional(),
@@ -28720,11 +28928,46 @@ var NativeObjectDetectionStatusSchema = object({
28720
28928
  supportedClasses: array(NativeObjectClassEnum).readonly(),
28721
28929
  /**
28722
28930
  * Whether forwarding of onboard AI detections is enabled for this device.
28723
- * Default FALSE (opt-in, cold-start) — onboard AI pushes are noisy/sparse and
28724
- * churn the tracker, so forwarding stays off until the operator enables it.
28931
+ *
28932
+ * Cold-start OFF on every camera. A vendor does not decide which detector a
28933
+ * camera runs — that is the operator's choice, and it is made where every
28934
+ * other detection choice is made. `geometry` on
28935
+ * {@link NativeObjectDetectionOptionsSchema} is how the form says what the
28936
+ * choice would buy this particular camera.
28725
28937
  */
28726
28938
  enabled: boolean()
28727
28939
  });
28940
+ /**
28941
+ * WHEN a camera's onboard detections carry geometry.
28942
+ *
28943
+ * - `boxed` — always. The provider holds a standing channel for the boxes, so
28944
+ * a detection is a trackable subject whenever the camera sees one.
28945
+ * - `while-streaming` — only while some stream of this camera is already open.
28946
+ * The boxes ride the video's own side-channel rather than a feed of their
28947
+ * own, so they cost nothing and they exist only when something is pulling.
28948
+ * On Reolink this is every BATTERY camera: a persistent second feed is real
28949
+ * radio drain, but a camera that is awake is awake precisely because
28950
+ * something is in front of it, and its stream is already being pulled.
28951
+ * - `flag-only` — never. The firmware ships the class and nothing else; the
28952
+ * pipeline needs geometry to make a subject, so such a push reaches the
28953
+ * tracker as motion and no further.
28954
+ *
28955
+ * Reported and not inferred, because the operator's question in front of the
28956
+ * picker is "what do I get", and these are three different answers (D14: the
28957
+ * derived form is only as honest as `getOptions`).
28958
+ */
28959
+ var NativeObjectGeometryEnum = _enum([
28960
+ "boxed",
28961
+ "while-streaming",
28962
+ "flag-only"
28963
+ ]);
28964
+ var NativeObjectDetectionOptionsSchema = object({
28965
+ /** Classes this firmware can detect — the same list the status reports. */
28966
+ supportedClasses: array(NativeObjectClassEnum).readonly(),
28967
+ /** What a detection from this camera carries. */
28968
+ geometry: NativeObjectGeometryEnum
28969
+ });
28970
+ var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
28728
28971
  var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
28729
28972
  /** Required by createRuntimeStateBridge — epoch ms of last refresh. */
28730
28973
  lastFetchedAt: number() });
@@ -28734,13 +28977,23 @@ var nativeObjectDetectionCapability = {
28734
28977
  deviceNative: true,
28735
28978
  mode: "singleton",
28736
28979
  deviceTypes: [DeviceType.Camera],
28737
- methods: { setEnabled: method(object({
28738
- deviceId: number(),
28739
- enabled: boolean()
28740
- }), _void(), {
28741
- kind: "mutation",
28742
- auth: "admin"
28743
- }) },
28980
+ methods: {
28981
+ getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
28982
+ setSettings: method(object({
28983
+ deviceId: number(),
28984
+ settings: NativeObjectDetectionSettingsPatchSchema
28985
+ }), _void(), {
28986
+ kind: "mutation",
28987
+ auth: "admin"
28988
+ }),
28989
+ setEnabled: method(object({
28990
+ deviceId: number(),
28991
+ enabled: boolean()
28992
+ }), _void(), {
28993
+ kind: "mutation",
28994
+ auth: "admin"
28995
+ })
28996
+ },
28744
28997
  events: { onDetected: { data: object({
28745
28998
  deviceId: number(),
28746
28999
  detection: NativeDetectionSchema
@@ -33894,7 +34147,28 @@ var PerScopeBreakdownSchema = object({
33894
34147
  /** Total tracked objects in this scope (frame / zone / unzoned). */
33895
34148
  totalObjects: number().int().nonnegative(),
33896
34149
  /** Per-class count. Keys are macro class names (e.g. `person`, `car`). */
33897
- byClass: record(string(), number().int().nonnegative())
34150
+ byClass: record(string(), number().int().nonnegative()),
34151
+ /**
34152
+ * Of `totalObjects`, how many are STANDING — objects the census holds,
34153
+ * present but not going anywhere (a parked car, a bin, a statue).
34154
+ *
34155
+ * The operator's question about a scene has two halves and they answer
34156
+ * different things: "what is parked here" is stable for hours and must not
34157
+ * flap, while "who is walking through" is the thing an alert is about. One
34158
+ * number could only serve one of them, and `totalObjects` served the first
34159
+ * badly — a passer-by moved it and every occupancy rule saw a change.
34160
+ *
34161
+ * `standing + transient === totalObjects`, always. The total keeps its old
34162
+ * meaning exactly, so no rule written against it changes behaviour.
34163
+ *
34164
+ * ABSENT means UNKNOWN, never zero (D393): a snapshot from a node older than
34165
+ * this field cannot say, and a reader that folds absence in as 0 reports "no
34166
+ * parked objects here" about a camera whose lot is full.
34167
+ */
34168
+ standingObjects: number().int().nonnegative().optional(),
34169
+ /** Of `totalObjects`, how many are passing through. See `standingObjects` —
34170
+ * same absence rule. */
34171
+ transientObjects: number().int().nonnegative().optional()
33898
34172
  });
33899
34173
  var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
33900
34174
  zoneId: string(),
@@ -34017,6 +34291,23 @@ var HistoryPointSchema = object({
34017
34291
  * triple covers the three spatial scopes (per-zone, frame-wide,
34018
34292
  * outside-any-zone). Crossing them with `className?` gives the full
34019
34293
  * combinatorial coverage the operator UI requested.
34294
+ *
34295
+ * ## ⚠️ The three history methods are DEPRECATED
34296
+ *
34297
+ * They are served from a 60-MINUTE in-memory ring. It dies with the process,
34298
+ * so after a restart they report nothing about a night that did happen, and any
34299
+ * window past an hour is silently clamped — a caller cannot tell a clamp from
34300
+ * an empty scene. The presets in the occupancy chart existed to hide that
34301
+ * horizon rather than to answer a question.
34302
+ *
34303
+ * Use `pipelineAnalytics.readLongTermSeries({ series: 'occupancy', scope })`
34304
+ * instead. Those rows are already written for every camera, ~60 bytes per
34305
+ * (camera, scope, 5-minute bucket), kept for a year, and survive a restart.
34306
+ * Spell the scope with `occupancyScope(zoneId?, className?)` — the same
34307
+ * function the writer uses, so the two cannot drift.
34308
+ *
34309
+ * The ring stays until these three go, and it is the only thing left reading
34310
+ * it. Do not add a fourth caller.
34020
34311
  */
34021
34312
  var zoneAnalyticsCapability = {
34022
34313
  name: "zone-analytics",
@@ -38622,12 +38913,24 @@ Object.freeze({
38622
38913
  addonId: null,
38623
38914
  access: "create"
38624
38915
  },
38916
+ "nativeObjectDetection.getOptions": {
38917
+ capName: "native-object-detection",
38918
+ capScope: "device",
38919
+ addonId: null,
38920
+ access: "view"
38921
+ },
38625
38922
  "nativeObjectDetection.setEnabled": {
38626
38923
  capName: "native-object-detection",
38627
38924
  capScope: "device",
38628
38925
  addonId: null,
38629
38926
  access: "create"
38630
38927
  },
38928
+ "nativeObjectDetection.setSettings": {
38929
+ capName: "native-object-detection",
38930
+ capScope: "device",
38931
+ addonId: null,
38932
+ access: "create"
38933
+ },
38631
38934
  "navigation.getFeatures": {
38632
38935
  capName: "navigation",
38633
38936
  capScope: "device",
@@ -38892,6 +39195,12 @@ Object.freeze({
38892
39195
  addonId: null,
38893
39196
  access: "view"
38894
39197
  },
39198
+ "notificationRules.getClipRetentionAsks": {
39199
+ capName: "notification-rules",
39200
+ capScope: "system",
39201
+ addonId: null,
39202
+ access: "view"
39203
+ },
38895
39204
  "notificationRules.getConditionCatalog": {
38896
39205
  capName: "notification-rules",
38897
39206
  capScope: "system",
@@ -39402,6 +39711,12 @@ Object.freeze({
39402
39711
  addonId: null,
39403
39712
  access: "create"
39404
39713
  },
39714
+ "pipelineAnalytics.readLongTermSeries": {
39715
+ capName: "pipeline-analytics",
39716
+ capScope: "device",
39717
+ addonId: null,
39718
+ access: "view"
39719
+ },
39405
39720
  "pipelineAnalytics.rebuildObjectEmbeddings": {
39406
39721
  capName: "pipeline-analytics",
39407
39722
  capScope: "device",
@@ -40074,6 +40389,12 @@ Object.freeze({
40074
40389
  addonId: null,
40075
40390
  access: "create"
40076
40391
  },
40392
+ "pipelineRunner.requestOccupancyBurst": {
40393
+ capName: "pipeline-runner",
40394
+ capScope: "system",
40395
+ addonId: null,
40396
+ access: "create"
40397
+ },
40077
40398
  "pipelineRunner.runDetailSubtree": {
40078
40399
  capName: "pipeline-runner",
40079
40400
  capScope: "system",
@@ -42854,11 +43175,21 @@ Object.freeze({
42854
43175
  form: "single",
42855
43176
  optional: false
42856
43177
  }],
43178
+ "nativeObjectDetection.getOptions": [{
43179
+ name: "deviceId",
43180
+ form: "single",
43181
+ optional: false
43182
+ }],
42857
43183
  "nativeObjectDetection.setEnabled": [{
42858
43184
  name: "deviceId",
42859
43185
  form: "single",
42860
43186
  optional: false
42861
43187
  }],
43188
+ "nativeObjectDetection.setSettings": [{
43189
+ name: "deviceId",
43190
+ form: "single",
43191
+ optional: false
43192
+ }],
42862
43193
  "navigation.getFeatures": [{
42863
43194
  name: "deviceId",
42864
43195
  form: "single",
@@ -43208,6 +43539,11 @@ Object.freeze({
43208
43539
  form: "single",
43209
43540
  optional: false
43210
43541
  }],
43542
+ "pipelineAnalytics.readLongTermSeries": [{
43543
+ name: "deviceId",
43544
+ form: "single",
43545
+ optional: false
43546
+ }],
43211
43547
  "pipelineAnalytics.rebuildObjectEmbeddings": [{
43212
43548
  name: "deviceId",
43213
43549
  form: "single",
@@ -43398,6 +43734,11 @@ Object.freeze({
43398
43734
  form: "single",
43399
43735
  optional: false
43400
43736
  }],
43737
+ "pipelineRunner.requestOccupancyBurst": [{
43738
+ name: "deviceId",
43739
+ form: "single",
43740
+ optional: false
43741
+ }],
43401
43742
  "pipelineRunner.runDetailSubtree": [{
43402
43743
  name: "deviceId",
43403
43744
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-amcrest",
3
- "version": "0.2.104",
3
+ "version": "0.2.106",
4
4
  "description": "Amcrest/Dahua camera device provider addon for CamStack — Dahua CGI over HTTP(S) with digest auth (snapshot, RTSP catalog, PTZ, image/day-night config)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -80,7 +80,7 @@
80
80
  "build": "vite build",
81
81
  "dev": "vite build --watch",
82
82
  "typecheck": "tsc --noEmit",
83
- "publish": "npm publish --access public"
83
+ "publish:npm": "npm publish --access public"
84
84
  },
85
85
  "peerDependencies": {
86
86
  "@camstack/types": "*",