@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
@@ -30,7 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  }) : target, mod));
31
31
  //#endregion
32
32
  let node_crypto = require("node:crypto");
33
- //#region ../types/dist/event-category-DHXxB8t9.mjs
33
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
34
34
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
35
35
  EventCategory["SystemBoot"] = "system.boot";
36
36
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -356,6 +356,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
356
356
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
357
357
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
358
358
  /**
359
+ * A camera's package LLM confirm has failed closed three times in a row
360
+ * (D931, 2026-10-26). One edge per camera until
361
+ * {@link PipelinePackageLlmAnswered}.
362
+ */
363
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
364
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
365
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
366
+ /**
359
367
  * Per-camera pipeline config was mutated by the orchestrator
360
368
  * (a settings change via `setAgentInferenceDevices` /
361
369
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5663,7 +5671,7 @@ var ZodIssueCode = {
5663
5671
  var ZodFirstPartyTypeKind;
5664
5672
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5665
5673
  //#endregion
5666
- //#region ../types/dist/sleep-BErAxhul.mjs
5674
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5667
5675
  /**
5668
5676
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5669
5677
  * window to float samples (D455).
@@ -8363,6 +8371,99 @@ function defineRecordKinds(specs) {
8363
8371
  }
8364
8372
  return out;
8365
8373
  }
8374
+ var READING_QUANTITIES = [
8375
+ "temperature",
8376
+ "pressure",
8377
+ "speed",
8378
+ "precipitation",
8379
+ "precipitation-rate",
8380
+ "length",
8381
+ "area",
8382
+ "illuminance",
8383
+ "irradiance",
8384
+ "power",
8385
+ "energy",
8386
+ "voltage",
8387
+ "current",
8388
+ "apparent-power",
8389
+ "humidity",
8390
+ "percentage",
8391
+ "concentration-volume",
8392
+ "concentration-mass",
8393
+ "concentration-ppb",
8394
+ "volume",
8395
+ "duration",
8396
+ "duration-ms",
8397
+ "information",
8398
+ "sound-level",
8399
+ "signal-level",
8400
+ "colour-temperature",
8401
+ "angle",
8402
+ "bitrate",
8403
+ "rate",
8404
+ "mass",
8405
+ "index",
8406
+ "ratio",
8407
+ "count",
8408
+ "none",
8409
+ "device"
8410
+ ];
8411
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8412
+ new Set(READING_QUANTITIES);
8413
+ /**
8414
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8415
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8416
+ */
8417
+ var QUANTITY_BASE = {
8418
+ temperature: "°C",
8419
+ pressure: "hPa",
8420
+ speed: "m/s",
8421
+ precipitation: "mm",
8422
+ "precipitation-rate": "mm/h",
8423
+ length: "m",
8424
+ area: "m²",
8425
+ illuminance: "lx",
8426
+ irradiance: "W/m²",
8427
+ power: "W",
8428
+ energy: "kWh",
8429
+ voltage: "V",
8430
+ current: "A",
8431
+ "apparent-power": "VA",
8432
+ humidity: "%",
8433
+ percentage: "%",
8434
+ "concentration-volume": "ppm",
8435
+ "concentration-mass": "µg/m³",
8436
+ "concentration-ppb": "ppb",
8437
+ volume: "L",
8438
+ duration: "s",
8439
+ "duration-ms": "ms",
8440
+ information: "B",
8441
+ "sound-level": "dBFS",
8442
+ "signal-level": "dBm",
8443
+ "colour-temperature": "mired",
8444
+ angle: "°",
8445
+ bitrate: "kbps",
8446
+ rate: "fps",
8447
+ mass: "kg",
8448
+ index: "none",
8449
+ ratio: "none",
8450
+ count: "none",
8451
+ none: "none",
8452
+ device: "device"
8453
+ };
8454
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8455
+ function baseUnitOf(quantity) {
8456
+ const symbol = QUANTITY_BASE[quantity];
8457
+ if (symbol === "none" || symbol === "device") return void 0;
8458
+ return symbol;
8459
+ }
8460
+ function readingMeta(quantity, extras) {
8461
+ return {
8462
+ quantity,
8463
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8464
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8465
+ };
8466
+ }
8366
8467
  /** Shared Zod schemas used across streaming capabilities. */
