@camstack/system 1.2.254 → 1.2.255

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 (88) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/autotrack/autotrack-cameras.d.ts +76 -0
  8. package/dist/builtins/autotrack/autotrack-config.d.ts +89 -0
  9. package/dist/builtins/autotrack/autotrack-decision.d.ts +99 -0
  10. package/dist/builtins/autotrack/autotrack-loop.d.ts +75 -0
  11. package/dist/builtins/autotrack/autotrack.addon.d.ts +71 -0
  12. package/dist/builtins/autotrack/frame-error.d.ts +50 -0
  13. package/dist/builtins/autotrack/frame-measurements.d.ts +97 -0
  14. package/dist/builtins/autotrack/index.d.ts +2 -0
  15. package/dist/builtins/autotrack/index.js +981 -0
  16. package/dist/builtins/autotrack/index.mjs +975 -0
  17. package/dist/builtins/autotrack/ptz-mirror.d.ts +25 -0
  18. package/dist/builtins/autotrack/target-selection.d.ts +60 -0
  19. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  20. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  21. package/dist/builtins/camera-grid/addon.d.ts +118 -0
  22. package/dist/builtins/camera-grid/flv-join.d.ts +66 -0
  23. package/dist/builtins/camera-grid/grid-camera-declarations.d.ts +59 -0
  24. package/dist/builtins/camera-grid/grid-camera-device.d.ts +32 -0
  25. package/dist/builtins/camera-grid/grid-child.d.ts +9 -0
  26. package/dist/builtins/camera-grid/grid-device-settings.d.ts +11 -0
  27. package/dist/builtins/camera-grid/grid-filter-graph.d.ts +53 -0
  28. package/dist/builtins/camera-grid/grid-frame-sample.d.ts +27 -0
  29. package/dist/builtins/camera-grid/grid-instances.d.ts +49 -0
  30. package/dist/builtins/camera-grid/grid-last-frame-store.d.ts +19 -0
  31. package/dist/builtins/camera-grid/grid-layout-native-provider.d.ts +8 -0
  32. package/dist/builtins/camera-grid/grid-plan.d.ts +22 -0
  33. package/dist/builtins/camera-grid/grid-profiles.d.ts +47 -0
  34. package/dist/builtins/camera-grid/grid-row-normalization.d.ts +10 -0
  35. package/dist/builtins/camera-grid/grid-sentry-sources.d.ts +52 -0
  36. package/dist/builtins/camera-grid/grid-snapshot.d.ts +62 -0
  37. package/dist/builtins/camera-grid/grid-stream-descriptor.d.ts +24 -0
  38. package/dist/builtins/camera-grid/grid-stream-invocation.d.ts +29 -0
  39. package/dist/builtins/camera-grid/grid-stream-relay.d.ts +36 -0
  40. package/dist/builtins/camera-grid/grid-stream-session.d.ts +113 -0
  41. package/dist/builtins/camera-grid/grid-wire-format.d.ts +14 -0
  42. package/dist/builtins/camera-grid/index.d.ts +44 -0
  43. package/dist/builtins/camera-grid/index.js +2309 -0
  44. package/dist/builtins/camera-grid/index.mjs +2276 -0
  45. package/dist/builtins/camera-grid/silence-analysis.d.ts +13 -0
  46. package/dist/builtins/console-logging/index.js +1 -1
  47. package/dist/builtins/console-logging/index.mjs +1 -1
  48. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  49. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  50. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  51. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  52. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  53. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  54. package/dist/builtins/hub-forwarder/index.js +1 -1
  55. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  56. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  57. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  58. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  59. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  60. package/dist/builtins/local-network/local-network.addon.js +1 -1
  61. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  62. package/dist/builtins/loki-logging/index.js +1 -1
  63. package/dist/builtins/loki-logging/index.mjs +1 -1
  64. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  65. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  66. package/dist/builtins/platform-probe/index.js +1 -1
  67. package/dist/builtins/platform-probe/index.mjs +1 -1
  68. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  69. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  70. package/dist/builtins/snapshot/index.js +1 -1
  71. package/dist/builtins/snapshot/index.mjs +1 -1
  72. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  73. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  74. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  75. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  76. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  77. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  78. package/dist/builtins/system-config/system-config.addon.js +1 -1
  79. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  80. package/dist/builtins/winston-logging/index.js +1 -1
  81. package/dist/builtins/winston-logging/index.mjs +1 -1
  82. package/dist/{dist-CnQsUe15.js → dist-BJj6Akye.js} +1319 -0
  83. package/dist/{dist-BhTy3CUQ.mjs → dist-Ch93tyHB.mjs} +1260 -1
  84. package/dist/index.js +1 -1
  85. package/dist/index.mjs +1 -1
  86. package/dist/{retired-settings-keys-DDU-VFTU.js → retired-settings-keys-BRCn6-mt.js} +1 -1
  87. package/dist/{retired-settings-keys-CMhNYMD9.mjs → retired-settings-keys-uCaSKuwd.mjs} +1 -1
  88. package/package.json +32 -1
@@ -1,4 +1,5 @@
1
1
  let zod = require("zod");
2
+ require("node:crypto");
2
3
  //#region ../types/dist/event-category-BVDXG4tB.mjs
3
4
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
5
  EventCategory["SystemBoot"] = "system.boot";
@@ -1025,6 +1026,22 @@ function typeOf(field) {
1025
1026
  }
1026
1027
  }
