@camstack/addon-pipeline 1.2.631 → 1.2.633

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 +12 -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-BcYQkMoD.mjs} +2 -2
  5. package/dist/{custom-model-catalog-5IT-gwL2.js → custom-model-catalog-Bsfg7BkC.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-DQcSGwGp.js} +1602 -916
  9. package/dist/{dist-DV6W_qKY.mjs → dist-rqX8QLAU.mjs} +1602 -916
  10. package/dist/{model-catalogs-lWWyJZqw.js → model-catalogs-BIQmZA1O.js} +1 -1
  11. package/dist/{model-catalogs-CI9KoY4D.mjs → model-catalogs-C3fsKl2H.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-BQ7P2No5.js → node-Bq2OdtOJ.js} +1 -1
  15. package/dist/{node-KEh_tmT2.mjs → node-CxeIzhfZ.mjs} +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-ChQO96Xb.mjs → process-memory-BJ4dGThE.mjs} +1 -1
  19. package/dist/{process-memory-C-3bSKF5.js → process-memory-DscuOZuW.js} +1 -1
  20. package/dist/recorder/index.js +3 -2
  21. package/dist/recorder/index.mjs +3 -2
  22. package/dist/{segment-demux-js-Dy75tkrR.mjs → segment-demux-js-CTN4M0L6.mjs} +1 -1
  23. package/dist/{segment-demux-js-8YepQCG3.js → segment-demux-js-CUvevVpm.js} +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-ypdxRUwS.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-BOIc62S5.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-CY6cGkjo.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-DskyymXn.mjs} +2 -2
  31. package/dist/stream-broker/index.js +42 -11
  32. package/dist/stream-broker/index.mjs +42 -11
  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
  import { createHash } from "node:crypto";
