@camstack/addon-provider-dreame 0.2.31 → 0.2.32

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 +495 -59
  2. package/dist/addon.mjs +495 -59
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -58031,6 +58031,66 @@ var OpsLogQueryInputSchema = object({
58031
58031
  /** Max rows returned, newest-first. */
58032
58032
  limit: number().int().min(1).max(1e3).optional()
58033
58033
  });
58034
+ var LabelDefinitionSchema = object({
58035
+ id: string(),
58036
+ name: string(),
58037
+ category: string().optional(),
58038
+ description: string().optional(),
58039
+ icon: string().optional()
58040
+ });
58041
+ /** Detection-macro targets a catalog `classMap` may resolve to. */
58042
+ var CLASS_MAP_MACRO_TARGETS = [
58043
+ "person",
58044
+ "vehicle",
58045
+ "animal",
58046
+ "package"
58047
+ ];
58048
+ /**
58049
+ * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
58050
+ * un operatore può selezionare.
58051
+ *
58052
+ * Sono le tre offerte dallo step `object-detection`
58053
+ * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
58054
+ * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
58055
+ * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
58056
+ * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
58057
+ * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
58058
+ * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
58059
+ *
58060
+ * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
58061
+ * dello step e una seconda volta come union `FirstLevelMacro`
58062
+ * (`types/detection.ts`); una terza copia per il trigger di registrazione
58063
+ * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
58064
+ * successiva.
58065
+ */
58066
+ var FIRST_LEVEL_MACRO_CLASSES = [
58067
+ "person",
58068
+ "vehicle",
58069
+ "animal"
58070
+ ];
58071
+ /**
58072
+ * Wire schema for a per-model CATALOG classMap override
58073
+ * (`ModelCatalogEntry.classMap` / `ModelConvertMetadata.classMap`) —
58074
+ * restricted to {@link CLASS_MAP_MACRO_TARGETS}, the only macros the
58075
+ * detection pipeline executor actually routes.
58076
+ *
58077
+ * This is deliberately a DIFFERENT, narrower shape than the general-purpose
58078
+ * `ClassMapDefinition` interface above (e.g. `IDetectionAddon.getClassMap()`
58079
+ * and the audio `YAMNET_TO_MACRO` catalog both use macro targets outside this
58080
+ * enum) — the two used to share the name `ClassMapDefinition`/
58081
+ * `ClassMapDefinitionSchema`, which made the schema-type-twin guard
58082
+ * (`scripts/check-schema-type-twins.ts`) flag them as a duplicated shape. They
58083
+ * are not: it is two different concepts colliding on a name. Keep this type
58084
+ * under its own name rather than reusing `ClassMapDefinition` — reusing it
58085
+ * would either narrow every `ClassMapDefinition` consumer to the four
58086
+ * detection macros (breaking `YAMNET_TO_MACRO`) or drop the validation this
58087
+ * schema exists for (see the "rejects a classMap whose target is not a
58088
+ * detection macro" test in `model-catalog-schema.test.ts`).
58089
+ */
58090
+ var DetectionCatalogClassMapSchema = object({
58091
+ mapping: record(string(), _enum(CLASS_MAP_MACRO_TARGETS)),
58092
+ preserveOriginal: boolean()
58093
+ });
58034
58094
  /**
58035
58095
  * Numeric day-of-week: 0 = Sunday … 6 = Saturday (matches `Date.getDay`).
58036
58096
  * Named `RecordingWeekday` to avoid collision with the string-union
@@ -58053,10 +58113,55 @@ var RecordingStorageModeSchema = _enum([
58053
58113
  "events",
58054
58114
  "continuous"
58055
58115
  ]);
58116
+ /**
58117
+ * Le macro classi che possono aprire una finestra di registrazione — le stesse
58118
+ * tre offerte dallo step `object-detection`, da UNA lista
58119
+ * ({@link FIRST_LEVEL_MACRO_CLASSES}).
58120
+ */
58121
+ var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
58122
+ /**
58123
+ * True quando `values` non ripete un elemento.
58124
+ *
58125
+ * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
58126
+ * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
58127
+ * conterebbe due volte le sue finestre in `segmentMissedByMs`.
58128
+ */
58129
+ var noDuplicates = (values) => new Set(values).size === values.length;
58056
58130
  /** Which detectors trigger an `events`-mode band. */
58057
58131
  var RecordingTriggersSchema = object({
58058
58132
  motion: boolean().optional(),
58059
- audioThresholdDbfs: number().optional()
58133
+ audioThresholdDbfs: number().optional(),
58134
+ /**
58135
+ * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
58136
+ * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
58137
+ * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
58138
+ * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
58139
+ *
58140
+ * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
58141
+ * quelle che hanno attraversato `enabledMacroClasses`, i
58142
+ * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
58143
+ * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
58144
+ * finestre — vedi `recorder/object-trigger.ts`.
58145
+ */
58146
+ objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
58147
+ /**
58148
+ * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
58149
+ * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
58150
+ * `objectClasses`.
58151
+ *
58152
+ * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
58153
+ * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
58154
+ * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
58155
+ * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
58156
+ * device (D12) — mai un elenco globale di cap.
58157
+ *
58158
+ * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
58159
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
58160
+ * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
58161
+ * il livello: un contatto trovato già aperto al riavvio del runner non fa
58162
+ * registrare.
58163
+ */
58164
+ sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
58060
58165
  });
58061
58166
  /**
58062
58167
  * Mode of a single recording band — the recorder per-band vocabulary.
@@ -58508,41 +58613,6 @@ var DecoderSessionConfigSchema = object({
58508
58613
  */
58509
58614
  debug: boolean().optional()
58510
58615
  });
