@camstack/types 1.2.187 → 1.2.189

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 (35) hide show
  1. package/dist/addon.js +2 -2
  2. package/dist/addon.mjs +2 -2
  3. package/dist/camera/stream-pick.d.ts +71 -0
  4. package/dist/{canonical-hash-DNV8S5ET.js → canonical-hash-9lydiEzx.js} +2 -0
  5. package/dist/{canonical-hash-rO1sRmEK.mjs → canonical-hash-Ch5bSjFG.mjs} +2 -0
  6. package/dist/capabilities/camera-streams.cap.d.ts +10 -0
  7. package/dist/capabilities/index.d.ts +4 -2
  8. package/dist/capabilities/notification-rules.cap.d.ts +48 -0
  9. package/dist/capabilities/osd-manager.cap.d.ts +18 -0
  10. package/dist/capabilities/stream-broker.cap.d.ts +41 -0
  11. package/dist/capabilities/stream-signals.cap.d.ts +80 -0
  12. package/dist/capabilities/webrtc-session.cap.d.ts +1 -0
  13. package/dist/encode-profile.d.ts +2 -0
  14. package/dist/enums/event-category.d.ts +14 -0
  15. package/dist/enums.js +1 -1
  16. package/dist/enums.mjs +1 -1
  17. package/dist/{event-category-ZyX6jcse.mjs → event-category-BVDXG4tB.mjs} +14 -0
  18. package/dist/{event-category-yW4s1MGD.js → event-category-BVfsrBYA.js} +14 -0
  19. package/dist/ffmpeg/invocation.d.ts +2 -0
  20. package/dist/generated/addon-api.d.ts +22 -0
  21. package/dist/generated/capability-router-map.d.ts +5 -2
  22. package/dist/generated/device-local-state.d.ts +3 -0
  23. package/dist/generated/device-proxy.d.ts +4 -1
  24. package/dist/generated/device-scoped-caps.d.ts +1 -1
  25. package/dist/generated/method-access-map.d.ts +1 -1
  26. package/dist/generated/method-device-selectors.d.ts +2 -2
  27. package/dist/generated/runtime-state-durability.d.ts +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +389 -58
  30. package/dist/index.mjs +384 -59
  31. package/dist/node.js +1 -1
  32. package/dist/node.mjs +1 -1
  33. package/dist/{sleep-DXsYD0RX.js → sleep-D7dV-FZB.js} +6 -2
  34. package/dist/{sleep-DBKu2-U5.mjs → sleep-DEuj7E3j.mjs} +6 -2
  35. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-DXsYD0RX.js");
3
- const require_event_category = require("./event-category-yW4s1MGD.js");
4
- const require_canonical_hash = require("./canonical-hash-DNV8S5ET.js");
2
+ const require_sleep = require("./sleep-D7dV-FZB.js");
3
+ const require_event_category = require("./event-category-BVfsrBYA.js");
4
+ const require_canonical_hash = require("./canonical-hash-9lydiEzx.js");
5
5
  const require_enums = require("./enums.js");
6
6
  const require_err_msg = require("./err-msg-COpsHMw2.js");
7
7
  let zod = require("zod");
@@ -321,7 +321,23 @@ var VideoEncodeSchema = zod.z.object({
321
321
  "zerolatency",
322
322
  "film",
323
323
  "animation"
324
- ]).optional()
324
+ ]).optional(),
325
+ /**
326
+ * ONE slice per access unit.
327
+ *
328
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
329
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
330
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
331
+ * frame-end per frame: the first pictures render and the video then freezes
332
+ * for good while the audio, on its own plane, plays on. Measured on this hub
333
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
334
+ *
335
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
336
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
337
+ * consumers meaning the same thing spelled differently would stop sharing one
338
+ * child. Absent means "whatever the encoder does" — today's behaviour.
339
+ */
340
+ singleSlicePerFrame: zod.z.boolean().optional()
325
341
  });
