@camstack/addon-pipeline 1.2.65 → 1.2.67

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 (32) hide show
  1. package/dist/audio-analyzer/index.js +1 -1
  2. package/dist/audio-analyzer/index.mjs +1 -1
  3. package/dist/detection-pipeline/index.js +2 -2
  4. package/dist/detection-pipeline/index.mjs +2 -2
  5. package/dist/{dist-DKrqeIfS.js → dist-B6GWr9am.js} +151 -6
  6. package/dist/{dist-B9_C-QmC.mjs → dist-BEbyhqoG.mjs} +146 -7
  7. package/dist/{event-loop-stall-monitor-CnJ5JGA2.js → event-loop-stall-monitor-BHOEgVnP.js} +1 -1
  8. package/dist/{event-loop-stall-monitor-BucTbe9v.mjs → event-loop-stall-monitor-BNyf5yEt.mjs} +1 -1
  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 +144 -14
  12. package/dist/pipeline-runner/index.mjs +144 -14
  13. package/dist/recorder/index.js +114 -3
  14. package/dist/recorder/index.mjs +114 -3
  15. package/dist/remote-restream-BYbAsgUf.js +89 -0
  16. package/dist/remote-restream-Ci7RXNGb.mjs +66 -0
  17. package/dist/session-decode/decode-worker-child.js +1 -1
  18. package/dist/session-decode/decode-worker-child.mjs +1 -1
  19. package/dist/stream-broker/_stub.js +2 -2
  20. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BCyIjQkw.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BkKnIpb0.mjs} +2 -2
  21. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-ircnzt0s.mjs +26 -0
  22. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CJAUKSrT.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DU7u9EJt.mjs} +1 -1
  23. package/dist/stream-broker/{hostInit-DqLdTCfo.mjs → hostInit-y3zWHWoe.mjs} +2 -2
  24. package/dist/stream-broker/index.js +54 -15
  25. package/dist/stream-broker/index.mjs +54 -15
  26. package/dist/stream-broker/remoteEntry.js +1 -1
  27. package/dist/{worker-protocol-CQHY2NeB.mjs → worker-protocol-CBg9bqCs.mjs} +1 -1
  28. package/dist/{worker-protocol-CDkbdSuM.js → worker-protocol-qzaozj7t.js} +1 -1
  29. package/package.json +1 -1
  30. package/dist/remote-restream-BeHi78PZ.mjs +0 -25
  31. package/dist/remote-restream-CO36Sr30.js +0 -36
  32. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C2tu4c14.mjs +0 -26
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-DKrqeIfS.js");
6
+ const require_dist = require("../dist-B6GWr9am.js");
7
7
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
8
8
  const require_addon_utils = require("../addon-utils-CQs-AjTJ.js");
9
9
  let node_fs = require("node:fs");
@@ -1,5 +1,5 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { T as audioAnalyzerCapability, dt as hydrateSchema, et as errMsg, m as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, ot as BaseAddon, w as audioAnalysisCapability, z as mapAudioLabelToMacro } from "../dist-B9_C-QmC.mjs";
2
+ import { B as mapAudioLabelToMacro, E as audioAnalyzerCapability, T as audioAnalysisCapability, ft as hydrateSchema, m as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, st as BaseAddon, tt as errMsg } from "../dist-BEbyhqoG.mjs";
3
3
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
4
4
  import { i as downloadFile } from "../addon-utils-CZ2xo67g.mjs";
5
5
  import * as fs from "node:fs";
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-DKrqeIfS.js");
7
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-CnJ5JGA2.js");
6
+ const require_dist = require("../dist-B6GWr9am.js");
7
+ const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-BHOEgVnP.js");
8
8
  const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
9
9
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
10
10
  const require_addon_utils = require("../addon-utils-CQs-AjTJ.js");
@@ -1,6 +1,6 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { At as union, Dt as object, I as enumerateInferenceDevices, L as evaluateZoneRules, N as detectionPipelineCapability, Q as supportedRuntimes$1, S as YAMNET_TO_MACRO, W as pipelineExecutorCapability, Y as runtimeDevices$1, dt as hydrateSchema, et as errMsg, gt as parseJsonUnknown, ht as nodePin, jt as EventCategory, k as defaultDeviceFor$1, kt as string, ot as BaseAddon, t as APPLE_SA_TO_MACRO, u as DEVICE_BACKEND_TO_FORMAT, ut as createEvent, xt as array, yt as sleep } from "../dist-B9_C-QmC.mjs";
3
- import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-BucTbe9v.mjs";
2
+ import { $ as supportedRuntimes$1, A as defaultDeviceFor$1, At as string, C as YAMNET_TO_MACRO, G as pipelineExecutorCapability, L as enumerateInferenceDevices, Mt as EventCategory, Ot as object, P as detectionPipelineCapability, R as evaluateZoneRules, St as array, X as runtimeDevices$1, _t as parseJsonUnknown, bt as sleep, dt as createEvent, ft as hydrateSchema, gt as nodePin, jt as union, st as BaseAddon, t as APPLE_SA_TO_MACRO, tt as errMsg, u as DEVICE_BACKEND_TO_FORMAT } from "../dist-BEbyhqoG.mjs";
3
+ import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-BNyf5yEt.mjs";
4
4
  import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