58511
- var LabelDefinitionSchema = object({
58512
- id: string(),
58513
- name: string(),
58514
- category: string().optional(),
58515
- description: string().optional(),
58516
- icon: string().optional()
58517
- });
58518
- /**
58519
- * Wire schema for a per-model CATALOG classMap override
58520
- * (`ModelCatalogEntry.classMap` / `ModelConvertMetadata.classMap`) —
58521
- * restricted to {@link CLASS_MAP_MACRO_TARGETS}, the only macros the
58522
- * detection pipeline executor actually routes.
58523
- *
58524
- * This is deliberately a DIFFERENT, narrower shape than the general-purpose
58525
- * `ClassMapDefinition` interface above (e.g. `IDetectionAddon.getClassMap()`
58526
- * and the audio `YAMNET_TO_MACRO` catalog both use macro targets outside this
58527
- * enum) — the two used to share the name `ClassMapDefinition`/
58528
- * `ClassMapDefinitionSchema`, which made the schema-type-twin guard
58529
- * (`scripts/check-schema-type-twins.ts`) flag them as a duplicated shape. They
58530
- * are not: it is two different concepts colliding on a name. Keep this type
58531
- * under its own name rather than reusing `ClassMapDefinition` — reusing it
58532
- * would either narrow every `ClassMapDefinition` consumer to the four
58533
- * detection macros (breaking `YAMNET_TO_MACRO`) or drop the validation this
58534
- * schema exists for (see the "rejects a classMap whose target is not a
58535
- * detection macro" test in `model-catalog-schema.test.ts`).
58536
- */
58537
- var DetectionCatalogClassMapSchema = object({
58538
- mapping: record(string(), _enum([
58539
- "person",
58540
- "vehicle",
58541
- "animal",
58542
- "package"
58543
- ])),
58544
- preserveOriginal: boolean()
58545
- });
58546
58616
  var MODEL_FORMATS = [
58547
58617
  "onnx",
58548
58618
  "coreml",
@@ -71675,7 +71745,7 @@ var lifecycleJobSchema = object({
71675
71745
  * `useAddonsOnAddonLogs`, etc. flow through the same codegen pipeline
71676
71746
  * as every other cap.
71677
71747
  */
71678
- var LogLevelSchema$1 = _enum([
71748
+ var LogLevelSchema$2 = _enum([
71679
71749
  "debug",
71680
71750
  "info",
71681
71751
  "warn",
@@ -71882,7 +71952,7 @@ var CustomActionInputSchema = object({
71882
71952
  method(_void(), array(AddonListItemSchema).readonly()), method(object({
71883
71953
  addonId: string(),
71884
71954
  limit: number().min(1).max(500).default(100),
71885
- level: LogLevelSchema$1.optional()
71955
+ level: LogLevelSchema$2.optional()
71886
71956
  }), array(LogQueryEntrySchema)), method(_void(), array(InstalledPackageSchema).readonly()), method(object({
71887
71957
  packageName: string(),
71888
71958
  version: string().optional()
@@ -71980,7 +72050,7 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
71980
72050
  auth: "admin"
71981
72051
  }), method(object({
71982
72052
  addonId: string(),
71983
- level: LogLevelSchema$1.optional()
72053
+ level: LogLevelSchema$2.optional()
71984
72054
  }), LogStreamEntrySchema, { kind: "subscription" });
71985
72055
  /**
71986
72056
  * Multi-metric air-quality slice. Covers CO₂, total VOCs, particulate
@@ -73729,6 +73799,35 @@ var FaceFilterEnum = _enum([
73729
73799
  "identified",
73730
73800
  "all"
73731
73801
  ]);
73802
+ /**
73803
+ * What a `listRecentFaces` page is ORDERED BY.
73804
+ *
73805
+ * - `timestamp` — when the face was seen. The historical (and default) order.
73806
+ * - `suggestionConfidence` — {@link FaceInfo.suggestedMatchScore}, the peak
73807
+ * cosine of the face's SUGGESTED identity. This is the "review by certainty"
73808
+ * order: it puts the suggestions an operator can confirm with one tap at the
73809
+ * top, and it is the reason this enum exists — a client that ranked a capped
73810
+ * page client-side was ranking the newest N, never the most certain N.
73811
+ *
73812
+ * A row with NO suggestion (`suggestedMatchScore` absent — a legacy row, an
73813
+ * auto-assigned face, or a face below the suggestion band) has no certainty to
73814
+ * compare. Under `suggestionConfidence` it sorts **LAST, in BOTH directions**
73815
+ * — flipping the direction reorders the rows that HAVE a certainty and never
73816
+ * floods the page with the ones that do not. `addon-post-analysis`'s
73817
+ * `store/face-sort.ts` is the single implementation, tiebreaks newest-first
73818
+ * then by faceId, and is what makes this a total order instead of the
73819
+ * backend's NULL-collation accident.
73820
+ */
73821
+ var FaceSortFieldEnum = _enum(["timestamp", "suggestionConfidence"]);
73822
+ var FaceSortDirectionEnum = _enum(["asc", "desc"]);
73823
+ /** One suggested group of look-alike UNASSIGNED faces. Ids only — an embedding
73824
+ * never leaves the server. */
73825
+ var FaceClusterSchema = object({
73826
+ faceIds: array(string()).readonly(),
73827
+ representativeFaceId: string(),
73828
+ size: number().int(),
73829
+ cohesion: number()
73830
+ });
73732
73831
  var MediaFileLiteSchema$1 = object({
73733
73832
  key: string(),
73734
73833
  kind: string(),
@@ -73775,24 +73874,72 @@ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly
73775
73874
  kind: "mutation",
73776
73875
  auth: "admin"
73777
73876
  }), method(object({
73778
- /** Restrict to one camera. Absent keeps the cluster-wide gallery view. */
73877
+ /**
73878
+ * Restrict to ONE camera. Absent keeps the cluster-wide gallery view.
73879
+ *
73880
+ * The legacy single-camera form, kept verbatim for every caller that
73881
+ * already sends it. A caller that wants a SET sends {@link deviceIds}
73882
+ * instead — never both: `deviceIds` is the authority whenever it is
73883
+ * present, and this field is then ignored rather than unioned, so
73884
+ * there is exactly one answer to "which cameras did I ask for".
73885
+ */
73779
73886
  deviceId: number().int().optional(),
73887
+ /**
73888
+ * Restrict to a SET of cameras — the review UI's camera filter, which
73889
+ * until now had to fetch the cluster-wide page and drop rows in the
73890
+ * client (so the `limit` it asked for was spent on cameras it was
73891
+ * about to discard).
73892
+ *
73893
+ * An **empty array reads NOTHING** — `[]` is an empty page, never
73894
+ * "every camera". A request for no devices is a request, not an
73895
+ * omission; same contract as `deviceManager.listFleet` and
73896
+ * `pipelineAnalytics.listRecentTracks`.
73897
+ *
73898
+ * Absent (`undefined`) is the omission, and keeps the cluster-wide view.
73899
+ */
73900
+ deviceIds: array(number().int()).optional(),
73780
73901
  limit: number().int().positive().optional(),
73781
73902
  filter: FaceFilterEnum.optional(),
73782
73903
  /**
73783
- * Inline the base64 crop on every row. Default `true` — the existing
73784
- * behaviour, kept so no caller breaks.
73904
+ * Window lower bound on {@link FaceInfo.timestamp}, INCLUSIVE.
73905
+ * Absent means no lower bound.
73906
+ */
73907
+ since: number().int().optional(),
73908
+ /**
73909
+ * Window upper bound on {@link FaceInfo.timestamp}, INCLUSIVE.
73910
+ * Absent means no upper bound.
73911
+ */
73912
+ until: number().int().optional(),
73913
+ /**
73914
+ * Order the page by time or by suggestion certainty. Default
73915
+ * `'timestamp'` — the historical order, unchanged for every caller
73916
+ * that does not ask.
73785
73917
  *
73786
- * Set `false` once the caller renders {@link FaceInfo.cropUrl}: that
73787
- * drops ~2.87 MiB per 500-row page to a few KiB of metadata and lets
73788
- * the browser cache the images.
73918
+ * See {@link FaceSortFieldEnum} for what a row with no suggestion
73919
+ * does under `'suggestionConfidence'`.
73789
73920
  *
73790
- * **This is an INPUT field, so it does not reach the addon until the
73791
- * next train.** The hub router validates cap inputs against its own
73792
- * compiled Zod, which strips a key it does not know — verified today
73793
- * on the OUTPUT side, where an additive field DOES arrive immediately
73794
- * (`Track.hasFace`). Until the train ships, sending `false` is
73795
- * harmless and simply keeps the crops inline.
73921
+ * Cost note: `'timestamp'` is served by the `(deviceId, timestamp)`
73922
+ * index and stops reading as soon as `limit` rows have PASSED the
73923
+ * filter. `'suggestionConfidence'` cannot stop early — the most
73924
+ * certain row may be the oldest — so it walks the window. Narrow it
73925
+ * with {@link since} / {@link until}.
73926
+ */
73927
+ sortBy: FaceSortFieldEnum.optional(),
73928
+ /** Sort direction for {@link sortBy}. Default `'desc'`. */
73929
+ sortDirection: FaceSortDirectionEnum.optional(),
73930
+ /**
73931
+ * Inline the base64 crop on every row.
73932
+ *
73933
+ * Default `false` since the 2026-08-25 inversion — see
73934
+ * `include-crops-default.ts`, which is the ONE place that resolves
73935
+ * this for every gallery, and which records why the inline shape had
73936
+ * to become the one you ASK for (60 457 ms → UDS timeout at 500 rows).
73937
+ * The doc here used to still say `true`; it was wrong, and a leftover
73938
+ * that describes the old design reads as permission to rely on it.
73939
+ *
73940
+ * Nothing loses its image: the row carries {@link FaceInfo.cropUrl},
73941
+ * which the browser fetches off the `event-media` plane in parallel,
73942
+ * cached and ETagged.
73796
73943
  */
73797
73944
  includeCrops: boolean().optional()
73798
73945
  }).optional(), array(FaceInfoSchema).readonly()), method(object({
@@ -73828,13 +73975,39 @@ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly
73828
73975
  }), method(object({
73829
73976
  threshold: number().min(0).max(1).optional(),
73830
73977
  minClusterSize: number().int().min(2).optional(),
73831
- limit: number().int().positive().optional()
73832
- }).optional(), array(object({
73833
- faceIds: array(string()).readonly(),
73834
- representativeFaceId: string(),
73835
- size: number().int(),
73836
- cohesion: number()
73837
- })).readonly());
73978
+ /**
73979
+ * Cap on the number of CLUSTERS returned. Renamed from `limit`,
73980
+ * which read as though it bounded the work — it never did.
73981
+ *
73982
+ * Wins over {@link limit} when both are sent.
73983
+ */
73984
+ maxClusters: number().int().positive().optional(),
73985
+ /**
73986
+ * @deprecated Ambiguous name for {@link maxClusters} — it cuts the
73987
+ * RESULT, not the scan. Kept so existing callers keep working; send
73988
+ * `maxClusters` (and, if you care about cost, {@link maxFacesScanned}).
73989
+ */
73990
+ limit: number().int().positive().optional(),
73991
+ /**
73992
+ * Cap on the number of unassigned faces READ AND CLUSTERED — the
73993
+ * POOL, not the result.
73994
+ *
73995
+ * This is the knob {@link maxClusters} was mistaken for. Clustering
73996
+ * used to read every unassigned face on the hub no matter what the
73997
+ * caller asked for, because the only bound cut the finished clusters
73998
+ * afterwards; a UI showing a window of 100 paid for a scan of the
73999
+ * whole corpus, on an addon whose disk is under contention.
74000
+ *
74001
+ * The pool is the NEWEST matching faces first — the same order the
74002
+ * gallery shows — so a bound here shortens the horizon, it does not
74003
+ * sample it randomly.
74004
+ *
74005
+ * Default: 1 000 (`FACE_SWEEP_PAGE`, exactly one store page). Chosen
74006
+ * so the live corpus — 372 face rows — is unaffected while the
74007
+ * unbounded scan can never come back as the table grows.
74008
+ */
74009
+ maxFacesScanned: number().int().positive().optional()
74010
+ }).optional(), array(FaceClusterSchema).readonly());
73838
74011
  /**
73839
74012
  * Fan-control cap. Models HA `fan.*` entity-specific surfaces:
73840
74013
  * speed percentage, preset modes, ceiling-fan direction, and
@@ -78776,6 +78949,211 @@ var SetSiteLocationInputSchema = object({
78776
78949
  latitude: number().min(-90).max(90),
78777
78950
  longitude: number().min(-180).max(180)
78778
78951
  }).nullable();
78952
+ /**
78953
+ * One `(procedure, user-agent, ip, principal)` tuple of the HTTP request
78954
+ * census. `principal` is the DERIVED identity (`apocaliss92 (admin)`,
78955
+ * `scoped:1a2b3c4d (scoped-token)`, `anonymous`) that the tRPC error log
78956
+ * already prints - never a token, never an `Authorization` header.
78957
+ */
78958
+ var RequestCensusGroupSchema = object({
78959
+ procedure: string(),
78960
+ userAgent: string(),
78961
+ ip: string(),
78962
+ principal: string(),
78963
+ calls: number(),
78964
+ perMin: number()
78965
+ });
78966
+ /**
78967
+ * A procedure's TOTAL over the window, across every caller.
78968
+ *
78969
+ * This block, not the group list, is what answers "did these calls arrive over
78970
+ * HTTP at all". A total far BELOW what a store-side census counted over the
78971
+ * same window excludes the HTTP plane, which is a result, not a failure.
78972
+ */
78973
+ var RequestCensusProcedureSchema = object({
78974
+ procedure: string(),
78975
+ calls: number(),
78976
+ perMin: number()
78977
+ });
78978
+ /**
78979
+ * The census as an operator sees it.
78980
+ *
78981
+ * `persisted` is the honest answer to "will this survive the restart I am
78982
+ * about to do": the arm deadline is written to `system-settings` so a window
78983
+ * armed now can measure the NEXT boot, and a write that failed must not look
78984
+ * like one that succeeded.
78985
+ */
78986
+ var RequestCensusStatusSchema = object({
78987
+ armed: boolean(),
78988
+ /** How long the current - or just-closed - window collected, in ms. */
78989
+ elapsedMs: number(),
78990
+ /** The window actually armed, after the server clamped the request. */
78991
+ windowMs: number(),
78992
+ /** Epoch ms the window closes at. 0 when disarmed. */
78993
+ armedUntilMs: number(),
78994
+ httpRequests: number(),
78995
+ batchedRequests: number(),
78996
+ /**
78997
+ * Procedure invocations. Higher than `httpRequests` whenever tRPC batching
78998
+ * is in play (`?batch=1` carries several procedures in one request); this is
78999
+ * the number comparable with a store-side call count.
79000
+ */
79001
+ procedureCalls: number(),
79002
+ /**
79003
+ * tRPC WebSocket connections opened during the window. NOT calls - the WS
79004
+ * transport resolves one context per connection - but the number that says
79005
+ * whether a plane this census cannot see was busy while HTTP was quiet.
79006
+ */
79007
+ wsConnections: number(),
79008
+ distinctGroups: number(),
79009
+ /** Calls counted in the totals whose group attribution was shed at the
79010
+ * cardinality bound. */
79011
+ unattributedCalls: number(),
79012
+ procedures: array(RequestCensusProcedureSchema).readonly(),
79013
+ groups: array(RequestCensusGroupSchema).readonly()
79014
+ }).extend({ persisted: boolean() });
79015
+ /** Severity vocabulary. Mirrors `LogLevel` in `interfaces/logging.ts`. */
79016
+ var LogLevelSchema$1 = _enum([
79017
+ "debug",
79018
+ "info",
79019
+ "warn",
79020
+ "error"
79021
+ ]);
79022
+ /**
79023
+ * The diagnostics that can be ARMED for a window. Exactly one today.
79024
+ *
79025
+ * A diagnostic is anything whose cost is only worth paying while a question is
79026
+ * open. It never persists as a boolean: see {@link DiagnosticWindowSchema}.
79027
+ */
79028
+ var DiagnosticIdSchema = _enum(["request-census"]);
79029
+ /**
79030
+ * The layers of the level hierarchy, general → specific. The most specific
79031
+ * layer that carries an explicit value wins.
79032
+ *
79033
+ * `component` is DECLARED and not yet resolvable: the per-component channels
79034
+ * are a later slice of the same plan, and a `levelSource` enum that has to
79035
+ * grow later would force every consumer of this document to change with it.
79036
+ * Nothing returns `component` today.
79037
+ */
79038
+ var LoggingScopeKindSchema = _enum([
79039
+ "cluster",
79040
+ "node",
79041
+ "component"
79042
+ ]);
79043
+ /** Where an effective level came from. `default` = nothing is set anywhere. */
79044
+ var LoggingLevelSourceSchema = _enum([
79045
+ "default",
79046
+ "cluster",
79047
+ "node",
79048
+ "component"
79049
+ ]);
79050
+ /**
79051
+ * One layer of the hierarchy as it actually STANDS.
79052
+ *
79053
+ * `level: null` is the whole reason this array is returned: it is the
79054
+ * difference between "this node is at `info` because I decided it" and
79055
+ * "...because it inherits". An operator who clears an override believing they
79056
+ * are clearing an inherited value has been handed the same defect as the two
79057
+ * contradicting knobs this document exists to remove, moved one floor up.
79058
+ */
79059
+ var LoggingLevelLayerSchema = object({
79060
+ scope: LoggingScopeKindSchema,
79061
+ /** The node this layer speaks for; `null` on the cluster layer. */
79062
+ nodeId: string().nullable(),
79063
+ /** Explicitly set here, or `null` when this layer inherits. */
79064
+ level: LogLevelSchema$1.nullable()
79065
+ });
79066
+ /** What a line is judged against, and WHICH layer decided it. */
79067
+ var LoggingEffectiveSchema = object({
79068
+ level: LogLevelSchema$1,
79069
+ levelSource: LoggingLevelSourceSchema
79070
+ });
79071
+ /** Every layer, general → specific. Never collapsed into the effective value. */
79072
+ var LoggingExplicitSchema = object({ layers: array(LoggingLevelLayerSchema).readonly() });
79073
+ /**
79074
+ * An armed diagnostic, with its DEADLINE.
79075
+ *
79076
+ * The shape of ADR-0244: what is stored is a deadline and never a flag, so a
79077
+ * diagnostic somebody forgot expires by itself, and a boot-window measurement
79078
+ * survives the restart it exists to measure. `remainingMs` is 0 whenever
79079
+ * `armed` is false — a window is never reported as slightly expired.
79080
+ */
79081
+ var DiagnosticWindowSchema = object({
79082
+ id: DiagnosticIdSchema,
79083
+ armed: boolean(),
79084
+ /** Epoch ms the window closes at. 0 when disarmed. */
79085
+ armedUntilMs: number(),
79086
+ /** Ms left before it expires on its own. 0 when disarmed. */
79087
+ remainingMs: number(),
79088
+ /** Whether the stored deadline is the one the live diagnostic is running —
79089
+ * i.e. whether this window would survive a restart. */
79090
+ persisted: boolean()
79091
+ });
79092
+ /**
79093
+ * `armMs: 0` DISARMS. Any positive value arms for that long, clamped by the
79094
+ * server — there is no maximum here on purpose: a bound repeated in a schema
79095
+ * is a second knob that disagrees with the first the day one of them moves.
79096
+ */
79097
+ var DiagnosticWindowPatchSchema = object({
79098
+ id: DiagnosticIdSchema,
79099
+ armMs: number().int().min(0),
79100
+ /** Cadence of the diagnostic's aggregated report line. Clamped by the server. */
79101
+ reportEveryMs: number().int().positive().optional()
79102
+ });
79103
+ /**
79104
+ * A PATCH, and patches MERGE.
79105
+ *
79106
+ * A field absent from the patch is left exactly as it was — arming a
79107
+ * diagnostic never resets a level, and setting a level never disarms a window.
79108
+ * The provider must not reconstruct the document as `{ ...snapshot, ...patch }`:
79109
+ * `setAll` already merges, and rebuilding the object is how an absent field
79110
+ * turns into an erased one.
79111
+ */
79112
+ var LoggingSettingsPatchSchema = object({
79113
+ /**
79114
+ * Absent leaves the level untouched. `null` CLEARS the explicit value at the
79115
+ * addressed scope so it inherits again. A value sets it.
79116
+ */
79117
+ level: LogLevelSchema$1.nullable().optional(),
79118
+ /**
79119
+ * Only the diagnostics NAMED here change. An armed window that is not listed
79120
+ * keeps running — a patch is never a full replacement.
79121
+ */
79122
+ diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional()
79123
+ });
79124
+ /**
79125
+ * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
79126
+ *
79127
+ * Deliberately NOT called `nodeId`: that key is reserved on every cap method
79128
+ * input — the generated router strips it and uses it to resolve the PROVIDER
79129
+ * on that node (`resolveProvider(cap, nodeId, …)`). A document about
79130
+ * `agent-1` addressed as `nodeId` would be forwarded to agent-1 and answered
79131
+ * by an agent that holds no cluster document at all. The hub is the single
79132
+ * authority over the whole hierarchy and answers for every layer, so the
79133
+ * layer selector needs a name the transport does not already own.
79134
+ */
79135
+ var GetLoggingSettingsInputSchema = object({ scopeNodeId: string().optional() });
79136
+ var SetLoggingSettingsInputSchema = object({
79137
+ scopeNodeId: string().optional(),
79138
+ patch: LoggingSettingsPatchSchema
79139
+ });
79140
+ /**
79141
+ * The whole document, as read and as returned after every write.
79142
+ *
79143
+ * `persisted: false` means the settings store could not be read or written.
79144
+ * The in-memory mirror still governs behaviour and is unchanged by the
79145
+ * failure — a read that fails neither switches a level nor disarms a window
79146
+ * (D49) — but the operator is told that what they are looking at would not
79147
+ * survive a restart.
79148
+ */
79149
+ var LoggingSettingsStateSchema = object({
79150
+ /** The layer this document was read at. `null` = the cluster layer. */
79151
+ scopeNodeId: string().nullable(),
79152
+ effective: LoggingEffectiveSchema,
79153
+ explicit: LoggingExplicitSchema,
79154
+ activeWindows: array(DiagnosticWindowSchema).readonly(),
79155
+ persisted: boolean()
79156
+ });
78779
79157
  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(), {
78780
79158
  kind: "mutation",
78781
79159
  auth: "admin"
@@ -78788,6 +79166,9 @@ method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), met
78788
79166
  }), method(_void(), SiteLocationStatusSchema, {
78789
79167
  kind: "mutation",
78790
79168
  auth: "admin"
79169
+ }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
79170
+ kind: "mutation",
79171
+ auth: "admin"
78791
79172
  });
78792
79173
  /**
78793
79174
  * Tamper / case-open detection sensor. Drives Home Assistant
@@ -86027,6 +86408,18 @@ Object.freeze({
86027
86408
  addonId: null,
86028
86409
  access: "create"
86029
86410
  },
86411
+ "system.getLoggingSettings": {
86412
+ capName: "system",
86413
+ capScope: "system",
86414
+ addonId: null,
86415
+ access: "view"
86416
+ },
86417
+ "system.getRequestCensus": {
86418
+ capName: "system",
86419
+ capScope: "system",
86420
+ addonId: null,
86421
+ access: "view"
86422
+ },
86030
86423
  "system.getRetentionConfig": {
86031
86424
  capName: "system",
86032
86425
  capScope: "system",
@@ -86057,6 +86450,12 @@ Object.freeze({
86057
86450
  addonId: null,
86058
86451
  access: "view"
86059
86452
  },
86453
+ "system.setLoggingSettings": {
86454
+ capName: "system",
86455
+ capScope: "system",
86456
+ addonId: null,
86457
+ access: "create"
86458
+ },
86060
86459
  "system.setRetentionConfig": {
86061
86460
  capName: "system",
86062
86461
  capScope: "system",
@@ -87212,6 +87611,10 @@ Object.freeze({
87212
87611
  name: "deviceId",
87213
87612
  form: "single",
87214
87613
  optional: true
87614
+ }, {
87615
+ name: "deviceIds",
87616
+ form: "array",
87617
+ optional: true
87215
87618
  }],
87216
87619
  "fanControl.setDirection": [{
87217
87620
  name: "deviceId",
@@ -88822,7 +89225,38 @@ object({
88822
89225
  * fallback — i.e. the pre-2026-08-13 miss profile. Set it there only to
88823
89226
  * reproduce that.
88824
89227
  */
88825
- tileBudgetMb: number().int().min(0).max(1024)
89228
+ tileBudgetMb: number().int().min(0).max(1024),
89229
+ /**
89230
+ * RAM ceiling per decode worker, in MB, for the SCENE TILES — one
89231
+ * JPEG-encoded copy of the WHOLE native frame, cut in the same instant as the
89232
+ * subject tiles, on frames that detected something.
89233
+ *
89234
+ * It exists because a subject tile cannot answer a FULL-FRAME request:
89235
+ * containment is strict by design, so the native `keyFrame`, the detail
89236
+ * plane's `frameJpeg` rung and the display-crop fallback had no rung at all
89237
+ * below the hold. Measured on the live cluster: 23.3% of key-frame captures
89238
+ * missed, 95.7% of them with `worker-lease-gone` — the raster released one
89239
+ * frame-time after delivery, with the request only p50 367 ms behind it.
89240
+ *
89241
+ * Sizing, and why this is a budget and not a duration: a scene tile is
89242
+ * ~1.5-2.5 MB at 4K (against ~23.75 MB for the raster it was cut from and
89243
+ * ~60-120 KB for a subject tile), and it is cut ~0.4 times a second per busy
89244
+ * camera — only detection-bearing frames get one. 48 MB is therefore ~20-30
89245
+ * frames, i.e. the store's 30 s TTL binds at the steady state and the budget
89246
+ * binds only through a detection burst, where it still covers well past the
89247
+ * measured p90 ask age of 4.3 s. Holding the RASTERS for the same window
89248
+ * would be ~498 MB per camera and ~6 GB at peak concurrency — the OOM this
89249
+ * whole shape exists to avoid.
89250
+ *
89251
+ * A SEPARATE ceiling from `tileBudgetMb` on purpose: a scene tile is ~20× a
89252
+ * subject tile, so one shared budget would let a busy camera's key frames
89253
+ * evict the face/plate tiles the recognisers depend on. Two budgets make that
89254
+ * impossible rather than unlikely. `0` DISABLES scene tiles and restores the
89255
+ * pre-existing behaviour, where a late full-frame request had nothing but the
89256
+ * ≤640 RAM raster — which the `keyFrame` gate rejects, so in practice it had
89257
+ * nothing.
89258
+ */
89259
+ sceneBudgetMb: number().int().min(0).max(1024)
88826
89260
  });
88827
89261
  /**
88828
89262
  * The values in force when the operator has set nothing.
@@ -88838,12 +89272,14 @@ var DEFAULT_NATIVE_LEASE_SETTINGS = {
88838
89272
  budgetMb: 1024,
88839
89273
  activityMs: 15e3,
88840
89274
  tileBudgetMb: 64,
89275
+ sceneBudgetMb: 48,
88841
89276
  admission: "inferred"
88842
89277
  };
88843
89278
  DEFAULT_NATIVE_LEASE_SETTINGS.holdFrames;
88844
89279
  DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
88845
89280
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
88846
89281
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
89282
+ DEFAULT_NATIVE_LEASE_SETTINGS.sceneBudgetMb;
88847
89283
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
88848
89284
  var MB = 1024 * 1024;
88849
89285
  1024 * MB, 3072 * MB;
package/dist/addon.mjs CHANGED
@@ -58031,6 +58031,66 @@ var OpsLogQueryInputSchema = object({
58031
58031
  /** Max rows returned, newest-first. */
58032
58032
  limit: number().int().min(1).max(1e3).optional()
58033
58033
  });
58034
+ var LabelDefinitionSchema = object({
58035
+ id: string(),
58036
+ name: string(),
58037
+ category: string().optional(),
58038
+ description: string().optional(),
58039
+ icon: string().optional()
58040
+ });
58041
+ /** Detection-macro targets a catalog `classMap` may resolve to. */
58042
+ var CLASS_MAP_MACRO_TARGETS = [
58043
+ "person",
58044
+ "vehicle",
58045
+ "animal",
58046
+ "package"
58047
+ ];
58048
+ /**
58049
+ * Le macro classi di PRIMO LIVELLO: quelle che un object detector emette e che
58050
+ * un operatore può selezionare.
58051
+ *
58052
+ * Sono le tre offerte dallo step `object-detection`
58053
+ * (`addon-pipeline/src/detection-pipeline/registry/step-definitions.ts`,
58054
+ * `enabledMacroClasses`). `package` sta in {@link CLASS_MAP_MACRO_TARGETS} e in
58055
+ * `MACRO_LABELS` — è una macro vera — ma NON qui: appartiene allo step
58056
+ * `package-detection`, la cui abilitazione è guidata dalle zone, e offrire la
58057
+ * stessa parola due volte ha già fatto accendere a un operatore il proxy COCO
58058
+ * (suitcase/backpack/handbag) lasciando spento il detector dedicato.
58059
+ *
58060
+ * UNA lista. Prima di oggi le stesse tre erano scritte a mano nell'offerta
58061
+ * dello step e una seconda volta come union `FirstLevelMacro`
58062
+ * (`types/detection.ts`); una terza copia per il trigger di registrazione
58063
+ * (`RecordingTriggers.objectClasses`) avrebbe reso invisibile la divergenza
58064
+ * successiva.
58065
+ */
58066
+ var FIRST_LEVEL_MACRO_CLASSES = [
58067
+ "person",
58068
+ "vehicle",
58069
+ "animal"
58070
+ ];
58071
+ /**
58072
+ * Wire schema for a per-model CATALOG classMap override
58073
+ * (`ModelCatalogEntry.classMap` / `ModelConvertMetadata.classMap`) —
58074
+ * restricted to {@link CLASS_MAP_MACRO_TARGETS}, the only macros the
58075
+ * detection pipeline executor actually routes.
58076
+ *
58077
+ * This is deliberately a DIFFERENT, narrower shape than the general-purpose
58078
+ * `ClassMapDefinition` interface above (e.g. `IDetectionAddon.getClassMap()`
58079
+ * and the audio `YAMNET_TO_MACRO` catalog both use macro targets outside this
58080
+ * enum) — the two used to share the name `ClassMapDefinition`/
58081
+ * `ClassMapDefinitionSchema`, which made the schema-type-twin guard
58082
+ * (`scripts/check-schema-type-twins.ts`) flag them as a duplicated shape. They
58083
+ * are not: it is two different concepts colliding on a name. Keep this type
58084
+ * under its own name rather than reusing `ClassMapDefinition` — reusing it
58085
+ * would either narrow every `ClassMapDefinition` consumer to the four
58086
+ * detection macros (breaking `YAMNET_TO_MACRO`) or drop the validation this
58087
+ * schema exists for (see the "rejects a classMap whose target is not a
58088
+ * detection macro" test in `model-catalog-schema.test.ts`).
58089
+ */
58090
+ var DetectionCatalogClassMapSchema = object({
58091
+ mapping: record(string(), _enum(CLASS_MAP_MACRO_TARGETS)),
58092
+ preserveOriginal: boolean()
58093
+ });
58034
58094
  /**
58035
58095
  * Numeric day-of-week: 0 = Sunday … 6 = Saturday (matches `Date.getDay`).
58036
58096
  * Named `RecordingWeekday` to avoid collision with the string-union
@@ -58053,10 +58113,55 @@ var RecordingStorageModeSchema = _enum([
58053
58113
  "events",
58054
58114
  "continuous"
58055
58115
  ]);
58116
+ /**
58117
+ * Le macro classi che possono aprire una finestra di registrazione — le stesse
58118
+ * tre offerte dallo step `object-detection`, da UNA lista
58119
+ * ({@link FIRST_LEVEL_MACRO_CLASSES}).
58120
+ */
58121
+ var RecordingObjectTriggerClassSchema = _enum(FIRST_LEVEL_MACRO_CLASSES);
58122
+ /**
58123
+ * True quando `values` non ripete un elemento.
58124
+ *
58125
+ * Un duplicato non è innocuo: ogni voce di `objectClasses` / `sensorDeviceIds`
58126
+ * diventa una SORGENTE in `bandTriggerSources`, e la stessa sorgente due volte
58127
+ * conterebbe due volte le sue finestre in `segmentMissedByMs`.
58128
+ */
58129
+ var noDuplicates = (values) => new Set(values).size === values.length;
58056
58130
  /** Which detectors trigger an `events`-mode band. */
58057
58131
  var RecordingTriggersSchema = object({
58058
58132
  motion: boolean().optional(),
58059
- audioThresholdDbfs: number().optional()
58133
+ audioThresholdDbfs: number().optional(),
58134
+ /**
58135
+ * Le macro classi la cui detection apre una finestra. ASSENTE = la sorgente
58136
+ * non è ascoltata; un array VUOTO è rifiutato, perché "banda events, trigger
58137
+ * object acceso, nessuna classe" è la stessa forma "abilitata e non registra
58138
+ * nulla, per sempre" contro cui è scritto `eventsBandCanEverDemand`.
58139
+ *
58140
+ * Il segnale letto è GIÀ FILTRATO: solo detection `source: 'pipeline'`, cioè
58141
+ * quelle che hanno attraversato `enabledMacroClasses`, i
58142
+ * `minConfidence<Macro>` e il full-frame guard. L'AI a bordo camera
58143
+ * (`source: 'onboard'`) non attraversa nessuno di quei gate e NON apre
58144
+ * finestre — vedi `recorder/object-trigger.ts`.
58145
+ */
58146
+ objectClasses: array(RecordingObjectTriggerClassSchema).min(1).refine(noDuplicates, { message: "objectClasses must not repeat a class" }).optional(),
58147
+ /**
58148
+ * I device LINKED il cui FRONTE ALTO apre una finestra. Assente = la sorgente
58149
+ * non è ascoltata; un array vuoto è rifiutato per la stessa ragione di
58150
+ * `objectClasses`.
58151
+ *
58152
+ * Sono id di device SORGENTE, non camere: la banda li nomina, quindi il
58153
+ * percorso caldo (`DeviceStateChanged`, a ritmo di bus su tutta la flotta)
58154
+ * non fa RPC. L'OFFERTA da cui l'operatore li sceglie è un'altra domanda, e
58155
+ * si risolve con `deviceManager.getLinkedDevices` + `getBindingsBatch` per
58156
+ * device (D12) — mai un elenco globale di cap.
58157
+ *
58158
+ * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
58159
+ * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
58160
+ * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
58161
+ * il livello: un contatto trovato già aperto al riavvio del runner non fa
58162
+ * registrare.
58163
+ */
58164
+ sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
58060
58165
  });
58061
58166
  /**
58062
58167
  * Mode of a single recording band — the recorder per-band vocabulary.
@@ -58508,41 +58613,6 @@ var DecoderSessionConfigSchema = object({
58508
58613
  */
58509
58614
  debug: boolean().optional()
58510
58615
  });
