@camstack/addon-provider-petkit 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
@@ -6462,7 +6462,7 @@ var ZodIssueCode = {
6462
6462
  var ZodFirstPartyTypeKind;
6463
6463
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6464
6464
  //#endregion
6465
- //#region ../types/dist/sleep-DEuj7E3j.mjs
6465
+ //#region ../types/dist/sleep-BDR76Ykr.mjs
6466
6466
  /**
6467
6467
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6468
6468
  * window to float samples (D455).
@@ -12664,6 +12664,21 @@ var streamSignalsCapability = {
12664
12664
  mode: "singleton",
12665
12665
  deviceTypes: Object.values(DeviceType),
12666
12666
  runtimeState: StreamSignalsStatusSchema,
12667
+ /**
12668
+ * Runtime-state durability: **session** — mirrored in RAM, never written.
12669
+ *
12670
+ * The slice holds what the DEVICE says it can emit. That is a probed fact,
12671
+ * not an operator choice: the provider re-declares it on every registration,
12672
+ * so losing it loses nothing and persisting it would freeze an answer the
12673
+ * camera is entitled to change. Measured the same day on the sibling case —
12674
+ * `native-object-detection.supportedClasses` was persisted, and a firmware
12675
+ * class the camera really detected stayed missing for the life of the row
12676
+ * because the fix could not reach it.
12677
+ *
12678
+ * See `RuntimeStateDurability`. Enforced by
12679
+ * `scripts/check-runtime-state-durability.ts`.
12680
+ */
12681
+ durability: "session",
12667
12682
  methods: {
12668
12683
  /**
12669
12684
  * What this device can emit. Empty is a valid and common answer — most
@@ -19295,6 +19310,48 @@ var NcAlarmConfigSchema = object({
19295
19310
  settings: NcAlarmSettingsSchema,
19296
19311
  coverage: array(NcAlarmModeCoverageSchema)
19297
19312
  });
19313
+ /**
19314
+ * ONE rule's demand on ONE camera's clip ring.
19315
+ *
19316
+ * A rule, not a camera: the broker takes the MAX over the asks that reach a
19317
+ * camera, so the answer stays a faithful description of the rules and the
19318
+ * bounding (the byte budget, the broker's own ceiling) stays where the cost
19319
+ * lives. A camera that appears in no ask is being told **nothing** — which is
19320
+ * a different thing from never having been told, and only the envelope
19321
+ * (`rulesLoaded`) can tell those apart.
19322
+ */
19323
+ var NcClipRetentionAskSchema = object({
19324
+ /**
19325
+ * The camera this ask is about. **Absent = every camera**, which is what a
19326
+ * rule with no `conditions.devices` means: it can fire anywhere, so it is
19327
+ * asking everywhere.
19328
+ */
19329
+ deviceId: number().int().nonnegative().optional(),
19330
+ /** Seconds of history the rule needs held for it. Never zero — a rule that
19331
+ * needs nothing produces no ask at all. */
19332
+ seconds: number().min(0).max(60),
19333
+ /** The profile the rule cuts from, when it named one. Absent = the cheapest
19334
+ * assigned, which is what the cut defaults to. */
19335
+ profile: CamProfileSchema.optional(),
19336
+ /** Who is asking — so a retention the operator did not expect names a rule. */
19337
+ ruleId: string(),
19338
+ ruleName: string(),
19339
+ /** Why this many seconds: the rule's own window, or the occupancy ceiling. */
19340
+ reason: _enum(["window", "occupancy"])
19341
+ });
19342
+ /**
19343
+ * The whole answer, with the one bit that keeps absence from reading as zero.
19344
+ *
19345
+ * `rulesLoaded` is false until the rule ledger has completed a load at least
19346
+ * once. A centre that is up but has not read its rules yet answers with an
19347
+ * empty ask list that means nothing at all — applying it would drop every
19348
+ * camera's ring to zero for the first events after a restart, which is the
19349
+ * footage this mechanism exists to keep.
19350
+ */
19351
+ var NcClipRetentionPlanSchema = object({
19352
+ rulesLoaded: boolean(),
19353
+ asks: array(NcClipRetentionAskSchema).readonly()
19354
+ });
19298
19355
  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 }), {
19299
19356
  kind: "mutation",
19300
19357
  auth: "admin",
@@ -19315,7 +19372,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
19315
19372
  }), object({ success: literal(true) }), {
19316
19373
  kind: "mutation",
19317
19374
  auth: "admin"
19318
- }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
19375
+ }), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
19319
19376
  deviceId: number().int(),
19320
19377
  muted: boolean()
19321
19378
  }), object({ success: literal(true) }), {
@@ -20500,13 +20557,26 @@ var TrackAudioLabelSchema = object({
20500
20557
  * - `audio` — an audio event on the camera itself that was anomalous for
20501
20558
  * THAT camera, loud, and heard while nothing visual was happening (D62).
20502
20559
  *
20560
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
20561
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
20562
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
20563
+ * detector.
20564
+ *
20565
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
20566
+ * a persisted onboard track failed `safeParse` on read and came back with NO
20567
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
20568
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
20569
+ * could not name.
20570
+ *
20503
20571
  * The spatial subsystems (tracker association, occupancy count, re-id /
20504
20572
  * embedding, resurrection) MUST skip every synthetic source. Test for that
20505
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
20506
- * check silently readmits every source added after it was written.
20573
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
20574
+ * `!== 'sensor'` check silently readmits every source added after it was
20575
+ * written.
20507
20576
  */
20508
20577
  var TrackSourceSchema = _enum([
20509
20578
  "pipeline",
20579
+ "onboard",
20510
20580
  "sensor",
20511
20581
  "audio"
20512
20582
  ]);
@@ -21889,6 +21959,33 @@ var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
21889
21959
  * solid is this track", cheaper than re-deriving it from a trajectory. */
21890
21960
  framesMatched: number().int()
21891
21961
  })).readonly() });
