@camstack/addon-pipeline 1.2.18 → 1.2.27

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 (36) hide show
  1. package/dist/audio-analyzer/index.js +1 -1
  2. package/dist/audio-analyzer/index.mjs +3 -2
  3. package/dist/chunk-DnnnRqeS.mjs +26 -0
  4. package/dist/detection-pipeline/index.js +2 -2
  5. package/dist/detection-pipeline/index.mjs +4 -3
  6. package/dist/{dist-OjOsoSZG.mjs → dist-CsKaXmP8.mjs} +500 -130
  7. package/dist/{dist-DDTAur0R.js → dist-DUHlZcWd.js} +505 -135
  8. package/dist/{model-download-service-Cp9f4dk6-WO-foD7W.mjs → model-download-service-Cp9f4dk6-CwUb5v3Y.mjs} +1 -26
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/pipeline-runner/index.js +162 -19
  12. package/dist/pipeline-runner/index.mjs +162 -19
  13. package/dist/recorder/index.js +2604 -458
  14. package/dist/recorder/index.mjs +2599 -459
  15. package/dist/{step-definitions-DGCuoeCj.js → step-definitions-Bo1hJQQw.js} +1 -1
  16. package/dist/{step-definitions-B9vyUNYR.mjs → step-definitions-C8A78dBi.mjs} +1 -1
  17. package/dist/stream-broker/_stub.js +2 -2
  18. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-S2nRs43G.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-tHahN-lK.mjs} +3 -3
  19. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D1zJmtUe.mjs +26 -0
  20. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CSWUWZ8c.mjs +26 -0
  21. package/dist/stream-broker/{hostInit-p_rpDGqr.mjs → hostInit-CIwilJUZ.mjs} +3 -3
  22. package/dist/stream-broker/index.js +7374 -250
  23. package/dist/stream-broker/index.mjs +7371 -249
  24. package/dist/stream-broker/remoteEntry.js +1 -1
  25. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-Hg_mGtXA.js → MaskShapeCanvas-DI4BY7W2-B7VdNJlO.js} +1 -1
  26. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-C0-pVeBX.js → MotionZonesSettings-NcxxQN8r-YyB4uLdH.js} +1 -1
  27. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-QVePTd0g.js → PrivacyMaskSettings-APgPLF7p-DfqixEad.js} +1 -1
  28. package/embed-dist/assets/{index-Dv5H-32C.css → index-CAe6Dg-X.css} +1 -1
  29. package/embed-dist/assets/index-zVLLVdIS.js +343 -0
  30. package/embed-dist/index.html +2 -2
  31. package/package.json +5 -1
  32. package/dist/fmp4-keyframes-5s8T9Mng.js +0 -6711
  33. package/dist/fmp4-keyframes-CV0X1mGy.mjs +0 -6694
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BDeEvhV5.mjs +0 -26
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BGtpZkgz.mjs +0 -26
  36. package/embed-dist/assets/index-CsaHbiy9.js +0 -116
