@camstack/addon-ai 0.4.23 → 0.4.25

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 +572 -61
  2. package/dist/addon.mjs +572 -61
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -7732,6 +7732,66 @@ var OpsLogQueryInputSchema = object({
7732
7732
  /** Max rows returned, newest-first. */
7733
7733
  limit: number$1().int().min(1).max(1e3).optional()
7734
7734
  });
7735
+ var LabelDefinitionSchema = object({
7736
+ id: string(),
7737
+ name: string(),
7738
+ category: string().optional(),
7739
+ description: string().optional(),
7740
+ icon: string().optional()
7741
+ });
7742
+ /** Detection-macro targets a catalog `classMap` may resolve to. */
7743
+ var CLASS_MAP_MACRO_TARGETS = [
7744
+ "person",
7745
+ "vehicle",
7746
+ "animal",
7747
+ "package"
7748
+ ];
7749
+ /**
7750
+ * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
7751
+ * un operatore può selezionare.
7752
+ *
7753
+ * Sono le tre offerte dallo step `object-detection`
7754
+ * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
7755
+ * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
7756
+ * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
7757
+ * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
7758
+ * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
7759
+ * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
7760
+ *
7761
+ * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
7762
+ * dello step e una seconda volta come union `FirstLevelMacro`
7763
+ * (`types/detection.ts`); una terza copia per il trigger di registrazione
7764
+ * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
7765
+ * successiva.
7766
+ */
7767
+ var FIRST_LEVEL_MACRO_CLASSES = [
7768
+ "person",
7769
+ "vehicle",
7770
+ "animal"
7771
+ ];
7772
+ /**
7773
+ * Wire schema for a per-model CATALOG classMap override
7774
+ * (`ModelCatalogEntry.classMap` / `ModelConvertMetadata.classMap`) —
7775
+ * restricted to {@link CLASS_MAP_MACRO_TARGETS}, the only macros the
7776
+ * detection pipeline executor actually routes.
7777
+ *
7778
+ * This is deliberately a DIFFERENT, narrower shape than the general-purpose
7779
+ * `ClassMapDefinition` interface above (e.g. `IDetectionAddon.getClassMap()`
7780
+ * and the audio `YAMNET_TO_MACRO` catalog both use macro targets outside this
7781
+ * enum) — the two used to share the name `ClassMapDefinition`/
7782
+ * `ClassMapDefinitionSchema`, which made the schema-type-twin guard
7783
+ * (`scripts/check-schema-type-twins.ts`) flag them as a duplicated shape. They
7784
+ * are not: it is two different concepts colliding on a name. Keep this type
7785
+ * under its own name rather than reusing `ClassMapDefinition` — reusing it
7786
+ * would either narrow every `ClassMapDefinition` consumer to the four
7787
+ * detection macros (breaking `YAMNET_TO_MACRO`) or drop the validation this
7788
+ * schema exists for (see the "rejects a classMap whose target is not a
7789
+ * detection macro" test in `model-catalog-schema.test.ts`).
7790
+ */
7791
+ var DetectionCatalogClassMapSchema = object({
7792
+ mapping: record(string(), _enum(CLASS_MAP_MACRO_TARGETS)),
7793
+ preserveOriginal: boolean()
7794
+ });
7735
7795
  /**
7736
7796
  * Numeric day-of-week: 0 = Sunday … 6 = Saturday (matches `Date.getDay`).
7737
7797
  * Named `RecordingWeekday` to avoid collision with the string-union
@@ -7754,10 +7814,55 @@ var RecordingStorageModeSchema = _enum([
7754
7814
  "events",
7755
7815
  "continuous"
7756
7816
  ]);
7817
+ /**
7818
+ * Le macro classi che possono aprire una finestra di registrazione — le stesse
7819
+ * tre offerte dallo step `object-detection`, da UNA lista
7820
+ * ({@link FIRST_LEVEL_MACRO_CLASSES}).
7821
+ */
7822
+ var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
7823
+ /**
7824
+ * True quando `values` non ripete un elemento.
7825
+ *
7826
+ * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
7827
+ * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
7828
+ * conterebbe due volte le sue finestre in `segmentMissedByMs`.
7829
+ */
7830
+ var noDuplicates = (values) => new Set(values).size === values.length;
7757
7831
  /** Which detectors trigger an `events`-mode band. */
7758
7832
  var RecordingTriggersSchema = object({
7759
7833
  motion: boolean().optional(),
7760
- audioThresholdDbfs: number$1().optional()
7834
+ audioThresholdDbfs: number$1().optional(),
7835
+ /**
7836
+ * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
7837
+ * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
7838
+ * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
7839
+ * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
7840
+ *
7841
+ * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
7842
+ * quelle che hanno attraversato `enabledMacroClasses`, i
7843
+ * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
7844
+ * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
7845
+ * finestre — vedi `recorder/object-trigger.ts`.
7846
+ */
7847
+ objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
7848
+ /**
7849
+ * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
7850
+ * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
7851
+ * `objectClasses`.
7852
+ *
7853
+ * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
7854
+ * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
7855
+ * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
7856
+ * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
7857
+ * device (D12) — mai un elenco globale di cap.
7858
+ *
7859
+ * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
7860
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
7861
+ * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
7862
+ * il livello: un contatto trovato già aperto al riavvio del runner non fa
7863
+ * registrare.
7864
+ */
7865
+ sensorDeviceIds: array(number$1().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
7761
7866
  });
7762
7867
  /**
7763
7868
  * Mode of a single recording band — the recorder per-band vocabulary.
@@ -8209,41 +8314,6 @@ var DecoderSessionConfigSchema = object({
8209
8314
  */
8210
8315
  debug: boolean().optional()
8211
8316
  });
