@camstack/addon-provider-bosch 0.1.47 → 0.1.49

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/composed-stable-id-DLcPInGv.mjs
805
+ //#region ../types/dist/composed-stable-id-DFNyOFzz.mjs
806
806
  /**
807
807
  import { errMsg } from '@camstack/types'
808
808
  * Extract a human-readable message from an unknown error value.
@@ -815,6 +815,7 @@ function errMsg(err) {
815
815
  }
816
816
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
817
817
  DeviceType["Camera"] = "camera";
818
+ /** Legacy stored value, read only by the hub-to-integration migrations; no live device carries it. */
818
819
  DeviceType["Hub"] = "hub";
819
820
  DeviceType["Light"] = "light";
820
821
  DeviceType["Siren"] = "siren";
@@ -1090,6 +1091,10 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
1090
1091
  /** Robot mower exposes targeted mowing (all-area / zones / edges /
1091
1092
  * spots) — gates the Mow action + map selectors. */
1092
1093
  DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
1094
+ /** The device has a drive/navigation surface (`navigation` cap): a robot camera. */
1095
+ DeviceFeature["Navigation"] = "navigation";
1096
+ /** The device has a network-link reading (`network-link` cap). */
1097
+ DeviceFeature["NetworkLink"] = "network-link";
1093
1098
  return DeviceFeature;
1094
1099
  }({});
1095
1100
  /**
@@ -6022,7 +6027,7 @@ var ZodIssueCode = {
6022
6027
  var ZodFirstPartyTypeKind;
6023
6028
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6024
6029
  //#endregion
6025
- //#region ../types/dist/sleep-B36hHDDz.mjs
6030
+ //#region ../types/dist/sleep-D_NpiLFP.mjs
6026
6031
  /**
6027
6032
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6028
6033
  * window to float samples (D455).
@@ -10109,15 +10114,52 @@ var PeerBytesTicketSchema = object({
10109
10114
  */
10110
10115
  declaredBytes: number$1().int().nonnegative().nullable()
10111
10116
  });
10112
- discriminatedUnion("kind", [object({
10113
- kind: literal("action"),
10114
- actionId: string(),
10115
- runId: string(),
10116
- automationId: string().optional()
10117
- }), object({
10118
- kind: literal("scheduler"),
10119
- schedulerId: string()
10120
- })]);
10117
+ /**
10118
+ * The match a notification rule fired on (D705). Carried as a command origin so an
10119
+ * Action the rule starts can press `alarm-panel.trigger` as that match, not as an
10120
+ * anonymous press (D735). The same fields a rule fire ref freezes.
10121
+ */
10122
+ var RuleFireOriginSchema = object({
10123
+ ruleId: string(),
10124
+ ruleName: string(),
10125
+ deviceId: number$1().int(),
10126
+ sourceDeviceId: number$1().int().optional(),
10127
+ recordId: string(),
10128
+ timestamp: number$1(),
10129
+ trackId: string().optional(),
10130
+ eventId: string().optional(),
10131
+ className: string().optional(),
10132
+ label: string().optional(),
10133
+ zones: array(string()).optional(),
10134
+ confidence: number$1().optional(),
10135
+ sensorChange: object({
10136
+ name: string(),
10137
+ kind: string(),
10138
+ state: string()
10139
+ }).optional(),
10140
+ extraStills: array(object({
10141
+ ownerId: string(),
10142
+ cameraId: number$1().int()
10143
+ })).optional()
10144
+ });
10145
+ discriminatedUnion("kind", [
10146
+ object({
10147
+ kind: literal("action"),
10148
+ actionId: string(),
10149
+ runId: string(),
10150
+ automationId: string().optional(),
10151
+ /** The rule fire that started this run, when one did (D705). */
10152
+ ruleFire: RuleFireOriginSchema.optional()
10153
+ }),
10154
+ object({
10155
+ kind: literal("scheduler"),
10156
+ schedulerId: string()
10157
+ }),
10158
+ object({
10159
+ kind: literal("rule"),
10160
+ fire: RuleFireOriginSchema
10161
+ })
10162
+ ]);
10121
10163
  /**
10122
10164
  * One recorded device transition (D1022): a status field of a device went from
10123
10165
  * one value to another, with who or what asked for it.
@@ -13851,10 +13893,6 @@ var CAMERA_CAP_VOCABULARY = {
13851
13893
  scope: "device",
13852
13894
  noun: t("Admin link", "Link di amministrazione")
13853
13895
  },
13854
- "device-discovery": {
13855
- scope: "device",
13856
- noun: t("Discovery", "Rilevamento dispositivi")
13857
- },
13858
13896
  "device-extension": {
13859
13897
  scope: "device",
13860
13898
  noun: t("Device extension", "Estensione del dispositivo")
@@ -15026,6 +15064,7 @@ var SYSTEM_CAP_VOCABULARY = {
15026
15064
  backup: system("system backups — run and restored from the Backups page"),
15027
15065
  broker: system("message-broker connections — configured, not actuated"),
15028
15066
  "calendar-source": system(INTERNAL),
15067
+ "cluster-update": system(CLUSTER),
15029
15068
  calendars: system("calendars feed schedules and conditions, not device actions"),
15030
15069
  composer: system("device composition — an authoring tool, not an action"),
15031
15070
  "core-blocks": system(INTERNAL),
@@ -15315,6 +15354,7 @@ var CAP_NAMES = Object.freeze([
15315
15354
  "camera-streams",
15316
15355
  "carbon-monoxide",
15317
15356
  "climate-control",
15357
+ "cluster-update",
15318
15358
  "color",
15319
15359
  "composer",
15320
15360
  "connectivity",
@@ -15332,7 +15372,6 @@ var CAP_NAMES = Object.freeze([
15332
15372
  "device-admin-link",
15333
15373
  "device-adoption",
15334
15374
  "device-credentials",
15335
- "device-discovery",
15336
15375
  "device-export",
15337
15376
  "device-extension",
15338
15377
  "device-history",
@@ -15954,13 +15993,6 @@ Object.freeze({
15954
15993
  "valueType": "string"
15955
15994
  }
15956
15995
  ],
15957
- "device-discovery": [{
15958
- "path": "lastDiscoveryAt",
15959
- "valueType": "number"
15960
- }, {
15961
- "path": "lastError",
15962
- "valueType": "string"
15963
- }],
15964
15996
  "device-export": [
15965
15997
  {
15966
15998
  "path": "linkState",
@@ -17412,7 +17444,7 @@ Object.freeze({
17412
17444
  }
17413
17445
  });
17414
17446
  //#endregion
17415
- //#region ../types/dist/composition-BFJq1Y88.mjs
17447
+ //#region ../types/dist/implemented-methods-M5aguGon.mjs
17416
17448
  /**
17417
17449
  * The ffmpeg build every CamStack node downloads, per platform and arch.
17418
17450
  *
@@ -19352,18 +19384,43 @@ var TargetKindLevelSchema = object({
19352
19384
  requires: array(string()).optional(),
19353
19385
  description: string().optional()
19354
19386
  });
19387
+ /** Attachment capabilities for a kind (drives the degrade engine + test panel). */
19388
+ var TargetKindAttachmentsCapsSchema = object({
19389
+ mediaTypes: array(AttachmentMediaTypeSchema),
19390
+ mode: _enum([
19391
+ "url",
19392
+ "bytes",
19393
+ "both"
19394
+ ]),
19395
+ max: number$1().int().nonnegative(),
19396
+ maxBytes: number$1().int().positive().optional()
19397
+ });
19398
+ /**
19399
+ * Which TEXT fields a kind delivers (#483). Alexa speaks a title and nothing
19400
+ * else; a push service shows both.
19401
+ *
19402
+ * `.optional()` on the caps block, never defaulted: absent = both, which is
19403
+ * what every kind declared before this field meant (D938's rule for a Zod
19404
+ * default on the addon cap path).
19405
+ */
19406
+ var TargetKindTextCapsSchema = object({
19407
+ title: boolean(),
19408
+ body: boolean()
19409
+ });
19410
+ _enum([
19411
+ "title",
19412
+ "body",
19413
+ "attachments",
19414
+ "actions",
19415
+ "priority",
19416
+ "sound",
19417
+ "clickUrl"
19418
+ ]);
19355
19419
  /** The full capability block consulted before dispatch. */
19356
19420
  var TargetKindCapsSchema = object({
19357
- attachments: object({
19358
- mediaTypes: array(AttachmentMediaTypeSchema),
19359
- mode: _enum([
19360
- "url",
19361
- "bytes",
19362
- "both"
19363
- ]),
19364
- max: number$1().int().nonnegative(),
19365
- maxBytes: number$1().int().positive().optional()
19366
- }),
19421
+ /** Text fields this kind delivers (#483). Absent = title AND body. */
19422
+ text: TargetKindTextCapsSchema.optional(),
19423
+ attachments: TargetKindAttachmentsCapsSchema,
19367
19424
  /** Max action buttons (0 = none). */
19368
19425
  actions: number$1().int().nonnegative(),
19369
19426
  /**
@@ -44027,6 +44084,62 @@ var WeekdayKeySchema = _enum([
44027
44084
  "sat",
44028
44085
  "sun"
44029
44086
  ]);
44087
+ /**
44088
+ * Hold semantics — "for N seconds" on a leaf of the `when` tree (#498).
44089
+ *
44090
+ * ONE primitive, `for: { ms, mode, tolerance? }`, shared by `device-becomes`,
44091
+ * `device-is` and `expression`:
44092
+ *
44093
+ * - `held`: the condition stays true for `ms`;
44094
+ * - `stable`: the value does not move by more than `tolerance` (absolute, in
44095
+ * the field's base unit) for `ms` while the condition holds.
44096
+ *
44097
+ * {@link HoldCore} is the pure state machine (an input observation → a reading).
44098
+ * {@link HoldTracker} keys cores by a leaf path for the STATE leaves the tree
44099
+ * evaluator judges. {@link BecomesWatch} wraps a core with a timer for the
44100
+ * `device-becomes` EVENT leaf: the deadline is armed when the hold starts and
44101
+ * cancelled when it breaks, and the leaf fires ONCE per episode.
44102
+ *
44103
+ * Hold state is memory only. A hold that is already running when the watcher
44104
+ * first looks (a restart) is INHERITED: nobody knows when it began, so it never
44105
+ * fires for that episode and a state leaf reads it as unknown until the window
44106
+ * has elapsed from the moment the watcher started (D49: not-known is not false).
44107
+ */
44108
+ var HOLD_MAX_MS = 864e5;
44109
+ var HoldModeSchema = _enum(["held", "stable"]);
44110
+ var HoldSpecSchema = object({
44111
+ /** How long the condition (or the value) must hold. */
44112
+ ms: number$1().int().min(1).max(HOLD_MAX_MS),
44113
+ mode: HoldModeSchema,
44114
+ /** `stable` only: the largest move of the value that still counts as unchanged (absolute, base unit). */
44115
+ tolerance: number$1().min(0).optional()
44116
+ });
44117
+ /** What a trace says about a hold. */
44118
+ var HoldTraceSchema = object({
44119
+ phase: _enum([
44120
+ "pending",
44121
+ "cancelled",
44122
+ "fired",
44123
+ "reseeded"
44124
+ ]),
44125
+ ms: number$1().int(),
44126
+ mode: HoldModeSchema,
44127
+ /** When the hold started (ms epoch). */
44128
+ since: number$1().optional(),
44129
+ /** When it completes (ms epoch), for a pending hold. */
44130
+ deadline: number$1().optional()
44131
+ });
44132
+ /** The operators a `device-becomes` leaf may carry: a threshold or a range. */
44133
+ var BecomesOperatorSchema = _enum([
44134
+ "eq",
44135
+ "ne",
44136
+ "gt",
44137
+ "gte",
44138
+ "lt",
44139
+ "lte",
44140
+ "between",
44141
+ "outside"
44142
+ ]);
44030
44143
  /** How a state leaf compares a device field to a value. Derived from the
44031
44144
  * field's `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
44032
44145
  var AutomationConditionOperatorSchema = _enum([
@@ -44050,18 +44163,52 @@ var DeviceBecomesLeafSchema = object({
44050
44163
  deviceId: number$1().int().nonnegative(),
44051
44164
  cap: string().min(1),
44052
44165
  fieldPath: string().min(1),
44053
- /** Fire when the field takes this value. Omit to fire on any change. */
44166
+ /** Fire when the field takes this value. Omit (with no `operator`) to fire on any change. */
44054
44167
  becomes: union([
44055
44168
  string(),
44056
44169
  number$1(),
44057
44170
  boolean()
44058
44171
  ]).optional(),
44059
- /** Only on a CHANGE of value, not on every re-report. */
44172
+ /**
44173
+ * A threshold or a range instead of an equality: "crosses above 3000", "enters
44174
+ * 18..24". The leaf fires on the TRANSITION into the predicate, never on a re-report.
44175
+ * `value` is in the field's base unit; `between` / `outside` take `[low, high]`.
44176
+ */
44177
+ operator: BecomesOperatorSchema.optional(),
44178
+ value: union([
44179
+ number$1(),
44180
+ string(),
44181
+ boolean(),
44182
+ array(number$1()).length(2)
44183
+ ]).optional(),
44184
+ /**
44185
+ * Hold: the transition (or, with no predicate, the new value) must stay for
44186
+ * `for.ms` before the leaf fires, ONCE per episode. See `hold.ts`.
44187
+ */
44188
+ for: HoldSpecSchema.optional(),
44189
+ /** @deprecated every leaf fires on a transition now; read as always on. */
44060
44190
  edge: boolean().optional(),
44061
- /** The new value must hold this long before it counts. */
44062
- forMs: number$1().int().min(0).max(864e5).optional(),
44191
+ /** @deprecated legacy `held` window — read through `holdOf` as `for: { ms, mode: 'held' }`. */
44192
+ forMs: number$1().int().min(0).max(HOLD_MAX_MS).optional(),
44063
44193
  /** Collapse a burst into one occurrence. */
44064
44194
  debounceMs: number$1().int().min(0).max(6e5).optional()
44195
+ }).superRefine((leaf, ctx) => {
44196
+ if (leaf.becomes !== void 0 && leaf.operator !== void 0) ctx.addIssue({
44197
+ code: "custom",
44198
+ message: "a leaf names `becomes` or `operator`, not both",
44199
+ path: ["operator"]
44200
+ });
44201
+ if (leaf.operator !== void 0 && leaf.value === void 0) ctx.addIssue({
44202
+ code: "custom",
44203
+ message: "an operator needs the value to compare to",
44204
+ path: ["value"]
44205
+ });
44206
+ const range = leaf.operator === "between" || leaf.operator === "outside";
44207
+ if (leaf.operator !== void 0 && range !== Array.isArray(leaf.value)) ctx.addIssue({
44208
+ code: "custom",
44209
+ message: range ? "a range takes [low, high]" : "only a range takes [low, high]",
44210
+ path: ["value"]
44211
+ });
44065
44212
  });
