@camstack/addon-provider-bosch 0.1.46 → 0.1.48

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.
@@ -802,7 +802,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
802
802
  return EventCategory;
803
803
  }({});
804
804
  //#endregion
805
- //#region ../types/dist/device-type-DTTZKB2I.mjs
805
+ //#region ../types/dist/composed-stable-id-jnvhwGoH.mjs
806
806
  /**
807
807
  import { errMsg } from '@camstack/types'
808
808
  * Extract a human-readable message from an unknown error value.
@@ -1090,6 +1090,10 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
1090
1090
  /** Robot mower exposes targeted mowing (all-area / zones / edges /
1091
1091
  * spots) — gates the Mow action + map selectors. */
1092
1092
  DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
1093
+ /** The device has a drive/navigation surface (`navigation` cap): a robot camera. */
1094
+ DeviceFeature["Navigation"] = "navigation";
1095
+ /** The device has a network-link reading (`network-link` cap). */
1096
+ DeviceFeature["NetworkLink"] = "network-link";
1093
1097
  return DeviceFeature;
1094
1098
  }({});
1095
1099
  /**
@@ -1204,6 +1208,12 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
1204
1208
  DeviceRole["IrTransmitter"] = "ir-transmitter";
1205
1209
  return DeviceRole;
1206
1210
  }({});
1211
+ /**
1212
+ * A composed device's stableId is this prefix plus the block id. Its own module,
1213
+ * free of zod, so the addon runner can read it through `@camstack/types/addon`
1214
+ * without loading the schema barrel (D28).
1215
+ */
1216
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
1207
1217
  //#endregion
1208
1218
  //#region ../../node_modules/zod/v4/core/core.js
1209
1219
  var _a$1;
@@ -6016,7 +6026,7 @@ var ZodIssueCode = {
6016
6026
  var ZodFirstPartyTypeKind;
6017
6027
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6018
6028
  //#endregion
6019
- //#region ../types/dist/sleep-DnVqHJc4.mjs
6029
+ //#region ../types/dist/sleep-Dz1P6--1.mjs
6020
6030
  /**
6021
6031
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6022
6032
  * window to float samples (D455).
@@ -10103,15 +10113,52 @@ var PeerBytesTicketSchema = object({
10103
10113
  */
10104
10114
  declaredBytes: number$1().int().nonnegative().nullable()
10105
10115
  });
10106
- discriminatedUnion("kind", [object({
10107
- kind: literal("action"),
10108
- actionId: string(),
10109
- runId: string(),
10110
- automationId: string().optional()
10111
- }), object({
10112
- kind: literal("scheduler"),
10113
- schedulerId: string()
10114
- })]);
10116
+ /**
10117
+ * The match a notification rule fired on (D705). Carried as a command origin so an
10118
+ * Action the rule starts can press `alarm-panel.trigger` as that match, not as an
10119
+ * anonymous press (D735). The same fields a rule fire ref freezes.
10120
+ */
10121
+ var RuleFireOriginSchema = object({
10122
+ ruleId: string(),
10123
+ ruleName: string(),
10124
+ deviceId: number$1().int(),
10125
+ sourceDeviceId: number$1().int().optional(),
10126
+ recordId: string(),
10127
+ timestamp: number$1(),
10128
+ trackId: string().optional(),
10129
+ eventId: string().optional(),
10130
+ className: string().optional(),
10131
+ label: string().optional(),
10132
+ zones: array(string()).optional(),
10133
+ confidence: number$1().optional(),
10134
+ sensorChange: object({
10135
+ name: string(),
10136
+ kind: string(),
10137
+ state: string()
10138
+ }).optional(),
10139
+ extraStills: array(object({
10140
+ ownerId: string(),
10141
+ cameraId: number$1().int()
10142
+ })).optional()
10143
+ });
10144
+ discriminatedUnion("kind", [
10145
+ object({
10146
+ kind: literal("action"),
10147
+ actionId: string(),
10148
+ runId: string(),
10149
+ automationId: string().optional(),
10150
+ /** The rule fire that started this run, when one did (D705). */
10151
+ ruleFire: RuleFireOriginSchema.optional()
10152
+ }),
10153
+ object({
10154
+ kind: literal("scheduler"),
10155
+ schedulerId: string()
10156
+ }),
10157
+ object({
10158
+ kind: literal("rule"),
10159
+ fire: RuleFireOriginSchema
10160
+ })
10161
+ ]);
10115
10162
  /**
10116
10163
  * One recorded device transition (D1022): a status field of a device went from
10117
10164
  * one value to another, with who or what asked for it.
@@ -17406,7 +17453,7 @@ Object.freeze({
17406
17453
  }
17407
17454
  });
17408
17455
  //#endregion
17409
- //#region ../types/dist/composition-C6TZGqUb.mjs
17456
+ //#region ../types/dist/implemented-methods-OitwyO1Y.mjs
17410
17457
  /**
17411
17458
  * The ffmpeg build every CamStack node downloads, per platform and arch.
17412
17459
  *
@@ -18361,8 +18408,6 @@ var HelperSpecSchema = discriminatedUnion("kind", [
18361
18408
  source: HelperMemberSchema
18362
18409
  })
18363
18410
  ]);
18364
- /** A composed device's stableId is this prefix plus the block id. */
18365
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
18366
18411
  /** Longest snippet a `code` source may carry (slice 4). */
18367
18412
  var MAX_COMPOSITION_CODE_LENGTH = 2e4;
18368
18413
  /** One field of one capability of one source device. */
@@ -19348,18 +19393,43 @@ var TargetKindLevelSchema = object({
19348
19393
  requires: array(string()).optional(),
19349
19394
  description: string().optional()
19350
19395
  });
19396
+ /** Attachment capabilities for a kind (drives the degrade engine + test panel). */
19397
+ var TargetKindAttachmentsCapsSchema = object({
19398
+ mediaTypes: array(AttachmentMediaTypeSchema),
19399
+ mode: _enum([
19400
+ "url",
19401
+ "bytes",
19402
+ "both"
19403
+ ]),
19404
+ max: number$1().int().nonnegative(),
19405
+ maxBytes: number$1().int().positive().optional()
19406
+ });
19407
+ /**
19408
+ * Which TEXT fields a kind delivers (#483). Alexa speaks a title and nothing
19409
+ * else; a push service shows both.
19410
+ *
19411
+ * `.optional()` on the caps block, never defaulted: absent = both, which is
19412
+ * what every kind declared before this field meant (D938's rule for a Zod
19413
+ * default on the addon cap path).
19414
+ */
19415
+ var TargetKindTextCapsSchema = object({
19416
+ title: boolean(),
19417
+ body: boolean()
19418
+ });
19419
+ _enum([
19420
+ "title",
19421
+ "body",
19422
+ "attachments",
19423
+ "actions",
19424
+ "priority",
19425
+ "sound",
19426
+ "clickUrl"
19427
+ ]);
19351
19428
  /** The full capability block consulted before dispatch. */
