@camstack/addon-pipeline 1.2.630 → 1.2.632

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 (37) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +39 -20
  3. package/dist/audio-analyzer/index.js +3 -3
  4. package/dist/audio-analyzer/index.mjs +3 -3
  5. package/dist/{custom-model-catalog-qwNT0qE-.mjs → custom-model-catalog-B2w26VBG.mjs} +2 -2
  6. package/dist/{custom-model-catalog-CsHQljey.js → custom-model-catalog-DN4xBVQE.js} +2 -2
  7. package/dist/detection-pipeline/index.js +4 -4
  8. package/dist/detection-pipeline/index.mjs +4 -4
  9. package/dist/{dist-M09KjuSx.js → dist-CLo3QHtG.js} +1574 -934
  10. package/dist/{dist-C56NTN3j.mjs → dist-DWkdwtle.mjs} +1574 -934
  11. package/dist/{model-catalogs-BmZHxpaD.js → model-catalogs-DIviCtiQ.js} +1 -1
  12. package/dist/{model-catalogs-vPj_kgtX.mjs → model-catalogs-rs7okKyp.mjs} +1 -1
  13. package/dist/motion-wasm/index.js +1 -1
  14. package/dist/motion-wasm/index.mjs +1 -1
  15. package/dist/{node-CdsOgsyN.mjs → node-CAJ1Dal-.mjs} +1 -1
  16. package/dist/{node-C0CzsJ0N.js → node-CPqDCFas.js} +1 -1
  17. package/dist/pipeline-runner/index.js +3 -3
  18. package/dist/pipeline-runner/index.mjs +3 -3
  19. package/dist/{process-memory-BURe-pwd.js → process-memory-BrKzYxim.js} +1 -1
  20. package/dist/{process-memory-DAhr_IbO.mjs → process-memory-DJWthb6k.mjs} +1 -1
  21. package/dist/recorder/index.js +3 -2
  22. package/dist/recorder/index.mjs +3 -2
  23. package/dist/{segment-demux-js-B391lmQZ.js → segment-demux-js-Bt5bxfyh.js} +1 -1
  24. package/dist/{segment-demux-js-CobIINvd.mjs → segment-demux-js-CZdgio0n.mjs} +1 -1
  25. package/dist/stream-broker/_stub.js +2 -2
  26. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Bxan5418.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CjjdxZUq.mjs} +3 -3
  27. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DZmxw-we.mjs +26 -0
  28. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CFflpMdb.mjs +26 -0
  29. package/dist/stream-broker/demux-worker-child.js +1 -1
  30. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  31. package/dist/stream-broker/{hostInit-CmFod5rw.mjs → hostInit-MWvZi8Nq.mjs} +3 -3
  32. package/dist/stream-broker/index.js +3 -3
  33. package/dist/stream-broker/index.mjs +3 -3
  34. package/dist/stream-broker/remoteEntry.js +1 -1
  35. package/package.json +2 -2
  36. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-inxALjZf.mjs +0 -26
  37. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-wSzIkKI-.mjs +0 -26
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- //#region ../types/dist/event-category-CT-hz5Rb.mjs
2
+ //#region ../types/dist/event-category-DmZDPDf_.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -626,7 +626,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
626
626
  * (a track was promoted to a stationary-registry entry) or departs (the
627
627
  * object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
628
628
  * the dedicated "Stationary" section — never the live event feed. Payload:
629
- * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
629
+ * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp, observedAt, zones }`.
630
630
  */
631
631
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
632
632
  /**
@@ -764,6 +764,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
764
764
  */
765
765
  EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
766
766
  /**
767
+ * An action run started or ended. Emitted by the `actions` surface.
768
+ * Payload: `ActionRunChanged` (`{ actionId, runId, isRunning, outcome?, lastRunAt, lastError }`).
769
+ */
770
+ EventCategory["ActionsRunChanged"] = "actions.onRunChanged";
771
+ /**
767
772
  * Progress update from a running model conversion job.
768
773
  * Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
769
774
  * Emitted by `addon-model-studio` on the converting node.
@@ -5575,7 +5580,7 @@ var ZodIssueCode = {
5575
5580
  var ZodFirstPartyTypeKind;
5576
5581
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5577
5582
  //#endregion
5578
- //#region ../types/dist/sleep--jXS9hWo.mjs
5583
+ //#region ../types/dist/sleep-Crlmu_tO.mjs
5579
5584
  /**
5580
5585
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5581
5586
  * window to float samples (D455).
@@ -9034,6 +9039,8 @@ Object.freeze({ "doorbell": "lastPressedAt" });
9034
9039
  /** Every capability name, so addon code can check a name without the schema graph. */
9035
9040
  var CAP_NAMES = Object.freeze([
9036
9041
  "accessories",
9042
+ "action-source",
9043
+ "actions",
9037
9044
  "addon-pages",
9038
9045
  "addon-pages-source",
9039
9046
  "addon-routes",
@@ -9222,11 +9229,11 @@ var CAP_NAMES = Object.freeze([
9222
9229
  Object.freeze({
9223
9230
  "air-quality-sensor": [
9224
9231
  {
9225
- "path": "co2Ppm",
9232
+ "path": "co2",
9226
9233
  "valueType": "number"
9227
9234
  },
9228
9235
  {
9229
- "path": "vocPpb",
9236
+ "path": "voc",
9230
9237
  "valueType": "number"
9231
9238
  },
9232
9239
  {
@@ -9276,7 +9283,7 @@ Object.freeze({
9276
9283
  }
9277
9284
  ],
9278
9285
  "ambient-light-sensor": [{
9279
- "path": "lux",
9286
+ "path": "illuminance",
9280
9287
  "valueType": "number"
9281
9288
  }],
9282
9289
  "audio-metrics": [
@@ -9337,7 +9344,7 @@ Object.freeze({
9337
9344
  ],
9338
9345
  "battery": [
9339
9346
  {
9340
- "path": "percentage",
9347
+ "path": "level",
9341
9348
  "valueType": "number"
9342
9349
  },
9343
9350
  {
@@ -9359,7 +9366,7 @@ Object.freeze({
9359
9366
  "valueType": "number"
9360
9367
  },
9361
9368
  {
9362
- "path": "volts",
9369
+ "path": "voltage",
9363
9370
  "valueType": "number"
9364
9371
  },
9365
9372
  {
@@ -9380,7 +9387,7 @@ Object.freeze({
9380
9387
  "valueType": "boolean"
9381
9388
  }],
9382
9389
  "brightness": [{
9383
- "path": "percentage",
9390
+ "path": "level",
9384
9391
  "valueType": "number"
9385
9392
  }],
9386
9393
  "broker": [{
@@ -9581,7 +9588,7 @@ Object.freeze({
9581
9588
  "valueType": "number"
9582
9589
  },
9583
9590
  {
9584
- "path": "mireds",
9591
+ "path": "colorTemperature",
9585
9592
  "valueType": "number"
9586
9593
  }
9587
9594
  ],
@@ -9760,15 +9767,15 @@ Object.freeze({
9760
9767
  "valueType": "string"
9761
9768
  },
9762
9769
  {
9763
- "path": "remainingSeconds",
9770
+ "path": "remaining",
9764
9771
  "valueType": "number"
9765
9772
  },
9766
9773
  {
9767
- "path": "delayedStartSeconds",
9774
+ "path": "delayedStart",
9768
9775
  "valueType": "number"
9769
9776
  },
9770
9777
  {
9771
- "path": "progressPercent",
9778
+ "path": "progress",
9772
9779
  "valueType": "number"
9773
9780
  },
9774
9781
  {
@@ -9882,7 +9889,7 @@ Object.freeze({
9882
9889
  ],
9883
9890
  "fan-control": [
9884
9891
  {
9885
- "path": "percentage",
9892
+ "path": "speed",
9886
9893
  "valueType": "number"
9887
9894
  },
9888
9895
  {
@@ -9982,7 +9989,7 @@ Object.freeze({
9982
9989
  }
9983
9990
  ],
9984
9991
  "humidity-sensor": [{
9985
- "path": "percent",
9992
+ "path": "humidity",
9986
9993
  "valueType": "number"
9987
9994
  }],
9988
9995
  "image": [{
@@ -10092,7 +10099,7 @@ Object.freeze({
10092
10099
  "valueType": "number"
10093
10100
  },
10094
10101
  {
10095
- "path": "progressPercent",
10102
+ "path": "progress",
10096
10103
  "valueType": "number"
10097
10104
  },
10098
10105
  {
@@ -10171,11 +10178,11 @@ Object.freeze({
10171
10178
  "valueType": "string"
10172
10179
  },
10173
10180
  {
10174
- "path": "positionMs",
10181
+ "path": "position",
10175
10182
  "valueType": "number"
10176
10183
  },
10177
10184
  {
10178
- "path": "durationMs",
10185
+ "path": "duration",
10179
10186
  "valueType": "number"
10180
10187
  },
10181
10188
  {
@@ -10277,11 +10284,11 @@ Object.freeze({
10277
10284
  ]
10278
10285
  },
10279
10286
  {
10280
- "path": "signalPercent",
10287
+ "path": "signal",
10281
10288
  "valueType": "number"
10282
10289
  },
10283
10290
  {
10284
- "path": "rssiDbm",
10291
+ "path": "rssi",
10285
10292
  "valueType": "number"
10286
10293
  },
10287
10294
  {
@@ -10336,6 +10343,18 @@ Object.freeze({
10336
10343
  "path": "foodLevel",
10337
10344
  "valueType": "number"
10338
10345
  },
10346
+ {
10347
+ "path": "bowlFood",
10348
+ "valueType": "number"
10349
+ },
10350
+ {
10351
+ "path": "eating",
10352
+ "valueType": "boolean"
10353
+ },
10354
+ {
10355
+ "path": "supportsMarkRefilled",
10356
+ "valueType": "boolean"
10357
+ },
10339
10358
  {
10340
10359
  "path": "food1",
10341
10360
  "valueType": "number"
@@ -10400,23 +10419,23 @@ Object.freeze({
10400
10419
  ],
10401
10420
  "power-meter": [
10402
10421
  {
10403
- "path": "watts",
10422
+ "path": "power",
10404
10423
  "valueType": "number"
10405
10424
  },
10406
10425
  {
10407
- "path": "kwhTotal",
10426
+ "path": "energy",
10408
10427
  "valueType": "number"
10409
10428
  },
10410
10429
  {
10411
- "path": "volts",
10430
+ "path": "voltage",
10412
10431
  "valueType": "number"
10413
10432
  },
10414
10433
  {
10415
- "path": "amps",
10434
+ "path": "current",
10416
10435
  "valueType": "number"
10417
10436
  },
10418
10437
  {
10419
- "path": "va",
10438
+ "path": "apparentPower",
10420
10439
  "valueType": "number"
10421
10440
  },
10422
10441
  {
@@ -10444,14 +10463,10 @@ Object.freeze({
10444
10463
  {
10445
10464
  "path": "gps.accuracyMeters",
10446
10465
  "valueType": "number"
10447
- },
10448
- {
10449
- "path": "batteryPercent",
10450
- "valueType": "number"
10451
10466
  }
10452
10467
  ],
10453
10468
  "pressure-sensor": [{
10454
- "path": "hpa",
10469
+ "path": "pressure",
10455
10470
  "valueType": "number"
10456
10471
  }],
10457
10472
  "privacy-mask": [{
@@ -10765,7 +10780,7 @@ Object.freeze({
10765
10780
  "valueType": "boolean"
10766
10781
  }],
10767
10782
  "temperature-sensor": [{
10768
- "path": "celsius",
10783
+ "path": "temperature",
10769
10784
  "valueType": "number"
10770
10785
  }],
10771
10786
  "update": [
@@ -10813,7 +10828,7 @@ Object.freeze({
10813
10828
  "valueType": "string"
10814
10829
  },
10815
10830
  {
10816
- "path": "progressPercent",
10831
+ "path": "progress",
10817
10832
  "valueType": "number"
10818
10833
  },
10819
10834
  {
@@ -11450,7 +11465,7 @@ function errMsg(err) {
11450
11465
  return String(err);
11451
11466
  }
11452
11467
  //#endregion
11453
- //#region ../types/dist/composition-Bb8iwHEJ.mjs
11468
+ //#region ../types/dist/composition-CZImLpOI.mjs
11454
11469
  var AUDIO_ENCODER_BY_CODEC = {
11455
11470
  opus: "libopus",
11456
11471
  aac: "aac",
@@ -18162,6 +18177,14 @@ var AutomationConditionLeafSchema = object({
18162
18177
  array(union$1([string(), number()]))
18163
18178
  ])
18164
18179
  });
18180
+ var AutomationConditionStationaryPresentSchema = object({
18181
+ kind: literal("stationary-present"),
18182
+ deviceId: number().int().nonnegative(),
18183
+ /** Class names; a macro class matches every raw class that maps to it. */
18184
+ classes: array(string().min(1)).min(1).optional(),
18185
+ /** The parked object must be in at least one of these zones. */
18186
+ zones: array(string().min(1)).min(1).optional()
18187
+ });
18165
18188
  /**
18166
18189
  * The expression leaf, declared as a plain object rather than an intersection
18167
18190
  * with {@link ExpressionSourceSchema}: a discriminated union has to be able to
@@ -18195,7 +18218,8 @@ var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
18195
18218
  child: AutomationConditionSchema
18196
18219
  }),
18197
18220
  AutomationConditionLeafSchema,
18198
- AutomationConditionExpressionSchema
18221
+ AutomationConditionExpressionSchema,
18222
+ AutomationConditionStationaryPresentSchema
18199
18223
  ]));
18200
18224
  /**
18201
18225
  * What starts a run.
@@ -18236,7 +18260,34 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
18236
18260
  kind: literal("schedule"),
18237
18261
  cron: string().min(1).max(120)
18238
18262
  }),
18239
- object({ kind: literal("manual") })
18263
+ object({ kind: literal("manual") }),
18264
+ object({
18265
+ kind: literal("detection"),
18266
+ /** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
18267
+ * matches every raw class that maps to it; any other name matches
18268
+ * exactly. */
18269
+ classes: array(string().min(1)).min(1),
18270
+ deviceIds: array(number().int().nonnegative()).min(1),
18271
+ /** The track must have visited at least one of these zones. */
18272
+ zones: array(string().min(1)).min(1).optional(),
18273
+ /** Best-so-far confidence the track must reach. */
18274
+ minScore: number().min(0).max(1).optional()
18275
+ }),
18276
+ object({
18277
+ kind: literal("stationary"),
18278
+ deviceIds: array(number().int().nonnegative()).min(1),
18279
+ /** Class names; a macro class matches like the detection trigger. */
18280
+ classes: array(string().min(1)).min(1).optional(),
18281
+ /** The parked object must be in at least one of these zones. */
18282
+ zones: array(string().min(1)).min(1).optional(),
18283
+ /** `appeared`: it became parked. `departed`: the registry retired it.
18284
+ * The vocabulary of `PipelineAnalyticsStationaryChanged`. */
18285
+ phase: _enum(["appeared", "departed"])
18286
+ }),
18287
+ object({
18288
+ kind: literal("interval"),
18289
+ minutes: number().int().min(1).max(1440)
18290
+ })
18240
18291
  ]);
18241
18292
  /**
18242
18293
  * One action step.
@@ -18256,28 +18307,47 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
18256
18307
  * is called. The runner still receives a number and is untouched; the
18257
18308
  * resolution is the recipe's job, not the runner's.
18258
18309
  */
18310
+ var AutomationWaitStepSchema = object({
18311
+ kind: literal("wait"),
18312
+ seconds: number().min(0).max(300)
18313
+ });
18314
+ var AutomationCapStepSchema = object({
18315
+ kind: literal("cap"),
18316
+ /**
18317
+ * One device, several same-family devices (locks / climate family / …),
18318
+ * or `{ $var }` for the device that triggered the run. An array is
18319
+ * fan-out at codegen — each id gets the same cap.method call.
18320
+ */
18321
+ deviceId: union$1([
18322
+ number().int(),
18323
+ array(number().int()).min(1),
18324
+ object({ $var: string().min(1) })
18325
+ ]),
18326
+ cap: string().min(1),
18327
+ method: string().min(1),
18328
+ /** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
18329
+ * SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
18330
+ * the expression leaf. */
18331
+ args: record(string(), unknown()).optional()
18332
+ });
18333
+ /**
18334
+ * The steps an internal Action may contain: cap and wait, and nothing that can
18335
+ * call back into an Action — so an Action can never reach itself (no cycles).
18336
+ */
18337
+ var ActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCapStepSchema]);
18259
18338
  var AutomationActionSchema = discriminatedUnion("kind", [
18339
+ AutomationWaitStepSchema,
18340
+ AutomationCapStepSchema,
18260
18341
  object({
18261
- kind: literal("wait"),
18262
- seconds: number().min(0).max(300)
18263
- }),
18264
- object({
18265
- kind: literal("cap"),
18266
- /**
18267
- * One device, several same-family devices (locks / climate family / …),
18268
- * or `{ $var }` for the device that triggered the run. An array is
18269
- * fan-out at codegen — each id gets the same cap.method call.
18270
- */
18271
- deviceId: union$1([
18272
- number().int(),
18273
- array(number().int()).min(1),
18274
- object({ $var: string().min(1) })
18275
- ]),
18276
- cap: string().min(1),
18277
- method: string().min(1),
18278
- /** Values may carry `{{vars.x}}` slots, which SUBSTITUTE and do not
18279
- * evaluate (§3.2.3). Anything beyond substitution is the expression leaf. */
18280
- args: record(string(), unknown()).optional()
18342
+ kind: literal("action"),
18343
+ /** An entry of the `actions` surface (`internal:<uuid>` or `<addonId>:<localId>`). */
18344
+ actionId: string().min(1),
18345
+ /** Values for the action's declared variables; the others take their default. */
18346
+ variables: record(string(), union$1([
18347
+ string(),
18348
+ number(),
18349
+ boolean()
18350
+ ])).optional()
18281
18351
  }),
18282
18352
  object({
18283
18353
  kind: literal("code"),
@@ -18286,74 +18356,743 @@ var AutomationActionSchema = discriminatedUnion("kind", [
18286
18356
  code: string().min(1).max(2e4)
18287
18357
  })
18288
18358
  ]);
18359
+ /**
18360
+ * How a trigger that arrives while the automation is busy (or cooling down) is
18361
+ * handled. One of the automation's execution modes, never a property of its
18362
+ * actions. Absent means `single`.
18363
+ *
18364
+ * - `single` — drop the trigger while a run is in progress (traced + logged).
18365
+ * - `restart` — abort the run in progress (at its next step boundary), start fresh.
18366
+ * - `queued` — queue up to `max` triggers, drop the rest (traced).
18367
+ * - `parallel` — up to `max` runs at once, drop the rest (traced).
18368
+ * - `cooldown` — `single`, and no new run until `minutes` after the last run
18369
+ * STARTED; persisted across a restart. A manual trigger bypasses only this
18370
+ * wait, never the single/queued limits.
18371
+ */
18372
+ var AutomationRunModeSchema = discriminatedUnion("mode", [
18373
+ object({ mode: literal("single") }),
18374
+ object({ mode: literal("restart") }),
18375
+ object({
18376
+ mode: literal("queued"),
18377
+ max: number().int().min(1).max(10)
18378
+ }),
18379
+ object({
18380
+ mode: literal("parallel"),
18381
+ max: number().int().min(1).max(10)
18382
+ }),
18383
+ object({
18384
+ mode: literal("cooldown"),
18385
+ minutes: number().int().min(1).max(1440)
18386
+ })
18387
+ ]);
18289
18388
  var AutomationRecipeSchema = object({
18290
18389
  triggers: array(AutomationTriggerSchema),
18291
18390
  conditions: AutomationConditionSchema.optional(),
18292
- actions: array(AutomationActionSchema)
18391
+ actions: array(AutomationActionSchema),
18392
+ runMode: AutomationRunModeSchema.optional(),
18393
+ /** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
18394
+ keepTraces: number().int().min(1).max(100).optional()
18293
18395
  });
18396
+ var COCO_80_LABELS = [
18397
+ {
18398
+ id: "person",
18399
+ name: "Person"
18400
+ },
18401
+ {
18402
+ id: "bicycle",
18403
+ name: "Bicycle"
18404
+ },
18405
+ {
18406
+ id: "car",
18407
+ name: "Car"
18408
+ },
18409
+ {
18410
+ id: "motorcycle",
18411
+ name: "Motorcycle"
18412
+ },
18413
+ {
18414
+ id: "airplane",
18415
+ name: "Airplane"
18416
+ },
18417
+ {
18418
+ id: "bus",
18419
+ name: "Bus"
18420
+ },
18421
+ {
18422
+ id: "train",
18423
+ name: "Train"
18424
+ },
18425
+ {
18426
+ id: "truck",
18427
+ name: "Truck"
18428
+ },
18429
+ {
18430
+ id: "boat",
18431
+ name: "Boat"
18432
+ },
18433
+ {
18434
+ id: "traffic light",
18435
+ name: "Traffic Light"
18436
+ },
18437
+ {
18438
+ id: "fire hydrant",
18439
+ name: "Fire Hydrant"
18440
+ },
18441
+ {
18442
+ id: "stop sign",
18443
+ name: "Stop Sign"
18444
+ },
18445
+ {
18446
+ id: "parking meter",
18447
+ name: "Parking Meter"
18448
+ },
18449
+ {
18450
+ id: "bench",
18451
+ name: "Bench"
18452
+ },
18453
+ {
18454
+ id: "bird",
18455
+ name: "Bird"
18456
+ },
18457
+ {
18458
+ id: "cat",
18459
+ name: "Cat"
18460
+ },
18461
+ {
18462
+ id: "dog",
18463
+ name: "Dog"
18464
+ },
18465
+ {
18466
+ id: "horse",
18467
+ name: "Horse"
18468
+ },
18469
+ {
18470
+ id: "sheep",
18471
+ name: "Sheep"
18472
+ },
18473
+ {
18474
+ id: "cow",
18475
+ name: "Cow"
18476
+ },
18477
+ {
18478
+ id: "elephant",
18479
+ name: "Elephant"
18480
+ },
18481
+ {
18482
+ id: "bear",
18483
+ name: "Bear"
18484
+ },
18485
+ {
18486
+ id: "zebra",
18487
+ name: "Zebra"
18488
+ },
18489
+ {
18490
+ id: "giraffe",
18491
+ name: "Giraffe"
18492
+ },
18493
+ {
18494
+ id: "backpack",
18495
+ name: "Backpack"
18496
+ },
18497
+ {
18498
+ id: "umbrella",
18499
+ name: "Umbrella"
18500
+ },
18501
+ {
18502
+ id: "handbag",
18503
+ name: "Handbag"
18504
+ },
18505
+ {
18506
+ id: "tie",
18507
+ name: "Tie"
18508
+ },
18509
+ {
18510
+ id: "suitcase",
18511
+ name: "Suitcase"
18512
+ },
18513
+ {
18514
+ id: "frisbee",
18515
+ name: "Frisbee"
18516
+ },
18517
+ {
18518
+ id: "skis",
18519
+ name: "Skis"
18520
+ },
18521
+ {
18522
+ id: "snowboard",
18523
+ name: "Snowboard"
18524
+ },
18525
+ {
18526
+ id: "sports ball",
18527
+ name: "Sports Ball"
18528
+ },
18529
+ {
18530
+ id: "kite",
18531
+ name: "Kite"
18532
+ },
18533
+ {
18534
+ id: "baseball bat",
18535
+ name: "Baseball Bat"
18536
+ },
18537
+ {
18538
+ id: "baseball glove",
18539
+ name: "Baseball Glove"
18540
+ },
18541
+ {
18542
+ id: "skateboard",
18543
+ name: "Skateboard"
18544
+ },
18545
+ {
18546
+ id: "surfboard",
18547
+ name: "Surfboard"
18548
+ },
18549
+ {
18550
+ id: "tennis racket",
18551
+ name: "Tennis Racket"
18552
+ },
18553
+ {
18554
+ id: "bottle",
18555
+ name: "Bottle"
18556
+ },
18557
+ {
18558
+ id: "wine glass",
18559
+ name: "Wine Glass"
18560
+ },
18561
+ {
18562
+ id: "cup",
18563
+ name: "Cup"
18564
+ },
18565
+ {
18566
+ id: "fork",
18567
+ name: "Fork"
18568
+ },
18569
+ {
18570
+ id: "knife",
18571
+ name: "Knife"
18572
+ },
18573
+ {
18574
+ id: "spoon",
18575
+ name: "Spoon"
18576
+ },
18577
+ {
18578
+ id: "bowl",
18579
+ name: "Bowl"
18580
+ },
18581
+ {
18582
+ id: "banana",
18583
+ name: "Banana"
18584
+ },
18585
+ {
18586
+ id: "apple",
18587
+ name: "Apple"
18588
+ },
18589
+ {
18590
+ id: "sandwich",
18591
+ name: "Sandwich"
18592
+ },
18593
+ {
18594
+ id: "orange",
18595
+ name: "Orange"
18596
+ },
18597
+ {
18598
+ id: "broccoli",
18599
+ name: "Broccoli"
18600
+ },
18601
+ {
18602
+ id: "carrot",
18603
+ name: "Carrot"
18604
+ },
18605
+ {
18606
+ id: "hot dog",
18607
+ name: "Hot Dog"
18608
+ },
18609
+ {
18610
+ id: "pizza",
18611
+ name: "Pizza"
18612
+ },
18613
+ {
18614
+ id: "donut",
18615
+ name: "Donut"
18616
+ },
18617
+ {
18618
+ id: "cake",
18619
+ name: "Cake"
18620
+ },
18621
+ {
18622
+ id: "chair",
18623
+ name: "Chair"
18624
+ },
18625
+ {
18626
+ id: "couch",
18627
+ name: "Couch"
18628
+ },
18629
+ {
18630
+ id: "potted plant",
18631
+ name: "Potted Plant"
18632
+ },
18633
+ {
18634
+ id: "bed",
18635
+ name: "Bed"
18636
+ },
18637
+ {
18638
+ id: "dining table",
18639
+ name: "Dining Table"
18640
+ },
18641
+ {
18642
+ id: "toilet",
18643
+ name: "Toilet"
18644
+ },
18645
+ {
18646
+ id: "tv",
18647
+ name: "TV"
18648
+ },
18649
+ {
18650
+ id: "laptop",
18651
+ name: "Laptop"
18652
+ },
18653
+ {
18654
+ id: "mouse",
18655
+ name: "Mouse"
18656
+ },
18657
+ {
18658
+ id: "remote",
18659
+ name: "Remote"
18660
+ },
18661
+ {
18662
+ id: "keyboard",
18663
+ name: "Keyboard"
18664
+ },
18665
+ {
18666
+ id: "cell phone",
18667
+ name: "Cell Phone"
18668
+ },
18669
+ {
18670
+ id: "microwave",
18671
+ name: "Microwave"
18672
+ },
18673
+ {
18674
+ id: "oven",
18675
+ name: "Oven"
18676
+ },
18677
+ {
18678
+ id: "toaster",
18679
+ name: "Toaster"
18680
+ },
18681
+ {
18682
+ id: "sink",
18683
+ name: "Sink"
18684
+ },
18685
+ {
18686
+ id: "refrigerator",
18687
+ name: "Refrigerator"
18688
+ },
18689
+ {
18690
+ id: "book",
18691
+ name: "Book"
18692
+ },
18693
+ {
18694
+ id: "clock",
18695
+ name: "Clock"
18696
+ },
18697
+ {
18698
+ id: "vase",
18699
+ name: "Vase"
18700
+ },
18701
+ {
18702
+ id: "scissors",
18703
+ name: "Scissors"
18704
+ },
18705
+ {
18706
+ id: "teddy bear",
18707
+ name: "Teddy Bear"
18708
+ },
18709
+ {
18710
+ id: "hair drier",
18711
+ name: "Hair Drier"
18712
+ },
18713
+ {
18714
+ id: "toothbrush",
18715
+ name: "Toothbrush"
18716
+ }
18717
+ ];
18718
+ new Set([
18719
+ {
18720
+ id: "person",
18721
+ name: "Person"
18722
+ },
18723
+ {
18724
+ id: "vehicle",
18725
+ name: "Vehicle"
18726
+ },
18727
+ {
18728
+ id: "animal",
18729
+ name: "Animal"
18730
+ },
18731
+ {
18732
+ id: "package",
18733
+ name: "Package"
18734
+ }
18735
+ ].map((l) => l.id));
18736
+ var COCO_TO_MACRO = {
18737
+ mapping: {
18738
+ person: "person",
18739
+ bicycle: "vehicle",
18740
+ car: "vehicle",
18741
+ motorcycle: "vehicle",
18742
+ airplane: "vehicle",
18743
+ bus: "vehicle",
18744
+ train: "vehicle",
18745
+ truck: "vehicle",
18746
+ boat: "vehicle",
18747
+ bird: "animal",
18748
+ cat: "animal",
18749
+ dog: "animal",
18750
+ horse: "animal",
18751
+ sheep: "animal",
18752
+ cow: "animal",
18753
+ elephant: "animal",
18754
+ bear: "animal",
18755
+ zebra: "animal",
18756
+ giraffe: "animal",
18757
+ suitcase: "package",
18758
+ backpack: "package",
18759
+ handbag: "package"
18760
+ },
18761
+ preserveOriginal: false
18762
+ };
18763
+ new Set(Object.values(COCO_TO_MACRO.mapping));
18294
18764
  /**
18295
- * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
18296
- * surface that admin-ui consumes through `useAddonPagesListPages()`.
18765
+ * Run traces — what an automation (or, later, an Action run) DID, node by node,
18766
+ * so the editor can paint every block green or red with its reason.
18297
18767
  *
18298
- * The provider iterates every `addon-pages-source` (collection) provider
18299
- * and emits `AddonPageInfo[]` enriched with versioned `bundleUrl` strings
18300
- * pointing at `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. The
18301
- * filesystem `mtime` cache-buster lets the browser pick up addon
18302
- * rebuilds without manual reload.
18768
+ * A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
18769
+ * every condition node (containers and leaves, including the ones a
18770
+ * short-circuit never reached), the run-mode verdict and every action step —
18771
+ * each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
18772
+ * for an Action run). Skipped
18773
+ * evaluations are traced too, and identical consecutive skips collapse into a
18774
+ * repeat count so a trigger that fails every 5 minutes cannot flush real runs.
18303
18775
  *
