@camstack/addon-pipeline 1.2.631 → 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 (36) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/audio-analyzer/index.js +3 -3
  3. package/dist/audio-analyzer/index.mjs +3 -3
  4. package/dist/{custom-model-catalog-DLv2FLul.mjs → custom-model-catalog-B2w26VBG.mjs} +2 -2
  5. package/dist/{custom-model-catalog-5IT-gwL2.js → custom-model-catalog-DN4xBVQE.js} +2 -2
  6. package/dist/detection-pipeline/index.js +4 -4
  7. package/dist/detection-pipeline/index.mjs +4 -4
  8. package/dist/{dist-CVvwej3J.js → dist-CLo3QHtG.js} +1540 -932
  9. package/dist/{dist-DV6W_qKY.mjs → dist-DWkdwtle.mjs} +1540 -932
  10. package/dist/{model-catalogs-lWWyJZqw.js → model-catalogs-DIviCtiQ.js} +1 -1
  11. package/dist/{model-catalogs-CI9KoY4D.mjs → model-catalogs-rs7okKyp.mjs} +1 -1
  12. package/dist/motion-wasm/index.js +1 -1
  13. package/dist/motion-wasm/index.mjs +1 -1
  14. package/dist/{node-KEh_tmT2.mjs → node-CAJ1Dal-.mjs} +1 -1
  15. package/dist/{node-BQ7P2No5.js → node-CPqDCFas.js} +1 -1
  16. package/dist/pipeline-runner/index.js +3 -3
  17. package/dist/pipeline-runner/index.mjs +3 -3
  18. package/dist/{process-memory-C-3bSKF5.js → process-memory-BrKzYxim.js} +1 -1
  19. package/dist/{process-memory-ChQO96Xb.mjs → process-memory-DJWthb6k.mjs} +1 -1
  20. package/dist/recorder/index.js +3 -2
  21. package/dist/recorder/index.mjs +3 -2
  22. package/dist/{segment-demux-js-8YepQCG3.js → segment-demux-js-Bt5bxfyh.js} +1 -1
  23. package/dist/{segment-demux-js-Dy75tkrR.mjs → segment-demux-js-CZdgio0n.mjs} +1 -1
  24. package/dist/stream-broker/_stub.js +2 -2
  25. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CKN6MEA_.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CjjdxZUq.mjs} +2 -2
  26. 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
  27. 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
  28. package/dist/stream-broker/demux-worker-child.js +1 -1
  29. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  30. package/dist/stream-broker/{hostInit-BqjKmOsK.mjs → hostInit-MWvZi8Nq.mjs} +2 -2
  31. package/dist/stream-broker/index.js +3 -3
  32. package/dist/stream-broker/index.mjs +3 -3
  33. package/dist/stream-broker/remoteEntry.js +1 -1
  34. package/package.json +1 -1
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BPnizAbp.mjs +0 -26
  36. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BL5-HIQd.mjs +0 -26