5
5
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
6
6
  import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../addon-utils-CZ2xo67g.mjs";
@@ -8150,8 +8150,31 @@ var AdoptionJobSchema = object({
8150
8150
  error: string().nullable()
8151
8151
  });
8152
8152
  /**
8153
- * Per-camera FUNCTION SWITCHES — the one coherent on/off surface over the
8154
- * pipeline functions an operator thinks in terms of.
8153
+ * Per-camera FUNCTION SWITCHES.
8154
+ *
8155
+ * ## The aggregate group is being withdrawn — the BADGE is not (D113)
8156
+ *
8157
+ * This file shipped as "the one coherent on/off surface over the pipeline
8158
+ * functions an operator thinks in terms of". The operator's verdict on
8159
+ * 2026-08-12 was that the coherent surface bought complexity and no clarity:
8160
+ * every function already had a settings page of its own, and a second place to
8161
+ * turn it off is a second place to look. Each switch is going back to its own
8162
+ * component's original options — detection to the detection-pipeline wrapper
8163
+ * binding, audio analysis to its own, recording to `RecordingConfig.enabled`
8164
+ * (which was always first-class; the switch was a veneer over
8165
+ * `recording.setDeviceConfig`), notifications to a notification-center
8166
+ * per-device setting, the two camera planes to their own components.
8167
+ *
8168
+ * What survives is {@link composeSwitchedOff}: `CameraStatus.switchedOff`, the
8169
+ * thing that lets a status surface say DISABLED instead of BROKEN, recomposed
8170
+ * straight from the authorities with no group in the middle. That rule was
8171
+ * never about a control panel.
8172
+ *
8173
+ * Everything else here — {@link CAMERA_SWITCH_CATALOG}, {@link CameraSwitch},
8174
+ * {@link deriveCameraSwitches}, the `pipelineOrchestrator.getCameraSwitches` /
8175
+ * `setCameraSwitch` pair — is a COMPATIBILITY surface for as long as deployed
8176
+ * viewers (v1.0.305) and the admin UI still call it. It is deleted when they
8177
+ * stop; nothing new may be built on it.
8155
8178
  *
8156
8179
  * ## This file adds no state
8157
8180
  *
@@ -19571,9 +19594,16 @@ var CameraStatusSchema = object({
19571
19594
  audio: CameraAudioStatusSchema.nullable(),
19572
19595
  recording: CameraRecordingStatusSchema.nullable(),
19573
19596
  /**
19574
- * Per-camera function switches an OPERATOR has turned off
19597
+ * Per-camera functions an OPERATOR has turned off
19575
19598
  * ([D61](../../../../docs/decisions/adr-0067.md)).
19576
19599
  *
19600
+ * Composed from the AUTHORITIES themselves — the wrapper bindings,
19601
+ * `RecordingConfig.enabled`, the notification mute, the broker's audio
19602
+ * policy, the camera's own microphone — via `composeSwitchedOff`, not from
19603
+ * the deprecated `getCameraSwitches` group ([D113](../../../../docs/decisions/adr-0113.md)).
19604
+ * The badge outlives the control panel: the panel was a convenience, this is
19605
+ * the difference between a camera being off and a camera being dead.
19606
+ *
19577
19607
  * This is the difference between DISABLED and BROKEN. A camera whose
19578
19608
  * `detection` block reports zero fps and whose `switchedOff` contains
19579
19609
  * `'object-detection'` was switched off by a person; the same camera with an
@@ -25467,10 +25497,42 @@ var recordingCapability = {
25467
25497
  */
25468
25498
  /** Playback-speed multiplier for the render (1 = realtime). */
25469
25499
  var ExportSpeedSchema = number().min(.25).max(32);