@@ -7371,35 +7371,22 @@ var EVENT_PAD_MS = {
7371
7371
  */
7372
7372
  var RecordingWeekdaySchema = number().int().min(0).max(6);
7373
7373
  var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
7374
- var RecordingScheduleSchema = discriminatedUnion("kind", [object({ kind: literal("always") }), object({
7375
- kind: literal("timeOfDay"),
7376
- start: string().regex(HHMM),
7377
- end: string().regex(HHMM),
7378
- /** Restrict to these weekdays; omit = every day. */
7379
- days: array(RecordingWeekdaySchema).optional()
7380
- })]);
7381
- var RecordingModeSchema = _enum([
7382
- "continuous",
7383
- "onMotion",
7384
- "onAudioThreshold"
7385
- ]);
7386
7374
  /**
7387
- * First-class, authoritative per-camera storage mode — the explicit choice the
7388
- * UI reads directly (never inferred from `rules`):
7389
- * - `off` — not recording.
7390
- * - `events` — record only around triggers (motion / audio threshold),
7391
- * with pre/post-buffer.
7392
- * - `continuous` — record 24/7 within the schedule.
7375
+ * DERIVED per-camera storage summary — the single field cheap consumers read
7376
+ * (the viewer's status dot, the camera list) instead of walking `bands`:
7377
+ * - `off` — no band covers the camera (or it is disabled).
7378
+ * - `events` — every band records around triggers only.
7379
+ * - `continuous` — at least one band records continuously.
7393
7380
  *
7394
- * `mode` compiles one-way to the internal `rules[]` consumed by the policy
7395
- * engine (see `compileRules`); `rules[]` is never authored directly anymore.
7381
+ * NEVER authored: the recorder stamps it from the authoritative `bands` on
7382
+ * every save (`activeModeForConfig`). Writing it has no effect.
7396
7383
  */
7397
7384
  var RecordingStorageModeSchema = _enum([
7398
7385
  "off",
7399
7386
  "events",
7400
7387
  "continuous"
7401
7388
  ]);
7402
- /** Which detectors trigger an `events`-mode recording. */
7389
+ /** Which detectors trigger an `events`-mode band. */
7403
7390
  var RecordingTriggersSchema = object({
7404
7391
  motion: boolean().optional(),
7405
7392
  audioThresholdDbfs: number().optional()
@@ -7435,18 +7422,6 @@ var RecordingBandSchema = object({
7435
7422
  preBufferSec: number().min(0).optional(),
7436
7423
  postBufferSec: number().min(0).optional()
7437
7424
  });
7438
- var RecordingRuleSchema = object({
7439
- schedule: RecordingScheduleSchema,
7440
- mode: RecordingModeSchema,
7441
- /** Seconds of footage to retain BEFORE a trigger (applied at keep/discard). */
7442
- preBufferSec: number().min(0).default(0),
7443
- /** Keep recording until this many seconds after the last trigger. */
7444
- postBufferSec: number().min(0).default(0),
7445
- /** Each new trigger restarts the post-buffer window. */
7446
- resetTimeoutOnNewEvent: boolean().default(true),
7447
- /** onAudioThreshold only — dBFS level that counts as a trigger. */
7448
- thresholdDbfs: number().optional()
7449
- });
7450
7425
  /**
7451
7426
  * Per-device retention overrides. Every field is optional; an unset or `0`
7452
7427
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -7480,40 +7455,28 @@ var ScrubThumbnailPresetSchema = _enum([
7480
7455
  /**
7481
7456
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7482
7457
  *
7483
- * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
7484
- * are its mode-specific parameters. `rules` is a DEPRECATED authoring input kept
7485
- * only for transition + migration (`migrateRulesToMode`); the policy engine
7486
- * consumes the compiled output of `compileRules(config)`, never `rules` directly.
7458
+ * `bands` is the ONLY authored recording intent: what to record, when, and on
7459
+ * which trigger. `mode` is a derived summary the recorder stamps on save; every
7460
+ * other field is a storage knob (profiles, segment length, retention, scrub).
7461
+ *
7462
+ * STRICT on purpose: the legacy authoring surface (`schedule`/`schedules`/
7463
+ * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30.
7464
+ * A stale caller must fail loudly — silently stripping its legacy intent would
7465
+ * persist a band-less config, i.e. silently stop recording the camera.
7487
7466
  */
7488
7467
  var RecordingConfigSchema = object({
7489
7468
  enabled: boolean(),
7490
- /** Authoritative storage mode. Absent on legacy targets derived once via
7491
- * `migrateRulesToMode`, then persisted. */
7469
+ /** DERIVED summary of `bands`, stamped by the recorder on every save.
7470
+ * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
7492
7471
  mode: RecordingStorageModeSchema.optional(),
7493
7472
  profiles: array(CamProfileSchema).optional(),
7494
7473
  segmentSeconds: number().int().positive().optional(),
7495
- /** Shared recording time-bands for `events` & `continuous` — record only when
7496
- * the wall-clock falls inside one of these windows. Omit or empty = always.
7497
- * `continuous` compiles to one rule per band; `events` to band × trigger. */
7498
- schedules: array(RecordingScheduleSchema).optional(),
7499
- /** Legacy single-band predecessor of `schedules`. Read-compat only — it is
7500
- * normalized into `schedules` on read and never written going forward. (Not
7501
- * tagged `@deprecated`: the normalization paths must read it cast-free.) */
7502
- schedule: RecordingScheduleSchema.optional(),
7503
- /** `events`-mode only — which detectors trigger a recording. */
7504
- triggers: RecordingTriggersSchema.optional(),
7505
- /** `events`-mode only — seconds retained before / after a trigger. */
7506
- preBufferSec: number().min(0).optional(),
7507
- postBufferSec: number().min(0).optional(),
7508
- /** DEPRECATED authoring input; retained for migration/transition. */
7509
- rules: array(RecordingRuleSchema).optional(),
7510
7474
  /**
7511
- * AUTHORITATIVE mode-per-band recording model (recorder). When present it
7512
- * is the single source of truth; the legacy `mode`/`schedules`/`schedule`/
7513
- * `triggers`/`rules` fields above are kept for READ-COMPAT only and are
7514
- * derived into bands once via `migrateConfigToBands`.
7475
+ * AUTHORITATIVE mode-per-band recording model the single source of truth
7476
+ * the recorder's band engine consumes. An empty array = record nothing;
7477
+ * "off" is the absence of a covering band, never a band value.
7515
7478
  */
7516
- bands: array(RecordingBandSchema).optional(),
7479
+ bands: array(RecordingBandSchema).default([]),
7517
7480
  retention: RecordingRetentionSchema.optional(),
7518
7481
  /**
7519
7482
  * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
@@ -7521,60 +7484,30 @@ var RecordingConfigSchema = object({
7521
7484
  * windows only — existing sheets are immutable, and each window's index
7522
7485
  * carries its own tile dims so mixed-preset history renders correctly.
7523
7486
  */
7524
- scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7525
- });
7526
- /** All seven weekdays (0 = Sunday 6 = Saturday), used when a schedule omits a
7527
- * weekday filter. */
7528
- var ALL_DAYS = [
7529
- 0,
7530
- 1,
7531
- 2,
7532
- 3,
7533
- 4,
7534
- 5,
7535
- 6
7536
- ];
7537
- /** Full-day span used to represent an `always` schedule as a band. The `HHMM`
7538
- * regex maxes out at 23:59, so a 00:00–23:59 same-day span all-day (the band
7539
- * engine treats `end > start` as a normal, non-wrapping span). */
7540
- var ALL_DAY_START = "00:00";
7541
- var ALL_DAY_END = "23:59";
7542
- function bandFromSchedule(schedule, mode, config) {
7543
- const days = schedule.kind === "timeOfDay" && schedule.days ? schedule.days : [...ALL_DAYS];
7544
- const start = schedule.kind === "timeOfDay" ? schedule.start : ALL_DAY_START;
7545
- const end = schedule.kind === "timeOfDay" ? schedule.end : ALL_DAY_END;
7546
- if (mode === "continuous") return {
7547
- days,
7548
- start,
7549
- end,
7550
- mode
7551
- };
7552
- return {
7553
- days,
7554
- start,
7555
- end,
7556
- mode,
7557
- ...config.triggers ? { triggers: config.triggers } : {},
7558
- ...config.preBufferSec !== void 0 ? { preBufferSec: config.preBufferSec } : {},
7559
- ...config.postBufferSec !== void 0 ? { postBufferSec: config.postBufferSec } : {}
7560
- };
7561
- }
7562
- /**
7563
- * One-way migration from the legacy recording-config shape to the authoritative
7564
- * mode-per-band model. Pure (no clock access).
7565
- *
7566
- * - If `config.bands` is present, it is authoritative and returned as-is.
7567
- * - Otherwise the result is derived from the legacy fields: a disabled config or
7568
- * `mode: 'off'` yields no bands; every legacy schedule maps to one band whose
7569
- * mode is `continuous` when `config.mode === 'continuous'`, else `events`
7570
- * (carrying `triggers`/`preBufferSec`/`postBufferSec`).
7571
- */
7572
- function migrateConfigToBands(config) {
7573
- if (config.bands) return config.bands;
7574
- if (!config.enabled || config.mode === "off") return [];
7575
- const schedules = config.schedules ?? (config.schedule ? [config.schedule] : [{ kind: "always" }]);
7576
- const bandMode = config.mode === "continuous" ? "continuous" : "events";
7577
- return schedules.map((schedule) => bandFromSchedule(schedule, bandMode, config));
7487
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional(),
7488
+ /**
7489
+ * OPT-IN thumbnail-strip generation for this camera: every keyframe of the
7490
+ * low recording saved as a JPEG (the fast-drag scrub depth), a derived
7491
+ * cache that eviction reclaims with the footage. Absent/false = no strips
7492
+ * are written and scrub reads exact keyframes at every velocity.
7493
+ */
7494
+ stripsEnabled: boolean().optional()
7495
+ }).strict();
7496
+ /**
7497
+ * Derive the {@link RecordingStorageModeSchema} summary from the authoritative
7498
+ * bands: `continuous` when any band records continuously, `events` when a band
7499
+ * exists at all, else `off`. Pure.
7500
+ *
7501
+ * THE one definition: the recorder stamps `config.mode` with it on save and
7502
+ * reports it as the cap's `activeMode`, and the settings UI reads it back to
7503
+ * decide which authoring tab a config opens in. Two copies would drift, and the
7504
+ * symptom (a status dot disagreeing with what is being recorded) is exactly the
7505
+ * bug this field was added to fix.
7506
+ */
7507
+ function deriveRecordingMode(config) {
7508
+ if (!config.enabled || config.bands.length === 0) return "off";
7509
+ if (config.bands.some((band) => band.mode === "continuous")) return "continuous";
7510
+ return "events";
7578
7511
  }
7579
7512
  /**
7580
7513
  * Ops-log — the durable, append-only operations audit shared by the
@@ -7594,7 +7527,8 @@ var OpsLogOpSchema = _enum([
7594
7527
  "prune",
7595
7528
  "manual-delete",
7596
7529
  "rescan",
7597
- "retention-run"
7530
+ "retention-run",
7531
+ "relocate"
7598
7532
  ]);
7599
7533
  /** Why the operation ran. */
7600
7534
  var OpsLogReasonSchema = _enum([
@@ -7633,6 +7567,55 @@ var OpsLogQueryInputSchema = object({
7633
7567
  limit: number().int().min(1).max(1e3).optional()
7634
7568
  });
7635
7569
  /**
7570
+ * Entity-relocation job state (storage entity-routing spec, Phase 4).
7571
+ *
7572
+ * One shape shared by the recorder's `relocateFootage` (segments + strips) and
7573
+ * pipeline-analytics' `relocateMedia` (event media blobs) so the admin Data
7574
+ * page renders both movers with one component. Jobs are in-RAM (a restart
7575
+ * forgets them — re-running is safe by construction: copy-if-absent, delete
7576
+ * after verify) and each completed/failed run also lands one durable ops-log
7577
+ * row on the owning addon's surface.
7578
+ */
7579
+ var RelocateJobStateSchema = _enum([
7580
+ "running",
7581
+ "done",
7582
+ "failed",
7583
+ "cancelled"
7584
+ ]);
7585
+ var RelocateJobSchema = object({
7586
+ jobId: string(),
7587
+ state: RelocateJobStateSchema,
7588
+ /** Source location — for media relocation this is informational ('*': rows
7589
+ * move from wherever they are to the target). */
7590
+ fromLocationId: string(),
7591
+ toLocationId: string(),
7592
+ /** Scoped device, or null = every device. */
7593
+ deviceId: number().nullable(),
7594
+ /** What the job moves (owner-addon specific: segments/strips or media). */
7595
+ entities: array(string()),
7596
+ filesMoved: number().int(),
7597
+ bytesMoved: number().int(),
7598
+ /** Total files discovered up front; null while (or when) unknown. */
7599
+ filesTotal: number().int().nullable(),
7600
+ startedAt: number(),
7601
+ finishedAt: number().nullable(),
7602
+ error: string().nullable()
7603
+ });
7604
+ var RelocateFootageInputSchema = object({
7605
+ deviceId: number().optional(),
7606
+ fromLocationId: string(),
7607
+ toLocationId: string(),
7608
+ entities: array(_enum(["segments", "strips"])).optional(),
7609
+ /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7610
+ * never allowed to starve live writers. */
7611
+ throttleMbps: number().min(1).max(1e3).optional()
7612
+ });
7613
+ var RelocateMediaInputSchema = object({
7614
+ deviceId: number().optional(),
7615
+ toLocationId: string(),
7616
+ throttleMbps: number().min(1).max(1e3).optional()
7617
+ });
7618
+ /**
7636
7619
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7637
7620
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7638
7621
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7682,6 +7665,13 @@ var StorageLocationSchema = object({
7682
7665
  nodeId: string().optional(),
7683
7666
  isDefault: boolean().default(false),
7684
7667
  isSystem: boolean().default(false),
7668
+ /** COMPUTED at read time by the orchestrator (statfs of the backing volume
7669
+ * for node-local locations it can reach) — never persisted, absent when the
7670
+ * volume is remote/unreachable. The single capacity truth every UI reads. */
7671
+ capacity: object({
7672
+ totalBytes: number(),
7673
+ availableBytes: number()
7674
+ }).nullable().optional(),
7685
7675
  createdAt: number(),
7686
7676
  updatedAt: number()
7687
7677
  });
@@ -7743,16 +7733,23 @@ var StorageLocationDeclarationSchema = object({
7743
7733
  * Which node root the seeded `<id>:default` instance is placed under on a
7744
7734
  * FRESH install:
7745
7735
  * - `'data'` (default) — the node's data dir (`CAMSTACK_DATA` / boot dir),
7746
- * the appData volume. Right for small/durable data (backups, logs, models).
7736
+ * the appData volume. Right for small/durable data (logs, models).
7747
7737
  * - `'media'` — the dedicated media volume (`CAMSTACK_MEDIA_ROOT`) when that
7748
7738
  * env is set, else falls back to the data root. Right for bulky, hot media
7749
7739
  * (recordings, event media) that should stay off the appData disk.
7740
+ * - `'backup'` — the dedicated backup volume (`CAMSTACK_BACKUP_ROOT`, default
7741
+ * `/backups` in the image) so archives live on their own mount rather than
7742
+ * filling the appData disk. Falls back to the data root when unset.
7750
7743
  *
7751
7744
  * Only affects the seeded default's `basePath`; operators can repoint any
7752
7745
  * location afterwards, and a `defaultsTo` slot inherits its parent's root
7753
7746
  * regardless of this field. Absent (the common case) is treated as `'data'`.
7754
7747
  */
7755
- defaultRoot: _enum(["data", "media"]).optional()
7748
+ defaultRoot: _enum([
7749
+ "data",
7750
+ "media",
7751
+ "backup"
7752
+ ]).optional()
7756
7753
  });
7757
7754
  var DecoderStatsSchema = object({
7758
7755
  inputFps: number(),
@@ -8790,7 +8787,8 @@ var NcTaxonomyEntrySchema = object({
8790
8787
  /** Macro/category parent for grouping ('car' → 'vehicle'); null for a top. */
8791
8788
  parentKind: string().nullable()
8792
8789
  });
8793
- object({
8790
+ /** The complete NC picker taxonomy — three grouped buckets. */
8791
+ var NcTaxonomySchema = object({
8794
8792
  videoClasses: array(NcTaxonomyEntrySchema),
8795
8793
  audioKinds: array(NcTaxonomyEntrySchema),
8796
8794
  labels: array(NcTaxonomyEntrySchema)
@@ -9450,6 +9448,7 @@ var AccessoryKind = {
9450
9448
  };
9451
9449
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
9452
9450
  DeviceFeature.BatteryOperated;
9451
+ new Set(["devices", "classes"]);
9453
9452
  /**
9454
9453
  * Shared geometry vocabulary for on-frame shape caps — privacy-mask,
9455
9454
  * motion-zones, and the detection zones/lines editor all speak this one
@@ -9766,12 +9765,60 @@ var NcRuleTargetSchema = object({
9766
9765
  * - `keyFrame` — the clean scene frame (no subject box).
9767
9766
  * - `none` — no attachment.
9768
9767
  */
9769
- var NcMediaPolicySchema = object({ attach: _enum([
9770
- "best",
9771
- "best-matching",
9772
- "keyFrame",
9773
- "none"
9774
- ]).default("best") });
9768
+ /**
9769
+ * WHAT THE PICTURE SHOWS — chosen explicitly, because the implicit ladders
9770
+ * conflate it with the selection strategy and betray the request: asking for
9771
+ * the clean scene frame on an object-event owner used to start at
9772
+ * `fullFrameBoxed`, i.e. the annotated frame (2026-07-30).
9773
+ * - `cropped` — the subject, tight. What "who is at the door" wants.
9774
+ * - `full` — the clean scene, no annotation. What "what is going on" wants.
9775
+ * - `boxed` — the scene WITH the detection boxes drawn, for verifying what
9776
+ * the pipeline actually saw.
9777
+ */
9778
+ var NcMediaFrameSchema = _enum([
9779
+ "cropped",
9780
+ "full",
9781
+ "boxed"
9782
+ ]);
9783
+ var NcMediaPolicySchema = object({
9784
+ attach: _enum([
9785
+ "best",
9786
+ "best-matching",
9787
+ "keyFrame",
9788
+ "none"
9789
+ ]).default("best"),
9790
+ /** What the still shows. Absent = `cropped` (the historical `best` shape). */
9791
+ frame: NcMediaFrameSchema.optional(),
9792
+ /** Crop the attached still to the rule's condition-zone bbox (padded) —
9793
+ * "show me the ZONE", not the whole scene or the subject crop. */
9794
+ zoneCrop: boolean().optional(),
9795
+ /**
9796
+ * Also attach a short GIF cut from the stream broker's clip ring around the
9797
+ * event — NOT from the recording, so the camera does not have to be
9798
+ * recording, and the window sits AROUND the moment instead of a segment
9799
+ * behind it. Fail-closed: a window the ring does not cover contributes no
9800
+ * gif, never a failed notification.
9801
+ */
9802
+ gif: boolean().optional(),
9803
+ /**
9804
+ * Also attach a short MP4 CLIP of the same cut. Same source and the same
9805
+ * fail-closed rule as `gif`. Prefer it where the backend takes video
9806
+ * (telegram, discord, zentik, webhook); backends that do not are handled by
9807
+ * the degrade engine, which drops the video and keeps the still.
9808
+ */
9809
+ clip: boolean().optional(),
9810
+ /** Seconds of footage BEFORE / AFTER the event instant. Defaults 3 / 7. */
9811
+ clipPreRollSec: number().int().min(0).max(30).optional(),
9812
+ clipPostRollSec: number().int().min(0).max(30).optional(),
9813
+ /**
9814
+ * Which stream profile the footage is cut from. Absent = the CHEAPEST
9815
+ * assigned profile: a notification is watched on a phone, so the 4K
9816
+ * rendition would burn CPU to produce a file the client downscales anyway.
9817
+ * A profile that is not assigned falls back to the cheapest, and the render
9818
+ * reports which one actually ran.
9819
+ */
9820
+ profile: CamProfileSchema.optional()
9821
+ });
9775
9822
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
9776
9823
  var NcThrottleSchema = object({
9777
9824
  cooldownSec: number().int().min(0).max(86400).default(60),
@@ -9785,7 +9832,19 @@ var NcRuleInputSchema = object({
9785
9832
  delivery: NcDeliverySchema,
9786
9833
  conditions: NcConditionsSchema.default({}),
9787
9834
  schedule: NcScheduleSchema.optional(),
9788
- targets: array(NcRuleTargetSchema).min(1),
9835
+ /** May be empty when `targetUsers` addresses at least one user — the
9836
+ * "at least one addressee" invariant is enforced by the provider, because
9837
+ * a cross-field refine here would break `NcRulePatchSchema.partial()`. */
9838
+ targets: array(NcRuleTargetSchema),
9839
+ /**
9840
+ * USERS this rule addresses in addition to `targets` (Phase 4). At fire
9841
+ * time each user fans out to the personal targets they own
9842
+ * (`config.ownerUserId`), filtered by that user's `allowedDevices` for the
9843
+ * firing camera — a user is never notified about a device they cannot open.
9844
+ * Per-user opt-outs (`disabledTargetIds`) still apply to the fanned-out
9845
+ * targets.
9846
+ */
9847
+ targetUsers: array(string()).optional(),
9789
9848
  media: NcMediaPolicySchema.default({ attach: "best" }),
9790
9849
  throttle: NcThrottleSchema.default({
9791
9850
  cooldownSec: 60,
@@ -9998,7 +10057,10 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
9998
10057
  }), object({ results: array(NcTestResultSchema) }), {
9999
10058
  kind: "mutation",
10000
10059
  auth: "admin"
10001
- }), method(object({}), object({ catalog: array(NcConditionDescriptorSchema) })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" });
10060
+ }), method(object({}), object({
10061
+ catalog: array(NcConditionDescriptorSchema),
10062
+ taxonomy: NcTaxonomySchema.optional()
10063
+ })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" });
10002
10064
  /**
10003
10065
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
10004
10066
  *
@@ -10745,6 +10807,48 @@ var streamBrokerCapability = {
10745
10807
  kind: "mutation",
10746
10808
  auth: "admin"
10747
10809
  }),
10810
+ /**
10811
+ * Render a short GIF or MP4 from the broker's PRE-BUFFER around an instant.
10812
+ *
10813
+ * The pre-buffer is the only source that already holds the seconds BEFORE
10814
+ * an event (it is what it exists for — "when an addon needs the last N
10815
+ * seconds of video for clip creation"). Cutting from recorded segments
10816
+ * cannot: only finalized segments are addressable and the one covering the
10817
+ * moment is still being written, so a segment-sourced clip is either
10818
+ * delayed or shows the approach instead of the event (operator,
10819
+ * 2026-07-30). It also means a camera does not have to be RECORDING to
10820
+ * produce a notification clip.
10821
+ *
10822
+ * Returns muxed BYTES, never packets: the frame plane never crosses a
10823
+ * process boundary at frame rate (D9/D18) — this is one compressed,
10824
+ * on-demand payload per notification.
10825
+ *
10826
+ * Waits up to `postRollSec` for the tail to accumulate, so the window is
10827
+ * CENTRED on `aroundMs`.
10828
+ */
10829
+ renderPreBufferClip: method(object({
10830
+ deviceId: number(),
10831
+ /** Absent = the LOWEST assigned profile — a notification attachment is
10832
+ * watched on a phone, and the cheap rendition is the right default. */
10833
+ profile: CamProfileSchema.optional(),
10834
+ aroundMs: number(),
10835
+ preRollSec: number().min(0).max(20).default(3),
10836
+ postRollSec: number().min(0).max(20).default(5),
10837
+ format: _enum(["gif", "mp4"]).default("gif"),
10838
+ maxWidth: number().int().min(120).max(1920).default(480),
10839
+ /** GIF only — MP4 keeps the source cadence. */
10840
+ fps: number().int().min(1).max(15).default(5)
10841
+ }), object({
10842
+ base64: string(),
10843
+ mime: string(),
10844
+ bytes: number().int(),
10845
+ /** The profile actually rendered (what the default resolved to). */
10846
+ profile: CamProfileSchema,
10847
+ durationMs: number()
10848
+ }), {
10849
+ kind: "mutation",
10850
+ auth: "admin"
10851
+ }),
10748
10852
  listAllCameraStreams: method(_void(), array(CameraStreamSchema).readonly()),
10749
10853
  listAllProfileSlots: method(_void(), array(ProfileSlotSchema).readonly()),
10750
10854
  getBrokerStats: method(object({ brokerId: string() }), BrokerStatsSchema),
@@ -15691,11 +15795,53 @@ var LocationStatSchema = object({
15691
15795
  fileCount: number(),
15692
15796
  present: boolean()
15693
15797
  });
15798
+ /**
15799
+ * A backup schedule — the N:M "entry" that binds one cron cadence to a
15800
+ * SET of destination locations. Supersedes the per-location cron on
15801
+ * `BackupDestinationPolicy`: an operator creates a schedule, picks the
15802
+ * `backups` locations it should write to, and the orchestrator fans a
15803
+ * single archive out to all of them when the cron fires.
15804
+ *
15805
+ * `retentionCount` is per-schedule (D-decision 2026-07-28): every
15806
+ * location targeted by this schedule keeps this many archives from
15807
+ * this schedule's runs.
15808
+ *
15809
+ * `dataSources` optionally narrows which top-level state locations
15810
+ * (db, addons, tls, …) are archived; omitted = the orchestrator's
15811
+ * default full set.
15812
+ */
15813
+ var BackupScheduleSchema = object({
15814
+ /** Stable id. Generated by the orchestrator on first upsert if absent. */
15815
+ id: string(),
15816
+ /** Operator-facing display name. */
15817
+ label: string(),
15818
+ /** 5-field POSIX cron. Empty = disabled cadence (kept for editing). */
15819
+ cron: string(),
15820
+ /** Master on/off toggle for the whole schedule. */
15821
+ enabled: boolean(),
15822
+ /** `backups`-location ids this schedule writes to (fan-out set). */
15823
+ locationIds: array(string()).readonly(),
15824
+ /** Archives kept per targeted location for this schedule. */
15825
+ retentionCount: number().int().min(1).max(1e3),
15826
+ /** Optional subset of source locations to include; omitted = all. */
15827
+ dataSources: array(string()).readonly().optional(),
15828
+ /** ms-epoch of last successful run. */
15829
+ lastRunAt: number().optional(),
15830
+ /** ms-epoch of next computed firing (read-only, filled on list). */
15831
+ nextRunAt: number().optional()
15832
+ });
15694
15833
  method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }), method(object({
15695
15834
  /** Subset of registered `backup-destination` addon ids to write to. */
15696
15835
  destinations: array(string()).optional(),
15697
15836
  locations: array(string()).optional(),
15698
- label: string().optional()
15837
+ label: string().optional(),
15838
+ /**
15839
+ * Per-run retention override applied to every targeted
15840
+ * destination. Used by schedule-driven runs (per-entry
15841
+ * retention). Omitted = each destination's own policy
15842
+ * retention (manual runs).
15843
+ */
15844
+ retentionCount: number().int().min(1).max(1e3).optional()
15699
15845
  }).optional(), array(BackupEntrySchema).readonly(), {
15700
15846
  kind: "mutation",
15701
15847
  auth: "admin"
@@ -15744,7 +15890,21 @@ method(_void(), array(BackupDestinationInfoSchema).readonly(), { auth: "admin" }
15744
15890
  ok: boolean(),
15745
15891
  error: string().optional(),
15746
15892
  nextRuns: array(number()).readonly()
15747
- }));
15893
+ })), method(_void(), array(BackupScheduleSchema).readonly(), { auth: "admin" }), method(object({
15894
+ id: string().optional(),
15895
+ label: string(),
15896
+ cron: string(),
15897
+ enabled: boolean(),
15898
+ locationIds: array(string()).readonly(),
15899
+ retentionCount: number().int().min(1).max(1e3),
15900
+ dataSources: array(string()).readonly().optional()
15901
+ }), BackupScheduleSchema, {
15902
+ kind: "mutation",
15903
+ auth: "admin"
15904
+ }), method(object({ id: string() }), _void(), {
15905
+ kind: "mutation",
15906
+ auth: "admin"
15907
+ });
15748
15908
  /**
15749
15909
  * `broker` — unified pub/sub broker registry, system-scoped collection.
15750
15910
  *
@@ -17863,6 +18023,36 @@ var TargetKindSchema = object({
17863
18023
  icon: string(),
17864
18024
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
17865
18025
  addonId: string(),
18026
+ /**
18027
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
18028
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
18029
+ * when the addon bundles no icon for that kind — the client then falls back
18030
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
18031
+ *
18032
+ * Root-relative on purpose: it resolves against whatever origin serves a web
18033
+ * client, and a native client joins it onto its own hub base.
18034
+ *
18035
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
18036
+ * field that survived only because the runtime cap-router forwards provider
18037
+ * output verbatim — so every consumer had to re-declare it by hand to stop
18038
+ * its own Zod parse from stripping it, and the whole arrangement would have
18039
+ * broken silently the moment output validation was tightened anywhere.
18040
+ */
18041
+ iconUrl: string().optional(),
18042
+ /**
18043
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
18044
+ *
18045
+ * The server knows this and therefore says it, because the client cannot
18046
+ * safely guess: a React-Native client renders SVG and raster through two
18047
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
18048
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
18049
+ * placeholder glyph for every vector icon while the web build looked fine.
18050
+ *
18051
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
18052
+ * not been updated — a client that cannot determine the type should prefer
18053
+ * its raster path, which is the safe default for an unknown image.
18054
+ */
18055
+ iconMediaType: string().optional(),
17866
18056
  configSchema: ConfigSchemaPassthrough,
17867
18057
  supportsDiscovery: boolean(),
17868
18058
  caps: TargetKindCapsSchema
@@ -18594,6 +18784,15 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18594
18784
  }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
18595
18785
  kind: "mutation",
18596
18786
  auth: "admin"
18787
+ }), method(RelocateMediaInputSchema, object({ jobId: string() }), {
18788
+ kind: "mutation",
18789
+ auth: "admin"
18790
+ }), method(object({}), array(RelocateJobSchema).readonly(), {
18791
+ kind: "query",
18792
+ auth: "admin"
18793
+ }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
18794
+ kind: "mutation",
18795
+ auth: "admin"
18597
18796
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
18598
18797
  kind: "query",
18599
18798
  auth: "admin"
@@ -21168,6 +21367,17 @@ var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
21168
21367
  */
21169
21368
  priority: number()
21170
21369
  })).readonly() });