2
- //#region ../types/dist/event-category-CT-hz5Rb.mjs
2
+ //#region ../types/dist/event-category-DmZDPDf_.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -626,7 +626,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
626
626
  * (a track was promoted to a stationary-registry entry) or departs (the
627
627
  * object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
628
628
  * the dedicated "Stationary" section — never the live event feed. Payload:
629
- * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
629
+ * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp, observedAt, zones }`.
630
630
  */
631
631
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
632
632
  /**
@@ -764,6 +764,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
764
764
  */
765
765
  EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
766
766
  /**
767
+ * An action run started or ended. Emitted by the `actions` surface.
768
+ * Payload: `ActionRunChanged` (`{ actionId, runId, isRunning, outcome?, lastRunAt, lastError }`).
769
+ */
770
+ EventCategory["ActionsRunChanged"] = "actions.onRunChanged";
771
+ /**
767
772
  * Progress update from a running model conversion job.
768
773
  * Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
769
774
  * Emitted by `addon-model-studio` on the converting node.
@@ -5575,7 +5580,7 @@ var ZodIssueCode = {
5575
5580
  var ZodFirstPartyTypeKind;
5576
5581
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5577
5582
  //#endregion
5578
- //#region ../types/dist/sleep--jXS9hWo.mjs
5583
+ //#region ../types/dist/sleep-Crlmu_tO.mjs
5579
5584
  /**
5580
5585
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5581
5586
  * window to float samples (D455).
@@ -9034,6 +9039,8 @@ Object.freeze({ "doorbell": "lastPressedAt" });
9034
9039
  /** Every capability name, so addon code can check a name without the schema graph. */
9035
9040
  var CAP_NAMES = Object.freeze([
9036
9041
  "accessories",
9042
+ "action-source",
9043
+ "actions",
9037
9044
  "addon-pages",
9038
9045
  "addon-pages-source",
9039
9046
  "addon-routes",
@@ -9222,11 +9229,11 @@ var CAP_NAMES = Object.freeze([
9222
9229
  Object.freeze({
9223
9230
  "air-quality-sensor": [
9224
9231
  {
9225
- "path": "co2Ppm",
9232
+ "path": "co2",
9226
9233
  "valueType": "number"
9227
9234
  },
9228
9235
  {
9229
- "path": "vocPpb",
9236
+ "path": "voc",
9230
9237
  "valueType": "number"
9231
9238
  },
9232
9239
  {
@@ -9276,7 +9283,7 @@ Object.freeze({
9276
9283
  }
9277
9284
  ],
9278
9285
  "ambient-light-sensor": [{
9279
- "path": "lux",
9286
+ "path": "illuminance",
9280
9287
  "valueType": "number"
9281
9288
  }],
9282
9289
  "audio-metrics": [
@@ -9337,7 +9344,7 @@ Object.freeze({
9337
9344
  ],
9338
9345
  "battery": [
9339
9346
  {
9340
- "path": "percentage",
9347
+ "path": "level",
9341
9348
  "valueType": "number"
9342
9349
  },
9343
9350
  {
@@ -9359,7 +9366,7 @@ Object.freeze({
9359
9366
  "valueType": "number"
9360
9367
  },
9361
9368
  {
9362
- "path": "volts",
9369
+ "path": "voltage",
9363
9370
  "valueType": "number"
9364
9371
  },
9365
9372
  {
@@ -9380,7 +9387,7 @@ Object.freeze({
9380
9387
  "valueType": "boolean"
9381
9388
  }],
9382
9389
  "brightness": [{
9383
- "path": "percentage",
9390
+ "path": "level",
9384
9391
  "valueType": "number"
9385
9392
  }],
9386
9393
  "broker": [{
@@ -9581,7 +9588,7 @@ Object.freeze({
9581
9588
  "valueType": "number"
9582
9589
  },
9583
9590
  {
9584
- "path": "mireds",
9591
+ "path": "colorTemperature",
9585
9592
  "valueType": "number"
9586
9593
  }
9587
9594
  ],
@@ -9760,15 +9767,15 @@ Object.freeze({
9760
9767
  "valueType": "string"
9761
9768
  },
9762
9769
  {
9763
- "path": "remainingSeconds",
9770
+ "path": "remaining",
9764
9771
  "valueType": "number"
9765
9772
  },
9766
9773
  {
9767
- "path": "delayedStartSeconds",
9774
+ "path": "delayedStart",
9768
9775
  "valueType": "number"
9769
9776
  },
9770
9777
  {
9771
- "path": "progressPercent",
9778
+ "path": "progress",
9772
9779
  "valueType": "number"
9773
9780
  },
9774
9781
  {
@@ -9882,7 +9889,7 @@ Object.freeze({
9882
9889
  ],
9883
9890
  "fan-control": [
9884
9891
  {
9885
- "path": "percentage",
9892
+ "path": "speed",
9886
9893
  "valueType": "number"
9887
9894
  },
9888
9895
  {
@@ -9982,7 +9989,7 @@ Object.freeze({
9982
9989
  }
9983
9990
  ],
9984
9991
  "humidity-sensor": [{
9985
- "path": "percent",
9992
+ "path": "humidity",
9986
9993
  "valueType": "number"
9987
9994
  }],
9988
9995
  "image": [{
@@ -10092,7 +10099,7 @@ Object.freeze({
10092
10099
  "valueType": "number"
10093
10100
  },
10094
10101
  {
10095
- "path": "progressPercent",
10102
+ "path": "progress",
10096
10103
  "valueType": "number"
10097
10104
  },
10098
10105
  {
@@ -10171,11 +10178,11 @@ Object.freeze({
10171
10178
  "valueType": "string"
10172
10179
  },
10173
10180
  {
10174
- "path": "positionMs",
10181
+ "path": "position",
10175
10182
  "valueType": "number"
10176
10183
  },
10177
10184
  {
10178
- "path": "durationMs",
10185
+ "path": "duration",
10179
10186
  "valueType": "number"
10180
10187
  },
10181
10188
  {
@@ -10277,11 +10284,11 @@ Object.freeze({
10277
10284
  ]
10278
10285
  },
10279
10286
  {
10280
- "path": "signalPercent",
10287
+ "path": "signal",
10281
10288
  "valueType": "number"
10282
10289
  },
10283
10290
  {
10284
- "path": "rssiDbm",
10291
+ "path": "rssi",
10285
10292
  "valueType": "number"
10286
10293
  },
10287
10294
  {
@@ -10336,6 +10343,18 @@ Object.freeze({
10336
10343
  "path": "foodLevel",
10337
10344
  "valueType": "number"
10338
10345
  },
10346
+ {
10347
+ "path": "bowlFood",
10348
+ "valueType": "number"
10349
+ },
10350
+ {
10351
+ "path": "eating",
10352
+ "valueType": "boolean"
10353
+ },
10354
+ {
10355
+ "path": "supportsMarkRefilled",
10356
+ "valueType": "boolean"
10357
+ },
10339
10358
  {
10340
10359
  "path": "food1",
10341
10360
  "valueType": "number"
@@ -10400,23 +10419,23 @@ Object.freeze({
10400
10419
  ],
10401
10420
  "power-meter": [
10402
10421
  {
10403
- "path": "watts",
10422
+ "path": "power",
10404
10423
  "valueType": "number"
10405
10424
  },
10406
10425
  {
10407
- "path": "kwhTotal",
10426
+ "path": "energy",
10408
10427
  "valueType": "number"
10409
10428
  },
10410
10429
  {
10411
- "path": "volts",
10430
+ "path": "voltage",
10412
10431
  "valueType": "number"
10413
10432
  },
10414
10433
  {
10415
- "path": "amps",
10434
+ "path": "current",
10416
10435
  "valueType": "number"
10417
10436
  },
10418
10437
  {
10419
- "path": "va",
10438
+ "path": "apparentPower",
10420
10439
  "valueType": "number"
10421
10440
  },
10422
10441
  {
@@ -10444,14 +10463,10 @@ Object.freeze({
10444
10463
  {
10445
10464
  "path": "gps.accuracyMeters",
10446
10465
  "valueType": "number"
10447
- },
10448
- {
10449
- "path": "batteryPercent",
10450
- "valueType": "number"
10451
10466
  }
10452
10467
  ],
10453
10468
  "pressure-sensor": [{
10454
- "path": "hpa",
10469
+ "path": "pressure",
10455
10470
  "valueType": "number"
10456
10471
  }],
10457
10472
  "privacy-mask": [{
@@ -10765,7 +10780,7 @@ Object.freeze({
10765
10780
  "valueType": "boolean"
10766
10781
  }],
10767
10782
  "temperature-sensor": [{
10768
- "path": "celsius",
10783
+ "path": "temperature",
10769
10784
  "valueType": "number"
10770
10785
  }],
10771
10786
  "update": [
@@ -10813,7 +10828,7 @@ Object.freeze({
10813
10828
  "valueType": "string"
10814
10829
  },
10815
10830
  {
10816
- "path": "progressPercent",
10831
+ "path": "progress",
10817
10832
  "valueType": "number"
10818
10833
  },
10819
10834
  {
@@ -11450,7 +11465,7 @@ function errMsg(err) {
11450
11465
  return String(err);
11451
11466
  }
11452
11467
  //#endregion
11453
- //#region ../types/dist/composition-Bb8iwHEJ.mjs
11468
+ //#region ../types/dist/composition-DwA0uVW5.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",
@@ -21624,12 +22363,13 @@ var CHANGED = {
21624
22363
  expr: "now"
21625
22364
  };
21626
22365
  function numeric(input) {
22366
+ const { alsoFrom, ...base } = input;
21627
22367
  return {
21628
- ...input,
22368
+ ...base,
21629
22369
  family: "numeric",
21630
22370
  deviceType: DeviceType.Sensor,
21631
22371
  memberDeviceType: DeviceType.Sensor,
21632
- memberDeviceTypes: [DeviceType.Sensor],
22372
+ memberDeviceTypes: [DeviceType.Sensor, ...alsoFrom?.deviceTypes ?? []],
21633
22373
  activeWhen: void 0,
21634
22374
  commandCaps: [],
21635
22375
  baseUnit: baseUnitOf(input.quantity),
@@ -21637,9 +22377,10 @@ function numeric(input) {
21637
22377
  accept: [{
21638
22378
  cap: input.outputCap,
21639
22379
  fieldPath: input.outputField
21640
- }]
22380
+ }, ...alsoFrom === void 0 ? [] : [alsoFrom.accept]]
21641
22381
  };
21642
22382
  }
22383
+ var CLIMATE_DEVICES = [DeviceType.Thermostat, DeviceType.Climate];
21643
22384
  function binary(input) {
21644
22385
  return {
21645
22386
  ...input,
@@ -21661,29 +22402,43 @@ var GROUP_TEMPLATES = [
21661
22402
  summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
21662
22403
  role: DeviceRole.TemperatureSensor,
21663
22404
  outputCap: "temperature-sensor",
21664
- outputField: "celsius",
22405
+ outputField: "temperature",
21665
22406
  quantity: "temperature",
21666
22407
  defaultAggregate: "mean",
21667
22408
  empty: "null",
21668
- companions: [NOW$2]
22409
+ companions: [NOW$2],
22410
+ alsoFrom: {
22411
+ deviceTypes: CLIMATE_DEVICES,
22412
+ accept: {
22413
+ cap: "climate-control",
22414
+ fieldPath: "currentTemp"
22415
+ }
22416
+ }
21669
22417
  }),
21670
22418
  numeric({
21671
22419
  id: "humidity",
21672
22420
  summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
21673
22421
  role: DeviceRole.HumiditySensor,
21674
22422
  outputCap: "humidity-sensor",
21675
- outputField: "percent",
22423
+ outputField: "humidity",
21676
22424
  quantity: "humidity",
21677
22425
  defaultAggregate: "mean",
21678
22426
  empty: "null",
21679
- companions: [NOW$2]
22427
+ companions: [NOW$2],
22428
+ alsoFrom: {
22429
+ deviceTypes: CLIMATE_DEVICES,
22430
+ accept: {
22431
+ cap: "climate-control",
22432
+ fieldPath: "currentHumidity"
22433
+ }
22434
+ }
21680
22435
  }),
21681
22436
  numeric({
21682
22437
  id: "pressure",
21683
22438
  summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
21684
22439
  role: DeviceRole.PressureSensor,
21685
22440
  outputCap: "pressure-sensor",
21686
- outputField: "hpa",
22441
+ outputField: "pressure",
21687
22442
  quantity: "pressure",
21688
22443
  defaultAggregate: "mean",
21689
22444
  empty: "null",
@@ -21694,7 +22449,7 @@ var GROUP_TEMPLATES = [
21694
22449
  summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
21695
22450
  role: DeviceRole.AmbientLightSensor,
21696
22451
  outputCap: "ambient-light-sensor",
21697
- outputField: "lux",
22452
+ outputField: "illuminance",
21698
22453
  quantity: "illuminance",
21699
22454
  defaultAggregate: "mean",
21700
22455
  empty: "null",
@@ -21705,7 +22460,7 @@ var GROUP_TEMPLATES = [
21705
22460
  summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
21706
22461
  role: DeviceRole.PowerSensor,
21707
22462
  outputCap: "power-meter",
21708
- outputField: "watts",
22463
+ outputField: "power",
21709
22464
  quantity: "power",
21710
22465
  defaultAggregate: "sum",
21711
22466
  empty: "omit",
@@ -21716,7 +22471,7 @@ var GROUP_TEMPLATES = [
21716
22471
  summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
21717
22472
  role: DeviceRole.EnergySensor,
21718
22473
  outputCap: "power-meter",
21719
- outputField: "kwhTotal",
22474
+ outputField: "energy",
21720
22475
  quantity: "energy",
21721
22476
  defaultAggregate: "sum",
21722
22477
  empty: "omit",
@@ -21727,7 +22482,7 @@ var GROUP_TEMPLATES = [
21727
22482
  summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
21728
22483
  role: DeviceRole.BatterySensor,
21729
22484
  outputCap: "battery",
21730
- outputField: "percentage",
22485
+ outputField: "level",
21731
22486
  quantity: "percentage",
21732
22487
  defaultAggregate: "mean",
21733
22488
  empty: "null",
@@ -22305,7 +23060,18 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
22305
23060
  kind: "mutation",
22306
23061
  auth: "admin",
22307
23062
  access: "view"
22308
- });
23063
+ }), method(object({
23064
+ blockId: string().min(1),
23065
+ trace: AutomationRunTraceSchema.optional(),
23066
+ runStartedAt: number().optional(),
23067
+ keepTraces: number().int().min(1).max(100).optional()
23068
+ }), _void(), {
23069
+ kind: "mutation",
23070
+ auth: "admin"
23071
+ }), method(object({ blockId: string().min(1) }), AutomationStateDocSchema, { auth: "admin" }), method(object({ blockId: string().min(1) }), object({
23072
+ traces: array(AutomationRunTraceSchema),
23073
+ lastError: string().nullable()
23074
+ }), { auth: "admin" });
22309
23075
  /**
22310
23076
  * `custom-model-registry` — collection cap exposing operator-registered
22311
23077
  * custom detection models. Each provider (today: `addon-model-studio`)
@@ -45950,6 +46716,17 @@ var LlmProfileSchema = object({
45950
46716
  * field in its editor only when its descriptor lists it in `profileFields`. */
45951
46717
  topK: number().int().positive().optional(),
45952
46718
  maxTokens: number().int().positive().optional(),
46719
+ /** How hard the model "thinks" before it answers. Omitted = the model's own
46720
+ * default. `off` matters for structured output: a thinking model can spend
46721
+ * the whole completion on reasoning and return an empty `content`. Carried
46722
+ * only by the wires that have it — a kind offers the field in its editor
46723
+ * only when its descriptor lists it in `profileFields`. */
46724
+ reasoningEffort: _enum([
46725
+ "off",
46726
+ "low",
46727
+ "medium",
46728
+ "high"
46729
+ ]).optional(),
45953
46730
  /** Prompt context window. Advisory for cloud kinds (they enforce their own). */
45954
46731
  contextLength: number().int().positive().optional(),
45955
46732
  /** Default system prompt. A caller's `system` REPLACES it (never appends —
@@ -45988,6 +46765,7 @@ var LlmProfileFieldSchema = _enum([
45988
46765
  "topP",
45989
46766
  "topK",
45990
46767
  "maxTokens",
46768
+ "reasoningEffort",
45991
46769
  "contextLength",
45992
46770
  "systemPrompt",
45993
46771
  "timeoutMs",
@@ -47180,710 +47958,391 @@ method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _v
47180
47958
  * adapter picks what it supports and the degrade engine filters the rest.
47181
47959
  */
47182
47960
  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
- };
47961
+ "image",
47962
+ "video",
47963
+ "gif",
47964
+ "audio",
47965
+ "icon"
47966
+ ]);
47967
+ /**
47968
+ * A single attachment. Exactly one of `url` (remote source, most adapters
47969
+ * prefer this) or `bytes` (inline source; required for Pushover-style
47970
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
47971
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
47972
+ */
47973
+ var AttachmentSchema = object({
47974
+ mediaType: AttachmentMediaTypeSchema,
47975
+ url: string().optional(),
47976
+ bytes: _instanceof(Uint8Array).optional(),
47977
+ mime: string().optional(),
47978
+ name: string().optional()
47979
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
47980
+ var NotificationFormatSchema = _enum([
47981
+ "text",
47982
+ "markdown",
47983
+ "html"
47984
+ ]);
47985
+ /**
47986
+ * The CLOSED icon vocabulary an action button may use.
47987
+ *
47988
+ * A closed set, not a free string, and that is the whole point: an arbitrary
47989
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
47990
+ * notices — the same class of gap as a zone vocabulary nothing produced
47991
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
47992
+ * declares `actionIcons: false` and the degrade engine strips the field.
47993
+ *
47994
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
47995
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
47996
+ * differently.
47997
+ *
47998
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
47999
+ *
48000
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
48001
+ * the whole set onto its own renderer's vocabulary through a
48002
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
48003
+ * fallback, so adding a member here fails every adapter's build until someone
48004
+ * decides its glyph, which is the only place that decision can be made
48005
+ * honestly.
48006
+ *
48007
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
48008
+ * `disarm` straight through; iOS feeds that string to
48009
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
48010
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
48011
+ * mapping, and "the field is documented" is not "the value renders".
48012
+ *
48013
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
48014
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
48015
+ * so an addon that emits a token the running hub does not know does not lose an
48016
+ * icon — its whole `send` fails Zod validation and the notification never
48017
+ * arrives. Never emit a new token from an addon before the train carrying it.
48018
+ */
48019
+ var NotificationActionIconSchema = _enum([
48020
+ "acknowledge",
48021
+ "dismiss",
48022
+ "silence",
48023
+ "snooze",
48024
+ "view",
48025
+ "play",
48026
+ "open",
48027
+ "close",
48028
+ "lock",
48029
+ "unlock",
48030
+ "arm",
48031
+ "arm-home",
48032
+ "arm-away",
48033
+ "arm-night",
48034
+ "disarm",
48035
+ "light",
48036
+ "alert",
48037
+ "camera"
48038
+ ]);
48039
+ /** A single tap-through action button. */
48040
+ var NotificationActionSchema = object({
48041
+ id: string(),
48042
+ label: string(),
48043
+ url: string().optional(),
48044
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
48045
+ icon: NotificationActionIconSchema.optional(),
48046
+ /**
48047
+ * Renders in a warning style where the notifier supports it.
48048
+ *
48049
+ * A HINT, never a gate. The callback's authority is its token and nothing
48050
+ * else — see `notification-center/action-token.ts` for what that does and
48051
+ * does not buy.
48052
+ */
48053
+ destructive: boolean().optional(),
48054
+ /**
48055
+ * How the tap should REACH the url.
48056
+ *
48057
+ * `navigate` (absent, and every button authored before this field) opens it:
48058
+ * the phone leaves the notification and shows whatever the callback returns.
48059
+ * That is right for a button whose answer the operator wants to read.
48060
+ *
48061
+ * `background` fires it as a POST and stays put. It exists for the buttons
48062
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
48063
+ * an answer to the notification, and being thrown into a browser tab to
48064
+ * confirm it costs more attention than the notification did. A backend that
48065
+ * cannot do a background call renders it as an ordinary link (the adapters
48066
+ * fall back rather than dropping the button), so this is a preference, never
48067
+ * a requirement.
48068
+ */
48069
+ mode: _enum(["navigate", "background"]).optional()
48070
+ });
48071
+ /** A delivery parameter value: what a notifier declared it takes. */
48072
+ var DeliveryParamValueSchema = union$1([
48073
+ string(),
48074
+ number(),
48075
+ boolean()
48076
+ ]);
48077
+ /**
48078
+ * One extra delivery parameter a notifier KIND declares (D938). A rule that
48079
+ * addresses a target of that kind may set it; a kind that declares nothing is
48080
+ * never handed one. The admin UI derives its control from this descriptor —
48081
+ * no editor knows a service by name.
48082
+ *
48083
+ * Optional fields are `.optional()`, never `.default()` (a Zod default does not
48084
+ * run on the addon cap path).
48085
+ */
48086
+ var DeliveryParamSchema = object({
48087
+ key: string().min(1),
48088
+ label: string(),
48089
+ /** i18n key of the label, resolved by the declaring addon's namespace. */
48090
+ labelKey: string().optional(),
48091
+ type: _enum([
48092
+ "enum",
48093
+ "number",
48094
+ "boolean",
48095
+ "string"
48096
+ ]),
48097
+ /** `enum` only: the closed set of accepted values. */
48098
+ options: array(object({
48099
+ value: string(),
48100
+ label: string()
48101
+ })).optional(),
48102
+ /** `number` only. */
48103
+ min: number().optional(),
48104
+ max: number().optional(),
48105
+ /** What the notifier does when the rule sets nothing; shown as the placeholder. */
48106
+ default: DeliveryParamValueSchema.optional()
48107
+ });
48108
+ /**
48109
+ * The canonical notification. `body` is the only hard field (Apprise model).
48110
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
48111
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
48112
+ * the adapter maps this ordinal onto its native level. `level?` is an
48113
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
48114
+ * `priority` for that one target.
48115
+ */
48116
+ var NotificationSchema = object({
48117
+ body: string(),
48118
+ title: string().optional(),
48119
+ format: NotificationFormatSchema.default("text"),
48120
+ priority: number().int().min(1).max(5).default(3),
48121
+ level: string().optional(),
48122
+ attachments: array(AttachmentSchema).optional(),
48123
+ clickUrl: string().optional(),
48124
+ actions: array(NotificationActionSchema).optional(),
48125
+ sound: string().optional(),
48126
+ ttl: number().optional(),
48127
+ tag: string().optional(),
48128
+ deviceId: number().optional(),
48129
+ eventId: string().optional(),
48130
+ metadata: record(string(), unknown()).optional(),
48131
+ /**
48132
+ * Values for the parameters the target's kind DECLARES in
48133
+ * {@link TargetKindSchema} `deliveryParams`. Already validated against the
48134
+ * declaration by the sender; a notifier reads only the keys it declared.
48135
+ */
48136
+ delivery: record(string(), DeliveryParamValueSchema).optional()
48137
+ });
48138
+ /** One declared native severity/priority level for a kind. */
48139
+ var TargetKindLevelSchema = object({
48140
+ id: string(),
48141
+ label: string(),
48142
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
48143
+ ordinal: number().int().min(1).max(5).nullable(),
48144
+ flags: object({
48145
+ critical: boolean().optional(),
48146
+ silent: boolean().optional(),
48147
+ noPush: boolean().optional()
48148
+ }).optional(),
48149
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
48150
+ requires: array(string()).optional(),
48151
+ description: string().optional()
48152
+ });
48153
+ /** The full capability block consulted before dispatch. */
48154
+ var TargetKindCapsSchema = object({
48155
+ attachments: object({
48156
+ mediaTypes: array(AttachmentMediaTypeSchema),
48157
+ mode: _enum([
48158
+ "url",
48159
+ "bytes",
48160
+ "both"
48161
+ ]),
48162
+ max: number().int().nonnegative(),
48163
+ maxBytes: number().int().positive().optional()
48164
+ }),
48165
+ /** Max action buttons (0 = none). */
48166
+ actions: number().int().nonnegative(),
48167
+ /**
48168
+ * Whether this kind renders a per-action ICON.
48169
+ *
48170
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
48171
+ * run on the addon cap path — three production failures in one day taught
48172
+ * this repo that once. Absent is read as false by the degrade engine, which
48173
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
48174
+ * assumed and dropped costs the operator's trust in the field.
48175
+ */
48176
+ actionIcons: boolean().optional(),
48177
+ levels: array(TargetKindLevelSchema),
48178
+ format: array(NotificationFormatSchema),
48179
+ clickUrl: boolean(),
48180
+ sound: boolean(),
48181
+ ttl: boolean(),
48182
+ bodyMaxLen: number().int().positive()
48183
+ });
48184
+ /**
48185
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
48186
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
48187
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
48188
+ * the union is large and not meant for runtime validation here; the exported
48189
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
48190
+ */
48191
+ var ConfigSchemaPassthrough$2 = unknown();
48192
+ var TargetKindSchema = object({
48193
+ kind: string(),
48194
+ label: string(),
48195
+ icon: string(),
48196
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
48197
+ addonId: string(),
48198
+ /**
48199
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
48200
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
48201
+ * when the addon bundles no icon for that kind — the client then falls back
48202
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
48203
+ *
48204
+ * Root-relative on purpose: it resolves against whatever origin serves a web
48205
+ * client, and a native client joins it onto its own hub base.
48206
+ *
48207
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
48208
+ * field that survived only because the runtime cap-router forwards provider
48209
+ * output verbatim — so every consumer had to re-declare it by hand to stop
48210
+ * its own Zod parse from stripping it, and the whole arrangement would have
48211
+ * broken silently the moment output validation was tightened anywhere.
48212
+ */
48213
+ iconUrl: string().optional(),
48214
+ /**
48215
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
48216
+ *
48217
+ * The server knows this and therefore says it, because the client cannot
48218
+ * safely guess: a React-Native client renders SVG and raster through two
48219
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
48220
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
48221
+ * placeholder glyph for every vector icon while the web build looked fine.
48222
+ *
48223
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
48224
+ * not been updated — a client that cannot determine the type should prefer
48225
+ * its raster path, which is the safe default for an unknown image.
48226
+ */
48227
+ iconMediaType: string().optional(),
48228
+ configSchema: ConfigSchemaPassthrough$2,
48229
+ /**
48230
+ * The config keys that decide WHERE this kind's secrets are sent (server,
48231
+ * base URL, instance, chat, recipient). A save that changes any of them
48232
+ * while leaving a secret redacted is refused by the provider (D634), so an
48233
+ * editor uses this to ask for the secret on exactly those edits instead of
48234
+ * on every edit.
48235
+ *
48236
+ * Optional: absent from a provider that holds no secret (the Home Assistant
48237
+ * kinds) and from a provider older than this field — an editor that finds
48238
+ * it absent must assume ANY edit may need the secret.
48239
+ */
48240
+ destinationKeys: array(string()).optional(),
48241
+ supportsDiscovery: boolean(),
48242
+ /**
48243
+ * Extra delivery parameters a rule may set for a target of this kind (D938).
48244
+ * Absent = none; the rule editor then offers nothing.
48245
+ */
48246
+ deliveryParams: array(DeliveryParamSchema).optional(),
48247
+ caps: TargetKindCapsSchema
48248
+ });
48249
+ /**
48250
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
48251
+ * (return a presence marker only) when serving `listTargets` — never
48252
+ * round-trip a stored secret to the UI.
48253
+ */
48254
+ var TargetSchema = object({
48255
+ id: string(),
48256
+ name: string(),
48257
+ kind: string(),
48258
+ addonId: string(),
48259
+ enabled: boolean(),
48260
+ config: record(string(), unknown())
48261
+ });
48262
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
48263
+ var DiscoveredTargetSchema = object({
48264
+ kind: string(),
48265
+ suggestedName: string(),
48266
+ config: record(string(), unknown())
48267
+ });
48268
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
48269
+ var RenderedAsSchema = object({
48270
+ level: string(),
48271
+ format: NotificationFormatSchema,
48272
+ attachmentsSent: number().int().nonnegative(),
48273
+ actionsSent: number().int().nonnegative(),
48274
+ truncated: boolean(),
48275
+ dropped: array(string())
48276
+ });
48277
+ var SendResultSchema = object({
48278
+ success: boolean(),
48279
+ error: string().optional(),
48280
+ renderedAs: RenderedAsSchema.optional()
48281
+ });
48282
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
48283
+ var TestResultSchema = SendResultSchema;
48284
+ /**
48285
+ * Pre-save credential probe against a partial (unsaved) target config.
48286
+ * `resolvedUrl` never includes secrets (token stripped from path).
48287
+ */
48288
+ var ProbeTargetConfigResultSchema = object({
48289
+ ok: boolean(),
48290
+ resolvedUrl: string(),
48291
+ httpStatus: number().int().optional(),
48292
+ stateInstance: string().optional(),
48293
+ error: string().optional()
48294
+ });
48295
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
48296
+ kind: string(),
48297
+ config: record(string(), unknown()).optional(),
48298
+ /**
48299
+ * When editing / duplicating, the blank password field never round-trips
48300
+ * the stored token. Pass the source target id so the provider can restore
48301
+ * secrets server-side before dialling (same merge as upsert).
48302
+ */
48303
+ targetId: string().optional()
48304
+ }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
48305
+ kind: string(),
48306
+ config: record(string(), unknown()),
48307
+ /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
48308
+ targetId: string().optional()
48309
+ }), ProbeTargetConfigResultSchema, {
48310
+ kind: "mutation",
48311
+ auth: "admin"
48312
+ }), method(object({
48313
+ targetId: string(),
48314
+ notification: NotificationSchema
48315
+ }), SendResultSchema, {
48316
+ kind: "mutation",
48317
+ auth: "admin"
48318
+ }), method(object({
48319
+ targetId: string(),
48320
+ sample: NotificationSchema.optional()
48321
+ }), TestResultSchema, {
48322
+ kind: "mutation",
48323
+ auth: "admin"
48324
+ }), method(object({
48325
+ target: TargetSchema,
48326
+ /**
48327
+ * Duplicate flow: create a NEW target id while copying secrets from an
48328
+ * existing row. The UI never holds the stored token; blank / redacted
48329
+ * password fields are restored from this source (D634 still applies if
48330
+ * destinations diverge without a re-typed secret).
48331
+ */
48332
+ cloneSecretsFrom: string().optional()
48333
+ }), TargetSchema, {
48334
+ kind: "mutation",
48335
+ auth: "admin"
48336
+ }), method(object({ targetId: string() }), _void(), {
48337
+ kind: "mutation",
48338
+ auth: "admin"
48339
+ }), method(object({
48340
+ targetId: string(),
48341
+ enabled: boolean()
48342
+ }), _void(), {
48343
+ kind: "mutation",
48344
+ auth: "admin"
48345
+ });
47887
48346
  var AUDIO_MACRO_LABELS = [
47888
48347
  {
47889
48348
  id: "speech",
@@ -60799,7 +61258,9 @@ var ManifestShapeSchema = object({
60799
61258
  icon: string().optional()
60800
61259
  }).optional(),
60801
61260
  protected: boolean().optional(),
60802
- removable: boolean().optional()
61261
+ removable: boolean().optional(),
61262
+ /** Declared Integrations-page block (mirror of `AddonDeclaration.integrationSection`). */
61263
+ integrationSection: _enum(["system", "providers"]).optional()
60803
61264
  });
60804
61265
  var AddonProcessSchema = object({
60805
61266
  pid: number().optional(),
@@ -61164,9 +61625,9 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
61164
61625
  }), LogStreamEntrySchema, { kind: "subscription" });
61165
61626
  object({
61166
61627
  /** Carbon dioxide concentration in ppm. */
61167
- co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
61628
+ co2: number().min(0).optional().meta(readingMeta("concentration-volume")),
61168
61629
  /** Total volatile organic compounds in ppb. */
61169
- vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
61630
+ voc: number().min(0).optional().meta(readingMeta("concentration-ppb")),
61170
61631
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
61171
61632
  pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
61172
61633
  /** Particulate matter ≤ 10 μm in µg/m³. */
@@ -61179,7 +61640,7 @@ object({
61179
61640
  DeviceType.Sensor;
61180
61641
  object({
61181
61642
  /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
61182
- lux: number().min(0).nullable().meta(readingMeta("illuminance", {
61643
+ illuminance: number().min(0).nullable().meta(readingMeta("illuminance", {
61183
61644
  precision: 0,
61184
61645
  label: "Illuminance"
61185
61646
  })),
@@ -61276,6 +61737,84 @@ DeviceType.Camera, method(object({ deviceId: number() }), AudioMetricsSnapshotSc
61276
61737
  * would produce more than `maxPoints` samples. */
61277
61738
  sampleEveryMs: number().int().positive().optional()
61278
61739
  }), AudioMetricsHistorySchema);
61740
+ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
61741
+ actionId: string().min(1),
61742
+ variables: record(string(), ActionVariableValueSchema).optional()
61743
+ }), _void(), {
61744
+ kind: "mutation",
61745
+ auth: "admin"
61746
+ }), method(object({ actionId: string().min(1) }), _void(), {
61747
+ kind: "mutation",
61748
+ auth: "admin"
61749
+ });
61750
+ /**
61751
+ * actions — the ONE surface for runnable actions, for the UI and for
61752
+ * automations. Provided by the core-blocks builtin on the hub.
61753
+ *
61754
+ * An action is a LIST ENTRY, not a device (D883): either internal (cap and wait
61755
+ * steps, kept in the hub's `actions` record kind) or declared by an addon over
61756
+ * `action-source`. `list` asks every `action-source` provider LIVE and overlays
61757
+ * the run status from the hub's `runs` ledger; there is no catalog here to
61758
+ * drift (D3).
61759
+ *
61760
+ * **Permissions follow the camera, as linked devices do.** An action is linked
61761
+ * to cameras by the operator (`setLinks`, D356: nothing links itself). A
61762
+ * non-admin may `get` / `run` an action only through a camera they may see that
61763
+ * links it: they name the camera (`deviceId`, a top-level field so the scope
61764
+ * gate sees it) and the provider refuses an action that camera does not link.
61765
+ * An admin, and an in-process caller (an automation block), may run any action.
61766
+ * Every refusal is typed (`[not-linked] …`) and logged.
61767
+ *
61768
+ * A second `run` of an action that is already running is REFUSED — traced and
61769
+ * logged — never queued.
61770
+ */
61771
+ var ActionIdInput = object({ actionId: string().min(1) });
61772
+ var ActionLinksSchema = object({
61773
+ deviceId: number().int().nonnegative(),
61774
+ actionIds: array(string().min(1)).max(100)
61775
+ });
61776
+ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
61777
+ actionId: string().min(1),
61778
+ deviceId: number().int().nonnegative().optional()
61779
+ }), object({ action: ActionEntrySchema.nullable() }), {
61780
+ role: "viewer",
61781
+ auth: "protected",
61782
+ kind: "query",
61783
+ caller: "required"
61784
+ }), method(object({
61785
+ actionId: string().min(1),
61786
+ variables: record(string(), ActionVariableValueSchema).optional(),
61787
+ wait: boolean().optional(),
61788
+ origin: ActionRunOriginSchema.optional(),
61789
+ deviceId: number().int().nonnegative().optional()
61790
+ }), ActionRunResultSchema, {
61791
+ role: "viewer",
61792
+ auth: "protected",
61793
+ kind: "mutation",
61794
+ caller: "required"
61795
+ }), method(ActionIdInput, _void(), {
61796
+ kind: "mutation",
61797
+ auth: "admin"
61798
+ }), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({ action: ActionEntrySchema }), {
61799
+ kind: "mutation",
61800
+ auth: "admin"
61801
+ }), method(object({
61802
+ actionId: string().min(1),
61803
+ action: ActionInputSchema.partial()
61804
+ }), object({ action: ActionEntrySchema }), {
61805
+ kind: "mutation",
61806
+ auth: "admin"
61807
+ }), method(ActionIdInput, _void(), {
61808
+ kind: "mutation",
61809
+ auth: "admin"
61810
+ }), method(object({ deviceId: number().int().nonnegative() }), array(ActionEntrySchema), {
61811
+ role: "viewer",
61812
+ auth: "protected",
61813
+ kind: "query"
61814
+ }), method(object({ deviceId: number().int().nonnegative() }), ActionLinksSchema, { auth: "admin" }), method(ActionLinksSchema, ActionLinksSchema, {
61815
+ kind: "mutation",
61816
+ auth: "admin"
61817
+ });
61279
61818
  object({
61280
61819
  /** Whether the automation is currently enabled. Disabled automations
61281
61820
  * ignore their trigger block — manual `trigger` still works. */
@@ -61329,7 +61868,7 @@ var BatteryStatusSchema = object({
61329
61868
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
61330
61869
  * declines to band a non-finite reading, which is the correct shape.
61331
61870
  */
61332
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
61871
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
61333
61872
  /**
61334
61873
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
61335
61874
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -61339,7 +61878,7 @@ var BatteryStatusSchema = object({
61339
61878
  * `'unknown'` is the absence of an answer: the provider has not read the
61340
61879
  * power fields yet, or the firmware reported neither. Before it existed,
61341
61880
  * two unreadable fields produced `'none'`, and a camera nobody had reached
61342
- * was drawn "on battery alone" — the same D315 defect `percentage` closed
61881
+ * was drawn "on battery alone" — the same D315 defect `level` closed
61343
61882
  * by going nullable. Consumers SKIP it (no charger state published, no
61344
61883
  * glyph, no HomeKit ChargingState) rather than coerce it either way.
61345
61884
  */
@@ -61363,7 +61902,7 @@ var BatteryStatusSchema = object({
61363
61902
  * that only report charge. A second reading on this cap, not a second
61364
61903
  * `numeric-sensor` on the same device.
61365
61904
  */
61366
- volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
61905
+ voltage: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
61367
61906
  /**
61368
61907
  * Ms epoch of the last time the device PROVED it was reachable — a
61369
61908
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -61397,7 +61936,7 @@ var BatteryStatusSchema = object({
61397
61936
  /**
61398
61937
  * True when the source is a BINARY low-battery indicator (HA
61399
61938
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
61400
- * charge level — `percentage` is then a coarse stand-in (100 = normal,
61939
+ * charge level — `level` is then a coarse stand-in (100 = normal,
61401
61940
  * sub-threshold = low). UI MUST render "Normal"/"Low" instead of a
61402
61941
  * misleading exact percentage. Absent/false → genuine 0–100 % reading.
61403
61942
  */
@@ -61417,15 +61956,15 @@ var BATTERY_DEVICE_TYPES = [
61417
61956
  var batteryCustomize = {
61418
61957
  version: 1,
61419
61958
  targets: [{
61420
- id: "percentage",
61959
+ id: "level",
61421
61960
  cardinality: "scalar",
61422
61961
  vocabulary: {
61423
61962
  cap: "battery",
61424
- stateField: "percentage"
61963
+ stateField: "level"
61425
61964
  },
61426
61965
  deviceTypes: BATTERY_DEVICE_TYPES,
61427
61966
  apply: "add",
61428
- path: "percentage",
61967
+ path: "level",
61429
61968
  sources: [{
61430
61969
  kind: "field",
61431
61970
  valueKinds: ["number", "string"],
@@ -61519,7 +62058,7 @@ onStatusChanged: { data: object({
61519
62058
  schema: BatteryStatusSchema,
61520
62059
  kind: "push",
61521
62060
  empty: {
61522
- percentage: null,
62061
+ level: null,
61523
62062
  charging: "unknown",
61524
62063
  sleeping: false,
61525
62064
  lastUpdated: 0
@@ -61546,11 +62085,11 @@ onStatusChanged: { data: object({
61546
62085
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
61547
62086
  */
61548
62087
  history: { fields: {
61549
- percentage: {
62088
+ level: {
61550
62089
  kind: "gauge",
61551
62090
  unit: "%"
61552
62091
  },
61553
- volts: {
62092
+ voltage: {
61554
62093
  kind: "gauge",
61555
62094
  unit: "V"
61556
62095
  }
@@ -61568,19 +62107,19 @@ object({
61568
62107
  DeviceType.Sensor;
61569
62108
  object({
61570
62109
  /** Current level as 0..100 inclusive. Firmware-reported. */
61571
- percentage: number().min(0).max(100).meta(readingMeta("percentage")),
62110
+ level: number().min(0).max(100).meta(readingMeta("percentage")),
61572
62111
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
61573
62112
  lastChangedAt: number()
61574
62113
  });
61575
62114
  DeviceType.Light, method(object({
61576
62115
  deviceId: number().int().nonnegative(),
61577
- percentage: number().min(0).max(100)
62116
+ level: number().min(0).max(100)
61578
62117
  }), _void(), {
61579
62118
  kind: "mutation",
61580
62119
  auth: "protected"
61581
62120
  }), object({
61582
62121
  deviceId: number(),
61583
- percentage: number().min(0).max(100),
62122
+ level: number().min(0).max(100),
61584
62123
  lastChangedAt: number()
61585
62124
  });
61586
62125
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
@@ -62081,11 +62620,11 @@ var ColorInputSchema = discriminatedUnion("mode", [
62081
62620
  }),
62082
62621
  object({
62083
62622
  mode: literal("mired"),
62084
- mireds: number().int().min(50).max(1e3)
62623
+ colorTemperature: number().int().min(50).max(1e3)
62085
62624
  })
62086
62625
  ]);
62087
62626
  object({
62088
- /** Active color mode — which of `rgb` / `hsv` / `mireds` reflects
62627
+ /** Active color mode — which of `rgb` / `hsv` / `colorTemperature` reflects
62089
62628
  * the bulb's current state. */
62090
62629
  mode: _enum([
62091
62630
  "rgb",
@@ -62097,7 +62636,7 @@ object({
62097
62636
  /** Populated when `mode === 'hsv'`. */
62098
62637
  hsv: HsvTripletSchema.optional(),
62099
62638
  /** Populated when `mode === 'mired'`. */
62100
- mireds: number().int().optional().meta(readingMeta("colour-temperature")),
62639
+ colorTemperature: number().int().optional().meta(readingMeta("colour-temperature")),
62101
62640
  /** Ms epoch of the last operator-driven change. */
62102
62641
  lastChangedAt: number()
62103
62642
  });
@@ -62116,7 +62655,7 @@ DeviceType.Light, method(object({
62116
62655
  ]),
62117
62656
  rgb: RgbTripletSchema.optional(),
62118
62657
  hsv: HsvTripletSchema.optional(),
62119
- mireds: number().int().optional(),
62658
+ colorTemperature: number().int().optional(),
62120
62659
  lastChangedAt: number()
62121
62660
  });
62122
62661
  var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
@@ -62542,11 +63081,11 @@ object({
62542
63081
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
62543
63082
  availablePrograms: array(string()),
62544
63083
  /** Seconds remaining on the active cycle, or null when not running / not known. */
62545
- remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
63084
+ remaining: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62546
63085
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
62547
- delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
63086
+ delayedStart: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62548
63087
  /** Cycle completion 0..100, or null when not applicable. */
62549
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63088
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62550
63089
  /** True when the door is reported open; null when the door state is not known. */
62551
63090
  doorOpen: boolean().nullable(),
62552
63091
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -63164,7 +63703,7 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
63164
63703
  object({
63165
63704
  /** Active speed as 0..100 inclusive. Null when the device has no
63166
63705
  * speed surface (single-speed fan). */
63167
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63706
+ speed: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63168
63707
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
63169
63708
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
63170
63709
  percentageStep: number().positive().optional().meta(readingMeta("percentage")),
@@ -63184,7 +63723,7 @@ object({
63184
63723
  });
63185
63724
  DeviceType.Fan, method(object({
63186
63725
  deviceId: number().int().nonnegative(),
63187
- percentage: number().min(0).max(100)
63726
+ speed: number().min(0).max(100)
63188
63727
  }), _void(), {
63189
63728
  kind: "mutation",
63190
63729
  auth: "protected"
@@ -63367,7 +63906,7 @@ DeviceType.Humidifier, method(object({
63367
63906
  });
63368
63907
  object({
63369
63908
  /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
63370
- percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63909
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63371
63910
  precision: 0,
63372
63911
  label: "Humidity"
63373
63912
  })),
@@ -63810,7 +64349,7 @@ object({
63810
64349
  batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63811
64350
  /** 0..100 mowing-completion percentage of the current task, or null when no
63812
64351
  * task is active / progress is unavailable. */
63813
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64352
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63814
64353
  /** Current device/event code (dynamic — mostly status, sometimes an error),
63815
64354
  * or null when unknown. */
63816
64355
  currentCode: number().nullable().meta(readingMeta("none")),
@@ -64300,10 +64839,10 @@ object({
64300
64839
  currentMedia: MediaInfoSchema.nullable(),
64301
64840
  /** Current playback position in ms. Null when not seekable or
64302
64841
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
64303
- positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64842
+ position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64304
64843
  /** Total duration of the current media in ms. Null when unknown
64305
64844
  * (live stream) or nothing is playing. */
64306
- durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64845
+ duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64307
64846
  /** Shuffle toggle. Null when no shuffle surface.
64308
64847
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
64309
64848
  shuffle: boolean().nullable(),
@@ -64330,7 +64869,7 @@ DeviceType.MediaPlayer, method(object({ deviceId: number().int().nonnegative() }
64330
64869
  auth: "protected"
64331
64870
  }), method(object({
64332
64871
  deviceId: number().int().nonnegative(),
64333
- positionMs: number().int().nonnegative()
64872
+ position: number().int().nonnegative()
64334
64873
  }), _void(), {
64335
64874
  kind: "mutation",
64336
64875
  auth: "protected"
@@ -65003,9 +65542,9 @@ var NetworkLinkStatusSchema = object({
65003
65542
  * one whose reading has not landed must not be drawn at 0 %. Consumers
65004
65543
  * SKIP a null rather than coerce it.
65005
65544
  */
65006
- signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65545
+ signal: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65007
65546
  /** Raw received signal strength in dBm, when the firmware reports one. */
65008
- rssiDbm: number().optional().meta(readingMeta("signal-level")),
65547
+ rssi: number().optional().meta(readingMeta("signal-level")),
65009
65548
  /** Network name of a wireless link, when the firmware reports it. */
65010
65549
  ssid: string().optional(),
65011
65550
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
@@ -65666,10 +66205,20 @@ var PetFeederDeviceStatusSchema = _enum([
65666
66205
  ]);
65667
66206
  var gramsPortion = number().int().min(4).max(200);
65668
66207
  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`. */