8367
8468
  var CamProfileSchema = _enum([
8368
8469
  "high",
@@ -8390,8 +8491,8 @@ var CamStreamKindSchema = _enum([
8390
8491
  "derived"
8391
8492
  ]);
8392
8493
  var CamStreamResolutionSchema = object({
8393
- width: number().int().positive(),
8394
- height: number().int().positive()
8494
+ width: number().int().positive().meta(readingMeta("none")),
8495
+ height: number().int().positive().meta(readingMeta("none"))
8395
8496
  });
8396
8497
  var CameraStreamSchema = object({
8397
8498
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8991,6 +9092,8 @@ var CAP_NAMES = Object.freeze([
8991
9092
  "filesystem-browse",
8992
9093
  "flood",
8993
9094
  "gas",
9095
+ "group-commands",
9096
+ "group-status",
8994
9097
  "heating-valve",
8995
9098
  "humidifier",
8996
9099
  "humidity-sensor",
@@ -9120,14 +9223,6 @@ Object.freeze({
9120
9223
  {
9121
9224
  "path": "aqi",
9122
9225
  "valueType": "number"
9123
- },
9124
- {
9125
- "path": "unit",
9126
- "valueType": "string"
9127
- },
9128
- {
9129
- "path": "precision",
9130
- "valueType": "number"
9131
9226
  }
9132
9227
  ],
9133
9228
  "alarm-panel": [
@@ -9163,20 +9258,10 @@ Object.freeze({
9163
9258
  "valueType": "boolean"
9164
9259
  }
9165
9260
  ],
9166
- "ambient-light-sensor": [
9167
- {
9168
- "path": "lux",
9169
- "valueType": "number"
9170
- },
9171
- {
9172
- "path": "unit",
9173
- "valueType": "string"
9174
- },
9175
- {
9176
- "path": "precision",
9177
- "valueType": "number"
9178
- }
9179
- ],
9261
+ "ambient-light-sensor": [{
9262
+ "path": "lux",
9263
+ "valueType": "number"
9264
+ }],
9180
9265
  "audio-metrics": [
9181
9266
  {
9182
9267
  "path": "ts",
@@ -9256,6 +9341,10 @@ Object.freeze({
9256
9341
  "path": "lastUpdated",
9257
9342
  "valueType": "number"
9258
9343
  },
9344
+ {
9345
+ "path": "volts",
9346
+ "valueType": "number"
9347
+ },
9259
9348
  {
9260
9349
  "path": "lastContactAt",
9261
9350
  "valueType": "number"
@@ -9498,10 +9587,6 @@ Object.freeze({
9498
9587
  "datetime"
9499
9588
  ]
9500
9589
  },
9501
- {
9502
- "path": "unit",
9503
- "valueType": "string"
9504
- },
9505
9590
  {
9506
9591
  "path": "min",
9507
9592
  "valueType": "number"
@@ -9513,19 +9598,6 @@ Object.freeze({
9513
9598
  {
9514
9599
  "path": "step",
9515
9600
  "valueType": "number"
9516
- },
9517
- {
9518
- "path": "precision",
9519
- "valueType": "number"
9520
- },
9521
- {
9522
- "path": "format",
9523
- "valueType": "enum",
9524
- "enumValues": [
9525
- "date",
9526
- "time",
9527
- "datetime"
9528
- ]
9529
9601
  }
9530
9602
  ],
9531
9603
  "cover": [
@@ -9766,13 +9838,8 @@ Object.freeze({
9766
9838
  "path": "value",
9767
9839
  "valueType": "string"
9768
9840
  }, {
9769
- "path": "format",
9770
- "valueType": "enum",
9771
- "enumValues": [
9772
- "date",
9773
- "time",
9774
- "datetime"
9775
- ]
9841
+ "path": "timestamp",
9842
+ "valueType": "boolean"
9776
9843
  }],
9777
9844
  "event-emitter": [
9778
9845
  {
@@ -9845,6 +9912,20 @@ Object.freeze({
9845
9912
  "path": "detected",
9846
9913
  "valueType": "boolean"
9847
9914
  }],
9915
+ "group-status": [
9916
+ {
9917
+ "path": "included",
9918
+ "valueType": "number"
9919
+ },
9920
+ {
9921
+ "path": "excluded",
9922
+ "valueType": "number"
9923
+ },
9924
+ {
9925
+ "path": "total",
9926
+ "valueType": "number"
9927
+ }
9928
+ ],
9848
9929
  "heating-valve": [{
9849
9930
  "path": "position",
9850
9931
  "valueType": "number"
@@ -9879,20 +9960,10 @@ Object.freeze({
9879
9960
  "valueType": "number"
9880
9961
  }
9881
9962
  ],
9882
- "humidity-sensor": [
9883
- {
9884
- "path": "percent",
9885
- "valueType": "number"
9886
- },
9887
- {
9888
- "path": "unit",
9889
- "valueType": "string"
9890
- },
9891
- {
9892
- "path": "precision",
9893
- "valueType": "number"
9894
- }
9895
- ],
9963
+ "humidity-sensor": [{
9964
+ "path": "percent",
9965
+ "valueType": "number"
9966
+ }],
9896
9967
  "image": [{
9897
9968
  "path": "url",
9898
9969
  "valueType": "string"
@@ -10231,20 +10302,10 @@ Object.freeze({
10231
10302
  "valueType": "boolean"
10232
10303
  }
10233
10304
  ],
10234
- "numeric-sensor": [
10235
- {
10236
- "path": "value",
10237
- "valueType": "number"
10238
- },
10239
- {
10240
- "path": "unit",
10241
- "valueType": "string"
10242
- },
10243
- {
10244
- "path": "precision",
10245
- "valueType": "number"
10246
- }
10247
- ],
10305
+ "numeric-sensor": [{
10306
+ "path": "value",
10307
+ "valueType": "number"
10308
+ }],
10248
10309
  "pet-feeder": [
10249
10310
  {
10250
10311
  "path": "foodLevel",
@@ -10336,14 +10397,6 @@ Object.freeze({
10336
10397
  {
10337
10398
  "path": "powerFactor",
10338
10399
  "valueType": "number"
10339
- },
10340
- {
10341
- "path": "unit",
10342
- "valueType": "string"
10343
- },
10344
- {
10345
- "path": "precision",
10346
- "valueType": "number"
10347
10400
  }
10348
10401
  ],
10349
10402
  "presence": [
@@ -10372,20 +10425,10 @@ Object.freeze({
10372
10425
  "valueType": "number"
10373
10426
  }
10374
10427
  ],
10375
- "pressure-sensor": [
10376
- {
10377
- "path": "hpa",
10378
- "valueType": "number"
10379
- },
10380
- {
10381
- "path": "unit",
10382
- "valueType": "string"
10383
- },
10384
- {
10385
- "path": "precision",
10386
- "valueType": "number"
10387
- }
10388
- ],
10428
+ "pressure-sensor": [{
10429
+ "path": "hpa",
10430
+ "valueType": "number"
10431
+ }],
10389
10432
  "privacy-mask": [{
10390
10433
  "path": "enabled",
10391
10434
  "valueType": "boolean"
@@ -10696,20 +10739,10 @@ Object.freeze({
10696
10739
  "path": "tampered",
10697
10740
  "valueType": "boolean"
10698
10741
  }],
10699
- "temperature-sensor": [
10700
- {
10701
- "path": "celsius",
10702
- "valueType": "number"
10703
- },
10704
- {
10705
- "path": "unit",
10706
- "valueType": "string"
10707
- },
10708
- {
10709
- "path": "precision",
10710
- "valueType": "number"
10711
- }
10712
- ],
10742
+ "temperature-sensor": [{
10743
+ "path": "celsius",
10744
+ "valueType": "number"
10745
+ }],
10713
10746
  "update": [
10714
10747
  {
10715
10748
  "path": "currentVersion",
@@ -10820,10 +10853,6 @@ Object.freeze({
10820
10853
  "path": "temperature",
10821
10854
  "valueType": "number"
10822
10855
  },
10823
- {
10824
- "path": "temperatureUnit",
10825
- "valueType": "string"
10826
- },
10827
10856
  {
10828
10857
  "path": "humidity",
10829
10858
  "valueType": "number"
@@ -10832,18 +10861,10 @@ Object.freeze({
10832
10861
  "path": "pressure",
10833
10862
  "valueType": "number"
10834
10863
  },
10835
- {
10836
- "path": "pressureUnit",
10837
- "valueType": "string"
10838
- },
10839
10864
  {
10840
10865
  "path": "windSpeed",
10841
10866
  "valueType": "number"
10842
10867
  },
10843
- {
10844
- "path": "windSpeedUnit",
10845
- "valueType": "string"
10846
- },
10847
10868
  {
10848
10869
  "path": "windBearing",
10849
10870
  "valueType": "number"
@@ -10895,7 +10916,7 @@ Object.freeze({
10895
10916
  * can answer "may a rule actuate this?" without importing the schema barrel
10896
10917
  * (~144MB RSS per runner, D28).
10897
10918
  *
10898
- * Coverage: 91 device-scoped capabilities.
10919
+ * Coverage: 93 device-scoped capabilities.
10899
10920
  */
10900
10921
  var DEVICE_SCOPED_CAPS = new Set([
10901
10922
  "accessories",
@@ -10936,6 +10957,8 @@ var DEVICE_SCOPED_CAPS = new Set([
10936
10957
  "feature-probe",
10937
10958
  "flood",
10938
10959
  "gas",
10960
+ "group-commands",
10961
+ "group-status",
10939
10962
  "heating-valve",
10940
10963
  "humidifier",
10941
10964
  "humidity-sensor",
@@ -11387,7 +11410,7 @@ function errMsg(err) {
11387
11410
  return String(err);
11388
11411
  }
11389
11412
  //#endregion
11390
- //#region ../types/dist/composition-kEl9mevu.mjs
11413
+ //#region ../types/dist/composition-OhpuespA.mjs
11391
11414
  /**
11392
11415
  * Deterministic SHA-256 hash of an arbitrary serialisable value. The
11393
11416
  * canonical form sorts object keys alphabetically at every depth so two
@@ -12302,15 +12325,74 @@ function validateExpressionSource(src) {
12302
12325
  }
12303
12326
  return null;
12304
12327
  }
12305
- /** A composed device's stableId is this prefix plus the block id. */
12306
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
12307
- /** Longest snippet a `code` source may carry (slice 4). */
12308
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
12328
+ /**
12329
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
12330
+ * id. Split out so helper schemas can name it without a cycle through
12331
+ * `composition.ts` (D659).
12332
+ */
12309
12333
  var CompositionSourceRefSchema = object({
12310
12334
  /** The owning addon; `stableId` is unique only within it. */
12311
12335
  addonId: string().min(1),
12312
12336
  stableId: string().min(1)
12313
12337
  });
12338
+ var GROUP_TEMPLATE_IDS = [
12339
+ "temperature",
12340
+ "humidity",
12341
+ "pressure",
12342
+ "illuminance",
12343
+ "power",
12344
+ "energy",
12345
+ "battery",
12346
+ "numeric",
12347
+ "motion",
12348
+ "contact",
12349
+ "leak",
12350
+ "smoke",
12351
+ "occupancy",
12352
+ "binary",
12353
+ "switch",
12354
+ "light"
12355
+ ];
12356
+ var NUMERIC_AGGREGATES = [
12357
+ "mean",
12358
+ "median",
12359
+ "min",
12360
+ "max",
12361
+ "sum"
12362
+ ];
12363
+ var BINARY_AGGREGATES = ["any", "all"];
12364
+ /** How an actuator group decides it is on. Unknown members never count as off. */
12365
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
12366
+ var AGGREGATE_REDUCERS = [
12367
+ "mean",
12368
+ "median",
12369
+ "min",
12370
+ "max",
12371
+ "sum",
12372
+ "any",
12373
+ "all"
12374
+ ];
12375
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
12376
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
12377
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
12378
+ /** One member. The field is stored so occupancy can name motion or binary. */
12379
+ var HelperMemberSchema = object({
12380
+ source: CompositionSourceRefSchema,
12381
+ cap: string().min(1),
12382
+ fieldPath: string().min(1)
12383
+ });
12384
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
12385
+ kind: literal("group"),
12386
+ templateId: GroupTemplateIdSchema,
12387
+ aggregate: AggregateReducerSchema,
12388
+ members: array(HelperMemberSchema).min(1).max(64),
12389
+ /** Actuator groups only. Readings leave this unset. */
12390
+ active: ActiveCriterionSchema.optional()
12391
+ })]);
12392
+ /** A composed device's stableId is this prefix plus the block id. */
12393
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
12394
+ /** Longest snippet a `code` source may carry (slice 4). */
12395
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
12314
12396
  /** One field of one capability of one source device. */
12315
12397
  var CompositionFieldReadSchema = object({
12316
12398
  source: CompositionSourceRefSchema,
@@ -12354,7 +12436,33 @@ var CompositionCodeSourceSchema = object({
12354
12436
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12355
12437
  CompositionFromSourceSchema,
12356
12438
  CompositionExpressionSourceSchema,
12357
- CompositionCodeSourceSchema
12439
+ CompositionCodeSourceSchema,
12440
+ object({
12441
+ kind: literal("aggregate"),
12442
+ reducer: _enum([
12443
+ "mean",
12444
+ "median",
12445
+ "min",
12446
+ "max",
12447
+ "sum",
12448
+ "any",
12449
+ "all"
12450
+ ]),
12451
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
12452
+ /** `value` is the reading; the others are the member counts. */
12453
+ publish: _enum([
12454
+ "value",
12455
+ "included",
12456
+ "excluded",
12457
+ "total"
12458
+ ]),
12459
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
12460
+ empty: _enum([
12461
+ "null",
12462
+ "omit",
12463
+ "offline"
12464
+ ])
12465
+ })
12358
12466
  ]);
12359
12467
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
12360
12468
  kind: literal("forward"),
@@ -12400,7 +12508,13 @@ var CompositionSchema = object({
12400
12508
  kind: literal("existing"),
12401
12509
  device: CompositionSourceRefSchema
12402
12510
  })]),
12403
- features: array(CompositionFeatureSchema).min(1).max(16)
12511
+ features: array(CompositionFeatureSchema).min(1).max(16),
12512
+ /**
12513
+ * Present when the block is a helper template. Absent on a free
12514
+ * composition (the room example, device 4603). The editor reads this,
12515
+ * never the compiled features.
12516
+ */
12517
+ helper: HelperSpecSchema.optional()
12404
12518
  }).superRefine((composition, ctx) => {
12405
12519
  const seen = /* @__PURE__ */ new Set();
12406
12520
  composition.features.forEach((feature, index) => {
@@ -19391,7 +19505,11 @@ var CompositionProblemSchema = object({
19391
19505
  "item-field-reserved",
19392
19506
  "target-missing",
19393
19507
  "target-unreadable",
19394
- "not-replaceable"
19508
+ "not-replaceable",
19509
+ "native-capability-replace",
19510
+ "mixed-quantity",
19511
+ "helper-mismatch",
19512
+ "member-constraint"
19395
19513
  ]),
19396
19514
  path: string(),
19397
19515
  message: string()
@@ -19412,7 +19530,8 @@ var CompositionUnavailableModeSchema = _enum([
19412
19530
  "null",
19413
19531
  "offline",
19414
19532
  "unknown",
19415
- "release"
19533
+ "release",
19534
+ "omit"
19416
19535
  ]);
19417
19536
  /**
19418
19537
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -19449,9 +19568,11 @@ var CompositionFieldPlanSchema = object({
19449
19568
  reads: array(CompositionFieldReadSchema)
19450
19569
  });
19451
19570
  /**
19452
- * What a feature does to its target (D663): `new` builds a capability on a new
19453
- * device; on an existing device it `add`s a capability the device lacks, or
19454
- * `replace`s named fields of one it has natively.
19571
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
19572
+ * new device; on an existing device it `add`s a capability the device lacks.
19573
+ * Replacing a capability the device already serves is refused
19574
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
19575
+ * wire for a claim the hub already holds.
19455
19576
  */
19456
19577
  var CompositionFeatureModeSchema = _enum([
19457
19578
  "new",
@@ -19624,6 +19745,275 @@ Object.freeze({
19624
19745
  result: ["number", "null"]
19625
19746
  }
19626
19747
  });
19748
+ /**
19749
+ * One catalog row per base reading a group helper can build (D944 §3).
19750
+ * Data, not a form default written into the block.
19751
+ */
19752
+ var NOW = {
19753
+ fieldPath: "lastFetchedAt",
19754
+ expr: "now"
19755
+ };
19756
+ var CHANGED = {
19757
+ fieldPath: "lastChangedAt",
19758
+ expr: "now"
19759
+ };
19760
+ function numeric(input) {
19761
+ return {
19762
+ ...input,
19763
+ family: "numeric",
19764
+ deviceType: DeviceType.Sensor,
19765
+ memberDeviceType: DeviceType.Sensor,
19766
+ commandCaps: [],
19767
+ baseUnit: baseUnitOf(input.quantity),
19768
+ aggregates: NUMERIC_AGGREGATES,
19769
+ accept: [{
19770
+ cap: input.outputCap,
19771
+ fieldPath: input.outputField
19772
+ }]
19773
+ };
19774
+ }
19775
+ function binary(input) {
19776
+ return {
19777
+ ...input,
19778
+ family: "binary",
19779
+ deviceType: DeviceType.Sensor,
19780
+ memberDeviceType: DeviceType.Sensor,
19781
+ commandCaps: [],
19782
+ quantity: "none",
19783
+ baseUnit: void 0,
19784
+ aggregates: BINARY_AGGREGATES,
19785
+ empty: "offline"
19786
+ };
19787
+ }
19788
+ var GROUP_TEMPLATES = [
19789
+ numeric({
19790
+ id: "temperature",
19791
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
19792
+ role: DeviceRole.TemperatureSensor,
19793
+ outputCap: "temperature-sensor",
19794
+ outputField: "celsius",
19795
+ quantity: "temperature",
19796
+ defaultAggregate: "mean",
19797
+ empty: "null",
19798
+ companions: [NOW]
19799
+ }),
19800
+ numeric({
19801
+ id: "humidity",
19802
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
19803
+ role: DeviceRole.HumiditySensor,
19804
+ outputCap: "humidity-sensor",
19805
+ outputField: "percent",
19806
+ quantity: "humidity",
19807
+ defaultAggregate: "mean",
19808
+ empty: "null",
19809
+ companions: [NOW]
19810
+ }),
19811
+ numeric({
19812
+ id: "pressure",
19813
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
19814
+ role: DeviceRole.PressureSensor,
19815
+ outputCap: "pressure-sensor",
19816
+ outputField: "hpa",
19817
+ quantity: "pressure",
19818
+ defaultAggregate: "mean",
19819
+ empty: "null",
19820
+ companions: [NOW]
19821
+ }),
19822
+ numeric({
19823
+ id: "illuminance",
19824
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
19825
+ role: DeviceRole.AmbientLightSensor,
19826
+ outputCap: "ambient-light-sensor",
19827
+ outputField: "lux",
19828
+ quantity: "illuminance",
19829
+ defaultAggregate: "mean",
19830
+ empty: "null",
19831
+ companions: [NOW]
19832
+ }),
19833
+ numeric({
19834
+ id: "power",
19835
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
19836
+ role: DeviceRole.PowerSensor,
19837
+ outputCap: "power-meter",
19838
+ outputField: "watts",
19839
+ quantity: "power",
19840
+ defaultAggregate: "sum",
19841
+ empty: "omit",
19842
+ companions: [NOW]
19843
+ }),
19844
+ numeric({
19845
+ id: "energy",
19846
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
19847
+ role: DeviceRole.EnergySensor,
19848
+ outputCap: "power-meter",
19849
+ outputField: "kwhTotal",
19850
+ quantity: "energy",
19851
+ defaultAggregate: "sum",
19852
+ empty: "omit",
19853
+ companions: [NOW]
19854
+ }),
19855
+ numeric({
19856
+ id: "battery",
19857
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
19858
+ role: DeviceRole.BatterySensor,
19859
+ outputCap: "battery",
19860
+ outputField: "percentage",
19861
+ quantity: "percentage",
19862
+ defaultAggregate: "mean",
19863
+ empty: "null",
19864
+ companions: [
19865
+ {
19866
+ fieldPath: "charging",
19867
+ expr: "\"unknown\""
19868
+ },
19869
+ {
19870
+ fieldPath: "sleeping",
19871
+ expr: "false"
19872
+ },
19873
+ {
19874
+ fieldPath: "lastUpdated",
19875
+ expr: "now"
19876
+ }
19877
+ ]
19878
+ }),
19879
+ numeric({
19880
+ id: "numeric",
19881
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
19882
+ role: DeviceRole.NumericSensor,
19883
+ outputCap: "numeric-sensor",
19884
+ outputField: "value",
19885
+ quantity: "device",
19886
+ defaultAggregate: "mean",
19887
+ empty: "null",
19888
+ companions: [NOW]
19889
+ }),
19890
+ binary({
19891
+ id: "motion",
19892
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
19893
+ role: DeviceRole.MotionSensor,
19894
+ outputCap: "motion",
19895
+ outputField: "detected",
19896
+ defaultAggregate: "any",
19897
+ accept: [{
19898
+ cap: "motion",
19899
+ fieldPath: "detected"
19900
+ }],
19901
+ companions: [{
19902
+ fieldPath: "lastDetectedAt",
19903
+ expr: "null"
19904
+ }, {
19905
+ fieldPath: "autoClearAfterMs",
19906
+ expr: "null"
19907
+ }]
19908
+ }),
19909
+ binary({
19910
+ id: "contact",
19911
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
19912
+ role: DeviceRole.ContactSensor,
19913
+ outputCap: "contact",
19914
+ outputField: "entryOpen",
19915
+ defaultAggregate: "any",
19916
+ accept: [{
19917
+ cap: "contact",
19918
+ fieldPath: "entryOpen"
19919
+ }],
19920
+ companions: [CHANGED]
19921
+ }),
19922
+ binary({
19923
+ id: "leak",
19924
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
19925
+ role: DeviceRole.LeakSensor,
19926
+ outputCap: "flood",
19927
+ outputField: "flooded",
19928
+ defaultAggregate: "any",
19929
+ accept: [{
19930
+ cap: "flood",
19931
+ fieldPath: "flooded"
19932
+ }],
19933
+ companions: [CHANGED]
19934
+ }),
19935
+ binary({
19936
+ id: "smoke",
19937
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
19938
+ role: DeviceRole.SmokeSensor,
19939
+ outputCap: "smoke",
19940
+ outputField: "detected",
19941
+ defaultAggregate: "any",
19942
+ accept: [{
19943
+ cap: "smoke",
19944
+ fieldPath: "detected"
19945
+ }],
19946
+ companions: [CHANGED]
19947
+ }),
19948
+ binary({
19949
+ id: "occupancy",
19950
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
19951
+ role: DeviceRole.BinarySensor,
19952
+ outputCap: "binary",
19953
+ outputField: "on",
19954
+ defaultAggregate: "any",
19955
+ accept: [{
19956
+ cap: "motion",
19957
+ fieldPath: "detected"
19958
+ }, {
19959
+ cap: "binary",
19960
+ fieldPath: "on"
19961
+ }],
19962
+ companions: [CHANGED]
19963
+ }),
19964
+ binary({
19965
+ id: "binary",
19966
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
19967
+ role: DeviceRole.BinarySensor,
19968
+ outputCap: "binary",
19969
+ outputField: "on",
19970
+ defaultAggregate: "any",
19971
+ accept: [{
19972
+ cap: "binary",
19973
+ fieldPath: "on"
19974
+ }],
19975
+ companions: [CHANGED]
19976
+ }),
19977
+ actuator({
19978
+ id: "switch",
19979
+ 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.",
19980
+ deviceType: DeviceType.Switch,
19981
+ outputCap: "switch",
19982
+ commandCaps: ["switch"]
19983
+ }),
19984
+ actuator({
19985
+ id: "light",
19986
+ 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.",
19987
+ deviceType: DeviceType.Light,
19988
+ outputCap: "switch",
19989
+ commandCaps: [
19990
+ "switch",
19991
+ "brightness",
19992
+ "color"
19993
+ ]
19994
+ })
19995
+ ];
19996
+ function actuator(input) {
19997
+ return {
19998
+ ...input,
19999
+ family: "actuator",
20000
+ role: void 0,
20001
+ memberDeviceType: input.deviceType,
20002
+ outputField: "on",
20003
+ quantity: "none",
20004
+ baseUnit: void 0,
20005
+ aggregates: BINARY_AGGREGATES,
20006
+ defaultAggregate: "any",
20007
+ empty: "offline",
20008
+ accept: [{
20009
+ cap: "switch",
20010
+ fieldPath: "on"
20011
+ }],
20012
+ companions: [CHANGED]
20013
+ };
20014
+ }
20015
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
20016
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
19627
20017
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
19628
20018
  /**
19629
20019
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -21344,12 +21734,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
21344
21734
  * providers detect renames by matching `uniqueId` and patch `id`
21345
21735
  * in-place so dispatch keeps working.
21346
21736
  *
21347
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
21348
- * `precision`) live in the cap STATUS SLICE so they flow live with every
21349
- * state push and are the single source of truth. The `device_class` tag is
21350
- * an HA-specific input that is consumed at the discovery boundary
21351
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
21352
- * stored after that point.
21737
+ * **Display unit and precision are NOT stored here.** They are a read-time
21738
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
21739
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
21740
+ * neither is repeated on each sample. The `device_class` tag is an
21741
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
21742
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
21353
21743
  *
21354
21744
  * The `raw` escape hatch carries provider-specific attributes that don't
21355
21745
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -21363,6 +21753,14 @@ var SourceInfoSchema = object({
21363
21753
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
21364
21754
  * Used to detect rename when `id` changes. */
21365
21755
  uniqueId: string().optional(),
21756
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
21757
+ quantity: ReadingQuantitySchema.optional(),
21758
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
21759
+ dateTimeShape: _enum([
21760
+ "date",
21761
+ "time",
21762
+ "datetime"
21763
+ ]).optional(),
21366
21764
  /** Provider-specific extras that don't fit the structured slots.
21367
21765
  * Whitelist what goes here per provider — do NOT dump entire HA
21368
21766
  * attribute blobs (would explode the meta JSON column). */
@@ -42390,7 +42788,8 @@ method(object({
42390
42788
  "enum"
42391
42789
  ]),
42392
42790
  enumValues: array(string()).optional(),
42393
- item: boolean().optional()
42791
+ item: boolean().optional(),
42792
+ quantity: ReadingQuantitySchema.optional()
42394
42793
  })).readonly(),
42395
42794
  itemArray: object({
42396
42795
  path: string(),
@@ -46457,8 +46856,8 @@ var MaskPolygonShapeSchema = object({
46457
46856
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
46458
46857
  var MaskGridShapeSchema = object({
46459
46858
  kind: literal("grid"),
46460
- gridWidth: number(),
46461
- gridHeight: number(),
46859
+ gridWidth: number().meta(readingMeta("none")),
46860
+ gridHeight: number().meta(readingMeta("none")),
46462
46861
  cells: array(boolean())
46463
46862
  });
46464
46863
  discriminatedUnion("kind", [
@@ -46570,6 +46969,8 @@ var NcSystemEventKindSchema = _enum([
46570
46969
  "detection-blind",
46571
46970
  "motion-noisy",
46572
46971
  "motion-calm",
46972
+ "package-llm-silent",
46973
+ "package-llm-answered",
46573
46974
  "recording-stalled",
46574
46975
  "recording-healthy",
46575
46976
  "recording-degraded",
@@ -48132,6 +48533,14 @@ var NC_CONDITION_CATALOG = [
48132
48533
  value: "motion-calm",
48133
48534
  label: "Camera motion calm again"
48134
48535
  },
48536
+ {
48537
+ value: "package-llm-silent",
48538
+ label: "Package confirm not answering"
48539
+ },
48540
+ {
48541
+ value: "package-llm-answered",
48542
+ label: "Package confirm answering again"
48543
+ },
48135
48544
  {
48136
48545
  value: "recording-stalled",
48137
48546
  label: "Recording stalled"
@@ -59507,6 +59916,28 @@ var PackageVersionInfoSchema = object({
59507
59916
  deprecated: string().optional(),
59508
59917
  distTags: array(string()).readonly()
59509
59918
  });
59919
+ /**
59920
+ * One published document. `ok` is markdown from the registry. `missing` means
59921
+ * the registry answered and the file was not there. `error` is a registry or
59922
+ * tarball failure — never an empty string standing in for either.
59923
+ */
59924
+ var PackageDocBodySchema = discriminatedUnion("status", [
59925
+ object({
59926
+ status: literal("ok"),
59927
+ markdown: string()
59928
+ }),
59929
+ object({ status: literal("missing") }),
59930
+ object({
59931
+ status: literal("error"),
59932
+ message: string()
59933
+ })
59934
+ ]);
59935
+ var PackageDocsSchema = object({
59936
+ name: string(),
59937
+ version: string(),
59938
+ readme: PackageDocBodySchema,
59939
+ changelog: PackageDocBodySchema
59940
+ });
59510
59941
  var SearchResultSchema = object({
59511
59942
  name: string(),
59512
59943
  version: string(),
@@ -59725,7 +60156,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
59725
60156
  }), object({ success: literal(true) }), {
59726
60157
  kind: "mutation",
59727
60158
  auth: "admin"
59728
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
60159
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
60160
+ name: string().min(1),
60161
+ version: string().min(1).optional()
60162
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
59729
60163
  kind: "mutation",
59730
60164
  auth: "admin"
59731
60165
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -59784,27 +60218,17 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
59784
60218
  */
59785
60219
  var AirQualitySensorStatusSchema = object({
59786
60220
  /** Carbon dioxide concentration in ppm. */
59787
- co2Ppm: number().min(0).optional(),
60221
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
59788
60222
  /** Total volatile organic compounds in ppb. */
59789
- vocPpb: number().min(0).optional(),
60223
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
59790
60224
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
59791
- pm25: number().min(0).optional(),
60225
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
59792
60226
  /** Particulate matter ≤ 10 μm in µg/m³. */
59793
- pm10: number().min(0).optional(),
60227
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
59794
60228
  /** Composite AQI value (typically 0..500). */
59795
- aqi: number().optional(),
60229
+ aqi: number().optional().meta(readingMeta("index")),
59796
60230
  /** Ms epoch when the slice was last updated. */
59797
- lastFetchedAt: number(),
59798
- /** Live display unit of the single metric this slice carries (e.g. HA
59799
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
59800
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
59801
- * per slice is unambiguous. */
59802
- unit: string().optional(),
59803
- /** Suggested decimal places for numeric display.
59804
- * Populated live from the upstream source when provided (e.g. HA
59805
- * `attributes.suggested_display_precision`). Falls back to
59806
- * auto-formatting when absent. */
59807
- precision: number().int().min(0).max(10).optional()
60231
+ lastFetchedAt: number()
59808
60232
  });
59809
60233
  var airQualitySensorCapability = {
59810
60234
  name: "air-quality-sensor",
@@ -59841,12 +60265,12 @@ var airQualitySensorCapability = {
59841
60265
  },
59842
60266
  pm25: {
59843
60267
  kind: "gauge",
59844
- unit: "ug/m3",
60268
+ unit: "µg/m³",
59845
60269
  deadband: 1
59846
60270
  },
59847
60271
  pm10: {
59848
60272
  kind: "gauge",
59849
- unit: "ug/m3",
60273
+ unit: "µg/m³",
59850
60274
  deadband: 1
59851
60275
  },
59852
60276
  aqi: {
@@ -59864,22 +60288,13 @@ var airQualitySensorCapability = {
59864
60288
  * entries with `device_class: illuminance`.
59865
60289
  */
59866
60290
  var AmbientLightSensorStatusSchema = object({
59867
- /** Current illuminance in lux (lx). */
59868
- lux: number().min(0).meta({
59869
- unit: "lx",
60291
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
60292
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
60293
+ precision: 0,
59870
60294
  label: "Illuminance"
59871
- }),
60295
+ })),
59872
60296
  /** Ms epoch when the slice was last updated. */
59873
- lastFetchedAt: number(),
59874
- /** Live display unit from the upstream source (e.g. HA
59875
- * `attributes.unit_of_measurement`). The UI prefers this over the
59876
- * role's canonical unit. Absent → fall back to the canonical unit. */
59877
- unit: string().optional(),
59878
- /** Suggested decimal places for numeric display.
59879
- * Populated live from the upstream source when provided (e.g. HA
59880
- * `attributes.suggested_display_precision`). Falls back to
59881
- * auto-formatting when absent. */
59882
- precision: number().int().min(0).max(10).optional()
60297
+ lastFetchedAt: number()
59883
60298
  });
59884
60299
  var ambientLightSensorCapability = {
59885
60300
  name: "ambient-light-sensor",
@@ -59911,12 +60326,7 @@ var ambientLightSensorCapability = {
59911
60326
  } } },
59912
60327
  /** Clock fields: written, but excluded from the compare that decides
59913
60328
  * whether persisting is worth a SQLite commit. */
59914
- volatileStateFields: ["lastFetchedAt"],
59915
- customize: {
59916
- version: 1,
59917
- advanced: "derive",
59918
- targets: []
59919
- }
60329
+ volatileStateFields: ["lastFetchedAt"]
59920
60330
  };
59921
60331
  /**
59922
60332
  * Per-class audio metrics aggregated over a sliding window.
@@ -60143,10 +60553,7 @@ var BatteryStatusSchema = object({
60143
60553
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
60144
60554
  * declines to band a non-finite reading, which is the correct shape.
60145
60555
  */
60146
- percentage: number().min(0).max(100).nullable().meta({
60147
- unit: "%",
60148
- label: "Charge"
60149
- }),
60556
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
60150
60557
  /**
60151
60558
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
60152
60559
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -60173,7 +60580,14 @@ var BatteryStatusSchema = object({
60173
60580
  */
60174
60581
  sleeping: boolean().meta({ label: "Sleeping" }),
60175
60582
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60176
- lastUpdated: number(),
60583
+ lastUpdated: number().meta(readingMeta("none")),
60584
+ /**
60585
+ * Battery voltage in volts, for a cell the firmware reports as voltage
60586
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
60587
+ * that only report charge. A second reading on this cap, not a second
60588
+ * `numeric-sensor` on the same device.
60589
+ */
60590
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
60177
60591
  /**
60178
60592
  * Ms epoch of the last time the device PROVED it was reachable — a
60179
60593
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -60225,7 +60639,6 @@ var BATTERY_DEVICE_TYPES = [
60225
60639
  /** Generic Customize target for the percentage reading (D936). */
60226
60640
  var batteryCustomize = {
60227
60641
  version: 1,
60228
- advanced: "derive",
60229
60642
  targets: [{
60230
60643
  id: "percentage",
60231
60644
  cardinality: "scalar",
@@ -60234,7 +60647,7 @@ var batteryCustomize = {
60234
60647
  stateField: "percentage"
60235
60648
  },
60236
60649
  deviceTypes: BATTERY_DEVICE_TYPES,
60237
- apply: "add-or-replace",
60650
+ apply: "add",
60238
60651
  path: "percentage",
60239
60652
  sources: [{
60240
60653
  kind: "field",
@@ -60355,10 +60768,16 @@ onStatusChanged: { data: object({
60355
60768
  /**
60356
60769
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
60357
60770
  */
60358
- history: { fields: { percentage: {
60359
- kind: "gauge",
60360
- unit: "%"
60361
- } } },
60771
+ history: { fields: {
60772
+ percentage: {
60773
+ kind: "gauge",
60774
+ unit: "%"
60775
+ },
60776
+ volts: {
60777
+ kind: "gauge",
60778
+ unit: "V"
60779
+ }
60780
+ } },
60362
60781
  customize: batteryCustomize,
60363
60782
  /** Clock fields: written, but excluded from the compare that decides
60364
60783
  * whether persisting is worth a SQLite commit. */
@@ -60405,12 +60824,7 @@ var binaryCapability = {
60405
60824
  history: { fields: { on: { kind: "boolean" } } },
60406
60825
  /** Clock fields: written, but excluded from the compare that decides
60407
60826
  * whether persisting is worth a SQLite commit. */
60408
- volatileStateFields: ["lastChangedAt"],
60409
- customize: {
60410
- version: 1,
60411
- advanced: "derive",
60412
- targets: []
60413
- }
60827
+ volatileStateFields: ["lastChangedAt"]
60414
60828
  };
60415
60829
  /**
60416
60830
  * Dimmable-light brightness control. Co-exists with `switch` on the
@@ -60426,7 +60840,7 @@ var binaryCapability = {
60426
60840
  */
60427
60841
  var BrightnessStatusSchema = object({
60428
60842
  /** Current level as 0..100 inclusive. Firmware-reported. */
60429
- percentage: number().min(0).max(100),
60843
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
60430
60844
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
60431
60845
  lastChangedAt: number()
60432
60846
  });
@@ -60907,21 +61321,21 @@ var ClimateControlStatusSchema = object({
60907
61321
  /** Single setpoint in Celsius. Used by single-target modes
60908
61322
  * (heat / cool / dry). Null when the device is in a range mode
60909
61323
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
60910
- target: number().nullable(),
61324
+ target: number().nullable().meta(readingMeta("temperature")),
60911
61325
  /** Upper bound of the dual setpoint in Celsius. Populated only
60912
61326
  * in `heat_cool` mode. */
60913
- targetHigh: number().nullable(),
61327
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
60914
61328
  /** Lower bound of the dual setpoint in Celsius. Populated only
60915
61329
  * in `heat_cool` mode. */
60916
- targetLow: number().nullable(),
61330
+ targetLow: number().nullable().meta(readingMeta("temperature")),
60917
61331
  /** Target relative humidity (0..100). Null when the device has no
60918
61332
  * humidity control. */
60919
- targetHumidity: number().min(0).max(100).nullable(),
61333
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
60920
61334
  /** Current measured temperature in Celsius. Read-only — pushed by
60921
61335
  * the device's internal sensor. */
60922
- currentTemp: number().nullable(),
61336
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
60923
61337
  /** Current measured relative humidity (0..100). Read-only. */
60924
- currentHumidity: number().min(0).max(100).nullable(),
61338
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
60925
61339
  /** Vertical louver swing on/off. Null when the device has no vertical
60926
61340
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
60927
61341
  swingVertical: boolean().nullable(),
@@ -60929,11 +61343,11 @@ var ClimateControlStatusSchema = object({
60929
61343
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
60930
61344
  swingHorizontal: boolean().nullable(),
60931
61345
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
60932
- minTemp: number().nullable().optional(),
61346
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
60933
61347
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
60934
- maxTemp: number().nullable().optional(),
61348
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
60935
61349
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
60936
- tempStep: number().positive().nullable().optional(),
61350
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
60937
61351
  /** Ms epoch when the slice was last updated (push or command). */
60938
61352
  lastFetchedAt: number()
60939
61353
  });
@@ -61075,17 +61489,17 @@ var climateControlCapability = {
61075
61489
  * modes to expose in the picker without a slice fetch.
61076
61490
  */
61077
61491
  var RgbTripletSchema = object({
61078
- r: number().int().min(0).max(255),
61079
- g: number().int().min(0).max(255),
61080
- b: number().int().min(0).max(255)
61492
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
61493
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
61494
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
61081
61495
  });
61082
61496
  var HsvTripletSchema = object({
61083
61497
  /** Hue in degrees, 0..360. */
61084
- h: number().min(0).max(360),
61498
+ h: number().min(0).max(360).meta(readingMeta("none")),
61085
61499
  /** Saturation as 0..100 inclusive. */
61086
- s: number().min(0).max(100),
61500
+ s: number().min(0).max(100).meta(readingMeta("none")),
61087
61501
  /** Value/brightness as 0..100 inclusive. */
61088
- v: number().min(0).max(100)
61502
+ v: number().min(0).max(100).meta(readingMeta("none"))
61089
61503
  });
61090
61504
  var ColorInputSchema = discriminatedUnion("mode", [
61091
61505
  object({
@@ -61114,7 +61528,7 @@ var ColorStatusSchema = object({
61114
61528
  /** Populated when `mode === 'hsv'`. */
61115
61529
  hsv: HsvTripletSchema.optional(),
61116
61530
  /** Populated when `mode === 'mired'`. */
61117
- mireds: number().int().optional(),
61531
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
61118
61532
  /** Ms epoch of the last operator-driven change. */
61119
61533
  lastChangedAt: number()
61120
61534
  });
@@ -61323,18 +61737,12 @@ var FORM_OF_KIND = {
61323
61737
  function consumableFormOf(item) {
61324
61738
  return item.form ?? FORM_OF_KIND[consumableKindOf(item)];
61325
61739
  }
61326
- /** Units a remaining-life counter is reported in. Closed: the panel renders
61327
- * each. */
61328
- var ConsumableRemainingUnitSchema = _enum([
61329
- "days",
61330
- "hours",
61331
- "cycles"
61332
- ]);
61333
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
61334
- * days). `value` null = unknown (D393). */
61740
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
61741
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
61742
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
61335
61743
  var ConsumableRemainingSchema = object({
61336
- value: number().min(0).nullable(),
61337
- unit: ConsumableRemainingUnitSchema
61744
+ value: number().min(0).nullable().meta(readingMeta("device")),
61745
+ kind: ConsumableRemainingKindSchema
61338
61746
  });
61339
61747
  /** A single consumable item. Either a continuous `level` (remaining
61340
61748
  * life %, or fill % for a `supply` / `waste` item) or a discrete `status`
@@ -61347,7 +61755,7 @@ var ConsumableItemSchema = object({
61347
61755
  /** Display name. */
61348
61756
  label: string().min(1),
61349
61757
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
61350
- level: number().min(0).max(100).nullable(),
61758
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61351
61759
  /** The provider's discrete verdict when known (binary mode). */
61352
61760
  status: ConsumableStatusSchema.nullable(),
61353
61761
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -61429,8 +61837,8 @@ var consumablesCustomize = {
61429
61837
  transforms: [{ kind: "identity" }]
61430
61838
  }],
61431
61839
  literals: [{
61432
- path: "remaining.unit",
61433
- values: [...ConsumableRemainingUnitSchema.options]
61840
+ path: "remaining.kind",
61841
+ values: [...ConsumableRemainingKindSchema.options]
61434
61842
  }]
61435
61843
  },
61436
61844
  {
@@ -61559,7 +61967,6 @@ reset: method(object({
61559
61967
  },
61560
61968
  "remaining.value": {
61561
61969
  kind: "gauge",
61562
- unitFrom: "remaining.unit",
61563
61970
  deadband: 1,
61564
61971
  displayName: "Remaining"
61565
61972
  }
@@ -61629,10 +62036,9 @@ var contactCapability = {
61629
62036
  * (empty array for the other kinds). It lives in the slice because
61630
62037
  * HA `select.*` entities may update their options at runtime.
61631
62038
  *
61632
- * The `unit`/`min`/`max`/`step` fields are meaningful for
61633
- * `kind === 'numeric'` and are absent (undefined) for all other
61634
- * kinds. They are populated live from HA attributes on each state
61635
- * push — consistent with `options` and the sensor-unit approach.
62039
+ * Numeric `min` / `max` / `step` live in the slice and share the device
62040
+ * quantity (`SourceInfo.quantity`, D944). Display precision is a read-time
62041
+ * preference. A datetime control's shape is `SourceInfo.dateTimeShape`.
61636
62042
  */
61637
62043
  var ControlStatusSchema = object({
61638
62044
  kind: _enum([
@@ -61644,39 +62050,17 @@ var ControlStatusSchema = object({
61644
62050
  /** Current value. Numeric kind → number; select/text/datetime → string.
61645
62051
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
61646
62052
  * or time-only — HA accepts all three forms). */
61647
- value: union([number(), string()]),
62053
+ value: union([number().meta(readingMeta("device")), string()]),
61648
62054
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
61649
62055
  options: array(string()),
61650
62056
  /** Ms epoch when the slice was last updated. */
61651
62057
  lastChangedAt: number(),
61652
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
61653
- * Populated live from HA `attributes.unit_of_measurement` on each push.
61654
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61655
- unit: string().optional(),
61656
- /** Minimum allowed value. Populated live from HA `attributes.min`.
61657
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61658
- min: number().optional(),
61659
- /** Maximum allowed value. Populated live from HA `attributes.max`.
61660
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61661
- max: number().optional(),
61662
- /** Step increment. Populated live from HA `attributes.step`.
61663
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61664
- step: number().optional(),
61665
- /** Suggested decimal places for numeric display. Populated live from HA
61666
- * `attributes.suggested_display_precision`, or derived from `step` when the
61667
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
61668
- * other kinds. Falls back to auto-formatting when absent. */
61669
- precision: number().int().min(0).max(10).optional(),
61670
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
61671
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
61672
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
61673
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
61674
- * `has_date`/`has_time` attributes on each push. */
61675
- format: _enum([
61676
- "date",
61677
- "time",
61678
- "datetime"
61679
- ]).optional()
62058
+ /** Minimum allowed value, in the device quantity's base unit. */
62059
+ min: number().optional().meta(readingMeta("device")),
62060
+ /** Maximum allowed value, in the device quantity's base unit. */
62061
+ max: number().optional().meta(readingMeta("device")),
62062
+ /** Step increment, in the device quantity's base unit. */
62063
+ step: number().optional().meta(readingMeta("device"))
61680
62064
  });
61681
62065
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
61682
62066
  object({
@@ -61764,9 +62148,9 @@ var CoverStatusSchema = object({
61764
62148
  state: CoverStateSchema,
61765
62149
  /** 0 = fully closed, 100 = fully open. Null when the device has
61766
62150
  * no intermediate position surface. */
61767
- position: number().min(0).max(100).nullable(),
62151
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61768
62152
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
61769
- tiltPosition: number().min(0).max(100).nullable(),
62153
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61770
62154
  /** Ms epoch when the slice was last updated. */
61771
62155
  lastChangedAt: number()
61772
62156
  });
@@ -61869,9 +62253,9 @@ var NormalizedRangeSchema$1 = object({
61869
62253
  var DayNightStatusSchema = object({
61870
62254
  mode: DayNightModeSchema,
61871
62255
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
61872
- sensitivity: number().optional(),
62256
+ sensitivity: number().optional().meta(readingMeta("none")),
61873
62257
  /** Delay before the IR-cut filter flips, in seconds. */
61874
- switchDelaySec: number().optional(),
62258
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
61875
62259
  lastFetchedAt: number()
61876
62260
  });
61877
62261
  /**
@@ -62089,11 +62473,11 @@ var DishwasherStatusSchema = object({
62089
62473
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
62090
62474
  availablePrograms: array(string()),
62091
62475
  /** Seconds remaining on the active cycle, or null when not running / not known. */
62092
- remainingSeconds: number().int().nonnegative().nullable(),
62476
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62093
62477
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
62094
- delayedStartSeconds: number().int().nonnegative().nullable(),
62478
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62095
62479
  /** Cycle completion 0..100, or null when not applicable. */
62096
- progressPercent: number().min(0).max(100).nullable(),
62480
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62097
62481
  /** True when the door is reported open; null when the door state is not known. */
62098
62482
  doorOpen: boolean().nullable(),
62099
62483
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -62117,7 +62501,7 @@ var DishwasherStatusSchema = object({
62117
62501
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
62118
62502
  machineCareDue: boolean().nullable(),
62119
62503
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
62120
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
62504
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
62121
62505
  /** Option states: null when the appliance does not expose the option or has not said. */
62122
62506
  halfLoad: boolean().nullable(),
62123
62507
  hygienePlus: boolean().nullable(),
@@ -62132,7 +62516,7 @@ var DishwasherStatusSchema = object({
62132
62516
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
62133
62517
  activeEvents: array(string()),
62134
62518
  /** Numeric error code when the appliance reports one; null otherwise. */
62135
- errorCode: number().nullable(),
62519
+ errorCode: number().nullable().meta(readingMeta("none")),
62136
62520
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
62137
62521
  errorLabel: string().nullable(),
62138
62522
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -62363,7 +62747,7 @@ var DoorbellStatusSchema = object({
62363
62747
  /** Ms epoch of the last press. null = never observed since this provider started. */
62364
62748
  lastPressedAt: number().nullable(),
62365
62749
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
62366
- pressCountSinceStart: number()
62750
+ pressCountSinceStart: number().meta(readingMeta("count"))
62367
62751
  });
62368
62752
  var DoorbellPressEventSchema = object({
62369
62753
  deviceId: number(),
@@ -62428,17 +62812,7 @@ onPressed: {
62428
62812
  history: "none",
62429
62813
  customize: doorbellCustomize
62430
62814
  };
62431
- /**
62432
- * Enum-state sensor — a string value picked from a finite option set.
62433
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
62434
- * states, weather conditions, contact-source channels, mode strings).
62435
- * The option set is stable across observations; it is persisted in the
62436
- * device config blob (via the control slice at adoption) and is NOT
62437
- * stored in `sourceInfo`.
62438
- */
62439
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
62440
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
62441
- var EnumSensorDateTimeFormatSchema = _enum([
62815
+ _enum([
62442
62816
  "date",
62443
62817
  "time",
62444
62818
  "datetime"
@@ -62446,12 +62820,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
62446
62820
  var EnumSensorStatusSchema = object({
62447
62821
  value: string(),
62448
62822
  /**
62449
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
62450
- * as a locale date/time/datetime; absent for plain enum sensors. HA
62451
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
62452
- * a time-only sensor → `'time'`.
62823
+ * Set when `value` is a timestamp rather than an enum state, so history
62824
+ * does not learn ISO strings as states. The display shape (date / time /
62825
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
62453
62826
  */
62454
- format: EnumSensorDateTimeFormatSchema.optional(),
62827
+ timestamp: boolean().optional(),
62455
62828
  /** Ms epoch when the slice was last updated. */
62456
62829
  lastFetchedAt: number()
62457
62830
  });
@@ -62478,11 +62851,11 @@ var enumSensorCapability = {
62478
62851
  * History (D850): the value as an `enum` series (an appliance's program or
62479
62852
  * operation state, an HVAC action). The option set is per device, so the
62480
62853
  * value list is LEARNED as values are first seen (bounded). A sensor with a
62481
- * `format` carries a timestamp, not a state, and is not recorded.
62854
+ * `timestamp` carries a clock, not a state, and is not recorded.
62482
62855
  */
62483
62856
  history: { fields: { value: {
62484
62857
  kind: "enum",
62485
- skipWhenSet: "format"
62858
+ skipWhenSet: "timestamp"
62486
62859
  } } },
62487
62860
  /** Clock fields: written, but excluded from the compare that decides
62488
62861
  * whether persisting is worth a SQLite commit. */
@@ -62502,13 +62875,13 @@ var EventFireSchema = object({
62502
62875
  deviceId: number(),
62503
62876
  eventType: string(),
62504
62877
  data: record(string(), unknown()).nullable(),
62505
- timestamp: number(),
62506
- seq: number()
62878
+ timestamp: number().meta(readingMeta("none")),
62879
+ seq: number().meta(readingMeta("count"))
62507
62880
  });
62508
62881
  var EventEmitterStatusSchema = object({
62509
62882
  eventTypes: array(string()),
62510
62883
  lastEvent: EventFireSchema.nullable(),
62511
- eventCountSinceStart: number()
62884
+ eventCountSinceStart: number().meta(readingMeta("count"))
62512
62885
  });
62513
62886
  var eventEmitterCapability = {
62514
62887
  name: "event-emitter",
@@ -62961,10 +63334,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
62961
63334
  var FanControlStatusSchema = object({
62962
63335
  /** Active speed as 0..100 inclusive. Null when the device has no
62963
63336
  * speed surface (single-speed fan). */
62964
- percentage: number().min(0).max(100).nullable(),
63337
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62965
63338
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
62966
63339
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
62967
- percentageStep: number().positive().optional(),
63340
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
62968
63341
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
62969
63342
  * — empty string when no preset is active or supported. */
62970
63343
  preset: string(),
@@ -63085,7 +63458,7 @@ var FeatureProbeStatusSchema = object({
63085
63458
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
63086
63459
  model: string().nullable(),
63087
63460
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
63088
- channelCount: number().nullable(),
63461
+ channelCount: number().nullable().meta(readingMeta("count")),
63089
63462
  /**
63090
63463
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
63091
63464
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -63208,9 +63581,91 @@ var gasCapability = {
63208
63581
  * whether persisting is worth a SQLite commit. */
63209
63582
  volatileStateFields: ["lastChangedAt"]
63210
63583
  };
63584
+ var GroupCommandsSchema = object({
63585
+ /** One row per member that has been commanded. A later command replaces that member's row. */
63586
+ results: array(object({
63587
+ deviceId: number().int().nonnegative(),
63588
+ cap: string().min(1),
63589
+ method: string().min(1),
63590
+ ok: boolean(),
63591
+ error: string().optional(),
63592
+ /** Ms epoch of the attempt. */
63593
+ at: number()
63594
+ })) });
63595
+ var groupCommandsCapability = {
63596
+ name: "group-commands",
63597
+ scope: "device",
63598
+ deviceNative: true,
63599
+ mode: "singleton",
63600
+ deviceTypes: [DeviceType.Switch, DeviceType.Light],
63601
+ methods: {},
63602
+ status: {
63603
+ schema: GroupCommandsSchema,
63604
+ kind: "push"
63605
+ },
63606
+ runtimeState: GroupCommandsSchema,
63607
+ /**
63608
+ * Runtime-state durability: **session** — the last attempt. A restored
63609
+ * failure would outlive the command that caused it.
63610
+ *
63611
+ * See `RuntimeStateDurability`. Enforced by
63612
+ * `scripts/check-runtime-state-durability.ts`.
63613
+ */
63614
+ durability: "session",
63615
+ history: "none"
63616
+ };
63617
+ /**
63618
+ * How many members of a helper group answered (D944). Counts are integers,
63619
+ * not measurements: zero included is a real count, never a stand-in reading.
63620
+ * The reading itself stays on the output capability. History is none — the
63621
+ * series that later statistics read is the output cap's own history block.
63622
+ */
63623
+ var GroupStatusSchema = object({
63624
+ /** Members whose current value entered the aggregate. */
63625
+ included: number().int().nonnegative().meta(readingMeta("count", {
63626
+ precision: 0,
63627
+ label: "Reporting"
63628
+ })),
63629
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
63630
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
63631
+ precision: 0,
63632
+ label: "Excluded"
63633
+ })),
63634
+ /** `included` + `excluded`. */
63635
+ total: number().int().nonnegative().meta(readingMeta("count", {
63636
+ precision: 0,
63637
+ label: "Members"
63638
+ }))
63639
+ });
63640
+ var groupStatusCapability = {
63641
+ name: "group-status",
63642
+ scope: "device",
63643
+ deviceNative: true,
63644
+ mode: "singleton",
63645
+ deviceTypes: [
63646
+ DeviceType.Sensor,
63647
+ DeviceType.Switch,
63648
+ DeviceType.Light
63649
+ ],
63650
+ methods: {},
63651
+ status: {
63652
+ schema: GroupStatusSchema,
63653
+ kind: "push"
63654
+ },
63655
+ runtimeState: GroupStatusSchema,
63656
+ /**
63657
+ * Runtime-state durability: **session** — recomputed from members on the
63658
+ * next source event; a restored count would outlive the readings it named.
63659
+ *
63660
+ * See `RuntimeStateDurability`. Enforced by
63661
+ * `scripts/check-runtime-state-durability.ts`.
63662
+ */
63663
+ durability: "session",
63664
+ history: "none"
63665
+ };
63211
63666
  var HeatingValveStatusSchema = object({
63212
63667
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
63213
- position: number().min(0).max(100).nullable(),
63668
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63214
63669
  /** Ms epoch when the slice was last updated (push or poll). */
63215
63670
  lastFetchedAt: number()
63216
63671
  });
@@ -63253,9 +63708,9 @@ var HumidifierStatusSchema = object({
63253
63708
  /** Whether the humidifier is currently on. */
63254
63709
  on: boolean(),
63255
63710
  /** Current measured relative humidity (0..100). Null when not reported. */
63256
- currentHumidity: number().min(0).max(100).nullable(),
63711
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
63257
63712
  /** Target relative humidity (0..100). Null when no setpoint surface. */
63258
- targetHumidity: number().min(0).max(100).nullable(),
63713
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
63259
63714
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
63260
63715
  * device has no mode surface. */
63261
63716
  mode: string().nullable(),
@@ -63265,9 +63720,9 @@ var HumidifierStatusSchema = object({
63265
63720
  * `idle` / `off`). Null when the device doesn't report it. */
63266
63721
  action: string().nullable(),
63267
63722
  /** HA `min_humidity` attribute. Null → UI uses 0. */
63268
- minHumidity: number().nullable(),
63723
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
63269
63724
  /** HA `max_humidity` attribute. Null → UI uses 100. */
63270
- maxHumidity: number().nullable(),
63725
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
63271
63726
  /** Ms epoch when the slice was last updated. */
63272
63727
  lastChangedAt: number()
63273
63728
  });
@@ -63337,22 +63792,13 @@ var humidifierCapability = {
63337
63792
  * `sourceInfo`.
63338
63793
  */
63339
63794
  var HumiditySensorStatusSchema = object({
63340
- /** Current relative humidity, 0..100. */
63341
- percent: number().min(0).max(100).meta({
63342
- unit: "%",
63795
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
63796
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63797
+ precision: 0,
63343
63798
  label: "Humidity"
63344
- }),
63799
+ })),
63345
63800
  /** Ms epoch when the slice was last updated. */
63346
- lastFetchedAt: number(),
63347
- /** Live display unit from the upstream source (e.g. HA
63348
- * `attributes.unit_of_measurement`). The UI prefers this over the
63349
- * role's canonical unit. Absent → fall back to the canonical unit. */
63350
- unit: string().optional(),
63351
- /** Suggested decimal places for numeric display.
63352
- * Populated live from the upstream source when provided (e.g. HA
63353
- * `attributes.suggested_display_precision`). Falls back to
63354
- * auto-formatting when absent. */
63355
- precision: number().int().min(0).max(10).optional()
63801
+ lastFetchedAt: number()
63356
63802
  });
63357
63803
  var humiditySensorCapability = {
63358
63804
  name: "humidity-sensor",
@@ -63383,12 +63829,7 @@ var humiditySensorCapability = {
63383
63829
  } } },
63384
63830
  /** Clock fields: written, but excluded from the compare that decides
63385
63831
  * whether persisting is worth a SQLite commit. */
63386
- volatileStateFields: ["lastFetchedAt"],
63387
- customize: {
63388
- version: 1,
63389
- advanced: "derive",
63390
- targets: []
63391
- }
63832
+ volatileStateFields: ["lastFetchedAt"]
63392
63833
  };
63393
63834
  /**
63394
63835
  * Image display cap. Models a single still image exposed by an integration —
@@ -63409,7 +63850,7 @@ var ImageStatusSchema = object({
63409
63850
  * entity exposes no `entity_picture` (yet). */
63410
63851
  url: string().nullable(),
63411
63852
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
63412
- lastUpdated: number().nullable()
63853
+ lastUpdated: number().nullable().meta(readingMeta("none"))
63413
63854
  });
63414
63855
  var imageCapability = {
63415
63856
  name: "image",
@@ -63497,19 +63938,19 @@ var NormalizedRangeSchema = object({
63497
63938
  */
63498
63939
  var ImageSettingsStatusSchema = object({
63499
63940
  /** Normalized 0–100. */
63500
- brightness: number().optional(),
63941
+ brightness: number().optional().meta(readingMeta("none")),
63501
63942
  /** Normalized 0–100. */
63502
- contrast: number().optional(),
63943
+ contrast: number().optional().meta(readingMeta("none")),
63503
63944
  /** Normalized 0–100. */
63504
- saturation: number().optional(),
63945
+ saturation: number().optional().meta(readingMeta("none")),
63505
63946
  /** Normalized 0–100. */
63506
- sharpness: number().optional(),
63947
+ sharpness: number().optional().meta(readingMeta("none")),
63507
63948
  mirror: boolean().optional(),
63508
63949
  flip: boolean().optional(),
63509
63950
  rotate: ImageRotateSchema.optional(),
63510
63951
  whiteBalance: WhiteBalanceModeSchema.optional(),
63511
63952
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
63512
- warmth: number().optional(),
63953
+ warmth: number().optional().meta(readingMeta("none")),
63513
63954
  exposureMode: ExposureModeSchema.optional(),
63514
63955
  backlightMode: BacklightModeSchema.optional(),
63515
63956
  lastFetchedAt: number()
@@ -63788,10 +64229,10 @@ var IntercomAbilitySchema = object({
63788
64229
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
63789
64230
  codecs: array(string()),
63790
64231
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
63791
- sampleRate: number(),
64232
+ sampleRate: number().meta(readingMeta("none")),
63792
64233
  duplex: _enum(["half", "full"]),
63793
64234
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
63794
- maxBacklogMs: number()
64235
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
63795
64236
  });
63796
64237
  var IntercomStatusSchema = object({
63797
64238
  /** True while a talk session is open. */
@@ -64018,13 +64459,13 @@ var LawnMowerControlStatusSchema = object({
64018
64459
  activity: LawnMowerActivitySchema,
64019
64460
  /** 0..100 battery percentage. Null when the device has no battery
64020
64461
  * reading. */
64021
- batteryLevel: number().min(0).max(100).nullable(),
64462
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64022
64463
  /** 0..100 mowing-completion percentage of the current task, or null when no
64023
64464
  * task is active / progress is unavailable. */
64024
- progressPercent: number().min(0).max(100).nullable(),
64465
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64025
64466
  /** Current device/event code (dynamic — mostly status, sometimes an error),
64026
64467
  * or null when unknown. */
64027
- currentCode: number().nullable(),
64468
+ currentCode: number().nullable().meta(readingMeta("none")),
64028
64469
  /** Human label for {@link currentCode}, or null when undecodable. */
64029
64470
  currentCodeLabel: string().nullable(),
64030
64471
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -64578,7 +65019,7 @@ var MediaPlayerStatusSchema = object({
64578
65019
  state: MediaPlayerStateSchema,
64579
65020
  /** Volume as 0..100 inclusive. Null when the device has no volume
64580
65021
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
64581
- volumeLevel: number().min(0).max(100).nullable(),
65022
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64582
65023
  /** Mute toggle distinct from volume=0. Null when no mute surface.
64583
65024
  * Pair with `DeviceFeature.MediaPlayerMute`. */
64584
65025
  isMuted: boolean().nullable(),
@@ -64591,10 +65032,10 @@ var MediaPlayerStatusSchema = object({
64591
65032
  currentMedia: MediaInfoSchema.nullable(),
64592
65033
  /** Current playback position in ms. Null when not seekable or
64593
65034
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
64594
- positionMs: number().int().nonnegative().nullable(),
65035
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64595
65036
  /** Total duration of the current media in ms. Null when unknown
64596
65037
  * (live stream) or nothing is playing. */
64597
- durationMs: number().int().nonnegative().nullable(),
65038
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64598
65039
  /** Shuffle toggle. Null when no shuffle surface.
64599
65040
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
64600
65041
  shuffle: boolean().nullable(),
@@ -64903,7 +65344,7 @@ var motionCustomize = {
64903
65344
  eventName: "onMotionChanged"
64904
65345
  },
64905
65346
  deviceTypes: [DeviceType.Camera],
64906
- apply: "replace",
65347
+ apply: "add",
64907
65348
  applyAuthority: {
64908
65349
  kind: "camera-sensors",
64909
65350
  timeoutField: "motionCooldownMs"
@@ -65000,7 +65441,7 @@ var MotionStatusSchema = object({
65000
65441
  * it, every provider seeded it at boot, and every session left it
65001
65442
  * `detected: true` after its detach.
65002
65443
  */
65003
- autoClearAfterMs: number().nullable()
65444
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
65004
65445
  });
65005
65446
  /**
65006
65447
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -65037,12 +65478,18 @@ var motionCapability = {
65037
65478
  methods: {
65038
65479
  /**
65039
65480
  * Pull the current motion state synchronously. Convenience shortcut
65040
- * for consumers that don't need the full status object; equivalent
65041
- * to `getStatus()?.detected ?? false`. Will likely be folded into
65042
- * `getStatus` once the auto-injected status surface lands in every
65043
- * consumer.
65481
+ * for consumers that don't need the full status object. A composed
65482
+ * group answers from its slice (D944): unknown throws, it is not off.
65044
65483
  */
65045
65484
  isDetected: method(object({ deviceId: number() }), boolean()) },
65485
+ /**
65486
+ * A composed motion group serves `isDetected` from `detected` (D944).
65487
+ * Without this the cap stays uncomposable (`has-commands`).
65488
+ */
65489
+ composedMethods: { isDetected: {
65490
+ kind: "slice",
65491
+ field: "detected"
65492
+ } },
65046
65493
  events: {
65047
65494
  /**
65048
65495
  * Fires every time the runner transitions a camera between
@@ -65182,7 +65629,7 @@ var MotionZoneRegionSchema = object({
65182
65629
  * the grid region(s). */
65183
65630
  var MotionZoneStatusSchema = object({
65184
65631
  enabled: boolean(),
65185
- sensitivity: number(),
65632
+ sensitivity: number().meta(readingMeta("none")),
65186
65633
  /** Grid region(s). Today exactly one `grid` shape. */
65187
65634
  regions: array(MotionZoneRegionSchema),
65188
65635
  lastFetchedAt: number()
@@ -65552,7 +65999,7 @@ var NavigationStatusSchema = object({
65552
65999
  following: boolean(),
65553
66000
  flash: boolean(),
65554
66001
  lightMode: NavigationLightModeSchema,
65555
- lightLevel: number().min(40).max(100),
66002
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
65556
66003
  /** Ms epoch when the slice was last updated. */
65557
66004
  lastChangedAt: number()
65558
66005
  });
@@ -65686,13 +66133,13 @@ var NetworkLinkStatusSchema = object({
65686
66133
  * one whose reading has not landed must not be drawn at 0 %. Consumers
65687
66134
  * SKIP a null rather than coerce it.
65688
66135
  */
65689
- signalPercent: number().min(0).max(100).nullable(),
66136
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65690
66137
  /** Raw received signal strength in dBm, when the firmware reports one. */
65691
- rssiDbm: number().optional(),
66138
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
65692
66139
  /** Network name of a wireless link, when the firmware reports it. */
65693
66140
  ssid: string().optional(),
65694
66141
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
65695
- lastUpdated: number()
66142
+ lastUpdated: number().meta(readingMeta("none"))
65696
66143
  });
65697
66144
  var networkLinkCapability = {
65698
66145
  name: "network-link",
@@ -66077,7 +66524,7 @@ var NotifierStatusSchema = object({
66077
66524
  lastError: string().nullable(),
66078
66525
  /** Number of deliveries currently buffered server-side (rate-limited
66079
66526
  * notifiers may queue). 0 when the provider sends synchronously. */
66080
- queueDepth: number().int().nonnegative(),
66527
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
66081
66528
  /** Per-feature capability matrix — authoritative source for the
66082
66529
  * compose-form field gating. */
66083
66530
  supports: NotifierSupportsSchema
@@ -66169,11 +66616,9 @@ onSent: { data: object({
66169
66616
  * cap fits (HA `sensor` whose `device_class` we don't have a typed cap
66170
66617
  * for: water flow, distance, weight, frequency, signal strength, …).
66171
66618
  *
66172
- * The `unit` and `precision` fields carry the live display metadata that
66173
- * the upstream source provides with each state push (HA
66174
- * `attributes.unit_of_measurement` / `attributes.suggested_display_precision`).
66175
- * The UI reads them directly from the slice — they are the single source
66176
- * of truth for generic numeric rendering and do NOT live on `sourceInfo`.
66619
+ * The quantity is not on this slice. It is chosen when the device is
66620
+ * adopted and stored once on `SourceInfo.quantity` (D944). `value` is in
66621
+ * that quantity's base unit. `null` is a refused sample (D393).
66177
66622
  *
66178
66623
  * Prefer the typed alternatives (`temperature-sensor`, `humidity-sensor`,
66179
66624
  * `ambient-light-sensor`, `pressure-sensor`, `power-meter`,
@@ -66182,15 +66627,8 @@ onSent: { data: object({
66182
66627
  * numeric values land here.
66183
66628
  */
66184
66629
  var NumericSensorStatusSchema = object({
66185
- value: number(),
66186
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
66187
- * Populated live from the upstream source on each state push.
66188
- * Absent for unitless sensors. */
66189
- unit: string().optional(),
66190
- /** Suggested decimal places for numeric display.
66191
- * Populated live from the upstream source when provided.
66192
- * Falls back to auto-formatting when absent. */
66193
- precision: number().int().min(0).max(10).optional(),
66630
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
66631
+ value: number().nullable().meta(readingMeta("device")),
66194
66632
  /** Ms epoch when the slice was last updated. */
66195
66633
  lastFetchedAt: number()
66196
66634
  });
@@ -66214,11 +66652,11 @@ var numericSensorCapability = {
66214
66652
  */
66215
66653
  durability: "restored",
66216
66654
  /**
66217
- * History (D850): recorded series. The unit is per device, read from the sibling `unit` field.
66655
+ * History (D850): recorded series. The unit is the device quantity's base,
66656
+ * resolved when the series is read — the cap has no single unit.
66218
66657
  */
66219
66658
  history: { fields: { value: {
66220
66659
  kind: "gauge",
66221
- unitFrom: "unit",
66222
66660
  minIntervalMs: 3e4
66223
66661
  } } },
66224
66662
  /** Clock fields: written, but excluded from the compare that decides
@@ -66510,23 +66948,26 @@ var PetFeederStatusSchema = object({
66510
66948
  /** Food currently in the bowl (grams). Null when the device has not
66511
66949
  * reported a reading yet. On dual-hopper models this is the combined
66512
66950
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
66513
- foodLevel: number().nullable(),
66951
+ foodLevel: number().nullable().meta(readingMeta("none")),
66514
66952
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
66515
66953
  * single-hopper models. */
66516
- food1: number().nullable(),
66954
+ food1: number().nullable().meta(readingMeta("none")),
66517
66955
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
66518
66956
  * single-hopper models. */
66519
- food2: number().nullable(),
66957
+ food2: number().nullable().meta(readingMeta("none")),
66520
66958
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
66521
66959
  * (`device_class: problem`, on = low). True when the bowl is empty /
66522
66960
  * below the feeder's low threshold. */
66523
66961
  lowFood: boolean(),
66524
66962
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
66525
66963
  * device has no battery reading. */
66526
- batteryPower: number().min(0).max(100).nullable(),
66527
- /** Days of desiccant life remaining. Null when the model has no
66528
- * desiccant sensor. */
66529
- desiccantLeftDays: number().nullable(),
66964
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
66965
+ /**
66966
+ * Desiccant life remaining, stored in seconds (duration base, D944).
66967
+ * Providers convert from the days the feeder reports. Null when the
66968
+ * model has no desiccant sensor.
66969
+ */
66970
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
66530
66971
  /** True while a feed is in progress. */
66531
66972
  feeding: boolean(),
66532
66973
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -66537,7 +66978,7 @@ var PetFeederStatusSchema = object({
66537
66978
  * with `errorCode` for consumers that want the raw integer. */
66538
66979
  error: string().nullable(),
66539
66980
  /** Raw device error code (0 / null = no error). */
66540
- errorCode: number().nullable(),
66981
+ errorCode: number().nullable().meta(readingMeta("none")),
66541
66982
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
66542
66983
  isDualHopper: boolean(),
66543
66984
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -66547,7 +66988,7 @@ var PetFeederStatusSchema = object({
66547
66988
  /** Play a chime when dispensing. */
66548
66989
  feedSound: boolean(),
66549
66990
  /** Speaker / prompt volume level (device-scaled integer). */
66550
- volume: number(),
66991
+ volume: number().meta(readingMeta("none")),
66551
66992
  /** Ms epoch when the slice was last refreshed from the cloud. */
66552
66993
  lastFetchedAt: number()
66553
66994
  });
@@ -66666,7 +67107,7 @@ var petFeederCapability = {
66666
67107
  },
66667
67108
  desiccantLeftDays: {
66668
67109
  kind: "gauge",
66669
- unit: "days"
67110
+ unit: "s"
66670
67111
  }
66671
67112
  } }
66672
67113
  };
@@ -67102,36 +67543,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
67102
67543
  */
67103
67544
  var PowerMeterStatusSchema = object({
67104
67545
  /** Instantaneous power draw in watts. */
67105
- watts: number().optional().meta({
67106
- unit: "W",
67107
- label: "Power"
67108
- }),
67546
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
67109
67547
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
67110
- kwhTotal: number().optional().meta({
67111
- unit: "kWh",
67112
- label: "Energy"
67113
- }),
67548
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
67114
67549
  /** Voltage in volts. */
67115
- volts: number().optional(),
67550
+ volts: number().optional().meta(readingMeta("voltage")),
67116
67551
  /** Current in amperes. */
67117
- amps: number().optional(),
67118
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
67119
- va: number().optional(),
67552
+ amps: number().optional().meta(readingMeta("current")),
67553
+ /** Apparent power in volt-amperes. */
67554
+ va: number().optional().meta(readingMeta("apparent-power")),
67120
67555
  /** Power factor (0..1) if reported. */
67121
- powerFactor: number().min(0).max(1).optional(),
67556
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
67122
67557
  /** Ms epoch when the slice was last updated. */
67123
- lastFetchedAt: number(),
67124
- /** Live display unit of the single metric this slice carries (e.g. HA
67125
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
67126
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
67127
- * unit per slice is unambiguous. The UI prefers this over the role's
67128
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
67129
- unit: string().optional(),
67130
- /** Suggested decimal places for numeric display.
67131
- * Populated live from the upstream source when provided (e.g. HA
67132
- * `attributes.suggested_display_precision`). Falls back to
67133
- * auto-formatting when absent. */
67134
- precision: number().int().min(0).max(10).optional()
67558
+ lastFetchedAt: number()
67135
67559
  });
67136
67560
  var powerMeterCapability = {
67137
67561
  name: "power-meter",
@@ -67195,12 +67619,7 @@ var powerMeterCapability = {
67195
67619
  } },
67196
67620
  /** Clock fields: written, but excluded from the compare that decides
67197
67621
  * whether persisting is worth a SQLite commit. */
67198
- volatileStateFields: ["lastFetchedAt"],
67199
- customize: {
67200
- version: 1,
67201
- advanced: "derive",
67202
- targets: []
67203
- }
67622
+ volatileStateFields: ["lastFetchedAt"]
67204
67623
  };
67205
67624
  /**
67206
67625
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -67220,11 +67639,11 @@ var powerMeterCapability = {
67220
67639
  */
67221
67640
  var GpsLocationSchema = object({
67222
67641
  /** Latitude in decimal degrees, -90..90. */
67223
- latitude: number().min(-90).max(90),
67642
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
67224
67643
  /** Longitude in decimal degrees, -180..180. */
67225
- longitude: number().min(-180).max(180),
67644
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
67226
67645
  /** Reported accuracy in meters (lower = better). */
67227
- accuracyMeters: number().nonnegative()
67646
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
67228
67647
  });
67229
67648
  var PresenceStatusSchema = object({
67230
67649
  /** `home` / `not_home` / any user-defined zone name. */
@@ -67236,7 +67655,7 @@ var PresenceStatusSchema = object({
67236
67655
  gps: GpsLocationSchema.nullable(),
67237
67656
  /** Optional battery level of the tracking device (0..100). Null
67238
67657
  * when not reported. */
67239
- batteryPercent: number().min(0).max(100).nullable(),
67658
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
67240
67659
  /** Ms epoch when the slice was last updated. */
67241
67660
  lastChangedAt: number()
67242
67661
  });
@@ -67286,19 +67705,10 @@ var presenceCapability = {
67286
67705
  * `sourceInfo`.
67287
67706
  */
67288
67707
  var PressureSensorStatusSchema = object({
67289
- /** Current pressure in hPa. */
67290
- hpa: number(),
67708
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
67709
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
67291
67710
  /** Ms epoch when the slice was last updated. */
67292
- lastFetchedAt: number(),
67293
- /** Live display unit from the upstream source (e.g. HA
67294
- * `attributes.unit_of_measurement`). The UI prefers this over the
67295
- * role's canonical unit. Absent → fall back to the canonical unit. */
67296
- unit: string().optional(),
67297
- /** Suggested decimal places for numeric display.
67298
- * Populated live from the upstream source when provided (e.g. HA
67299
- * `attributes.suggested_display_precision`). Falls back to
67300
- * auto-formatting when absent. */
67301
- precision: number().int().min(0).max(10).optional()
67711
+ lastFetchedAt: number()
67302
67712
  });
67303
67713
  var pressureSensorCapability = {
67304
67714
  name: "pressure-sensor",
@@ -67712,8 +68122,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
67712
68122
  }), _void(), { kind: "mutation" });
67713
68123
  var PtzAutotrackSettingsSchema = object({
67714
68124
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
67715
- stopDelaySeconds: number().int().min(0).max(300),
67716
- disappearDelaySeconds: number().int().min(0).max(300)
68125
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
68126
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
67717
68127
  });
67718
68128
  /**
67719
68129
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -67867,7 +68277,7 @@ var RunningStateStatusSchema = object({
67867
68277
  /** null = unknown — never coerced to false (D393). */
67868
68278
  running: boolean().nullable(),
67869
68279
  /** Epoch ms the current boolean began; null while `running` is null. */
67870
- since: number().nullable(),
68280
+ since: number().nullable().meta(readingMeta("none")),
67871
68281
  source: RunningStateSourceSchema.meta({ label: "Source" }),
67872
68282
  phase: string().nullable(),
67873
68283
  /** null until a reading has been published (D393). */
@@ -67882,7 +68292,6 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
67882
68292
  /** Generic Customize target for the running boolean (D936). */
67883
68293
  var runningCustomize = {
67884
68294
  version: 1,
67885
- advanced: "derive",
67886
68295
  targets: [{
67887
68296
  id: "running",
67888
68297
  cardinality: "scalar",
@@ -67902,7 +68311,7 @@ var runningCustomize = {
67902
68311
  DeviceType.Thermostat,
67903
68312
  DeviceType.PetFeeder
67904
68313
  ],
67905
- apply: "add-or-replace",
68314
+ apply: "add",
67906
68315
  path: "running",
67907
68316
  sources: [
67908
68317
  {
@@ -69198,9 +69607,9 @@ var RecordTriggerSchema = _enum([
69198
69607
  */
69199
69608
  var RecordWindowSchema = object({
69200
69609
  trigger: RecordTriggerSchema,
69201
- day: number().int().min(0).max(6),
69202
- startMinute: number().int().min(0).max(1439),
69203
- endMinute: number().int().min(1).max(1440)
69610
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
69611
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
69612
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
69204
69613
  });
69205
69614
  /** Status of one physical volume, as the camera itself describes it. */
69206
69615
  var OnboardStorageVolumeSchema = object({
@@ -69276,10 +69685,10 @@ var RecordingOnboardStatusSchema = object({
69276
69685
  /** Master "record to the card at all" switch. */
69277
69686
  enabled: boolean().nullable(),
69278
69687
  overwriteWhenFull: boolean().nullable(),
69279
- preRecordSec: number().nullable(),
69280
- postRecordSec: number().nullable(),
69688
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
69689
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
69281
69690
  /** Length of one recorded file, in minutes. */
69282
- segmentMinutes: number().nullable(),
69691
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
69283
69692
  /** The primary track's weekly windows, flattened. */
69284
69693
  windows: array(RecordWindowSchema),
69285
69694
  /**
@@ -69295,7 +69704,7 @@ var RecordingOnboardStatusSchema = object({
69295
69704
  * provider reporting a non-zero count also reports the schedule as not
69296
69705
  * writable.
69297
69706
  */
69298
- unreadableWindows: number(),
69707
+ unreadableWindows: number().meta(readingMeta("count")),
69299
69708
  /**
69300
69709
  * The camera is scheduled to record and has NO usable storage.
69301
69710
  *
@@ -70158,7 +70567,7 @@ var CamStreamDescriptorSchema = object({
70158
70567
  url: string().optional(),
70159
70568
  codec: string().optional(),
70160
70569
  resolution: CamStreamResolutionSchema.optional(),
70161
- fps: number().positive().optional(),
70570
+ fps: number().positive().optional().meta(readingMeta("rate")),
70162
70571
  label: string().optional(),
70163
70572
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
70164
70573
  deviceFeatures: array(string()).optional(),
@@ -70245,18 +70654,18 @@ var StreamProfileSchema = _enum([
70245
70654
  ]);
70246
70655
  /** The current encoding config of one stream profile. */
70247
70656
  var StreamProfileConfigSchema = object({
70248
- width: number(),
70249
- height: number(),
70657
+ width: number().meta(readingMeta("none")),
70658
+ height: number().meta(readingMeta("none")),
70250
70659
  codec: _enum(["h264", "h265"]),
70251
- framerate: number(),
70252
- bitrate: number(),
70660
+ framerate: number().meta(readingMeta("rate")),
70661
+ bitrate: number().meta(readingMeta("bitrate")),
70253
70662
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
70254
70663
  encoderProfile: _enum([
70255
70664
  "high",
70256
70665
  "main",
70257
70666
  "baseline"
70258
70667
  ]).optional(),
70259
- gop: number().optional(),
70668
+ gop: number().optional().meta(readingMeta("none")),
70260
70669
  /**
70261
70670
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
70262
70671
  *
@@ -70477,12 +70886,7 @@ var switchCapability = {
70477
70886
  label: "On",
70478
70887
  kind: "boolean"
70479
70888
  }
70480
- }] },
70481
- customize: {
70482
- version: 1,
70483
- advanced: "derive",
70484
- targets: []
70485
- }
70889
+ }] }
70486
70890
  };
70487
70891
  /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
70488
70892
  function hasControlCharacter(value) {
@@ -71065,35 +71469,21 @@ var tamperCapability = {
71065
71469
  * entries with `device_class: temperature` and any future native
71066
71470
  * thermometer.
71067
71471
  *
71068
- * Unit normalisation: providers convert to Celsius before storing.
71069
- * The slice value is always Celsius so cross-cap aggregators
71070
- * (climate-control's `currentTemp`, energy analytics) can compose
71071
- * without per-source unit fixups. The canonical display unit (`°C`) is
71072
- * a descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
71073
- * `sourceInfo`.
71472
+ * The number is Celsius (D944). Providers convert on ingest. A failed
71473
+ * conversion is `null`, never a guess. Display unit is a read-time preference.
71074
71474
  *
71075
71475
  * Status `lastFetchedAt` lets staleness-aware consumers detect a
71076
71476
  * frozen feed (provider hung) distinct from a "temperature hasn't
71077
71477
  * changed" steady state.
71078
71478
  */
71079
71479
  var TemperatureSensorStatusSchema = object({
71080
- /** Current temperature in Celsius. */
71081
- celsius: number().meta({
71082
- unit: "°C",
71480
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
71481
+ celsius: number().nullable().meta(readingMeta("temperature", {
71482
+ precision: 1,
71083
71483
  label: "Temperature"
71084
- }),
71484
+ })),
71085
71485
  /** Ms epoch when the slice was last updated (push or poll). */
71086
- lastFetchedAt: number(),
71087
- /** Live display unit from the upstream source (e.g. HA
71088
- * `attributes.unit_of_measurement`). The UI prefers this over the
71089
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
71090
- * Absent → the UI falls back to the role's canonical unit. */
71091
- unit: string().optional(),
71092
- /** Suggested decimal places for numeric display.
71093
- * Populated live from the upstream source when provided (e.g. HA
71094
- * `attributes.suggested_display_precision`). Falls back to
71095
- * auto-formatting when absent. */
71096
- precision: number().int().min(0).max(10).optional()
71486
+ lastFetchedAt: number()
71097
71487
  });
71098
71488
  var temperatureSensorCapability = {
71099
71489
  name: "temperature-sensor",
@@ -71124,12 +71514,7 @@ var temperatureSensorCapability = {
71124
71514
  } } },
71125
71515
  /** Clock fields: written, but excluded from the compare that decides
71126
71516
  * whether persisting is worth a SQLite commit. */
71127
- volatileStateFields: ["lastFetchedAt"],
71128
- customize: {
71129
- version: 1,
71130
- advanced: "derive",
71131
- targets: []
71132
- }
71517
+ volatileStateFields: ["lastFetchedAt"]
71133
71518
  };
71134
71519
  /**
71135
71520
  * toast — system-scoped singleton capability that streams toast
@@ -71412,16 +71797,16 @@ var VacuumControlStatusSchema = object({
71412
71797
  ]),
71413
71798
  /** 0..100 battery percentage. Null when the device has no battery
71414
71799
  * reading. */
71415
- batteryLevel: number().min(0).max(100).nullable(),
71800
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71416
71801
  /** Current fan-speed token (provider-verbatim). Null when unknown or
71417
71802
  * the vacuum has no speed control. */
71418
71803
  fanSpeed: string().nullable(),
71419
71804
  /** Speed tokens the hardware accepts — drives the UI selector. */
71420
71805
  availableFanSpeeds: array(string()),
71421
71806
  /** 0..100 cleaning-completion percentage of the current task, or null. */
71422
- progressPercent: number().min(0).max(100).nullable(),
71807
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71423
71808
  /** Current error code (0 / null = no error). */
71424
- errorCode: number().nullable(),
71809
+ errorCode: number().nullable().meta(readingMeta("none")),
71425
71810
  /** Human label for {@link errorCode}, or null when none / undecodable. */
71426
71811
  errorLabel: string().nullable(),
71427
71812
  /** Ms epoch when the slice was last updated. */
@@ -71499,7 +71884,7 @@ var ValveStatusSchema = object({
71499
71884
  ]),
71500
71885
  /** 0 = fully closed, 100 = fully open. Null when the device has no
71501
71886
  * intermediate position surface. */
71502
- position: number().min(0).max(100).nullable(),
71887
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71503
71888
  /** Ms epoch when the slice was last updated. */
71504
71889
  lastChangedAt: number()
71505
71890
  });
@@ -71607,9 +71992,9 @@ var vibrationCapability = {
71607
71992
  */
71608
71993
  var WaterHeaterStatusSchema = object({
71609
71994
  /** Current measured temperature. Null when not reported. */
71610
- currentTemp: number().nullable(),
71995
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
71611
71996
  /** Target temperature setpoint. Null when no setpoint surface. */
71612
- targetTemp: number().nullable(),
71997
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
71613
71998
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
71614
71999
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
71615
72000
  * device reports an unknown state. */
@@ -71620,9 +72005,9 @@ var WaterHeaterStatusSchema = object({
71620
72005
  * has no away surface. */
71621
72006
  away: boolean().nullable(),
71622
72007
  /** HA `min_temp` attribute. Null when not reported. */
71623
- minTemp: number().nullable(),
72008
+ minTemp: number().nullable().meta(readingMeta("temperature")),
71624
72009
  /** HA `max_temp` attribute. Null when not reported. */
71625
- maxTemp: number().nullable(),
72010
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
71626
72011
  /** Ms epoch when the slice was last updated. */
71627
72012
  lastChangedAt: number()
71628
72013
  });
@@ -71707,25 +72092,18 @@ var waterHeaterCapability = {
71707
72092
  * Forecast deferred — current conditions only for v1.
71708
72093
  */
71709
72094
  var WeatherStatusSchema = object({
71710
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
71711
- * when no condition has been reported yet. */
72095
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
71712
72096
  condition: string().nullable(),
71713
- /** Current temperature in the reported unit. Null when not provided. */
71714
- temperature: number().nullable(),
71715
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
71716
- temperatureUnit: string().nullable(),
72097
+ /** Current temperature in Celsius. Null when not provided or refused. */
72098
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
71717
72099
  /** Relative humidity (0..100). Null when not provided. */
71718
- humidity: number().min(0).max(100).nullable(),
71719
- /** Barometric pressure in the reported unit. Null when not provided. */
71720
- pressure: number().nullable(),
71721
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
71722
- pressureUnit: string().nullable(),
71723
- /** Wind speed in the reported unit. Null when not provided. */
71724
- windSpeed: number().nullable(),
71725
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
71726
- windSpeedUnit: string().nullable(),
72100
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
72101
+ /** Barometric pressure in hPa. Null when not provided or refused. */
72102
+ pressure: number().nullable().meta(readingMeta("pressure")),
72103
+ /** Wind speed in metres per second. Null when not provided or refused. */
72104
+ windSpeed: number().nullable().meta(readingMeta("speed")),
71727
72105
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
71728
- windBearing: number().nullable(),
72106
+ windBearing: number().nullable().meta(readingMeta("angle")),
71729
72107
  /** Ms epoch when the slice was last updated. */
71730
72108
  lastFetchedAt: number()
71731
72109
  });
@@ -71753,12 +72131,12 @@ var weatherCapability = {
71753
72131
  */
71754
72132
  durability: "session",
71755
72133
  /**
71756
- * History (D850): recorded series. Units follow the sibling `*Unit` fields.
72134
+ * History (D850): recorded series. Numbers are the quantity base (D944).
71757
72135
  */
71758
72136
  history: { fields: {
71759
72137
  temperature: {
71760
72138
  kind: "gauge",
71761
- unitFrom: "temperatureUnit",
72139
+ unit: "°C",
71762
72140
  deadband: .2
71763
72141
  },
71764
72142
  humidity: {
@@ -71768,12 +72146,12 @@ var weatherCapability = {
71768
72146
  },
71769
72147
  pressure: {
71770
72148
  kind: "gauge",
71771
- unitFrom: "pressureUnit",
72149
+ unit: "hPa",
71772
72150
  deadband: .5
71773
72151
  },
71774
72152
  windSpeed: {
71775
72153
  kind: "gauge",
71776
- unitFrom: "windSpeedUnit",
72154
+ unit: "m/s",
71777
72155
  deadband: .5
71778
72156
  }
71779
72157
  } }
@@ -73057,6 +73435,8 @@ var DEVICE_LOCAL_STATE_CAPS = {
73057
73435
  featureProbe: featureProbeCapability,
73058
73436
  flood: floodCapability,
73059
73437
  gas: gasCapability,
73438
+ groupCommands: groupCommandsCapability,
73439
+ groupStatus: groupStatusCapability,
73060
73440
  heatingValve: heatingValveCapability,
73061
73441
  humidifier: humidifierCapability,
73062
73442
  humiditySensor: humiditySensorCapability,
@@ -73950,6 +74330,13 @@ Object.freeze({
73950
74330
  access: "view",
73951
74331
  role: "admin"
73952
74332
  },
74333
+ "addons.getPackageDocs": {
74334
+ capName: "addons",
74335
+ capScope: "system",
74336
+ addonId: null,
74337
+ access: "view",
74338
+ role: "admin"
74339
+ },
73953
74340
  "addons.getVersions": {
73954
74341
  capName: "addons",
73955
74342
  capScope: "system",
@@ -77058,6 +77445,20 @@ Object.freeze({
77058
77445
  access: "view",
77059
77446
  role: "viewer"
77060
77447
  },
77448
+ "groupCommands.getStatus": {
77449
+ capName: "group-commands",
77450
+ capScope: "device",
77451
+ addonId: null,
77452
+ access: "view",
77453
+ role: "viewer"
77454
+ },
77455
+ "groupStatus.getStatus": {
77456
+ capName: "group-status",
77457
+ capScope: "device",
77458
+ addonId: null,
77459
+ access: "view",
77460
+ role: "viewer"
77461
+ },
77061
77462
  "heatingValve.getStatus": {
77062
77463
  capName: "heating-valve",
77063
77464
  capScope: "device",
@@ -86880,6 +87281,37 @@ var SENSOR_CAP_VOCABULARY = {
86880
87281
  icon: "flame"
86881
87282
  }]
86882
87283
  },
87284
+ "group-commands": {
87285
+ scope: "device",
87286
+ noun: t("Group command", "Comando di gruppo")
87287
+ },
87288
+ "group-status": {
87289
+ scope: "device",
87290
+ noun: t("Group", "Gruppo"),
87291
+ state: [
87292
+ {
87293
+ kind: "numeric",
87294
+ field: "included",
87295
+ quantity: t("the members reporting", "i membri che rispondono"),
87296
+ label: t("Reporting", "Rispondono"),
87297
+ icon: "users"
87298
+ },
87299
+ {
87300
+ kind: "numeric",
87301
+ field: "excluded",
87302
+ quantity: t("the members left out", "i membri esclusi"),
87303
+ label: t("Excluded", "Esclusi"),
87304
+ icon: "users"
87305
+ },
87306
+ {
87307
+ kind: "numeric",
87308
+ field: "total",
87309
+ quantity: t("the member count", "il numero di membri"),
87310
+ label: t("Members", "Membri"),
87311
+ icon: "sigma"
87312
+ }
87313
+ ]
87314
+ },
86883
87315
  "humidity-sensor": {
86884
87316
  scope: "device",
86885
87317
  noun: t("Humidity", "Umidità"),
@@ -86913,7 +87345,6 @@ var SENSOR_CAP_VOCABULARY = {
86913
87345
  field: "value",
86914
87346
  quantity: t("the reading", "il valore"),
86915
87347
  label: t("Reading", "Valore"),
86916
- unitField: "unit",
86917
87348
  icon: "gauge"
86918
87349
  }]
86919
87350
  },
@@ -87049,7 +87480,7 @@ var SENSOR_CAP_VOCABULARY = {
87049
87480
  field: "temperature",
87050
87481
  quantity: t("the outdoor temperature", "la temperatura esterna"),
87051
87482
  label: t("Temperature", "Temperatura"),
87052
- unitField: "temperatureUnit",
87483
+ unit: "°C",
87053
87484
  icon: "thermometer"
87054
87485
  },
87055
87486
  {
@@ -87065,7 +87496,7 @@ var SENSOR_CAP_VOCABULARY = {
87065
87496
  field: "windSpeed",
87066
87497
  quantity: t("the wind speed", "la velocità del vento"),
87067
87498
  label: t("Wind", "Vento"),
87068
- unitField: "windSpeedUnit",
87499
+ unit: "m/s",
87069
87500
  icon: "wind"
87070
87501
  },
87071
87502
  {
@@ -87073,7 +87504,7 @@ var SENSOR_CAP_VOCABULARY = {
87073
87504
  field: "pressure",
87074
87505
  quantity: t("the air pressure", "la pressione atmosferica"),
87075
87506
  label: t("Pressure", "Pressione"),
87076
- unitField: "pressureUnit",
87507
+ unit: "hPa",
87077
87508
  icon: "gauge"
87078
87509
  }
87079
87510
  ]
@@ -88346,6 +88777,16 @@ Object.freeze({
88346
88777
  form: "single",
88347
88778
  optional: false
88348
88779
  }],
88780
+ "groupCommands.getStatus": [{
88781
+ name: "deviceId",
88782
+ form: "single",
88783
+ optional: false
88784
+ }],
88785
+ "groupStatus.getStatus": [{
88786
+ name: "deviceId",
88787
+ form: "single",
88788
+ optional: false
88789
+ }],
88349
88790
  "heatingValve.getStatus": [{
88350
88791
  name: "deviceId",
88351
88792
  form: "single",
@@ -90491,8 +90932,8 @@ var NC_SYSTEM_EVENT_FILTER_KEYS = [
90491
90932
  function systemEventFilterApplies(kind, filter) {
90492
90933
  if (LEGACY_KINDS_WITHOUT_FILTERS.has(kind)) return false;
90493
90934
  switch (filter) {
90494
- case "deviceIds": return kind.startsWith("device-") || kind.startsWith("stream-") || kind === "detection-blind" || kind.startsWith("motion-") || kind.startsWith("recording-") || kind === "alarm-triggered";
90495
- case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind" || kind.startsWith("motion-");
90935
+ 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";
90936
+ case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind" || kind.startsWith("motion-") || kind.startsWith("package-llm-");
90496
90937
  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-");
90497
90938
  case "packageNames": return kind.endsWith("update-available") || kind === "addon-updated" || kind === "node-updating" || kind === "server-updated";
90498
90939
  case "consumableLowPercent": return NC_CONSUMABLE_SYSTEM_EVENT_KINDS.includes(kind);
@@ -90536,6 +90977,8 @@ var NC_SYSTEM_EVENT_FAMILY = {
90536
90977
  "detection-blind": "device-events",
90537
90978
  "motion-noisy": "device-events",
90538
90979
  "motion-calm": "device-events",
90980
+ "package-llm-silent": "device-events",
90981
+ "package-llm-answered": "device-events",
90539
90982
  "recording-stalled": "device-events",
90540
90983
  "recording-healthy": "device-events",
90541
90984
  "recording-degraded": "device-events",
@@ -90637,6 +91080,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
90637
91080
  label: "Motion without a subject",
90638
91081
  kinds: ["motion-noisy", "motion-calm"]
90639
91082
  },
91083
+ {
91084
+ id: "package-llm",
91085
+ family: "device-events",
91086
+ label: "Package confirm",
91087
+ kinds: ["package-llm-silent", "package-llm-answered"]
91088
+ },
90640
91089
  {
90641
91090
  id: "recording",
90642
91091
  family: "device-events",
@@ -92069,6 +92518,8 @@ var DEVICE_EVENTS = [
92069
92518
  "detection-blind",
92070
92519
  "motion-noisy",
92071
92520
  "motion-calm",
92521
+ "package-llm-silent",
92522
+ "package-llm-answered",
92072
92523
  "recording-stalled",
92073
92524
  "recording-healthy",
92074
92525
  "recording-degraded",
@@ -92672,7 +93123,8 @@ var NC_TEMPLATE_VARS = [
92672
93123
  systemEventKinds: [
92673
93124
  "recording-degraded",
92674
93125
  ...DEVICE_ATTENTION,
92675
- ...ALERT_EPISODE
93126
+ ...ALERT_EPISODE,
93127
+ "package-llm-silent"
92676
93128
  ]
92677
93129
  },
92678
93130
  {
@@ -92765,7 +93217,8 @@ var NC_TEMPLATE_VARS = [
92765
93217
  systemEventKinds: [
92766
93218
  "storage-location-low",
92767
93219
  "storage-location-critical",
92768
- "storage-location-ok"
93220
+ "storage-location-ok",
93221
+ "package-llm-silent"
92769
93222
  ]
92770
93223
  },
92771
93224
  {
@@ -93176,7 +93629,9 @@ var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
93176
93629
  "device-attention-required",
93177
93630
  "device-attention-resolved",
93178
93631
  "rule-broken",
93179
- "rule-repaired"
93632
+ "rule-repaired",
93633
+ "package-llm-silent",
93634
+ "package-llm-answered"
93180
93635
  ];
93181
93636
  /**
93182
93637
  * The catalog as `getTemplateCatalog` SERVES it: every descriptor, with the
@@ -93336,6 +93791,15 @@ var CLASS_BY_KIND = new Map([
93336
93791
  snapshotLabel: "Cameras with constant motion now",
93337
93792
  snapshotSection: null
93338
93793
  },
93794
+ {
93795
+ id: "package-llm-confirm",
93796
+ family: "device-events",
93797
+ label: "Package confirm (not answering / answering again)",
93798
+ bad: ["package-llm-silent"],
93799
+ recovery: ["package-llm-answered"],
93800
+ snapshotLabel: "Cameras whose package confirm is not answering",
93801
+ snapshotSection: null
93802
+ },
93339
93803
  {
93340
93804
  id: "recording-health",
93341
93805
  family: "device-events",