8212
- var LabelDefinitionSchema = object({
8213
- id: string(),
8214
- name: string(),
8215
- category: string().optional(),
8216
- description: string().optional(),
8217
- icon: string().optional()
8218
- });
8219
- /**
8220
- * Wire schema for a per-model CATALOG classMap override
8221
- * (`ModelCatalogEntry.classMap` / `ModelConvertMetadata.classMap`) —
8222
- * restricted to {@link CLASS_MAP_MACRO_TARGETS}, the only macros the
8223
- * detection pipeline executor actually routes.
8224
- *
8225
- * This is deliberately a DIFFERENT, narrower shape than the general-purpose
8226
- * `ClassMapDefinition` interface above (e.g. `IDetectionAddon.getClassMap()`
8227
- * and the audio `YAMNET_TO_MACRO` catalog both use macro targets outside this
8228
- * enum) — the two used to share the name `ClassMapDefinition`/
8229
- * `ClassMapDefinitionSchema`, which made the schema-type-twin guard
8230
- * (`scripts/check-schema-type-twins.ts`) flag them as a duplicated shape. They
8231
- * are not: it is two different concepts colliding on a name. Keep this type
8232
- * under its own name rather than reusing `ClassMapDefinition` — reusing it
8233
- * would either narrow every `ClassMapDefinition` consumer to the four
8234
- * detection macros (breaking `YAMNET_TO_MACRO`) or drop the validation this
8235
- * schema exists for (see the "rejects a classMap whose target is not a
8236
- * detection macro" test in `model-catalog-schema.test.ts`).
8237
- */
8238
- var DetectionCatalogClassMapSchema = object({
8239
- mapping: record(string(), _enum([
8240
- "person",
8241
- "vehicle",
8242
- "animal",
8243
- "package"
8244
- ])),
8245
- preserveOriginal: boolean()
8246
- });
8247
8317
  var MODEL_FORMATS = [
8248
8318
  "onnx",
8249
8319
  "coreml",
@@ -15780,7 +15850,23 @@ var NcHistoryEntrySchema = object({
15780
15850
  updatedAt: number$1(),
15781
15851
  /** Failure detail — present on a `dead` row. */
15782
15852
  error: string().optional(),
15783
- subject: NcHistorySubjectSchema
15853
+ subject: NcHistorySubjectSchema,
15854
+ /**
15855
+ * Ids of the artefacts (still, then gif, then clip) this row's successful
15856
+ * delivery indexed in the artefact library — a REFERENCE, never the bytes
15857
+ * (an artefact is often megabytes; this row is durable JSON rewritten on
15858
+ * every delivery attempt). Absent on a row still pending/dead, a row
15859
+ * delivered before this field shipped, or a wiring with no artefact index.
15860
+ *
15861
+ * Resolve one to a fetchable URL with `resolveArtifactUrl` — an id
15862
+ * outlives any one URL's TTL, so a caller mints a fresh link on demand
15863
+ * rather than trusting one frozen at delivery time. `resolveArtifactUrl`
15864
+ * also answers `null` for an id whose artefact has since expired past the
15865
+ * retained shelf's own age bound — the degrade a caller (the Home
15866
+ * Assistant export) must render as "no image right now", never as a
15867
+ * broken link.
15868
+ */
15869
+ artifactIds: array(string().min(1)).optional()
15784
15870
  });
15785
15871
  /**
15786
15872
  * Query filter for `getHistory` (spec §4.2). Every field is a narrowing
@@ -16007,7 +16093,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
16007
16093
  }), method(object({}), object({
16008
16094
  catalog: array(NcConditionDescriptorSchema),
16009
16095
  taxonomy: NcTaxonomySchema.optional()
16010
- })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
16096
+ })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({ artifactId: string().min(1) }), object({ url: string().nullable() }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
16011
16097
  kind: "mutation",
16012
16098
  caller: "required"
16013
16099
  }), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
@@ -21095,7 +21181,7 @@ var lifecycleJobSchema = object({
21095
21181
  * `useAddonsOnAddonLogs`, etc. flow through the same codegen pipeline
21096
21182
  * as every other cap.
21097
21183
  */
21098
- var LogLevelSchema$1 = _enum([
21184
+ var LogLevelSchema$2 = _enum([
21099
21185
  "debug",
21100
21186
  "info",
21101
21187
  "warn",
@@ -21302,7 +21388,7 @@ var CustomActionInputSchema = object({
21302
21388
  method(_void(), array(AddonListItemSchema).readonly()), method(object({
21303
21389
  addonId: string(),
21304
21390
  limit: number$1().min(1).max(500).default(100),
21305
- level: LogLevelSchema$1.optional()
21391
+ level: LogLevelSchema$2.optional()
21306
21392
  }), array(LogQueryEntrySchema)), method(_void(), array(InstalledPackageSchema).readonly()), method(object({
21307
21393
  packageName: string(),
21308
21394
  version: string().optional()
@@ -21400,7 +21486,7 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
21400
21486
  auth: "admin"
21401
21487
  }), method(object({
21402
21488
  addonId: string(),
21403
- level: LogLevelSchema$1.optional()
21489
+ level: LogLevelSchema$2.optional()
21404
21490
  }), LogStreamEntrySchema, { kind: "subscription" });
21405
21491
  object({
21406
21492
  /** Carbon dioxide concentration in ppm. */
@@ -22394,6 +22480,35 @@ var FaceFilterEnum = _enum([
22394
22480
  "identified",
22395
22481
  "all"
22396
22482
  ]);
22483
+ /**
22484
+ * What a `listRecentFaces` page is ORDERED BY.
22485
+ *
22486
+ * - `timestamp` — when the face was seen. The historical (and default) order.
22487
+ * - `suggestionConfidence` — {@link FaceInfo.suggestedMatchScore}, the peak
22488
+ * cosine of the face's SUGGESTED identity. This is the "review by certainty"
22489
+ * order: it puts the suggestions an operator can confirm with one tap at the
22490
+ * top, and it is the reason this enum exists — a client that ranked a capped
22491
+ * page client-side was ranking the newest N, never the most certain N.
22492
+ *
22493
+ * A row with NO suggestion (`suggestedMatchScore` absent — a legacy row, an
22494
+ * auto-assigned face, or a face below the suggestion band) has no certainty to
22495
+ * compare. Under `suggestionConfidence` it sorts **LAST, in BOTH directions**
22496
+ * — flipping the direction reorders the rows that HAVE a certainty and never
22497
+ * floods the page with the ones that do not. `addon-post-analysis`'s
22498
+ * `store/face-sort.ts` is the single implementation, tiebreaks newest-first
22499
+ * then by faceId, and is what makes this a total order instead of the
22500
+ * backend's NULL-collation accident.
22501
+ */
22502
+ var FaceSortFieldEnum = _enum(["timestamp", "suggestionConfidence"]);
22503
+ var FaceSortDirectionEnum = _enum(["asc", "desc"]);
22504
+ /** One suggested group of look-alike UNASSIGNED faces. Ids only — an embedding
22505
+ * never leaves the server. */
22506
+ var FaceClusterSchema = object({
22507
+ faceIds: array(string()).readonly(),
22508
+ representativeFaceId: string(),
22509
+ size: number$1().int(),
22510
+ cohesion: number$1()
22511
+ });
22397
22512
  var MediaFileLiteSchema$1 = object({
22398
22513
  key: string(),
22399
22514
  kind: string(),
@@ -22440,24 +22555,72 @@ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly
22440
22555
  kind: "mutation",
22441
22556
  auth: "admin"
22442
22557
  }), method(object({
22443
- /** Restrict to one camera. Absent keeps the cluster-wide gallery view. */
22558
+ /**
22559
+ * Restrict to ONE camera. Absent keeps the cluster-wide gallery view.
22560
+ *
22561
+ * The legacy single-camera form, kept verbatim for every caller that
22562
+ * already sends it. A caller that wants a SET sends {@link deviceIds}
22563
+ * instead — never both: `deviceIds` is the authority whenever it is
22564
+ * present, and this field is then ignored rather than unioned, so
22565
+ * there is exactly one answer to "which cameras did I ask for".
22566
+ */
22444
22567
  deviceId: number$1().int().optional(),
22568
+ /**
22569
+ * Restrict to a SET of cameras — the review UI's camera filter, which
22570
+ * until now had to fetch the cluster-wide page and drop rows in the
22571
+ * client (so the `limit` it asked for was spent on cameras it was
22572
+ * about to discard).
22573
+ *
22574
+ * An **empty array reads NOTHING** — `[]` is an empty page, never
22575
+ * "every camera". A request for no devices is a request, not an
22576
+ * omission; same contract as `deviceManager.listFleet` and
22577
+ * `pipelineAnalytics.listRecentTracks`.
22578
+ *
22579
+ * Absent (`undefined`) is the omission, and keeps the cluster-wide view.
22580
+ */
22581
+ deviceIds: array(number$1().int()).optional(),
22445
22582
  limit: number$1().int().positive().optional(),
22446
22583
  filter: FaceFilterEnum.optional(),
22447
22584
  /**
22448
- * Inline the base64 crop on every row. Default `true` — the existing
22449
- * behaviour, kept so no caller breaks.
22585
+ * Window lower bound on {@link FaceInfo.timestamp}, INCLUSIVE.
22586
+ * Absent means no lower bound.
22587
+ */
22588
+ since: number$1().int().optional(),
22589
+ /**
22590
+ * Window upper bound on {@link FaceInfo.timestamp}, INCLUSIVE.
22591
+ * Absent means no upper bound.
22592
+ */
22593
+ until: number$1().int().optional(),
22594
+ /**
22595
+ * Order the page by time or by suggestion certainty. Default
22596
+ * `'timestamp'` — the historical order, unchanged for every caller
22597
+ * that does not ask.
22450
22598
  *
22451
- * Set `false` once the caller renders {@link FaceInfo.cropUrl}: that
22452
- * drops ~2.87 MiB per 500-row page to a few KiB of metadata and lets
22453
- * the browser cache the images.
22599
+ * See {@link FaceSortFieldEnum} for what a row with no suggestion
22600
+ * does under `'suggestionConfidence'`.
22454
22601
  *
22455
- * **This is an INPUT field, so it does not reach the addon until the
22456
- * next train.** The hub router validates cap inputs against its own
22457
- * compiled Zod, which strips a key it does not know — verified today
22458
- * on the OUTPUT side, where an additive field DOES arrive immediately
22459
- * (`Track.hasFace`). Until the train ships, sending `false` is
22460
- * harmless and simply keeps the crops inline.
22602
+ * Cost note: `'timestamp'` is served by the `(deviceId, timestamp)`
22603
+ * index and stops reading as soon as `limit` rows have PASSED the
22604
+ * filter. `'suggestionConfidence'` cannot stop early — the most
22605
+ * certain row may be the oldest — so it walks the window. Narrow it
22606
+ * with {@link since} / {@link until}.
22607
+ */
22608
+ sortBy: FaceSortFieldEnum.optional(),
22609
+ /** Sort direction for {@link sortBy}. Default `'desc'`. */
22610
+ sortDirection: FaceSortDirectionEnum.optional(),
22611
+ /**
22612
+ * Inline the base64 crop on every row.
22613
+ *
22614
+ * Default `false` since the 2026-08-25 inversion — see
22615
+ * `include-crops-default.ts`, which is the ONE place that resolves
22616
+ * this for every gallery, and which records why the inline shape had
22617
+ * to become the one you ASK for (60 457 ms → UDS timeout at 500 rows).
22618
+ * The doc here used to still say `true`; it was wrong, and a leftover
22619
+ * that describes the old design reads as permission to rely on it.
22620
+ *
22621
+ * Nothing loses its image: the row carries {@link FaceInfo.cropUrl},
22622
+ * which the browser fetches off the `event-media` plane in parallel,
22623
+ * cached and ETagged.
22461
22624
  */
22462
22625
  includeCrops: boolean().optional()
22463
22626
  }).optional(), array(FaceInfoSchema).readonly()), method(object({
@@ -22493,13 +22656,39 @@ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly
22493
22656
  }), method(object({
22494
22657
  threshold: number$1().min(0).max(1).optional(),
22495
22658
  minClusterSize: number$1().int().min(2).optional(),
22496
- limit: number$1().int().positive().optional()
22497
- }).optional(), array(object({
22498
- faceIds: array(string()).readonly(),
22499
- representativeFaceId: string(),
22500
- size: number$1().int(),
22501
- cohesion: number$1()
22502
- })).readonly());
22659
+ /**
22660
+ * Cap on the number of CLUSTERS returned. Renamed from `limit`,
22661
+ * which read as though it bounded the work — it never did.
22662
+ *
22663
+ * Wins over {@link limit} when both are sent.
22664
+ */
22665
+ maxClusters: number$1().int().positive().optional(),
22666
+ /**
22667
+ * @deprecated Ambiguous name for {@link maxClusters} — it cuts the
22668
+ * RESULT, not the scan. Kept so existing callers keep working; send
22669
+ * `maxClusters` (and, if you care about cost, {@link maxFacesScanned}).
22670
+ */
22671
+ limit: number$1().int().positive().optional(),
22672
+ /**
22673
+ * Cap on the number of unassigned faces READ AND CLUSTERED — the
22674
+ * POOL, not the result.
22675
+ *
22676
+ * This is the knob {@link maxClusters} was mistaken for. Clustering
22677
+ * used to read every unassigned face on the hub no matter what the
22678
+ * caller asked for, because the only bound cut the finished clusters
22679
+ * afterwards; a UI showing a window of 100 paid for a scan of the
22680
+ * whole corpus, on an addon whose disk is under contention.
22681
+ *
22682
+ * The pool is the NEWEST matching faces first — the same order the
22683
+ * gallery shows — so a bound here shortens the horizon, it does not
22684
+ * sample it randomly.
22685
+ *
22686
+ * Default: 1 000 (`FACE_SWEEP_PAGE`, exactly one store page). Chosen
22687
+ * so the live corpus — 372 face rows — is unaffected while the
22688
+ * unbounded scan can never come back as the table grows.
22689
+ */
22690
+ maxFacesScanned: number$1().int().positive().optional()
22691
+ }).optional(), array(FaceClusterSchema).readonly());
22503
22692
  /**
22504
22693
  * Fan-control cap. Models HA `fan.*` entity-specific surfaces:
22505
22694
  * speed percentage, preset modes, ceiling-fan direction, and
@@ -25299,6 +25488,39 @@ var ReadGopBytesResultSchema = object({
25299
25488
  /** Media ms the returned fragment covers. */
25300
25489
  gopDurMs: number$1()
25301
25490
  });
25491
+ /**
25492
+ * A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
25493
+ * twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
25494
+ * replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
25495
+ * a replay needs several seconds of native pixels, not one frame.
25496
+ *
25497
+ * `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
25498
+ * is `false` when the returned bytes were cut short by the read's own safety
25499
+ * byte cap before covering `[fromMs, toMs)` — a truncation, reported, not a
25500
+ * silently shorter answer. `spans-multiple-segments` is a REFUSAL, not a
25501
+ * degradation: a window whose end falls past the covering segment would need
25502
+ * bytes stitched from a second segment file (its own `ftyp`+`moov`), which is
25503
+ * not one standalone-demuxable stream — the caller's answer is to request a
25504
+ * shorter window or one aligned to a single segment, not to receive spliced
25505
+ * bytes nothing has proven decodable.
25506
+ */
25507
+ var ReadWindowBytesResultSchema = discriminatedUnion("kind", [object({
25508
+ kind: literal("ok"),
25509
+ data: _instanceof(Uint8Array),
25510
+ /** Absolute epoch ms of the returned bytes' first sample — at or before
25511
+ * the requested `fromMs` (anchored on the nearest keyframe). */
25512
+ gopStartMs: number$1(),
25513
+ /** Media ms the returned bytes cover, from `gopStartMs`. */
25514
+ gopDurMs: number$1(),
25515
+ /** `false` ⇒ the safety byte cap cut the read short before it reached
25516
+ * the requested `toMs`; the caller got fewer frames than asked for. */
25517
+ reachesRequestedEnd: boolean()
25518
+ }), object({
25519
+ kind: literal("spans-multiple-segments"),
25520
+ /** Where the covering segment's own footage runs out — informational,
25521
+ * not a retry hint (retrying the same window would refuse again). */
25522
+ segmentEndMs: number$1()
25523
+ })]);
25302
25524
  method(object({
25303
25525
  deviceId: number$1(),
25304
25526
  fromMs: number$1(),
@@ -25349,6 +25571,15 @@ method(object({
25349
25571
  }), ReadGopBytesResultSchema, {
25350
25572
  kind: "query",
25351
25573
  auth: "admin"
25574
+ }), method(object({
25575
+ deviceId: number$1(),
25576
+ profile: string(),
25577
+ startMs: number$1(),
25578
+ fromMs: number$1(),
25579
+ toMs: number$1()
25580
+ }), ReadWindowBytesResultSchema, {
25581
+ kind: "query",
25582
+ auth: "admin"
25352
25583
  }), method(object({
25353
25584
  deviceId: number$1(),
25354
25585
  config: RecordingConfigSchema
@@ -26180,6 +26411,211 @@ var SetSiteLocationInputSchema = object({
26180
26411
  latitude: number$1().min(-90).max(90),
26181
26412
  longitude: number$1().min(-180).max(180)
26182
26413
  }).nullable();
26414
+ /**
26415
+ * One `(procedure, user-agent, ip, principal)` tuple of the HTTP request
26416
+ * census. `principal` is the DERIVED identity (`apocaliss92 (admin)`,
26417
+ * `scoped:1a2b3c4d (scoped-token)`, `anonymous`) that the tRPC error log
26418
+ * already prints - never a token, never an `Authorization` header.
26419
+ */
26420
+ var RequestCensusGroupSchema = object({
26421
+ procedure: string(),
26422
+ userAgent: string(),
26423
+ ip: string(),
26424
+ principal: string(),
26425
+ calls: number$1(),
26426
+ perMin: number$1()
26427
+ });
26428
+ /**
26429
+ * A procedure's TOTAL over the window, across every caller.
26430
+ *
26431
+ * This block, not the group list, is what answers "did these calls arrive over
26432
+ * HTTP at all". A total far BELOW what a store-side census counted over the
26433
+ * same window excludes the HTTP plane, which is a result, not a failure.
26434
+ */
26435
+ var RequestCensusProcedureSchema = object({
26436
+ procedure: string(),
26437
+ calls: number$1(),
26438
+ perMin: number$1()
26439
+ });
26440
+ /**
26441
+ * The census as an operator sees it.
26442
+ *
26443
+ * `persisted` is the honest answer to "will this survive the restart I am
26444
+ * about to do": the arm deadline is written to `system-settings` so a window
26445
+ * armed now can measure the NEXT boot, and a write that failed must not look
26446
+ * like one that succeeded.
26447
+ */
26448
+ var RequestCensusStatusSchema = object({
26449
+ armed: boolean(),
26450
+ /** How long the current - or just-closed - window collected, in ms. */
26451
+ elapsedMs: number$1(),
26452
+ /** The window actually armed, after the server clamped the request. */
26453
+ windowMs: number$1(),
26454
+ /** Epoch ms the window closes at. 0 when disarmed. */
26455
+ armedUntilMs: number$1(),
26456
+ httpRequests: number$1(),
26457
+ batchedRequests: number$1(),
26458
+ /**
26459
+ * Procedure invocations. Higher than `httpRequests` whenever tRPC batching
26460
+ * is in play (`?batch=1` carries several procedures in one request); this is
26461
+ * the number comparable with a store-side call count.
26462
+ */
26463
+ procedureCalls: number$1(),
26464
+ /**
26465
+ * tRPC WebSocket connections opened during the window. NOT calls - the WS
26466
+ * transport resolves one context per connection - but the number that says
26467
+ * whether a plane this census cannot see was busy while HTTP was quiet.
26468
+ */
26469
+ wsConnections: number$1(),
26470
+ distinctGroups: number$1(),
26471
+ /** Calls counted in the totals whose group attribution was shed at the
26472
+ * cardinality bound. */
26473
+ unattributedCalls: number$1(),
26474
+ procedures: array(RequestCensusProcedureSchema).readonly(),
26475
+ groups: array(RequestCensusGroupSchema).readonly()
26476
+ }).extend({ persisted: boolean() });
26477
+ /** Severity vocabulary. Mirrors `LogLevel` in `interfaces/logging.ts`. */
26478
+ var LogLevelSchema$1 = _enum([
26479
+ "debug",
26480
+ "info",
26481
+ "warn",
26482
+ "error"
26483
+ ]);
26484
+ /**
26485
+ * The diagnostics that can be ARMED for a window. Exactly one today.
26486
+ *
26487
+ * A diagnostic is anything whose cost is only worth paying while a question is
26488
+ * open. It never persists as a boolean: see {@link DiagnosticWindowSchema}.
26489
+ */
26490
+ var DiagnosticIdSchema = _enum(["request-census"]);
26491
+ /**
26492
+ * The layers of the level hierarchy, general → specific. The most specific
26493
+ * layer that carries an explicit value wins.
26494
+ *
26495
+ * `component` is DECLARED and not yet resolvable: the per-component channels
26496
+ * are a later slice of the same plan, and a `levelSource` enum that has to
26497
+ * grow later would force every consumer of this document to change with it.
26498
+ * Nothing returns `component` today.
26499
+ */
26500
+ var LoggingScopeKindSchema = _enum([
26501
+ "cluster",
26502
+ "node",
26503
+ "component"
26504
+ ]);
26505
+ /** Where an effective level came from. `default` = nothing is set anywhere. */
26506
+ var LoggingLevelSourceSchema = _enum([
26507
+ "default",
26508
+ "cluster",
26509
+ "node",
26510
+ "component"
26511
+ ]);
26512
+ /**
26513
+ * One layer of the hierarchy as it actually STANDS.
26514
+ *
26515
+ * `level: null` is the whole reason this array is returned: it is the
26516
+ * difference between "this node is at `info` because I decided it" and
26517
+ * "...because it inherits". An operator who clears an override believing they
26518
+ * are clearing an inherited value has been handed the same defect as the two
26519
+ * contradicting knobs this document exists to remove, moved one floor up.
26520
+ */
26521
+ var LoggingLevelLayerSchema = object({
26522
+ scope: LoggingScopeKindSchema,
26523
+ /** The node this layer speaks for; `null` on the cluster layer. */
26524
+ nodeId: string().nullable(),
26525
+ /** Explicitly set here, or `null` when this layer inherits. */
26526
+ level: LogLevelSchema$1.nullable()
26527
+ });
26528
+ /** What a line is judged against, and WHICH layer decided it. */
26529
+ var LoggingEffectiveSchema = object({
26530
+ level: LogLevelSchema$1,
26531
+ levelSource: LoggingLevelSourceSchema
26532
+ });
26533
+ /** Every layer, general → specific. Never collapsed into the effective value. */
26534
+ var LoggingExplicitSchema = object({ layers: array(LoggingLevelLayerSchema).readonly() });
26535
+ /**
26536
+ * An armed diagnostic, with its DEADLINE.
26537
+ *
26538
+ * The shape of ADR-0244: what is stored is a deadline and never a flag, so a
26539
+ * diagnostic somebody forgot expires by itself, and a boot-window measurement
26540
+ * survives the restart it exists to measure. `remainingMs` is 0 whenever
26541
+ * `armed` is false — a window is never reported as slightly expired.
26542
+ */
26543
+ var DiagnosticWindowSchema = object({
26544
+ id: DiagnosticIdSchema,
26545
+ armed: boolean(),
26546
+ /** Epoch ms the window closes at. 0 when disarmed. */
26547
+ armedUntilMs: number$1(),
26548
+ /** Ms left before it expires on its own. 0 when disarmed. */
26549
+ remainingMs: number$1(),
26550
+ /** Whether the stored deadline is the one the live diagnostic is running —
26551
+ * i.e. whether this window would survive a restart. */
26552
+ persisted: boolean()
26553
+ });
26554
+ /**
26555
+ * `armMs: 0` DISARMS. Any positive value arms for that long, clamped by the
26556
+ * server — there is no maximum here on purpose: a bound repeated in a schema
26557
+ * is a second knob that disagrees with the first the day one of them moves.
26558
+ */
26559
+ var DiagnosticWindowPatchSchema = object({
26560
+ id: DiagnosticIdSchema,
26561
+ armMs: number$1().int().min(0),
26562
+ /** Cadence of the diagnostic's aggregated report line. Clamped by the server. */
26563
+ reportEveryMs: number$1().int().positive().optional()
26564
+ });
26565
+ /**
26566
+ * A PATCH, and patches MERGE.
26567
+ *
26568
+ * A field absent from the patch is left exactly as it was — arming a
26569
+ * diagnostic never resets a level, and setting a level never disarms a window.
26570
+ * The provider must not reconstruct the document as `{ ...snapshot, ...patch }`:
26571
+ * `setAll` already merges, and rebuilding the object is how an absent field
26572
+ * turns into an erased one.
26573
+ */
26574
+ var LoggingSettingsPatchSchema = object({
26575
+ /**
26576
+ * Absent leaves the level untouched. `null` CLEARS the explicit value at the
26577
+ * addressed scope so it inherits again. A value sets it.
26578
+ */
26579
+ level: LogLevelSchema$1.nullable().optional(),
26580
+ /**
26581
+ * Only the diagnostics NAMED here change. An armed window that is not listed
26582
+ * keeps running — a patch is never a full replacement.
26583
+ */
26584
+ diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional()
26585
+ });
26586
+ /**
26587
+ * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
26588
+ *
26589
+ * Deliberately NOT called `nodeId`: that key is reserved on every cap method
26590
+ * input — the generated router strips it and uses it to resolve the PROVIDER
26591
+ * on that node (`resolveProvider(cap, nodeId, …)`). A document about
26592
+ * `agent-1` addressed as `nodeId` would be forwarded to agent-1 and answered
26593
+ * by an agent that holds no cluster document at all. The hub is the single
26594
+ * authority over the whole hierarchy and answers for every layer, so the
26595
+ * layer selector needs a name the transport does not already own.
26596
+ */
26597
+ var GetLoggingSettingsInputSchema = object({ scopeNodeId: string().optional() });
26598
+ var SetLoggingSettingsInputSchema = object({
26599
+ scopeNodeId: string().optional(),
26600
+ patch: LoggingSettingsPatchSchema
26601
+ });
26602
+ /**
26603
+ * The whole document, as read and as returned after every write.
26604
+ *
26605
+ * `persisted: false` means the settings store could not be read or written.
26606
+ * The in-memory mirror still governs behaviour and is unchanged by the
26607
+ * failure — a read that fails neither switches a level nor disarms a window
26608
+ * (D49) — but the operator is told that what they are looking at would not
26609
+ * survive a restart.
26610
+ */
26611
+ var LoggingSettingsStateSchema = object({
26612
+ /** The layer this document was read at. `null` = the cluster layer. */
26613
+ scopeNodeId: string().nullable(),
26614
+ effective: LoggingEffectiveSchema,
26615
+ explicit: LoggingExplicitSchema,
26616
+ activeWindows: array(DiagnosticWindowSchema).readonly(),
26617
+ persisted: boolean()
26618
+ });
26183
26619
  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(), {
26184
26620
  kind: "mutation",
26185
26621
  auth: "admin"
@@ -26192,6 +26628,9 @@ method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), met
26192
26628
  }), method(_void(), SiteLocationStatusSchema, {
26193
26629
  kind: "mutation",
26194
26630
  auth: "admin"
26631
+ }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
26632
+ kind: "mutation",
26633
+ auth: "admin"
26195
26634
  });
26196
26635
  object({
26197
26636
  /** True when the device's tamper switch / case-open contact is
@@ -29767,6 +30206,12 @@ Object.freeze({
29767
30206
  addonId: null,
29768
30207
  access: "view"
29769
30208
  },
30209
+ "notificationRules.resolveArtifactUrl": {
30210
+ capName: "notification-rules",
30211
+ capScope: "system",
30212
+ addonId: null,
30213
+ access: "view"
30214
+ },
29770
30215
  "notificationRules.setAlarmConfig": {
29771
30216
  capName: "notification-rules",
29772
30217
  capScope: "system",
@@ -31171,6 +31616,12 @@ Object.freeze({
31171
31616
  addonId: null,
31172
31617
  access: "view"
31173
31618
  },
31619
+ "recording.readWindowBytes": {
31620
+ capName: "recording",
31621
+ capScope: "system",
31622
+ addonId: null,
31623
+ access: "view"
31624
+ },
31174
31625
  "recording.refreshStorageLocationsForMigration": {
31175
31626
  capName: "recording",
31176
31627
  capScope: "system",
@@ -32011,6 +32462,18 @@ Object.freeze({
32011
32462
  addonId: null,
32012
32463
  access: "create"
32013
32464
  },
32465
+ "system.getLoggingSettings": {
32466
+ capName: "system",
32467
+ capScope: "system",
32468
+ addonId: null,
32469
+ access: "view"
32470
+ },
32471
+ "system.getRequestCensus": {
32472
+ capName: "system",
32473
+ capScope: "system",
32474
+ addonId: null,
32475
+ access: "view"
32476
+ },
32014
32477
  "system.getRetentionConfig": {
32015
32478
  capName: "system",
32016
32479
  capScope: "system",
@@ -32041,6 +32504,12 @@ Object.freeze({
32041
32504
  addonId: null,
32042
32505
  access: "view"
32043
32506
  },
32507
+ "system.setLoggingSettings": {
32508
+ capName: "system",
32509
+ capScope: "system",
32510
+ addonId: null,
32511
+ access: "create"
32512
+ },
32044
32513
  "system.setRetentionConfig": {
32045
32514
  capName: "system",
32046
32515
  capScope: "system",
@@ -33196,6 +33665,10 @@ Object.freeze({
33196
33665
  name: "deviceId",
33197
33666
  form: "single",
33198
33667
  optional: true
33668
+ }, {
33669
+ name: "deviceIds",
33670
+ form: "array",
33671
+ optional: true
33199
33672
  }],
33200
33673
  "fanControl.setDirection": [{
33201
33674
  name: "deviceId",
@@ -34001,6 +34474,11 @@ Object.freeze({
34001
34474
  form: "single",
34002
34475
  optional: false
34003
34476
  }],
34477
+ "recording.readWindowBytes": [{
34478
+ name: "deviceId",
34479
+ form: "single",
34480
+ optional: false
34481
+ }],
34004
34482
  "recording.relocateFootage": [{
34005
34483
  name: "deviceId",
34006
34484
  form: "single",
@@ -34801,7 +35279,38 @@ object({
34801
35279
  * fallback — i.e. the pre-2026-08-13 miss profile. Set it there only to
34802
35280
  * reproduce that.
34803
35281
  */
34804
- tileBudgetMb: number$1().int().min(0).max(1024)
35282
+ tileBudgetMb: number$1().int().min(0).max(1024),
35283
+ /**
35284
+ * RAM ceiling per decode worker, in MB, for the SCENE TILES — one
35285
+ * JPEG-encoded copy of the WHOLE native frame, cut in the same instant as the
35286
+ * subject tiles, on frames that detected something.
35287
+ *
35288
+ * It exists because a subject tile cannot answer a FULL-FRAME request:
35289
+ * containment is strict by design, so the native `keyFrame`, the detail
35290
+ * plane's `frameJpeg` rung and the display-crop fallback had no rung at all
35291
+ * below the hold. Measured on the live cluster: 23.3% of key-frame captures
35292
+ * missed, 95.7% of them with `worker-lease-gone` — the raster released one
35293
+ * frame-time after delivery, with the request only p50 367 ms behind it.
35294
+ *
35295
+ * Sizing, and why this is a budget and not a duration: a scene tile is
35296
+ * ~1.5-2.5 MB at 4K (against ~23.75 MB for the raster it was cut from and
35297
+ * ~60-120 KB for a subject tile), and it is cut ~0.4 times a second per busy
35298
+ * camera — only detection-bearing frames get one. 48 MB is therefore ~20-30
35299
+ * frames, i.e. the store's 30 s TTL binds at the steady state and the budget
35300
+ * binds only through a detection burst, where it still covers well past the
35301
+ * measured p90 ask age of 4.3 s. Holding the RASTERS for the same window
35302
+ * would be ~498 MB per camera and ~6 GB at peak concurrency — the OOM this
35303
+ * whole shape exists to avoid.
35304
+ *
35305
+ * A SEPARATE ceiling from `tileBudgetMb` on purpose: a scene tile is ~20× a
35306
+ * subject tile, so one shared budget would let a busy camera's key frames
35307
+ * evict the face/plate tiles the recognisers depend on. Two budgets make that
35308
+ * impossible rather than unlikely. `0` DISABLES scene tiles and restores the
35309
+ * pre-existing behaviour, where a late full-frame request had nothing but the
35310
+ * ≤640 RAM raster — which the `keyFrame` gate rejects, so in practice it had
35311
+ * nothing.
35312
+ */
35313
+ sceneBudgetMb: number$1().int().min(0).max(1024)
34805
35314
  });
34806
35315
  /**
34807
35316
  * The values in force when the operator has set nothing.
@@ -34817,12 +35326,14 @@ var DEFAULT_NATIVE_LEASE_SETTINGS = {
34817
35326
  budgetMb: 1024,
34818
35327
  activityMs: 15e3,
34819
35328
  tileBudgetMb: 64,
35329
+ sceneBudgetMb: 48,
34820
35330
  admission: "inferred"
34821
35331
  };
34822
35332
  DEFAULT_NATIVE_LEASE_SETTINGS.holdFrames;
34823
35333
  DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
34824
35334
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
34825
35335
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
35336
+ DEFAULT_NATIVE_LEASE_SETTINGS.sceneBudgetMb;
34826
35337
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
34827
35338
  var MB = 1024 * 1024;
34828
35339
  1024 * MB, 3072 * MB;