18304
- * The hub-local builtin `addon-pages-aggregator` (see
18305
- * `@camstack/system/builtins/addon-pages-aggregator`) registers the
18306
- * provider. Splitting the public aggregator from the raw collection
18307
- * keeps both ends in codegen — there's no hand-written
18308
- * `addon-pages.router.ts` wrapper anymore.
18776
+ * {@link AutomationStepTraceSchema} is deliberately free of automation
18777
+ * vocabulary so an Action run records the same step shape.
18309
18778
  */
18310
- var AddonPageDeclarationSchema$1 = object({
18311
- id: string(),
18312
- label: string(),
18313
- icon: string(),
18314
- path: string(),
18315
- remoteName: string(),
18316
- bundle: string(),
18317
- section: string().optional(),
18318
- sectionLabel: string().optional()
18779
+ /** A value a trace may show: JSON primitives and short lists, nothing deeper. */
18780
+ var TraceValueSchema = union$1([
18781
+ string(),
18782
+ number(),
18783
+ boolean(),
18784
+ _null(),
18785
+ array(union$1([string(), number()]))
18786
+ ]);
18787
+ var AutomationRunOutcomeSchema = _enum([
18788
+ "ran",
18789
+ "skipped-trigger",
18790
+ "skipped-conditions",
18791
+ "skipped-cooldown",
18792
+ "skipped-overlap",
18793
+ "failed",
18794
+ "aborted"
18795
+ ]);
18796
+ /** `not-evaluated` = a short-circuit never reached the node. */
18797
+ var TraceNodeOutcomeSchema = _enum([
18798
+ "pass",
18799
+ "fail",
18800
+ "not-evaluated"
18801
+ ]);
18802
+ var AutomationTriggerTraceSchema = object({
18803
+ kind: string().min(1),
18804
+ outcome: _enum(["pass", "fail"]),
18805
+ /** Why an event was rejected ("class person not in [dog]") or what fired. */
18806
+ reason: string(),
18807
+ /** deviceId, className, trackId, score … — only what the trigger exposes. */
18808
+ vars: record(string(), TraceValueSchema)
18319
18809
  });
18320
- var AddonPageInfoSchema = object({
18321
- addonId: string(),
18322
- page: AddonPageDeclarationSchema$1,
18323
- bundleUrl: string()
18810
+ var AutomationConditionTraceNodeSchema = object({
18811
+ /** `conditions` for the root, `conditions.<i>` for its i-th child, … */
18812
+ path: string().min(1),
18813
+ kind: _enum([
18814
+ "all",
18815
+ "any",
18816
+ "not",
18817
+ "condition",
18818
+ "expression",
18819
+ "stationary-present"
18820
+ ]),
18821
+ outcome: TraceNodeOutcomeSchema,
18822
+ reason: string(),
18823
+ deviceId: number().int().optional(),
18824
+ cap: string().optional(),
18825
+ fieldPath: string().optional(),
18826
+ operator: string().optional(),
18827
+ expected: TraceValueSchema.optional(),
18828
+ /** What the mirror held; `null` when it held nothing (unknown). */
18829
+ observed: TraceValueSchema.optional()
18830
+ });
18831
+ var AutomationRunVerdictSchema = object({
18832
+ mode: AutomationRunModeSchema,
18833
+ decision: _enum([
18834
+ "run",
18835
+ "queued",
18836
+ "restart",
18837
+ "drop",
18838
+ "cooldown",
18839
+ "bypass"
18840
+ ]),
18841
+ reason: string()
18842
+ });
18843
+ /** One executed (or not-run) step — shared by automations and Action runs. */
18844
+ var AutomationStepTraceSchema = object({
18845
+ /** `actions.<i>` */
18846
+ path: string().min(1),
18847
+ kind: string().min(1),
18848
+ cap: string().optional(),
18849
+ method: string().optional(),
18850
+ deviceId: number().int().optional(),
18851
+ startedAt: number().optional(),
18852
+ endedAt: number().optional(),
18853
+ outcome: _enum([
18854
+ "ok",
18855
+ "error",
18856
+ "not-run"
18857
+ ]),
18858
+ message: string().optional()
18859
+ });
18860
+ var AutomationRunTraceSchema = object({
18861
+ runId: string().min(1),
18862
+ startedAt: number(),
18863
+ endedAt: number(),
18864
+ trigger: AutomationTriggerTraceSchema,
18865
+ conditions: array(AutomationConditionTraceNodeSchema),
18866
+ verdict: AutomationRunVerdictSchema.optional(),
18867
+ steps: array(AutomationStepTraceSchema),
18868
+ outcome: AutomationRunOutcomeSchema,
18869
+ /** How many identical consecutive skips this trace stands for (>= 1). */
18870
+ repeat: number().int().min(1),
18871
+ /** When the latest of those repeats happened. */
18872
+ lastAt: number()
18324
18873
  });
18325
- method(_void(), array(AddonPageInfoSchema).readonly());
18326
18874
  /**
18327
- * `addon-pages-source` — collection cap exposing per-provider raw page
18328
- * declarations. Every addon that contributes a UI page registers a
18329
- * provider here. The hub-side singleton aggregator (`addon-pages` cap,
18330
- * see `addon-pages.cap.ts`) walks this collection, stamps versioned
18331
- * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
18332
- * that admin-ui consumes.
18875
+ * What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
18876
+ * `action:<actionId>`): the cooldown stamp and the trace ring.
18877
+ */
18878
+ var AutomationStateDocSchema = object({
18879
+ version: literal(1),
18880
+ lastRunStartedAt: number().nullable(),
18881
+ traces: array(AutomationRunTraceSchema)
18882
+ });
18883
+ /**
18884
+ * Actions — named, runnable things an operator (or an automation) can start,
18885
+ * declared as LIST ENTRIES, never as devices.
18333
18886
  *
18334
- * The split exists because the public listing has a different output
18335
- * shape than the per-provider raw declarations, and we want both ends
18336
- * to flow through codegen instead of relying on a hand-written wrapper.
18887
+ * An entry is either **internal** (a short list of cap/wait steps kept in the
18888
+ * hub's `actions` record kind) or declared by an addon through the
18889
+ * `action-source` collection (a Home Assistant script, a scene …). The one
18890
+ * surface the UI and automations talk to is the `actions` cap; it keeps no
18891
+ * catalog of its own (D3) — every `list` asks the providers live.
18892
+ *
18893
+ * This file is the data half: the entry schema, the variables an entry may
18894
+ * declare, and the pure resolution of the values a run was given. It holds no
18895
+ * runtime.
18337
18896
  */
18338
- var AddonPageDeclarationSchema = object({
18339
- id: string(),
18340
- label: string(),
18341
- icon: string(),
18342
- path: string(),
18343
- /**
18344
- * Module Federation remote name — must match the `name` field on the
18345
- * page addon's `federation()` plugin config. Used by admin-ui's
18346
- * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
18347
- * Conventionally `addon_<id>_page` (snake_case; MF names cannot
18348
- * contain hyphens).
18349
- */
18350
- remoteName: string(),
18351
- /**
18352
- * Bundle filename inside the addon's `dist/` dir served at
18353
- * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
18354
- * is always `'remoteEntry.js'`; the value is kept on the metadata so
18355
- * the static-file route can compute an mtime-based cache-buster URL
18356
- * without a separate filesystem stat.
18897
+ /** What a variable's value is. `select` takes one of `options`. */
18898
+ var ActionVariableTypeSchema = _enum([
18899
+ "string",
18900
+ "number",
18901
+ "boolean",
18902
+ "select"
18903
+ ]);
18904
+ var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
18905
+ var ActionVariableValueSchema = union$1([
18906
+ string(),
18907
+ number(),
18908
+ boolean()
18909
+ ]);
18910
+ /**
18911
+ * One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
18912
+ * value) or `{{vars.name}}` (inside a string) in their args.
18913
+ */
18914
+ var ActionVariableSchema = object({
18915
+ name: string().regex(ACTION_VARIABLE_NAME_RE),
18916
+ label: string().min(1).max(120).optional(),
18917
+ type: ActionVariableTypeSchema,
18918
+ required: boolean().optional(),
18919
+ default: ActionVariableValueSchema.optional(),
18920
+ /** The allowed values of a `select`. */
18921
+ options: array(string().min(1)).min(1).optional()
18922
+ }).superRefine((v, ctx) => {
18923
+ if (v.type === "select" && v.options === void 0) ctx.addIssue({
18924
+ code: "custom",
18925
+ message: "a select variable needs options",
18926
+ path: ["options"]
18927
+ });
18928
+ if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
18929
+ code: "custom",
18930
+ message: variableValueError(v, v.default) ?? "invalid default",
18931
+ path: ["default"]
18932
+ });
18933
+ });
18934
+ /** Why `value` is not a legal value for the variable, or null when it is. */
18935
+ function variableValueError(variable, value) {
18936
+ switch (variable.type) {
18937
+ case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
18938
+ case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
18939
+ case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
18940
+ case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
18941
+ }
18942
+ }
18943
+ var ActionSourceKindSchema = _enum(["internal", "addon"]);
18944
+ function isInternalActionId(actionId) {
18945
+ return actionId.startsWith("internal:") && actionId.length > 9;
18946
+ }
18947
+ /** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
18948
+ function addonIdOfActionId(actionId) {
18949
+ if (isInternalActionId(actionId)) return null;
18950
+ const at = actionId.indexOf(":");
18951
+ return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
18952
+ }
18953
+ var ActionStatusSchema = object({
18954
+ isRunning: boolean(),
18955
+ /** Ms epoch the latest run STARTED; null when it never ran. */
18956
+ lastRunAt: number().nullable(),
18957
+ /** The latest run's failure; null on success or when it never ran. */
18958
+ lastError: string().nullable()
18959
+ });
18960
+ var ActionEntrySchema = object({
18961
+ /** `internal:<uuid>` or `<addonId>:<localId>`. */
18962
+ id: string().min(3),
18963
+ name: string().min(1).max(120),
18964
+ /** The addon that declared it; `core-blocks` for an internal one. */
18965
+ addonId: string().min(1),
18966
+ source: ActionSourceKindSchema,
18967
+ /** An addon-declared entry is edited where it was declared. */
18968
+ readOnly: boolean(),
18969
+ /** Internal only: cap and wait steps. No action-in-action, so no cycles. */
18970
+ steps: array(ActionStepSchema).min(1).max(50).optional(),
18971
+ variables: array(ActionVariableSchema).max(20).optional(),
18972
+ status: ActionStatusSchema
18973
+ }).superRefine((entry, ctx) => {
18974
+ if (entry.source === "internal") {
18975
+ if (!isInternalActionId(entry.id)) ctx.addIssue({
18976
+ code: "custom",
18977
+ message: "an internal action id is internal:<uuid>",
18978
+ path: ["id"]
18979
+ });
18980
+ return;
18981
+ }
18982
+ if (entry.steps !== void 0) ctx.addIssue({
18983
+ code: "custom",
18984
+ message: "only an internal action carries steps",
18985
+ path: ["steps"]
18986
+ });
18987
+ if (!entry.readOnly) ctx.addIssue({
18988
+ code: "custom",
18989
+ message: "an addon action is read-only here",
18990
+ path: ["readOnly"]
18991
+ });
18992
+ if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
18993
+ code: "custom",
18994
+ message: "an addon action id is <addonId>:<localId>",
18995
+ path: ["id"]
18996
+ });
18997
+ });
18998
+ /** What an operator writes for an internal action. */
18999
+ var ActionInputSchema = object({
19000
+ name: string().min(1).max(120),
19001
+ steps: array(ActionStepSchema).min(1).max(50),
19002
+ variables: array(ActionVariableSchema).max(20).optional()
19003
+ });
19004
+ /** Who started a run. Absent = the operator pressed it. */
19005
+ var ActionRunOriginSchema = object({
19006
+ /** The automation block whose step started it. */
19007
+ blockId: string().min(1).optional() });
19008
+ /** `started` = the caller did not ask to wait. */
19009
+ var ActionRunOutcomeSchema = union$1([AutomationRunOutcomeSchema, literal("started")]);
19010
+ var ActionRunResultSchema = object({
19011
+ runId: string().min(1),
19012
+ outcome: ActionRunOutcomeSchema,
19013
+ /** Why it did not run, or what failed. */
19014
+ message: string().optional()
19015
+ });
19016
+ _enum([
19017
+ "not-found",
19018
+ "read-only",
19019
+ "invalid-variables",
19020
+ "invalid-steps",
19021
+ "not-linked",
19022
+ "busy"
19023
+ ]);
19024
+ object({
19025
+ actionId: string(),
19026
+ runId: string(),
19027
+ isRunning: boolean(),
19028
+ /** Set when the run ended. */
19029
+ outcome: AutomationRunOutcomeSchema.optional(),
19030
+ lastRunAt: number().nullable(),
19031
+ lastError: string().nullable()
19032
+ });
19033
+ /**
19034
+ * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
19035
+ * surface that admin-ui consumes through `useAddonPagesListPages()`.
19036
+ *
19037
+ * The provider iterates every `addon-pages-source` (collection) provider
19038
+ * and emits `AddonPageInfo[]` enriched with versioned `bundleUrl` strings
19039
+ * pointing at `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. The
19040
+ * filesystem `mtime` cache-buster lets the browser pick up addon
19041
+ * rebuilds without manual reload.
19042
+ *
19043
+ * The hub-local builtin `addon-pages-aggregator` (see
19044
+ * `@camstack/system/builtins/addon-pages-aggregator`) registers the
19045
+ * provider. Splitting the public aggregator from the raw collection
19046
+ * keeps both ends in codegen — there's no hand-written
19047
+ * `addon-pages.router.ts` wrapper anymore.
19048
+ */
19049
+ var AddonPageDeclarationSchema$1 = object({
19050
+ id: string(),
19051
+ label: string(),
19052
+ icon: string(),
19053
+ path: string(),
19054
+ remoteName: string(),
19055
+ bundle: string(),
19056
+ section: string().optional(),
19057
+ sectionLabel: string().optional()
19058
+ });
19059
+ var AddonPageInfoSchema = object({
19060
+ addonId: string(),
19061
+ page: AddonPageDeclarationSchema$1,
19062
+ bundleUrl: string()
19063
+ });
19064
+ method(_void(), array(AddonPageInfoSchema).readonly());
19065
+ /**
19066
+ * `addon-pages-source` — collection cap exposing per-provider raw page
19067
+ * declarations. Every addon that contributes a UI page registers a
19068
+ * provider here. The hub-side singleton aggregator (`addon-pages` cap,
19069
+ * see `addon-pages.cap.ts`) walks this collection, stamps versioned
19070
+ * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
19071
+ * that admin-ui consumes.
19072
+ *
19073
+ * The split exists because the public listing has a different output
19074
+ * shape than the per-provider raw declarations, and we want both ends
19075
+ * to flow through codegen instead of relying on a hand-written wrapper.
19076
+ */
19077
+ var AddonPageDeclarationSchema = object({
19078
+ id: string(),
19079
+ label: string(),
19080
+ icon: string(),
19081
+ path: string(),
19082
+ /**
19083
+ * Module Federation remote name — must match the `name` field on the
19084
+ * page addon's `federation()` plugin config. Used by admin-ui's
19085
+ * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
19086
+ * Conventionally `addon_<id>_page` (snake_case; MF names cannot
19087
+ * contain hyphens).
19088
+ */
19089
+ remoteName: string(),
19090
+ /**
19091
+ * Bundle filename inside the addon's `dist/` dir served at
19092
+ * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
19093
+ * is always `'remoteEntry.js'`; the value is kept on the metadata so
19094
+ * the static-file route can compute an mtime-based cache-buster URL
19095
+ * without a separate filesystem stat.
18357
19096
  */
18358
19097
  bundle: string(),
18359
19098
  /**
@@ -20002,7 +20741,22 @@ var BrokerProviderInfoSchema = object({
20002
20741
  */
20003
20742
  interactiveConnectPath: string().min(1).optional(),
20004
20743
  /** Relative hub path to disconnect the linked account (same popup pattern). */
20005
- interactiveDisconnectPath: string().min(1).optional()
20744
+ interactiveDisconnectPath: string().min(1).optional(),
20745
+ /**
20746
+ * The kind signs in through a server-held interactive session instead of
20747
+ * (or before) typed credentials. `basePath` is a hub-relative route set
20748
+ * served by the owning addon (`/addon/<id>/...`) speaking the sign-in
20749
+ * contract: `POST {basePath}/start` (body: the form's settings) answers
20750
+ * `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
20751
+ * `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
20752
+ * 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
20753
+ * (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
20754
+ * existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
20755
+ * (`{ sessionId }`) drops the session. The dialog that edits a broker
20756
+ * offers it as the default path; the fields the session fills are
20757
+ * marked `filledBySignIn` in the kind's settings schema.
20758
+ */
20759
+ signIn: object({ basePath: string().min(1) }).optional()
20006
20760
  }))