326
342
  var AudioEncodeSchema = zod.z.union([zod.z.literal("passthrough"), zod.z.object({
327
343
  codec: zod.z.enum([
@@ -378,6 +394,84 @@ function encodeProfileFromStreamShape(stream) {
378
394
  };
379
395
  }
380
396
  //#endregion
397
+ //#region src/camera/stream-pick.ts
398
+ /**
399
+ * THE target rule: which of two sizes serves a consumer better.
400
+ *
401
+ * "Prefer at or above the target so the consumer downscales instead of
402
+ * upscaling; among those the SMALLEST, because the extra pixels are thrown
403
+ * away and the decode is not; among the rest the LARGEST, because it is the
404
+ * closest we can get." One sentence, and until now it was written twice — once
405
+ * here in lines, once in `stream-selection.ts` in pixels, for the same two
406
+ * exporters. The two agreed, which is luck, not a property.
407
+ *
408
+ * The `size` is whatever measure the caller ranks by: the cap ranks by LINES
409
+ * (a consumer's requirement is stated in p), the RTSP picker by PIXELS (it
410
+ * compares entries of possibly different aspect). The rule is the same either
411
+ * way and the measure is the caller's business.
412
+ *
413
+ * Returns a sort delta: negative when `a` should come first.
414
+ */
415
+ function compareToTarget(a, b, target) {
416
+ const aReaches = a >= target;
417
+ if (aReaches !== b >= target) return aReaches ? -1 : 1;
418
+ return aReaches ? a - b : b - a;
419
+ }
420
+ /** `h265` and `hevc` are one codec spelled two ways, and a camera publishes
421
+ * whichever its vendor felt like. */
422
+ function canonicalCodec(value) {
423
+ const v = value.toLowerCase().replace(/[.\s-]/g, "");
424
+ if (v === "hevc" || v === "hvc1" || v === "hev1") return "h265";
425
+ if (v === "avc" || v === "avc1") return "h264";
426
+ return v;
427
+ }
428
+ function codecIsAccepted(codec, accept) {
429
+ if (accept.length === 0) return true;
430
+ if (codec === void 0) return false;
431
+ const c = canonicalCodec(codec);
432
+ return accept.some((a) => canonicalCodec(a) === c);
433
+ }
434
+ function providerRank(camStreamId, preferred) {
435
+ for (let i = 0; i < preferred.length; i += 1) if (camStreamId.startsWith(preferred[i])) return i;
436
+ return preferred.length;
437
+ }
438
+ /**
439
+ * Order the candidates and return the winner, or `null` when none qualifies.
440
+ *
441
+ * The order, and every term is there for a measured reason:
442
+ *
443
+ * 1. **A stream we can take AS IS beats one we must re-encode.** A transcode
444
+ * is a decode plus an encode for as long as the session lasts, and on this
445
+ * hub a concurrent HEVC decode is what exhausted the VAAPI device.
446
+ * 2. **Provider preference**, unchanged — a native dial beats a relayed one.
447
+ * 3. **The target**, when the consumer named one: the smallest stream that
448
+ * reaches it, else the tallest that does not. Absent, the caller's
449
+ * `resolutionPreference` decides, exactly as before.
450
+ */
451
+ function rankStreams(candidates, ranking) {
452
+ const usable = candidates.filter((c) => ranking.allowTranscode || codecIsAccepted(c.codec, ranking.acceptCodecs));
453
+ if (usable.length === 0) return null;
454
+ const target = ranking.targetHeight;
455
+ const scored = usable.map((candidate) => ({
456
+ candidate,
457
+ transcodes: !codecIsAccepted(candidate.codec, ranking.acceptCodecs),
458
+ provider: providerRank(candidate.camStreamId, ranking.preferredProviders),
459
+ height: candidate.height ?? 0
460
+ }));
461
+ scored.sort((a, b) => {
462
+ if (a.transcodes !== b.transcodes) return a.transcodes ? 1 : -1;
463
+ if (a.provider !== b.provider) return a.provider - b.provider;
464
+ if (target !== void 0) return compareToTarget(a.height, b.height, target);
465
+ return ranking.resolutionPreference === "highest" ? b.height - a.height : a.height - b.height;
466
+ });
467
+ const winner = scored[0];
468
+ if (winner === void 0) return null;
469
+ return {
470
+ candidate: winner.candidate,
471
+ transcodes: winner.transcodes
472
+ };
473
+ }
474
+ //#endregion
381
475
  //#region src/ffmpeg/encode-defaults.ts
382
476
  /**
383
477
  * The shape every live egress starts from: H.264 Baseline 3.1 at 720p25.
@@ -443,31 +537,10 @@ var WEBRTC_EGRESS_PROFILE = {
443
537
  var ALEXA_EGRESS_PROFILE = {
444
538
  ...BASE_LIVE_EGRESS_PROFILE,
445
539
  audio: { codec: "opus" },
446
- /**
447
- * ONE slice per access unit.
448
- *
449
- * `-tune zerolatency` turns on x264's sliced threads, and every access unit
450
- * came out of this encode as FIVE slices — measured on the hub 2026-09-13 on
451
- * the exact argv this path spawns, and reduced to one by this single flag.
452
- *
453
- * Five slices share one RTP timestamp and carry one marker bit, so a
454
- * libwebrtc depacketiser sees five frame-starts and one frame-end per frame.
455
- * The Echo renders the first pictures and then freezes for good, while the
456
- * audio — a separate plane — plays on. It is the only structural property
457
- * that survived the elimination: the same freeze reproduced at 2304×1296
458
- * HEVC with 217 KB keyframes AND at 640×360 H.264 with 67 KB keyframes and a
459
- * fifth of the bitrate, so source, resolution, bitrate, keyframe burst,
460
- * hardware decode, parameter sets, drift and bitstream conformance are all
461
- * out — each ruled out by a measurement, not by elimination.
462
- *
463
- * The tune STAYS: it also drops the lookahead and the B-frames, which a live
464
- * path wants. Only the slicing is unwanted, and this is the flag that says so.
465
- *
466
- * Scoped to Alexa deliberately. HomeKit and the browser transcode inherit
467
- * {@link BASE_LIVE_EGRESS_PROFILE}; whether they want the same shape is a
468
- * question for their own evidence, and the browser has not been measured.
469
- */
470
- outputArgs: ["-x264-params", "sliced-threads=0"]
540
+ video: {
541
+ ...BASE_LIVE_EGRESS_PROFILE.video,
542
+ singleSlicePerFrame: true
543
+ }
471
544
  };
472
545
  /** VBV window for a consumer whose budget is enforced per second (HomeKit). */
473
546
  var RATE_CONTROL_TIGHT = {
@@ -610,7 +683,8 @@ function canonicalVideo(video) {
610
683
  gopFrames: video.gopFrames ?? -1,
611
684
  bf: video.bf ?? -1,
612
685
  preset: video.preset ?? UNSET,
613
- tune: video.tune ?? UNSET
686
+ tune: video.tune ?? UNSET,
687
+ singleSlicePerFrame: video.singleSlicePerFrame === void 0 ? UNSET : String(video.singleSlicePerFrame)
614
688
  };
615
689
  }
616
690
  function canonicalAudio(audio) {
@@ -4523,6 +4597,47 @@ function reducePoints(points, bucketMs, origin) {
4523
4597
  }
4524
4598
  //#endregion
4525
4599
  //#region src/stream-selection.ts
4600
+ /**
4601
+ * Shared stream-selection picker for export addons.
4602
+ *
4603
+ * Both `addon-export-alexa` and `addon-export-hap` resolve the
4604
+ * operator's `streamPreference` setting against the broker's RTSP
4605
+ * entries before dialling a camera. Each addon used to roll its own:
4606
+ * Alexa had `pickPreferredRtspEntry` under `handlers/`, HAP had an
4607
+ * inline `"prefer /mid, then /high, then first"` chain inside its
4608
+ * camera-streams mapper. The chains drifted (Alexa honoured
4609
+ * `streamPreference`, HAP didn't; only Alexa got the smart
4610
+ * resolution-targeted auto-pick) and the cross-addon import rule
4611
+ * blocks deduping in either direction.
4612
+ *
4613
+ * Lifting the picker into `@camstack/types` lets every exporter call
4614
+ * the same code path with the consumer's target resolution baked into
4615
+ * the call site — Alexa passes 1280×720, HomeKit passes 1920×1080,
4616
+ * the picker handles the rest.
4617
+ *
4618
+ * Selection algorithm:
4619
+ * - pref === '<profile>' → profile-keyed entries (from
4620
+ * `getProfileRtspEntries`) match on `e.profile`;
4621
+ * legacy raw entries match on the brokerId
4622
+ * suffix (`brokerId` ENDS WITH `/${pref}`, shape
4623
+ * `${deviceId}/${camStreamId}`). Addons store the
4624
+ * bare profile/camStream id in their
4625
+ * `ExportDeviceSettings.streamPreference`. Falls
4626
+ * back to 'auto' when no enabled entry matches
4627
+ * either way.
4628
+ * - pref === 'auto' → when `targetResolution` is given, pick the
4629
+ * entry whose source resolution is closest to
4630
+ * it (prefer ≥ target so the consumer
4631
+ * downscales instead of upscaling). Without a
4632
+ * target, fall back to first-enabled. If no
4633
+ * entry advertises a resolution at all
4634
+ * (broker hasn't probed yet), also fall back
4635
+ * to first-enabled so cold start still serves.
4636
+ *
4637
+ * Returns `null` when nothing is publishable — callers fold that into
4638
+ * the consumer's "no stream" error (Alexa: STREAM_TYPE_UNSUPPORTED;
4639
+ * HAP: a thrown error inside `startFfmpegForSession`).
4640
+ */
4526
4641
  function pickPreferredRtspEntry(entries, pref, deviceId, options = {}) {
4527
4642
  if (entries.length === 0) return null;
4528
4643
  const prefix = `${deviceId}/`;
@@ -4552,30 +4667,23 @@ function pickPreferredRtspEntry(entries, pref, deviceId, options = {}) {
4552
4667
  return toPicked(eligible[0]);
4553
4668
  }
4554
4669
  /**
4555
- * Pick the entry whose `resolution` is closest to `target`. Prefer
4556
- * entries ≥ target (downscale is cheap at the consumer; upscale is
4557
- * lossy). Among the rest pick the LARGEST still ≤ target so the
4558
- * consumer gets the best feasible quality. "Closeness" is the
4559
- * `width × height` pixel delta.
4670
+ * Pick the entry whose `resolution` is closest to `target`, by the ONE target
4671
+ * rule ({@link compareToTarget}): prefer at or above the target so the consumer
4672
+ * downscales instead of upscaling; among those the smallest; among the rest the
4673
+ * largest.
4674
+ *
4675
+ * This function used to implement that rule itself, in pixels, while
4676
+ * `rankStreams` implemented it again in lines — the same sentence written twice
4677
+ * for the same two exporters, agreeing by luck rather than by construction.
4678
+ * Only the MEASURE is this picker's own: it compares whole pixel counts because
4679
+ * its entries can differ in aspect, where the cap's requirement is stated in p.
4560
4680
  */
4561
4681
  function pickClosestResolution(entries, target) {
4562
4682
  const targetPixels = target.width * target.height;
4563
- let bestAbove;
4564
- let bestBelow;
4565
- for (const e of entries) {
4566
- if (e.resolution === void 0) continue;
4567
- const pixels = e.resolution.width * e.resolution.height;
4568
- if (pixels >= targetPixels) {
4569
- if (!bestAbove || pixels < bestAbove.pixels) bestAbove = {
4570
- entry: e,
4571
- pixels
4572
- };
4573
- } else if (!bestBelow || pixels > bestBelow.pixels) bestBelow = {
4574
- entry: e,
4575
- pixels
4576
- };
4577
- }
4578
- return bestAbove?.entry ?? bestBelow?.entry;
4683
+ return [...entries.flatMap((entry) => entry.resolution === void 0 ? [] : [{
4684
+ entry,
4685
+ pixels: entry.resolution.width * entry.resolution.height
4686
+ }])].sort((a, b) => compareToTarget(a.pixels, b.pixels, targetPixels))[0]?.entry;
4579
4687
  }
4580
4688
  //#endregion
4581
4689
  //#region src/types/model-variant-groups.ts
@@ -8203,6 +8311,74 @@ var cameraPipelineConfigCapability = {
8203
8311
  methods: {}
8204
8312
  };
8205
8313
  //#endregion
8314
+ //#region src/capabilities/stream-signals.cap.ts
8315
+ /**
8316
+ * The signals a device can emit to WAKE its own stream.
8317
+ *
8318
+ * A camera whose stream is built on demand sleeps until something asks for it,
8319
+ * and "something" cannot be a consumer that is merely attached — a Frigate-style
8320
+ * puller holds a session open for ever, and treating that as demand would keep
8321
+ * a battery camera awake for ever, which is the whole thing the battery is for
8322
+ * (D173). So the wake has to come from the CAMERA: an event it noticed by
8323
+ * itself, with no stream running.
8324
+ *
8325
+ * ## The vocabulary is the PROVIDER'S, not ours
8326
+ *
8327
+ * Like `consumables`, this cap declares no vocabulary of its own. A provider
8328
+ * names each signal with a `code` it chooses and a `label` an operator reads.
8329
+ * Reolink offers motion and camera-native detection; another provider may offer
8330
+ * a tamper, a doorbell press, a PIR, or something no camera in this fleet has
8331
+ * yet. A fixed enum here would mean every new signal is a framework release.
8332
+ *
8333
+ * It is deliberately NOT derived from the caps a device already binds. Whether
8334
+ * a camera CAN push firmware motion is expressed by `motionSources` containing
8335
+ * `'onboard'`, and whether it does AI on-camera by the `native-object-detection`
8336
+ * binding — but both answer "what drives the detection pipeline", which is a
8337
+ * different question from "what may wake a sleeping stream". A camera can do
8338
+ * the first and not be trusted with the second, and the operator picks per
8339
+ * camera. Two questions, two authorities.
8340
+ *
8341
+ * ## Availability is not permission
8342
+ *
8343
+ * `listSignals` says what the device CAN emit. Whether a given signal actually
8344
+ * wakes the stream is the operator's per-camera choice, held by the broker
8345
+ * alongside the cooldown — see the stream-broker cap's wake settings. A
8346
+ * provider declaring a signal is not a provider enabling it.
8347
+ */
8348
+ /** One signal a device can emit. */
8349
+ var StreamSignalSchema = zod.z.object({
8350
+ /** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
8351
+ code: zod.z.string().min(1),
8352
+ /** What an operator reads in the picker. The provider's own wording. */
8353
+ label: zod.z.string().min(1),
8354
+ /**
8355
+ * Whether the provider recommends this signal ON when a camera is first set
8356
+ * up. A provider knows which of its signals are cheap and reliable; an
8357
+ * operator should not have to discover that by trial. Reolink recommends
8358
+ * both of its own.
8359
+ */
8360
+ recommended: zod.z.boolean()
8361
+ });
8362
+ var StreamSignalsStatusSchema = zod.z.object({
8363
+ signals: zod.z.array(StreamSignalSchema),
8364
+ lastFetchedAt: zod.z.number()
8365
+ });
8366
+ var streamSignalsCapability = {
8367
+ name: "stream-signals",
8368
+ scope: "device",
8369
+ deviceNative: true,
8370
+ mode: "singleton",
8371
+ deviceTypes: Object.values(require_sleep.DeviceType),
8372
+ runtimeState: StreamSignalsStatusSchema,
8373
+ methods: {
8374
+ /**
8375
+ * What this device can emit. Empty is a valid and common answer — most
8376
+ * cameras have nothing to offer here, and an empty list is what makes the
8377
+ * broker's picker show nothing rather than a false choice.
8378
+ */
8379
+ listSignals: require_sleep.method(zod.z.void(), zod.z.array(StreamSignalSchema).readonly()) }
8380
+ };
8381
+ //#endregion
8206
8382
  //#region src/capabilities/stream-broker.cap.ts
8207
8383
  /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
8208
8384
  var StreamFormatSchema = zod.z.enum([
@@ -8613,6 +8789,46 @@ var streamBrokerCapability = {
8613
8789
  mode: "singleton",
8614
8790
  exposesDeviceSettings: true,
8615
8791
  methods: {
8792
+ /**
8793
+ * A device reporting that one of ITS OWN signals just fired.
8794
+ *
8795
+ * The provider calls this; nothing polls. The broker then decides whether
8796
+ * to wake the source, and the decision has two terms that must BOTH hold:
8797
+ *
8798
+ * - the signal is ENABLED for this camera (the operator's per-camera
8799
+ * choice — a camera may declare a signal the operator does not want
8800
+ * waking anything), and
8801
+ * - a consumer is actually attached and waiting on the prestream. Waking
8802
+ * a camera nobody is watching is the one thing a battery camera must
8803
+ * never do, and a signal arriving with no consumer is exactly that.
8804
+ *
8805
+ * Reported, never inferred: a signal that fires while the stream is
8806
+ * already up refreshes the hold instead of starting a second one, which is
8807
+ * what makes continuous motion keep a camera awake without stacking
8808
+ * timers.
8809
+ *
8810
+ * `code` is the provider's own, from `streamSignals.listSignals`. An
8811
+ * unknown code is REFUSED rather than treated as a wildcard wake — a
8812
+ * provider and an operator disagreeing about a name must not become a
8813
+ * camera that wakes for everything.
8814
+ */
8815
+ reportStreamSignal: require_sleep.method(zod.z.object({
8816
+ deviceId: zod.z.number().int().nonnegative(),
8817
+ /** The provider's signal code. */
8818
+ code: zod.z.string().min(1),
8819
+ /** Ms epoch. Absent ⇒ now. */
8820
+ at: zod.z.number().optional()
8821
+ }), zod.z.object({
8822
+ /** Whether the broker acted on it, and if not, why. */
8823
+ accepted: zod.z.boolean(),
8824
+ reason: zod.z.enum([
8825
+ "woke",
8826
+ "hold-extended",
8827
+ "not-enabled",
8828
+ "no-consumer",
8829
+ "unknown-code"
8830
+ ])
8831
+ }), { kind: "mutation" }),
8616
8832
  publishCameraStream: require_sleep.method(zod.z.object({
8617
8833
  deviceId: zod.z.number().int().nonnegative(),
8618
8834
  camStreamId: zod.z.string().min(1),
@@ -9090,6 +9306,16 @@ var PickStreamRequirementsSchema = zod.z.object({
9090
9306
  acceptCodecs: zod.z.array(StreamCodecSchema).readonly().optional(),
9091
9307
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
9092
9308
  minHeight: zod.z.number().int().positive().optional(),
9309
+ /**
9310
+ * Maximum vertical resolution. Streams TALLER than this are dropped.
9311
+ *
9312
+ * A consumer can have a ceiling as real as its floor: Alexa documents
9313
+ * 480p to 1080p, and a 4K stream is as unusable to an Echo as a 360p one.
9314
+ * Without this the ceiling had to be re-implemented by every caller — and
9315
+ * one caller implementing it privately is how a second scoring authority
9316
+ * gets born.
9317
+ */
9318
+ maxHeight: zod.z.number().int().positive().optional(),
9093
9319
  /** Minimum horizontal resolution. */
9094
9320
  minWidth: zod.z.number().int().positive().optional(),
9095
9321
  /**
@@ -9106,7 +9332,22 @@ var PickStreamRequirementsSchema = zod.z.object({
9106
9332
  * transcoded" guard: if the device is already serving the consumer's
9107
9333
  * codec end-to-end, there's nothing to optimise.
9108
9334
  */
9109
- requireSiblingCodec: zod.z.array(StreamCodecSchema).readonly().optional()
9335
+ requireSiblingCodec: zod.z.array(StreamCodecSchema).readonly().optional(),
9336
+ /**
9337
+ * Whether a stream the consumer CANNOT decode may still be picked, on the
9338
+ * understanding that it will be transcoded.
9339
+ *
9340
+ * Default `false` — today's behaviour, and the right one for a bypass
9341
+ * question ("is there a stream I can forward untouched?"). Set `true` to
9342
+ * ask the larger question: "what is the best stream for me, transcoding if
9343
+ * I must?" A stream that satisfies `acceptCodecs` always outranks one that
9344
+ * does not, so a passthrough is never lost to a transcode; the answer says
9345
+ * which it is in {@link PickedCamStreamSchema.transcodes}.
9346
+ *
9347
+ * This is what lets one picker serve both the bypass and the full source
9348
+ * choice, instead of a consumer scoring privately when the bypass misses.
9349
+ */
9350
+ allowTranscode: zod.z.boolean().optional()
9110
9351
  }).readonly();
9111
9352
  var PickStreamPreferencesSchema = zod.z.object({
9112
9353
  /**
@@ -9120,12 +9361,32 @@ var PickStreamPreferencesSchema = zod.z.object({
9120
9361
  * picks the tallest stream; `'lowest'` picks the shortest (used by
9121
9362
  * memory-constrained consumers / Apple Home guest sessions).
9122
9363
  */
9123
- resolutionPreference: zod.z.enum(["highest", "lowest"]).optional()
9364
+ resolutionPreference: zod.z.enum(["highest", "lowest"]).optional(),
9365
+ /**
9366
+ * The height the consumer actually wants to DELIVER.
9367
+ *
9368
+ * Not a constraint — an ordering. With it set, the SMALLEST stream at or
9369
+ * above the target wins, and only if nothing reaches it does the tallest
9370
+ * take over. Pulling 1296 lines to draw 720 on a 1280x800 Echo panel costs
9371
+ * a decode and buys nothing, and `resolutionPreference: 'highest'` cannot
9372
+ * express that: it says "as big as possible", which is a different wish.
9373
+ *
9374
+ * Ignored when absent, so every existing caller keeps its ordering.
9375
+ */
9376
+ targetHeight: zod.z.number().int().positive().optional()
9124
9377
  }).readonly();
9125
9378
  var PickedCamStreamSchema = zod.z.object({
9126
9379
  camStreamId: zod.z.string(),
9127
9380
  codec: zod.z.string().optional(),
9128
9381
  resolution: require_sleep.CamStreamResolutionSchema.optional(),
9382
+ /**
9383
+ * Whether serving this stream requires a decode + re-encode.
9384
+ *
9385
+ * `false` is a stream the consumer can take as it stands. Only ever `true`
9386
+ * when the caller asked for it with `allowTranscode`, so a caller that did
9387
+ * not ask cannot be handed a cost it never agreed to pay.
9388
+ */
9389
+ transcodes: zod.z.boolean(),
9129
9390
  /** One-line explanation of why this stream won — for logs / debug UI. */
9130
9391
  reason: zod.z.string()
9131
9392
  });
@@ -15921,6 +16182,8 @@ var NcSystemEventKindSchema = zod.z.enum([
15921
16182
  "device-enabled",
15922
16183
  "device-battery-low",
15923
16184
  "device-battery-normal",
16185
+ "device-consumable-low",
16186
+ "device-consumable-normal",
15924
16187
  "stream-online",
15925
16188
  "stream-offline",
15926
16189
  "node-online",
@@ -15939,6 +16202,7 @@ var NcSystemEventKindSchema = zod.z.enum([
15939
16202
  "addon-updated",
15940
16203
  "server-updated",
15941
16204
  "export-completed",
16205
+ "backup-completed",
15942
16206
  "camera-online",
15943
16207
  "camera-offline",
15944
16208
  "camera-disabled",
@@ -17121,10 +17385,22 @@ var NC_CONDITION_CATALOG = [
17121
17385
  value: "server-updated",
17122
17386
  label: "Server updated"
17123
17387
  },
17388
+ {
17389
+ value: "device-consumable-low",
17390
+ label: "Consumable low"
17391
+ },
17392
+ {
17393
+ value: "device-consumable-normal",
17394
+ label: "Consumable replaced"
17395
+ },
17124
17396
  {
17125
17397
  value: "export-completed",
17126
17398
  label: "Export completed"
17127
17399
  },
17400
+ {
17401
+ value: "backup-completed",
17402
+ label: "Backup completed"
17403
+ },
17128
17404
  {
17129
17405
  value: "alarm-arming",
17130
17406
  label: "Alarm arming (exit delay)"
@@ -26261,10 +26537,28 @@ var webrtcSessionCapability = {
26261
26537
  getSessionState: require_sleep.method(zod.z.object({
26262
26538
  deviceId: zod.z.number().int().nonnegative(),
26263
26539
  sessionId: zod.z.string()
26264
- }), zod.z.object({ pendingRenegotiation: zod.z.object({
26265
- target: WebrtcStreamTargetSchema,
26266
- epoch: zod.z.number()
26267
- }).nullable() }))
26540
+ }), zod.z.object({
26541
+ pendingRenegotiation: zod.z.object({
26542
+ target: WebrtcStreamTargetSchema,
26543
+ epoch: zod.z.number()
26544
+ }).nullable(),
26545
+ /**
26546
+ * Whether the session still EXISTS.
26547
+ *
26548
+ * A consumer that holds a resource for the life of a session needs to
26549
+ * be able to ask, because a session does not always end the way it
26550
+ * began. Measured on this hub 2026-09-13: an Echo that got stuck never
26551
+ * sent `SessionDisconnected`, so the Alexa exporter's
26552
+ * `releaseEgressTranscode` never ran, and a 2304x1296 HEVC to 720p
26553
+ * H.264 transcode kept running for NOBODY for more than twenty
26554
+ * minutes. The WebRTC session had logged `WebRTC session closed`
26555
+ * minutes earlier — the broker knew; the holder had no way to ask.
26556
+ *
26557
+ * `false` for a session id the provider has never heard of, which is
26558
+ * the same answer as "it ended": either way nothing is holding it up.
26559
+ */
26560
+ alive: zod.z.boolean()
26561
+ }))
26268
26562
  },
26269
26563
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
26270
26564
  mount: { kind: "server-provided" }
@@ -38657,6 +38951,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
38657
38951
  smoke: smokeCapability,
38658
38952
  streamCatalog: streamCatalogCapability,
38659
38953
  streamParams: streamParamsCapability,
38954
+ streamSignals: streamSignalsCapability,
38660
38955
  switch: switchCapability,
38661
38956
  tamper: tamperCapability,
38662
38957
  temperatureSensor: temperatureSensorCapability,
@@ -41801,6 +42096,7 @@ var CAPABILITY_NAMES = {
41801
42096
  streamBroker: "stream-broker",
41802
42097
  streamCatalog: "stream-catalog",
41803
42098
  streamParams: "stream-params",
42099
+ streamSignals: "stream-signals",
41804
42100
  switch: "switch",
41805
42101
  system: "system",
41806
42102
  tamper: "tamper",
@@ -42362,6 +42658,10 @@ var CAPABILITY_ROUTER_KEYS = [
42362
42658
  key: "streamParams",
42363
42659
  name: "stream-params"
42364
42660
  },
42661
+ {
42662
+ key: "streamSignals",
42663
+ name: "stream-signals"
42664
+ },
42365
42665
  {
42366
42666
  key: "switch",
42367
42667
  name: "switch"
@@ -42595,6 +42895,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
42595
42895
  streamBrokerCapability,
42596
42896
  streamCatalogCapability,
42597
42897
  streamParamsCapability,
42898
+ streamSignalsCapability,
42598
42899
  switchCapability,
42599
42900
  systemCapability,
42600
42901
  tamperCapability,
@@ -48039,6 +48340,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
48039
48340
  addonId: null,
48040
48341
  access: "create"
48041
48342
  },
48343
+ "streamBroker.reportStreamSignal": {
48344
+ capName: "stream-broker",
48345
+ capScope: "system",
48346
+ addonId: null,
48347
+ access: "create"
48348
+ },
48042
48349
  "streamBroker.restartProfile": {
48043
48350
  capName: "stream-broker",
48044
48351
  capScope: "system",
@@ -48123,6 +48430,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
48123
48430
  addonId: null,
48124
48431
  access: "create"
48125
48432
  },
48433
+ "streamSignals.listSignals": {
48434
+ capName: "stream-signals",
48435
+ capScope: "device",
48436
+ addonId: null,
48437
+ access: "view"
48438
+ },
48126
48439
  "switch.setState": {
48127
48440
  capName: "switch",
48128
48441
  capScope: "device",
@@ -48927,6 +49240,7 @@ var KNOWN_CAP_NAMES = [
48927
49240
  "stream-broker",
48928
49241
  "stream-catalog",
48929
49242
  "stream-params",
49243
+ "stream-signals",
48930
49244
  "switch",
48931
49245
  "system",
48932
49246
  "terminal-session",
@@ -48994,6 +49308,7 @@ var DEVICE_CAP_NAMES = [
48994
49308
  "snapshot",
48995
49309
  "stream-catalog",
48996
49310
  "stream-params",
49311
+ "stream-signals",
48997
49312
  "switch",
48998
49313
  "update",
48999
49314
  "vacuum-control",
@@ -50764,6 +51079,11 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
50764
51079
  form: "single",
50765
51080
  optional: false
50766
51081
  }],
51082
+ "streamBroker.reportStreamSignal": [{
51083
+ name: "deviceId",
51084
+ form: "single",
51085
+ optional: false
51086
+ }],
50767
51087
  "streamBroker.restartProfile": [{
50768
51088
  name: "deviceId",
50769
51089
  form: "single",
@@ -51123,6 +51443,7 @@ var SYSTEM_SCOPE_DEVICE_METHODS = [
51123
51443
  "streamBroker.produceEventMedia",
51124
51444
  "streamBroker.publishCameraStream",
51125
51445
  "streamBroker.renderPreBufferClip",
51446
+ "streamBroker.reportStreamSignal",
51126
51447
  "streamBroker.restartProfile",
51127
51448
  "streamBroker.retractCameraStream",
51128
51449
  "streamBroker.setDeviceAudioMute",
@@ -51162,7 +51483,7 @@ function getCapsByProviderKind(kind) {
51162
51483
  //#region src/generated/runtime-state-durability.ts
51163
51484
  /**
51164
51485
  * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
51165
- * `runtimeState`. 67 caps: 35 `restored`, 32 `session`.
51486
+ * `runtimeState`. 68 caps: 35 `restored`, 33 `session`.
51166
51487
  *
51167
51488
  * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
51168
51489
  * with `durability: 'session'` has one and has decided not to keep it.
@@ -51388,6 +51709,10 @@ var RUNTIME_STATE_POLICY = {
51388
51709
  durability: "restored",
51389
51710
  volatileFields: ["lastFetchedAt"]
51390
51711
  },
51712
+ "stream-signals": {
51713
+ durability: "session",
51714
+ volatileFields: []
51715
+ },
51391
51716
  "switch": {
51392
51717
  durability: "restored",
51393
51718
  volatileFields: ["lastChangedAt"]
@@ -57305,6 +57630,8 @@ exports.StreamProfileConfigSchema = StreamProfileConfigSchema;
57305
57630
  exports.StreamProfileOptionsSchema = StreamProfileOptionsSchema;
57306
57631
  exports.StreamProfilePatchSchema = StreamProfilePatchSchema;
57307
57632
  exports.StreamProfileSchema = StreamProfileSchema;
57633
+ exports.StreamSignalSchema = StreamSignalSchema;
57634
+ exports.StreamSignalsStatusSchema = StreamSignalsStatusSchema;
57308
57635
  exports.StreamSourceEntrySchema = require_sleep.StreamSourceEntrySchema;
57309
57636
  exports.StreamSourceSchema = require_sleep.StreamSourceSchema;
57310
57637
  exports.SubscribeAudioChunksInputSchema = require_sleep.SubscribeAudioChunksInputSchema;
@@ -57490,12 +57817,14 @@ exports.climateControlCapability = climateControlCapability;
57490
57817
  exports.clusterModelSettingKey = clusterModelSettingKey;
57491
57818
  exports.clusterStepSettingFieldsFor = clusterStepSettingFieldsFor;
57492
57819
  exports.clusterStepSettingKey = clusterStepSettingKey;
57820
+ exports.codecIsAccepted = codecIsAccepted;
57493
57821
  exports.collectHydratedFieldEntries = require_sleep.collectHydratedFieldEntries;
57494
57822
  exports.collectHydratedFieldValues = require_sleep.collectHydratedFieldValues;
57495
57823
  exports.collectSecretConfigKeys = collectSecretConfigKeys;
57496
57824
  exports.colorCapability = colorCapability;
57497
57825
  exports.colorForKind = colorForKind;
57498
57826
  exports.commitWatchdogRestart = commitWatchdogRestart;
57827
+ exports.compareToTarget = compareToTarget;
57499
57828
  exports.compileExpression = compileExpression;
57500
57829
  exports.compileExpressionSafe = compileExpressionSafe;
57501
57830
  exports.composeSwitchedOff = composeSwitchedOff;
@@ -57754,6 +58083,7 @@ exports.procedureAuthKey = procedureAuthKey;
57754
58083
  exports.ptzAutotrackCapability = ptzAutotrackCapability;
57755
58084
  exports.ptzCapability = ptzCapability;
57756
58085
  exports.pythonScriptForBackend = pythonScriptForBackend;
58086
+ exports.rankStreams = rankStreams;
57757
58087
  exports.readClusterStepModels = readClusterStepModels;
57758
58088
  exports.readClusterStepSettings = readClusterStepSettings;
57759
58089
  exports.readDetailCropConvention = readDetailCropConvention;
@@ -57838,6 +58168,7 @@ exports.streamCatalogCapability = streamCatalogCapability;
57838
58168
  exports.streamParamsCapability = streamParamsCapability;
57839
58169
  exports.streamPixels = streamPixels;
57840
58170
  exports.streamQualityLabel = streamQualityLabel;
58171
+ exports.streamSignalsCapability = streamSignalsCapability;
57841
58172
  exports.stripRetiredBandPreBufferSec = stripRetiredBandPreBufferSec;
57842
58173
  exports.strippedOfLegacyReadOnly = strippedOfLegacyReadOnly;
57843
58174
  exports.subKindsOf = subKindsOf;