@camstack/addon-post-analysis 1.2.636 → 1.2.638

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 (17) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +22 -0
  3. package/dist/{clip-model-registry-BV-RBus_.mjs → clip-model-registry-B-Dv5y3Z.mjs} +979 -515
  4. package/dist/{clip-model-registry-DgRbne5j.js → clip-model-registry-DEcrrqRz.js} +979 -515
  5. package/dist/embedding-encoder/index.js +1 -1
  6. package/dist/embedding-encoder/index.mjs +1 -1
  7. package/dist/pipeline-analytics/_stub.js +2 -2
  8. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Coin4M0B.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-LOOCKNGE.mjs} +2 -2
  9. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D4TYbuPV.mjs +26 -0
  10. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DXlqUmY1.mjs +26 -0
  11. package/dist/pipeline-analytics/{hostInit-c7viEcQt.mjs → hostInit-C2kfB4nI.mjs} +2 -2
  12. package/dist/pipeline-analytics/index.js +156 -22
  13. package/dist/pipeline-analytics/index.mjs +156 -22
  14. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  15. package/package.json +4 -2
  16. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-j6sD2jfA.mjs +0 -26
  17. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Bcrs7B9G.mjs +0 -26
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- //#region ../types/dist/event-category-DHXxB8t9.mjs
2
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -325,6 +325,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
325
325
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
326
326
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
327
327
  /**
328
+ * A camera's package LLM confirm has failed closed three times in a row
329
+ * (D931, 2026-10-26). One edge per camera until
330
+ * {@link PipelinePackageLlmAnswered}.
331
+ */
332
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
333
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
334
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
335
+ /**
328
336
  * Per-camera pipeline config was mutated by the orchestrator
329
337
  * (a settings change via `setAgentInferenceDevices` /
330
338
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5632,7 +5640,7 @@ var ZodIssueCode = {
5632
5640
  var ZodFirstPartyTypeKind;
5633
5641
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5634
5642
  //#endregion
5635
- //#region ../types/dist/sleep-BErAxhul.mjs
5643
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5636
5644
  /**
5637
5645
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5638
5646
  * window to float samples (D455).
@@ -8332,6 +8340,99 @@ function defineRecordKinds(specs) {
8332
8340
  }
8333
8341
  return out;
8334
8342
  }
8343
+ var READING_QUANTITIES = [
8344
+ "temperature",
8345
+ "pressure",
8346
+ "speed",
8347
+ "precipitation",
8348
+ "precipitation-rate",
8349
+ "length",
8350
+ "area",
8351
+ "illuminance",
8352
+ "irradiance",
8353
+ "power",
8354
+ "energy",
8355
+ "voltage",
8356
+ "current",
8357
+ "apparent-power",
8358
+ "humidity",
8359
+ "percentage",
8360
+ "concentration-volume",
8361
+ "concentration-mass",
8362
+ "concentration-ppb",
8363
+ "volume",
8364
+ "duration",
8365
+ "duration-ms",
8366
+ "information",
8367
+ "sound-level",
8368
+ "signal-level",
8369
+ "colour-temperature",
8370
+ "angle",
8371
+ "bitrate",
8372
+ "rate",
8373
+ "mass",
8374
+ "index",
8375
+ "ratio",
8376
+ "count",
8377
+ "none",
8378
+ "device"
8379
+ ];
8380
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8381
+ new Set(READING_QUANTITIES);
8382
+ /**
8383
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8384
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8385
+ */
8386
+ var QUANTITY_BASE = {
8387
+ temperature: "°C",
8388
+ pressure: "hPa",
8389
+ speed: "m/s",
8390
+ precipitation: "mm",
8391
+ "precipitation-rate": "mm/h",
8392
+ length: "m",
8393
+ area: "m²",
8394
+ illuminance: "lx",
8395
+ irradiance: "W/m²",
8396
+ power: "W",
8397
+ energy: "kWh",
8398
+ voltage: "V",
8399
+ current: "A",
8400
+ "apparent-power": "VA",
8401
+ humidity: "%",
8402
+ percentage: "%",
8403
+ "concentration-volume": "ppm",
8404
+ "concentration-mass": "µg/m³",
8405
+ "concentration-ppb": "ppb",
8406
+ volume: "L",
8407
+ duration: "s",
8408
+ "duration-ms": "ms",
8409
+ information: "B",
8410
+ "sound-level": "dBFS",
8411
+ "signal-level": "dBm",
8412
+ "colour-temperature": "mired",
8413
+ angle: "°",
8414
+ bitrate: "kbps",
8415
+ rate: "fps",
8416
+ mass: "kg",
8417
+ index: "none",
8418
+ ratio: "none",
8419
+ count: "none",
8420
+ none: "none",
8421
+ device: "device"
8422
+ };
8423
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8424
+ function baseUnitOf(quantity) {
8425
+ const symbol = QUANTITY_BASE[quantity];
8426
+ if (symbol === "none" || symbol === "device") return void 0;
8427
+ return symbol;
8428
+ }
8429
+ function readingMeta(quantity, extras) {
8430
+ return {
8431
+ quantity,
8432
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8433
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8434
+ };
8435
+ }
8335
8436
  /** Shared Zod schemas used across streaming capabilities. */