25500
+ /**
25501
+ * One dense interval, in SECONDS FROM THE EXPORT'S OWN `fromMs`.
25502
+ *
25503
+ * Relative and not absolute epoch on purpose: the renderer's frame-select
25504
+ * expression sees ffmpeg's `t`, which starts at 0 for the export's source
25505
+ * playlist. Handing it absolute epochs would make every call site responsible
25506
+ * for the same subtraction, and the one that forgot would emit a filter that
25507
+ * selects nothing — silently, as a uniform timelapse.
25508
+ */
25509
+ var ExportDenseRangeSchema = object({
25510
+ fromSec: number().nonnegative(),
25511
+ toSec: number().nonnegative()
25512
+ }).refine((r) => r.toSec > r.fromSec, { message: "dense range must have toSec > fromSec" });
25513
+ /**
25514
+ * Dense-interval overlay for a timelapse: sample at `dense.everyMs` INSIDE the
25515
+ * listed ranges and at the base `everyMs` everywhere else.
25516
+ *
25517
+ * `everyMs` must be strictly smaller than the base cadence — a dense rate that
25518
+ * is not denser renders a uniform timelapse the operator believes is two-rate.
25519
+ */
25520
+ var ExportDenseSchema = object({
25521
+ everyMs: number().int().positive(),
25522
+ ranges: array(ExportDenseRangeSchema).min(1).max(200)
25523
+ });
25470
25524
  /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
25471
25525
  var ExportTimelapseSchema = object({
25472
25526
  everyMs: number().int().positive(),
25473
- outputFps: number().int().min(1).max(60).optional()
25527
+ outputFps: number().int().min(1).max(60).optional(),
25528
+ /** Optional second, FASTER rate over the intervals that matter. */
25529
+ dense: ExportDenseSchema.optional()
25530
+ }).superRefine((v, ctx) => {
25531
+ if (v.dense !== void 0 && v.dense.everyMs >= v.everyMs) ctx.addIssue({
25532
+ code: ZodIssueCode.custom,
25533
+ message: "dense.everyMs must be strictly smaller than the base everyMs",
25534
+ path: ["dense", "everyMs"]
25535
+ });
25474
25536
  });
25475
25537
  /**
25476
25538
  * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
@@ -25528,6 +25590,38 @@ var ExportDownloadSchema = object({
25528
25590
  url: string(),
25529
25591
  endpoints: array(string())
25530
25592
  });
25593
+ /**
25594
+ * Hard ceiling on ONE {@link recordingExportCapability} byte read — 50 MiB.
25595
+ *
25596
+ * Two independent reasons land on the same number, which is why it is this one
25597
+ * and not a rounder guess:
25598
+ *
25599
+ * - **Nobody would accept more.** The roomiest byte cap any notifier backend
25600
+ * declares is telegram's 50 MiB, and the degrade engine DROPS an over-cap
25601
+ * attachment outright rather than degrading it to a link. Bytes above this
25602
+ * are read, encoded and moved to be thrown away at the last step.
25603
+ * - **The envelope is unary.** A base64 payload is held whole, ~1.33× its
25604
+ * size, in the provider AND in the caller — on a hub this repo has already
25605
+ * OOM'd once (D9/D18). A bounded on-demand read at human speed is the shape
25606
+ * those records permit; an unbounded one is the shape they forbid.
25607
+ *
25608
+ * Above it the provider REFUSES with a log line rather than truncating: half a
25609
+ * video is worse than a notification that says there is no attachment.
25610
+ */
25611
+ var RECORDING_EXPORT_MAX_READ_BYTES = 50 * 1024 * 1024;
25612
+ /**
25613
+ * A finished export's bytes, inline.
25614
+ *
25615
+ * `bytes` is the DECODED length — the number the caller bounds and logs
25616
+ * against, so nobody has to infer it from the base64 length.
25617
+ */
25618
+ var ExportBytesSchema = object({
25619
+ base64: string(),
25620
+ contentType: string(),
25621
+ /** Suggested filename, extension included. */
25622
+ name: string(),
25623
+ bytes: number().int().nonnegative()
25624
+ });
25531
25625
  var recordingExportCapability = {
25532
25626
  name: "recordingExport",
25533
25627
  scope: "system",
@@ -25567,6 +25661,27 @@ var recordingExportCapability = {
25567
25661
  getDownloadUrl: method(object({ exportId: string() }), ExportDownloadSchema, {
25568
25662
  kind: "query",
25569
25663
  auth: "protected"
25664
+ }),
25665
+ /**
25666
+ * The finished file's BYTES, base64, for a caller that must republish them
25667
+ * somewhere a session-less fetcher can reach.
25668
+ *
25669
+ * `getDownloadUrl` is the right answer for a human: the download route is
25670
+ * served `access: 'authenticated'`, which a browser satisfies and a
25671
+ * notifier BACKEND does not. It answers a RELATIVE path, so it is not even
25672
+ * a URL an outside fetcher could try. This method exists for the one case
25673
+ * that needs the other thing — a scheduled timelapse whose video has to
25674
+ * become a public attachment on the notification artifact plane.
25675
+ *
25676
+ * Deliberately narrow: `ready` only (a queued, rendering, failed, expired
25677
+ * or deleted export has no file, and answering "0 bytes" for one is how a
25678
+ * caller ships an empty attachment), still inside its lifetime, and under
25679
+ * {@link RECORDING_EXPORT_MAX_READ_BYTES}. Every refusal throws with the
25680
+ * reason — none of them is silent.
25681
+ */
25682
+ readExportBytes: method(object({ exportId: string() }), ExportBytesSchema, {
25683
+ kind: "query",
25684
+ auth: "protected"
25570
25685
  })
25571
25686
  }
25572
25687
  };
