@camstack/addon-provider-gree 0.2.101 → 0.2.103

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
@@ -5363,7 +5363,7 @@ var ZodIssueCode = {
5363
5363
  var ZodFirstPartyTypeKind;
5364
5364
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5365
5365
  //#endregion
5366
- //#region ../types/dist/sleep-DEuj7E3j.mjs
5366
+ //#region ../types/dist/sleep-BDR76Ykr.mjs
5367
5367
  /**
5368
5368
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5369
5369
  * window to float samples (D455).
@@ -11565,6 +11565,21 @@ var streamSignalsCapability = {
11565
11565
  mode: "singleton",
11566
11566
  deviceTypes: Object.values(DeviceType),
11567
11567
  runtimeState: StreamSignalsStatusSchema,
11568
+ /**
11569
+ * Runtime-state durability: **session** — mirrored in RAM, never written.
11570
+ *
11571
+ * The slice holds what the DEVICE says it can emit. That is a probed fact,
11572
+ * not an operator choice: the provider re-declares it on every registration,
11573
+ * so losing it loses nothing and persisting it would freeze an answer the
11574
+ * camera is entitled to change. Measured the same day on the sibling case —
11575
+ * `native-object-detection.supportedClasses` was persisted, and a firmware
11576
+ * class the camera really detected stayed missing for the life of the row
11577
+ * because the fix could not reach it.
11578
+ *
11579
+ * See `RuntimeStateDurability`. Enforced by
11580
+ * `scripts/check-runtime-state-durability.ts`.
11581
+ */
11582
+ durability: "session",
11568
11583
  methods: {
11569
11584
  /**
11570
11585
  * What this device can emit. Empty is a valid and common answer — most
@@ -18179,6 +18194,48 @@ var NcAlarmConfigSchema = object({
18179
18194
  settings: NcAlarmSettingsSchema,
18180
18195
  coverage: array(NcAlarmModeCoverageSchema)
18181
18196
  });
18197
+ /**
18198
+ * ONE rule's demand on ONE camera's clip ring.
18199
+ *
18200
+ * A rule, not a camera: the broker takes the MAX over the asks that reach a
18201
+ * camera, so the answer stays a faithful description of the rules and the
18202
+ * bounding (the byte budget, the broker's own ceiling) stays where the cost
18203
+ * lives. A camera that appears in no ask is being told **nothing** — which is
18204
+ * a different thing from never having been told, and only the envelope
18205
+ * (`rulesLoaded`) can tell those apart.
18206
+ */
18207
+ var NcClipRetentionAskSchema = object({
18208
+ /**
18209
+ * The camera this ask is about. **Absent = every camera**, which is what a
18210
+ * rule with no `conditions.devices` means: it can fire anywhere, so it is
18211
+ * asking everywhere.
18212
+ */
18213
+ deviceId: number().int().nonnegative().optional(),
18214
+ /** Seconds of history the rule needs held for it. Never zero — a rule that
18215
+ * needs nothing produces no ask at all. */
18216
+ seconds: number().min(0).max(60),
18217
+ /** The profile the rule cuts from, when it named one. Absent = the cheapest
18218
+ * assigned, which is what the cut defaults to. */
18219
+ profile: CamProfileSchema.optional(),
18220
+ /** Who is asking — so a retention the operator did not expect names a rule. */
18221
+ ruleId: string(),
18222
+ ruleName: string(),
18223
+ /** Why this many seconds: the rule's own window, or the occupancy ceiling. */
18224
+ reason: _enum(["window", "occupancy"])
18225
+ });
18226
+ /**
18227
+ * The whole answer, with the one bit that keeps absence from reading as zero.
18228
+ *
18229
+ * `rulesLoaded` is false until the rule ledger has completed a load at least
18230
+ * once. A centre that is up but has not read its rules yet answers with an
18231
+ * empty ask list that means nothing at all — applying it would drop every
18232
+ * camera's ring to zero for the first events after a restart, which is the
18233
+ * footage this mechanism exists to keep.
18234
+ */
18235
+ var NcClipRetentionPlanSchema = object({
18236
+ rulesLoaded: boolean(),
18237
+ asks: array(NcClipRetentionAskSchema).readonly()
18238
+ });
18182
18239
  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 }), {
18183
18240
  kind: "mutation",
18184
18241
  auth: "admin",
@@ -18199,7 +18256,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
18199
18256
  }), object({ success: literal(true) }), {
18200
18257
  kind: "mutation",
18201
18258
  auth: "admin"
18202
- }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18259
+ }), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18203
18260
  deviceId: number().int(),
18204
18261
  muted: boolean()
18205
18262
  }), object({ success: literal(true) }), {
@@ -19384,13 +19441,26 @@ var TrackAudioLabelSchema = object({
19384
19441
  * - `audio` — an audio event on the camera itself that was anomalous for
19385
19442
  * THAT camera, loud, and heard while nothing visual was happening (D62).
19386
19443
  *
19444
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
19445
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
19446
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
19447
+ * detector.
19448
+ *
19449
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
19450
+ * a persisted onboard track failed `safeParse` on read and came back with NO
19451
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
19452
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
19453
+ * could not name.
19454
+ *
19387
19455
  * The spatial subsystems (tracker association, occupancy count, re-id /
19388
19456
  * embedding, resurrection) MUST skip every synthetic source. Test for that
19389
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
19390
- * check silently readmits every source added after it was written.
19457
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
19458
+ * `!== 'sensor'` check silently readmits every source added after it was
19459
+ * written.
19391
19460
  */
19392
19461
  var TrackSourceSchema = _enum([
19393
19462
  "pipeline",
19463
+ "onboard",
19394
19464
  "sensor",
19395
19465
  "audio"
19396
19466
  ]);
@@ -20773,6 +20843,33 @@ var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
20773
20843
  * solid is this track", cheaper than re-deriving it from a trajectory. */
20774
20844
  framesMatched: number().int()
20775
20845
  })).readonly() });
20846
+ /**
20847
+ * The long-term series a camera produces. A closed union so a typo cannot
20848
+ * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
20849
+ * is an alias of this, not a second list.
20850
+ */
20851
+ var AnalyticsLtsSeriesSchema = _enum([
20852
+ "motion",
20853
+ "audio-dbfs",
20854
+ "battery",
20855
+ "occupancy"
20856
+ ]);
20857
+ /**
20858
+ * One closed 5-minute bucket of a long-term series.
20859
+ *
20860
+ * `samples` is how many readings the bucket folded — it is the ACTIVITY for a
20861
+ * series like `motion`, and the confidence for one like `occupancy`. A bucket
20862
+ * that exists at all was measured; a bucket that is absent was not, which is a
20863
+ * different claim from zero and the reason this is a sparse series.
20864
+ */
20865
+ var LtsBucketSchema = object({
20866
+ scope: string(),
20867
+ bucketStart: number().int(),
20868
+ samples: number().int().nonnegative(),
20869
+ sum: number(),
20870
+ min: number(),
20871
+ max: number()
20872
+ });
20776
20873
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
20777
20874
  deviceId: number(),