21370
+ /**
21371
+ * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
21372
+ * AUTO (resolved from the candidate ranking at send time); `resolved` reports
21373
+ * what AUTO currently picks, so the UI can show the effective value either way.
21374
+ */
21375
+ var NotificationEndpointSchema = object({
21376
+ /** The operator's explicit choice, or null for AUTO. */
21377
+ baseUrl: string().nullable(),
21378
+ /** What the ranking currently resolves to (null when nothing is reachable). */
21379
+ resolved: string().nullable()
21380
+ });
21171
21381
  var AllowedAddressesSchema = object({
21172
21382
  /**
21173
21383
  * Allowlist of interface addresses operators have explicitly opted
@@ -21190,7 +21400,7 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
21190
21400
  * to avoid mixed-content blocks in the browser. The public
21191
21401
  * tunnel always emits `https://` regardless. */
21192
21402
  scheme: _enum(["http", "https"]).optional()
21193
- }), GetConnectionEndpointsResultSchema), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
21403
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
21194
21404
  /**
21195
21405
  * mesh-network — collection cap for mesh-VPN providers.
21196
21406
  *
@@ -21989,7 +22199,12 @@ var RecordingDeviceUsageSchema = object({
21989
22199
  var RecordingLocationUsageSchema = object({
21990
22200
  /** StorageLocation id; null for the legacy/degraded single-root fallback. */