@@ -30925,6 +31040,12 @@ Object.freeze({
30925
31040
  addonId: null,
30926
31041
  access: "view"
30927
31042
  },
31043
+ "recordingExport.readExportBytes": {
31044
+ capName: "recordingExport",
31045
+ capScope: "system",
31046
+ addonId: null,
31047
+ access: "view"
31048
+ },
30928
31049
  "sceneMonitor.captureReference": {
30929
31050
  capName: "scene-monitor",
30930
31051
  capScope: "device",
@@ -32342,10 +32463,28 @@ TimelapseRuleInputSchema.extend({
32342
32463
  */
32343
32464
  ownerUserId: string().optional(),
32344
32465
  /**
32345
- * Epoch-ms of the last successful generation the 1-hour re-generation
32346
- * guard's durable state (predecessor parity). Absent = never generated.
32466
+ * Epoch-ms of the NEWEST successful generation across every camera of this
32467
+ * rule. What a UI shows, and the compatibility floor for
32468
+ * {@link readTimelapseGeneratedAt}. Absent = never generated.
32347
32469
  */
32348
32470
  lastGeneratedAt: number().optional(),
32471
+ /**
32472
+ * PER-CAMERA generation state, keyed by `String(deviceId)` — the
32473
+ * re-generation guard's real durable state.
32474
+ *
32475
+ * One rule covers several cameras and each renders its own video, so a rule
32476
+ * -wide stamp is wrong in the direction that DESTROYS work: camera A
32477
+ * succeeding at 06:05 tells camera B, whose render failed, that it is
32478
+ * already done — and B's night is gone for good, because the window will not
32479
+ * come back.
32480
+ *
32481
+ * ADDITIVE, so the migration is free: a row written before this field simply
32482
+ * has no map, and {@link readTimelapseGeneratedAt} falls back to
32483
+ * {@link TimelapseRuleSchema.shape.lastGeneratedAt}. Reading an old row as
32484
+ * "never generated" would re-render and re-notify every camera of every rule
32485
+ * once, on the deploy that shipped the map.
32486
+ */
32487
+ generatedByDevice: record(string(), number()).optional(),
32349
32488
  /** userId of the caller who created the rule (server-stamped). */
32350
32489
  createdBy: string(),
32351
32490
  createdAt: number(),
@@ -33229,6 +33368,12 @@ Object.defineProperty(exports, "RATE_CONTROL_TIGHT", {
33229
33368
  return RATE_CONTROL_TIGHT;
33230
33369
  }
33231
33370
  });
33371
+ Object.defineProperty(exports, "RECORDING_EXPORT_MAX_READ_BYTES", {
33372
+ enumerable: true,
33373
+ get: function() {
33374
+ return RECORDING_EXPORT_MAX_READ_BYTES;
33375
+ }
33376
+ });
33232
33377
  Object.defineProperty(exports, "RecordingConfigSchema", {
33233
33378
  enumerable: true,
33234
33379
  get: function() {
@@ -8150,8 +8150,31 @@ var AdoptionJobSchema = object({
8150
8150
  error: string().nullable()
8151
8151
  });
8152
8152
  /**
8153
- * Per-camera FUNCTION SWITCHES — the one coherent on/off surface over the
8154
- * pipeline functions an operator thinks in terms of.
8153
+ * Per-camera FUNCTION SWITCHES.
8154
+ *
8155
+ * ## The aggregate group is being withdrawn — the BADGE is not (D113)
8156
+ *
8157
+ * This file shipped as "the one coherent on/off surface over the pipeline
8158
+ * functions an operator thinks in terms of". The operator's verdict on
8159
+ * 2026-08-12 was that the coherent surface bought complexity and no clarity:
8160
+ * every function already had a settings page of its own, and a second place to
8161
+ * turn it off is a second place to look. Each switch is going back to its own
8162
+ * component's original options — detection to the detection-pipeline wrapper
8163
+ * binding, audio analysis to its own, recording to `RecordingConfig.enabled`
8164
+ * (which was always first-class; the switch was a veneer over
8165
+ * `recording.setDeviceConfig`), notifications to a notification-center
8166
+ * per-device setting, the two camera planes to their own components.
8167
+ *
8168
+ * What survives is {@link composeSwitchedOff}: `CameraStatus.switchedOff`, the
8169
+ * thing that lets a status surface say DISABLED instead of BROKEN, recomposed
8170
+ * straight from the authorities with no group in the middle. That rule was
8171
+ * never about a control panel.
8172
+ *
8173
+ * Everything else here — {@link CAMERA_SWITCH_CATALOG}, {@link CameraSwitch},
8174
+ * {@link deriveCameraSwitches}, the `pipelineOrchestrator.getCameraSwitches` /
8175
+ * `setCameraSwitch` pair — is a COMPATIBILITY surface for as long as deployed
8176
+ * viewers (v1.0.305) and the admin UI still call it. It is deleted when they
8177
+ * stop; nothing new may be built on it.
8155
8178
  *
8156
8179
  * ## This file adds no state
8157
8180
  *
@@ -19571,9 +19594,16 @@ var CameraStatusSchema = object({
19571
19594
  audio: CameraAudioStatusSchema.nullable(),
19572
19595
  recording: CameraRecordingStatusSchema.nullable(),
19573
19596
  /**
19574
- * Per-camera function switches an OPERATOR has turned off
19597
+ * Per-camera functions an OPERATOR has turned off
19575
19598
  * ([D61](../../../../docs/decisions/adr-0067.md)).
19576
19599
  *
19600
+ * Composed from the AUTHORITIES themselves — the wrapper bindings,
19601
+ * `RecordingConfig.enabled`, the notification mute, the broker's audio
19602
+ * policy, the camera's own microphone — via `composeSwitchedOff`, not from
19603
+ * the deprecated `getCameraSwitches` group ([D113](../../../../docs/decisions/adr-0113.md)).
19604
+ * The badge outlives the control panel: the panel was a convenience, this is
19605
+ * the difference between a camera being off and a camera being dead.
19606
+ *
19577
19607
  * This is the difference between DISABLED and BROKEN. A camera whose
19578
19608
  * `detection` block reports zero fps and whose `switchedOff` contains
19579
19609
  * `'object-detection'` was switched off by a person; the same camera with an
@@ -25467,10 +25497,42 @@ var recordingCapability = {
25467
25497
  */
25468
25498
  /** Playback-speed multiplier for the render (1 = realtime). */
25469
25499
  var ExportSpeedSchema = number().min(.25).max(32);
25500
+ /**
25501
+ * One dense interval, in SECONDS FROM THE EXPORT'S OWN `fromMs`.
25502
+ *
25503
+ * Relative and not absolute epoch on purpose: the renderer's frame-select
25504
+ * expression sees ffmpeg's `t`, which starts at 0 for the export's source
25505
+ * playlist. Handing it absolute epochs would make every call site responsible
25506
+ * for the same subtraction, and the one that forgot would emit a filter that
25507
+ * selects nothing — silently, as a uniform timelapse.
25508
+ */
25509
+ var ExportDenseRangeSchema = object({
25510
+ fromSec: number().nonnegative(),
25511
+ toSec: number().nonnegative()
25512
+ }).refine((r) => r.toSec > r.fromSec, { message: "dense range must have toSec > fromSec" });
25513
+ /**
25514
+ * Dense-interval overlay for a timelapse: sample at `dense.everyMs` INSIDE the
25515
+ * listed ranges and at the base `everyMs` everywhere else.
25516
+ *
25517
+ * `everyMs` must be strictly smaller than the base cadence — a dense rate that
25518
+ * is not denser renders a uniform timelapse the operator believes is two-rate.
25519
+ */
25520
+ var ExportDenseSchema = object({
25521
+ everyMs: number().int().positive(),
25522
+ ranges: array(ExportDenseRangeSchema).min(1).max(200)
25523
+ });
25470
25524
  /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
25471
25525
  var ExportTimelapseSchema = object({
25472
25526
  everyMs: number().int().positive(),
25473
- outputFps: number().int().min(1).max(60).optional()
25527
+ outputFps: number().int().min(1).max(60).optional(),
25528
+ /** Optional second, FASTER rate over the intervals that matter. */
25529
+ dense: ExportDenseSchema.optional()
25530
+ }).superRefine((v, ctx) => {
25531
+ if (v.dense !== void 0 && v.dense.everyMs >= v.everyMs) ctx.addIssue({
25532
+ code: ZodIssueCode.custom,
25533
+ message: "dense.everyMs must be strictly smaller than the base everyMs",
25534
+ path: ["dense", "everyMs"]
25535
+ });
25474
25536
  });
25475
25537
  /**
25476
25538
  * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
@@ -25528,6 +25590,38 @@ var ExportDownloadSchema = object({
25528
25590
  url: string(),
25529
25591
  endpoints: array(string())
25530
25592
  });
25593
+ /**
25594
+ * Hard ceiling on ONE {@link recordingExportCapability} byte read — 50 MiB.
25595
+ *
25596
+ * Two independent reasons land on the same number, which is why it is this one
25597
+ * and not a rounder guess:
25598
+ *
25599
+ * - **Nobody would accept more.** The roomiest byte cap any notifier backend
25600
+ * declares is telegram's 50 MiB, and the degrade engine DROPS an over-cap
25601
+ * attachment outright rather than degrading it to a link. Bytes above this
25602
+ * are read, encoded and moved to be thrown away at the last step.
25603
+ * - **The envelope is unary.** A base64 payload is held whole, ~1.33× its
25604
+ * size, in the provider AND in the caller — on a hub this repo has already
25605
+ * OOM'd once (D9/D18). A bounded on-demand read at human speed is the shape
25606
+ * those records permit; an unbounded one is the shape they forbid.
25607
+ *
25608
+ * Above it the provider REFUSES with a log line rather than truncating: half a
25609
+ * video is worse than a notification that says there is no attachment.
25610
+ */
25611
+ var RECORDING_EXPORT_MAX_READ_BYTES = 50 * 1024 * 1024;
25612
+ /**
25613
+ * A finished export's bytes, inline.
25614
+ *
25615
+ * `bytes` is the DECODED length — the number the caller bounds and logs
25616
+ * against, so nobody has to infer it from the base64 length.
25617
+ */
25618
+ var ExportBytesSchema = object({
25619
+ base64: string(),
25620
+ contentType: string(),
25621
+ /** Suggested filename, extension included. */
25622
+ name: string(),
25623
+ bytes: number().int().nonnegative()
25624
+ });
25531
25625
  var recordingExportCapability = {
25532
25626
  name: "recordingExport",
25533
25627
  scope: "system",
@@ -25567,6 +25661,27 @@ var recordingExportCapability = {
25567
25661
  getDownloadUrl: method(object({ exportId: string() }), ExportDownloadSchema, {
25568
25662
  kind: "query",
25569
25663
  auth: "protected"
25664
+ }),
25665
+ /**
25666
+ * The finished file's BYTES, base64, for a caller that must republish them
25667
+ * somewhere a session-less fetcher can reach.
25668
+ *
25669
+ * `getDownloadUrl` is the right answer for a human: the download route is
25670
+ * served `access: 'authenticated'`, which a browser satisfies and a
25671
+ * notifier BACKEND does not. It answers a RELATIVE path, so it is not even
25672
+ * a URL an outside fetcher could try. This method exists for the one case
25673
+ * that needs the other thing — a scheduled timelapse whose video has to
25674
+ * become a public attachment on the notification artifact plane.
25675
+ *
25676
+ * Deliberately narrow: `ready` only (a queued, rendering, failed, expired
25677
+ * or deleted export has no file, and answering "0 bytes" for one is how a
25678
+ * caller ships an empty attachment), still inside its lifetime, and under
25679
+ * {@link RECORDING_EXPORT_MAX_READ_BYTES}. Every refusal throws with the
25680
+ * reason — none of them is silent.
25681
+ */
25682
+ readExportBytes: method(object({ exportId: string() }), ExportBytesSchema, {
25683
+ kind: "query",
25684
+ auth: "protected"
25570
25685
  })
25571
25686
  }
25572
25687
  };
@@ -30925,6 +31040,12 @@ Object.freeze({
30925
31040
  addonId: null,
30926
31041
  access: "view"
30927
31042
  },
31043
+ "recordingExport.readExportBytes": {
31044
+ capName: "recordingExport",
31045
+ capScope: "system",
31046
+ addonId: null,
31047
+ access: "view"
31048
+ },
30928
31049
  "sceneMonitor.captureReference": {
30929
31050
  capName: "scene-monitor",
30930
31051
  capScope: "device",
@@ -32342,10 +32463,28 @@ TimelapseRuleInputSchema.extend({
32342
32463
  */
32343
32464
  ownerUserId: string().optional(),
32344
32465
  /**
32345
- * Epoch-ms of the last successful generation the 1-hour re-generation
32346
- * guard's durable state (predecessor parity). Absent = never generated.
32466
+ * Epoch-ms of the NEWEST successful generation across every camera of this
32467
+ * rule. What a UI shows, and the compatibility floor for
32468
+ * {@link readTimelapseGeneratedAt}. Absent = never generated.
32347
32469
  */
32348
32470
  lastGeneratedAt: number().optional(),
32471
+ /**
32472
+ * PER-CAMERA generation state, keyed by `String(deviceId)` — the
32473
+ * re-generation guard's real durable state.
32474
+ *
32475
+ * One rule covers several cameras and each renders its own video, so a rule
32476
+ * -wide stamp is wrong in the direction that DESTROYS work: camera A
32477
+ * succeeding at 06:05 tells camera B, whose render failed, that it is
32478
+ * already done — and B's night is gone for good, because the window will not
32479
+ * come back.
32480
+ *
32481
+ * ADDITIVE, so the migration is free: a row written before this field simply
32482
+ * has no map, and {@link readTimelapseGeneratedAt} falls back to
32483
+ * {@link TimelapseRuleSchema.shape.lastGeneratedAt}. Reading an old row as
32484
+ * "never generated" would re-render and re-notify every camera of every rule
32485
+ * once, on the deploy that shipped the map.
32486
+ */
32487
+ generatedByDevice: record(string(), number()).optional(),
32349
32488
  /** userId of the caller who created the rule (server-stamped). */
32350
32489
  createdBy: string(),
32351
32490
  createdAt: number(),
@@ -33073,4 +33212,4 @@ function enumerateInferenceDevices(hw) {
33073
33212
  return out;
33074
33213
  }
33075
33214
  //#endregion
33076
- export { webrtcSessionCapability as $, defineCustomActions as A, union as At, maskUrlCredentials as B, addonWidgetsSourceCapability as C, discriminatedUnion as Ct, createHwAccelCache as D, object as Dt, cameraStreamsCapability as E, number as Et, egressTransportFromRequest as F, pipelineRunnerCapability as G, pickDetailCropConvention as H, enumerateInferenceDevices as I, resolveEgressDecodeHwAccel as J, recordingCapability as K, evaluateZoneRules as L, deriveRecordingMode as M, detectionPipelineCapability as N, customAction as O, record as Ot, egressTranscodeSharingKey as P, supportedRuntimes as Q, hfModelUrl as R, YAMNET_TO_MACRO as S, boolean as St, audioAnalyzerCapability as T, literal as Tt, pickNativeLeaseOverride as U, motionDetectionCapability as V, pipelineExecutorCapability as W, storageEvictableCapability as X, runtimeDevices as Y, streamBrokerCapability as Z, OpsLogEntrySchema as _, parseProfileBrokerId as _t, COCO_TO_MACRO as a, isSoftwareDecode as at, RecordingConfigSchema as b, _enum as bt, DEFAULT_EVENTS_BAND_BUFFER_SEC as c, DeviceFeature as ct, EVENT_PAD_MS as d, hydrateSchema as dt, errMsg as et, EncodeProfileSchema as f, isEvent as ft, NativeLeaseSettingsSchema as g, parseJsonUnknown as gt, NativeLeaseAdmissionSchema as h, nodePin as ht, COCO_80_LABELS as i, invocationFromEncodeProfile as it, deriveDetailCropRect as j, EventCategory as jt, defaultDeviceFor as k, string as kt, DEFAULT_NATIVE_LEASE_SETTINGS as l, DeviceType as lt, HF_BASE_URL as m, makeSourceBrokerId as mt, AUDIO_BACKEND_CHOICES as n, Fmp4BoxSplitter as nt, DEFAULT_AUDIO_ANALYZER_CONFIG as o, BaseAddon as ot, ExportRecordSchema as p, makeProfileBrokerId as pt, recordingExportCapability as q, AUDIO_MACRO_LABELS as r, buildFfmpegArgs as rt, DEFAULT_DETAIL_CROP_CONVENTION as s, CAM_PROFILE_ORDER as st, APPLE_SA_TO_MACRO as t, AUDIO_PRESETS as tt, DEVICE_BACKEND_TO_FORMAT as u, createEvent as ut, RATE_CONTROL_RELAXED as v, selectAssignedProfileSlots as vt, audioAnalysisCapability as w, lazy as wt, RingBuffer as x, array as xt, RATE_CONTROL_TIGHT as y, sleep as yt, mapAudioLabelToMacro as z };
33215
+ export { supportedRuntimes as $, defaultDeviceFor as A, string as At, mapAudioLabelToMacro as B, YAMNET_TO_MACRO as C, boolean as Ct, cameraStreamsCapability as D, number as Dt, audioAnalyzerCapability as E, literal as Et, egressTranscodeSharingKey as F, pipelineExecutorCapability as G, motionDetectionCapability as H, egressTransportFromRequest as I, recordingExportCapability as J, pipelineRunnerCapability as K, enumerateInferenceDevices as L, deriveDetailCropRect as M, EventCategory as Mt, deriveRecordingMode as N, createHwAccelCache as O, object as Ot, detectionPipelineCapability as P, streamBrokerCapability as Q, evaluateZoneRules as R, RingBuffer as S, array as St, audioAnalysisCapability as T, lazy as Tt, pickDetailCropConvention as U, maskUrlCredentials as V, pickNativeLeaseOverride as W, runtimeDevices as X, resolveEgressDecodeHwAccel as Y, storageEvictableCapability as Z, OpsLogEntrySchema as _, parseJsonUnknown as _t, COCO_TO_MACRO as a, invocationFromEncodeProfile as at, RECORDING_EXPORT_MAX_READ_BYTES as b, sleep as bt, DEFAULT_EVENTS_BAND_BUFFER_SEC as c, CAM_PROFILE_ORDER as ct, EVENT_PAD_MS as d, createEvent as dt, webrtcSessionCapability as et, EncodeProfileSchema as f, hydrateSchema as ft, NativeLeaseSettingsSchema as g, nodePin as gt, NativeLeaseAdmissionSchema as h, makeSourceBrokerId as ht, COCO_80_LABELS as i, buildFfmpegArgs as it, defineCustomActions as j, union as jt, customAction as k, record as kt, DEFAULT_NATIVE_LEASE_SETTINGS as l, DeviceFeature as lt, HF_BASE_URL as m, makeProfileBrokerId as mt, AUDIO_BACKEND_CHOICES as n, AUDIO_PRESETS as nt, DEFAULT_AUDIO_ANALYZER_CONFIG as o, isSoftwareDecode as ot, ExportRecordSchema as p, isEvent as pt, recordingCapability as q, AUDIO_MACRO_LABELS as r, Fmp4BoxSplitter as rt, DEFAULT_DETAIL_CROP_CONVENTION as s, BaseAddon as st, APPLE_SA_TO_MACRO as t, errMsg as tt, DEVICE_BACKEND_TO_FORMAT as u, DeviceType as ut, RATE_CONTROL_RELAXED as v, parseProfileBrokerId as vt, addonWidgetsSourceCapability as w, discriminatedUnion as wt, RecordingConfigSchema as x, _enum as xt, RATE_CONTROL_TIGHT as y, selectAssignedProfileSlots as yt, hfModelUrl as z };
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-DKrqeIfS.js");
1
+ const require_dist = require("./dist-B6GWr9am.js");
2
2
  let node_perf_hooks = require("node:perf_hooks");
3
3
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
4
  var HF_REPO = "camstack/camstack-models";
@@ -1,4 +1,4 @@
1
- import { R as hfModelUrl, a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS } from "./dist-B9_C-QmC.mjs";
1
+ import { a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS, z as hfModelUrl } from "./dist-BEbyhqoG.mjs";
2
2
  import { PerformanceObserver } from "node:perf_hooks";
3
3
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
4
  var HF_REPO = "camstack/camstack-models";
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-DKrqeIfS.js");
5
+ const require_dist = require("../dist-B6GWr9am.js");
6
6
  const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { L as evaluateZoneRules, V as motionDetectionCapability, dt as hydrateSchema, lt as DeviceType, ot as BaseAddon } from "../dist-B9_C-QmC.mjs";
1
+ import { H as motionDetectionCapability, R as evaluateZoneRules, ft as hydrateSchema, st as BaseAddon, ut as DeviceType } from "../dist-BEbyhqoG.mjs";
2
2
  import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";