44066
44213
  var DeviceEventLeafSchema = object({
44067
44214
  kind: literal("device-event"),
@@ -44126,7 +44273,9 @@ var DeviceIsLeafSchema = object({
44126
44273
  QuantityValueSchema
44127
44274
  ]),
44128
44275
  /** The unit the person picked for `value` (display only; `value` is in the base unit). */
44129
- unit: string().min(1).optional()
44276
+ unit: string().min(1).optional(),
44277
+ /** The comparison must hold (`held`) or the value stay put (`stable`) this long. */
44278
+ for: HoldSpecSchema.optional()
44130
44279
  });
44131
44280
  /**
44132
44281
  * True when the camera currently has a PARKED object matching `subjects` in at
@@ -44149,7 +44298,9 @@ var StationaryPresentLeafSchema = object({
44149
44298
  var ExpressionLeafSchema = object({
44150
44299
  kind: literal("expression"),
44151
44300
  expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
44152
- bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
44301
+ bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema),
44302
+ /** The expression must stay true this long. An expression has no single value, so only `held`. */
44303
+ for: HoldSpecSchema.optional()
44153
44304
  }).superRefine((src, ctx) => {
44154
44305
  const err = validateExpressionSource(src);
44155
44306
  if (err !== null) ctx.addIssue({
@@ -44157,6 +44308,11 @@ var ExpressionLeafSchema = object({
44157
44308
  message: err,
44158
44309
  path: ["expr"]
44159
44310
  });
44311
+ if (src.for?.mode === "stable") ctx.addIssue({
44312
+ code: "custom",
44313
+ message: "an expression is held, not stable: it has no single value to keep still",
44314
+ path: ["for", "mode"]
44315
+ });
44160
44316
  });
44161
44317
  var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
44162
44318
  object({
@@ -44487,30 +44643,21 @@ function convertLegacyWhen(half) {
44487
44643
  * (instants) and state leaves (durations). It replaces the flat trigger list
44488
44644
  * plus the separate condition tree; the firing rule is in that file and in
44489
44645
  * ADR "one tree".
44490
- * - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
44491
- * fragment of §3.2.2. See {@link AutomationActionSchema} for the ONE place
44492
- * this had to diverge from "verbatim", and why.
44646
+ * - **Actions** — the `wait` and `cap` steps, plus the `{kind:'code'}`
44647
+ * fragment of §3.2.2. See {@link AutomationActionSchema}.
44493
44648
  *
44494
44649
  * A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
44495
44650
  * {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
44496
44651
  */
44497
44652
  /**
44498
- * One action step.
44653
+ * One action step: `wait`, or a `cap` call.
44499
44654
  *
44500
- * `wait` and `cap` are `NcRuleActionSchema`'s two members, kept structurally
44501
- * identical so `NcRuleActionRunner` runs them unchanged — its device-scope
44502
- * check, stop-at-first-failure and per-sequence throttle are the whole reason
44503
- * to reuse it, and none of them are re-implemented here.
44504
- *
44505
- * **The one divergence, and it is forced.** `NcRuleActionSchema.cap.deviceId` is
44506
- * a literal `z.number().int()`, and the NC runner's own `RunSequencesInput`
44507
- * documents its subject device as *"for the log tag, never for routing"*. So an
44508
- * NC action can never target the device that triggered it — which is fine for
44509
- * the NC (its rules already scope to a device) and fatal for an automation
44510
- * ("sound the siren of the camera that saw the person"). `deviceId` therefore
44511
- * also accepts `{ $var }`, resolved from the run's `vars` bag BEFORE the runner
44512
- * is called. The runner still receives a number and is untouched; the
44513
- * resolution is the recipe's job, not the runner's.
44655
+ * A cap step's target is who it acts on (D983: `devices`, a `domain`, or a
44656
+ * `{ $var }` resolved from the run's variables); the legacy `deviceId` is still
44657
+ * READ, never written. The step runner (`runSteps`) owns the device-scope
44658
+ * check, the stop-at-first-failure and the variable substitution, once, for
44659
+ * every caller — automations, Actions, and the notification centre, which runs
44660
+ * only Actions (planning#473).
44514
44661
  */
44515
44662
  var AutomationWaitStepSchema = object({
44516
44663
  kind: literal("wait"),
@@ -44925,7 +45072,9 @@ var AutomationTriggerTraceSchema = object({
44925
45072
  /** Why an event was rejected ("class person not in [dog]") or what fired. */
44926
45073
  reason: string(),
44927
45074
  /** deviceId, className, trackId, score … — only what the trigger exposes. */
44928
- vars: record(string(), TraceValueSchema)
45075
+ vars: record(string(), TraceValueSchema),
45076
+ /** A hold (`for`) this occurrence went through: pending, cancelled, fired or re-seeded. */
45077
+ hold: HoldTraceSchema.optional()
44929
45078
  });
44930
45079
  var AutomationConditionTraceNodeSchema = object({
44931
45080
  /** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
@@ -45128,6 +45277,8 @@ var ActionStatusSchema = object({
45128
45277
  /** The latest run's failure; null on success or when it never ran. */
45129
45278
  lastError: string().nullable()
45130
45279
  });
45280
+ /** The throttle window of an action, in seconds; absent or 0 = no throttle. */
45281
+ var ActionMinDelaySecSchema = number$1().int().min(0).max(86400);
45131
45282
  var ActionEntrySchema = object({
45132
45283
  /** `internal:<uuid>` or `<addonId>:<localId>`. */
45133
45284
  id: string().min(3),
@@ -45140,6 +45291,14 @@ var ActionEntrySchema = object({
45140
45291
  /** Internal only: cap and wait steps. No action-in-action, so no cycles. */
45141
45292
  steps: array(ActionStepSchema).min(1).max(50).optional(),
45142
45293
  variables: array(ActionVariableSchema).max(20).optional(),
45294
+ /**
45295
+ * Internal only: the least time between two run STARTS. A run that begins
45296
+ * inside the window is skipped (`skipped-cooldown`), traced and logged. It
45297
+ * is an actuator's budget — "how often may this gate physically open" —
45298
+ * not a notification cooldown, and it is shared by every caller of the
45299
+ * action (two rules, a button, an automation).
45300
+ */
45301
+ minDelaySec: ActionMinDelaySecSchema.optional(),
45143
45302
  status: ActionStatusSchema
45144
45303
  }).superRefine((entry, ctx) => {
45145
45304
  if (entry.source === "internal") {
@@ -45170,7 +45329,8 @@ var ActionEntrySchema = object({
45170
45329
  var ActionInputSchema = object({
45171
45330
  name: string().min(1).max(120),
45172
45331
  steps: array(ActionStepSchema).min(1).max(50),
45173
- variables: array(ActionVariableSchema).max(20).optional()
45332
+ variables: array(ActionVariableSchema).max(20).optional(),
45333
+ minDelaySec: ActionMinDelaySecSchema.optional()
45174
45334
  });
45175
45335
  /** Who started a run. Absent = the operator pressed it. */
45176
45336
  var ActionRunOriginSchema = object({
@@ -45260,7 +45420,7 @@ object({
45260
45420
  * is irrelevant; the array is treated as a set by consumers. */
45261
45421
  hiddenChildIds: array(number$1()).readonly()
45262
45422
  });
45263
- DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Light, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Cover, DeviceType.Lock, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Generic, method(object({
45423
+ DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Cover, DeviceType.Lock, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Generic, method(object({
45264
45424
  deviceId: number$1().int().nonnegative(),
45265
45425
  childDeviceId: number$1().int().nonnegative(),
45266
45426
  hidden: boolean()
@@ -49330,14 +49490,14 @@ var climateControlCapability = {
49330
49490
  kind: "mutation",
49331
49491
  auth: "protected"
49332
49492
  }),
49333
- setFanMode: method(object({
49493
+ setFanMode: optionalMethod(object({
49334
49494
  deviceId: number$1().int().nonnegative(),
49335
49495
  fanMode: string().min(1)
49336
49496
  }), _void(), {
49337
49497
  kind: "mutation",
49338
49498
  auth: "protected"
49339
49499
  }),
49340
- setPreset: method(object({
49500
+ setPreset: optionalMethod(object({
49341
49501
  deviceId: number$1().int().nonnegative(),
49342
49502
  preset: string().min(1)
49343
49503
  }), _void(), {
@@ -49351,7 +49511,7 @@ var climateControlCapability = {
49351
49511
  kind: "mutation",
49352
49512
  auth: "protected"
49353
49513
  }),
49354
- setTargetRange: method(object({
49514
+ setTargetRange: optionalMethod(object({
49355
49515
  deviceId: number$1().int().nonnegative(),
49356
49516
  targetLow: number$1(),
49357
49517
  targetHigh: number$1()
@@ -49359,21 +49519,21 @@ var climateControlCapability = {
49359
49519
  kind: "mutation",
49360
49520
  auth: "protected"
49361
49521
  }),
49362
- setTargetHumidity: method(object({
49522
+ setTargetHumidity: optionalMethod(object({
49363
49523
  deviceId: number$1().int().nonnegative(),
49364
49524
  targetHumidity: number$1().min(0).max(100)
49365
49525
  }), _void(), {
49366
49526
  kind: "mutation",
49367
49527
  auth: "protected"
49368
49528
  }),
49369
- setSwingVertical: method(object({
49529
+ setSwingVertical: optionalMethod(object({
49370
49530
  deviceId: number$1().int().nonnegative(),
49371
49531
  on: boolean()
49372
49532
  }), _void(), {
49373
49533
  kind: "mutation",
49374
49534
  auth: "protected"
49375
49535
  }),
49376
- setSwingHorizontal: method(object({
49536
+ setSwingHorizontal: optionalMethod(object({
49377
49537
  deviceId: number$1().int().nonnegative(),
49378
49538
  on: boolean()
49379
49539
  }), _void(), {
@@ -49431,6 +49591,140 @@ var climateControlCapability = {
49431
49591
  } }
49432
49592
  };
49433
49593
  /**
49594
+ * cluster-update — the ONE flow that brings the whole cluster to a release
49595
+ * (planning#493). Hub-provided (`server-provided`), hub-only: the hub runs
49596
+ * the sequence itself, so the admin UI, the MCP and the CLI all start the same
49597
+ * job and read the same report instead of each driving their own loop.
49598
+ *
49599
+ * 1. backup (`backup.trigger`) — skipped only when the caller says so
49600
+ * 2. the hub's server closure (`applyServerUpdate`); the hub restarts and the
49601
+ * job RESUMES on the new boot, verified against the hub's running version
49602
+ * 3. every online agent behind the target, one at a time, verified: runs the
49603
+ * target, settled, online, and its capabilities registered (D848) — an
49604
+ * agent that comes back capless is a FAILED step, never a done one
49605
+ * 4. every stale addon on the hub (`addons.updatePackage`); agents follow by
49606
+ * reconciliation, which is verified, never pushed
49607
+ *
49608
+ * The job stops on the first failed step with a typed reason (D877). Its state
49609
+ * is durable on the hub and read by RPC (`getClusterUpdateJob`) — events are
49610
+ * telemetry and never carry it (D8, D11).
49611
+ */
49612
+ /** The steps, in the order they run. */
49613
+ var ClusterUpdateStepKindSchema = _enum([
49614
+ "backup",
49615
+ "hub-server",
49616
+ "agent-server",
49617
+ "addons"
49618
+ ]);
49619
+ /**
49620
+ * - `pending` — not reached yet.
49621
+ * - `running` — in progress (for `hub-server`: possibly across the restart).
49622
+ * - `ok` — done AND verified.
49623
+ * - `failed` — did not land; `reason` + `detail` say why. Stops the job.
49624
+ * - `skipped` — not run: nothing to do, opted out, the node is offline, or a
49625
+ * step before it failed. `detail` says which.
49626
+ */
49627
+ var ClusterUpdateStepStatusSchema = _enum([
49628
+ "pending",
49629
+ "running",
49630
+ "ok",
49631
+ "failed",
49632
+ "skipped"
49633
+ ]);
49634
+ /**
49635
+ * Why a step (and with it the job) failed. Stable, greppable; `detail` carries
49636
+ * the prose.
49637
+ */
49638
+ var ClusterUpdateFailureReasonSchema = _enum([
49639
+ "backup-unavailable",
49640
+ "backup-failed",
49641
+ "server-refused",
49642
+ "server-not-on-target",
49643
+ "node-capless",
49644
+ "status-unreadable",
49645
+ "addon-update-failed",
49646
+ "addons-not-converged",
49647
+ "interrupted"
49648
+ ]);
49649
+ var ClusterUpdateStepSchema = object({
49650
+ kind: ClusterUpdateStepKindSchema,
49651
+ /** The node the step acts on; absent for `backup` and `addons`. */
49652
+ nodeId: string().optional(),
49653
+ status: ClusterUpdateStepStatusSchema,
49654
+ /** One line for the operator: what happened, or why not. */
49655
+ detail: string(),
49656
+ reason: ClusterUpdateFailureReasonSchema.optional(),
49657
+ /** `hub-server` / `agent-server`: the version the node ran before. */
49658
+ fromVersion: string().nullable().optional(),
49659
+ /** `addons`: the packages this step updates, `name from -> to`. */
49660
+ packages: array(string()).readonly().optional(),
49661
+ startedAt: number$1().optional(),
49662
+ finishedAt: number$1().optional()
49663
+ });
49664
+ var ClusterUpdateJobStateSchema = _enum([
49665
+ "running",
49666
+ "succeeded",
49667
+ "failed"
49668
+ ]);
49669
+ var ClusterUpdateJobSchema = object({
49670
+ id: string(),
49671
+ state: ClusterUpdateJobStateSchema,
49672
+ /** The server version every node is brought to. */
49673
+ targetVersion: string(),
49674
+ /** Who started it (the user name, or `system`). */
49675
+ requestedBy: string(),
49676
+ startedAt: number$1(),
49677
+ finishedAt: number$1().optional(),
49678
+ /** Every step, in run order; later steps stay `pending` while it runs. */
49679
+ steps: array(ClusterUpdateStepSchema).readonly(),
49680
+ /** On `failed`: the step that stopped it (`nodeId` or the step kind). */
49681
+ stoppedAt: string().optional(),
49682
+ reason: ClusterUpdateFailureReasonSchema.optional(),
49683
+ /** Agents offline when the plan was read: never touched. */
49684
+ skippedOffline: array(string()).readonly()
49685
+ });
49686
+ var ClusterUpdateOptionsSchema = object({
49687
+ /** Explicit server target; omitted = the hub's latest registry check. */
49688
+ version: string().optional(),
49689
+ /** Update the addons too (default true). */
49690
+ addons: boolean().optional(),
49691
+ /**
49692
+ * Back up before touching anything (default true). `false` is an explicit
49693
+ * operator opt-out; a missing backup provider is otherwise a failure.
49694
+ */
49695
+ backup: boolean().optional()
49696
+ });
49697
+ /** Why a plan cannot start. */
49698
+ var ClusterUpdateBlockerSchema = _enum([
49699
+ "no-target",
49700
+ "node-busy",
49701
+ "job-running",
49702
+ "nothing-to-do",
49703
+ "status-unreadable"
49704
+ ]);
49705
+ var ClusterUpdatePlanSchema = object({
49706
+ /** `null` when no target could be resolved (`blocker: 'no-target'`). */
49707
+ targetVersion: string().nullable(),
49708
+ /** The steps the job WOULD run, all `pending` (or `skipped` with a reason). */
49709
+ steps: array(ClusterUpdateStepSchema).readonly(),
49710
+ skippedOffline: array(string()).readonly(),
49711
+ /** `null` = the plan can start. */
49712
+ blocker: ClusterUpdateBlockerSchema.nullable(),
49713
+ /** Prose for `blocker`. */
49714
+ blockerDetail: string().nullable()
49715
+ });
49716
+ var StartClusterUpdateResultSchema = object({
49717
+ accepted: boolean(),
49718
+ /** The job to follow with `getClusterUpdateJob`; `null` when refused. */
49719
+ jobId: string().nullable(),
49720
+ blocker: ClusterUpdateBlockerSchema.nullable(),
49721
+ message: string()
49722
+ });
49723
+ method(ClusterUpdateOptionsSchema, ClusterUpdatePlanSchema, { auth: "admin" }), method(ClusterUpdateOptionsSchema, StartClusterUpdateResultSchema, {
49724
+ kind: "mutation",
49725
+ auth: "admin"
49726
+ }), method(object({ jobId: string().optional() }), ClusterUpdateJobSchema.nullable(), { auth: "admin" });
49727
+ /**
49434
49728
  * Color-light cap. Coexists with `switch` (on/off) and `brightness`
49435
49729
  * (level) on the same device — `switch` toggles the bulb, `brightness`
49436
49730
  * sets the dim level, this cap sets the chromaticity.
@@ -50632,27 +50926,27 @@ var mediaPlayerCapability = {
50632
50926
  mode: "singleton",
50633
50927
  deviceTypes: [DeviceType.MediaPlayer],
50634
50928
  methods: {
50635
- turnOn: method(deviceOnly, _void(), {
50929
+ turnOn: optionalMethod(deviceOnly, _void(), {
50636
50930
  kind: "mutation",
50637
50931
  auth: "protected"
50638
50932
  }),
50639
- turnOff: method(deviceOnly, _void(), {
50933
+ turnOff: optionalMethod(deviceOnly, _void(), {
50640
50934
  kind: "mutation",
50641
50935
  auth: "protected"
50642
50936
  }),
50643
- volumeUp: method(stepInput, _void(), {
50937
+ volumeUp: optionalMethod(stepInput, _void(), {
50644
50938
  kind: "mutation",
50645
50939
  auth: "protected"
50646
50940
  }),
50647
- volumeDown: method(stepInput, _void(), {
50941
+ volumeDown: optionalMethod(stepInput, _void(), {
50648
50942
  kind: "mutation",
50649
50943
  auth: "protected"
50650
50944
  }),
50651
- channelUp: method(stepInput, _void(), {
50945
+ channelUp: optionalMethod(stepInput, _void(), {
50652
50946
  kind: "mutation",
50653
50947
  auth: "protected"
50654
50948
  }),
50655
- channelDown: method(stepInput, _void(), {
50949
+ channelDown: optionalMethod(stepInput, _void(), {
50656
50950
  kind: "mutation",
50657
50951
  auth: "protected"
50658
50952
  }),
@@ -50664,61 +50958,61 @@ var mediaPlayerCapability = {
50664
50958
  kind: "mutation",
50665
50959
  auth: "protected"
50666
50960
  }),
50667
- stop: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50961
+ stop: optionalMethod(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50668
50962
  kind: "mutation",
50669
50963
  auth: "protected"
50670
50964
  }),
50671
- next: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50965
+ next: optionalMethod(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50672
50966
  kind: "mutation",
50673
50967
  auth: "protected"
50674
50968
  }),
50675
- previous: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50969
+ previous: optionalMethod(object({ deviceId: number$1().int().nonnegative() }), _void(), {
50676
50970
  kind: "mutation",
50677
50971
  auth: "protected"
50678
50972
  }),
50679
- seek: method(object({
50973
+ seek: optionalMethod(object({
50680
50974
  deviceId: number$1().int().nonnegative(),
50681
50975
  position: number$1().int().nonnegative()
50682
50976
  }), _void(), {
50683
50977
  kind: "mutation",
50684
50978
  auth: "protected"
50685
50979
  }),
50686
- setVolume: method(object({
50980
+ setVolume: optionalMethod(object({
50687
50981
  deviceId: number$1().int().nonnegative(),
50688
50982
  volumeLevel: number$1().min(0).max(100)
50689
50983
  }), _void(), {
50690
50984
  kind: "mutation",
50691
50985
  auth: "protected"
50692
50986
  }),
50693
- setMute: method(object({
50987
+ setMute: optionalMethod(object({
50694
50988
  deviceId: number$1().int().nonnegative(),
50695
50989
  muted: boolean()
50696
50990
  }), _void(), {
50697
50991
  kind: "mutation",
50698
50992
  auth: "protected"
50699
50993
  }),
50700
- setShuffle: method(object({
50994
+ setShuffle: optionalMethod(object({
50701
50995
  deviceId: number$1().int().nonnegative(),
50702
50996
  shuffle: boolean()
50703
50997
  }), _void(), {
50704
50998
  kind: "mutation",
50705
50999
  auth: "protected"
50706
51000
  }),
50707
- setRepeat: method(object({
51001
+ setRepeat: optionalMethod(object({
50708
51002
  deviceId: number$1().int().nonnegative(),
50709
51003
  repeat: MediaPlayerRepeatSchema
50710
51004
  }), _void(), {
50711
51005
  kind: "mutation",
50712
51006
  auth: "protected"
50713
51007
  }),
50714
- selectSource: method(object({
51008
+ selectSource: optionalMethod(object({
50715
51009
  deviceId: number$1().int().nonnegative(),
50716
51010
  source: string().min(1)
50717
51011
  }), _void(), {
50718
51012
  kind: "mutation",
50719
51013
  auth: "protected"
50720
51014
  }),
50721
- playMedia: method(object({
51015
+ playMedia: optionalMethod(object({
50722
51016
  deviceId: number$1().int().nonnegative(),
50723
51017
  /** Media identifier / URL. */
50724
51018
  mediaId: string().min(1),
@@ -50761,7 +51055,7 @@ var mediaPlayerCapability = {
50761
51055
  isMuted: { kind: "boolean" }
50762
51056
  } }
50763
51057
  };
50764
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Heater, DeviceType.Dryer, DeviceType.Gateway, DeviceType.HeatPump, DeviceType.Camera, DeviceType.Fan, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.MediaPlayer;
51058
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Heater, DeviceType.Dryer, DeviceType.Gateway, DeviceType.HeatPump, DeviceType.Camera, DeviceType.Fan, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Gateway, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.MediaPlayer;
50765
51059
  var NUMBER = {
50766
51060
  kind: "fixed",
50767
51061
  result: ["number"]
@@ -51740,18 +52034,18 @@ var coverCapability = {
51740
52034
  kind: "mutation",
51741
52035
  auth: "protected"
51742
52036
  }),
51743
- stop: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
52037
+ stop: optionalMethod(object({ deviceId: number$1().int().nonnegative() }), _void(), {
51744
52038
  kind: "mutation",
51745
52039
  auth: "protected"
51746
52040
  }),
51747
- setPosition: method(object({
52041
+ setPosition: optionalMethod(object({
51748
52042
  deviceId: number$1().int().nonnegative(),
51749
52043
  position: number$1().min(0).max(100)
51750
52044
  }), _void(), {
51751
52045
  kind: "mutation",
51752
52046
  auth: "protected"
51753
52047
  }),
51754
- setTiltPosition: method(object({
52048
+ setTiltPosition: optionalMethod(object({
51755
52049
  deviceId: number$1().int().nonnegative(),
51756
52050
  tiltPosition: number$1().min(0).max(100)
51757
52051
  }), _void(), {
@@ -52233,27 +52527,35 @@ var DeviceAdminLinkSchema = object({
52233
52527
  });
52234
52528
  method(object({ deviceId: number$1().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" });
52235
52529
  /**
52236
- * device-discovery — device-scoped capability for parents that host /
52237
- * enumerate child devices (Reolink Hub / NVR, ONVIF gateway, future
52238
- * integrations). The parent advertises a list of discoverable children,
52239
- * and the operator promotes a subset to real devices via `adoptDevice`.
52240
- * Child lifecycle (kernel device create/remove, parent reference) is
52241
- * owned by the framework so a single shared `<DiscoveryPanel />` can
52242
- * drive any integration.
52243
- *
52244
- * Boundaries:
52245
- * - The cap exposes ENUMERATION + ADOPT/RELEASE only. The protocol
52246
- * used to discover children (CGI poll, mDNS, push from hub, ...)
52247
- * is the integration's business; results land in `runtimeState`
52248
- * so the panel can render without a fresh round-trip.
52249
- * - `childNativeId` is integration-defined and must be stable across
52250
- * reboots so re-discovery doesn't duplicate adopted children.
52251
- * - `adoptDevice` returns the new `deviceId` so the UI can navigate
52252
- * directly to the freshly-created child's settings page.
52253
- */
52254
- /**
52255
- * Status enum for a discovered device. Mirrors the Reolink simpleEvent
52256
- * lifecycle (`online` / `sleeping` / `offline`) — generic enough for
52530
+ * `device-adoption` — generic discovery + adoption surface,
52531
+ * system-scoped collection (one provider per integration addon), routed
52532
+ * by explicit `{ addonId }` on direct `deviceAdoption.*` calls — the same
52533
+ * pin model as `connection-test`. The admin UI keeps using
52534
+ * `deviceManager.adoption*` (which already threads `addonId`). The de-HA-ified
52535
+ * successor to `ha-discovery`:
52536
+ * keyed by `integrationId` (the addon resolves integrationId → broker
52537
+ * internally) rather than a connection-specific `brokerId`, and it
52538
+ * OWNS the shared `DiscoveredChildDevice` candidate type
52539
+ * (declared below) so a single shared adoption modal renders any integration's
52540
+ * candidate tree.
52541
+ *
52542
+ * UI flow (Integrations page → integration detail):
52543
+ * 1. `listCandidates({integrationId, page, pageSize, filter})` →
52544
+ * paginated candidate list, each row collapsed.
52545
+ * 2. User expands a row → `getCandidate({integrationId,
52546
+ * childNativeId})` returns the single candidate (with its nested
52547
+ * `children`) for the accordion render.
52548
+ * 3. User clicks "Adopt" on one or more candidates →
52549
+ * `adopt({integrationId, childNativeIds, perCandidate?})`
52550
+ * materialises one parent CamStack device per candidate plus its
52551
+ * accessory children. `perCandidate[childNativeId]` carries an
52552
+ * optional display-name override + a `hiddenChildIds` pre-hide set
52553
+ * (the parent's `accessories.hiddenChildIds` gates UI visibility).
52554
+ * 4. User clicks "Release" → `release({integrationId, camDeviceId})`
52555
+ * removes the parent + every child from the kernel registry.
52556
+ */
52557
+ /**
52558
+ * Status enum for a discovered device. Generic enough for
52257
52559
  * battery cams, awake cams, and unreachable cams alike.
52258
52560
  */
52259
52561
  var DiscoveredChildStatusSchema = _enum([
@@ -52270,12 +52572,12 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
52270
52572
  /** DeviceType the child should be created with on adopt. */
52271
52573
  type: _enum(DeviceType),
52272
52574
  status: DiscoveredChildStatusSchema,
52273
- /** Free-form integration-specific metadata surfaced in the panel. */
52575
+ /** Free-form integration-specific metadata surfaced in the adoption modal. */
52274
52576
  metadata: object({
52275
52577
  model: string().optional(),
52276
52578
  serialNumber: string().optional(),
52277
52579
  uid: string().optional(),
52278
- /** Reolink: 0-based channel index inside the parent NVR/Hub. */
52580
+ /** Reolink: 0-based channel index inside the parent NVR. */
52279
52581
  rtspChannel: number$1().int().nonnegative().optional(),
52280
52582
  isBattery: boolean().optional(),
52281
52583
  isDoorbell: boolean().optional(),
@@ -52291,137 +52593,24 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
52291
52593
  }).default({}),
52292
52594
  /**
52293
52595
  * `true` when the framework already created a child device for this
52294
- * `childNativeId` under the current parent. The panel uses it to
52596
+ * `childNativeId` under the current parent. The adoption modal uses it to
52295
52597
  * gate the Add/Remove button and surface the existing `deviceId`.
52296
52598
  */
52297
52599
  alreadyAdopted: boolean(),
52298
52600
  /** When `alreadyAdopted=true`, the framework-assigned child device id. */
52299
52601
  adoptedDeviceId: number$1().int().nonnegative().nullable(),
52300
- /** Capability names the candidate advertises — integrations populate, Hubs omit. */
52602
+ /** Capability names the candidate advertises — integrations populate, flat enumerations omit. */
52301
52603
  capabilities: array(string()).readonly().optional(),
52302
- /** Feature flags the candidate advertises — integrations populate, Hubs omit. */
52604
+ /** Feature flags the candidate advertises — integrations populate, flat enumerations omit. */
52303
52605
  features: array(string()).readonly().optional(),
52304
52606
  /** Upstream-system identity + rendering hints for the candidate. */
52305
52607
  sourceInfo: SourceInfoSchema.optional(),
52306
- /** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
52608
+ /** Nested entity-children for accordion preview — integrations populate, flat enumerations omit. */
52307
52609
  children: array(DiscoveredChildDeviceSchema).readonly().optional(),
52308
52610
  /** `childNativeId` of the gateway candidate in the same list this device sits behind. */
52309
52611
  parentStableId: string().optional()
52310
52612
  }));
52311
52613
  /**
52312
- * Status block — populated by the integration after each
52313
- * `refreshDiscovery` (or push-driven update). The runtime-state slice
52314
- * is what the panel reads via the per-device proxy; `getStatus`
52315
- * returns the same payload via the codegen'd auto-injection.
52316
- */
52317
- var DeviceDiscoveryStatusSchema = object({
52318
- discovered: array(DiscoveredChildDeviceSchema),
52319
- /** Wall-clock ms of the last successful enumeration. */
52320
- lastDiscoveryAt: number$1().int().nonnegative().nullable(),
52321
- /** Last error surfaced from the source (rendered as a banner). */
52322
- lastError: string().nullable()
52323
- });
52324
- var deviceDiscoveryCapability = {
52325
- name: "device-discovery",
52326
- scope: "device",
52327
- deviceNative: true,
52328
- mode: "singleton",
52329
- deviceTypes: [DeviceType.Hub],
52330
- status: {
52331
- schema: DeviceDiscoveryStatusSchema,
52332
- kind: "poll"
52333
- },
52334
- runtimeState: DeviceDiscoveryStatusSchema.extend({ lastFetchedAt: number$1().int().nonnegative() }),
52335
- /**
52336
- * Runtime-state durability: **session** — 5.7 KB of scan output on the largest device, fully re-derivable by re-scanning.
52337
- *
52338
- * See `RuntimeStateDurability`. Enforced by
52339
- * `scripts/check-runtime-state-durability.ts`.
52340
- */
52341
- durability: "session",
52342
- /**
52343
- * History (D850): none — inventory.
52344
- */
52345
- history: "none",
52346
- methods: {
52347
- /**
52348
- * Snapshot of the current `discovered` list. Returns the
52349
- * runtime-state cache — call `refreshDiscovery` first if a
52350
- * fresh round-trip to the source is required.
52351
- */
52352
- listDiscovered: method(object({ deviceId: number$1().int().nonnegative() }), array(DiscoveredChildDeviceSchema).readonly()),
52353
- /**
52354
- * Force the integration to re-enumerate and update the
52355
- * runtime-state slice. Returns the freshly-enumerated list (also
52356
- * available via `listDiscovered` post-call).
52357
- */
52358
- refreshDiscovery: method(object({ deviceId: number$1().int().nonnegative() }), array(DiscoveredChildDeviceSchema).readonly(), {
52359
- kind: "mutation",
52360
- auth: "admin"
52361
- }),
52362
- /**
52363
- * Promote a discovered entry to a real child device. The framework
52364
- * creates the child via `kernel.devices.create()` with
52365
- * `parentDeviceId = parent.id` and seeds the child's config from
52366
- * `childInitialConfig` (driver-defined; usually carries channel +
52367
- * uid + parent reference). Returns the kernel-assigned numeric id.
52368
- */
52369
- adoptDevice: method(object({
52370
- deviceId: number$1().int().nonnegative(),
52371
- childNativeId: string(),
52372
- /** Optional override for the child's display name. */
52373
- name: string().optional()
52374
- }), object({
52375
- deviceId: number$1().int().nonnegative(),
52376
- stableId: string()
52377
- }), {
52378
- kind: "mutation",
52379
- auth: "admin"
52380
- }),
52381
- /**
52382
- * Inverse of `adoptDevice`: removes the child device from the
52383
- * kernel registry. The discovered entry remains in the
52384
- * enumeration (status updates resume) so the operator can re-adopt
52385
- * it later without a fresh refresh.
52386
- */
52387
- releaseDevice: method(object({
52388
- deviceId: number$1().int().nonnegative(),
52389
- childDeviceId: number$1().int().nonnegative()
52390
- }), _void(), {
52391
- kind: "mutation",
52392
- auth: "admin"
52393
- })
52394
- }
52395
- };
52396
- /**
52397
- * `device-adoption` — generic discovery + adoption surface,
52398
- * system-scoped collection (one provider per integration addon), routed
52399
- * by explicit `{ addonId }` on direct `deviceAdoption.*` calls — the same
52400
- * pin model as `connection-test`. The admin UI keeps using
52401
- * `deviceManager.adoption*` (which already threads `addonId`). The de-HA-ified
52402
- * successor to `ha-discovery`:
52403
- * keyed by `integrationId` (the addon resolves integrationId → broker
52404
- * internally) rather than a connection-specific `brokerId`, and it
52405
- * REUSES the shared `DiscoveredChildDevice` candidate type from
52406
- * `device-discovery.cap.ts` so a single shared adoption panel renders
52407
- * any integration's candidate tree.
52408
- *
52409
- * UI flow (Integrations page → integration detail):
52410
- * 1. `listCandidates({integrationId, page, pageSize, filter})` →
52411
- * paginated candidate list, each row collapsed.
52412
- * 2. User expands a row → `getCandidate({integrationId,
52413
- * childNativeId})` returns the single candidate (with its nested
52414
- * `children`) for the accordion render.
52415
- * 3. User clicks "Adopt" on one or more candidates →
52416
- * `adopt({integrationId, childNativeIds, perCandidate?})`
52417
- * materialises one parent CamStack device per candidate plus its
52418
- * accessory children. `perCandidate[childNativeId]` carries an
52419
- * optional display-name override + a `hiddenChildIds` pre-hide set
52420
- * (the parent's `accessories.hiddenChildIds` gates UI visibility).
52421
- * 4. User clicks "Release" → `release({integrationId, camDeviceId})`
52422
- * removes the parent + every child from the kernel registry.
52423
- */
52424
- /**
52425
52614
  * Provider-declared discovery *granularity* filter (the keystone of the
52426
52615
  * single-entity-import design). A provider advertises which discovery
52427
52616
  * granularities it supports via `listCandidateFilters`; the operator picks
@@ -52781,6 +52970,22 @@ method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(Expor
52781
52970
  });
52782
52971
  var DeviceAliasSchema = string().max(64).regex(/^[a-z][a-z0-9_]*$/);
52783
52972
  var DeviceTemplateSchema = string().min(1).max(4096);
52973
+ /** One value a caller hands the renderer for `thisDevice`. */
52974
+ var DeviceTemplateValueSchema = union([
52975
+ string(),
52976
+ number$1(),
52977
+ boolean()
52978
+ ]);
52979
+ /**
52980
+ * The device a render is FOR (`camera` in the text) and the values the caller
52981
+ * knows about it that no capability carries, for example `lastPerson` and
52982
+ * `lastPlate`. `values` read as `camera.<name>`; a name a capability also carries
52983
+ * is `ambiguous` and is written `camera.values.<name>`.
52984
+ */
52985
+ var DeviceTemplateThisDeviceSchema = object({
52986
+ deviceId: number$1().int(),
52987
+ values: record(string().min(1).max(64), DeviceTemplateValueSchema).optional()
52988
+ });
52784
52989
  var DeviceTemplateRefusalSchema = object({
52785
52990
  reason: _enum([
52786
52991
  "too-long",
@@ -52866,6 +53071,90 @@ var SiteZoneSchema = object({
52866
53071
  * the ones that most want grouping. */
52867
53072
  locationKeys: array(string())
52868
53073
  });
53074
+ var DeviceSearchRequiresSchema = object({
53075
+ /** Capability names the field wants a value of. */
53076
+ caps: array(string()).readonly(),
53077
+ /** `any` (default): a device with one of them is compatible. `all`: it must hold every one. */
53078
+ mode: _enum(["any", "all"]).optional()
53079
+ });
53080
+ var DeviceSearchFiltersSchema = object({
53081
+ locations: array(string()).readonly().optional(),
53082
+ integrationIds: array(string()).readonly().optional(),
53083
+ types: array(string()).readonly().optional()
53084
+ });
53085
+ var DeviceSearchInputSchema = object({
53086
+ /** Matches name, location, type, integration (id and label) and id. Case-insensitive. */
53087
+ text: string().optional(),
53088
+ requires: DeviceSearchRequiresSchema.optional(),
53089
+ filters: DeviceSearchFiltersSchema.optional(),
53090
+ /** List the devices that do not provide the required cap too (greyed, not pickable). */
53091
+ includeIncompatible: boolean().optional(),
53092
+ /** The device the field belongs to: its location drives `suggested`. */
53093
+ contextDeviceId: number$1().optional(),
53094
+ /**
53095
+ * Resolve exactly these devices, regardless of every filter, so a picked
53096
+ * value can be rendered. When given, `items` is these devices in the order
53097
+ * asked (no paging, `nextCursor` null) and the ones that do not exist are
53098
+ * listed in `missingIds`.
53099
+ */
53100
+ ids: array(number$1()).readonly().optional(),
53101
+ cursor: string().nullable().optional(),
53102
+ limit: number$1().int().min(1).max(200).optional()
53103
+ });
53104
+ var DeviceValueOptionSchema = object({
53105
+ cap: string(),
53106
+ /** Status-schema field path inside the cap (same paths as `getWireableFields`). */
53107
+ path: string(),
53108
+ label: string(),
53109
+ /** The current value as text; `null` when it is unknown or the read FAILED — never `0`. */
53110
+ reading: string().nullable(),
53111
+ unit: string().nullable().optional(),
53112
+ /** The field's measured quantity, when the cap declares one, so a client can format the reading. */
53113
+ quantity: ReadingQuantitySchema.optional()
53114
+ });
53115
+ var DeviceSearchItemSchema = object({
53116
+ id: number$1(),
53117
+ name: string(),
53118
+ type: _enum(DeviceType),
53119
+ location: string().nullable(),
53120
+ integrationId: string().nullable(),
53121
+ integrationLabel: string().nullable(),
53122
+ online: boolean().nullable(),
53123
+ disabled: boolean(),
53124
+ compatible: boolean(),
53125
+ /** Only the required caps' fields; empty when nothing was required or the device is incompatible. */
53126
+ values: array(DeviceValueOptionSchema).readonly()
53127
+ });
53128
+ var DeviceSearchFacetValueSchema = object({
53129
+ value: string(),
53130
+ count: number$1()
53131
+ });
53132
+ var DeviceSearchFacetIntegrationSchema = object({
53133
+ id: string(),
53134
+ label: string(),
53135
+ count: number$1()
53136
+ });
53137
+ var DeviceSearchFacetsSchema = object({
53138
+ locations: array(DeviceSearchFacetValueSchema).readonly(),
53139
+ integrations: array(DeviceSearchFacetIntegrationSchema).readonly(),
53140
+ types: array(DeviceSearchFacetValueSchema).readonly()
53141
+ });
53142
+ var DeviceSearchResultSchema = object({
53143
+ items: array(DeviceSearchItemSchema).readonly(),
53144
+ nextCursor: string().nullable(),
53145
+ /** Devices listed by this query across all pages (filters + text applied; incompatible counted only when `includeIncompatible`). */
53146
+ total: number$1(),
53147
+ /** Compatible devices after filters + text. */
53148
+ compatibleTotal: number$1(),
53149
+ /** Every device the caller's fleet holds, before anything is applied. */
53150
+ fleetTotal: number$1(),
53151
+ /** Incompatible devices after filters + text — the K of "show the K that don't". */
53152
+ hiddenIncompatible: number$1(),
53153
+ facets: DeviceSearchFacetsSchema,
53154
+ /** Same location as `contextDeviceId`; first page only. */
53155
+ suggested: array(DeviceSearchItemSchema).readonly(),
53156
+ missingIds: array(number$1()).readonly()
53157
+ });
52869
53158
  var ProviderStatusSchema = object({
52870
53159
  connected: boolean(),
52871
53160
  deviceCount: number$1(),
@@ -53351,6 +53640,9 @@ var DeviceInfoSchema = object({
53351
53640
  * named accordion sections (with optional intra-section order). See
53352
53641
  * `DeviceMeta.childLayout`. Absent ⇒ no layout declared. */
53353
53642
  childLayout: array(ChildLayoutEntrySchema).readonly().optional(),
53643
+ /** The children a container stands for, most preferred first, by accessory
53644
+ * `childKey`. Provider-declared. See `DeviceMeta.primaryChildPreference`. */
53645
+ primaryChildPreference: array(string()).readonly().optional(),
53354
53646
  /** Operator-authored per-device display override. See `DeviceMeta.display`. */
53355
53647
  display: DeviceDisplayOverrideSchema.optional()
53356
53648
  });
@@ -53412,7 +53704,9 @@ var DeviceBindingsForDeviceSchema = object({
53412
53704
  kind: _enum(["native", "wrapped"]),
53413
53705
  providerAddonId: string(),
53414
53706
  providerNodeId: string(),
53415
- nativeAddonId: string()
53707
+ nativeAddonId: string(),
53708
+ /** The cap's methods the native provider implements; absent = all. */
53709
+ implementedMethods: array(string()).optional()
53416
53710
  }))
53417
53711
  });
53418
53712
  /**
@@ -53497,6 +53791,9 @@ var DeviceMetaSchema = object({
53497
53791
  * accordion sections (with optional intra-section order). See
53498
53792
  * `DeviceMeta.childLayout`. Absent ⇒ no layout declared. */
53499
53793
  childLayout: array(ChildLayoutEntrySchema).readonly().optional(),
53794
+ /** The children a container stands for, most preferred first, by accessory
53795
+ * `childKey`. Provider-declared. See `DeviceMeta.primaryChildPreference`. */
53796
+ primaryChildPreference: array(string()).readonly().optional(),
53500
53797
  /** Semantic role string (`DeviceRole`) — propagated from the spawn pre-seed.
53501
53798
  * Optional: only present for accessory children that carry a known role. */
53502
53799
  role: string().nullable().optional(),
@@ -53655,17 +53952,24 @@ method(object({
53655
53952
  auth: "admin"
53656
53953
  }), method(object({
53657
53954
  templates: array(DeviceTemplateSchema).max(64),
53658
- allowedDeviceIds: array(number$1().int()).optional()
53955
+ allowedDeviceIds: array(number$1().int()).optional(),
53956
+ /** The device `camera` names in the texts, and the values the caller knows of it. */
53957
+ thisDevice: DeviceTemplateThisDeviceSchema.optional()
53659
53958
  }), object({ results: array(DeviceTemplateRenderSchema) }), { auth: "admin" }), method(object({
53660
53959
  templates: array(string()).max(64),
53661
- allowedDeviceIds: array(number$1().int()).optional()
53960
+ allowedDeviceIds: array(number$1().int()).optional(),
53961
+ thisDevice: DeviceTemplateThisDeviceSchema.optional()
53662
53962
  }), object({ results: array(discriminatedUnion("ok", [object({
53663
53963
  ok: literal(true),
53664
53964
  aliases: array(object({
53665
53965
  alias: string(),
53666
53966
  deviceId: number$1().int().nullable(),
53667
- allowed: boolean()
53668
- }))
53967
+ allowed: boolean(),
53968
+ /** The fields the text reads on this alias, as written (`temperature`, `cap.field`). */
53969
+ reads: array(string())
53970
+ })),
53971
+ /** Whether the text reads the clock (`now`): it changes every minute. */
53972
+ readsClock: boolean()
53669
53973
  }), object({
53670
53974
  ok: literal(false),
53671
53975
  refusal: DeviceTemplateRefusalSchema
@@ -53705,6 +54009,12 @@ method(object({
53705
54009
  }), _void(), {
53706
54010
  kind: "mutation",
53707
54011
  auth: "admin"
54012
+ }), method(object({
54013
+ deviceId: number$1(),
54014
+ primaryChildPreference: array(string()).readonly()
54015
+ }), _void(), {
54016
+ kind: "mutation",
54017
+ auth: "admin"
53708
54018
  }), method(object({
53709
54019
  deviceId: number$1(),
53710
54020
  display: DeviceDisplayOverrideSchema.nullable()
@@ -53842,7 +54152,7 @@ method(object({
53842
54152
  * it answers today and the caller filters as it already does.
53843
54153
  */
53844
54154
  deviceIds: array(number$1()).optional()
53845
- }), array(DeviceInfoSchema), { role: "viewer" }), method(object({ deviceId: number$1() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number$1() }), array(DeviceInfoSchema), { role: "viewer" }), method(object({ parentDeviceIds: array(number$1()).max(256) }), record(string(), array(DeviceInfoSchema))), method(object({ deviceId: number$1() }), object({
54155
+ }), array(DeviceInfoSchema), { role: "viewer" }), method(DeviceSearchInputSchema, DeviceSearchResultSchema, { role: "viewer" }), method(object({ deviceId: number$1() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number$1() }), array(DeviceInfoSchema), { role: "viewer" }), method(object({ parentDeviceIds: array(number$1()).max(256) }), record(string(), array(DeviceInfoSchema))), method(object({ deviceId: number$1() }), object({
53846
54156
  mode: LinkedDevicesModeSchema,
53847
54157
  devices: array(LinkedDeviceSchema)
53848
54158
  }), { role: "viewer" }), method(object({ deviceIds: array(number$1()) }), array(LinkedDevicesForDeviceSchema), { role: "viewer" }), method(object({ deviceId: number$1() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number$1() }), array(ConfigEntrySchema)), method(object({ deviceId: number$1() }), ConfigUISchemaOutput), method(object({
@@ -56387,28 +56697,28 @@ var fanControlCapability = {
56387
56697
  mode: "singleton",
56388
56698
  deviceTypes: [DeviceType.Fan],
56389
56699
  methods: {
56390
- setPercentage: method(object({
56700
+ setPercentage: optionalMethod(object({
56391
56701
  deviceId: number$1().int().nonnegative(),
56392
56702
  speed: number$1().min(0).max(100)
56393
56703
  }), _void(), {
56394
56704
  kind: "mutation",
56395
56705
  auth: "protected"
56396
56706
  }),
56397
- setPreset: method(object({
56707
+ setPreset: optionalMethod(object({
56398
56708
  deviceId: number$1().int().nonnegative(),
56399
56709
  preset: string().min(1)
56400
56710
  }), _void(), {
56401
56711
  kind: "mutation",
56402
56712
  auth: "protected"
56403
56713
  }),
56404
- setDirection: method(object({
56714
+ setDirection: optionalMethod(object({
56405
56715
  deviceId: number$1().int().nonnegative(),
56406
56716
  direction: FanDirectionSchema
56407
56717
  }), _void(), {
56408
56718
  kind: "mutation",
56409
56719
  auth: "protected"
56410
56720
  }),
56411
- setOscillating: method(object({
56721
+ setOscillating: optionalMethod(object({
56412
56722
  deviceId: number$1().int().nonnegative(),
56413
56723
  oscillating: boolean()
56414
56724
  }), _void(), {
@@ -57510,13 +57820,11 @@ var HeroConfirmSchema = _enum([
57510
57820
  "press"
57511
57821
  ]);
57512
57822
  var HeroActionSchema = object({
57513
- /** Unique within the model; the `gated` list and the specs name it. */
57514
57823
  id: string(),
57515
57824
  label: HeroTextSchema,
57516
57825
  icon: IconKeySchema,
57517
57826
  call: HeroCallSchema,
57518
57827
  emphasis: _enum(["primary", "normal"]),
57519
- /** Ask before running it. */
57520
57828
  confirm: HeroConfirmSchema.nullable()
57521
57829
  });
57522
57830
  var HeroControlSchema = discriminatedUnion("kind", [
@@ -57567,6 +57875,8 @@ var HeroPillSchema = object({
57567
57875
  });
57568
57876
  var HeroRowSchema = object({
57569
57877
  id: HeroRowIdSchema,
57878
+ /** `scene`: the row's action and slider controls are drawn on the scene. Absent: an ordinary row. */
57879
+ placement: _enum(["row", "scene"]).optional(),
57570
57880
  /** The row's own name when the kind's label would not tell rows of one kind apart (a circuit's label). */
57571
57881
  label: HeroTextSchema.optional(),
57572
57882
  /** A group header drawn above the row when it differs from the previous row's ("System"). */
@@ -57597,9 +57907,16 @@ var HeroBadgeSchema = object({
57597
57907
  value: number$1().nullable(),
57598
57908
  label: HeroTextSchema
57599
57909
  });
57600
- var HeroRemoteSlotSchema = object({
57601
- /** Saved commands; `null` = not known yet (the button still shows). */
57602
- commands: number$1().int().nullable() });
57910
+ var HeroRemoteSlotSchema = object({
57911
+ /** Saved commands; `null` = not known yet (the button still shows). */
57912
+ commands: number$1().int().nullable(),
57913
+ /**
57914
+ * The control a list row draws as the "open the remote" icon. Recognised by its id
57915
+ * ({@link REMOTE_OPEN_ACTION_ID}); it changes no state, so a client opens the remote
57916
+ * instead of running `call`. Absent on a model from an older server.
57917
+ */
57918
+ open: HeroActionSchema.optional()
57919
+ });
57603
57920
  var HeroConsumablesSlotSchema = object({
57604
57921
  items: array(object({
57605
57922
  key: string(),
@@ -57767,6 +58084,8 @@ var HeroModelSchema = object({
57767
58084
  * the FIRST row that has any (a later row's control is never promoted). When
57768
58085
  * the model lists that part in `gated`, the row has no inline control: the
57769
58086
  * projection never falls through to another control the caller could use.
58087
+ * - a climate or thermostat row's primary is its `climate.mode` control (no power
58088
+ * toggle: "off" is a mode); when the model gates it the rule above applies as before;
57770
58089
  * - a timed run, when the model carries the slot, sits beside that primary:
57771
58090
  * Run while idle, Stop while armed. Gating the slot hides the control.
57772
58091
  * - the status line is the head (what plays, else the state) plus at most
@@ -57774,6 +58093,13 @@ var HeroModelSchema = object({
57774
58093
  * time left on an armed timed run;
57775
58094
  * - the trailing fact is the first of power, battery, consumables.
57776
58095
  *
58096
+ * - `extras` are the further inline controls of the types that carry more than one
58097
+ * ({@link ROW_EXTRAS_BY_TYPE}): climate setpoint, a robot's return to base,
58098
+ * a player's volume and remote. Each is a control the model already has, named by id,
58099
+ * in that order and at most {@link ROW_EXTRAS_MAX}; one the model lacks, or lists in
58100
+ * `gated`, is absent, and the one `primary` already is never repeats. Empty = the
58101
+ * row of an older server: `primary` alone.
58102
+ *
57777
58103
  * `projectDeviceRow` is pure and computes nothing: it selects from a
57778
58104
  * {@link HeroModel}. A row that needed a value the hero lacks would mean the
57779
58105
  * model is missing it, so the fix goes in the model.
@@ -57805,8 +58131,16 @@ var HeroRowModelSchema = object({
57805
58131
  * while armed. `null` when there is no slot, or the caller may not act.
57806
58132
  */
57807
58133
  timedRun: HeroRowTimedRunSchema.nullable(),
58134
+ /**
58135
+ * The further inline controls after `primary`, in order (see the header). The remote
58136
+ * is an `action` with id `remote.open`: the client opens the remote, it does not run
58137
+ * the call. Absent or empty = `primary` alone.
58138
+ */
58139
+ extras: array(HeroControlSchema).max(2).optional(),
57808
58140
  trailing: HeroFactSchema.nullable()
57809
58141
  });
58142
+ DeviceType.Climate, DeviceType.Thermostat;
58143
+ DeviceType.Climate, DeviceType.Thermostat, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer;
57810
58144
  /**
57811
58145
  * hero-layout — what a device's hero says (D1023). Read-only: the FIXED
57812
58146
  * layouts live in `@camstack/types` and nothing here stores or edits one.
@@ -60513,7 +60847,7 @@ var lockControlCapability = {
60513
60847
  kind: "mutation",
60514
60848
  auth: "protected"
60515
60849
  }),
60516
- open: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
60850
+ open: optionalMethod(object({ deviceId: number$1().int().nonnegative() }), _void(), {
60517
60851
  kind: "mutation",
60518
60852
  auth: "protected"
60519
60853
  })
@@ -61710,7 +62044,7 @@ object({
61710
62044
  deviceId: number$1().int().optional(),
61711
62045
  atBefore: number$1().optional(),
61712
62046
  where: RecordWhereSchema.optional()
61713
- }), 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({
62047
+ }), 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({
61714
62048
  ref: string().min(1),
61715
62049
  value: string()
61716
62050
  }), object({}), object({ refs: array(string()) }), object({ deleted: number$1().int().nonnegative() }), object({}), object({ integrations: array(MigrationIntegrationSchema) }), object({ id: string().min(1) }), MigrationIntegrationSchema.nullable(), object({
@@ -61721,7 +62055,7 @@ object({
61721
62055
  integrationId: string().nullable(),
61722
62056
  /** Whether the device carries any stored config; its content is not offered. */
61723
62057
  hasConfig: boolean()
61724
- }).nullable(), object({
62058
+ }).nullable(), object({ deviceIds: array(number$1().int()) }), object({ aliases: record(string(), string()) }), object({
61725
62059
  deviceId: number$1().int(),
61726
62060
  integrationId: string().min(1)
61727
62061
  }), object({}), object({}), object({ nodeId: string() }), object({}), object({});
@@ -68999,92 +69333,21 @@ var NcZoneConditionSchema = object({
68999
69333
  * membership lists are OR within the list (spec §2.3).
69000
69334
  */
69001
69335
  /**
69002
- * What a rule may actuate.
69336
+ * What a rule or a button runs: an **Action** of the `actions` surface
69337
+ * (`internal:<uuid>` or an addon's), by id, and nothing else (planning#473).
69003
69338
  *
69004
- * **No hand-maintained allowlist** (operator decision, and the right one — a
69005
- * written list of methods is a third parallel map to keep aligned, and this
69006
- * repo has paid for those). The boundary instead comes from a property the
69007
- * capabilities already carry: an action may target only a **device-scoped**
69008
- * capability method.
69009
- *
69010
- * That is not decoration. A rule can be authored by a NON-ADMIN — personal
69011
- * rules are a supported flow — and the executor runs with the addon's
69012
- * privileges, so an unbounded action is an arbitrary RPC channel with a
69013
- * privilege escalation attached. Restricting to device scope excludes the
69014
- * system caps (`device-manager.removeDevice` and friends) by construction,
69015
- * costs nothing to maintain, and cannot rot: a cap that stops being
69016
- * device-scoped stops being actuatable in the same change.
69017
- *
69018
- * The executor enforces it; {@link NcRuleActionSchema} carries the intent.
69339
+ * The notification centre has no step vocabulary of its own. The steps, the
69340
+ * device-scope boundary, the stop-at-first-failure, the throttle
69341
+ * (`minDelaySec`), the variables and the runs ledger all belong to the Action;
69342
+ * an operator edits them in ONE place (the Actions page) and a rule points at
69343
+ * the result. A rule can be authored by a non-admin (personal rules), which is
69344
+ * safe for the same reason: it can only name an Action an admin already wrote.
69019
69345
  */
69346
+ var NcActionIdSchema = string().min(1);
69347
+ /** Action ids one hook edge runs, in order. Capped so a slot never becomes a second editor. */
69348
+ var NcHookActionIdsSchema = array(NcActionIdSchema).max(4);
69020
69349
  /**
69021
- * One step of a sequence.
69022
- *
69023
- * `wait` is a first-class step rather than a property of the next action: it is
69024
- * what makes a sequence a SEQUENCE and not a list — "unlock, wait 5s, open"
69025
- * cannot be expressed otherwise.
69026
- */
69027
- var NcRuleActionSchema = discriminatedUnion("kind", [object({
69028
- kind: literal("wait"),
69029
- seconds: number$1().min(0).max(300)
69030
- }), object({
69031
- kind: literal("cap"),
69032
- /**
69033
- * WHO the step acts on (D983) — the only shape that is written. A sequence
69034
- * stored before D983 carries `deviceId`; the sequence store reads it as a
69035
- * `devices` target.
69036
- */
69037
- target: ActionTargetSchema.optional(),
69038
- /** The vocabulary action the step performs; required for a `domain` target. */
69039
- actionId: string().min(1).optional(),
69040
- /** @deprecated The pre-D983 single device. Read, never written. */
69041
- deviceId: number$1().int().optional(),
69042
- /** Capability name, e.g. `alarm-panel`. */
69043
- cap: string().min(1),
69044
- /** Method on it. The executor refuses a non-device-scoped cap. */
69045
- method: string().min(1),
69046
- /** Method arguments, minus `deviceId` (the executor injects it). */
69047
- args: record(string(), unknown()).optional()
69048
- }).refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
69049
- message: "a cap step needs a target",
69050
- path: ["target"]
69051
- }).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
69052
- message: "a domain target needs the vocabulary action it applies to (actionId)",
69053
- path: ["actionId"]
69054
- })]);
69055
- /**
69056
- * A named, ordered run of steps shared across rules, with its own throttle.
69057
- *
69058
- * Sequences live in the notification centre's sequence store — never inline on
69059
- * a rule. A rule binds to one by id ({@link NcActionBindingSchema}), and a
69060
- * button names one the same way. `minDelaySec` exists because a noisy rule
69061
- * otherwise hammers a physical actuator — the rule's own cooldown governs
69062
- * NOTIFICATIONS, which is a different budget from "how often may this gate
69063
- * actually open". The throttle key is the sequence id, so two rules sharing
69064
- * one sequence share its budget.
69065
- */
69066
- var NcSharedSequenceSchema = object({
69067
- id: string().min(1),
69068
- name: string().min(1).max(120),
69069
- minDelaySec: number$1().int().min(0).max(86400).optional(),
69070
- actions: array(NcRuleActionSchema).min(1)
69071
- });
69072
- /** Create / update body — the id is minted by the provider. */
69073
- var NcSharedSequenceInputSchema = object({
69074
- name: string().min(1).max(120),
69075
- minDelaySec: number$1().int().min(0).max(86400).optional(),
69076
- actions: array(NcRuleActionSchema).min(1)
69077
- });
69078
- /** Bindings on one hook edge. Cap keeps a slot from becoming a second sequence editor. */
69079
- var NcHookBindingsSchema = array(discriminatedUnion("kind", [object({
69080
- kind: literal("sequence"),
69081
- sequenceId: string().min(1)
69082
- }), object({
69083
- kind: literal("do"),
69084
- action: NcRuleActionSchema
69085
- })])).max(4);
69086
- /**
69087
- * One button carried by the notification, running a shared sequence on tap.
69350
+ * One button carried by the notification, running an Action on tap.
69088
69351
  *
69089
69352
  * **Read this before adding a button that does something physical.** The tap
69090
69353
  * arrives over a link that travelled through third-party infrastructure — ntfy,
@@ -69099,9 +69362,9 @@ var NcRuleNotificationButtonSchema = object({
69099
69362
  /** Stable id — travels in the callback and identifies the button in logs. */
69100
69363
  id: string().min(1).max(64),
69101
69364
  label: string().min(1).max(40),
69102
- /** Id of a shared sequence. The dispatcher drops a button whose sequence
69103
- * does not exist rather than minting a token for nothing. */
69104
- sequenceId: string().min(1),
69365
+ /** The Action the tap runs. The dispatcher drops a button whose Action does
69366
+ * not exist rather than minting a token for nothing. */
69367
+ actionId: NcActionIdSchema,
69105
69368
  icon: NotificationActionIconSchema.optional(),
69106
69369
  destructive: boolean().optional()
69107
69370
  });
@@ -69116,25 +69379,19 @@ var NcRuleNotificationButtonSchema = object({
69116
69379
  * leave). A delivery with no clear edge never produces this hook.
69117
69380
  */
69118
69381
  var NcRuleActionsSchema = object({
69119
- onActivation: NcHookBindingsSchema.optional(),
69120
- onDeactivation: NcHookBindingsSchema.optional(),
69382
+ onActivation: NcHookActionIdsSchema.optional(),
69383
+ onDeactivation: NcHookActionIdsSchema.optional(),
69121
69384
  /** Runs on a positive / raised match. */
69122
- onTrigger: NcHookBindingsSchema.optional(),
69385
+ onTrigger: NcHookActionIdsSchema.optional(),
69123
69386
  /** Runs on a clear / leave match, when the subject carries one. */
69124
- onTriggerOff: NcHookBindingsSchema.optional(),
69387
+ onTriggerOff: NcHookActionIdsSchema.optional(),
69125
69388
  /**
69126
- * Buttons the NOTIFICATION carries, each running one shared sequence when
69127
- * tapped. Deliberately a REFERENCE — a button that could define its own
69128
- * actions would be a parallel actuation vocabulary.
69389
+ * Buttons the NOTIFICATION carries, each running one Action when tapped.
69390
+ * Deliberately a REFERENCE — a button that could define its own steps would
69391
+ * be a parallel actuation vocabulary.
69129
69392
  */
69130
69393
  buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
69131
69394
  });
69132
- object({
69133
- name: string().min(1).max(120),
69134
- enabled: boolean(),
69135
- minDelaySec: number$1().int().min(0).max(86400).optional(),
69136
- actions: array(NcRuleActionSchema).min(1)
69137
- });
69138
69395
  /**
69139
69396
  * "This rule applies only while `deviceId` is in one of `states`."
69140
69397
  *
@@ -69433,6 +69690,26 @@ var NcRuleTargetSchema = object({
69433
69690
  */
69434
69691
  params: record(string(), unknown()).optional()
69435
69692
  });
69693
+ /** Which picture kind one target receives (#487). */
69694
+ var NcTargetAttachmentSchema = _enum([
69695
+ "none",
69696
+ "image",
69697
+ "clip",
69698
+ "gif"
69699
+ ]);
69700
+ var NcTargetOverrideSchema = object({
69701
+ template: object({
69702
+ title: string().max(500).optional(),
69703
+ body: string().max(2e3).optional()
69704
+ }).optional(),
69705
+ schedule: NcScheduleSchema.optional(),
69706
+ priority: number$1().int().min(1).max(5).optional(),
69707
+ attachment: NcTargetAttachmentSchema.optional(),
69708
+ /** `false` = no buttons for this target; `true` is the same as absent. */
69709
+ actions: boolean().optional()
69710
+ });
69711
+ /** Per-target settings keyed by target id (#487). */
69712
+ var NcTargetOverridesSchema = record(string(), NcTargetOverrideSchema);
69436
69713
  /**
69437
69714
  * WHAT THE NOTIFICATION'S PICTURE IS (D897) — the ONE media choice of a rule.
69438
69715
  *
@@ -69714,6 +69991,13 @@ var NcRuleInputSchema = object({
69714
69991
  * targets.
69715
69992
  */
69716
69993
  targetUsers: array(string()).optional(),
69994
+ /**
69995
+ * Per-target settings keyed by target id (#487): texts, schedule, priority.
69996
+ * Absent (and an absent entry) = the rule's own values. A patch that omits
69997
+ * the key keeps the stored map; a patch that names it REPLACES it whole —
69998
+ * `{}` empties it.
69999
+ */
70000
+ targetOverrides: NcTargetOverridesSchema.optional(),
69717
70001
  /** Absent = the kind's default picture (`resolveNcMediaImage`): the subject crop for a
69718
70002
  * detection, the clean scene for a sensor or a sound. Set explicitly on save (D897). */
69719
70003
  media: NcMediaPolicySchema.default({}),
@@ -70815,18 +71099,6 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
70815
71099
  }), object({ success: literal(true) }), {
70816
71100
  kind: "mutation",
70817
71101
  auth: "admin"
70818
- }), 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 }), {
70819
- kind: "mutation",
70820
- auth: "admin"
70821
- }), method(object({
70822
- sequenceId: string().min(1),
70823
- sequence: NcSharedSequenceInputSchema
70824
- }), object({ sequence: NcSharedSequenceSchema }), {
70825
- kind: "mutation",
70826
- auth: "admin"
70827
- }), method(object({ sequenceId: string().min(1) }), object({ success: literal(true) }), {
70828
- kind: "mutation",
70829
- auth: "admin"
70830
71102
  }), method(object({}), NcClipRetentionPlanSchema, { auth: "admin" }), method(object({}), object({ mutedDeviceIds: array(number$1().int()).readonly() }), { auth: "admin" }), method(object({
70831
71103
  deviceId: number$1().int(),
70832
71104
  muted: boolean()
@@ -71265,53 +71537,41 @@ DeviceType.Camera, method(object({
71265
71537
  deviceId: number$1(),
71266
71538
  status: OsdStatusSchema
71267
71539
  });
71268
- _enum([
71269
- "value",
71270
- "unit",
71271
- "full"
71272
- ]);
71273
- _enum([
71274
- "value+unit",
71275
- "value",
71276
- "unit"
71277
- ]);
71278
71540
  /**
71279
- * How the operator reached the leaf device. `deviceId` is ALWAYS the
71280
- * OSD-supported leaf that is read; `containerId` is set when the pick
71281
- * went through a container (gateway) first.
71541
+ * `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
71542
+ *
71543
+ * The `osd` cap is the firmware contract: it probes a camera's overlay
71544
+ * SLOTS and writes literal text into one. It has no idea WHERE that text
71545
+ * comes from, and it must not — a driver that grew a "show the temperature
71546
+ * here" feature would grow it once per vendor.
71547
+ *
71548
+ * This cap owns the other half: a per-(camera, slot) BINDING that says
71549
+ * which value feeds the slot, how it is formatted, and under which
71550
+ * conditions it is shown at all. Named TEMPLATES combine many device
71551
+ * fields into one overlay string (plugin catalog OSD Manager parity).
71282
71552
  */
71283
- var OsdTemplatePickSchema = object({
71284
- containerId: number$1().int().optional(),
71285
- deviceId: number$1().int()
71286
- });
71287
71553
  /**
71288
- * One named binding inside a multi-device template: device path + unit
71289
- * selection + precision. The parser expands `{alias}`, `{alias.value}`,
71290
- * `{alias.unit}`, or `{alias.full}` from the same binding — there is no
71291
- * per-binding format lock.
71554
+ * The text of a named template: a device template (D1021) —
71555
+ * `{{ camera.temperature | round(1) }}`, `{{ now | time('HH:mm') }}`,
71556
+ * `{% if camera.motion %}MOTION{% endif %}` — rendered by the ONE hub renderer
71557
+ * (`device-manager.renderDeviceTemplates`), never by this addon.
71558
+ *
71559
+ * Two names belong to the render rather than to a device:
71560
+ * - `now` is the clock; `time('HH:mm')` prints it in the server's zone.
71561
+ * - `camera` is the camera the overlay is on, so one template serves every
71562
+ * camera. `camera.lastPerson` and `camera.lastPlate` are the most recent
71563
+ * recognition persisted for it (empty text until there is one, with
71564
+ * `| default('')`).
71292
71565
  */
71293
- var OsdTemplateBindingSchema = object({
71294
- /** Token stem in the parser, e.g. `t` → `{t}` / `{t.value}`. Unique within the template. */
71295
- alias: string().min(1).max(32).regex(/^[A-Za-z][A-Za-z0-9_-]*$/, "alias must start with a letter"),
71296
- pick: OsdTemplatePickSchema,
71297
- capName: string().min(1).max(64),
71298
- valuePath: string().min(1).max(64),
71299
- /** Convert TO this unit at render (`tryConvertUnit`). Absent = source unit. */
71300
- displayUnit: string().max(16).optional(),
71301
- maxDecimals: number$1().int().min(0).max(4).default(1),
71302
- /** Raw value → display text (same shape as slot binding). Empty string hides. */
71303
- valueMap: record(string(), string()).optional()
71304
- });
71566
+ var OSD_TEMPLATE_TEXT_MAX = 1024;
71305
71567
  /**
71306
- * A named template: bindings (alias → leaf field) + a parser that
71307
- * references `{alias}` / `{alias.value}` / `{alias.unit}` / `{alias.full}`.
71568
+ * A named template: the unit of reuse. A slot points at one by id; editing it
71569
+ * changes every slot that points at it, on every camera.
71308
71570
  */
71309
71571
  var OsdNamedTemplateSchema = object({
71310
71572
  id: string().min(1).max(64),
71311
71573
  name: string().min(1).max(64),
71312
- bindings: array(OsdTemplateBindingSchema).max(32).default([]),
71313
- /** Parser with alias placeholders, e.g. `{t.value} {t.unit} / {h}`. */
71314
- parser: string().max(256).default("")
71574
+ text: string().max(OSD_TEMPLATE_TEXT_MAX).default("")
71315
71575
  });
71316
71576
  /** Where a slot's value comes from. */
71317
71577
  var OsdSourceSchema = discriminatedUnion("kind", [
@@ -71320,26 +71580,6 @@ var OsdSourceSchema = discriminatedUnion("kind", [
71320
71580
  kind: literal("static"),
71321
71581
  text: string().max(64)
71322
71582
  }),
71323
- object({
71324
- kind: literal("clock"),
71325
- /** Token pattern: `YYYY MM DD HH mm ss`. Everything else is literal. */
71326
- pattern: string().min(1).max(32).default("HH:mm"),
71327
- /** IANA zone. Omitted = the server's zone. */
71328
- timezone: string().min(1).max(64).optional()
71329
- }),
71330
- object({
71331
- kind: literal("device-state"),
71332
- deviceId: number$1().int().optional(),
71333
- capName: string().min(1).max(64),
71334
- /** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
71335
- valuePath: string().min(1).max(64),
71336
- /** Convert TO this unit at render. Absent = no conversion. */
71337
- displayUnit: string().max(16).optional()
71338
- }),
71339
- object({
71340
- kind: literal("latest-recognition"),
71341
- recognition: _enum(["person", "plate"])
71342
- }),
71343
71583
  object({
71344
71584
  kind: literal("template"),
71345
71585
  templateId: string().min(1).max(64)
@@ -71349,15 +71589,6 @@ var OsdSlotBindingSchema = object({
71349
71589
  /** Off = the manager stops driving this slot. It does NOT clear it. */
71350
71590
  enabled: boolean().default(true),
71351
71591
  source: OsdSourceSchema,
71352
- /** `${value}` and `${unit}` are substituted; every occurrence. */
71353
- template: string().max(96).default("${value}"),
71354
- /**
71355
- * Decimal places for a numeric value. `0` yields an integer — the
71356
- * documented workaround for firmwares that reject `.` in overlay text.
71357
- */
71358
- maxDecimals: number$1().int().min(0).max(4).default(1),
71359
- /** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
71360
- valueMap: record(string(), string()).optional(),
71361
71592
  /** Time windows in which the slot is shown. Absent = always. */
71362
71593
  schedule: NcScheduleSchema.optional(),
71363
71594
  /**
@@ -71399,31 +71630,21 @@ var OsdRenderResultSchema = object({
71399
71630
  /** Why, whenever the outcome is not a plain write. Never silent. */
71400
71631
  reason: string().optional()
71401
71632
  });
71402
- var OsdSourceValueTypeEnum = _enum([
71403
- "number",
71404
- "boolean",
71405
- "string",
71406
- "enum"
71407
- ]);
71408
71633
  /**
71409
- * One bindable value, derived from a cap's `runtimeState` schema — never
71410
- * hand-listed. The editor renders from this, so a cap that ships a new
71411
- * state field becomes bindable with no UI change.
71634
+ * A template rendered for one camera without being saved: what the editor
71635
+ * previews. `text` is what the overlay would carry; a refused render has a
71636
+ * `reason` and an empty text.
71412
71637
  */
71413
- var OsdSourceOptionSchema = object({
71414
- deviceId: number$1().int(),
71415
- deviceName: string(),
71416
- capName: string(),
71417
- valuePath: string(),
71418
- label: string(),
71419
- valueType: OsdSourceValueTypeEnum,
71420
- /** Present for `enum`; the editor offers these as `valueMap` keys. */
71421
- enumValues: array(string()).readonly().optional()
71638
+ var OsdTemplatePreviewSchema = object({
71639
+ ok: boolean(),
71640
+ text: string(),
71641
+ /** The refusal, or `ref: reason` per value that printed `—`. */
71642
+ notes: array(string())
71422
71643
  });
71423
71644
  method(object({ deviceId: number$1().int() }), object({
71424
71645
  supported: boolean(),
71425
71646
  slots: array(OsdSlotViewSchema)
71426
- }), { auth: "admin" }), method(object({ deviceId: number$1().int() }), object({ sources: array(OsdSourceOptionSchema) }), { auth: "admin" }), method(object({}), object({
71647
+ }), { auth: "admin" }), method(object({}), object({
71427
71648
  supported: array(string()),
71428
71649
  catalog: array(NcConditionDescriptorSchema)
71429
71650
  }), { auth: "admin" }), method(object({
@@ -71465,6 +71686,12 @@ method(object({ deviceId: number$1().int() }), object({
71465
71686
  }), OsdRenderResultSchema, {
71466
71687
  kind: "mutation",
71467
71688
  auth: "admin"
71689
+ }), method(object({
71690
+ deviceId: number$1().int(),
71691
+ text: string().max(OSD_TEMPLATE_TEXT_MAX)
71692
+ }), OsdTemplatePreviewSchema, {
71693
+ kind: "mutation",
71694
+ auth: "admin"
71468
71695
  }), method(object({ deviceId: number$1().int() }), object({ results: array(OsdRenderResultSchema) }), {
71469
71696
  kind: "mutation",
71470
71697
  auth: "admin"
@@ -71670,9 +71897,13 @@ var petFeederCapability = {
71670
71897
  */
71671
71898
  durability: "session",
71672
71899
  /**
71673
- * History (D850): recorded series.
71900
+ * History (D850): recorded series. `feeding` / `eating` are the meals the
71901
+ * device hero's "Meals" button names — change-only booleans, so a dispense
71902
+ * or a pet at the bowl is one span on the history.
71674
71903
  */
71675
71904
  history: { fields: {
71905
+ feeding: { kind: "boolean" },
71906
+ eating: { kind: "boolean" },
71676
71907
  foodLevel: { kind: "gauge" },
71677
71908
  bowlFood: { kind: "gauge" },
71678
71909
  food1: { kind: "gauge" },
@@ -73465,7 +73696,6 @@ var PowerSourceStateSchema = object({
73465
73696
  });
73466
73697
  var POWER_SOURCE_DEVICE_TYPES = [
73467
73698
  DeviceType.Gateway,
73468
- DeviceType.Hub,
73469
73699
  DeviceType.Camera,
73470
73700
  DeviceType.Sensor,
73471
73701
  DeviceType.MediaPlayer,
@@ -73517,18 +73747,18 @@ var powerSourceCapability = {
73517
73747
  mode: "singleton",
73518
73748
  deviceTypes: POWER_SOURCE_DEVICE_TYPES,
73519
73749
  methods: {
73520
- getStatus: systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }),
73750
+ getStatus: systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "protected" }),
73521
73751
  powerOn: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
73522
73752
  kind: "mutation",
73523
- auth: "admin"
73753
+ auth: "protected"
73524
73754
  }),
73525
73755
  powerOff: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
73526
73756
  kind: "mutation",
73527
- auth: "admin"
73757
+ auth: "protected"
73528
73758
  }),
73529
73759
  forceRestart: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
73530
73760
  kind: "mutation",
73531
- auth: "admin"
73761
+ auth: "protected"
73532
73762
  })
73533
73763
  },
73534
73764
  runtimeState: PowerSourceStateSchema,
@@ -81064,7 +81294,7 @@ var valveCapability = {
81064
81294
  kind: "mutation",
81065
81295
  auth: "protected"
81066
81296
  }),
81067
- setPosition: method(object({
81297
+ setPosition: optionalMethod(object({
81068
81298
  deviceId: number$1().int().nonnegative(),
81069
81299
  position: number$1().min(0).max(100)
81070
81300
  }), _void(), {
@@ -83046,7 +83276,6 @@ var DEVICE_LOCAL_STATE_CAPS = {
83046
83276
  control: controlCapability,
83047
83277
  cover: coverCapability,
83048
83278
  dayNight: dayNightCapability,
83049
- deviceDiscovery: deviceDiscoveryCapability,
83050
83279
  deviceStatus: deviceStatusCapability,
83051
83280
  dishwasher: dishwasherCapability,
83052
83281
  doNotDisturb: doNotDisturbCapability,
@@ -88225,6 +88454,27 @@ Object.freeze({
88225
88454
  access: "create",
88226
88455
  role: "viewer"
88227
88456
  },
88457
+ "clusterUpdate.getClusterUpdateJob": {
88458
+ capName: "cluster-update",
88459
+ capScope: "system",
88460
+ addonId: null,
88461
+ access: "view",
88462
+ role: "admin"
88463
+ },
88464
+ "clusterUpdate.previewClusterUpdate": {
88465
+ capName: "cluster-update",
88466
+ capScope: "system",
88467
+ addonId: null,
88468
+ access: "view",
88469
+ role: "admin"
88470
+ },
88471
+ "clusterUpdate.startClusterUpdate": {
88472
+ capName: "cluster-update",
88473
+ capScope: "system",
88474
+ addonId: null,
88475
+ access: "create",
88476
+ role: "admin"
88477
+ },
88228
88478
  "color.getStatus": {
88229
88479
  capName: "color",
88230
88480
  capScope: "device",
@@ -88827,41 +89077,6 @@ Object.freeze({
88827
89077
  access: "create",
88828
89078
  role: "admin"
88829
89079
  },
88830
- "deviceDiscovery.adoptDevice": {
88831
- capName: "device-discovery",
88832
- capScope: "device",
88833
- addonId: null,
88834
- access: "create",
88835
- role: "admin"
88836
- },
88837
- "deviceDiscovery.getStatus": {
88838
- capName: "device-discovery",
88839
- capScope: "device",
88840
- addonId: null,
88841
- access: "view",
88842
- role: "viewer"
88843
- },
88844
- "deviceDiscovery.listDiscovered": {
88845
- capName: "device-discovery",
88846
- capScope: "device",
88847
- addonId: null,
88848
- access: "view",
88849
- role: "viewer"
88850
- },
88851
- "deviceDiscovery.refreshDiscovery": {
88852
- capName: "device-discovery",
88853
- capScope: "device",
88854
- addonId: null,
88855
- access: "create",
88856
- role: "admin"
88857
- },
88858
- "deviceDiscovery.releaseDevice": {
88859
- capName: "device-discovery",
88860
- capScope: "device",
88861
- addonId: null,
88862
- access: "create",
88863
- role: "admin"
88864
- },
88865
89080
  "deviceExport.applyDeviceSettingsPatch": {
88866
89081
  capName: "device-export",
88867
89082
  capScope: "system",
@@ -89597,6 +89812,13 @@ Object.freeze({
89597
89812
  access: "create",
89598
89813
  role: "admin"
89599
89814
  },
89815
+ "deviceManager.search": {
89816
+ capName: "device-manager",
89817
+ capScope: "system",
89818
+ addonId: null,
89819
+ access: "view",
89820
+ role: "viewer"
89821
+ },
89600
89822
  "deviceManager.setAlias": {
89601
89823
  capName: "device-manager",
89602
89824
  capScope: "system",
@@ -89688,6 +89910,13 @@ Object.freeze({
89688
89910
  access: "create",
89689
89911
  role: "admin"
89690
89912
  },
89913
+ "deviceManager.setPrimaryChildPreference": {
89914
+ capName: "device-manager",
89915
+ capScope: "system",
89916
+ addonId: null,
89917
+ access: "create",
89918
+ role: "admin"
89919
+ },
89691
89920
  "deviceManager.setRole": {
89692
89921
  capName: "device-manager",
89693
89922
  capScope: "system",
@@ -92369,13 +92598,6 @@ Object.freeze({
92369
92598
  access: "create",
92370
92599
  role: "admin"
92371
92600
  },
92372
- "notificationRules.createSequence": {
92373
- capName: "notification-rules",
92374
- capScope: "system",
92375
- addonId: null,
92376
- access: "create",
92377
- role: "admin"
92378
- },
92379
92601
  "notificationRules.createSnooze": {
92380
92602
  capName: "notification-rules",
92381
92603
  capScope: "system",
@@ -92397,13 +92619,6 @@ Object.freeze({
92397
92619
  access: "delete",
92398
92620
  role: "admin"
92399
92621
  },
92400
- "notificationRules.deleteSequence": {
92401
- capName: "notification-rules",
92402
- capScope: "system",
92403
- addonId: null,
92404
- access: "delete",
92405
- role: "admin"
92406
- },
92407
92622
  "notificationRules.getAlarmConfig": {
92408
92623
  capName: "notification-rules",
92409
92624
  capScope: "system",
@@ -92453,13 +92668,6 @@ Object.freeze({
92453
92668
  access: "view",
92454
92669
  role: "admin"
92455
92670
  },
92456
- "notificationRules.getSequence": {
92457
- capName: "notification-rules",
92458
- capScope: "system",
92459
- addonId: null,
92460
- access: "view",
92461
- role: "admin"
92462
- },
92463
92671
  "notificationRules.getTemplateCatalog": {
92464
92672
  capName: "notification-rules",
92465
92673
  capScope: "system",
@@ -92481,13 +92689,6 @@ Object.freeze({
92481
92689
  access: "view",
92482
92690
  role: "admin"
92483
92691
  },
92484
- "notificationRules.listSequences": {
92485
- capName: "notification-rules",
92486
- capScope: "system",
92487
- addonId: null,
92488
- access: "view",
92489
- role: "admin"
92490
- },
92491
92692
  "notificationRules.listSnoozes": {
92492
92693
  capName: "notification-rules",
92493
92694
  capScope: "system",
@@ -92558,13 +92759,6 @@ Object.freeze({
92558
92759
  access: "create",
92559
92760
  role: "admin"
92560
92761
  },
92561
- "notificationRules.updateSequence": {
92562
- capName: "notification-rules",
92563
- capScope: "system",
92564
- addonId: null,
92565
- access: "create",
92566
- role: "admin"
92567
- },
92568
92762
  "notifier.cancel": {
92569
92763
  capName: "notifier",
92570
92764
  capScope: "device",
@@ -92649,21 +92843,21 @@ Object.freeze({
92649
92843
  access: "view",
92650
92844
  role: "admin"
92651
92845
  },
92652
- "osdManager.getSourceCatalog": {
92846
+ "osdManager.listTemplates": {
92653
92847
  capName: "osd-manager",
92654
92848
  capScope: "system",
92655
92849
  addonId: null,
92656
92850
  access: "view",
92657
92851
  role: "admin"
92658
92852
  },
92659
- "osdManager.listTemplates": {
92853
+ "osdManager.previewSlot": {
92660
92854
  capName: "osd-manager",
92661
92855
  capScope: "system",
92662
92856
  addonId: null,
92663
- access: "view",
92857
+ access: "create",
92664
92858
  role: "admin"
92665
92859
  },
92666
- "osdManager.previewSlot": {
92860
+ "osdManager.previewTemplate": {
92667
92861
  capName: "osd-manager",
92668
92862
  capScope: "system",
92669
92863
  addonId: null,
@@ -94362,28 +94556,28 @@ Object.freeze({
94362
94556
  capScope: "device",
94363
94557
  addonId: null,
94364
94558
  access: "create",
94365
- role: "admin"
94559
+ role: "viewer"
94366
94560
  },
94367
94561
  "powerSource.getStatus": {
94368
94562
  capName: "power-source",
94369
94563
  capScope: "device",
94370
94564
  addonId: null,
94371
94565
  access: "view",
94372
- role: "admin"
94566
+ role: "viewer"
94373
94567
  },
94374
94568
  "powerSource.powerOff": {
94375
94569
  capName: "power-source",
94376
94570
  capScope: "device",
94377
94571
  addonId: null,
94378
94572
  access: "create",
94379
- role: "admin"
94573
+ role: "viewer"
94380
94574
  },
94381
94575
  "powerSource.powerOn": {
94382
94576
  capName: "power-source",
94383
94577
  capScope: "device",
94384
94578
  addonId: null,
94385
94579
  access: "create",
94386
- role: "admin"
94580
+ role: "viewer"
94387
94581
  },
94388
94582
  "presence.getStatus": {
94389
94583
  capName: "presence",
@@ -97453,6 +97647,24 @@ function projectAction(cap, action) {
97453
97647
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
97454
97648
  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;
97455
97649
  DeviceFeature.BatteryOperated;
97650
+ function bound(cap, methods = [], authority = "reported") {
97651
+ return {
97652
+ kind: "cap",
97653
+ cap,
97654
+ methods,
97655
+ authority
97656
+ };
97657
+ }
97658
+ function derived(cap, methods = []) {
97659
+ return bound(cap, methods, "derived");
97660
+ }
97661
+ DeviceFeature.BatteryOperated, DeviceFeature.Rebootable, derived("reboot", ["reboot"]), DeviceFeature.Resyncable, DeviceFeature.NativeSnapshot, DeviceFeature.DoorbellButton, derived("doorbell"), DeviceFeature.TwoWayAudio, derived("intercom", [
97662
+ "startTalkSession",
97663
+ "pushTalkAudio",
97664
+ "endTalkSession"
97665
+ ]), 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");
97666
+ var ALL_FEATURES = Object.values(DeviceFeature);
97667
+ new Set(ALL_FEATURES);
97456
97668
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
97457
97669
  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.";
97458
97670
  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.";
@@ -97922,35 +98134,6 @@ Object.freeze({
97922
98134
  form: "single",
97923
98135
  optional: false
97924
98136
  }],
97925
- "deviceDiscovery.adoptDevice": [{
97926
- name: "deviceId",
97927
- form: "single",
97928
- optional: false
97929
- }],
97930
- "deviceDiscovery.getStatus": [{
97931
- name: "deviceId",
97932
- form: "single",
97933
- optional: false
97934
- }],
97935
- "deviceDiscovery.listDiscovered": [{
97936
- name: "deviceId",
97937
- form: "single",
97938
- optional: false
97939
- }],
97940
- "deviceDiscovery.refreshDiscovery": [{
97941
- name: "deviceId",
97942
- form: "single",
97943
- optional: false
97944
- }],
97945
- "deviceDiscovery.releaseDevice": [{
97946
- name: "childDeviceId",
97947
- form: "single",
97948
- optional: false
97949
- }, {
97950
- name: "deviceId",
97951
- form: "single",
97952
- optional: false
97953
- }],
97954
98137
  "deviceExport.applyDeviceSettingsPatch": [{
97955
98138
  name: "deviceId",
97956
98139
  form: "single",
@@ -98229,6 +98412,11 @@ Object.freeze({
98229
98412
  form: "single",
98230
98413
  optional: false
98231
98414
  }],
98415
+ "deviceManager.setPrimaryChildPreference": [{
98416
+ name: "deviceId",
98417
+ form: "single",
98418
+ optional: false
98419
+ }],
98232
98420
  "deviceManager.setRole": [{
98233
98421
  name: "deviceId",
98234
98422
  form: "single",
@@ -99108,12 +99296,12 @@ Object.freeze({
99108
99296
  form: "single",
99109
99297
  optional: false
99110
99298
  }],
99111
- "osdManager.getSourceCatalog": [{
99299
+ "osdManager.previewSlot": [{
99112
99300
  name: "deviceId",
99113
99301
  form: "single",
99114
99302
  optional: false
99115
99303
  }],
99116
- "osdManager.previewSlot": [{
99304
+ "osdManager.previewTemplate": [{
99117
99305
  name: "deviceId",
99118
99306
  form: "single",
99119
99307
  optional: false
@@ -101697,7 +101885,9 @@ var TimelapseRuleInputSchema = object({
101697
101885
  /** Classes the notification counts — see {@link ReportClassesField}. */
101698
101886
  reportClasses: ReportClassesField.optional(),
101699
101887
  /** Canonical notification priority ordinal (1..5); per-target overridable. */
101700
- priority: PriorityField.default(3)
101888
+ priority: PriorityField.default(3),
101889
+ /** Per-target settings keyed by target id (D1032) — absent = every target follows the rule. */
101890
+ targetOverrides: NcTargetOverridesSchema.optional()
101701
101891
  });
101702
101892
  object({
101703
101893
  name: NameField.optional(),
@@ -101713,7 +101903,9 @@ object({
101713
101903
  previewText: PreviewTextField.optional(),
101714
101904
  previewMode: PreviewModeField.optional(),
101715
101905
  reportClasses: ReportClassesField.optional(),
101716
- priority: PriorityField.optional()
101906
+ priority: PriorityField.optional(),
101907
+ /** Per-target settings keyed by target id (D1032) — absent = every target follows the rule. */
101908
+ targetOverrides: NcTargetOverridesSchema.optional()
101717
101909
  });
101718
101910
  TimelapseRuleInputSchema.extend({
101719
101911
  id: string(),