@camstack/addon-post-analysis 1.2.49 → 1.2.50

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.
@@ -6460,7 +6460,20 @@ var BrokerStatsSchema = object({
6460
6460
  sampleRate: number(),
6461
6461
  channels: number(),
6462
6462
  supported: boolean()
6463
- }).nullable().optional()
6463
+ }).nullable().optional(),
6464
+ /**
6465
+ * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
6466
+ * distributing none of the device's audio, on live or recording.
6467
+ *
6468
+ * Present so a silent camera can be told apart from a broken one on the
6469
+ * stream panel itself, without cross-referencing the switch group: a
6470
+ * broker holding an `audio` track descriptor while `audioMuted` is true is
6471
+ * working exactly as asked. `audioMutedDropped` counts the audio units
6472
+ * thrown away since the current dial — it is how you confirm from stats
6473
+ * alone that the mute is on the packet path and not merely persisted.
6474
+ */
6475
+ audioMuted: boolean().optional(),
6476
+ audioMutedDropped: number().optional()
6464
6477
  });
6465
6478
  /**
6466
6479
  * Exporter-facing "profile restream" entry. Returned by
@@ -7291,6 +7304,19 @@ object({
7291
7304
  * | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
7292
7305
  * | `privacy-mask` | `privacyMask.setMask({ enabled })` → the CAMERA | the camera blanks the masked regions itself; every stream and recording carries the black boxes |
7293
7306
  * | `device-audio` | `privacyMask.setAudioEnabled` → the CAMERA | the camera stops encoding an audio track at all; every consumer sees silent video |
7307
+ * | `broker-audio` | `streamBroker.setDeviceAudioMute` → `DeviceOverride.audioMuted` | `StreamBroker.setAudioMuted` drops the audio plane at the source: no `type:'audio'` packet leaves `fanOutEncoded`, no RTP reaches the restreamer, and the restreamer serves the video-only SDP |
7308
+ *
7309
+ * ## `device-audio` and `broker-audio` are two functions, not two knobs
7310
+ *
7311
+ * They look adjacent and they are not the same control ([D83](../../../../docs/decisions/adr-0083.md)):
7312
+ * `device-audio` writes the CAMERA, so it is hardware privacy — the microphone
7313
+ * genuinely stops, it survives CamStack entirely, and it costs a multi-second
7314
+ * encoder restart on every flip. `broker-audio` writes THIS server, so it is
7315
+ * instant, vendor-independent and reversible without touching the camera, and
7316
+ * a camera that ignores or lacks the ISAPI/Reolink control is still silenced.
7317
+ * D62 forbids a second switch that *disagrees* with the first; these two
7318
+ * cannot disagree, because neither reads the other's store — the camera holds
7319
+ * one, the broker holds the other, and each reports its own fact.
7294
7320
  *
7295
7321
  * ## The two switches whose authority is not on this server
7296
7322
  *
@@ -7352,6 +7378,7 @@ var CameraSwitchIdSchema = _enum([
7352
7378
  "object-detection",
7353
7379
  "privacy-mask",
7354
7380
  "device-audio",
7381
+ "broker-audio",
7355
7382
  "audio-analysis",
7356
7383
  "recording",
7357
7384
  "notifications"
@@ -7377,7 +7404,8 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
7377
7404
  object({
7378
7405
  kind: literal("camera-mask"),
7379
7406
  capName: string()
7380
- })
7407
+ }),
7408
+ object({ kind: literal("broker-audio-mute") })
7381
7409
  ]);
7382
7410
  /**
7383
7411
  * Why a switch is not offered for this camera. Rendered instead of the
@@ -9599,7 +9627,25 @@ method(object({
9599
9627
  }), _void(), {
9600
9628
  kind: "mutation",
9601
9629
  auth: "admin"
9602
- }), method(object({ brokerId: string() }), boolean()), object({
9630
+ }), method(object({ brokerId: string() }), boolean()), method(object({ deviceId: number().int() }), object({
9631
+ muted: boolean(),
9632
+ /**
9633
+ * How many live non-derived brokers currently hold the mute. Purely
9634
+ * diagnostic: `muted` is the policy and is authoritative on its own
9635
+ * (it applies to brokers that do not exist yet), while this says
9636
+ * whether anything is presently being silenced.
9637
+ */
9638
+ appliedBrokers: number().int().nonnegative()
9639
+ })), method(object({
9640
+ deviceId: number().int(),
9641
+ muted: boolean()
9642
+ }), object({
9643
+ muted: boolean(),
9644
+ appliedBrokers: number().int().nonnegative()
9645
+ }), {
9646
+ kind: "mutation",
9647
+ auth: "admin"
9648
+ }), object({
9603
9649
  deviceId: number().int().nonnegative(),
9604
9650
  camStreamId: string(),
9605
9651
  profile: CamProfileSchema
@@ -15555,6 +15601,30 @@ var TrackSourceSchema = _enum([
15555
15601
  "audio"
15556
15602
  ]);
15557
15603
  /**
15604
+ * Where a track sits in the RETRAIN lifecycle (D81).
15605
+ *
15606
+ * - `none` — never marked, or un-marked. Evictable.
15607
+ * - `staging` — the operator wants this track as training material and has not
15608
+ * finished with it. **This is the only state retention holds**: the track and
15609
+ * everything it owns (object events, crops, keyframes, CLIP vector) survive
15610
+ * the device's age window.
15611
+ * - `trained` — the retrain page has taken what it needed. The frames it chose
15612
+ * were COPIED into the retrain dataset at selection time, so the dataset no
15613
+ * longer depends on the track's media and the track becomes EVICTABLE again.
15614
+ * Terminal for the plain `markForTrain` toggle: returning it to `staging` is
15615
+ * a deliberate action of the retrain page, not a side effect of a checkbox.
15616
+ *
15617
+ * There is no `null`. The state is stored `TEXT NOT NULL DEFAULT 'none'` because
15618
+ * the store's filter language has only positive equality and `whereIn` — no
15619
+ * negation, no IS NULL — so a NULL would be unselectable by ANY predicate and
15620
+ * would make the entire pre-column history immortal in one deploy.
15621
+ */
15622
+ var RetrainStatusSchema = _enum([
15623
+ "none",
15624
+ "staging",
15625
+ "trained"
15626
+ ]);
15627
+ /**
15558
15628
  * Per-track OPERATOR flags — set by hand from the admin UI or the viewer, never
15559
15629
  * by the pipeline. Spread into `TrackSchema` and `KeyEventSchema` from one place
15560
15630
  * so the two surfaces cannot drift.
@@ -15564,18 +15634,31 @@ var TrackSourceSchema = _enum([
15564
15634
  * columns existed read as absent, and a consumer that needs a boolean should say
15565
15635
  * `flag === true`, not `flag !== false`.
15566
15636
  *
15567
- * What the flags DO is deliberately UNDEFINED at the time of writing: they are
15568
- * operator curation, and the behaviour they drive will be specified separately.
15569
- * In particular a `markForTrain` track is NOT pinned against retention — see
15570
- * `docs/decisions/adr-0059.md` for why that is a store-level change, not a flag.
15637
+ * `markForTrain` is the WIRE FACE of {@link RetrainStatusSchema}, not a column:
15638
+ * it is exactly `retrainStatus === 'staging'`, in both directions. Writing
15639
+ * `true` moves `none → staging`, writing `false` moves `staging → none`, and a
15640
+ * `trained` track reports `false` while refusing both writes. The boolean is
15641
+ * kept because three surfaces drive a toggle off it; anything that needs to tell
15642
+ * "never marked" from "already trained" must read `retrainStatus`.
15643
+ *
15644
+ * `debug` does NOT pin; it is attention, not durability.
15571
15645
  */
15572
15646
  var TrackFlagFields = {
15573
- /** Operator marked this track as training material. */
15647
+ /** Operator marked this track as training material — i.e. `retrainStatus` is
15648
+ * `'staging'`. */
15574
15649
  markForTrain: boolean().optional(),
15575
15650
  /** Operator marked this track for diagnostic attention. */
15576
15651
  debug: boolean().optional()
15577
15652
  };
15578
15653
  /**
15654
+ * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
15655
+ * Deliberately NOT part of {@link TrackFlagFields}: that group also builds the
15656
+ * write patch, and the status is not something the toggle sets — it is what the
15657
+ * toggle's boolean is derived from. Absent on an in-RAM track never touched;
15658
+ * always present on a persisted row (the column default materialises `'none'`).
15659
+ */
15660
+ var TrackRetrainFields = { retrainStatus: RetrainStatusSchema.optional() };
15661
+ /**
15579
15662
  * The write half: a PARTIAL patch. An omitted key is left untouched, so setting
15580
15663
  * one flag can never clear the other — the toggles are independent and are
15581
15664
  * driven from three surfaces that do not know about each other.
@@ -15589,7 +15672,32 @@ var TrackFlagsPatchSchema = object(TrackFlagFields);
15589
15672
  var TrackFlagsSchema = object({
15590
15673
  trackId: string(),
15591
15674
  markForTrain: boolean(),
15592
- debug: boolean()
15675
+ debug: boolean(),
15676
+ /** The lifecycle state the boolean was derived from. Required here (unlike on
15677
+ * a track row) because this shape is only ever produced by the write body,
15678
+ * which always knows it — and a surface that has just written needs to render
15679
+ * `trained` without a re-fetch. */
15680
+ retrainStatus: RetrainStatusSchema
15681
+ });
15682
+ /** Per-camera slice of a training-export estimate. */
15683
+ var TrainingExportDeviceTotalsSchema = object({
15684
+ deviceId: number(),
15685
+ tracks: number().int(),
15686
+ files: number().int(),
15687
+ bytes: number().int()
15688
+ });
15689
+ /**
15690
+ * What a training export WOULD contain. Computed from media index rows only —
15691
+ * no blob is read to produce this.
15692
+ */
15693
+ var TrainingExportSummarySchema = object({
15694
+ generatedAt: number(),
15695
+ trackCount: number().int(),
15696
+ fileCount: number().int(),
15697
+ byteCount: number().int(),
15698
+ /** More marked tracks exist than a single pass carries. */
15699
+ truncated: boolean(),
15700
+ devices: array(TrainingExportDeviceTotalsSchema).readonly()
15593
15701
  });