@@ -1,5 +1,5 @@
1
1
  let node_crypto = require("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
  /**
@@ -21342,7 +22081,7 @@ var CompositionEventPlanSchema = object({
21342
22081
  stateField: string(),
21343
22082
  fieldKey: string()
21344
22083
  });
21345
- /** 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`. */
21346
22085
  var CompositionProblemSchema = object({
21347
22086
  code: _enum([
21348
22087
  "reserved-variant",
@@ -21661,7 +22400,7 @@ var GROUP_TEMPLATES = [
21661
22400
  summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
21662
22401
  role: DeviceRole.TemperatureSensor,
21663
22402
  outputCap: "temperature-sensor",
21664
- outputField: "celsius",
22403
+ outputField: "temperature",
21665
22404
  quantity: "temperature",
21666
22405
  defaultAggregate: "mean",
21667
22406
  empty: "null",
@@ -21672,7 +22411,7 @@ var GROUP_TEMPLATES = [
21672
22411
  summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
21673
22412
  role: DeviceRole.HumiditySensor,
21674
22413
  outputCap: "humidity-sensor",
21675
- outputField: "percent",
22414
+ outputField: "humidity",
21676
22415
  quantity: "humidity",
21677
22416
  defaultAggregate: "mean",
21678
22417
  empty: "null",
@@ -21683,7 +22422,7 @@ var GROUP_TEMPLATES = [
21683
22422
  summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
21684
22423
  role: DeviceRole.PressureSensor,
21685
22424
  outputCap: "pressure-sensor",
21686
- outputField: "hpa",
22425
+ outputField: "pressure",
21687
22426
  quantity: "pressure",
21688
22427
  defaultAggregate: "mean",
21689
22428
  empty: "null",
@@ -21694,7 +22433,7 @@ var GROUP_TEMPLATES = [
21694
22433
  summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
21695
22434
  role: DeviceRole.AmbientLightSensor,
21696
22435
  outputCap: "ambient-light-sensor",
21697
- outputField: "lux",
22436
+ outputField: "illuminance",
21698
22437
  quantity: "illuminance",
21699
22438
  defaultAggregate: "mean",
21700
22439
  empty: "null",
@@ -21705,7 +22444,7 @@ var GROUP_TEMPLATES = [
21705
22444
  summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
21706
22445
  role: DeviceRole.PowerSensor,
21707
22446
  outputCap: "power-meter",
21708
- outputField: "watts",
22447
+ outputField: "power",
21709
22448
  quantity: "power",
21710
22449
  defaultAggregate: "sum",
21711
22450
  empty: "omit",
@@ -21716,7 +22455,7 @@ var GROUP_TEMPLATES = [
21716
22455
  summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
21717
22456
  role: DeviceRole.EnergySensor,
21718
22457
  outputCap: "power-meter",
21719
- outputField: "kwhTotal",
22458
+ outputField: "energy",
21720
22459
  quantity: "energy",
21721
22460
  defaultAggregate: "sum",
21722
22461
  empty: "omit",
@@ -21727,7 +22466,7 @@ var GROUP_TEMPLATES = [
21727
22466
  summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
21728
22467
  role: DeviceRole.BatterySensor,
21729
22468
  outputCap: "battery",
21730
- outputField: "percentage",
22469
+ outputField: "level",
21731
22470
  quantity: "percentage",
21732
22471
  defaultAggregate: "mean",
21733
22472
  empty: "null",
@@ -22305,7 +23044,18 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
22305
23044
  kind: "mutation",
22306
23045
  auth: "admin",
22307
23046
  access: "view"
22308
- });
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" });
22309
23059
  /**
22310
23060
  * `custom-model-registry` — collection cap exposing operator-registered
22311
23061
  * custom detection models. Each provider (today: `addon-model-studio`)
@@ -47158,732 +47908,365 @@ method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _v
47158
47908
  * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
47159
47909
  * `setTargetEnabled`), each provider persisting via the `settings-store`
47160
47910
  * cap. Rationale: the admin UI needs one uniform surface across the
47161
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
47162
- * alternative would fork the UI per addon and cannot host the
47163
- * discovery→adopt flow.
47164
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
47165
- * the generated cap-mount auto-`concatCollection`-fans them across every
47166
- * registered provider (notifiers addon + HA addon) so one catalog is
47167
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
47168
- * `addonId` the generated collection router extracts from the call input.
47169
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
47170
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
47171
- * `storage` / `storage-provider` / `recording` caps over the same path. No
47172
- * base64 fallback needed.
47173
- *
47174
- * TODO (deferred, closed-set change — separate decision): add
47175
- * `providerKind: 'notify'` so notification providers surface on the unified
47176
- * admin "Integrations" page.
47177
- */
47178
- /**
47179
- * Zentik-derived typed-media enum — the superset across every kind. Each
47180
- * adapter picks what it supports and the degrade engine filters the rest.
47181
- */
47182
- var AttachmentMediaTypeSchema = _enum([
47183
- "image",
47184
- "video",
47185
- "gif",
47186
- "audio",
47187
- "icon"
47188
- ]);
47189
- /**
47190
- * A single attachment. Exactly one of `url` (remote source, most adapters
47191
- * prefer this) or `bytes` (inline source; required for Pushover-style
47192
- * bytes-only kinds) MUST be present — the degrade engine expresses a
47193
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
47194
- */
47195
- var AttachmentSchema = object({
47196
- mediaType: AttachmentMediaTypeSchema,
47197
- url: string().optional(),
47198
- bytes: _instanceof(Uint8Array).optional(),
47199
- mime: string().optional(),
47200
- name: string().optional()
47201
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
47202
- var NotificationFormatSchema = _enum([
47203
- "text",
47204
- "markdown",
47205
- "html"
47206
- ]);
47207
- /**
47208
- * The CLOSED icon vocabulary an action button may use.
47209
- *
47210
- * A closed set, not a free string, and that is the whole point: an arbitrary
47211
- * icon name is one that ntfy renders, zentik silently drops, and nobody
47212
- * notices — the same class of gap as a zone vocabulary nothing produced
47213
- * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
47214
- * declares `actionIcons: false` and the degrade engine strips the field.
47215
- *
47216
- * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
47217
- * renderer's icon set; "acknowledge" survives an adapter that draws it
47218
- * differently.
47219
- *
47220
- * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
47221
- *
47222
- * These names are for US. **No adapter may forward one verbatim.** Each maps
47223
- * the whole set onto its own renderer's vocabulary through a
47224
- * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
47225
- * fallback, so adding a member here fails every adapter's build until someone
47226
- * decides its glyph, which is the only place that decision can be made
47227
- * honestly.
47228
- *
47229
- * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
47230
- * `disarm` straight through; iOS feeds that string to
47231
- * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
47232
- * and every snooze and alarm button arrived BLANK. A pass-through is not a
47233
- * mapping, and "the field is documented" is not "the value renders".
47234
- *
47235
- * Adding a member is TRAIN-BOUND. The enum lives in the published
47236
- * `@camstack/server` closure and the cap seam validates against the HUB's copy,
47237
- * so an addon that emits a token the running hub does not know does not lose an
47238
- * icon — its whole `send` fails Zod validation and the notification never
47239
- * arrives. Never emit a new token from an addon before the train carrying it.
47240
- */
47241
- var NotificationActionIconSchema = _enum([
47242
- "acknowledge",
47243
- "dismiss",
47244
- "silence",
47245
- "snooze",
47246
- "view",
47247
- "play",
47248
- "open",
47249
- "close",
47250
- "lock",
47251
- "unlock",
47252
- "arm",
47253
- "arm-home",
47254
- "arm-away",
47255
- "arm-night",
47256
- "disarm",
47257
- "light",
47258
- "alert",
47259
- "camera"
47260
- ]);
47261
- /** A single tap-through action button. */
47262
- var NotificationActionSchema = object({
47263
- id: string(),
47264
- label: string(),
47265
- url: string().optional(),
47266
- /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
47267
- icon: NotificationActionIconSchema.optional(),
47268
- /**
47269
- * Renders in a warning style where the notifier supports it.
47270
- *
47271
- * A HINT, never a gate. The callback's authority is its token and nothing
47272
- * else — see `notification-center/action-token.ts` for what that does and
47273
- * does not buy.
47274
- */
47275
- destructive: boolean().optional(),
47276
- /**
47277
- * How the tap should REACH the url.
47278
- *
47279
- * `navigate` (absent, and every button authored before this field) opens it:
47280
- * the phone leaves the notification and shows whatever the callback returns.
47281
- * That is right for a button whose answer the operator wants to read.
47282
- *
47283
- * `background` fires it as a POST and stays put. It exists for the buttons
47284
- * whose whole point is not to interrupt — "silence this for 30 minutes" is
47285
- * an answer to the notification, and being thrown into a browser tab to
47286
- * confirm it costs more attention than the notification did. A backend that
47287
- * cannot do a background call renders it as an ordinary link (the adapters
47288
- * fall back rather than dropping the button), so this is a preference, never
47289
- * a requirement.
47290
- */
47291
- mode: _enum(["navigate", "background"]).optional()
47292
- });
47293
- /**
47294
- * The canonical notification. `body` is the only hard field (Apprise model).
47295
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
47296
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
47297
- * the adapter maps this ordinal onto its native level. `level?` is an
47298
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
47299
- * `priority` for that one target.
47300
- */
47301
- var NotificationSchema = object({
47302
- body: string(),
47303
- title: string().optional(),
47304
- format: NotificationFormatSchema.default("text"),
47305
- priority: number().int().min(1).max(5).default(3),
47306
- level: string().optional(),
47307
- attachments: array(AttachmentSchema).optional(),
47308
- clickUrl: string().optional(),
47309
- actions: array(NotificationActionSchema).optional(),
47310
- sound: string().optional(),
47311
- ttl: number().optional(),
47312
- tag: string().optional(),
47313
- deviceId: number().optional(),
47314
- eventId: string().optional(),
47315
- metadata: record(string(), unknown()).optional()
47316
- });
47317
- /** One declared native severity/priority level for a kind. */
47318
- var TargetKindLevelSchema = object({
47319
- id: string(),
47320
- label: string(),
47321
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
47322
- ordinal: number().int().min(1).max(5).nullable(),
47323
- flags: object({
47324
- critical: boolean().optional(),
47325
- silent: boolean().optional(),
47326
- noPush: boolean().optional()
47327
- }).optional(),
47328
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
47329
- requires: array(string()).optional(),
47330
- description: string().optional()
47331
- });
47332
- /** The full capability block consulted before dispatch. */
47333
- var TargetKindCapsSchema = object({
47334
- attachments: object({
47335
- mediaTypes: array(AttachmentMediaTypeSchema),
47336
- mode: _enum([
47337
- "url",
47338
- "bytes",
47339
- "both"
47340
- ]),
47341
- max: number().int().nonnegative(),
47342
- maxBytes: number().int().positive().optional()
47343
- }),
47344
- /** Max action buttons (0 = none). */
47345
- actions: number().int().nonnegative(),
47346
- /**
47347
- * Whether this kind renders a per-action ICON.
47348
- *
47349
- * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
47350
- * run on the addon cap path — three production failures in one day taught
47351
- * this repo that once. Absent is read as false by the degrade engine, which
47352
- * is the safe direction: an icon that is not rendered costs nothing, an icon
47353
- * assumed and dropped costs the operator's trust in the field.
47354
- */
47355
- actionIcons: boolean().optional(),
47356
- levels: array(TargetKindLevelSchema),
47357
- format: array(NotificationFormatSchema),
47358
- clickUrl: boolean(),
47359
- sound: boolean(),
47360
- ttl: boolean(),
47361
- bodyMaxLen: number().int().positive()
47362
- });
47363
- /**
47364
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
47365
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
47366
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
47367
- * the union is large and not meant for runtime validation here; the exported
47368
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
47369
- */
47370
- var ConfigSchemaPassthrough$2 = unknown();
47371
- var TargetKindSchema = object({
47372
- kind: string(),
47373
- label: string(),
47374
- icon: string(),
47375
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
47376
- addonId: string(),
47377
- /**
47378
- * URL of the kind's bundled BRAND icon, served by the providing addon over
47379
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
47380
- * when the addon bundles no icon for that kind — the client then falls back
47381
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
47382
- *
47383
- * Root-relative on purpose: it resolves against whatever origin serves a web
47384
- * client, and a native client joins it onto its own hub base.
47385
- *
47386
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
47387
- * field that survived only because the runtime cap-router forwards provider
47388
- * output verbatim — so every consumer had to re-declare it by hand to stop
47389
- * its own Zod parse from stripping it, and the whole arrangement would have
47390
- * broken silently the moment output validation was tightened anywhere.
47391
- */
47392
- iconUrl: string().optional(),
47393
- /**
47394
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
47395
- *
47396
- * The server knows this and therefore says it, because the client cannot
47397
- * safely guess: a React-Native client renders SVG and raster through two
47398
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
47399
- * not decode SVG on iOS/Android), so without this it silently fell back to a
47400
- * placeholder glyph for every vector icon while the web build looked fine.
47401
- *
47402
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
47403
- * not been updated — a client that cannot determine the type should prefer
47404
- * its raster path, which is the safe default for an unknown image.
47405
- */
47406
- iconMediaType: string().optional(),
47407
- configSchema: ConfigSchemaPassthrough$2,
47408
- /**
47409
- * The config keys that decide WHERE this kind's secrets are sent (server,
47410
- * base URL, instance, chat, recipient). A save that changes any of them
47411
- * while leaving a secret redacted is refused by the provider (D634), so an
47412
- * editor uses this to ask for the secret on exactly those edits instead of
47413
- * on every edit.
47414
- *
47415
- * Optional: absent from a provider that holds no secret (the Home Assistant
47416
- * kinds) and from a provider older than this field — an editor that finds
47417
- * it absent must assume ANY edit may need the secret.
47418
- */
47419
- destinationKeys: array(string()).optional(),
47420
- supportsDiscovery: boolean(),
47421
- caps: TargetKindCapsSchema
47422
- });
47423
- /**
47424
- * A persisted target. `config` holds secrets; providers REDACT secret fields
47425
- * (return a presence marker only) when serving `listTargets` — never
47426
- * round-trip a stored secret to the UI.
47427
- */
47428
- var TargetSchema = object({
47429
- id: string(),
47430
- name: string(),
47431
- kind: string(),
47432
- addonId: string(),
47433
- enabled: boolean(),
47434
- config: record(string(), unknown())
47435
- });
47436
- /** A discovery-surfaced candidate (config is partial + non-secret). */
47437
- var DiscoveredTargetSchema = object({
47438
- kind: string(),
47439
- suggestedName: string(),
47440
- config: record(string(), unknown())
47441
- });
47442
- /** The degrade engine's report — what was resolved / dropped / degraded. */
47443
- var RenderedAsSchema = object({
47444
- level: string(),
47445
- format: NotificationFormatSchema,
47446
- attachmentsSent: number().int().nonnegative(),
47447
- actionsSent: number().int().nonnegative(),
47448
- truncated: boolean(),
47449
- dropped: array(string())
47450
- });
47451
- var SendResultSchema = object({
47452
- success: boolean(),
47453
- error: string().optional(),
47454
- renderedAs: RenderedAsSchema.optional()
47455
- });
47456
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
47457
- var TestResultSchema = SendResultSchema;
47458
- /**
47459
- * Pre-save credential probe against a partial (unsaved) target config.
47460
- * `resolvedUrl` never includes secrets (token stripped from path).
47461
- */
47462
- var ProbeTargetConfigResultSchema = object({
47463
- ok: boolean(),
47464
- resolvedUrl: string(),
47465
- httpStatus: number().int().optional(),
47466
- stateInstance: string().optional(),
47467
- error: string().optional()
47468
- });
47469
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
47470
- kind: string(),
47471
- config: record(string(), unknown()).optional(),
47472
- /**
47473
- * When editing / duplicating, the blank password field never round-trips
47474
- * the stored token. Pass the source target id so the provider can restore
47475
- * secrets server-side before dialling (same merge as upsert).
47476
- */
47477
- targetId: string().optional()
47478
- }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
47479
- kind: string(),
47480
- config: record(string(), unknown()),
47481
- /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
47482
- targetId: string().optional()
47483
- }), ProbeTargetConfigResultSchema, {
47484
- kind: "mutation",
47485
- auth: "admin"
47486
- }), method(object({
47487
- targetId: string(),
47488
- notification: NotificationSchema
47489
- }), SendResultSchema, {
47490
- kind: "mutation",
47491
- auth: "admin"
47492
- }), method(object({
47493
- targetId: string(),
47494
- sample: NotificationSchema.optional()
47495
- }), TestResultSchema, {
47496
- kind: "mutation",
47497
- auth: "admin"
47498
- }), method(object({
47499
- target: TargetSchema,
47500
- /**
47501
- * Duplicate flow: create a NEW target id while copying secrets from an
47502
- * existing row. The UI never holds the stored token; blank / redacted
47503
- * password fields are restored from this source (D634 still applies if
47504
- * destinations diverge without a re-typed secret).
47505
- */
47506
- cloneSecretsFrom: string().optional()
47507
- }), TargetSchema, {
47508
- kind: "mutation",
47509
- auth: "admin"
47510
- }), method(object({ targetId: string() }), _void(), {
47511
- kind: "mutation",
47512
- auth: "admin"
47513
- }), method(object({
47514
- targetId: string(),
47515
- enabled: boolean()
47516
- }), _void(), {
47517
- kind: "mutation",
47518
- auth: "admin"
47519
- });
47520
- var COCO_80_LABELS = [
47521
- {
47522
- id: "person",
47523
- name: "Person"
47524
- },
47525
- {
47526
- id: "bicycle",
47527
- name: "Bicycle"
47528
- },
47529
- {
47530
- id: "car",
47531
- name: "Car"
47532
- },
47533
- {
47534
- id: "motorcycle",
47535
- name: "Motorcycle"
47536
- },
47537
- {
47538
- id: "airplane",
47539
- name: "Airplane"
47540
- },
47541
- {
47542
- id: "bus",
47543
- name: "Bus"
47544
- },
47545
- {
47546
- id: "train",
47547
- name: "Train"
47548
- },
47549
- {
47550
- id: "truck",
47551
- name: "Truck"
47552
- },
47553
- {
47554
- id: "boat",
47555
- name: "Boat"
47556
- },
47557
- {
47558
- id: "traffic light",
47559
- name: "Traffic Light"
47560
- },
47561
- {
47562
- id: "fire hydrant",
47563
- name: "Fire Hydrant"
47564
- },
47565
- {
47566
- id: "stop sign",
47567
- name: "Stop Sign"
47568
- },
47569
- {
47570
- id: "parking meter",
47571
- name: "Parking Meter"
47572
- },
47573
- {
47574
- id: "bench",
47575
- name: "Bench"
47576
- },
47577
- {
47578
- id: "bird",
47579
- name: "Bird"
47580
- },
47581
- {
47582
- id: "cat",
47583
- name: "Cat"
47584
- },
47585
- {
47586
- id: "dog",
47587
- name: "Dog"
47588
- },
47589
- {
47590
- id: "horse",
47591
- name: "Horse"
47592
- },
47593
- {
47594
- id: "sheep",
47595
- name: "Sheep"
47596
- },
47597
- {
47598
- id: "cow",
47599
- name: "Cow"
47600
- },
47601
- {
47602
- id: "elephant",
47603
- name: "Elephant"
47604
- },
47605
- {
47606
- id: "bear",
47607
- name: "Bear"
47608
- },
47609
- {
47610
- id: "zebra",
47611
- name: "Zebra"
47612
- },
47613
- {
47614
- id: "giraffe",
47615
- name: "Giraffe"
47616
- },
47617
- {
47618
- id: "backpack",
47619
- name: "Backpack"
47620
- },
47621
- {
47622
- id: "umbrella",
47623
- name: "Umbrella"
47624
- },
47625
- {
47626
- id: "handbag",
47627
- name: "Handbag"
47628
- },
47629
- {
47630
- id: "tie",
47631
- name: "Tie"
47632
- },
47633
- {
47634
- id: "suitcase",
47635
- name: "Suitcase"
47636
- },
47637
- {
47638
- id: "frisbee",
47639
- name: "Frisbee"
47640
- },
47641
- {
47642
- id: "skis",
47643
- name: "Skis"
47644
- },
47645
- {
47646
- id: "snowboard",
47647
- name: "Snowboard"
47648
- },
47649
- {
47650
- id: "sports ball",
47651
- name: "Sports Ball"
47652
- },
47653
- {
47654
- id: "kite",
47655
- name: "Kite"
47656
- },
47657
- {
47658
- id: "baseball bat",
47659
- name: "Baseball Bat"
47660
- },
47661
- {
47662
- id: "baseball glove",
47663
- name: "Baseball Glove"
47664
- },
47665
- {
47666
- id: "skateboard",
47667
- name: "Skateboard"
47668
- },
47669
- {
47670
- id: "surfboard",
47671
- name: "Surfboard"
47672
- },
47673
- {
47674
- id: "tennis racket",
47675
- name: "Tennis Racket"
47676
- },
47677
- {
47678
- id: "bottle",
47679
- name: "Bottle"
47680
- },
47681
- {
47682
- id: "wine glass",
47683
- name: "Wine Glass"
47684
- },
47685
- {
47686
- id: "cup",
47687
- name: "Cup"
47688
- },
47689
- {
47690
- id: "fork",
47691
- name: "Fork"
47692
- },
47693
- {
47694
- id: "knife",
47695
- name: "Knife"
47696
- },
47697
- {
47698
- id: "spoon",
47699
- name: "Spoon"
47700
- },
47701
- {
47702
- id: "bowl",
47703
- name: "Bowl"
47704
- },
47705
- {
47706
- id: "banana",
47707
- name: "Banana"
47708
- },
47709
- {
47710
- id: "apple",
47711
- name: "Apple"
47712
- },
47713
- {
47714
- id: "sandwich",
47715
- name: "Sandwich"
47716
- },
47717
- {
47718
- id: "orange",
47719
- name: "Orange"
47720
- },
47721
- {
47722
- id: "broccoli",
47723
- name: "Broccoli"
47724
- },
47725
- {
47726
- id: "carrot",
47727
- name: "Carrot"
47728
- },
47729
- {
47730
- id: "hot dog",
47731
- name: "Hot Dog"
47732
- },
47733
- {
47734
- id: "pizza",
47735
- name: "Pizza"
47736
- },
47737
- {
47738
- id: "donut",
47739
- name: "Donut"
47740
- },
47741
- {
47742
- id: "cake",
47743
- name: "Cake"
47744
- },
47745
- {
47746
- id: "chair",
47747
- name: "Chair"
47748
- },
47749
- {
47750
- id: "couch",
47751
- name: "Couch"
47752
- },
47753
- {
47754
- id: "potted plant",
47755
- name: "Potted Plant"
47756
- },
47757
- {
47758
- id: "bed",
47759
- name: "Bed"
47760
- },
47761
- {
47762
- id: "dining table",
47763
- name: "Dining Table"
47764
- },
47765
- {
47766
- id: "toilet",
47767
- name: "Toilet"
47768
- },
47769
- {
47770
- id: "tv",
47771
- name: "TV"
47772
- },
47773
- {
47774
- id: "laptop",
47775
- name: "Laptop"
47776
- },
47777
- {
47778
- id: "mouse",
47779
- name: "Mouse"
47780
- },
47781
- {
47782
- id: "remote",
47783
- name: "Remote"
47784
- },
47785
- {
47786
- id: "keyboard",
47787
- name: "Keyboard"
47788
- },
47789
- {
47790
- id: "cell phone",
47791
- name: "Cell Phone"
47792
- },
47793
- {
47794
- id: "microwave",
47795
- name: "Microwave"
47796
- },
47797
- {
47798
- id: "oven",
47799
- name: "Oven"
47800
- },
47801
- {
47802
- id: "toaster",
47803
- name: "Toaster"
47804
- },
47805
- {
47806
- id: "sink",
47807
- name: "Sink"
47808
- },
47809
- {
47810
- id: "refrigerator",
47811
- name: "Refrigerator"
47812
- },
47813
- {
47814
- id: "book",
47815
- name: "Book"
47816
- },
47817
- {
47818
- id: "clock",
47819
- name: "Clock"
47820
- },
47821
- {
47822
- id: "vase",
47823
- name: "Vase"
47824
- },
47825
- {
47826
- id: "scissors",
47827
- name: "Scissors"
47828
- },
47829
- {
47830
- id: "teddy bear",
47831
- name: "Teddy Bear"
47832
- },
47833
- {
47834
- id: "hair drier",
47835
- name: "Hair Drier"
47836
- },
47837
- {
47838
- id: "toothbrush",
47839
- name: "Toothbrush"
47840
- }
47841
- ];
47842
- new Set([
47843
- {
47844
- id: "person",
47845
- name: "Person"
47846
- },
47847
- {
47848
- id: "vehicle",
47849
- name: "Vehicle"
47850
- },
47851
- {
47852
- id: "animal",
47853
- name: "Animal"
47854
- },
47855
- {
47856
- id: "package",
47857
- name: "Package"
47858
- }
47859
- ].map((l) => l.id));
47860
- var COCO_TO_MACRO = {
47861
- mapping: {
47862
- person: "person",
47863
- bicycle: "vehicle",
47864
- car: "vehicle",
47865
- motorcycle: "vehicle",
47866
- airplane: "vehicle",
47867
- bus: "vehicle",
47868
- train: "vehicle",
47869
- truck: "vehicle",
47870
- boat: "vehicle",
47871
- bird: "animal",
47872
- cat: "animal",
47873
- dog: "animal",
47874
- horse: "animal",
47875
- sheep: "animal",
47876
- cow: "animal",
47877
- elephant: "animal",
47878
- bear: "animal",
47879
- zebra: "animal",
47880
- giraffe: "animal",
47881
- suitcase: "package",
47882
- backpack: "package",
47883
- handbag: "package"
47884
- },
47885
- preserveOriginal: false
47886
- };
47911
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
47912
+ * alternative would fork the UI per addon and cannot host the
47913
+ * discovery→adopt flow.
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
+ });
47887
48270
  var AUDIO_MACRO_LABELS = [
47888
48271
  {
47889
48272
  id: "speech",
@@ -61164,9 +61547,9 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
61164
61547
  }), LogStreamEntrySchema, { kind: "subscription" });
61165
61548
  object({
61166
61549
  /** Carbon dioxide concentration in ppm. */
61167
- co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
61550
+ co2: number().min(0).optional().meta(readingMeta("concentration-volume")),
61168
61551
  /** Total volatile organic compounds in ppb. */
61169
- vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
61552
+ voc: number().min(0).optional().meta(readingMeta("concentration-ppb")),
61170
61553
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
61171
61554
  pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
61172
61555
  /** Particulate matter ≤ 10 μm in µg/m³. */
@@ -61179,7 +61562,7 @@ object({
61179
61562
  DeviceType.Sensor;
61180
61563
  object({
61181
61564
  /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
61182
- lux: number().min(0).nullable().meta(readingMeta("illuminance", {
61565
+ illuminance: number().min(0).nullable().meta(readingMeta("illuminance", {
61183
61566
  precision: 0,
61184
61567
  label: "Illuminance"
61185
61568
  })),
@@ -61276,6 +61659,84 @@ DeviceType.Camera, method(object({ deviceId: number() }), AudioMetricsSnapshotSc
61276
61659
  * would produce more than `maxPoints` samples. */
61277
61660
  sampleEveryMs: number().int().positive().optional()
61278
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
+ });
61279
61740
  object({
61280
61741
  /** Whether the automation is currently enabled. Disabled automations
61281
61742
  * ignore their trigger block — manual `trigger` still works. */
@@ -61329,7 +61790,7 @@ var BatteryStatusSchema = object({
61329
61790
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
61330
61791
  * declines to band a non-finite reading, which is the correct shape.
61331
61792
  */
61332
- 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" })),
61333
61794
  /**
61334
61795
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
61335
61796
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -61339,7 +61800,7 @@ var BatteryStatusSchema = object({
61339
61800
  * `'unknown'` is the absence of an answer: the provider has not read the
61340
61801
  * power fields yet, or the firmware reported neither. Before it existed,
61341
61802
  * two unreadable fields produced `'none'`, and a camera nobody had reached
61342
- * was drawn "on battery alone" — the same D315 defect `percentage` closed
61803
+ * was drawn "on battery alone" — the same D315 defect `level` closed
61343
61804
  * by going nullable. Consumers SKIP it (no charger state published, no
61344
61805
  * glyph, no HomeKit ChargingState) rather than coerce it either way.
61345
61806
  */
@@ -61363,7 +61824,7 @@ var BatteryStatusSchema = object({
61363
61824
  * that only report charge. A second reading on this cap, not a second
61364
61825
  * `numeric-sensor` on the same device.
61365
61826
  */
61366
- volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
61827
+ voltage: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
61367
61828
  /**
61368
61829
  * Ms epoch of the last time the device PROVED it was reachable — a
61369
61830
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -61397,7 +61858,7 @@ var BatteryStatusSchema = object({
61397
61858
  /**
61398
61859
  * True when the source is a BINARY low-battery indicator (HA
61399
61860
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
61400
- * charge level — `percentage` is then a coarse stand-in (100 = normal,
61861
+ * charge level — `level` is then a coarse stand-in (100 = normal,
61401
61862
  * sub-threshold = low). UI MUST render "Normal"/"Low" instead of a
61402
61863
  * misleading exact percentage. Absent/false → genuine 0–100 % reading.
61403
61864
  */
@@ -61417,15 +61878,15 @@ var BATTERY_DEVICE_TYPES = [
61417
61878
  var batteryCustomize = {
61418
61879
  version: 1,
61419
61880
  targets: [{
61420
- id: "percentage",
61881
+ id: "level",
61421
61882
  cardinality: "scalar",
61422
61883
  vocabulary: {
61423
61884
  cap: "battery",
61424
- stateField: "percentage"
61885
+ stateField: "level"
61425
61886
  },
61426
61887
  deviceTypes: BATTERY_DEVICE_TYPES,
61427
61888
  apply: "add",
61428
- path: "percentage",
61889
+ path: "level",
61429
61890
  sources: [{
61430
61891
  kind: "field",
61431
61892
  valueKinds: ["number", "string"],
@@ -61519,7 +61980,7 @@ onStatusChanged: { data: object({
61519
61980
  schema: BatteryStatusSchema,
61520
61981
  kind: "push",
61521
61982
  empty: {
61522
- percentage: null,
61983
+ level: null,
61523
61984
  charging: "unknown",
61524
61985
  sleeping: false,
61525
61986
  lastUpdated: 0
@@ -61546,11 +62007,11 @@ onStatusChanged: { data: object({
61546
62007
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
61547
62008
  */
61548
62009
  history: { fields: {
61549
- percentage: {
62010
+ level: {
61550
62011
  kind: "gauge",
61551
62012
  unit: "%"
61552
62013
  },
61553
- volts: {
62014
+ voltage: {
61554
62015
  kind: "gauge",
61555
62016
  unit: "V"
61556
62017
  }
@@ -61568,19 +62029,19 @@ object({
61568
62029
  DeviceType.Sensor;
61569
62030
  object({
61570
62031
  /** Current level as 0..100 inclusive. Firmware-reported. */
61571
- percentage: number().min(0).max(100).meta(readingMeta("percentage")),
62032
+ level: number().min(0).max(100).meta(readingMeta("percentage")),
61572
62033
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
61573
62034
  lastChangedAt: number()
61574
62035
  });
61575
62036
  DeviceType.Light, method(object({
61576
62037
  deviceId: number().int().nonnegative(),
61577
- percentage: number().min(0).max(100)
62038
+ level: number().min(0).max(100)
61578
62039
  }), _void(), {
61579
62040
  kind: "mutation",
61580
62041
  auth: "protected"
61581
62042
  }), object({
61582
62043
  deviceId: number(),
61583
- percentage: number().min(0).max(100),
62044
+ level: number().min(0).max(100),
61584
62045
  lastChangedAt: number()
61585
62046
  });
61586
62047
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
@@ -62081,11 +62542,11 @@ var ColorInputSchema = discriminatedUnion("mode", [
62081
62542
  }),
62082
62543
  object({
62083
62544
  mode: literal("mired"),
62084
- mireds: number().int().min(50).max(1e3)
62545
+ colorTemperature: number().int().min(50).max(1e3)
62085
62546
  })
62086
62547
  ]);
62087
62548
  object({
62088
- /** Active color mode — which of `rgb` / `hsv` / `mireds` reflects
62549
+ /** Active color mode — which of `rgb` / `hsv` / `colorTemperature` reflects
62089
62550
  * the bulb's current state. */
62090
62551
  mode: _enum([
62091
62552
  "rgb",
@@ -62097,7 +62558,7 @@ object({
62097
62558
  /** Populated when `mode === 'hsv'`. */
62098
62559
  hsv: HsvTripletSchema.optional(),
62099
62560
  /** Populated when `mode === 'mired'`. */
62100
- mireds: number().int().optional().meta(readingMeta("colour-temperature")),
62561
+ colorTemperature: number().int().optional().meta(readingMeta("colour-temperature")),
62101
62562
  /** Ms epoch of the last operator-driven change. */
62102
62563
  lastChangedAt: number()
62103
62564
  });
@@ -62116,7 +62577,7 @@ DeviceType.Light, method(object({
62116
62577
  ]),
62117
62578
  rgb: RgbTripletSchema.optional(),
62118
62579
  hsv: HsvTripletSchema.optional(),
62119
- mireds: number().int().optional(),
62580
+ colorTemperature: number().int().optional(),
62120
62581
  lastChangedAt: number()
62121
62582
  });
62122
62583
  var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
@@ -62542,11 +63003,11 @@ object({
62542
63003
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
62543
63004
  availablePrograms: array(string()),
62544
63005
  /** Seconds remaining on the active cycle, or null when not running / not known. */
62545
- remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
63006
+ remaining: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62546
63007
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
62547
- delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
63008
+ delayedStart: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62548
63009
  /** Cycle completion 0..100, or null when not applicable. */
62549
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63010
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62550
63011
  /** True when the door is reported open; null when the door state is not known. */
62551
63012
  doorOpen: boolean().nullable(),
62552
63013
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -63164,7 +63625,7 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
63164
63625
  object({
63165
63626
  /** Active speed as 0..100 inclusive. Null when the device has no
63166
63627
  * speed surface (single-speed fan). */
63167
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63628
+ speed: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63168
63629
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
63169
63630
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
63170
63631
  percentageStep: number().positive().optional().meta(readingMeta("percentage")),
@@ -63184,7 +63645,7 @@ object({
63184
63645
  });
63185
63646
  DeviceType.Fan, method(object({
63186
63647
  deviceId: number().int().nonnegative(),
63187
- percentage: number().min(0).max(100)
63648
+ speed: number().min(0).max(100)
63188
63649
  }), _void(), {
63189
63650
  kind: "mutation",
63190
63651
  auth: "protected"
@@ -63367,7 +63828,7 @@ DeviceType.Humidifier, method(object({
63367
63828
  });
63368
63829
  object({
63369
63830
  /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
63370
- percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63831
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63371
63832
  precision: 0,
63372
63833
  label: "Humidity"
63373
63834
  })),
@@ -63810,7 +64271,7 @@ object({
63810
64271
  batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63811
64272
  /** 0..100 mowing-completion percentage of the current task, or null when no
63812
64273
  * task is active / progress is unavailable. */
63813
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64274
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63814
64275
  /** Current device/event code (dynamic — mostly status, sometimes an error),
63815
64276
  * or null when unknown. */
63816
64277
  currentCode: number().nullable().meta(readingMeta("none")),
@@ -64300,10 +64761,10 @@ object({
64300
64761
  currentMedia: MediaInfoSchema.nullable(),
64301
64762
  /** Current playback position in ms. Null when not seekable or
64302
64763
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
64303
- positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64764
+ position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64304
64765
  /** Total duration of the current media in ms. Null when unknown
64305
64766
  * (live stream) or nothing is playing. */
64306
- durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64767
+ duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64307
64768
  /** Shuffle toggle. Null when no shuffle surface.
64308
64769
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
64309
64770
  shuffle: boolean().nullable(),
@@ -64330,7 +64791,7 @@ DeviceType.MediaPlayer, method(object({ deviceId: number().int().nonnegative() }
64330
64791
  auth: "protected"
64331
64792
  }), method(object({
64332
64793
  deviceId: number().int().nonnegative(),
64333
- positionMs: number().int().nonnegative()
64794
+ position: number().int().nonnegative()
64334
64795
  }), _void(), {
64335
64796
  kind: "mutation",
64336
64797
  auth: "protected"
@@ -65003,9 +65464,9 @@ var NetworkLinkStatusSchema = object({
65003
65464
  * one whose reading has not landed must not be drawn at 0 %. Consumers
65004
65465
  * SKIP a null rather than coerce it.
65005
65466
  */
65006
- signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65467
+ signal: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65007
65468
  /** Raw received signal strength in dBm, when the firmware reports one. */
65008
- rssiDbm: number().optional().meta(readingMeta("signal-level")),
65469
+ rssi: number().optional().meta(readingMeta("signal-level")),
65009
65470
  /** Network name of a wireless link, when the firmware reports it. */
65010
65471
  ssid: string().optional(),
65011
65472
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
@@ -65666,10 +66127,20 @@ var PetFeederDeviceStatusSchema = _enum([
65666
66127
  ]);
65667
66128
  var gramsPortion = number().int().min(4).max(200);
65668
66129
  object({
65669
- /** Food currently in the bowl (grams). Null when the device has not
65670
- * reported a reading yet. On dual-hopper models this is the combined
65671
- * 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. */
65672
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(),
65673
66144
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
65674
66145
  * single-hopper models. */
65675
66146
  food1: number().nullable().meta(readingMeta("none")),
@@ -65677,8 +66148,8 @@ object({
65677
66148
  * single-hopper models. */
65678
66149
  food2: number().nullable().meta(readingMeta("none")),
65679
66150
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
65680
- * (`device_class: problem`, on = low). True when the bowl is empty /
65681
- * 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. */
65682
66153
  lowFood: boolean(),
65683
66154
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
65684
66155
  * device has no battery reading. */
@@ -66145,16 +66616,16 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
66145
66616
  auth: "admin"
66146
66617
  });
66147
66618
  object({
66148
- /** Instantaneous power draw in watts. */
66149
- watts: number().optional().meta(readingMeta("power", { label: "Power" })),
66619
+ /** Instantaneous power draw (watts). */
66620
+ power: number().optional().meta(readingMeta("power", { label: "Power" })),
66150
66621
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
66151
- kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
66152
- /** Voltage in volts. */
66153
- volts: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
66622
+ energy: number().optional().meta(readingMeta("energy", { label: "Energy" })),
66623
+ /** Voltage (volts). */
66624
+ voltage: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
66154
66625
  /** Current in amperes. */
66155
- amps: number().optional().meta(readingMeta("current", { label: "Current" })),
66626
+ current: number().optional().meta(readingMeta("current", { label: "Current" })),
66156
66627
  /** Apparent power in volt-amperes. */
66157
- va: number().optional().meta(readingMeta("apparent-power")),
66628
+ apparentPower: number().optional().meta(readingMeta("apparent-power")),
66158
66629
  /** Power factor (0..1) if reported. */
66159
66630
  powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
66160
66631
  /** Ms epoch when the slice was last updated. */
@@ -66209,16 +66680,13 @@ object({
66209
66680
  location: string().nullable(),
66210
66681
  /** GPS coordinates when available. Null otherwise. */
66211
66682
  gps: GpsLocationSchema.nullable(),
66212
- /** Optional battery level of the tracking device (0..100). Null
66213
- * when not reported. */
66214
- batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
66215
66683
  /** Ms epoch when the slice was last updated. */
66216
66684
  lastChangedAt: number()
66217
66685
  });
66218
66686
  DeviceType.Presence;
66219
66687
  object({
66220
66688
  /** Current pressure in hPa. `null` when the sample was refused (D393). */
66221
- hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
66689
+ pressure: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
66222
66690
  /** Ms epoch when the slice was last updated. */
66223
66691
  lastFetchedAt: number()
66224
66692
  });
@@ -69745,7 +70213,7 @@ object({
69745
70213
  DeviceType.Sensor;
69746
70214
  object({
69747
70215
  /** Current temperature in Celsius. `null` when the sample was refused (D393). */
69748
- celsius: number().nullable().meta(readingMeta("temperature", {
70216
+ temperature: number().nullable().meta(readingMeta("temperature", {
69749
70217
  precision: 1,
69750
70218
  label: "Temperature"
69751
70219
  })),
@@ -70009,7 +70477,7 @@ object({
70009
70477
  /** Speed tokens the hardware accepts — drives the UI selector. */
70010
70478
  availableFanSpeeds: array(string()),
70011
70479
  /** 0..100 cleaning-completion percentage of the current task, or null. */
70012
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
70480
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
70013
70481
  /** Current error code (0 / null = no error). */
70014
70482
  errorCode: number().nullable().meta(readingMeta("none")),
70015
70483
  /** Human label for {@link errorCode}, or null when none / undecodable. */
@@ -71008,6 +71476,104 @@ Object.freeze({
71008
71476
  access: "create",
71009
71477
  role: "admin"
71010
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
+ },
71011
71577
  "addonPages.listPages": {
71012
71578
  capName: "addon-pages",
71013
71579
  capScope: "system",
@@ -72289,6 +72855,13 @@ Object.freeze({
72289
72855
  access: "view",
72290
72856
  role: "admin"
72291
72857
  },
72858
+ "coreBlocks.getAutomationRunState": {
72859
+ capName: "core-blocks",
72860
+ capScope: "system",
72861
+ addonId: null,
72862
+ access: "view",
72863
+ role: "admin"
72864
+ },
72292
72865
  "coreBlocks.getBlockSettings": {
72293
72866
  capName: "core-blocks",
72294
72867
  capScope: "system",
@@ -72310,6 +72883,13 @@ Object.freeze({
72310
72883
  access: "view",
72311
72884
  role: "admin"
72312
72885
  },
72886
+ "coreBlocks.getTraces": {
72887
+ capName: "core-blocks",
72888
+ capScope: "system",
72889
+ addonId: null,
72890
+ access: "view",
72891
+ role: "admin"
72892
+ },
72313
72893
  "coreBlocks.getTypeDefs": {
72314
72894
  capName: "core-blocks",
72315
72895
  capScope: "system",
@@ -72345,6 +72925,13 @@ Object.freeze({
72345
72925
  access: "create",
72346
72926
  role: "admin"
72347
72927
  },
72928
+ "coreBlocks.reportAutomationRun": {
72929
+ capName: "core-blocks",
72930
+ capScope: "system",
72931
+ addonId: null,
72932
+ access: "create",
72933
+ role: "admin"
72934
+ },
72348
72935
  "coreBlocks.restart": {
72349
72936
  capName: "core-blocks",
72350
72937
  capScope: "system",
@@ -80918,7 +81505,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80918
81505
  label: t("Full brightness", "Luminosità massima"),
80919
81506
  phrase: t("Set {device} to full brightness", "Porta {device} alla massima luminosità"),
80920
81507
  icon: "sun",
80921
- args: { percentage: 100 }
81508
+ args: { level: 100 }
80922
81509
  },
80923
81510
  {
80924
81511
  id: "brightness.half",
@@ -80926,7 +81513,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80926
81513
  label: t("Half brightness", "Luminosità a metà"),
80927
81514
  phrase: t("Dim {device} to half", "Abbassa {device} a metà luminosità"),
80928
81515
  icon: "sun-dim",
80929
- args: { percentage: 50 }
81516
+ args: { level: 50 }
80930
81517
  },
80931
81518
  {
80932
81519
  id: "brightness.off",
@@ -80934,16 +81521,16 @@ var ACTUATOR_CAP_VOCABULARY = {
80934
81521
  label: t("Brightness off", "Luminosità a zero"),
80935
81522
  phrase: t("Dim {device} to zero", "Abbassa {device} a zero"),
80936
81523
  icon: "lightbulb-off",
80937
- args: { percentage: 0 }
81524
+ args: { level: 0 }
80938
81525
  },
80939
81526
  {
80940
81527
  id: "brightness.set",
80941
81528
  method: "setBrightness",
80942
81529
  label: t("Set brightness", "Imposta la luminosità"),
80943
- 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}"),
80944
81531
  icon: "sun-medium",
80945
81532
  openArgs: [{
80946
- key: "percentage",
81533
+ key: "level",
80947
81534
  label: PERCENT,
80948
81535
  offer: {
80949
81536
  kind: "number",
@@ -80957,13 +81544,13 @@ var ACTUATOR_CAP_VOCABULARY = {
80957
81544
  ],
80958
81545
  state: [{
80959
81546
  kind: "numeric",
80960
- field: "percentage",
81547
+ field: "level",
80961
81548
  quantity: t("the brightness", "la luminosità"),
80962
81549
  label: t("Brightness", "Luminosità"),
80963
81550
  unit: "%",
80964
81551
  icon: "sun"
80965
81552
  }],
80966
- events: { onBrightnessChanged: { notOperatorFacing: "the `percentage` state trigger reads the same change" } }
81553
+ events: { onBrightnessChanged: { notOperatorFacing: "the `level` state trigger reads the same change" } }
80967
81554
  },
80968
81555
  color: {
80969
81556
  scope: "device",
@@ -80977,7 +81564,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80977
81564
  icon: "sun",
80978
81565
  args: { color: {
80979
81566
  mode: "mired",
80980
- mireds: 370
81567
+ colorTemperature: 370
80981
81568
  } }
80982
81569
  },
80983
81570
  {
@@ -80988,7 +81575,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80988
81575
  icon: "snowflake",
80989
81576
  args: { color: {
80990
81577
  mode: "mired",
80991
- mireds: 200
81578
+ colorTemperature: 200
80992
81579
  } }
80993
81580
  },
80994
81581
  {
@@ -81602,7 +82189,7 @@ var ACTUATOR_CAP_VOCABULARY = {
81602
82189
  label: t("Fan off", "Ventilatore spento"),
81603
82190
  phrase: t("Stop {device}", "Ferma {device}"),
81604
82191
  icon: "power-off",
81605
- args: { percentage: 0 }
82192
+ args: { speed: 0 }
81606
82193
  },
81607
82194
  {
81608
82195
  id: "fan.full",
@@ -81610,16 +82197,16 @@ var ACTUATOR_CAP_VOCABULARY = {
81610
82197
  label: t("Fan full", "Ventilatore al massimo"),
81611
82198
  phrase: t("Run {device} at full speed", "Porta {device} alla massima velocità"),
81612
82199
  icon: "fan",
81613
- args: { percentage: 100 }
82200
+ args: { speed: 100 }
81614
82201
  },
81615
82202
  {
81616
82203
  id: "fan.speed",
81617
82204
  method: "setPercentage",
81618
82205
  label: t("Set speed", "Imposta la velocità"),
81619
- 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}"),
81620
82207
  icon: "fan",
81621
82208
  openArgs: [{
81622
- key: "percentage",
82209
+ key: "speed",
81623
82210
  label: t("Speed", "Velocità"),
81624
82211
  offer: {
81625
82212
  kind: "number",
@@ -81681,7 +82268,7 @@ var ACTUATOR_CAP_VOCABULARY = {
81681
82268
  ],
81682
82269
  state: [{
81683
82270
  kind: "numeric",
81684
- field: "percentage",
82271
+ field: "speed",
81685
82272
  quantity: t("the speed", "la velocità"),
81686
82273
  label: t("Speed", "Velocità"),
81687
82274
  unit: "%",
@@ -82479,7 +83066,7 @@ var APPLIANCE_CAP_VOCABULARY = {
82479
83066
  },
82480
83067
  {
82481
83068
  kind: "numeric",
82482
- field: "progressPercent",
83069
+ field: "progress",
82483
83070
  quantity: t("the cycle progress", "l’avanzamento del lavaggio"),
82484
83071
  label: t("Progress", "Avanzamento"),
82485
83072
  unit: "%",
@@ -82837,10 +83424,10 @@ var APPLIANCE_CAP_VOCABULARY = {
82837
83424
  id: "media.seek",
82838
83425
  method: "seek",
82839
83426
  label: t("Seek", "Vai al punto"),
82840
- phrase: t("Move {device} to {positionMs}", "Porta {device} al punto {positionMs}"),
83427
+ phrase: t("Move {device} to {position}", "Porta {device} al punto {position}"),
82841
83428
  icon: "fast-forward",
82842
83429
  openArgs: [{
82843
- key: "positionMs",
83430
+ key: "position",
82844
83431
  label: t("Position", "Posizione"),
82845
83432
  offer: {
82846
83433
  kind: "number",
@@ -83889,7 +84476,7 @@ var SENSOR_CAP_VOCABULARY = {
83889
84476
  state: [
83890
84477
  {
83891
84478
  kind: "numeric",
83892
- field: "co2Ppm",
84479
+ field: "co2",
83893
84480
  quantity: t("the CO₂", "la CO₂"),
83894
84481
  label: t("CO₂", "CO₂"),
83895
84482
  unit: "ppm",
@@ -83897,7 +84484,7 @@ var SENSOR_CAP_VOCABULARY = {
83897
84484
  },
83898
84485
  {
83899
84486
  kind: "numeric",
83900
- field: "vocPpb",
84487
+ field: "voc",
83901
84488
  quantity: t("the VOC level", "il livello di COV"),
83902
84489
  label: t("VOC", "COV"),
83903
84490
  unit: "ppb",
@@ -83933,7 +84520,7 @@ var SENSOR_CAP_VOCABULARY = {
83933
84520
  noun: t("Light level", "Luminosità ambiente"),
83934
84521
  state: [{
83935
84522
  kind: "numeric",
83936
- field: "lux",
84523
+ field: "illuminance",
83937
84524
  quantity: t("the light level", "la luminosità"),
83938
84525
  label: t("Illuminance", "Illuminamento"),
83939
84526
  unit: "lx",
@@ -84136,7 +84723,7 @@ var SENSOR_CAP_VOCABULARY = {
84136
84723
  noun: t("Humidity", "Umidità"),
84137
84724
  state: [{
84138
84725
  kind: "numeric",
84139
- field: "percent",
84726
+ field: "humidity",
84140
84727
  quantity: t("the humidity", "l’umidità"),
84141
84728
  label: t("Humidity", "Umidità"),
84142
84729
  unit: "%",
@@ -84184,7 +84771,7 @@ var SENSOR_CAP_VOCABULARY = {
84184
84771
  state: [
84185
84772
  {
84186
84773
  kind: "numeric",
84187
- field: "watts",
84774
+ field: "power",
84188
84775
  quantity: t("the power draw", "il consumo"),
84189
84776
  label: t("Power", "Potenza"),
84190
84777
  unit: "W",
@@ -84192,7 +84779,7 @@ var SENSOR_CAP_VOCABULARY = {
84192
84779
  },
84193
84780
  {
84194
84781
  kind: "numeric",
84195
- field: "kwhTotal",
84782
+ field: "energy",
84196
84783
  quantity: t("the energy used", "l’energia consumata"),
84197
84784
  label: t("Energy", "Energia"),
84198
84785
  unit: "kWh",
@@ -84200,7 +84787,7 @@ var SENSOR_CAP_VOCABULARY = {
84200
84787
  },
84201
84788
  {
84202
84789
  kind: "numeric",
84203
- field: "volts",
84790
+ field: "voltage",
84204
84791
  quantity: t("the voltage", "la tensione"),
84205
84792
  label: t("Voltage", "Tensione"),
84206
84793
  unit: "V",
@@ -84208,7 +84795,7 @@ var SENSOR_CAP_VOCABULARY = {
84208
84795
  },
84209
84796
  {
84210
84797
  kind: "numeric",
84211
- field: "amps",
84798
+ field: "current",
84212
84799
  quantity: t("the current", "la corrente"),
84213
84800
  label: t("Current", "Corrente"),
84214
84801
  unit: "A",
@@ -84225,13 +84812,6 @@ var SENSOR_CAP_VOCABULARY = {
84225
84812
  label: t("Where", "Posizione"),
84226
84813
  phrase: t("When {device} is at {value}", "Quando {device} è in {value}"),
84227
84814
  icon: "map-pin"
84228
- }, {
84229
- kind: "numeric",
84230
- field: "batteryPercent",
84231
- quantity: t("the phone battery", "la batteria del telefono"),
84232
- label: t("Battery", "Batteria"),
84233
- unit: "%",
84234
- icon: "battery"
84235
84815
  }]
84236
84816
  },
84237
84817
  "pressure-sensor": {
@@ -84239,7 +84819,7 @@ var SENSOR_CAP_VOCABULARY = {
84239
84819
  noun: t("Pressure", "Pressione"),
84240
84820
  state: [{
84241
84821
  kind: "numeric",
84242
- field: "hpa",
84822
+ field: "pressure",
84243
84823
  quantity: t("the pressure", "la pressione"),
84244
84824
  label: t("Pressure", "Pressione"),
84245
84825
  unit: "hPa",
@@ -84275,7 +84855,7 @@ var SENSOR_CAP_VOCABULARY = {
84275
84855
  noun: t("Temperature", "Temperatura"),
84276
84856
  state: [{
84277
84857
  kind: "numeric",
84278
- field: "celsius",
84858
+ field: "temperature",
84279
84859
  quantity: t("the temperature", "la temperatura"),
84280
84860
  label: t("Temperature", "Temperatura"),
84281
84861
  unit: "°C",
@@ -84346,7 +84926,7 @@ var SENSOR_CAP_VOCABULARY = {
84346
84926
  state: [
84347
84927
  {
84348
84928
  kind: "numeric",
84349
- field: "percentage",
84929
+ field: "level",
84350
84930
  quantity: t("the battery level", "il livello della batteria"),
84351
84931
  label: t("Battery", "Batteria"),
84352
84932
  unit: "%",
@@ -84388,7 +84968,7 @@ var SENSOR_CAP_VOCABULARY = {
84388
84968
  icon: "battery-charging"
84389
84969
  }
84390
84970
  ],
84391
- 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" } },
84392
84972
  extraEvents: [{
84393
84973
  category: EventCategory.DeviceAwake,
84394
84974
  label: t("Woke up", "Risvegliato"),
@@ -84406,7 +84986,7 @@ var SENSOR_CAP_VOCABULARY = {
84406
84986
  noun: t("Network link", "Collegamento di rete"),
84407
84987
  state: [{
84408
84988
  kind: "numeric",
84409
- field: "signalPercent",
84989
+ field: "signal",
84410
84990
  quantity: t("the signal strength", "l’intensità del segnale"),
84411
84991
  label: t("Signal", "Segnale"),
84412
84992
  unit: "%",
@@ -84499,6 +85079,8 @@ var ML = "model and training machinery — no device action";
84499
85079
  var INTERNAL = "internal plumbing between addons — never operator-facing";
84500
85080
  /** Every system-scoped cap, with why it is not a device's vocabulary. */
84501
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"),
84502
85084
  "addon-pages": system(UI_PLUMBING),
84503
85085
  "addon-pages-source": system(UI_PLUMBING),
84504
85086
  "addon-routes": system(UI_PLUMBING),
@@ -84695,6 +85277,31 @@ Object.freeze({
84695
85277
  form: "single",
84696
85278
  optional: false
84697
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
+ }],
84698
85305
  "addonSettings.getDeviceSettings": [{
84699
85306
  name: "deviceId",
84700
85307
  form: "single",
@@ -89738,6 +90345,7 @@ var BackgroundRunRegistry = class {
89738
90345
  }
89739
90346
  }
89740
90347
  };
90348
+ String.raw`(?<=^|["'(\s>,\[{])`;
89741
90349
  var MB = 1024 * 1024;
89742
90350
  var DEFAULT_POOL_MEMORY_POLICY = {
89743
90351
  sampleIntervalMs: 6e4,