@camstack/addon-mqtt-broker 1.2.90 → 1.2.93

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.
@@ -44,7 +44,7 @@ let crypto$10 = __toESM(crypto$1, 1);
44
44
  crypto$1 = __toESM(crypto$1);
45
45
  let node_fs = require("node:fs");
46
46
  let node_path = require("node:path");
47
- //#region ../types/dist/event-category-CnLqLOKs.mjs
47
+ //#region ../types/dist/event-category-DAXzJeTX.mjs
48
48
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
49
49
  EventCategory["SystemBoot"] = "system.boot";
50
50
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -636,7 +636,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
636
636
  * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
637
637
  */
638
638
  EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
639
- EventCategory["FrigateLiveEvent"] = "frigate.live-event";
640
639
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
641
640
  /**
642
641
  * Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
@@ -12601,7 +12600,7 @@ DeviceType.Camera;
12601
12600
  var SourceInfoSchema = object({
12602
12601
  /** Live dispatch key — mutable when the source system allows rename. */
12603
12602
  id: string(),
12604
- /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'frigate'. */
12603
+ /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
12605
12604
  system: string(),
12606
12605
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
12607
12606
  * Used to detect rename when `id` changes. */
@@ -13088,7 +13087,7 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
13088
13087
  * - `device-management.router.ts` (deleted in Phase 2)
13089
13088
  * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
13090
13089
  *
13091
- * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
13090
+ * All device provider addons (rtsp, onvif, …) are hub-local: they may
13092
13091
  * fork into separate processes but never run on remote cluster agents. Therefore:
13093
13092
  * - No nodeId routing needed — this is a pure hub singleton.
13094
13093
  * - The hub's DeviceRegistry is the single source of truth for all live devices.