1027
1028
  /**
1029
+ * Narrow a SystemEvent to a typed event by checking its category.
1030
+ * Returns `true` (and narrows the type) if the category matches.
1031
+ *
1032
+ * @example
1033
+ * ```typescript
1034
+ * eventBus.subscribe({ category: 'addon.started' }, (event) => {
1035
+ * if (isEvent(event, 'addon.started')) {
1036
+ * event.data.addonId // ✓ typed as string
1037
+ * }
1038
+ * })
1039
+ * ```
1040
+ */
1041
+ function isEvent(event, category) {
1042
+ return event.category === category;
1043
+ }
1044
+ /**
1028
1045
  * Create a typed event with less boilerplate.
1029
1046
  *
1030
1047
  * @example
@@ -3387,6 +3404,360 @@ function sleep(ms) {
3387
3404
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
3388
3405
  }
3389
3406
  //#endregion
3407
+ //#region ../types/dist/canonical-hash-biq7T_dN.mjs
3408
+ var AUDIO_ENCODER_BY_CODEC = {
3409
+ opus: "libopus",
3410
+ aac: "aac",
3411
+ pcmu: "pcm_mulaw",
3412
+ pcma: "pcm_alaw"
3413
+ };
3414
+ /**
3415
+ * Camera-microphone audio, per codec. Lives HERE rather than in
3416
+ * `encode-defaults.ts` only to avoid an import cycle (`encode-defaults` depends
3417
+ * on these types); it is re-exported from there, which is where to read it.
3418
+ *
3419
+ * Every source in this repo is a mono camera mic. The former broker preset
3420
+ * encoded Opus at `channels: 2`, spending bitrate duplicating one channel —
3421
+ * that is the value this consolidation changed.
3422
+ */
3423
+ var AUDIO_PRESETS = {
3424
+ aac: {
3425
+ kind: "encode",
3426
+ codec: "aac",
3427
+ bitrateKbps: 128,
3428
+ sampleRateHz: 48e3,
3429
+ channels: 1
3430
+ },
3431
+ opus: {
3432
+ kind: "encode",
3433
+ codec: "opus",
3434
+ bitrateKbps: 64,
3435
+ sampleRateHz: 48e3,
3436
+ channels: 1
3437
+ },
3438
+ pcmu: {
3439
+ kind: "encode",
3440
+ codec: "pcmu",
3441
+ sampleRateHz: 8e3,
3442
+ channels: 1
3443
+ }
3444
+ };
3445
+ /** `-hide_banner -loglevel <level>` — every ffmpeg site opens with this. */
3446
+ function logBannerArgs(level) {
3447
+ return [
3448
+ "-hide_banner",
3449
+ "-loglevel",
3450
+ level
3451
+ ];
3452
+ }
3453
+ /** `true` when the resolved value means "decode in software" (⇒ no `-hwaccel`). */
3454
+ function isSoftwareDecode(decodeHwAccel) {
3455
+ return !decodeHwAccel || decodeHwAccel === "none" || decodeHwAccel === "copy";
3456
+ }
3457
+ /**
3458
+ * Every INPUT option, in order, terminated by `-i <url>`. Nothing may be
3459
+ * appended to this list by a caller — that is the whole point of the function.
3460
+ */
3461
+ function buildInputArgs(input, decodeHwAccel) {
3462
+ const args = [];
3463
+ if (!isSoftwareDecode(decodeHwAccel)) args.push("-hwaccel", String(decodeHwAccel));
3464
+ if (input.extraArgs?.length) args.push(...input.extraArgs);
3465
+ if (input.analyzeDurationUs !== void 0) args.push("-analyzeduration", String(input.analyzeDurationUs));
3466
+ if (input.probeSizeBytes !== void 0) args.push("-probesize", String(input.probeSizeBytes));
3467
+ if (input.lowDelay === true) args.push("-flags", "low_delay");
3468
+ if (input.fflags?.length) for (const flag of input.fflags) args.push("-fflags", flag);
3469
+ if (input.rtspTransport) args.push("-rtsp_transport", input.rtspTransport);
3470
+ args.push("-i", input.url);
3471
+ return args;
3472
+ }
3473
+ /**
3474
+ * Derive the output `-map` selectors. The three cases, in the order they are
3475
+ * decided:
3476
+ *
3477
+ * 1. **A filter graph** — the picture is the graph's declared video pad, and
3478
+ * the audio is its audio pad when it has one, otherwise input 0's mic. A
3479
+ * graph's output is not addressable any other way.
3480
+ * 2. **N inputs, no graph** — map input 0 EXPLICITLY. ffmpeg's own selection
3481
+ * picks the "best" stream across ALL inputs, so the egress would silently
3482
+ * become whichever camera happens to have the largest frame.
3483
+ * 3. **One input, no graph** — no `-map`. Anything else would change the argv
3484
+ * of every call site that exists today for no gain.
3485
+ */
3486
+ function resolveStreamMaps(selection) {
3487
+ const graph = selection.filterGraph;
3488
+ if (graph) return {
3489
+ video: `[${graph.videoOutLabel}]`,
3490
+ audio: graph.audioOutLabel ? `[${graph.audioOutLabel}]` : "0:a:0?"
3491
+ };
3492
+ if (selection.extraInputs?.length) return {
3493
+ video: "0:v:0",
3494
+ audio: "0:a:0?"
3495
+ };
3496
+ return {
3497
+ video: null,
3498
+ audio: null
3499
+ };
3500
+ }
3501
+ /** The `-vf` filter args, or `[]` when a consumer `-vf` already claims the slot. */
3502
+ function buildVideoFilterArgs(scale, outputArgs, filterGraph) {
3503
+ if (filterGraph) {
3504
+ if (scale) throw new Error("FfmpegInvocation: video.scale cannot be combined with a filter graph — scale inside the graph instead (the -vf would feed an output the graph already feeds)");
3505
+ if (outputArgs.some((a) => a === "-vf")) throw new Error("FfmpegInvocation: outputArgs carries a -vf while a filter graph feeds the same output — fold the filter into the graph");
3506
+ return [];
3507
+ }
3508
+ if (!scale) return [];
3509
+ if (outputArgs.some((a) => a === "-vf")) return [];
3510
+ if (scale.mode === "exact") return ["-vf", `scale=${scale.width}:${scale.height}`];
3511
+ return ["-vf", `scale='min(${scale.width},iw)':'min(${scale.height},ih)':force_original_aspect_ratio=decrease:force_divisible_by=2`];
3512
+ }
3513
+ /** Rate-control args for an encode plan. */
3514
+ function buildRateControlArgs(video) {
3515
+ const kbps = video.bitrateKbps;
3516
+ if (kbps === void 0) return [];
3517
+ const rc = video.rateControl ?? {
3518
+ kind: "cap",
3519
+ vbvSeconds: 2
3520
+ };
3521
+ const bufsize = Math.max(1, Math.round(kbps * rc.vbvSeconds));
3522
+ return [
3523
+ ...rc.kind === "cbr" ? ["-b:v", `${kbps}k`] : [],
3524
+ "-maxrate",
3525
+ `${kbps}k`,
3526
+ "-bufsize",
3527
+ `${bufsize}k`
3528
+ ];
3529
+ }
3530
+ /** The whole video block (`-vf` … `-c:v` … knobs), after `-i`. */
3531
+ function buildVideoArgs(video, outputArgs, filterGraph) {
3532
+ if (video.kind === "copy") {
3533
+ if (filterGraph) throw new Error("FfmpegInvocation: a filter graph produces a NEW picture, so video.kind must be `encode`");
3534
+ return [
3535
+ "-c:v",
3536
+ "copy",
3537
+ ...video.bitstreamFilter ? ["-bsf:v", video.bitstreamFilter] : []
3538
+ ];
3539
+ }
3540
+ const args = [
3541
+ ...buildVideoFilterArgs(video.scale, outputArgs, filterGraph),
3542
+ "-c:v",
3543
+ video.encoder
3544
+ ];
3545
+ if (video.preset !== void 0) args.push("-preset", video.preset);
3546
+ if (video.tune !== void 0) args.push("-tune", video.tune);
3547
+ if (video.singleSlicePerFrame === true && video.encoder === "libx264") args.push("-x264-params", "sliced-threads=0");
3548
+ if (video.profile !== void 0) args.push("-profile:v", video.profile);
3549
+ if (video.level !== void 0) args.push("-level", video.level);
3550
+ if (video.pixelFormat !== void 0) args.push("-pix_fmt", video.pixelFormat);
3551
+ if (video.fps !== void 0) args.push("-r", String(video.fps));
3552
+ if (video.gopFrames !== void 0) args.push("-g", String(video.gopFrames));
3553
+ if (video.forceKeyFramesSeconds !== void 0) args.push("-force_key_frames", `expr:gte(t,n_forced*${video.forceKeyFramesSeconds})`);
3554
+ if (video.bf !== void 0) args.push("-bf", String(video.bf));
3555
+ args.push(...buildRateControlArgs(video));
3556
+ if (video.bitstreamFilter !== void 0) args.push("-bsf:v", video.bitstreamFilter);
3557
+ return args;
3558
+ }
3559
+ /** The whole audio block, after `-i`. */
3560
+ function buildAudioArgs(audio) {
3561
+ if (audio.kind === "none") return ["-an"];
3562
+ if (audio.kind === "copy") return ["-c:a", "copy"];
3563
+ const args = [];
3564
+ if (audio.filter !== void 0) args.push("-af", audio.filter);
3565
+ args.push("-c:a", AUDIO_ENCODER_BY_CODEC[audio.codec]);
3566
+ if (audio.application !== void 0) args.push("-application", audio.application);
3567
+ if (audio.frameDurationMs !== void 0) args.push("-frame_duration", String(audio.frameDurationMs));
3568
+ if (audio.globalHeader === true) args.push("-flags", "+global_header");
3569
+ if (audio.sampleRateHz !== void 0) args.push("-ar", String(audio.sampleRateHz));
3570
+ if (audio.bitrateKbps !== void 0) args.push("-b:a", `${audio.bitrateKbps}k`);
3571
+ if (audio.vbvBufferKbits !== void 0) args.push("-bufsize", `${audio.vbvBufferKbits}k`);
3572
+ if (audio.channels !== void 0) args.push("-ac", String(audio.channels));
3573
+ return args;
3574
+ }
3575
+ /** RTP output-leg args (`-payload_type`, `-ssrc`, `-sdp_file`, `-f rtp <url>`). */
3576
+ function buildRtpOutputArgs(out) {
3577
+ const args = [];
3578
+ if (out.payloadType !== void 0) args.push("-payload_type", String(out.payloadType));
3579
+ if (out.ssrc !== void 0) args.push("-ssrc", String(out.ssrc));
3580
+ if (out.sdpFile !== void 0) args.push("-sdp_file", out.sdpFile);
3581
+ args.push("-f", "rtp", out.url);
3582
+ return args;
3583
+ }
3584
+ /** `true` when the sink is a raw elementary bytestream that cannot mux audio. */
3585
+ function isElementaryVideoSink(sink) {
3586
+ return sink.kind === "stdout" && (sink.container === "h264" || sink.container === "hevc");
3587
+ }
3588
+ /**
3589
+ * The fragmented-MP4 muxer flags, in the order the recorder has proven them
3590
+ * (`recorder/addon/ffmpeg-args.ts` passes the same `movflags` string through
3591
+ * `-segment_format_options`, across every vendor in the fleet):
3592
+ *
3593
+ * - `frag_keyframe` — cut a fragment at each key frame, so every fragment
3594
+ * opens on a sync sample. HKSV's whole requirement.
3595
+ * - `empty_moov` — write `ftyp`+`moov` up front with no samples in it, which
3596
+ * is what makes the head a standalone INITIALISATION segment.
3597
+ * - `default_base_moof` — fragment offsets are self-relative, so a fragment is
3598
+ * demuxable without the bytes that preceded it. D31's byte-range read path
3599
+ * depends on exactly this property of the recorder's segments.
3600
+ */
3601
+ var FMP4_MOVFLAGS = "+frag_keyframe+empty_moov+default_base_moof";
3602
+ /**
3603
+ * The terminal sink args for every non-`rtp-outputs` sink. Exhaustive over the
3604
+ * union so a new member cannot fall through to `['-f', container, 'pipe:1']`,
3605
+ * which is what a plain `container` read would have done for `mp4` — a valid
3606
+ * argv that writes a NON-fragmented, unseekable-to-a-pipe MP4 and produces one
3607
+ * unusable byte stream.
3608
+ */
3609
+ function buildStdoutOrRtspSinkArgs(sink) {
3610
+ if (sink.kind === "rtsp-listen") return [
3611
+ "-f",
3612
+ "rtsp",
3613
+ "-rtsp_transport",
3614
+ "tcp",
3615
+ "-rtsp_flags",
3616
+ "listen",
3617
+ sink.url
3618
+ ];
3619
+ if (sink.kind === "rtp-outputs") return [];
3620
+ return sink.container === "mp4" ? buildFmp4SinkArgs(sink) : [
3621
+ "-f",
3622
+ sink.container,
3623
+ "pipe:1"
3624
+ ];
3625
+ }
3626
+ /**
3627
+ * How far BELOW the negotiated fragment length `-min_frag_duration` is set.
3628
+ *
3629
+ * `-min_frag_duration` refuses to cut before that much media has accumulated,
3630
+ * and then waits for the next key frame. Set to exactly `fragmentMs`, the
3631
+ * commonest camera configuration in existence — a key-frame grid EQUAL to the
3632
+ * requested fragment length — lands the deadline on the same instant as the key
3633
+ * frame, loses the race, and skips to the following one: **every fragment comes
3634
+ * out at twice the requested length.**
3635
+ *
3636
+ * Measured on the live fleet 2026-08-07, camera 615, `-c:v copy` (D84):
3637
+ *
3638
+ * | slot | GOP | `-min_frag_duration` | median gap |
3639
+ * | --- | --- | --- | --- |
3640
+ * | 1280×720 | 40 f @ 10 fps = 4.0 s | 4000 ms | **7944 ms** |
3641
+ * | 1280×720 | 40 f @ 10 fps = 4.0 s | 3600 ms | 3973 ms |
3642
+ * | 3840×2160 | 100 f @ 25 fps = 4.0 s | 4000 ms | 8042 ms |
3643
+ * | 3840×2160 | 100 f @ 25 fps = 4.0 s | 3600 ms | 3998 ms |
3644
+ *
3645
+ * A doubled fragment is not a cosmetic overshoot: HKSV requires every fragment
3646
+ * to be no longer than the length the controller SELECTED, so the shipped-but-
3647
+ * inert phase-1 sink would have violated the contract on its first real clip.
3648
+ *
3649
+ * 10 % is chosen against the two failures either side of it. Too small and
3650
+ * ordinary jitter (measured spread 3953-4096 ms) re-loses the race; too large
3651
+ * and a source with a key frame slightly EARLY than the grid gets cut there,
3652
+ * yielding a short fragment for no reason.
3653
+ */
3654
+ var FMP4_MIN_FRAG_MARGIN = .9;
3655
+ /** `-movflags … -min_frag_duration <us> -f mp4 pipe:1`. */
3656
+ function buildFmp4SinkArgs(sink) {
3657
+ return [
3658
+ "-movflags",
3659
+ FMP4_MOVFLAGS,
3660
+ "-min_frag_duration",
3661
+ String(Math.max(0, Math.round(sink.fragmentMs * FMP4_MIN_FRAG_MARGIN * 1e3))),
3662
+ "-f",
3663
+ "mp4",
3664
+ "pipe:1"
3665
+ ];
3666
+ }
3667
+ /**
3668
+ * A second output mapping source audio to RTP-over-UDP. `0:a:0?` makes the
3669
+ * audio optional so a source with no audio skips it instead of failing the
3670
+ * whole invocation.
3671
+ */
3672
+ function buildAudioSidecarArgs(sidecar) {
3673
+ return [
3674
+ "-map",
3675
+ "0:a:0?",
3676
+ ...buildAudioArgs(sidecar.codec === "pcma" ? {
3677
+ kind: "encode",
3678
+ codec: "pcma",
3679
+ sampleRateHz: 8e3,
3680
+ channels: 1
3681
+ } : AUDIO_PRESETS[sidecar.codec]),
3682
+ ...buildRtpOutputArgs({
3683
+ url: sidecar.rtpUrl,
3684
+ sdpFile: sidecar.sdpFile
3685
+ })
3686
+ ];
3687
+ }
3688
+ /**
3689
+ * Assemble the full ffmpeg argument list. Layout:
3690
+ *
3691
+ * -hide_banner -loglevel <level>
3692
+ * [-hwaccel <backend|auto>] ─┐ INPUT options — strictly before -i.
3693
+ * [<input.extraArgs>] │
3694
+ * [-fflags <flag>…] │
3695
+ * [-rtsp_transport tcp] │
3696
+ * -i <url> ─┘ …repeated per extraInput
3697
+ * [-filter_complex <graph>] ─ global, after the LAST -i
3698
+ * [-map <video>] <video block> <threads> ─┐ OUTPUT options.
3699
+ * [-map <audio>] <audio block> │
3700
+ * <consumer outputArgs verbatim> │
3701
+ * <sink> ─┘ terminal
3702
+ *
3703
+ * The `-map`s are DERIVED (see {@link resolveStreamMaps}), never constants:
3704
+ * one input with no graph emits none at all, which is why the argv of every
3705
+ * call site that predates the N-input extension is unchanged to the byte.
3706
+ *
3707
+ * {@link FfmpegInvocation.audioSidecar} is the one map that stays literal
3708
+ * (`0:a:0?`): the sidecar is defined as the SOURCE's microphone on its own RTP
3709
+ * output, not as whatever the main output happens to carry.
3710
+ */
3711
+ function buildFfmpegArgs(inv) {
3712
+ const graph = inv.filterGraph ?? null;
3713
+ const head = [
3714
+ ...logBannerArgs(inv.logLevel),
3715
+ ...buildInputArgs(inv.input, inv.decodeHwAccel),
3716
+ ...(inv.extraInputs ?? []).flatMap((extra) => buildInputArgs(extra, inv.decodeHwAccel)),
3717
+ ...graph ? ["-filter_complex", graph.graph] : []
3718
+ ];
3719
+ const threadArgs = inv.threadCount > 0 ? ["-threads", String(inv.threadCount)] : [];
3720
+ const maps = resolveStreamMaps(inv);
3721
+ const videoMapArgs = maps.video ? ["-map", maps.video] : [];
3722
+ const audioMapArgs = maps.audio ? ["-map", maps.audio] : [];
3723
+ if (inv.sink.kind === "rtp-outputs") {
3724
+ const videoLeg = inv.sink.video ? [
3725
+ "-an",
3726
+ "-map",
3727
+ maps.video ?? "0:v:0",
3728
+ ...buildVideoArgs(inv.video, inv.outputArgs, graph),
3729
+ ...threadArgs,
3730
+ ...inv.outputArgs,
3731
+ ...buildRtpOutputArgs(inv.sink.video)
3732
+ ] : [];
3733
+ const audioLeg = inv.sink.audio ? [
3734
+ "-vn",
3735
+ "-map",
3736
+ maps.audio ?? "0:a:0?",
3737
+ ...buildAudioArgs(inv.audio),
3738
+ ...buildRtpOutputArgs(inv.sink.audio)
3739
+ ] : [];
3740
+ return [
3741
+ ...head,
3742
+ ...videoLeg,
3743
+ ...audioLeg
3744
+ ];
3745
+ }
3746
+ const mutedAudio = isElementaryVideoSink(inv.sink) || inv.audio.kind === "none";
3747
+ const audioArgs = isElementaryVideoSink(inv.sink) ? ["-an"] : buildAudioArgs(inv.audio);
3748
+ return [
3749
+ ...head,
3750
+ ...videoMapArgs,
3751
+ ...buildVideoArgs(inv.video, inv.outputArgs, graph),
3752
+ ...threadArgs,
3753
+ ...mutedAudio ? [] : audioMapArgs,
3754
+ ...audioArgs,
3755
+ ...inv.outputArgs,
3756
+ ...buildStdoutOrRtspSinkArgs(inv.sink),
3757
+ ...inv.audioSidecar ? buildAudioSidecarArgs(inv.audioSidecar) : []
3758
+ ];
3759
+ }
3760
+ //#endregion
3390
3761
  //#region ../types/dist/err-msg-IQTHeDzc.mjs
