@camstack/addon-smtp-nodemailer 1.2.99 → 1.2.101

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.
@@ -36,7 +36,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
36
36
  }) : target, mod));
37
37
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
38
38
  //#endregion
39
- //#region ../types/dist/event-category-ZyX6jcse.mjs
39
+ //#region ../types/dist/event-category-BVDXG4tB.mjs
40
40
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
41
41
  EventCategory["SystemBoot"] = "system.boot";
42
42
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -653,6 +653,20 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
653
653
  * pull-reconcile from the provider's `getStatus` on reconnect. */
654
654
  EventCategory["MeshNetworkChanged"] = "network.mesh.changed";
655
655
  EventCategory["BackupCompleted"] = "backup.completed";
656
+ /**
657
+ * A whole backup RUN finished — every destination attempted, win or lose.
658
+ *
659
+ * `backup.completed` fires once per DESTINATION, which is the right grain for
660
+ * a progress UI and the wrong one for a notification: an operator with three
661
+ * destinations would be told three times. And a run where two of three
662
+ * destinations succeeded is not a clean success — a single "backup
663
+ * completed" that hid the failed one would be a lie, so the count of each is
664
+ * carried here and the message says both.
665
+ *
666
+ * Emitted by the backup orchestrator only after the destination loop, so a
667
+ * run that dies during the BUILD phase produces no completion at all.
668
+ */
669
+ EventCategory["BackupRunCompleted"] = "backup.run-completed";
656
670
  EventCategory["BackupRestored"] = "backup.restored";
657
671
  EventCategory["NotificationDispatched"] = "notification.dispatched";
658
672
  EventCategory["NotificationFailed"] = "notification.failed";
@@ -5383,7 +5397,7 @@ var ZodIssueCode = {
5383
5397
  var ZodFirstPartyTypeKind;
5384
5398
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5385
5399
  //#endregion
5386
- //#region ../types/dist/sleep-CnLyvg3w.mjs
5400
+ //#region ../types/dist/sleep-DEuj7E3j.mjs
5387
5401
  /**
5388
5402
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5389
5403
  * window to float samples (D455).
@@ -7396,7 +7410,23 @@ var EncodeProfileSchema = object({
7396
7410
  "zerolatency",
7397
7411
  "film",
7398
7412
  "animation"
7399
- ]).optional()
7413
+ ]).optional(),
7414
+ /**
7415
+ * ONE slice per access unit.
7416
+ *
7417
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7418
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7419
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7420
+ * frame-end per frame: the first pictures render and the video then freezes
7421
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7422
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7423
+ *
7424
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7425
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7426
+ * consumers meaning the same thing spelled differently would stop sharing one
7427
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7428
+ */
7429
+ singleSlicePerFrame: boolean().optional()
7400
7430
  }),