66208
+ /** Food in the TANK (hopper), as the feeder's own sensor reports it
66209
+ * (`state.food`). A coarse level code, not grams — `0` is empty. The
66210
+ * amount in the bowl is `bowlFood`. On dual-hopper models this is the
66211
+ * combined reading; per-hopper levels live in `food1`/`food2`. Null when
66212
+ * the device has not reported yet. */
65672
66213
  foodLevel: number().nullable().meta(readingMeta("none")),
66214
+ /** Food in the BOWL as the feeder reports it (`state.bowl`), in the
66215
+ * feeder's own units. Null when the model reports none. */
66216
+ bowlFood: number().nullable().meta(readingMeta("none")),
66217
+ /** True while the pet is eating (`state.eating`). */
66218
+ eating: boolean(),
66219
+ /** Whether `markFoodReplenished` does anything on this model. When false
66220
+ * the client must not offer it. */
66221
+ supportsMarkRefilled: boolean(),
65673
66222
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
65674
66223
  * single-hopper models. */
65675
66224
  food1: number().nullable().meta(readingMeta("none")),
@@ -65677,8 +66226,8 @@ object({
65677
66226
  * single-hopper models. */
65678
66227
  food2: number().nullable().meta(readingMeta("none")),
65679
66228
  /** 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. */
66229
+ * (`device_class: problem`, on = low). True when the tank reports empty,
66230
+ * or, on a dual-hopper model, when EITHER hopper is empty. */
65682
66231
  lowFood: boolean(),
65683
66232
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
65684
66233
  * device has no battery reading. */
@@ -66145,16 +66694,16 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
66145
66694
  auth: "admin"
66146
66695
  });