3391
3762
  /**
3392
3763
  import { errMsg } from '@camstack/types'
@@ -3912,6 +4283,14 @@ var CameraSwitchGroupSchema = zod.z.object({
3912
4283
  fetchedAt: zod.z.number()
3913
4284
  });
3914
4285
  /**
4286
+ * The wrapper capability each wrapper-backed switch controls. Named constants
4287
+ * because the same strings appear in `legacy-migrations.ts`, in
4288
+ * `isCapActiveForDevice` call sites and in the fake harness — a typo in any of
4289
+ * them is a switch that silently writes a binding nobody reads.
4290
+ */
4291
+ var DETECTION_PIPELINE_CAP_NAME = "detection-pipeline";
4292
+ var AUDIO_ANALYSIS_CAP_NAME = "audio-analysis";
4293
+ /**
3915
4294
  * The value a redacted secret is replaced with on every read surface.
3916
4295
  *
3917
4296
  * It is also the WRITE-BACK token: an `upsertLocation` that sends this value
@@ -10974,6 +11353,45 @@ var SourceInfoSchema = zod.z.object({
10974
11353
  raw: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
10975
11354
  });
10976
11355
  /**
11356
+ * Build the synthetic SourceInfo every existing provider falls back to
11357
+ * when no upstream value has been persisted. Keeps non-migrated providers
11358
+ * (Reolink / Hikvision / ONVIF / RTSP) functional without code
11359
+ * changes — `id` reuses the CamStack stableId, `system` reuses the addon
11360
+ * id. Real upstream identity replaces this once a provider migrates and
11361
+ * calls `updateSourceInfo()` with concrete values.
11362
+ */
11363
+ function synthesizeSourceInfo(input) {
11364
+ return {
11365
+ id: input.stableId,
11366
+ system: input.addonId
11367
+ };
11368
+ }
11369
+ /**
11370
+ * Shallow-merge a partial patch over a prior SourceInfo. `undefined`
11371
+ * patch values are ignored (no field clobber); to clear a field
11372
+ * explicitly, providers omit it from the patch and rebuild from scratch
11373
+ * via `updateSourceInfo({})` cycles — uncommon enough that we don't
11374
+ * bother with a `null`-means-delete sentinel.
11375
+ *
11376
+ * The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
11377
+ * merges over existing `raw` keys instead of replacing the whole bag.
11378
+ */
11379
+ function mergeSourceInfo(prev, patch) {
11380
+ const next = { ...prev };
11381
+ for (const [k, v] of Object.entries(patch)) {
11382
+ if (v === void 0) continue;
11383
+ if (k === "raw" && typeof v === "object" && v !== null) {
11384
+ next.raw = {
11385
+ ...prev.raw,
11386
+ ...v
11387
+ };
11388
+ continue;
11389
+ }
11390
+ next[k] = v;
11391
+ }
11392
+ return next;
11393
+ }
11394
+ /**
10977
11395
  * Coerce common unit-of-measurement string variants to a canonical form
10978
11396
  * so the UI's display templates can rely on stable strings. Pure
10979
11397
  * passthrough for unknown units — kept conservative on purpose, the goal
@@ -11019,6 +11437,27 @@ function normalizeUnit(raw) {
11019
11437
  return trimmed;
11020
11438
  }
11021
11439
  /**
11440
+ * Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
11441
+ * blob is a free-form `Record<string, unknown>` shared with hardware-
11442
+ * identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
11443
+ * lives under a single nested key to keep it composable with those.
11444
+ */
11445
+ var SOURCE_INFO_METADATA_KEY = "sourceInfo";
11446
+ /**
11447
+ * Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
11448
+ * with Zod. Returns `null` when the blob is missing or the nested
11449
+ * `sourceInfo` key fails validation — callers fall back to the
11450
+ * synthetic default in that case (see `synthesizeSourceInfo`).
11451
+ */
11452
+ function extractSourceInfoFromMetadata(metadata) {
11453
+ if (!metadata) return null;
11454
+ const raw = metadata[SOURCE_INFO_METADATA_KEY];
11455
+ if (!raw) return null;
11456
+ const parsed = SourceInfoSchema.safeParse(raw);
11457
+ if (!parsed.success) return null;
11458
+ return parsed.data;
11459
+ }
11460
+ /**
11022
11461
  * device-discovery — device-scoped capability for parents that host /
11023
11462
  * enumerate child devices (Reolink Hub / NVR, ONVIF gateway, future
11024
11463
  * integrations). The parent advertises a list of discoverable children,
@@ -33369,6 +33808,25 @@ var ptzAutotrackCapability = {
33369
33808
  scope: "device",
33370
33809
  deviceNative: true,
33371
33810
  mode: "singleton",
33811
+ /**
33812
+ * Two kinds of provider behind one name, the same shape `snapshot` and
33813
+ * `doorbell` use:
33814
+ *
33815
+ * - **Native**: the camera's own firmware autotrack, registered per device by
33816
+ * the driver when the probe says it exists (Reolink: yes; Hikvision:
33817
+ * model-dependent).
33818
+ * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
33819
+ * which follows a subject with `ptz.move` pulses driven by the detection
33820
+ * pipeline. `defaultActive: false` — the operator binds it per camera in
33821
+ * the bindings UI, exactly as `virtual-doorbell` is bound.
33822
+ *
33823
+ * The binding chooses WHICH implementation answers; `setEnabled` stays the
33824
+ * one switch for the function either way. A camera never has both driving its
33825
+ * head, which is the whole reason this is not a second cap with a second
33826
+ * toggle (D62, D520).
33827
+ */
33828
+ kind: "wrapper",
33829
+ defaultActive: false,
33372
33830
  deviceTypes: [DeviceType.Camera],