58511
- var LabelDefinitionSchema = object({
58512
- id: string(),
58513
- name: string(),
58514
- category: string().optional(),
58515
- description: string().optional(),
58516
- icon: string().optional()
58517
- });
58518
- /**
58519
- * Wire schema for a per-model CATALOG classMap override
58520
- * (`ModelCatalogEntry.classMap` / `ModelConvertMetadata.classMap`) —
58521
- * restricted to {@link CLASS_MAP_MACRO_TARGETS}, the only macros the
58522
- * detection pipeline executor actually routes.
58523
- *
58524
- * This is deliberately a DIFFERENT, narrower shape than the general-purpose
58525
- * `ClassMapDefinition` interface above (e.g. `IDetectionAddon.getClassMap()`
58526
- * and the audio `YAMNET_TO_MACRO` catalog both use macro targets outside this
58527
- * enum) — the two used to share the name `ClassMapDefinition`/
58528
- * `ClassMapDefinitionSchema`, which made the schema-type-twin guard
58529
- * (`scripts/check-schema-type-twins.ts`) flag them as a duplicated shape. They
58530
- * are not: it is two different concepts colliding on a name. Keep this type
58531
- * under its own name rather than reusing `ClassMapDefinition` — reusing it
58532
- * would either narrow every `ClassMapDefinition` consumer to the four
58533
- * detection macros (breaking `YAMNET_TO_MACRO`) or drop the validation this
58534
- * schema exists for (see the "rejects a classMap whose target is not a
58535
- * detection macro" test in `model-catalog-schema.test.ts`).
58536
- */
58537
- var DetectionCatalogClassMapSchema = object({
58538
- mapping: record(string(), _enum([
58539
- "person",
58540
- "vehicle",
58541
- "animal",
58542
- "package"
58543
- ])),
58544
- preserveOriginal: boolean()
58545
- });
58546
58616
  var MODEL_FORMATS = [
58547
58617
  "onnx",
58548
58618
  "coreml",
@@ -71675,7 +71745,7 @@ var lifecycleJobSchema = object({
71675
71745
  * `useAddonsOnAddonLogs`, etc. flow through the same codegen pipeline
71676
71746
  * as every other cap.
71677
71747
  */
71678
- var LogLevelSchema$1 = _enum([
71748
+ var LogLevelSchema$2 = _enum([
71679
71749
  "debug",
71680
71750
  "info",
71681
71751
  "warn",
@@ -71882,7 +71952,7 @@ var CustomActionInputSchema = object({
71882
71952
  method(_void(), array(AddonListItemSchema).readonly()), method(object({
71883
71953
  addonId: string(),
71884
71954
  limit: number().min(1).max(500).default(100),
71885
- level: LogLevelSchema$1.optional()
71955
+ level: LogLevelSchema$2.optional()
71886
71956
  }), array(LogQueryEntrySchema)), method(_void(), array(InstalledPackageSchema).readonly()), method(object({
71887
71957
  packageName: string(),
71888
71958
  version: string().optional()
@@ -71980,7 +72050,7 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
71980
72050
  auth: "admin"
71981
72051
  }), method(object({
71982
72052
  addonId: string(),
71983
- level: LogLevelSchema$1.optional()
72053
+ level: LogLevelSchema$2.optional()
71984
72054
  }), LogStreamEntrySchema, { kind: "subscription" });
71985
72055
  /**
71986
72056
  * Multi-metric air-quality slice. Covers CO₂, total VOCs, particulate
@@ -73729,6 +73799,35 @@ var FaceFilterEnum = _enum([
73729
73799
  "identified",
73730
73800
  "all"
73731
73801
  ]);
73802
+ /**
73803
+ * What a `listRecentFaces` page is ORDERED BY.
73804
+ *
73805
+ * - `timestamp` — when the face was seen. The historical (and default) order.
73806
+ * - `suggestionConfidence` — {@link FaceInfo.suggestedMatchScore}, the peak
73807
+ * cosine of the face's SUGGESTED identity. This is the "review by certainty"
73808
+ * order: it puts the suggestions an operator can confirm with one tap at the
73809
+ * top, and it is the reason this enum exists — a client that ranked a capped
73810
+ * page client-side was ranking the newest N, never the most certain N.
73811
+ *
73812
+ * A row with NO suggestion (`suggestedMatchScore` absent — a legacy row, an
73813
+ * auto-assigned face, or a face below the suggestion band) has no certainty to
73814
+ * compare. Under `suggestionConfidence` it sorts **LAST, in BOTH directions**
73815
+ * — flipping the direction reorders the rows that HAVE a certainty and never
73816
+ * floods the page with the ones that do not. `addon-post-analysis`'s
73817
+ * `store/face-sort.ts` is the single implementation, tiebreaks newest-first
73818
+ * then by faceId, and is what makes this a total order instead of the
73819
+ * backend's NULL-collation accident.
73820
+ */
73821
+ var FaceSortFieldEnum = _enum(["timestamp", "suggestionConfidence"]);
73822
+ var FaceSortDirectionEnum = _enum(["asc", "desc"]);
73823
+ /** One suggested group of look-alike UNASSIGNED faces. Ids only — an embedding
73824
+ * never leaves the server. */
73825
+ var FaceClusterSchema = object({
73826
+ faceIds: array(string()).readonly(),
73827
+ representativeFaceId: string(),
73828
+ size: number().int(),
73829
+ cohesion: number()
73830
+ });
73732
73831
  var MediaFileLiteSchema$1 = object({
73733
73832
  key: string(),
73734
73833
  kind: string(),
@@ -73775,24 +73874,72 @@ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly
73775
73874
  kind: "mutation",
73776
73875
  auth: "admin"
73777
73876
  }), method(object({
73778
- /** Restrict to one camera. Absent keeps the cluster-wide gallery view. */
73877
+ /**
73878
+ * Restrict to ONE camera. Absent keeps the cluster-wide gallery view.
73879
+ *
73880
+ * The legacy single-camera form, kept verbatim for every caller that
73881
+ * already sends it. A caller that wants a SET sends {@link deviceIds}
73882
+ * instead — never both: `deviceIds` is the authority whenever it is
73883
+ * present, and this field is then ignored rather than unioned, so
73884
+ * there is exactly one answer to "which cameras did I ask for".
73885
+ */
73779
73886
  deviceId: number().int().optional(),
73887
+ /**
73888
+ * Restrict to a SET of cameras — the review UI's camera filter, which
73889
+ * until now had to fetch the cluster-wide page and drop rows in the
73890
+ * client (so the `limit` it asked for was spent on cameras it was
73891
+ * about to discard).
73892
+ *
73893
+ * An **empty array reads NOTHING** — `[]` is an empty page, never
73894
+ * "every camera". A request for no devices is a request, not an
73895
+ * omission; same contract as `deviceManager.listFleet` and
73896
+ * `pipelineAnalytics.listRecentTracks`.
73897
+ *
73898
+ * Absent (`undefined`) is the omission, and keeps the cluster-wide view.
73899
+ */
73900
+ deviceIds: array(number().int()).optional(),
73780
73901
  limit: number().int().positive().optional(),
73781
73902
  filter: FaceFilterEnum.optional(),
73782
73903
  /**
73783
- * Inline the base64 crop on every row. Default `true` — the existing
73784
- * behaviour, kept so no caller breaks.
73904
+ * Window lower bound on {@link FaceInfo.timestamp}, INCLUSIVE.
73905
+ * Absent means no lower bound.
73906
+ */
73907
+ since: number().int().optional(),
73908
+ /**
73909
+ * Window upper bound on {@link FaceInfo.timestamp}, INCLUSIVE.
73910
+ * Absent means no upper bound.
73911
+ */
73912
+ until: number().int().optional(),
73913
+ /**
73914
+ * Order the page by time or by suggestion certainty. Default
73915
+ * `'timestamp'` — the historical order, unchanged for every caller
73916
+ * that does not ask.
73785
73917
  *
73786
- * Set `false` once the caller renders {@link FaceInfo.cropUrl}: that
73787
- * drops ~2.87 MiB per 500-row page to a few KiB of metadata and lets
73788
- * the browser cache the images.
73918
+ * See {@link FaceSortFieldEnum} for what a row with no suggestion
73919
+ * does under `'suggestionConfidence'`.
73789
73920
  *
73790
- * **This is an INPUT field, so it does not reach the addon until the
73791
- * next train.** The hub router validates cap inputs against its own
73792
- * compiled Zod, which strips a key it does not know — verified today
73793
- * on the OUTPUT side, where an additive field DOES arrive immediately
73794
- * (`Track.hasFace`). Until the train ships, sending `false` is
73795
- * harmless and simply keeps the crops inline.
73921
+ * Cost note: `'timestamp'` is served by the `(deviceId, timestamp)`
73922
+ * index and stops reading as soon as `limit` rows have PASSED the
73923
+ * filter. `'suggestionConfidence'` cannot stop early — the most
73924
+ * certain row may be the oldest — so it walks the window. Narrow it
73925
+ * with {@link since} / {@link until}.
73926
+ */
73927
+ sortBy: FaceSortFieldEnum.optional(),
73928
+ /** Sort direction for {@link sortBy}. Default `'desc'`. */
73929
+ sortDirection: FaceSortDirectionEnum.optional(),
73930
+ /**
73931
+ * Inline the base64 crop on every row.
73932
+ *
73933
+ * Default `false` since the 2026-08-25 inversion — see
73934
+ * `include-crops-default.ts`, which is the ONE place that resolves
73935
+ * this for every gallery, and which records why the inline shape had
73936
+ * to become the one you ASK for (60 457 ms → UDS timeout at 500 rows).
73937
+ * The doc here used to still say `true`; it was wrong, and a leftover
73938
+ * that describes the old design reads as permission to rely on it.
73939
+ *
73940
+ * Nothing loses its image: the row carries {@link FaceInfo.cropUrl},
73941
+ * which the browser fetches off the `event-media` plane in parallel,
73942
+ * cached and ETagged.
73796
73943
  */
73797
73944
  includeCrops: boolean().optional()
73798
73945
  }).optional(), array(FaceInfoSchema).readonly()), method(object({
@@ -73828,13 +73975,39 @@ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly
73828
73975
  }), method(object({
73829
73976
  threshold: number().min(0).max(1).optional(),
73830
73977
  minClusterSize: number().int().min(2).optional(),
73831
- limit: number().int().positive().optional()
73832
- }).optional(), array(object({
73833
- faceIds: array(string()).readonly(),
73834
- representativeFaceId: string(),
73835
- size: number().int(),
73836
- cohesion: number()
73837
- })).readonly());
73978
+ /**
73979
+ * Cap on the number of CLUSTERS returned. Renamed from `limit`,
73980
+ * which read as though it bounded the work — it never did.
73981
+ *
73982
+ * Wins over {@link limit} when both are sent.
73983
+ */
73984
+ maxClusters: number().int().positive().optional(),
73985
+ /**
73986
+ * @deprecated Ambiguous name for {@link maxClusters} — it cuts the
73987
+ * RESULT, not the scan. Kept so existing callers keep working; send
73988
+ * `maxClusters` (and, if you care about cost, {@link maxFacesScanned}).
73989
+ */
73990
+ limit: number().int().positive().optional(),
73991
+ /**
73992
+ * Cap on the number of unassigned faces READ AND CLUSTERED — the
73993
+ * POOL, not the result.
73994
+ *
73995
+ * This is the knob {@link maxClusters} was mistaken for. Clustering
73996
+ * used to read every unassigned face on the hub no matter what the
73997
+ * caller asked for, because the only bound cut the finished clusters
73998
+ * afterwards; a UI showing a window of 100 paid for a scan of the
73999
+ * whole corpus, on an addon whose disk is under contention.
74000
+ *
74001
+ * The pool is the NEWEST matching faces first — the same order the
74002
+ * gallery shows — so a bound here shortens the horizon, it does not
74003
+ * sample it randomly.
74004
+ *
74005
+ * Default: 1 000 (`FACE_SWEEP_PAGE`, exactly one store page). Chosen
74006
+ * so the live corpus — 372 face rows — is unaffected while the
74007
+ * unbounded scan can never come back as the table grows.
74008
+ */
74009
+ maxFacesScanned: number().int().positive().optional()
74010
+ }).optional(), array(FaceClusterSchema).readonly());
73838
74011
  /**
73839
74012
  * Fan-control cap. Models HA `fan.*` entity-specific surfaces:
73840
74013
  * speed percentage, preset modes, ceiling-fan direction, and
@@ -78776,6 +78949,211 @@ var SetSiteLocationInputSchema = object({
78776
78949
  latitude: number().min(-90).max(90),
78777
78950
  longitude: number().min(-180).max(180)
78778
78951
  }).nullable();
78952
+ /**
78953
+ * One `(procedure, user-agent, ip, principal)` tuple of the HTTP request
78954
+ * census. `principal` is the DERIVED identity (`apocaliss92 (admin)`,
78955
+ * `scoped:1a2b3c4d (scoped-token)`, `anonymous`) that the tRPC error log
78956
+ * already prints - never a token, never an `Authorization` header.
78957
+ */
78958
+ var RequestCensusGroupSchema = object({
78959
+ procedure: string(),
78960
+ userAgent: string(),
78961
+ ip: string(),
78962
+ principal: string(),
78963
+ calls: number(),
78964
+ perMin: number()
78965
+ });
78966
+ /**
78967
+ * A procedure's TOTAL over the window, across every caller.
78968
+ *
78969
+ * This block, not the group list, is what answers "did these calls arrive over
78970
+ * HTTP at all". A total far BELOW what a store-side census counted over the
78971
+ * same window excludes the HTTP plane, which is a result, not a failure.
78972
+ */
78973
+ var RequestCensusProcedureSchema = object({
78974
+ procedure: string(),
78975
+ calls: number(),
78976
+ perMin: number()
78977
+ });
78978
+ /**
78979
+ * The census as an operator sees it.
78980
+ *
78981
+ * `persisted` is the honest answer to "will this survive the restart I am
78982
+ * about to do": the arm deadline is written to `system-settings` so a window
78983
+ * armed now can measure the NEXT boot, and a write that failed must not look
78984
+ * like one that succeeded.
78985
+ */
78986
+ var RequestCensusStatusSchema = object({
78987
+ armed: boolean(),
78988
+ /** How long the current - or just-closed - window collected, in ms. */
78989
+ elapsedMs: number(),
78990
+ /** The window actually armed, after the server clamped the request. */
78991
+ windowMs: number(),
78992
+ /** Epoch ms the window closes at. 0 when disarmed. */
78993
+ armedUntilMs: number(),
78994
+ httpRequests: number(),
78995
+ batchedRequests: number(),
78996
+ /**
78997
+ * Procedure invocations. Higher than `httpRequests` whenever tRPC batching
78998
+ * is in play (`?batch=1` carries several procedures in one request); this is
78999
+ * the number comparable with a store-side call count.
79000
+ */
79001
+ procedureCalls: number(),
79002
+ /**
79003
+ * tRPC WebSocket connections opened during the window. NOT calls - the WS
79004
+ * transport resolves one context per connection - but the number that says
79005
+ * whether a plane this census cannot see was busy while HTTP was quiet.
79006
+ */
79007
+ wsConnections: number(),
79008
+ distinctGroups: number(),
79009
+ /** Calls counted in the totals whose group attribution was shed at the
79010
+ * cardinality bound. */
79011
+ unattributedCalls: number(),
79012
+ procedures: array(RequestCensusProcedureSchema).readonly(),
79013
+ groups: array(RequestCensusGroupSchema).readonly()
79014
+ }).extend({ persisted: boolean() });
79015
+ /** Severity vocabulary. Mirrors `LogLevel` in `interfaces/logging.ts`. */
79016
+ var LogLevelSchema$1 = _enum([
79017
+ "debug",
79018
+ "info",
79019
+ "warn",
79020
+ "error"
79021
+ ]);
79022
+ /**
79023
+ * The diagnostics that can be ARMED for a window. Exactly one today.
79024
+ *
79025
+ * A diagnostic is anything whose cost is only worth paying while a question is
79026
+ * open. It never persists as a boolean: see {@link DiagnosticWindowSchema}.
79027
+ */
79028
+ var DiagnosticIdSchema = _enum(["request-census"]);
79029
+ /**
79030
+ * The layers of the level hierarchy, general → specific. The most specific
79031
+ * layer that carries an explicit value wins.
79032
+ *
79033
+ * `component` is DECLARED and not yet resolvable: the per-component channels
79034
+ * are a later slice of the same plan, and a `levelSource` enum that has to
79035
+ * grow later would force every consumer of this document to change with it.
79036
+ * Nothing returns `component` today.
79037
+ */
79038
+ var LoggingScopeKindSchema = _enum([
79039
+ "cluster",
79040
+ "node",
79041
+ "component"
79042
+ ]);
79043
+ /** Where an effective level came from. `default` = nothing is set anywhere. */
79044
+ var LoggingLevelSourceSchema = _enum([
79045
+ "default",
79046
+ "cluster",
79047
+ "node",
79048
+ "component"
79049
+ ]);
79050
+ /**
79051
+ * One layer of the hierarchy as it actually STANDS.
79052
+ *
79053
+ * `level: null` is the whole reason this array is returned: it is the
79054
+ * difference between "this node is at `info` because I decided it" and
79055
+ * "...because it inherits". An operator who clears an override believing they
79056
+ * are clearing an inherited value has been handed the same defect as the two
79057
+ * contradicting knobs this document exists to remove, moved one floor up.
79058
+ */
79059
+ var LoggingLevelLayerSchema = object({
79060
+ scope: LoggingScopeKindSchema,
79061
+ /** The node this layer speaks for; `null` on the cluster layer. */
79062
+ nodeId: string().nullable(),
79063
+ /** Explicitly set here, or `null` when this layer inherits. */
79064
+ level: LogLevelSchema$1.nullable()
79065
+ });
79066
+ /** What a line is judged against, and WHICH layer decided it. */
79067
+ var LoggingEffectiveSchema = object({
79068
+ level: LogLevelSchema$1,
79069
+ levelSource: LoggingLevelSourceSchema
79070
+ });
79071
+ /** Every layer, general → specific. Never collapsed into the effective value. */
79072
+ var LoggingExplicitSchema = object({ layers: array(LoggingLevelLayerSchema).readonly() });
79073
+ /**
79074
+ * An armed diagnostic, with its DEADLINE.
79075
+ *
79076
+ * The shape of ADR-0244: what is stored is a deadline and never a flag, so a
79077
+ * diagnostic somebody forgot expires by itself, and a boot-window measurement
79078
+ * survives the restart it exists to measure. `remainingMs` is 0 whenever
79079
+ * `armed` is false — a window is never reported as slightly expired.
79080
+ */
79081
+ var DiagnosticWindowSchema = object({
79082
+ id: DiagnosticIdSchema,
79083
+ armed: boolean(),
79084
+ /** Epoch ms the window closes at. 0 when disarmed. */
79085
+ armedUntilMs: number(),
79086
+ /** Ms left before it expires on its own. 0 when disarmed. */
79087
+ remainingMs: number(),
79088
+ /** Whether the stored deadline is the one the live diagnostic is running —
79089
+ * i.e. whether this window would survive a restart. */
79090
+ persisted: boolean()
79091
+ });
79092
+ /**
79093
+ * `armMs: 0` DISARMS. Any positive value arms for that long, clamped by the
79094
+ * server — there is no maximum here on purpose: a bound repeated in a schema
79095
+ * is a second knob that disagrees with the first the day one of them moves.
79096
+ */
79097
+ var DiagnosticWindowPatchSchema = object({
79098
+ id: DiagnosticIdSchema,
79099
+ armMs: number().int().min(0),
79100
+ /** Cadence of the diagnostic's aggregated report line. Clamped by the server. */
79101
+ reportEveryMs: number().int().positive().optional()
79102
+ });
79103
+ /**
79104
+ * A PATCH, and patches MERGE.
79105
+ *
79106
+ * A field absent from the patch is left exactly as it was — arming a
79107
+ * diagnostic never resets a level, and setting a level never disarms a window.
79108
+ * The provider must not reconstruct the document as `{ ...snapshot, ...patch }`:
79109
+ * `setAll` already merges, and rebuilding the object is how an absent field
79110
+ * turns into an erased one.
79111
+ */
79112
+ var LoggingSettingsPatchSchema = object({
79113
+ /**
79114
+ * Absent leaves the level untouched. `null` CLEARS the explicit value at the
79115
+ * addressed scope so it inherits again. A value sets it.
79116
+ */
79117
+ level: LogLevelSchema$1.nullable().optional(),
79118
+ /**
79119
+ * Only the diagnostics NAMED here change. An armed window that is not listed
79120
+ * keeps running — a patch is never a full replacement.
79121
+ */
79122
+ diagnostics: array(DiagnosticWindowPatchSchema).readonly().optional()
79123
+ });
79124
+ /**
79125
+ * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
79126
+ *
79127
+ * Deliberately NOT called `nodeId`: that key is reserved on every cap method
79128
+ * input — the generated router strips it and uses it to resolve the PROVIDER
79129
+ * on that node (`resolveProvider(cap, nodeId, …)`). A document about
79130
+ * `agent-1` addressed as `nodeId` would be forwarded to agent-1 and answered
79131
+ * by an agent that holds no cluster document at all. The hub is the single
79132
+ * authority over the whole hierarchy and answers for every layer, so the
79133
+ * layer selector needs a name the transport does not already own.
79134
+ */
79135
+ var GetLoggingSettingsInputSchema = object({ scopeNodeId: string().optional() });
79136
+ var SetLoggingSettingsInputSchema = object({
79137
+ scopeNodeId: string().optional(),
79138
+ patch: LoggingSettingsPatchSchema
79139
+ });
79140
+ /**
79141
+ * The whole document, as read and as returned after every write.
79142
+ *
79143
+ * `persisted: false` means the settings store could not be read or written.
79144
+ * The in-memory mirror still governs behaviour and is unchanged by the
79145
+ * failure — a read that fails neither switches a level nor disarms a window
79146
+ * (D49) — but the operator is told that what they are looking at would not
79147
+ * survive a restart.
79148
+ */
79149
+ var LoggingSettingsStateSchema = object({
79150
+ /** The layer this document was read at. `null` = the cluster layer. */
79151
+ scopeNodeId: string().nullable(),
79152
+ effective: LoggingEffectiveSchema,
79153
+ explicit: LoggingExplicitSchema,
79154
+ activeWindows: array(DiagnosticWindowSchema).readonly(),
79155
+ persisted: boolean()
79156
+ });
78779
79157
  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(), {
78780
79158
  kind: "mutation",
78781
79159
  auth: "admin"
@@ -78788,6 +79166,9 @@ method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), met
78788
79166
  }), method(_void(), SiteLocationStatusSchema, {
78789
79167
  kind: "mutation",
78790
79168
  auth: "admin"
79169
+ }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
79170
+ kind: "mutation",
79171
+ auth: "admin"
78791
79172
  });
78792
79173
  /**
78793
79174
  * Tamper / case-open detection sensor. Drives Home Assistant
@@ -86027,6 +86408,18 @@ Object.freeze({
86027
86408
  addonId: null,
86028
86409
  access: "create"
86029
86410
  },
86411
+ "system.getLoggingSettings": {
86412
+ capName: "system",
86413
+ capScope: "system",
86414
+ addonId: null,
86415
+ access: "view"
86416
+ },
86417
+ "system.getRequestCensus": {
86418
+ capName: "system",
86419
+ capScope: "system",
86420
+ addonId: null,
86421
+ access: "view"
86422
+ },
86030
86423
  "system.getRetentionConfig": {
86031
86424
  capName: "system",
86032
86425
  capScope: "system",
@@ -86057,6 +86450,12 @@ Object.freeze({
86057
86450
  addonId: null,
86058
86451
  access: "view"
86059
86452
  },
86453
+ "system.setLoggingSettings": {
86454
+ capName: "system",
86455
+ capScope: "system",
86456
+ addonId: null,
86457
+ access: "create"
86458
+ },
86060
86459
  "system.setRetentionConfig": {
86061
86460
  capName: "system",
86062
86461
  capScope: "system",
@@ -87212,6 +87611,10 @@ Object.freeze({
87212
87611
  name: "deviceId",
87213
87612
  form: "single",
87214
87613
  optional: true
87614
+ }, {
87615
+ name: "deviceIds",
87616
+ form: "array",
87617
+ optional: true
87215
87618
  }],
87216
87619
  "fanControl.setDirection": [{
87217
87620
  name: "deviceId",
@@ -88822,7 +89225,38 @@ object({
88822
89225
  * fallback — i.e. the pre-2026-08-13 miss profile. Set it there only to
88823
89226
  * reproduce that.
88824
89227
  */
88825
- tileBudgetMb: number().int().min(0).max(1024)
89228
+ tileBudgetMb: number().int().min(0).max(1024),
89229
+ /**
89230
+ * RAM ceiling per decode worker, in MB, for the SCENE TILES — one
89231
+ * JPEG-encoded copy of the WHOLE native frame, cut in the same instant as the
89232
+ * subject tiles, on frames that detected something.
89233
+ *
89234
+ * It exists because a subject tile cannot answer a FULL-FRAME request:
89235
+ * containment is strict by design, so the native `keyFrame`, the detail
89236
+ * plane's `frameJpeg` rung and the display-crop fallback had no rung at all
89237
+ * below the hold. Measured on the live cluster: 23.3% of key-frame captures
89238
+ * missed, 95.7% of them with `worker-lease-gone` — the raster released one
89239
+ * frame-time after delivery, with the request only p50 367 ms behind it.
89240
+ *
89241
+ * Sizing, and why this is a budget and not a duration: a scene tile is
89242
+ * ~1.5-2.5 MB at 4K (against ~23.75 MB for the raster it was cut from and
89243
+ * ~60-120 KB for a subject tile), and it is cut ~0.4 times a second per busy
89244
+ * camera — only detection-bearing frames get one. 48 MB is therefore ~20-30
89245
+ * frames, i.e. the store's 30 s TTL binds at the steady state and the budget
89246
+ * binds only through a detection burst, where it still covers well past the
89247
+ * measured p90 ask age of 4.3 s. Holding the RASTERS for the same window
89248
+ * would be ~498 MB per camera and ~6 GB at peak concurrency — the OOM this
89249
+ * whole shape exists to avoid.
89250
+ *
89251
+ * A SEPARATE ceiling from `tileBudgetMb` on purpose: a scene tile is ~20× a
89252
+ * subject tile, so one shared budget would let a busy camera's key frames
89253
+ * evict the face/plate tiles the recognisers depend on. Two budgets make that
89254
+ * impossible rather than unlikely. `0` DISABLES scene tiles and restores the
89255
+ * pre-existing behaviour, where a late full-frame request had nothing but the
89256
+ * ≤640 RAM raster — which the `keyFrame` gate rejects, so in practice it had
89257
+ * nothing.
89258
+ */
89259
+ sceneBudgetMb: number().int().min(0).max(1024)
88826
89260
  });
88827
89261
  /**
88828
89262
  * The values in force when the operator has set nothing.
@@ -88838,12 +89272,14 @@ var DEFAULT_NATIVE_LEASE_SETTINGS = {
88838
89272
  budgetMb: 1024,
88839
89273
  activityMs: 15e3,
88840
89274
  tileBudgetMb: 64,
89275
+ sceneBudgetMb: 48,
88841
89276
  admission: "inferred"
88842
89277
  };
88843
89278
  DEFAULT_NATIVE_LEASE_SETTINGS.holdFrames;
88844
89279
  DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
88845
89280
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
88846
89281
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
89282
+ DEFAULT_NATIVE_LEASE_SETTINGS.sceneBudgetMb;
88847
89283
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
88848
89284
  var MB = 1024 * 1024;
88849
89285
  1024 * MB, 3072 * MB;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreame",
3
- "version": "0.2.31",
3
+ "version": "0.2.32",
4
4
  "description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
5
5
  "keywords": [
6
6
  "camstack",