camstack 1.2.146 → 1.2.149

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.
@@ -14522,7 +14522,7 @@ function date4(params) {
14522
14522
  // ../../node_modules/zod/v4/classic/external.js
14523
14523
  config(en_default());
14524
14524
 
14525
- // ../types/dist/sleep-DBKu2-U5.mjs
14525
+ // ../types/dist/sleep-DEuj7E3j.mjs
14526
14526
  var AUDIO_CHUNK_FORMATS = [
14527
14527
  "f32le",
14528
14528
  "pcmu",
@@ -18788,6 +18788,39 @@ var cameraPipelineConfigCapability = {
18788
18788
  exposesDeviceSettings: true,
18789
18789
  methods: {}
18790
18790
  };
18791
+ var StreamSignalSchema = external_exports.object({
18792
+ /** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
18793
+ code: external_exports.string().min(1),
18794
+ /** What an operator reads in the picker. The provider's own wording. */
18795
+ label: external_exports.string().min(1),
18796
+ /**
18797
+ * Whether the provider recommends this signal ON when a camera is first set
18798
+ * up. A provider knows which of its signals are cheap and reliable; an
18799
+ * operator should not have to discover that by trial. Reolink recommends
18800
+ * both of its own.
18801
+ */
18802
+ recommended: external_exports.boolean()
18803
+ });
18804
+ var StreamSignalsStatusSchema = external_exports.object({
18805
+ signals: external_exports.array(StreamSignalSchema),
18806
+ lastFetchedAt: external_exports.number()
18807
+ });
18808
+ var streamSignalsCapability = {
18809
+ name: "stream-signals",
18810
+ scope: "device",
18811
+ deviceNative: true,
18812
+ mode: "singleton",
18813
+ deviceTypes: Object.values(DeviceType),
18814
+ runtimeState: StreamSignalsStatusSchema,
18815
+ methods: {
18816
+ /**
18817
+ * What this device can emit. Empty is a valid and common answer — most
18818
+ * cameras have nothing to offer here, and an empty list is what makes the
18819
+ * broker's picker show nothing rather than a false choice.
18820
+ */
18821
+ listSignals: method(external_exports.void(), external_exports.array(StreamSignalSchema).readonly())
18822
+ }
18823
+ };
18791
18824
  var StreamFormatSchema = external_exports.enum([
18792
18825
  "webrtc",
18793
18826
  "hls",
@@ -19140,6 +19173,46 @@ var streamBrokerCapability = {
19140
19173
  mode: "singleton",
19141
19174
  exposesDeviceSettings: true,
19142
19175
  methods: {
19176
+ /**
19177
+ * A device reporting that one of ITS OWN signals just fired.
19178
+ *
19179
+ * The provider calls this; nothing polls. The broker then decides whether
19180
+ * to wake the source, and the decision has two terms that must BOTH hold:
19181
+ *
19182
+ * - the signal is ENABLED for this camera (the operator's per-camera
19183
+ * choice — a camera may declare a signal the operator does not want
19184
+ * waking anything), and
19185
+ * - a consumer is actually attached and waiting on the prestream. Waking
19186
+ * a camera nobody is watching is the one thing a battery camera must
19187
+ * never do, and a signal arriving with no consumer is exactly that.
19188
+ *
19189
+ * Reported, never inferred: a signal that fires while the stream is
19190
+ * already up refreshes the hold instead of starting a second one, which is
19191
+ * what makes continuous motion keep a camera awake without stacking
19192
+ * timers.
19193
+ *
19194
+ * `code` is the provider's own, from `streamSignals.listSignals`. An
19195
+ * unknown code is REFUSED rather than treated as a wildcard wake — a
19196
+ * provider and an operator disagreeing about a name must not become a
19197
+ * camera that wakes for everything.
19198
+ */
19199
+ reportStreamSignal: method(external_exports.object({
19200
+ deviceId: external_exports.number().int().nonnegative(),
19201
+ /** The provider's signal code. */
19202
+ code: external_exports.string().min(1),
19203
+ /** Ms epoch. Absent ⇒ now. */
19204
+ at: external_exports.number().optional()
19205
+ }), external_exports.object({
19206
+ /** Whether the broker acted on it, and if not, why. */
19207
+ accepted: external_exports.boolean(),
19208
+ reason: external_exports.enum([
19209
+ "woke",
19210
+ "hold-extended",
19211
+ "not-enabled",
19212
+ "no-consumer",
19213
+ "unknown-code"
19214
+ ])
19215
+ }), { kind: "mutation" }),
19143
19216
  publishCameraStream: method(external_exports.object({
19144
19217
  deviceId: external_exports.number().int().nonnegative(),
19145
19218
  camStreamId: external_exports.string().min(1),
@@ -19617,6 +19690,16 @@ var PickStreamRequirementsSchema = external_exports.object({
19617
19690
  acceptCodecs: external_exports.array(StreamCodecSchema).readonly().optional(),
19618
19691
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
19619
19692
  minHeight: external_exports.number().int().positive().optional(),
19693
+ /**
19694
+ * Maximum vertical resolution. Streams TALLER than this are dropped.
19695
+ *
19696
+ * A consumer can have a ceiling as real as its floor: Alexa documents
19697
+ * 480p to 1080p, and a 4K stream is as unusable to an Echo as a 360p one.
19698
+ * Without this the ceiling had to be re-implemented by every caller — and
19699
+ * one caller implementing it privately is how a second scoring authority
19700
+ * gets born.
19701
+ */
19702
+ maxHeight: external_exports.number().int().positive().optional(),
19620
19703
  /** Minimum horizontal resolution. */
19621
19704
  minWidth: external_exports.number().int().positive().optional(),
19622
19705
  /**
@@ -19633,7 +19716,22 @@ var PickStreamRequirementsSchema = external_exports.object({
19633
19716
  * transcoded" guard: if the device is already serving the consumer's
19634
19717
  * codec end-to-end, there's nothing to optimise.
19635
19718
  */
19636
- requireSiblingCodec: external_exports.array(StreamCodecSchema).readonly().optional()
19719
+ requireSiblingCodec: external_exports.array(StreamCodecSchema).readonly().optional(),
19720
+ /**
19721
+ * Whether a stream the consumer CANNOT decode may still be picked, on the
19722
+ * understanding that it will be transcoded.
19723
+ *
19724
+ * Default `false` — today's behaviour, and the right one for a bypass
19725
+ * question ("is there a stream I can forward untouched?"). Set `true` to
19726
+ * ask the larger question: "what is the best stream for me, transcoding if
19727
+ * I must?" A stream that satisfies `acceptCodecs` always outranks one that
19728
+ * does not, so a passthrough is never lost to a transcode; the answer says
19729
+ * which it is in {@link PickedCamStreamSchema.transcodes}.
19730
+ *
19731
+ * This is what lets one picker serve both the bypass and the full source
19732
+ * choice, instead of a consumer scoring privately when the bypass misses.
19733
+ */
19734
+ allowTranscode: external_exports.boolean().optional()
19637
19735
  }).readonly();
19638
19736
  var PickStreamPreferencesSchema = external_exports.object({
19639
19737
  /**
@@ -19647,12 +19745,32 @@ var PickStreamPreferencesSchema = external_exports.object({
19647
19745
  * picks the tallest stream; `'lowest'` picks the shortest (used by
19648
19746
  * memory-constrained consumers / Apple Home guest sessions).
19649
19747
  */
19650
- resolutionPreference: external_exports.enum(["highest", "lowest"]).optional()
19748
+ resolutionPreference: external_exports.enum(["highest", "lowest"]).optional(),
19749
+ /**
19750
+ * The height the consumer actually wants to DELIVER.
19751
+ *
19752
+ * Not a constraint — an ordering. With it set, the SMALLEST stream at or
19753
+ * above the target wins, and only if nothing reaches it does the tallest
19754
+ * take over. Pulling 1296 lines to draw 720 on a 1280x800 Echo panel costs
19755
+ * a decode and buys nothing, and `resolutionPreference: 'highest'` cannot
19756
+ * express that: it says "as big as possible", which is a different wish.
19757
+ *
19758
+ * Ignored when absent, so every existing caller keeps its ordering.
19759
+ */
19760
+ targetHeight: external_exports.number().int().positive().optional()
19651
19761
  }).readonly();
19652
19762
  var PickedCamStreamSchema = external_exports.object({
19653
19763
  camStreamId: external_exports.string(),
19654
19764
  codec: external_exports.string().optional(),
19655
19765
  resolution: CamStreamResolutionSchema.optional(),
19766
+ /**
19767
+ * Whether serving this stream requires a decode + re-encode.
19768
+ *
19769
+ * `false` is a stream the consumer can take as it stands. Only ever `true`
19770
+ * when the caller asked for it with `allowTranscode`, so a caller that did
19771
+ * not ask cannot be handed a cost it never agreed to pay.
19772
+ */
19773
+ transcodes: external_exports.boolean(),
19656
19774
  /** One-line explanation of why this stream won — for logs / debug UI. */
19657
19775
  reason: external_exports.string()
19658
19776
  });
@@ -24463,6 +24581,8 @@ var NcSystemEventKindSchema = external_exports.enum([
24463
24581
  "device-enabled",
24464
24582
  "device-battery-low",
24465
24583
  "device-battery-normal",
24584
+ "device-consumable-low",
24585
+ "device-consumable-normal",
24466
24586
  "stream-online",
24467
24587
  "stream-offline",
24468
24588
  "node-online",
@@ -24481,6 +24601,7 @@ var NcSystemEventKindSchema = external_exports.enum([
24481
24601
  "addon-updated",
24482
24602
  "server-updated",
24483
24603
  "export-completed",
24604
+ "backup-completed",
24484
24605
  "camera-online",
24485
24606
  "camera-offline",
24486
24607
  "camera-disabled",
@@ -32245,10 +32366,28 @@ var webrtcSessionCapability = {
32245
32366
  getSessionState: method(external_exports.object({
32246
32367
  deviceId: external_exports.number().int().nonnegative(),
32247
32368
  sessionId: external_exports.string()
32248
- }), external_exports.object({ pendingRenegotiation: external_exports.object({
32249
- target: WebrtcStreamTargetSchema,
32250
- epoch: external_exports.number()
32251
- }).nullable() }))
32369
+ }), external_exports.object({
32370
+ pendingRenegotiation: external_exports.object({
32371
+ target: WebrtcStreamTargetSchema,
32372
+ epoch: external_exports.number()
32373
+ }).nullable(),
32374
+ /**
32375
+ * Whether the session still EXISTS.
32376
+ *
32377
+ * A consumer that holds a resource for the life of a session needs to
32378
+ * be able to ask, because a session does not always end the way it
32379
+ * began. Measured on this hub 2026-09-13: an Echo that got stuck never
32380
+ * sent `SessionDisconnected`, so the Alexa exporter's
32381
+ * `releaseEgressTranscode` never ran, and a 2304x1296 HEVC to 720p
32382
+ * H.264 transcode kept running for NOBODY for more than twenty
32383
+ * minutes. The WebRTC session had logged `WebRTC session closed`
32384
+ * minutes earlier — the broker knew; the holder had no way to ask.
32385
+ *
32386
+ * `false` for a session id the provider has never heard of, which is
32387
+ * the same answer as "it ended": either way nothing is holding it up.
32388
+ */
32389
+ alive: external_exports.boolean()
32390
+ }))
32252
32391
  },
32253
32392
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
32254
32393
  mount: { kind: "server-provided" }
@@ -46536,6 +46675,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
46536
46675
  addonId: null,
46537
46676
  access: "create"
46538
46677
  },
46678
+ "streamBroker.reportStreamSignal": {
46679
+ capName: "stream-broker",
46680
+ capScope: "system",
46681
+ addonId: null,
46682
+ access: "create"
46683
+ },
46539
46684
  "streamBroker.restartProfile": {
46540
46685
  capName: "stream-broker",
46541
46686
  capScope: "system",
@@ -46620,6 +46765,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
46620
46765
  addonId: null,
46621
46766
  access: "create"
46622
46767
  },
46768
+ "streamSignals.listSignals": {
46769
+ capName: "stream-signals",
46770
+ capScope: "device",
46771
+ addonId: null,
46772
+ access: "view"
46773
+ },
46623
46774
  "switch.setState": {
46624
46775
  capName: "switch",
46625
46776
  capScope: "device",
@@ -48992,6 +49143,11 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
48992
49143
  form: "single",
48993
49144
  optional: false
48994
49145
  }],
49146
+ "streamBroker.reportStreamSignal": [{
49147
+ name: "deviceId",
49148
+ form: "single",
49149
+ optional: false
49150
+ }],
48995
49151
  "streamBroker.restartProfile": [{
48996
49152
  name: "deviceId",
48997
49153
  form: "single",
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runDiscover
4
- } from "./chunk-F3AFDVKF.js";
4
+ } from "./chunk-IGFZA22D.js";
5
5
  import "./chunk-LMMQX4CK.js";
6
6
 
7
7
  // src/cli.ts
@@ -38,7 +38,7 @@ async function runServe(args) {
38
38
  ...typeof values.data === "string" ? { data: values.data } : {}
39
39
  };
40
40
  Object.assign(process.env, buildServeEnv(opts));
41
- await import("./launcher-L5YPTDNE.js");
41
+ await import("./launcher-26BZ4JHD.js");
42
42
  }
43
43
 
44
44
  // src/commands/agent.ts
@@ -83,7 +83,7 @@ async function runAgent(args) {
83
83
  ...typeof values.port === "string" ? { port: values.port } : {}
84
84
  };
85
85
  Object.assign(process.env, buildAgentEnv(opts));
86
- await import("./launcher-L5YPTDNE.js");
86
+ await import("./launcher-26BZ4JHD.js");
87
87
  }
88
88
 
89
89
  // src/commands/setup.ts
@@ -1140,7 +1140,7 @@ function isUnknown(_value) {
1140
1140
  return true;
1141
1141
  }
1142
1142
  async function resolveServerInteractive(presetNamespace) {
1143
- const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-CLO7P5ID.js");
1143
+ const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-ZCOCQKSU.js");
1144
1144
  if (presetNamespace) {
1145
1145
  const spinner4 = clack.spinner();
1146
1146
  spinner4.start(`Discovering hub on LAN (namespace "${presetNamespace}")`);
@@ -5,7 +5,7 @@ import {
5
5
  filterHubNodes,
6
6
  resolveHubFromDiscovered,
7
7
  runDiscover
8
- } from "./chunk-F3AFDVKF.js";
8
+ } from "./chunk-IGFZA22D.js";
9
9
  import "./chunk-LMMQX4CK.js";
10
10
  export {
11
11
  DEFAULT_HUB_HTTPS_PORT,
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-DUSfUAgz.js
23637
- var require_dist_DUSfUAgz = __commonJS({
23638
- "../system/dist/dist-DUSfUAgz.js"(exports) {
23636
+ // ../system/dist/dist-CEIkHEdy.js
23637
+ var require_dist_CEIkHEdy = __commonJS({
23638
+ "../system/dist/dist-CEIkHEdy.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -23776,6 +23776,7 @@ var require_dist_DUSfUAgz = __commonJS({
23776
23776
  EventCategory2["LocalNetworkChanged"] = "network.local.changed";
23777
23777
  EventCategory2["MeshNetworkChanged"] = "network.mesh.changed";
23778
23778
  EventCategory2["BackupCompleted"] = "backup.completed";
23779
+ EventCategory2["BackupRunCompleted"] = "backup.run-completed";
23779
23780
  EventCategory2["BackupRestored"] = "backup.restored";
23780
23781
  EventCategory2["NotificationDispatched"] = "notification.dispatched";
23781
23782
  EventCategory2["NotificationFailed"] = "notification.failed";
@@ -29446,6 +29447,39 @@ var require_dist_DUSfUAgz = __commonJS({
29446
29447
  exposesDeviceSettings: true,
29447
29448
  methods: {}
29448
29449
  };
29450
+ var StreamSignalSchema = zod.z.object({
29451
+ /** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
29452
+ code: zod.z.string().min(1),
29453
+ /** What an operator reads in the picker. The provider's own wording. */
29454
+ label: zod.z.string().min(1),
29455
+ /**
29456
+ * Whether the provider recommends this signal ON when a camera is first set
29457
+ * up. A provider knows which of its signals are cheap and reliable; an
29458
+ * operator should not have to discover that by trial. Reolink recommends
29459
+ * both of its own.
29460
+ */
29461
+ recommended: zod.z.boolean()
29462
+ });
29463
+ var StreamSignalsStatusSchema = zod.z.object({
29464
+ signals: zod.z.array(StreamSignalSchema),
29465
+ lastFetchedAt: zod.z.number()
29466
+ });
29467
+ var streamSignalsCapability = {
29468
+ name: "stream-signals",
29469
+ scope: "device",
29470
+ deviceNative: true,
29471
+ mode: "singleton",
29472
+ deviceTypes: Object.values(DeviceType),
29473
+ runtimeState: StreamSignalsStatusSchema,
29474
+ methods: {
29475
+ /**
29476
+ * What this device can emit. Empty is a valid and common answer — most
29477
+ * cameras have nothing to offer here, and an empty list is what makes the
29478
+ * broker's picker show nothing rather than a false choice.
29479
+ */
29480
+ listSignals: method(zod.z.void(), zod.z.array(StreamSignalSchema).readonly())
29481
+ }
29482
+ };
29449
29483
  var StreamFormatSchema = zod.z.enum([
29450
29484
  "webrtc",
29451
29485
  "hls",
@@ -29798,6 +29832,46 @@ var require_dist_DUSfUAgz = __commonJS({
29798
29832
  mode: "singleton",
29799
29833
  exposesDeviceSettings: true,
29800
29834
  methods: {
29835
+ /**
29836
+ * A device reporting that one of ITS OWN signals just fired.
29837
+ *
29838
+ * The provider calls this; nothing polls. The broker then decides whether
29839
+ * to wake the source, and the decision has two terms that must BOTH hold:
29840
+ *
29841
+ * - the signal is ENABLED for this camera (the operator's per-camera
29842
+ * choice — a camera may declare a signal the operator does not want
29843
+ * waking anything), and
29844
+ * - a consumer is actually attached and waiting on the prestream. Waking
29845
+ * a camera nobody is watching is the one thing a battery camera must
29846
+ * never do, and a signal arriving with no consumer is exactly that.
29847
+ *
29848
+ * Reported, never inferred: a signal that fires while the stream is
29849
+ * already up refreshes the hold instead of starting a second one, which is
29850
+ * what makes continuous motion keep a camera awake without stacking
29851
+ * timers.
29852
+ *
29853
+ * `code` is the provider's own, from `streamSignals.listSignals`. An
29854
+ * unknown code is REFUSED rather than treated as a wildcard wake — a
29855
+ * provider and an operator disagreeing about a name must not become a
29856
+ * camera that wakes for everything.
29857
+ */
29858
+ reportStreamSignal: method(zod.z.object({
29859
+ deviceId: zod.z.number().int().nonnegative(),
29860
+ /** The provider's signal code. */
29861
+ code: zod.z.string().min(1),
29862
+ /** Ms epoch. Absent ⇒ now. */
29863
+ at: zod.z.number().optional()
29864
+ }), zod.z.object({
29865
+ /** Whether the broker acted on it, and if not, why. */
29866
+ accepted: zod.z.boolean(),
29867
+ reason: zod.z.enum([
29868
+ "woke",
29869
+ "hold-extended",
29870
+ "not-enabled",
29871
+ "no-consumer",
29872
+ "unknown-code"
29873
+ ])
29874
+ }), { kind: "mutation" }),
29801
29875
  publishCameraStream: method(zod.z.object({
29802
29876
  deviceId: zod.z.number().int().nonnegative(),
29803
29877
  camStreamId: zod.z.string().min(1),
@@ -30275,6 +30349,16 @@ var require_dist_DUSfUAgz = __commonJS({
30275
30349
  acceptCodecs: zod.z.array(StreamCodecSchema).readonly().optional(),
30276
30350
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
30277
30351
  minHeight: zod.z.number().int().positive().optional(),
30352
+ /**
30353
+ * Maximum vertical resolution. Streams TALLER than this are dropped.
30354
+ *
30355
+ * A consumer can have a ceiling as real as its floor: Alexa documents
30356
+ * 480p to 1080p, and a 4K stream is as unusable to an Echo as a 360p one.
30357
+ * Without this the ceiling had to be re-implemented by every caller — and
30358
+ * one caller implementing it privately is how a second scoring authority
30359
+ * gets born.
30360
+ */
30361
+ maxHeight: zod.z.number().int().positive().optional(),
30278
30362
  /** Minimum horizontal resolution. */
30279
30363
  minWidth: zod.z.number().int().positive().optional(),
30280
30364
  /**
@@ -30291,7 +30375,22 @@ var require_dist_DUSfUAgz = __commonJS({
30291
30375
  * transcoded" guard: if the device is already serving the consumer's
30292
30376
  * codec end-to-end, there's nothing to optimise.
30293
30377
  */
30294
- requireSiblingCodec: zod.z.array(StreamCodecSchema).readonly().optional()
30378
+ requireSiblingCodec: zod.z.array(StreamCodecSchema).readonly().optional(),
30379
+ /**
30380
+ * Whether a stream the consumer CANNOT decode may still be picked, on the
30381
+ * understanding that it will be transcoded.
30382
+ *
30383
+ * Default `false` — today's behaviour, and the right one for a bypass
30384
+ * question ("is there a stream I can forward untouched?"). Set `true` to
30385
+ * ask the larger question: "what is the best stream for me, transcoding if
30386
+ * I must?" A stream that satisfies `acceptCodecs` always outranks one that
30387
+ * does not, so a passthrough is never lost to a transcode; the answer says
30388
+ * which it is in {@link PickedCamStreamSchema.transcodes}.
30389
+ *
30390
+ * This is what lets one picker serve both the bypass and the full source
30391
+ * choice, instead of a consumer scoring privately when the bypass misses.
30392
+ */
30393
+ allowTranscode: zod.z.boolean().optional()
30295
30394
  }).readonly();
30296
30395
  var PickStreamPreferencesSchema = zod.z.object({
30297
30396
  /**
@@ -30305,12 +30404,32 @@ var require_dist_DUSfUAgz = __commonJS({
30305
30404
  * picks the tallest stream; `'lowest'` picks the shortest (used by
30306
30405
  * memory-constrained consumers / Apple Home guest sessions).
30307
30406
  */
30308
- resolutionPreference: zod.z.enum(["highest", "lowest"]).optional()
30407
+ resolutionPreference: zod.z.enum(["highest", "lowest"]).optional(),
30408
+ /**
30409
+ * The height the consumer actually wants to DELIVER.
30410
+ *
30411
+ * Not a constraint — an ordering. With it set, the SMALLEST stream at or
30412
+ * above the target wins, and only if nothing reaches it does the tallest
30413
+ * take over. Pulling 1296 lines to draw 720 on a 1280x800 Echo panel costs
30414
+ * a decode and buys nothing, and `resolutionPreference: 'highest'` cannot
30415
+ * express that: it says "as big as possible", which is a different wish.
30416
+ *
30417
+ * Ignored when absent, so every existing caller keeps its ordering.
30418
+ */
30419
+ targetHeight: zod.z.number().int().positive().optional()
30309
30420
  }).readonly();
30310
30421
  var PickedCamStreamSchema = zod.z.object({
30311
30422
  camStreamId: zod.z.string(),
30312
30423
  codec: zod.z.string().optional(),
30313
30424
  resolution: CamStreamResolutionSchema.optional(),
30425
+ /**
30426
+ * Whether serving this stream requires a decode + re-encode.
30427
+ *
30428
+ * `false` is a stream the consumer can take as it stands. Only ever `true`
30429
+ * when the caller asked for it with `allowTranscode`, so a caller that did
30430
+ * not ask cannot be handed a cost it never agreed to pay.
30431
+ */
30432
+ transcodes: zod.z.boolean(),
30314
30433
  /** One-line explanation of why this stream won — for logs / debug UI. */
30315
30434
  reason: zod.z.string()
30316
30435
  });
@@ -35224,6 +35343,8 @@ var require_dist_DUSfUAgz = __commonJS({
35224
35343
  "device-enabled",
35225
35344
  "device-battery-low",
35226
35345
  "device-battery-normal",
35346
+ "device-consumable-low",
35347
+ "device-consumable-normal",
35227
35348
  "stream-online",
35228
35349
  "stream-offline",
35229
35350
  "node-online",
@@ -35242,6 +35363,7 @@ var require_dist_DUSfUAgz = __commonJS({
35242
35363
  "addon-updated",
35243
35364
  "server-updated",
35244
35365
  "export-completed",
35366
+ "backup-completed",
35245
35367
  "camera-online",
35246
35368
  "camera-offline",
35247
35369
  "camera-disabled",
@@ -42830,10 +42952,28 @@ var require_dist_DUSfUAgz = __commonJS({
42830
42952
  getSessionState: method(zod.z.object({
42831
42953
  deviceId: zod.z.number().int().nonnegative(),
42832
42954
  sessionId: zod.z.string()
42833
- }), zod.z.object({ pendingRenegotiation: zod.z.object({
42834
- target: WebrtcStreamTargetSchema,
42835
- epoch: zod.z.number()
42836
- }).nullable() }))
42955
+ }), zod.z.object({
42956
+ pendingRenegotiation: zod.z.object({
42957
+ target: WebrtcStreamTargetSchema,
42958
+ epoch: zod.z.number()
42959
+ }).nullable(),
42960
+ /**
42961
+ * Whether the session still EXISTS.
42962
+ *
42963
+ * A consumer that holds a resource for the life of a session needs to
42964
+ * be able to ask, because a session does not always end the way it
42965
+ * began. Measured on this hub 2026-09-13: an Echo that got stuck never
42966
+ * sent `SessionDisconnected`, so the Alexa exporter's
42967
+ * `releaseEgressTranscode` never ran, and a 2304x1296 HEVC to 720p
42968
+ * H.264 transcode kept running for NOBODY for more than twenty
42969
+ * minutes. The WebRTC session had logged `WebRTC session closed`
42970
+ * minutes earlier — the broker knew; the holder had no way to ask.
42971
+ *
42972
+ * `false` for a session id the provider has never heard of, which is
42973
+ * the same answer as "it ended": either way nothing is holding it up.
42974
+ */
42975
+ alive: zod.z.boolean()
42976
+ }))
42837
42977
  },
42838
42978
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
42839
42979
  mount: { kind: "server-provided" }
@@ -52506,6 +52646,7 @@ var require_dist_DUSfUAgz = __commonJS({
52506
52646
  streamBrokerCapability,
52507
52647
  streamCatalogCapability,
52508
52648
  streamParamsCapability,
52649
+ streamSignalsCapability,
52509
52650
  switchCapability,
52510
52651
  systemCapability,
52511
52652
  tamperCapability,
@@ -57948,6 +58089,12 @@ var require_dist_DUSfUAgz = __commonJS({
57948
58089
  addonId: null,
57949
58090
  access: "create"
57950
58091
  },
58092
+ "streamBroker.reportStreamSignal": {
58093
+ capName: "stream-broker",
58094
+ capScope: "system",
58095
+ addonId: null,
58096
+ access: "create"
58097
+ },
57951
58098
  "streamBroker.restartProfile": {
57952
58099
  capName: "stream-broker",
57953
58100
  capScope: "system",
@@ -58032,6 +58179,12 @@ var require_dist_DUSfUAgz = __commonJS({
58032
58179
  addonId: null,
58033
58180
  access: "create"
58034
58181
  },
58182
+ "streamSignals.listSignals": {
58183
+ capName: "stream-signals",
58184
+ capScope: "device",
58185
+ addonId: null,
58186
+ access: "view"
58187
+ },
58035
58188
  "switch.setState": {
58036
58189
  capName: "switch",
58037
58190
  capScope: "device",
@@ -60404,6 +60557,11 @@ var require_dist_DUSfUAgz = __commonJS({
60404
60557
  form: "single",
60405
60558
  optional: false
60406
60559
  }],
60560
+ "streamBroker.reportStreamSignal": [{
60561
+ name: "deviceId",
60562
+ form: "single",
60563
+ optional: false
60564
+ }],
60407
60565
  "streamBroker.restartProfile": [{
60408
60566
  name: "deviceId",
60409
60567
  form: "single",
@@ -60855,6 +61013,10 @@ var require_dist_DUSfUAgz = __commonJS({
60855
61013
  durability: "restored",
60856
61014
  volatileFields: ["lastFetchedAt"]
60857
61015
  },
61016
+ "stream-signals": {
61017
+ durability: "session",
61018
+ volatileFields: []
61019
+ },
60858
61020
  "switch": {
60859
61021
  durability: "restored",
60860
61022
  volatileFields: ["lastChangedAt"]
@@ -62139,7 +62301,7 @@ var require_alerts_addon = __commonJS({
62139
62301
  [Symbol.toStringTag]: { value: "Module" }
62140
62302
  });
62141
62303
  require_chunk_Cek0wNdY();
62142
- var require_dist10 = require_dist_DUSfUAgz();
62304
+ var require_dist10 = require_dist_CEIkHEdy();
62143
62305
  function selectExpired(alerts, cutoffMs) {
62144
62306
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
62145
62307
  }
@@ -62958,7 +63120,7 @@ var require_console_logging = __commonJS({
62958
63120
  [Symbol.toStringTag]: { value: "Module" }
62959
63121
  });
62960
63122
  require_chunk_Cek0wNdY();
62961
- var require_dist10 = require_dist_DUSfUAgz();
63123
+ var require_dist10 = require_dist_CEIkHEdy();
62962
63124
  var require_formatter = require_formatter_DqAKDlvN();
62963
63125
  var LEVEL_RANK = {
62964
63126
  debug: 0,
@@ -63052,7 +63214,7 @@ var require_core_blocks_addon = __commonJS({
63052
63214
  "use strict";
63053
63215
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
63054
63216
  var require_chunk = require_chunk_Cek0wNdY();
63055
- var require_dist10 = require_dist_DUSfUAgz();
63217
+ var require_dist10 = require_dist_CEIkHEdy();
63056
63218
  var node_crypto = __require("crypto");
63057
63219
  var node_fs_promises = __require("fs/promises");
63058
63220
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -63949,11 +64111,11 @@ var require_core_blocks = __commonJS({
63949
64111
  }
63950
64112
  });
63951
64113
 
63952
- // ../system/dist/retired-settings-keys-D61PxN8L.js
63953
- var require_retired_settings_keys_D61PxN8L = __commonJS({
63954
- "../system/dist/retired-settings-keys-D61PxN8L.js"(exports) {
64114
+ // ../system/dist/retired-settings-keys-FLZlwa2U.js
64115
+ var require_retired_settings_keys_FLZlwa2U = __commonJS({
64116
+ "../system/dist/retired-settings-keys-FLZlwa2U.js"(exports) {
63955
64117
  "use strict";
63956
- var require_dist10 = require_dist_DUSfUAgz();
64118
+ var require_dist10 = require_dist_CEIkHEdy();
63957
64119
  function settingsStoreIsAuthoritativeHere(env) {
63958
64120
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
63959
64121
  return raw === "" || raw === "hub";
@@ -66190,8 +66352,8 @@ var require_device_manager_addon = __commonJS({
66190
66352
  [Symbol.toStringTag]: { value: "Module" }
66191
66353
  });
66192
66354
  require_chunk_Cek0wNdY();
66193
- var require_dist10 = require_dist_DUSfUAgz();
66194
- var require_retired_settings_keys = require_retired_settings_keys_D61PxN8L();
66355
+ var require_dist10 = require_dist_CEIkHEdy();
66356
+ var require_retired_settings_keys = require_retired_settings_keys_FLZlwa2U();
66195
66357
  var node_crypto = __require("crypto");
66196
66358
  var _camstack_types_node = require_node();
66197
66359
  var JOB_HISTORY = 20;
@@ -71816,7 +71978,7 @@ var require_hub_forwarder = __commonJS({
71816
71978
  [Symbol.toStringTag]: { value: "Module" }
71817
71979
  });
71818
71980
  require_chunk_Cek0wNdY();
71819
- var require_dist10 = require_dist_DUSfUAgz();
71981
+ var require_dist10 = require_dist_CEIkHEdy();
71820
71982
  var require_formatter = require_formatter_DqAKDlvN();
71821
71983
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
71822
71984
  var HubForwarderDestination = class {
@@ -71953,7 +72115,7 @@ var require_liveness_monitor_addon = __commonJS({
71953
72115
  "use strict";
71954
72116
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
71955
72117
  require_chunk_Cek0wNdY();
71956
- var require_dist10 = require_dist_DUSfUAgz();
72118
+ var require_dist10 = require_dist_CEIkHEdy();
71957
72119
  var NO_DEVICES = "liveness:no-devices";
71958
72120
  var ALL_OFFLINE = "liveness:all-devices-offline";
71959
72121
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -72149,7 +72311,7 @@ var require_local_auth_addon = __commonJS({
72149
72311
  [Symbol.toStringTag]: { value: "Module" }
72150
72312
  });
72151
72313
  var require_chunk = require_chunk_Cek0wNdY();
72152
- var require_dist10 = require_dist_DUSfUAgz();
72314
+ var require_dist10 = require_dist_CEIkHEdy();
72153
72315
  var node_crypto = __require("crypto");
72154
72316
  node_crypto = require_chunk.__toESM(node_crypto);
72155
72317
  var crypto$1 = __require("crypto");
@@ -79962,7 +80124,7 @@ var require_loki_logging = __commonJS({
79962
80124
  [Symbol.toStringTag]: { value: "Module" }
79963
80125
  });
79964
80126
  require_chunk_Cek0wNdY();
79965
- var require_dist10 = require_dist_DUSfUAgz();
80127
+ var require_dist10 = require_dist_CEIkHEdy();
79966
80128
  function sanitizeLabelName(raw) {
79967
80129
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
79968
80130
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -80549,7 +80711,7 @@ var require_native_metrics_addon = __commonJS({
80549
80711
  [Symbol.toStringTag]: { value: "Module" }
80550
80712
  });
80551
80713
  var require_chunk = require_chunk_Cek0wNdY();
80552
- var require_dist10 = require_dist_DUSfUAgz();
80714
+ var require_dist10 = require_dist_CEIkHEdy();
80553
80715
  var node_fs_promises = __require("fs/promises");
80554
80716
  var node_fs = __require("fs");
80555
80717
  node_fs = require_chunk.__toESM(node_fs);
@@ -83302,7 +83464,7 @@ var require_filesystem_storage_addon = __commonJS({
83302
83464
  [Symbol.toStringTag]: { value: "Module" }
83303
83465
  });
83304
83466
  var require_chunk = require_chunk_Cek0wNdY();
83305
- var require_dist10 = require_dist_DUSfUAgz();
83467
+ var require_dist10 = require_dist_CEIkHEdy();
83306
83468
  var node_crypto = __require("crypto");
83307
83469
  var node_fs_promises = __require("fs/promises");
83308
83470
  var node_path = __require("path");
@@ -84529,8 +84691,8 @@ var require_sqlite_settings_addon = __commonJS({
84529
84691
  [Symbol.toStringTag]: { value: "Module" }
84530
84692
  });
84531
84693
  var require_chunk = require_chunk_Cek0wNdY();
84532
- var require_dist10 = require_dist_DUSfUAgz();
84533
- var require_retired_settings_keys = require_retired_settings_keys_D61PxN8L();
84694
+ var require_dist10 = require_dist_CEIkHEdy();
84695
+ var require_retired_settings_keys = require_retired_settings_keys_FLZlwa2U();
84534
84696
  var require_wal_checkpoint_policy = require_wal_checkpoint_policy_CCJZngds();
84535
84697
  var node_crypto = __require("crypto");
84536
84698
  var node_path = __require("path");
@@ -87317,7 +87479,7 @@ var require_storage_orchestrator_addon = __commonJS({
87317
87479
  [Symbol.toStringTag]: { value: "Module" }
87318
87480
  });
87319
87481
  var require_chunk = require_chunk_Cek0wNdY();
87320
- var require_dist10 = require_dist_DUSfUAgz();
87482
+ var require_dist10 = require_dist_CEIkHEdy();
87321
87483
  var require_hub_cap_forward = require_hub_cap_forward_DmHNjbB0();
87322
87484
  var zod = require_zod();
87323
87485
  var node_crypto = __require("crypto");
@@ -91044,7 +91206,7 @@ var require_system_config_addon = __commonJS({
91044
91206
  [Symbol.toStringTag]: { value: "Module" }
91045
91207
  });
91046
91208
  require_chunk_Cek0wNdY();
91047
- var require_dist10 = require_dist_DUSfUAgz();
91209
+ var require_dist10 = require_dist_CEIkHEdy();
91048
91210
  var SECTION_TITLES = {
91049
91211
  server: "Server",
91050
91212
  auth: "Authentication"
@@ -109105,7 +109267,7 @@ var require_winston_logging = __commonJS({
109105
109267
  [Symbol.toStringTag]: { value: "Module" }
109106
109268
  });
109107
109269
  var require_chunk = require_chunk_Cek0wNdY();
109108
- var require_dist10 = require_dist_DUSfUAgz();
109270
+ var require_dist10 = require_dist_CEIkHEdy();
109109
109271
  var require_formatter = require_formatter_DqAKDlvN();
109110
109272
  var node_path = __require("path");
109111
109273
  node_path = require_chunk.__toESM(node_path);
@@ -110974,9 +111136,9 @@ ${(0, node_fs.readFileSync)(ca.caCertPath, "utf-8").trimEnd()}
110974
111136
  }
110975
111137
  });
110976
111138
 
110977
- // ../types/dist/event-category-yW4s1MGD.js
110978
- var require_event_category_yW4s1MGD = __commonJS({
110979
- "../types/dist/event-category-yW4s1MGD.js"(exports) {
111139
+ // ../types/dist/event-category-BVfsrBYA.js
111140
+ var require_event_category_BVfsrBYA = __commonJS({
111141
+ "../types/dist/event-category-BVfsrBYA.js"(exports) {
110980
111142
  "use strict";
110981
111143
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
110982
111144
  EventCategory2["SystemBoot"] = "system.boot";
@@ -111116,6 +111278,7 @@ var require_event_category_yW4s1MGD = __commonJS({
111116
111278
  EventCategory2["LocalNetworkChanged"] = "network.local.changed";
111117
111279
  EventCategory2["MeshNetworkChanged"] = "network.mesh.changed";
111118
111280
  EventCategory2["BackupCompleted"] = "backup.completed";
111281
+ EventCategory2["BackupRunCompleted"] = "backup.run-completed";
111119
111282
  EventCategory2["BackupRestored"] = "backup.restored";
111120
111283
  EventCategory2["NotificationDispatched"] = "notification.dispatched";
111121
111284
  EventCategory2["NotificationFailed"] = "notification.failed";
@@ -111142,11 +111305,11 @@ var require_event_category_yW4s1MGD = __commonJS({
111142
111305
  }
111143
111306
  });
111144
111307
 
111145
- // ../types/dist/sleep-DXsYD0RX.js
111146
- var require_sleep_DXsYD0RX = __commonJS({
111147
- "../types/dist/sleep-DXsYD0RX.js"(exports) {
111308
+ // ../types/dist/sleep-D7dV-FZB.js
111309
+ var require_sleep_D7dV_FZB = __commonJS({
111310
+ "../types/dist/sleep-D7dV-FZB.js"(exports) {
111148
111311
  "use strict";
111149
- var require_event_category = require_event_category_yW4s1MGD();
111312
+ var require_event_category = require_event_category_BVfsrBYA();
111150
111313
  var zod = require_zod();
111151
111314
  var AUDIO_CHUNK_FORMATS = [
111152
111315
  "f32le",
@@ -113641,6 +113804,7 @@ var require_sleep_DXsYD0RX = __commonJS({
113641
113804
  smoke: createSliceHandle(stateSource, binding.deviceId, "smoke"),
113642
113805
  streamCatalog: createSliceHandle(stateSource, binding.deviceId, "stream-catalog"),
113643
113806
  streamParams: createSliceHandle(stateSource, binding.deviceId, "stream-params"),
113807
+ streamSignals: createSliceHandle(stateSource, binding.deviceId, "stream-signals"),
113644
113808
  switch: createSliceHandle(stateSource, binding.deviceId, "switch"),
113645
113809
  tamper: createSliceHandle(stateSource, binding.deviceId, "tamper"),
113646
113810
  temperatureSensor: createSliceHandle(stateSource, binding.deviceId, "temperature-sensor"),
@@ -114055,6 +114219,7 @@ var require_sleep_DXsYD0RX = __commonJS({
114055
114219
  getConfigSchema: (input) => dispatch("stream-params", "streamParams", "getConfigSchema", "query", input),
114056
114220
  getStatus: (input) => dispatch("stream-params", "streamParams", "getStatus", "query", input)
114057
114221
  },
114222
+ streamSignals: { listSignals: (input) => dispatch("stream-signals", "streamSignals", "listSignals", "query", input) },
114058
114223
  switch: {
114059
114224
  setState: (input) => dispatch("switch", "switch", "setState", "mutation", input),
114060
114225
  getStatus: (input) => dispatch("switch", "switch", "getStatus", "query", input)
@@ -114264,6 +114429,7 @@ var require_sleep_DXsYD0RX = __commonJS({
114264
114429
  },
114265
114430
  recordingExport: { listExports: (input) => dispatchSystem("recordingExport", "listExports", "query", input) },
114266
114431
  streamBroker: {
114432
+ reportStreamSignal: (input) => dispatchSystem("streamBroker", "reportStreamSignal", "mutation", input),
114267
114433
  publishCameraStream: (input) => dispatchSystem("streamBroker", "publishCameraStream", "mutation", input),
114268
114434
  retractCameraStream: (input) => dispatchSystem("streamBroker", "retractCameraStream", "mutation", input),
114269
114435
  assignProfile: (input) => dispatchSystem("streamBroker", "assignProfile", "mutation", input),
@@ -114350,6 +114516,7 @@ var require_sleep_DXsYD0RX = __commonJS({
114350
114516
  "snapshot",
114351
114517
  "stream-catalog",
114352
114518
  "stream-params",
114519
+ "stream-signals",
114353
114520
  "switch",
114354
114521
  "tamper",
114355
114522
  "temperature-sensor",
@@ -114977,8 +115144,8 @@ var require_addon = __commonJS({
114977
115144
  "../types/dist/addon.js"(exports) {
114978
115145
  "use strict";
114979
115146
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
114980
- var require_sleep = require_sleep_DXsYD0RX();
114981
- var require_event_category = require_event_category_yW4s1MGD();
115147
+ var require_sleep = require_sleep_D7dV_FZB();
115148
+ var require_event_category = require_event_category_BVfsrBYA();
114982
115149
  var require_err_msg = require_err_msg_COpsHMw2();
114983
115150
  var CAP_INPUT_DEFAULTS = Object.freeze({
114984
115151
  "addons": { "getLogs": { "limit": 100 } },
@@ -131387,11 +131554,11 @@ var require_manifest_system_deps_CrSP0Kkp = __commonJS({
131387
131554
  }
131388
131555
  });
131389
131556
 
131390
- // ../system/dist/custom-action-registry-DAFhWd0h.js
131391
- var require_custom_action_registry_DAFhWd0h = __commonJS({
131392
- "../system/dist/custom-action-registry-DAFhWd0h.js"(exports) {
131557
+ // ../system/dist/custom-action-registry-YHpAdksL.js
131558
+ var require_custom_action_registry_YHpAdksL = __commonJS({
131559
+ "../system/dist/custom-action-registry-YHpAdksL.js"(exports) {
131393
131560
  "use strict";
131394
- var require_dist10 = require_dist_DUSfUAgz();
131561
+ var require_dist10 = require_dist_CEIkHEdy();
131395
131562
  var CustomActionRegistry = class {
131396
131563
  byAddon = /* @__PURE__ */ new Map();
131397
131564
  registerAddon(addonId, catalog, handler) {
@@ -135205,7 +135372,7 @@ var require_dist3 = __commonJS({
135205
135372
  "use strict";
135206
135373
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
135207
135374
  var require_chunk = require_chunk_Cek0wNdY();
135208
- var require_dist10 = require_dist_DUSfUAgz();
135375
+ var require_dist10 = require_dist_CEIkHEdy();
135209
135376
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
135210
135377
  require_alerts();
135211
135378
  var require_formatter = require_formatter_DqAKDlvN();
@@ -135234,7 +135401,7 @@ var require_dist3 = __commonJS({
135234
135401
  var require_tls$1 = require_tls_BFs4PzoW();
135235
135402
  var require_manifest_system_deps = require_manifest_system_deps_CrSP0Kkp();
135236
135403
  var require_resource_monitor = require_resource_monitor_LVE1BoLs();
135237
- var require_custom_action_registry = require_custom_action_registry_DAFhWd0h();
135404
+ var require_custom_action_registry = require_custom_action_registry_YHpAdksL();
135238
135405
  var zod = require_zod();
135239
135406
  var node_crypto = __require("crypto");
135240
135407
  var _camstack_types_node = require_node();
@@ -216323,7 +216490,7 @@ var require_enums = __commonJS({
216323
216490
  "../types/dist/enums.js"(exports) {
216324
216491
  "use strict";
216325
216492
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
216326
- var require_event_category = require_event_category_yW4s1MGD();
216493
+ var require_event_category = require_event_category_BVfsrBYA();
216327
216494
  var EventSourceType = /* @__PURE__ */ (function(EventSourceType2) {
216328
216495
  EventSourceType2["Addon"] = "addon";
216329
216496
  EventSourceType2["Core"] = "core";
@@ -216343,8 +216510,8 @@ var require_dist4 = __commonJS({
216343
216510
  "../types/dist/index.js"(exports) {
216344
216511
  "use strict";
216345
216512
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
216346
- var require_sleep = require_sleep_DXsYD0RX();
216347
- var require_event_category = require_event_category_yW4s1MGD();
216513
+ var require_sleep = require_sleep_D7dV_FZB();
216514
+ var require_event_category = require_event_category_BVfsrBYA();
216348
216515
  var require_canonical_hash = require_canonical_hash_9lydiEzx();
216349
216516
  var require_enums2 = require_enums();
216350
216517
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -216586,6 +216753,50 @@ var require_dist4 = __commonJS({
216586
216753
  audio: "passthrough"
216587
216754
  };
216588
216755
  }
216756
+ function compareToTarget(a, b, target) {
216757
+ const aReaches = a >= target;
216758
+ if (aReaches !== b >= target) return aReaches ? -1 : 1;
216759
+ return aReaches ? a - b : b - a;
216760
+ }
216761
+ function canonicalCodec(value) {
216762
+ const v = value.toLowerCase().replace(/[.\s-]/g, "");
216763
+ if (v === "hevc" || v === "hvc1" || v === "hev1") return "h265";
216764
+ if (v === "avc" || v === "avc1") return "h264";
216765
+ return v;
216766
+ }
216767
+ function codecIsAccepted(codec, accept) {
216768
+ if (accept.length === 0) return true;
216769
+ if (codec === void 0) return false;
216770
+ const c = canonicalCodec(codec);
216771
+ return accept.some((a) => canonicalCodec(a) === c);
216772
+ }
216773
+ function providerRank(camStreamId, preferred) {
216774
+ for (let i = 0; i < preferred.length; i += 1) if (camStreamId.startsWith(preferred[i])) return i;
216775
+ return preferred.length;
216776
+ }
216777
+ function rankStreams(candidates, ranking) {
216778
+ const usable = candidates.filter((c) => ranking.allowTranscode || codecIsAccepted(c.codec, ranking.acceptCodecs));
216779
+ if (usable.length === 0) return null;
216780
+ const target = ranking.targetHeight;
216781
+ const scored = usable.map((candidate) => ({
216782
+ candidate,
216783
+ transcodes: !codecIsAccepted(candidate.codec, ranking.acceptCodecs),
216784
+ provider: providerRank(candidate.camStreamId, ranking.preferredProviders),
216785
+ height: candidate.height ?? 0
216786
+ }));
216787
+ scored.sort((a, b) => {
216788
+ if (a.transcodes !== b.transcodes) return a.transcodes ? 1 : -1;
216789
+ if (a.provider !== b.provider) return a.provider - b.provider;
216790
+ if (target !== void 0) return compareToTarget(a.height, b.height, target);
216791
+ return ranking.resolutionPreference === "highest" ? b.height - a.height : a.height - b.height;
216792
+ });
216793
+ const winner = scored[0];
216794
+ if (winner === void 0) return null;
216795
+ return {
216796
+ candidate: winner.candidate,
216797
+ transcodes: winner.transcodes
216798
+ };
216799
+ }
216589
216800
  var BASE_LIVE_EGRESS_PROFILE = {
216590
216801
  video: {
216591
216802
  codec: "h264",
@@ -219104,22 +219315,10 @@ var require_dist4 = __commonJS({
219104
219315
  }
219105
219316
  function pickClosestResolution(entries2, target) {
219106
219317
  const targetPixels = target.width * target.height;
219107
- let bestAbove;
219108
- let bestBelow;
219109
- for (const e of entries2) {
219110
- if (e.resolution === void 0) continue;
219111
- const pixels = e.resolution.width * e.resolution.height;
219112
- if (pixels >= targetPixels) {
219113
- if (!bestAbove || pixels < bestAbove.pixels) bestAbove = {
219114
- entry: e,
219115
- pixels
219116
- };
219117
- } else if (!bestBelow || pixels > bestBelow.pixels) bestBelow = {
219118
- entry: e,
219119
- pixels
219120
- };
219121
- }
219122
- return bestAbove?.entry ?? bestBelow?.entry;
219318
+ return [...entries2.flatMap((entry) => entry.resolution === void 0 ? [] : [{
219319
+ entry,
219320
+ pixels: entry.resolution.width * entry.resolution.height
219321
+ }])].sort((a, b) => compareToTarget(a.pixels, b.pixels, targetPixels))[0]?.entry;
219123
219322
  }
219124
219323
  var FORMAT_KEYS = [
219125
219324
  "onnx",
@@ -221981,6 +222180,39 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
221981
222180
  exposesDeviceSettings: true,
221982
222181
  methods: {}
221983
222182
  };
222183
+ var StreamSignalSchema = zod.z.object({
222184
+ /** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
222185
+ code: zod.z.string().min(1),
222186
+ /** What an operator reads in the picker. The provider's own wording. */
222187
+ label: zod.z.string().min(1),
222188
+ /**
222189
+ * Whether the provider recommends this signal ON when a camera is first set
222190
+ * up. A provider knows which of its signals are cheap and reliable; an
222191
+ * operator should not have to discover that by trial. Reolink recommends
222192
+ * both of its own.
222193
+ */
222194
+ recommended: zod.z.boolean()
222195
+ });
222196
+ var StreamSignalsStatusSchema = zod.z.object({
222197
+ signals: zod.z.array(StreamSignalSchema),
222198
+ lastFetchedAt: zod.z.number()
222199
+ });
222200
+ var streamSignalsCapability = {
222201
+ name: "stream-signals",
222202
+ scope: "device",
222203
+ deviceNative: true,
222204
+ mode: "singleton",
222205
+ deviceTypes: Object.values(require_sleep.DeviceType),
222206
+ runtimeState: StreamSignalsStatusSchema,
222207
+ methods: {
222208
+ /**
222209
+ * What this device can emit. Empty is a valid and common answer — most
222210
+ * cameras have nothing to offer here, and an empty list is what makes the
222211
+ * broker's picker show nothing rather than a false choice.
222212
+ */
222213
+ listSignals: require_sleep.method(zod.z.void(), zod.z.array(StreamSignalSchema).readonly())
222214
+ }
222215
+ };
221984
222216
  var StreamFormatSchema = zod.z.enum([
221985
222217
  "webrtc",
221986
222218
  "hls",
@@ -222333,6 +222565,46 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222333
222565
  mode: "singleton",
222334
222566
  exposesDeviceSettings: true,
222335
222567
  methods: {
222568
+ /**
222569
+ * A device reporting that one of ITS OWN signals just fired.
222570
+ *
222571
+ * The provider calls this; nothing polls. The broker then decides whether
222572
+ * to wake the source, and the decision has two terms that must BOTH hold:
222573
+ *
222574
+ * - the signal is ENABLED for this camera (the operator's per-camera
222575
+ * choice — a camera may declare a signal the operator does not want
222576
+ * waking anything), and
222577
+ * - a consumer is actually attached and waiting on the prestream. Waking
222578
+ * a camera nobody is watching is the one thing a battery camera must
222579
+ * never do, and a signal arriving with no consumer is exactly that.
222580
+ *
222581
+ * Reported, never inferred: a signal that fires while the stream is
222582
+ * already up refreshes the hold instead of starting a second one, which is
222583
+ * what makes continuous motion keep a camera awake without stacking
222584
+ * timers.
222585
+ *
222586
+ * `code` is the provider's own, from `streamSignals.listSignals`. An
222587
+ * unknown code is REFUSED rather than treated as a wildcard wake — a
222588
+ * provider and an operator disagreeing about a name must not become a
222589
+ * camera that wakes for everything.
222590
+ */
222591
+ reportStreamSignal: require_sleep.method(zod.z.object({
222592
+ deviceId: zod.z.number().int().nonnegative(),
222593
+ /** The provider's signal code. */
222594
+ code: zod.z.string().min(1),
222595
+ /** Ms epoch. Absent ⇒ now. */
222596
+ at: zod.z.number().optional()
222597
+ }), zod.z.object({
222598
+ /** Whether the broker acted on it, and if not, why. */
222599
+ accepted: zod.z.boolean(),
222600
+ reason: zod.z.enum([
222601
+ "woke",
222602
+ "hold-extended",
222603
+ "not-enabled",
222604
+ "no-consumer",
222605
+ "unknown-code"
222606
+ ])
222607
+ }), { kind: "mutation" }),
222336
222608
  publishCameraStream: require_sleep.method(zod.z.object({
222337
222609
  deviceId: zod.z.number().int().nonnegative(),
222338
222610
  camStreamId: zod.z.string().min(1),
@@ -222810,6 +223082,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222810
223082
  acceptCodecs: zod.z.array(StreamCodecSchema).readonly().optional(),
222811
223083
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
222812
223084
  minHeight: zod.z.number().int().positive().optional(),
223085
+ /**
223086
+ * Maximum vertical resolution. Streams TALLER than this are dropped.
223087
+ *
223088
+ * A consumer can have a ceiling as real as its floor: Alexa documents
223089
+ * 480p to 1080p, and a 4K stream is as unusable to an Echo as a 360p one.
223090
+ * Without this the ceiling had to be re-implemented by every caller — and
223091
+ * one caller implementing it privately is how a second scoring authority
223092
+ * gets born.
223093
+ */
223094
+ maxHeight: zod.z.number().int().positive().optional(),
222813
223095
  /** Minimum horizontal resolution. */
222814
223096
  minWidth: zod.z.number().int().positive().optional(),
222815
223097
  /**
@@ -222826,7 +223108,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222826
223108
  * transcoded" guard: if the device is already serving the consumer's
222827
223109
  * codec end-to-end, there's nothing to optimise.
222828
223110
  */
222829
- requireSiblingCodec: zod.z.array(StreamCodecSchema).readonly().optional()
223111
+ requireSiblingCodec: zod.z.array(StreamCodecSchema).readonly().optional(),
223112
+ /**
223113
+ * Whether a stream the consumer CANNOT decode may still be picked, on the
223114
+ * understanding that it will be transcoded.
223115
+ *
223116
+ * Default `false` — today's behaviour, and the right one for a bypass
223117
+ * question ("is there a stream I can forward untouched?"). Set `true` to
223118
+ * ask the larger question: "what is the best stream for me, transcoding if
223119
+ * I must?" A stream that satisfies `acceptCodecs` always outranks one that
223120
+ * does not, so a passthrough is never lost to a transcode; the answer says
223121
+ * which it is in {@link PickedCamStreamSchema.transcodes}.
223122
+ *
223123
+ * This is what lets one picker serve both the bypass and the full source
223124
+ * choice, instead of a consumer scoring privately when the bypass misses.
223125
+ */
223126
+ allowTranscode: zod.z.boolean().optional()
222830
223127
  }).readonly();
222831
223128
  var PickStreamPreferencesSchema = zod.z.object({
222832
223129
  /**
@@ -222840,12 +223137,32 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222840
223137
  * picks the tallest stream; `'lowest'` picks the shortest (used by
222841
223138
  * memory-constrained consumers / Apple Home guest sessions).
222842
223139
  */
222843
- resolutionPreference: zod.z.enum(["highest", "lowest"]).optional()
223140
+ resolutionPreference: zod.z.enum(["highest", "lowest"]).optional(),
223141
+ /**
223142
+ * The height the consumer actually wants to DELIVER.
223143
+ *
223144
+ * Not a constraint — an ordering. With it set, the SMALLEST stream at or
223145
+ * above the target wins, and only if nothing reaches it does the tallest
223146
+ * take over. Pulling 1296 lines to draw 720 on a 1280x800 Echo panel costs
223147
+ * a decode and buys nothing, and `resolutionPreference: 'highest'` cannot
223148
+ * express that: it says "as big as possible", which is a different wish.
223149
+ *
223150
+ * Ignored when absent, so every existing caller keeps its ordering.
223151
+ */
223152
+ targetHeight: zod.z.number().int().positive().optional()
222844
223153
  }).readonly();
222845
223154
  var PickedCamStreamSchema = zod.z.object({
222846
223155
  camStreamId: zod.z.string(),
222847
223156
  codec: zod.z.string().optional(),
222848
223157
  resolution: require_sleep.CamStreamResolutionSchema.optional(),
223158
+ /**
223159
+ * Whether serving this stream requires a decode + re-encode.
223160
+ *
223161
+ * `false` is a stream the consumer can take as it stands. Only ever `true`
223162
+ * when the caller asked for it with `allowTranscode`, so a caller that did
223163
+ * not ask cannot be handed a cost it never agreed to pay.
223164
+ */
223165
+ transcodes: zod.z.boolean(),
222849
223166
  /** One-line explanation of why this stream won — for logs / debug UI. */
222850
223167
  reason: zod.z.string()
222851
223168
  });
@@ -228224,6 +228541,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
228224
228541
  "device-enabled",
228225
228542
  "device-battery-low",
228226
228543
  "device-battery-normal",
228544
+ "device-consumable-low",
228545
+ "device-consumable-normal",
228227
228546
  "stream-online",
228228
228547
  "stream-offline",
228229
228548
  "node-online",
@@ -228242,6 +228561,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
228242
228561
  "addon-updated",
228243
228562
  "server-updated",
228244
228563
  "export-completed",
228564
+ "backup-completed",
228245
228565
  "camera-online",
228246
228566
  "camera-offline",
228247
228567
  "camera-disabled",
@@ -229103,10 +229423,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
229103
229423
  value: "server-updated",
229104
229424
  label: "Server updated"
229105
229425
  },
229426
+ {
229427
+ value: "device-consumable-low",
229428
+ label: "Consumable low"
229429
+ },
229430
+ {
229431
+ value: "device-consumable-normal",
229432
+ label: "Consumable replaced"
229433
+ },
229106
229434
  {
229107
229435
  value: "export-completed",
229108
229436
  label: "Export completed"
229109
229437
  },
229438
+ {
229439
+ value: "backup-completed",
229440
+ label: "Backup completed"
229441
+ },
229110
229442
  {
229111
229443
  value: "alarm-arming",
229112
229444
  label: "Alarm arming (exit delay)"
@@ -236491,10 +236823,28 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
236491
236823
  getSessionState: require_sleep.method(zod.z.object({
236492
236824
  deviceId: zod.z.number().int().nonnegative(),
236493
236825
  sessionId: zod.z.string()
236494
- }), zod.z.object({ pendingRenegotiation: zod.z.object({
236495
- target: WebrtcStreamTargetSchema,
236496
- epoch: zod.z.number()
236497
- }).nullable() }))
236826
+ }), zod.z.object({
236827
+ pendingRenegotiation: zod.z.object({
236828
+ target: WebrtcStreamTargetSchema,
236829
+ epoch: zod.z.number()
236830
+ }).nullable(),
236831
+ /**
236832
+ * Whether the session still EXISTS.
236833
+ *
236834
+ * A consumer that holds a resource for the life of a session needs to
236835
+ * be able to ask, because a session does not always end the way it
236836
+ * began. Measured on this hub 2026-09-13: an Echo that got stuck never
236837
+ * sent `SessionDisconnected`, so the Alexa exporter's
236838
+ * `releaseEgressTranscode` never ran, and a 2304x1296 HEVC to 720p
236839
+ * H.264 transcode kept running for NOBODY for more than twenty
236840
+ * minutes. The WebRTC session had logged `WebRTC session closed`
236841
+ * minutes earlier — the broker knew; the holder had no way to ask.
236842
+ *
236843
+ * `false` for a session id the provider has never heard of, which is
236844
+ * the same answer as "it ended": either way nothing is holding it up.
236845
+ */
236846
+ alive: zod.z.boolean()
236847
+ }))
236498
236848
  },
236499
236849
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
236500
236850
  mount: { kind: "server-provided" }
@@ -246161,6 +246511,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246161
246511
  smoke: smokeCapability,
246162
246512
  streamCatalog: streamCatalogCapability,
246163
246513
  streamParams: streamParamsCapability,
246514
+ streamSignals: streamSignalsCapability,
246164
246515
  switch: switchCapability,
246165
246516
  tamper: tamperCapability,
246166
246517
  temperatureSensor: temperatureSensorCapability,
@@ -248898,6 +249249,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
248898
249249
  streamBroker: "stream-broker",
248899
249250
  streamCatalog: "stream-catalog",
248900
249251
  streamParams: "stream-params",
249252
+ streamSignals: "stream-signals",
248901
249253
  switch: "switch",
248902
249254
  system: "system",
248903
249255
  tamper: "tamper",
@@ -249458,6 +249810,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249458
249810
  key: "streamParams",
249459
249811
  name: "stream-params"
249460
249812
  },
249813
+ {
249814
+ key: "streamSignals",
249815
+ name: "stream-signals"
249816
+ },
249461
249817
  {
249462
249818
  key: "switch",
249463
249819
  name: "switch"
@@ -249682,6 +250038,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249682
250038
  streamBrokerCapability,
249683
250039
  streamCatalogCapability,
249684
250040
  streamParamsCapability,
250041
+ streamSignalsCapability,
249685
250042
  switchCapability,
249686
250043
  systemCapability,
249687
250044
  tamperCapability,
@@ -255124,6 +255481,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255124
255481
  addonId: null,
255125
255482
  access: "create"
255126
255483
  },
255484
+ "streamBroker.reportStreamSignal": {
255485
+ capName: "stream-broker",
255486
+ capScope: "system",
255487
+ addonId: null,
255488
+ access: "create"
255489
+ },
255127
255490
  "streamBroker.restartProfile": {
255128
255491
  capName: "stream-broker",
255129
255492
  capScope: "system",
@@ -255208,6 +255571,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255208
255571
  addonId: null,
255209
255572
  access: "create"
255210
255573
  },
255574
+ "streamSignals.listSignals": {
255575
+ capName: "stream-signals",
255576
+ capScope: "device",
255577
+ addonId: null,
255578
+ access: "view"
255579
+ },
255211
255580
  "switch.setState": {
255212
255581
  capName: "switch",
255213
255582
  capScope: "device",
@@ -256011,6 +256380,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
256011
256380
  "stream-broker",
256012
256381
  "stream-catalog",
256013
256382
  "stream-params",
256383
+ "stream-signals",
256014
256384
  "switch",
256015
256385
  "system",
256016
256386
  "terminal-session",
@@ -256077,6 +256447,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
256077
256447
  "snapshot",
256078
256448
  "stream-catalog",
256079
256449
  "stream-params",
256450
+ "stream-signals",
256080
256451
  "switch",
256081
256452
  "update",
256082
256453
  "vacuum-control",
@@ -257844,6 +258215,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
257844
258215
  form: "single",
257845
258216
  optional: false
257846
258217
  }],
258218
+ "streamBroker.reportStreamSignal": [{
258219
+ name: "deviceId",
258220
+ form: "single",
258221
+ optional: false
258222
+ }],
257847
258223
  "streamBroker.restartProfile": [{
257848
258224
  name: "deviceId",
257849
258225
  form: "single",
@@ -258201,6 +258577,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
258201
258577
  "streamBroker.produceEventMedia",
258202
258578
  "streamBroker.publishCameraStream",
258203
258579
  "streamBroker.renderPreBufferClip",
258580
+ "streamBroker.reportStreamSignal",
258204
258581
  "streamBroker.restartProfile",
258205
258582
  "streamBroker.retractCameraStream",
258206
258583
  "streamBroker.setDeviceAudioMute",
@@ -258450,6 +258827,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
258450
258827
  durability: "restored",
258451
258828
  volatileFields: ["lastFetchedAt"]
258452
258829
  },
258830
+ "stream-signals": {
258831
+ durability: "session",
258832
+ volatileFields: []
258833
+ },
258453
258834
  "switch": {
258454
258835
  durability: "restored",
258455
258836
  volatileFields: ["lastChangedAt"]
@@ -262908,6 +263289,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
262908
263289
  exports.StreamProfileOptionsSchema = StreamProfileOptionsSchema;
262909
263290
  exports.StreamProfilePatchSchema = StreamProfilePatchSchema;
262910
263291
  exports.StreamProfileSchema = StreamProfileSchema;
263292
+ exports.StreamSignalSchema = StreamSignalSchema;
263293
+ exports.StreamSignalsStatusSchema = StreamSignalsStatusSchema;
262911
263294
  exports.StreamSourceEntrySchema = require_sleep.StreamSourceEntrySchema;
262912
263295
  exports.StreamSourceSchema = require_sleep.StreamSourceSchema;
262913
263296
  exports.SubscribeAudioChunksInputSchema = require_sleep.SubscribeAudioChunksInputSchema;
@@ -263093,12 +263476,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263093
263476
  exports.clusterModelSettingKey = clusterModelSettingKey;
263094
263477
  exports.clusterStepSettingFieldsFor = clusterStepSettingFieldsFor;
263095
263478
  exports.clusterStepSettingKey = clusterStepSettingKey;
263479
+ exports.codecIsAccepted = codecIsAccepted;
263096
263480
  exports.collectHydratedFieldEntries = require_sleep.collectHydratedFieldEntries;
263097
263481
  exports.collectHydratedFieldValues = require_sleep.collectHydratedFieldValues;
263098
263482
  exports.collectSecretConfigKeys = collectSecretConfigKeys;
263099
263483
  exports.colorCapability = colorCapability;
263100
263484
  exports.colorForKind = colorForKind;
263101
263485
  exports.commitWatchdogRestart = commitWatchdogRestart;
263486
+ exports.compareToTarget = compareToTarget;
263102
263487
  exports.compileExpression = compileExpression;
263103
263488
  exports.compileExpressionSafe = compileExpressionSafe;
263104
263489
  exports.composeSwitchedOff = composeSwitchedOff;
@@ -263357,6 +263742,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263357
263742
  exports.ptzAutotrackCapability = ptzAutotrackCapability;
263358
263743
  exports.ptzCapability = ptzCapability;
263359
263744
  exports.pythonScriptForBackend = pythonScriptForBackend;
263745
+ exports.rankStreams = rankStreams;
263360
263746
  exports.readClusterStepModels = readClusterStepModels;
263361
263747
  exports.readClusterStepSettings = readClusterStepSettings;
263362
263748
  exports.readDetailCropConvention = readDetailCropConvention;
@@ -263441,6 +263827,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263441
263827
  exports.streamParamsCapability = streamParamsCapability;
263442
263828
  exports.streamPixels = streamPixels;
263443
263829
  exports.streamQualityLabel = streamQualityLabel;
263830
+ exports.streamSignalsCapability = streamSignalsCapability;
263444
263831
  exports.stripRetiredBandPreBufferSec = stripRetiredBandPreBufferSec;
263445
263832
  exports.strippedOfLegacyReadOnly = strippedOfLegacyReadOnly;
263446
263833
  exports.subKindsOf = subKindsOf;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "1.2.146",
3
+ "version": "1.2.149",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",