20007
20761
  });
20008
20762
  var ListInputSchema = object({
@@ -21327,7 +22081,7 @@ var CompositionEventPlanSchema = object({
21327
22081
  stateField: string(),
21328
22082
  fieldKey: string()
21329
22083
  });
21330
- /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
22084
+ /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.temperature`. */
21331
22085
  var CompositionProblemSchema = object({
21332
22086
  code: _enum([
21333
22087
  "reserved-variant",
@@ -21646,7 +22400,7 @@ var GROUP_TEMPLATES = [
21646
22400
  summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
21647
22401
  role: DeviceRole.TemperatureSensor,
21648
22402
  outputCap: "temperature-sensor",
21649
- outputField: "celsius",
22403
+ outputField: "temperature",
21650
22404
  quantity: "temperature",
21651
22405
  defaultAggregate: "mean",
21652
22406
  empty: "null",
@@ -21657,7 +22411,7 @@ var GROUP_TEMPLATES = [
21657
22411
  summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
21658
22412
  role: DeviceRole.HumiditySensor,
21659
22413
  outputCap: "humidity-sensor",
21660
- outputField: "percent",
22414
+ outputField: "humidity",
21661
22415
  quantity: "humidity",
21662
22416
  defaultAggregate: "mean",
21663
22417
  empty: "null",
@@ -21668,7 +22422,7 @@ var GROUP_TEMPLATES = [
21668
22422
  summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
21669
22423
  role: DeviceRole.PressureSensor,
21670
22424
  outputCap: "pressure-sensor",
21671
- outputField: "hpa",
22425
+ outputField: "pressure",
21672
22426
  quantity: "pressure",
21673
22427
  defaultAggregate: "mean",
21674
22428
  empty: "null",
@@ -21679,7 +22433,7 @@ var GROUP_TEMPLATES = [
21679
22433
  summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
21680
22434
  role: DeviceRole.AmbientLightSensor,
21681
22435
  outputCap: "ambient-light-sensor",
21682
- outputField: "lux",
22436
+ outputField: "illuminance",
21683
22437
  quantity: "illuminance",
21684
22438
  defaultAggregate: "mean",
21685
22439
  empty: "null",
@@ -21690,7 +22444,7 @@ var GROUP_TEMPLATES = [
21690
22444
  summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
21691
22445
  role: DeviceRole.PowerSensor,
21692
22446
  outputCap: "power-meter",
21693
- outputField: "watts",
22447
+ outputField: "power",
21694
22448
  quantity: "power",
21695
22449
  defaultAggregate: "sum",
21696
22450
  empty: "omit",
@@ -21701,7 +22455,7 @@ var GROUP_TEMPLATES = [
21701
22455
  summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
21702
22456
  role: DeviceRole.EnergySensor,
21703
22457
  outputCap: "power-meter",
21704
- outputField: "kwhTotal",
22458
+ outputField: "energy",
21705
22459
  quantity: "energy",
21706
22460
  defaultAggregate: "sum",
21707
22461
  empty: "omit",
@@ -21712,7 +22466,7 @@ var GROUP_TEMPLATES = [
21712
22466
  summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
21713
22467
  role: DeviceRole.BatterySensor,
21714
22468
  outputCap: "battery",
21715
- outputField: "percentage",
22469
+ outputField: "level",
21716
22470
  quantity: "percentage",
21717
22471
  defaultAggregate: "mean",
21718
22472
  empty: "null",
@@ -22290,7 +23044,18 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
22290
23044
  kind: "mutation",
22291
23045
  auth: "admin",
22292
23046
  access: "view"
22293
- });
23047
+ }), method(object({
23048
+ blockId: string().min(1),
23049
+ trace: AutomationRunTraceSchema.optional(),
23050
+ runStartedAt: number().optional(),
23051
+ keepTraces: number().int().min(1).max(100).optional()
23052
+ }), _void(), {
23053
+ kind: "mutation",
23054
+ auth: "admin"
23055
+ }), method(object({ blockId: string().min(1) }), AutomationStateDocSchema, { auth: "admin" }), method(object({ blockId: string().min(1) }), object({
23056
+ traces: array(AutomationRunTraceSchema),
23057
+ lastError: string().nullable()
23058
+ }), { auth: "admin" });
22294
23059
  /**
22295
23060
  * `custom-model-registry` — collection cap exposing operator-registered
22296
23061
  * custom detection models. Each provider (today: `addon-model-studio`)
@@ -47109,7 +47874,7 @@ var NetworkAccessStatusSchema = object({
47109
47874
  });
47110
47875
  /**
47111
47876
  * Optional, richer endpoint shape returned by providers that expose
47112
- * MORE than one ingress concurrently (Tailscale Ingress with mixed
47877
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
47113
47878
  * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
47114
47879
  * the originating provider config (mode + sourcePort) so the
47115
47880
  * orchestrator UI can label rows distinctly. Providers that expose only
@@ -47146,729 +47911,362 @@ method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _v
47146
47911
  * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
47147
47912
  * alternative would fork the UI per addon and cannot host the
47148
47913
  * discovery→adopt flow.
47149
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
47150
- * the generated cap-mount auto-`concatCollection`-fans them across every
47151
- * registered provider (notifiers addon + HA addon) so one catalog is
47152
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
47153
- * `addonId` the generated collection router extracts from the call input.
47154
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
47155
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
47156
- * `storage` / `storage-provider` / `recording` caps over the same path. No
47157
- * base64 fallback needed.
47158
- *
47159
- * TODO (deferred, closed-set change — separate decision): add
47160
- * `providerKind: 'notify'` so notification providers surface on the unified
47161
- * admin "Integrations" page.
47162
- */
47163
- /**
47164
- * Zentik-derived typed-media enum — the superset across every kind. Each
47165
- * adapter picks what it supports and the degrade engine filters the rest.
47166
- */
47167
- var AttachmentMediaTypeSchema = _enum([
47168
- "image",
47169
- "video",
47170
- "gif",
47171
- "audio",
47172
- "icon"
47173
- ]);
47174
- /**
47175
- * A single attachment. Exactly one of `url` (remote source, most adapters
47176
- * prefer this) or `bytes` (inline source; required for Pushover-style
47177
- * bytes-only kinds) MUST be present — the degrade engine expresses a
47178
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
47179
- */
47180
- var AttachmentSchema = object({
47181
- mediaType: AttachmentMediaTypeSchema,
47182
- url: string().optional(),
47183
- bytes: _instanceof(Uint8Array).optional(),
47184
- mime: string().optional(),
47185
- name: string().optional()
47186
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
47187
- var NotificationFormatSchema = _enum([
47188
- "text",
47189
- "markdown",
47190
- "html"
47191
- ]);
47192
- /**
47193
- * The CLOSED icon vocabulary an action button may use.
47194
- *
47195
- * A closed set, not a free string, and that is the whole point: an arbitrary
47196
- * icon name is one that ntfy renders, zentik silently drops, and nobody
47197
- * notices — the same class of gap as a zone vocabulary nothing produced
47198
- * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
47199
- * declares `actionIcons: false` and the degrade engine strips the field.
47200
- *
47201
- * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
47202
- * renderer's icon set; "acknowledge" survives an adapter that draws it
47203
- * differently.
47204
- *
47205
- * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
47206
- *
47207
- * These names are for US. **No adapter may forward one verbatim.** Each maps
47208
- * the whole set onto its own renderer's vocabulary through a
47209
- * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
47210
- * fallback, so adding a member here fails every adapter's build until someone
47211
- * decides its glyph, which is the only place that decision can be made
47212
- * honestly.
47213
- *
47214
- * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
47215
- * `disarm` straight through; iOS feeds that string to
47216
- * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
47217
- * and every snooze and alarm button arrived BLANK. A pass-through is not a
47218
- * mapping, and "the field is documented" is not "the value renders".
47219
- *
47220
- * Adding a member is TRAIN-BOUND. The enum lives in the published
47221
- * `@camstack/server` closure and the cap seam validates against the HUB's copy,
47222
- * so an addon that emits a token the running hub does not know does not lose an
47223
- * icon — its whole `send` fails Zod validation and the notification never
47224
- * arrives. Never emit a new token from an addon before the train carrying it.
47225
- */
47226
- var NotificationActionIconSchema = _enum([
47227
- "acknowledge",
47228
- "dismiss",
47229
- "silence",
47230
- "snooze",
47231
- "view",
47232
- "play",
47233
- "open",
47234
- "close",
47235
- "lock",
47236
- "unlock",
47237
- "arm",
47238
- "arm-home",
47239
- "arm-away",
47240
- "arm-night",
47241
- "disarm",
47242
- "light",
47243
- "alert",
47244
- "camera"
47245
- ]);
47246
- /** A single tap-through action button. */
47247
- var NotificationActionSchema = object({
47248
- id: string(),
47249
- label: string(),
47250
- url: string().optional(),
47251
- /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
47252
- icon: NotificationActionIconSchema.optional(),
47253
- /**
47254
- * Renders in a warning style where the notifier supports it.
47255
- *
47256
- * A HINT, never a gate. The callback's authority is its token and nothing
47257
- * else — see `notification-center/action-token.ts` for what that does and
47258
- * does not buy.
47259
- */
47260
- destructive: boolean().optional(),
47261
- /**
47262
- * How the tap should REACH the url.
47263
- *
47264
- * `navigate` (absent, and every button authored before this field) opens it:
47265
- * the phone leaves the notification and shows whatever the callback returns.
47266
- * That is right for a button whose answer the operator wants to read.
47267
- *
47268
- * `background` fires it as a POST and stays put. It exists for the buttons
47269
- * whose whole point is not to interrupt — "silence this for 30 minutes" is
47270
- * an answer to the notification, and being thrown into a browser tab to
47271
- * confirm it costs more attention than the notification did. A backend that
47272
- * cannot do a background call renders it as an ordinary link (the adapters
47273
- * fall back rather than dropping the button), so this is a preference, never
47274
- * a requirement.
47275
- */
47276
- mode: _enum(["navigate", "background"]).optional()
47277
- });
47278
- /**
47279
- * The canonical notification. `body` is the only hard field (Apprise model).
47280
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
47281
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
47282
- * the adapter maps this ordinal onto its native level. `level?` is an
47283
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
47284
- * `priority` for that one target.
47285
- */
47286
- var NotificationSchema = object({
47287
- body: string(),
47288
- title: string().optional(),
47289
- format: NotificationFormatSchema.default("text"),
47290
- priority: number().int().min(1).max(5).default(3),
47291
- level: string().optional(),
47292
- attachments: array(AttachmentSchema).optional(),
47293
- clickUrl: string().optional(),
47294
- actions: array(NotificationActionSchema).optional(),
47295
- sound: string().optional(),
47296
- ttl: number().optional(),
47297
- tag: string().optional(),
47298
- deviceId: number().optional(),
47299
- eventId: string().optional(),
47300
- metadata: record(string(), unknown()).optional()
47301
- });
47302
- /** One declared native severity/priority level for a kind. */
47303
- var TargetKindLevelSchema = object({
47304
- id: string(),
47305
- label: string(),
47306
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
47307
- ordinal: number().int().min(1).max(5).nullable(),
47308
- flags: object({
47309
- critical: boolean().optional(),
47310
- silent: boolean().optional(),
47311
- noPush: boolean().optional()
47312
- }).optional(),
47313
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
47314
- requires: array(string()).optional(),
47315
- description: string().optional()
47316
- });
47317
- /** The full capability block consulted before dispatch. */
47318
- var TargetKindCapsSchema = object({
47319
- attachments: object({
47320
- mediaTypes: array(AttachmentMediaTypeSchema),
47321
- mode: _enum([
47322
- "url",
47323
- "bytes",
47324
- "both"
47325
- ]),
47326
- max: number().int().nonnegative(),
47327
- maxBytes: number().int().positive().optional()
47328
- }),
47329
- /** Max action buttons (0 = none). */
47330
- actions: number().int().nonnegative(),
47331
- /**
47332
- * Whether this kind renders a per-action ICON.
47333
- *
47334
- * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
47335
- * run on the addon cap path — three production failures in one day taught
47336
- * this repo that once. Absent is read as false by the degrade engine, which
47337
- * is the safe direction: an icon that is not rendered costs nothing, an icon
47338
- * assumed and dropped costs the operator's trust in the field.
47339
- */
47340
- actionIcons: boolean().optional(),
47341
- levels: array(TargetKindLevelSchema),
47342
- format: array(NotificationFormatSchema),
47343
- clickUrl: boolean(),
47344
- sound: boolean(),
47345
- ttl: boolean(),
47346
- bodyMaxLen: number().int().positive()
47347
- });
47348
- /**
47349
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
47350
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
47351
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
47352
- * the union is large and not meant for runtime validation here; the exported
47353
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
47354
- */
47355
- var ConfigSchemaPassthrough$2 = unknown();
47356
- var TargetKindSchema = object({
47357
- kind: string(),
47358
- label: string(),
47359
- icon: string(),
47360
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
47361
- addonId: string(),
47362
- /**
47363
- * URL of the kind's bundled BRAND icon, served by the providing addon over
47364
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
47365
- * when the addon bundles no icon for that kind — the client then falls back
47366
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
47367
- *
47368
- * Root-relative on purpose: it resolves against whatever origin serves a web
47369
- * client, and a native client joins it onto its own hub base.
47370
- *
47371
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
47372
- * field that survived only because the runtime cap-router forwards provider
47373
- * output verbatim — so every consumer had to re-declare it by hand to stop
47374
- * its own Zod parse from stripping it, and the whole arrangement would have
47375
- * broken silently the moment output validation was tightened anywhere.
47376
- */
47377
- iconUrl: string().optional(),
47378
- /**
47379
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
47380
- *
47381
- * The server knows this and therefore says it, because the client cannot
47382
- * safely guess: a React-Native client renders SVG and raster through two
47383
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
47384
- * not decode SVG on iOS/Android), so without this it silently fell back to a
47385
- * placeholder glyph for every vector icon while the web build looked fine.
47386
- *
47387
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
47388
- * not been updated — a client that cannot determine the type should prefer
47389
- * its raster path, which is the safe default for an unknown image.
47390
- */
47391
- iconMediaType: string().optional(),
47392
- configSchema: ConfigSchemaPassthrough$2,
47393
- /**
47394
- * The config keys that decide WHERE this kind's secrets are sent (server,
47395
- * base URL, instance, chat, recipient). A save that changes any of them
47396
- * while leaving a secret redacted is refused by the provider (D634), so an
47397
- * editor uses this to ask for the secret on exactly those edits instead of
47398
- * on every edit.
47399
- *
47400
- * Optional: absent from a provider that holds no secret (the Home Assistant
47401
- * kinds) and from a provider older than this field — an editor that finds
47402
- * it absent must assume ANY edit may need the secret.
47403
- */
47404
- destinationKeys: array(string()).optional(),
47405
- supportsDiscovery: boolean(),
47406
- caps: TargetKindCapsSchema
47407
- });
47408
- /**
47409
- * A persisted target. `config` holds secrets; providers REDACT secret fields
47410
- * (return a presence marker only) when serving `listTargets` — never
47411
- * round-trip a stored secret to the UI.
47412
- */
47413
- var TargetSchema = object({
47414
- id: string(),
47415
- name: string(),
47416
- kind: string(),
47417
- addonId: string(),
47418
- enabled: boolean(),
47419
- config: record(string(), unknown())
47420
- });
47421
- /** A discovery-surfaced candidate (config is partial + non-secret). */
47422
- var DiscoveredTargetSchema = object({
47423
- kind: string(),
47424
- suggestedName: string(),
47425
- config: record(string(), unknown())
47426
- });
47427
- /** The degrade engine's report — what was resolved / dropped / degraded. */
47428
- var RenderedAsSchema = object({
47429
- level: string(),
47430
- format: NotificationFormatSchema,
47431
- attachmentsSent: number().int().nonnegative(),
47432
- actionsSent: number().int().nonnegative(),
47433
- truncated: boolean(),
47434
- dropped: array(string())
47435
- });
47436
- var SendResultSchema = object({
47437
- success: boolean(),
47438
- error: string().optional(),
47439
- renderedAs: RenderedAsSchema.optional()
47440
- });
47441
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
47442
- var TestResultSchema = SendResultSchema;
47443
- /**
47444
- * Pre-save credential probe against a partial (unsaved) target config.
47445
- * `resolvedUrl` never includes secrets (token stripped from path).
47446
- */
47447
- var ProbeTargetConfigResultSchema = object({
47448
- ok: boolean(),
47449
- resolvedUrl: string(),
47450
- httpStatus: number().int().optional(),
47451
- stateInstance: string().optional(),
47452
- error: string().optional()
47453
- });
47454
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
47455
- kind: string(),
47456
- config: record(string(), unknown()).optional(),
47457
- /**
47458
- * When editing / duplicating, the blank password field never round-trips
47459
- * the stored token. Pass the source target id so the provider can restore
47460
- * secrets server-side before dialling (same merge as upsert).
47461
- */
47462
- targetId: string().optional()
47463
- }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
47464
- kind: string(),
47465
- config: record(string(), unknown()),
47466
- /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
47467
- targetId: string().optional()
47468
- }), ProbeTargetConfigResultSchema, {
47469
- kind: "mutation",
47470
- auth: "admin"
47471
- }), method(object({
47472
- targetId: string(),
47473
- notification: NotificationSchema
47474
- }), SendResultSchema, {
47475
- kind: "mutation",
47476
- auth: "admin"
47477
- }), method(object({
47478
- targetId: string(),
47479
- sample: NotificationSchema.optional()
47480
- }), TestResultSchema, {
47481
- kind: "mutation",
47482
- auth: "admin"
47483
- }), method(object({
47484
- target: TargetSchema,
47485
- /**
47486
- * Duplicate flow: create a NEW target id while copying secrets from an
47487
- * existing row. The UI never holds the stored token; blank / redacted
47488
- * password fields are restored from this source (D634 still applies if
47489
- * destinations diverge without a re-typed secret).
47490
- */
47491
- cloneSecretsFrom: string().optional()
47492
- }), TargetSchema, {
47493
- kind: "mutation",
47494
- auth: "admin"
47495
- }), method(object({ targetId: string() }), _void(), {
47496
- kind: "mutation",
47497
- auth: "admin"
47498
- }), method(object({
47499
- targetId: string(),
47500
- enabled: boolean()
47501
- }), _void(), {
47502
- kind: "mutation",
47503
- auth: "admin"
47504
- });
47505
- var COCO_80_LABELS = [
47506
- {
47507
- id: "person",
47508
- name: "Person"
47509
- },
47510
- {
47511
- id: "bicycle",
47512
- name: "Bicycle"
47513
- },
47514
- {
47515
- id: "car",
47516
- name: "Car"
47517
- },
47518
- {
47519
- id: "motorcycle",
47520
- name: "Motorcycle"
47521
- },
47522
- {
47523
- id: "airplane",
47524
- name: "Airplane"
47525
- },
47526
- {
47527
- id: "bus",
47528
- name: "Bus"
47529
- },
47530
- {
47531
- id: "train",
47532
- name: "Train"
47533
- },
47534
- {
47535
- id: "truck",
47536
- name: "Truck"
47537
- },
47538
- {
47539
- id: "boat",
47540
- name: "Boat"
47541
- },
47542
- {
47543
- id: "traffic light",
47544
- name: "Traffic Light"
47545
- },
47546
- {
47547
- id: "fire hydrant",
47548
- name: "Fire Hydrant"
47549
- },
47550
- {
47551
- id: "stop sign",
47552
- name: "Stop Sign"
47553
- },
47554
- {
47555
- id: "parking meter",
47556
- name: "Parking Meter"
47557
- },
47558
- {
47559
- id: "bench",
47560
- name: "Bench"
47561
- },
47562
- {
47563
- id: "bird",
47564
- name: "Bird"
47565
- },
47566
- {
47567
- id: "cat",
47568
- name: "Cat"
47569
- },
47570
- {
47571
- id: "dog",
47572
- name: "Dog"
47573
- },
47574
- {
47575
- id: "horse",
47576
- name: "Horse"
47577
- },
47578
- {
47579
- id: "sheep",
47580
- name: "Sheep"
47581
- },
47582
- {
47583
- id: "cow",
47584
- name: "Cow"
47585
- },
47586
- {
47587
- id: "elephant",
47588
- name: "Elephant"
47589
- },
47590
- {
47591
- id: "bear",
47592
- name: "Bear"
47593
- },
47594
- {
47595
- id: "zebra",
47596
- name: "Zebra"
47597
- },
47598
- {
47599
- id: "giraffe",
47600
- name: "Giraffe"
47601
- },
47602
- {
47603
- id: "backpack",
47604
- name: "Backpack"
47605
- },
47606
- {
47607
- id: "umbrella",
47608
- name: "Umbrella"
47609
- },
47610
- {
47611
- id: "handbag",
47612
- name: "Handbag"
47613
- },
47614
- {
47615
- id: "tie",
47616
- name: "Tie"
47617
- },
47618
- {
47619
- id: "suitcase",
47620
- name: "Suitcase"
47621
- },
47622
- {
47623
- id: "frisbee",
47624
- name: "Frisbee"
47625
- },
47626
- {
47627
- id: "skis",
47628
- name: "Skis"
47629
- },
47630
- {
47631
- id: "snowboard",
47632
- name: "Snowboard"
47633
- },
47634
- {
47635
- id: "sports ball",
47636
- name: "Sports Ball"
47637
- },
47638
- {
47639
- id: "kite",
47640
- name: "Kite"
47641
- },
47642
- {
47643
- id: "baseball bat",
47644
- name: "Baseball Bat"
47645
- },
47646
- {
47647
- id: "baseball glove",
47648
- name: "Baseball Glove"
47649
- },
47650
- {
47651
- id: "skateboard",
47652
- name: "Skateboard"
47653
- },
47654
- {
47655
- id: "surfboard",
47656
- name: "Surfboard"
47657
- },
47658
- {
47659
- id: "tennis racket",
47660
- name: "Tennis Racket"
47661
- },
47662
- {
47663
- id: "bottle",
47664
- name: "Bottle"
47665
- },
47666
- {
47667
- id: "wine glass",
47668
- name: "Wine Glass"
47669
- },
47670
- {
47671
- id: "cup",
47672
- name: "Cup"
47673
- },
47674
- {
47675
- id: "fork",
47676
- name: "Fork"
47677
- },
47678
- {
47679
- id: "knife",
47680
- name: "Knife"
47681
- },
47682
- {
47683
- id: "spoon",
47684
- name: "Spoon"
47685
- },
47686
- {
47687
- id: "bowl",
47688
- name: "Bowl"
47689
- },
47690
- {
47691
- id: "banana",
47692
- name: "Banana"
47693
- },
47694
- {
47695
- id: "apple",
47696
- name: "Apple"
47697
- },
47698
- {
47699
- id: "sandwich",
47700
- name: "Sandwich"
47701
- },
47702
- {
47703
- id: "orange",
47704
- name: "Orange"
47705
- },
47706
- {
47707
- id: "broccoli",
47708
- name: "Broccoli"
47709
- },
47710
- {
47711
- id: "carrot",
47712
- name: "Carrot"
47713
- },
47714
- {
47715
- id: "hot dog",
47716
- name: "Hot Dog"
47717
- },
47718
- {
47719
- id: "pizza",
47720
- name: "Pizza"
47721
- },
47722
- {
47723
- id: "donut",
47724
- name: "Donut"
47725
- },
47726
- {
47727
- id: "cake",
47728
- name: "Cake"
47729
- },
47730
- {
47731
- id: "chair",
47732
- name: "Chair"
47733
- },
47734
- {
47735
- id: "couch",
47736
- name: "Couch"
47737
- },
47738
- {
47739
- id: "potted plant",
47740
- name: "Potted Plant"
47741
- },
47742
- {
47743
- id: "bed",
47744
- name: "Bed"
47745
- },
47746
- {
47747
- id: "dining table",
47748
- name: "Dining Table"
47749
- },
47750
- {
47751
- id: "toilet",
47752
- name: "Toilet"
47753
- },
47754
- {
47755
- id: "tv",
47756
- name: "TV"
47757
- },
47758
- {
47759
- id: "laptop",
47760
- name: "Laptop"
47761
- },
47762
- {
47763
- id: "mouse",
47764
- name: "Mouse"
47765
- },
47766
- {
47767
- id: "remote",
47768
- name: "Remote"
47769
- },
47770
- {
47771
- id: "keyboard",
47772
- name: "Keyboard"
47773
- },
47774
- {
47775
- id: "cell phone",
47776
- name: "Cell Phone"
47777
- },
47778
- {
47779
- id: "microwave",
47780
- name: "Microwave"
47781
- },
47782
- {
47783
- id: "oven",
47784
- name: "Oven"
47785
- },
47786
- {
47787
- id: "toaster",
47788
- name: "Toaster"
47789
- },
47790
- {
47791
- id: "sink",
47792
- name: "Sink"
47793
- },
47794
- {
47795
- id: "refrigerator",
47796
- name: "Refrigerator"
47797
- },
47798
- {
47799
- id: "book",
47800
- name: "Book"
47801
- },
47802
- {
47803
- id: "clock",
47804
- name: "Clock"
47805
- },
47806
- {
47807
- id: "vase",
47808
- name: "Vase"
47809
- },
47810
- {
47811
- id: "scissors",
47812
- name: "Scissors"
47813
- },
47814
- {
47815
- id: "teddy bear",
47816
- name: "Teddy Bear"
47817
- },
47818
- {
47819
- id: "hair drier",
47820
- name: "Hair Drier"
47821
- },
47822
- {
47823
- id: "toothbrush",
47824
- name: "Toothbrush"
47825
- }
47826
- ];
47827
- new Set([
47828
- {
47829
- id: "person",
47830
- name: "Person"
47831
- },
47832
- {
47833
- id: "vehicle",
47834
- name: "Vehicle"
47835
- },
47836
- {
47837
- id: "animal",
47838
- name: "Animal"
47839
- },
47840
- {
47841
- id: "package",
47842
- name: "Package"
47843
- }
47844
- ].map((l) => l.id));
47845
- var COCO_TO_MACRO = {
47846
- mapping: {
47847
- person: "person",
47848
- bicycle: "vehicle",
47849
- car: "vehicle",
47850
- motorcycle: "vehicle",
47851
- airplane: "vehicle",
47852
- bus: "vehicle",
47853
- train: "vehicle",
47854
- truck: "vehicle",
47855
- boat: "vehicle",
47856
- bird: "animal",
47857
- cat: "animal",
47858
- dog: "animal",
47859
- horse: "animal",
47860
- sheep: "animal",
47861
- cow: "animal",
47862
- elephant: "animal",
47863
- bear: "animal",
47864
- zebra: "animal",
47865
- giraffe: "animal",
47866
- suitcase: "package",
47867
- backpack: "package",
47868
- handbag: "package"
47869
- },
47870
- preserveOriginal: false
47871
- };
47914
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
47915
+ * the generated cap-mount auto-`concatCollection`-fans them across every
47916
+ * registered provider (notifiers addon + HA addon) so one catalog is
47917
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
47918
+ * `addonId` the generated collection router extracts from the call input.
47919
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
47920
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
47921
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
47922
+ * base64 fallback needed.
47923
+ *
47924
+ * TODO (deferred, closed-set change — separate decision): add
47925
+ * `providerKind: 'notify'` so notification providers surface on the unified
47926
+ * admin "Integrations" page.
47927
+ */
47928
+ /**
47929
+ * Zentik-derived typed-media enum — the superset across every kind. Each
47930
+ * adapter picks what it supports and the degrade engine filters the rest.
47931
+ */
47932
+ var AttachmentMediaTypeSchema = _enum([
47933
+ "image",
47934
+ "video",
47935
+ "gif",
47936
+ "audio",
47937
+ "icon"
47938
+ ]);
47939
+ /**
47940
+ * A single attachment. Exactly one of `url` (remote source, most adapters
47941
+ * prefer this) or `bytes` (inline source; required for Pushover-style
47942
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
47943
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
47944
+ */
47945
+ var AttachmentSchema = object({
47946
+ mediaType: AttachmentMediaTypeSchema,
47947
+ url: string().optional(),
47948
+ bytes: _instanceof(Uint8Array).optional(),
47949
+ mime: string().optional(),
47950
+ name: string().optional()
47951
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
47952
+ var NotificationFormatSchema = _enum([
47953
+ "text",
47954
+ "markdown",
47955
+ "html"
47956
+ ]);
47957
+ /**
47958
+ * The CLOSED icon vocabulary an action button may use.
47959
+ *
47960
+ * A closed set, not a free string, and that is the whole point: an arbitrary
47961
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
47962
+ * notices — the same class of gap as a zone vocabulary nothing produced
47963
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
47964
+ * declares `actionIcons: false` and the degrade engine strips the field.
47965
+ *
47966
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
47967
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
47968
+ * differently.
47969
+ *
47970
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
47971
+ *
47972
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
47973
+ * the whole set onto its own renderer's vocabulary through a
47974
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
47975
+ * fallback, so adding a member here fails every adapter's build until someone
47976
+ * decides its glyph, which is the only place that decision can be made
47977
+ * honestly.
47978
+ *
47979
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
47980
+ * `disarm` straight through; iOS feeds that string to
47981
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
47982
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
47983
+ * mapping, and "the field is documented" is not "the value renders".
47984
+ *
47985
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
47986
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
47987
+ * so an addon that emits a token the running hub does not know does not lose an
47988
+ * icon — its whole `send` fails Zod validation and the notification never
47989
+ * arrives. Never emit a new token from an addon before the train carrying it.
47990
+ */
47991
+ var NotificationActionIconSchema = _enum([
47992
+ "acknowledge",
47993
+ "dismiss",
47994
+ "silence",
47995
+ "snooze",
47996
+ "view",
47997
+ "play",
47998
+ "open",
47999
+ "close",
48000
+ "lock",
48001
+ "unlock",
48002
+ "arm",
48003
+ "arm-home",
48004
+ "arm-away",
48005
+ "arm-night",
48006
+ "disarm",
48007
+ "light",
48008
+ "alert",
48009
+ "camera"
48010
+ ]);
48011
+ /** A single tap-through action button. */
48012
+ var NotificationActionSchema = object({
48013
+ id: string(),
48014
+ label: string(),
48015
+ url: string().optional(),
48016
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
48017
+ icon: NotificationActionIconSchema.optional(),
48018
+ /**
48019
+ * Renders in a warning style where the notifier supports it.
48020
+ *
48021
+ * A HINT, never a gate. The callback's authority is its token and nothing
48022
+ * else — see `notification-center/action-token.ts` for what that does and
48023
+ * does not buy.
48024
+ */
48025
+ destructive: boolean().optional(),
48026
+ /**
48027
+ * How the tap should REACH the url.
48028
+ *
48029
+ * `navigate` (absent, and every button authored before this field) opens it:
48030
+ * the phone leaves the notification and shows whatever the callback returns.
48031
+ * That is right for a button whose answer the operator wants to read.
48032
+ *
48033
+ * `background` fires it as a POST and stays put. It exists for the buttons
48034
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
48035
+ * an answer to the notification, and being thrown into a browser tab to
48036
+ * confirm it costs more attention than the notification did. A backend that
48037
+ * cannot do a background call renders it as an ordinary link (the adapters
48038
+ * fall back rather than dropping the button), so this is a preference, never
48039
+ * a requirement.
48040
+ */
48041
+ mode: _enum(["navigate", "background"]).optional()
48042
+ });
48043
+ /**
48044
+ * The canonical notification. `body` is the only hard field (Apprise model).
48045
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
48046
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
48047
+ * the adapter maps this ordinal onto its native level. `level?` is an
48048
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
48049
+ * `priority` for that one target.
48050
+ */
48051
+ var NotificationSchema = object({
48052
+ body: string(),
48053
+ title: string().optional(),
48054
+ format: NotificationFormatSchema.default("text"),
48055
+ priority: number().int().min(1).max(5).default(3),
48056
+ level: string().optional(),
48057
+ attachments: array(AttachmentSchema).optional(),
48058
+ clickUrl: string().optional(),
48059
+ actions: array(NotificationActionSchema).optional(),
48060
+ sound: string().optional(),
48061
+ ttl: number().optional(),
48062
+ tag: string().optional(),
48063
+ deviceId: number().optional(),
48064
+ eventId: string().optional(),
48065
+ metadata: record(string(), unknown()).optional()
48066
+ });
48067
+ /** One declared native severity/priority level for a kind. */
48068
+ var TargetKindLevelSchema = object({
48069
+ id: string(),
48070
+ label: string(),
48071
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
48072
+ ordinal: number().int().min(1).max(5).nullable(),
48073
+ flags: object({
48074
+ critical: boolean().optional(),
48075
+ silent: boolean().optional(),
48076
+ noPush: boolean().optional()
48077
+ }).optional(),
48078
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
48079
+ requires: array(string()).optional(),
48080
+ description: string().optional()
48081
+ });
48082
+ /** The full capability block consulted before dispatch. */
48083
+ var TargetKindCapsSchema = object({
48084
+ attachments: object({
48085
+ mediaTypes: array(AttachmentMediaTypeSchema),
48086
+ mode: _enum([
48087
+ "url",
48088
+ "bytes",
48089
+ "both"
48090
+ ]),
48091
+ max: number().int().nonnegative(),
48092
+ maxBytes: number().int().positive().optional()
48093
+ }),
48094
+ /** Max action buttons (0 = none). */
48095
+ actions: number().int().nonnegative(),
48096
+ /**
48097
+ * Whether this kind renders a per-action ICON.
48098
+ *
48099
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
48100
+ * run on the addon cap path — three production failures in one day taught
48101
+ * this repo that once. Absent is read as false by the degrade engine, which
48102
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
48103
+ * assumed and dropped costs the operator's trust in the field.
48104
+ */
48105
+ actionIcons: boolean().optional(),
48106
+ levels: array(TargetKindLevelSchema),
48107
+ format: array(NotificationFormatSchema),
48108
+ clickUrl: boolean(),
48109
+ sound: boolean(),
48110
+ ttl: boolean(),
48111
+ bodyMaxLen: number().int().positive()
48112
+ });
48113
+ /**
48114
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
48115
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
48116
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
48117
+ * the union is large and not meant for runtime validation here; the exported
48118
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
48119
+ */
48120
+ var ConfigSchemaPassthrough$2 = unknown();
48121
+ var TargetKindSchema = object({
48122
+ kind: string(),
48123
+ label: string(),
48124
+ icon: string(),
48125
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
48126
+ addonId: string(),
48127
+ /**
48128
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
48129
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
48130
+ * when the addon bundles no icon for that kind — the client then falls back
48131
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
48132
+ *
48133
+ * Root-relative on purpose: it resolves against whatever origin serves a web
48134
+ * client, and a native client joins it onto its own hub base.
48135
+ *
48136
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
48137
+ * field that survived only because the runtime cap-router forwards provider
48138
+ * output verbatim — so every consumer had to re-declare it by hand to stop
48139
+ * its own Zod parse from stripping it, and the whole arrangement would have
48140
+ * broken silently the moment output validation was tightened anywhere.
48141
+ */
48142
+ iconUrl: string().optional(),
48143
+ /**
48144
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
48145
+ *
48146
+ * The server knows this and therefore says it, because the client cannot
48147
+ * safely guess: a React-Native client renders SVG and raster through two
48148
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
48149
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
48150
+ * placeholder glyph for every vector icon while the web build looked fine.
48151
+ *
48152
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
48153
+ * not been updated — a client that cannot determine the type should prefer
48154
+ * its raster path, which is the safe default for an unknown image.
48155
+ */
48156
+ iconMediaType: string().optional(),
48157
+ configSchema: ConfigSchemaPassthrough$2,
48158
+ /**
48159
+ * The config keys that decide WHERE this kind's secrets are sent (server,
48160
+ * base URL, instance, chat, recipient). A save that changes any of them
48161
+ * while leaving a secret redacted is refused by the provider (D634), so an
48162
+ * editor uses this to ask for the secret on exactly those edits instead of
48163
+ * on every edit.
48164
+ *
48165
+ * Optional: absent from a provider that holds no secret (the Home Assistant
48166
+ * kinds) and from a provider older than this field — an editor that finds
48167
+ * it absent must assume ANY edit may need the secret.
48168
+ */
48169
+ destinationKeys: array(string()).optional(),
48170
+ supportsDiscovery: boolean(),
48171
+ caps: TargetKindCapsSchema
48172
+ });
48173
+ /**
48174
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
48175
+ * (return a presence marker only) when serving `listTargets` — never
48176
+ * round-trip a stored secret to the UI.
48177
+ */
48178
+ var TargetSchema = object({
48179
+ id: string(),
48180
+ name: string(),
48181
+ kind: string(),
48182
+ addonId: string(),
48183
+ enabled: boolean(),
48184
+ config: record(string(), unknown())
48185
+ });
48186
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
48187
+ var DiscoveredTargetSchema = object({
48188
+ kind: string(),
48189
+ suggestedName: string(),
48190
+ config: record(string(), unknown())
48191
+ });
48192
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
48193
+ var RenderedAsSchema = object({
48194
+ level: string(),
48195
+ format: NotificationFormatSchema,
48196
+ attachmentsSent: number().int().nonnegative(),
48197
+ actionsSent: number().int().nonnegative(),
48198
+ truncated: boolean(),
48199
+ dropped: array(string())
48200
+ });
48201
+ var SendResultSchema = object({
48202
+ success: boolean(),
48203
+ error: string().optional(),
48204
+ renderedAs: RenderedAsSchema.optional()
48205
+ });
48206
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
48207
+ var TestResultSchema = SendResultSchema;
48208
+ /**
48209
+ * Pre-save credential probe against a partial (unsaved) target config.
48210
+ * `resolvedUrl` never includes secrets (token stripped from path).
48211
+ */
48212
+ var ProbeTargetConfigResultSchema = object({
48213
+ ok: boolean(),
48214
+ resolvedUrl: string(),
48215
+ httpStatus: number().int().optional(),
48216
+ stateInstance: string().optional(),
48217
+ error: string().optional()
48218
+ });
48219
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
48220
+ kind: string(),
48221
+ config: record(string(), unknown()).optional(),
48222
+ /**
48223
+ * When editing / duplicating, the blank password field never round-trips
48224
+ * the stored token. Pass the source target id so the provider can restore
48225
+ * secrets server-side before dialling (same merge as upsert).
48226
+ */
48227
+ targetId: string().optional()
48228
+ }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
48229
+ kind: string(),
48230
+ config: record(string(), unknown()),
48231
+ /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
48232
+ targetId: string().optional()
48233
+ }), ProbeTargetConfigResultSchema, {
48234
+ kind: "mutation",
48235
+ auth: "admin"
48236
+ }), method(object({
48237
+ targetId: string(),
48238
+ notification: NotificationSchema
48239
+ }), SendResultSchema, {
48240
+ kind: "mutation",
48241
+ auth: "admin"
48242
+ }), method(object({
48243
+ targetId: string(),
48244
+ sample: NotificationSchema.optional()
48245
+ }), TestResultSchema, {
48246
+ kind: "mutation",
48247
+ auth: "admin"
48248
+ }), method(object({
48249
+ target: TargetSchema,
48250
+ /**
48251
+ * Duplicate flow: create a NEW target id while copying secrets from an
48252
+ * existing row. The UI never holds the stored token; blank / redacted
48253
+ * password fields are restored from this source (D634 still applies if
48254
+ * destinations diverge without a re-typed secret).
48255
+ */
48256
+ cloneSecretsFrom: string().optional()
48257
+ }), TargetSchema, {
48258
+ kind: "mutation",
48259
+ auth: "admin"
48260
+ }), method(object({ targetId: string() }), _void(), {
48261
+ kind: "mutation",
48262
+ auth: "admin"
48263
+ }), method(object({
48264
+ targetId: string(),
48265
+ enabled: boolean()
48266
+ }), _void(), {
48267
+ kind: "mutation",
48268
+ auth: "admin"
48269
+ });
47872
48270
  var AUDIO_MACRO_LABELS = [
47873
48271
  {
47874
48272
  id: "speech",
@@ -61149,9 +61547,9 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
61149
61547
  }), LogStreamEntrySchema, { kind: "subscription" });
61150
61548
  object({
61151
61549
  /** Carbon dioxide concentration in ppm. */
61152
- co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
61550
+ co2: number().min(0).optional().meta(readingMeta("concentration-volume")),
61153
61551
  /** Total volatile organic compounds in ppb. */
61154
- vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
61552
+ voc: number().min(0).optional().meta(readingMeta("concentration-ppb")),
61155
61553
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
61156
61554
  pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
61157
61555
  /** Particulate matter ≤ 10 μm in µg/m³. */
@@ -61164,7 +61562,7 @@ object({
61164
61562
  DeviceType.Sensor;
61165
61563
  object({
61166
61564
  /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
61167
- lux: number().min(0).nullable().meta(readingMeta("illuminance", {
61565
+ illuminance: number().min(0).nullable().meta(readingMeta("illuminance", {
61168
61566
  precision: 0,
61169
61567
  label: "Illuminance"
61170
61568
  })),
@@ -61261,6 +61659,84 @@ DeviceType.Camera, method(object({ deviceId: number() }), AudioMetricsSnapshotSc
61261
61659
  * would produce more than `maxPoints` samples. */
61262
61660
  sampleEveryMs: number().int().positive().optional()
61263
61661
  }), AudioMetricsHistorySchema);
61662
+ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
61663
+ actionId: string().min(1),
61664
+ variables: record(string(), ActionVariableValueSchema).optional()
61665
+ }), _void(), {
61666
+ kind: "mutation",
61667
+ auth: "admin"
61668
+ }), method(object({ actionId: string().min(1) }), _void(), {
61669
+ kind: "mutation",
61670
+ auth: "admin"
61671
+ });
61672
+ /**
61673
+ * actions — the ONE surface for runnable actions, for the UI and for
61674
+ * automations. Provided by the core-blocks builtin on the hub.
61675
+ *
61676
+ * An action is a LIST ENTRY, not a device (D883): either internal (cap and wait
61677
+ * steps, kept in the hub's `actions` record kind) or declared by an addon over
61678
+ * `action-source`. `list` asks every `action-source` provider LIVE and overlays
61679
+ * the run status from the hub's `runs` ledger; there is no catalog here to
61680
+ * drift (D3).
61681
+ *
61682
+ * **Permissions follow the camera, as linked devices do.** An action is linked
61683
+ * to cameras by the operator (`setLinks`, D356: nothing links itself). A
61684
+ * non-admin may `get` / `run` an action only through a camera they may see that
61685
+ * links it: they name the camera (`deviceId`, a top-level field so the scope
61686
+ * gate sees it) and the provider refuses an action that camera does not link.
61687
+ * An admin, and an in-process caller (an automation block), may run any action.
61688
+ * Every refusal is typed (`[not-linked] …`) and logged.
61689
+ *
61690
+ * A second `run` of an action that is already running is REFUSED — traced and
61691
+ * logged — never queued.
61692
+ */
61693
+ var ActionIdInput = object({ actionId: string().min(1) });
61694
+ var ActionLinksSchema = object({
61695
+ deviceId: number().int().nonnegative(),
61696
+ actionIds: array(string().min(1)).max(100)
61697
+ });
61698
+ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
61699
+ actionId: string().min(1),
61700
+ deviceId: number().int().nonnegative().optional()
61701
+ }), object({ action: ActionEntrySchema.nullable() }), {
61702
+ role: "viewer",
61703
+ auth: "protected",
61704
+ kind: "query",
61705
+ caller: "required"
61706
+ }), method(object({
61707
+ actionId: string().min(1),
61708
+ variables: record(string(), ActionVariableValueSchema).optional(),
61709
+ wait: boolean().optional(),
61710
+ origin: ActionRunOriginSchema.optional(),
61711
+ deviceId: number().int().nonnegative().optional()
61712
+ }), ActionRunResultSchema, {
61713
+ role: "viewer",
61714
+ auth: "protected",
61715
+ kind: "mutation",
61716
+ caller: "required"
61717
+ }), method(ActionIdInput, _void(), {
61718
+ kind: "mutation",
61719
+ auth: "admin"
61720
+ }), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({ action: ActionEntrySchema }), {
61721
+ kind: "mutation",
61722
+ auth: "admin"
61723
+ }), method(object({
61724
+ actionId: string().min(1),
61725
+ action: ActionInputSchema.partial()
61726
+ }), object({ action: ActionEntrySchema }), {
61727
+ kind: "mutation",
61728
+ auth: "admin"
61729
+ }), method(ActionIdInput, _void(), {
61730
+ kind: "mutation",
61731
+ auth: "admin"
61732
+ }), method(object({ deviceId: number().int().nonnegative() }), array(ActionEntrySchema), {
61733
+ role: "viewer",
61734
+ auth: "protected",
61735
+ kind: "query"
61736
+ }), method(object({ deviceId: number().int().nonnegative() }), ActionLinksSchema, { auth: "admin" }), method(ActionLinksSchema, ActionLinksSchema, {
61737
+ kind: "mutation",
61738
+ auth: "admin"
61739
+ });
61264
61740
  object({
61265
61741
  /** Whether the automation is currently enabled. Disabled automations
61266
61742
  * ignore their trigger block — manual `trigger` still works. */
@@ -61314,7 +61790,7 @@ var BatteryStatusSchema = object({
61314
61790
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
61315
61791
  * declines to band a non-finite reading, which is the correct shape.
61316
61792
  */
61317
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
61793
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
61318
61794
  /**
61319
61795
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
61320
61796
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -61324,7 +61800,7 @@ var BatteryStatusSchema = object({
61324
61800
  * `'unknown'` is the absence of an answer: the provider has not read the
61325
61801
  * power fields yet, or the firmware reported neither. Before it existed,
61326
61802
  * two unreadable fields produced `'none'`, and a camera nobody had reached
61327
- * was drawn "on battery alone" — the same D315 defect `percentage` closed
61803
+ * was drawn "on battery alone" — the same D315 defect `level` closed
61328
61804
  * by going nullable. Consumers SKIP it (no charger state published, no
61329
61805
  * glyph, no HomeKit ChargingState) rather than coerce it either way.
61330
61806
  */
@@ -61348,7 +61824,7 @@ var BatteryStatusSchema = object({
61348
61824
  * that only report charge. A second reading on this cap, not a second
61349
61825
  * `numeric-sensor` on the same device.
61350
61826
  */
61351
- volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
61827
+ voltage: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
61352
61828
  /**
61353
61829
  * Ms epoch of the last time the device PROVED it was reachable — a
61354
61830
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -61382,7 +61858,7 @@ var BatteryStatusSchema = object({
61382
61858
  /**
61383
61859
  * True when the source is a BINARY low-battery indicator (HA
61384
61860
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
61385
- * charge level — `percentage` is then a coarse stand-in (100 = normal,
61861
+ * charge level — `level` is then a coarse stand-in (100 = normal,
61386
61862
  * sub-threshold = low). UI MUST render "Normal"/"Low" instead of a
61387
61863
  * misleading exact percentage. Absent/false → genuine 0–100 % reading.
61388
61864
  */
@@ -61395,21 +61871,22 @@ var BATTERY_DEVICE_TYPES = [
61395
61871
  DeviceType.Switch,
61396
61872
  DeviceType.PetFeeder,
61397
61873
  DeviceType.Container,
61398
- DeviceType.Thermostat
61874
+ DeviceType.Thermostat,
61875
+ DeviceType.EventEmitter
61399
61876
  ];
61400
61877
  /** Generic Customize target for the percentage reading (D936). */
61401
61878
  var batteryCustomize = {
61402
61879
  version: 1,
61403
61880
  targets: [{
61404
- id: "percentage",
61881
+ id: "level",
61405
61882
  cardinality: "scalar",
61406
61883
  vocabulary: {
61407
61884
  cap: "battery",
61408
- stateField: "percentage"
61885
+ stateField: "level"
61409
61886
  },
61410
61887
  deviceTypes: BATTERY_DEVICE_TYPES,
61411
61888
  apply: "add",
61412
- path: "percentage",
61889
+ path: "level",
61413
61890
  sources: [{
61414
61891
  kind: "field",
61415
61892
  valueKinds: ["number", "string"],
@@ -61503,7 +61980,7 @@ onStatusChanged: { data: object({
61503
61980
  schema: BatteryStatusSchema,
61504
61981
  kind: "push",
61505
61982
  empty: {
61506
- percentage: null,
61983
+ level: null,
61507
61984
  charging: "unknown",
61508
61985
  sleeping: false,
61509
61986
  lastUpdated: 0
@@ -61530,11 +62007,11 @@ onStatusChanged: { data: object({
61530
62007
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
61531
62008
  */
61532
62009
  history: { fields: {
61533
- percentage: {
62010
+ level: {
61534
62011
  kind: "gauge",
61535
62012
  unit: "%"
61536
62013
  },
61537
- volts: {
62014
+ voltage: {
61538
62015
  kind: "gauge",
61539
62016
  unit: "V"
61540
62017
  }
@@ -61552,19 +62029,19 @@ object({
61552
62029
  DeviceType.Sensor;
61553
62030
  object({
61554
62031
  /** Current level as 0..100 inclusive. Firmware-reported. */
61555
- percentage: number().min(0).max(100).meta(readingMeta("percentage")),
62032
+ level: number().min(0).max(100).meta(readingMeta("percentage")),
61556
62033
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
61557
62034
  lastChangedAt: number()
61558
62035
  });
61559
62036
  DeviceType.Light, method(object({
61560
62037
  deviceId: number().int().nonnegative(),
61561
- percentage: number().min(0).max(100)
62038
+ level: number().min(0).max(100)
61562
62039
  }), _void(), {
61563
62040
  kind: "mutation",
61564
62041
  auth: "protected"
61565
62042
  }), object({
61566
62043
  deviceId: number(),
61567
- percentage: number().min(0).max(100),
62044
+ level: number().min(0).max(100),
61568
62045
  lastChangedAt: number()
61569
62046
  });
61570
62047
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
@@ -62065,11 +62542,11 @@ var ColorInputSchema = discriminatedUnion("mode", [
62065
62542
  }),
62066
62543
  object({
62067
62544
  mode: literal("mired"),
62068
- mireds: number().int().min(50).max(1e3)
62545
+ colorTemperature: number().int().min(50).max(1e3)
62069
62546
  })
62070
62547
  ]);
62071
62548
  object({
62072
- /** Active color mode — which of `rgb` / `hsv` / `mireds` reflects
62549
+ /** Active color mode — which of `rgb` / `hsv` / `colorTemperature` reflects
62073
62550
  * the bulb's current state. */
62074
62551
  mode: _enum([
62075
62552
  "rgb",
@@ -62081,7 +62558,7 @@ object({
62081
62558
  /** Populated when `mode === 'hsv'`. */
62082
62559
  hsv: HsvTripletSchema.optional(),
62083
62560
  /** Populated when `mode === 'mired'`. */
62084
- mireds: number().int().optional().meta(readingMeta("colour-temperature")),
62561
+ colorTemperature: number().int().optional().meta(readingMeta("colour-temperature")),
62085
62562
  /** Ms epoch of the last operator-driven change. */
62086
62563
  lastChangedAt: number()
62087
62564
  });
@@ -62100,7 +62577,7 @@ DeviceType.Light, method(object({
62100
62577
  ]),
62101
62578
  rgb: RgbTripletSchema.optional(),
62102
62579
  hsv: HsvTripletSchema.optional(),
62103
- mireds: number().int().optional(),
62580
+ colorTemperature: number().int().optional(),
62104
62581
  lastChangedAt: number()
62105
62582
  });
62106
62583
  var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
@@ -62526,11 +63003,11 @@ object({
62526
63003
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
62527
63004
  availablePrograms: array(string()),
62528
63005
  /** Seconds remaining on the active cycle, or null when not running / not known. */
62529
- remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
63006
+ remaining: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62530
63007
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
62531
- delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
63008
+ delayedStart: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62532
63009
  /** Cycle completion 0..100, or null when not applicable. */
62533
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63010
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62534
63011
  /** True when the door is reported open; null when the door state is not known. */
62535
63012
  doorOpen: boolean().nullable(),
62536
63013
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -63148,7 +63625,7 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
63148
63625
  object({
63149
63626
  /** Active speed as 0..100 inclusive. Null when the device has no
63150
63627
  * speed surface (single-speed fan). */
63151
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63628
+ speed: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63152
63629
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
63153
63630
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
63154
63631
  percentageStep: number().positive().optional().meta(readingMeta("percentage")),
@@ -63168,7 +63645,7 @@ object({
63168
63645
  });
63169
63646
  DeviceType.Fan, method(object({
63170
63647
  deviceId: number().int().nonnegative(),
63171
- percentage: number().min(0).max(100)
63648
+ speed: number().min(0).max(100)
63172
63649
  }), _void(), {
63173
63650
  kind: "mutation",
63174
63651
  auth: "protected"
@@ -63351,7 +63828,7 @@ DeviceType.Humidifier, method(object({
63351
63828
  });
63352
63829
  object({
63353
63830
  /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
63354
- percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63831
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63355
63832
  precision: 0,
63356
63833
  label: "Humidity"
63357
63834
  })),
@@ -63794,7 +64271,7 @@ object({
63794
64271
  batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63795
64272
  /** 0..100 mowing-completion percentage of the current task, or null when no
63796
64273
  * task is active / progress is unavailable. */
63797
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64274
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63798
64275
  /** Current device/event code (dynamic — mostly status, sometimes an error),
63799
64276
  * or null when unknown. */
63800
64277
  currentCode: number().nullable().meta(readingMeta("none")),
@@ -64284,10 +64761,10 @@ object({
64284
64761
  currentMedia: MediaInfoSchema.nullable(),
64285
64762
  /** Current playback position in ms. Null when not seekable or
64286
64763
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
64287
- positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64764
+ position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64288
64765
  /** Total duration of the current media in ms. Null when unknown
64289
64766
  * (live stream) or nothing is playing. */
64290
- durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64767
+ duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64291
64768
  /** Shuffle toggle. Null when no shuffle surface.
64292
64769
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
64293
64770
  shuffle: boolean().nullable(),
@@ -64314,7 +64791,7 @@ DeviceType.MediaPlayer, method(object({ deviceId: number().int().nonnegative() }
64314
64791
  auth: "protected"
64315
64792
  }), method(object({
64316
64793
  deviceId: number().int().nonnegative(),
64317
- positionMs: number().int().nonnegative()
64794
+ position: number().int().nonnegative()
64318
64795
  }), _void(), {
64319
64796
  kind: "mutation",
64320
64797
  auth: "protected"
@@ -64987,9 +65464,9 @@ var NetworkLinkStatusSchema = object({
64987
65464
  * one whose reading has not landed must not be drawn at 0 %. Consumers
64988
65465
  * SKIP a null rather than coerce it.
64989
65466
  */
64990
- signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65467
+ signal: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64991
65468
  /** Raw received signal strength in dBm, when the firmware reports one. */
64992
- rssiDbm: number().optional().meta(readingMeta("signal-level")),
65469
+ rssi: number().optional().meta(readingMeta("signal-level")),
64993
65470
  /** Network name of a wireless link, when the firmware reports it. */
64994
65471
  ssid: string().optional(),
64995
65472
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
@@ -65650,10 +66127,20 @@ var PetFeederDeviceStatusSchema = _enum([
65650
66127
  ]);
65651
66128
  var gramsPortion = number().int().min(4).max(200);
65652
66129
  object({
65653
- /** Food currently in the bowl (grams). Null when the device has not
65654
- * reported a reading yet. On dual-hopper models this is the combined
65655
- * bowl reading; per-hopper levels live in `food1`/`food2`. */
66130
+ /** Food in the TANK (hopper), as the feeder's own sensor reports it
66131
+ * (`state.food`). A coarse level code, not grams — `0` is empty. The
66132
+ * amount in the bowl is `bowlFood`. On dual-hopper models this is the
66133
+ * combined reading; per-hopper levels live in `food1`/`food2`. Null when
66134
+ * the device has not reported yet. */
65656
66135
  foodLevel: number().nullable().meta(readingMeta("none")),
66136
+ /** Food in the BOWL as the feeder reports it (`state.bowl`), in the
66137
+ * feeder's own units. Null when the model reports none. */
66138
+ bowlFood: number().nullable().meta(readingMeta("none")),
66139
+ /** True while the pet is eating (`state.eating`). */
66140
+ eating: boolean(),
66141
+ /** Whether `markFoodReplenished` does anything on this model. When false
66142
+ * the client must not offer it. */
66143
+ supportsMarkRefilled: boolean(),
65657
66144
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
65658
66145
  * single-hopper models. */
65659
66146
  food1: number().nullable().meta(readingMeta("none")),
@@ -65661,8 +66148,8 @@ object({
65661
66148
  * single-hopper models. */
65662
66149
  food2: number().nullable().meta(readingMeta("none")),
65663
66150
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
65664
- * (`device_class: problem`, on = low). True when the bowl is empty /
65665
- * below the feeder's low threshold. */
66151
+ * (`device_class: problem`, on = low). True when the tank reports empty,
66152
+ * or, on a dual-hopper model, when EITHER hopper is empty. */
65666
66153
  lowFood: boolean(),
65667
66154
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
65668
66155
  * device has no battery reading. */
@@ -66129,22 +66616,38 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
66129
66616
  auth: "admin"
66130
66617
  });
66131
66618
  object({
66132
- /** Instantaneous power draw in watts. */
66133
- watts: number().optional().meta(readingMeta("power", { label: "Power" })),
66619
+ /** Instantaneous power draw (watts). */
66620
+ power: number().optional().meta(readingMeta("power", { label: "Power" })),
66134
66621
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
66135
- kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
66136
- /** Voltage in volts. */
66137
- volts: number().optional().meta(readingMeta("voltage")),
66622
+ energy: number().optional().meta(readingMeta("energy", { label: "Energy" })),
66623
+ /** Voltage (volts). */
66624
+ voltage: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
66138
66625
  /** Current in amperes. */
66139
- amps: number().optional().meta(readingMeta("current")),
66626
+ current: number().optional().meta(readingMeta("current", { label: "Current" })),
66140
66627
  /** Apparent power in volt-amperes. */
66141
- va: number().optional().meta(readingMeta("apparent-power")),
66628
+ apparentPower: number().optional().meta(readingMeta("apparent-power")),
66142
66629
  /** Power factor (0..1) if reported. */
66143
66630
  powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
66144
66631
  /** Ms epoch when the slice was last updated. */
66145
66632
  lastFetchedAt: number()
66146
66633
  });
66147
- DeviceType.Sensor, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
66634
+ [
66635
+ DeviceType.Sensor,
66636
+ DeviceType.Switch,
66637
+ DeviceType.Light,
66638
+ DeviceType.Fan,
66639
+ DeviceType.Climate,
66640
+ DeviceType.Thermostat,
66641
+ DeviceType.Humidifier,
66642
+ DeviceType.WaterHeater,
66643
+ DeviceType.MediaPlayer,
66644
+ DeviceType.Washer,
66645
+ DeviceType.Dryer,
66646
+ DeviceType.Dishwasher,
66647
+ DeviceType.Vacuum,
66648
+ DeviceType.LawnMower,
66649
+ DeviceType.PetFeeder
66650
+ ].filter((type) => type !== DeviceType.Sensor);
66148
66651
  /**
66149
66652
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
66150
66653
  * on `DeviceType.Presence`. Read-only — no setters: presence is
@@ -66177,16 +66680,13 @@ object({
66177
66680
  location: string().nullable(),
66178
66681
  /** GPS coordinates when available. Null otherwise. */
66179
66682
  gps: GpsLocationSchema.nullable(),
66180
- /** Optional battery level of the tracking device (0..100). Null
66181
- * when not reported. */
66182
- batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
66183
66683
  /** Ms epoch when the slice was last updated. */
66184
66684
  lastChangedAt: number()
66185
66685
  });
66186
66686
  DeviceType.Presence;
66187
66687
  object({
66188
66688
  /** Current pressure in hPa. `null` when the sample was refused (D393). */
66189
- hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
66689
+ pressure: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
66190
66690
  /** Ms epoch when the slice was last updated. */
66191
66691
  lastFetchedAt: number()
66192
66692
  });
@@ -69713,7 +70213,7 @@ object({
69713
70213
  DeviceType.Sensor;
69714
70214
  object({
69715
70215
  /** Current temperature in Celsius. `null` when the sample was refused (D393). */
69716
- celsius: number().nullable().meta(readingMeta("temperature", {
70216
+ temperature: number().nullable().meta(readingMeta("temperature", {
69717
70217
  precision: 1,
69718
70218
  label: "Temperature"
69719
70219
  })),
@@ -69977,7 +70477,7 @@ object({
69977
70477
  /** Speed tokens the hardware accepts — drives the UI selector. */
69978
70478
  availableFanSpeeds: array(string()),
69979
70479
  /** 0..100 cleaning-completion percentage of the current task, or null. */
69980
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
70480
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
69981
70481
  /** Current error code (0 / null = no error). */
69982
70482
  errorCode: number().nullable().meta(readingMeta("none")),
69983
70483
  /** Human label for {@link errorCode}, or null when none / undecodable. */
@@ -70888,7 +71388,7 @@ function dominantClipLabels(events) {
70888
71388
  }
70889
71389
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
70890
71390
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
70891
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
71391
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
70892
71392
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
70893
71393
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
70894
71394
  new Set(CAP_NAMES);
@@ -70976,6 +71476,104 @@ Object.freeze({
70976
71476
  access: "create",
70977
71477
  role: "admin"
70978
71478
  },
71479
+ "actions.create": {
71480
+ capName: "actions",
71481
+ capScope: "system",
71482
+ addonId: null,
71483
+ access: "create",
71484
+ role: "admin"
71485
+ },
71486
+ "actions.delete": {
71487
+ capName: "actions",
71488
+ capScope: "system",
71489
+ addonId: null,
71490
+ access: "delete",
71491
+ role: "admin"
71492
+ },
71493
+ "actions.get": {
71494
+ capName: "actions",
71495
+ capScope: "system",
71496
+ addonId: null,
71497
+ access: "view",
71498
+ role: "viewer"
71499
+ },
71500
+ "actions.getLinks": {
71501
+ capName: "actions",
71502
+ capScope: "system",
71503
+ addonId: null,
71504
+ access: "view",
71505
+ role: "admin"
71506
+ },
71507
+ "actions.getTraces": {
71508
+ capName: "actions",
71509
+ capScope: "system",
71510
+ addonId: null,
71511
+ access: "view",
71512
+ role: "admin"
71513
+ },
71514
+ "actions.list": {
71515
+ capName: "actions",
71516
+ capScope: "system",
71517
+ addonId: null,
71518
+ access: "view",
71519
+ role: "admin"
71520
+ },
71521
+ "actions.listForCamera": {
71522
+ capName: "actions",
71523
+ capScope: "system",
71524
+ addonId: null,
71525
+ access: "view",
71526
+ role: "viewer"
71527
+ },
71528
+ "actions.run": {
71529
+ capName: "actions",
71530
+ capScope: "system",
71531
+ addonId: null,
71532
+ access: "create",
71533
+ role: "viewer"
71534
+ },
71535
+ "actions.setLinks": {
71536
+ capName: "actions",
71537
+ capScope: "system",
71538
+ addonId: null,
71539
+ access: "create",
71540
+ role: "admin"
71541
+ },
71542
+ "actions.stop": {
71543
+ capName: "actions",
71544
+ capScope: "system",
71545
+ addonId: null,
71546
+ access: "create",
71547
+ role: "admin"
71548
+ },
71549
+ "actions.update": {
71550
+ capName: "actions",
71551
+ capScope: "system",
71552
+ addonId: null,
71553
+ access: "create",
71554
+ role: "admin"
71555
+ },
71556
+ "actionSource.listActions": {
71557
+ capName: "action-source",
71558
+ capScope: "system",
71559
+ addonId: null,
71560
+ access: "view",
71561
+ role: "admin"
71562
+ },
71563
+ "actionSource.runAction": {
71564
+ capName: "action-source",
71565
+ capScope: "system",
71566
+ addonId: null,
71567
+ access: "create",
71568
+ role: "admin"
71569
+ },
71570
+ "actionSource.stopAction": {
71571
+ capName: "action-source",
71572
+ capScope: "system",
71573
+ addonId: null,
71574
+ access: "create",
71575
+ role: "admin"
71576
+ },
70979
71577
  "addonPages.listPages": {
70980
71578
  capName: "addon-pages",
70981
71579
  capScope: "system",
@@ -72257,6 +72855,13 @@ Object.freeze({
72257
72855
  access: "view",
72258
72856
  role: "admin"
72259
72857
  },
72858
+ "coreBlocks.getAutomationRunState": {
72859
+ capName: "core-blocks",
72860
+ capScope: "system",
72861
+ addonId: null,
72862
+ access: "view",
72863
+ role: "admin"
72864
+ },
72260
72865
  "coreBlocks.getBlockSettings": {
72261
72866
  capName: "core-blocks",
72262
72867
  capScope: "system",
@@ -72278,6 +72883,13 @@ Object.freeze({
72278
72883
  access: "view",
72279
72884
  role: "admin"
72280
72885
  },
72886
+ "coreBlocks.getTraces": {
72887
+ capName: "core-blocks",
72888
+ capScope: "system",
72889
+ addonId: null,
72890
+ access: "view",
72891
+ role: "admin"
72892
+ },
72281
72893
  "coreBlocks.getTypeDefs": {
72282
72894
  capName: "core-blocks",
72283
72895
  capScope: "system",
@@ -72313,6 +72925,13 @@ Object.freeze({
72313
72925
  access: "create",
72314
72926
  role: "admin"
72315
72927
  },
72928
+ "coreBlocks.reportAutomationRun": {
72929
+ capName: "core-blocks",
72930
+ capScope: "system",
72931
+ addonId: null,
72932
+ access: "create",
72933
+ role: "admin"
72934
+ },
72316
72935
  "coreBlocks.restart": {
72317
72936
  capName: "core-blocks",
72318
72937
  capScope: "system",
@@ -80886,7 +81505,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80886
81505
  label: t("Full brightness", "Luminosità massima"),
80887
81506
  phrase: t("Set {device} to full brightness", "Porta {device} alla massima luminosità"),
80888
81507
  icon: "sun",
80889
- args: { percentage: 100 }
81508
+ args: { level: 100 }
80890
81509
  },
80891
81510
  {
80892
81511
  id: "brightness.half",
@@ -80894,7 +81513,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80894
81513
  label: t("Half brightness", "Luminosità a metà"),
80895
81514
  phrase: t("Dim {device} to half", "Abbassa {device} a metà luminosità"),
80896
81515
  icon: "sun-dim",
80897
- args: { percentage: 50 }
81516
+ args: { level: 50 }
80898
81517
  },
80899
81518
  {
80900
81519
  id: "brightness.off",
@@ -80902,16 +81521,16 @@ var ACTUATOR_CAP_VOCABULARY = {
80902
81521
  label: t("Brightness off", "Luminosità a zero"),
80903
81522
  phrase: t("Dim {device} to zero", "Abbassa {device} a zero"),
80904
81523
  icon: "lightbulb-off",
80905
- args: { percentage: 0 }
81524
+ args: { level: 0 }
80906
81525
  },
80907
81526
  {
80908
81527
  id: "brightness.set",
80909
81528
  method: "setBrightness",
80910
81529
  label: t("Set brightness", "Imposta la luminosità"),
80911
- phrase: t("Set the brightness of {device} to {percentage}", "Imposta la luminosità di {device} a {percentage}"),
81530
+ phrase: t("Set the brightness of {device} to {level}", "Imposta la luminosità di {device} a {level}"),
80912
81531
  icon: "sun-medium",
80913
81532
  openArgs: [{
80914
- key: "percentage",
81533
+ key: "level",
80915
81534
  label: PERCENT,
80916
81535
  offer: {
80917
81536
  kind: "number",
@@ -80925,13 +81544,13 @@ var ACTUATOR_CAP_VOCABULARY = {
80925
81544
  ],
80926
81545
  state: [{
80927
81546
  kind: "numeric",
80928
- field: "percentage",
81547
+ field: "level",
80929
81548
  quantity: t("the brightness", "la luminosità"),
80930
81549
  label: t("Brightness", "Luminosità"),
80931
81550
  unit: "%",
80932
81551
  icon: "sun"
80933
81552
  }],
80934
- events: { onBrightnessChanged: { notOperatorFacing: "the `percentage` state trigger reads the same change" } }
81553
+ events: { onBrightnessChanged: { notOperatorFacing: "the `level` state trigger reads the same change" } }
80935
81554
  },
80936
81555
  color: {
80937
81556
  scope: "device",
@@ -80945,7 +81564,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80945
81564
  icon: "sun",
80946
81565
  args: { color: {
80947
81566
  mode: "mired",
80948
- mireds: 370
81567
+ colorTemperature: 370
80949
81568
  } }
80950
81569
  },
80951
81570
  {
@@ -80956,7 +81575,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80956
81575
  icon: "snowflake",
80957
81576
  args: { color: {
80958
81577
  mode: "mired",
80959
- mireds: 200
81578
+ colorTemperature: 200
80960
81579
  } }
80961
81580
  },
80962
81581
  {
@@ -81570,7 +82189,7 @@ var ACTUATOR_CAP_VOCABULARY = {
81570
82189
  label: t("Fan off", "Ventilatore spento"),
81571
82190
  phrase: t("Stop {device}", "Ferma {device}"),
81572
82191
  icon: "power-off",
81573
- args: { percentage: 0 }
82192
+ args: { speed: 0 }
81574
82193
  },
81575
82194
  {
81576
82195
  id: "fan.full",
@@ -81578,16 +82197,16 @@ var ACTUATOR_CAP_VOCABULARY = {
81578
82197
  label: t("Fan full", "Ventilatore al massimo"),
81579
82198
  phrase: t("Run {device} at full speed", "Porta {device} alla massima velocità"),
81580
82199
  icon: "fan",
81581
- args: { percentage: 100 }
82200
+ args: { speed: 100 }
81582
82201
  },
81583
82202
  {
81584
82203
  id: "fan.speed",
81585
82204
  method: "setPercentage",
81586
82205
  label: t("Set speed", "Imposta la velocità"),
81587
- phrase: t("Set the speed of {device} to {percentage}", "Imposta la velocità di {device} al {percentage}"),
82206
+ phrase: t("Set the speed of {device} to {speed}", "Imposta la velocità di {device} al {speed}"),
81588
82207
  icon: "fan",
81589
82208
  openArgs: [{
81590
- key: "percentage",
82209
+ key: "speed",
81591
82210
  label: t("Speed", "Velocità"),
81592
82211
  offer: {
81593
82212
  kind: "number",
@@ -81649,7 +82268,7 @@ var ACTUATOR_CAP_VOCABULARY = {
81649
82268
  ],
81650
82269
  state: [{
81651
82270
  kind: "numeric",
81652
- field: "percentage",
82271
+ field: "speed",
81653
82272
  quantity: t("the speed", "la velocità"),
81654
82273
  label: t("Speed", "Velocità"),
81655
82274
  unit: "%",
@@ -82447,7 +83066,7 @@ var APPLIANCE_CAP_VOCABULARY = {
82447
83066
  },
82448
83067
  {
82449
83068
  kind: "numeric",
82450
- field: "progressPercent",
83069
+ field: "progress",
82451
83070
  quantity: t("the cycle progress", "l’avanzamento del lavaggio"),
82452
83071
  label: t("Progress", "Avanzamento"),
82453
83072
  unit: "%",
@@ -82805,10 +83424,10 @@ var APPLIANCE_CAP_VOCABULARY = {
82805
83424
  id: "media.seek",
82806
83425
  method: "seek",
82807
83426
  label: t("Seek", "Vai al punto"),
82808
- phrase: t("Move {device} to {positionMs}", "Porta {device} al punto {positionMs}"),
83427
+ phrase: t("Move {device} to {position}", "Porta {device} al punto {position}"),
82809
83428
  icon: "fast-forward",
82810
83429
  openArgs: [{
82811
- key: "positionMs",
83430
+ key: "position",
82812
83431
  label: t("Position", "Posizione"),
82813
83432
  offer: {
82814
83433
  kind: "number",
@@ -83857,7 +84476,7 @@ var SENSOR_CAP_VOCABULARY = {
83857
84476
  state: [
83858
84477
  {
83859
84478
  kind: "numeric",
83860
- field: "co2Ppm",
84479
+ field: "co2",
83861
84480
  quantity: t("the CO₂", "la CO₂"),
83862
84481
  label: t("CO₂", "CO₂"),
83863
84482
  unit: "ppm",
@@ -83865,7 +84484,7 @@ var SENSOR_CAP_VOCABULARY = {
83865
84484
  },
83866
84485
  {
83867
84486
  kind: "numeric",
83868
- field: "vocPpb",
84487
+ field: "voc",
83869
84488
  quantity: t("the VOC level", "il livello di COV"),
83870
84489
  label: t("VOC", "COV"),
83871
84490
  unit: "ppb",
@@ -83901,7 +84520,7 @@ var SENSOR_CAP_VOCABULARY = {
83901
84520
  noun: t("Light level", "Luminosità ambiente"),
83902
84521
  state: [{
83903
84522
  kind: "numeric",
83904
- field: "lux",
84523
+ field: "illuminance",
83905
84524
  quantity: t("the light level", "la luminosità"),
83906
84525
  label: t("Illuminance", "Illuminamento"),
83907
84526
  unit: "lx",
@@ -84104,7 +84723,7 @@ var SENSOR_CAP_VOCABULARY = {
84104
84723
  noun: t("Humidity", "Umidità"),
84105
84724
  state: [{
84106
84725
  kind: "numeric",
84107
- field: "percent",
84726
+ field: "humidity",
84108
84727
  quantity: t("the humidity", "l’umidità"),
84109
84728
  label: t("Humidity", "Umidità"),
84110
84729
  unit: "%",
@@ -84152,7 +84771,7 @@ var SENSOR_CAP_VOCABULARY = {
84152
84771
  state: [
84153
84772
  {
84154
84773
  kind: "numeric",
84155
- field: "watts",
84774
+ field: "power",
84156
84775
  quantity: t("the power draw", "il consumo"),
84157
84776
  label: t("Power", "Potenza"),
84158
84777
  unit: "W",
@@ -84160,7 +84779,7 @@ var SENSOR_CAP_VOCABULARY = {
84160
84779
  },
84161
84780
  {
84162
84781
  kind: "numeric",
84163
- field: "kwhTotal",
84782
+ field: "energy",
84164
84783
  quantity: t("the energy used", "l’energia consumata"),
84165
84784
  label: t("Energy", "Energia"),
84166
84785
  unit: "kWh",
@@ -84168,7 +84787,7 @@ var SENSOR_CAP_VOCABULARY = {
84168
84787
  },
84169
84788
  {
84170
84789
  kind: "numeric",
84171
- field: "volts",
84790
+ field: "voltage",
84172
84791
  quantity: t("the voltage", "la tensione"),
84173
84792
  label: t("Voltage", "Tensione"),
84174
84793
  unit: "V",
@@ -84176,7 +84795,7 @@ var SENSOR_CAP_VOCABULARY = {
84176
84795
  },
84177
84796
  {
84178
84797
  kind: "numeric",
84179
- field: "amps",
84798
+ field: "current",
84180
84799
  quantity: t("the current", "la corrente"),
84181
84800
  label: t("Current", "Corrente"),
84182
84801
  unit: "A",
@@ -84193,13 +84812,6 @@ var SENSOR_CAP_VOCABULARY = {
84193
84812
  label: t("Where", "Posizione"),
84194
84813
  phrase: t("When {device} is at {value}", "Quando {device} è in {value}"),
84195
84814
  icon: "map-pin"
84196
- }, {
84197
- kind: "numeric",
84198
- field: "batteryPercent",
84199
- quantity: t("the phone battery", "la batteria del telefono"),
84200
- label: t("Battery", "Batteria"),
84201
- unit: "%",
84202
- icon: "battery"
84203
84815
  }]
84204
84816
  },
84205
84817
  "pressure-sensor": {
@@ -84207,7 +84819,7 @@ var SENSOR_CAP_VOCABULARY = {
84207
84819
  noun: t("Pressure", "Pressione"),
84208
84820
  state: [{
84209
84821
  kind: "numeric",
84210
- field: "hpa",
84822
+ field: "pressure",
84211
84823
  quantity: t("the pressure", "la pressione"),
84212
84824
  label: t("Pressure", "Pressione"),
84213
84825
  unit: "hPa",
@@ -84243,7 +84855,7 @@ var SENSOR_CAP_VOCABULARY = {
84243
84855
  noun: t("Temperature", "Temperatura"),
84244
84856
  state: [{
84245
84857
  kind: "numeric",
84246
- field: "celsius",
84858
+ field: "temperature",
84247
84859
  quantity: t("the temperature", "la temperatura"),
84248
84860
  label: t("Temperature", "Temperatura"),
84249
84861
  unit: "°C",
@@ -84314,7 +84926,7 @@ var SENSOR_CAP_VOCABULARY = {
84314
84926
  state: [
84315
84927
  {
84316
84928
  kind: "numeric",
84317
- field: "percentage",
84929
+ field: "level",
84318
84930
  quantity: t("the battery level", "il livello della batteria"),
84319
84931
  label: t("Battery", "Batteria"),
84320
84932
  unit: "%",
@@ -84356,7 +84968,7 @@ var SENSOR_CAP_VOCABULARY = {
84356
84968
  icon: "battery-charging"
84357
84969
  }
84358
84970
  ],
84359
- events: { onStatusChanged: { notOperatorFacing: "the percentage / sleeping / charging state triggers each read one field of this change" } },
84971
+ events: { onStatusChanged: { notOperatorFacing: "the level / sleeping / charging state triggers each read one field of this change" } },
84360
84972
  extraEvents: [{
84361
84973
  category: EventCategory.DeviceAwake,
84362
84974
  label: t("Woke up", "Risvegliato"),
@@ -84374,7 +84986,7 @@ var SENSOR_CAP_VOCABULARY = {
84374
84986
  noun: t("Network link", "Collegamento di rete"),
84375
84987
  state: [{
84376
84988
  kind: "numeric",
84377
- field: "signalPercent",
84989
+ field: "signal",
84378
84990
  quantity: t("the signal strength", "l’intensità del segnale"),
84379
84991
  label: t("Signal", "Segnale"),
84380
84992
  unit: "%",
@@ -84467,6 +85079,8 @@ var ML = "model and training machinery — no device action";
84467
85079
  var INTERNAL = "internal plumbing between addons — never operator-facing";
84468
85080
  /** Every system-scoped cap, with why it is not a device's vocabulary. */
84469
85081
  var SYSTEM_CAP_VOCABULARY = {
85082
+ "action-source": system(INTERNAL),
85083
+ actions: system("declared actions — run from the Actions page and from the cameras that link them"),
84470
85084
  "addon-pages": system(UI_PLUMBING),
84471
85085
  "addon-pages-source": system(UI_PLUMBING),
84472
85086
  "addon-routes": system(UI_PLUMBING),
@@ -84663,6 +85277,31 @@ Object.freeze({
84663
85277
  form: "single",
84664
85278
  optional: false
84665
85279
  }],
85280
+ "actions.get": [{
85281
+ name: "deviceId",
85282
+ form: "single",
85283
+ optional: true
85284
+ }],
85285
+ "actions.getLinks": [{
85286
+ name: "deviceId",
85287
+ form: "single",
85288
+ optional: false
85289
+ }],
85290
+ "actions.listForCamera": [{
85291
+ name: "deviceId",
85292
+ form: "single",
85293
+ optional: false
85294
+ }],
85295
+ "actions.run": [{
85296
+ name: "deviceId",
85297
+ form: "single",
85298
+ optional: true
85299
+ }],
85300
+ "actions.setLinks": [{
85301
+ name: "deviceId",
85302
+ form: "single",
85303
+ optional: false
85304
+ }],
84666
85305
  "addonSettings.getDeviceSettings": [{
84667
85306
  name: "deviceId",
84668
85307
  form: "single",
@@ -89706,6 +90345,7 @@ var BackgroundRunRegistry = class {
89706
90345
  }
89707
90346
  }
89708
90347
  };
90348
+ String.raw`(?<=^|["'(\s>,\[{])`;
89709
90349
  var MB = 1024 * 1024;
89710
90350
  var DEFAULT_POOL_MEMORY_POLICY = {
89711
90351
  sampleIntervalMs: 6e4,