19352
19429
  var TargetKindCapsSchema = object({
19353
- attachments: object({
19354
- mediaTypes: array(AttachmentMediaTypeSchema),
19355
- mode: _enum([
19356
- "url",
19357
- "bytes",
19358
- "both"
19359
- ]),
19360
- max: number$1().int().nonnegative(),
19361
- maxBytes: number$1().int().positive().optional()
19362
- }),
19430
+ /** Text fields this kind delivers (#483). Absent = title AND body. */
19431
+ text: TargetKindTextCapsSchema.optional(),
19432
+ attachments: TargetKindAttachmentsCapsSchema,
19363
19433
  /** Max action buttons (0 = none). */
19364
19434
  actions: number$1().int().nonnegative(),
19365
19435
  /**
@@ -44483,30 +44553,21 @@ function convertLegacyWhen(half) {
44483
44553
  * (instants) and state leaves (durations). It replaces the flat trigger list
44484
44554
  * plus the separate condition tree; the firing rule is in that file and in
44485
44555
  * ADR "one tree".
44486
- * - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
44487
- * fragment of §3.2.2. See {@link AutomationActionSchema} for the ONE place
44488
- * this had to diverge from "verbatim", and why.
44556
+ * - **Actions** — the `wait` and `cap` steps, plus the `{kind:'code'}`
44557
+ * fragment of §3.2.2. See {@link AutomationActionSchema}.
44489
44558
  *
44490
44559
  * A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
44491
44560
  * {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
44492
44561
  */
44493
44562
  /**
44494
- * One action step.
44495
- *
44496
- * `wait` and `cap` are `NcRuleActionSchema`'s two members, kept structurally
44497
- * identical so `NcRuleActionRunner` runs them unchanged — its device-scope
44498
- * check, stop-at-first-failure and per-sequence throttle are the whole reason
44499
- * to reuse it, and none of them are re-implemented here.
44563
+ * One action step: `wait`, or a `cap` call.
44500
44564
  *
44501
- * **The one divergence, and it is forced.** `NcRuleActionSchema.cap.deviceId` is
44502
- * a literal `z.number().int()`, and the NC runner's own `RunSequencesInput`
44503
- * documents its subject device as *"for the log tag, never for routing"*. So an
44504
- * NC action can never target the device that triggered it — which is fine for
44505
- * the NC (its rules already scope to a device) and fatal for an automation
44506
- * ("sound the siren of the camera that saw the person"). `deviceId` therefore
44507
- * also accepts `{ $var }`, resolved from the run's `vars` bag BEFORE the runner
44508
- * is called. The runner still receives a number and is untouched; the
44509
- * resolution is the recipe's job, not the runner's.
44565
+ * A cap step's target is who it acts on (D983: `devices`, a `domain`, or a
44566
+ * `{ $var }` resolved from the run's variables); the legacy `deviceId` is still
44567
+ * READ, never written. The step runner (`runSteps`) owns the device-scope
44568
+ * check, the stop-at-first-failure and the variable substitution, once, for
44569
+ * every caller — automations, Actions, and the notification centre, which runs
44570
+ * only Actions (planning#473).
44510
44571
  */
44511
44572
  var AutomationWaitStepSchema = object({
44512
44573
  kind: literal("wait"),
@@ -45124,6 +45185,8 @@ var ActionStatusSchema = object({
45124
45185
  /** The latest run's failure; null on success or when it never ran. */
45125
45186
  lastError: string().nullable()
45126
45187
  });
45188
+ /** The throttle window of an action, in seconds; absent or 0 = no throttle. */
45189
+ var ActionMinDelaySecSchema = number$1().int().min(0).max(86400);
45127
45190
  var ActionEntrySchema = object({
45128
45191
  /** `internal:<uuid>` or `<addonId>:<localId>`. */
45129
45192
  id: string().min(3),
@@ -45136,6 +45199,14 @@ var ActionEntrySchema = object({
45136
45199
  /** Internal only: cap and wait steps. No action-in-action, so no cycles. */
45137
45200
  steps: array(ActionStepSchema).min(1).max(50).optional(),
45138
45201
  variables: array(ActionVariableSchema).max(20).optional(),
45202
+ /**
45203
+ * Internal only: the least time between two run STARTS. A run that begins
45204
+ * inside the window is skipped (`skipped-cooldown`), traced and logged. It
45205
+ * is an actuator's budget — "how often may this gate physically open" —
45206
+ * not a notification cooldown, and it is shared by every caller of the
45207
+ * action (two rules, a button, an automation).
45208
+ */
45209
+ minDelaySec: ActionMinDelaySecSchema.optional(),
45139
45210
  status: ActionStatusSchema
45140
45211
  }).superRefine((entry, ctx) => {
45141
45212
  if (entry.source === "internal") {
@@ -45166,7 +45237,8 @@ var ActionEntrySchema = object({
45166
45237
  var ActionInputSchema = object({
45167
45238
  name: string().min(1).max(120),
45168
45239
  steps: array(ActionStepSchema).min(1).max(50),
45169
- variables: array(ActionVariableSchema).max(20).optional()
45240
+ variables: array(ActionVariableSchema).max(20).optional(),
45241
+ minDelaySec: ActionMinDelaySecSchema.optional()
45170
45242
  });
45171
45243
  /** Who started a run. Absent = the operator pressed it. */
45172
45244
  var ActionRunOriginSchema = object({
@@ -49326,14 +49398,14 @@ var climateControlCapability = {
49326
49398
  kind: "mutation",
49327
49399
  auth: "protected"
49328
49400
  }),
49329
- setFanMode: method(object({
49401
+ setFanMode: optionalMethod(object({
49330
49402
  deviceId: number$1().int().nonnegative(),
49331
49403
  fanMode: string().min(1)
49332
49404
  }), _void(), {
49333
49405
  kind: "mutation",
49334
49406
  auth: "protected"
49335
49407
  }),
49336
- setPreset: method(object({
49408
+ setPreset: optionalMethod(object({
49337
49409
  deviceId: number$1().int().nonnegative(),
49338
49410
  preset: string().min(1)
49339
49411
  }), _void(), {
@@ -49347,7 +49419,7 @@ var climateControlCapability = {
49347
49419
  kind: "mutation",
49348
49420
  auth: "protected"
49349
49421
  }),
49350
- setTargetRange: method(object({
49422
+ setTargetRange: optionalMethod(object({
49351
49423
  deviceId: number$1().int().nonnegative(),
49352
49424
  targetLow: number$1(),
49353
49425
  targetHigh: number$1()
@@ -49355,21 +49427,21 @@ var climateControlCapability = {
49355
49427
  kind: "mutation",
49356
49428
  auth: "protected"
49357
49429
  }),
49358
- setTargetHumidity: method(object({
49430
+ setTargetHumidity: optionalMethod(object({
49359
49431
  deviceId: number$1().int().nonnegative(),
49360
49432
  targetHumidity: number$1().min(0).max(100)
49361
49433
  }), _void(), {
49362
49434
  kind: "mutation",
49363
49435
  auth: "protected"
49364
49436
  }),
49365
- setSwingVertical: method(object({
49437
+ setSwingVertical: optionalMethod(object({
49366
49438
  deviceId: number$1().int().nonnegative(),
49367
49439
  on: boolean()
49368
49440
  }), _void(), {
49369
49441
  kind: "mutation",
49370
49442
  auth: "protected"
49371
49443
  }),
49372
- setSwingHorizontal: method(object({
49444
+ setSwingHorizontal: optionalMethod(object({
49373
49445
  deviceId: number$1().int().nonnegative(),
49374
49446
  on: boolean()
49375
49447
  }), _void(), {
@@ -50628,27 +50700,27 @@ var mediaPlayerCapability = {
50628
50700
  mode: "singleton",
50629
50701
  deviceTypes: [DeviceType.MediaPlayer],
50630
50702
  methods: {
50631
- turnOn: method(deviceOnly, _void(), {
50703
+ turnOn: optionalMethod(deviceOnly, _void(), {
50632
50704
  kind: "mutation",
50633
50705
  auth: "protected"
50634
50706
  }),
50635
- turnOff: method(deviceOnly, _void(), {
50707
+ turnOff: optionalMethod(deviceOnly, _void(), {
50636
50708
  kind: "mutation",
50637
50709
  auth: "protected"
50638
50710
  }),
50639
- volumeUp: method(stepInput, _void(), {
50711
+ volumeUp: optionalMethod(stepInput, _void(), {
50640
50712
  kind: "mutation",
50641
50713
  auth: "protected"
50642
50714
  }),
50643
- volumeDown: method(stepInput, _void(), {
50715
+ volumeDown: optionalMethod(stepInput, _void(), {
50644
50716
  kind: "mutation",
50645
50717
  auth: "protected"
50646
50718
  }),
50647
- channelUp: method(stepInput, _void(), {
50719
+ channelUp: optionalMethod(stepInput, _void(), {
50648
50720
  kind: "mutation",
50649
50721
  auth: "protected"
50650
50722
  }),
50651
- channelDown: method(stepInput, _void(), {
50723
+ channelDown: optionalMethod(stepInput, _void(), {
50652
50724
  kind: "mutation",
50653
50725
  auth: "protected"
50654
50726
  }),
@@ -50660,61 +50732,61 @@ var mediaPlayerCapability = {
50660
50732
  kind: "mutation",
50661
50733
  auth: "protected"
50662
50734
  }),
50663
- stop: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50735
+ stop: optionalMethod(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50664
50736
  kind: "mutation",
50665
50737
  auth: "protected"
50666
50738
  }),
50667
- next: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50739
+ next: optionalMethod(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50668
50740
  kind: "mutation",
50669
50741
  auth: "protected"
50670
50742
  }),
50671
- previous: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50743
+ previous: optionalMethod(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50672
50744
  kind: "mutation",
50673
50745
  auth: "protected"
50674
50746
  }),
50675
- seek: method(object({
50747
+ seek: optionalMethod(object({
50676
50748
  deviceId: number$1().int().nonnegative(),
50677
50749
  position: number$1().int().nonnegative()
50678
50750
  }), _void(), {
50679
50751
  kind: "mutation",
50680
50752
  auth: "protected"
50681
50753
  }),
50682
- setVolume: method(object({
50754
+ setVolume: optionalMethod(object({
50683
50755
  deviceId: number$1().int().nonnegative(),
50684
50756
  volumeLevel: number$1().min(0).max(100)
50685
50757
  }), _void(), {
50686
50758
  kind: "mutation",
50687
50759
  auth: "protected"
50688
50760
  }),
50689
- setMute: method(object({
50761
+ setMute: optionalMethod(object({
50690
50762
  deviceId: number$1().int().nonnegative(),
50691
50763
  muted: boolean()
50692
50764
  }), _void(), {
50693
50765
  kind: "mutation",
50694
50766
  auth: "protected"
50695
50767
  }),
50696
- setShuffle: method(object({
50768
+ setShuffle: optionalMethod(object({
50697
50769
  deviceId: number$1().int().nonnegative(),
50698
50770
  shuffle: boolean()
50699
50771
  }), _void(), {
50700
50772
  kind: "mutation",
50701
50773
  auth: "protected"
50702
50774
  }),
50703
- setRepeat: method(object({
50775
+ setRepeat: optionalMethod(object({
50704
50776
  deviceId: number$1().int().nonnegative(),
50705
50777
  repeat: MediaPlayerRepeatSchema
50706
50778
  }), _void(), {
50707
50779
  kind: "mutation",
50708
50780
  auth: "protected"
50709
50781
  }),
50710
- selectSource: method(object({
50782
+ selectSource: optionalMethod(object({
50711
50783
  deviceId: number$1().int().nonnegative(),
50712
50784
  source: string().min(1)
50713
50785
  }), _void(), {
50714
50786
  kind: "mutation",
50715
50787
  auth: "protected"
50716
50788
  }),
50717
- playMedia: method(object({
50789
+ playMedia: optionalMethod(object({
50718
50790
  deviceId: number$1().int().nonnegative(),
50719
50791
  /** Media identifier / URL. */
50720
50792
  mediaId: string().min(1),
@@ -51736,18 +51808,18 @@ var coverCapability = {
51736
51808
  kind: "mutation",
51737
51809
  auth: "protected"
51738
51810
  }),
51739
- stop: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
51811
+ stop: optionalMethod(object({ deviceId: number$1().int().nonnegative() }), _void(), {
51740
51812
  kind: "mutation",
51741
51813
  auth: "protected"
51742
51814
  }),
51743
- setPosition: method(object({
51815
+ setPosition: optionalMethod(object({
51744
51816
  deviceId: number$1().int().nonnegative(),
51745
51817
  position: number$1().min(0).max(100)
51746
51818
  }), _void(), {
51747
51819
  kind: "mutation",
51748
51820
  auth: "protected"
51749
51821
  }),
51750
- setTiltPosition: method(object({
51822
+ setTiltPosition: optionalMethod(object({
51751
51823
  deviceId: number$1().int().nonnegative(),
51752
51824
  tiltPosition: number$1().min(0).max(100)
51753
51825
  }), _void(), {
@@ -52777,6 +52849,22 @@ method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(Expor
52777
52849
  });
52778
52850
  var DeviceAliasSchema = string().max(64).regex(/^[a-z][a-z0-9_]*$/);
52779
52851
  var DeviceTemplateSchema = string().min(1).max(4096);
52852
+ /** One value a caller hands the renderer for `thisDevice`. */
52853
+ var DeviceTemplateValueSchema = union([
52854
+ string(),
52855
+ number$1(),
52856
+ boolean()
52857
+ ]);
52858
+ /**
52859
+ * The device a render is FOR (`camera` in the text) and the values the caller
52860
+ * knows about it that no capability carries, for example `lastPerson` and
52861
+ * `lastPlate`. `values` read as `camera.<name>`; a name a capability also carries
52862
+ * is `ambiguous` and is written `camera.values.<name>`.
52863
+ */
52864
+ var DeviceTemplateThisDeviceSchema = object({
52865
+ deviceId: number$1().int(),
52866
+ values: record(string().min(1).max(64), DeviceTemplateValueSchema).optional()
52867
+ });
52780
52868
  var DeviceTemplateRefusalSchema = object({
52781
52869
  reason: _enum([
52782
52870
  "too-long",
@@ -53408,7 +53496,9 @@ var DeviceBindingsForDeviceSchema = object({
53408
53496
  kind: _enum(["native", "wrapped"]),
53409
53497
  providerAddonId: string(),
53410
53498
  providerNodeId: string(),
53411
- nativeAddonId: string()
53499
+ nativeAddonId: string(),
53500
+ /** The cap's methods the native provider implements; absent = all. */
53501
+ implementedMethods: array(string()).optional()
53412
53502
  }))
53413
53503
  });
53414
53504
  /**
@@ -53620,7 +53710,13 @@ method(object({
53620
53710
  auth: "admin"
53621
53711
  }), method(object({ deviceId: number$1() }), record(string(), unknown()), { auth: "admin" }), method(object({ deviceId: number$1() }), record(string(), unknown()), { auth: "admin" }), method(object({ deviceId: number$1() }), DeviceMetaSchema.nullable()), method(object({
53622
53712
  deviceId: number$1(),
53623
- name: string()
53713
+ name: string(),
53714
+ /**
53715
+ * Absent or true: an operator rename, which stamps `userNamed`.
53716
+ * False: the system copying a block name onto the device. That write
53717
+ * must not look like the operator's, or the next reconcile freezes it.
53718
+ */
53719
+ stampUserNamed: boolean().optional()
53624
53720
  }), _void(), {
53625
53721
  kind: "mutation",
53626
53722
  auth: "admin"
@@ -53645,17 +53741,24 @@ method(object({
53645
53741
  auth: "admin"
53646
53742
  }), method(object({
53647
53743
  templates: array(DeviceTemplateSchema).max(64),
53648
- allowedDeviceIds: array(number$1().int()).optional()
53744
+ allowedDeviceIds: array(number$1().int()).optional(),
53745
+ /** The device `camera` names in the texts, and the values the caller knows of it. */
53746
+ thisDevice: DeviceTemplateThisDeviceSchema.optional()
53649
53747
  }), object({ results: array(DeviceTemplateRenderSchema) }), { auth: "admin" }), method(object({
53650
53748
  templates: array(string()).max(64),
53651
- allowedDeviceIds: array(number$1().int()).optional()
53749
+ allowedDeviceIds: array(number$1().int()).optional(),
53750
+ thisDevice: DeviceTemplateThisDeviceSchema.optional()
53652
53751
  }), object({ results: array(discriminatedUnion("ok", [object({
53653
53752
  ok: literal(true),
53654
53753
  aliases: array(object({
53655
53754
  alias: string(),
53656
53755
  deviceId: number$1().int().nullable(),
53657
- allowed: boolean()
53658
- }))
53756
+ allowed: boolean(),
53757
+ /** The fields the text reads on this alias, as written (`temperature`, `cap.field`). */
53758
+ reads: array(string())
53759
+ })),
53760
+ /** Whether the text reads the clock (`now`): it changes every minute. */
53761
+ readsClock: boolean()
53659
53762
  }), object({
53660
53763
  ok: literal(false),
53661
53764
  refusal: DeviceTemplateRefusalSchema
@@ -56377,28 +56480,28 @@ var fanControlCapability = {
56377
56480
  mode: "singleton",
56378
56481
  deviceTypes: [DeviceType.Fan],
56379
56482
  methods: {
56380
- setPercentage: method(object({
56483
+ setPercentage: optionalMethod(object({
56381
56484
  deviceId: number$1().int().nonnegative(),
56382
56485
  speed: number$1().min(0).max(100)
56383
56486
  }), _void(), {
56384
56487
  kind: "mutation",
56385
56488
  auth: "protected"
56386
56489
  }),
56387
- setPreset: method(object({
56490
+ setPreset: optionalMethod(object({
56388
56491
  deviceId: number$1().int().nonnegative(),
56389
56492
  preset: string().min(1)
56390
56493
  }), _void(), {
56391
56494
  kind: "mutation",
56392
56495
  auth: "protected"
56393
56496
  }),
56394
- setDirection: method(object({
56497
+ setDirection: optionalMethod(object({
56395
56498
  deviceId: number$1().int().nonnegative(),
56396
56499
  direction: FanDirectionSchema
56397
56500
  }), _void(), {
56398
56501
  kind: "mutation",
56399
56502
  auth: "protected"
56400
56503
  }),
56401
- setOscillating: method(object({
56504
+ setOscillating: optionalMethod(object({
56402
56505
  deviceId: number$1().int().nonnegative(),
56403
56506
  oscillating: boolean()
56404
56507
  }), _void(), {
@@ -60503,7 +60606,7 @@ var lockControlCapability = {
60503
60606
  kind: "mutation",
60504
60607
  auth: "protected"
60505
60608
  }),
60506
- open: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
60609
+ open: optionalMethod(object({ deviceId: number$1().int().nonnegative() }), _void(), {
60507
60610
  kind: "mutation",
60508
60611
  auth: "protected"
60509
60612
  })
@@ -61700,7 +61803,7 @@ object({
61700
61803
  deviceId: number$1().int().optional(),
61701
61804
  atBefore: number$1().optional(),
61702
61805
  where: RecordWhereSchema.optional()
61703
- }), object({ count: number$1().int().nonnegative() }), object({}), object({ store: Rows }), object({ patch: Rows }), object({}), object({ keys: array(string().min(1)) }), object({ removed: array(string()) }), object({ refs: array(string()) }), object({ values: record(string(), string().nullable()) }), object({
61806
+ }), object({ count: number$1().int().nonnegative() }), object({}), object({ store: Rows }), object({ patch: Rows }), object({}), object({ keys: array(string().min(1)) }), object({ removed: array(string()) }), object({}), object({ deviceIds: array(number$1().int()) }), object({ deviceIds: array(number$1().int()) }), object({ blobs: record(string(), Rows) }), object({ refs: array(string()) }), object({ values: record(string(), string().nullable()) }), object({
61704
61807
  ref: string().min(1),
61705
61808
  value: string()
61706
61809
  }), object({}), object({ refs: array(string()) }), object({ deleted: number$1().int().nonnegative() }), object({}), object({ integrations: array(MigrationIntegrationSchema) }), object({ id: string().min(1) }), MigrationIntegrationSchema.nullable(), object({
@@ -61711,7 +61814,7 @@ object({
61711
61814
  integrationId: string().nullable(),
61712
61815
  /** Whether the device carries any stored config; its content is not offered. */
61713
61816
  hasConfig: boolean()
61714
- }).nullable(), object({
61817
+ }).nullable(), object({ deviceIds: array(number$1().int()) }), object({ aliases: record(string(), string()) }), object({
61715
61818
  deviceId: number$1().int(),
61716
61819
  integrationId: string().min(1)
61717
61820
  }), object({}), object({}), object({ nodeId: string() }), object({}), object({});
@@ -68989,92 +69092,21 @@ var NcZoneConditionSchema = object({
68989
69092
  * membership lists are OR within the list (spec §2.3).
68990
69093
  */
68991
69094
  /**
68992
- * What a rule may actuate.
68993
- *
68994
- * **No hand-maintained allowlist** (operator decision, and the right one — a
68995
- * written list of methods is a third parallel map to keep aligned, and this
68996
- * repo has paid for those). The boundary instead comes from a property the
68997
- * capabilities already carry: an action may target only a **device-scoped**
68998
- * capability method.
68999
- *
69000
- * That is not decoration. A rule can be authored by a NON-ADMIN — personal
69001
- * rules are a supported flow — and the executor runs with the addon's
69002
- * privileges, so an unbounded action is an arbitrary RPC channel with a
69003
- * privilege escalation attached. Restricting to device scope excludes the
69004
- * system caps (`device-manager.removeDevice` and friends) by construction,
69005
- * costs nothing to maintain, and cannot rot: a cap that stops being
69006
- * device-scoped stops being actuatable in the same change.
69007
- *
69008
- * The executor enforces it; {@link NcRuleActionSchema} carries the intent.
69009
- */
69010
- /**
69011
- * One step of a sequence.
69095
+ * What a rule or a button runs: an **Action** of the `actions` surface
69096
+ * (`internal:<uuid>` or an addon's), by id, and nothing else (planning#473).
69012
69097
  *
69013
- * `wait` is a first-class step rather than a property of the next action: it is
69014
- * what makes a sequence a SEQUENCE and not a list — "unlock, wait 5s, open"
69015
- * cannot be expressed otherwise.
69098
+ * The notification centre has no step vocabulary of its own. The steps, the
69099
+ * device-scope boundary, the stop-at-first-failure, the throttle
69100
+ * (`minDelaySec`), the variables and the runs ledger all belong to the Action;
69101
+ * an operator edits them in ONE place (the Actions page) and a rule points at
69102
+ * the result. A rule can be authored by a non-admin (personal rules), which is
69103
+ * safe for the same reason: it can only name an Action an admin already wrote.
69016
69104
  */
69017
- var NcRuleActionSchema = discriminatedUnion("kind", [object({
69018
- kind: literal("wait"),
69019
- seconds: number$1().min(0).max(300)
69020
- }), object({
69021
- kind: literal("cap"),
69022
- /**
69023
- * WHO the step acts on (D983) — the only shape that is written. A sequence
69024
- * stored before D983 carries `deviceId`; the sequence store reads it as a
69025
- * `devices` target.
69026
- */
69027
- target: ActionTargetSchema.optional(),
69028
- /** The vocabulary action the step performs; required for a `domain` target. */
69029
- actionId: string().min(1).optional(),
69030
- /** @deprecated The pre-D983 single device. Read, never written. */
69031
- deviceId: number$1().int().optional(),
69032
- /** Capability name, e.g. `alarm-panel`. */
69033
- cap: string().min(1),
69034
- /** Method on it. The executor refuses a non-device-scoped cap. */
69035
- method: string().min(1),
69036
- /** Method arguments, minus `deviceId` (the executor injects it). */
69037
- args: record(string(), unknown()).optional()
69038
- }).refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
69039
- message: "a cap step needs a target",
69040
- path: ["target"]
69041
- }).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
69042
- message: "a domain target needs the vocabulary action it applies to (actionId)",
69043
- path: ["actionId"]
69044
- })]);
69105
+ var NcActionIdSchema = string().min(1);
69106
+ /** Action ids one hook edge runs, in order. Capped so a slot never becomes a second editor. */
69107
+ var NcHookActionIdsSchema = array(NcActionIdSchema).max(4);
69045
69108
  /**
69046
- * A named, ordered run of steps shared across rules, with its own throttle.
69047
- *
69048
- * Sequences live in the notification centre's sequence store — never inline on
69049
- * a rule. A rule binds to one by id ({@link NcActionBindingSchema}), and a
69050
- * button names one the same way. `minDelaySec` exists because a noisy rule
69051
- * otherwise hammers a physical actuator — the rule's own cooldown governs
69052
- * NOTIFICATIONS, which is a different budget from "how often may this gate
69053
- * actually open". The throttle key is the sequence id, so two rules sharing
69054
- * one sequence share its budget.
69055
- */
69056
- var NcSharedSequenceSchema = object({
69057
- id: string().min(1),
69058
- name: string().min(1).max(120),
69059
- minDelaySec: number$1().int().min(0).max(86400).optional(),
69060
- actions: array(NcRuleActionSchema).min(1)
69061
- });
69062
- /** Create / update body — the id is minted by the provider. */
69063
- var NcSharedSequenceInputSchema = object({
69064
- name: string().min(1).max(120),
69065
- minDelaySec: number$1().int().min(0).max(86400).optional(),
69066
- actions: array(NcRuleActionSchema).min(1)
69067
- });
69068
- /** Bindings on one hook edge. Cap keeps a slot from becoming a second sequence editor. */
69069
- var NcHookBindingsSchema = array(discriminatedUnion("kind", [object({
69070
- kind: literal("sequence"),
69071
- sequenceId: string().min(1)
69072
- }), object({
69073
- kind: literal("do"),
69074
- action: NcRuleActionSchema
69075
- })])).max(4);
69076
- /**
69077
- * One button carried by the notification, running a shared sequence on tap.
69109
+ * One button carried by the notification, running an Action on tap.
69078
69110
  *
69079
69111
  * **Read this before adding a button that does something physical.** The tap
69080
69112
  * arrives over a link that travelled through third-party infrastructure — ntfy,
@@ -69089,9 +69121,9 @@ var NcRuleNotificationButtonSchema = object({
69089
69121
  /** Stable id — travels in the callback and identifies the button in logs. */
69090
69122
  id: string().min(1).max(64),
69091
69123
  label: string().min(1).max(40),
69092
- /** Id of a shared sequence. The dispatcher drops a button whose sequence
69093
- * does not exist rather than minting a token for nothing. */
69094
- sequenceId: string().min(1),
69124
+ /** The Action the tap runs. The dispatcher drops a button whose Action does
69125
+ * not exist rather than minting a token for nothing. */
69126
+ actionId: NcActionIdSchema,
69095
69127
  icon: NotificationActionIconSchema.optional(),
69096
69128
  destructive: boolean().optional()
69097
69129
  });
@@ -69106,25 +69138,19 @@ var NcRuleNotificationButtonSchema = object({
69106
69138
  * leave). A delivery with no clear edge never produces this hook.
69107
69139
  */
69108
69140
  var NcRuleActionsSchema = object({
69109
- onActivation: NcHookBindingsSchema.optional(),
69110
- onDeactivation: NcHookBindingsSchema.optional(),
69141
+ onActivation: NcHookActionIdsSchema.optional(),
69142
+ onDeactivation: NcHookActionIdsSchema.optional(),
69111
69143
  /** Runs on a positive / raised match. */
69112
- onTrigger: NcHookBindingsSchema.optional(),
69144
+ onTrigger: NcHookActionIdsSchema.optional(),
69113
69145
  /** Runs on a clear / leave match, when the subject carries one. */
69114
- onTriggerOff: NcHookBindingsSchema.optional(),
69146
+ onTriggerOff: NcHookActionIdsSchema.optional(),
69115
69147
  /**
69116
- * Buttons the NOTIFICATION carries, each running one shared sequence when
69117
- * tapped. Deliberately a REFERENCE — a button that could define its own
69118
- * actions would be a parallel actuation vocabulary.
69148
+ * Buttons the NOTIFICATION carries, each running one Action when tapped.
69149
+ * Deliberately a REFERENCE — a button that could define its own steps would
69150
+ * be a parallel actuation vocabulary.
69119
69151
  */
69120
69152
  buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
69121
69153
  });
69122
- object({
69123
- name: string().min(1).max(120),
69124
- enabled: boolean(),
69125
- minDelaySec: number$1().int().min(0).max(86400).optional(),
69126
- actions: array(NcRuleActionSchema).min(1)
69127
- });
69128
69154
  /**
69129
69155
  * "This rule applies only while `deviceId` is in one of `states`."
69130
69156
  *
@@ -69423,6 +69449,16 @@ var NcRuleTargetSchema = object({
69423
69449
  */
69424
69450
  params: record(string(), unknown()).optional()
69425
69451
  });
69452
+ var NcTargetOverrideSchema = object({
69453
+ template: object({
69454
+ title: string().max(500).optional(),
69455
+ body: string().max(2e3).optional()
69456
+ }).optional(),
69457
+ schedule: NcScheduleSchema.optional(),
69458
+ priority: number$1().int().min(1).max(5).optional()
69459
+ });
69460
+ /** Per-target settings keyed by target id (#487). */
69461
+ var NcTargetOverridesSchema = record(string(), NcTargetOverrideSchema);
69426
69462
  /**
69427
69463
  * WHAT THE NOTIFICATION'S PICTURE IS (D897) — the ONE media choice of a rule.
69428
69464
  *
@@ -69704,6 +69740,13 @@ var NcRuleInputSchema = object({
69704
69740
  * targets.
69705
69741
  */
69706
69742
  targetUsers: array(string()).optional(),
69743
+ /**
69744
+ * Per-target settings keyed by target id (#487): texts, schedule, priority.
69745
+ * Absent (and an absent entry) = the rule's own values. A patch that omits
69746
+ * the key keeps the stored map; a patch that names it REPLACES it whole —
69747
+ * `{}` empties it.
69748
+ */
69749
+ targetOverrides: NcTargetOverridesSchema.optional(),
69707
69750
  /** Absent = the kind's default picture (`resolveNcMediaImage`): the subject crop for a
69708
69751
  * detection, the clean scene for a sensor or a sound. Set explicitly on save (D897). */
69709
69752
  media: NcMediaPolicySchema.default({}),
@@ -70805,18 +70848,6 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
70805
70848
  }), object({ success: literal(true) }), {
70806
70849
  kind: "mutation",
70807
70850
  auth: "admin"
70808
- }), method(object({}), object({ sequences: array(NcSharedSequenceSchema) }), { auth: "admin" }), method(object({ sequenceId: string().min(1) }), object({ sequence: NcSharedSequenceSchema.nullable() }), { auth: "admin" }), method(object({ sequence: NcSharedSequenceInputSchema }), object({ sequence: NcSharedSequenceSchema }), {
70809
- kind: "mutation",
70810
- auth: "admin"
70811
- }), method(object({
70812
- sequenceId: string().min(1),
70813
- sequence: NcSharedSequenceInputSchema
70814
- }), object({ sequence: NcSharedSequenceSchema }), {
70815
- kind: "mutation",
70816
- auth: "admin"
70817
- }), method(object({ sequenceId: string().min(1) }), object({ success: literal(true) }), {
70818
- kind: "mutation",
70819
- auth: "admin"
70820
70851
  }), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number$1().int()).readonly() }), { auth: "admin" }), method(object({
70821
70852
  deviceId: number$1().int(),
70822
70853
  muted: boolean()
@@ -71255,53 +71286,41 @@ DeviceType.Camera, method(object({
71255
71286
  deviceId: number$1(),
71256
71287
  status: OsdStatusSchema
71257
71288
  });
71258
- _enum([
71259
- "value",
71260
- "unit",
71261
- "full"
71262
- ]);
71263
- _enum([
71264
- "value+unit",
71265
- "value",
71266
- "unit"
71267
- ]);
71268
71289
  /**
71269
- * How the operator reached the leaf device. `deviceId` is ALWAYS the
71270
- * OSD-supported leaf that is read; `containerId` is set when the pick
71271
- * went through a container (gateway) first.
71290
+ * `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
71291
+ *
71292
+ * The `osd` cap is the firmware contract: it probes a camera's overlay
71293
+ * SLOTS and writes literal text into one. It has no idea WHERE that text
71294
+ * comes from, and it must not — a driver that grew a "show the temperature
71295
+ * here" feature would grow it once per vendor.
71296
+ *
71297
+ * This cap owns the other half: a per-(camera, slot) BINDING that says
71298
+ * which value feeds the slot, how it is formatted, and under which
71299
+ * conditions it is shown at all. Named TEMPLATES combine many device
71300
+ * fields into one overlay string (plugin catalog OSD Manager parity).
71272
71301
  */
71273
- var OsdTemplatePickSchema = object({
71274
- containerId: number$1().int().optional(),
71275
- deviceId: number$1().int()
71276
- });
71277
71302
  /**
71278
- * One named binding inside a multi-device template: device path + unit
71279
- * selection + precision. The parser expands `{alias}`, `{alias.value}`,
71280
- * `{alias.unit}`, or `{alias.full}` from the same binding — there is no
71281
- * per-binding format lock.
71303
+ * The text of a named template: a device template (D1021) —
71304
+ * `{{ camera.temperature | round(1) }}`, `{{ now | time('HH:mm') }}`,
71305
+ * `{% if camera.motion %}MOTION{% endif %}` — rendered by the ONE hub renderer
71306
+ * (`device-manager.renderDeviceTemplates`), never by this addon.
71307
+ *
71308
+ * Two names belong to the render rather than to a device:
71309
+ * - `now` is the clock; `time('HH:mm')` prints it in the server's zone.
71310
+ * - `camera` is the camera the overlay is on, so one template serves every
71311
+ * camera. `camera.lastPerson` and `camera.lastPlate` are the most recent
71312
+ * recognition persisted for it (empty text until there is one, with
71313
+ * `| default('')`).
71282
71314
  */
71283
- var OsdTemplateBindingSchema = object({
71284
- /** Token stem in the parser, e.g. `t` → `{t}` / `{t.value}`. Unique within the template. */
71285
- alias: string().min(1).max(32).regex(/^[A-Za-z][A-Za-z0-9_-]*$/, "alias must start with a letter"),
71286
- pick: OsdTemplatePickSchema,
71287
- capName: string().min(1).max(64),
71288
- valuePath: string().min(1).max(64),
71289
- /** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
71290
- displayUnit: string().max(16).optional(),
71291
- maxDecimals: number$1().int().min(0).max(4).default(1),
71292
- /** Raw value → display text (same shape as slot binding). Empty string hides. */
71293
- valueMap: record(string(), string()).optional()
71294
- });
71315
+ var OSD_TEMPLATE_TEXT_MAX = 1024;
71295
71316
  /**
71296
- * A named template: bindings (alias → leaf field) + a parser that
71297
- * references `{alias}` / `{alias.value}` / `{alias.unit}` / `{alias.full}`.
71317
+ * A named template: the unit of reuse. A slot points at one by id; editing it
71318
+ * changes every slot that points at it, on every camera.
71298
71319
  */
71299
71320
  var OsdNamedTemplateSchema = object({
71300
71321
  id: string().min(1).max(64),
71301
71322
  name: string().min(1).max(64),
71302
- bindings: array(OsdTemplateBindingSchema).max(32).default([]),
71303
- /** Parser with alias placeholders, e.g. `{t.value} {t.unit} / {h}`. */
71304
- parser: string().max(256).default("")
71323
+ text: string().max(OSD_TEMPLATE_TEXT_MAX).default("")
71305
71324
  });
71306
71325
  /** Where a slot's value comes from. */
71307
71326
  var OsdSourceSchema = discriminatedUnion("kind", [
@@ -71310,26 +71329,6 @@ var OsdSourceSchema = discriminatedUnion("kind", [
71310
71329
  kind: literal("static"),
71311
71330
  text: string().max(64)
71312
71331
  }),
71313
- object({
71314
- kind: literal("clock"),
71315
- /** Token pattern: `YYYY MM DD HH mm ss`. Everything else is literal. */
71316
- pattern: string().min(1).max(32).default("HH:mm"),
71317
- /** IANA zone. Omitted = the server's zone. */
71318
- timezone: string().min(1).max(64).optional()
71319
- }),
71320
- object({
71321
- kind: literal("device-state"),
71322
- deviceId: number$1().int().optional(),
71323
- capName: string().min(1).max(64),
71324
- /** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
71325
- valuePath: string().min(1).max(64),
71326
- /** Convert TO this unit at render. Absent = no conversion. */
71327
- displayUnit: string().max(16).optional()
71328
- }),
71329
- object({
71330
- kind: literal("latest-recognition"),
71331
- recognition: _enum(["person", "plate"])
71332
- }),
71333
71332
  object({
71334
71333
  kind: literal("template"),
71335
71334
  templateId: string().min(1).max(64)
@@ -71339,15 +71338,6 @@ var OsdSlotBindingSchema = object({
71339
71338
  /** Off = the manager stops driving this slot. It does NOT clear it. */
71340
71339
  enabled: boolean().default(true),
71341
71340
  source: OsdSourceSchema,
71342
- /** `${value}` and `${unit}` are substituted; every occurrence. */
71343
- template: string().max(96).default("${value}"),
71344
- /**
71345
- * Decimal places for a numeric value. `0` yields an integer — the
71346
- * documented workaround for firmwares that reject `.` in overlay text.
71347
- */
71348
- maxDecimals: number$1().int().min(0).max(4).default(1),
71349
- /** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
71350
- valueMap: record(string(), string()).optional(),
71351
71341
  /** Time windows in which the slot is shown. Absent = always. */
71352
71342
  schedule: NcScheduleSchema.optional(),
71353
71343
  /**
@@ -71389,31 +71379,21 @@ var OsdRenderResultSchema = object({
71389
71379
  /** Why, whenever the outcome is not a plain write. Never silent. */
71390
71380
  reason: string().optional()
71391
71381
  });
71392
- var OsdSourceValueTypeEnum = _enum([
71393
- "number",
71394
- "boolean",
71395
- "string",
71396
- "enum"
71397
- ]);
71398
71382
  /**
71399
- * One bindable value, derived from a cap's `runtimeState` schema — never
71400
- * hand-listed. The editor renders from this, so a cap that ships a new
71401
- * state field becomes bindable with no UI change.
71383
+ * A template rendered for one camera without being saved: what the editor
71384
+ * previews. `text` is what the overlay would carry; a refused render has a
71385
+ * `reason` and an empty text.
71402
71386
  */
71403
- var OsdSourceOptionSchema = object({
71404
- deviceId: number$1().int(),
71405
- deviceName: string(),
71406
- capName: string(),
71407
- valuePath: string(),
71408
- label: string(),
71409
- valueType: OsdSourceValueTypeEnum,
71410
- /** Present for `enum`; the editor offers these as `valueMap` keys. */
71411
- enumValues: array(string()).readonly().optional()
71387
+ var OsdTemplatePreviewSchema = object({
71388
+ ok: boolean(),
71389
+ text: string(),
71390
+ /** The refusal, or `ref: reason` per value that printed `—`. */
71391
+ notes: array(string())
71412
71392
  });
71413
71393
  method(object({ deviceId: number$1().int() }), object({
71414
71394
  supported: boolean(),
71415
71395
  slots: array(OsdSlotViewSchema)
71416
- }), { auth: "admin" }), method(object({ deviceId: number$1().int() }), object({ sources: array(OsdSourceOptionSchema) }), { auth: "admin" }), method(object({}), object({
71396
+ }), { auth: "admin" }), method(object({}), object({
71417
71397
  supported: array(string()),
71418
71398
  catalog: array(NcConditionDescriptorSchema)
71419
71399
  }), { auth: "admin" }), method(object({
@@ -71455,6 +71435,12 @@ method(object({ deviceId: number$1().int() }), object({
71455
71435
  }), OsdRenderResultSchema, {
71456
71436
  kind: "mutation",
71457
71437
  auth: "admin"
71438
+ }), method(object({
71439
+ deviceId: number$1().int(),
71440
+ text: string().max(OSD_TEMPLATE_TEXT_MAX)
71441
+ }), OsdTemplatePreviewSchema, {
71442
+ kind: "mutation",
71443
+ auth: "admin"
71458
71444
  }), method(object({ deviceId: number$1().int() }), object({ results: array(OsdRenderResultSchema) }), {
71459
71445
  kind: "mutation",
71460
71446
  auth: "admin"
@@ -81054,7 +81040,7 @@ var valveCapability = {
81054
81040
  kind: "mutation",
81055
81041
  auth: "protected"
81056
81042
  }),
81057
- setPosition: method(object({
81043
+ setPosition: optionalMethod(object({
81058
81044
  deviceId: number$1().int().nonnegative(),
81059
81045
  position: number$1().min(0).max(100)
81060
81046
  }), _void(), {
@@ -84335,6 +84321,7 @@ var BaseDeviceProvider = class extends BaseAddon {
84335
84321
  return toDeviceSummary(device, this.addonId);
84336
84322
  }
84337
84323
  };
84324
+ DeviceType.HeatPump;
84338
84325
  DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Heater, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
84339
84326
  new Set(Object.values(DeviceType));
84340
84327
  /** Parses the raw slice with the cap's own schema; `undefined` when it does not parse. */
@@ -86081,7 +86068,7 @@ var CLIMATE_FAMILY_CONTRIBUTIONS = {
86081
86068
  /**
86082
86069
  * The heat pump's contribution to a hero (D1026): the compressor scene, every
86083
86070
  * circuit as a row with its own label and icon, the powers as pills (the
86084
- * instantaneous COP only when both powers are known) and a "System" group with
86071
+ * instantaneous COP is "—" when it cannot be computed) and a "System" group with
86085
86072
  * one selector per mode. Values and their limits come from the slice, which the
86086
86073
  * gateway filled: a selector with no allowed value is not drawn, a missing
86087
86074
  * reading is never a 0 (D393).
@@ -86153,9 +86140,22 @@ function circuitRow(circuit) {
86153
86140
  };
86154
86141
  }
86155
86142
  var kilowatts = (watts) => `${Number((watts / 1e3).toFixed(2))} kW`;
86143
+ /** A COP that cannot be computed is still the pill: "—", never an absent reading (D393). */
86144
+ function copPill(cop) {
86145
+ if (cop === null) return {
86146
+ icon: "gauge",
86147
+ text: t("Instantaneous COP —", "COP istantaneo —"),
86148
+ tone: "muted"
86149
+ };
86150
+ const text = String(Number(cop.toFixed(1)));
86151
+ return {
86152
+ icon: "gauge",
86153
+ text: t(`Instantaneous COP ${text}`, `COP istantaneo ${text}`),
86154
+ tone: "success"
86155
+ };
86156
+ }
86156
86157
  function powerPills(status) {
86157
86158
  const { thermalW, electricalW } = status.power;
86158
- const cop = heatPumpInstantCop(status.power);
86159
86159
  const pills = [];
86160
86160
  if (thermalW !== null) pills.push({
86161
86161
  icon: "flame",
@@ -86167,14 +86167,7 @@ function powerPills(status) {
86167
86167
  text: t(`Electrical ${kilowatts(electricalW)}`, `Elettrica ${kilowatts(electricalW)}`),
86168
86168
  tone: "muted"
86169
86169
  });
86170
- if (cop !== null) {
86171
- const text = String(Number(cop.toFixed(1)));
86172
- pills.push({
86173
- icon: "gauge",
86174
- text: t(`Instantaneous COP ${text}`, `COP istantaneo ${text}`),
86175
- tone: "success"
86176
- });
86177
- }
86170
+ pills.push(copPill(heatPumpInstantCop(status.power)));
86178
86171
  return pills;
86179
86172
  }
86180
86173
  /** The independent system modes, in the order the card draws them. */
@@ -92352,13 +92345,6 @@ Object.freeze({
92352
92345
  access: "create",
92353
92346
  role: "admin"
92354
92347
  },
92355
- "notificationRules.createSequence": {
92356
- capName: "notification-rules",
92357
- capScope: "system",
92358
- addonId: null,
92359
- access: "create",
92360
- role: "admin"
92361
- },
92362
92348
  "notificationRules.createSnooze": {
92363
92349
  capName: "notification-rules",
92364
92350
  capScope: "system",
@@ -92380,13 +92366,6 @@ Object.freeze({
92380
92366
  access: "delete",
92381
92367
  role: "admin"
92382
92368
  },
92383
- "notificationRules.deleteSequence": {
92384
- capName: "notification-rules",
92385
- capScope: "system",
92386
- addonId: null,
92387
- access: "delete",
92388
- role: "admin"
92389
- },
92390
92369
  "notificationRules.getAlarmConfig": {
92391
92370
  capName: "notification-rules",
92392
92371
  capScope: "system",
@@ -92436,13 +92415,6 @@ Object.freeze({
92436
92415
  access: "view",
92437
92416
  role: "admin"
92438
92417
  },
92439
- "notificationRules.getSequence": {
92440
- capName: "notification-rules",
92441
- capScope: "system",
92442
- addonId: null,
92443
- access: "view",
92444
- role: "admin"
92445
- },
92446
92418
  "notificationRules.getTemplateCatalog": {
92447
92419
  capName: "notification-rules",
92448
92420
  capScope: "system",
@@ -92464,13 +92436,6 @@ Object.freeze({
92464
92436
  access: "view",
92465
92437
  role: "admin"
92466
92438
  },
92467
- "notificationRules.listSequences": {
92468
- capName: "notification-rules",
92469
- capScope: "system",
92470
- addonId: null,
92471
- access: "view",
92472
- role: "admin"
92473
- },
92474
92439
  "notificationRules.listSnoozes": {
92475
92440
  capName: "notification-rules",
92476
92441
  capScope: "system",
@@ -92541,13 +92506,6 @@ Object.freeze({
92541
92506
  access: "create",
92542
92507
  role: "admin"
92543
92508
  },
92544
- "notificationRules.updateSequence": {
92545
- capName: "notification-rules",
92546
- capScope: "system",
92547
- addonId: null,
92548
- access: "create",
92549
- role: "admin"
92550
- },
92551
92509
  "notifier.cancel": {
92552
92510
  capName: "notifier",
92553
92511
  capScope: "device",
@@ -92632,21 +92590,21 @@ Object.freeze({
92632
92590
  access: "view",
92633
92591
  role: "admin"
92634
92592
  },
92635
- "osdManager.getSourceCatalog": {
92593
+ "osdManager.listTemplates": {
92636
92594
  capName: "osd-manager",
92637
92595
  capScope: "system",
92638
92596
  addonId: null,
92639
92597
  access: "view",
92640
92598
  role: "admin"
92641
92599
  },
92642
- "osdManager.listTemplates": {
92600
+ "osdManager.previewSlot": {
92643
92601
  capName: "osd-manager",
92644
92602
  capScope: "system",
92645
92603
  addonId: null,
92646
- access: "view",
92604
+ access: "create",
92647
92605
  role: "admin"
92648
92606
  },
92649
- "osdManager.previewSlot": {
92607
+ "osdManager.previewTemplate": {
92650
92608
  capName: "osd-manager",
92651
92609
  capScope: "system",
92652
92610
  addonId: null,
@@ -97436,6 +97394,24 @@ function projectAction(cap, action) {
97436
97394
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
97437
97395
  DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Heater, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
97438
97396
  DeviceFeature.BatteryOperated;
97397
+ function bound(cap, methods = [], authority = "reported") {
97398
+ return {
97399
+ kind: "cap",
97400
+ cap,
97401
+ methods,
97402
+ authority
97403
+ };
97404
+ }
97405
+ function derived(cap, methods = []) {
97406
+ return bound(cap, methods, "derived");
97407
+ }
97408
+ DeviceFeature.BatteryOperated, DeviceFeature.Rebootable, derived("reboot", ["reboot"]), DeviceFeature.Resyncable, DeviceFeature.NativeSnapshot, DeviceFeature.DoorbellButton, derived("doorbell"), DeviceFeature.TwoWayAudio, derived("intercom", [
97409
+ "startTalkSession",
97410
+ "pushTalkAudio",
97411
+ "endTalkSession"
97412
+ ]), DeviceFeature.PanTiltZoom, derived("ptz"), DeviceFeature.PtzAutotrack, derived("ptz-autotrack"), DeviceFeature.MotionTrigger, derived("motion-trigger", ["setMotionTrigger"]), DeviceFeature.TimedRun, derived("timed-run", ["runFor"]), DeviceFeature.Switch, DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired, DeviceFeature.ClimateDualSetpoint, DeviceFeature.ClimateHumidity, DeviceFeature.ClimateFanMode, DeviceFeature.ClimatePreset, DeviceFeature.ClimateSwingVertical, DeviceFeature.ClimateSwingHorizontal, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.CoverStoppable, DeviceFeature.ValvePositionable, DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.LockPinRequired, DeviceFeature.LockOpen, DeviceFeature.MediaPlayerSeek, DeviceFeature.MediaPlayerVolume, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerShuffle, DeviceFeature.MediaPlayerRepeat, DeviceFeature.MediaPlayerSelectSource, DeviceFeature.MediaPlayerPlayMedia, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerPower, DeviceFeature.AlarmPinRequired, DeviceFeature.PresenceGps, DeviceFeature.NotifierImage, DeviceFeature.NotifierPriority, DeviceFeature.NotifierData, DeviceFeature.NotifierActions, DeviceFeature.NotifierRecipients, DeviceFeature.ScriptVariables, DeviceFeature.AutomationSkipCondition, DeviceFeature.VacuumHasMap, DeviceFeature.VacuumHasAi, DeviceFeature.MowerHasMap, DeviceFeature.MowerHasTargetedMowing, DeviceFeature.Navigation, derived("navigation"), DeviceFeature.NetworkLink, derived("network-link");
97413
+ var ALL_FEATURES = Object.values(DeviceFeature);
97414
+ new Set(ALL_FEATURES);
97439
97415
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
97440
97416
  var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";
97441
97417
  var HOMEKIT_REASON = "HomeKit has no laundry-appliance service. The export tab would pair an accessory with nothing on it: running-state is withheld until a null reading can be unavailable, and power is not a HomeKit service this export honours.";
@@ -99091,12 +99067,12 @@ Object.freeze({
99091
99067
  form: "single",
99092
99068
  optional: false
99093
99069
  }],
99094
- "osdManager.getSourceCatalog": [{
99070
+ "osdManager.previewSlot": [{
99095
99071
  name: "deviceId",
99096
99072
  form: "single",
99097
99073
  optional: false
99098
99074
  }],
99099
- "osdManager.previewSlot": [{
99075
+ "osdManager.previewTemplate": [{
99100
99076
  name: "deviceId",
99101
99077
  form: "single",
99102
99078
  optional: false