21991
22201
  locationId: string().nullable(),
21992
- /** Bytes of recordings stored on this location. */
22202
+ /** Every location id sharing this row's PHYSICAL volume (aliases). One row
22203
+ * is emitted per physical disk (2026-07-29): two locations on one root
22204
+ * previously rendered as two identical "disks" with a nonsensical used
22205
+ * split — hydrate attribution across aliases is arbitrary by nature. */
22206
+ locationIds: array(string()).optional(),
22207
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
21993
22208
  usedBytes: number(),
21994
22209
  /** Free bytes on the location's volume; null when capacity is unknown (remote). */
21995
22210
  availableBytes: number().nullable(),
@@ -22145,6 +22360,77 @@ var recordingCapability = {
22145
22360
  listOpsLog: method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22146
22361
  kind: "query",
22147
22362
  auth: "admin"
22363
+ }),
22364
+ /**
22365
+ * Move footage (segments and/or strips) from one recordings location to
22366
+ * another — the drain workflow's mover (entity-routing spec Phase 4).
22367
+ * Throttled copy → size-verify → delete source → index refresh; resumable
22368
+ * by construction (copy-if-absent). Single-flight: one job at a time.
22369
+ */
22370
+ /**
22371
+ * Render a short GIF from recorded footage around `aroundMs` (low profile,
22372
+ * palette-free ffmpeg gif). Synchronous — the window is a few seconds of
22373
+ * `low`, rendering in ~1-2s. Built for notification attachments: the
22374
+ * caller gets bytes, nothing persists. Fails when no footage covers the
22375
+ * window (camera not recording → caller skips the attachment).
22376
+ */
22377
+ renderGif: method(object({
22378
+ deviceId: number(),
22379
+ aroundMs: number(),
22380
+ preRollSec: number().min(0).max(30).default(2),
22381
+ postRollSec: number().min(0).max(30).default(5),
22382
+ maxWidth: number().int().min(120).max(1280).default(480),
22383
+ fps: number().int().min(1).max(15).default(5)
22384
+ }), object({
22385
+ gifBase64: string(),
22386
+ fromMs: number(),
22387
+ toMs: number()
22388
+ }), {
22389
+ kind: "mutation",
22390
+ auth: "admin"
22391
+ }),
22392
+ /**
22393
+ * Render a short MP4 clip from recorded footage around `aroundMs` — the
22394
+ * "send a clip, not a gif" notification attachment. Same window, source
22395
+ * and fail-closed contract as {@link renderGif}: no footage covering the
22396
+ * window ⇒ it throws and the caller ships no attachment. Re-encoded (never
22397
+ * stream-copied) so a concat across segments with different encoder
22398
+ * parameters still plays. Silent by contract.
22399
+ *
22400
+ * The returned window is SEGMENT-GRANULAR: the source playlist is built
22401
+ * from whole segments, so the clip starts at or before `fromMs` and ends
22402
+ * at or after `toMs` — overshoot bounded by one segment per side (an 8 s
22403
+ * ask over 4 s segments measured 12 s). `fromMs`/`toMs` report the window
22404
+ * ASKED for, not the encoded duration.
22405
+ */
22406
+ renderClip: method(object({
22407
+ deviceId: number(),
22408
+ aroundMs: number(),
22409
+ preRollSec: number().min(0).max(30).default(3),
22410
+ postRollSec: number().min(0).max(30).default(7),
22411
+ maxWidth: number().int().min(160).max(1920).default(640)
22412
+ }), object({
22413
+ clipBase64: string(),
22414
+ mime: string(),
22415
+ fromMs: number(),
22416
+ toMs: number(),
22417
+ bytes: number().int()
22418
+ }), {
22419
+ kind: "mutation",
22420
+ auth: "admin"
22421
+ }),
22422
+ relocateFootage: method(RelocateFootageInputSchema, object({ jobId: string() }), {
22423
+ kind: "mutation",
22424
+ auth: "admin"
22425
+ }),
22426
+ /** Every known relocate job (in-RAM, newest-first). */
22427
+ getRelocateStatus: method(object({}), array(RelocateJobSchema).readonly(), {
22428
+ kind: "query",
22429
+ auth: "admin"
22430
+ }),
22431
+ cancelRelocate: method(object({ jobId: string() }), object({ cancelled: boolean() }), {
22432
+ kind: "mutation",
22433
+ auth: "admin"
22148
22434
  })
