@camstack/addon-matter-broker 0.2.99 → 0.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.
Files changed (3) hide show
  1. package/dist/addon.js +349 -16
  2. package/dist/addon.mjs +349 -16
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -13,7 +13,7 @@ let node_stream_promises = require("node:stream/promises");
13
13
  let node_net = require("node:net");
14
14
  let node_dgram = require("node:dgram");
15
15
  node_dgram = require_esm.__toESM(node_dgram, 1);
16
- //#region ../types/dist/event-category-ZyX6jcse.mjs
16
+ //#region ../types/dist/event-category-BVDXG4tB.mjs
17
17
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
18
18
  EventCategory["SystemBoot"] = "system.boot";
19
19
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -630,6 +630,20 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
630
630
  * pull-reconcile from the provider's `getStatus` on reconnect. */
631
631
  EventCategory["MeshNetworkChanged"] = "network.mesh.changed";
632
632
  EventCategory["BackupCompleted"] = "backup.completed";
633
+ /**
634
+ * A whole backup RUN finished — every destination attempted, win or lose.
635
+ *
636
+ * `backup.completed` fires once per DESTINATION, which is the right grain for
637
+ * a progress UI and the wrong one for a notification: an operator with three
638
+ * destinations would be told three times. And a run where two of three
639
+ * destinations succeeded is not a clean success — a single "backup
640
+ * completed" that hid the failed one would be a lie, so the count of each is
641
+ * carried here and the message says both.
642
+ *
643
+ * Emitted by the backup orchestrator only after the destination loop, so a
644
+ * run that dies during the BUILD phase produces no completion at all.
645
+ */
646
+ EventCategory["BackupRunCompleted"] = "backup.run-completed";
633
647
  EventCategory["BackupRestored"] = "backup.restored";
634
648
  EventCategory["NotificationDispatched"] = "notification.dispatched";
635
649
  EventCategory["NotificationFailed"] = "notification.failed";
@@ -5360,7 +5374,7 @@ var ZodIssueCode = {
5360
5374
  var ZodFirstPartyTypeKind;
5361
5375
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5362
5376
  //#endregion
5363
- //#region ../types/dist/sleep-CnLyvg3w.mjs
5377
+ //#region ../types/dist/sleep-DEuj7E3j.mjs
5364
5378
  /**
5365
5379
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5366
5380
  * window to float samples (D455).
@@ -7389,7 +7403,23 @@ var EncodeProfileSchema = object({
7389
7403
  "zerolatency",
7390
7404
  "film",
7391
7405
  "animation"
7392
- ]).optional()
7406
+ ]).optional(),
7407
+ /**
7408
+ * ONE slice per access unit.
7409
+ *
7410
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7411
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7412
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7413
+ * frame-end per frame: the first pictures render and the video then freezes
7414
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7415
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7416
+ *
7417
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7418
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7419
+ * consumers meaning the same thing spelled differently would stop sharing one
7420
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7421
+ */
7422
+ singleSlicePerFrame: boolean().optional()
7393
7423
  }),