@@ -17117,7 +17116,7 @@ var NcRuleInputSchema = object({
17117
17116
  * The measured seven-person arrival on device 590 spans 110 s with every
17118
17117
  * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
17119
17118
  * idle cutoff holds it as one and ends it when the arrival actually ends.
17120
- * 30 is Frigate's shipped value for the same decision.
17119
+ * 30 s is a widely shipped value for the same decision.
17121
17120
  *
17122
17121
  * ### What it replaces
17123
17122
  *
@@ -19159,6 +19158,13 @@ var ZoneCrossingSchema = object({
19159
19158
  /** Zone display name at crossing time (falls back to the id). */
19160
19159
  zoneName: string().optional()
19161
19160
  });
19161
+ /** One zone a box was stamped with, and how much of the BOX fell inside it. */
19162
+ var ZoneOverlapSchema = object({
19163
+ zoneId: string(),
19164
+ zoneName: string().optional(),
19165
+ /** Percentage of the BOX area inside the zone, 0–100. */
19166
+ overlapPct: number()
19167
+ });
19162
19168
  var ObjectEventSchema = object({
19163
19169
  ...BaseEventFields,
19164
19170
  kind: literal("object"),
@@ -19183,6 +19189,35 @@ var ObjectEventSchema = object({
19183
19189
  bbox: BoundingBoxSchema.optional(),
19184
19190
  /** Heavy JSON — omitted in slim projection. */
19185
19191
  zones: array(string()).readonly().optional(),
19192
+ /**
19193
+ * How far inside each stamped zone the box actually was, as a percentage of
19194
+ * the BOX area.
19195
+ *
19196
+ * Membership alone cannot express "at least a third of it inside", which is
19197
+ * the bar an operator argues with — and the number already existed: the
19198
+ * engine computed it, the pipeline logged it, and then discarded it. It
19199
+ * matters most on the events that carry no track, where a rule has nothing
19200
+ * else to judge by.
19201
+ */
19202
+ zoneOverlaps: array(ZoneOverlapSchema).readonly().optional(),
19203
+ /**
19204
+ * Present when this event describes a detection the TRACKER refused, so it
19205
+ * has no track and never will.
19206
+ *
19207
+ * A zone-scoped immediate rule asks "is there an animal in the flowerbed
19208
+ * NOW?"; tracking answers a different question ("is this the same subject as
19209
+ * before?") whose confirmation threshold presumes continuity. On 2026-09-09 a
19210
+ * dog at 0.7729, 100% inside a watched zone, was refused three times in six
19211
+ * seconds with three different candidate ids — seven analysed frames in the
19212
+ * window, the subject in a handful of them, metres apart — and produced no
19213
+ * event at all.
19214
+ *
19215
+ * It is NAMED rather than merely implied by a missing `trackId`: an event
19216
+ * with no track and no reason is indistinguishable from a track whose media
19217
+ * went missing, and a consumer that asks this one for a best shot or a
19218
+ * history is asking for something that never existed.
19219
+ */
19220
+ spawnRefused: string().optional(),
19186
19221
  /** Omitted in slim projection. */
19187
19222
  state: TrackStateSchema.optional(),
19188
19223
  /**
@@ -19609,7 +19644,17 @@ var EventDensityBucketSchema = object({
19609
19644
  bucketStart: number(),
19610
19645
  motion: number().int(),
19611
19646
  object: number().int(),
19612
- audio: number().int()
19647
+ audio: number().int(),
19648
+ /**
19649
+ * Energy-weighted mean dBFS of the audio events in this bucket — the LEVEL,
19650
+ * as opposed to `audio`, which is their COUNT (D431).
19651
+ *
19652
+ * ABSENT when the bucket holds no audio event. Absence means "nothing was
19653
+ * measured", which is not the same claim as silence, and a lane that cannot
19654
+ * tell them apart paints a quiet hour at full scale — which is exactly what
19655
+ * the count-on-a-level-meter did.
19656
+ */
19657
+ audioDbfs: number().optional()
19613
19658
  });
19614
19659
  /**
19615
19660
  * One camera's row in a `getEventDensityBatch` answer.
@@ -20256,6 +20301,60 @@ var NativeCropResultSchema = object({
20256
20301
  */
20257
20302
  tier: _enum(["native", "ram-fullframe"]).optional()
20258
20303
  });
20304
+ /**
20305
+ * What a PARKED frame is for — the `kind` half of the `(deviceId, trackId,
20306
+ * kind)` key ([D422](../../../docs/decisions/adr-0422-a-relevant-frame-is-parked-when-it-becomes-relevant.md)).
20307
+ *
20308
+ * - `keyFrame` — the track's clean best FULL frame at native width (capped by
20309
+ * the caller's `maxWidth`). What `keyFrame` + `keyFrameSmall` are written
20310
+ * from, and what the detail plane's full-frame rung cuts from.
20311
+ * - `thumbnail` — the best-shot subject slab: the caller's 16:9 central-square
20312
+ * window, cut by the runner at uncapped native resolution.
20313
+ *
20314
+ * The runner never interprets `trackId`; it is an opaque key the hub owns.
20315
+ */
20316
+ var ParkedFrameKindSchema = _enum(["keyFrame", "thumbnail"]);
20317
+ /**
20318
+ * Why a park did NOT happen. Every one is logged on the runner with
20319
+ * `tags: { deviceId }` and the handle's measured age, because a handle already
20320
+ * gone at park time IS the defect the 150–500 ms window exists to prevent —
20321
+ * never a reason to retry in a loop.
20322
+ *
20323
+ * - `handle-gone` — the native surface no longer resolves this handle (never
20324
+ * registered, past the lease bound, or the worker released it). `detail`
20325
+ * carries the runner's own miss reason.
20326
+ * - `encode-failed` — pixels resolved and the JPEG encode threw.
20327
+ */
20328
+ var ParkRefusalSchema = _enum(["handle-gone", "encode-failed"]);
20329
+ /** Answer to `parkTrackFrame` — a discriminated union, so "not parked" always names why. */
20330
+ var ParkTrackFrameResultSchema = discriminatedUnion("parked", [object({
20331
+ parked: literal(true),
20332
+ /** The node holding the parcel — what a later retrieval must be pinned to. */
20333
+ nodeId: string(),
20334
+ width: number().int().positive(),
20335
+ height: number().int().positive(),
20336
+ /** JPEG bytes the parcel costs on the node. */
20337
+ bytes: number().int().nonnegative(),
20338
+ /** Whether this park replaced a parcel already held for the key. */
20339
+ replaced: boolean()
20340
+ }), object({
20341
+ parked: literal(false),
20342
+ nodeId: string(),
20343
+ reason: ParkRefusalSchema,
20344
+ /** Age of the handle at the park, on the runner's clock; null if unknown. */
20345
+ handleAgeMs: number().nullable(),
20346
+ detail: string().optional()
20347
+ })]);
20348
+ /** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
20349
+ var ParkedTrackFrameSchema = object({
20350
+ jpeg: string(),
20351
+ width: number().int().positive(),
20352
+ height: number().int().positive(),
20353
+ /** The frame instant the parcel shows (the caller's clock, echoed back). */
20354
+ timestamp: number(),
20355
+ /** When the node stored it (the node's clock). */
20356
+ parkedAt: number()
20357
+ });
20259
20358
  /** Parent detection context passed to `runDetailSubtree` — the crop's
20260
20359
  * originating detection, in FRAME-space coordinates. Reuses
20261
20360
  * `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
@@ -20645,7 +20744,7 @@ var RunnerCameraConfigSchema = object({
20645
20744
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
20646
20745
  * analyzer providers) running on decoded frames in this runner.
20647
20746
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
20648
- * analytics, Frigate motion). The provider emits ProviderMotion
20747
+ * analytics). The provider emits ProviderMotion
20649
20748
  * events; the orchestrator forwards to `reportMotion`.
20650
20749
  */
20651
20750
  motionSources: MotionSourcesSchema.default(["analyzer"]),
@@ -20887,7 +20986,26 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
20887
20986
  className: string(),