20778
20875
  trackId: string()
@@ -20819,6 +20916,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20819
20916
  kinds: array(string()).optional(),
20820
20917
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20821
20918
  }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
20919
+ deviceId: number(),
20920
+ series: AnalyticsLtsSeriesSchema,
20921
+ /** Omit for EVERY scope of this series in the range. */
20922
+ scope: string().optional(),
20923
+ from: number(),
20924
+ to: number(),
20925
+ limit: number().int().positive().optional()
20926
+ }), array(LtsBucketSchema).readonly()), method(object({
20822
20927
  deviceId: number(),
20823
20928
  since: number(),
20824
20929
  until: number(),
@@ -21437,6 +21542,40 @@ var MotionSourceEnum = _enum([
21437
21542
  */
21438
21543
  var MotionSourcesSchema = array(MotionSourceEnum);
21439
21544
  /**
21545
+ * Which root detector a camera runs — EXACTLY ONE.
21546
+ *
21547
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
21548
+ * with (`DetectionSource`) rather than a second spelling of the same two
21549
+ * ideas: the value an operator picks here is the value that comes back on the
21550
+ * track, the overlay and the debug row.
21551
+ *
21552
+ * ## Why one, and why it is still an array
21553
+ *
21554
+ * Two roots on one camera is a capability nobody asked for and the operator has
21555
+ * since ruled out. It was never free: both planes feed the SAME per-device
21556
+ * stationary registry, both can promote the same parked object, and the two
21557
+ * detectors disagree about the same box by construction — which is the
21558
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
21559
+ *
21560
+ * The ARRAY shape survives because the stored settings and the attach payload
21561
+ * already speak it on every camera of every fleet, and a cap input that
21562
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
21563
+ * in the one direction that costs an operator their cameras. So the wire stays
21564
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
21565
+ * element rather than refused, and `max(1)` is what every consumer can then
21566
+ * rely on.
21567
+ *
21568
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
21569
+ * a camera an operator left with no root detector — and `planDetectionSources`
21570
+ * has always said so: inventing a root for them is how a default acts without
21571
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
21572
+ * suite caught it.
21573
+ *
21574
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
21575
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
21576
+ */
21577
+ var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
21578
+ /**
21440
21579
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
21441
21580
  * so cap-side consumers (the orchestrator forward, the runner addon's
21442
21581
  * cap implementation, tests) can reuse the type instead of redeclaring
@@ -21587,6 +21726,35 @@ var RunnerCameraConfigSchema = object({
21587
21726
  * events; the orchestrator forwards to `reportMotion`.
21588
21727
  */
21589
21728
  motionSources: MotionSourcesSchema.default(["analyzer"]),
21729
+ /**
21730
+ * WHICH root detector runs for this camera. The detection counterpart of
21731
+ * {@link motionSources}, same shape and same discipline: a per-camera list
21732
+ * of sources the system understands, vendor-agnostic, owned here.
21733
+ *
21734
+ * - `pipeline` — this runner's own root step on decoded pixels (`steps`).
21735
+ * - `onboard` — the CAMERA's own detector, admitted only when it is named
21736
+ * here. Its boxes reach post-analysis on the same
21737
+ * `PipelineInferenceResult` payload as the pipeline's, so an onboard-born
21738
+ * track gets the same tracker, the same overlay and the same detail
21739
+ * subtree (face / plate / embedding).
21740
+ *
21741
+ * `['onboard']` alone is the REPLACEMENT: this runner's root step does not
21742
+ * run, and that is where a camera with a usable onboard detector buys back
21743
+ * its share of the accelerator. It saves the root inference, not the decode
21744
+ * — the detail subtree still needs pixels to cut crops from.
21745
+ *
21746
+ * A camera whose onboard detections carry no geometry cannot serve as a root
21747
+ * step at all (there is nothing to track), and the cap that knows this says
21748
+ * so on `native-object-detection.getOptions().geometry`. On Reolink that is
21749
+ * every battery camera: the boxes ride a sub-stream a battery camera never
21750
+ * attaches, so such a camera stays on `['pipeline']` and its onboard AI is
21751
+ * worth exactly what it already is — a wake signal with a class.
21752
+ *
21753
+ * Defaults to `['pipeline']`, which is byte-for-byte today's behaviour: a
21754
+ * vendor never turns a detector on for the operator, and neither does a
21755
+ * default.
21756
+ */
21757
+ detectionSources: DetectionSourcesSchema.default(["pipeline"]),
21590
21758
  pipelineEnabled: boolean().default(true),
21591
21759
  /** Ordered tree of video steps. Absent → runner skips video detection. */
21592
21760
  steps: array(PipelineStepInputSchema).readonly().optional(),
@@ -21768,7 +21936,47 @@ var RunnerLocalMetricsSchema = object({
21768
21936
  queueDepth: number(),
21769
21937
  frameLazy: FrameLazyMetricsSchema.optional()
21770
21938
  });
21939
+ /**
21940
+ * Pipeline Runner capability — runtime detection workhorse.
21941
+ *
21942
+ * One instance per node. Receives camera assignments from
21943
+ * `addon-pipeline-orchestrator`, subscribes to the local stream-broker for
21944
+ * decoded frames, drains motion + detection queues, calls the local
21945
+ * `motion-detection` and `pipeline-executor` capabilities, and emits typed
21946
+ * `pipeline.inference-result` and `detection.motion-analysis` events on
21947
+ * the bus.
21948
+ *
21949
+ * Distinct from `pipeline-orchestrator` (the hub-side load balancer) — the
21950
+ * runner has zero knowledge of other agents, no global state, and never
21951
+ * makes assignment decisions itself.
21952
+ */
21953
+ /**
21954
+ * Why a runner declined to look at a camera out of band. Named rather than a
21955
+ * bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
21956
+ * already mid-session and one already bursting are four different facts, and a
21957
+ * caller told only "no" cannot tell a protection from a bug.
21958
+ */
21959
+ var OccupancyBurstRefusalSchema = _enum([
21960
+ "not-attached",
21961
+ "battery-asleep",
21962
+ "not-watching",
21963
+ "already-bursting"
21964
+ ]);
21771
21965
  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({
21966
+ deviceId: number(),
21967
+ /** How many frames to collect. The caller sizes this against what its
21968
+ * own confirmation needs; the runner clamps it to the field's range. */
21969
+ frames: number().int().positive().optional(),
21970
+ /** Why, for the log. A burst nobody can attribute is a cost nobody can
21971
+ * defend — the periodic one at least has a timer to point at. */
21972
+ reason: string()
21973
+ }), object({
21974
+ started: boolean(),
21975
+ refusedBecause: OccupancyBurstRefusalSchema.optional()
21976
+ }), {
21977
+ auth: "admin",
21978
+ kind: "mutation"
21979
+ }), method(object({
21772
21980
  handle: FrameHandleSchema,
21773
21981
  bbox: NativeCropBboxSchema,
21774
21982
  maxWidth: number().int().positive().optional(),
@@ -28598,11 +28806,46 @@ var NativeObjectDetectionStatusSchema = object({
28598
28806
  supportedClasses: array(NativeObjectClassEnum).readonly(),
28599
28807
  /**
28600
28808
  * Whether forwarding of onboard AI detections is enabled for this device.
28601
- * Default FALSE (opt-in, cold-start) — onboard AI pushes are noisy/sparse and
28602
- * churn the tracker, so forwarding stays off until the operator enables it.
28809
+ *
28810
+ * Cold-start OFF on every camera. A vendor does not decide which detector a
28811
+ * camera runs — that is the operator's choice, and it is made where every
28812
+ * other detection choice is made. `geometry` on
28813
+ * {@link NativeObjectDetectionOptionsSchema} is how the form says what the
28814
+ * choice would buy this particular camera.
28603
28815
  */
28604
28816
  enabled: boolean()
28605
28817
  });
28818
+ /**
28819
+ * WHEN a camera's onboard detections carry geometry.
28820
+ *
28821
+ * - `boxed` — always. The provider holds a standing channel for the boxes, so
28822
+ * a detection is a trackable subject whenever the camera sees one.
28823
+ * - `while-streaming` — only while some stream of this camera is already open.
28824
+ * The boxes ride the video's own side-channel rather than a feed of their
28825
+ * own, so they cost nothing and they exist only when something is pulling.
28826
+ * On Reolink this is every BATTERY camera: a persistent second feed is real
28827
+ * radio drain, but a camera that is awake is awake precisely because
28828
+ * something is in front of it, and its stream is already being pulled.
28829
+ * - `flag-only` — never. The firmware ships the class and nothing else; the
28830
+ * pipeline needs geometry to make a subject, so such a push reaches the
28831
+ * tracker as motion and no further.
28832
+ *
28833
+ * Reported and not inferred, because the operator's question in front of the
28834
+ * picker is "what do I get", and these are three different answers (D14: the
28835
+ * derived form is only as honest as `getOptions`).
28836
+ */
28837
+ var NativeObjectGeometryEnum = _enum([
28838
+ "boxed",
28839
+ "while-streaming",
28840
+ "flag-only"
28841
+ ]);
28842
+ var NativeObjectDetectionOptionsSchema = object({
28843
+ /** Classes this firmware can detect — the same list the status reports. */
28844
+ supportedClasses: array(NativeObjectClassEnum).readonly(),
28845
+ /** What a detection from this camera carries. */
28846
+ geometry: NativeObjectGeometryEnum
28847
+ });
28848
+ var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
28606
28849
  var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
28607
28850
  /** Required by createRuntimeStateBridge — epoch ms of last refresh. */
28608
28851
  lastFetchedAt: number() });
@@ -28612,13 +28855,23 @@ var nativeObjectDetectionCapability = {
28612
28855
  deviceNative: true,
28613
28856
  mode: "singleton",
28614
28857
  deviceTypes: [DeviceType.Camera],
28615
- methods: { setEnabled: method(object({
28616
- deviceId: number(),
28617
- enabled: boolean()
28618
- }), _void(), {
28619
- kind: "mutation",
28620
- auth: "admin"
28621
- }) },
28858
+ methods: {
28859
+ getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
28860
+ setSettings: method(object({
28861
+ deviceId: number(),
28862
+ settings: NativeObjectDetectionSettingsPatchSchema
28863
+ }), _void(), {
28864
+ kind: "mutation",
28865
+ auth: "admin"
28866
+ }),
28867
+ setEnabled: method(object({
28868
+ deviceId: number(),
28869
+ enabled: boolean()
28870
+ }), _void(), {
28871
+ kind: "mutation",
28872
+ auth: "admin"
28873
+ })
28874
+ },
28622
28875
  events: { onDetected: { data: object({
28623
28876
  deviceId: number(),
28624
28877
  detection: NativeDetectionSchema
@@ -33466,7 +33719,28 @@ var PerScopeBreakdownSchema = object({
33466
33719
  /** Total tracked objects in this scope (frame / zone / unzoned). */
33467
33720
  totalObjects: number().int().nonnegative(),
33468
33721
  /** Per-class count. Keys are macro class names (e.g. `person`, `car`). */
33469
- byClass: record(string(), number().int().nonnegative())
33722
+ byClass: record(string(), number().int().nonnegative()),
33723
+ /**
33724
+ * Of `totalObjects`, how many are STANDING — objects the census holds,
33725
+ * present but not going anywhere (a parked car, a bin, a statue).
33726
+ *
33727
+ * The operator's question about a scene has two halves and they answer
33728
+ * different things: "what is parked here" is stable for hours and must not
33729
+ * flap, while "who is walking through" is the thing an alert is about. One
33730
+ * number could only serve one of them, and `totalObjects` served the first
33731
+ * badly — a passer-by moved it and every occupancy rule saw a change.
33732
+ *
33733
+ * `standing + transient === totalObjects`, always. The total keeps its old
33734
+ * meaning exactly, so no rule written against it changes behaviour.
33735
+ *
33736
+ * ABSENT means UNKNOWN, never zero (D393): a snapshot from a node older than
33737
+ * this field cannot say, and a reader that folds absence in as 0 reports "no
33738
+ * parked objects here" about a camera whose lot is full.
33739
+ */
33740
+ standingObjects: number().int().nonnegative().optional(),
33741
+ /** Of `totalObjects`, how many are passing through. See `standingObjects` —
33742
+ * same absence rule. */
33743
+ transientObjects: number().int().nonnegative().optional()
33470
33744
  });
33471
33745
  var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
33472
33746
  zoneId: string(),
@@ -33589,6 +33863,23 @@ var HistoryPointSchema = object({
33589
33863
  * triple covers the three spatial scopes (per-zone, frame-wide,
33590
33864
  * outside-any-zone). Crossing them with `className?` gives the full
33591
33865
  * combinatorial coverage the operator UI requested.
33866
+ *
33867
+ * ## ⚠️ The three history methods are DEPRECATED
33868
+ *
33869
+ * They are served from a 60-MINUTE in-memory ring. It dies with the process,
33870
+ * so after a restart they report nothing about a night that did happen, and any
33871
+ * window past an hour is silently clamped — a caller cannot tell a clamp from
33872
+ * an empty scene. The presets in the occupancy chart existed to hide that
33873
+ * horizon rather than to answer a question.
33874
+ *
33875
+ * Use `pipelineAnalytics.readLongTermSeries({ series: 'occupancy', scope })`
33876
+ * instead. Those rows are already written for every camera, ~60 bytes per
33877
+ * (camera, scope, 5-minute bucket), kept for a year, and survive a restart.
33878
+ * Spell the scope with `occupancyScope(zoneId?, className?)` — the same
33879
+ * function the writer uses, so the two cannot drift.
33880
+ *
33881
+ * The ring stays until these three go, and it is the only thing left reading
33882
+ * it. Do not add a fourth caller.
33592
33883
  */
33593
33884
  var zoneAnalyticsCapability = {
33594
33885
  name: "zone-analytics",
@@ -37988,12 +38279,24 @@ Object.freeze({
37988
38279
  addonId: null,
37989
38280
  access: "create"
37990
38281
  },
38282
+ "nativeObjectDetection.getOptions": {
38283
+ capName: "native-object-detection",
38284
+ capScope: "device",
38285
+ addonId: null,
38286
+ access: "view"
38287
+ },
37991
38288
  "nativeObjectDetection.setEnabled": {
37992
38289
  capName: "native-object-detection",
37993
38290
  capScope: "device",
37994
38291
  addonId: null,
37995
38292
  access: "create"
37996
38293
  },
38294
+ "nativeObjectDetection.setSettings": {
38295
+ capName: "native-object-detection",
38296
+ capScope: "device",
38297
+ addonId: null,
38298
+ access: "create"
38299
+ },
37997
38300
  "navigation.getFeatures": {
37998
38301
  capName: "navigation",
37999
38302
  capScope: "device",
@@ -38258,6 +38561,12 @@ Object.freeze({
38258
38561
  addonId: null,
38259
38562
  access: "view"
38260
38563
  },
38564
+ "notificationRules.getClipRetentionAsks": {
38565
+ capName: "notification-rules",
38566
+ capScope: "system",
38567
+ addonId: null,
38568
+ access: "view"
38569
+ },
38261
38570
  "notificationRules.getConditionCatalog": {
38262
38571
  capName: "notification-rules",
38263
38572
  capScope: "system",
@@ -38768,6 +39077,12 @@ Object.freeze({
38768
39077
  addonId: null,
38769
39078
  access: "create"
38770
39079
  },
39080
+ "pipelineAnalytics.readLongTermSeries": {
39081
+ capName: "pipeline-analytics",
39082
+ capScope: "device",
39083
+ addonId: null,
39084
+ access: "view"
39085
+ },
38771
39086
  "pipelineAnalytics.rebuildObjectEmbeddings": {
38772
39087
  capName: "pipeline-analytics",
38773
39088
  capScope: "device",
@@ -39440,6 +39755,12 @@ Object.freeze({
39440
39755
  addonId: null,
39441
39756
  access: "create"
39442
39757
  },
39758
+ "pipelineRunner.requestOccupancyBurst": {
39759
+ capName: "pipeline-runner",
39760
+ capScope: "system",
39761
+ addonId: null,
39762
+ access: "create"
39763
+ },
39443
39764
  "pipelineRunner.runDetailSubtree": {
39444
39765
  capName: "pipeline-runner",
39445
39766
  capScope: "system",
@@ -42220,11 +42541,21 @@ Object.freeze({
42220
42541
  form: "single",
42221
42542
  optional: false
42222
42543
  }],
42544
+ "nativeObjectDetection.getOptions": [{
42545
+ name: "deviceId",
42546
+ form: "single",
42547
+ optional: false
42548
+ }],
42223
42549
  "nativeObjectDetection.setEnabled": [{
42224
42550
  name: "deviceId",
42225
42551
  form: "single",
42226
42552
  optional: false
42227
42553
  }],
42554
+ "nativeObjectDetection.setSettings": [{
42555
+ name: "deviceId",
42556
+ form: "single",
42557
+ optional: false
42558
+ }],
42228
42559
  "navigation.getFeatures": [{
42229
42560
  name: "deviceId",
42230
42561
  form: "single",
@@ -42574,6 +42905,11 @@ Object.freeze({
42574
42905
  form: "single",
42575
42906
  optional: false
42576
42907
  }],
42908
+ "pipelineAnalytics.readLongTermSeries": [{
42909
+ name: "deviceId",
42910
+ form: "single",
42911
+ optional: false
42912
+ }],
42577
42913
  "pipelineAnalytics.rebuildObjectEmbeddings": [{
42578
42914
  name: "deviceId",
42579
42915
  form: "single",
@@ -42764,6 +43100,11 @@ Object.freeze({
42764
43100
  form: "single",
42765
43101
  optional: false
42766
43102
  }],
43103
+ "pipelineRunner.requestOccupancyBurst": [{
43104
+ name: "deviceId",
43105
+ form: "single",
43106
+ optional: false
43107
+ }],
42767
43108
  "pipelineRunner.runDetailSubtree": [{
42768
43109
  name: "deviceId",
42769
43110
  form: "single",
package/dist/addon.mjs CHANGED
@@ -5362,7 +5362,7 @@ var ZodIssueCode = {
5362
5362
  var ZodFirstPartyTypeKind;
5363
5363
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5364
5364
  //#endregion
5365
- //#region ../types/dist/sleep-DEuj7E3j.mjs
5365
+ //#region ../types/dist/sleep-BDR76Ykr.mjs
5366
5366
  /**
5367
5367
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5368
5368
  * window to float samples (D455).
@@ -11564,6 +11564,21 @@ var streamSignalsCapability = {
11564
11564
  mode: "singleton",
11565
11565
  deviceTypes: Object.values(DeviceType),
11566
11566
  runtimeState: StreamSignalsStatusSchema,
11567
+ /**
11568
+ * Runtime-state durability: **session** — mirrored in RAM, never written.
11569
+ *
11570
+ * The slice holds what the DEVICE says it can emit. That is a probed fact,
11571
+ * not an operator choice: the provider re-declares it on every registration,
11572
+ * so losing it loses nothing and persisting it would freeze an answer the
11573
+ * camera is entitled to change. Measured the same day on the sibling case —
11574
+ * `native-object-detection.supportedClasses` was persisted, and a firmware
11575
+ * class the camera really detected stayed missing for the life of the row
11576
+ * because the fix could not reach it.
11577
+ *
11578
+ * See `RuntimeStateDurability`. Enforced by
11579
+ * `scripts/check-runtime-state-durability.ts`.
11580
+ */
11581
+ durability: "session",
11567
11582
  methods: {
11568
11583
  /**
11569
11584
  * What this device can emit. Empty is a valid and common answer — most
@@ -18178,6 +18193,48 @@ var NcAlarmConfigSchema = object({
18178
18193
  settings: NcAlarmSettingsSchema,
18179
18194
  coverage: array(NcAlarmModeCoverageSchema)
18180
18195
  });
18196
+ /**
18197
+ * ONE rule's demand on ONE camera's clip ring.
18198
+ *
18199
+ * A rule, not a camera: the broker takes the MAX over the asks that reach a
18200
+ * camera, so the answer stays a faithful description of the rules and the
18201
+ * bounding (the byte budget, the broker's own ceiling) stays where the cost
18202
+ * lives. A camera that appears in no ask is being told **nothing** — which is
18203
+ * a different thing from never having been told, and only the envelope
18204
+ * (`rulesLoaded`) can tell those apart.
18205
+ */
18206
+ var NcClipRetentionAskSchema = object({
18207
+ /**
18208
+ * The camera this ask is about. **Absent = every camera**, which is what a
18209
+ * rule with no `conditions.devices` means: it can fire anywhere, so it is
18210
+ * asking everywhere.
18211
+ */
18212
+ deviceId: number().int().nonnegative().optional(),
18213
+ /** Seconds of history the rule needs held for it. Never zero — a rule that
18214
+ * needs nothing produces no ask at all. */
18215
+ seconds: number().min(0).max(60),
18216
+ /** The profile the rule cuts from, when it named one. Absent = the cheapest
18217
+ * assigned, which is what the cut defaults to. */
18218
+ profile: CamProfileSchema.optional(),
18219
+ /** Who is asking — so a retention the operator did not expect names a rule. */
18220
+ ruleId: string(),
18221
+ ruleName: string(),
18222
+ /** Why this many seconds: the rule's own window, or the occupancy ceiling. */
18223
+ reason: _enum(["window", "occupancy"])
18224
+ });
18225
+ /**
18226
+ * The whole answer, with the one bit that keeps absence from reading as zero.
18227
+ *
18228
+ * `rulesLoaded` is false until the rule ledger has completed a load at least
18229
+ * once. A centre that is up but has not read its rules yet answers with an
18230
+ * empty ask list that means nothing at all — applying it would drop every
18231
+ * camera's ring to zero for the first events after a restart, which is the
18232
+ * footage this mechanism exists to keep.
18233
+ */
18234
+ var NcClipRetentionPlanSchema = object({
18235
+ rulesLoaded: boolean(),
18236
+ asks: array(NcClipRetentionAskSchema).readonly()
18237
+ });
18181
18238
  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 }), {
18182
18239
  kind: "mutation",
18183
18240
  auth: "admin",
@@ -18198,7 +18255,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
18198
18255
  }), object({ success: literal(true) }), {
18199
18256
  kind: "mutation",
18200
18257
  auth: "admin"
18201
- }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18258
+ }), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
18202
18259
  deviceId: number().int(),
18203
18260
  muted: boolean()
18204
18261
  }), object({ success: literal(true) }), {
@@ -19383,13 +19440,26 @@ var TrackAudioLabelSchema = object({
19383
19440
  * - `audio` — an audio event on the camera itself that was anomalous for
19384
19441
  * THAT camera, loud, and heard while nothing visual was happening (D62).
19385
19442
  *
19443
+ * - `onboard` — the CAMERA's own firmware, paired with a decoded frame in
19444
+ * that frame's pixel space. A real root detector and a SPATIAL one: its
19445
+ * boxes are the same kind of fact `pipeline`'s are, produced by a different
19446
+ * detector.
19447
+ *
19448
+ * `onboard` was missing here while `DetectionSourceSchema` already had it, so
19449
+ * a persisted onboard track failed `safeParse` on read and came back with NO
19450
+ * source at all — which `isSpatialTrack` then admitted by accident, through the
19451
+ * `undefined` arm rather than by anybody's decision, and which `excludeSources`
19452
+ * could not name.
19453
+ *
19386
19454
  * The spatial subsystems (tracker association, occupancy count, re-id /
19387
19455
  * embedding, resurrection) MUST skip every synthetic source. Test for that
19388
- * with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
19389
- * check silently readmits every source added after it was written.
19456
+ * with `isSpatialTrack`, which allow-lists the spatial sources explicitly — a
19457
+ * `!== 'sensor'` check silently readmits every source added after it was
19458
+ * written.
19390
19459
  */
19391
19460
  var TrackSourceSchema = _enum([
19392
19461
  "pipeline",
19462
+ "onboard",
19393
19463
  "sensor",
19394
19464
  "audio"
19395
19465
  ]);
@@ -20772,6 +20842,33 @@ var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
20772
20842
  * solid is this track", cheaper than re-deriving it from a trajectory. */
20773
20843
  framesMatched: number().int()
20774
20844
  })).readonly() });
20845
+ /**
20846
+ * The long-term series a camera produces. A closed union so a typo cannot
20847
+ * invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
20848
+ * is an alias of this, not a second list.
20849
+ */
20850
+ var AnalyticsLtsSeriesSchema = _enum([
20851
+ "motion",
20852
+ "audio-dbfs",
20853
+ "battery",
20854
+ "occupancy"
20855
+ ]);
20856
+ /**
20857
+ * One closed 5-minute bucket of a long-term series.
20858
+ *
20859
+ * `samples` is how many readings the bucket folded — it is the ACTIVITY for a
20860
+ * series like `motion`, and the confidence for one like `occupancy`. A bucket
20861
+ * that exists at all was measured; a bucket that is absent was not, which is a
20862
+ * different claim from zero and the reason this is a sparse series.
20863
+ */
20864
+ var LtsBucketSchema = object({
20865
+ scope: string(),
20866
+ bucketStart: number().int(),
20867
+ samples: number().int().nonnegative(),
20868
+ sum: number(),
20869
+ min: number(),
20870
+ max: number()
20871
+ });
20775
20872
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
20776
20873
  deviceId: number(),
20777
20874
  trackId: string()
@@ -20818,6 +20915,14 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20818
20915
  kinds: array(string()).optional(),
20819
20916
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20820
20917
  }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
20918
+ deviceId: number(),
20919
+ series: AnalyticsLtsSeriesSchema,
20920
+ /** Omit for EVERY scope of this series in the range. */
20921
+ scope: string().optional(),
20922
+ from: number(),
20923
+ to: number(),
20924
+ limit: number().int().positive().optional()
20925
+ }), array(LtsBucketSchema).readonly()), method(object({
20821
20926
  deviceId: number(),
20822
20927
  since: number(),
20823
20928
  until: number(),
@@ -21436,6 +21541,40 @@ var MotionSourceEnum = _enum([
21436
21541
  */
21437
21542
  var MotionSourcesSchema = array(MotionSourceEnum);
21438
21543
  /**
21544
+ * Which root detector a camera runs — EXACTLY ONE.
21545
+ *
21546
+ * Deliberately the SAME vocabulary post-analysis already tags every detection
21547
+ * with (`DetectionSource`) rather than a second spelling of the same two
21548
+ * ideas: the value an operator picks here is the value that comes back on the
21549
+ * track, the overlay and the debug row.
21550
+ *
21551
+ * ## Why one, and why it is still an array
21552
+ *
21553
+ * Two roots on one camera is a capability nobody asked for and the operator has
21554
+ * since ruled out. It was never free: both planes feed the SAME per-device
21555
+ * stationary registry, both can promote the same parked object, and the two
21556
+ * detectors disagree about the same box by construction — which is the
21557
+ * disagreement D505 had to arbitrate. Removing the case removes the arbitration.
21558
+ *
21559
+ * The ARRAY shape survives because the stored settings and the attach payload
21560
+ * already speak it on every camera of every fleet, and a cap input that
21561
+ * suddenly refuses a stored value makes the camera un-attachable — a fail-closed
21562
+ * in the one direction that costs an operator their cameras. So the wire stays
21563
+ * tolerant and the TYPE is exact: a longer list is truncated to its first
21564
+ * element rather than refused, and `max(1)` is what every consumer can then
21565
+ * rely on.
21566
+ *
21567
+ * AT MOST one, not exactly one. The EMPTY list is a legal, deliberate pick —
21568
+ * a camera an operator left with no root detector — and `planDetectionSources`
21569
+ * has always said so: inventing a root for them is how a default acts without
21570
+ * anybody choosing it. Tightening this to `length(1)` broke that, and the
21571
+ * suite caught it.
21572
+ *
21573
+ * Measured before tightening (2026-09-15, this fleet, 6 h of attaches): 3327
21574
+ * `["pipeline"]` and 2 `["onboard"]`. Not one camera had two.
21575
+ */
21576
+ var DetectionSourcesSchema = preprocess((value) => Array.isArray(value) && value.length > 1 ? value.slice(0, 1) : value, array(DetectionSourceSchema).max(1));
21577
+ /**
21439
21578
  * Input shape for `pipeline-runner.reportMotion` cap method. Exported
21440
21579
  * so cap-side consumers (the orchestrator forward, the runner addon's
21441
21580
  * cap implementation, tests) can reuse the type instead of redeclaring
@@ -21586,6 +21725,35 @@ var RunnerCameraConfigSchema = object({
21586
21725
  * events; the orchestrator forwards to `reportMotion`.
21587
21726
  */
21588
21727
  motionSources: MotionSourcesSchema.default(["analyzer"]),
21728
+ /**
21729
+ * WHICH root detector runs for this camera. The detection counterpart of
21730
+ * {@link motionSources}, same shape and same discipline: a per-camera list
21731
+ * of sources the system understands, vendor-agnostic, owned here.
21732
+ *
21733
+ * - `pipeline` — this runner's own root step on decoded pixels (`steps`).
21734
+ * - `onboard` — the CAMERA's own detector, admitted only when it is named
21735
+ * here. Its boxes reach post-analysis on the same
21736
+ * `PipelineInferenceResult` payload as the pipeline's, so an onboard-born
21737
+ * track gets the same tracker, the same overlay and the same detail
21738
+ * subtree (face / plate / embedding).
21739
+ *
21740
+ * `['onboard']` alone is the REPLACEMENT: this runner's root step does not
21741
+ * run, and that is where a camera with a usable onboard detector buys back
21742
+ * its share of the accelerator. It saves the root inference, not the decode
21743
+ * — the detail subtree still needs pixels to cut crops from.
21744
+ *
21745
+ * A camera whose onboard detections carry no geometry cannot serve as a root
21746
+ * step at all (there is nothing to track), and the cap that knows this says
21747
+ * so on `native-object-detection.getOptions().geometry`. On Reolink that is
21748
+ * every battery camera: the boxes ride a sub-stream a battery camera never
21749
+ * attaches, so such a camera stays on `['pipeline']` and its onboard AI is
21750
+ * worth exactly what it already is — a wake signal with a class.
21751
+ *
21752
+ * Defaults to `['pipeline']`, which is byte-for-byte today's behaviour: a
21753
+ * vendor never turns a detector on for the operator, and neither does a
21754
+ * default.
21755
+ */
21756
+ detectionSources: DetectionSourcesSchema.default(["pipeline"]),
21589
21757
  pipelineEnabled: boolean().default(true),
21590
21758
  /** Ordered tree of video steps. Absent → runner skips video detection. */
21591
21759
  steps: array(PipelineStepInputSchema).readonly().optional(),
@@ -21767,7 +21935,47 @@ var RunnerLocalMetricsSchema = object({
21767
21935
  queueDepth: number(),
21768
21936
  frameLazy: FrameLazyMetricsSchema.optional()
21769
21937
  });
21938
+ /**
21939
+ * Pipeline Runner capability — runtime detection workhorse.
21940
+ *
21941
+ * One instance per node. Receives camera assignments from
21942
+ * `addon-pipeline-orchestrator`, subscribes to the local stream-broker for
21943
+ * decoded frames, drains motion + detection queues, calls the local
21944
+ * `motion-detection` and `pipeline-executor` capabilities, and emits typed
21945
+ * `pipeline.inference-result` and `detection.motion-analysis` events on
21946
+ * the bus.
21947
+ *
21948
+ * Distinct from `pipeline-orchestrator` (the hub-side load balancer) — the
21949
+ * runner has zero knowledge of other agents, no global state, and never
21950
+ * makes assignment decisions itself.
21951
+ */
21952
+ /**
21953
+ * Why a runner declined to look at a camera out of band. Named rather than a
21954
+ * bare `false`: a camera asleep on battery (D173), one attached elsewhere, one
21955
+ * already mid-session and one already bursting are four different facts, and a
21956
+ * caller told only "no" cannot tell a protection from a bug.
21957
+ */
21958
+ var OccupancyBurstRefusalSchema = _enum([
21959
+ "not-attached",
21960
+ "battery-asleep",
21961
+ "not-watching",
21962
+ "already-bursting"
21963
+ ]);
21770
21964
  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({
21965
+ deviceId: number(),
21966
+ /** How many frames to collect. The caller sizes this against what its
21967
+ * own confirmation needs; the runner clamps it to the field's range. */
21968
+ frames: number().int().positive().optional(),
21969
+ /** Why, for the log. A burst nobody can attribute is a cost nobody can
21970
+ * defend — the periodic one at least has a timer to point at. */
21971
+ reason: string()
21972
+ }), object({
21973
+ started: boolean(),
21974
+ refusedBecause: OccupancyBurstRefusalSchema.optional()
21975
+ }), {
21976
+ auth: "admin",
21977
+ kind: "mutation"
21978
+ }), method(object({
21771
21979
  handle: FrameHandleSchema,
21772
21980
  bbox: NativeCropBboxSchema,
21773
21981
  maxWidth: number().int().positive().optional(),
@@ -28597,11 +28805,46 @@ var NativeObjectDetectionStatusSchema = object({
28597
28805
  supportedClasses: array(NativeObjectClassEnum).readonly(),
28598
28806
  /**
28599
28807
  * Whether forwarding of onboard AI detections is enabled for this device.
28600
- * Default FALSE (opt-in, cold-start) — onboard AI pushes are noisy/sparse and
28601
- * churn the tracker, so forwarding stays off until the operator enables it.
28808
+ *
28809
+ * Cold-start OFF on every camera. A vendor does not decide which detector a
28810
+ * camera runs — that is the operator's choice, and it is made where every
28811
+ * other detection choice is made. `geometry` on
28812
+ * {@link NativeObjectDetectionOptionsSchema} is how the form says what the
28813
+ * choice would buy this particular camera.
28602
28814
  */
28603
28815
  enabled: boolean()
28604
28816
  });
28817
+ /**
28818
+ * WHEN a camera's onboard detections carry geometry.
28819
+ *
28820
+ * - `boxed` — always. The provider holds a standing channel for the boxes, so
28821
+ * a detection is a trackable subject whenever the camera sees one.
28822
+ * - `while-streaming` — only while some stream of this camera is already open.
28823
+ * The boxes ride the video's own side-channel rather than a feed of their
28824
+ * own, so they cost nothing and they exist only when something is pulling.
28825
+ * On Reolink this is every BATTERY camera: a persistent second feed is real
28826
+ * radio drain, but a camera that is awake is awake precisely because
28827
+ * something is in front of it, and its stream is already being pulled.
28828
+ * - `flag-only` — never. The firmware ships the class and nothing else; the
28829
+ * pipeline needs geometry to make a subject, so such a push reaches the
28830
+ * tracker as motion and no further.
28831
+ *
28832
+ * Reported and not inferred, because the operator's question in front of the
28833
+ * picker is "what do I get", and these are three different answers (D14: the
28834
+ * derived form is only as honest as `getOptions`).
28835
+ */
28836
+ var NativeObjectGeometryEnum = _enum([
28837
+ "boxed",
28838
+ "while-streaming",
28839
+ "flag-only"
28840
+ ]);
28841
+ var NativeObjectDetectionOptionsSchema = object({
28842
+ /** Classes this firmware can detect — the same list the status reports. */
28843
+ supportedClasses: array(NativeObjectClassEnum).readonly(),
28844
+ /** What a detection from this camera carries. */
28845
+ geometry: NativeObjectGeometryEnum
28846
+ });
28847
+ var NativeObjectDetectionSettingsPatchSchema = object({ enabled: boolean().optional() });
28605
28848
  var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
28606
28849
  /** Required by createRuntimeStateBridge — epoch ms of last refresh. */
28607
28850
  lastFetchedAt: number() });
@@ -28611,13 +28854,23 @@ var nativeObjectDetectionCapability = {
28611
28854
  deviceNative: true,
28612
28855
  mode: "singleton",
28613
28856
  deviceTypes: [DeviceType.Camera],
28614
- methods: { setEnabled: method(object({
28615
- deviceId: number(),
28616
- enabled: boolean()
28617
- }), _void(), {
28618
- kind: "mutation",
28619
- auth: "admin"
28620
- }) },
28857
+ methods: {
28858
+ getOptions: method(object({ deviceId: number() }), NativeObjectDetectionOptionsSchema),
28859
+ setSettings: method(object({
28860
+ deviceId: number(),
28861
+ settings: NativeObjectDetectionSettingsPatchSchema
28862
+ }), _void(), {
28863
+ kind: "mutation",
28864
+ auth: "admin"
28865
+ }),
28866
+ setEnabled: method(object({
28867
+ deviceId: number(),
28868
+ enabled: boolean()
28869
+ }), _void(), {
28870
+ kind: "mutation",
28871
+ auth: "admin"
28872
+ })
28873
+ },
28621
28874
  events: { onDetected: { data: object({
28622
28875
  deviceId: number(),
28623
28876
  detection: NativeDetectionSchema
@@ -33465,7 +33718,28 @@ var PerScopeBreakdownSchema = object({
33465
33718
  /** Total tracked objects in this scope (frame / zone / unzoned). */
33466
33719
  totalObjects: number().int().nonnegative(),
33467
33720
  /** Per-class count. Keys are macro class names (e.g. `person`, `car`). */
33468
- byClass: record(string(), number().int().nonnegative())
33721
+ byClass: record(string(), number().int().nonnegative()),
33722
+ /**
33723
+ * Of `totalObjects`, how many are STANDING — objects the census holds,
33724
+ * present but not going anywhere (a parked car, a bin, a statue).
33725
+ *
33726
+ * The operator's question about a scene has two halves and they answer
33727
+ * different things: "what is parked here" is stable for hours and must not
33728
+ * flap, while "who is walking through" is the thing an alert is about. One
33729
+ * number could only serve one of them, and `totalObjects` served the first
33730
+ * badly — a passer-by moved it and every occupancy rule saw a change.
33731
+ *
33732
+ * `standing + transient === totalObjects`, always. The total keeps its old
33733
+ * meaning exactly, so no rule written against it changes behaviour.
33734
+ *
33735
+ * ABSENT means UNKNOWN, never zero (D393): a snapshot from a node older than
33736
+ * this field cannot say, and a reader that folds absence in as 0 reports "no
33737
+ * parked objects here" about a camera whose lot is full.
33738
+ */
33739
+ standingObjects: number().int().nonnegative().optional(),
33740
+ /** Of `totalObjects`, how many are passing through. See `standingObjects` —
33741
+ * same absence rule. */
33742
+ transientObjects: number().int().nonnegative().optional()
33469
33743
  });
33470
33744
  var ZoneScopeBreakdownSchema = PerScopeBreakdownSchema.extend({
33471
33745
  zoneId: string(),
@@ -33588,6 +33862,23 @@ var HistoryPointSchema = object({
33588
33862
  * triple covers the three spatial scopes (per-zone, frame-wide,
33589
33863
  * outside-any-zone). Crossing them with `className?` gives the full
33590
33864
  * combinatorial coverage the operator UI requested.
33865
+ *
33866
+ * ## ⚠️ The three history methods are DEPRECATED
33867
+ *
33868
+ * They are served from a 60-MINUTE in-memory ring. It dies with the process,
33869
+ * so after a restart they report nothing about a night that did happen, and any
33870
+ * window past an hour is silently clamped — a caller cannot tell a clamp from
33871
+ * an empty scene. The presets in the occupancy chart existed to hide that
33872
+ * horizon rather than to answer a question.
33873
+ *
33874
+ * Use `pipelineAnalytics.readLongTermSeries({ series: 'occupancy', scope })`
33875
+ * instead. Those rows are already written for every camera, ~60 bytes per
33876
+ * (camera, scope, 5-minute bucket), kept for a year, and survive a restart.
33877
+ * Spell the scope with `occupancyScope(zoneId?, className?)` — the same
33878
+ * function the writer uses, so the two cannot drift.
33879
+ *
33880
+ * The ring stays until these three go, and it is the only thing left reading
33881
+ * it. Do not add a fourth caller.
33591
33882
  */
33592
33883
  var zoneAnalyticsCapability = {
33593
33884
  name: "zone-analytics",
@@ -37987,12 +38278,24 @@ Object.freeze({
37987
38278
  addonId: null,
37988
38279
  access: "create"
37989
38280
  },
38281
+ "nativeObjectDetection.getOptions": {
38282
+ capName: "native-object-detection",
38283
+ capScope: "device",
38284
+ addonId: null,
38285
+ access: "view"
38286
+ },
37990
38287
  "nativeObjectDetection.setEnabled": {
37991
38288
  capName: "native-object-detection",
37992
38289
  capScope: "device",
37993
38290
  addonId: null,
37994
38291
  access: "create"
37995
38292
  },
38293
+ "nativeObjectDetection.setSettings": {
38294
+ capName: "native-object-detection",
38295
+ capScope: "device",
38296
+ addonId: null,
38297
+ access: "create"
38298
+ },
37996
38299
  "navigation.getFeatures": {
37997
38300
  capName: "navigation",
37998
38301
  capScope: "device",
@@ -38257,6 +38560,12 @@ Object.freeze({
38257
38560
  addonId: null,
38258
38561
  access: "view"
38259
38562
  },
38563
+ "notificationRules.getClipRetentionAsks": {
38564
+ capName: "notification-rules",
38565
+ capScope: "system",
38566
+ addonId: null,
38567
+ access: "view"
38568
+ },
38260
38569
  "notificationRules.getConditionCatalog": {
38261
38570
  capName: "notification-rules",
38262
38571
  capScope: "system",
@@ -38767,6 +39076,12 @@ Object.freeze({
38767
39076
  addonId: null,
38768
39077
  access: "create"
38769
39078
  },
39079
+ "pipelineAnalytics.readLongTermSeries": {
39080
+ capName: "pipeline-analytics",
39081
+ capScope: "device",
39082
+ addonId: null,
39083
+ access: "view"
39084
+ },
38770
39085
  "pipelineAnalytics.rebuildObjectEmbeddings": {
38771
39086
  capName: "pipeline-analytics",
38772
39087
  capScope: "device",
@@ -39439,6 +39754,12 @@ Object.freeze({
39439
39754
  addonId: null,
39440
39755
  access: "create"
39441
39756
  },
39757
+ "pipelineRunner.requestOccupancyBurst": {
39758
+ capName: "pipeline-runner",
39759
+ capScope: "system",
39760
+ addonId: null,
39761
+ access: "create"
39762
+ },
39442
39763
  "pipelineRunner.runDetailSubtree": {
39443
39764
  capName: "pipeline-runner",
39444
39765
  capScope: "system",
@@ -42219,11 +42540,21 @@ Object.freeze({
42219
42540
  form: "single",
42220
42541
  optional: false
42221
42542
  }],
42543
+ "nativeObjectDetection.getOptions": [{
42544
+ name: "deviceId",
42545
+ form: "single",
42546
+ optional: false
42547
+ }],
42222
42548
  "nativeObjectDetection.setEnabled": [{
42223
42549
  name: "deviceId",
42224
42550
  form: "single",
42225
42551
  optional: false
42226
42552
  }],
42553
+ "nativeObjectDetection.setSettings": [{
42554
+ name: "deviceId",
42555
+ form: "single",
42556
+ optional: false
42557
+ }],
42227
42558
  "navigation.getFeatures": [{
42228
42559
  name: "deviceId",
42229
42560
  form: "single",
@@ -42573,6 +42904,11 @@ Object.freeze({
42573
42904
  form: "single",
42574
42905
  optional: false
42575
42906
  }],
42907
+ "pipelineAnalytics.readLongTermSeries": [{
42908
+ name: "deviceId",
42909
+ form: "single",
42910
+ optional: false
42911
+ }],
42576
42912
  "pipelineAnalytics.rebuildObjectEmbeddings": [{
42577
42913
  name: "deviceId",
42578
42914
  form: "single",
@@ -42763,6 +43099,11 @@ Object.freeze({
42763
43099
  form: "single",
42764
43100
  optional: false
42765
43101
  }],
43102
+ "pipelineRunner.requestOccupancyBurst": [{
43103
+ name: "deviceId",
43104
+ form: "single",
43105
+ optional: false
43106
+ }],
42766
43107
  "pipelineRunner.runDetailSubtree": [{
42767
43108
  name: "deviceId",
42768
43109
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-gree",
3
- "version": "0.2.101",
3
+ "version": "0.2.103",
4
4
  "description": "Gree air-conditioner device-provider addon for CamStack — wraps the @apocaliss92/nodegree local-UDP client (LAN discovery + AES control), exposing climate-control and fan-control",
5
5
  "keywords": [
6
6
  "camstack",
@@ -63,7 +63,7 @@
63
63
  "dev": "vite build --watch",
64
64
  "typecheck": "tsc --noEmit",
65
65
  "test": "vitest run",
66
- "publish": "npm publish --access public"
66
+ "publish:npm": "npm publish --access public"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@apocaliss92/nodegree": "file:./vendor/apocaliss92-nodegree-0.1.0.tgz",