8336
8437
  var CamProfileSchema = _enum([
8337
8438
  "high",
@@ -8359,8 +8460,8 @@ var CamStreamKindSchema = _enum([
8359
8460
  "derived"
8360
8461
  ]);
8361
8462
  var CamStreamResolutionSchema = object({
8362
- width: number().int().positive(),
8363
- height: number().int().positive()
8463
+ width: number().int().positive().meta(readingMeta("none")),
8464
+ height: number().int().positive().meta(readingMeta("none"))
8364
8465
  });
8365
8466
  var CameraStreamSchema = object({
8366
8467
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8960,6 +9061,8 @@ var CAP_NAMES = Object.freeze([
8960
9061
  "filesystem-browse",
8961
9062
  "flood",
8962
9063
  "gas",
9064
+ "group-commands",
9065
+ "group-status",
8963
9066
  "heating-valve",
8964
9067
  "humidifier",
8965
9068
  "humidity-sensor",
@@ -9089,14 +9192,6 @@ Object.freeze({
9089
9192
  {
9090
9193
  "path": "aqi",
9091
9194
  "valueType": "number"
9092
- },
9093
- {
9094
- "path": "unit",
9095
- "valueType": "string"
9096
- },
9097
- {
9098
- "path": "precision",
9099
- "valueType": "number"
9100
9195
  }
9101
9196
  ],
9102
9197
  "alarm-panel": [
@@ -9132,20 +9227,10 @@ Object.freeze({
9132
9227
  "valueType": "boolean"
9133
9228
  }
9134
9229
  ],
9135
- "ambient-light-sensor": [
9136
- {
9137
- "path": "lux",
9138
- "valueType": "number"
9139
- },
9140
- {
9141
- "path": "unit",
9142
- "valueType": "string"
9143
- },
9144
- {
9145
- "path": "precision",
9146
- "valueType": "number"
9147
- }
9148
- ],
9230
+ "ambient-light-sensor": [{
9231
+ "path": "lux",
9232
+ "valueType": "number"
9233
+ }],
9149
9234
  "audio-metrics": [
9150
9235
  {
9151
9236
  "path": "ts",
@@ -9225,6 +9310,10 @@ Object.freeze({
9225
9310
  "path": "lastUpdated",
9226
9311
  "valueType": "number"
9227
9312
  },
9313
+ {
9314
+ "path": "volts",
9315
+ "valueType": "number"
9316
+ },
9228
9317
  {
9229
9318
  "path": "lastContactAt",
9230
9319
  "valueType": "number"
@@ -9467,10 +9556,6 @@ Object.freeze({
9467
9556
  "datetime"
9468
9557
  ]
9469
9558
  },
9470
- {
9471
- "path": "unit",
9472
- "valueType": "string"
9473
- },
9474
9559
  {
9475
9560
  "path": "min",
9476
9561
  "valueType": "number"
@@ -9482,19 +9567,6 @@ Object.freeze({
9482
9567
  {
9483
9568
  "path": "step",
9484
9569
  "valueType": "number"
9485
- },
9486
- {
9487
- "path": "precision",
9488
- "valueType": "number"
9489
- },
9490
- {
9491
- "path": "format",
9492
- "valueType": "enum",
9493
- "enumValues": [
9494
- "date",
9495
- "time",
9496
- "datetime"
9497
- ]
9498
9570
  }
9499
9571
  ],
9500
9572
  "cover": [
@@ -9735,13 +9807,8 @@ Object.freeze({
9735
9807
  "path": "value",
9736
9808
  "valueType": "string"
9737
9809
  }, {
9738
- "path": "format",
9739
- "valueType": "enum",
9740
- "enumValues": [
9741
- "date",
9742
- "time",
9743
- "datetime"
9744
- ]
9810
+ "path": "timestamp",
9811
+ "valueType": "boolean"
9745
9812
  }],
9746
9813
  "event-emitter": [
9747
9814
  {
@@ -9814,6 +9881,20 @@ Object.freeze({
9814
9881
  "path": "detected",
9815
9882
  "valueType": "boolean"
9816
9883
  }],
9884
+ "group-status": [
9885
+ {
9886
+ "path": "included",
9887
+ "valueType": "number"
9888
+ },
9889
+ {
9890
+ "path": "excluded",
9891
+ "valueType": "number"
9892
+ },
9893
+ {
9894
+ "path": "total",
9895
+ "valueType": "number"
9896
+ }
9897
+ ],
9817
9898
  "heating-valve": [{
9818
9899
  "path": "position",
9819
9900
  "valueType": "number"
@@ -9848,20 +9929,10 @@ Object.freeze({
9848
9929
  "valueType": "number"
9849
9930
  }
9850
9931
  ],
9851
- "humidity-sensor": [
9852
- {
9853
- "path": "percent",
9854
- "valueType": "number"
9855
- },
9856
- {
9857
- "path": "unit",
9858
- "valueType": "string"
9859
- },
9860
- {
9861
- "path": "precision",
9862
- "valueType": "number"
9863
- }
9864
- ],
9932
+ "humidity-sensor": [{
9933
+ "path": "percent",
9934
+ "valueType": "number"
9935
+ }],
9865
9936
  "image": [{
9866
9937
  "path": "url",
9867
9938
  "valueType": "string"
@@ -10200,20 +10271,10 @@ Object.freeze({
10200
10271
  "valueType": "boolean"
10201
10272
  }
10202
10273
  ],
10203
- "numeric-sensor": [
10204
- {
10205
- "path": "value",
10206
- "valueType": "number"
10207
- },
10208
- {
10209
- "path": "unit",
10210
- "valueType": "string"
10211
- },
10212
- {
10213
- "path": "precision",
10214
- "valueType": "number"
10215
- }
10216
- ],
10274
+ "numeric-sensor": [{
10275
+ "path": "value",
10276
+ "valueType": "number"
10277
+ }],
10217
10278
  "pet-feeder": [
10218
10279
  {
10219
10280
  "path": "foodLevel",
@@ -10305,14 +10366,6 @@ Object.freeze({
10305
10366
  {
10306
10367
  "path": "powerFactor",
10307
10368
  "valueType": "number"
10308
- },
10309
- {
10310
- "path": "unit",
10311
- "valueType": "string"
10312
- },
10313
- {
10314
- "path": "precision",
10315
- "valueType": "number"
10316
10369
  }
10317
10370
  ],
10318
10371
  "presence": [
@@ -10341,20 +10394,10 @@ Object.freeze({
10341
10394
  "valueType": "number"
10342
10395
  }
10343
10396
  ],
10344
- "pressure-sensor": [
10345
- {
10346
- "path": "hpa",
10347
- "valueType": "number"
10348
- },
10349
- {
10350
- "path": "unit",
10351
- "valueType": "string"
10352
- },
10353
- {
10354
- "path": "precision",
10355
- "valueType": "number"
10356
- }
10357
- ],
10397
+ "pressure-sensor": [{
10398
+ "path": "hpa",
10399
+ "valueType": "number"
10400
+ }],
10358
10401
  "privacy-mask": [{
10359
10402
  "path": "enabled",
10360
10403
  "valueType": "boolean"
@@ -10665,20 +10708,10 @@ Object.freeze({
10665
10708
  "path": "tampered",
10666
10709
  "valueType": "boolean"
10667
10710
  }],
10668
- "temperature-sensor": [
10669
- {
10670
- "path": "celsius",
10671
- "valueType": "number"
10672
- },
10673
- {
10674
- "path": "unit",
10675
- "valueType": "string"
10676
- },
10677
- {
10678
- "path": "precision",
10679
- "valueType": "number"
10680
- }
10681
- ],
10711
+ "temperature-sensor": [{
10712
+ "path": "celsius",
10713
+ "valueType": "number"
10714
+ }],
10682
10715
  "update": [
10683
10716
  {
10684
10717
  "path": "currentVersion",
@@ -10789,10 +10822,6 @@ Object.freeze({
10789
10822
  "path": "temperature",
10790
10823
  "valueType": "number"
10791
10824
  },
10792
- {
10793
- "path": "temperatureUnit",
10794
- "valueType": "string"
10795
- },
10796
10825
  {
10797
10826
  "path": "humidity",
10798
10827
  "valueType": "number"
@@ -10801,18 +10830,10 @@ Object.freeze({
10801
10830
  "path": "pressure",
10802
10831
  "valueType": "number"
10803
10832
  },
10804
- {
10805
- "path": "pressureUnit",
10806
- "valueType": "string"
10807
- },
10808
10833
  {
10809
10834
  "path": "windSpeed",
10810
10835
  "valueType": "number"
10811
10836
  },
10812
- {
10813
- "path": "windSpeedUnit",
10814
- "valueType": "string"
10815
- },
10816
10837
  {
10817
10838
  "path": "windBearing",
10818
10839
  "valueType": "number"
@@ -10864,7 +10885,7 @@ Object.freeze({
10864
10885
  * can answer "may a rule actuate this?" without importing the schema barrel
10865
10886
  * (~144MB RSS per runner, D28).
10866
10887
  *
10867
- * Coverage: 91 device-scoped capabilities.
10888
+ * Coverage: 93 device-scoped capabilities.
10868
10889
  */
10869
10890
  var DEVICE_SCOPED_CAPS = new Set([
10870
10891
  "accessories",
@@ -10905,6 +10926,8 @@ var DEVICE_SCOPED_CAPS = new Set([
10905
10926
  "feature-probe",
10906
10927
  "flood",
10907
10928
  "gas",
10929
+ "group-commands",
10930
+ "group-status",
10908
10931
  "heating-valve",
10909
10932
  "humidifier",
10910
10933
  "humidity-sensor",
@@ -11356,7 +11379,7 @@ function errMsg(err) {
11356
11379
  return String(err);
11357
11380
  }
11358
11381
  //#endregion
11359
- //#region ../types/dist/composition-kEl9mevu.mjs
11382
+ //#region ../types/dist/composition-OhpuespA.mjs
11360
11383
  /**
11361
11384
  * Deterministic SHA-256 hash of an arbitrary serialisable value. The
11362
11385
  * canonical form sorts object keys alphabetically at every depth so two
@@ -12271,15 +12294,74 @@ function validateExpressionSource(src) {
12271
12294
  }
12272
12295
  return null;
12273
12296
  }
12274
- /** A composed device's stableId is this prefix plus the block id. */
12275
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
12276
- /** Longest snippet a `code` source may carry (slice 4). */
12277
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
12297
+ /**
12298
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
12299
+ * id. Split out so helper schemas can name it without a cycle through
12300
+ * `composition.ts` (D659).
12301
+ */
12278
12302
  var CompositionSourceRefSchema = object({
12279
12303
  /** The owning addon; `stableId` is unique only within it. */
12280
12304
  addonId: string().min(1),
12281
12305
  stableId: string().min(1)
12282
12306
  });
12307
+ var GROUP_TEMPLATE_IDS = [
12308
+ "temperature",
12309
+ "humidity",
12310
+ "pressure",
12311
+ "illuminance",
12312
+ "power",
12313
+ "energy",
12314
+ "battery",
12315
+ "numeric",
12316
+ "motion",
12317
+ "contact",
12318
+ "leak",
12319
+ "smoke",
12320
+ "occupancy",
12321
+ "binary",
12322
+ "switch",
12323
+ "light"
12324
+ ];
12325
+ var NUMERIC_AGGREGATES = [
12326
+ "mean",
12327
+ "median",
12328
+ "min",
12329
+ "max",
12330
+ "sum"
12331
+ ];
12332
+ var BINARY_AGGREGATES = ["any", "all"];
12333
+ /** How an actuator group decides it is on. Unknown members never count as off. */
12334
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
12335
+ var AGGREGATE_REDUCERS = [
12336
+ "mean",
12337
+ "median",
12338
+ "min",
12339
+ "max",
12340
+ "sum",
12341
+ "any",
12342
+ "all"
12343
+ ];
12344
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
12345
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
12346
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
12347
+ /** One member. The field is stored so occupancy can name motion or binary. */
12348
+ var HelperMemberSchema = object({
12349
+ source: CompositionSourceRefSchema,
12350
+ cap: string().min(1),
12351
+ fieldPath: string().min(1)
12352
+ });
12353
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
12354
+ kind: literal("group"),
12355
+ templateId: GroupTemplateIdSchema,
12356
+ aggregate: AggregateReducerSchema,
12357
+ members: array(HelperMemberSchema).min(1).max(64),
12358
+ /** Actuator groups only. Readings leave this unset. */
12359
+ active: ActiveCriterionSchema.optional()
12360
+ })]);
12361
+ /** A composed device's stableId is this prefix plus the block id. */
12362
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
12363
+ /** Longest snippet a `code` source may carry (slice 4). */
12364
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
12283
12365
  /** One field of one capability of one source device. */
12284
12366
  var CompositionFieldReadSchema = object({
12285
12367
  source: CompositionSourceRefSchema,
@@ -12323,7 +12405,33 @@ var CompositionCodeSourceSchema = object({
12323
12405
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12324
12406
  CompositionFromSourceSchema,
12325
12407
  CompositionExpressionSourceSchema,
12326
- CompositionCodeSourceSchema
12408
+ CompositionCodeSourceSchema,
12409
+ object({
12410
+ kind: literal("aggregate"),
12411
+ reducer: _enum([
12412
+ "mean",
12413
+ "median",
12414
+ "min",
12415
+ "max",
12416
+ "sum",
12417
+ "any",
12418
+ "all"
12419
+ ]),
12420
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
12421
+ /** `value` is the reading; the others are the member counts. */
12422
+ publish: _enum([
12423
+ "value",
12424
+ "included",
12425
+ "excluded",
12426
+ "total"
12427
+ ]),
12428
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
12429
+ empty: _enum([
12430
+ "null",
12431
+ "omit",
12432
+ "offline"
12433
+ ])
12434
+ })
12327
12435
  ]);
12328
12436
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
12329
12437
  kind: literal("forward"),
@@ -12369,7 +12477,13 @@ var CompositionSchema = object({
12369
12477
  kind: literal("existing"),
12370
12478
  device: CompositionSourceRefSchema
12371
12479
  })]),
12372
- features: array(CompositionFeatureSchema).min(1).max(16)
12480
+ features: array(CompositionFeatureSchema).min(1).max(16),
12481
+ /**
12482
+ * Present when the block is a helper template. Absent on a free
12483
+ * composition (the room example, device 4603). The editor reads this,
12484
+ * never the compiled features.
12485
+ */
12486
+ helper: HelperSpecSchema.optional()
12373
12487
  }).superRefine((composition, ctx) => {
12374
12488
  const seen = /* @__PURE__ */ new Set();
12375
12489
  composition.features.forEach((feature, index) => {
@@ -19360,7 +19474,11 @@ var CompositionProblemSchema = object({
19360
19474
  "item-field-reserved",
19361
19475
  "target-missing",
19362
19476
  "target-unreadable",
19363
- "not-replaceable"
19477
+ "not-replaceable",
19478
+ "native-capability-replace",
19479
+ "mixed-quantity",
19480
+ "helper-mismatch",
19481
+ "member-constraint"
19364
19482
  ]),
19365
19483
  path: string(),
19366
19484
  message: string()
@@ -19381,7 +19499,8 @@ var CompositionUnavailableModeSchema = _enum([
19381
19499
  "null",
19382
19500
  "offline",
19383
19501
  "unknown",
19384
- "release"
19502
+ "release",
19503
+ "omit"
19385
19504
  ]);
19386
19505
  /**
19387
19506
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -19418,9 +19537,11 @@ var CompositionFieldPlanSchema = object({
19418
19537
  reads: array(CompositionFieldReadSchema)
19419
19538
  });
19420
19539
  /**
19421
- * What a feature does to its target (D663): `new` builds a capability on a new
19422
- * device; on an existing device it `add`s a capability the device lacks, or
19423
- * `replace`s named fields of one it has natively.
19540
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
19541
+ * new device; on an existing device it `add`s a capability the device lacks.
19542
+ * Replacing a capability the device already serves is refused
19543
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
19544
+ * wire for a claim the hub already holds.
19424
19545
  */
19425
19546
  var CompositionFeatureModeSchema = _enum([
19426
19547
  "new",
@@ -19593,6 +19714,275 @@ Object.freeze({
19593
19714
  result: ["number", "null"]
19594
19715
  }
19595
19716
  });
19717
+ /**
19718
+ * One catalog row per base reading a group helper can build (D944 §3).
19719
+ * Data, not a form default written into the block.
19720
+ */
19721
+ var NOW = {
19722
+ fieldPath: "lastFetchedAt",
19723
+ expr: "now"
19724
+ };
19725
+ var CHANGED = {
19726
+ fieldPath: "lastChangedAt",
19727
+ expr: "now"
19728
+ };
19729
+ function numeric(input) {
19730
+ return {
19731
+ ...input,
19732
+ family: "numeric",
19733
+ deviceType: DeviceType.Sensor,
19734
+ memberDeviceType: DeviceType.Sensor,
19735
+ commandCaps: [],
19736
+ baseUnit: baseUnitOf(input.quantity),
19737
+ aggregates: NUMERIC_AGGREGATES,
19738
+ accept: [{
19739
+ cap: input.outputCap,
19740
+ fieldPath: input.outputField
19741
+ }]
19742
+ };
19743
+ }
19744
+ function binary(input) {
19745
+ return {
19746
+ ...input,
19747
+ family: "binary",
19748
+ deviceType: DeviceType.Sensor,
19749
+ memberDeviceType: DeviceType.Sensor,
19750
+ commandCaps: [],
19751
+ quantity: "none",
19752
+ baseUnit: void 0,
19753
+ aggregates: BINARY_AGGREGATES,
19754
+ empty: "offline"
19755
+ };
19756
+ }
19757
+ var GROUP_TEMPLATES = [
19758
+ numeric({
19759
+ id: "temperature",
19760
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
19761
+ role: DeviceRole.TemperatureSensor,
19762
+ outputCap: "temperature-sensor",
19763
+ outputField: "celsius",
19764
+ quantity: "temperature",
19765
+ defaultAggregate: "mean",
19766
+ empty: "null",
19767
+ companions: [NOW]
19768
+ }),
19769
+ numeric({
19770
+ id: "humidity",
19771
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
19772
+ role: DeviceRole.HumiditySensor,
19773
+ outputCap: "humidity-sensor",
19774
+ outputField: "percent",
19775
+ quantity: "humidity",
19776
+ defaultAggregate: "mean",
19777
+ empty: "null",
19778
+ companions: [NOW]
19779
+ }),
19780
+ numeric({
19781
+ id: "pressure",
19782
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
19783
+ role: DeviceRole.PressureSensor,
19784
+ outputCap: "pressure-sensor",
19785
+ outputField: "hpa",
19786
+ quantity: "pressure",
19787
+ defaultAggregate: "mean",
19788
+ empty: "null",
19789
+ companions: [NOW]
19790
+ }),
19791
+ numeric({
19792
+ id: "illuminance",
19793
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
19794
+ role: DeviceRole.AmbientLightSensor,
19795
+ outputCap: "ambient-light-sensor",
19796
+ outputField: "lux",
19797
+ quantity: "illuminance",
19798
+ defaultAggregate: "mean",
19799
+ empty: "null",
19800
+ companions: [NOW]
19801
+ }),
19802
+ numeric({
19803
+ id: "power",
19804
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
19805
+ role: DeviceRole.PowerSensor,
19806
+ outputCap: "power-meter",
19807
+ outputField: "watts",
19808
+ quantity: "power",
19809
+ defaultAggregate: "sum",
19810
+ empty: "omit",
19811
+ companions: [NOW]
19812
+ }),
19813
+ numeric({
19814
+ id: "energy",
19815
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
19816
+ role: DeviceRole.EnergySensor,
19817
+ outputCap: "power-meter",
19818
+ outputField: "kwhTotal",
19819
+ quantity: "energy",
19820
+ defaultAggregate: "sum",
19821
+ empty: "omit",
19822
+ companions: [NOW]
19823
+ }),
19824
+ numeric({
19825
+ id: "battery",
19826
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
19827
+ role: DeviceRole.BatterySensor,
19828
+ outputCap: "battery",
19829
+ outputField: "percentage",
19830
+ quantity: "percentage",
19831
+ defaultAggregate: "mean",
19832
+ empty: "null",
19833
+ companions: [
19834
+ {
19835
+ fieldPath: "charging",
19836
+ expr: "\"unknown\""
19837
+ },
19838
+ {
19839
+ fieldPath: "sleeping",
19840
+ expr: "false"
19841
+ },
19842
+ {
19843
+ fieldPath: "lastUpdated",
19844
+ expr: "now"
19845
+ }
19846
+ ]
19847
+ }),
19848
+ numeric({
19849
+ id: "numeric",
19850
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
19851
+ role: DeviceRole.NumericSensor,
19852
+ outputCap: "numeric-sensor",
19853
+ outputField: "value",
19854
+ quantity: "device",
19855
+ defaultAggregate: "mean",
19856
+ empty: "null",
19857
+ companions: [NOW]
19858
+ }),
19859
+ binary({
19860
+ id: "motion",
19861
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
19862
+ role: DeviceRole.MotionSensor,
19863
+ outputCap: "motion",
19864
+ outputField: "detected",
19865
+ defaultAggregate: "any",
19866
+ accept: [{
19867
+ cap: "motion",
19868
+ fieldPath: "detected"
19869
+ }],
19870
+ companions: [{
19871
+ fieldPath: "lastDetectedAt",
19872
+ expr: "null"
19873
+ }, {
19874
+ fieldPath: "autoClearAfterMs",
19875
+ expr: "null"
19876
+ }]
19877
+ }),
19878
+ binary({
19879
+ id: "contact",
19880
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
19881
+ role: DeviceRole.ContactSensor,
19882
+ outputCap: "contact",
19883
+ outputField: "entryOpen",
19884
+ defaultAggregate: "any",
19885
+ accept: [{
19886
+ cap: "contact",
19887
+ fieldPath: "entryOpen"
19888
+ }],
19889
+ companions: [CHANGED]
19890
+ }),
19891
+ binary({
19892
+ id: "leak",
19893
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
19894
+ role: DeviceRole.LeakSensor,
19895
+ outputCap: "flood",
19896
+ outputField: "flooded",
19897
+ defaultAggregate: "any",
19898
+ accept: [{
19899
+ cap: "flood",
19900
+ fieldPath: "flooded"
19901
+ }],
19902
+ companions: [CHANGED]
19903
+ }),
19904
+ binary({
19905
+ id: "smoke",
19906
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
19907
+ role: DeviceRole.SmokeSensor,
19908
+ outputCap: "smoke",
19909
+ outputField: "detected",
19910
+ defaultAggregate: "any",
19911
+ accept: [{
19912
+ cap: "smoke",
19913
+ fieldPath: "detected"
19914
+ }],
19915
+ companions: [CHANGED]
19916
+ }),
19917
+ binary({
19918
+ id: "occupancy",
19919
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
19920
+ role: DeviceRole.BinarySensor,
19921
+ outputCap: "binary",
19922
+ outputField: "on",
19923
+ defaultAggregate: "any",
19924
+ accept: [{
19925
+ cap: "motion",
19926
+ fieldPath: "detected"
19927
+ }, {
19928
+ cap: "binary",
19929
+ fieldPath: "on"
19930
+ }],
19931
+ companions: [CHANGED]
19932
+ }),
19933
+ binary({
19934
+ id: "binary",
19935
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
19936
+ role: DeviceRole.BinarySensor,
19937
+ outputCap: "binary",
19938
+ outputField: "on",
19939
+ defaultAggregate: "any",
19940
+ accept: [{
19941
+ cap: "binary",
19942
+ fieldPath: "on"
19943
+ }],
19944
+ companions: [CHANGED]
19945
+ }),
19946
+ actuator({
19947
+ id: "switch",
19948
+ summary: "On when at least one switch is on, or when all of them are. Offline and unknown members are left out of that decision, never read as off, and still receive the command.",
19949
+ deviceType: DeviceType.Switch,
19950
+ outputCap: "switch",
19951
+ commandCaps: ["switch"]
19952
+ }),
19953
+ actuator({
19954
+ id: "light",
19955
+ summary: "On when at least one light is on, or when all of them are. Brightness and colour are offered only when every member has them. A command is not sent to a member that lacks it.",
19956
+ deviceType: DeviceType.Light,
19957
+ outputCap: "switch",
19958
+ commandCaps: [
19959
+ "switch",
19960
+ "brightness",
19961
+ "color"
19962
+ ]
19963
+ })
19964
+ ];
19965
+ function actuator(input) {
19966
+ return {
19967
+ ...input,
19968
+ family: "actuator",
19969
+ role: void 0,
19970
+ memberDeviceType: input.deviceType,
19971
+ outputField: "on",
19972
+ quantity: "none",
19973
+ baseUnit: void 0,
19974
+ aggregates: BINARY_AGGREGATES,
19975
+ defaultAggregate: "any",
19976
+ empty: "offline",
19977
+ accept: [{
19978
+ cap: "switch",
19979
+ fieldPath: "on"
19980
+ }],
19981
+ companions: [CHANGED]
19982
+ };
19983
+ }
19984
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
19985
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
19596
19986
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
19597
19987
  /**
19598
19988
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -21313,12 +21703,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
21313
21703
  * providers detect renames by matching `uniqueId` and patch `id`
21314
21704
  * in-place so dispatch keeps working.
21315
21705
  *
21316
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
21317
- * `precision`) live in the cap STATUS SLICE so they flow live with every
21318
- * state push and are the single source of truth. The `device_class` tag is
21319
- * an HA-specific input that is consumed at the discovery boundary
21320
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
21321
- * stored after that point.
21706
+ * **Display unit and precision are NOT stored here.** They are a read-time
21707
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
21708
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
21709
+ * neither is repeated on each sample. The `device_class` tag is an
21710
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
21711
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
21322
21712
  *
21323
21713
  * The `raw` escape hatch carries provider-specific attributes that don't
21324
21714
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -21332,6 +21722,14 @@ var SourceInfoSchema = object({
21332
21722
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
21333
21723
  * Used to detect rename when `id` changes. */
21334
21724
  uniqueId: string().optional(),
21725
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
21726
+ quantity: ReadingQuantitySchema.optional(),
21727
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
21728
+ dateTimeShape: _enum([
21729
+ "date",
21730
+ "time",
21731
+ "datetime"
21732
+ ]).optional(),
21335
21733
  /** Provider-specific extras that don't fit the structured slots.
21336
21734
  * Whitelist what goes here per provider — do NOT dump entire HA
21337
21735
  * attribute blobs (would explode the meta JSON column). */
@@ -42359,7 +42757,8 @@ method(object({
42359
42757
  "enum"
42360
42758
  ]),
42361
42759
  enumValues: array(string()).optional(),
42362
- item: boolean().optional()
42760
+ item: boolean().optional(),
42761
+ quantity: ReadingQuantitySchema.optional()
42363
42762
  })).readonly(),
42364
42763
  itemArray: object({
42365
42764
  path: string(),
@@ -46426,8 +46825,8 @@ var MaskPolygonShapeSchema = object({
46426
46825
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
46427
46826
  var MaskGridShapeSchema = object({
46428
46827
  kind: literal("grid"),
46429
- gridWidth: number(),
46430
- gridHeight: number(),
46828
+ gridWidth: number().meta(readingMeta("none")),
46829
+ gridHeight: number().meta(readingMeta("none")),
46431
46830
  cells: array(boolean())
46432
46831
  });
46433
46832
  discriminatedUnion("kind", [
@@ -46539,6 +46938,8 @@ var NcSystemEventKindSchema = _enum([
46539
46938
  "detection-blind",
46540
46939
  "motion-noisy",
46541
46940
  "motion-calm",
46941
+ "package-llm-silent",
46942
+ "package-llm-answered",
46542
46943
  "recording-stalled",
46543
46944
  "recording-healthy",
46544
46945
  "recording-degraded",
@@ -48101,6 +48502,14 @@ var NC_CONDITION_CATALOG = [
48101
48502
  value: "motion-calm",
48102
48503
  label: "Camera motion calm again"
48103
48504
  },
48505
+ {
48506
+ value: "package-llm-silent",
48507
+ label: "Package confirm not answering"
48508
+ },
48509
+ {
48510
+ value: "package-llm-answered",
48511
+ label: "Package confirm answering again"
48512
+ },
48104
48513
  {
48105
48514
  value: "recording-stalled",
48106
48515
  label: "Recording stalled"
@@ -59476,6 +59885,28 @@ var PackageVersionInfoSchema = object({
59476
59885
  deprecated: string().optional(),
59477
59886
  distTags: array(string()).readonly()
59478
59887
  });
59888
+ /**
59889
+ * One published document. `ok` is markdown from the registry. `missing` means
59890
+ * the registry answered and the file was not there. `error` is a registry or
59891
+ * tarball failure — never an empty string standing in for either.
59892
+ */
59893
+ var PackageDocBodySchema = discriminatedUnion("status", [
59894
+ object({
59895
+ status: literal("ok"),
59896
+ markdown: string()
59897
+ }),
59898
+ object({ status: literal("missing") }),
59899
+ object({
59900
+ status: literal("error"),
59901
+ message: string()
59902
+ })
59903
+ ]);
59904
+ var PackageDocsSchema = object({
59905
+ name: string(),
59906
+ version: string(),
59907
+ readme: PackageDocBodySchema,
59908
+ changelog: PackageDocBodySchema
59909
+ });
59479
59910
  var SearchResultSchema = object({
59480
59911
  name: string(),
59481
59912
  version: string(),
@@ -59694,7 +60125,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
59694
60125
  }), object({ success: literal(true) }), {
59695
60126
  kind: "mutation",
59696
60127
  auth: "admin"
59697
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
60128
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
60129
+ name: string().min(1),
60130
+ version: string().min(1).optional()
60131
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
59698
60132
  kind: "mutation",
59699
60133
  auth: "admin"
59700
60134
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -59753,27 +60187,17 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
59753
60187
  */
59754
60188
  var AirQualitySensorStatusSchema = object({
59755
60189
  /** Carbon dioxide concentration in ppm. */
59756
- co2Ppm: number().min(0).optional(),
60190
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
59757
60191
  /** Total volatile organic compounds in ppb. */
59758
- vocPpb: number().min(0).optional(),
60192
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
59759
60193
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
59760
- pm25: number().min(0).optional(),
60194
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
59761
60195
  /** Particulate matter ≤ 10 μm in µg/m³. */
59762
- pm10: number().min(0).optional(),
60196
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
59763
60197
  /** Composite AQI value (typically 0..500). */
59764
- aqi: number().optional(),
60198
+ aqi: number().optional().meta(readingMeta("index")),
59765
60199
  /** Ms epoch when the slice was last updated. */
59766
- lastFetchedAt: number(),
59767
- /** Live display unit of the single metric this slice carries (e.g. HA
59768
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
59769
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
59770
- * per slice is unambiguous. */
59771
- unit: string().optional(),
59772
- /** Suggested decimal places for numeric display.
59773
- * Populated live from the upstream source when provided (e.g. HA
59774
- * `attributes.suggested_display_precision`). Falls back to
59775
- * auto-formatting when absent. */
59776
- precision: number().int().min(0).max(10).optional()
60200
+ lastFetchedAt: number()
59777
60201
  });
59778
60202
  var airQualitySensorCapability = {
59779
60203
  name: "air-quality-sensor",
@@ -59810,12 +60234,12 @@ var airQualitySensorCapability = {
59810
60234
  },
59811
60235
  pm25: {
59812
60236
  kind: "gauge",
59813
- unit: "ug/m3",
60237
+ unit: "µg/m³",
59814
60238
  deadband: 1
59815
60239
  },
59816
60240
  pm10: {
59817
60241
  kind: "gauge",
59818
- unit: "ug/m3",
60242
+ unit: "µg/m³",
59819
60243
  deadband: 1
59820
60244
  },
59821
60245
  aqi: {
@@ -59833,22 +60257,13 @@ var airQualitySensorCapability = {
59833
60257
  * entries with `device_class: illuminance`.
59834
60258
  */
59835
60259
  var AmbientLightSensorStatusSchema = object({
59836
- /** Current illuminance in lux (lx). */
59837
- lux: number().min(0).meta({
59838
- unit: "lx",
60260
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
60261
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
60262
+ precision: 0,
59839
60263
  label: "Illuminance"
59840
- }),
60264
+ })),
59841
60265
  /** Ms epoch when the slice was last updated. */
59842
- lastFetchedAt: number(),
59843
- /** Live display unit from the upstream source (e.g. HA
59844
- * `attributes.unit_of_measurement`). The UI prefers this over the
59845
- * role's canonical unit. Absent → fall back to the canonical unit. */
59846
- unit: string().optional(),
59847
- /** Suggested decimal places for numeric display.
59848
- * Populated live from the upstream source when provided (e.g. HA
59849
- * `attributes.suggested_display_precision`). Falls back to
59850
- * auto-formatting when absent. */
59851
- precision: number().int().min(0).max(10).optional()
60266
+ lastFetchedAt: number()
59852
60267
  });
59853
60268
  var ambientLightSensorCapability = {
59854
60269
  name: "ambient-light-sensor",
@@ -59880,12 +60295,7 @@ var ambientLightSensorCapability = {
59880
60295
  } } },
59881
60296
  /** Clock fields: written, but excluded from the compare that decides
59882
60297
  * whether persisting is worth a SQLite commit. */
59883
- volatileStateFields: ["lastFetchedAt"],
59884
- customize: {
59885
- version: 1,
59886
- advanced: "derive",
59887
- targets: []
59888
- }
60298
+ volatileStateFields: ["lastFetchedAt"]
59889
60299
  };
59890
60300
  /**
59891
60301
  * Per-class audio metrics aggregated over a sliding window.
@@ -60112,10 +60522,7 @@ var BatteryStatusSchema = object({
60112
60522
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
60113
60523
  * declines to band a non-finite reading, which is the correct shape.
60114
60524
  */
60115
- percentage: number().min(0).max(100).nullable().meta({
60116
- unit: "%",
60117
- label: "Charge"
60118
- }),
60525
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
60119
60526
  /**
60120
60527
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
60121
60528
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -60142,7 +60549,14 @@ var BatteryStatusSchema = object({
60142
60549
  */
60143
60550
  sleeping: boolean().meta({ label: "Sleeping" }),
60144
60551
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60145
- lastUpdated: number(),
60552
+ lastUpdated: number().meta(readingMeta("none")),
60553
+ /**
60554
+ * Battery voltage in volts, for a cell the firmware reports as voltage
60555
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
60556
+ * that only report charge. A second reading on this cap, not a second
60557
+ * `numeric-sensor` on the same device.
60558
+ */
60559
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
60146
60560
  /**
60147
60561
  * Ms epoch of the last time the device PROVED it was reachable — a
60148
60562
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -60194,7 +60608,6 @@ var BATTERY_DEVICE_TYPES = [
60194
60608
  /** Generic Customize target for the percentage reading (D936). */
60195
60609
  var batteryCustomize = {
60196
60610
  version: 1,
60197
- advanced: "derive",
60198
60611
  targets: [{
60199
60612
  id: "percentage",
60200
60613
  cardinality: "scalar",
@@ -60203,7 +60616,7 @@ var batteryCustomize = {
60203
60616
  stateField: "percentage"
60204
60617
  },
60205
60618
  deviceTypes: BATTERY_DEVICE_TYPES,
60206
- apply: "add-or-replace",
60619
+ apply: "add",
60207
60620
  path: "percentage",
60208
60621
  sources: [{
60209
60622
  kind: "field",
@@ -60324,10 +60737,16 @@ onStatusChanged: { data: object({
60324
60737
  /**
60325
60738
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
60326
60739
  */
60327
- history: { fields: { percentage: {
60328
- kind: "gauge",
60329
- unit: "%"
60330
- } } },
60740
+ history: { fields: {
60741
+ percentage: {
60742
+ kind: "gauge",
60743
+ unit: "%"
60744
+ },
60745
+ volts: {
60746
+ kind: "gauge",
60747
+ unit: "V"
60748
+ }
60749
+ } },
60331
60750
  customize: batteryCustomize,
60332
60751
  /** Clock fields: written, but excluded from the compare that decides
60333
60752
  * whether persisting is worth a SQLite commit. */
@@ -60374,12 +60793,7 @@ var binaryCapability = {
60374
60793
  history: { fields: { on: { kind: "boolean" } } },
60375
60794
  /** Clock fields: written, but excluded from the compare that decides
60376
60795
  * whether persisting is worth a SQLite commit. */
60377
- volatileStateFields: ["lastChangedAt"],
60378
- customize: {
60379
- version: 1,
60380
- advanced: "derive",
60381
- targets: []
60382
- }
60796
+ volatileStateFields: ["lastChangedAt"]
60383
60797
  };
60384
60798
  /**
60385
60799
  * Dimmable-light brightness control. Co-exists with `switch` on the
@@ -60395,7 +60809,7 @@ var binaryCapability = {
60395
60809
  */
60396
60810
  var BrightnessStatusSchema = object({
60397
60811
  /** Current level as 0..100 inclusive. Firmware-reported. */
60398
- percentage: number().min(0).max(100),
60812
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
60399
60813
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
60400
60814
  lastChangedAt: number()
60401
60815
  });
@@ -60876,21 +61290,21 @@ var ClimateControlStatusSchema = object({
60876
61290
  /** Single setpoint in Celsius. Used by single-target modes
60877
61291
  * (heat / cool / dry). Null when the device is in a range mode
60878
61292
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
60879
- target: number().nullable(),
61293
+ target: number().nullable().meta(readingMeta("temperature")),
60880
61294
  /** Upper bound of the dual setpoint in Celsius. Populated only
60881
61295
  * in `heat_cool` mode. */
60882
- targetHigh: number().nullable(),
61296
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
60883
61297
  /** Lower bound of the dual setpoint in Celsius. Populated only
60884
61298
  * in `heat_cool` mode. */
60885
- targetLow: number().nullable(),
61299
+ targetLow: number().nullable().meta(readingMeta("temperature")),
60886
61300
  /** Target relative humidity (0..100). Null when the device has no
60887
61301
  * humidity control. */
60888
- targetHumidity: number().min(0).max(100).nullable(),
61302
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
60889
61303
  /** Current measured temperature in Celsius. Read-only — pushed by
60890
61304
  * the device's internal sensor. */
60891
- currentTemp: number().nullable(),
61305
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
60892
61306
  /** Current measured relative humidity (0..100). Read-only. */
60893
- currentHumidity: number().min(0).max(100).nullable(),
61307
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
60894
61308
  /** Vertical louver swing on/off. Null when the device has no vertical
60895
61309
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
60896
61310
  swingVertical: boolean().nullable(),
@@ -60898,11 +61312,11 @@ var ClimateControlStatusSchema = object({
60898
61312
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
60899
61313
  swingHorizontal: boolean().nullable(),
60900
61314
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
60901
- minTemp: number().nullable().optional(),
61315
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
60902
61316
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
60903
- maxTemp: number().nullable().optional(),
61317
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
60904
61318
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
60905
- tempStep: number().positive().nullable().optional(),
61319
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
60906
61320
  /** Ms epoch when the slice was last updated (push or command). */
60907
61321
  lastFetchedAt: number()
60908
61322
  });
@@ -61044,17 +61458,17 @@ var climateControlCapability = {
61044
61458
  * modes to expose in the picker without a slice fetch.
61045
61459
  */
61046
61460
  var RgbTripletSchema = object({
61047
- r: number().int().min(0).max(255),
61048
- g: number().int().min(0).max(255),
61049
- b: number().int().min(0).max(255)
61461
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
61462
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
61463
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
61050
61464
  });
61051
61465
  var HsvTripletSchema = object({
61052
61466
  /** Hue in degrees, 0..360. */
61053
- h: number().min(0).max(360),
61467
+ h: number().min(0).max(360).meta(readingMeta("none")),
61054
61468
  /** Saturation as 0..100 inclusive. */
61055
- s: number().min(0).max(100),
61469
+ s: number().min(0).max(100).meta(readingMeta("none")),
61056
61470
  /** Value/brightness as 0..100 inclusive. */
61057
- v: number().min(0).max(100)
61471
+ v: number().min(0).max(100).meta(readingMeta("none"))
61058
61472
  });
61059
61473
  var ColorInputSchema = discriminatedUnion("mode", [
61060
61474
  object({
@@ -61083,7 +61497,7 @@ var ColorStatusSchema = object({
61083
61497
  /** Populated when `mode === 'hsv'`. */
61084
61498
  hsv: HsvTripletSchema.optional(),
61085
61499
  /** Populated when `mode === 'mired'`. */
61086
- mireds: number().int().optional(),
61500
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
61087
61501
  /** Ms epoch of the last operator-driven change. */
61088
61502
  lastChangedAt: number()
61089
61503
  });
@@ -61292,18 +61706,12 @@ var FORM_OF_KIND = {
61292
61706
  function consumableFormOf(item) {
61293
61707
  return item.form ?? FORM_OF_KIND[consumableKindOf(item)];
61294
61708
  }
61295
- /** Units a remaining-life counter is reported in. Closed: the panel renders
61296
- * each. */
61297
- var ConsumableRemainingUnitSchema = _enum([
61298
- "days",
61299
- "hours",
61300
- "cycles"
61301
- ]);
61302
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
61303
- * days). `value` null = unknown (D393). */
61709
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
61710
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
61711
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
61304
61712
  var ConsumableRemainingSchema = object({
61305
- value: number().min(0).nullable(),
61306
- unit: ConsumableRemainingUnitSchema
61713
+ value: number().min(0).nullable().meta(readingMeta("device")),
61714
+ kind: ConsumableRemainingKindSchema
61307
61715
  });
61308
61716
  /** A single consumable item. Either a continuous `level` (remaining
61309
61717
  * life %, or fill % for a `supply` / `waste` item) or a discrete `status`
@@ -61316,7 +61724,7 @@ var ConsumableItemSchema = object({
61316
61724
  /** Display name. */
61317
61725
  label: string().min(1),
61318
61726
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
61319
- level: number().min(0).max(100).nullable(),
61727
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61320
61728
  /** The provider's discrete verdict when known (binary mode). */
61321
61729
  status: ConsumableStatusSchema.nullable(),
61322
61730
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -61398,8 +61806,8 @@ var consumablesCustomize = {
61398
61806
  transforms: [{ kind: "identity" }]
61399
61807
  }],
61400
61808
  literals: [{
61401
- path: "remaining.unit",
61402
- values: [...ConsumableRemainingUnitSchema.options]
61809
+ path: "remaining.kind",
61810
+ values: [...ConsumableRemainingKindSchema.options]
61403
61811
  }]
61404
61812
  },
61405
61813
  {
@@ -61528,7 +61936,6 @@ reset: method(object({
61528
61936
  },
61529
61937
  "remaining.value": {
61530
61938
  kind: "gauge",
61531
- unitFrom: "remaining.unit",
61532
61939
  deadband: 1,
61533
61940
  displayName: "Remaining"
61534
61941
  }
@@ -61598,10 +62005,9 @@ var contactCapability = {
61598
62005
  * (empty array for the other kinds). It lives in the slice because
61599
62006
  * HA `select.*` entities may update their options at runtime.
61600
62007
  *
61601
- * The `unit`/`min`/`max`/`step` fields are meaningful for
61602
- * `kind === 'numeric'` and are absent (undefined) for all other
61603
- * kinds. They are populated live from HA attributes on each state
61604
- * push — consistent with `options` and the sensor-unit approach.
62008
+ * Numeric `min` / `max` / `step` live in the slice and share the device
62009
+ * quantity (`SourceInfo.quantity`, D944). Display precision is a read-time
62010
+ * preference. A datetime control's shape is `SourceInfo.dateTimeShape`.
61605
62011
  */
61606
62012
  var ControlStatusSchema = object({
61607
62013
  kind: _enum([
@@ -61613,39 +62019,17 @@ var ControlStatusSchema = object({
61613
62019
  /** Current value. Numeric kind → number; select/text/datetime → string.
61614
62020
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
61615
62021
  * or time-only — HA accepts all three forms). */
61616
- value: union([number(), string()]),
62022
+ value: union([number().meta(readingMeta("device")), string()]),
61617
62023
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
61618
62024
  options: array(string()),
61619
62025
  /** Ms epoch when the slice was last updated. */
61620
62026
  lastChangedAt: number(),
61621
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
61622
- * Populated live from HA `attributes.unit_of_measurement` on each push.
61623
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61624
- unit: string().optional(),
61625
- /** Minimum allowed value. Populated live from HA `attributes.min`.
61626
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61627
- min: number().optional(),
61628
- /** Maximum allowed value. Populated live from HA `attributes.max`.
61629
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61630
- max: number().optional(),
61631
- /** Step increment. Populated live from HA `attributes.step`.
61632
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61633
- step: number().optional(),
61634
- /** Suggested decimal places for numeric display. Populated live from HA
61635
- * `attributes.suggested_display_precision`, or derived from `step` when the
61636
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
61637
- * other kinds. Falls back to auto-formatting when absent. */
61638
- precision: number().int().min(0).max(10).optional(),
61639
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
61640
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
61641
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
61642
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
61643
- * `has_date`/`has_time` attributes on each push. */
61644
- format: _enum([
61645
- "date",
61646
- "time",
61647
- "datetime"
61648
- ]).optional()
62027
+ /** Minimum allowed value, in the device quantity's base unit. */
62028
+ min: number().optional().meta(readingMeta("device")),
62029
+ /** Maximum allowed value, in the device quantity's base unit. */
62030
+ max: number().optional().meta(readingMeta("device")),
62031
+ /** Step increment, in the device quantity's base unit. */
62032
+ step: number().optional().meta(readingMeta("device"))
61649
62033
  });
61650
62034
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
61651
62035
  object({
@@ -61733,9 +62117,9 @@ var CoverStatusSchema = object({
61733
62117
  state: CoverStateSchema,
61734
62118
  /** 0 = fully closed, 100 = fully open. Null when the device has
61735
62119
  * no intermediate position surface. */
61736
- position: number().min(0).max(100).nullable(),
62120
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61737
62121
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
61738
- tiltPosition: number().min(0).max(100).nullable(),
62122
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61739
62123
  /** Ms epoch when the slice was last updated. */
61740
62124
  lastChangedAt: number()
61741
62125
  });
@@ -61838,9 +62222,9 @@ var NormalizedRangeSchema$1 = object({
61838
62222
  var DayNightStatusSchema = object({
61839
62223
  mode: DayNightModeSchema,
61840
62224
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
61841
- sensitivity: number().optional(),
62225
+ sensitivity: number().optional().meta(readingMeta("none")),
61842
62226
  /** Delay before the IR-cut filter flips, in seconds. */
61843
- switchDelaySec: number().optional(),
62227
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
61844
62228
  lastFetchedAt: number()
61845
62229
  });
61846
62230
  /**
@@ -62058,11 +62442,11 @@ var DishwasherStatusSchema = object({
62058
62442
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
62059
62443
  availablePrograms: array(string()),
62060
62444
  /** Seconds remaining on the active cycle, or null when not running / not known. */
62061
- remainingSeconds: number().int().nonnegative().nullable(),
62445
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62062
62446
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
62063
- delayedStartSeconds: number().int().nonnegative().nullable(),
62447
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62064
62448
  /** Cycle completion 0..100, or null when not applicable. */
62065
- progressPercent: number().min(0).max(100).nullable(),
62449
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62066
62450
  /** True when the door is reported open; null when the door state is not known. */
62067
62451
  doorOpen: boolean().nullable(),
62068
62452
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -62086,7 +62470,7 @@ var DishwasherStatusSchema = object({
62086
62470
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
62087
62471
  machineCareDue: boolean().nullable(),
62088
62472
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
62089
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
62473
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
62090
62474
  /** Option states: null when the appliance does not expose the option or has not said. */
62091
62475
  halfLoad: boolean().nullable(),
62092
62476
  hygienePlus: boolean().nullable(),
@@ -62101,7 +62485,7 @@ var DishwasherStatusSchema = object({
62101
62485
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
62102
62486
  activeEvents: array(string()),
62103
62487
  /** Numeric error code when the appliance reports one; null otherwise. */
62104
- errorCode: number().nullable(),
62488
+ errorCode: number().nullable().meta(readingMeta("none")),
62105
62489
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
62106
62490
  errorLabel: string().nullable(),
62107
62491
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -62332,7 +62716,7 @@ var DoorbellStatusSchema = object({
62332
62716
  /** Ms epoch of the last press. null = never observed since this provider started. */
62333
62717
  lastPressedAt: number().nullable(),
62334
62718
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
62335
- pressCountSinceStart: number()
62719
+ pressCountSinceStart: number().meta(readingMeta("count"))
62336
62720
  });
62337
62721
  var DoorbellPressEventSchema = object({
62338
62722
  deviceId: number(),
@@ -62397,17 +62781,7 @@ onPressed: {
62397
62781
  history: "none",
62398
62782
  customize: doorbellCustomize
62399
62783
  };
62400
- /**
62401
- * Enum-state sensor — a string value picked from a finite option set.
62402
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
62403
- * states, weather conditions, contact-source channels, mode strings).
62404
- * The option set is stable across observations; it is persisted in the
62405
- * device config blob (via the control slice at adoption) and is NOT
62406
- * stored in `sourceInfo`.
62407
- */
62408
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
62409
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
62410
- var EnumSensorDateTimeFormatSchema = _enum([
62784
+ _enum([
62411
62785
  "date",
62412
62786
  "time",
62413
62787
  "datetime"
@@ -62415,12 +62789,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
62415
62789
  var EnumSensorStatusSchema = object({
62416
62790
  value: string(),
62417
62791
  /**
62418
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
62419
- * as a locale date/time/datetime; absent for plain enum sensors. HA
62420
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
62421
- * a time-only sensor → `'time'`.
62792
+ * Set when `value` is a timestamp rather than an enum state, so history
62793
+ * does not learn ISO strings as states. The display shape (date / time /
62794
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
62422
62795
  */
62423
- format: EnumSensorDateTimeFormatSchema.optional(),
62796
+ timestamp: boolean().optional(),
62424
62797
  /** Ms epoch when the slice was last updated. */
62425
62798
  lastFetchedAt: number()
62426
62799
  });
@@ -62447,11 +62820,11 @@ var enumSensorCapability = {
62447
62820
  * History (D850): the value as an `enum` series (an appliance's program or
62448
62821
  * operation state, an HVAC action). The option set is per device, so the
62449
62822
  * value list is LEARNED as values are first seen (bounded). A sensor with a
62450
- * `format` carries a timestamp, not a state, and is not recorded.
62823
+ * `timestamp` carries a clock, not a state, and is not recorded.
62451
62824
  */
62452
62825
  history: { fields: { value: {
62453
62826
  kind: "enum",
62454
- skipWhenSet: "format"
62827
+ skipWhenSet: "timestamp"
62455
62828
  } } },
62456
62829
  /** Clock fields: written, but excluded from the compare that decides
62457
62830
  * whether persisting is worth a SQLite commit. */
@@ -62471,13 +62844,13 @@ var EventFireSchema = object({
62471
62844
  deviceId: number(),
62472
62845
  eventType: string(),
62473
62846
  data: record(string(), unknown()).nullable(),
62474
- timestamp: number(),
62475
- seq: number()
62847
+ timestamp: number().meta(readingMeta("none")),
62848
+ seq: number().meta(readingMeta("count"))
62476
62849
  });
62477
62850
  var EventEmitterStatusSchema = object({
62478
62851
  eventTypes: array(string()),
62479
62852
  lastEvent: EventFireSchema.nullable(),
62480
- eventCountSinceStart: number()
62853
+ eventCountSinceStart: number().meta(readingMeta("count"))
62481
62854
  });
62482
62855
  var eventEmitterCapability = {
62483
62856
  name: "event-emitter",
@@ -62930,10 +63303,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
62930
63303
  var FanControlStatusSchema = object({
62931
63304
  /** Active speed as 0..100 inclusive. Null when the device has no
62932
63305
  * speed surface (single-speed fan). */
62933
- percentage: number().min(0).max(100).nullable(),
63306
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62934
63307
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
62935
63308
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
62936
- percentageStep: number().positive().optional(),
63309
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
62937
63310
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
62938
63311
  * — empty string when no preset is active or supported. */
62939
63312
  preset: string(),
@@ -63054,7 +63427,7 @@ var FeatureProbeStatusSchema = object({
63054
63427
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
63055
63428
  model: string().nullable(),
63056
63429
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
63057
- channelCount: number().nullable(),
63430
+ channelCount: number().nullable().meta(readingMeta("count")),
63058
63431
  /**
63059
63432
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
63060
63433
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -63177,9 +63550,91 @@ var gasCapability = {
63177
63550
  * whether persisting is worth a SQLite commit. */
63178
63551
  volatileStateFields: ["lastChangedAt"]
63179
63552
  };
63553
+ var GroupCommandsSchema = object({
63554
+ /** One row per member that has been commanded. A later command replaces that member's row. */
63555
+ results: array(object({
63556
+ deviceId: number().int().nonnegative(),
63557
+ cap: string().min(1),
63558
+ method: string().min(1),
63559
+ ok: boolean(),
63560
+ error: string().optional(),
63561
+ /** Ms epoch of the attempt. */
63562
+ at: number()
63563
+ })) });
63564
+ var groupCommandsCapability = {
63565
+ name: "group-commands",
63566
+ scope: "device",
63567
+ deviceNative: true,
63568
+ mode: "singleton",
63569
+ deviceTypes: [DeviceType.Switch, DeviceType.Light],
63570
+ methods: {},
63571
+ status: {
63572
+ schema: GroupCommandsSchema,
63573
+ kind: "push"
63574
+ },
63575
+ runtimeState: GroupCommandsSchema,
63576
+ /**
63577
+ * Runtime-state durability: **session** — the last attempt. A restored
63578
+ * failure would outlive the command that caused it.
63579
+ *
63580
+ * See `RuntimeStateDurability`. Enforced by
63581
+ * `scripts/check-runtime-state-durability.ts`.
63582
+ */
63583
+ durability: "session",
63584
+ history: "none"
63585
+ };
63586
+ /**
63587
+ * How many members of a helper group answered (D944). Counts are integers,
63588
+ * not measurements: zero included is a real count, never a stand-in reading.
63589
+ * The reading itself stays on the output capability. History is none — the
63590
+ * series that later statistics read is the output cap's own history block.
63591
+ */
63592
+ var GroupStatusSchema = object({
63593
+ /** Members whose current value entered the aggregate. */
63594
+ included: number().int().nonnegative().meta(readingMeta("count", {
63595
+ precision: 0,
63596
+ label: "Reporting"
63597
+ })),
63598
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
63599
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
63600
+ precision: 0,
63601
+ label: "Excluded"
63602
+ })),
63603
+ /** `included` + `excluded`. */
63604
+ total: number().int().nonnegative().meta(readingMeta("count", {
63605
+ precision: 0,
63606
+ label: "Members"
63607
+ }))
63608
+ });
63609
+ var groupStatusCapability = {
63610
+ name: "group-status",
63611
+ scope: "device",
63612
+ deviceNative: true,
63613
+ mode: "singleton",
63614
+ deviceTypes: [
63615
+ DeviceType.Sensor,
63616
+ DeviceType.Switch,
63617
+ DeviceType.Light
63618
+ ],
63619
+ methods: {},
63620
+ status: {
63621
+ schema: GroupStatusSchema,
63622
+ kind: "push"
63623
+ },
63624
+ runtimeState: GroupStatusSchema,
63625
+ /**
63626
+ * Runtime-state durability: **session** — recomputed from members on the
63627
+ * next source event; a restored count would outlive the readings it named.
63628
+ *
63629
+ * See `RuntimeStateDurability`. Enforced by
63630
+ * `scripts/check-runtime-state-durability.ts`.
63631
+ */
63632
+ durability: "session",
63633
+ history: "none"
63634
+ };
63180
63635
  var HeatingValveStatusSchema = object({
63181
63636
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
63182
- position: number().min(0).max(100).nullable(),
63637
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63183
63638
  /** Ms epoch when the slice was last updated (push or poll). */
63184
63639
  lastFetchedAt: number()
63185
63640
  });
@@ -63222,9 +63677,9 @@ var HumidifierStatusSchema = object({
63222
63677
  /** Whether the humidifier is currently on. */
63223
63678
  on: boolean(),
63224
63679
  /** Current measured relative humidity (0..100). Null when not reported. */
63225
- currentHumidity: number().min(0).max(100).nullable(),
63680
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
63226
63681
  /** Target relative humidity (0..100). Null when no setpoint surface. */
63227
- targetHumidity: number().min(0).max(100).nullable(),
63682
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
63228
63683
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
63229
63684
  * device has no mode surface. */
63230
63685
  mode: string().nullable(),
@@ -63234,9 +63689,9 @@ var HumidifierStatusSchema = object({
63234
63689
  * `idle` / `off`). Null when the device doesn't report it. */
63235
63690
  action: string().nullable(),
63236
63691
  /** HA `min_humidity` attribute. Null → UI uses 0. */
63237
- minHumidity: number().nullable(),
63692
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
63238
63693
  /** HA `max_humidity` attribute. Null → UI uses 100. */
63239
- maxHumidity: number().nullable(),
63694
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
63240
63695
  /** Ms epoch when the slice was last updated. */
63241
63696
  lastChangedAt: number()
63242
63697
  });
@@ -63306,22 +63761,13 @@ var humidifierCapability = {
63306
63761
  * `sourceInfo`.
63307
63762
  */
63308
63763
  var HumiditySensorStatusSchema = object({
63309
- /** Current relative humidity, 0..100. */
63310
- percent: number().min(0).max(100).meta({
63311
- unit: "%",
63764
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
63765
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63766
+ precision: 0,
63312
63767
  label: "Humidity"
63313
- }),
63768
+ })),
63314
63769
  /** Ms epoch when the slice was last updated. */
63315
- lastFetchedAt: number(),
63316
- /** Live display unit from the upstream source (e.g. HA
63317
- * `attributes.unit_of_measurement`). The UI prefers this over the
63318
- * role's canonical unit. Absent → fall back to the canonical unit. */
63319
- unit: string().optional(),
63320
- /** Suggested decimal places for numeric display.
63321
- * Populated live from the upstream source when provided (e.g. HA
63322
- * `attributes.suggested_display_precision`). Falls back to
63323
- * auto-formatting when absent. */
63324
- precision: number().int().min(0).max(10).optional()
63770
+ lastFetchedAt: number()
63325
63771
  });
63326
63772
  var humiditySensorCapability = {
63327
63773
  name: "humidity-sensor",
@@ -63352,12 +63798,7 @@ var humiditySensorCapability = {
63352
63798
  } } },
63353
63799
  /** Clock fields: written, but excluded from the compare that decides
63354
63800
  * whether persisting is worth a SQLite commit. */
63355
- volatileStateFields: ["lastFetchedAt"],
63356
- customize: {
63357
- version: 1,
63358
- advanced: "derive",
63359
- targets: []
63360
- }
63801
+ volatileStateFields: ["lastFetchedAt"]
63361
63802
  };
63362
63803
  /**
63363
63804
  * Image display cap. Models a single still image exposed by an integration —
@@ -63378,7 +63819,7 @@ var ImageStatusSchema = object({
63378
63819
  * entity exposes no `entity_picture` (yet). */
63379
63820
  url: string().nullable(),
63380
63821
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
63381
- lastUpdated: number().nullable()
63822
+ lastUpdated: number().nullable().meta(readingMeta("none"))
63382
63823
  });
63383
63824
  var imageCapability = {
63384
63825
  name: "image",
@@ -63466,19 +63907,19 @@ var NormalizedRangeSchema = object({
63466
63907
  */
63467
63908
  var ImageSettingsStatusSchema = object({
63468
63909
  /** Normalized 0–100. */
63469
- brightness: number().optional(),
63910
+ brightness: number().optional().meta(readingMeta("none")),
63470
63911
  /** Normalized 0–100. */
63471
- contrast: number().optional(),
63912
+ contrast: number().optional().meta(readingMeta("none")),
63472
63913
  /** Normalized 0–100. */
63473
- saturation: number().optional(),
63914
+ saturation: number().optional().meta(readingMeta("none")),
63474
63915
  /** Normalized 0–100. */
63475
- sharpness: number().optional(),
63916
+ sharpness: number().optional().meta(readingMeta("none")),
63476
63917
  mirror: boolean().optional(),
63477
63918
  flip: boolean().optional(),
63478
63919
  rotate: ImageRotateSchema.optional(),
63479
63920
  whiteBalance: WhiteBalanceModeSchema.optional(),
63480
63921
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
63481
- warmth: number().optional(),
63922
+ warmth: number().optional().meta(readingMeta("none")),
63482
63923
  exposureMode: ExposureModeSchema.optional(),
63483
63924
  backlightMode: BacklightModeSchema.optional(),
63484
63925
  lastFetchedAt: number()
@@ -63757,10 +64198,10 @@ var IntercomAbilitySchema = object({
63757
64198
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
63758
64199
  codecs: array(string()),
63759
64200
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
63760
- sampleRate: number(),
64201
+ sampleRate: number().meta(readingMeta("none")),
63761
64202
  duplex: _enum(["half", "full"]),
63762
64203
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
63763
- maxBacklogMs: number()
64204
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
63764
64205
  });
63765
64206
  var IntercomStatusSchema = object({
63766
64207
  /** True while a talk session is open. */
@@ -63987,13 +64428,13 @@ var LawnMowerControlStatusSchema = object({
63987
64428
  activity: LawnMowerActivitySchema,
63988
64429
  /** 0..100 battery percentage. Null when the device has no battery
63989
64430
  * reading. */
63990
- batteryLevel: number().min(0).max(100).nullable(),
64431
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63991
64432
  /** 0..100 mowing-completion percentage of the current task, or null when no
63992
64433
  * task is active / progress is unavailable. */
63993
- progressPercent: number().min(0).max(100).nullable(),
64434
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63994
64435
  /** Current device/event code (dynamic — mostly status, sometimes an error),
63995
64436
  * or null when unknown. */
63996
- currentCode: number().nullable(),
64437
+ currentCode: number().nullable().meta(readingMeta("none")),
63997
64438
  /** Human label for {@link currentCode}, or null when undecodable. */
63998
64439
  currentCodeLabel: string().nullable(),
63999
64440
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -64547,7 +64988,7 @@ var MediaPlayerStatusSchema = object({
64547
64988
  state: MediaPlayerStateSchema,
64548
64989
  /** Volume as 0..100 inclusive. Null when the device has no volume
64549
64990
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
64550
- volumeLevel: number().min(0).max(100).nullable(),
64991
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64551
64992
  /** Mute toggle distinct from volume=0. Null when no mute surface.
64552
64993
  * Pair with `DeviceFeature.MediaPlayerMute`. */
64553
64994
  isMuted: boolean().nullable(),
@@ -64560,10 +65001,10 @@ var MediaPlayerStatusSchema = object({
64560
65001
  currentMedia: MediaInfoSchema.nullable(),
64561
65002
  /** Current playback position in ms. Null when not seekable or
64562
65003
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
64563
- positionMs: number().int().nonnegative().nullable(),
65004
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64564
65005
  /** Total duration of the current media in ms. Null when unknown
64565
65006
  * (live stream) or nothing is playing. */
64566
- durationMs: number().int().nonnegative().nullable(),
65007
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64567
65008
  /** Shuffle toggle. Null when no shuffle surface.
64568
65009
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
64569
65010
  shuffle: boolean().nullable(),
@@ -64872,7 +65313,7 @@ var motionCustomize = {
64872
65313
  eventName: "onMotionChanged"
64873
65314
  },
64874
65315
  deviceTypes: [DeviceType.Camera],
64875
- apply: "replace",
65316
+ apply: "add",
64876
65317
  applyAuthority: {
64877
65318
  kind: "camera-sensors",
64878
65319
  timeoutField: "motionCooldownMs"
@@ -64969,7 +65410,7 @@ var MotionStatusSchema = object({
64969
65410
  * it, every provider seeded it at boot, and every session left it
64970
65411
  * `detected: true` after its detach.
64971
65412
  */
64972
- autoClearAfterMs: number().nullable()
65413
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
64973
65414
  });
64974
65415
  /**
64975
65416
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -65006,12 +65447,18 @@ var motionCapability = {
65006
65447
  methods: {
65007
65448
  /**
65008
65449
  * Pull the current motion state synchronously. Convenience shortcut
65009
- * for consumers that don't need the full status object; equivalent
65010
- * to `getStatus()?.detected ?? false`. Will likely be folded into
65011
- * `getStatus` once the auto-injected status surface lands in every
65012
- * consumer.
65450
+ * for consumers that don't need the full status object. A composed
65451
+ * group answers from its slice (D944): unknown throws, it is not off.
65013
65452
  */
65014
65453
  isDetected: method(object({ deviceId: number() }), boolean()) },
65454
+ /**
65455
+ * A composed motion group serves `isDetected` from `detected` (D944).
65456
+ * Without this the cap stays uncomposable (`has-commands`).
65457
+ */
65458
+ composedMethods: { isDetected: {
65459
+ kind: "slice",
65460
+ field: "detected"
65461
+ } },
65015
65462
  events: {
65016
65463
  /**
65017
65464
  * Fires every time the runner transitions a camera between
@@ -65151,7 +65598,7 @@ var MotionZoneRegionSchema = object({
65151
65598
  * the grid region(s). */
65152
65599
  var MotionZoneStatusSchema = object({
65153
65600
  enabled: boolean(),
65154
- sensitivity: number(),
65601
+ sensitivity: number().meta(readingMeta("none")),
65155
65602
  /** Grid region(s). Today exactly one `grid` shape. */
65156
65603
  regions: array(MotionZoneRegionSchema),
65157
65604
  lastFetchedAt: number()
@@ -65521,7 +65968,7 @@ var NavigationStatusSchema = object({
65521
65968
  following: boolean(),
65522
65969
  flash: boolean(),
65523
65970
  lightMode: NavigationLightModeSchema,
65524
- lightLevel: number().min(40).max(100),
65971
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
65525
65972
  /** Ms epoch when the slice was last updated. */
65526
65973
  lastChangedAt: number()
65527
65974
  });
@@ -65655,13 +66102,13 @@ var NetworkLinkStatusSchema = object({
65655
66102
  * one whose reading has not landed must not be drawn at 0 %. Consumers
65656
66103
  * SKIP a null rather than coerce it.
65657
66104
  */
65658
- signalPercent: number().min(0).max(100).nullable(),
66105
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65659
66106
  /** Raw received signal strength in dBm, when the firmware reports one. */
65660
- rssiDbm: number().optional(),
66107
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
65661
66108
  /** Network name of a wireless link, when the firmware reports it. */
65662
66109
  ssid: string().optional(),
65663
66110
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
65664
- lastUpdated: number()
66111
+ lastUpdated: number().meta(readingMeta("none"))
65665
66112
  });
65666
66113
  var networkLinkCapability = {
65667
66114
  name: "network-link",
@@ -66046,7 +66493,7 @@ var NotifierStatusSchema = object({
66046
66493
  lastError: string().nullable(),
66047
66494
  /** Number of deliveries currently buffered server-side (rate-limited
66048
66495
  * notifiers may queue). 0 when the provider sends synchronously. */
66049
- queueDepth: number().int().nonnegative(),
66496
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
66050
66497
  /** Per-feature capability matrix — authoritative source for the
66051
66498
  * compose-form field gating. */
66052
66499
  supports: NotifierSupportsSchema
@@ -66138,11 +66585,9 @@ onSent: { data: object({
66138
66585
  * cap fits (HA `sensor` whose `device_class` we don't have a typed cap
66139
66586
  * for: water flow, distance, weight, frequency, signal strength, …).
66140
66587
  *
66141
- * The `unit` and `precision` fields carry the live display metadata that
66142
- * the upstream source provides with each state push (HA
66143
- * `attributes.unit_of_measurement` / `attributes.suggested_display_precision`).
66144
- * The UI reads them directly from the slice — they are the single source
66145
- * of truth for generic numeric rendering and do NOT live on `sourceInfo`.
66588
+ * The quantity is not on this slice. It is chosen when the device is
66589
+ * adopted and stored once on `SourceInfo.quantity` (D944). `value` is in
66590
+ * that quantity's base unit. `null` is a refused sample (D393).
66146
66591
  *
66147
66592
  * Prefer the typed alternatives (`temperature-sensor`, `humidity-sensor`,
66148
66593
  * `ambient-light-sensor`, `pressure-sensor`, `power-meter`,
@@ -66151,15 +66596,8 @@ onSent: { data: object({
66151
66596
  * numeric values land here.
66152
66597
  */
66153
66598
  var NumericSensorStatusSchema = object({
66154
- value: number(),
66155
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
66156
- * Populated live from the upstream source on each state push.
66157
- * Absent for unitless sensors. */
66158
- unit: string().optional(),
66159
- /** Suggested decimal places for numeric display.
66160
- * Populated live from the upstream source when provided.
66161
- * Falls back to auto-formatting when absent. */
66162
- precision: number().int().min(0).max(10).optional(),
66599
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
66600
+ value: number().nullable().meta(readingMeta("device")),
66163
66601
  /** Ms epoch when the slice was last updated. */
66164
66602
  lastFetchedAt: number()
66165
66603
  });
@@ -66183,11 +66621,11 @@ var numericSensorCapability = {
66183
66621
  */
66184
66622
  durability: "restored",
66185
66623
  /**
66186
- * History (D850): recorded series. The unit is per device, read from the sibling `unit` field.
66624
+ * History (D850): recorded series. The unit is the device quantity's base,
66625
+ * resolved when the series is read — the cap has no single unit.
66187
66626
  */
66188
66627
  history: { fields: { value: {
66189
66628
  kind: "gauge",
66190
- unitFrom: "unit",
66191
66629
  minIntervalMs: 3e4
66192
66630
  } } },
66193
66631
  /** Clock fields: written, but excluded from the compare that decides
@@ -66479,23 +66917,26 @@ var PetFeederStatusSchema = object({
66479
66917
  /** Food currently in the bowl (grams). Null when the device has not
66480
66918
  * reported a reading yet. On dual-hopper models this is the combined
66481
66919
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
66482
- foodLevel: number().nullable(),
66920
+ foodLevel: number().nullable().meta(readingMeta("none")),
66483
66921
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
66484
66922
  * single-hopper models. */
66485
- food1: number().nullable(),
66923
+ food1: number().nullable().meta(readingMeta("none")),
66486
66924
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
66487
66925
  * single-hopper models. */
66488
- food2: number().nullable(),
66926
+ food2: number().nullable().meta(readingMeta("none")),
66489
66927
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
66490
66928
  * (`device_class: problem`, on = low). True when the bowl is empty /
66491
66929
  * below the feeder's low threshold. */
66492
66930
  lowFood: boolean(),
66493
66931
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
66494
66932
  * device has no battery reading. */
66495
- batteryPower: number().min(0).max(100).nullable(),
66496
- /** Days of desiccant life remaining. Null when the model has no
66497
- * desiccant sensor. */
66498
- desiccantLeftDays: number().nullable(),
66933
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
66934
+ /**
66935
+ * Desiccant life remaining, stored in seconds (duration base, D944).
66936
+ * Providers convert from the days the feeder reports. Null when the
66937
+ * model has no desiccant sensor.
66938
+ */
66939
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
66499
66940
  /** True while a feed is in progress. */
66500
66941
  feeding: boolean(),
66501
66942
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -66506,7 +66947,7 @@ var PetFeederStatusSchema = object({
66506
66947
  * with `errorCode` for consumers that want the raw integer. */
66507
66948
  error: string().nullable(),
66508
66949
  /** Raw device error code (0 / null = no error). */
66509
- errorCode: number().nullable(),
66950
+ errorCode: number().nullable().meta(readingMeta("none")),
66510
66951
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
66511
66952
  isDualHopper: boolean(),
66512
66953
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -66516,7 +66957,7 @@ var PetFeederStatusSchema = object({
66516
66957
  /** Play a chime when dispensing. */
66517
66958
  feedSound: boolean(),
66518
66959
  /** Speaker / prompt volume level (device-scaled integer). */
66519
- volume: number(),
66960
+ volume: number().meta(readingMeta("none")),
66520
66961
  /** Ms epoch when the slice was last refreshed from the cloud. */
66521
66962
  lastFetchedAt: number()
66522
66963
  });
@@ -66635,7 +67076,7 @@ var petFeederCapability = {
66635
67076
  },
66636
67077
  desiccantLeftDays: {
66637
67078
  kind: "gauge",
66638
- unit: "days"
67079
+ unit: "s"
66639
67080
  }
66640
67081
  } }
66641
67082
  };
@@ -67071,36 +67512,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
67071
67512
  */
67072
67513
  var PowerMeterStatusSchema = object({
67073
67514
  /** Instantaneous power draw in watts. */
67074
- watts: number().optional().meta({
67075
- unit: "W",
67076
- label: "Power"
67077
- }),
67515
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
67078
67516
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
67079
- kwhTotal: number().optional().meta({
67080
- unit: "kWh",
67081
- label: "Energy"
67082
- }),
67517
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
67083
67518
  /** Voltage in volts. */
67084
- volts: number().optional(),
67519
+ volts: number().optional().meta(readingMeta("voltage")),
67085
67520
  /** Current in amperes. */
67086
- amps: number().optional(),
67087
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
67088
- va: number().optional(),
67521
+ amps: number().optional().meta(readingMeta("current")),
67522
+ /** Apparent power in volt-amperes. */
67523
+ va: number().optional().meta(readingMeta("apparent-power")),
67089
67524
  /** Power factor (0..1) if reported. */
67090
- powerFactor: number().min(0).max(1).optional(),
67525
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
67091
67526
  /** Ms epoch when the slice was last updated. */
67092
- lastFetchedAt: number(),
67093
- /** Live display unit of the single metric this slice carries (e.g. HA
67094
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
67095
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
67096
- * unit per slice is unambiguous. The UI prefers this over the role's
67097
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
67098
- unit: string().optional(),
67099
- /** Suggested decimal places for numeric display.
67100
- * Populated live from the upstream source when provided (e.g. HA
67101
- * `attributes.suggested_display_precision`). Falls back to
67102
- * auto-formatting when absent. */
67103
- precision: number().int().min(0).max(10).optional()
67527
+ lastFetchedAt: number()
67104
67528
  });
67105
67529
  var powerMeterCapability = {
67106
67530
  name: "power-meter",
@@ -67164,12 +67588,7 @@ var powerMeterCapability = {
67164
67588
  } },
67165
67589
  /** Clock fields: written, but excluded from the compare that decides
67166
67590
  * whether persisting is worth a SQLite commit. */
67167
- volatileStateFields: ["lastFetchedAt"],
67168
- customize: {
67169
- version: 1,
67170
- advanced: "derive",
67171
- targets: []
67172
- }
67591
+ volatileStateFields: ["lastFetchedAt"]
67173
67592
  };
67174
67593
  /**
67175
67594
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -67189,11 +67608,11 @@ var powerMeterCapability = {
67189
67608
  */
67190
67609
  var GpsLocationSchema = object({
67191
67610
  /** Latitude in decimal degrees, -90..90. */
67192
- latitude: number().min(-90).max(90),
67611
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
67193
67612
  /** Longitude in decimal degrees, -180..180. */
67194
- longitude: number().min(-180).max(180),
67613
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
67195
67614
  /** Reported accuracy in meters (lower = better). */
67196
- accuracyMeters: number().nonnegative()
67615
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
67197
67616
  });
67198
67617
  var PresenceStatusSchema = object({
67199
67618
  /** `home` / `not_home` / any user-defined zone name. */
@@ -67205,7 +67624,7 @@ var PresenceStatusSchema = object({
67205
67624
  gps: GpsLocationSchema.nullable(),
67206
67625
  /** Optional battery level of the tracking device (0..100). Null
67207
67626
  * when not reported. */
67208
- batteryPercent: number().min(0).max(100).nullable(),
67627
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
67209
67628
  /** Ms epoch when the slice was last updated. */
67210
67629
  lastChangedAt: number()
67211
67630
  });
@@ -67255,19 +67674,10 @@ var presenceCapability = {
67255
67674
  * `sourceInfo`.
67256
67675
  */
67257
67676
  var PressureSensorStatusSchema = object({
67258
- /** Current pressure in hPa. */
67259
- hpa: number(),
67677
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
67678
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
67260
67679
  /** Ms epoch when the slice was last updated. */
67261
- lastFetchedAt: number(),
67262
- /** Live display unit from the upstream source (e.g. HA
67263
- * `attributes.unit_of_measurement`). The UI prefers this over the
67264
- * role's canonical unit. Absent → fall back to the canonical unit. */
67265
- unit: string().optional(),
67266
- /** Suggested decimal places for numeric display.
67267
- * Populated live from the upstream source when provided (e.g. HA
67268
- * `attributes.suggested_display_precision`). Falls back to
67269
- * auto-formatting when absent. */
67270
- precision: number().int().min(0).max(10).optional()
67680
+ lastFetchedAt: number()
67271
67681
  });
67272
67682
  var pressureSensorCapability = {
67273
67683
  name: "pressure-sensor",
@@ -67681,8 +68091,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
67681
68091
  }), _void(), { kind: "mutation" });
67682
68092
  var PtzAutotrackSettingsSchema = object({
67683
68093
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
67684
- stopDelaySeconds: number().int().min(0).max(300),
67685
- disappearDelaySeconds: number().int().min(0).max(300)
68094
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
68095
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
67686
68096
  });
67687
68097
  /**
67688
68098
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -67836,7 +68246,7 @@ var RunningStateStatusSchema = object({
67836
68246
  /** null = unknown — never coerced to false (D393). */
67837
68247
  running: boolean().nullable(),
67838
68248
  /** Epoch ms the current boolean began; null while `running` is null. */
67839
- since: number().nullable(),
68249
+ since: number().nullable().meta(readingMeta("none")),
67840
68250
  source: RunningStateSourceSchema.meta({ label: "Source" }),
67841
68251
  phase: string().nullable(),
67842
68252
  /** null until a reading has been published (D393). */
@@ -67851,7 +68261,6 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
67851
68261
  /** Generic Customize target for the running boolean (D936). */
67852
68262
  var runningCustomize = {
67853
68263
  version: 1,
67854
- advanced: "derive",
67855
68264
  targets: [{
67856
68265
  id: "running",
67857
68266
  cardinality: "scalar",
@@ -67871,7 +68280,7 @@ var runningCustomize = {
67871
68280
  DeviceType.Thermostat,
67872
68281
  DeviceType.PetFeeder
67873
68282
  ],
67874
- apply: "add-or-replace",
68283
+ apply: "add",
67875
68284
  path: "running",
67876
68285
  sources: [
67877
68286
  {
@@ -69167,9 +69576,9 @@ var RecordTriggerSchema = _enum([
69167
69576
  */
69168
69577
  var RecordWindowSchema = object({
69169
69578
  trigger: RecordTriggerSchema,
69170
- day: number().int().min(0).max(6),
69171
- startMinute: number().int().min(0).max(1439),
69172
- endMinute: number().int().min(1).max(1440)
69579
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
69580
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
69581
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
69173
69582
  });
69174
69583
  /** Status of one physical volume, as the camera itself describes it. */
69175
69584
  var OnboardStorageVolumeSchema = object({
@@ -69245,10 +69654,10 @@ var RecordingOnboardStatusSchema = object({
69245
69654
  /** Master "record to the card at all" switch. */
69246
69655
  enabled: boolean().nullable(),
69247
69656
  overwriteWhenFull: boolean().nullable(),
69248
- preRecordSec: number().nullable(),
69249
- postRecordSec: number().nullable(),
69657
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
69658
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
69250
69659
  /** Length of one recorded file, in minutes. */
69251
- segmentMinutes: number().nullable(),
69660
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
69252
69661
  /** The primary track's weekly windows, flattened. */
69253
69662
  windows: array(RecordWindowSchema),
69254
69663
  /**
@@ -69264,7 +69673,7 @@ var RecordingOnboardStatusSchema = object({
69264
69673
  * provider reporting a non-zero count also reports the schedule as not
69265
69674
  * writable.
69266
69675
  */
69267
- unreadableWindows: number(),
69676
+ unreadableWindows: number().meta(readingMeta("count")),
69268
69677
  /**
69269
69678
  * The camera is scheduled to record and has NO usable storage.
69270
69679
  *
@@ -70127,7 +70536,7 @@ var CamStreamDescriptorSchema = object({
70127
70536
  url: string().optional(),
70128
70537
  codec: string().optional(),
70129
70538
  resolution: CamStreamResolutionSchema.optional(),
70130
- fps: number().positive().optional(),
70539
+ fps: number().positive().optional().meta(readingMeta("rate")),
70131
70540
  label: string().optional(),
70132
70541
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
70133
70542
  deviceFeatures: array(string()).optional(),
@@ -70214,18 +70623,18 @@ var StreamProfileSchema = _enum([
70214
70623
  ]);
70215
70624
  /** The current encoding config of one stream profile. */
70216
70625
  var StreamProfileConfigSchema = object({
70217
- width: number(),
70218
- height: number(),
70626
+ width: number().meta(readingMeta("none")),
70627
+ height: number().meta(readingMeta("none")),
70219
70628
  codec: _enum(["h264", "h265"]),
70220
- framerate: number(),
70221
- bitrate: number(),
70629
+ framerate: number().meta(readingMeta("rate")),
70630
+ bitrate: number().meta(readingMeta("bitrate")),
70222
70631
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
70223
70632
  encoderProfile: _enum([
70224
70633
  "high",
70225
70634
  "main",
70226
70635
  "baseline"
70227
70636
  ]).optional(),
70228
- gop: number().optional(),
70637
+ gop: number().optional().meta(readingMeta("none")),
70229
70638
  /**
70230
70639
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
70231
70640
  *
@@ -70446,12 +70855,7 @@ var switchCapability = {
70446
70855
  label: "On",
70447
70856
  kind: "boolean"
70448
70857
  }
70449
- }] },
70450
- customize: {
70451
- version: 1,
70452
- advanced: "derive",
70453
- targets: []
70454
- }
70858
+ }] }
70455
70859
  };
70456
70860
  /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
70457
70861
  function hasControlCharacter(value) {
@@ -71034,35 +71438,21 @@ var tamperCapability = {
71034
71438
  * entries with `device_class: temperature` and any future native
71035
71439
  * thermometer.
71036
71440
  *
71037
- * Unit normalisation: providers convert to Celsius before storing.
71038
- * The slice value is always Celsius so cross-cap aggregators
71039
- * (climate-control's `currentTemp`, energy analytics) can compose
71040
- * without per-source unit fixups. The canonical display unit (`°C`) is
71041
- * a descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
71042
- * `sourceInfo`.
71441
+ * The number is Celsius (D944). Providers convert on ingest. A failed
71442
+ * conversion is `null`, never a guess. Display unit is a read-time preference.
71043
71443
  *
71044
71444
  * Status `lastFetchedAt` lets staleness-aware consumers detect a
71045
71445
  * frozen feed (provider hung) distinct from a "temperature hasn't
71046
71446
  * changed" steady state.
71047
71447
  */
71048
71448
  var TemperatureSensorStatusSchema = object({
71049
- /** Current temperature in Celsius. */
71050
- celsius: number().meta({
71051
- unit: "°C",
71449
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
71450
+ celsius: number().nullable().meta(readingMeta("temperature", {
71451
+ precision: 1,
71052
71452
  label: "Temperature"
71053
- }),
71453
+ })),
71054
71454
  /** Ms epoch when the slice was last updated (push or poll). */
71055
- lastFetchedAt: number(),
71056
- /** Live display unit from the upstream source (e.g. HA
71057
- * `attributes.unit_of_measurement`). The UI prefers this over the
71058
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
71059
- * Absent → the UI falls back to the role's canonical unit. */
71060
- unit: string().optional(),
71061
- /** Suggested decimal places for numeric display.
71062
- * Populated live from the upstream source when provided (e.g. HA
71063
- * `attributes.suggested_display_precision`). Falls back to
71064
- * auto-formatting when absent. */
71065
- precision: number().int().min(0).max(10).optional()
71455
+ lastFetchedAt: number()
71066
71456
  });
71067
71457
  var temperatureSensorCapability = {
71068
71458
  name: "temperature-sensor",
@@ -71093,12 +71483,7 @@ var temperatureSensorCapability = {
71093
71483
  } } },
71094
71484
  /** Clock fields: written, but excluded from the compare that decides
71095
71485
  * whether persisting is worth a SQLite commit. */
71096
- volatileStateFields: ["lastFetchedAt"],
71097
- customize: {
71098
- version: 1,
71099
- advanced: "derive",
71100
- targets: []
71101
- }
71486
+ volatileStateFields: ["lastFetchedAt"]
71102
71487
  };
71103
71488
  /**
71104
71489
  * toast — system-scoped singleton capability that streams toast
@@ -71381,16 +71766,16 @@ var VacuumControlStatusSchema = object({
71381
71766
  ]),
71382
71767
  /** 0..100 battery percentage. Null when the device has no battery
71383
71768
  * reading. */
71384
- batteryLevel: number().min(0).max(100).nullable(),
71769
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71385
71770
  /** Current fan-speed token (provider-verbatim). Null when unknown or
71386
71771
  * the vacuum has no speed control. */
71387
71772
  fanSpeed: string().nullable(),
71388
71773
  /** Speed tokens the hardware accepts — drives the UI selector. */
71389
71774
  availableFanSpeeds: array(string()),
71390
71775
  /** 0..100 cleaning-completion percentage of the current task, or null. */
71391
- progressPercent: number().min(0).max(100).nullable(),
71776
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71392
71777
  /** Current error code (0 / null = no error). */
71393
- errorCode: number().nullable(),
71778
+ errorCode: number().nullable().meta(readingMeta("none")),
71394
71779
  /** Human label for {@link errorCode}, or null when none / undecodable. */
71395
71780
  errorLabel: string().nullable(),
71396
71781
  /** Ms epoch when the slice was last updated. */
@@ -71468,7 +71853,7 @@ var ValveStatusSchema = object({
71468
71853
  ]),
71469
71854
  /** 0 = fully closed, 100 = fully open. Null when the device has no
71470
71855
  * intermediate position surface. */
71471
- position: number().min(0).max(100).nullable(),
71856
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71472
71857
  /** Ms epoch when the slice was last updated. */
71473
71858
  lastChangedAt: number()
71474
71859
  });
@@ -71576,9 +71961,9 @@ var vibrationCapability = {
71576
71961
  */
71577
71962
  var WaterHeaterStatusSchema = object({
71578
71963
  /** Current measured temperature. Null when not reported. */
71579
- currentTemp: number().nullable(),
71964
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
71580
71965
  /** Target temperature setpoint. Null when no setpoint surface. */
71581
- targetTemp: number().nullable(),
71966
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
71582
71967
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
71583
71968
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
71584
71969
  * device reports an unknown state. */
@@ -71589,9 +71974,9 @@ var WaterHeaterStatusSchema = object({
71589
71974
  * has no away surface. */
71590
71975
  away: boolean().nullable(),
71591
71976
  /** HA `min_temp` attribute. Null when not reported. */
71592
- minTemp: number().nullable(),
71977
+ minTemp: number().nullable().meta(readingMeta("temperature")),
71593
71978
  /** HA `max_temp` attribute. Null when not reported. */
71594
- maxTemp: number().nullable(),
71979
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
71595
71980
  /** Ms epoch when the slice was last updated. */
71596
71981
  lastChangedAt: number()
71597
71982
  });
@@ -71676,25 +72061,18 @@ var waterHeaterCapability = {
71676
72061
  * Forecast deferred — current conditions only for v1.
71677
72062
  */
71678
72063
  var WeatherStatusSchema = object({
71679
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
71680
- * when no condition has been reported yet. */
72064
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
71681
72065
  condition: string().nullable(),
71682
- /** Current temperature in the reported unit. Null when not provided. */
71683
- temperature: number().nullable(),
71684
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
71685
- temperatureUnit: string().nullable(),
72066
+ /** Current temperature in Celsius. Null when not provided or refused. */
72067
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
71686
72068
  /** Relative humidity (0..100). Null when not provided. */
71687
- humidity: number().min(0).max(100).nullable(),
71688
- /** Barometric pressure in the reported unit. Null when not provided. */
71689
- pressure: number().nullable(),
71690
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
71691
- pressureUnit: string().nullable(),
71692
- /** Wind speed in the reported unit. Null when not provided. */
71693
- windSpeed: number().nullable(),
71694
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
71695
- windSpeedUnit: string().nullable(),
72069
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
72070
+ /** Barometric pressure in hPa. Null when not provided or refused. */
72071
+ pressure: number().nullable().meta(readingMeta("pressure")),
72072
+ /** Wind speed in metres per second. Null when not provided or refused. */
72073
+ windSpeed: number().nullable().meta(readingMeta("speed")),
71696
72074
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
71697
- windBearing: number().nullable(),
72075
+ windBearing: number().nullable().meta(readingMeta("angle")),
71698
72076
  /** Ms epoch when the slice was last updated. */
71699
72077
  lastFetchedAt: number()
71700
72078
  });
@@ -71722,12 +72100,12 @@ var weatherCapability = {
71722
72100
  */
71723
72101
  durability: "session",
71724
72102
  /**
71725
- * History (D850): recorded series. Units follow the sibling `*Unit` fields.
72103
+ * History (D850): recorded series. Numbers are the quantity base (D944).
71726
72104
  */
71727
72105
  history: { fields: {
71728
72106
  temperature: {
71729
72107
  kind: "gauge",
71730
- unitFrom: "temperatureUnit",
72108
+ unit: "°C",
71731
72109
  deadband: .2
71732
72110
  },
71733
72111
  humidity: {
@@ -71737,12 +72115,12 @@ var weatherCapability = {
71737
72115
  },
71738
72116
  pressure: {
71739
72117
  kind: "gauge",
71740
- unitFrom: "pressureUnit",
72118
+ unit: "hPa",
71741
72119
  deadband: .5
71742
72120
  },
71743
72121
  windSpeed: {
71744
72122
  kind: "gauge",
71745
- unitFrom: "windSpeedUnit",
72123
+ unit: "m/s",
71746
72124
  deadband: .5
71747
72125
  }
71748
72126
  } }
@@ -73026,6 +73404,8 @@ var DEVICE_LOCAL_STATE_CAPS = {
73026
73404
  featureProbe: featureProbeCapability,
73027
73405
  flood: floodCapability,
73028
73406
  gas: gasCapability,
73407
+ groupCommands: groupCommandsCapability,
73408
+ groupStatus: groupStatusCapability,
73029
73409
  heatingValve: heatingValveCapability,
73030
73410
  humidifier: humidifierCapability,
73031
73411
  humiditySensor: humiditySensorCapability,
@@ -73919,6 +74299,13 @@ Object.freeze({
73919
74299
  access: "view",
73920
74300
  role: "admin"
73921
74301
  },
74302
+ "addons.getPackageDocs": {
74303
+ capName: "addons",
74304
+ capScope: "system",
74305
+ addonId: null,
74306
+ access: "view",
74307
+ role: "admin"
74308
+ },
73922
74309
  "addons.getVersions": {
73923
74310
  capName: "addons",
73924
74311
  capScope: "system",
@@ -77027,6 +77414,20 @@ Object.freeze({
77027
77414
  access: "view",
77028
77415
  role: "viewer"
77029
77416
  },
77417
+ "groupCommands.getStatus": {
77418
+ capName: "group-commands",
77419
+ capScope: "device",
77420
+ addonId: null,
77421
+ access: "view",
77422
+ role: "viewer"
77423
+ },
77424
+ "groupStatus.getStatus": {
77425
+ capName: "group-status",
77426
+ capScope: "device",
77427
+ addonId: null,
77428
+ access: "view",
77429
+ role: "viewer"
77430
+ },
77030
77431
  "heatingValve.getStatus": {
77031
77432
  capName: "heating-valve",
77032
77433
  capScope: "device",
@@ -86849,6 +87250,37 @@ var SENSOR_CAP_VOCABULARY = {
86849
87250
  icon: "flame"
86850
87251
  }]
86851
87252
  },
87253
+ "group-commands": {
87254
+ scope: "device",
87255
+ noun: t("Group command", "Comando di gruppo")
87256
+ },
87257
+ "group-status": {
87258
+ scope: "device",
87259
+ noun: t("Group", "Gruppo"),
87260
+ state: [
87261
+ {
87262
+ kind: "numeric",
87263
+ field: "included",
87264
+ quantity: t("the members reporting", "i membri che rispondono"),
87265
+ label: t("Reporting", "Rispondono"),
87266
+ icon: "users"
87267
+ },
87268
+ {
87269
+ kind: "numeric",
87270
+ field: "excluded",
87271
+ quantity: t("the members left out", "i membri esclusi"),
87272
+ label: t("Excluded", "Esclusi"),
87273
+ icon: "users"
87274
+ },
87275
+ {
87276
+ kind: "numeric",
87277
+ field: "total",
87278
+ quantity: t("the member count", "il numero di membri"),
87279
+ label: t("Members", "Membri"),
87280
+ icon: "sigma"
87281
+ }
87282
+ ]
87283
+ },
86852
87284
  "humidity-sensor": {
86853
87285
  scope: "device",
86854
87286
  noun: t("Humidity", "Umidità"),
@@ -86882,7 +87314,6 @@ var SENSOR_CAP_VOCABULARY = {
86882
87314
  field: "value",
86883
87315
  quantity: t("the reading", "il valore"),
86884
87316
  label: t("Reading", "Valore"),
86885
- unitField: "unit",
86886
87317
  icon: "gauge"
86887
87318
  }]
86888
87319
  },
@@ -87018,7 +87449,7 @@ var SENSOR_CAP_VOCABULARY = {
87018
87449
  field: "temperature",
87019
87450
  quantity: t("the outdoor temperature", "la temperatura esterna"),
87020
87451
  label: t("Temperature", "Temperatura"),
87021
- unitField: "temperatureUnit",
87452
+ unit: "°C",
87022
87453
  icon: "thermometer"
87023
87454
  },
87024
87455
  {
@@ -87034,7 +87465,7 @@ var SENSOR_CAP_VOCABULARY = {
87034
87465
  field: "windSpeed",
87035
87466
  quantity: t("the wind speed", "la velocità del vento"),
87036
87467
  label: t("Wind", "Vento"),
87037
- unitField: "windSpeedUnit",
87468
+ unit: "m/s",
87038
87469
  icon: "wind"
87039
87470
  },
87040
87471
  {
@@ -87042,7 +87473,7 @@ var SENSOR_CAP_VOCABULARY = {
87042
87473
  field: "pressure",
87043
87474
  quantity: t("the air pressure", "la pressione atmosferica"),
87044
87475
  label: t("Pressure", "Pressione"),
87045
- unitField: "pressureUnit",
87476
+ unit: "hPa",
87046
87477
  icon: "gauge"
87047
87478
  }
87048
87479
  ]
@@ -88315,6 +88746,16 @@ Object.freeze({
88315
88746
  form: "single",
88316
88747
  optional: false
88317
88748
  }],
88749
+ "groupCommands.getStatus": [{
88750
+ name: "deviceId",
88751
+ form: "single",
88752
+ optional: false
88753
+ }],
88754
+ "groupStatus.getStatus": [{
88755
+ name: "deviceId",
88756
+ form: "single",
88757
+ optional: false
88758
+ }],
88318
88759
  "heatingValve.getStatus": [{
88319
88760
  name: "deviceId",
88320
88761
  form: "single",
@@ -90460,8 +90901,8 @@ var NC_SYSTEM_EVENT_FILTER_KEYS = [
90460
90901
  function systemEventFilterApplies(kind, filter) {
90461
90902
  if (LEGACY_KINDS_WITHOUT_FILTERS.has(kind)) return false;
90462
90903
  switch (filter) {
90463
- case "deviceIds": return kind.startsWith("device-") || kind.startsWith("stream-") || kind === "detection-blind" || kind.startsWith("motion-") || kind.startsWith("recording-") || kind === "alarm-triggered";
90464
- case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind" || kind.startsWith("motion-");
90904
+ case "deviceIds": return kind.startsWith("device-") || kind.startsWith("stream-") || kind === "detection-blind" || kind.startsWith("motion-") || kind.startsWith("package-llm-") || kind.startsWith("recording-") || kind === "alarm-triggered";
90905
+ case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind" || kind.startsWith("motion-") || kind.startsWith("package-llm-");
90465
90906
  case "nodeIds": return kind.startsWith("node-") || kind === "addon-crash-loop" || kind === "addon-update-available" || kind === "addon-updated" || kind === "server-update-available" || kind === "wrapper-update-available" || kind === "server-updated" || kind.startsWith("storage-location-") || kind.startsWith("alert-");
90466
90907
  case "packageNames": return kind.endsWith("update-available") || kind === "addon-updated" || kind === "node-updating" || kind === "server-updated";
90467
90908
  case "consumableLowPercent": return NC_CONSUMABLE_SYSTEM_EVENT_KINDS.includes(kind);
@@ -90505,6 +90946,8 @@ var NC_SYSTEM_EVENT_FAMILY = {
90505
90946
  "detection-blind": "device-events",
90506
90947
  "motion-noisy": "device-events",
90507
90948
  "motion-calm": "device-events",
90949
+ "package-llm-silent": "device-events",
90950
+ "package-llm-answered": "device-events",
90508
90951
  "recording-stalled": "device-events",
90509
90952
  "recording-healthy": "device-events",
90510
90953
  "recording-degraded": "device-events",
@@ -90606,6 +91049,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
90606
91049
  label: "Motion without a subject",
90607
91050
  kinds: ["motion-noisy", "motion-calm"]
90608
91051
  },
91052
+ {
91053
+ id: "package-llm",
91054
+ family: "device-events",
91055
+ label: "Package confirm",
91056
+ kinds: ["package-llm-silent", "package-llm-answered"]
91057
+ },
90609
91058
  {
90610
91059
  id: "recording",
90611
91060
  family: "device-events",
@@ -92038,6 +92487,8 @@ var DEVICE_EVENTS = [
92038
92487
  "detection-blind",
92039
92488
  "motion-noisy",
92040
92489
  "motion-calm",
92490
+ "package-llm-silent",
92491
+ "package-llm-answered",
92041
92492
  "recording-stalled",
92042
92493
  "recording-healthy",
92043
92494
  "recording-degraded",
@@ -92641,7 +93092,8 @@ var NC_TEMPLATE_VARS = [
92641
93092
  systemEventKinds: [
92642
93093
  "recording-degraded",
92643
93094
  ...DEVICE_ATTENTION,
92644
- ...ALERT_EPISODE
93095
+ ...ALERT_EPISODE,
93096
+ "package-llm-silent"
92645
93097
  ]
92646
93098
  },
92647
93099
  {
@@ -92734,7 +93186,8 @@ var NC_TEMPLATE_VARS = [
92734
93186
  systemEventKinds: [
92735
93187
  "storage-location-low",
92736
93188
  "storage-location-critical",
92737
- "storage-location-ok"
93189
+ "storage-location-ok",
93190
+ "package-llm-silent"
92738
93191
  ]
92739
93192
  },
92740
93193
  {
@@ -93145,7 +93598,9 @@ var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
93145
93598
  "device-attention-required",
93146
93599
  "device-attention-resolved",
93147
93600
  "rule-broken",
93148
- "rule-repaired"
93601
+ "rule-repaired",
93602
+ "package-llm-silent",
93603
+ "package-llm-answered"
93149
93604
  ];
93150
93605
  /**
93151
93606
  * The catalog as `getTemplateCatalog` SERVES it: every descriptor, with the
@@ -93305,6 +93760,15 @@ var CLASS_BY_KIND = new Map([
93305
93760
  snapshotLabel: "Cameras with constant motion now",
93306
93761
  snapshotSection: null
93307
93762
  },
93763
+ {
93764
+ id: "package-llm-confirm",
93765
+ family: "device-events",
93766
+ label: "Package confirm (not answering / answering again)",
93767
+ bad: ["package-llm-silent"],
93768
+ recovery: ["package-llm-answered"],
93769
+ snapshotLabel: "Cameras whose package confirm is not answering",
93770
+ snapshotSection: null
93771
+ },
93308
93772
  {
93309
93773
  id: "recording-health",
93310
93774
  family: "device-events",