15594
15702
  var TrackSchema = object({
15595
15703
  trackId: string(),
@@ -15634,7 +15742,8 @@ var TrackSchema = object({
15634
15742
  * Populated from the persisted envelope columns on historical reads;
15635
15743
  * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
15636
15744
  envelope: TrackEnvelopeSchema.optional(),
15637
- ...TrackFlagFields
15745
+ ...TrackFlagFields,
15746
+ ...TrackRetrainFields
15638
15747
  });
15639
15748
  var BaseEventFields = {
15640
15749
  id: string(),
@@ -15848,7 +15957,8 @@ var KeyEventSchema = object({
15848
15957
  bestEventId: string(),
15849
15958
  /** Track lifetime in ms (lastSeen - firstSeen). */
15850
15959
  windowMs: number().optional(),
15851
- ...TrackFlagFields
15960
+ ...TrackFlagFields,
15961
+ ...TrackRetrainFields
15852
15962
  });
15853
15963
  object({
15854
15964
  trackId: string(),
@@ -16210,11 +16320,29 @@ var pipelineAnalyticsCapability = {
16210
16320
  *
16211
16321
  * `auth: 'protected'` (the default), NOT `admin`: the viewer is an
16212
16322
  * authenticated non-admin surface and two of the three call sites are
16213
- * there. Revisit if a flag ever gains an effect that costs storage —
16214
- * `deleteTracks` next door is admin for exactly that reason.
16323
+ * there. The note that used to sit here said to revisit this the day a flag
16324
+ * gained an effect that costs storage, and D81 is that day — `markForTrain`
16325
+ * now pins. It STAYS protected, and the reason is that the alternative
16326
+ * makes the feature pointless: marking a track is something you do while
16327
+ * looking at it, on the surface you were already looking at it on, and that
16328
+ * surface is the viewer. What the storage cost gets instead is a BOUND — a
16329
+ * per-device pin budget enforced in the body, refusing a new pin past the
16330
+ * limit while always allowing un-marking. `deleteTracks` next door is still
16331
+ * admin, because destroying evidence and preserving it are not symmetric.
16332
+ *
16333
+ * `markForTrain` writes the retrain LIFECYCLE, not a boolean column: `true`
16334
+ * is `none → staging`, `false` is `staging → none`. A track already
16335
+ * `trained` refuses BOTH — its frames are copies inside the retrain dataset
16336
+ * and re-staging it from a generic toggle is how the same material gets
16337
+ * annotated twice under two ground truths. Returning a trained track to
16338
+ * staging is a deliberate action of the retrain page, which is also the only
16339
+ * thing that produces `trained` in the first place.
16215
16340
  *
16216
- * Returns the RESOLVED state of both flags (absent → `false`) so a caller
16217
- * can drive its toggle without a re-fetch. Rejects an unknown track.
16341
+ * Returns the RESOLVED state of both flags (absent → `false`) plus the
16342
+ * `retrainStatus` they were derived from, so a caller can drive its toggle
16343
+ * — and render a `trained` badge — without a re-fetch. Rejects an unknown
16344
+ * track, a new staging mark on a device already holding its full budget, and
16345
+ * any `markForTrain` write against a trained track.
16218
16346
  */
16219
16347
  setTrackFlags: method(object({
16220
16348
  /** Log/audit scope only — the trackId is globally unique on its own. */
@@ -16278,6 +16406,42 @@ var pipelineAnalyticsCapability = {
16278
16406
  kind: "query",
16279
16407
  auth: "admin"
16280
16408
  }),
16409
+ /**
16410
+ * The CHEAP QUESTION, asked before any media moves: how big is the dataset
16411
+ * the marked (`markForTrain`) tracks would produce?
16412
+ *
16413
+ * Answered from media INDEX rows only — key, kind, size, timestamp — so it
16414
+ * costs ~2 KB of reads per track and no blob reads at all. The measured harm
16415
+ * behind D56 was a bulk pass that read and base64'd every blob a track owned
16416
+ * before deciding anything, taking hub-main to 82 s busy out of 120; an
16417
+ * export is that same I/O shape, so it inherits the same discipline: know
16418
+ * the size, then decide.
16419
+ *
16420
+ * `truncated` reports that more marked tracks exist than one pass carries.
16421
+ * Empty `deviceIds` ⇒ every device that has marked tracks.
16422
+ */
16423
+ getTrainingExportSummary: method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
16424
+ kind: "query",
16425
+ auth: "admin"
16426
+ }),
16427
+ /**
16428
+ * Where to download the dataset archive.
16429
+ *
16430
+ * The BYTES do not come back through this cap — they come from the returned
16431
+ * data-plane URL, which streams a tar built entry by entry. A multi-gigabyte
16432
+ * archive base64'd through a unary RPC envelope would be held whole in
16433
+ * memory twice on a hub this repo has already OOM'd once (D9/D18 are the
16434
+ * same lesson about frames). `getDownloadUrl` on `recordingExport` is the
16435
+ * precedent, and this follows it deliberately.
16436
+ *
16437
+ * The archive contains a `manifest.json` FIRST, then the stored media
16438
+ * VERBATIM under `tracks/<deviceId>/<trackId>/…`. No crop is derived and no
16439
+ * model is run: a training set's pixels must be the pixels the pipeline saw.
16440
+ */
16441
+ getTrainingExportUrl: method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
16442
+ kind: "query",
16443
+ auth: "admin"
16444
+ }),
16281
16445
  getEventMedia: method(object({
16282
16446
  eventId: string(),
16283
16447
  kind: MediaFileKindEnum.optional()
@@ -23110,6 +23274,173 @@ DeviceType.Camera, method(object({
23110
23274
  status: OsdStatusSchema
23111
23275
  });
23112
23276
  /**
23277
+ * `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
23278
+ *
23279
+ * The `osd` cap is the firmware contract: it probes a camera's overlay
23280
+ * SLOTS and writes literal text into one. It has no idea WHERE that text
23281
+ * comes from, and it must not — a driver that grew a "show the temperature
23282
+ * here" feature would grow it once per vendor.
23283
+ *
23284
+ * This cap owns the other half: a per-(camera, slot) BINDING that says
23285
+ * which value feeds the slot, how it is formatted, and under which
23286
+ * conditions it is shown at all. One addon renders every binding on every
23287
+ * camera, so a new source costs zero driver code.
23288
+ *
23289
+ * Three deliberate choices, each with a rejected alternative:
23290
+ *
23291
+ * 1. A source is `(capName, valuePath)` over the kernel's device
23292
+ * runtime-state mirror — NOT a closed enum of source kinds. Every
23293
+ * cap-keyed slice a device publishes is bindable the day the cap
23294
+ * ships. The rejected alternative (one enum member per source, with
23295
+ * a resolver branch each) is what makes "add the humidity too" a
23296
+ * code change.
23297
+ * 2. The display gate reuses `NcConditionsSchema` verbatim — the
23298
+ * notification centre's condition vocabulary — rather than a parallel
23299
+ * model. An operator who has learned one condition editor has learned
23300
+ * both.
23301
+ * 3. Because the renderer's facts are device STATE and not a detection
23302
+ * record, only a SUBSET of that vocabulary can be answered here.
23303
+ * `setSlotBinding` REJECTS the rest at write time (see
23304
+ * `getConditionSupport`). It does not accept-then-fail-closed: a
23305
+ * condition that can never be true renders a permanently blank
23306
+ * overlay, and a blank overlay looks exactly like a broken camera.
23307
+ */
23308
+ /** Where a slot's value comes from. */
23309
+ var OsdSourceSchema = discriminatedUnion("kind", [
23310
+ object({
23311
+ kind: literal("static"),
23312
+ text: string().max(64)
23313
+ }),
23314
+ object({
23315
+ kind: literal("clock"),
23316
+ /** Token pattern: `YYYY MM DD HH mm ss`. Everything else is literal. */
23317
+ pattern: string().min(1).max(32).default("HH:mm"),
23318
+ /** IANA zone. Omitted = the server's zone. */
23319
+ timezone: string().min(1).max(64).optional()
23320
+ }),
23321
+ object({
23322
+ kind: literal("device-state"),
23323
+ deviceId: number().int().optional(),
23324
+ capName: string().min(1).max(64),
23325
+ /** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
23326
+ valuePath: string().min(1).max(64)
23327
+ })
23328
+ ]);
23329
+ var OsdSlotBindingSchema = object({
23330
+ /** Off = the manager stops driving this slot. It does NOT clear it. */
23331
+ enabled: boolean().default(true),
23332
+ source: OsdSourceSchema,
23333
+ /** `${value}` and `${unit}` are substituted; every occurrence. */
23334
+ template: string().max(96).default("${value}"),
23335
+ /** Truncate with an ellipsis past this length. Absent = no limit. */
23336
+ maxCharacters: number().int().min(4).max(64).optional(),
23337
+ /**
23338
+ * Decimal places for a numeric value. `0` yields an integer — the
23339
+ * documented workaround for firmwares that reject `.` in overlay text.
23340
+ */
23341
+ maxDecimals: number().int().min(0).max(4).default(1),
23342
+ /** Appended via `${unit}`. The state mirror does not carry units. */
23343
+ unitLabel: string().max(8).optional(),
23344
+ /** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
23345
+ valueMap: record(string(), string()).optional(),
23346
+ /** Time windows in which the slot is shown. Absent = always. */
23347
+ schedule: NcScheduleSchema.optional(),
23348
+ /**
23349
+ * Display gate, in the notification centre's condition vocabulary.
23350
+ * Only the keys reported by `getConditionSupport` are accepted.
23351
+ */
23352
+ conditions: NcConditionsSchema.optional(),
23353
+ /** Rendered when the gate is closed or the value unreadable. Empty = hide. */
23354
+ fallbackText: string().max(64).default("")
23355
+ });
23356
+ /** One camera slot, as the operator sees it: firmware truth + our binding. */
23357
+ var OsdSlotViewSchema = object({
23358
+ slotId: string(),
23359
+ kind: OsdOverlayKindEnum,
23360
+ /** Firmware refuses text edits (a timestamp, the channel name). */
23361
+ readOnly: boolean(),
23362
+ cameraEnabled: boolean(),
23363
+ cameraText: string().optional(),
23364
+ binding: OsdSlotBindingSchema.nullable()
23365
+ });
23366
+ /**
23367
+ * What happened to one slot on one render pass. `unchanged` exists so the
23368
+ * operator can tell "we are driving this and the value is steady" from
23369
+ * "we never got there" — and so the loop can prove it is not rewriting
23370
+ * identical text to the camera every tick.
23371
+ */
23372
+ var OsdRenderOutcomeEnum = _enum([
23373
+ "written",
23374
+ "unchanged",
23375
+ "gated",
23376
+ "unreadable",
23377
+ "disabled",
23378
+ "unbound",
23379
+ "failed"
23380
+ ]);
23381
+ var OsdRenderResultSchema = object({
23382
+ slotId: string(),
23383
+ outcome: OsdRenderOutcomeEnum,
23384
+ /** The text the slot should carry. Empty = the slot is switched off. */
23385
+ text: string(),
23386
+ /** Why, whenever the outcome is not a plain write. Never silent. */
23387
+ reason: string().optional()
23388
+ });
23389
+ var OsdSourceValueTypeEnum = _enum([
23390
+ "number",
23391
+ "boolean",
23392
+ "string",
23393
+ "enum"
23394
+ ]);
23395
+ /**
23396
+ * One bindable value, derived from a cap's `runtimeState` schema — never
23397
+ * hand-listed. The editor renders from this, so a cap that ships a new
23398
+ * state field becomes bindable with no UI change.
23399
+ */
23400
+ var OsdSourceOptionSchema = object({
23401
+ deviceId: number().int(),
23402
+ deviceName: string(),
23403
+ capName: string(),
23404
+ valuePath: string(),
23405
+ label: string(),
23406
+ valueType: OsdSourceValueTypeEnum,
23407
+ /** Present for `enum`; the editor offers these as `valueMap` keys. */
23408
+ enumValues: array(string()).readonly().optional()
23409
+ });
23410
+ method(object({ deviceId: number().int() }), object({
23411
+ supported: boolean(),
23412
+ slots: array(OsdSlotViewSchema)
23413
+ }), { auth: "admin" }), method(object({ deviceId: number().int() }), object({ sources: array(OsdSourceOptionSchema) }), { auth: "admin" }), method(object({}), object({
23414
+ supported: array(string()),
23415
+ catalog: array(NcConditionDescriptorSchema)
23416
+ }), { auth: "admin" }), method(object({
23417
+ deviceId: number().int(),
23418
+ slotId: string().min(1),
23419
+ binding: OsdSlotBindingSchema
23420
+ }), object({
23421
+ slot: OsdSlotViewSchema,
23422
+ render: OsdRenderResultSchema
23423
+ }), {
23424
+ kind: "mutation",
23425
+ auth: "admin"
23426
+ }), method(object({
23427
+ deviceId: number().int(),
23428
+ slotId: string().min(1)
23429
+ }), object({ success: literal(true) }), {
23430
+ kind: "mutation",
23431
+ auth: "admin"
23432
+ }), method(object({
23433
+ deviceId: number().int(),
23434
+ slotId: string().min(1),
23435
+ binding: OsdSlotBindingSchema.optional()
23436
+ }), OsdRenderResultSchema, {
23437
+ kind: "mutation",
23438
+ auth: "admin"
23439
+ }), method(object({ deviceId: number().int() }), object({ results: array(OsdRenderResultSchema) }), {
23440
+ kind: "mutation",
23441
+ auth: "admin"
23442
+ });
23443
+ /**
23113
23444
  * Feeder connectivity / power status — mirrors the HA petkit device-status
23114
23445
  * enum: `normal` (online, mains), `offline` (not reaching PetKit cloud),
23115
23446
  * `on_batteries` (running on battery backup). `null` until first reported.
@@ -29610,6 +29941,48 @@ Object.freeze({
29610
29941
  addonId: null,
29611
29942
  access: "create"
29612
29943
  },
29944
+ "osdManager.clearSlotBinding": {
29945
+ capName: "osd-manager",
29946
+ capScope: "system",
29947
+ addonId: null,
29948
+ access: "delete"
29949
+ },
29950
+ "osdManager.getConditionSupport": {
29951
+ capName: "osd-manager",
29952
+ capScope: "system",
29953
+ addonId: null,
29954
+ access: "view"
29955
+ },
29956
+ "osdManager.getDeviceOsd": {
29957
+ capName: "osd-manager",
29958
+ capScope: "system",
29959
+ addonId: null,
29960
+ access: "view"
29961
+ },
29962
+ "osdManager.getSourceCatalog": {
29963
+ capName: "osd-manager",
29964
+ capScope: "system",
29965
+ addonId: null,
29966
+ access: "view"
29967
+ },
29968
+ "osdManager.previewSlot": {
29969
+ capName: "osd-manager",
29970
+ capScope: "system",
29971
+ addonId: null,
29972
+ access: "create"
29973
+ },
29974
+ "osdManager.renderDevice": {
29975
+ capName: "osd-manager",
29976
+ capScope: "system",
29977
+ addonId: null,
29978
+ access: "create"
29979
+ },
29980
+ "osdManager.setSlotBinding": {
29981
+ capName: "osd-manager",
29982
+ capScope: "system",
29983
+ addonId: null,
29984
+ access: "create"
29985
+ },
29613
29986
  "petFeeder.callPet": {
29614
29987
  capName: "pet-feeder",
29615
29988
  capScope: "device",
@@ -29772,6 +30145,18 @@ Object.freeze({
29772
30145
  addonId: null,
29773
30146
  access: "view"
29774
30147
  },
30148
+ "pipelineAnalytics.getTrainingExportSummary": {
30149
+ capName: "pipeline-analytics",
30150
+ capScope: "device",
30151
+ addonId: null,
30152
+ access: "view"
30153
+ },
30154
+ "pipelineAnalytics.getTrainingExportUrl": {
30155
+ capName: "pipeline-analytics",
30156
+ capScope: "device",
30157
+ addonId: null,
30158
+ access: "view"
30159
+ },
29775
30160
  "pipelineAnalytics.listEventKinds": {
29776
30161
  capName: "pipeline-analytics",
29777
30162
  capScope: "device",
@@ -31248,6 +31633,12 @@ Object.freeze({
31248
31633
  addonId: null,
31249
31634
  access: "view"
31250
31635
  },
31636
+ "streamBroker.getDeviceAudioMute": {
31637
+ capName: "stream-broker",
31638
+ capScope: "system",
31639
+ addonId: null,
31640
+ access: "view"
31641
+ },
31251
31642
  "streamBroker.getPreBufferInfo": {
31252
31643
  capName: "stream-broker",
31253
31644
  capScope: "system",
@@ -31368,6 +31759,12 @@ Object.freeze({
31368
31759
  addonId: null,
31369
31760
  access: "create"
31370
31761
  },
31762
+ "streamBroker.setDeviceAudioMute": {
31763
+ capName: "stream-broker",
31764
+ capScope: "system",
31765
+ addonId: null,
31766
+ access: "create"
31767
+ },
31371
31768
  "streamBroker.setPreBufferDuration": {
31372
31769
  capName: "stream-broker",
31373
31770
  capScope: "system",
@@ -32015,6 +32412,66 @@ Object.freeze({
32015
32412
  "smtp-provider": "email"
32016
32413
  });
32017
32414
  new Set(["devices", "classes"]);
32415
+ var WEEKDAY_TO_DAY = {
32416
+ Sun: 0,
32417
+ Mon: 1,
32418
+ Tue: 2,
32419
+ Wed: 3,
32420
+ Thu: 4,
32421
+ Fri: 5,
32422
+ Sat: 6
32423
+ };
32424
+ /** Resolve (weekday, minute-of-day) of `atMs` in the schedule's timezone.
32425
+ * An invalid/unknown IANA name falls back to the host timezone. */
32426
+ function localDayMinute(atMs, timezone) {
32427
+ const d = new Date(atMs);
32428
+ if (timezone !== void 0) try {
32429
+ const parts = new Intl.DateTimeFormat("en-US", {
32430
+ timeZone: timezone,
32431
+ weekday: "short",
32432
+ hour: "numeric",
32433
+ minute: "numeric",
32434
+ hourCycle: "h23"
32435
+ }).formatToParts(d);
32436
+ let weekday;
32437
+ let hour;
32438
+ let minute;
32439
+ for (const p of parts) if (p.type === "weekday") weekday = p.value;
32440
+ else if (p.type === "hour") hour = Number(p.value);
32441
+ else if (p.type === "minute") minute = Number(p.value);
32442
+ const day = weekday !== void 0 ? WEEKDAY_TO_DAY[weekday] : void 0;
32443
+ if (day !== void 0 && hour !== void 0 && minute !== void 0) return {
32444
+ day,
32445
+ minute: hour * 60 + minute
32446
+ };
32447
+ } catch {}
32448
+ return {
32449
+ day: d.getDay(),
32450
+ minute: d.getHours() * 60 + d.getMinutes()
32451
+ };
32452
+ }
32453
+ /**
32454
+ * Is the schedule active at `atMs`? No schedule = always active. Windows
32455
+ * are OR'd; a window with `startMinute > endMinute` crosses midnight (it
32456
+ * starts on a listed day and spills into the next). `invert` flips the
32457
+ * result (active OUTSIDE the windows).
32458
+ */
32459
+ function isScheduleActive(schedule, atMs) {
32460
+ if (schedule === void 0) return true;
32461
+ const { day, minute } = localDayMinute(atMs, schedule.timezone);
32462
+ const prevDay = (day + 6) % 7;
32463
+ let inside = false;
32464
+ for (const w of schedule.windows) if (w.startMinute <= w.endMinute) {
32465
+ if (w.days.includes(day) && minute >= w.startMinute && minute < w.endMinute) {
32466
+ inside = true;
32467
+ break;
32468
+ }
32469
+ } else if (w.days.includes(day) && minute >= w.startMinute || w.days.includes(prevDay) && minute < w.endMinute) {
32470
+ inside = true;
32471
+ break;
32472
+ }
32473
+ return schedule.invert === true ? !inside : inside;
32474
+ }
32018
32475
  /**
32019
32476
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
32020
32477
  *
@@ -32257,4 +32714,4 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
32257
32714
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
32258
32715
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
32259
32716
  //#endregion
32260
- export { object as $, hfModelUrl as A, errMsg as B, buildEventKindDescriptor as C, embeddingEncoderCapability as D, defineCustomActions as E, readDeviceStateFrom as F, isDeviceScopedCap as G, DeviceType as H, subKindsOf as I, _enum as J, nodePin as K, vectorDimFromBase64 as L, notificationRulesCapability as M, pipelineAnalyticsCapability as N, encodeVectorBase64 as O, plateGalleryCapability as P, number as Q, videoclipsCapability as R, audioMetricsCapability as S, customAction as T, createEvent as U, BaseAddon as V, hydrateSchema as W, boolean as X, array as Y, literal as Z, TimelapseRuleInputSchema as _, MACRO_LABELS as a, addonWidgetsSourceCapability as b, NcConditionDescriptorSchema as c, NcRuleSchema as d, record as et, NcSnoozeInputSchema as f, OpsLogEntrySchema as g, NcTaxonomySchema as h, EVENT_PAD_MS as i, kebabToCamel as j, faceGalleryCapability as k, NcRuleInputSchema as l, NcSnoozeSuppressedSchema as m, DEFAULT_EVENT_COLOR as n, unknown as nt, NC_CONDITION_CATALOG as o, NcSnoozeSchema as p, sleep as q, EVENT_KIND_BY_CAP as r, EventCategory as rt, NC_TAXONOMY as s, BaseDevice as t, string as tt, NcRulePatchSchema as u, TimelapseRuleSchema as v, cosineSimilarity as w, alarmPanelCapability as x, TrackSourceSchema as y, zoneAnalyticsCapability as z };
32717
+ export { literal as $, faceGalleryCapability as A, videoclipsCapability as B, audioMetricsCapability as C, defineCustomActions as D, customAction as E, pipelineAnalyticsCapability as F, createEvent as G, errMsg as H, plateGalleryCapability as I, nodePin as J, hydrateSchema as K, readDeviceStateFrom as L, isScheduleActive as M, kebabToCamel as N, embeddingEncoderCapability as O, notificationRulesCapability as P, boolean as Q, subKindsOf as R, alarmPanelCapability as S, cosineSimilarity as T, BaseAddon as U, zoneAnalyticsCapability as V, DeviceType as W, _enum as X, sleep as Y, array as Z, RetrainStatusSchema as _, MACRO_LABELS as a, EventCategory as at, TrackSourceSchema as b, NcConditionDescriptorSchema as c, NcRuleSchema as d, number as et, NcSnoozeInputSchema as f, OpsLogEntrySchema as g, NcTaxonomySchema as h, EVENT_PAD_MS as i, unknown as it, hfModelUrl as j, encodeVectorBase64 as k, NcRuleInputSchema as l, NcSnoozeSuppressedSchema as m, DEFAULT_EVENT_COLOR as n, record as nt, NC_CONDITION_CATALOG as o, NcSnoozeSchema as p, isDeviceScopedCap as q, EVENT_KIND_BY_CAP as r, string as rt, NC_TAXONOMY as s, BaseDevice as t, object as tt, NcRulePatchSchema as u, TimelapseRuleInputSchema as v, buildEventKindDescriptor as w, addonWidgetsSourceCapability as x, TimelapseRuleSchema as y, vectorDimFromBase64 as z };