7401
7431
  audio: union([literal("passthrough"), object({
7402
7432
  codec: _enum([
@@ -7446,7 +7476,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7446
7476
  audio: "passthrough"
7447
7477
  };
7448
7478
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7449
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7479
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7450
7480
  /**
7451
7481
  * Deep wiring healthcheck — snapshot of active reachability probes across
7452
7482
  * every declared capability + widget of every installed plugin, on every
@@ -11495,6 +11525,58 @@ method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, Broke
11495
11525
  auth: "admin"
11496
11526
  }), method(GetStateInputSchema, unknown().nullable()), method(_void(), RegistryStatusSchema);
11497
11527
  DeviceType.Camera;
11528
+ /**
11529
+ * The signals a device can emit to WAKE its own stream.
11530
+ *
11531
+ * A camera whose stream is built on demand sleeps until something asks for it,
11532
+ * and "something" cannot be a consumer that is merely attached — a Frigate-style
11533
+ * puller holds a session open for ever, and treating that as demand would keep
11534
+ * a battery camera awake for ever, which is the whole thing the battery is for
11535
+ * (D173). So the wake has to come from the CAMERA: an event it noticed by
11536
+ * itself, with no stream running.
11537
+ *
11538
+ * ## The vocabulary is the PROVIDER'S, not ours
11539
+ *
11540
+ * Like `consumables`, this cap declares no vocabulary of its own. A provider
11541
+ * names each signal with a `code` it chooses and a `label` an operator reads.
11542
+ * Reolink offers motion and camera-native detection; another provider may offer
11543
+ * a tamper, a doorbell press, a PIR, or something no camera in this fleet has
11544
+ * yet. A fixed enum here would mean every new signal is a framework release.
11545
+ *
11546
+ * It is deliberately NOT derived from the caps a device already binds. Whether
11547
+ * a camera CAN push firmware motion is expressed by `motionSources` containing
11548
+ * `'onboard'`, and whether it does AI on-camera by the `native-object-detection`
11549
+ * binding — but both answer "what drives the detection pipeline", which is a
11550
+ * different question from "what may wake a sleeping stream". A camera can do
11551
+ * the first and not be trusted with the second, and the operator picks per
11552
+ * camera. Two questions, two authorities.
11553
+ *
11554
+ * ## Availability is not permission
11555
+ *
11556
+ * `listSignals` says what the device CAN emit. Whether a given signal actually
11557
+ * wakes the stream is the operator's per-camera choice, held by the broker
11558
+ * alongside the cooldown — see the stream-broker cap's wake settings. A
11559
+ * provider declaring a signal is not a provider enabling it.
11560
+ */
11561
+ /** One signal a device can emit. */
11562
+ var StreamSignalSchema = object({
11563
+ /** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
11564
+ code: string().min(1),
11565
+ /** What an operator reads in the picker. The provider's own wording. */
11566
+ label: string().min(1),
11567
+ /**
11568
+ * Whether the provider recommends this signal ON when a camera is first set
11569
+ * up. A provider knows which of its signals are cheap and reliable; an
11570
+ * operator should not have to discover that by trial. Reolink recommends
11571
+ * both of its own.
11572
+ */
11573
+ recommended: boolean()
11574
+ });
11575
+ object({
11576
+ signals: array(StreamSignalSchema),
11577
+ lastFetchedAt: number()
11578
+ });
11579
+ Object.values(DeviceType), method(_void(), array(StreamSignalSchema).readonly());
11498
11580
  /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
11499
11581
  var StreamFormatSchema = _enum([
11500
11582
  "webrtc",
@@ -11891,6 +11973,22 @@ var EgressTranscodeSchema = object({
11891
11973
  camStreamId: string().nullable()
11892
11974
  });
11893
11975
  method(object({
11976
+ deviceId: number().int().nonnegative(),
11977
+ /** The provider's signal code. */
11978
+ code: string().min(1),
11979
+ /** Ms epoch. Absent ⇒ now. */
11980
+ at: number().optional()
11981
+ }), object({
11982
+ /** Whether the broker acted on it, and if not, why. */
11983
+ accepted: boolean(),
11984
+ reason: _enum([
11985
+ "woke",
11986
+ "hold-extended",
11987
+ "not-enabled",
11988
+ "no-consumer",
11989
+ "unknown-code"
11990
+ ])
11991
+ }), { kind: "mutation" }), method(object({
11894
11992
  deviceId: number().int().nonnegative(),
11895
11993
  camStreamId: string().min(1),
11896
11994
  kind: CamStreamKindSchema,
@@ -12145,6 +12243,16 @@ var PickStreamRequirementsSchema = object({
12145
12243
  acceptCodecs: array(StreamCodecSchema).readonly().optional(),
12146
12244
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
12147
12245
  minHeight: number().int().positive().optional(),
12246
+ /**
12247
+ * Maximum vertical resolution. Streams TALLER than this are dropped.
12248
+ *
12249
+ * A consumer can have a ceiling as real as its floor: Alexa documents
12250
+ * 480p to 1080p, and a 4K stream is as unusable to an Echo as a 360p one.
12251
+ * Without this the ceiling had to be re-implemented by every caller — and
12252
+ * one caller implementing it privately is how a second scoring authority
12253
+ * gets born.
12254
+ */
12255
+ maxHeight: number().int().positive().optional(),
12148
12256
  /** Minimum horizontal resolution. */
12149
12257
  minWidth: number().int().positive().optional(),
12150
12258
  /**
@@ -12161,7 +12269,22 @@ var PickStreamRequirementsSchema = object({
12161
12269
  * transcoded" guard: if the device is already serving the consumer's
12162
12270
  * codec end-to-end, there's nothing to optimise.
12163
12271
  */
12164
- requireSiblingCodec: array(StreamCodecSchema).readonly().optional()
12272
+ requireSiblingCodec: array(StreamCodecSchema).readonly().optional(),
12273
+ /**
12274
+ * Whether a stream the consumer CANNOT decode may still be picked, on the
12275
+ * understanding that it will be transcoded.
12276
+ *
12277
+ * Default `false` — today's behaviour, and the right one for a bypass
12278
+ * question ("is there a stream I can forward untouched?"). Set `true` to
12279
+ * ask the larger question: "what is the best stream for me, transcoding if
12280
+ * I must?" A stream that satisfies `acceptCodecs` always outranks one that
12281
+ * does not, so a passthrough is never lost to a transcode; the answer says
12282
+ * which it is in {@link PickedCamStreamSchema.transcodes}.
12283
+ *
12284
+ * This is what lets one picker serve both the bypass and the full source
12285
+ * choice, instead of a consumer scoring privately when the bypass misses.
12286
+ */
12287
+ allowTranscode: boolean().optional()
12165
12288
  }).readonly();
12166
12289
  var PickStreamPreferencesSchema = object({
12167
12290
  /**
@@ -12175,12 +12298,32 @@ var PickStreamPreferencesSchema = object({
12175
12298
  * picks the tallest stream; `'lowest'` picks the shortest (used by
12176
12299
  * memory-constrained consumers / Apple Home guest sessions).
12177
12300
  */
12178
- resolutionPreference: _enum(["highest", "lowest"]).optional()
12301
+ resolutionPreference: _enum(["highest", "lowest"]).optional(),
12302
+ /**
12303
+ * The height the consumer actually wants to DELIVER.
12304
+ *
12305
+ * Not a constraint — an ordering. With it set, the SMALLEST stream at or
12306
+ * above the target wins, and only if nothing reaches it does the tallest
12307
+ * take over. Pulling 1296 lines to draw 720 on a 1280x800 Echo panel costs
12308
+ * a decode and buys nothing, and `resolutionPreference: 'highest'` cannot
12309
+ * express that: it says "as big as possible", which is a different wish.
12310
+ *
12311
+ * Ignored when absent, so every existing caller keeps its ordering.
12312
+ */
12313
+ targetHeight: number().int().positive().optional()
12179
12314
  }).readonly();
12180
12315
  var PickedCamStreamSchema = object({
12181
12316
  camStreamId: string(),
12182
12317
  codec: string().optional(),
12183
12318
  resolution: CamStreamResolutionSchema.optional(),
12319
+ /**
12320
+ * Whether serving this stream requires a decode + re-encode.
12321
+ *
12322
+ * `false` is a stream the consumer can take as it stands. Only ever `true`
12323
+ * when the caller asked for it with `allowTranscode`, so a caller that did
12324
+ * not ask cannot be handed a cost it never agreed to pay.
12325
+ */
12326
+ transcodes: boolean(),
12184
12327
  /** One-line explanation of why this stream won — for logs / debug UI. */
12185
12328
  reason: string()
12186
12329
  });
@@ -16369,6 +16512,8 @@ var NcSystemEventKindSchema = _enum([
16369
16512
  "device-enabled",
16370
16513
  "device-battery-low",
16371
16514
  "device-battery-normal",
16515
+ "device-consumable-low",
16516
+ "device-consumable-normal",
16372
16517
  "stream-online",
16373
16518
  "stream-offline",
16374
16519
  "node-online",
@@ -16387,6 +16532,7 @@ var NcSystemEventKindSchema = _enum([
16387
16532
  "addon-updated",
16388
16533
  "server-updated",
16389
16534
  "export-completed",
16535
+ "backup-completed",
16390
16536
  "camera-online",
16391
16537
  "camera-offline",
16392
16538
  "camera-disabled",
@@ -19783,13 +19929,109 @@ var SummariesQueryInput = object({
19783
19929
  deviceIds: array(number()).min(1),
19784
19930
  since: number().optional(),
19785
19931
  until: number().optional(),
19786
- limit: number().int().min(1).max(200).default(50)
19932
+ limit: number().int().min(1).max(200).default(50),
19933
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
19934
+ * meaning of "dog" on every summary surface. */
19935
+ classes: array(string().min(1).max(60)).max(40).optional()
19787
19936
  });
19788
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
19789
19937
  var SummaryDetailSchema = object({
19790
19938
  summary: SummarySchema,
19791
- /** Member track ids, in absorption order. */
19792
- trackIds: array(string()).readonly()
19939
+ /** Members in absorption order, oldest join first. */
19940
+ members: array(object({
19941
+ trackId: string(),
19942
+ /**
19943
+ * The track's class at the time it JOINED, or `null` when it was unresolved
19944
+ * and the track no longer exists to fill it.
19945
+ *
19946
+ * The join, not the latest opinion: class voting can re-decide a track's
19947
+ * class afterwards, and a group's composition is the history of who came
19948
+ * together. A consumer counts these to badge a thumbnail — "two people and a
19949
+ * dog" — rather than being handed a second, denormalised tally that would
19950
+ * drift from the members it claims to describe.
19951
+ */
19952
+ className: string().nullable(),
19953
+ /** `primary` is the one member that carries the group's identity. */
19954
+ role: _enum([
19955
+ "primary",
19956
+ "sibling",
19957
+ "child"
19958
+ ]).nullable(),
19959
+ joinedAt: number(),
19960
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
19961
+ * at read time. Absent when nothing resolved, never an empty string. */
19962
+ label: string().optional(),
19963
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
19964
+ * recognised nobody, and absent for a member whose track has been pruned —
19965
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
19966
+ subLabel: string().optional()
19967
+ })).readonly()
19968
+ });
19969
+ /**
19970
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
19971
+ *
19972
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
19973
+ * we extend when we notice we need more". Enough to draw the card ("two people
19974
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
19975
+ * are on {@link getSummary}: shipping every member of every group so a client
19976
+ * can count them turns a page of two hundred groups into a thousand records to
19977
+ * draw two hundred badges.
19978
+ */
19979
+ var SummaryListItemSchema = object({
19980
+ summary: SummarySchema,
19981
+ /** Class counts, most numerous first; `className: null` is a member whose
19982
+ * class never resolved, counted so the counts add up to `memberCount`. */
19983
+ classes: array(object({
19984
+ className: string().nullable(),
19985
+ count: number().int().positive()
19986
+ })).readonly()
19987
+ });
19988
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
19989
+ /**
19990
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
19991
+ *
19992
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
19993
+ * summaries feed that could only answer "the last N for these cameras"
19994
+ * (`listSummaries`) would make groups available in the timeline and nowhere
19995
+ * else. Two feeds with different ordering contracts would be two ideas of what
19996
+ * "newest" means, on one screen.
19997
+ */
19998
+ var RecentSummariesQueryInput = object({
19999
+ /** Devices to merge. An empty array yields an empty page. */
20000
+ deviceIds: array(number()),
20001
+ /** Window lower bound on `lastActiveAt` (inclusive). */
20002
+ since: number().optional(),
20003
+ /** Window upper bound on `lastActiveAt` (inclusive). */
20004
+ until: number().optional(),
20005
+ limit: number().int().min(1).max(500).default(100),
20006
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
20007
+ cursor: string().optional(),
20008
+ /**
20009
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
20010
+ * deliberately the same name: the events filter tree emits one list of ticked
20011
+ * taxonomy nodes, and a summaries feed that called it something else would
20012
+ * make the operator's one filter component produce two shapes.
20013
+ *
20014
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
20015
+ * me the arrivals with a dog" has to return the group of four people who had
20016
+ * one with them; a filter on the group's own fields could only ever answer
20017
+ * about its primary.
20018
+ *
20019
+ * Matched against all THREE tiers of a member — the class (`animal`), the
20020
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
20021
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
20022
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
20023
+ * that read only the class would answer "animal" and never "dog".
20024
+ *
20025
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
20026
+ */
20027
+ classes: array(string().min(1).max(60)).max(40).optional()
20028
+ });
20029
+ var RecentSummariesPageSchema = object({
20030
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
20031
+ * {@link listSummaries} — one list shape, so a card renders the same
20032
+ * whichever feed drew it. */
20033
+ summaries: array(SummaryListItemSchema).readonly(),
20034
+ nextCursor: string().nullable()
19793
20035
  });
19794
20036
  var RecentTracksPageSchema = object({
19795
20037
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -20203,7 +20445,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20203
20445
  classes: array(string()).optional(),
20204
20446
  /** See {@link ExcludeSourcesDoc}. */
20205
20447
  excludeSources: array(TrackSourceSchema).optional()
20206
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20448
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20207
20449
  kind: "mutation",
20208
20450
  auth: "admin"
20209
20451
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -23545,10 +23787,28 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
23545
23787
  }), boolean()), method(object({
23546
23788
  deviceId: number().int().nonnegative(),
23547
23789
  sessionId: string()
23548
- }), object({ pendingRenegotiation: object({
23549
- target: WebrtcStreamTargetSchema,
23550
- epoch: number()
23551
- }).nullable() }));
23790
+ }), object({
23791
+ pendingRenegotiation: object({
23792
+ target: WebrtcStreamTargetSchema,
23793
+ epoch: number()
23794
+ }).nullable(),
23795
+ /**
23796
+ * Whether the session still EXISTS.
23797
+ *
23798
+ * A consumer that holds a resource for the life of a session needs to
23799
+ * be able to ask, because a session does not always end the way it
23800
+ * began. Measured on this hub 2026-09-13: an Echo that got stuck never
23801
+ * sent `SessionDisconnected`, so the Alexa exporter's
23802
+ * `releaseEgressTranscode` never ran, and a 2304x1296 HEVC to 720p
23803
+ * H.264 transcode kept running for NOBODY for more than twenty
23804
+ * minutes. The WebRTC session had logged `WebRTC session closed`
23805
+ * minutes earlier — the broker knew; the holder had no way to ask.
23806
+ *
23807
+ * `false` for a session id the provider has never heard of, which is
23808
+ * the same answer as "it ended": either way nothing is holding it up.
23809
+ */
23810
+ alive: boolean()
23811
+ }));
23552
23812
  object({
23553
23813
  /** All accessory children of the parent. */
23554
23814
  childDeviceIds: array(number()).readonly(),
@@ -29822,7 +30082,29 @@ var LoggingSettingsPatchSchema = object({
29822
30082
  * disarmed the channels it did not mention would make the Levels page and
29823
30083
  * the Diagnostics page fight over the same value.
29824
30084
  */
29825
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
30085
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
30086
+ /**
30087
+ * How many days the analytics OPS LOG is kept — the audit trail of what
30088
+ * retention actually deleted.
30089
+ *
30090
+ * It lives HERE, on the logging document, and not on the analytics addon's
30091
+ * settings form, on the operator's instruction: every other log-ish window in
30092
+ * this system is configured through this one document, and a retention audit
30093
+ * configured somewhere else is a second place to look when the question is
30094
+ * "what happened to my data".
30095
+ *
30096
+ * That placement has a cost worth stating: this document ships in the
30097
+ * framework closure, so this knob needs a published `@camstack/server` while
30098
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
30099
+ *
30100
+ * `retention-model.ts` argued this one must NOT follow a device window,
30101
+ * because the audit trail is the history of the SWEEP and not of the camera.
30102
+ * That argument stands and is untouched — what changes is that 30 days stops
30103
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
30104
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
30105
+ * state in which the next 9.5 GiB leak cannot be explained.
30106
+ */
30107
+ opsLogRetentionDays: number().int().min(0).max(3650).optional()
29826
30108
  });
29827
30109
  /**
29828
30110
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -29895,6 +30177,14 @@ var LoggingSettingsStateSchema = object({
29895
30177
  channels: array(LogChannelDescriptorSchema).readonly(),
29896
30178
  /** The channels ARMED right now, each with its deadline. */
29897
30179
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
30180
+ /**
30181
+ * The ops-log window in days, as it is in force. `0` = kept forever.
30182
+ *
30183
+ * Reported rather than left to the caller's memory of what it wrote: this is
30184
+ * the value the analytics sweep will actually use, and an operator asking how
30185
+ * long their audit trail lives should not have to infer it from a default.
30186
+ */
30187
+ opsLogRetentionDays: number().int().min(0),
29898
30188
  persisted: boolean()
29899
30189
  });
29900
30190
  method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
@@ -34054,6 +34344,12 @@ Object.freeze({
34054
34344
  addonId: null,
34055
34345
  access: "view"
34056
34346
  },
34347
+ "pipelineAnalytics.listRecentSummaries": {
34348
+ capName: "pipeline-analytics",
34349
+ capScope: "device",
34350
+ addonId: null,
34351
+ access: "view"
34352
+ },
34057
34353
  "pipelineAnalytics.listRecentTracks": {
34058
34354
  capName: "pipeline-analytics",
34059
34355
  capScope: "device",
@@ -36016,6 +36312,12 @@ Object.freeze({
36016
36312
  addonId: null,
36017
36313
  access: "create"
36018
36314
  },
36315
+ "streamBroker.reportStreamSignal": {
36316
+ capName: "stream-broker",
36317
+ capScope: "system",
36318
+ addonId: null,
36319
+ access: "create"
36320
+ },
36019
36321
  "streamBroker.restartProfile": {
36020
36322
  capName: "stream-broker",
36021
36323
  capScope: "system",
@@ -36100,6 +36402,12 @@ Object.freeze({
36100
36402
  addonId: null,
36101
36403
  access: "create"
36102
36404
  },
36405
+ "streamSignals.listSignals": {
36406
+ capName: "stream-signals",
36407
+ capScope: "device",
36408
+ addonId: null,
36409
+ access: "view"
36410
+ },
36103
36411
  "switch.setState": {
36104
36412
  capName: "switch",
36105
36413
  capScope: "device",
@@ -37881,6 +38189,11 @@ Object.freeze({
37881
38189
  form: "single",
37882
38190
  optional: true
37883
38191
  }],
38192
+ "pipelineAnalytics.listRecentSummaries": [{
38193
+ name: "deviceIds",
38194
+ form: "array",
38195
+ optional: false
38196
+ }],
37884
38197
  "pipelineAnalytics.listRecentTracks": [{
37885
38198
  name: "deviceIds",
37886
38199
  form: "array",
@@ -38467,6 +38780,11 @@ Object.freeze({
38467
38780
  form: "single",
38468
38781
  optional: false
38469
38782
  }],
38783
+ "streamBroker.reportStreamSignal": [{
38784
+ name: "deviceId",
38785
+ form: "single",
38786
+ optional: false
38787
+ }],
38470
38788
  "streamBroker.restartProfile": [{
38471
38789
  name: "deviceId",
38472
38790
  form: "single",
@@ -34,7 +34,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
35
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
36
36
  //#endregion
37
- //#region ../types/dist/event-category-ZyX6jcse.mjs
37
+ //#region ../types/dist/event-category-BVDXG4tB.mjs
38
38
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
39
39
  EventCategory["SystemBoot"] = "system.boot";
40
40
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -651,6 +651,20 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
651
651
  * pull-reconcile from the provider's `getStatus` on reconnect. */
652
652
  EventCategory["MeshNetworkChanged"] = "network.mesh.changed";
653
653
  EventCategory["BackupCompleted"] = "backup.completed";
654
+ /**
655
+ * A whole backup RUN finished — every destination attempted, win or lose.
656
+ *
657
+ * `backup.completed` fires once per DESTINATION, which is the right grain for
658
+ * a progress UI and the wrong one for a notification: an operator with three
659
+ * destinations would be told three times. And a run where two of three
660
+ * destinations succeeded is not a clean success — a single "backup
661
+ * completed" that hid the failed one would be a lie, so the count of each is
662
+ * carried here and the message says both.
663
+ *
664
+ * Emitted by the backup orchestrator only after the destination loop, so a
665
+ * run that dies during the BUILD phase produces no completion at all.
666
+ */
667
+ EventCategory["BackupRunCompleted"] = "backup.run-completed";
654
668
  EventCategory["BackupRestored"] = "backup.restored";
655
669
  EventCategory["NotificationDispatched"] = "notification.dispatched";
656
670
  EventCategory["NotificationFailed"] = "notification.failed";
@@ -5381,7 +5395,7 @@ var ZodIssueCode = {
5381
5395
  var ZodFirstPartyTypeKind;
5382
5396
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5383
5397
  //#endregion
5384
- //#region ../types/dist/sleep-CnLyvg3w.mjs
5398
+ //#region ../types/dist/sleep-DEuj7E3j.mjs
5385
5399
  /**
5386
5400
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5387
5401
  * window to float samples (D455).
@@ -7394,7 +7408,23 @@ var EncodeProfileSchema = object({
7394
7408
  "zerolatency",
7395
7409
  "film",
7396
7410
  "animation"
7397
- ]).optional()
7411
+ ]).optional(),
7412
+ /**
7413
+ * ONE slice per access unit.
7414
+ *
7415
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7416
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7417
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7418
+ * frame-end per frame: the first pictures render and the video then freezes
7419
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7420
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7421
+ *
7422
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7423
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7424
+ * consumers meaning the same thing spelled differently would stop sharing one
7425
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7426
+ */
7427
+ singleSlicePerFrame: boolean().optional()
7398
7428
  }),
7399
7429
  audio: union([literal("passthrough"), object({
7400
7430
  codec: _enum([
@@ -7444,7 +7474,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7444
7474
  audio: "passthrough"
7445
7475
  };
7446
7476
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7447
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7477
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7448
7478
  /**
7449
7479
  * Deep wiring healthcheck — snapshot of active reachability probes across
7450
7480
  * every declared capability + widget of every installed plugin, on every
@@ -11493,6 +11523,58 @@ method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, Broke
11493
11523
  auth: "admin"
11494
11524
  }), method(GetStateInputSchema, unknown().nullable()), method(_void(), RegistryStatusSchema);
11495
11525
  DeviceType.Camera;
11526
+ /**
11527
+ * The signals a device can emit to WAKE its own stream.
11528
+ *
11529
+ * A camera whose stream is built on demand sleeps until something asks for it,
11530
+ * and "something" cannot be a consumer that is merely attached — a Frigate-style
11531
+ * puller holds a session open for ever, and treating that as demand would keep
11532
+ * a battery camera awake for ever, which is the whole thing the battery is for
11533
+ * (D173). So the wake has to come from the CAMERA: an event it noticed by
11534
+ * itself, with no stream running.
11535
+ *
11536
+ * ## The vocabulary is the PROVIDER'S, not ours
11537
+ *
11538
+ * Like `consumables`, this cap declares no vocabulary of its own. A provider
11539
+ * names each signal with a `code` it chooses and a `label` an operator reads.
11540
+ * Reolink offers motion and camera-native detection; another provider may offer
11541
+ * a tamper, a doorbell press, a PIR, or something no camera in this fleet has
11542
+ * yet. A fixed enum here would mean every new signal is a framework release.
11543
+ *
11544
+ * It is deliberately NOT derived from the caps a device already binds. Whether
11545
+ * a camera CAN push firmware motion is expressed by `motionSources` containing
11546
+ * `'onboard'`, and whether it does AI on-camera by the `native-object-detection`
11547
+ * binding — but both answer "what drives the detection pipeline", which is a
11548
+ * different question from "what may wake a sleeping stream". A camera can do
11549
+ * the first and not be trusted with the second, and the operator picks per
11550
+ * camera. Two questions, two authorities.
11551
+ *
11552
+ * ## Availability is not permission
11553
+ *
11554
+ * `listSignals` says what the device CAN emit. Whether a given signal actually
11555
+ * wakes the stream is the operator's per-camera choice, held by the broker
11556
+ * alongside the cooldown — see the stream-broker cap's wake settings. A
11557
+ * provider declaring a signal is not a provider enabling it.
11558
+ */
11559
+ /** One signal a device can emit. */
11560
+ var StreamSignalSchema = object({
11561
+ /** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
11562
+ code: string().min(1),
11563
+ /** What an operator reads in the picker. The provider's own wording. */
11564
+ label: string().min(1),
11565
+ /**
11566
+ * Whether the provider recommends this signal ON when a camera is first set
11567
+ * up. A provider knows which of its signals are cheap and reliable; an
11568
+ * operator should not have to discover that by trial. Reolink recommends
11569
+ * both of its own.
11570
+ */
11571
+ recommended: boolean()
11572
+ });
11573
+ object({
11574
+ signals: array(StreamSignalSchema),
11575
+ lastFetchedAt: number()
11576
+ });
11577
+ Object.values(DeviceType), method(_void(), array(StreamSignalSchema).readonly());
11496
11578
  /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
11497
11579
  var StreamFormatSchema = _enum([
11498
11580
  "webrtc",
@@ -11889,6 +11971,22 @@ var EgressTranscodeSchema = object({
11889
11971
  camStreamId: string().nullable()
11890
11972
  });
11891
11973
  method(object({
11974
+ deviceId: number().int().nonnegative(),
11975
+ /** The provider's signal code. */
11976
+ code: string().min(1),
11977
+ /** Ms epoch. Absent ⇒ now. */
11978
+ at: number().optional()
11979
+ }), object({
11980
+ /** Whether the broker acted on it, and if not, why. */
11981
+ accepted: boolean(),
11982
+ reason: _enum([
11983
+ "woke",
11984
+ "hold-extended",
11985
+ "not-enabled",
11986
+ "no-consumer",
11987
+ "unknown-code"
11988
+ ])
11989
+ }), { kind: "mutation" }), method(object({
11892
11990
  deviceId: number().int().nonnegative(),
11893
11991
  camStreamId: string().min(1),
11894
11992
  kind: CamStreamKindSchema,
@@ -12143,6 +12241,16 @@ var PickStreamRequirementsSchema = object({
12143
12241
  acceptCodecs: array(StreamCodecSchema).readonly().optional(),
12144
12242
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
12145
12243
  minHeight: number().int().positive().optional(),
12244
+ /**
12245
+ * Maximum vertical resolution. Streams TALLER than this are dropped.
12246
+ *
12247
+ * A consumer can have a ceiling as real as its floor: Alexa documents
12248
+ * 480p to 1080p, and a 4K stream is as unusable to an Echo as a 360p one.
12249
+ * Without this the ceiling had to be re-implemented by every caller — and
12250
+ * one caller implementing it privately is how a second scoring authority
12251
+ * gets born.
12252
+ */
12253
+ maxHeight: number().int().positive().optional(),
12146
12254
  /** Minimum horizontal resolution. */
12147
12255
  minWidth: number().int().positive().optional(),
12148
12256
  /**
@@ -12159,7 +12267,22 @@ var PickStreamRequirementsSchema = object({
12159
12267
  * transcoded" guard: if the device is already serving the consumer's
12160
12268
  * codec end-to-end, there's nothing to optimise.
12161
12269
  */
12162
- requireSiblingCodec: array(StreamCodecSchema).readonly().optional()
12270
+ requireSiblingCodec: array(StreamCodecSchema).readonly().optional(),
12271
+ /**
12272
+ * Whether a stream the consumer CANNOT decode may still be picked, on the
12273
+ * understanding that it will be transcoded.
12274
+ *
12275
+ * Default `false` — today's behaviour, and the right one for a bypass
12276
+ * question ("is there a stream I can forward untouched?"). Set `true` to
12277
+ * ask the larger question: "what is the best stream for me, transcoding if
12278
+ * I must?" A stream that satisfies `acceptCodecs` always outranks one that
12279
+ * does not, so a passthrough is never lost to a transcode; the answer says
12280
+ * which it is in {@link PickedCamStreamSchema.transcodes}.
12281
+ *
12282
+ * This is what lets one picker serve both the bypass and the full source
12283
+ * choice, instead of a consumer scoring privately when the bypass misses.
12284
+ */
12285
+ allowTranscode: boolean().optional()
12163
12286
  }).readonly();
12164
12287
  var PickStreamPreferencesSchema = object({
12165
12288
  /**
@@ -12173,12 +12296,32 @@ var PickStreamPreferencesSchema = object({
12173
12296
  * picks the tallest stream; `'lowest'` picks the shortest (used by
12174
12297
  * memory-constrained consumers / Apple Home guest sessions).
12175
12298
  */
12176
- resolutionPreference: _enum(["highest", "lowest"]).optional()
12299
+ resolutionPreference: _enum(["highest", "lowest"]).optional(),
12300
+ /**
12301
+ * The height the consumer actually wants to DELIVER.
12302
+ *
12303
+ * Not a constraint — an ordering. With it set, the SMALLEST stream at or
12304
+ * above the target wins, and only if nothing reaches it does the tallest
12305
+ * take over. Pulling 1296 lines to draw 720 on a 1280x800 Echo panel costs
12306
+ * a decode and buys nothing, and `resolutionPreference: 'highest'` cannot
12307
+ * express that: it says "as big as possible", which is a different wish.
12308
+ *
12309
+ * Ignored when absent, so every existing caller keeps its ordering.
12310
+ */
12311
+ targetHeight: number().int().positive().optional()
12177
12312
  }).readonly();
12178
12313
  var PickedCamStreamSchema = object({
12179
12314
  camStreamId: string(),
12180
12315
  codec: string().optional(),
12181
12316
  resolution: CamStreamResolutionSchema.optional(),
12317
+ /**
12318
+ * Whether serving this stream requires a decode + re-encode.
12319
+ *
12320
+ * `false` is a stream the consumer can take as it stands. Only ever `true`
12321
+ * when the caller asked for it with `allowTranscode`, so a caller that did
12322
+ * not ask cannot be handed a cost it never agreed to pay.
12323
+ */
12324
+ transcodes: boolean(),
12182
12325
  /** One-line explanation of why this stream won — for logs / debug UI. */
12183
12326
  reason: string()
12184
12327
  });
@@ -16367,6 +16510,8 @@ var NcSystemEventKindSchema = _enum([
16367
16510
  "device-enabled",
16368
16511
  "device-battery-low",
16369
16512
  "device-battery-normal",
16513
+ "device-consumable-low",
16514
+ "device-consumable-normal",
16370
16515
  "stream-online",
16371
16516
  "stream-offline",
16372
16517
  "node-online",
@@ -16385,6 +16530,7 @@ var NcSystemEventKindSchema = _enum([
16385
16530
  "addon-updated",
16386
16531
  "server-updated",
16387
16532
  "export-completed",
16533
+ "backup-completed",
16388
16534
  "camera-online",
16389
16535
  "camera-offline",
16390
16536
  "camera-disabled",
@@ -19781,13 +19927,109 @@ var SummariesQueryInput = object({
19781
19927
  deviceIds: array(number()).min(1),
19782
19928
  since: number().optional(),
19783
19929
  until: number().optional(),
19784
- limit: number().int().min(1).max(200).default(50)
19930
+ limit: number().int().min(1).max(200).default(50),
19931
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
19932
+ * meaning of "dog" on every summary surface. */
19933
+ classes: array(string().min(1).max(60)).max(40).optional()
19785
19934
  });
19786
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
19787
19935
  var SummaryDetailSchema = object({
19788
19936
  summary: SummarySchema,
19789
- /** Member track ids, in absorption order. */
19790
- trackIds: array(string()).readonly()
19937
+ /** Members in absorption order, oldest join first. */
19938
+ members: array(object({
19939
+ trackId: string(),
19940
+ /**
19941
+ * The track's class at the time it JOINED, or `null` when it was unresolved
19942
+ * and the track no longer exists to fill it.
19943
+ *
19944
+ * The join, not the latest opinion: class voting can re-decide a track's
19945
+ * class afterwards, and a group's composition is the history of who came
19946
+ * together. A consumer counts these to badge a thumbnail — "two people and a
19947
+ * dog" — rather than being handed a second, denormalised tally that would
19948
+ * drift from the members it claims to describe.
19949
+ */
19950
+ className: string().nullable(),
19951
+ /** `primary` is the one member that carries the group's identity. */
19952
+ role: _enum([
19953
+ "primary",
19954
+ "sibling",
19955
+ "child"
19956
+ ]).nullable(),
19957
+ joinedAt: number(),
19958
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
19959
+ * at read time. Absent when nothing resolved, never an empty string. */
19960
+ label: string().optional(),
19961
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
19962
+ * recognised nobody, and absent for a member whose track has been pruned —
19963
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
19964
+ subLabel: string().optional()
19965
+ })).readonly()
19966
+ });
19967
+ /**
19968
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
19969
+ *
19970
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
19971
+ * we extend when we notice we need more". Enough to draw the card ("two people
19972
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
19973
+ * are on {@link getSummary}: shipping every member of every group so a client
19974
+ * can count them turns a page of two hundred groups into a thousand records to
19975
+ * draw two hundred badges.
19976
+ */
19977
+ var SummaryListItemSchema = object({
19978
+ summary: SummarySchema,
19979
+ /** Class counts, most numerous first; `className: null` is a member whose
19980
+ * class never resolved, counted so the counts add up to `memberCount`. */
19981
+ classes: array(object({
19982
+ className: string().nullable(),
19983
+ count: number().int().positive()
19984
+ })).readonly()
19985
+ });
19986
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
19987
+ /**
19988
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
19989
+ *
19990
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
19991
+ * summaries feed that could only answer "the last N for these cameras"
19992
+ * (`listSummaries`) would make groups available in the timeline and nowhere
19993
+ * else. Two feeds with different ordering contracts would be two ideas of what
19994
+ * "newest" means, on one screen.
19995
+ */
19996
+ var RecentSummariesQueryInput = object({
19997
+ /** Devices to merge. An empty array yields an empty page. */
19998
+ deviceIds: array(number()),
19999
+ /** Window lower bound on `lastActiveAt` (inclusive). */
20000
+ since: number().optional(),
20001
+ /** Window upper bound on `lastActiveAt` (inclusive). */
20002
+ until: number().optional(),
20003
+ limit: number().int().min(1).max(500).default(100),
20004
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
20005
+ cursor: string().optional(),
20006
+ /**
20007
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
20008
+ * deliberately the same name: the events filter tree emits one list of ticked
20009
+ * taxonomy nodes, and a summaries feed that called it something else would
20010
+ * make the operator's one filter component produce two shapes.
20011
+ *
20012
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
20013
+ * me the arrivals with a dog" has to return the group of four people who had
20014
+ * one with them; a filter on the group's own fields could only ever answer
20015
+ * about its primary.
20016
+ *
20017
+ * Matched against all THREE tiers of a member — the class (`animal`), the
20018
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
20019
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
20020
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
20021
+ * that read only the class would answer "animal" and never "dog".
20022
+ *
20023
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
20024
+ */
20025
+ classes: array(string().min(1).max(60)).max(40).optional()
20026
+ });
20027
+ var RecentSummariesPageSchema = object({
20028
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
20029
+ * {@link listSummaries} — one list shape, so a card renders the same
20030
+ * whichever feed drew it. */
20031
+ summaries: array(SummaryListItemSchema).readonly(),
20032
+ nextCursor: string().nullable()
19791
20033
  });
19792
20034
  var RecentTracksPageSchema = object({
19793
20035
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -20201,7 +20443,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20201
20443
  classes: array(string()).optional(),
20202
20444
  /** See {@link ExcludeSourcesDoc}. */
20203
20445
  excludeSources: array(TrackSourceSchema).optional()
20204
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20446
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20205
20447
  kind: "mutation",
20206
20448
  auth: "admin"
20207
20449
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -23543,10 +23785,28 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
23543
23785
  }), boolean()), method(object({
23544
23786
  deviceId: number().int().nonnegative(),
23545
23787
  sessionId: string()
23546
- }), object({ pendingRenegotiation: object({
23547
- target: WebrtcStreamTargetSchema,
23548
- epoch: number()
23549
- }).nullable() }));
23788
+ }), object({
23789
+ pendingRenegotiation: object({
23790
+ target: WebrtcStreamTargetSchema,
23791
+ epoch: number()
23792
+ }).nullable(),
23793
+ /**
23794
+ * Whether the session still EXISTS.
23795
+ *
23796
+ * A consumer that holds a resource for the life of a session needs to
23797
+ * be able to ask, because a session does not always end the way it
23798
+ * began. Measured on this hub 2026-09-13: an Echo that got stuck never
23799
+ * sent `SessionDisconnected`, so the Alexa exporter's
23800
+ * `releaseEgressTranscode` never ran, and a 2304x1296 HEVC to 720p
23801
+ * H.264 transcode kept running for NOBODY for more than twenty
23802
+ * minutes. The WebRTC session had logged `WebRTC session closed`
23803
+ * minutes earlier — the broker knew; the holder had no way to ask.
23804
+ *
23805
+ * `false` for a session id the provider has never heard of, which is
23806
+ * the same answer as "it ended": either way nothing is holding it up.
23807
+ */
23808
+ alive: boolean()
23809
+ }));
23550
23810
  object({
23551
23811
  /** All accessory children of the parent. */
23552
23812
  childDeviceIds: array(number()).readonly(),
@@ -29820,7 +30080,29 @@ var LoggingSettingsPatchSchema = object({
29820
30080
  * disarmed the channels it did not mention would make the Levels page and
29821
30081
  * the Diagnostics page fight over the same value.
29822
30082
  */
29823
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
30083
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
30084
+ /**
30085
+ * How many days the analytics OPS LOG is kept — the audit trail of what
30086
+ * retention actually deleted.
30087
+ *
30088
+ * It lives HERE, on the logging document, and not on the analytics addon's
30089
+ * settings form, on the operator's instruction: every other log-ish window in
30090
+ * this system is configured through this one document, and a retention audit
30091
+ * configured somewhere else is a second place to look when the question is
30092
+ * "what happened to my data".
30093
+ *
30094
+ * That placement has a cost worth stating: this document ships in the
30095
+ * framework closure, so this knob needs a published `@camstack/server` while
30096
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
30097
+ *
30098
+ * `retention-model.ts` argued this one must NOT follow a device window,
30099
+ * because the audit trail is the history of the SWEEP and not of the camera.
30100
+ * That argument stands and is untouched — what changes is that 30 days stops
30101
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
30102
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
30103
+ * state in which the next 9.5 GiB leak cannot be explained.
30104
+ */
30105
+ opsLogRetentionDays: number().int().min(0).max(3650).optional()
29824
30106
  });
29825
30107
  /**
29826
30108
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -29893,6 +30175,14 @@ var LoggingSettingsStateSchema = object({
29893
30175
  channels: array(LogChannelDescriptorSchema).readonly(),
29894
30176
  /** The channels ARMED right now, each with its deadline. */
29895
30177
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
30178
+ /**
30179
+ * The ops-log window in days, as it is in force. `0` = kept forever.
30180
+ *
30181
+ * Reported rather than left to the caller's memory of what it wrote: this is
30182
+ * the value the analytics sweep will actually use, and an operator asking how
30183
+ * long their audit trail lives should not have to infer it from a default.
30184
+ */
30185
+ opsLogRetentionDays: number().int().min(0),
29896
30186
  persisted: boolean()
29897
30187
  });
29898
30188
  method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
@@ -34052,6 +34342,12 @@ Object.freeze({
34052
34342
  addonId: null,
34053
34343
  access: "view"
34054
34344
  },
34345
+ "pipelineAnalytics.listRecentSummaries": {
34346
+ capName: "pipeline-analytics",
34347
+ capScope: "device",
34348
+ addonId: null,
34349
+ access: "view"
34350
+ },
34055
34351
  "pipelineAnalytics.listRecentTracks": {
34056
34352
  capName: "pipeline-analytics",
34057
34353
  capScope: "device",
@@ -36014,6 +36310,12 @@ Object.freeze({
36014
36310
  addonId: null,
36015
36311
  access: "create"
36016
36312
  },
36313
+ "streamBroker.reportStreamSignal": {
36314
+ capName: "stream-broker",
36315
+ capScope: "system",
36316
+ addonId: null,
36317
+ access: "create"
36318
+ },
36017
36319
  "streamBroker.restartProfile": {
36018
36320
  capName: "stream-broker",
36019
36321
  capScope: "system",
@@ -36098,6 +36400,12 @@ Object.freeze({
36098
36400
  addonId: null,
36099
36401
  access: "create"
36100
36402
  },
36403
+ "streamSignals.listSignals": {
36404
+ capName: "stream-signals",
36405
+ capScope: "device",
36406
+ addonId: null,
36407
+ access: "view"
36408
+ },
36101
36409
  "switch.setState": {
36102
36410
  capName: "switch",
36103
36411
  capScope: "device",
@@ -37879,6 +38187,11 @@ Object.freeze({
37879
38187
  form: "single",
37880
38188
  optional: true
37881
38189
  }],
38190
+ "pipelineAnalytics.listRecentSummaries": [{
38191
+ name: "deviceIds",
38192
+ form: "array",
38193
+ optional: false
38194
+ }],
37882
38195
  "pipelineAnalytics.listRecentTracks": [{
37883
38196
  name: "deviceIds",
37884
38197
  form: "array",
@@ -38465,6 +38778,11 @@ Object.freeze({
38465
38778
  form: "single",
38466
38779
  optional: false
38467
38780
  }],
38781
+ "streamBroker.reportStreamSignal": [{
38782
+ name: "deviceId",
38783
+ form: "single",
38784
+ optional: false
38785
+ }],
38468
38786
  "streamBroker.restartProfile": [{
38469
38787
  name: "deviceId",
38470
38788
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-smtp-nodemailer",
3
- "version": "1.2.99",
3
+ "version": "1.2.101",
4
4
  "description": "SMTP email provider addon for CamStack — wraps `nodemailer` and registers a `smtp-provider` cap collection entry. Used by magic-link login + notifier addons.",
5
5
  "keywords": [
6
6
  "camstack",