7394
7424
  audio: union([literal("passthrough"), object({
7395
7425
  codec: _enum([
@@ -7439,7 +7469,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7439
7469
  audio: "passthrough"
7440
7470
  };
7441
7471
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7442
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7472
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7443
7473
  /**
7444
7474
  * Deep wiring healthcheck — snapshot of active reachability probes across
7445
7475
  * every declared capability + widget of every installed plugin, on every
@@ -11537,6 +11567,72 @@ var brokerCapability = {
11537
11567
  }
11538
11568
  };
11539
11569
  DeviceType.Camera;
11570
+ /**
11571
+ * The signals a device can emit to WAKE its own stream.
11572
+ *
11573
+ * A camera whose stream is built on demand sleeps until something asks for it,
11574
+ * and "something" cannot be a consumer that is merely attached — a Frigate-style
11575
+ * puller holds a session open for ever, and treating that as demand would keep
11576
+ * a battery camera awake for ever, which is the whole thing the battery is for
11577
+ * (D173). So the wake has to come from the CAMERA: an event it noticed by
11578
+ * itself, with no stream running.
11579
+ *
11580
+ * ## The vocabulary is the PROVIDER'S, not ours
11581
+ *
11582
+ * Like `consumables`, this cap declares no vocabulary of its own. A provider
11583
+ * names each signal with a `code` it chooses and a `label` an operator reads.
11584
+ * Reolink offers motion and camera-native detection; another provider may offer
11585
+ * a tamper, a doorbell press, a PIR, or something no camera in this fleet has
11586
+ * yet. A fixed enum here would mean every new signal is a framework release.
11587
+ *
11588
+ * It is deliberately NOT derived from the caps a device already binds. Whether
11589
+ * a camera CAN push firmware motion is expressed by `motionSources` containing
11590
+ * `'onboard'`, and whether it does AI on-camera by the `native-object-detection`
11591
+ * binding — but both answer "what drives the detection pipeline", which is a
11592
+ * different question from "what may wake a sleeping stream". A camera can do
11593
+ * the first and not be trusted with the second, and the operator picks per
11594
+ * camera. Two questions, two authorities.
11595
+ *
11596
+ * ## Availability is not permission
11597
+ *
11598
+ * `listSignals` says what the device CAN emit. Whether a given signal actually
11599
+ * wakes the stream is the operator's per-camera choice, held by the broker
11600
+ * alongside the cooldown — see the stream-broker cap's wake settings. A
11601
+ * provider declaring a signal is not a provider enabling it.
11602
+ */
11603
+ /** One signal a device can emit. */
11604
+ var StreamSignalSchema = object({
11605
+ /** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
11606
+ code: string$2().min(1),
11607
+ /** What an operator reads in the picker. The provider's own wording. */
11608
+ label: string$2().min(1),
11609
+ /**
11610
+ * Whether the provider recommends this signal ON when a camera is first set
11611
+ * up. A provider knows which of its signals are cheap and reliable; an
11612
+ * operator should not have to discover that by trial. Reolink recommends
11613
+ * both of its own.
11614
+ */
11615
+ recommended: boolean()
11616
+ });
11617
+ var StreamSignalsStatusSchema = object({
11618
+ signals: array(StreamSignalSchema),
11619
+ lastFetchedAt: number()
11620
+ });
11621
+ var streamSignalsCapability = {
11622
+ name: "stream-signals",
11623
+ scope: "device",
11624
+ deviceNative: true,
11625
+ mode: "singleton",
11626
+ deviceTypes: Object.values(DeviceType),
11627
+ runtimeState: StreamSignalsStatusSchema,
11628
+ methods: {
11629
+ /**
11630
+ * What this device can emit. Empty is a valid and common answer — most
11631
+ * cameras have nothing to offer here, and an empty list is what makes the
11632
+ * broker's picker show nothing rather than a false choice.
11633
+ */
11634
+ listSignals: method(_void(), array(StreamSignalSchema).readonly()) }
11635
+ };
11540
11636
  /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
11541
11637
  var StreamFormatSchema = _enum([
11542
11638
  "webrtc",
@@ -11933,6 +12029,22 @@ var EgressTranscodeSchema = object({
11933
12029
  camStreamId: string$2().nullable()
11934
12030
  });
11935
12031
  method(object({
12032
+ deviceId: number().int().nonnegative(),
12033
+ /** The provider's signal code. */
12034
+ code: string$2().min(1),
12035
+ /** Ms epoch. Absent ⇒ now. */
12036
+ at: number().optional()
12037
+ }), object({
12038
+ /** Whether the broker acted on it, and if not, why. */
12039
+ accepted: boolean(),
12040
+ reason: _enum([
12041
+ "woke",
12042
+ "hold-extended",
12043
+ "not-enabled",
12044
+ "no-consumer",
12045
+ "unknown-code"
12046
+ ])
12047
+ }), { kind: "mutation" }), method(object({
11936
12048
  deviceId: number().int().nonnegative(),
11937
12049
  camStreamId: string$2().min(1),
11938
12050
  kind: CamStreamKindSchema,
@@ -12187,6 +12299,16 @@ var PickStreamRequirementsSchema = object({
12187
12299
  acceptCodecs: array(StreamCodecSchema).readonly().optional(),
12188
12300
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
12189
12301
  minHeight: number().int().positive().optional(),
12302
+ /**
12303
+ * Maximum vertical resolution. Streams TALLER than this are dropped.
12304
+ *
12305
+ * A consumer can have a ceiling as real as its floor: Alexa documents
12306
+ * 480p to 1080p, and a 4K stream is as unusable to an Echo as a 360p one.
12307
+ * Without this the ceiling had to be re-implemented by every caller — and
12308
+ * one caller implementing it privately is how a second scoring authority
12309
+ * gets born.
12310
+ */
12311
+ maxHeight: number().int().positive().optional(),
12190
12312
  /** Minimum horizontal resolution. */
12191
12313
  minWidth: number().int().positive().optional(),
12192
12314
  /**
@@ -12203,7 +12325,22 @@ var PickStreamRequirementsSchema = object({
12203
12325
  * transcoded" guard: if the device is already serving the consumer's
12204
12326
  * codec end-to-end, there's nothing to optimise.
12205
12327
  */
12206
- requireSiblingCodec: array(StreamCodecSchema).readonly().optional()
12328
+ requireSiblingCodec: array(StreamCodecSchema).readonly().optional(),
12329
+ /**
12330
+ * Whether a stream the consumer CANNOT decode may still be picked, on the
12331
+ * understanding that it will be transcoded.
12332
+ *
12333
+ * Default `false` — today's behaviour, and the right one for a bypass
12334
+ * question ("is there a stream I can forward untouched?"). Set `true` to
12335
+ * ask the larger question: "what is the best stream for me, transcoding if
12336
+ * I must?" A stream that satisfies `acceptCodecs` always outranks one that
12337
+ * does not, so a passthrough is never lost to a transcode; the answer says
12338
+ * which it is in {@link PickedCamStreamSchema.transcodes}.
12339
+ *
12340
+ * This is what lets one picker serve both the bypass and the full source
12341
+ * choice, instead of a consumer scoring privately when the bypass misses.
12342
+ */
12343
+ allowTranscode: boolean().optional()
12207
12344
  }).readonly();
12208
12345
  var PickStreamPreferencesSchema = object({
12209
12346
  /**
@@ -12217,12 +12354,32 @@ var PickStreamPreferencesSchema = object({
12217
12354
  * picks the tallest stream; `'lowest'` picks the shortest (used by
12218
12355
  * memory-constrained consumers / Apple Home guest sessions).
12219
12356
  */
12220
- resolutionPreference: _enum(["highest", "lowest"]).optional()
12357
+ resolutionPreference: _enum(["highest", "lowest"]).optional(),
12358
+ /**
12359
+ * The height the consumer actually wants to DELIVER.
12360
+ *
12361
+ * Not a constraint — an ordering. With it set, the SMALLEST stream at or
12362
+ * above the target wins, and only if nothing reaches it does the tallest
12363
+ * take over. Pulling 1296 lines to draw 720 on a 1280x800 Echo panel costs
12364
+ * a decode and buys nothing, and `resolutionPreference: 'highest'` cannot
12365
+ * express that: it says "as big as possible", which is a different wish.
12366
+ *
12367
+ * Ignored when absent, so every existing caller keeps its ordering.
12368
+ */
12369
+ targetHeight: number().int().positive().optional()
12221
12370
  }).readonly();
12222
12371
  var PickedCamStreamSchema = object({
12223
12372
  camStreamId: string$2(),
12224
12373
  codec: string$2().optional(),
12225
12374
  resolution: CamStreamResolutionSchema.optional(),
12375
+ /**
12376
+ * Whether serving this stream requires a decode + re-encode.
12377
+ *
12378
+ * `false` is a stream the consumer can take as it stands. Only ever `true`
12379
+ * when the caller asked for it with `allowTranscode`, so a caller that did
12380
+ * not ask cannot be handed a cost it never agreed to pay.
12381
+ */
12382
+ transcodes: boolean(),
12226
12383
  /** One-line explanation of why this stream won — for logs / debug UI. */
12227
12384
  reason: string$2()
12228
12385
  });
@@ -16756,6 +16913,8 @@ var NcSystemEventKindSchema = _enum([
16756
16913
  "device-enabled",
16757
16914
  "device-battery-low",
16758
16915
  "device-battery-normal",
16916
+ "device-consumable-low",
16917
+ "device-consumable-normal",
16759
16918
  "stream-online",
16760
16919
  "stream-offline",
16761
16920
  "node-online",
@@ -16774,6 +16933,7 @@ var NcSystemEventKindSchema = _enum([
16774
16933
  "addon-updated",
16775
16934
  "server-updated",
16776
16935
  "export-completed",
16936
+ "backup-completed",
16777
16937
  "camera-online",
16778
16938
  "camera-offline",
16779
16939
  "camera-disabled",
@@ -20210,13 +20370,109 @@ var SummariesQueryInput = object({
20210
20370
  deviceIds: array(number()).min(1),
20211
20371
  since: number().optional(),
20212
20372
  until: number().optional(),
20213
- limit: number().int().min(1).max(200).default(50)
20373
+ limit: number().int().min(1).max(200).default(50),
20374
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
20375
+ * meaning of "dog" on every summary surface. */
20376
+ classes: array(string$2().min(1).max(60)).max(40).optional()
20214
20377
  });
20215
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20216
20378
  var SummaryDetailSchema = object({
20217
20379
  summary: SummarySchema,
20218
- /** Member track ids, in absorption order. */
20219
- trackIds: array(string$2()).readonly()
20380
+ /** Members in absorption order, oldest join first. */
20381
+ members: array(object({
20382
+ trackId: string$2(),
20383
+ /**
20384
+ * The track's class at the time it JOINED, or `null` when it was unresolved
20385
+ * and the track no longer exists to fill it.
20386
+ *
20387
+ * The join, not the latest opinion: class voting can re-decide a track's
20388
+ * class afterwards, and a group's composition is the history of who came
20389
+ * together. A consumer counts these to badge a thumbnail — "two people and a
20390
+ * dog" — rather than being handed a second, denormalised tally that would
20391
+ * drift from the members it claims to describe.
20392
+ */
20393
+ className: string$2().nullable(),
20394
+ /** `primary` is the one member that carries the group's identity. */
20395
+ role: _enum([
20396
+ "primary",
20397
+ "sibling",
20398
+ "child"
20399
+ ]).nullable(),
20400
+ joinedAt: number(),
20401
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
20402
+ * at read time. Absent when nothing resolved, never an empty string. */
20403
+ label: string$2().optional(),
20404
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
20405
+ * recognised nobody, and absent for a member whose track has been pruned —
20406
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
20407
+ subLabel: string$2().optional()
20408
+ })).readonly()
20409
+ });
20410
+ /**
20411
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
20412
+ *
20413
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
20414
+ * we extend when we notice we need more". Enough to draw the card ("two people
20415
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
20416
+ * are on {@link getSummary}: shipping every member of every group so a client
20417
+ * can count them turns a page of two hundred groups into a thousand records to
20418
+ * draw two hundred badges.
20419
+ */
20420
+ var SummaryListItemSchema = object({
20421
+ summary: SummarySchema,
20422
+ /** Class counts, most numerous first; `className: null` is a member whose
20423
+ * class never resolved, counted so the counts add up to `memberCount`. */
20424
+ classes: array(object({
20425
+ className: string$2().nullable(),
20426
+ count: number().int().positive()
20427
+ })).readonly()
20428
+ });
20429
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
20430
+ /**
20431
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
20432
+ *
20433
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
20434
+ * summaries feed that could only answer "the last N for these cameras"
20435
+ * (`listSummaries`) would make groups available in the timeline and nowhere
20436
+ * else. Two feeds with different ordering contracts would be two ideas of what
20437
+ * "newest" means, on one screen.
20438
+ */
20439
+ var RecentSummariesQueryInput = object({
20440
+ /** Devices to merge. An empty array yields an empty page. */
20441
+ deviceIds: array(number()),
20442
+ /** Window lower bound on `lastActiveAt` (inclusive). */
20443
+ since: number().optional(),
20444
+ /** Window upper bound on `lastActiveAt` (inclusive). */
20445
+ until: number().optional(),
20446
+ limit: number().int().min(1).max(500).default(100),
20447
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
20448
+ cursor: string$2().optional(),
20449
+ /**
20450
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
20451
+ * deliberately the same name: the events filter tree emits one list of ticked
20452
+ * taxonomy nodes, and a summaries feed that called it something else would
20453
+ * make the operator's one filter component produce two shapes.
20454
+ *
20455
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
20456
+ * me the arrivals with a dog" has to return the group of four people who had
20457
+ * one with them; a filter on the group's own fields could only ever answer
20458
+ * about its primary.
20459
+ *
20460
+ * Matched against all THREE tiers of a member — the class (`animal`), the
20461
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
20462
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
20463
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
20464
+ * that read only the class would answer "animal" and never "dog".
20465
+ *
20466
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
20467
+ */
20468
+ classes: array(string$2().min(1).max(60)).max(40).optional()
20469
+ });
20470
+ var RecentSummariesPageSchema = object({
20471
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
20472
+ * {@link listSummaries} — one list shape, so a card renders the same
20473
+ * whichever feed drew it. */
20474
+ summaries: array(SummaryListItemSchema).readonly(),
20475
+ nextCursor: string$2().nullable()
20220
20476
  });
20221
20477
  var RecentTracksPageSchema = object({
20222
20478
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -20630,7 +20886,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20630
20886
  classes: array(string$2()).optional(),
20631
20887
  /** See {@link ExcludeSourcesDoc}. */
20632
20888
  excludeSources: array(TrackSourceSchema).optional()
20633
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string$2() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20889
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string$2() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20634
20890
  kind: "mutation",
20635
20891
  auth: "admin"
20636
20892
  }), 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({
@@ -23959,10 +24215,28 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
23959
24215
  }), boolean()), method(object({
23960
24216
  deviceId: number().int().nonnegative(),
23961
24217
  sessionId: string$2()
23962
- }), object({ pendingRenegotiation: object({
23963
- target: WebrtcStreamTargetSchema,
23964
- epoch: number()
23965
- }).nullable() }));
24218
+ }), object({
24219
+ pendingRenegotiation: object({
24220
+ target: WebrtcStreamTargetSchema,
24221
+ epoch: number()
24222
+ }).nullable(),
24223
+ /**
24224
+ * Whether the session still EXISTS.
24225
+ *
24226
+ * A consumer that holds a resource for the life of a session needs to
24227
+ * be able to ask, because a session does not always end the way it
24228
+ * began. Measured on this hub 2026-09-13: an Echo that got stuck never
24229
+ * sent `SessionDisconnected`, so the Alexa exporter's
24230
+ * `releaseEgressTranscode` never ran, and a 2304x1296 HEVC to 720p
24231
+ * H.264 transcode kept running for NOBODY for more than twenty
24232
+ * minutes. The WebRTC session had logged `WebRTC session closed`
24233
+ * minutes earlier — the broker knew; the holder had no way to ask.
24234
+ *
24235
+ * `false` for a session id the provider has never heard of, which is
24236
+ * the same answer as "it ended": either way nothing is holding it up.
24237
+ */
24238
+ alive: boolean()
24239
+ }));
23966
24240
  object({
23967
24241
  /** All accessory children of the parent. */
23968
24242
  childDeviceIds: array(number()).readonly(),
@@ -32400,7 +32674,29 @@ var LoggingSettingsPatchSchema = object({
32400
32674
  * disarmed the channels it did not mention would make the Levels page and
32401
32675
  * the Diagnostics page fight over the same value.
32402
32676
  */
32403
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
32677
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
32678
+ /**
32679
+ * How many days the analytics OPS LOG is kept — the audit trail of what
32680
+ * retention actually deleted.
32681
+ *
32682
+ * It lives HERE, on the logging document, and not on the analytics addon's
32683
+ * settings form, on the operator's instruction: every other log-ish window in
32684
+ * this system is configured through this one document, and a retention audit
32685
+ * configured somewhere else is a second place to look when the question is
32686
+ * "what happened to my data".
32687
+ *
32688
+ * That placement has a cost worth stating: this document ships in the
32689
+ * framework closure, so this knob needs a published `@camstack/server` while
32690
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
32691
+ *
32692
+ * `retention-model.ts` argued this one must NOT follow a device window,
32693
+ * because the audit trail is the history of the SWEEP and not of the camera.
32694
+ * That argument stands and is untouched — what changes is that 30 days stops
32695
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
32696
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
32697
+ * state in which the next 9.5 GiB leak cannot be explained.
32698
+ */
32699
+ opsLogRetentionDays: number().int().min(0).max(3650).optional()
32404
32700
  });
32405
32701
  /**
32406
32702
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -32473,6 +32769,14 @@ var LoggingSettingsStateSchema = object({
32473
32769
  channels: array(LogChannelDescriptorSchema).readonly(),
32474
32770
  /** The channels ARMED right now, each with its deadline. */
32475
32771
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
32772
+ /**
32773
+ * The ops-log window in days, as it is in force. `0` = kept forever.
32774
+ *
32775
+ * Reported rather than left to the caller's memory of what it wrote: this is
32776
+ * the value the analytics sweep will actually use, and an operator asking how
32777
+ * long their audit trail lives should not have to infer it from a default.
32778
+ */
32779
+ opsLogRetentionDays: number().int().min(0),
32476
32780
  persisted: boolean()
32477
32781
  });
32478
32782
  method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string$2(), unknown()), _null(), {
@@ -33868,6 +34172,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
33868
34172
  smoke: smokeCapability,
33869
34173
  streamCatalog: streamCatalogCapability,
33870
34174
  streamParams: streamParamsCapability,
34175
+ streamSignals: streamSignalsCapability,
33871
34176
  switch: switchCapability,
33872
34177
  tamper: tamperCapability,
33873
34178
  temperatureSensor: temperatureSensorCapability,
@@ -38473,6 +38778,12 @@ Object.freeze({
38473
38778
  addonId: null,
38474
38779
  access: "view"
38475
38780
  },
38781
+ "pipelineAnalytics.listRecentSummaries": {
38782
+ capName: "pipeline-analytics",
38783
+ capScope: "device",
38784
+ addonId: null,
38785
+ access: "view"
38786
+ },
38476
38787
  "pipelineAnalytics.listRecentTracks": {
38477
38788
  capName: "pipeline-analytics",
38478
38789
  capScope: "device",
@@ -40435,6 +40746,12 @@ Object.freeze({
40435
40746
  addonId: null,
40436
40747
  access: "create"
40437
40748
  },
40749
+ "streamBroker.reportStreamSignal": {
40750
+ capName: "stream-broker",
40751
+ capScope: "system",
40752
+ addonId: null,
40753
+ access: "create"
40754
+ },
40438
40755
  "streamBroker.restartProfile": {
40439
40756
  capName: "stream-broker",
40440
40757
  capScope: "system",
@@ -40519,6 +40836,12 @@ Object.freeze({
40519
40836
  addonId: null,
40520
40837
  access: "create"
40521
40838
  },
40839
+ "streamSignals.listSignals": {
40840
+ capName: "stream-signals",
40841
+ capScope: "device",
40842
+ addonId: null,
40843
+ access: "view"
40844
+ },
40522
40845
  "switch.setState": {
40523
40846
  capName: "switch",
40524
40847
  capScope: "device",
@@ -42300,6 +42623,11 @@ Object.freeze({
42300
42623
  form: "single",
42301
42624
  optional: true
42302
42625
  }],
42626
+ "pipelineAnalytics.listRecentSummaries": [{
42627
+ name: "deviceIds",
42628
+ form: "array",
42629
+ optional: false
42630
+ }],
42303
42631
  "pipelineAnalytics.listRecentTracks": [{
42304
42632
  name: "deviceIds",
42305
42633
  form: "array",
@@ -42886,6 +43214,11 @@ Object.freeze({
42886
43214
  form: "single",
42887
43215
  optional: false
42888
43216
  }],
43217
+ "streamBroker.reportStreamSignal": [{
43218
+ name: "deviceId",
43219
+ form: "single",
43220
+ optional: false
43221
+ }],
42889
43222
  "streamBroker.restartProfile": [{
42890
43223
  name: "deviceId",
42891
43224
  form: "single",
package/dist/addon.mjs CHANGED
@@ -11,7 +11,7 @@ import { networkInterfaces, tmpdir, uptime } from "node:os";
11
11
  import { finished } from "node:stream/promises";
12
12
  import { createConnection, createServer as createServer$2 } from "node:net";
13
13
  import * as dgram from "node:dgram";
14
- //#region ../types/dist/event-category-ZyX6jcse.mjs
14
+ //#region ../types/dist/event-category-BVDXG4tB.mjs
15
15
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
16
16
  EventCategory["SystemBoot"] = "system.boot";
17
17
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -628,6 +628,20 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
628
628
  * pull-reconcile from the provider's `getStatus` on reconnect. */
629
629
  EventCategory["MeshNetworkChanged"] = "network.mesh.changed";
630
630
  EventCategory["BackupCompleted"] = "backup.completed";
631
+ /**
632
+ * A whole backup RUN finished — every destination attempted, win or lose.
633
+ *
634
+ * `backup.completed` fires once per DESTINATION, which is the right grain for
635
+ * a progress UI and the wrong one for a notification: an operator with three
636
+ * destinations would be told three times. And a run where two of three
637
+ * destinations succeeded is not a clean success — a single "backup
638
+ * completed" that hid the failed one would be a lie, so the count of each is
639
+ * carried here and the message says both.
640
+ *
641
+ * Emitted by the backup orchestrator only after the destination loop, so a
642
+ * run that dies during the BUILD phase produces no completion at all.
643
+ */
644
+ EventCategory["BackupRunCompleted"] = "backup.run-completed";
631
645
  EventCategory["BackupRestored"] = "backup.restored";
632
646
  EventCategory["NotificationDispatched"] = "notification.dispatched";
633
647
  EventCategory["NotificationFailed"] = "notification.failed";
@@ -5358,7 +5372,7 @@ var ZodIssueCode = {
5358
5372
  var ZodFirstPartyTypeKind;
5359
5373
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5360
5374
  //#endregion
5361
- //#region ../types/dist/sleep-CnLyvg3w.mjs
5375
+ //#region ../types/dist/sleep-DEuj7E3j.mjs
5362
5376
  /**
5363
5377
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5364
5378
  * window to float samples (D455).
@@ -7387,7 +7401,23 @@ var EncodeProfileSchema = object({
7387
7401
  "zerolatency",
7388
7402
  "film",
7389
7403
  "animation"
7390
- ]).optional()
7404
+ ]).optional(),
7405
+ /**
7406
+ * ONE slice per access unit.
7407
+ *
7408
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7409
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7410
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7411
+ * frame-end per frame: the first pictures render and the video then freezes
7412
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7413
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7414
+ *
7415
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7416
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7417
+ * consumers meaning the same thing spelled differently would stop sharing one
7418
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7419
+ */
7420
+ singleSlicePerFrame: boolean().optional()
7391
7421
  }),
7392
7422
  audio: union([literal("passthrough"), object({
7393
7423
  codec: _enum([
@@ -7437,7 +7467,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7437
7467
  audio: "passthrough"
7438
7468
  };
7439
7469
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7440
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7470
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7441
7471
  /**
7442
7472
  * Deep wiring healthcheck — snapshot of active reachability probes across
7443
7473
  * every declared capability + widget of every installed plugin, on every
@@ -11535,6 +11565,72 @@ var brokerCapability = {
11535
11565
  }
11536
11566
  };
11537
11567
  DeviceType.Camera;
11568
+ /**
11569
+ * The signals a device can emit to WAKE its own stream.
11570
+ *
11571
+ * A camera whose stream is built on demand sleeps until something asks for it,
11572
+ * and "something" cannot be a consumer that is merely attached — a Frigate-style
11573
+ * puller holds a session open for ever, and treating that as demand would keep
11574
+ * a battery camera awake for ever, which is the whole thing the battery is for
11575
+ * (D173). So the wake has to come from the CAMERA: an event it noticed by
11576
+ * itself, with no stream running.
11577
+ *
11578
+ * ## The vocabulary is the PROVIDER'S, not ours
11579
+ *
11580
+ * Like `consumables`, this cap declares no vocabulary of its own. A provider
11581
+ * names each signal with a `code` it chooses and a `label` an operator reads.
11582
+ * Reolink offers motion and camera-native detection; another provider may offer
11583
+ * a tamper, a doorbell press, a PIR, or something no camera in this fleet has
11584
+ * yet. A fixed enum here would mean every new signal is a framework release.
11585
+ *
11586
+ * It is deliberately NOT derived from the caps a device already binds. Whether
11587
+ * a camera CAN push firmware motion is expressed by `motionSources` containing
11588
+ * `'onboard'`, and whether it does AI on-camera by the `native-object-detection`
11589
+ * binding — but both answer "what drives the detection pipeline", which is a
11590
+ * different question from "what may wake a sleeping stream". A camera can do
11591
+ * the first and not be trusted with the second, and the operator picks per
11592
+ * camera. Two questions, two authorities.
11593
+ *
11594
+ * ## Availability is not permission
11595
+ *
11596
+ * `listSignals` says what the device CAN emit. Whether a given signal actually
11597
+ * wakes the stream is the operator's per-camera choice, held by the broker
11598
+ * alongside the cooldown — see the stream-broker cap's wake settings. A
11599
+ * provider declaring a signal is not a provider enabling it.
11600
+ */
11601
+ /** One signal a device can emit. */
11602
+ var StreamSignalSchema = object({
11603
+ /** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
11604
+ code: string$2().min(1),
11605
+ /** What an operator reads in the picker. The provider's own wording. */
11606
+ label: string$2().min(1),
11607
+ /**
11608
+ * Whether the provider recommends this signal ON when a camera is first set
11609
+ * up. A provider knows which of its signals are cheap and reliable; an
11610
+ * operator should not have to discover that by trial. Reolink recommends
11611
+ * both of its own.
11612
+ */
11613
+ recommended: boolean()
11614
+ });
11615
+ var StreamSignalsStatusSchema = object({
11616
+ signals: array(StreamSignalSchema),
11617
+ lastFetchedAt: number()
11618
+ });
11619
+ var streamSignalsCapability = {
11620
+ name: "stream-signals",
11621
+ scope: "device",
11622
+ deviceNative: true,
11623
+ mode: "singleton",
11624
+ deviceTypes: Object.values(DeviceType),
11625
+ runtimeState: StreamSignalsStatusSchema,
11626
+ methods: {
11627
+ /**
11628
+ * What this device can emit. Empty is a valid and common answer — most
11629
+ * cameras have nothing to offer here, and an empty list is what makes the
11630
+ * broker's picker show nothing rather than a false choice.
11631
+ */
11632
+ listSignals: method(_void(), array(StreamSignalSchema).readonly()) }
11633
+ };
11538
11634
  /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
11539
11635
  var StreamFormatSchema = _enum([
11540
11636
  "webrtc",
@@ -11931,6 +12027,22 @@ var EgressTranscodeSchema = object({
11931
12027
  camStreamId: string$2().nullable()
11932
12028
  });
11933
12029
  method(object({
12030
+ deviceId: number().int().nonnegative(),
12031
+ /** The provider's signal code. */
12032
+ code: string$2().min(1),
12033
+ /** Ms epoch. Absent ⇒ now. */
12034
+ at: number().optional()
12035
+ }), object({
12036
+ /** Whether the broker acted on it, and if not, why. */
12037
+ accepted: boolean(),
12038
+ reason: _enum([
12039
+ "woke",
12040
+ "hold-extended",
12041
+ "not-enabled",
12042
+ "no-consumer",
12043
+ "unknown-code"
12044
+ ])
12045
+ }), { kind: "mutation" }), method(object({
11934
12046
  deviceId: number().int().nonnegative(),
11935
12047
  camStreamId: string$2().min(1),
11936
12048
  kind: CamStreamKindSchema,
@@ -12185,6 +12297,16 @@ var PickStreamRequirementsSchema = object({
12185
12297
  acceptCodecs: array(StreamCodecSchema).readonly().optional(),
12186
12298
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
12187
12299
  minHeight: number().int().positive().optional(),
12300
+ /**
12301
+ * Maximum vertical resolution. Streams TALLER than this are dropped.
12302
+ *
12303
+ * A consumer can have a ceiling as real as its floor: Alexa documents
12304
+ * 480p to 1080p, and a 4K stream is as unusable to an Echo as a 360p one.
12305
+ * Without this the ceiling had to be re-implemented by every caller — and
12306
+ * one caller implementing it privately is how a second scoring authority
12307
+ * gets born.
12308
+ */
12309
+ maxHeight: number().int().positive().optional(),
12188
12310
  /** Minimum horizontal resolution. */
12189
12311
  minWidth: number().int().positive().optional(),
12190
12312
  /**
@@ -12201,7 +12323,22 @@ var PickStreamRequirementsSchema = object({
12201
12323
  * transcoded" guard: if the device is already serving the consumer's
12202
12324
  * codec end-to-end, there's nothing to optimise.
12203
12325
  */
12204
- requireSiblingCodec: array(StreamCodecSchema).readonly().optional()
12326
+ requireSiblingCodec: array(StreamCodecSchema).readonly().optional(),
12327
+ /**
12328
+ * Whether a stream the consumer CANNOT decode may still be picked, on the
12329
+ * understanding that it will be transcoded.
12330
+ *
12331
+ * Default `false` — today's behaviour, and the right one for a bypass
12332
+ * question ("is there a stream I can forward untouched?"). Set `true` to
12333
+ * ask the larger question: "what is the best stream for me, transcoding if
12334
+ * I must?" A stream that satisfies `acceptCodecs` always outranks one that
12335
+ * does not, so a passthrough is never lost to a transcode; the answer says
12336
+ * which it is in {@link PickedCamStreamSchema.transcodes}.
12337
+ *
12338
+ * This is what lets one picker serve both the bypass and the full source
12339
+ * choice, instead of a consumer scoring privately when the bypass misses.
12340
+ */
12341
+ allowTranscode: boolean().optional()
12205
12342
  }).readonly();
12206
12343
  var PickStreamPreferencesSchema = object({
12207
12344
  /**
@@ -12215,12 +12352,32 @@ var PickStreamPreferencesSchema = object({
12215
12352
  * picks the tallest stream; `'lowest'` picks the shortest (used by
12216
12353
  * memory-constrained consumers / Apple Home guest sessions).
12217
12354
  */
12218
- resolutionPreference: _enum(["highest", "lowest"]).optional()
12355
+ resolutionPreference: _enum(["highest", "lowest"]).optional(),
12356
+ /**
12357
+ * The height the consumer actually wants to DELIVER.
12358
+ *
12359
+ * Not a constraint — an ordering. With it set, the SMALLEST stream at or
12360
+ * above the target wins, and only if nothing reaches it does the tallest
12361
+ * take over. Pulling 1296 lines to draw 720 on a 1280x800 Echo panel costs
12362
+ * a decode and buys nothing, and `resolutionPreference: 'highest'` cannot
12363
+ * express that: it says "as big as possible", which is a different wish.
12364
+ *
12365
+ * Ignored when absent, so every existing caller keeps its ordering.
12366
+ */
12367
+ targetHeight: number().int().positive().optional()
12219
12368
  }).readonly();
12220
12369
  var PickedCamStreamSchema = object({
12221
12370
  camStreamId: string$2(),
12222
12371
  codec: string$2().optional(),
12223
12372
  resolution: CamStreamResolutionSchema.optional(),
12373
+ /**
12374
+ * Whether serving this stream requires a decode + re-encode.
12375
+ *
12376
+ * `false` is a stream the consumer can take as it stands. Only ever `true`
12377
+ * when the caller asked for it with `allowTranscode`, so a caller that did
12378
+ * not ask cannot be handed a cost it never agreed to pay.
12379
+ */
12380
+ transcodes: boolean(),
12224
12381
  /** One-line explanation of why this stream won — for logs / debug UI. */
12225
12382
  reason: string$2()
12226
12383
  });
@@ -16754,6 +16911,8 @@ var NcSystemEventKindSchema = _enum([
16754
16911
  "device-enabled",
16755
16912
  "device-battery-low",
16756
16913
  "device-battery-normal",
16914
+ "device-consumable-low",
16915
+ "device-consumable-normal",
16757
16916
  "stream-online",
16758
16917
  "stream-offline",
16759
16918
  "node-online",
@@ -16772,6 +16931,7 @@ var NcSystemEventKindSchema = _enum([
16772
16931
  "addon-updated",
16773
16932
  "server-updated",
16774
16933
  "export-completed",
16934
+ "backup-completed",
16775
16935
  "camera-online",
16776
16936
  "camera-offline",
16777
16937
  "camera-disabled",
@@ -20208,13 +20368,109 @@ var SummariesQueryInput = object({
20208
20368
  deviceIds: array(number()).min(1),
20209
20369
  since: number().optional(),
20210
20370
  until: number().optional(),
20211
- limit: number().int().min(1).max(200).default(50)
20371
+ limit: number().int().min(1).max(200).default(50),
20372
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
20373
+ * meaning of "dog" on every summary surface. */
20374
+ classes: array(string$2().min(1).max(60)).max(40).optional()
20212
20375
  });
20213
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20214
20376
  var SummaryDetailSchema = object({
20215
20377
  summary: SummarySchema,
20216
- /** Member track ids, in absorption order. */
20217
- trackIds: array(string$2()).readonly()
20378
+ /** Members in absorption order, oldest join first. */
20379
+ members: array(object({
20380
+ trackId: string$2(),
20381
+ /**
20382
+ * The track's class at the time it JOINED, or `null` when it was unresolved
20383
+ * and the track no longer exists to fill it.
20384
+ *
20385
+ * The join, not the latest opinion: class voting can re-decide a track's
20386
+ * class afterwards, and a group's composition is the history of who came
20387
+ * together. A consumer counts these to badge a thumbnail — "two people and a
20388
+ * dog" — rather than being handed a second, denormalised tally that would
20389
+ * drift from the members it claims to describe.
20390
+ */
20391
+ className: string$2().nullable(),
20392
+ /** `primary` is the one member that carries the group's identity. */
20393
+ role: _enum([
20394
+ "primary",
20395
+ "sibling",
20396
+ "child"
20397
+ ]).nullable(),
20398
+ joinedAt: number(),
20399
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
20400
+ * at read time. Absent when nothing resolved, never an empty string. */
20401
+ label: string$2().optional(),
20402
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
20403
+ * recognised nobody, and absent for a member whose track has been pruned —
20404
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
20405
+ subLabel: string$2().optional()
20406
+ })).readonly()
20407
+ });
20408
+ /**
20409
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
20410
+ *
20411
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
20412
+ * we extend when we notice we need more". Enough to draw the card ("two people
20413
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
20414
+ * are on {@link getSummary}: shipping every member of every group so a client
20415
+ * can count them turns a page of two hundred groups into a thousand records to
20416
+ * draw two hundred badges.
20417
+ */
20418
+ var SummaryListItemSchema = object({
20419
+ summary: SummarySchema,
20420
+ /** Class counts, most numerous first; `className: null` is a member whose
20421
+ * class never resolved, counted so the counts add up to `memberCount`. */
20422
+ classes: array(object({
20423
+ className: string$2().nullable(),
20424
+ count: number().int().positive()
20425
+ })).readonly()
20426
+ });
20427
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
20428
+ /**
20429
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
20430
+ *
20431
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
20432
+ * summaries feed that could only answer "the last N for these cameras"
20433
+ * (`listSummaries`) would make groups available in the timeline and nowhere
20434
+ * else. Two feeds with different ordering contracts would be two ideas of what
20435
+ * "newest" means, on one screen.
20436
+ */
20437
+ var RecentSummariesQueryInput = object({
20438
+ /** Devices to merge. An empty array yields an empty page. */
20439
+ deviceIds: array(number()),
20440
+ /** Window lower bound on `lastActiveAt` (inclusive). */
20441
+ since: number().optional(),
20442
+ /** Window upper bound on `lastActiveAt` (inclusive). */
20443
+ until: number().optional(),
20444
+ limit: number().int().min(1).max(500).default(100),
20445
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
20446
+ cursor: string$2().optional(),
20447
+ /**
20448
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
20449
+ * deliberately the same name: the events filter tree emits one list of ticked
20450
+ * taxonomy nodes, and a summaries feed that called it something else would
20451
+ * make the operator's one filter component produce two shapes.
20452
+ *
20453
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
20454
+ * me the arrivals with a dog" has to return the group of four people who had
20455
+ * one with them; a filter on the group's own fields could only ever answer
20456
+ * about its primary.
20457
+ *
20458
+ * Matched against all THREE tiers of a member — the class (`animal`), the
20459
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
20460
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
20461
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
20462
+ * that read only the class would answer "animal" and never "dog".
20463
+ *
20464
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
20465
+ */
20466
+ classes: array(string$2().min(1).max(60)).max(40).optional()
20467
+ });
20468
+ var RecentSummariesPageSchema = object({
20469
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
20470
+ * {@link listSummaries} — one list shape, so a card renders the same
20471
+ * whichever feed drew it. */
20472
+ summaries: array(SummaryListItemSchema).readonly(),
20473
+ nextCursor: string$2().nullable()
20218
20474
  });
20219
20475
  var RecentTracksPageSchema = object({
20220
20476
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -20628,7 +20884,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20628
20884
  classes: array(string$2()).optional(),
20629
20885
  /** See {@link ExcludeSourcesDoc}. */
20630
20886
  excludeSources: array(TrackSourceSchema).optional()
20631
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string$2() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20887
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string$2() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20632
20888
  kind: "mutation",
20633
20889
  auth: "admin"
20634
20890
  }), 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({
@@ -23957,10 +24213,28 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
23957
24213
  }), boolean()), method(object({
23958
24214
  deviceId: number().int().nonnegative(),
23959
24215
  sessionId: string$2()
23960
- }), object({ pendingRenegotiation: object({
23961
- target: WebrtcStreamTargetSchema,
23962
- epoch: number()
23963
- }).nullable() }));
24216
+ }), object({
24217
+ pendingRenegotiation: object({
24218
+ target: WebrtcStreamTargetSchema,
24219
+ epoch: number()
24220
+ }).nullable(),
24221
+ /**
24222
+ * Whether the session still EXISTS.
24223
+ *
24224
+ * A consumer that holds a resource for the life of a session needs to
24225
+ * be able to ask, because a session does not always end the way it
24226
+ * began. Measured on this hub 2026-09-13: an Echo that got stuck never
24227
+ * sent `SessionDisconnected`, so the Alexa exporter's
24228
+ * `releaseEgressTranscode` never ran, and a 2304x1296 HEVC to 720p
24229
+ * H.264 transcode kept running for NOBODY for more than twenty
24230
+ * minutes. The WebRTC session had logged `WebRTC session closed`
24231
+ * minutes earlier — the broker knew; the holder had no way to ask.
24232
+ *
24233
+ * `false` for a session id the provider has never heard of, which is
24234
+ * the same answer as "it ended": either way nothing is holding it up.
24235
+ */
24236
+ alive: boolean()
24237
+ }));
23964
24238
  object({
23965
24239
  /** All accessory children of the parent. */
23966
24240
  childDeviceIds: array(number()).readonly(),
@@ -32398,7 +32672,29 @@ var LoggingSettingsPatchSchema = object({
32398
32672
  * disarmed the channels it did not mention would make the Levels page and
32399
32673
  * the Diagnostics page fight over the same value.
32400
32674
  */
32401
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
32675
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
32676
+ /**
32677
+ * How many days the analytics OPS LOG is kept — the audit trail of what
32678
+ * retention actually deleted.
32679
+ *
32680
+ * It lives HERE, on the logging document, and not on the analytics addon's
32681
+ * settings form, on the operator's instruction: every other log-ish window in
32682
+ * this system is configured through this one document, and a retention audit
32683
+ * configured somewhere else is a second place to look when the question is
32684
+ * "what happened to my data".
32685
+ *
32686
+ * That placement has a cost worth stating: this document ships in the
32687
+ * framework closure, so this knob needs a published `@camstack/server` while
32688
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
32689
+ *
32690
+ * `retention-model.ts` argued this one must NOT follow a device window,
32691
+ * because the audit trail is the history of the SWEEP and not of the camera.
32692
+ * That argument stands and is untouched — what changes is that 30 days stops
32693
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
32694
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
32695
+ * state in which the next 9.5 GiB leak cannot be explained.
32696
+ */
32697
+ opsLogRetentionDays: number().int().min(0).max(3650).optional()
32402
32698
  });
32403
32699
  /**
32404
32700
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -32471,6 +32767,14 @@ var LoggingSettingsStateSchema = object({
32471
32767
  channels: array(LogChannelDescriptorSchema).readonly(),
32472
32768
  /** The channels ARMED right now, each with its deadline. */
32473
32769
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
32770
+ /**
32771
+ * The ops-log window in days, as it is in force. `0` = kept forever.
32772
+ *
32773
+ * Reported rather than left to the caller's memory of what it wrote: this is
32774
+ * the value the analytics sweep will actually use, and an operator asking how
32775
+ * long their audit trail lives should not have to infer it from a default.
32776
+ */
32777
+ opsLogRetentionDays: number().int().min(0),
32474
32778
  persisted: boolean()
32475
32779
  });
32476
32780
  method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string$2(), unknown()), _null(), {
@@ -33866,6 +34170,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
33866
34170
  smoke: smokeCapability,
33867
34171
  streamCatalog: streamCatalogCapability,
33868
34172
  streamParams: streamParamsCapability,
34173
+ streamSignals: streamSignalsCapability,
33869
34174
  switch: switchCapability,
33870
34175
  tamper: tamperCapability,
33871
34176
  temperatureSensor: temperatureSensorCapability,
@@ -38471,6 +38776,12 @@ Object.freeze({
38471
38776
  addonId: null,
38472
38777
  access: "view"
38473
38778
  },
38779
+ "pipelineAnalytics.listRecentSummaries": {
38780
+ capName: "pipeline-analytics",
38781
+ capScope: "device",
38782
+ addonId: null,
38783
+ access: "view"
38784
+ },
38474
38785
  "pipelineAnalytics.listRecentTracks": {
38475
38786
  capName: "pipeline-analytics",
38476
38787
  capScope: "device",
@@ -40433,6 +40744,12 @@ Object.freeze({
40433
40744
  addonId: null,
40434
40745
  access: "create"
40435
40746
  },
40747
+ "streamBroker.reportStreamSignal": {
40748
+ capName: "stream-broker",
40749
+ capScope: "system",
40750
+ addonId: null,
40751
+ access: "create"
40752
+ },
40436
40753
  "streamBroker.restartProfile": {
40437
40754
  capName: "stream-broker",
40438
40755
  capScope: "system",
@@ -40517,6 +40834,12 @@ Object.freeze({
40517
40834
  addonId: null,
40518
40835
  access: "create"
40519
40836
  },
40837
+ "streamSignals.listSignals": {
40838
+ capName: "stream-signals",
40839
+ capScope: "device",
40840
+ addonId: null,
40841
+ access: "view"
40842
+ },
40520
40843
  "switch.setState": {
40521
40844
  capName: "switch",
40522
40845
  capScope: "device",
@@ -42298,6 +42621,11 @@ Object.freeze({
42298
42621
  form: "single",
42299
42622
  optional: true
42300
42623
  }],
42624
+ "pipelineAnalytics.listRecentSummaries": [{
42625
+ name: "deviceIds",
42626
+ form: "array",
42627
+ optional: false
42628
+ }],
42301
42629
  "pipelineAnalytics.listRecentTracks": [{
42302
42630
  name: "deviceIds",
42303
42631
  form: "array",
@@ -42884,6 +43212,11 @@ Object.freeze({
42884
43212
  form: "single",
42885
43213
  optional: false
42886
43214
  }],
43215
+ "streamBroker.reportStreamSignal": [{
43216
+ name: "deviceId",
43217
+ form: "single",
43218
+ optional: false
43219
+ }],
42887
43220
  "streamBroker.restartProfile": [{
42888
43221
  name: "deviceId",
42889
43222
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-matter-broker",
3
- "version": "0.2.99",
3
+ "version": "0.2.101",
4
4
  "description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
5
5
  "keywords": [
6
6
  "camstack",