21962
+ /**
21963
+ * The long-term series a camera produces. A closed union so a typo cannot
21964
+ * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
21965
+ * is an alias of this, not a second list.
21966
+ */
21967
+ var AnalyticsLtsSeriesSchema = _enum([
21968
+ "motion",
21969
+ "audio-dbfs",
21970
+ "battery",
21971
+ "occupancy"
21972
+ ]);
21973
+ /**
21974
+ * One closed 5-minute bucket of a long-term series.
21975
+ *
21976
+ * `samples` is how many readings the bucket folded — it is the ACTIVITY for a
21977
+ * series like `motion`, and the confidence for one like `occupancy`. A bucket
21978
+ * that exists at all was measured; a bucket that is absent was not, which is a
21979
+ * different claim from zero and the reason this is a sparse series.
21980
+ */
21981
+ var LtsBucketSchema = object({
21982
+ scope: string(),
21983
+ bucketStart: number().int(),
21984
+ samples: number().int().nonnegative(),
21985
+ sum: number(),
21986
+ min: number(),
21987
+ max: number()
21988
+ });
21892
21989
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
21893
21990
  deviceId: number(),
21894
21991
  trackId: string()
@@ -21935,6 +22032,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21935
22032
  kinds: array(string()).optional(),
21936
22033
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
21937
22034
  }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
