camstack 1.2.145 → 1.2.147

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.
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-B84YlKAH.js
23637
- var require_dist_B84YlKAH = __commonJS({
23638
- "../system/dist/dist-B84YlKAH.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_B84YlKAH = __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";
@@ -25889,7 +25890,23 @@ var require_dist_B84YlKAH = __commonJS({
25889
25890
  "zerolatency",
25890
25891
  "film",
25891
25892
  "animation"
25892
- ]).optional()
25893
+ ]).optional(),
25894
+ /**
25895
+ * ONE slice per access unit.
25896
+ *
25897
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
25898
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
25899
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
25900
+ * frame-end per frame: the first pictures render and the video then freezes
25901
+ * for good while the audio, on its own plane, plays on. Measured on this hub
25902
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
25903
+ *
25904
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
25905
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
25906
+ * consumers meaning the same thing spelled differently would stop sharing one
25907
+ * child. Absent means "whatever the encoder does" — today's behaviour.
25908
+ */
25909
+ singleSlicePerFrame: zod.z.boolean().optional()
25893
25910
  });
25894
25911
  var AudioEncodeSchema = zod.z.union([zod.z.literal("passthrough"), zod.z.object({
25895
25912
  codec: zod.z.enum([
@@ -25937,7 +25954,7 @@ var require_dist_B84YlKAH = __commonJS({
25937
25954
  audio: "passthrough"
25938
25955
  };
25939
25956
  ({ ...BASE_LIVE_EGRESS_PROFILE }, { ...BASE_LIVE_EGRESS_PROFILE.video });
25940
- ({ ...BASE_LIVE_EGRESS_PROFILE });
25957
+ ({ ...BASE_LIVE_EGRESS_PROFILE }, { ...BASE_LIVE_EGRESS_PROFILE.video });
25941
25958
  var wiringProbeKindSchema = zod.z.enum([
25942
25959
  "singleton",
25943
25960
  "device",
@@ -29430,6 +29447,39 @@ var require_dist_B84YlKAH = __commonJS({
29430
29447
  exposesDeviceSettings: true,
29431
29448
  methods: {}
29432
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
+ };
29433
29483
  var StreamFormatSchema = zod.z.enum([
29434
29484
  "webrtc",
29435
29485
  "hls",
@@ -29782,6 +29832,46 @@ var require_dist_B84YlKAH = __commonJS({
29782
29832
  mode: "singleton",
29783
29833
  exposesDeviceSettings: true,
29784
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" }),
29785
29875
  publishCameraStream: method(zod.z.object({
29786
29876
  deviceId: zod.z.number().int().nonnegative(),
29787
29877
  camStreamId: zod.z.string().min(1),
@@ -30259,6 +30349,16 @@ var require_dist_B84YlKAH = __commonJS({
30259
30349
  acceptCodecs: zod.z.array(StreamCodecSchema).readonly().optional(),
30260
30350
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
30261
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(),
30262
30362
  /** Minimum horizontal resolution. */
30263
30363
  minWidth: zod.z.number().int().positive().optional(),
30264
30364
  /**
@@ -30275,7 +30375,22 @@ var require_dist_B84YlKAH = __commonJS({
30275
30375
  * transcoded" guard: if the device is already serving the consumer's
30276
30376
  * codec end-to-end, there's nothing to optimise.
30277
30377
  */
30278
- 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()
30279
30394
  }).readonly();
30280
30395
  var PickStreamPreferencesSchema = zod.z.object({
30281
30396
  /**
@@ -30289,12 +30404,32 @@ var require_dist_B84YlKAH = __commonJS({
30289
30404
  * picks the tallest stream; `'lowest'` picks the shortest (used by
30290
30405
  * memory-constrained consumers / Apple Home guest sessions).
30291
30406
  */
30292
- 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()
30293
30420
  }).readonly();
30294
30421
  var PickedCamStreamSchema = zod.z.object({
30295
30422
  camStreamId: zod.z.string(),
30296
30423
  codec: zod.z.string().optional(),
30297
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(),
30298
30433
  /** One-line explanation of why this stream won — for logs / debug UI. */
30299
30434
  reason: zod.z.string()
30300
30435
  });
@@ -35208,6 +35343,8 @@ var require_dist_B84YlKAH = __commonJS({
35208
35343
  "device-enabled",
35209
35344
  "device-battery-low",
35210
35345
  "device-battery-normal",
35346
+ "device-consumable-low",
35347
+ "device-consumable-normal",
35211
35348
  "stream-online",
35212
35349
  "stream-offline",
35213
35350
  "node-online",
@@ -35226,6 +35363,7 @@ var require_dist_B84YlKAH = __commonJS({
35226
35363
  "addon-updated",
35227
35364
  "server-updated",
35228
35365
  "export-completed",
35366
+ "backup-completed",
35229
35367
  "camera-online",
35230
35368
  "camera-offline",
35231
35369
  "camera-disabled",
@@ -38084,13 +38222,91 @@ var require_dist_B84YlKAH = __commonJS({
38084
38222
  deviceIds: zod.z.array(zod.z.number()).min(1),
38085
38223
  since: zod.z.number().optional(),
38086
38224
  until: zod.z.number().optional(),
38087
- limit: zod.z.number().int().min(1).max(200).default(50)
38225
+ limit: zod.z.number().int().min(1).max(200).default(50),
38226
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
38227
+ * meaning of "dog" on every summary surface. */
38228
+ classes: zod.z.array(zod.z.string().min(1).max(60)).max(40).optional()
38229
+ });
38230
+ var SummaryMemberSchema = zod.z.object({
38231
+ trackId: zod.z.string(),
38232
+ /**
38233
+ * The track's class at the time it JOINED, or `null` when it was unresolved
38234
+ * and the track no longer exists to fill it.
38235
+ *
38236
+ * The join, not the latest opinion: class voting can re-decide a track's
38237
+ * class afterwards, and a group's composition is the history of who came
38238
+ * together. A consumer counts these to badge a thumbnail — "two people and a
38239
+ * dog" — rather than being handed a second, denormalised tally that would
38240
+ * drift from the members it claims to describe.
38241
+ */
38242
+ className: zod.z.string().nullable(),
38243
+ /** `primary` is the one member that carries the group's identity. */
38244
+ role: zod.z.enum([
38245
+ "primary",
38246
+ "sibling",
38247
+ "child"
38248
+ ]).nullable(),
38249
+ joinedAt: zod.z.number(),
38250
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
38251
+ * at read time. Absent when nothing resolved, never an empty string. */
38252
+ label: zod.z.string().optional(),
38253
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
38254
+ * recognised nobody, and absent for a member whose track has been pruned —
38255
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
38256
+ subLabel: zod.z.string().optional()
38088
38257
  });
38089
- var SummariesPageSchema = zod.z.object({ summaries: zod.z.array(SummarySchema).readonly() });
38090
38258
  var SummaryDetailSchema = zod.z.object({
38091
38259
  summary: SummarySchema,
38092
- /** Member track ids, in absorption order. */
38093
- trackIds: zod.z.array(zod.z.string()).readonly()
38260
+ /** Members in absorption order, oldest join first. */
38261
+ members: zod.z.array(SummaryMemberSchema).readonly()
38262
+ });
38263
+ var SummaryListItemSchema = zod.z.object({
38264
+ summary: SummarySchema,
38265
+ /** Class counts, most numerous first; `className: null` is a member whose
38266
+ * class never resolved, counted so the counts add up to `memberCount`. */
38267
+ classes: zod.z.array(zod.z.object({
38268
+ className: zod.z.string().nullable(),
38269
+ count: zod.z.number().int().positive()
38270
+ })).readonly()
38271
+ });
38272
+ var SummariesPageSchema = zod.z.object({ summaries: zod.z.array(SummaryListItemSchema).readonly() });
38273
+ var RecentSummariesQueryInput = zod.z.object({
38274
+ /** Devices to merge. An empty array yields an empty page. */
38275
+ deviceIds: zod.z.array(zod.z.number()),
38276
+ /** Window lower bound on `lastActiveAt` (inclusive). */
38277
+ since: zod.z.number().optional(),
38278
+ /** Window upper bound on `lastActiveAt` (inclusive). */
38279
+ until: zod.z.number().optional(),
38280
+ limit: zod.z.number().int().min(1).max(500).default(100),
38281
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
38282
+ cursor: zod.z.string().optional(),
38283
+ /**
38284
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
38285
+ * deliberately the same name: the events filter tree emits one list of ticked
38286
+ * taxonomy nodes, and a summaries feed that called it something else would
38287
+ * make the operator's one filter component produce two shapes.
38288
+ *
38289
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
38290
+ * me the arrivals with a dog" has to return the group of four people who had
38291
+ * one with them; a filter on the group's own fields could only ever answer
38292
+ * about its primary.
38293
+ *
38294
+ * Matched against all THREE tiers of a member — the class (`animal`), the
38295
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
38296
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
38297
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
38298
+ * that read only the class would answer "animal" and never "dog".
38299
+ *
38300
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
38301
+ */
38302
+ classes: zod.z.array(zod.z.string().min(1).max(60)).max(40).optional()
38303
+ });
38304
+ var RecentSummariesPageSchema = zod.z.object({
38305
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
38306
+ * {@link listSummaries} — one list shape, so a card renders the same
38307
+ * whichever feed drew it. */
38308
+ summaries: zod.z.array(SummaryListItemSchema).readonly(),
38309
+ nextCursor: zod.z.string().nullable()
38094
38310
  });
38095
38311
  var RecentTracksPageSchema = zod.z.object({
38096
38312
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -38442,6 +38658,11 @@ var require_dist_B84YlKAH = __commonJS({
38442
38658
  listRecentTracks: method(RecentTracksQueryInput, RecentTracksPageSchema),
38443
38659
  /** Newest first by `openedAt`, for a camera set, inside an optional window. */
38444
38660
  listSummaries: method(SummariesQueryInput, SummariesPageSchema),
38661
+ /**
38662
+ * The PAGED feed — the reel and the Events gallery. Same cursor contract as
38663
+ * {@link listRecentTracks}, so one screen cannot hold two ideas of newest.
38664
+ */
38665
+ listRecentSummaries: method(RecentSummariesQueryInput, RecentSummariesPageSchema),
38445
38666
  /** One summary with its member track ids; `null` for an id nobody wrote. */
38446
38667
  getSummary: method(zod.z.object({ id: zod.z.string() }), SummaryDetailSchema.nullable()),
38447
38668
  clearTracks: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), {
@@ -42731,10 +42952,28 @@ var require_dist_B84YlKAH = __commonJS({
42731
42952
  getSessionState: method(zod.z.object({
42732
42953
  deviceId: zod.z.number().int().nonnegative(),
42733
42954
  sessionId: zod.z.string()
42734
- }), zod.z.object({ pendingRenegotiation: zod.z.object({
42735
- target: WebrtcStreamTargetSchema,
42736
- epoch: zod.z.number()
42737
- }).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
+ }))
42738
42977
  },
42739
42978
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
42740
42979
  mount: { kind: "server-provided" }
@@ -50683,7 +50922,29 @@ var require_dist_B84YlKAH = __commonJS({
50683
50922
  * disarmed the channels it did not mention would make the Levels page and
50684
50923
  * the Diagnostics page fight over the same value.
50685
50924
  */
50686
- channels: zod.z.array(LogChannelWindowPatchSchema).readonly().optional()
50925
+ channels: zod.z.array(LogChannelWindowPatchSchema).readonly().optional(),
50926
+ /**
50927
+ * How many days the analytics OPS LOG is kept — the audit trail of what
50928
+ * retention actually deleted.
50929
+ *
50930
+ * It lives HERE, on the logging document, and not on the analytics addon's
50931
+ * settings form, on the operator's instruction: every other log-ish window in
50932
+ * this system is configured through this one document, and a retention audit
50933
+ * configured somewhere else is a second place to look when the question is
50934
+ * "what happened to my data".
50935
+ *
50936
+ * That placement has a cost worth stating: this document ships in the
50937
+ * framework closure, so this knob needs a published `@camstack/server` while
50938
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
50939
+ *
50940
+ * `retention-model.ts` argued this one must NOT follow a device window,
50941
+ * because the audit trail is the history of the SWEEP and not of the camera.
50942
+ * That argument stands and is untouched — what changes is that 30 days stops
50943
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
50944
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
50945
+ * state in which the next 9.5 GiB leak cannot be explained.
50946
+ */
50947
+ opsLogRetentionDays: zod.z.number().int().min(0).max(3650).optional()
50687
50948
  });
50688
50949
  var ReportedLoadContributionSchema = LoadContributionSchema.extend({ addonId: zod.z.string() });
50689
50950
  var ReportedFailureContributionSchema = FailureContributionSchema.extend({ addonId: zod.z.string() });
@@ -50722,6 +50983,14 @@ var require_dist_B84YlKAH = __commonJS({
50722
50983
  channels: zod.z.array(LogChannelDescriptorSchema).readonly(),
50723
50984
  /** The channels ARMED right now, each with its deadline. */
50724
50985
  activeChannels: zod.z.array(LogChannelWindowStateSchema).readonly(),
50986
+ /**
50987
+ * The ops-log window in days, as it is in force. `0` = kept forever.
50988
+ *
50989
+ * Reported rather than left to the caller's memory of what it wrote: this is
50990
+ * the value the analytics sweep will actually use, and an operator asking how
50991
+ * long their audit trail lives should not have to infer it from a default.
50992
+ */
50993
+ opsLogRetentionDays: zod.z.number().int().min(0),
50725
50994
  persisted: zod.z.boolean()
50726
50995
  });
50727
50996
  var systemCapability = {
@@ -52377,6 +52646,7 @@ var require_dist_B84YlKAH = __commonJS({
52377
52646
  streamBrokerCapability,
52378
52647
  streamCatalogCapability,
52379
52648
  streamParamsCapability,
52649
+ streamSignalsCapability,
52380
52650
  switchCapability,
52381
52651
  systemCapability,
52382
52652
  tamperCapability,
@@ -55851,6 +56121,12 @@ var require_dist_B84YlKAH = __commonJS({
55851
56121
  addonId: null,
55852
56122
  access: "view"
55853
56123
  },
56124
+ "pipelineAnalytics.listRecentSummaries": {
56125
+ capName: "pipeline-analytics",
56126
+ capScope: "device",
56127
+ addonId: null,
56128
+ access: "view"
56129
+ },
55854
56130
  "pipelineAnalytics.listRecentTracks": {
55855
56131
  capName: "pipeline-analytics",
55856
56132
  capScope: "device",
@@ -57813,6 +58089,12 @@ var require_dist_B84YlKAH = __commonJS({
57813
58089
  addonId: null,
57814
58090
  access: "create"
57815
58091
  },
58092
+ "streamBroker.reportStreamSignal": {
58093
+ capName: "stream-broker",
58094
+ capScope: "system",
58095
+ addonId: null,
58096
+ access: "create"
58097
+ },
57816
58098
  "streamBroker.restartProfile": {
57817
58099
  capName: "stream-broker",
57818
58100
  capScope: "system",
@@ -57897,6 +58179,12 @@ var require_dist_B84YlKAH = __commonJS({
57897
58179
  addonId: null,
57898
58180
  access: "create"
57899
58181
  },
58182
+ "streamSignals.listSignals": {
58183
+ capName: "stream-signals",
58184
+ capScope: "device",
58185
+ addonId: null,
58186
+ access: "view"
58187
+ },
57900
58188
  "switch.setState": {
57901
58189
  capName: "switch",
57902
58190
  capScope: "device",
@@ -59678,6 +59966,11 @@ var require_dist_B84YlKAH = __commonJS({
59678
59966
  form: "single",
59679
59967
  optional: true
59680
59968
  }],
59969
+ "pipelineAnalytics.listRecentSummaries": [{
59970
+ name: "deviceIds",
59971
+ form: "array",
59972
+ optional: false
59973
+ }],
59681
59974
  "pipelineAnalytics.listRecentTracks": [{
59682
59975
  name: "deviceIds",
59683
59976
  form: "array",
@@ -60264,6 +60557,11 @@ var require_dist_B84YlKAH = __commonJS({
60264
60557
  form: "single",
60265
60558
  optional: false
60266
60559
  }],
60560
+ "streamBroker.reportStreamSignal": [{
60561
+ name: "deviceId",
60562
+ form: "single",
60563
+ optional: false
60564
+ }],
60267
60565
  "streamBroker.restartProfile": [{
60268
60566
  name: "deviceId",
60269
60567
  form: "single",
@@ -60715,6 +61013,10 @@ var require_dist_B84YlKAH = __commonJS({
60715
61013
  durability: "restored",
60716
61014
  volatileFields: ["lastFetchedAt"]
60717
61015
  },
61016
+ "stream-signals": {
61017
+ durability: "session",
61018
+ volatileFields: []
61019
+ },
60718
61020
  "switch": {
60719
61021
  durability: "restored",
60720
61022
  volatileFields: ["lastChangedAt"]
@@ -61999,7 +62301,7 @@ var require_alerts_addon = __commonJS({
61999
62301
  [Symbol.toStringTag]: { value: "Module" }
62000
62302
  });
62001
62303
  require_chunk_Cek0wNdY();
62002
- var require_dist10 = require_dist_B84YlKAH();
62304
+ var require_dist10 = require_dist_CEIkHEdy();
62003
62305
  function selectExpired(alerts, cutoffMs) {
62004
62306
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
62005
62307
  }
@@ -62818,7 +63120,7 @@ var require_console_logging = __commonJS({
62818
63120
  [Symbol.toStringTag]: { value: "Module" }
62819
63121
  });
62820
63122
  require_chunk_Cek0wNdY();
62821
- var require_dist10 = require_dist_B84YlKAH();
63123
+ var require_dist10 = require_dist_CEIkHEdy();
62822
63124
  var require_formatter = require_formatter_DqAKDlvN();
62823
63125
  var LEVEL_RANK = {
62824
63126
  debug: 0,
@@ -62912,7 +63214,7 @@ var require_core_blocks_addon = __commonJS({
62912
63214
  "use strict";
62913
63215
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
62914
63216
  var require_chunk = require_chunk_Cek0wNdY();
62915
- var require_dist10 = require_dist_B84YlKAH();
63217
+ var require_dist10 = require_dist_CEIkHEdy();
62916
63218
  var node_crypto = __require("crypto");
62917
63219
  var node_fs_promises = __require("fs/promises");
62918
63220
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -63809,11 +64111,11 @@ var require_core_blocks = __commonJS({
63809
64111
  }
63810
64112
  });
63811
64113
 
63812
- // ../system/dist/retired-settings-keys-Cxgwa0bk.js
63813
- var require_retired_settings_keys_Cxgwa0bk = __commonJS({
63814
- "../system/dist/retired-settings-keys-Cxgwa0bk.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) {
63815
64117
  "use strict";
63816
- var require_dist10 = require_dist_B84YlKAH();
64118
+ var require_dist10 = require_dist_CEIkHEdy();
63817
64119
  function settingsStoreIsAuthoritativeHere(env) {
63818
64120
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
63819
64121
  return raw === "" || raw === "hub";
@@ -64093,9 +64395,9 @@ var require_retired_settings_keys_Cxgwa0bk = __commonJS({
64093
64395
  }
64094
64396
  });
64095
64397
 
64096
- // ../types/dist/canonical-hash-DNV8S5ET.js
64097
- var require_canonical_hash_DNV8S5ET = __commonJS({
64098
- "../types/dist/canonical-hash-DNV8S5ET.js"(exports) {
64398
+ // ../types/dist/canonical-hash-9lydiEzx.js
64399
+ var require_canonical_hash_9lydiEzx = __commonJS({
64400
+ "../types/dist/canonical-hash-9lydiEzx.js"(exports) {
64099
64401
  "use strict";
64100
64402
  var node_crypto = __require("crypto");
64101
64403
  var AUDIO_ENCODER_BY_CODEC = {
@@ -64182,6 +64484,7 @@ var require_canonical_hash_DNV8S5ET = __commonJS({
64182
64484
  ];
64183
64485
  if (video.preset !== void 0) args.push("-preset", video.preset);
64184
64486
  if (video.tune !== void 0) args.push("-tune", video.tune);
64487
+ if (video.singleSlicePerFrame === true && video.encoder === "libx264") args.push("-x264-params", "sliced-threads=0");
64185
64488
  if (video.profile !== void 0) args.push("-profile:v", video.profile);
64186
64489
  if (video.level !== void 0) args.push("-level", video.level);
64187
64490
  if (video.pixelFormat !== void 0) args.push("-pix_fmt", video.pixelFormat);
@@ -64365,6 +64668,7 @@ var require_canonical_hash_DNV8S5ET = __commonJS({
64365
64668
  scale,
64366
64669
  ...v.preset !== void 0 ? { preset: v.preset } : {},
64367
64670
  ...v.tune !== void 0 ? { tune: v.tune } : {},
64671
+ ...v.singleSlicePerFrame !== void 0 ? { singleSlicePerFrame: v.singleSlicePerFrame } : {},
64368
64672
  ...v.profile !== void 0 ? { profile: v.profile } : {},
64369
64673
  ...v.level !== void 0 ? { level: v.level } : {},
64370
64674
  ...input.pixelFormat !== void 0 ? { pixelFormat: input.pixelFormat } : {},
@@ -64695,7 +64999,7 @@ var require_node = __commonJS({
64695
64999
  value: mod,
64696
65000
  enumerable: true
64697
65001
  }) : target, mod));
64698
- var require_canonical_hash = require_canonical_hash_DNV8S5ET();
65002
+ var require_canonical_hash = require_canonical_hash_9lydiEzx();
64699
65003
  var require_err_msg = require_err_msg_COpsHMw2();
64700
65004
  var node_crypto = __require("crypto");
64701
65005
  var node_fs = __require("fs");
@@ -66048,8 +66352,8 @@ var require_device_manager_addon = __commonJS({
66048
66352
  [Symbol.toStringTag]: { value: "Module" }
66049
66353
  });
66050
66354
  require_chunk_Cek0wNdY();
66051
- var require_dist10 = require_dist_B84YlKAH();
66052
- var require_retired_settings_keys = require_retired_settings_keys_Cxgwa0bk();
66355
+ var require_dist10 = require_dist_CEIkHEdy();
66356
+ var require_retired_settings_keys = require_retired_settings_keys_FLZlwa2U();
66053
66357
  var node_crypto = __require("crypto");
66054
66358
  var _camstack_types_node = require_node();
66055
66359
  var JOB_HISTORY = 20;
@@ -71674,7 +71978,7 @@ var require_hub_forwarder = __commonJS({
71674
71978
  [Symbol.toStringTag]: { value: "Module" }
71675
71979
  });
71676
71980
  require_chunk_Cek0wNdY();
71677
- var require_dist10 = require_dist_B84YlKAH();
71981
+ var require_dist10 = require_dist_CEIkHEdy();
71678
71982
  var require_formatter = require_formatter_DqAKDlvN();
71679
71983
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
71680
71984
  var HubForwarderDestination = class {
@@ -71811,7 +72115,7 @@ var require_liveness_monitor_addon = __commonJS({
71811
72115
  "use strict";
71812
72116
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
71813
72117
  require_chunk_Cek0wNdY();
71814
- var require_dist10 = require_dist_B84YlKAH();
72118
+ var require_dist10 = require_dist_CEIkHEdy();
71815
72119
  var NO_DEVICES = "liveness:no-devices";
71816
72120
  var ALL_OFFLINE = "liveness:all-devices-offline";
71817
72121
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -72007,7 +72311,7 @@ var require_local_auth_addon = __commonJS({
72007
72311
  [Symbol.toStringTag]: { value: "Module" }
72008
72312
  });
72009
72313
  var require_chunk = require_chunk_Cek0wNdY();
72010
- var require_dist10 = require_dist_B84YlKAH();
72314
+ var require_dist10 = require_dist_CEIkHEdy();
72011
72315
  var node_crypto = __require("crypto");
72012
72316
  node_crypto = require_chunk.__toESM(node_crypto);
72013
72317
  var crypto$1 = __require("crypto");
@@ -79820,7 +80124,7 @@ var require_loki_logging = __commonJS({
79820
80124
  [Symbol.toStringTag]: { value: "Module" }
79821
80125
  });
79822
80126
  require_chunk_Cek0wNdY();
79823
- var require_dist10 = require_dist_B84YlKAH();
80127
+ var require_dist10 = require_dist_CEIkHEdy();
79824
80128
  function sanitizeLabelName(raw) {
79825
80129
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
79826
80130
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -80407,7 +80711,7 @@ var require_native_metrics_addon = __commonJS({
80407
80711
  [Symbol.toStringTag]: { value: "Module" }
80408
80712
  });
80409
80713
  var require_chunk = require_chunk_Cek0wNdY();
80410
- var require_dist10 = require_dist_B84YlKAH();
80714
+ var require_dist10 = require_dist_CEIkHEdy();
80411
80715
  var node_fs_promises = __require("fs/promises");
80412
80716
  var node_fs = __require("fs");
80413
80717
  node_fs = require_chunk.__toESM(node_fs);
@@ -83160,7 +83464,7 @@ var require_filesystem_storage_addon = __commonJS({
83160
83464
  [Symbol.toStringTag]: { value: "Module" }
83161
83465
  });
83162
83466
  var require_chunk = require_chunk_Cek0wNdY();
83163
- var require_dist10 = require_dist_B84YlKAH();
83467
+ var require_dist10 = require_dist_CEIkHEdy();
83164
83468
  var node_crypto = __require("crypto");
83165
83469
  var node_fs_promises = __require("fs/promises");
83166
83470
  var node_path = __require("path");
@@ -84387,8 +84691,8 @@ var require_sqlite_settings_addon = __commonJS({
84387
84691
  [Symbol.toStringTag]: { value: "Module" }
84388
84692
  });
84389
84693
  var require_chunk = require_chunk_Cek0wNdY();
84390
- var require_dist10 = require_dist_B84YlKAH();
84391
- var require_retired_settings_keys = require_retired_settings_keys_Cxgwa0bk();
84694
+ var require_dist10 = require_dist_CEIkHEdy();
84695
+ var require_retired_settings_keys = require_retired_settings_keys_FLZlwa2U();
84392
84696
  var require_wal_checkpoint_policy = require_wal_checkpoint_policy_CCJZngds();
84393
84697
  var node_crypto = __require("crypto");
84394
84698
  var node_path = __require("path");
@@ -87175,7 +87479,7 @@ var require_storage_orchestrator_addon = __commonJS({
87175
87479
  [Symbol.toStringTag]: { value: "Module" }
87176
87480
  });
87177
87481
  var require_chunk = require_chunk_Cek0wNdY();
87178
- var require_dist10 = require_dist_B84YlKAH();
87482
+ var require_dist10 = require_dist_CEIkHEdy();
87179
87483
  var require_hub_cap_forward = require_hub_cap_forward_DmHNjbB0();
87180
87484
  var zod = require_zod();
87181
87485
  var node_crypto = __require("crypto");
@@ -88520,6 +88824,57 @@ var require_storage_orchestrator_addon = __commonJS({
88520
88824
  const percent = require_dist10.gbToBytes(minFreeGb) / totalBytes * 100;
88521
88825
  return Math.min(100, percent);
88522
88826
  }
88827
+ function unknown(reason) {
88828
+ return {
88829
+ kind: "unknown",
88830
+ reason
88831
+ };
88832
+ }
88833
+ function freePercentOf(capacity) {
88834
+ if (capacity === null || capacity.totalBytes <= 0) return 100;
88835
+ return capacity.availableBytes / capacity.totalBytes * 100;
88836
+ }
88837
+ function volumeTotalBytesOf(capacity) {
88838
+ return capacity === null ? 0 : capacity.totalBytes;
88839
+ }
88840
+ function probePathFor(location, locality, localNodeId, cwd) {
88841
+ if (locality !== true) {
88842
+ const why = locality === false ? `is served by the remote provider "${location.providerId}" (nodeLocal: false) \u2014 its bytes live on the remote host and no local statfs describes them` : `is served by "${location.providerId}", which this node has not classified yet (no getProviderInfo answer) \u2014 a local statfs would be a guess`;
88843
+ return { reason: `location "${location.id}" ${why}` };
88844
+ }
88845
+ if (location.nodeId !== void 0 && location.nodeId !== localNodeId) return { reason: `location "${location.id}" lives on node "${location.nodeId}" and this is "${localNodeId}" \u2014 only the owning node can statfs its own volumes` };
88846
+ const basePath = location.config["basePath"];
88847
+ if (typeof basePath !== "string" || basePath.length === 0) return { reason: `location "${location.id}" declares no basePath to measure` };
88848
+ if (!node_path.default.isAbsolute(basePath)) return { reason: `location "${location.id}" declares a RELATIVE basePath ("${basePath}") \u2014 that is a path inside its provider's own namespace, not on this node's filesystem, and resolving it against "${cwd}" would measure an unrelated volume` };
88849
+ return { path: node_path.default.resolve(basePath) };
88850
+ }
88851
+ async function readLocalVolumeCapacity(location, locality, localNodeId, deps) {
88852
+ const target = probePathFor(location, locality, localNodeId, deps.cwd);
88853
+ if ("reason" in target) return unknown(target.reason);
88854
+ const root = node_path.default.parse(target.path).root;
88855
+ let probed = target.path;
88856
+ for (; ; ) try {
88857
+ await deps.probe.access(probed);
88858
+ break;
88859
+ } catch {
88860
+ const parent = node_path.default.dirname(probed);
88861
+ if (parent === probed) return unknown(`nothing on the path "${target.path}" exists on this node \u2014 the volume is not mounted, so the root filesystem's size is not an answer about it`);
88862
+ probed = parent;
88863
+ }
88864
+ if (probed === root && target.path !== root) return unknown(`only the filesystem root exists on the path "${target.path}" \u2014 the volume is not mounted, so the root filesystem's size is not an answer about it`);
88865
+ try {
88866
+ const st = await deps.probe.statfs(probed);
88867
+ return {
88868
+ kind: "measured",
88869
+ path: probed,
88870
+ totalBytes: st.blocks * st.bsize,
88871
+ availableBytes: st.bavail * st.bsize
88872
+ };
88873
+ } catch (err) {
88874
+ const reason = err instanceof Error ? err.message : String(err);
88875
+ return unknown(`statfs("${probed}") failed: ${reason}`);
88876
+ }
88877
+ }
88523
88878
  var FRAMEWORK_CONFIG_KEYS = /* @__PURE__ */ new Set([
88524
88879
  "basePath",
88525
88880
  "readOnly",
@@ -90572,31 +90927,53 @@ var require_storage_orchestrator_addon = __commonJS({
90572
90927
  } });
90573
90928
  throw new Error(refusal);
90574
90929
  }
90575
- /** statfs capacity of a location's basePath, walking up to the nearest
90576
- * existing ancestor. Null for remote-node locations or unstattable paths. */
90930
+ /**
90931
+ * The volume figures for one location, or `null` when this node cannot take
90932
+ * a measurement that is genuinely about it.
90933
+ *
90934
+ * The decision lives in `location-volume-capacity.ts` — including why a
90935
+ * remote-provider location (the SMB share that was reporting the hub's own
90936
+ * disk) gets nothing rather than a number. Here we only supply the node id,
90937
+ * the provider's declared locality and the real filesystem, and log the
90938
+ * reason once per distinct fault: `listLocations` is on the admin UI's poll,
90939
+ * so a line per call would be a line per second.
90940
+ */
90577
90941
  async localCapacityOf(loc) {
90578
- const localNode = this.service?.getLocalNodeId() ?? "hub";
90579
- if (loc.nodeId !== void 0 && loc.nodeId !== localNode) return null;
90580
- const bp = loc.config["basePath"];
90581
- if (typeof bp !== "string" || bp.length === 0) return null;
90582
- try {
90583
- let target = node_path.resolve(bp);
90584
- for (; ; ) try {
90585
- await node_fs_promises.access(target);
90586
- break;
90587
- } catch {
90588
- const parent = node_path.dirname(target);
90589
- if (!parent || parent === target) return null;
90590
- target = parent;
90591
- }
90592
- const st = await node_fs_promises.statfs(target);
90593
- return {
90594
- totalBytes: st.blocks * st.bsize,
90595
- availableBytes: st.bavail * st.bsize
90596
- };
90597
- } catch {
90942
+ const localNode = this.service?.getLocalNodeId() ?? HUB_NODE_ID;
90943
+ const result = await readLocalVolumeCapacity(loc, this.providerIsNodeLocal(loc), localNode, {
90944
+ probe: {
90945
+ access: (target) => node_fs_promises.access(target),
90946
+ statfs: async (target) => node_fs_promises.statfs(target)
90947
+ },
90948
+ cwd: node_path.resolve(".")
90949
+ });
90950
+ if (result.kind === "unknown") {
90951
+ this.logCapacityUnknownOnce(loc.id, result.reason);
90598
90952
  return null;
90599
90953
  }
90954
+ this.clearCapacityUnknown(loc.id, result.path);
90955
+ return {
90956
+ totalBytes: result.totalBytes,
90957
+ availableBytes: result.availableBytes
90958
+ };
90959
+ }
90960
+ /** locationId → the last unmeasurable reason logged for it. */
90961
+ capacityUnknownLogged = /* @__PURE__ */ new Map();
90962
+ logCapacityUnknownOnce(locationId, reason) {
90963
+ if (this.capacityUnknownLogged.get(locationId) === reason) return;
90964
+ this.capacityUnknownLogged.set(locationId, reason);
90965
+ this.ctx.logger.warn("storage-orchestrator: capacity is UNKNOWN for a location \u2014 reporting nothing rather than another volume\u2019s figures", { meta: {
90966
+ locationId,
90967
+ reason
90968
+ } });
90969
+ }
90970
+ clearCapacityUnknown(locationId, measuredPath) {
90971
+ if (!this.capacityUnknownLogged.has(locationId)) return;
90972
+ this.capacityUnknownLogged.delete(locationId);
90973
+ this.ctx.logger.info("storage-orchestrator: capacity is measurable again", { meta: {
90974
+ locationId,
90975
+ measuredPath
90976
+ } });
90600
90977
  }
90601
90978
  /** Per-location cap on EVICTABLE bytes (`config.maxUsedGb`): the operator's
90602
90979
  * "recordings may use at most N GB in total" — enforced by the pressure
@@ -90685,28 +91062,30 @@ var require_storage_orchestrator_addon = __commonJS({
90685
91062
  } });
90686
91063
  return occupancy;
90687
91064
  }
90688
- /** Free capacity (%) on a location's volume via `statfs`; 100 (guard inert) when unstattable. */
91065
+ /**
91066
+ * The pressure guard's two readings come from the SAME probe the UI's
91067
+ * capacity does — one capacity truth per location, not two.
91068
+ *
91069
+ * They used to `statfs` the raw `basePath` themselves, which on this node
91070
+ * answers for whatever local path happens to carry that name: a location
91071
+ * owned by another node, or served by a remote provider, could decide
91072
+ * eviction from the hub's own disk. Both now degrade exactly as they did
91073
+ * when the path was unstattable — `100` (guard inert) and `0` (no byte
91074
+ * target) — but they do so for every reading that is not ABOUT the location,
91075
+ * not only for the ones that threw.
91076
+ */
91077
+ async volumeCapacityOf(locationId) {
91078
+ const loc = this.service?.listLocations().find((l) => l.id === locationId);
91079
+ if (!loc) return null;
91080
+ return this.localCapacityOf(loc);
91081
+ }
91082
+ /** Free capacity (%) on a location's volume; 100 (guard inert) when unmeasurable. */
90689
91083
  async locationFreePercent(locationId) {
90690
- const bp = this.locationBasePath(locationId);
90691
- if (!bp) return 100;
90692
- try {
90693
- const st = await node_fs_promises.statfs(bp);
90694
- if (st.blocks <= 0) return 100;
90695
- return st.bavail / st.blocks * 100;
90696
- } catch {
90697
- return 100;
90698
- }
91084
+ return freePercentOf(await this.volumeCapacityOf(locationId));
90699
91085
  }
90700
- /** Total bytes on a location's volume via `statfs`; 0 when unstattable. */
91086
+ /** Total bytes on a location's volume; 0 when unmeasurable. */
90701
91087
  async locationVolumeTotalBytes(locationId) {
90702
- const bp = this.locationBasePath(locationId);
90703
- if (!bp) return 0;
90704
- try {
90705
- const st = await node_fs_promises.statfs(bp);
90706
- return st.blocks * st.bsize;
90707
- } catch {
90708
- return 0;
90709
- }
91088
+ return volumeTotalBytesOf(await this.volumeCapacityOf(locationId));
90710
91089
  }
90711
91090
  /**
90712
91091
  * Seed default storage locations from the addon-declared
@@ -90827,7 +91206,7 @@ var require_system_config_addon = __commonJS({
90827
91206
  [Symbol.toStringTag]: { value: "Module" }
90828
91207
  });
90829
91208
  require_chunk_Cek0wNdY();
90830
- var require_dist10 = require_dist_B84YlKAH();
91209
+ var require_dist10 = require_dist_CEIkHEdy();
90831
91210
  var SECTION_TITLES = {
90832
91211
  server: "Server",
90833
91212
  auth: "Authentication"
@@ -108888,7 +109267,7 @@ var require_winston_logging = __commonJS({
108888
109267
  [Symbol.toStringTag]: { value: "Module" }
108889
109268
  });
108890
109269
  var require_chunk = require_chunk_Cek0wNdY();
108891
- var require_dist10 = require_dist_B84YlKAH();
109270
+ var require_dist10 = require_dist_CEIkHEdy();
108892
109271
  var require_formatter = require_formatter_DqAKDlvN();
108893
109272
  var node_path = __require("path");
108894
109273
  node_path = require_chunk.__toESM(node_path);
@@ -110757,9 +111136,9 @@ ${(0, node_fs.readFileSync)(ca.caCertPath, "utf-8").trimEnd()}
110757
111136
  }
110758
111137
  });
110759
111138
 
110760
- // ../types/dist/event-category-yW4s1MGD.js
110761
- var require_event_category_yW4s1MGD = __commonJS({
110762
- "../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) {
110763
111142
  "use strict";
110764
111143
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
110765
111144
  EventCategory2["SystemBoot"] = "system.boot";
@@ -110899,6 +111278,7 @@ var require_event_category_yW4s1MGD = __commonJS({
110899
111278
  EventCategory2["LocalNetworkChanged"] = "network.local.changed";
110900
111279
  EventCategory2["MeshNetworkChanged"] = "network.mesh.changed";
110901
111280
  EventCategory2["BackupCompleted"] = "backup.completed";
111281
+ EventCategory2["BackupRunCompleted"] = "backup.run-completed";
110902
111282
  EventCategory2["BackupRestored"] = "backup.restored";
110903
111283
  EventCategory2["NotificationDispatched"] = "notification.dispatched";
110904
111284
  EventCategory2["NotificationFailed"] = "notification.failed";
@@ -110925,11 +111305,11 @@ var require_event_category_yW4s1MGD = __commonJS({
110925
111305
  }
110926
111306
  });
110927
111307
 
110928
- // ../types/dist/sleep-CfDqEZrs.js
110929
- var require_sleep_CfDqEZrs = __commonJS({
110930
- "../types/dist/sleep-CfDqEZrs.js"(exports) {
111308
+ // ../types/dist/sleep-D7dV-FZB.js
111309
+ var require_sleep_D7dV_FZB = __commonJS({
111310
+ "../types/dist/sleep-D7dV-FZB.js"(exports) {
110931
111311
  "use strict";
110932
- var require_event_category = require_event_category_yW4s1MGD();
111312
+ var require_event_category = require_event_category_BVfsrBYA();
110933
111313
  var zod = require_zod();
110934
111314
  var AUDIO_CHUNK_FORMATS = [
110935
111315
  "f32le",
@@ -113424,6 +113804,7 @@ var require_sleep_CfDqEZrs = __commonJS({
113424
113804
  smoke: createSliceHandle(stateSource, binding.deviceId, "smoke"),
113425
113805
  streamCatalog: createSliceHandle(stateSource, binding.deviceId, "stream-catalog"),
113426
113806
  streamParams: createSliceHandle(stateSource, binding.deviceId, "stream-params"),
113807
+ streamSignals: createSliceHandle(stateSource, binding.deviceId, "stream-signals"),
113427
113808
  switch: createSliceHandle(stateSource, binding.deviceId, "switch"),
113428
113809
  tamper: createSliceHandle(stateSource, binding.deviceId, "tamper"),
113429
113810
  temperatureSensor: createSliceHandle(stateSource, binding.deviceId, "temperature-sensor"),
@@ -113706,6 +114087,7 @@ var require_sleep_CfDqEZrs = __commonJS({
113706
114087
  listTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listTracks", "query", input),
113707
114088
  listRecentTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRecentTracks", "query", input),
113708
114089
  listSummaries: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listSummaries", "query", input),
114090
+ listRecentSummaries: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRecentSummaries", "query", input),
113709
114091
  getSummary: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getSummary", "query", input),
113710
114092
  clearTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "clearTracks", "mutation", input),
113711
114093
  getMotionEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getMotionEvents", "query", input),
@@ -113837,6 +114219,7 @@ var require_sleep_CfDqEZrs = __commonJS({
113837
114219
  getConfigSchema: (input) => dispatch("stream-params", "streamParams", "getConfigSchema", "query", input),
113838
114220
  getStatus: (input) => dispatch("stream-params", "streamParams", "getStatus", "query", input)
113839
114221
  },
114222
+ streamSignals: { listSignals: (input) => dispatch("stream-signals", "streamSignals", "listSignals", "query", input) },
113840
114223
  switch: {
113841
114224
  setState: (input) => dispatch("switch", "switch", "setState", "mutation", input),
113842
114225
  getStatus: (input) => dispatch("switch", "switch", "getStatus", "query", input)
@@ -114046,6 +114429,7 @@ var require_sleep_CfDqEZrs = __commonJS({
114046
114429
  },
114047
114430
  recordingExport: { listExports: (input) => dispatchSystem("recordingExport", "listExports", "query", input) },
114048
114431
  streamBroker: {
114432
+ reportStreamSignal: (input) => dispatchSystem("streamBroker", "reportStreamSignal", "mutation", input),
114049
114433
  publishCameraStream: (input) => dispatchSystem("streamBroker", "publishCameraStream", "mutation", input),
114050
114434
  retractCameraStream: (input) => dispatchSystem("streamBroker", "retractCameraStream", "mutation", input),
114051
114435
  assignProfile: (input) => dispatchSystem("streamBroker", "assignProfile", "mutation", input),
@@ -114132,6 +114516,7 @@ var require_sleep_CfDqEZrs = __commonJS({
114132
114516
  "snapshot",
114133
114517
  "stream-catalog",
114134
114518
  "stream-params",
114519
+ "stream-signals",
114135
114520
  "switch",
114136
114521
  "tamper",
114137
114522
  "temperature-sensor",
@@ -114759,8 +115144,8 @@ var require_addon = __commonJS({
114759
115144
  "../types/dist/addon.js"(exports) {
114760
115145
  "use strict";
114761
115146
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
114762
- var require_sleep = require_sleep_CfDqEZrs();
114763
- 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();
114764
115149
  var require_err_msg = require_err_msg_COpsHMw2();
114765
115150
  var CAP_INPUT_DEFAULTS = Object.freeze({
114766
115151
  "addons": { "getLogs": { "limit": 100 } },
@@ -114880,6 +115265,7 @@ var require_addon = __commonJS({
114880
115265
  "getMotionEvents": { "limit": 1e3 },
114881
115266
  "getObjectEvents": { "limit": 1e3 },
114882
115267
  "getSensorEvents": { "limit": 1e3 },
115268
+ "listRecentSummaries": { "limit": 100 },
114883
115269
  "listRecentTracks": { "limit": 200 },
114884
115270
  "listSummaries": { "limit": 50 },
114885
115271
  "reclaimDebugMedia": { "mode": "report" },
@@ -121624,9 +122010,9 @@ var require_dist2 = __commonJS({
121624
122010
  }
121625
122011
  });
121626
122012
 
121627
- // ../system/dist/manifest-system-deps-8x1jgPz_.js
121628
- var require_manifest_system_deps_8x1jgPz = __commonJS({
121629
- "../system/dist/manifest-system-deps-8x1jgPz_.js"(exports) {
122013
+ // ../system/dist/manifest-system-deps-CrSP0Kkp.js
122014
+ var require_manifest_system_deps_CrSP0Kkp = __commonJS({
122015
+ "../system/dist/manifest-system-deps-CrSP0Kkp.js"(exports) {
121630
122016
  "use strict";
121631
122017
  var require_chunk = require_chunk_Cek0wNdY();
121632
122018
  require_hub_cap_forward_DmHNjbB0();
@@ -123070,6 +123456,19 @@ var require_manifest_system_deps_8x1jgPz = __commonJS({
123070
123456
  });
123071
123457
  return cachedInvocation;
123072
123458
  }
123459
+ function buildNpmChildEnv(env, execPath) {
123460
+ const nodeDir = node_path.dirname(execPath);
123461
+ const current = env["PATH"];
123462
+ if (current === void 0 || current.length === 0) return {
123463
+ ...env,
123464
+ PATH: nodeDir
123465
+ };
123466
+ if (current === nodeDir || current.startsWith(`${nodeDir}${node_path.delimiter}`)) return { ...env };
123467
+ return {
123468
+ ...env,
123469
+ PATH: `${nodeDir}${node_path.delimiter}${current}`
123470
+ };
123471
+ }
123073
123472
  async function runNpm(args, options) {
123074
123473
  const invocation = await resolveNpmInvocation({
123075
123474
  cacheDir: options.cacheDir,
@@ -123078,7 +123477,8 @@ var require_manifest_system_deps_8x1jgPz = __commonJS({
123078
123477
  });
123079
123478
  return execFileAsync$2(invocation.command, [...invocation.argsPrefix, ...args], {
123080
123479
  ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
123081
- timeout: options.timeout ?? 3e5
123480
+ timeout: options.timeout ?? 3e5,
123481
+ env: buildNpmChildEnv(options.env ?? process.env, process.execPath)
123082
123482
  });
123083
123483
  }
123084
123484
  async function resolveUncached(options) {
@@ -130581,6 +130981,12 @@ var require_manifest_system_deps_8x1jgPz = __commonJS({
130581
130981
  return buildNativeCapProxy;
130582
130982
  }
130583
130983
  });
130984
+ Object.defineProperty(exports, "buildNpmChildEnv", {
130985
+ enumerable: true,
130986
+ get: function() {
130987
+ return buildNpmChildEnv;
130988
+ }
130989
+ });
130584
130990
  Object.defineProperty(exports, "buildUdsNativeCapProxy", {
130585
130991
  enumerable: true,
130586
130992
  get: function() {
@@ -131148,11 +131554,11 @@ var require_manifest_system_deps_8x1jgPz = __commonJS({
131148
131554
  }
131149
131555
  });
131150
131556
 
131151
- // ../system/dist/custom-action-registry-DwaSEiji.js
131152
- var require_custom_action_registry_DwaSEiji = __commonJS({
131153
- "../system/dist/custom-action-registry-DwaSEiji.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) {
131154
131560
  "use strict";
131155
- var require_dist10 = require_dist_B84YlKAH();
131561
+ var require_dist10 = require_dist_CEIkHEdy();
131156
131562
  var CustomActionRegistry = class {
131157
131563
  byAddon = /* @__PURE__ */ new Map();
131158
131564
  registerAddon(addonId, catalog, handler) {
@@ -134966,7 +135372,7 @@ var require_dist3 = __commonJS({
134966
135372
  "use strict";
134967
135373
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
134968
135374
  var require_chunk = require_chunk_Cek0wNdY();
134969
- var require_dist10 = require_dist_B84YlKAH();
135375
+ var require_dist10 = require_dist_CEIkHEdy();
134970
135376
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
134971
135377
  require_alerts();
134972
135378
  var require_formatter = require_formatter_DqAKDlvN();
@@ -134993,9 +135399,9 @@ var require_dist3 = __commonJS({
134993
135399
  var require_builtins_winston_logging_index = require_winston_logging();
134994
135400
  var require_event_loop_stall_monitor = require_event_loop_stall_monitor_CAsHdMwP();
134995
135401
  var require_tls$1 = require_tls_BFs4PzoW();
134996
- var require_manifest_system_deps = require_manifest_system_deps_8x1jgPz();
135402
+ var require_manifest_system_deps = require_manifest_system_deps_CrSP0Kkp();
134997
135403
  var require_resource_monitor = require_resource_monitor_LVE1BoLs();
134998
- var require_custom_action_registry = require_custom_action_registry_DwaSEiji();
135404
+ var require_custom_action_registry = require_custom_action_registry_YHpAdksL();
134999
135405
  var zod = require_zod();
135000
135406
  var node_crypto = __require("crypto");
135001
135407
  var _camstack_types_node = require_node();
@@ -215841,6 +216247,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
215841
216247
  exports.buildLinkChain = require_manifest_system_deps.buildLinkChain;
215842
216248
  exports.buildNativeCapProxy = require_manifest_system_deps.buildNativeCapProxy;
215843
216249
  exports.buildNodeManifest = buildNodeManifest;
216250
+ exports.buildNpmChildEnv = require_manifest_system_deps.buildNpmChildEnv;
215844
216251
  exports.buildStorageLocationRegistry = require_builtins_storage_orchestrator_storage_orchestrator_addon.buildStorageLocationRegistry;
215845
216252
  exports.buildUdsNativeCapProxy = require_manifest_system_deps.buildUdsNativeCapProxy;
215846
216253
  exports.builderMountedCapNames = builderMountedCapNames;
@@ -216083,7 +216490,7 @@ var require_enums = __commonJS({
216083
216490
  "../types/dist/enums.js"(exports) {
216084
216491
  "use strict";
216085
216492
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
216086
- var require_event_category = require_event_category_yW4s1MGD();
216493
+ var require_event_category = require_event_category_BVfsrBYA();
216087
216494
  var EventSourceType = /* @__PURE__ */ (function(EventSourceType2) {
216088
216495
  EventSourceType2["Addon"] = "addon";
216089
216496
  EventSourceType2["Core"] = "core";
@@ -216103,9 +216510,9 @@ var require_dist4 = __commonJS({
216103
216510
  "../types/dist/index.js"(exports) {
216104
216511
  "use strict";
216105
216512
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
216106
- var require_sleep = require_sleep_CfDqEZrs();
216107
- var require_event_category = require_event_category_yW4s1MGD();
216108
- var require_canonical_hash = require_canonical_hash_DNV8S5ET();
216513
+ var require_sleep = require_sleep_D7dV_FZB();
216514
+ var require_event_category = require_event_category_BVfsrBYA();
216515
+ var require_canonical_hash = require_canonical_hash_9lydiEzx();
216109
216516
  var require_enums2 = require_enums();
216110
216517
  var require_err_msg = require_err_msg_COpsHMw2();
216111
216518
  var zod = require_zod();
@@ -216285,7 +216692,23 @@ var require_dist4 = __commonJS({
216285
216692
  "zerolatency",
216286
216693
  "film",
216287
216694
  "animation"
216288
- ]).optional()
216695
+ ]).optional(),
216696
+ /**
216697
+ * ONE slice per access unit.
216698
+ *
216699
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
216700
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
216701
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
216702
+ * frame-end per frame: the first pictures render and the video then freezes
216703
+ * for good while the audio, on its own plane, plays on. Measured on this hub
216704
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
216705
+ *
216706
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
216707
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
216708
+ * consumers meaning the same thing spelled differently would stop sharing one
216709
+ * child. Absent means "whatever the encoder does" — today's behaviour.
216710
+ */
216711
+ singleSlicePerFrame: zod.z.boolean().optional()
216289
216712
  });
216290
216713
  var AudioEncodeSchema = zod.z.union([zod.z.literal("passthrough"), zod.z.object({
216291
216714
  codec: zod.z.enum([
@@ -216330,6 +216753,50 @@ var require_dist4 = __commonJS({
216330
216753
  audio: "passthrough"
216331
216754
  };
216332
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
+ }
216333
216800
  var BASE_LIVE_EGRESS_PROFILE = {
216334
216801
  video: {
216335
216802
  codec: "h264",
@@ -216358,7 +216825,11 @@ var require_dist4 = __commonJS({
216358
216825
  };
216359
216826
  var ALEXA_EGRESS_PROFILE = {
216360
216827
  ...BASE_LIVE_EGRESS_PROFILE,
216361
- audio: { codec: "opus" }
216828
+ audio: { codec: "opus" },
216829
+ video: {
216830
+ ...BASE_LIVE_EGRESS_PROFILE.video,
216831
+ singleSlicePerFrame: true
216832
+ }
216362
216833
  };
216363
216834
  var RATE_CONTROL_TIGHT = {
216364
216835
  kind: "cbr",
@@ -216446,7 +216917,8 @@ var require_dist4 = __commonJS({
216446
216917
  gopFrames: video.gopFrames ?? -1,
216447
216918
  bf: video.bf ?? -1,
216448
216919
  preset: video.preset ?? UNSET,
216449
- tune: video.tune ?? UNSET
216920
+ tune: video.tune ?? UNSET,
216921
+ singleSlicePerFrame: video.singleSlicePerFrame === void 0 ? UNSET : String(video.singleSlicePerFrame)
216450
216922
  };
216451
216923
  }
216452
216924
  function canonicalAudio(audio) {
@@ -218843,22 +219315,10 @@ var require_dist4 = __commonJS({
218843
219315
  }
218844
219316
  function pickClosestResolution(entries2, target) {
218845
219317
  const targetPixels = target.width * target.height;
218846
- let bestAbove;
218847
- let bestBelow;
218848
- for (const e of entries2) {
218849
- if (e.resolution === void 0) continue;
218850
- const pixels = e.resolution.width * e.resolution.height;
218851
- if (pixels >= targetPixels) {
218852
- if (!bestAbove || pixels < bestAbove.pixels) bestAbove = {
218853
- entry: e,
218854
- pixels
218855
- };
218856
- } else if (!bestBelow || pixels > bestBelow.pixels) bestBelow = {
218857
- entry: e,
218858
- pixels
218859
- };
218860
- }
218861
- 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;
218862
219322
  }
218863
219323
  var FORMAT_KEYS = [
218864
219324
  "onnx",
@@ -221720,6 +222180,39 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
221720
222180
  exposesDeviceSettings: true,
221721
222181
  methods: {}
221722
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
+ };
221723
222216
  var StreamFormatSchema = zod.z.enum([
221724
222217
  "webrtc",
221725
222218
  "hls",
@@ -222072,6 +222565,46 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222072
222565
  mode: "singleton",
222073
222566
  exposesDeviceSettings: true,
222074
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" }),
222075
222608
  publishCameraStream: require_sleep.method(zod.z.object({
222076
222609
  deviceId: zod.z.number().int().nonnegative(),
222077
222610
  camStreamId: zod.z.string().min(1),
@@ -222549,6 +223082,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222549
223082
  acceptCodecs: zod.z.array(StreamCodecSchema).readonly().optional(),
222550
223083
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
222551
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(),
222552
223095
  /** Minimum horizontal resolution. */
222553
223096
  minWidth: zod.z.number().int().positive().optional(),
222554
223097
  /**
@@ -222565,7 +223108,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222565
223108
  * transcoded" guard: if the device is already serving the consumer's
222566
223109
  * codec end-to-end, there's nothing to optimise.
222567
223110
  */
222568
- 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()
222569
223127
  }).readonly();
222570
223128
  var PickStreamPreferencesSchema = zod.z.object({
222571
223129
  /**
@@ -222579,12 +223137,32 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222579
223137
  * picks the tallest stream; `'lowest'` picks the shortest (used by
222580
223138
  * memory-constrained consumers / Apple Home guest sessions).
222581
223139
  */
222582
- 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()
222583
223153
  }).readonly();
222584
223154
  var PickedCamStreamSchema = zod.z.object({
222585
223155
  camStreamId: zod.z.string(),
222586
223156
  codec: zod.z.string().optional(),
222587
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(),
222588
223166
  /** One-line explanation of why this stream won — for logs / debug UI. */
222589
223167
  reason: zod.z.string()
222590
223168
  });
@@ -227963,6 +228541,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
227963
228541
  "device-enabled",
227964
228542
  "device-battery-low",
227965
228543
  "device-battery-normal",
228544
+ "device-consumable-low",
228545
+ "device-consumable-normal",
227966
228546
  "stream-online",
227967
228547
  "stream-offline",
227968
228548
  "node-online",
@@ -227981,6 +228561,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
227981
228561
  "addon-updated",
227982
228562
  "server-updated",
227983
228563
  "export-completed",
228564
+ "backup-completed",
227984
228565
  "camera-online",
227985
228566
  "camera-offline",
227986
228567
  "camera-disabled",
@@ -228842,10 +229423,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
228842
229423
  value: "server-updated",
228843
229424
  label: "Server updated"
228844
229425
  },
229426
+ {
229427
+ value: "device-consumable-low",
229428
+ label: "Consumable low"
229429
+ },
229430
+ {
229431
+ value: "device-consumable-normal",
229432
+ label: "Consumable replaced"
229433
+ },
228845
229434
  {
228846
229435
  value: "export-completed",
228847
229436
  label: "Export completed"
228848
229437
  },
229438
+ {
229439
+ value: "backup-completed",
229440
+ label: "Backup completed"
229441
+ },
228849
229442
  {
228850
229443
  value: "alarm-arming",
228851
229444
  label: "Alarm arming (exit delay)"
@@ -231303,13 +231896,91 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
231303
231896
  deviceIds: zod.z.array(zod.z.number()).min(1),
231304
231897
  since: zod.z.number().optional(),
231305
231898
  until: zod.z.number().optional(),
231306
- limit: zod.z.number().int().min(1).max(200).default(50)
231899
+ limit: zod.z.number().int().min(1).max(200).default(50),
231900
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
231901
+ * meaning of "dog" on every summary surface. */
231902
+ classes: zod.z.array(zod.z.string().min(1).max(60)).max(40).optional()
231903
+ });
231904
+ var SummaryMemberSchema = zod.z.object({
231905
+ trackId: zod.z.string(),
231906
+ /**
231907
+ * The track's class at the time it JOINED, or `null` when it was unresolved
231908
+ * and the track no longer exists to fill it.
231909
+ *
231910
+ * The join, not the latest opinion: class voting can re-decide a track's
231911
+ * class afterwards, and a group's composition is the history of who came
231912
+ * together. A consumer counts these to badge a thumbnail — "two people and a
231913
+ * dog" — rather than being handed a second, denormalised tally that would
231914
+ * drift from the members it claims to describe.
231915
+ */
231916
+ className: zod.z.string().nullable(),
231917
+ /** `primary` is the one member that carries the group's identity. */
231918
+ role: zod.z.enum([
231919
+ "primary",
231920
+ "sibling",
231921
+ "child"
231922
+ ]).nullable(),
231923
+ joinedAt: zod.z.number(),
231924
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
231925
+ * at read time. Absent when nothing resolved, never an empty string. */
231926
+ label: zod.z.string().optional(),
231927
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
231928
+ * recognised nobody, and absent for a member whose track has been pruned —
231929
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
231930
+ subLabel: zod.z.string().optional()
231307
231931
  });
231308
- var SummariesPageSchema = zod.z.object({ summaries: zod.z.array(SummarySchema).readonly() });
231309
231932
  var SummaryDetailSchema = zod.z.object({
231310
231933
  summary: SummarySchema,
231311
- /** Member track ids, in absorption order. */
231312
- trackIds: zod.z.array(zod.z.string()).readonly()
231934
+ /** Members in absorption order, oldest join first. */
231935
+ members: zod.z.array(SummaryMemberSchema).readonly()
231936
+ });
231937
+ var SummaryListItemSchema = zod.z.object({
231938
+ summary: SummarySchema,
231939
+ /** Class counts, most numerous first; `className: null` is a member whose
231940
+ * class never resolved, counted so the counts add up to `memberCount`. */
231941
+ classes: zod.z.array(zod.z.object({
231942
+ className: zod.z.string().nullable(),
231943
+ count: zod.z.number().int().positive()
231944
+ })).readonly()
231945
+ });
231946
+ var SummariesPageSchema = zod.z.object({ summaries: zod.z.array(SummaryListItemSchema).readonly() });
231947
+ var RecentSummariesQueryInput = zod.z.object({
231948
+ /** Devices to merge. An empty array yields an empty page. */
231949
+ deviceIds: zod.z.array(zod.z.number()),
231950
+ /** Window lower bound on `lastActiveAt` (inclusive). */
231951
+ since: zod.z.number().optional(),
231952
+ /** Window upper bound on `lastActiveAt` (inclusive). */
231953
+ until: zod.z.number().optional(),
231954
+ limit: zod.z.number().int().min(1).max(500).default(100),
231955
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
231956
+ cursor: zod.z.string().optional(),
231957
+ /**
231958
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
231959
+ * deliberately the same name: the events filter tree emits one list of ticked
231960
+ * taxonomy nodes, and a summaries feed that called it something else would
231961
+ * make the operator's one filter component produce two shapes.
231962
+ *
231963
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
231964
+ * me the arrivals with a dog" has to return the group of four people who had
231965
+ * one with them; a filter on the group's own fields could only ever answer
231966
+ * about its primary.
231967
+ *
231968
+ * Matched against all THREE tiers of a member — the class (`animal`), the
231969
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
231970
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
231971
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
231972
+ * that read only the class would answer "animal" and never "dog".
231973
+ *
231974
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
231975
+ */
231976
+ classes: zod.z.array(zod.z.string().min(1).max(60)).max(40).optional()
231977
+ });
231978
+ var RecentSummariesPageSchema = zod.z.object({
231979
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
231980
+ * {@link listSummaries} — one list shape, so a card renders the same
231981
+ * whichever feed drew it. */
231982
+ summaries: zod.z.array(SummaryListItemSchema).readonly(),
231983
+ nextCursor: zod.z.string().nullable()
231313
231984
  });
231314
231985
  var RecentTracksPageSchema = zod.z.object({
231315
231986
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -231662,6 +232333,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
231662
232333
  listRecentTracks: require_sleep.method(RecentTracksQueryInput, RecentTracksPageSchema),
231663
232334
  /** Newest first by `openedAt`, for a camera set, inside an optional window. */
231664
232335
  listSummaries: require_sleep.method(SummariesQueryInput, SummariesPageSchema),
232336
+ /**
232337
+ * The PAGED feed — the reel and the Events gallery. Same cursor contract as
232338
+ * {@link listRecentTracks}, so one screen cannot hold two ideas of newest.
232339
+ */
232340
+ listRecentSummaries: require_sleep.method(RecentSummariesQueryInput, RecentSummariesPageSchema),
231665
232341
  /** One summary with its member track ids; `null` for an id nobody wrote. */
231666
232342
  getSummary: require_sleep.method(zod.z.object({ id: zod.z.string() }), SummaryDetailSchema.nullable()),
231667
232343
  clearTracks: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), {
@@ -236147,10 +236823,28 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
236147
236823
  getSessionState: require_sleep.method(zod.z.object({
236148
236824
  deviceId: zod.z.number().int().nonnegative(),
236149
236825
  sessionId: zod.z.string()
236150
- }), zod.z.object({ pendingRenegotiation: zod.z.object({
236151
- target: WebrtcStreamTargetSchema,
236152
- epoch: zod.z.number()
236153
- }).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
+ }))
236154
236848
  },
236155
236849
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
236156
236850
  mount: { kind: "server-provided" }
@@ -244265,7 +244959,29 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
244265
244959
  * disarmed the channels it did not mention would make the Levels page and
244266
244960
  * the Diagnostics page fight over the same value.
244267
244961
  */
244268
- channels: zod.z.array(LogChannelWindowPatchSchema).readonly().optional()
244962
+ channels: zod.z.array(LogChannelWindowPatchSchema).readonly().optional(),
244963
+ /**
244964
+ * How many days the analytics OPS LOG is kept — the audit trail of what
244965
+ * retention actually deleted.
244966
+ *
244967
+ * It lives HERE, on the logging document, and not on the analytics addon's
244968
+ * settings form, on the operator's instruction: every other log-ish window in
244969
+ * this system is configured through this one document, and a retention audit
244970
+ * configured somewhere else is a second place to look when the question is
244971
+ * "what happened to my data".
244972
+ *
244973
+ * That placement has a cost worth stating: this document ships in the
244974
+ * framework closure, so this knob needs a published `@camstack/server` while
244975
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
244976
+ *
244977
+ * `retention-model.ts` argued this one must NOT follow a device window,
244978
+ * because the audit trail is the history of the SWEEP and not of the camera.
244979
+ * That argument stands and is untouched — what changes is that 30 days stops
244980
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
244981
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
244982
+ * state in which the next 9.5 GiB leak cannot be explained.
244983
+ */
244984
+ opsLogRetentionDays: zod.z.number().int().min(0).max(3650).optional()
244269
244985
  });
244270
244986
  var ReportedLoadContributionSchema = LoadContributionSchema.extend({ addonId: zod.z.string() });
244271
244987
  var ReportedFailureContributionSchema = FailureContributionSchema.extend({ addonId: zod.z.string() });
@@ -244304,6 +245020,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
244304
245020
  channels: zod.z.array(LogChannelDescriptorSchema).readonly(),
244305
245021
  /** The channels ARMED right now, each with its deadline. */
244306
245022
  activeChannels: zod.z.array(LogChannelWindowStateSchema).readonly(),
245023
+ /**
245024
+ * The ops-log window in days, as it is in force. `0` = kept forever.
245025
+ *
245026
+ * Reported rather than left to the caller's memory of what it wrote: this is
245027
+ * the value the analytics sweep will actually use, and an operator asking how
245028
+ * long their audit trail lives should not have to infer it from a default.
245029
+ */
245030
+ opsLogRetentionDays: zod.z.number().int().min(0),
244307
245031
  persisted: zod.z.boolean()
244308
245032
  });
244309
245033
  var systemCapability = {
@@ -245787,6 +246511,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
245787
246511
  smoke: smokeCapability,
245788
246512
  streamCatalog: streamCatalogCapability,
245789
246513
  streamParams: streamParamsCapability,
246514
+ streamSignals: streamSignalsCapability,
245790
246515
  switch: switchCapability,
245791
246516
  tamper: tamperCapability,
245792
246517
  temperatureSensor: temperatureSensorCapability,
@@ -248524,6 +249249,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
248524
249249
  streamBroker: "stream-broker",
248525
249250
  streamCatalog: "stream-catalog",
248526
249251
  streamParams: "stream-params",
249252
+ streamSignals: "stream-signals",
248527
249253
  switch: "switch",
248528
249254
  system: "system",
248529
249255
  tamper: "tamper",
@@ -249084,6 +249810,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249084
249810
  key: "streamParams",
249085
249811
  name: "stream-params"
249086
249812
  },
249813
+ {
249814
+ key: "streamSignals",
249815
+ name: "stream-signals"
249816
+ },
249087
249817
  {
249088
249818
  key: "switch",
249089
249819
  name: "switch"
@@ -249308,6 +250038,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249308
250038
  streamBrokerCapability,
249309
250039
  streamCatalogCapability,
249310
250040
  streamParamsCapability,
250041
+ streamSignalsCapability,
249311
250042
  switchCapability,
249312
250043
  systemCapability,
249313
250044
  tamperCapability,
@@ -252782,6 +253513,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252782
253513
  addonId: null,
252783
253514
  access: "view"
252784
253515
  },
253516
+ "pipelineAnalytics.listRecentSummaries": {
253517
+ capName: "pipeline-analytics",
253518
+ capScope: "device",
253519
+ addonId: null,
253520
+ access: "view"
253521
+ },
252785
253522
  "pipelineAnalytics.listRecentTracks": {
252786
253523
  capName: "pipeline-analytics",
252787
253524
  capScope: "device",
@@ -254744,6 +255481,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254744
255481
  addonId: null,
254745
255482
  access: "create"
254746
255483
  },
255484
+ "streamBroker.reportStreamSignal": {
255485
+ capName: "stream-broker",
255486
+ capScope: "system",
255487
+ addonId: null,
255488
+ access: "create"
255489
+ },
254747
255490
  "streamBroker.restartProfile": {
254748
255491
  capName: "stream-broker",
254749
255492
  capScope: "system",
@@ -254828,6 +255571,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254828
255571
  addonId: null,
254829
255572
  access: "create"
254830
255573
  },
255574
+ "streamSignals.listSignals": {
255575
+ capName: "stream-signals",
255576
+ capScope: "device",
255577
+ addonId: null,
255578
+ access: "view"
255579
+ },
254831
255580
  "switch.setState": {
254832
255581
  capName: "switch",
254833
255582
  capScope: "device",
@@ -255631,6 +256380,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255631
256380
  "stream-broker",
255632
256381
  "stream-catalog",
255633
256382
  "stream-params",
256383
+ "stream-signals",
255634
256384
  "switch",
255635
256385
  "system",
255636
256386
  "terminal-session",
@@ -255697,6 +256447,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255697
256447
  "snapshot",
255698
256448
  "stream-catalog",
255699
256449
  "stream-params",
256450
+ "stream-signals",
255700
256451
  "switch",
255701
256452
  "update",
255702
256453
  "vacuum-control",
@@ -256873,6 +257624,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
256873
257624
  form: "single",
256874
257625
  optional: true
256875
257626
  }],
257627
+ "pipelineAnalytics.listRecentSummaries": [{
257628
+ name: "deviceIds",
257629
+ form: "array",
257630
+ optional: false
257631
+ }],
256876
257632
  "pipelineAnalytics.listRecentTracks": [{
256877
257633
  name: "deviceIds",
256878
257634
  form: "array",
@@ -257459,6 +258215,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
257459
258215
  form: "single",
257460
258216
  optional: false
257461
258217
  }],
258218
+ "streamBroker.reportStreamSignal": [{
258219
+ name: "deviceId",
258220
+ form: "single",
258221
+ optional: false
258222
+ }],
257462
258223
  "streamBroker.restartProfile": [{
257463
258224
  name: "deviceId",
257464
258225
  form: "single",
@@ -257816,6 +258577,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
257816
258577
  "streamBroker.produceEventMedia",
257817
258578
  "streamBroker.publishCameraStream",
257818
258579
  "streamBroker.renderPreBufferClip",
258580
+ "streamBroker.reportStreamSignal",
257819
258581
  "streamBroker.restartProfile",
257820
258582
  "streamBroker.retractCameraStream",
257821
258583
  "streamBroker.setDeviceAudioMute",
@@ -258065,6 +258827,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
258065
258827
  durability: "restored",
258066
258828
  volatileFields: ["lastFetchedAt"]
258067
258829
  },
258830
+ "stream-signals": {
258831
+ durability: "session",
258832
+ volatileFields: []
258833
+ },
258068
258834
  "switch": {
258069
258835
  durability: "restored",
258070
258836
  volatileFields: ["lastChangedAt"]
@@ -262523,6 +263289,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
262523
263289
  exports.StreamProfileOptionsSchema = StreamProfileOptionsSchema;
262524
263290
  exports.StreamProfilePatchSchema = StreamProfilePatchSchema;
262525
263291
  exports.StreamProfileSchema = StreamProfileSchema;
263292
+ exports.StreamSignalSchema = StreamSignalSchema;
263293
+ exports.StreamSignalsStatusSchema = StreamSignalsStatusSchema;
262526
263294
  exports.StreamSourceEntrySchema = require_sleep.StreamSourceEntrySchema;
262527
263295
  exports.StreamSourceSchema = require_sleep.StreamSourceSchema;
262528
263296
  exports.SubscribeAudioChunksInputSchema = require_sleep.SubscribeAudioChunksInputSchema;
@@ -262708,12 +263476,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
262708
263476
  exports.clusterModelSettingKey = clusterModelSettingKey;
262709
263477
  exports.clusterStepSettingFieldsFor = clusterStepSettingFieldsFor;
262710
263478
  exports.clusterStepSettingKey = clusterStepSettingKey;
263479
+ exports.codecIsAccepted = codecIsAccepted;
262711
263480
  exports.collectHydratedFieldEntries = require_sleep.collectHydratedFieldEntries;
262712
263481
  exports.collectHydratedFieldValues = require_sleep.collectHydratedFieldValues;
262713
263482
  exports.collectSecretConfigKeys = collectSecretConfigKeys;
262714
263483
  exports.colorCapability = colorCapability;
262715
263484
  exports.colorForKind = colorForKind;
262716
263485
  exports.commitWatchdogRestart = commitWatchdogRestart;
263486
+ exports.compareToTarget = compareToTarget;
262717
263487
  exports.compileExpression = compileExpression;
262718
263488
  exports.compileExpressionSafe = compileExpressionSafe;
262719
263489
  exports.composeSwitchedOff = composeSwitchedOff;
@@ -262972,6 +263742,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
262972
263742
  exports.ptzAutotrackCapability = ptzAutotrackCapability;
262973
263743
  exports.ptzCapability = ptzCapability;
262974
263744
  exports.pythonScriptForBackend = pythonScriptForBackend;
263745
+ exports.rankStreams = rankStreams;
262975
263746
  exports.readClusterStepModels = readClusterStepModels;
262976
263747
  exports.readClusterStepSettings = readClusterStepSettings;
262977
263748
  exports.readDetailCropConvention = readDetailCropConvention;
@@ -263056,6 +263827,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263056
263827
  exports.streamParamsCapability = streamParamsCapability;
263057
263828
  exports.streamPixels = streamPixels;
263058
263829
  exports.streamQualityLabel = streamQualityLabel;
263830
+ exports.streamSignalsCapability = streamSignalsCapability;
263059
263831
  exports.stripRetiredBandPreBufferSec = stripRetiredBandPreBufferSec;
263060
263832
  exports.strippedOfLegacyReadOnly = strippedOfLegacyReadOnly;
263061
263833
  exports.subKindsOf = subKindsOf;
@@ -264680,6 +265452,15 @@ var require_server_root = __commonJS({
264680
265452
  __name(buildNpmRegistryArgs, "buildNpmRegistryArgs");
264681
265453
  var NPM_VIEW_TIMEOUT_MS = 2e4;
264682
265454
  var NPM_INSTALL_TIMEOUT_MS = 15 * 6e4;
265455
+ var CLOSURE_ALLOW_SCRIPTS = {
265456
+ "@camstack/server": true,
265457
+ "better-sqlite3": true,
265458
+ "node-av": true,
265459
+ "node-pty": true,
265460
+ ssh2: true,
265461
+ "cpu-features": true,
265462
+ esbuild: true
265463
+ };
264683
265464
  var RESTART_REASON_PREFIX = "server-update";
264684
265465
  var STALE_TRANSIENT_MS = 24 * 60 * 60 * 1e3;
264685
265466
  function readWrapperProbes() {
@@ -264727,6 +265508,7 @@ var require_server_root = __commonJS({
264727
265508
  dataDir;
264728
265509
  execNpm;
264729
265510
  ensureNativePrebuildsFn;
265511
+ applyClosurePatchesFn;
264730
265512
  env;
264731
265513
  readWrapperProbesFn;
264732
265514
  now;
@@ -264755,6 +265537,7 @@ var require_server_root = __commonJS({
264755
265537
  };
264756
265538
  });
264757
265539
  this.ensureNativePrebuildsFn = options.ensureNativePrebuilds ?? (async () => void 0);
265540
+ this.applyClosurePatchesFn = options.applyClosurePatches ?? (async () => void 0);
264758
265541
  this.env = options.env ?? process.env;
264759
265542
  this.readWrapperProbesFn = options.readWrapperProbes ?? readWrapperProbes;
264760
265543
  this.now = options.now ?? Date.now;
@@ -265070,7 +265853,8 @@ var require_server_root = __commonJS({
265070
265853
  } else {
265071
265854
  fs6.writeFileSync(path6.join(stagingDir, "package.json"), JSON.stringify({
265072
265855
  name: "camstack-node-root",
265073
- private: true
265856
+ private: true,
265857
+ allowScripts: CLOSURE_ALLOW_SCRIPTS
265074
265858
  }, null, 2), "utf-8");
265075
265859
  await this.execNpm([
265076
265860
  ...installArgs,
@@ -265103,6 +265887,15 @@ var require_server_root = __commonJS({
265103
265887
  if (missingNatives.length > 0) {
265104
265888
  throw new Error(`staged closure missing native prebuilds: ${missingNatives.join(", ")} \u2014 refusing to arm a swap that would break the forked runners`);
265105
265889
  }
265890
+ try {
265891
+ await this.applyClosurePatchesFn(stagingDir);
265892
+ } catch (err) {
265893
+ this.logger.warn("closure-patch apply step failed \u2014 validator will decide", {
265894
+ meta: {
265895
+ error: err instanceof Error ? err.message : String(err)
265896
+ }
265897
+ });
265898
+ }
265106
265899
  const unpatched = findUnappliedClosurePatches(stagingDir);
265107
265900
  if (unpatched.length > 0) {
265108
265901
  throw new Error(`staged closure did not apply its shipped patches: ${unpatched.join(", ")} \u2014 refusing to arm a swap into an unpatched closure`);
@@ -265150,6 +265943,7 @@ var require_server_root = __commonJS({
265150
265943
  return {
265151
265944
  name: "camstack-node-root",
265152
265945
  private: true,
265946
+ allowScripts: CLOSURE_ALLOW_SCRIPTS,
265153
265947
  dependencies: {
265154
265948
  [this.spec.packageName]: fileRef(rootTgz)
265155
265949
  },
@@ -396107,6 +396901,78 @@ var require_system_ensure_prebuilds = __commonJS({
396107
396901
  }
396108
396902
  });
396109
396903
 
396904
+ // ../../server/backend/dist/core/server-update/system-apply-closure-patches.js
396905
+ var require_system_apply_closure_patches = __commonJS({
396906
+ "../../server/backend/dist/core/server-update/system-apply-closure-patches.js"(exports) {
396907
+ "use strict";
396908
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
396909
+ if (k2 === void 0) k2 = k;
396910
+ var desc = Object.getOwnPropertyDescriptor(m, k);
396911
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
396912
+ desc = { enumerable: true, get: function() {
396913
+ return m[k];
396914
+ } };
396915
+ }
396916
+ Object.defineProperty(o, k2, desc);
396917
+ }) : (function(o, m, k, k2) {
396918
+ if (k2 === void 0) k2 = k;
396919
+ o[k2] = m[k];
396920
+ }));
396921
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
396922
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
396923
+ }) : function(o, v) {
396924
+ o["default"] = v;
396925
+ });
396926
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
396927
+ var ownKeys = function(o) {
396928
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
396929
+ var ar = [];
396930
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
396931
+ return ar;
396932
+ };
396933
+ return ownKeys(o);
396934
+ };
396935
+ return function(mod) {
396936
+ if (mod && mod.__esModule) return mod;
396937
+ var result = {};
396938
+ if (mod != null) {
396939
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
396940
+ }
396941
+ __setModuleDefault(result, mod);
396942
+ return result;
396943
+ };
396944
+ })();
396945
+ Object.defineProperty(exports, "__esModule", { value: true });
396946
+ exports.buildSystemApplyClosurePatches = buildSystemApplyClosurePatches;
396947
+ var node_child_process_1 = __require("child_process");
396948
+ var fs = __importStar(__require("fs"));
396949
+ var path = __importStar(__require("path"));
396950
+ var node_util_1 = __require("util");
396951
+ var execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
396952
+ var APPLIER_REL = ["node_modules", "@camstack", "server", "scripts", "closure-postinstall.mjs"];
396953
+ var APPLY_TIMEOUT_MS = 6e4;
396954
+ function buildSystemApplyClosurePatches(logger) {
396955
+ return async (closureDir) => {
396956
+ const applier = path.join(closureDir, ...APPLIER_REL);
396957
+ if (!fs.existsSync(applier)) {
396958
+ logger.debug("closure ships no patch applier \u2014 nothing to apply", {
396959
+ meta: { applier }
396960
+ });
396961
+ return;
396962
+ }
396963
+ const { stdout, stderr } = await execFileAsync(process.execPath, [applier], {
396964
+ cwd: path.dirname(path.dirname(applier)),
396965
+ timeout: APPLY_TIMEOUT_MS
396966
+ });
396967
+ const output = `${stdout}${stderr}`.trim();
396968
+ if (output.length > 0) {
396969
+ logger.info("closure patches applied", { meta: { output } });
396970
+ }
396971
+ };
396972
+ }
396973
+ }
396974
+ });
396975
+
396110
396976
  // ../../server/backend/dist/core/server-update/system-exec-npm.js
396111
396977
  var require_system_exec_npm = __commonJS({
396112
396978
  "../../server/backend/dist/core/server-update/system-exec-npm.js"(exports) {
@@ -396494,6 +397360,7 @@ var require_agent_update_service = __commonJS({
396494
397360
  var system_1 = require_dist3();
396495
397361
  var types_1 = require_dist4();
396496
397362
  var system_ensure_prebuilds_js_1 = require_system_ensure_prebuilds();
397363
+ var system_apply_closure_patches_js_1 = require_system_apply_closure_patches();
396497
397364
  var system_exec_npm_js_1 = require_system_exec_npm();
396498
397365
  var update_availability_emitter_js_1 = require_update_availability_emitter();
396499
397366
  var update_availability_store_js_1 = require_update_availability_store();
@@ -396582,6 +397449,11 @@ var require_agent_update_service = __commonJS({
396582
397449
  // ships the identical bare Node, so its `applyServerUpdate` staging would
396583
397450
  // hit the same source-only `better-sqlite3` miss without this.
396584
397451
  ensureNativePrebuilds: options.ensureNativePrebuilds ?? (0, system_ensure_prebuilds_js_1.buildSystemEnsureNativePrebuilds)(options.logger),
397452
+ // Apply the closure patches ourselves. npm 12 blocks the root package's
397453
+ // postinstall by default, and a silently-unpatched closure is refused by
397454
+ // the validator — an update that can never apply. Idempotent, so this
397455
+ // costs nothing where npm did run the lifecycle.
397456
+ applyClosurePatches: options.applyClosurePatches ?? (0, system_apply_closure_patches_js_1.buildSystemApplyClosurePatches)(options.logger),
396585
397457
  env: options.env,
396586
397458
  now: options.now
396587
397459
  });
@@ -421021,7 +421893,7 @@ var require_logging_settings = __commonJS({
421021
421893
  "../../server/backend/dist/api/core/logging-settings.js"(exports) {
421022
421894
  "use strict";
421023
421895
  Object.defineProperty(exports, "__esModule", { value: true });
421024
- exports.LoggingSettingsService = exports.MAX_CHANNEL_WINDOW_MS = exports.EMPTY_LOG_CHANNEL_PLANE = exports.HUB_NODE_ID = exports.LOGGING_SETTINGS_KEY = void 0;
421896
+ exports.LoggingSettingsService = exports.MAX_CHANNEL_WINDOW_MS = exports.EMPTY_LOG_CHANNEL_PLANE = exports.OPS_LOG_RETENTION_DEFAULT_DAYS = exports.HUB_NODE_ID = exports.LOGGING_SETTINGS_KEY = void 0;
421025
421897
  exports.buildLogChannelPlane = buildLogChannelPlane;
421026
421898
  exports.toLoggingSettingsRecord = toLoggingSettingsRecord;
421027
421899
  exports.resolveLevel = resolveLevel;
@@ -421037,6 +421909,7 @@ var require_logging_settings = __commonJS({
421037
421909
  var SYSTEM_SETTINGS_COLLECTION = "system-settings";
421038
421910
  exports.HUB_NODE_ID = "hub";
421039
421911
  var LOG_LEVELS = ["debug", "info", "warn", "error"];
421912
+ exports.OPS_LOG_RETENTION_DEFAULT_DAYS = 30;
421040
421913
  exports.EMPTY_LOG_CHANNEL_PLANE = {
421041
421914
  declarations: async () => [],
421042
421915
  apply: async () => {
@@ -421118,6 +421991,9 @@ var require_logging_settings = __commonJS({
421118
421991
  if (levelChanged) {
421119
421992
  next = mergeLevel(next, scope, component, patch.level ?? null, this.now());
421120
421993
  }
421994
+ if (patch.opsLogRetentionDays !== void 0) {
421995
+ next = { ...next, opsLogRetentionDays: patch.opsLogRetentionDays, updatedAt: this.now() };
421996
+ }
421121
421997
  if (channelsChanged) {
421122
421998
  const declared = await this.channels.declarations();
421123
421999
  const rejected = validateChannelPatch(patch.channels ?? [], declared);
@@ -421207,6 +422083,7 @@ var require_logging_settings = __commonJS({
421207
422083
  activeWindows: await this.describeWindows(),
421208
422084
  channels: await this.declaredChannels(),
421209
422085
  activeChannels: describeChannelWindows(record ?? {}, this.now()),
422086
+ opsLogRetentionDays: record?.opsLogRetentionDays ?? exports.OPS_LOG_RETENTION_DEFAULT_DAYS,
421210
422087
  persisted: record !== null
421211
422088
  };
421212
422089
  }
@@ -434655,6 +435532,7 @@ var require_server_update_service = __commonJS({
434655
435532
  var index_js_1 = require_server_root();
434656
435533
  var update_availability_emitter_js_1 = require_update_availability_emitter();
434657
435534
  var system_ensure_prebuilds_js_1 = require_system_ensure_prebuilds();
435535
+ var system_apply_closure_patches_js_1 = require_system_apply_closure_patches();
434658
435536
  var system_exec_npm_js_1 = require_system_exec_npm();
434659
435537
  var ServerUpdateService = class extends index_js_1.RootUpdateService {
434660
435538
  updateAvailability;
@@ -434688,6 +435566,11 @@ var require_server_update_service = __commonJS({
434688
435566
  // native-prebuild validator refuses the swap (toolchain-less packaged
434689
435567
  // desktop). No-op on docker where the install already carried it.
434690
435568
  ensureNativePrebuilds: options.ensureNativePrebuilds ?? (0, system_ensure_prebuilds_js_1.buildSystemEnsureNativePrebuilds)(options.logger),
435569
+ // Apply the closure patches ourselves. npm 12 blocks the root package's
435570
+ // postinstall by default, and a silently-unpatched closure is refused by
435571
+ // the validator — an update that can never apply. Idempotent, so this
435572
+ // costs nothing where npm did run the lifecycle.
435573
+ applyClosurePatches: options.applyClosurePatches ?? (0, system_apply_closure_patches_js_1.buildSystemApplyClosurePatches)(options.logger),
434691
435574
  env: options.env,
434692
435575
  now: options.now
434693
435576
  });