66147
66696
  object({
66148
- /** Instantaneous power draw in watts. */
66149
- watts: number().optional().meta(readingMeta("power", { label: "Power" })),
66697
+ /** Instantaneous power draw (watts). */
66698
+ power: number().optional().meta(readingMeta("power", { label: "Power" })),
66150
66699
  /** 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" })),
66700
+ energy: number().optional().meta(readingMeta("energy", { label: "Energy" })),
66701
+ /** Voltage (volts). */
66702
+ voltage: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
66154
66703
  /** Current in amperes. */
66155
- amps: number().optional().meta(readingMeta("current", { label: "Current" })),
66704
+ current: number().optional().meta(readingMeta("current", { label: "Current" })),
66156
66705
  /** Apparent power in volt-amperes. */
66157
- va: number().optional().meta(readingMeta("apparent-power")),
66706
+ apparentPower: number().optional().meta(readingMeta("apparent-power")),
66158
66707
  /** Power factor (0..1) if reported. */
66159
66708
  powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
66160
66709
  /** Ms epoch when the slice was last updated. */
@@ -66209,16 +66758,13 @@ object({
66209
66758
  location: string().nullable(),
66210
66759
  /** GPS coordinates when available. Null otherwise. */
66211
66760
  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
66761
  /** Ms epoch when the slice was last updated. */
66216
66762
  lastChangedAt: number()
66217
66763
  });
66218
66764
  DeviceType.Presence;
66219
66765
  object({
66220
66766
  /** Current pressure in hPa. `null` when the sample was refused (D393). */
66221
- hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
66767
+ pressure: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
66222
66768
  /** Ms epoch when the slice was last updated. */
66223
66769
  lastFetchedAt: number()
66224
66770
  });
@@ -69745,7 +70291,7 @@ object({
69745
70291
  DeviceType.Sensor;
69746
70292
  object({
69747
70293
  /** Current temperature in Celsius. `null` when the sample was refused (D393). */
69748
- celsius: number().nullable().meta(readingMeta("temperature", {
70294
+ temperature: number().nullable().meta(readingMeta("temperature", {
69749
70295
  precision: 1,
69750
70296
  label: "Temperature"
69751
70297
  })),
@@ -70009,7 +70555,7 @@ object({
70009
70555
  /** Speed tokens the hardware accepts — drives the UI selector. */
70010
70556
  availableFanSpeeds: array(string()),
70011
70557
  /** 0..100 cleaning-completion percentage of the current task, or null. */
70012
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
70558
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
70013
70559
  /** Current error code (0 / null = no error). */
70014
70560
  errorCode: number().nullable().meta(readingMeta("none")),
70015
70561
  /** Human label for {@link errorCode}, or null when none / undecodable. */
@@ -71008,6 +71554,104 @@ Object.freeze({
71008
71554
  access: "create",
71009
71555
  role: "admin"
71010
71556
  },
71557
+ "actions.create": {
71558
+ capName: "actions",
71559
+ capScope: "system",
71560
+ addonId: null,
71561
+ access: "create",
71562
+ role: "admin"
71563
+ },
71564
+ "actions.delete": {
71565
+ capName: "actions",
71566
+ capScope: "system",
71567
+ addonId: null,
71568
+ access: "delete",
71569
+ role: "admin"
71570
+ },
71571
+ "actions.get": {
71572
+ capName: "actions",
71573
+ capScope: "system",
71574
+ addonId: null,
71575
+ access: "view",
71576
+ role: "viewer"
71577
+ },
71578
+ "actions.getLinks": {
71579
+ capName: "actions",
71580
+ capScope: "system",
71581
+ addonId: null,
71582
+ access: "view",
71583
+ role: "admin"
71584
+ },
71585
+ "actions.getTraces": {
71586
+ capName: "actions",
71587
+ capScope: "system",
71588
+ addonId: null,
71589
+ access: "view",
71590
+ role: "admin"
71591
+ },
71592
+ "actions.list": {
71593
+ capName: "actions",
71594
+ capScope: "system",
71595
+ addonId: null,
71596
+ access: "view",
71597
+ role: "admin"
71598
+ },
71599
+ "actions.listForCamera": {
71600
+ capName: "actions",
71601
+ capScope: "system",
71602
+ addonId: null,
71603
+ access: "view",
71604
+ role: "viewer"
71605
+ },
71606
+ "actions.run": {
71607
+ capName: "actions",
71608
+ capScope: "system",
71609
+ addonId: null,
71610
+ access: "create",
71611
+ role: "viewer"
71612
+ },
71613
+ "actions.setLinks": {
71614
+ capName: "actions",
71615
+ capScope: "system",
71616
+ addonId: null,
71617
+ access: "create",
71618
+ role: "admin"
71619
+ },
71620
+ "actions.stop": {
71621
+ capName: "actions",
71622
+ capScope: "system",
71623
+ addonId: null,
71624
+ access: "create",
71625
+ role: "admin"
71626
+ },
71627
+ "actions.update": {
71628
+ capName: "actions",
71629
+ capScope: "system",
71630
+ addonId: null,
71631
+ access: "create",
71632
+ role: "admin"
71633
+ },
71634
+ "actionSource.listActions": {
71635
+ capName: "action-source",
71636
+ capScope: "system",
71637
+ addonId: null,
71638
+ access: "view",
71639
+ role: "admin"
71640
+ },
71641
+ "actionSource.runAction": {
71642
+ capName: "action-source",
71643
+ capScope: "system",
71644
+ addonId: null,
71645
+ access: "create",
71646
+ role: "admin"
71647
+ },
71648
+ "actionSource.stopAction": {
71649
+ capName: "action-source",
71650
+ capScope: "system",
71651
+ addonId: null,
71652
+ access: "create",
71653
+ role: "admin"
71654
+ },
71011
71655
  "addonPages.listPages": {
71012
71656
  capName: "addon-pages",
71013
71657
  capScope: "system",
@@ -72289,6 +72933,13 @@ Object.freeze({
72289
72933
  access: "view",
72290
72934
  role: "admin"
72291
72935
  },
72936
+ "coreBlocks.getAutomationRunState": {
72937
+ capName: "core-blocks",
72938
+ capScope: "system",
72939
+ addonId: null,
72940
+ access: "view",
72941
+ role: "admin"
72942
+ },
72292
72943
  "coreBlocks.getBlockSettings": {
72293
72944
  capName: "core-blocks",
72294
72945
  capScope: "system",
@@ -72310,6 +72961,13 @@ Object.freeze({
72310
72961
  access: "view",
72311
72962
  role: "admin"
72312
72963
  },
72964
+ "coreBlocks.getTraces": {
72965
+ capName: "core-blocks",
72966
+ capScope: "system",
72967
+ addonId: null,
72968
+ access: "view",
72969
+ role: "admin"
72970
+ },
72313
72971
  "coreBlocks.getTypeDefs": {
72314
72972
  capName: "core-blocks",
72315
72973
  capScope: "system",
@@ -72345,6 +73003,13 @@ Object.freeze({
72345
73003
  access: "create",
72346
73004
  role: "admin"
72347
73005
  },
73006
+ "coreBlocks.reportAutomationRun": {
73007
+ capName: "core-blocks",
73008
+ capScope: "system",
73009
+ addonId: null,
73010
+ access: "create",
73011
+ role: "admin"
73012
+ },
72348
73013
  "coreBlocks.restart": {
72349
73014
  capName: "core-blocks",
72350
73015
  capScope: "system",
@@ -80918,7 +81583,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80918
81583
  label: t("Full brightness", "Luminosità massima"),
80919
81584
  phrase: t("Set {device} to full brightness", "Porta {device} alla massima luminosità"),
80920
81585
  icon: "sun",
80921
- args: { percentage: 100 }
81586
+ args: { level: 100 }
80922
81587
  },
80923
81588
  {
80924
81589
  id: "brightness.half",
@@ -80926,7 +81591,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80926
81591
  label: t("Half brightness", "Luminosità a metà"),
80927
81592
  phrase: t("Dim {device} to half", "Abbassa {device} a metà luminosità"),
80928
81593
  icon: "sun-dim",
80929
- args: { percentage: 50 }
81594
+ args: { level: 50 }
80930
81595
  },
80931
81596
  {
80932
81597
  id: "brightness.off",
@@ -80934,16 +81599,16 @@ var ACTUATOR_CAP_VOCABULARY = {
80934
81599
  label: t("Brightness off", "Luminosità a zero"),
80935
81600
  phrase: t("Dim {device} to zero", "Abbassa {device} a zero"),
80936
81601
  icon: "lightbulb-off",
80937
- args: { percentage: 0 }
81602
+ args: { level: 0 }
80938
81603
  },
80939
81604
  {
80940
81605
  id: "brightness.set",
80941
81606
  method: "setBrightness",
80942
81607
  label: t("Set brightness", "Imposta la luminosità"),
80943
- phrase: t("Set the brightness of {device} to {percentage}", "Imposta la luminosità di {device} a {percentage}"),
81608
+ phrase: t("Set the brightness of {device} to {level}", "Imposta la luminosità di {device} a {level}"),
80944
81609
  icon: "sun-medium",
80945
81610
  openArgs: [{
80946
- key: "percentage",
81611
+ key: "level",
80947
81612
  label: PERCENT,
80948
81613
  offer: {
80949
81614
  kind: "number",
@@ -80957,13 +81622,13 @@ var ACTUATOR_CAP_VOCABULARY = {
80957
81622
  ],
80958
81623
  state: [{
80959
81624
  kind: "numeric",
80960
- field: "percentage",
81625
+ field: "level",
80961
81626
  quantity: t("the brightness", "la luminosità"),
80962
81627
  label: t("Brightness", "Luminosità"),
80963
81628
  unit: "%",
80964
81629
  icon: "sun"
80965
81630
  }],
80966
- events: { onBrightnessChanged: { notOperatorFacing: "the `percentage` state trigger reads the same change" } }
81631
+ events: { onBrightnessChanged: { notOperatorFacing: "the `level` state trigger reads the same change" } }
80967
81632
  },
80968
81633
  color: {
80969
81634
  scope: "device",
@@ -80977,7 +81642,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80977
81642
  icon: "sun",
80978
81643
  args: { color: {
80979
81644
  mode: "mired",
80980
- mireds: 370
81645
+ colorTemperature: 370
80981
81646
  } }
80982
81647
  },
80983
81648
  {
@@ -80988,7 +81653,7 @@ var ACTUATOR_CAP_VOCABULARY = {
80988
81653
  icon: "snowflake",
80989
81654
  args: { color: {
80990
81655
  mode: "mired",
80991
- mireds: 200
81656
+ colorTemperature: 200
80992
81657
  } }
80993
81658
  },
80994
81659
  {
@@ -81602,7 +82267,7 @@ var ACTUATOR_CAP_VOCABULARY = {
81602
82267
  label: t("Fan off", "Ventilatore spento"),
81603
82268
  phrase: t("Stop {device}", "Ferma {device}"),
81604
82269
  icon: "power-off",
81605
- args: { percentage: 0 }
82270
+ args: { speed: 0 }
81606
82271
  },
81607
82272
  {
81608
82273
  id: "fan.full",
@@ -81610,16 +82275,16 @@ var ACTUATOR_CAP_VOCABULARY = {
81610
82275
  label: t("Fan full", "Ventilatore al massimo"),
81611
82276
  phrase: t("Run {device} at full speed", "Porta {device} alla massima velocità"),
81612
82277
  icon: "fan",
81613
- args: { percentage: 100 }
82278
+ args: { speed: 100 }
81614
82279
  },
81615
82280
  {
81616
82281
  id: "fan.speed",
81617
82282
  method: "setPercentage",
81618
82283
  label: t("Set speed", "Imposta la velocità"),
81619
- phrase: t("Set the speed of {device} to {percentage}", "Imposta la velocità di {device} al {percentage}"),
82284
+ phrase: t("Set the speed of {device} to {speed}", "Imposta la velocità di {device} al {speed}"),
81620
82285
  icon: "fan",
81621
82286
  openArgs: [{
81622
- key: "percentage",
82287
+ key: "speed",
81623
82288
  label: t("Speed", "Velocità"),
81624
82289
  offer: {
81625
82290
  kind: "number",
@@ -81681,7 +82346,7 @@ var ACTUATOR_CAP_VOCABULARY = {
81681
82346
  ],
81682
82347
  state: [{
81683
82348
  kind: "numeric",
81684
- field: "percentage",
82349
+ field: "speed",
81685
82350
  quantity: t("the speed", "la velocità"),
81686
82351
  label: t("Speed", "Velocità"),
81687
82352
  unit: "%",
@@ -82479,7 +83144,7 @@ var APPLIANCE_CAP_VOCABULARY = {
82479
83144
  },
82480
83145
  {
82481
83146
  kind: "numeric",
82482
- field: "progressPercent",
83147
+ field: "progress",
82483
83148
  quantity: t("the cycle progress", "l’avanzamento del lavaggio"),
82484
83149
  label: t("Progress", "Avanzamento"),
82485
83150
  unit: "%",
@@ -82837,10 +83502,10 @@ var APPLIANCE_CAP_VOCABULARY = {
82837
83502
  id: "media.seek",
82838
83503
  method: "seek",
82839
83504
  label: t("Seek", "Vai al punto"),
82840
- phrase: t("Move {device} to {positionMs}", "Porta {device} al punto {positionMs}"),
83505
+ phrase: t("Move {device} to {position}", "Porta {device} al punto {position}"),
82841
83506
  icon: "fast-forward",
82842
83507
  openArgs: [{
82843
- key: "positionMs",
83508
+ key: "position",
82844
83509
  label: t("Position", "Posizione"),
82845
83510
  offer: {
82846
83511
  kind: "number",
@@ -83889,7 +84554,7 @@ var SENSOR_CAP_VOCABULARY = {
83889
84554
  state: [
83890
84555
  {
83891
84556
  kind: "numeric",
83892
- field: "co2Ppm",
84557
+ field: "co2",
83893
84558
  quantity: t("the CO₂", "la CO₂"),
83894
84559
  label: t("CO₂", "CO₂"),
83895
84560
  unit: "ppm",
@@ -83897,7 +84562,7 @@ var SENSOR_CAP_VOCABULARY = {
83897
84562
  },
83898
84563
  {
83899
84564
  kind: "numeric",
83900
- field: "vocPpb",
84565
+ field: "voc",
83901
84566
  quantity: t("the VOC level", "il livello di COV"),
83902
84567
  label: t("VOC", "COV"),
83903
84568
  unit: "ppb",
@@ -83933,7 +84598,7 @@ var SENSOR_CAP_VOCABULARY = {
83933
84598
  noun: t("Light level", "Luminosità ambiente"),
83934
84599
  state: [{
83935
84600
  kind: "numeric",
83936
- field: "lux",
84601
+ field: "illuminance",
83937
84602
  quantity: t("the light level", "la luminosità"),
83938
84603
  label: t("Illuminance", "Illuminamento"),
83939
84604
  unit: "lx",
@@ -84136,7 +84801,7 @@ var SENSOR_CAP_VOCABULARY = {
84136
84801
  noun: t("Humidity", "Umidità"),
84137
84802
  state: [{
84138
84803
  kind: "numeric",
84139
- field: "percent",
84804
+ field: "humidity",
84140
84805
  quantity: t("the humidity", "l’umidità"),
84141
84806
  label: t("Humidity", "Umidità"),
84142
84807
  unit: "%",
@@ -84184,7 +84849,7 @@ var SENSOR_CAP_VOCABULARY = {
84184
84849
  state: [
84185
84850
  {
84186
84851
  kind: "numeric",
84187
- field: "watts",
84852
+ field: "power",
84188
84853
  quantity: t("the power draw", "il consumo"),
84189
84854
  label: t("Power", "Potenza"),
84190
84855
  unit: "W",
@@ -84192,7 +84857,7 @@ var SENSOR_CAP_VOCABULARY = {
84192
84857
  },
84193
84858
  {
84194
84859
  kind: "numeric",
84195
- field: "kwhTotal",
84860
+ field: "energy",
84196
84861
  quantity: t("the energy used", "l’energia consumata"),
84197
84862
  label: t("Energy", "Energia"),
84198
84863
  unit: "kWh",
@@ -84200,7 +84865,7 @@ var SENSOR_CAP_VOCABULARY = {
84200
84865
  },
84201
84866
  {
84202
84867
  kind: "numeric",
84203
- field: "volts",
84868
+ field: "voltage",
84204
84869
  quantity: t("the voltage", "la tensione"),
84205
84870
  label: t("Voltage", "Tensione"),
84206
84871
  unit: "V",
@@ -84208,7 +84873,7 @@ var SENSOR_CAP_VOCABULARY = {
84208
84873
  },
84209
84874
  {
84210
84875
  kind: "numeric",
84211
- field: "amps",
84876
+ field: "current",
84212
84877
  quantity: t("the current", "la corrente"),
84213
84878
  label: t("Current", "Corrente"),
84214
84879
  unit: "A",
@@ -84225,13 +84890,6 @@ var SENSOR_CAP_VOCABULARY = {
84225
84890
  label: t("Where", "Posizione"),
84226
84891
  phrase: t("When {device} is at {value}", "Quando {device} è in {value}"),
84227
84892
  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
84893
  }]
84236
84894
  },
84237
84895
  "pressure-sensor": {
@@ -84239,7 +84897,7 @@ var SENSOR_CAP_VOCABULARY = {
84239
84897
  noun: t("Pressure", "Pressione"),
84240
84898
  state: [{
84241
84899
  kind: "numeric",
84242
- field: "hpa",
84900
+ field: "pressure",
84243
84901
  quantity: t("the pressure", "la pressione"),
84244
84902
  label: t("Pressure", "Pressione"),
84245
84903
  unit: "hPa",
@@ -84275,7 +84933,7 @@ var SENSOR_CAP_VOCABULARY = {
84275
84933
  noun: t("Temperature", "Temperatura"),
84276
84934
  state: [{
84277
84935
  kind: "numeric",
84278
- field: "celsius",
84936
+ field: "temperature",
84279
84937
  quantity: t("the temperature", "la temperatura"),
84280
84938
  label: t("Temperature", "Temperatura"),
84281
84939
  unit: "°C",
@@ -84346,7 +85004,7 @@ var SENSOR_CAP_VOCABULARY = {
84346
85004
  state: [
84347
85005
  {
84348
85006
  kind: "numeric",
84349
- field: "percentage",
85007
+ field: "level",
84350
85008
  quantity: t("the battery level", "il livello della batteria"),
84351
85009
  label: t("Battery", "Batteria"),
84352
85010
  unit: "%",
@@ -84388,7 +85046,7 @@ var SENSOR_CAP_VOCABULARY = {
84388
85046
  icon: "battery-charging"
84389
85047
  }
84390
85048
  ],
84391
- events: { onStatusChanged: { notOperatorFacing: "the percentage / sleeping / charging state triggers each read one field of this change" } },
85049
+ events: { onStatusChanged: { notOperatorFacing: "the level / sleeping / charging state triggers each read one field of this change" } },
84392
85050
  extraEvents: [{
84393
85051
  category: EventCategory.DeviceAwake,
84394
85052
  label: t("Woke up", "Risvegliato"),
@@ -84406,7 +85064,7 @@ var SENSOR_CAP_VOCABULARY = {
84406
85064
  noun: t("Network link", "Collegamento di rete"),
84407
85065
  state: [{
84408
85066
  kind: "numeric",
84409
- field: "signalPercent",
85067
+ field: "signal",
84410
85068
  quantity: t("the signal strength", "l’intensità del segnale"),
84411
85069
  label: t("Signal", "Segnale"),
84412
85070
  unit: "%",
@@ -84499,6 +85157,8 @@ var ML = "model and training machinery — no device action";
84499
85157
  var INTERNAL = "internal plumbing between addons — never operator-facing";
84500
85158
  /** Every system-scoped cap, with why it is not a device's vocabulary. */
84501
85159
  var SYSTEM_CAP_VOCABULARY = {
85160
+ "action-source": system(INTERNAL),
85161
+ actions: system("declared actions — run from the Actions page and from the cameras that link them"),
84502
85162
  "addon-pages": system(UI_PLUMBING),
84503
85163
  "addon-pages-source": system(UI_PLUMBING),
84504
85164
  "addon-routes": system(UI_PLUMBING),
@@ -84695,6 +85355,31 @@ Object.freeze({
84695
85355
  form: "single",
84696
85356
  optional: false
84697
85357
  }],
85358
+ "actions.get": [{
85359
+ name: "deviceId",
85360
+ form: "single",
85361
+ optional: true
85362
+ }],
85363
+ "actions.getLinks": [{
85364
+ name: "deviceId",
85365
+ form: "single",
85366
+ optional: false
85367
+ }],
85368
+ "actions.listForCamera": [{
85369
+ name: "deviceId",
85370
+ form: "single",
85371
+ optional: false
85372
+ }],
85373
+ "actions.run": [{
85374
+ name: "deviceId",
85375
+ form: "single",
85376
+ optional: true
85377
+ }],
85378
+ "actions.setLinks": [{
85379
+ name: "deviceId",
85380
+ form: "single",
85381
+ optional: false
85382
+ }],
84698
85383
  "addonSettings.getDeviceSettings": [{
84699
85384
  name: "deviceId",
84700
85385
  form: "single",
@@ -89738,6 +90423,7 @@ var BackgroundRunRegistry = class {
89738
90423
  }
89739
90424
  }
89740
90425
  };
90426
+ String.raw`(?<=^|["'(\s>,\[{])`;
89741
90427
  var MB = 1024 * 1024;
89742
90428
  var DEFAULT_POOL_MEMORY_POLICY = {
89743
90429
  sampleIntervalMs: 6e4,