33373
33831
  deviceConfig: { ui: {
33374
33832
  kind: "widget",
@@ -37511,6 +37969,807 @@ var AccessoryKind = {
37511
37969
  PrivacyMask: DeviceRole.PrivacyMask
37512
37970
  };
37513
37971
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
37972
+ var DeviceConfig = class DeviceConfig {
37973
+ schema;
37974
+ data;
37975
+ persistFn;
37976
+ constructor(schema, data, persist) {
37977
+ this.schema = schema;
37978
+ this.data = data;
37979
+ this.persistFn = persist;
37980
+ }
37981
+ /**
37982
+ * Build a `DeviceConfig` from a persisted blob, with automatic
37983
+ * recovery from schema-validation failures. Boot must never be
37984
+ * blocked by stale persisted values: if Zod rejects the blob,
37985
+ * we drop every offending top-level field, retry, and persist
37986
+ * the cleaned blob so the bad value is healed in the DB on next
37987
+ * write. The most common trigger is a tightened range constraint
37988
+ * (e.g. `max(100) → max(50)`) on a field that already has an
37989
+ * out-of-range value persisted from the previous schema. Without
37990
+ * this safety net, the device would fail to instantiate and end
37991
+ * up with no caps registered — exactly the failure mode that
37992
+ * stranded device 15 when `motionSensitivity: 90` no longer fit
37993
+ * the new `1..50` schema.
37994
+ *
37995
+ * Recovery rules:
37996
+ * 1. Try `safeParse(initialData)`. If it succeeds, done.
37997
+ * 2. On failure, walk `error.issues`, collect the top-level path
37998
+ * of each issue, and drop those keys from `initialData`.
37999
+ * 3. Re-run `safeParse`. If the cleaned blob now passes (Zod
38000
+ * fills the missing keys with schema defaults / undefined for
38001
+ * `.optional()`), persist it via `persist()` so the bad
38002
+ * values disappear from the DB, and return the device.
38003
+ * 4. If the cleaned blob STILL fails (very rare — would require
38004
+ * a non-recoverable required field), fall back to
38005
+ * `schema.parse({})` so the device still boots with pure
38006
+ * schema defaults. Persist nothing in that path so the next
38007
+ * successful `setAll` still writes a coherent blob.
38008
+ */
38009
+ static fromSchema(schema, persist, initialData = {}, onRecover) {
38010
+ const first = schema.safeParse(initialData);
38011
+ if (first.success) return new DeviceConfig(schema, first.data, persist);
38012
+ const droppedKeys = /* @__PURE__ */ new Set();
38013
+ for (const issue of first.error.issues) {
38014
+ const top = issue.path[0];
38015
+ if (typeof top === "string") droppedKeys.add(top);
38016
+ }
38017
+ const cleaned = { ...initialData };
38018
+ for (const k of droppedKeys) delete cleaned[k];
38019
+ const second = schema.safeParse(cleaned);
38020
+ onRecover?.({
38021
+ droppedKeys: [...droppedKeys],
38022
+ issues: first.error.issues
38023
+ });
38024
+ if (second.success) {
38025
+ persist(second.data).catch(() => {});
38026
+ return new DeviceConfig(schema, second.data, persist);
38027
+ }
38028
+ return new DeviceConfig(schema, schema.parse({}), persist);
38029
+ }
38030
+ get values() {
38031
+ return this.data;
38032
+ }
38033
+ get(key) {
38034
+ return this.data[key];
38035
+ }
38036
+ async set(key, value) {
38037
+ const next = this.schema.parse({
38038
+ ...this.data,
38039
+ [key]: value
38040
+ });
38041
+ this.data = next;
38042
+ await this.persistFn(this.data);
38043
+ }
38044
+ /**
38045
+ * Merge an untyped patch onto the current config and persist. Accepts
38046
+ * `Record<string, unknown>` because the patch typically comes from the
38047
+ * UI form layer (a `ConfigField.key → value` map) where the caller
38048
+ * doesn't hold the Zod schema's static type. Runtime validation is
38049
+ * authoritative: `this.schema.parse` rejects unknown keys or invalid
38050
+ * shapes before touching storage.
38051
+ */
38052
+ async setAll(partial) {
38053
+ const next = this.schema.parse({
38054
+ ...this.data,
38055
+ ...partial
38056
+ });
38057
+ this.data = next;
38058
+ await this.persistFn(this.data);
38059
+ }
38060
+ async deleteKey(key) {
38061
+ const { [key]: _, ...rest } = this.data;
38062
+ const next = this.schema.parse(rest);
38063
+ this.data = next;
38064
+ await this.persistFn(this.data);
38065
+ }
38066
+ entries() {
38067
+ const shape = this.schema.shape;
38068
+ return Object.entries(shape).map(([key, fieldSchema]) => ({
38069
+ key,
38070
+ schema: fieldSchema,
38071
+ value: this.data[key],
38072
+ description: fieldSchema.description
38073
+ }));
38074
+ }
38075
+ };
38076
+ /**
38077
+ * Concrete implementation. Routes every successful write through
38078
+ * `writer(capName, slice)` — the kernel hooks this up to
38079
+ * `device-state.setCapSlice`, the canonical cross-layer write
38080
+ * entrypoint, which handles disk persistence (debounced on the hub)
38081
+ * and mirror updates.
38082
+ *
38083
+ * Schema validation runs in-process before the writer is called —
38084
+ * the round-trip should never carry an invalid slice. `flush()`
38085
+ * awaits any in-flight writer promises so shutdown is lossless.
38086
+ *
38087
+ * `initial` is the persisted blob loaded at boot. Slices for caps
38088
+ * whose schema hasn't been installed yet are kept in-memory verbatim
38089
+ * and validated when the cap registers later.
38090
+ */
38091
+ var DeviceRuntimeState = class DeviceRuntimeState {
38092
+ writer;
38093
+ /** In-flight writer promises tracked so `flush()` can await them. */
38094
+ pendingWrites = /* @__PURE__ */ new Set();
38095
+ /** Per-cap committed slice — after schema validation when known. */
38096
+ slices;
38097
+ /** Per-cap registered schema (set by `installCapSchema`). */
38098
+ schemas = /* @__PURE__ */ new Map();
38099
+ listeners = /* @__PURE__ */ new Set();
38100
+ capListeners = /* @__PURE__ */ new Map();
38101
+ constructor(initial, writer) {
38102
+ this.writer = writer;
38103
+ this.slices = /* @__PURE__ */ new Map();
38104
+ for (const [k, v] of Object.entries(initial)) if (v && typeof v === "object" && !Array.isArray(v)) this.slices.set(k, { ...v });
38105
+ }
38106
+ static fromInitial(initial, writer) {
38107
+ return new DeviceRuntimeState(initial, writer);
38108
+ }
38109
+ installCapSchema(capName, schema) {
38110
+ const existing = this.schemas.get(capName);
38111
+ if (existing) {
38112
+ if (existing !== schema) throw new Error(`[DeviceRuntimeState] capability "${capName}" registered a different runtime-state schema; each cap must declare ONE shape across every provider`);
38113
+ return;
38114
+ }
38115
+ this.schemas.set(capName, schema);
38116
+ const stored = this.slices.get(capName);
38117
+ if (stored) {
38118
+ const result = schema.safeParse(stored);
38119
+ if (result.success) this.slices.set(capName, result.data);
38120
+ else this.slices.delete(capName);
38121
+ }
38122
+ }
38123
+ getCapState(capName) {
38124
+ const slice = this.slices.get(capName);
38125
+ if (!slice) return void 0;
38126
+ return Object.freeze({ ...slice });
38127
+ }
38128
+ getCapField(capName, key) {
38129
+ return this.slices.get(capName)?.[key];
38130
+ }
38131
+ setCapState(capName, value) {
38132
+ this.applyCapWrite(capName, value, false);
38133
+ }
38134
+ patchCapState(capName, partial) {
38135
+ this.applyCapWrite(capName, partial, true);
38136
+ }
38137
+ /**
38138
+ * Internal worker. `merge` controls whether `value` replaces or
38139
+ * shallow-merges into the existing slice. Schema validation runs
38140
+ * on the FINAL composed object regardless.
38141
+ */
38142
+ applyCapWrite(capName, value, merge) {
38143
+ const schema = this.schemas.get(capName);
38144
+ if (!schema) throw new Error(`[DeviceRuntimeState] no schema registered for cap "${capName}" — did the device register it via ctx.registerNativeCap before writing?`);
38145
+ const current = this.slices.get(capName) ?? {};
38146
+ const next = merge ? {
38147
+ ...current,
38148
+ ...value
38149
+ } : { ...value };
38150
+ const parsed = schema.parse(next);
38151
+ if (shallowEqual(current, parsed)) return;
38152
+ this.slices.set(capName, parsed);
38153
+ this.fireListeners([capName]);
38154
+ const writePromise = this.writer(capName, { ...parsed }).catch(() => {});
38155
+ this.pendingWrites.add(writePromise);
38156
+ writePromise.finally(() => {
38157
+ this.pendingWrites.delete(writePromise);
38158
+ });
38159
+ }
38160
+ fireListeners(changed) {
38161
+ const snap = this.snapshot();
38162
+ for (const cb of this.listeners) try {
38163
+ cb(changed, snap);
38164
+ } catch {}
38165
+ for (const capName of changed) {
38166
+ const subs = this.capListeners.get(capName);
38167
+ if (!subs) continue;
38168
+ const slice = this.getCapState(capName);
38169
+ for (const cb of subs) try {
38170
+ cb(slice);
38171
+ } catch {}
38172
+ }
38173
+ }
38174
+ subscribe(cb) {
38175
+ this.listeners.add(cb);
38176
+ return () => {
38177
+ this.listeners.delete(cb);
38178
+ };
38179
+ }
38180
+ subscribeCap(capName, cb) {
38181
+ let subs = this.capListeners.get(capName);
38182
+ if (!subs) {
38183
+ subs = /* @__PURE__ */ new Set();
38184
+ this.capListeners.set(capName, subs);
38185
+ }
38186
+ const adapter = (slice) => {
38187
+ cb(slice);
38188
+ };
38189
+ subs.add(adapter);
38190
+ return () => {
38191
+ const set = this.capListeners.get(capName);
38192
+ if (!set) return;
38193
+ set.delete(adapter);
38194
+ if (set.size === 0) this.capListeners.delete(capName);
38195
+ };
38196
+ }
38197
+ snapshot() {
38198
+ const out = {};
38199
+ for (const [k, v] of this.slices) out[k] = Object.freeze({ ...v });
38200
+ return Object.freeze(out);
38201
+ }
38202
+ async flush() {
38203
+ if (this.pendingWrites.size === 0) return;
38204
+ const inflight = [...this.pendingWrites];
38205
+ await Promise.allSettled(inflight);
38206
+ }
38207
+ };
38208
+ function shallowEqual(a, b) {
38209
+ const ak = Object.keys(a);
38210
+ const bk = Object.keys(b);
38211
+ if (ak.length !== bk.length) return false;
38212
+ for (const k of ak) if (a[k] !== b[k]) return false;
38213
+ return true;
38214
+ }
38215
+ /**
38216
+ * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
38217
+ * `state` getter looks up the cap definition here to construct a
38218
+ * `sliceProxy()` lazily on first access. Generated alongside the type
38219
+ * so type and runtime registry can never drift apart.
38220
+ */
38221
+ var DEVICE_LOCAL_STATE_CAPS = {
38222
+ airQualitySensor: airQualitySensorCapability,
38223
+ alarmPanel: alarmPanelCapability,
38224
+ ambientLightSensor: ambientLightSensorCapability,
38225
+ audioMetrics: audioMetricsCapability,
38226
+ automationControl: automationControlCapability,
38227
+ battery: batteryCapability,
38228
+ binary: binaryCapability,
38229
+ brightness: brightnessCapability,
38230
+ cameraStreams: cameraStreamsCapability,
38231
+ carbonMonoxide: carbonMonoxideCapability,
38232
+ climateControl: climateControlCapability,
38233
+ color: colorCapability,
38234
+ connectivity: connectivityCapability,
38235
+ consumables: consumablesCapability,
38236
+ contact: contactCapability,
38237
+ control: controlCapability,
38238
+ cover: coverCapability,
38239
+ dayNight: dayNightCapability,
38240
+ deviceDiscovery: deviceDiscoveryCapability,
38241
+ deviceStatus: deviceStatusCapability,
38242
+ doorbell: doorbellCapability,
38243
+ enumSensor: enumSensorCapability,
38244
+ eventEmitter: eventEmitterCapability,
38245
+ fanControl: fanControlCapability,
38246
+ featureProbe: featureProbeCapability,
38247
+ flood: floodCapability,
38248
+ gas: gasCapability,
38249
+ humidifier: humidifierCapability,
38250
+ humiditySensor: humiditySensorCapability,
38251
+ image: imageCapability,
38252
+ imageSettings: imageSettingsCapability,
38253
+ intercom: intercomCapability,
38254
+ lawnMowerControl: lawnMowerControlCapability,
38255
+ lockControl: lockControlCapability,
38256
+ mediaPlayer: mediaPlayerCapability,
38257
+ motion: motionCapability,
38258
+ motionTrigger: motionTriggerCapability,
38259
+ motionZones: motionZonesCapability,
38260
+ nativeObjectDetection: nativeObjectDetectionCapability,
38261
+ navigation: navigationCapability,
38262
+ networkLink: networkLinkCapability,
38263
+ notifier: notifierCapability,
38264
+ numericSensor: numericSensorCapability,
38265
+ petFeeder: petFeederCapability,
38266
+ powerMeter: powerMeterCapability,
38267
+ presence: presenceCapability,
38268
+ pressureSensor: pressureSensorCapability,
38269
+ privacyMask: privacyMaskCapability,
38270
+ ptzAutotrack: ptzAutotrackCapability,
38271
+ recordingSignal: recordingSignalCapability,
38272
+ sceneMonitor: sceneMonitorCapability,
38273
+ scriptRunner: scriptRunnerCapability,
38274
+ smoke: smokeCapability,
38275
+ streamCatalog: streamCatalogCapability,
38276
+ streamParams: streamParamsCapability,
38277
+ streamSignals: streamSignalsCapability,
38278
+ switch: switchCapability,
38279
+ tamper: tamperCapability,
38280
+ temperatureSensor: temperatureSensorCapability,
38281
+ update: updateCapability,
38282
+ vacuumControl: vacuumControlCapability,
38283
+ valve: valveCapability,
38284
+ vibration: vibrationCapability,
38285
+ waterHeater: waterHeaterCapability,
38286
+ weather: weatherCapability,
38287
+ zoneAnalytics: zoneAnalyticsCapability,
38288
+ zoneRules: zoneRulesCapability,
38289
+ zones: zonesCapability
38290
+ };
38291
+ var BaseDevice = class {
38292
+ id;
38293
+ stableId;
38294
+ type;
38295
+ name;
38296
+ parentDeviceId;
38297
+ role;
38298
+ /**
38299
+ * Cap-keyed runtime-state slice is the single source of truth for
38300
+ * `online`. Both getter and setter proxy to the slice — drivers can
38301
+ * write `this.online = true` ergonomically, and the cap event fires
38302
+ * automatically through the runtime-state writer. `markOnline()` is
38303
+ * kept as the explicit method form mandated by `IDevice`.
38304
+ */
38305
+ get online() {
38306
+ return this.runtimeState.getCapState("device-status")?.online ?? false;
38307
+ }
38308
+ set online(value) {
38309
+ this.markOnline(value);
38310
+ }
38311
+ /**
38312
+ * Generic per-cap runtime-state namespace. One entry per cap with
38313
+ * `runtimeState:` declared, auto-generated by codegen — see
38314
+ * `device-local-state.ts`. Drivers access via:
38315
+ *
38316
+ * `this.state.battery.sleeping = true` // patches the battery slice
38317
+ * `const pct = this.state.battery.percentage` // reads the battery slice
38318
+ * `this.state.deviceStatus.online = true` // mirrors `markOnline(true)`
38319
+ *
38320
+ * Adding a new cap with `runtimeState:` automatically extends this
38321
+ * namespace — drivers don't have to declare proxies. Reads return
38322
+ * `undefined` when the slice hasn't been seeded; writes patch via
38323
+ * `runtimeState.patchCapState` and validate against the cap's schema
38324
+ * (so partial writes need the slice to be seeded with the required
38325
+ * fields first — drivers do this on cap registration).
38326
+ *
38327
+ * For caps not exposed in `DeviceLocalState`, drivers can build their
38328
+ * own typed proxy via `this.sliceProxy(cap)`.
38329
+ */
38330
+ get state() {
38331
+ if (!this._stateProxyCache) {
38332
+ const cache = {};
38333
+ const handler = { get: (_target, key) => {
38334
+ const k = key;
38335
+ if (k in cache) return cache[k];
38336
+ const cap = DEVICE_LOCAL_STATE_CAPS[k];
38337
+ if (!cap) return void 0;
38338
+ const proxy = this.sliceProxy(cap);
38339
+ cache[k] = proxy;
38340
+ return proxy;
38341
+ } };
38342
+ this._stateProxyCache = new Proxy(cache, handler);
38343
+ }
38344
+ return this._stateProxyCache;
38345
+ }
38346
+ _stateProxyCache;
38347
+ config;
38348
+ /**
38349
+ * Per-device runtime state, cap-keyed. Always installed — slices
38350
+ * for individual caps materialise as those caps register their
38351
+ * native providers (`ctx.registerNativeCap`). The cap's own
38352
+ * `runtimeState` schema is the source of truth for the slice
38353
+ * shape; drivers don't redeclare it, they just write through.
38354
+ *
38355
+ * Read: `this.runtimeState.getCapState('battery')` →
38356
+ * `{percentage, charging, sleeping, lastUpdated}` for any
38357
+ * provider that registers `batteryCapability`.
38358
+ * Write: `this.runtimeState.setCapState('battery', { … })`.
38359
+ *
38360
+ * Cross-process consumers reach this state through the
38361
+ * `deviceState` cap router (or via cap-specific events the driver
38362
+ * emits — e.g. `battery.onStatusChanged`). The local handle is
38363
+ * accessed in-process by the driver to avoid roundtrips.
38364
+ */
38365
+ runtimeState;
38366
+ ctx;
38367
+ /**
38368
+ * Operator-organisational location label (room / area / zone).
38369
+ * Read from `ctx.deviceMeta.location`; mutated via
38370
+ * `kernel.devices.setLocation(id, value)`. Free-text — providers
38371
+ * don't interpret it; the UI groups devices by this for filters
38372
+ * like "show me all cameras in Kitchen". `null` when unset.
38373
+ */
38374
+ location;
38375
+ /**
38376
+ * Soft-disabled flag. When `true`, the device class is still
38377
+ * instantiated and visible in the UI (so the operator can flip
38378
+ * back on without re-adding) but lifecycle hooks (publishToBroker,
38379
+ * alarm-stream subscribe, …) MUST be gated by the driver to skip
38380
+ * work. The `BaseDevice` enforces this by exposing the flag here;
38381
+ * it does NOT mutate cap behaviour automatically — drivers consult
38382
+ * `this.disabled` at the top of their lifecycle methods. Read from
38383
+ * `ctx.deviceMeta.disabled`; mutated via
38384
+ * `kernel.devices.setDisabled(id, value)`.
38385
+ */
38386
+ disabled;
38387
+ /**
38388
+ * Cached materialised `SourceInfo` — either the value persisted under
38389
+ * `metadata.sourceInfo` at construction time, or a synthetic
38390
+ * `{ id: stableId, system: addonId }` for providers that haven't
38391
+ * migrated yet. Lazily populated on first `sourceInfo` read so the
38392
+ * cost of Zod-parsing the meta blob is paid once per device boot.
38393
+ * Invalidated by `updateSourceInfo()` so providers see the new value
38394
+ * back through the getter without a re-fetch from the meta surface.
38395
+ */
38396
+ _sourceInfoCache = null;
38397
+ constructor(ctx, schema, options) {
38398
+ this.ctx = ctx;
38399
+ this.id = ctx.id;
38400
+ this.stableId = ctx.stableId;
38401
+ this.type = options.type;
38402
+ if (!ctx.deviceMeta) throw new Error(`BaseDevice constructor: ctx.deviceMeta is required (id=${ctx.id} stableId=${ctx.stableId})`);
38403
+ this.name = ctx.deviceMeta.name;
38404
+ this.location = ctx.deviceMeta.location;
38405
+ this.disabled = ctx.deviceMeta.disabled;
38406
+ this.role = options.role;
38407
+ this.parentDeviceId = ctx.parentDeviceId;
38408
+ const seedData = ctx.persistedConfig ?? {};
38409
+ this.config = DeviceConfig.fromSchema(schema, (data) => ctx.persistConfig(data), seedData, ({ droppedKeys, issues }) => {
38410
+ ctx.logger.warn("Device config recovery: dropping invalid persisted fields", {
38411
+ tags: {
38412
+ deviceId: ctx.id,
38413
+ stableId: ctx.stableId
38414
+ },
38415
+ meta: {
38416
+ droppedKeys: [...droppedKeys],
38417
+ firstIssue: issues[0]?.message ?? null
38418
+ }
38419
+ });
38420
+ });
38421
+ let cachedProxy = null;
38422
+ const writer = async (capName, slice) => {
38423
+ if (!cachedProxy) cachedProxy = ctx.fetchDevice(ctx.id);
38424
+ await (await cachedProxy).deviceState.setCapSlice({
38425
+ capName,
38426
+ slice
38427
+ });
38428
+ };
38429
+ const initial = ctx.initialRuntimeState ?? {};
38430
+ this.runtimeState = DeviceRuntimeState.fromInitial(initial, writer);
38431
+ ctx.bindRuntimeState?.(this.runtimeState);
38432
+ ctx.registerNativeCap?.(deviceStatusCapability, {});
38433
+ const seed = {
38434
+ online: true,
38435
+ lastChangedAt: Date.now()
38436
+ };
38437
+ this.runtimeState.setCapState("device-status", seed);
38438
+ ctx.registerNativeCap?.(featureProbeCapability, {});
38439
+ this.runtimeState.setCapState("feature-probe", {
38440
+ flags: {},
38441
+ deviceType: null,
38442
+ model: null,
38443
+ channelCount: null,
38444
+ lastProbedAt: 0,
38445
+ lastFetchedAt: 0
38446
+ });
38447
+ }
38448
+ deviceActions = /* @__PURE__ */ new Map();
38449
+ /** Declare a device custom action + its typed handler. Idempotent per name. */
38450
+ registerDeviceAction(name, spec, handler) {
38451
+ this.deviceActions.set(name, {
38452
+ spec,
38453
+ handler
38454
+ });
38455
+ }
38456
+ /** Invoke a registered device action. Validates input against the spec. */
38457
+ async runDeviceAction(action, input) {
38458
+ const entry = this.deviceActions.get(action);
38459
+ if (!entry) throw new Error(`unknown device action "${action}" on device ${this.id}`);
38460
+ const parsed = entry.spec.input.parse(input);
38461
+ return entry.handler(parsed);
38462
+ }
38463
+ async removeDevice() {}
38464
+ /**
38465
+ * Set the device's online flag. Called by `BaseDeviceProvider` after
38466
+ * aggregating per-profile stream-broker health, or directly by drivers
38467
+ * that have provider-side liveness signals (e.g. ONVIF heartbeats,
38468
+ * Reolink Baichuan firmware push events). Mirrors the new value into
38469
+ * the `device-status` runtime-state slice so cross-process consumers
38470
+ * pick it up via the standard cap-state channel. Subclasses can
38471
+ * override to gate side effects on the transition.
38472
+ */
38473
+ markOnline(online) {
38474
+ if (this.online === online) return;
38475
+ const next = {
38476
+ online,
38477
+ lastChangedAt: Date.now()
38478
+ };
38479
+ this.runtimeState.setCapState("device-status", next);
38480
+ }
38481
+ /**
38482
+ * Upstream-system identity + rendering envelope for this device. See
38483
+ * `SourceInfo` for the field contract. Always returns a valid object:
38484
+ * if the persisted `metadata.sourceInfo` blob is absent or fails Zod
38485
+ * validation, falls back to a synthetic `{ id: stableId, system: addonId }`
38486
+ * so providers that haven't migrated keep working without code changes.
38487
+ *
38488
+ * The value is cached after the first read. `updateSourceInfo()`
38489
+ * invalidates the cache so subsequent reads see the new patch. The
38490
+ * returned object is frozen to prevent accidental in-place mutation —
38491
+ * use `updateSourceInfo({ patch })` to change fields.
38492
+ */
38493
+ get sourceInfo() {
38494
+ if (this._sourceInfoCache) return this._sourceInfoCache;
38495
+ const resolved = extractSourceInfoFromMetadata(this.ctx.deviceMeta.metadata) ?? synthesizeSourceInfo({
38496
+ stableId: this.stableId,
38497
+ addonId: this.ctx.deviceMeta.addonId
38498
+ });
38499
+ this._sourceInfoCache = Object.freeze({ ...resolved });
38500
+ return this._sourceInfoCache;
38501
+ }
38502
+ /**
38503
+ * Convenience accessor for the upstream dispatch key. Equivalent to
38504
+ * `this.sourceInfo.id` — providers use this to keep a
38505
+ * `Map<sourceId, IDevice>` for routing inbound push events.
38506
+ */
38507
+ get sourceId() {
38508
+ return this.sourceInfo.id;
38509
+ }
38510
+ /**
38511
+ * Patch the device's `SourceInfo`. Shallow-merges `patch` over the
38512
+ * current value, persists the merged result under
38513
+ * `metadata.sourceInfo` via the `device-manager.setMetadata` cap, and
38514
+ * emits `EventCategory.DeviceSourceInfoChanged` for live consumers.
38515
+ *
38516
+ * Safe to call from anywhere in the device's lifetime — the call is
38517
+ * idempotent for `undefined` patch values (ignored) and best-effort
38518
+ * for persistence (a transient device-manager error doesn't unwind
38519
+ * the local cache update, so subsequent reads still see the patch).
38520
+ *
38521
+ * Drivers populate this on adoption + on every metadata change push
38522
+ * from the upstream source. Subscribers (UI, export adapters) react
38523
+ * via the `DeviceSourceInfoChanged` event without polling.
38524
+ */
38525
+ async updateSourceInfo(patch) {
38526
+ const next = mergeSourceInfo(this.sourceInfo, patch);
38527
+ this._sourceInfoCache = Object.freeze({ ...next });
38528
+ const action = this.ctx.api?.deviceManager?.setMetadata;
38529
+ if (action) try {
38530
+ await action.mutate({
38531
+ deviceId: this.id,
38532
+ patch: { [SOURCE_INFO_METADATA_KEY]: next }
38533
+ });
38534
+ } catch {}
38535
+ this.ctx.eventBus.emit(createEvent("device.source-info-changed", {
38536
+ type: "device",
38537
+ id: this.stableId
38538
+ }, {
38539
+ deviceId: this.id,
38540
+ sourceInfo: next
38541
+ }));
38542
+ }
38543
+ /**
38544
+ * Re-publish the device's current `features` array to the persisted
38545
+ * meta blob. Drivers call this after a probe finishes when the live
38546
+ * `features` getter has gained new flags (e.g. `hasIntercom` flips
38547
+ * to true → `DeviceFeature.TwoWayAudio` joins the list).
38548
+ *
38549
+ * Without this, only the construction-time snapshot is written —
38550
+ * `deviceManager.registerDevice` is invoked once per boot, so probe-
38551
+ * driven additions don't reach the persisted index until the next
38552
+ * server restart, and `getDevice` / `listAll` keep returning the
38553
+ * stale list for forked-worker devices (whose live IDevice instance
38554
+ * is invisible to the hub registry).
38555
+ *
38556
+ * Idempotent: re-calling with the same features just no-ops on the
38557
+ * persisted meta. Best-effort: lookup or write failures are logged
38558
+ * at debug and swallowed — the live `device.features` getter is
38559
+ * still authoritative within this process, so callers never block
38560
+ * device boot on a meta refresh.
38561
+ */
38562
+ async refreshFeatures() {
38563
+ const action = this.ctx.api?.deviceManager?.registerDevice;
38564
+ if (!action) return;
38565
+ try {
38566
+ await action.mutate({
38567
+ addonId: this.ctx.deviceMeta.addonId,
38568
+ stableId: this.stableId,
38569
+ id: this.id,
38570
+ type: this.type,
38571
+ name: this.name,
38572
+ parentDeviceId: this.parentDeviceId,
38573
+ features: [...this.features],
38574
+ config: {}
38575
+ });
38576
+ } catch (err) {}
38577
+ }
38578
+ /**
38579
+ * Typed read-through to a cap-keyed runtime-state slice. Drivers
38580
+ * call `this.getCapSlice(batteryCapability)` and the return type
38581
+ * is inferred from the cap's `runtimeState` Zod schema — no string
38582
+ * key, no manual generic. Returns `null` when the slice hasn't
38583
+ * been written yet (e.g. driver hasn't seeded battery yet).
38584
+ */
38585
+ getCapSlice(cap) {
38586
+ return this.runtimeState.getCapState(cap.name) ?? null;
38587
+ }
38588
+ /**
38589
+ * Typed writer to a cap-keyed runtime-state slice. Routes through
38590
+ * the runtime-state writer (validate → persist → emit cap event).
38591
+ * Equivalent to `this.runtimeState.setCapState(cap.name, value)`
38592
+ * but with the cap's `runtimeState` schema enforcing the value
38593
+ * shape at compile time. Mirrors the symmetry of
38594
+ * `getCapSlice` / `setCapSlice` for cross-cap consistency.
38595
+ */
38596
+ setCapSlice(cap, value) {
38597
+ this.runtimeState.setCapState(cap.name, value);
38598
+ }
38599
+ /**
38600
+ * Field-level read/write proxy over a cap's runtime-state slice.
38601
+ * Drivers that want ergonomic per-field access declare:
38602
+ *
38603
+ * ```ts
38604
+ * protected battery = this.sliceProxy(batteryCapability)
38605
+ * // …
38606
+ * this.battery.sleeping = true // patches the slice
38607
+ * const charging = this.battery.charging // reads the slice
38608
+ * ```
38609
+ *
38610
+ * Reads return `undefined` when the slice hasn't been seeded yet
38611
+ * (cap not registered, or seeded but the field is absent). Writes
38612
+ * route through `runtimeState.patchCapState` so the cap's `runtimeState`
38613
+ * schema validates the merged result and the cap event fires.
38614
+ *
38615
+ * Pattern is generic — same shape works for `battery`, `device-status`,
38616
+ * `motion`, `doorbell`, anything with a `runtimeState:` schema. Drivers
38617
+ * declare one proxy per cap they read/write directly.
38618
+ */
38619
+ sliceProxy(cap) {
38620
+ return new Proxy({}, {
38621
+ get: (_, key) => {
38622
+ return this.runtimeState.getCapState(cap.name)?.[key];
38623
+ },
38624
+ set: (_, key, value) => {
38625
+ this.runtimeState.patchCapState(cap.name, { [key]: value });
38626
+ return true;
38627
+ },
38628
+ has: (_, key) => {
38629
+ const slice = this.runtimeState.getCapState(cap.name);
38630
+ return slice ? key in slice : false;
38631
+ },
38632
+ ownKeys: () => {
38633
+ const slice = this.runtimeState.getCapState(cap.name);
38634
+ return slice ? Object.keys(slice) : [];
38635
+ },
38636
+ getOwnPropertyDescriptor: (_, key) => {
38637
+ const slice = this.runtimeState.getCapState(cap.name);
38638
+ if (!slice || !(key in slice)) return void 0;
38639
+ return {
38640
+ configurable: true,
38641
+ enumerable: true,
38642
+ value: slice[key]
38643
+ };
38644
+ }
38645
+ });
38646
+ }
38647
+ /**
38648
+ * Default empty settings UI. Drivers override this to expose an
38649
+ * editable form in the device-details page. Returning an empty sections
38650
+ * array signals "nothing to contribute" — the aggregator drops the
38651
+ * contribution entirely rather than rendering a blank panel.
38652
+ */
38653
+ getSettingsUISchema() {
38654
+ return { sections: [] };
38655
+ }
38656
+ /**
38657
+ * Default write path: forward the flat patch directly to storage.
38658
+ * Drivers that project a UI shape different from storage (e.g. `RtspCamera`
38659
+ * exposing `mainStreamUrl`/`subStreamUrl` over `streams[]`) override this
38660
+ * to reshape before `config.setAll`.
38661
+ */
38662
+ async applySettingsPatch(patch) {
38663
+ await this.config.setAll(patch);
38664
+ }
38665
+ /**
38666
+ * Phase 3 — populate device-scoped state needed by downstream phases
38667
+ * (accessory reconciliation, public `features` array, optional cap
38668
+ * registration). Called ONCE per construction, after register but
38669
+ * before `getAccessoryChildren()`.
38670
+ *
38671
+ * Drivers write the `feature-probe` runtime-state slice via
38672
+ * `this.runtimeState.setCapState('feature-probe', {...})` — flag bag
38673
+ * is open (Reolink writes `hasPtz/hasIntercom`, Hikvision writes
38674
+ * `hasSupplementalLight/hasAlarmIo`, etc).
38675
+ *
38676
+ * Default: nothing to probe → mark the device PROBED (set `lastProbedAt`) so
38677
+ * the kernel treats it as ready immediately. A device that derives its shape
38678
+ * from a spec (a container, or an accessory sensor) rather than from a
38679
+ * hardware probe has no probe to "complete"; without stamping `lastProbedAt`
38680
+ * it would look perpetually un-probed — logging "Initial probe did not
38681
+ * complete" on every boot and spinning a pointless retry chain. Drivers that
38682
+ * DO probe override this and write their own `feature-probe` slice (including
38683
+ * `lastProbedAt`) once their probe actually succeeds.
38684
+ */
38685
+ async onProbe() {
38686
+ const base = this.runtimeState.getCapState("feature-probe") ?? {
38687
+ flags: {},
38688
+ deviceType: null,
38689
+ model: null,
38690
+ channelCount: null,
38691
+ lastProbedAt: 0,
38692
+ lastFetchedAt: 0
38693
+ };
38694
+ this.runtimeState.setCapState("feature-probe", {
38695
+ ...base,
38696
+ lastProbedAt: Date.now()
38697
+ });
38698
+ }
38699
+ /**
38700
+ * Phase 5 — fired after the device + its accessories are registered.
38701
+ * Drivers publish streams to the broker, kick off background tasks,
38702
+ * or subscribe to lib events that need a fully-registered device id.
38703
+ *
38704
+ * Default: no-op.
38705
+ *
38706
+ * RENAMED FROM `onCreated` (which still exists for back-compat in this
38707
+ * pass). The new name reflects the post-probe, post-accessory contract.
38708
+ */
38709
+ async onActivate() {}
38710
+ /**
38711
+ * Re-run the probe + reconcile accessories + refresh features meta.
38712
+ * Drivers call this when device-side state changes (battery cam wakes,
38713
+ * firmware update, manual operator trigger).
38714
+ *
38715
+ * The kernel injects `_kernelReprobe` on registration so this method
38716
+ * delegates to the same orchestrator that runs the boot-time phase
38717
+ * 3 + 4 sequence. Drivers should NOT override this — they override
38718
+ * `onProbe()` instead.
38719
+ */
38720
+ async reprobe() {
38721
+ if (this._kernelReprobe) await this._kernelReprobe();
38722
+ else await this.onProbe();
38723
+ }
38724
+ /**
38725
+ * Kernel-injected callback that runs the full post-probe orchestration
38726
+ * (onProbe → registerDevice meta refresh → accessory reconciliation).
38727
+ * Set by `device-cap-proxy.register()`. Drivers should not touch this
38728
+ * directly — call `reprobe()` instead.
38729
+ */
38730
+ _kernelReprobe;
38731
+ /**
38732
+ * Declare accessory child devices the kernel should auto-spawn
38733
+ * after `onProbe()` resolves. Each spec fully describes one child
38734
+ * — stableId suffix (deterministic per kind for restore-safety),
38735
+ * meta (type / name / location), config (initial blob the child
38736
+ * self-hydrates), and a factory that constructs the concrete
38737
+ * class with whatever closure-captured refs it needs (typically
38738
+ * `this` for the parent reference).
38739
+ *
38740
+ * The kernel handles the rest: allocateDeviceId, persistInitialConfig
38741
+ * (skipped on restore when the row already exists),
38742
+ * persistInitialMeta, createContext, factory invocation, register,
38743
+ * and recursive lifecycle (probe + accessories + activate).
38744
+ *
38745
+ * Implementations should derive children from
38746
+ * `this.runtimeState.getCapState('feature-probe')` (post-probe truth).
38747
+ * Drivers can use the `getProbeFlags()` helper to read the flag bag
38748
+ * with a typed cast.
38749
+ *
38750
+ * Default: no children.
38751
+ */
38752
+ getAccessoryChildren() {
38753
+ return [];
38754
+ }
38755
+ /**
38756
+ * Read the current feature-probe flag bag with a typed cast. Helper
38757
+ * for `getAccessoryChildren()` and `features` getters that derive
38758
+ * outputs from the probe results.
38759
+ */
38760
+ getProbeFlags() {
38761
+ return this.runtimeState.getCapState("feature-probe")?.flags ?? {};
38762
+ }
38763
+ /**
38764
+ * Returns true once `onProbe` has completed at least once
38765
+ * (`lastProbedAt > 0`). Drivers gate `getAccessoryChildren()` on this
38766
+ * to avoid spawning stale accessories on a fresh device whose probe
38767
+ * hasn't landed yet.
38768
+ */
38769
+ hasProbed() {
38770
+ return (this.runtimeState.getCapState("feature-probe")?.lastProbedAt ?? 0) > 0;
38771
+ }
38772
+ };
37514
38773
  /**
37515
38774
  * THE derivation. One crop rectangle, one presence verdict — see D52 for why
37516
38775
  * this repo insists a derived value has exactly one implementation.
@@ -47503,6 +48762,12 @@ Object.defineProperty(exports, "ALL_CAPABILITY_DEFINITIONS", {
47503
48762
  return ALL_CAPABILITY_DEFINITIONS;
47504
48763
  }
47505
48764
  });
48765
+ Object.defineProperty(exports, "AUDIO_ANALYSIS_CAP_NAME", {
48766
+ enumerable: true,
48767
+ get: function() {
48768
+ return AUDIO_ANALYSIS_CAP_NAME;
48769
+ }
48770
+ });
47506
48771
  Object.defineProperty(exports, "AlertSchema", {
47507
48772
  enumerable: true,
47508
48773
  get: function() {
@@ -47521,6 +48786,12 @@ Object.defineProperty(exports, "BaseAddon", {
47521
48786
  return BaseAddon;
47522
48787
  }
47523
48788
  });
48789
+ Object.defineProperty(exports, "BaseDevice", {
48790
+ enumerable: true,
48791
+ get: function() {
48792
+ return BaseDevice;
48793
+ }
48794
+ });
47524
48795
  Object.defineProperty(exports, "BatteryStatusSchema", {
47525
48796
  enumerable: true,
47526
48797
  get: function() {
@@ -47569,6 +48840,12 @@ Object.defineProperty(exports, "DEFAULT_FIRST_SIGHTING_FRESHNESS_MS", {
47569
48840
  return DEFAULT_FIRST_SIGHTING_FRESHNESS_MS;
47570
48841
  }
47571
48842
  });
48843
+ Object.defineProperty(exports, "DETECTION_PIPELINE_CAP_NAME", {
48844
+ enumerable: true,
48845
+ get: function() {
48846
+ return DETECTION_PIPELINE_CAP_NAME;
48847
+ }
48848
+ });
47572
48849
  Object.defineProperty(exports, "DEVICE_SETTINGS_CONTRIBUTION_METHODS", {
47573
48850
  enumerable: true,
47574
48851
  get: function() {
@@ -47617,6 +48894,12 @@ Object.defineProperty(exports, "EventCategory", {
47617
48894
  return EventCategory;
47618
48895
  }
47619
48896
  });
48897
+ Object.defineProperty(exports, "FIRST_LEVEL_MACRO_CLASSES", {
48898
+ enumerable: true,
48899
+ get: function() {
48900
+ return FIRST_LEVEL_MACRO_CLASSES;
48901
+ }
48902
+ });
47620
48903
  Object.defineProperty(exports, "LOG_LEVEL_RANK", {
47621
48904
  enumerable: true,
47622
48905
  get: function() {
@@ -47803,12 +49086,24 @@ Object.defineProperty(exports, "batteryCapability", {
47803
49086
  return batteryCapability;
47804
49087
  }
47805
49088
  });
49089
+ Object.defineProperty(exports, "buildFfmpegArgs", {
49090
+ enumerable: true,
49091
+ get: function() {
49092
+ return buildFfmpegArgs;
49093
+ }
49094
+ });
47806
49095
  Object.defineProperty(exports, "buildStreamParamsConfigSchema", {
47807
49096
  enumerable: true,
47808
49097
  get: function() {
47809
49098
  return buildStreamParamsConfigSchema;
47810
49099
  }
47811
49100
  });
49101
+ Object.defineProperty(exports, "cameraGridLayoutCapability", {
49102
+ enumerable: true,
49103
+ get: function() {
49104
+ return cameraGridLayoutCapability;
49105
+ }
49106
+ });
47812
49107
  Object.defineProperty(exports, "collectSecretConfigKeys", {
47813
49108
  enumerable: true,
47814
49109
  get: function() {
@@ -47845,6 +49140,12 @@ Object.defineProperty(exports, "dataStoreProviderCapability", {
47845
49140
  return dataStoreProviderCapability;
47846
49141
  }
47847
49142
  });
49143
+ Object.defineProperty(exports, "declarationOwnerNodeId", {
49144
+ enumerable: true,
49145
+ get: function() {
49146
+ return declarationOwnerNodeId;
49147
+ }
49148
+ });
47848
49149
  Object.defineProperty(exports, "decodeVectorBase64", {
47849
49150
  enumerable: true,
47850
49151
  get: function() {
@@ -47983,6 +49284,12 @@ Object.defineProperty(exports, "isDeviceConfigCap", {
47983
49284
  return isDeviceConfigCap;
47984
49285
  }
47985
49286
  });
49287
+ Object.defineProperty(exports, "isEvent", {
49288
+ enumerable: true,
49289
+ get: function() {
49290
+ return isEvent;
49291
+ }
49292
+ });
47986
49293
  Object.defineProperty(exports, "isLocationEnabled", {
47987
49294
  enumerable: true,
47988
49295
  get: function() {
@@ -48133,6 +49440,12 @@ Object.defineProperty(exports, "procedureAuthKey", {
48133
49440
  return procedureAuthKey;
48134
49441
  }
48135
49442
  });
49443
+ Object.defineProperty(exports, "ptzAutotrackCapability", {
49444
+ enumerable: true,
49445
+ get: function() {
49446
+ return ptzAutotrackCapability;
49447
+ }
49448
+ });
48136
49449
  Object.defineProperty(exports, "readinessKey", {
48137
49450
  enumerable: true,
48138
49451
  get: function() {
@@ -48253,6 +49566,12 @@ Object.defineProperty(exports, "storageProviderCapability", {
48253
49566
  return storageProviderCapability;
48254
49567
  }
48255
49568
  });
49569
+ Object.defineProperty(exports, "streamCatalogCapability", {
49570
+ enumerable: true,
49571
+ get: function() {
49572
+ return streamCatalogCapability;
49573
+ }
49574
+ });
48256
49575
  Object.defineProperty(exports, "streamQualityLabel", {
48257
49576
  enumerable: true,
48258
49577
  get: function() {