20888
20987
  /** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
20889
20988
  sourceDeviceId: number()
20890
- }), RunStatelessStepResultSchema, { kind: "mutation" });
20989
+ }), RunStatelessStepResultSchema, { kind: "mutation" }), method(object({
20990
+ deviceId: number(),
20991
+ /** Opaque to the runner — the hub's track id. */
20992
+ trackId: string(),
20993
+ kind: ParkedFrameKindSchema,
20994
+ handle: FrameHandleSchema,
20995
+ /** Normalized [0,1] window to cut. `{0,0,1,1}` for a full frame. */
20996
+ bbox: NativeCropBboxSchema,
20997
+ /** Width cap for a full-frame park (the `keyFrame` native cap). */
20998
+ maxWidth: number().int().positive().optional(),
20999
+ /** The frame instant (caller's clock) — orders parks for one key. */
21000
+ timestamp: number()
21001
+ }), ParkTrackFrameResultSchema, { kind: "mutation" }), method(object({
21002
+ deviceId: number(),
21003
+ trackId: string(),
21004
+ kind: ParkedFrameKindSchema
21005
+ }), ParkedTrackFrameSchema.nullable()), method(object({
21006
+ deviceId: number(),
21007
+ trackId: string()
21008
+ }), object({ released: number().int().nonnegative() }), { kind: "mutation" });
20891
21009
  var CameraPipelineConfigSchema = object({
20892
21010
  engine: PipelineEngineChoiceSchema.optional(),
20893
21011
  steps: array(PipelineStepInputSchema).readonly(),
@@ -23816,7 +23934,7 @@ DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _v
23816
23934
  *
23817
23935
  * Implementations:
23818
23936
  * - Each camera-providing addon registers this cap natively per
23819
- * device (Reolink, Hikvision, RTSP, Frigate, ONVIF self).
23937
+ * device (Reolink, Hikvision, RTSP, ONVIF self).
23820
23938
  * - Returns a snapshot from the persisted device config blob — no
23821
23939
  * extra round-trip to the camera, no probing.
23822
23940
  * - The cap is admin-only (`auth: 'admin'`) — credentials must not
@@ -25202,7 +25320,7 @@ var AvailableIntegrationTypeSchema = object({
25202
25320
  * Which integration-marker cap the addon declared, so the wizard can
25203
25321
  * branch on CAP — never on addon name. `device-adoption` integrations
25204
25322
  * (Home Assistant, …) route through the broker step (Approach A);
25205
- * `device-provider` integrations (Reolink/Frigate/ONVIF) keep the
25323
+ * `device-provider` integrations (Reolink/ONVIF) keep the
25206
25324
  * legacy config → discovery flow.
25207
25325
  */
25208
25326
  kind: _enum(["device-adoption", "device-provider"]),
@@ -27703,7 +27821,23 @@ var RecordingRangeSchema = object({
27703
27821
  });
27704
27822
  var RecordingAvailabilitySchema = object({
27705
27823
  deviceId: number(),
27706
- ranges: array(RecordingRangeSchema)
27824
+ ranges: array(RecordingRangeSchema),
27825
+ /**
27826
+ * Every profile this camera has footage in — not only the one `ranges`
27827
+ * describes (D433).
27828
+ *
27829
+ * `ranges` answers for ONE profile by design: the timeline is a single bar,
27830
+ * and enumerating all of them triples the directory reads for a bar that
27831
+ * would look identical. But the answer used to say nothing about that, so a
27832
+ * caller asking "what can I export?" read the single preferred profile as
27833
+ * the only one that exists — on this deployment every camera records `high`
27834
+ * AND `low`, the low tree holding 14 GB for camera 615 alone, and the export
27835
+ * UI could not see it.
27836
+ *
27837
+ * Cheap: it is the same shallow directory read the profile CHOICE already
27838
+ * makes. Ask for a specific profile's ranges with the `profile` input.
27839
+ */
27840
+ profilesWithFootage: array(string())
27707
27841
  });
27708
27842
  var RecordingDaysSchema = object({
27709
27843
  deviceId: number(),
@@ -27729,7 +27863,11 @@ var RecordingDaysSchema = object({
27729
27863
  var RecordingAvailabilityForDeviceSchema = object({
27730
27864
  deviceId: number(),
27731
27865
  read: _enum(["read", "unreadable"]),
27732
- ranges: array(RecordingRangeSchema).readonly()
27866
+ ranges: array(RecordingRangeSchema).readonly(),
27867
+ /** Same field, same meaning, as the singular answer (D433). A row that
27868
+ * dropped it would tell a grid caller the archive holds one profile.
27869
+ * Empty on an `'unreadable'` row: nobody looked. */
27870
+ profilesWithFootage: array(string()).readonly()
27733
27871
  });
27734
27872
  /**
27735
27873
  * One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
@@ -27759,13 +27897,31 @@ var RecordingManifestSchema = object({
27759
27897
  */
27760
27898
  playbackEndpoints: array(string())
27761
27899
  });
27762
- /** Recording storage usage for one camera (bytes on disk across all its
27763
- * profiles/subtrees/locations on this node). */
27900
+ /**
27901
+ * Recording storage usage for one camera — what the ARCHIVE holds for it,
27902
+ * across every profile and every resolvable location on this node.
27903
+ *
27904
+ * ## Archive vs residency, which is the whole point of this shape (D418)
27905
+ *
27906
+ * `usedBytes` / `oldestMs` / `segments` are the durable hour ledger's numbers:
27907
+ * the same quantity `RecordingLocationUsage.usedBytes` reports for the volume
27908
+ * this camera writes to, so the two can be added up and compared.
27909
+ *
27910
+ * They were not always. Until D418 this row came from the recorder's in-RAM
27911
+ * `RecordingIndex`, which since D287 holds only the live tail plus the hours
27912
+ * somebody hydrated — so a hub with 1.9 TB and 3.3 days of footage reported
27913
+ * 60.5 GB and "oldest: three hours ago" on the same page as the volume row
27914
+ * that said otherwise, and two people read it as retention eating recent
27915
+ * footage. Residency is still reported, under `residentBytes` /
27916
+ * `residentOldestMs`, which answer a different question and say so.
27917
+ */
27764
27918
  var RecordingDeviceUsageSchema = object({
27765
27919
  deviceId: number(),
27920
+ /** Bytes the ARCHIVE holds for this camera on this node's resolvable
27921
+ * recordings locations. */
27766
27922
  usedBytes: number(),
27767
27923
  /**
27768
- * Start of this camera's OLDEST indexed segment, across every profile and
27924
+ * Start of this camera's OLDEST ARCHIVED segment, across every profile and
27769
27925
  * location — the "Oldest footage" column in Recordings → Storage, and the
27770
27926
  * only honest answer to "is retention actually holding?" per camera.
27771
27927
  *
@@ -27775,7 +27931,24 @@ var RecordingDeviceUsageSchema = object({
27775
27931
  * (types) and the addon ship on different trains, and the addon is usually
27776
27932
  * the later of the two.
27777
27933
  */
27778
- oldestMs: number().nullable().optional()
27934
+ oldestMs: number().nullable().optional(),
27935
+ /** Segments the archive holds for this camera — the "M" beside `usedBytes`.
27936
+ * OPTIONAL for the same train-skew reason as `oldestMs`. */
27937
+ segments: number().optional(),
27938
+ /**
27939
+ * RESIDENCY, not the archive: bytes the recorder process currently holds in
27940
+ * its RAM index — the live tail plus the hours a reader hydrated.
27941
+ *
27942
+ * It answers "what can be served without touching the disk", never "how much
27943
+ * footage this camera has". A UI that renders it must label it as such, and
27944
+ * must never total it as storage. OPTIONAL: a recorder predating D418 omits
27945
+ * it, and so does a node whose ledger could not answer.
27946
+ */
27947
+ residentBytes: number().optional(),
27948
+ /** The residency counterpart of `oldestMs` — the oldest segment currently in
27949
+ * RAM, which on a healthy recorder is minutes ago and means nothing about
27950
+ * retention. `null` = nothing resident. */
27951
+ residentOldestMs: number().nullable().optional()
27779
27952
  });
27780
27953
  /** Recording storage usage + capacity for one storage location. */
27781
27954
  var RecordingLocationUsageSchema = object({
@@ -27813,6 +27986,17 @@ var RecordingStorageUsageSchema = object({
27813
27986
  totalUsedBytes: number(),
27814
27987
  devices: array(RecordingDeviceUsageSchema),
27815
27988
  /**
27989
+ * WHERE `devices[]` came from, so an empty table is never mistaken for empty
27990
+ * disks (D418).
27991
+ *
27992
+ * `archive` = the durable hour ledger answered. `unavailable` = this node
27993
+ * declared no ledger and the recorder REFUSED rather than answering the
27994
+ * question with the resident index (D295 rule 3) — the rows are UNKNOWN, not
27995
+ * zero, and a UI must say so. OPTIONAL: a recorder predating D418 omits it,
27996
+ * and its rows are residency.
27997
+ */
27998
+ devicesRead: _enum(["archive", "unavailable"]).optional(),
27999
+ /**
27816
28000
  * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
27817
28001
  * of the payload without it: the hub serves this method through a BAKED
27818
28002
  * router, so a hub whose framework train predates a change to this array
@@ -27973,7 +28157,14 @@ var ReadWindowBytesResultSchema = discriminatedUnion("kind", [object({
27973
28157
  method(object({
27974
28158
  deviceId: number(),
27975
28159
  fromMs: number(),
27976
- toMs: number()
28160
+ toMs: number(),
28161
+ /**
28162
+ * Answer for THIS profile instead of the preferred one (D433). Absent
28163
+ * keeps the timeline's behaviour — one bar, one profile, one set of
28164
+ * reads. `profilesWithFootage` on the answer says what may be asked
28165
+ * for.
28166
+ */
28167
+ profile: string().optional()
27977
28168
  }), RecordingAvailabilitySchema, {
27978
28169
  kind: "query",
27979
28170
  auth: "protected"
@@ -34200,6 +34391,24 @@ Object.freeze({
34200
34391
  addonId: null,
34201
34392
  access: "view"
34202
34393
  },
34394
+ "pipelineRunner.getParkedTrackFrame": {
34395
+ capName: "pipeline-runner",
34396
+ capScope: "system",
34397
+ addonId: null,
34398
+ access: "view"
34399
+ },
34400
+ "pipelineRunner.parkTrackFrame": {
34401
+ capName: "pipeline-runner",
34402
+ capScope: "system",
34403
+ addonId: null,
34404
+ access: "create"
34405
+ },
34406
+ "pipelineRunner.releaseParkedTrackFrames": {
34407
+ capName: "pipeline-runner",
34408
+ capScope: "system",
34409
+ addonId: null,
34410
+ access: "create"
34411
+ },
34203
34412
  "pipelineRunner.reportMotion": {
34204
34413
  capName: "pipeline-runner",
34205
34414
  capScope: "system",
@@ -37477,6 +37686,21 @@ Object.freeze({
37477
37686
  form: "single",
37478
37687
  optional: false
37479
37688
  }],
37689
+ "pipelineRunner.getParkedTrackFrame": [{
37690
+ name: "deviceId",
37691
+ form: "single",
37692
+ optional: false
37693
+ }],
37694
+ "pipelineRunner.parkTrackFrame": [{
37695
+ name: "deviceId",
37696
+ form: "single",
37697
+ optional: false
37698
+ }],
37699
+ "pipelineRunner.releaseParkedTrackFrames": [{
37700
+ name: "deviceId",
37701
+ form: "single",
37702
+ optional: false
37703
+ }],
37480
37704
  "pipelineRunner.reportMotion": [{
37481
37705
  name: "deviceId",
37482
37706
  form: "single",
@@ -38168,7 +38392,7 @@ var PreviewTextField = string().max(200);
38168
38392
  * Whether the notification's preview is a STILL or a short animation.
38169
38393
  *
38170
38394
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
38171
- * rule)"* — his Scrypted advanced-notifier has a `gifRule`, and a ten-hour
38395
+ * rule)"* — the other rules already offer an animated preview, and a ten-hour
38172
38396
  * night reads better as three seconds of motion than as one frame of it. Both
38173
38397
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
38174
38398
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -39,7 +39,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
40
40
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
41
41
  //#endregion
42
- //#region ../types/dist/event-category-CnLqLOKs.mjs
42
+ //#region ../types/dist/event-category-DAXzJeTX.mjs
43
43
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
44
44
  EventCategory["SystemBoot"] = "system.boot";
45
45
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -631,7 +631,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
631
631
  * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
632
632
  */
633
633
  EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
634
- EventCategory["FrigateLiveEvent"] = "frigate.live-event";
635
634
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
636
635
  /**
637
636
  * Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
@@ -12596,7 +12595,7 @@ DeviceType.Camera;
12596
12595
  var SourceInfoSchema = object({
12597
12596
  /** Live dispatch key — mutable when the source system allows rename. */
12598
12597
  id: string(),
12599
- /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'frigate'. */
12598
+ /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
12600
12599
  system: string(),
12601
12600
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
12602
12601
  * Used to detect rename when `id` changes. */
@@ -13083,7 +13082,7 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
13083
13082
  * - `device-management.router.ts` (deleted in Phase 2)
13084
13083
  * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
13085
13084
  *
13086
- * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
13085
+ * All device provider addons (rtsp, onvif, …) are hub-local: they may
13087
13086
  * fork into separate processes but never run on remote cluster agents. Therefore:
13088
13087
  * - No nodeId routing needed — this is a pure hub singleton.
13089
13088
  * - The hub's DeviceRegistry is the single source of truth for all live devices.
@@ -17112,7 +17111,7 @@ var NcRuleInputSchema = object({
17112
17111
  * The measured seven-person arrival on device 590 spans 110 s with every
17113
17112
  * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
17114
17113
  * idle cutoff holds it as one and ends it when the arrival actually ends.
17115
- * 30 is Frigate's shipped value for the same decision.
17114
+ * 30 s is a widely shipped value for the same decision.
17116
17115
  *
17117
17116
  * ### What it replaces
17118
17117
  *
@@ -19154,6 +19153,13 @@ var ZoneCrossingSchema = object({
19154
19153
  /** Zone display name at crossing time (falls back to the id). */
19155
19154
  zoneName: string().optional()
19156
19155
  });
19156
+ /** One zone a box was stamped with, and how much of the BOX fell inside it. */
19157
+ var ZoneOverlapSchema = object({
19158
+ zoneId: string(),
19159
+ zoneName: string().optional(),
19160
+ /** Percentage of the BOX area inside the zone, 0–100. */
19161
+ overlapPct: number()
19162
+ });
19157
19163
  var ObjectEventSchema = object({
19158
19164
  ...BaseEventFields,
19159
19165
  kind: literal("object"),
@@ -19178,6 +19184,35 @@ var ObjectEventSchema = object({
19178
19184
  bbox: BoundingBoxSchema.optional(),
19179
19185
  /** Heavy JSON — omitted in slim projection. */
19180
19186
  zones: array(string()).readonly().optional(),
19187
+ /**
19188
+ * How far inside each stamped zone the box actually was, as a percentage of
19189
+ * the BOX area.
19190
+ *
19191
+ * Membership alone cannot express "at least a third of it inside", which is
19192
+ * the bar an operator argues with — and the number already existed: the
19193
+ * engine computed it, the pipeline logged it, and then discarded it. It
19194
+ * matters most on the events that carry no track, where a rule has nothing
19195
+ * else to judge by.
19196
+ */
19197
+ zoneOverlaps: array(ZoneOverlapSchema).readonly().optional(),
19198
+ /**
19199
+ * Present when this event describes a detection the TRACKER refused, so it
19200
+ * has no track and never will.
19201
+ *
19202
+ * A zone-scoped immediate rule asks "is there an animal in the flowerbed
19203
+ * NOW?"; tracking answers a different question ("is this the same subject as
19204
+ * before?") whose confirmation threshold presumes continuity. On 2026-09-09 a
19205
+ * dog at 0.7729, 100% inside a watched zone, was refused three times in six
19206
+ * seconds with three different candidate ids — seven analysed frames in the
19207
+ * window, the subject in a handful of them, metres apart — and produced no
19208
+ * event at all.
19209
+ *
19210
+ * It is NAMED rather than merely implied by a missing `trackId`: an event
19211
+ * with no track and no reason is indistinguishable from a track whose media
19212
+ * went missing, and a consumer that asks this one for a best shot or a
19213
+ * history is asking for something that never existed.
19214
+ */
19215
+ spawnRefused: string().optional(),
19181
19216
  /** Omitted in slim projection. */
19182
19217
  state: TrackStateSchema.optional(),
19183
19218
  /**
@@ -19604,7 +19639,17 @@ var EventDensityBucketSchema = object({
19604
19639
  bucketStart: number(),
19605
19640
  motion: number().int(),
19606
19641
  object: number().int(),
19607
- audio: number().int()
19642
+ audio: number().int(),
19643
+ /**
19644
+ * Energy-weighted mean dBFS of the audio events in this bucket — the LEVEL,
19645
+ * as opposed to `audio`, which is their COUNT (D431).
19646
+ *
19647
+ * ABSENT when the bucket holds no audio event. Absence means "nothing was
19648
+ * measured", which is not the same claim as silence, and a lane that cannot
19649
+ * tell them apart paints a quiet hour at full scale — which is exactly what
19650
+ * the count-on-a-level-meter did.
19651
+ */
19652
+ audioDbfs: number().optional()
19608
19653
  });
19609
19654
  /**
19610
19655
  * One camera's row in a `getEventDensityBatch` answer.
@@ -20251,6 +20296,60 @@ var NativeCropResultSchema = object({
20251
20296
  */
20252
20297
  tier: _enum(["native", "ram-fullframe"]).optional()
20253
20298
  });
20299
+ /**
20300
+ * What a PARKED frame is for — the `kind` half of the `(deviceId, trackId,
20301
+ * kind)` key ([D422](../../../docs/decisions/adr-0422-a-relevant-frame-is-parked-when-it-becomes-relevant.md)).
20302
+ *
20303
+ * - `keyFrame` — the track's clean best FULL frame at native width (capped by
20304
+ * the caller's `maxWidth`). What `keyFrame` + `keyFrameSmall` are written
20305
+ * from, and what the detail plane's full-frame rung cuts from.
20306
+ * - `thumbnail` — the best-shot subject slab: the caller's 16:9 central-square
20307
+ * window, cut by the runner at uncapped native resolution.
20308
+ *
20309
+ * The runner never interprets `trackId`; it is an opaque key the hub owns.
20310
+ */
20311
+ var ParkedFrameKindSchema = _enum(["keyFrame", "thumbnail"]);
20312
+ /**
20313
+ * Why a park did NOT happen. Every one is logged on the runner with
20314
+ * `tags: { deviceId }` and the handle's measured age, because a handle already
20315
+ * gone at park time IS the defect the 150–500 ms window exists to prevent —
20316
+ * never a reason to retry in a loop.
20317
+ *
20318
+ * - `handle-gone` — the native surface no longer resolves this handle (never
20319
+ * registered, past the lease bound, or the worker released it). `detail`
20320
+ * carries the runner's own miss reason.
20321
+ * - `encode-failed` — pixels resolved and the JPEG encode threw.
20322
+ */
20323
+ var ParkRefusalSchema = _enum(["handle-gone", "encode-failed"]);
20324
+ /** Answer to `parkTrackFrame` — a discriminated union, so "not parked" always names why. */
20325
+ var ParkTrackFrameResultSchema = discriminatedUnion("parked", [object({
20326
+ parked: literal(true),
20327
+ /** The node holding the parcel — what a later retrieval must be pinned to. */
20328
+ nodeId: string(),
20329
+ width: number().int().positive(),
20330
+ height: number().int().positive(),
20331
+ /** JPEG bytes the parcel costs on the node. */
20332
+ bytes: number().int().nonnegative(),
20333
+ /** Whether this park replaced a parcel already held for the key. */
20334
+ replaced: boolean()
20335
+ }), object({
20336
+ parked: literal(false),
20337
+ nodeId: string(),
20338
+ reason: ParkRefusalSchema,
20339
+ /** Age of the handle at the park, on the runner's clock; null if unknown. */
20340
+ handleAgeMs: number().nullable(),
20341
+ detail: string().optional()
20342
+ })]);
20343
+ /** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
20344
+ var ParkedTrackFrameSchema = object({
20345
+ jpeg: string(),
20346
+ width: number().int().positive(),
20347
+ height: number().int().positive(),
20348
+ /** The frame instant the parcel shows (the caller's clock, echoed back). */
20349
+ timestamp: number(),
20350
+ /** When the node stored it (the node's clock). */
20351
+ parkedAt: number()
20352
+ });
20254
20353
  /** Parent detection context passed to `runDetailSubtree` — the crop's
20255
20354
  * originating detection, in FRAME-space coordinates. Reuses
20256
20355
  * `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
@@ -20640,7 +20739,7 @@ var RunnerCameraConfigSchema = object({
20640
20739
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
20641
20740
  * analyzer providers) running on decoded frames in this runner.
20642
20741
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
20643
- * analytics, Frigate motion). The provider emits ProviderMotion
20742
+ * analytics). The provider emits ProviderMotion
20644
20743
  * events; the orchestrator forwards to `reportMotion`.
20645
20744
  */
20646
20745
  motionSources: MotionSourcesSchema.default(["analyzer"]),
@@ -20882,7 +20981,26 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
20882
20981
  className: string(),
20883
20982
  /** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
20884
20983
  sourceDeviceId: number()
20885
- }), RunStatelessStepResultSchema, { kind: "mutation" });
20984
+ }), RunStatelessStepResultSchema, { kind: "mutation" }), method(object({
20985
+ deviceId: number(),
20986
+ /** Opaque to the runner — the hub's track id. */
20987
+ trackId: string(),
20988
+ kind: ParkedFrameKindSchema,
20989
+ handle: FrameHandleSchema,
20990
+ /** Normalized [0,1] window to cut. `{0,0,1,1}` for a full frame. */
20991
+ bbox: NativeCropBboxSchema,
20992
+ /** Width cap for a full-frame park (the `keyFrame` native cap). */
20993
+ maxWidth: number().int().positive().optional(),
20994
+ /** The frame instant (caller's clock) — orders parks for one key. */
20995
+ timestamp: number()
20996
+ }), ParkTrackFrameResultSchema, { kind: "mutation" }), method(object({
20997
+ deviceId: number(),
20998
+ trackId: string(),
20999
+ kind: ParkedFrameKindSchema
21000
+ }), ParkedTrackFrameSchema.nullable()), method(object({
21001
+ deviceId: number(),
21002
+ trackId: string()
21003
+ }), object({ released: number().int().nonnegative() }), { kind: "mutation" });
20886
21004
  var CameraPipelineConfigSchema = object({
20887
21005
  engine: PipelineEngineChoiceSchema.optional(),
20888
21006
  steps: array(PipelineStepInputSchema).readonly(),
@@ -23811,7 +23929,7 @@ DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _v
23811
23929
  *
23812
23930
  * Implementations:
23813
23931
  * - Each camera-providing addon registers this cap natively per
23814
- * device (Reolink, Hikvision, RTSP, Frigate, ONVIF self).
23932
+ * device (Reolink, Hikvision, RTSP, ONVIF self).
23815
23933
  * - Returns a snapshot from the persisted device config blob — no
23816
23934
  * extra round-trip to the camera, no probing.
23817
23935
  * - The cap is admin-only (`auth: 'admin'`) — credentials must not
@@ -25197,7 +25315,7 @@ var AvailableIntegrationTypeSchema = object({
25197
25315
  * Which integration-marker cap the addon declared, so the wizard can
25198
25316
  * branch on CAP — never on addon name. `device-adoption` integrations
25199
25317
  * (Home Assistant, …) route through the broker step (Approach A);
25200
- * `device-provider` integrations (Reolink/Frigate/ONVIF) keep the
25318
+ * `device-provider` integrations (Reolink/ONVIF) keep the
25201
25319
  * legacy config → discovery flow.
25202
25320
  */
25203
25321
  kind: _enum(["device-adoption", "device-provider"]),
@@ -27698,7 +27816,23 @@ var RecordingRangeSchema = object({
27698
27816
  });
27699
27817
  var RecordingAvailabilitySchema = object({
27700
27818
  deviceId: number(),
27701
- ranges: array(RecordingRangeSchema)
27819
+ ranges: array(RecordingRangeSchema),
27820
+ /**
27821
+ * Every profile this camera has footage in — not only the one `ranges`
27822
+ * describes (D433).
27823
+ *
27824
+ * `ranges` answers for ONE profile by design: the timeline is a single bar,
27825
+ * and enumerating all of them triples the directory reads for a bar that
27826
+ * would look identical. But the answer used to say nothing about that, so a
27827
+ * caller asking "what can I export?" read the single preferred profile as
27828
+ * the only one that exists — on this deployment every camera records `high`
27829
+ * AND `low`, the low tree holding 14 GB for camera 615 alone, and the export
27830
+ * UI could not see it.
27831
+ *
27832
+ * Cheap: it is the same shallow directory read the profile CHOICE already
27833
+ * makes. Ask for a specific profile's ranges with the `profile` input.
27834
+ */
27835
+ profilesWithFootage: array(string())
27702
27836
  });
27703
27837
  var RecordingDaysSchema = object({
27704
27838
  deviceId: number(),
@@ -27724,7 +27858,11 @@ var RecordingDaysSchema = object({
27724
27858
  var RecordingAvailabilityForDeviceSchema = object({
27725
27859
  deviceId: number(),
27726
27860
  read: _enum(["read", "unreadable"]),
27727
- ranges: array(RecordingRangeSchema).readonly()
27861
+ ranges: array(RecordingRangeSchema).readonly(),
27862
+ /** Same field, same meaning, as the singular answer (D433). A row that
27863
+ * dropped it would tell a grid caller the archive holds one profile.
27864
+ * Empty on an `'unreadable'` row: nobody looked. */
27865
+ profilesWithFootage: array(string()).readonly()
27728
27866
  });
27729
27867
  /**
27730
27868
  * One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
@@ -27754,13 +27892,31 @@ var RecordingManifestSchema = object({
27754
27892
  */
27755
27893
  playbackEndpoints: array(string())
27756
27894
  });
27757
- /** Recording storage usage for one camera (bytes on disk across all its
27758
- * profiles/subtrees/locations on this node). */
27895
+ /**
27896
+ * Recording storage usage for one camera — what the ARCHIVE holds for it,
27897
+ * across every profile and every resolvable location on this node.
27898
+ *
27899
+ * ## Archive vs residency, which is the whole point of this shape (D418)
27900
+ *
27901
+ * `usedBytes` / `oldestMs` / `segments` are the durable hour ledger's numbers:
27902
+ * the same quantity `RecordingLocationUsage.usedBytes` reports for the volume
27903
+ * this camera writes to, so the two can be added up and compared.
27904
+ *
27905
+ * They were not always. Until D418 this row came from the recorder's in-RAM
27906
+ * `RecordingIndex`, which since D287 holds only the live tail plus the hours
27907
+ * somebody hydrated — so a hub with 1.9 TB and 3.3 days of footage reported
27908
+ * 60.5 GB and "oldest: three hours ago" on the same page as the volume row
27909
+ * that said otherwise, and two people read it as retention eating recent
27910
+ * footage. Residency is still reported, under `residentBytes` /
27911
+ * `residentOldestMs`, which answer a different question and say so.
27912
+ */
27759
27913
  var RecordingDeviceUsageSchema = object({
27760
27914
  deviceId: number(),
27915
+ /** Bytes the ARCHIVE holds for this camera on this node's resolvable
27916
+ * recordings locations. */
27761
27917
  usedBytes: number(),
27762
27918
  /**
27763
- * Start of this camera's OLDEST indexed segment, across every profile and
27919
+ * Start of this camera's OLDEST ARCHIVED segment, across every profile and
27764
27920
  * location — the "Oldest footage" column in Recordings → Storage, and the
27765
27921
  * only honest answer to "is retention actually holding?" per camera.
27766
27922
  *
@@ -27770,7 +27926,24 @@ var RecordingDeviceUsageSchema = object({
27770
27926
  * (types) and the addon ship on different trains, and the addon is usually
27771
27927
  * the later of the two.
27772
27928
  */
27773
- oldestMs: number().nullable().optional()
27929
+ oldestMs: number().nullable().optional(),
27930
+ /** Segments the archive holds for this camera — the "M" beside `usedBytes`.
27931
+ * OPTIONAL for the same train-skew reason as `oldestMs`. */
27932
+ segments: number().optional(),
27933
+ /**
27934
+ * RESIDENCY, not the archive: bytes the recorder process currently holds in
27935
+ * its RAM index — the live tail plus the hours a reader hydrated.
27936
+ *
27937
+ * It answers "what can be served without touching the disk", never "how much
27938
+ * footage this camera has". A UI that renders it must label it as such, and
27939
+ * must never total it as storage. OPTIONAL: a recorder predating D418 omits
27940
+ * it, and so does a node whose ledger could not answer.
27941
+ */
27942
+ residentBytes: number().optional(),
27943
+ /** The residency counterpart of `oldestMs` — the oldest segment currently in
27944
+ * RAM, which on a healthy recorder is minutes ago and means nothing about
27945
+ * retention. `null` = nothing resident. */
27946
+ residentOldestMs: number().nullable().optional()
27774
27947
  });
27775
27948
  /** Recording storage usage + capacity for one storage location. */
27776
27949
  var RecordingLocationUsageSchema = object({
@@ -27808,6 +27981,17 @@ var RecordingStorageUsageSchema = object({
27808
27981
  totalUsedBytes: number(),
27809
27982
  devices: array(RecordingDeviceUsageSchema),
27810
27983
  /**
27984
+ * WHERE `devices[]` came from, so an empty table is never mistaken for empty
27985
+ * disks (D418).
27986
+ *
27987
+ * `archive` = the durable hour ledger answered. `unavailable` = this node
27988
+ * declared no ledger and the recorder REFUSED rather than answering the
27989
+ * question with the resident index (D295 rule 3) — the rows are UNKNOWN, not
27990
+ * zero, and a UI must say so. OPTIONAL: a recorder predating D418 omits it,
27991
+ * and its rows are residency.
27992
+ */
27993
+ devicesRead: _enum(["archive", "unavailable"]).optional(),
27994
+ /**
27811
27995
  * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
27812
27996
  * of the payload without it: the hub serves this method through a BAKED
27813
27997
  * router, so a hub whose framework train predates a change to this array
@@ -27968,7 +28152,14 @@ var ReadWindowBytesResultSchema = discriminatedUnion("kind", [object({
27968
28152
  method(object({
27969
28153
  deviceId: number(),
27970
28154
  fromMs: number(),
27971
- toMs: number()
28155
+ toMs: number(),
28156
+ /**
28157
+ * Answer for THIS profile instead of the preferred one (D433). Absent
28158
+ * keeps the timeline's behaviour — one bar, one profile, one set of
28159
+ * reads. `profilesWithFootage` on the answer says what may be asked
28160
+ * for.
28161
+ */
28162
+ profile: string().optional()
27972
28163
  }), RecordingAvailabilitySchema, {
27973
28164
  kind: "query",
27974
28165
  auth: "protected"
@@ -34195,6 +34386,24 @@ Object.freeze({
34195
34386
  addonId: null,
34196
34387
  access: "view"
34197
34388
  },
34389
+ "pipelineRunner.getParkedTrackFrame": {
34390
+ capName: "pipeline-runner",
34391
+ capScope: "system",
34392
+ addonId: null,
34393
+ access: "view"
34394
+ },
34395
+ "pipelineRunner.parkTrackFrame": {
34396
+ capName: "pipeline-runner",
34397
+ capScope: "system",
34398
+ addonId: null,
34399
+ access: "create"
34400
+ },
34401
+ "pipelineRunner.releaseParkedTrackFrames": {
34402
+ capName: "pipeline-runner",
34403
+ capScope: "system",
34404
+ addonId: null,
34405
+ access: "create"
34406
+ },
34198
34407
  "pipelineRunner.reportMotion": {
34199
34408
  capName: "pipeline-runner",
34200
34409
  capScope: "system",
@@ -37472,6 +37681,21 @@ Object.freeze({
37472
37681
  form: "single",
37473
37682
  optional: false
37474
37683
  }],
37684
+ "pipelineRunner.getParkedTrackFrame": [{
37685
+ name: "deviceId",
37686
+ form: "single",
37687
+ optional: false
37688
+ }],
37689
+ "pipelineRunner.parkTrackFrame": [{
37690
+ name: "deviceId",
37691
+ form: "single",
37692
+ optional: false
37693
+ }],
37694
+ "pipelineRunner.releaseParkedTrackFrames": [{
37695
+ name: "deviceId",
37696
+ form: "single",
37697
+ optional: false
37698
+ }],
37475
37699
  "pipelineRunner.reportMotion": [{
37476
37700
  name: "deviceId",
37477
37701
  form: "single",
@@ -38163,7 +38387,7 @@ var PreviewTextField = string().max(200);
38163
38387
  * Whether the notification's preview is a STILL or a short animation.
38164
38388
  *
38165
38389
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
38166
- * rule)"* — his Scrypted advanced-notifier has a `gifRule`, and a ten-hour
38390
+ * rule)"* — the other rules already offer an animated preview, and a ten-hour
38167
38391
  * night reads better as three seconds of motion than as one frame of it. Both
38168
38392
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
38169
38393
  * simply applies it to a dozen frames sampled across the render and assembles
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-mqtt-broker",
3
- "version": "1.2.90",
3
+ "version": "1.2.93",
4
4
  "description": "MQTT broker registry addon for CamStack — manages external broker entries + an optional embedded aedes broker. Consumers spin up their own `mqtt.js` clients via the `mqtt-broker` cap.",
5
5
  "keywords": [
6
6
  "camstack",