22149
22435
  }
22150
22436
  };
@@ -23077,6 +23363,12 @@ Object.freeze({
23077
23363
  addonId: null,
23078
23364
  access: "delete"
23079
23365
  },
23366
+ "backup.deleteSchedule": {
23367
+ capName: "backup",
23368
+ capScope: "system",
23369
+ addonId: null,
23370
+ access: "delete"
23371
+ },
23080
23372
  "backup.getEntries": {
23081
23373
  capName: "backup",
23082
23374
  capScope: "system",
@@ -23107,6 +23399,12 @@ Object.freeze({
23107
23399
  addonId: null,
23108
23400
  access: "view"
23109
23401
  },
23402
+ "backup.listSchedules": {
23403
+ capName: "backup",
23404
+ capScope: "system",
23405
+ addonId: null,
23406
+ access: "view"
23407
+ },
23110
23408
  "backup.previewSchedule": {
23111
23409
  capName: "backup",
23112
23410
  capScope: "system",
@@ -23131,6 +23429,12 @@ Object.freeze({
23131
23429
  addonId: null,
23132
23430
  access: "create"
23133
23431
  },
23432
+ "backup.upsertSchedule": {
23433
+ capName: "backup",
23434
+ capScope: "system",
23435
+ addonId: null,
23436
+ access: "create"
23437
+ },
23134
23438
  "battery.wakeForStream": {
23135
23439
  capName: "battery",
23136
23440
  capScope: "device",
@@ -24631,6 +24935,12 @@ Object.freeze({
24631
24935
  addonId: null,
24632
24936
  access: "view"
24633
24937
  },
24938
+ "localNetwork.getNotificationEndpoint": {
24939
+ capName: "local-network",
24940
+ capScope: "system",
24941
+ addonId: null,
24942
+ access: "view"
24943
+ },
24634
24944
  "localNetwork.getPreferred": {
24635
24945
  capName: "local-network",
24636
24946
  capScope: "system",
@@ -24655,6 +24965,12 @@ Object.freeze({
24655
24965
  addonId: null,
24656
24966
  access: "create"
24657
24967
  },
24968
+ "localNetwork.setNotificationEndpoint": {
24969
+ capName: "local-network",
24970
+ capScope: "system",
24971
+ addonId: null,
24972
+ access: "create"
24973
+ },
24658
24974
  "lockControl.lock": {
24659
24975
  capName: "lock-control",
24660
24976
  capScope: "device",
@@ -25297,6 +25613,12 @@ Object.freeze({
25297
25613
  addonId: null,
25298
25614
  access: "create"
25299
25615
  },
25616
+ "pipelineAnalytics.cancelMediaRelocate": {
25617
+ capName: "pipeline-analytics",
25618
+ capScope: "device",
25619
+ addonId: null,
25620
+ access: "create"
25621
+ },
25300
25622
  "pipelineAnalytics.clearTracks": {
25301
25623
  capName: "pipeline-analytics",
25302
25624
  capScope: "device",
@@ -25351,6 +25673,12 @@ Object.freeze({
25351
25673
  addonId: null,
25352
25674
  access: "view"
25353
25675
  },
25676
+ "pipelineAnalytics.getMediaRelocateStatus": {
25677
+ capName: "pipeline-analytics",
25678
+ capScope: "device",
25679
+ addonId: null,
25680
+ access: "view"
25681
+ },
25354
25682
  "pipelineAnalytics.getMotionEvents": {
25355
25683
  capName: "pipeline-analytics",
25356
25684
  capScope: "device",
@@ -25423,6 +25751,12 @@ Object.freeze({
25423
25751
  addonId: null,
25424
25752
  access: "create"
25425
25753
  },
25754
+ "pipelineAnalytics.relocateMedia": {
25755
+ capName: "pipeline-analytics",
25756
+ capScope: "device",
25757
+ addonId: null,
25758
+ access: "create"
25759
+ },
25426
25760
  "pipelineAnalytics.searchObjectEvents": {
25427
25761
  capName: "pipeline-analytics",
25428
25762
  capScope: "device",
@@ -26185,6 +26519,12 @@ Object.freeze({
26185
26519
  addonId: null,
26186
26520
  access: "create"
26187
26521
  },
26522
+ "recording.cancelRelocate": {
26523
+ capName: "recording",
26524
+ capScope: "system",
26525
+ addonId: null,
26526
+ access: "create"
26527
+ },
26188
26528
  "recording.deleteFootprint": {
26189
26529
  capName: "recording",
26190
26530
  capScope: "system",
@@ -26215,6 +26555,12 @@ Object.freeze({
26215
26555
  addonId: null,
26216
26556
  access: "view"
26217
26557
  },
26558
+ "recording.getRelocateStatus": {
26559
+ capName: "recording",
26560
+ capScope: "system",
26561
+ addonId: null,
26562
+ access: "view"
26563
+ },
26218
26564
  "recording.getStorageUsage": {
26219
26565
  capName: "recording",
26220
26566
  capScope: "system",
@@ -26245,6 +26591,24 @@ Object.freeze({
26245
26591
  addonId: null,
26246
26592
  access: "view"
26247
26593
  },
26594
+ "recording.relocateFootage": {
26595
+ capName: "recording",
26596
+ capScope: "system",
26597
+ addonId: null,
26598
+ access: "create"
26599
+ },
26600
+ "recording.renderClip": {
26601
+ capName: "recording",
26602
+ capScope: "system",
26603
+ addonId: null,
26604
+ access: "create"
26605
+ },
26606
+ "recording.renderGif": {
26607
+ capName: "recording",
26608
+ capScope: "system",
26609
+ addonId: null,
26610
+ access: "create"
26611
+ },
26248
26612
  "recording.rescanStorage": {
26249
26613
  capName: "recording",
26250
26614
  capScope: "system",
@@ -26839,6 +27203,12 @@ Object.freeze({
26839
27203
  addonId: null,
26840
27204
  access: "create"
26841
27205
  },
27206
+ "streamBroker.renderPreBufferClip": {
27207
+ capName: "stream-broker",
27208
+ capScope: "system",
27209
+ addonId: null,
27210
+ access: "create"
27211
+ },
26842
27212
  "streamBroker.restartProfile": {
26843
27213
  capName: "stream-broker",
26844
27214
  capScope: "system",
@@ -27771,4 +28141,4 @@ function enumerateInferenceDevices(hw) {
27771
28141
  return out;
27772
28142
  }
27773
28143
  //#endregion
27774
- export { selectAssignedProfileSlots as $, motionDetectionCapability as A, errMsg as B, detectionPipelineCapability as C, mapAudioLabelToMacro as D, hfModelUrl as E, runtimeDevices as F, createEvent as G, CAM_PROFILE_ORDER as H, storageEvictableCapability as I, makeProfileBrokerId as J, hydrateSchema as K, streamBrokerCapability as L, pipelineRunnerCapability as M, recordingCapability as N, maskUrlCredentials as O, recordingExportCapability as P, parseProfileBrokerId as Q, supportedRuntimes as R, defineCustomActions as S, evaluateZoneRules as T, DeviceFeature as U, BaseAddon as V, DeviceType as W, nodePin as X, makeSourceBrokerId as Y, parseJsonUnknown as Z, audioAnalysisCapability as _, COCO_TO_MACRO as a, lazy as at, customAction as b, EVENT_PAD_MS as c, object as ct, HF_BASE_URL as d, union as dt, sleep as et, OpsLogEntrySchema as f, EventCategory as ft, addonWidgetsSourceCapability as g, YAMNET_TO_MACRO as h, COCO_80_LABELS as i, discriminatedUnion as it, pipelineExecutorCapability as j, migrateConfigToBands as k, EncodeProfileSchema as l, record as lt, RingBuffer as m, AUDIO_BACKEND_CHOICES as n, array as nt, DEFAULT_AUDIO_ANALYZER_CONFIG as o, literal as ot, RecordingConfigSchema as p, isEvent as q, AUDIO_MACRO_LABELS as r, boolean as rt, DEVICE_BACKEND_TO_FORMAT as s, number as st, APPLE_SA_TO_MACRO as t, _enum as tt, ExportRecordSchema as u, string as ut, audioAnalyzerCapability as v, enumerateInferenceDevices as w, defaultDeviceFor as x, cameraStreamsCapability as y, webrtcSessionCapability as z };
28144
+ export { selectAssignedProfileSlots as $, motionDetectionCapability as A, errMsg as B, deriveRecordingMode as C, hfModelUrl as D, evaluateZoneRules as E, runtimeDevices as F, createEvent as G, CAM_PROFILE_ORDER as H, storageEvictableCapability as I, makeProfileBrokerId as J, hydrateSchema as K, streamBrokerCapability as L, pipelineRunnerCapability as M, recordingCapability as N, mapAudioLabelToMacro as O, recordingExportCapability as P, parseProfileBrokerId as Q, supportedRuntimes as R, defineCustomActions as S, enumerateInferenceDevices as T, DeviceFeature as U, BaseAddon as V, DeviceType as W, nodePin as X, makeSourceBrokerId as Y, parseJsonUnknown as Z, audioAnalysisCapability as _, COCO_TO_MACRO as a, lazy as at, customAction as b, EVENT_PAD_MS as c, object as ct, HF_BASE_URL as d, union as dt, sleep as et, OpsLogEntrySchema as f, EventCategory as ft, addonWidgetsSourceCapability as g, YAMNET_TO_MACRO as h, COCO_80_LABELS as i, discriminatedUnion as it, pipelineExecutorCapability as j, maskUrlCredentials as k, EncodeProfileSchema as l, record as lt, RingBuffer as m, AUDIO_BACKEND_CHOICES as n, array as nt, DEFAULT_AUDIO_ANALYZER_CONFIG as o, literal as ot, RecordingConfigSchema as p, isEvent as q, AUDIO_MACRO_LABELS as r, boolean as rt, DEVICE_BACKEND_TO_FORMAT as s, number as st, APPLE_SA_TO_MACRO as t, _enum as tt, ExportRecordSchema as u, string as ut, audioAnalyzerCapability as v, detectionPipelineCapability as w, defaultDeviceFor as x, cameraStreamsCapability as y, webrtcSessionCapability as z };