22035
+ deviceId: number(),
22036
+ series: AnalyticsLtsSeriesSchema,
22037
+ /** Omit for EVERY scope of this series in the range. */
22038
+ scope: string().optional(),
22039
+ from: number(),
22040
+ to: number(),
22041
+ limit: number().int().positive().optional()
22042
+ }), array(LtsBucketSchema).readonly()), method(object({
21938
22043
  deviceId: number(),
21939
22044
  since: number(),
21940
22045
  until: number(),
@@ -22553,6 +22658,40 @@ var MotionSourceEnum = _enum([
22553
22658
  */
22554
22659
  var MotionSourcesSchema = array(MotionSourceEnum);
22555
22660
  /**
22661
+ * Which root detector a camera runs — EXACTLY ONE.
22662
+ *
22663
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
22664
+ * with (`DetectionSource`) rather than a second spelling of the same two
22665
+ * ideas: the value an operator picks here is the value that comes back on the
22666
+ * track, the overlay and the debug row.
22667
+ *
22668
+ * ## Why one, and why it is still an array
22669
+ *
22670
+ * Two roots on one camera is a capability nobody asked for and the operator has
22671
+ * since ruled out. It was never free: both planes feed the SAME per-device
22672
+ * stationary registry, both can promote the same parked object, and the two
22673
+ * detectors disagree about the same box by construction — which is the
22674
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
22675
+ *
22676
+ * The ARRAY shape survives because the stored settings and the attach payload
22677
+ * already speak it on every camera of every fleet, and a cap input that
22678
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
22679
+ * in the one direction that costs an operator their cameras. So the wire stays
22680
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
22681
+ * element rather than refused, and `max(1)` is what every consumer can then
22682
+ * rely on.
22683
+ *
22684
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
22685
+ * a camera an operator left with no root detector — and `planDetectionSources`
22686
+ * has always said so: inventing a root for them is how a default acts without
22687
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
22688
+ * suite caught it.
22689
+ *
22690
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
22691
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
22692
+ */
22693
+ var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
22694
+ /**
22556
22695
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
22557
22696
  * so cap-side consumers (the orchestrator forward, the runner addon's
22558
22697
  * cap implementation, tests) can reuse the type instead of redeclaring
@@ -22703,6 +22842,35 @@ var RunnerCameraConfigSchema = object({
22703
22842
  * events; the orchestrator forwards to `reportMotion`.
22704
22843
  */
22705
22844
  motionSources: MotionSourcesSchema.default(["analyzer"]),
22845
+ /**
22846
+ * WHICH root detector runs for this camera. The detection counterpart of
22847
+ * {@link motionSources}, same shape and same discipline: a per-camera list
22848
+ * of sources the system understands, vendor-agnostic, owned here.
22849
+ *
22850
+ * - `pipeline` — this runner's own root step on decoded pixels (`steps`).
22851
+ * - `onboard` — the CAMERA's own detector, admitted only when it is named
22852
+ * here. Its boxes reach post-analysis on the same
22853
+ * `PipelineInferenceResult` payload as the pipeline's, so an onboard-born
22854
+ * track gets the same tracker, the same overlay and the same detail
22855
+ * subtree (face / plate / embedding).
22856
+ *
22857
+ * `['onboard']` alone is the REPLACEMENT: this runner's root step does not
22858
+ * run, and that is where a camera with a usable onboard detector buys back
22859
+ * its share of the accelerator. It saves the root inference, not the decode
22860
+ * — the detail subtree still needs pixels to cut crops from.
22861
+ *
22862
+ * A camera whose onboard detections carry no geometry cannot serve as a root
22863
+ * step at all (there is nothing to track), and the cap that knows this says
22864
+ * so on `native-object-detection.getOptions().geometry`. On Reolink that is
22865
+ * every battery camera: the boxes ride a sub-stream a battery camera never
22866
+ * attaches, so such a camera stays on `['pipeline']` and its onboard AI is
22867
+ * worth exactly what it already is — a wake signal with a class.
22868
+ *
22869
+ * Defaults to `['pipeline']`, which is byte-for-byte today's behaviour: a
22870
+ * vendor never turns a detector on for the operator, and neither does a
22871
+ * default.
22872
+ */
22873
+ detectionSources: DetectionSourcesSchema.default(["pipeline"]),
22706
22874
  pipelineEnabled: boolean().default(true),
22707
22875
  /** Ordered tree of video steps. Absent → runner skips video detection. */
22708
22876
  steps: array(PipelineStepInputSchema).readonly().optional(),
@@ -22884,7 +23052,47 @@ var RunnerLocalMetricsSchema = object({
22884
23052
  queueDepth: number(),
22885
23053
  frameLazy: FrameLazyMetricsSchema.optional()
22886
23054
  });
23055
+ /**
23056
+ * Pipeline Runner capability — runtime detection workhorse.
23057
+ *
23058
+ * One instance per node. Receives camera assignments from
23059
+ * `addon-pipeline-orchestrator`, subscribes to the local stream-broker for
23060
+ * decoded frames, drains motion + detection queues, calls the local
23061
+ * `motion-detection` and `pipeline-executor` capabilities, and emits typed
23062
+ * `pipeline.inference-result` and `detection.motion-analysis` events on
23063
+ * the bus.
23064
+ *
23065
+ * Distinct from `pipeline-orchestrator` (the hub-side load balancer) — the
23066
+ * runner has zero knowledge of other agents, no global state, and never
23067
+ * makes assignment decisions itself.
23068
+ */
23069
+ /**
23070
+ * Why a runner declined to look at a camera out of band. Named rather than a
23071
+ * bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
23072
+ * already mid-session and one already bursting are four different facts, and a
23073
+ * caller told only "no" cannot tell a protection from a bug.
23074
+ */
23075
+ var OccupancyBurstRefusalSchema = _enum([
23076
+ "not-attached",
23077
+ "battery-asleep",
23078
+ "not-watching",
23079
+ "already-bursting"
23080
+ ]);
22887
23081
  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({
23082
+ deviceId: number(),
23083
+ /** How many frames to collect. The caller sizes this against what its
23084
+ * own confirmation needs; the runner clamps it to the field's range. */
23085
+ frames: number().int().positive().optional(),
23086
+ /** Why, for the log. A burst nobody can attribute is a cost nobody can
23087
+ * defend — the periodic one at least has a timer to point at. */
23088
+ reason: string()
23089
+ }), object({
23090
+ started: boolean(),
23091
+ refusedBecause: OccupancyBurstRefusalSchema.optional()
23092
+ }), {
23093
+ auth: "admin",
23094
+ kind: "mutation"
23095
+ }), method(object({
22888
23096
  handle: FrameHandleSchema,
22889
23097
  bbox: NativeCropBboxSchema,
22890
23098
  maxWidth: number().int().positive().optional(),
@@ -29722,11 +29930,46 @@ var NativeObjectDetectionStatusSchema = object({
29722
29930
  supportedClasses: array(NativeObjectClassEnum).readonly(),
29723
29931
  /**
29724
29932
  * Whether forwarding of onboard AI detections is enabled for this device.
29725
- * Default FALSE (opt-in, cold-start) — onboard AI pushes are noisy/sparse and
29726
- * churn the tracker, so forwarding stays off until the operator enables it.
29933
+ *
29934
+ * Cold-start OFF on every camera. A vendor does not decide which detector a
29935
+ * camera runs — that is the operator's choice, and it is made where every
29936
+ * other detection choice is made. `geometry` on
29937
+ * {@link NativeObjectDetectionOptionsSchema} is how the form says what the
29938
+ * choice would buy this particular camera.
29727
29939
  */
29728
29940
  enabled: boolean()
29729
29941
  });
29942
+ /**
29943
+ * WHEN a camera's onboard detections carry geometry.
29944
+ *
29945
+ * - `boxed` — always. The provider holds a standing channel for the boxes, so
29946
+ * a detection is a trackable subject whenever the camera sees one.
29947
+ * - `while-streaming` — only while some stream of this camera is already open.
29948
+ * The boxes ride the video's own side-channel rather than a feed of their
29949
+ * own, so they cost nothing and they exist only when something is pulling.
29950
+ * On Reolink this is every BATTERY camera: a persistent second feed is real
29951
+ * radio drain, but a camera that is awake is awake precisely because
29952
+ * something is in front of it, and its stream is already being pulled.
29953
+ * - `flag-only` — never. The firmware ships the class and nothing else; the
29954
+ * pipeline needs geometry to make a subject, so such a push reaches the
29955
+ * tracker as motion and no further.
29956
+ *
29957
+ * Reported and not inferred, because the operator's question in front of the
29958
+ * picker is "what do I get", and these are three different answers (D14: the
29959
+ * derived form is only as honest as `getOptions`).
29960
+ */
29961
+ var NativeObjectGeometryEnum = _enum([
29962
+ "boxed",
29963
+ "while-streaming",
29964
+ "flag-only"
29965
+ ]);
29966
+ var NativeObjectDetectionOptionsSchema = object({
29967
+ /** Classes this firmware can detect — the same list the status reports. */
29968
+ supportedClasses: array(NativeObjectClassEnum).readonly(),
29969
+ /** What a detection from this camera carries. */
29970
+ geometry: NativeObjectGeometryEnum
29971
+ });
29972
+ var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
29730
29973
  var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
29731
29974
  /** Required by createRuntimeStateBridge — epoch ms of last refresh. */
29732
29975
  lastFetchedAt: number() });
@@ -29736,13 +29979,23 @@ var nativeObjectDetectionCapability = {
29736
29979
  deviceNative: true,
29737
29980
  mode: "singleton",
29738
29981
  deviceTypes: [DeviceType.Camera],
29739
- methods: { setEnabled: method(object({
29740
- deviceId: number(),
29741
- enabled: boolean()
29742
- }), _void(), {
29743
- kind: "mutation",
29744
- auth: "admin"
29745
- }) },
29982
+ methods: {
29983
+ getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
29984
+ setSettings: method(object({
29985
+ deviceId: number(),
29986
+ settings: NativeObjectDetectionSettingsPatchSchema
29987
+ }), _void(), {
29988
+ kind: "mutation",
29989
+ auth: "admin"
29990
+ }),
29991
+ setEnabled: method(object({
29992
+ deviceId: number(),
29993
+ enabled: boolean()
29994
+ }), _void(), {
29995
+ kind: "mutation",
29996
+ auth: "admin"
29997
+ })
29998
+ },
29746
29999
  events: { onDetected: { data: object({
29747
30000
  deviceId: number(),
29748
30001
  detection: NativeDetectionSchema
@@ -34590,7 +34843,28 @@ var PerScopeBreakdownSchema = object({
34590
34843
  /** Total tracked objects in this scope (frame / zone / unzoned). */
34591
34844
  totalObjects: number().int().nonnegative(),
34592
34845
  /** Per-class count. Keys are macro class names (e.g. `person`, `car`). */
34593
- byClass: record(string(), number().int().nonnegative())
34846
+ byClass: record(string(), number().int().nonnegative()),
34847
+ /**
34848
+ * Of `totalObjects`, how many are STANDING — objects the census holds,
34849
+ * present but not going anywhere (a parked car, a bin, a statue).
34850
+ *
34851
+ * The operator's question about a scene has two halves and they answer
34852
+ * different things: "what is parked here" is stable for hours and must not
34853
+ * flap, while "who is walking through" is the thing an alert is about. One
34854
+ * number could only serve one of them, and `totalObjects` served the first
34855
+ * badly — a passer-by moved it and every occupancy rule saw a change.
34856
+ *
34857
+ * `standing + transient === totalObjects`, always. The total keeps its old
34858
+ * meaning exactly, so no rule written against it changes behaviour.
34859
+ *
34860
+ * ABSENT means UNKNOWN, never zero (D393): a snapshot from a node older than
34861
+ * this field cannot say, and a reader that folds absence in as 0 reports "no
34862
+ * parked objects here" about a camera whose lot is full.
34863
+ */
34864
+ standingObjects: number().int().nonnegative().optional(),
34865
+ /** Of `totalObjects`, how many are passing through. See `standingObjects` —
34866
+ * same absence rule. */
34867
+ transientObjects: number().int().nonnegative().optional()
34594
34868
  });
34595
34869
  var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
34596
34870
  zoneId: string(),
@@ -34713,6 +34987,23 @@ var HistoryPointSchema = object({
34713
34987
  * triple covers the three spatial scopes (per-zone, frame-wide,
34714
34988
  * outside-any-zone). Crossing them with `className?` gives the full
34715
34989
  * combinatorial coverage the operator UI requested.
34990
+ *
34991
+ * ## ⚠️ The three history methods are DEPRECATED
34992
+ *
34993
+ * They are served from a 60-MINUTE in-memory ring. It dies with the process,
34994
+ * so after a restart they report nothing about a night that did happen, and any
34995
+ * window past an hour is silently clamped — a caller cannot tell a clamp from
34996
+ * an empty scene. The presets in the occupancy chart existed to hide that
34997
+ * horizon rather than to answer a question.
34998
+ *
34999
+ * Use `pipelineAnalytics.readLongTermSeries({ series: 'occupancy', scope })`
35000
+ * instead. Those rows are already written for every camera, ~60 bytes per
35001
+ * (camera, scope, 5-minute bucket), kept for a year, and survive a restart.
35002
+ * Spell the scope with `occupancyScope(zoneId?, className?)` — the same
35003
+ * function the writer uses, so the two cannot drift.
35004
+ *
35005
+ * The ring stays until these three go, and it is the only thing left reading
35006
+ * it. Do not add a fourth caller.
34716
35007
  */
34717
35008
  var zoneAnalyticsCapability = {
34718
35009
  name: "zone-analytics",
@@ -39112,12 +39403,24 @@ Object.freeze({
39112
39403
  addonId: null,
39113
39404
  access: "create"
39114
39405
  },
39406
+ "nativeObjectDetection.getOptions": {
39407
+ capName: "native-object-detection",
39408
+ capScope: "device",
39409
+ addonId: null,
39410
+ access: "view"
39411
+ },
39115
39412
  "nativeObjectDetection.setEnabled": {
39116
39413
  capName: "native-object-detection",
39117
39414
  capScope: "device",
39118
39415
  addonId: null,
39119
39416
  access: "create"
39120
39417
  },
39418
+ "nativeObjectDetection.setSettings": {
39419
+ capName: "native-object-detection",
39420
+ capScope: "device",
39421
+ addonId: null,
39422
+ access: "create"
39423
+ },
39121
39424
  "navigation.getFeatures": {
39122
39425
  capName: "navigation",
39123
39426
  capScope: "device",
@@ -39382,6 +39685,12 @@ Object.freeze({
39382
39685
  addonId: null,
39383
39686
  access: "view"
39384
39687
  },
39688
+ "notificationRules.getClipRetentionAsks": {
39689
+ capName: "notification-rules",
39690
+ capScope: "system",
39691
+ addonId: null,
39692
+ access: "view"
39693
+ },
39385
39694
  "notificationRules.getConditionCatalog": {
39386
39695
  capName: "notification-rules",
39387
39696
  capScope: "system",
@@ -39892,6 +40201,12 @@ Object.freeze({
39892
40201
  addonId: null,
39893
40202
  access: "create"
39894
40203
  },
40204
+ "pipelineAnalytics.readLongTermSeries": {
40205
+ capName: "pipeline-analytics",
40206
+ capScope: "device",
40207
+ addonId: null,
40208
+ access: "view"
40209
+ },
39895
40210
  "pipelineAnalytics.rebuildObjectEmbeddings": {
39896
40211
  capName: "pipeline-analytics",
39897
40212
  capScope: "device",
@@ -40564,6 +40879,12 @@ Object.freeze({
40564
40879
  addonId: null,
40565
40880
  access: "create"
40566
40881
  },
40882
+ "pipelineRunner.requestOccupancyBurst": {
40883
+ capName: "pipeline-runner",
40884
+ capScope: "system",
40885
+ addonId: null,
40886
+ access: "create"
40887
+ },
40567
40888
  "pipelineRunner.runDetailSubtree": {
40568
40889
  capName: "pipeline-runner",
40569
40890
  capScope: "system",
@@ -43344,11 +43665,21 @@ Object.freeze({
43344
43665
  form: "single",
43345
43666
  optional: false
43346
43667
  }],
43668
+ "nativeObjectDetection.getOptions": [{
43669
+ name: "deviceId",
43670
+ form: "single",
43671
+ optional: false
43672
+ }],
43347
43673
  "nativeObjectDetection.setEnabled": [{
43348
43674
  name: "deviceId",
43349
43675
  form: "single",
43350
43676
  optional: false
43351
43677
  }],
43678
+ "nativeObjectDetection.setSettings": [{
43679
+ name: "deviceId",
43680
+ form: "single",
43681
+ optional: false
43682
+ }],
43352
43683
  "navigation.getFeatures": [{
43353
43684
  name: "deviceId",
43354
43685
  form: "single",
@@ -43698,6 +44029,11 @@ Object.freeze({
43698
44029
  form: "single",
43699
44030
  optional: false
43700
44031
  }],
44032
+ "pipelineAnalytics.readLongTermSeries": [{
44033
+ name: "deviceId",
44034
+ form: "single",
44035
+ optional: false
44036
+ }],
43701
44037
  "pipelineAnalytics.rebuildObjectEmbeddings": [{
43702
44038
  name: "deviceId",
43703
44039
  form: "single",
@@ -43888,6 +44224,11 @@ Object.freeze({
43888
44224
  form: "single",
43889
44225
  optional: false
43890
44226
  }],
44227
+ "pipelineRunner.requestOccupancyBurst": [{
44228
+ name: "deviceId",
44229
+ form: "single",
44230
+ optional: false
44231
+ }],
43891
44232
  "pipelineRunner.runDetailSubtree": [{
43892
44233
  name: "deviceId",
43893
44234
  form: "single",
package/dist/addon.mjs CHANGED
@@ -6461,7 +6461,7 @@ var ZodIssueCode = {
6461
6461
  var ZodFirstPartyTypeKind;
6462
6462
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6463
6463
  //#endregion
6464
- //#region ../types/dist/sleep-DEuj7E3j.mjs
6464
+ //#region ../types/dist/sleep-BDR76Ykr.mjs
6465
6465
  /**
6466
6466
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6467
6467
  * window to float samples (D455).
@@ -12663,6 +12663,21 @@ var streamSignalsCapability = {
12663
12663
  mode: "singleton",
12664
12664
  deviceTypes: Object.values(DeviceType),
12665
12665
  runtimeState: StreamSignalsStatusSchema,
12666
+ /**
12667
+ * Runtime-state durability: **session** — mirrored in RAM, never written.
12668
+ *
12669
+ * The slice holds what the DEVICE says it can emit. That is a probed fact,
12670
+ * not an operator choice: the provider re-declares it on every registration,
12671
+ * so losing it loses nothing and persisting it would freeze an answer the
12672
+ * camera is entitled to change. Measured the same day on the sibling case —
12673
+ * `native-object-detection.supportedClasses` was persisted, and a firmware
12674
+ * class the camera really detected stayed missing for the life of the row
12675
+ * because the fix could not reach it.
12676
+ *
12677
+ * See `RuntimeStateDurability`. Enforced by
12678
+ * `scripts/check-runtime-state-durability.ts`.
12679
+ */
12680
+ durability: "session",
12666
12681
  methods: {
12667
12682
  /**
12668
12683
  * What this device can emit. Empty is a valid and common answer — most
@@ -19294,6 +19309,48 @@ var NcAlarmConfigSchema = object({
19294
19309
  settings: NcAlarmSettingsSchema,
19295
19310
  coverage: array(NcAlarmModeCoverageSchema)
19296
19311
  });
19312
+ /**
19313
+ * ONE rule's demand on ONE camera's clip ring.
19314
+ *
19315
+ * A rule, not a camera: the broker takes the MAX over the asks that reach a
19316
+ * camera, so the answer stays a faithful description of the rules and the
19317
+ * bounding (the byte budget, the broker's own ceiling) stays where the cost
19318
+ * lives. A camera that appears in no ask is being told **nothing** — which is
19319
+ * a different thing from never having been told, and only the envelope
19320
+ * (`rulesLoaded`) can tell those apart.
19321
+ */
19322
+ var NcClipRetentionAskSchema = object({
19323
+ /**
19324
+ * The camera this ask is about. **Absent = every camera**, which is what a
19325
+ * rule with no `conditions.devices` means: it can fire anywhere, so it is
19326
+ * asking everywhere.
19327
+ */
19328
+ deviceId: number().int().nonnegative().optional(),
19329
+ /** Seconds of history the rule needs held for it. Never zero — a rule that
19330
+ * needs nothing produces no ask at all. */
19331
+ seconds: number().min(0).max(60),
19332
+ /** The profile the rule cuts from, when it named one. Absent = the cheapest
19333
+ * assigned, which is what the cut defaults to. */
19334
+ profile: CamProfileSchema.optional(),
19335
+ /** Who is asking — so a retention the operator did not expect names a rule. */
19336
+ ruleId: string(),
19337
+ ruleName: string(),
19338
+ /** Why this many seconds: the rule's own window, or the occupancy ceiling. */
19339
+ reason: _enum(["window", "occupancy"])
19340
+ });
19341
+ /**
19342
+ * The whole answer, with the one bit that keeps absence from reading as zero.
19343
+ *
19344
+ * `rulesLoaded` is false until the rule ledger has completed a load at least
19345
+ * once. A centre that is up but has not read its rules yet answers with an
19346
+ * empty ask list that means nothing at all — applying it would drop every
19347
+ * camera's ring to zero for the first events after a restart, which is the
19348
+ * footage this mechanism exists to keep.
19349
+ */
19350
+ var NcClipRetentionPlanSchema = object({
19351
+ rulesLoaded: boolean(),
19352
+ asks: array(NcClipRetentionAskSchema).readonly()
19353
+ });
19297
19354
  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 }), {
19298
19355
  kind: "mutation",
19299
19356
  auth: "admin",
@@ -19314,7 +19371,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
19314
19371
  }), object({ success: literal(true) }), {
19315
19372
  kind: "mutation",
19316
19373
  auth: "admin"
19317
- }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
19374
+ }), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
19318
19375
  deviceId: number().int(),
19319
19376
  muted: boolean()
19320
19377
  }), object({ success: literal(true) }), {
@@ -20499,13 +20556,26 @@ var TrackAudioLabelSchema = object({
20499
20556
  * - `audio` — an audio event on the camera itself that was anomalous for
20500
20557
  * THAT camera, loud, and heard while nothing visual was happening (D62).
20501
20558
  *
20559
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
20560
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
20561
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
20562
+ * detector.
20563
+ *
20564
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
20565
+ * a persisted onboard track failed `safeParse` on read and came back with NO
20566
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
20567
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
20568
+ * could not name.
20569
+ *
20502
20570
  * The spatial subsystems (tracker association, occupancy count, re-id /
20503
20571
  * embedding, resurrection) MUST skip every synthetic source. Test for that
20504
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
20505
- * check silently readmits every source added after it was written.
20572
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
20573
+ * `!== 'sensor'` check silently readmits every source added after it was
20574
+ * written.
20506
20575
  */
20507
20576
  var TrackSourceSchema = _enum([
20508
20577
  "pipeline",
20578
+ "onboard",
20509
20579
  "sensor",
20510
20580
  "audio"
20511
20581
  ]);
@@ -21888,6 +21958,33 @@ var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
21888
21958
  * solid is this track", cheaper than re-deriving it from a trajectory. */
21889
21959
  framesMatched: number().int()
21890
21960
  })).readonly() });
21961
+ /**
21962
+ * The long-term series a camera produces. A closed union so a typo cannot
21963
+ * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
21964
+ * is an alias of this, not a second list.
21965
+ */
21966
+ var AnalyticsLtsSeriesSchema = _enum([
21967
+ "motion",
21968
+ "audio-dbfs",
21969
+ "battery",
21970
+ "occupancy"
21971
+ ]);
21972
+ /**
21973
+ * One closed 5-minute bucket of a long-term series.
21974
+ *
21975
+ * `samples` is how many readings the bucket folded — it is the ACTIVITY for a
21976
+ * series like `motion`, and the confidence for one like `occupancy`. A bucket
21977
+ * that exists at all was measured; a bucket that is absent was not, which is a
21978
+ * different claim from zero and the reason this is a sparse series.
21979
+ */
21980
+ var LtsBucketSchema = object({
21981
+ scope: string(),
21982
+ bucketStart: number().int(),
21983
+ samples: number().int().nonnegative(),
21984
+ sum: number(),
21985
+ min: number(),
21986
+ max: number()
21987
+ });
21891
21988
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
21892
21989
  deviceId: number(),
21893
21990
  trackId: string()
@@ -21934,6 +22031,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21934
22031
  kinds: array(string()).optional(),
21935
22032
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
21936
22033
  }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
22034
+ deviceId: number(),
22035
+ series: AnalyticsLtsSeriesSchema,
22036
+ /** Omit for EVERY scope of this series in the range. */
22037
+ scope: string().optional(),
22038
+ from: number(),
22039
+ to: number(),
22040
+ limit: number().int().positive().optional()
22041
+ }), array(LtsBucketSchema).readonly()), method(object({
21937
22042
  deviceId: number(),
21938
22043
  since: number(),
21939
22044
  until: number(),
@@ -22552,6 +22657,40 @@ var MotionSourceEnum = _enum([
22552
22657
  */
22553
22658
  var MotionSourcesSchema = array(MotionSourceEnum);
22554
22659
  /**
22660
+ * Which root detector a camera runs — EXACTLY ONE.
22661
+ *
22662
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
22663
+ * with (`DetectionSource`) rather than a second spelling of the same two
22664
+ * ideas: the value an operator picks here is the value that comes back on the
22665
+ * track, the overlay and the debug row.
22666
+ *
22667
+ * ## Why one, and why it is still an array
22668
+ *
22669
+ * Two roots on one camera is a capability nobody asked for and the operator has
22670
+ * since ruled out. It was never free: both planes feed the SAME per-device
22671
+ * stationary registry, both can promote the same parked object, and the two
22672
+ * detectors disagree about the same box by construction — which is the
22673
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
22674
+ *
22675
+ * The ARRAY shape survives because the stored settings and the attach payload
22676
+ * already speak it on every camera of every fleet, and a cap input that
22677
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
22678
+ * in the one direction that costs an operator their cameras. So the wire stays
22679
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
22680
+ * element rather than refused, and `max(1)` is what every consumer can then
22681
+ * rely on.
22682
+ *
22683
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
22684
+ * a camera an operator left with no root detector — and `planDetectionSources`
22685
+ * has always said so: inventing a root for them is how a default acts without
22686
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
22687
+ * suite caught it.
22688
+ *
22689
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
22690
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
22691
+ */
22692
+ var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
22693
+ /**
22555
22694
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
22556
22695
  * so cap-side consumers (the orchestrator forward, the runner addon's
22557
22696
  * cap implementation, tests) can reuse the type instead of redeclaring
@@ -22702,6 +22841,35 @@ var RunnerCameraConfigSchema = object({
22702
22841
  * events; the orchestrator forwards to `reportMotion`.
22703
22842
  */
22704
22843
  motionSources: MotionSourcesSchema.default(["analyzer"]),
22844
+ /**
22845
+ * WHICH root detector runs for this camera. The detection counterpart of
22846
+ * {@link motionSources}, same shape and same discipline: a per-camera list
22847
+ * of sources the system understands, vendor-agnostic, owned here.
22848
+ *
22849
+ * - `pipeline` — this runner's own root step on decoded pixels (`steps`).
22850
+ * - `onboard` — the CAMERA's own detector, admitted only when it is named
22851
+ * here. Its boxes reach post-analysis on the same
22852
+ * `PipelineInferenceResult` payload as the pipeline's, so an onboard-born
22853
+ * track gets the same tracker, the same overlay and the same detail
22854
+ * subtree (face / plate / embedding).
22855
+ *
22856
+ * `['onboard']` alone is the REPLACEMENT: this runner's root step does not
22857
+ * run, and that is where a camera with a usable onboard detector buys back
22858
+ * its share of the accelerator. It saves the root inference, not the decode
22859
+ * — the detail subtree still needs pixels to cut crops from.
22860
+ *
22861
+ * A camera whose onboard detections carry no geometry cannot serve as a root
22862
+ * step at all (there is nothing to track), and the cap that knows this says
22863
+ * so on `native-object-detection.getOptions().geometry`. On Reolink that is
22864
+ * every battery camera: the boxes ride a sub-stream a battery camera never
22865
+ * attaches, so such a camera stays on `['pipeline']` and its onboard AI is
22866
+ * worth exactly what it already is — a wake signal with a class.
22867
+ *
22868
+ * Defaults to `['pipeline']`, which is byte-for-byte today's behaviour: a
22869
+ * vendor never turns a detector on for the operator, and neither does a
22870
+ * default.
22871
+ */
22872
+ detectionSources: DetectionSourcesSchema.default(["pipeline"]),
22705
22873
  pipelineEnabled: boolean().default(true),
22706
22874
  /** Ordered tree of video steps. Absent → runner skips video detection. */
22707
22875
  steps: array(PipelineStepInputSchema).readonly().optional(),
@@ -22883,7 +23051,47 @@ var RunnerLocalMetricsSchema = object({
22883
23051
  queueDepth: number(),
22884
23052
  frameLazy: FrameLazyMetricsSchema.optional()
22885
23053
  });
23054
+ /**
23055
+ * Pipeline Runner capability — runtime detection workhorse.
23056
+ *
23057
+ * One instance per node. Receives camera assignments from
23058
+ * `addon-pipeline-orchestrator`, subscribes to the local stream-broker for
23059
+ * decoded frames, drains motion + detection queues, calls the local
23060
+ * `motion-detection` and `pipeline-executor` capabilities, and emits typed
23061
+ * `pipeline.inference-result` and `detection.motion-analysis` events on
23062
+ * the bus.
23063
+ *
23064
+ * Distinct from `pipeline-orchestrator` (the hub-side load balancer) — the
23065
+ * runner has zero knowledge of other agents, no global state, and never
23066
+ * makes assignment decisions itself.
23067
+ */
23068
+ /**
23069
+ * Why a runner declined to look at a camera out of band. Named rather than a
23070
+ * bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
23071
+ * already mid-session and one already bursting are four different facts, and a
23072
+ * caller told only "no" cannot tell a protection from a bug.
23073
+ */
23074
+ var OccupancyBurstRefusalSchema = _enum([
23075
+ "not-attached",
23076
+ "battery-asleep",
23077
+ "not-watching",
23078
+ "already-bursting"
23079
+ ]);
22886
23080
  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({
23081
+ deviceId: number(),
23082
+ /** How many frames to collect. The caller sizes this against what its
23083
+ * own confirmation needs; the runner clamps it to the field's range. */
23084
+ frames: number().int().positive().optional(),
23085
+ /** Why, for the log. A burst nobody can attribute is a cost nobody can
23086
+ * defend — the periodic one at least has a timer to point at. */
23087
+ reason: string()
23088
+ }), object({
23089
+ started: boolean(),
23090
+ refusedBecause: OccupancyBurstRefusalSchema.optional()
23091
+ }), {
23092
+ auth: "admin",
23093
+ kind: "mutation"
23094
+ }), method(object({
22887
23095
  handle: FrameHandleSchema,
22888
23096
  bbox: NativeCropBboxSchema,
22889
23097
  maxWidth: number().int().positive().optional(),
@@ -29721,11 +29929,46 @@ var NativeObjectDetectionStatusSchema = object({
29721
29929
  supportedClasses: array(NativeObjectClassEnum).readonly(),
29722
29930
  /**
29723
29931
  * Whether forwarding of onboard AI detections is enabled for this device.
29724
- * Default FALSE (opt-in, cold-start) — onboard AI pushes are noisy/sparse and
29725
- * churn the tracker, so forwarding stays off until the operator enables it.
29932
+ *
29933
+ * Cold-start OFF on every camera. A vendor does not decide which detector a
29934
+ * camera runs — that is the operator's choice, and it is made where every
29935
+ * other detection choice is made. `geometry` on
29936
+ * {@link NativeObjectDetectionOptionsSchema} is how the form says what the
29937
+ * choice would buy this particular camera.
29726
29938
  */
29727
29939
  enabled: boolean()
29728
29940
  });
29941
+ /**
29942
+ * WHEN a camera's onboard detections carry geometry.
29943
+ *
29944
+ * - `boxed` — always. The provider holds a standing channel for the boxes, so
29945
+ * a detection is a trackable subject whenever the camera sees one.
29946
+ * - `while-streaming` — only while some stream of this camera is already open.
29947
+ * The boxes ride the video's own side-channel rather than a feed of their
29948
+ * own, so they cost nothing and they exist only when something is pulling.
29949
+ * On Reolink this is every BATTERY camera: a persistent second feed is real
29950
+ * radio drain, but a camera that is awake is awake precisely because
29951
+ * something is in front of it, and its stream is already being pulled.
29952
+ * - `flag-only` — never. The firmware ships the class and nothing else; the
29953
+ * pipeline needs geometry to make a subject, so such a push reaches the
29954
+ * tracker as motion and no further.
29955
+ *
29956
+ * Reported and not inferred, because the operator's question in front of the
29957
+ * picker is "what do I get", and these are three different answers (D14: the
29958
+ * derived form is only as honest as `getOptions`).
29959
+ */
29960
+ var NativeObjectGeometryEnum = _enum([
29961
+ "boxed",
29962
+ "while-streaming",
29963
+ "flag-only"
29964
+ ]);
29965
+ var NativeObjectDetectionOptionsSchema = object({
29966
+ /** Classes this firmware can detect — the same list the status reports. */
29967
+ supportedClasses: array(NativeObjectClassEnum).readonly(),
29968
+ /** What a detection from this camera carries. */
29969
+ geometry: NativeObjectGeometryEnum
29970
+ });
29971
+ var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
29729
29972
  var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
29730
29973
  /** Required by createRuntimeStateBridge — epoch ms of last refresh. */
29731
29974
  lastFetchedAt: number() });
@@ -29735,13 +29978,23 @@ var nativeObjectDetectionCapability = {
29735
29978
  deviceNative: true,
29736
29979
  mode: "singleton",
29737
29980
  deviceTypes: [DeviceType.Camera],
29738
- methods: { setEnabled: method(object({
29739
- deviceId: number(),
29740
- enabled: boolean()
29741
- }), _void(), {
29742
- kind: "mutation",
29743
- auth: "admin"
29744
- }) },
29981
+ methods: {
29982
+ getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
29983
+ setSettings: method(object({
29984
+ deviceId: number(),
29985
+ settings: NativeObjectDetectionSettingsPatchSchema
29986
+ }), _void(), {
29987
+ kind: "mutation",
29988
+ auth: "admin"
29989
+ }),
29990
+ setEnabled: method(object({
29991
+ deviceId: number(),
29992
+ enabled: boolean()
29993
+ }), _void(), {
29994
+ kind: "mutation",
29995
+ auth: "admin"
29996
+ })
29997
+ },
29745
29998
  events: { onDetected: { data: object({
29746
29999
  deviceId: number(),
29747
30000
  detection: NativeDetectionSchema
@@ -34589,7 +34842,28 @@ var PerScopeBreakdownSchema = object({
34589
34842
  /** Total tracked objects in this scope (frame / zone / unzoned). */
34590
34843
  totalObjects: number().int().nonnegative(),
34591
34844
  /** Per-class count. Keys are macro class names (e.g. `person`, `car`). */
34592
- byClass: record(string(), number().int().nonnegative())
34845
+ byClass: record(string(), number().int().nonnegative()),
34846
+ /**
34847
+ * Of `totalObjects`, how many are STANDING — objects the census holds,
34848
+ * present but not going anywhere (a parked car, a bin, a statue).
34849
+ *
34850
+ * The operator's question about a scene has two halves and they answer
34851
+ * different things: "what is parked here" is stable for hours and must not
34852
+ * flap, while "who is walking through" is the thing an alert is about. One
34853
+ * number could only serve one of them, and `totalObjects` served the first
34854
+ * badly — a passer-by moved it and every occupancy rule saw a change.
34855
+ *
34856
+ * `standing + transient === totalObjects`, always. The total keeps its old
34857
+ * meaning exactly, so no rule written against it changes behaviour.
34858
+ *
34859
+ * ABSENT means UNKNOWN, never zero (D393): a snapshot from a node older than
34860
+ * this field cannot say, and a reader that folds absence in as 0 reports "no
34861
+ * parked objects here" about a camera whose lot is full.
34862
+ */
34863
+ standingObjects: number().int().nonnegative().optional(),
34864
+ /** Of `totalObjects`, how many are passing through. See `standingObjects` —
34865
+ * same absence rule. */
34866
+ transientObjects: number().int().nonnegative().optional()
34593
34867
  });
34594
34868
  var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
34595
34869
  zoneId: string(),
@@ -34712,6 +34986,23 @@ var HistoryPointSchema = object({
34712
34986
  * triple covers the three spatial scopes (per-zone, frame-wide,
34713
34987
  * outside-any-zone). Crossing them with `className?` gives the full
34714
34988
  * combinatorial coverage the operator UI requested.
34989
+ *
34990
+ * ## ⚠️ The three history methods are DEPRECATED
34991
+ *
34992
+ * They are served from a 60-MINUTE in-memory ring. It dies with the process,
34993
+ * so after a restart they report nothing about a night that did happen, and any
34994
+ * window past an hour is silently clamped — a caller cannot tell a clamp from
34995
+ * an empty scene. The presets in the occupancy chart existed to hide that
34996
+ * horizon rather than to answer a question.
34997
+ *
34998
+ * Use `pipelineAnalytics.readLongTermSeries({ series: 'occupancy', scope })`
34999
+ * instead. Those rows are already written for every camera, ~60 bytes per
35000
+ * (camera, scope, 5-minute bucket), kept for a year, and survive a restart.
35001
+ * Spell the scope with `occupancyScope(zoneId?, className?)` — the same
35002
+ * function the writer uses, so the two cannot drift.
35003
+ *
35004
+ * The ring stays until these three go, and it is the only thing left reading
35005
+ * it. Do not add a fourth caller.
34715
35006
  */
34716
35007
  var zoneAnalyticsCapability = {
34717
35008
  name: "zone-analytics",
@@ -39111,12 +39402,24 @@ Object.freeze({
39111
39402
  addonId: null,
39112
39403
  access: "create"
39113
39404
  },
39405
+ "nativeObjectDetection.getOptions": {
39406
+ capName: "native-object-detection",
39407
+ capScope: "device",
39408
+ addonId: null,
39409
+ access: "view"
39410
+ },
39114
39411
  "nativeObjectDetection.setEnabled": {
39115
39412
  capName: "native-object-detection",
39116
39413
  capScope: "device",
39117
39414
  addonId: null,
39118
39415
  access: "create"
39119
39416
  },
39417
+ "nativeObjectDetection.setSettings": {
39418
+ capName: "native-object-detection",
39419
+ capScope: "device",
39420
+ addonId: null,
39421
+ access: "create"
39422
+ },
39120
39423
  "navigation.getFeatures": {
39121
39424
  capName: "navigation",
39122
39425
  capScope: "device",
@@ -39381,6 +39684,12 @@ Object.freeze({
39381
39684
  addonId: null,
39382
39685
  access: "view"
39383
39686
  },
39687
+ "notificationRules.getClipRetentionAsks": {
39688
+ capName: "notification-rules",
39689
+ capScope: "system",
39690
+ addonId: null,
39691
+ access: "view"
39692
+ },
39384
39693
  "notificationRules.getConditionCatalog": {
39385
39694
  capName: "notification-rules",
39386
39695
  capScope: "system",
@@ -39891,6 +40200,12 @@ Object.freeze({
39891
40200
  addonId: null,
39892
40201
  access: "create"
39893
40202
  },
40203
+ "pipelineAnalytics.readLongTermSeries": {
40204
+ capName: "pipeline-analytics",
40205
+ capScope: "device",
40206
+ addonId: null,
40207
+ access: "view"
40208
+ },
39894
40209
  "pipelineAnalytics.rebuildObjectEmbeddings": {
39895
40210
  capName: "pipeline-analytics",
39896
40211
  capScope: "device",
@@ -40563,6 +40878,12 @@ Object.freeze({
40563
40878
  addonId: null,
40564
40879
  access: "create"
40565
40880
  },
40881
+ "pipelineRunner.requestOccupancyBurst": {
40882
+ capName: "pipeline-runner",
40883
+ capScope: "system",
40884
+ addonId: null,
40885
+ access: "create"
40886
+ },
40566
40887
  "pipelineRunner.runDetailSubtree": {
40567
40888
  capName: "pipeline-runner",
40568
40889
  capScope: "system",
@@ -43343,11 +43664,21 @@ Object.freeze({
43343
43664
  form: "single",
43344
43665
  optional: false
43345
43666
  }],
43667
+ "nativeObjectDetection.getOptions": [{
43668
+ name: "deviceId",
43669
+ form: "single",
43670
+ optional: false
43671
+ }],
43346
43672
  "nativeObjectDetection.setEnabled": [{
43347
43673
  name: "deviceId",
43348
43674
  form: "single",
43349
43675
  optional: false
43350
43676
  }],
43677
+ "nativeObjectDetection.setSettings": [{
43678
+ name: "deviceId",
43679
+ form: "single",
43680
+ optional: false
43681
+ }],
43351
43682
  "navigation.getFeatures": [{
43352
43683
  name: "deviceId",
43353
43684
  form: "single",
@@ -43697,6 +44028,11 @@ Object.freeze({
43697
44028
  form: "single",
43698
44029
  optional: false
43699
44030
  }],
44031
+ "pipelineAnalytics.readLongTermSeries": [{
44032
+ name: "deviceId",
44033
+ form: "single",
44034
+ optional: false
44035
+ }],
43700
44036
  "pipelineAnalytics.rebuildObjectEmbeddings": [{
43701
44037
  name: "deviceId",
43702
44038
  form: "single",
@@ -43887,6 +44223,11 @@ Object.freeze({
43887
44223
  form: "single",
43888
44224
  optional: false
43889
44225
  }],
44226
+ "pipelineRunner.requestOccupancyBurst": [{
44227
+ name: "deviceId",
44228
+ form: "single",
44229
+ optional: false
44230
+ }],
43890
44231
  "pipelineRunner.runDetailSubtree": [{
43891
44232
  name: "deviceId",
43892
44233
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-petkit",
3
- "version": "0.2.102",
3
+ "version": "0.2.104",
4
4
  "description": "PetKit smart-feeder device-provider addon for CamStack — wraps the @apocaliss92/nodepetkit PetKit cloud client",
5
5
  "keywords": [
6
6
  "camstack",
@@ -70,7 +70,7 @@
70
70
  "dev": "vite build --watch",
71
71
  "typecheck": "tsc --noEmit",
72
72
  "test": "vitest run",
73
- "publish": "npm publish --access public"
73
+ "publish:npm": "npm publish --access public"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@apocaliss92/nodepetkit": "0.1.0",