@camstack/addon-smtp-nodemailer 1.2.146 → 1.2.147

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.
@@ -824,7 +824,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
824
824
  return EventCategory;
825
825
  }({});
826
826
  //#endregion
827
- //#region ../types/dist/device-type-CfHw_quR.mjs
827
+ //#region ../types/dist/device-type-D1fH5kaC.mjs
828
828
  /**
829
829
  import { errMsg } from '@camstack/types'
830
830
  * Extract a human-readable message from an unknown error value.
@@ -1055,6 +1055,12 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
1055
1055
  DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
1056
1056
  /** Media player exposes stop distinct from pause. */
1057
1057
  DeviceFeature["MediaPlayerStop"] = "media-player-stop";
1058
+ /** Media player exposes volume up / down in steps (an infrared TV has no level). */
1059
+ DeviceFeature["MediaPlayerVolumeStep"] = "media-player-volume-step";
1060
+ /** Media player exposes channel up / down in steps. */
1061
+ DeviceFeature["MediaPlayerChannelStep"] = "media-player-channel-step";
1062
+ /** Media player exposes power on / off. */
1063
+ DeviceFeature["MediaPlayerPower"] = "media-player-power";
1058
1064
  /** Alarm panel requires a PIN code on arm/disarm. */
1059
1065
  DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
1060
1066
  /** Presence device carries GPS coordinates (lat/lng/accuracy) in
@@ -6014,7 +6020,7 @@ var ZodIssueCode = {
6014
6020
  var ZodFirstPartyTypeKind;
6015
6021
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6016
6022
  //#endregion
6017
- //#region ../types/dist/sleep-QsoHs66J.mjs
6023
+ //#region ../types/dist/sleep-BaWhXZFm.mjs
6018
6024
  /**
6019
6025
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6020
6026
  * window to float samples (D455).
@@ -6093,6 +6099,22 @@ function buildAlawTable() {
6093
6099
  }
6094
6100
  buildUlawTable();
6095
6101
  buildAlawTable();
6102
+ /**
6103
+ * An addon whose own migrations did not apply refuses to initialise (D987). The error is typed
6104
+ * so a runner hosting several addons can tell "this addon is blocked" from a crash and keep its
6105
+ * co-located siblings running. Recognised by NAME and shape, never `instanceof`: a forked addon
6106
+ * bundles its own copy of this module, so the class the runner sees is not the one that threw.
6107
+ */
6108
+ var MIGRATIONS_BLOCKED_ERROR_NAME = "AddonMigrationsBlockedError";
6109
+ var AddonMigrationsBlockedError = class extends Error {
6110
+ /** The ledger ids that did not apply and block the owner. */
6111
+ migrationIds;
6112
+ constructor(migrationIds) {
6113
+ super(`migration(s) ${migrationIds.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
6114
+ this.name = MIGRATIONS_BLOCKED_ERROR_NAME;
6115
+ this.migrationIds = migrationIds;
6116
+ }
6117
+ };
6096
6118
  /** Is this field's stored value a credential? */
6097
6119
  function isSecretConfigField(field) {
6098
6120
  if (field.type === "password") return true;
@@ -7795,7 +7817,7 @@ var BaseAddon = class {
7795
7817
  startMigration: (input) => api.startMigration.mutate(input),
7796
7818
  finishMigration: (input) => api.finishMigration.mutate(input)
7797
7819
  }, migrations, this.ctx.logger);
7798
- if (report.blockOwner.length > 0) throw new Error(`migration(s) ${report.blockOwner.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
7820
+ if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7799
7821
  return report;
7800
7822
  }
7801
7823
  /**
@@ -8135,6 +8157,10 @@ var BaseAddon = class {
8135
8157
  const stored = keychain ? await this.readAddonStoreWithRetry() : {};
8136
8158
  const patch = {};
8137
8159
  for (const key of keys) {
8160
+ if (tree.get(key)?.kind === "array") {
8161
+ patch[key] = [];
8162
+ continue;
8163
+ }
8138
8164
  const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
8139
8165
  patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
8140
8166
  }
@@ -8837,6 +8863,10 @@ var RECORD_SECRET_COLLECTION_PREFIX = "records:";
8837
8863
  * (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
8838
8864
  * (`auth.accessToken`, `headers[].value`).
8839
8865
  *
8866
+ * `{}` after a key means "each value of this map" (`applianceSecrets{}`): the map's own key
8867
+ * names the value (`applianceSecrets{fridge}`), so no id key is declared and a body that drops a
8868
+ * key releases exactly that key's secret.
8869
+ *
8840
8870
  * Array elements are matched by a DECLARED id key (`secretArrayKeys`:
8841
8871
  * `{ headers: 'name' }`), never by index, so reordering a list keeps every
8842
8872
  * keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
@@ -8849,11 +8879,13 @@ function parseSecretPath(path) {
8849
8879
  if (path.length === 0) throw new InvalidRecordError("an empty secret path");
8850
8880
  return path.split(".").map((raw) => {
8851
8881
  const each = raw.endsWith("[]");
8852
- const key = each ? raw.slice(0, -2) : raw;
8882
+ const map = raw.endsWith("{}");
8883
+ const key = each || map ? raw.slice(0, -2) : raw;
8853
8884
  if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
8854
8885
  return {
8855
8886
  key,
8856
- each
8887
+ each,
8888
+ map
8857
8889
  };
8858
8890
  });
8859
8891
  }
@@ -8905,6 +8937,22 @@ function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arra
8905
8937
  [segment.key]: mapped
8906
8938
  };
8907
8939
  }
8940
+ if (segment.map) {
8941
+ if (!isRecord$4(child)) return node;
8942
+ const kept = Object.entries(child).map(([mapKey, value]) => {
8943
+ const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}{${encodeURIComponent(mapKey)}}`;
8944
+ if (!last) return [mapKey, mapAt(value, segments, index + 1, keys, concrete, declared, arrayKeys, visit)];
8945
+ return [mapKey, visit({
8946
+ declared,
8947
+ concrete,
8948
+ value
8949
+ })];
8950
+ }).filter(([, value]) => value !== REMOVE_LEAF);
8951
+ return {
8952
+ ...node,
8953
+ [segment.key]: Object.fromEntries(kept)
8954
+ };
8955
+ }
8908
8956
  const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
8909
8957
  if (last) {
8910
8958
  const replacement = visit({
@@ -9046,7 +9094,8 @@ function isZodSecret(schema) {
9046
9094
  }
9047
9095
  /**
9048
9096
  * Every declared secret path in a config schema: dotted keys, `[]` for "each
9049
- * element of this array" (`connection.password`, `rows[].token`). A declared
9097
+ * element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
9098
+ * map" (`applianceSecrets{}`). A declared
9050
9099
  * object or array is reported as one path and not descended into — everything
9051
9100
  * under it is secret.
9052
9101
  */
@@ -9057,6 +9106,7 @@ function collectZodSecretPaths(schema, prefix = "") {
9057
9106
  const def = defOf(layer);
9058
9107
  if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
9059
9108
  else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
9109
+ else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
9060
9110
  else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
9061
9111
  else if (def.type === "intersection") {
9062
9112
  if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
@@ -9196,7 +9246,7 @@ function defineRecordKinds(specs) {
9196
9246
  retention: spec.retention,
9197
9247
  deviceRemoval: spec.deviceRemoval,
9198
9248
  maxDataBytes: spec.maxDataBytes,
9199
- secretPaths: [...secretPaths],
9249
+ secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
9200
9250
  secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
9201
9251
  transactFamily: spec.transactFamily,
9202
9252
  entry: spec.entry === void 0 ? void 0 : {
@@ -9241,6 +9291,7 @@ function defineEntryKind(name, spec) {
9241
9291
  ...spec.database === void 0 ? {} : { database: spec.database },
9242
9292
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9243
9293
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
9294
+ ...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
9244
9295
  entry: {
9245
9296
  title: spec.title,
9246
9297
  ...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
@@ -9901,6 +9952,7 @@ var CAP_NAMES = Object.freeze([
9901
9952
  "backup",
9902
9953
  "battery",
9903
9954
  "binary",
9955
+ "board-key-source",
9904
9956
  "brightness",
9905
9957
  "broker",
9906
9958
  "button",
@@ -10022,6 +10074,7 @@ var CAP_NAMES = Object.freeze([
10022
10074
  "privacy-mask",
10023
10075
  "ptz",
10024
10076
  "ptz-autotrack",
10077
+ "pump-cycle",
10025
10078
  "reboot",
10026
10079
  "record-entries",
10027
10080
  "recording",
@@ -11314,18 +11367,6 @@ Object.freeze({
11314
11367
  "path": "pumpRunning",
11315
11368
  "valueType": "boolean"
11316
11369
  },
11317
- {
11318
- "path": "pumpIntervalEnabled",
11319
- "valueType": "boolean"
11320
- },
11321
- {
11322
- "path": "pumpOnTime",
11323
- "valueType": "number"
11324
- },
11325
- {
11326
- "path": "pumpOffTime",
11327
- "valueType": "number"
11328
- },
11329
11370
  {
11330
11371
  "path": "lastDrink",
11331
11372
  "valueType": "number"
@@ -11449,6 +11490,20 @@ Object.freeze({
11449
11490
  "valueType": "number"
11450
11491
  }
11451
11492
  ],
11493
+ "pump-cycle": [
11494
+ {
11495
+ "path": "enabled",
11496
+ "valueType": "boolean"
11497
+ },
11498
+ {
11499
+ "path": "onTime",
11500
+ "valueType": "number"
11501
+ },
11502
+ {
11503
+ "path": "offTime",
11504
+ "valueType": "number"
11505
+ }
11506
+ ],
11452
11507
  "recording-archive": [
11453
11508
  {
11454
11509
  "path": "deviceId",
@@ -11912,7 +11967,7 @@ function userError(code, params, englishFallback) {
11912
11967
  return new CamstackError(code, params, englishFallback);
11913
11968
  }
11914
11969
  //#endregion
11915
- //#region ../types/dist/composition-Dh3RfMx5.mjs
11970
+ //#region ../types/dist/composition-CRNwcFYY.mjs
11916
11971
  /**
11917
11972
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11918
11973
  *
@@ -12675,38 +12730,6 @@ function validateExpressionSource(src) {
12675
12730
  return null;
12676
12731
  }
12677
12732
  /**
12678
- * One power reading becomes a washer, dryer or dishwasher (D944 helper).
12679
- *
12680
- * Standby of the control board is under 5 W; a motor or a heater is far
12681
- * above that, so 5 W is the line. A door lock or a short pump spike is not
12682
- * a cycle, so running starts only after 30 s above the line.
12683
- *
12684
- * A wash, dry or dishwasher pause (soak, heat-wait, heater off between
12685
- * bursts) lasts several minutes. Ending the cycle on the first dip would
12686
- * report "finished" in the middle of the program, so the stop dwell is
12687
- * 10 minutes on every type. The number is stored per type so one machine
12688
- * can move later without moving the others.
12689
- */
12690
- var APPLIANCE_TEMPLATE_ID = "appliance";
12691
- var APPLIANCE_KINDS = [
12692
- "washer",
12693
- "dryer",
12694
- "dishwasher"
12695
- ];
12696
- DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
12697
- /**
12698
- * A dumb plug-in stove or heater, built from a smart plug (a `Heater`).
12699
- *
12700
- * The plug is the actuator: `switch.setState` is a FORWARD to the plug's own
12701
- * (D967), so the heater switches exactly as the plug does. Running is the
12702
- * plug being on; when a power reading is also named, running is the draw
12703
- * staying above the line instead (a plug left on with the stove's own
12704
- * thermostat open is not heating). 50 W is far above any standby and below
12705
- * the smallest radiator.
12706
- */
12707
- var HEATER_TEMPLATE_ID = "heater";
12708
- DeviceType.Heater;
12709
- /**
12710
12733
  * Statistics and integrals over a history read (D944).
12711
12734
  *
12712
12735
  * Pure: the same points always give the same answer, so a restart recomputes
@@ -12850,34 +12873,6 @@ var HelperSpecSchema = discriminatedUnion("kind", [
12850
12873
  /** Actuator groups only. Readings leave this unset. */
12851
12874
  active: ActiveCriterionSchema.optional()
12852
12875
  }),
12853
- object({
12854
- kind: literal("appliance"),
12855
- templateId: literal(APPLIANCE_TEMPLATE_ID),
12856
- appliance: _enum(APPLIANCE_KINDS),
12857
- thresholdWatts: number().positive(),
12858
- startDwellMs: number().int().nonnegative(),
12859
- stopDwellMs: number().int().nonnegative(),
12860
- member: HelperMemberSchema,
12861
- /**
12862
- * The plug that powers the appliance (`switch.on`). Optional: when set, the
12863
- * composition carries a `power-source` feature, so the hub can cut and restore
12864
- * that plug (power-source link of a NEW-target composition).
12865
- */
12866
- powerSwitch: HelperMemberSchema.optional(),
12867
- /** How long a forced restart keeps the plug off; the cap's default when unset. */
12868
- offSeconds: number().optional()
12869
- }),
12870
- object({
12871
- kind: literal("heater"),
12872
- templateId: literal(HEATER_TEMPLATE_ID),
12873
- /** The plug, read and commanded at `switch.on`. */
12874
- plug: HelperMemberSchema,
12875
- /** The plug's power reading, when it has one: running is then the draw, not the switch. */
12876
- meter: HelperMemberSchema.optional(),
12877
- thresholdWatts: number().positive(),
12878
- startDwellMs: number().int().nonnegative(),
12879
- stopDwellMs: number().int().nonnegative()
12880
- }),
12881
12876
  object({
12882
12877
  kind: literal("statistic"),
12883
12878
  stat: _enum(STATISTIC_STATS),
@@ -13049,7 +13044,14 @@ var CompositionForwardCommandSchema = object({
13049
13044
  method: string().min(1),
13050
13045
  args: record(string().min(1), CompositionForwardArgSchema).optional()
13051
13046
  });
13052
- var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
13047
+ var CompositionCommandTargetSchema = discriminatedUnion("kind", [
13048
+ CompositionForwardCommandSchema,
13049
+ CompositionCodeSourceSchema,
13050
+ object({
13051
+ kind: literal("extension"),
13052
+ target: literal("remote")
13053
+ })
13054
+ ]);
13053
13055
  /** An item key inside an item-array cap (`consumables.items[<key>]`). */
13054
13056
  var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
13055
13057
  /**
@@ -13094,7 +13096,7 @@ var CompositionSchema = object({
13094
13096
  kind: literal("existing"),
13095
13097
  device: CompositionSourceRefSchema
13096
13098
  })]),
13097
- features: array(CompositionFeatureSchema).min(1).max(16),
13099
+ features: array(CompositionFeatureSchema).min(0).max(16),
13098
13100
  /**
13099
13101
  * Present when the block is a helper template. Absent on a free
13100
13102
  * composition (the room example, device 4603). The editor reads this,
@@ -13329,11 +13331,49 @@ var IntegrationInstanceRefSchema = object({
13329
13331
  ]),
13330
13332
  candidateStableId: string().optional()
13331
13333
  });
13334
+ /**
13335
+ * `RecordIntegrationInstances` - the `integration-instances` provider over ONE
13336
+ * entry kind, so an addon that holds a connection serves it in ~30 lines.
13337
+ *
13338
+ * Every instance is one record of the addon's own kind, keyed by the
13339
+ * `integrations` registry id. The records door does the secret work: a field the
13340
+ * kind marks `.meta({ secret: true })` is a keychain marker bound to the row, so
13341
+ * deleting the row deletes the secret, and one row's value is never another's.
13342
+ *
13343
+ * Patch rules for a secret (`updateInstance`): omitted, `''`, the redaction
13344
+ * token or the view the form was given KEEP the stored secret;
13345
+ * `{ $secretClear: true }` clears it; any other string replaces it.
13346
+ */
13332
13347
  /** The most instances one addon lists (an integrations page, not a table). */
13333
13348
  var INSTANCES_LIST_MAX = 1e3;
13334
13349
  /** The keys of the kind's secret fields that sit at the top level of the body. */
13335
13350
  function topLevelSecretKeys(paths) {
13336
- return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[")));
13351
+ return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[") && !p.includes("{")));
13352
+ }
13353
+ /** The keys of the kind's top-level MAPS whose values are secrets (`applianceSecrets{}`). */
13354
+ function secretMapKeys(paths) {
13355
+ return new Set(paths.filter((p) => p.endsWith("{}") && !p.slice(0, -2).match(/[.[{]/)).map((p) => p.slice(0, -2)));
13356
+ }
13357
+ /**
13358
+ * A patch value for a map of secrets: an entry that is omitted, `''`, the redaction token or the
13359
+ * view the form was given KEEPS its stored secret (it is left out of the merge); `{ $secretClear }`
13360
+ * or `null` removes the entry; any other string replaces it. Null when nothing is left to merge.
13361
+ */
13362
+ function secretMapPatch(value) {
13363
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
13364
+ const out = {};
13365
+ for (const [key, entry] of Object.entries(value)) if (entry === null || isSecretClear(entry)) out[key] = null;
13366
+ else if (typeof entry === "string" && entry !== "" && entry !== "__camstack_redacted__" && !isSecretFieldView(entry)) out[key] = entry;
13367
+ return Object.keys(out).length > 0 ? out : null;
13368
+ }
13369
+ /** Does the kind's body schema carry an `enabled` field? */
13370
+ function declaresEnabled(kind) {
13371
+ const json = toJSONSchema(kind.schema, {
13372
+ unrepresentable: "any",
13373
+ io: "input"
13374
+ });
13375
+ const properties = Reflect.get(json, "properties");
13376
+ return typeof properties === "object" && properties !== null && "enabled" in properties;
13337
13377
  }
13338
13378
  var RecordIntegrationInstances = class {
13339
13379
  records;
@@ -13341,14 +13381,20 @@ var RecordIntegrationInstances = class {
13341
13381
  hooks;
13342
13382
  handle;
13343
13383
  secretKeys;
13384
+ secretMaps;
13344
13385
  nameField;
13386
+ /** The `integrations` registry owns the on/off switch (`role: devices`); the body has none. */
13387
+ registryOwnsEnabled;
13345
13388
  constructor(records, kind, hooks) {
13346
13389
  this.records = records;
13347
13390
  this.kind = kind;
13348
13391
  this.hooks = hooks;
13349
13392
  this.handle = records.kind(kind);
13350
13393
  this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
13394
+ this.secretMaps = secretMapKeys(kind.declaration.secretPaths);
13351
13395
  this.nameField = kind.declaration.entry?.nameField ?? "name";
13396
+ this.registryOwnsEnabled = hooks.description.role === "devices";
13397
+ if (this.registryOwnsEnabled && declaresEnabled(kind)) throw new Error(`kind "${kind.name}": the integrations registry owns the on/off switch of a role "devices" addon, so the body must not declare "enabled"`);
13352
13398
  }
13353
13399
  /**
13354
13400
  * One instance read for the addon's own runtime; null when absent. When the keychain is
@@ -13393,15 +13439,23 @@ var RecordIntegrationInstances = class {
13393
13439
  viewOf(record) {
13394
13440
  const shown = secretViewsOf(this.records.owner, this.kind.name, record.id, this.kind.declaration, record.data);
13395
13441
  const rawName = record.data[this.nameField];
13442
+ const kind = this.hooks.kindOf?.(record.data);
13443
+ const health = this.hooks.health?.(record.data, record.id);
13396
13444
  return {
13397
13445
  id: record.id,
13398
13446
  name: typeof rawName === "string" ? rawName : record.id,
13399
13447
  configured: this.hooks.configured(record.data),
13400
- enabled: record.data.enabled !== false,
13401
- formValues: shown
13448
+ ...this.registryOwnsEnabled ? {} : { enabled: record.data.enabled !== false },
13449
+ formValues: shown,
13450
+ ...kind !== void 0 ? { kind } : {},
13451
+ ...health?.status !== void 0 ? { status: health.status } : {},
13452
+ ...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
13402
13453
  };
13403
13454
  }
13404
- describe = async () => this.hooks.description;
13455
+ describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
13456
+ ...this.hooks.description,
13457
+ mintsId: true
13458
+ };
13405
13459
  instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
13406
13460
  listInstances = async () => {
13407
13461
  return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
@@ -13411,37 +13465,46 @@ var RecordIntegrationInstances = class {
13411
13465
  return record === null ? null : this.viewOf(record);
13412
13466
  };
13413
13467
  /** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
13414
- createInstance = async ({ id, name }) => {
13468
+ createInstance = async ({ id, name, kind }) => {
13469
+ const minted = id ?? this.hooks.mintId?.({
13470
+ name,
13471
+ ...kind !== void 0 ? { kind } : {}
13472
+ });
13473
+ if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
13415
13474
  await this.handle.put({
13416
- id,
13417
- data: this.hooks.initialBody(name)
13475
+ id: minted,
13476
+ data: this.hooks.initialBody(name, kind)
13418
13477
  }, { ifRev: 0 });
13419
- await this.hooks.onChanged?.(id);
13420
- return { id };
13478
+ await this.hooks.onChanged?.(minted, "created");
13479
+ return { id: minted };
13421
13480
  };
13422
13481
  updateInstance = async ({ id, name, values }) => {
13423
13482
  const patch = {};
13424
13483
  for (const [key, value] of Object.entries(values)) {
13425
13484
  if (value === void 0) continue;
13426
- if (!this.secretKeys.has(key)) patch[key] = value;
13485
+ if (this.secretMaps.has(key)) {
13486
+ const entries = secretMapPatch(value);
13487
+ if (entries !== null) patch[key] = entries;
13488
+ } else if (!this.secretKeys.has(key)) patch[key] = value;
13427
13489
  else if (isSecretClear(value)) patch[key] = null;
13428
13490
  else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
13429
13491
  }
13430
13492
  if (name !== void 0) patch[this.nameField] = name;
13431
13493
  await this.handle.patch(id, patch);
13432
- await this.hooks.onChanged?.(id);
13494
+ await this.hooks.onChanged?.(id, "updated");
13433
13495
  return { ok: true };
13434
13496
  };
13435
13497
  /** The switch lives in the record body; the addon is told so it applies it (D996). */
13436
13498
  setInstanceEnabled = async ({ id, enabled }) => {
13499
+ if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
13437
13500
  await this.handle.patch(id, { enabled });
13438
- await this.hooks.onChanged?.(id);
13501
+ await this.hooks.onChanged?.(id, "enabled");
13439
13502
  return { ok: true };
13440
13503
  };
13441
13504
  /** The row and its keychain refs go together (the records door). */
13442
13505
  deleteInstance = async ({ id }) => {
13443
13506
  await this.handle.delete(id);
13444
- await this.hooks.onChanged?.(id);
13507
+ await this.hooks.onChanged?.(id, "deleted");
13445
13508
  return { ok: true };
13446
13509
  };
13447
13510
  testInstance = async (input) => {
@@ -19630,24 +19693,52 @@ var APPLIANCE_CAP_VOCABULARY = {
19630
19693
  args: { on: false }
19631
19694
  },
19632
19695
  {
19633
- id: "petFountain.intervalOn",
19634
- method: "setPumpInterval",
19696
+ id: "petFountain.resetDailyConsumption",
19697
+ method: "resetDailyConsumption",
19698
+ label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
19699
+ phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
19700
+ icon: "rotate-ccw"
19701
+ }
19702
+ ],
19703
+ state: [{
19704
+ kind: "boolean",
19705
+ field: "empty",
19706
+ label: t("Tank empty", "Serbatoio vuoto"),
19707
+ whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
19708
+ whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
19709
+ icon: "droplet"
19710
+ }, {
19711
+ kind: "numeric",
19712
+ field: "waterLevel",
19713
+ quantity: t("the water level", "il livello dell’acqua"),
19714
+ label: t("Water level", "Livello dell’acqua"),
19715
+ unit: "%",
19716
+ icon: "droplets"
19717
+ }]
19718
+ },
19719
+ "pump-cycle": {
19720
+ scope: "device",
19721
+ noun: t("Pump cycle", "Ciclo della pompa"),
19722
+ actions: [
19723
+ {
19724
+ id: "pumpCycle.on",
19725
+ method: "setEnabled",
19635
19726
  label: t("Pump cycle on", "Ciclo pompa attivo"),
19636
19727
  phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
19637
19728
  icon: "timer",
19638
19729
  args: { on: true }
19639
19730
  },
19640
19731
  {
19641
- id: "petFountain.intervalOff",
19642
- method: "setPumpInterval",
19732
+ id: "pumpCycle.off",
19733
+ method: "setEnabled",
19643
19734
  label: t("Pump cycle off", "Ciclo pompa spento"),
19644
19735
  phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
19645
19736
  icon: "timer-off",
19646
19737
  args: { on: false }
19647
19738
  },
19648
19739
  {
19649
- id: "petFountain.pumpOnMinutes",
19650
- method: "setPumpOnMinutes",
19740
+ id: "pumpCycle.onMinutes",
19741
+ method: "setOnMinutes",
19651
19742
  label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
19652
19743
  phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
19653
19744
  icon: "timer",
@@ -19663,8 +19754,8 @@ var APPLIANCE_CAP_VOCABULARY = {
19663
19754
  }]
19664
19755
  },
19665
19756
  {
19666
- id: "petFountain.pumpOffMinutes",
19667
- method: "setPumpOffMinutes",
19757
+ id: "pumpCycle.offMinutes",
19758
+ method: "setOffMinutes",
19668
19759
  label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
19669
19760
  phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
19670
19761
  icon: "timer-off",
@@ -19678,35 +19769,63 @@ var APPLIANCE_CAP_VOCABULARY = {
19678
19769
  unit: "min"
19679
19770
  }
19680
19771
  }]
19681
- },
19682
- {
19683
- id: "petFountain.resetDailyConsumption",
19684
- method: "resetDailyConsumption",
19685
- label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
19686
- phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
19687
- icon: "rotate-ccw"
19688
19772
  }
19689
19773
  ],
19690
19774
  state: [{
19691
19775
  kind: "boolean",
19692
- field: "empty",
19693
- label: t("Tank empty", "Serbatoio vuoto"),
19694
- whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
19695
- whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
19696
- icon: "droplet"
19697
- }, {
19698
- kind: "numeric",
19699
- field: "waterLevel",
19700
- quantity: t("the water level", "il livello dell’acqua"),
19701
- label: t("Water level", "Livello dell’acqua"),
19702
- unit: "%",
19703
- icon: "droplets"
19776
+ field: "enabled",
19777
+ label: t("Pump cycle", "Ciclo pompa"),
19778
+ whenTrue: t("When {device} starts running its pump on a cycle", "Quando {device} inizia a far lavorare la pompa a ciclo"),
19779
+ whenFalse: t("When {device} runs its pump continuously", "Quando {device} fa lavorare la pompa in continuo"),
19780
+ icon: "timer"
19704
19781
  }]
19705
19782
  },
19706
19783
  "media-player": {
19707
19784
  scope: "device",
19708
19785
  noun: t("Media player", "Lettore multimediale"),
19709
19786
  actions: [
19787
+ {
19788
+ id: "media.turnOn",
19789
+ method: "turnOn",
19790
+ label: t("Turn on", "Accendi"),
19791
+ phrase: t("Turn on {device}", "Accendi {device}"),
19792
+ icon: "power"
19793
+ },
19794
+ {
19795
+ id: "media.turnOff",
19796
+ method: "turnOff",
19797
+ label: t("Turn off", "Spegni"),
19798
+ phrase: t("Turn off {device}", "Spegni {device}"),
19799
+ icon: "power"
19800
+ },
19801
+ {
19802
+ id: "media.volumeUp",
19803
+ method: "volumeUp",
19804
+ label: t("Volume up", "Alza il volume"),
19805
+ phrase: t("Raise the volume of {device}", "Alza il volume di {device}"),
19806
+ icon: "volume-2"
19807
+ },
19808
+ {
19809
+ id: "media.volumeDown",
19810
+ method: "volumeDown",
19811
+ label: t("Volume down", "Abbassa il volume"),
19812
+ phrase: t("Lower the volume of {device}", "Abbassa il volume di {device}"),
19813
+ icon: "volume-2"
19814
+ },
19815
+ {
19816
+ id: "media.channelUp",
19817
+ method: "channelUp",
19818
+ label: t("Channel up", "Canale successivo"),
19819
+ phrase: t("Go to the next channel on {device}", "Passa al canale successivo su {device}"),
19820
+ icon: "skip-forward"
19821
+ },
19822
+ {
19823
+ id: "media.channelDown",
19824
+ method: "channelDown",
19825
+ label: t("Channel down", "Canale precedente"),
19826
+ phrase: t("Go to the previous channel on {device}", "Torna al canale precedente su {device}"),
19827
+ icon: "skip-back"
19828
+ },
19710
19829
  {
19711
19830
  id: "media.play",
19712
19831
  method: "play",
@@ -21593,6 +21712,7 @@ var SYSTEM_CAP_VOCABULARY = {
21593
21712
  "audio-analyzer": system(PIPELINE),
21594
21713
  "audio-codec": system(INTERNAL),
21595
21714
  "auth-provider": system(IDENTITY),
21715
+ "board-key-source": system(INTERNAL),
21596
21716
  backup: system("system backups — run and restored from the Backups page"),
21597
21717
  broker: system("message-broker connections — configured, not actuated"),
21598
21718
  "calendar-source": system(INTERNAL),
@@ -46036,6 +46156,49 @@ object({
46036
46156
  lastChangedAt: number()
46037
46157
  });
46038
46158
  DeviceType.Sensor;
46159
+ /**
46160
+ * Why a board key could not be read. Typed so a caller reports the cause and
46161
+ * never treats "could not read" as "has no key" (D49).
46162
+ */
46163
+ var BoardKeyFailureReasonSchema = _enum([
46164
+ "unknown-source",
46165
+ "not-admin",
46166
+ "no-builder",
46167
+ "no-ingress-url",
46168
+ "unreachable",
46169
+ "unknown-board",
46170
+ "no-encryption-key",
46171
+ "unreadable-config"
46172
+ ]);
46173
+ var BoardKeySourceSchema = object({
46174
+ sourceId: string(),
46175
+ name: string()
46176
+ });
46177
+ var BoardKeyBoardSchema = object({
46178
+ /** The builder's file name, e.g. `kitchen.yaml`: the handle `getBoardKey` takes. */
46179
+ configuration: string(),
46180
+ /** The node name the builder gives the board. */
46181
+ name: string(),
46182
+ /** The address the builder knows the board by, when it has one. */
46183
+ address: string().nullable()
46184
+ });
46185
+ var BoardKeyFailureSchema = object({
46186
+ ok: literal(false),
46187
+ reason: BoardKeyFailureReasonSchema,
46188
+ detail: string().optional()
46189
+ });
46190
+ var ListBoardsResultSchema = discriminatedUnion("ok", [object({
46191
+ ok: literal(true),
46192
+ boards: array(BoardKeyBoardSchema).readonly()
46193
+ }), BoardKeyFailureSchema]);
46194
+ var GetBoardKeyResultSchema = discriminatedUnion("ok", [object({
46195
+ ok: literal(true),
46196
+ key: string()
46197
+ }), BoardKeyFailureSchema]);
46198
+ method(_void(), array(BoardKeySourceSchema).readonly(), { auth: "admin" }), method(object({ sourceId: string().min(1) }), ListBoardsResultSchema, { auth: "admin" }), method(object({
46199
+ sourceId: string().min(1),
46200
+ configuration: string().min(1)
46201
+ }), GetBoardKeyResultSchema, { auth: "admin" });
46039
46202
  object({
46040
46203
  /** Current level as 0..100 inclusive. Firmware-reported. */
46041
46204
  level: number().min(0).max(100).meta(readingMeta("percentage")),
@@ -48162,6 +48325,188 @@ DeviceType.Control, method(object({
48162
48325
  kind: "mutation",
48163
48326
  auth: "protected"
48164
48327
  });
48328
+ /**
48329
+ * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
48330
+ * Apple TV, Spotify, Roku, generic OTT receivers, …) on
48331
+ * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
48332
+ * surface — those use `DeviceType.Camera` + the broker pipeline.
48333
+ *
48334
+ * The cap declares a wide method surface; not every backend supports
48335
+ * every method. Drivers signal sub-capability via the
48336
+ * `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
48337
+ * corresponding method) so the UI gates buttons without a slice
48338
+ * fetch — same pattern as `PanTiltZoom` for cameras.
48339
+ */
48340
+ var MediaPlayerStateSchema = _enum([
48341
+ "off",
48342
+ "on",
48343
+ "idle",
48344
+ "playing",
48345
+ "paused",
48346
+ "buffering",
48347
+ "standby"
48348
+ ]);
48349
+ var MediaPlayerRepeatSchema = _enum([
48350
+ "off",
48351
+ "all",
48352
+ "one"
48353
+ ]);
48354
+ var MediaInfoSchema = object({
48355
+ /** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
48356
+ * `podcast`, …). */
48357
+ type: string(),
48358
+ /** Human-readable title. */
48359
+ title: string(),
48360
+ /** Optional artist / channel / station label. */
48361
+ artist: string().optional(),
48362
+ /** Optional album / season / show name. */
48363
+ album: string().optional(),
48364
+ /** Optional cover-art / thumbnail URL. */
48365
+ imageUrl: string().optional()
48366
+ });
48367
+ /**
48368
+ * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
48369
+ * the device said so. `assumed`: the driver has no feedback (infrared) and
48370
+ * tracks what it last SENT; it moves only after a send was accepted, and a
48371
+ * human at the real remote makes it wrong. A UI shows an assumed value as a
48372
+ * guess, never as a reading.
48373
+ */
48374
+ var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
48375
+ /** The fields of a media-player status a driver can only guess. */
48376
+ var MediaPlayerAssumedFieldSchema = _enum([
48377
+ "state",
48378
+ "volumeLevel",
48379
+ "isMuted",
48380
+ "source"
48381
+ ]);
48382
+ var MediaPlayerStatusSchema = object({
48383
+ /** Playback lifecycle state. */
48384
+ state: MediaPlayerStateSchema,
48385
+ /**
48386
+ * Whether the state fields are readings or the driver's own bookkeeping.
48387
+ * A slice written before this field existed parses as `reported`.
48388
+ */
48389
+ stateSource: MediaPlayerStateSourceSchema.default("reported"),
48390
+ /**
48391
+ * Which fields are the driver's own guess. `stateSource` is `assumed` when
48392
+ * this is not empty; a field not listed here is a reading even then (power
48393
+ * from a running-state source, mute guessed from the last send).
48394
+ */
48395
+ assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
48396
+ /** Volume as 0..100 inclusive. Null when the device has no volume
48397
+ * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
48398
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
48399
+ /** Mute toggle distinct from volume=0. Null when no mute surface.
48400
+ * Pair with `DeviceFeature.MediaPlayerMute`. */
48401
+ isMuted: boolean().nullable(),
48402
+ /** Active source / input. Empty when no source surface. */
48403
+ source: string(),
48404
+ /** Selectable sources. Empty when no source surface.
48405
+ * Pair with `DeviceFeature.MediaPlayerSelectSource`. */
48406
+ availableSources: array(string()),
48407
+ /** Currently-playing media info. Null when nothing is playing. */
48408
+ currentMedia: MediaInfoSchema.nullable(),
48409
+ /** Current playback position in ms. Null when not seekable or
48410
+ * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
48411
+ position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
48412
+ /** Total duration of the current media in ms. Null when unknown
48413
+ * (live stream) or nothing is playing. */
48414
+ duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
48415
+ /** Shuffle toggle. Null when no shuffle surface.
48416
+ * Pair with `DeviceFeature.MediaPlayerShuffle`. */
48417
+ shuffle: boolean().nullable(),
48418
+ /** Repeat mode. Null when no repeat surface.
48419
+ * Pair with `DeviceFeature.MediaPlayerRepeat`. */
48420
+ repeat: MediaPlayerRepeatSchema.nullable(),
48421
+ /** Ms epoch when the slice was last updated. */
48422
+ lastChangedAt: number()
48423
+ });
48424
+ var stepInput = object({
48425
+ deviceId: number().int().nonnegative(),
48426
+ /** How many presses: the caller's count (Alexa `volumeSteps`, `channelCount`). */
48427
+ steps: number().int().min(1).max(50).default(1)
48428
+ });
48429
+ var deviceOnly = object({ deviceId: number().int().nonnegative() });
48430
+ DeviceType.MediaPlayer, method(deviceOnly, _void(), {
48431
+ kind: "mutation",
48432
+ auth: "protected"
48433
+ }), method(deviceOnly, _void(), {
48434
+ kind: "mutation",
48435
+ auth: "protected"
48436
+ }), method(stepInput, _void(), {
48437
+ kind: "mutation",
48438
+ auth: "protected"
48439
+ }), method(stepInput, _void(), {
48440
+ kind: "mutation",
48441
+ auth: "protected"
48442
+ }), method(stepInput, _void(), {
48443
+ kind: "mutation",
48444
+ auth: "protected"
48445
+ }), method(stepInput, _void(), {
48446
+ kind: "mutation",
48447
+ auth: "protected"
48448
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48449
+ kind: "mutation",
48450
+ auth: "protected"
48451
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48452
+ kind: "mutation",
48453
+ auth: "protected"
48454
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48455
+ kind: "mutation",
48456
+ auth: "protected"
48457
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48458
+ kind: "mutation",
48459
+ auth: "protected"
48460
+ }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
48461
+ kind: "mutation",
48462
+ auth: "protected"
48463
+ }), method(object({
48464
+ deviceId: number().int().nonnegative(),
48465
+ position: number().int().nonnegative()
48466
+ }), _void(), {
48467
+ kind: "mutation",
48468
+ auth: "protected"
48469
+ }), method(object({
48470
+ deviceId: number().int().nonnegative(),
48471
+ volumeLevel: number().min(0).max(100)
48472
+ }), _void(), {
48473
+ kind: "mutation",
48474
+ auth: "protected"
48475
+ }), method(object({
48476
+ deviceId: number().int().nonnegative(),
48477
+ muted: boolean()
48478
+ }), _void(), {
48479
+ kind: "mutation",
48480
+ auth: "protected"
48481
+ }), method(object({
48482
+ deviceId: number().int().nonnegative(),
48483
+ shuffle: boolean()
48484
+ }), _void(), {
48485
+ kind: "mutation",
48486
+ auth: "protected"
48487
+ }), method(object({
48488
+ deviceId: number().int().nonnegative(),
48489
+ repeat: MediaPlayerRepeatSchema
48490
+ }), _void(), {
48491
+ kind: "mutation",
48492
+ auth: "protected"
48493
+ }), method(object({
48494
+ deviceId: number().int().nonnegative(),
48495
+ source: string().min(1)
48496
+ }), _void(), {
48497
+ kind: "mutation",
48498
+ auth: "protected"
48499
+ }), method(object({
48500
+ deviceId: number().int().nonnegative(),
48501
+ /** Media identifier / URL. */
48502
+ mediaId: string().min(1),
48503
+ /** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
48504
+ * passes it through to the upstream service. */
48505
+ mediaType: string().min(1)
48506
+ }), _void(), {
48507
+ kind: "mutation",
48508
+ auth: "protected"
48509
+ });
48165
48510
  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.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;
48166
48511
  var NUMBER = {
48167
48512
  kind: "fixed",
@@ -48609,49 +48954,6 @@ function actuator(input) {
48609
48954
  };
48610
48955
  }
48611
48956
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
48612
- var PowerSourceRefusalReasonSchema = _enum([
48613
- "no-power-source",
48614
- "switch-missing",
48615
- "switch-offline",
48616
- "switch-unknown",
48617
- "restart-in-progress",
48618
- "switch-call-failed"
48619
- ]);
48620
- var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
48621
- accepted: literal(false),
48622
- reason: PowerSourceRefusalReasonSchema,
48623
- /** One line for the operator, naming the switch. */
48624
- message: string()
48625
- })]);
48626
- var PowerSourceInfoSchema = object({
48627
- /** False when the device has no power source linked; the rest is then empty. */
48628
- linked: boolean(),
48629
- switchDeviceId: number().nullable(),
48630
- switchName: string().nullable(),
48631
- /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
48632
- switchOn: boolean().nullable(),
48633
- switchOnline: boolean().nullable(),
48634
- restartInProgress: boolean(),
48635
- offSeconds: number().int()
48636
- });
48637
- object({
48638
- /** Null while the switch has not reported: unknown is never `false` (D393). */
48639
- switchOn: boolean().nullable(),
48640
- offSeconds: number().int(),
48641
- lastFetchedAt: number()
48642
- });
48643
- 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;
48644
- var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
48645
- systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
48646
- kind: "mutation",
48647
- auth: "admin"
48648
- }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
48649
- kind: "mutation",
48650
- auth: "admin"
48651
- }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
48652
- kind: "mutation",
48653
- auth: "admin"
48654
- });
48655
48957
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
48656
48958
  DeviceType.Camera;
48657
48959
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
@@ -48679,6 +48981,165 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
48679
48981
  }).optional()
48680
48982
  })) });
48681
48983
  /**
48984
+ * A saved Customize assignment (D936). The schema is the type: callers use
48985
+ * `z.infer`, they do not restate the shape.
48986
+ */
48987
+ var OverrideSourceSchema = object({
48988
+ addonId: string().min(1),
48989
+ stableId: string().min(1),
48990
+ cap: string().min(1),
48991
+ fieldPath: string().min(1)
48992
+ });
48993
+ var IdentityTransformDraftSchema = object({ kind: literal("identity") });
48994
+ var InvertTransformDraftSchema = object({ kind: literal("invert") });
48995
+ var ThresholdTransformDraftSchema = object({
48996
+ kind: literal("threshold"),
48997
+ comparison: _enum(["gte", "lte"]),
48998
+ threshold: number(),
48999
+ startDwellMs: number().nonnegative(),
49000
+ stopDwellMs: number().nonnegative()
49001
+ });
49002
+ var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
49003
+ IdentityTransformDraftSchema,
49004
+ InvertTransformDraftSchema,
49005
+ ThresholdTransformDraftSchema,
49006
+ object({
49007
+ kind: literal("linear"),
49008
+ scale: number(),
49009
+ offset: number(),
49010
+ clampMin: number().optional(),
49011
+ clampMax: number().optional()
49012
+ }),
49013
+ object({
49014
+ kind: literal("enum-map"),
49015
+ mapping: record(string(), string().nullable())
49016
+ }),
49017
+ object({ kind: literal("number") })
49018
+ ]);
49019
+ var ScalarOverrideDraftSchema = object({
49020
+ kind: literal("scalar"),
49021
+ cap: string().min(1),
49022
+ targetId: string().min(1),
49023
+ source: OverrideSourceSchema,
49024
+ transform: CustomizeTransformDraftSchema
49025
+ });
49026
+ var KeyedArrayOverrideDraftSchema = object({
49027
+ kind: literal("keyedArray"),
49028
+ cap: string().min(1),
49029
+ targetId: string().min(1),
49030
+ key: string().min(1),
49031
+ label: string().min(1),
49032
+ fieldPath: string().min(1),
49033
+ source: OverrideSourceSchema,
49034
+ transform: CustomizeTransformDraftSchema
49035
+ });
49036
+ var PulseOverrideDraftSchema = object({
49037
+ kind: literal("pulse"),
49038
+ cap: string().min(1),
49039
+ targetId: string().min(1),
49040
+ enabled: boolean(),
49041
+ debounceMs: number().nonnegative(),
49042
+ edges: array(_enum([
49043
+ "rising",
49044
+ "button",
49045
+ "native-event"
49046
+ ])).min(1).optional(),
49047
+ sources: array(OverrideSourceSchema).min(1)
49048
+ });
49049
+ var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
49050
+ "in-addition",
49051
+ "confirm",
49052
+ "replace"
49053
+ ]).optional() });
49054
+ discriminatedUnion("kind", [
49055
+ ScalarOverrideDraftSchema,
49056
+ KeyedArrayOverrideDraftSchema,
49057
+ PulseOverrideDraftSchema,
49058
+ object({
49059
+ kind: literal("span"),
49060
+ cap: string().min(1),
49061
+ targetId: string().min(1),
49062
+ timeoutMs: number().nonnegative(),
49063
+ cooldownMs: number().nonnegative().optional(),
49064
+ sources: array(SpanSourceDraftSchema)
49065
+ })
49066
+ ]);
49067
+ object({
49068
+ targetId: string().min(1),
49069
+ configured: boolean(),
49070
+ source: _enum(["native", "assigned"]),
49071
+ state: _enum([
49072
+ "active",
49073
+ "stale",
49074
+ "released",
49075
+ "unavailable"
49076
+ ]),
49077
+ reason: string().nullable()
49078
+ });
49079
+ /**
49080
+ * What a running-threshold proposal says (planning#405). The schema is the
49081
+ * type: the algorithm, the capability method and the UI all read `z.infer`.
49082
+ *
49083
+ * A proposal is read-only advice: it carries the numbers it was derived from
49084
+ * (`evidence`) so the operator can judge it, and an `insufficient` answer names
49085
+ * WHY with a typed reason and the catalog defaults, never an invented number.
49086
+ */
49087
+ var RunningProposalTierSchema = _enum(["raw", "5m"]);
49088
+ var RunningProposalConfidenceSchema = _enum([
49089
+ "high",
49090
+ "medium",
49091
+ "low"
49092
+ ]);
49093
+ var RunningProposalInsufficientReasonSchema = _enum([
49094
+ "no-history",
49095
+ "span-too-short",
49096
+ "truncated",
49097
+ "no-idle-floor",
49098
+ "no-runs",
49099
+ "no-separation"
49100
+ ]);
49101
+ var RunningProposalReplaySchema = object({
49102
+ /** Intervals the real dwell expression held true over the window. */
49103
+ intervals: number().int().nonnegative(),
49104
+ /** Intervals outside every run, or a second interval inside one. */
49105
+ extra: number().int().nonnegative(),
49106
+ /** Runs without an interval of their own (none, or merged into a neighbour). */
49107
+ missed: number().int().nonnegative()
49108
+ });
49109
+ var RunningProposalEvidenceSchema = object({
49110
+ tier: RunningProposalTierSchema,
49111
+ spanMs: number().nonnegative(),
49112
+ from: number(),
49113
+ to: number(),
49114
+ points: number().int().nonnegative(),
49115
+ runsFound: number().int().nonnegative(),
49116
+ pausesFound: number().int().nonnegative(),
49117
+ longestPauseMs: number().nonnegative(),
49118
+ idleFloorW: number(),
49119
+ noiseCeilingW: number(),
49120
+ plateauW: number(),
49121
+ replay: RunningProposalReplaySchema
49122
+ });
49123
+ var RunningProposalInsufficientEvidenceSchema = object({
49124
+ spanMs: number().nonnegative(),
49125
+ points: number().int().nonnegative(),
49126
+ runsFound: number().int().nonnegative()
49127
+ });
49128
+ var RunningThresholdProposalSchema = discriminatedUnion("state", [object({
49129
+ state: literal("proposal"),
49130
+ transform: ThresholdTransformDraftSchema,
49131
+ evidence: RunningProposalEvidenceSchema,
49132
+ confidence: RunningProposalConfidenceSchema,
49133
+ /** Why the confidence is not higher; empty when nothing held it back. */
49134
+ notes: array(string())
49135
+ }), object({
49136
+ state: literal("insufficient"),
49137
+ reason: RunningProposalInsufficientReasonSchema,
49138
+ evidence: RunningProposalInsufficientEvidenceSchema,
49139
+ /** The catalog defaults, offered as what stands when nothing better is known. */
49140
+ fallback: ThresholdTransformDraftSchema
49141
+ })]);
49142
+ /**
48682
49143
  * The report of a one-shot "move this owner's plaintext secrets into the
48683
49144
  * keychain" run (D878). COUNTS only - never a value, never a ref.
48684
49145
  *
@@ -48968,6 +49429,14 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
48968
49429
  kind: "mutation",
48969
49430
  auth: "admin",
48970
49431
  access: "view"
49432
+ }), method(object({
49433
+ deviceId: number().int(),
49434
+ cap: string().min(1),
49435
+ fieldPath: string().min(1),
49436
+ deviceType: _enum(DeviceType).optional()
49437
+ }), RunningThresholdProposalSchema, {
49438
+ auth: "admin",
49439
+ access: "view"
48971
49440
  }), method(object({
48972
49441
  blockId: string().min(1),
48973
49442
  trace: AutomationRunTraceSchema.optional(),
@@ -49776,7 +50245,27 @@ method(object({
49776
50245
  * exposes only the COMMON surface: link state, supported device
49777
50246
  * kinds (so the UI can filter the device picker), and per-device
49778
50247
  * expose/unexpose.
49779
- */
50248
+ *
50249
+ * INSTANCES. One export addon can serve several independent targets (a
50250
+ * HomeKit bridge, a Home Assistant broker). `listInstances` names them.
50251
+ * `instanceId` on a READ is optional: absent = the whole addon (linked if any
50252
+ * enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
50253
+ * default instance's); present = that instance; an id the provider does not
50254
+ * serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
50255
+ * never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
50256
+ * change always names the target it changes. A single-instance provider
50257
+ * accepts an absent id on a read or its own id, and refuses any other.
50258
+ */
50259
+ /** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
50260
+ var InstanceIdSchema$1 = string().min(1);
50261
+ var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
50262
+ /** One independent export target of an addon. */
50263
+ var ExportInstanceSchema = object({
50264
+ instanceId: InstanceIdSchema$1,
50265
+ name: string(),
50266
+ enabled: boolean(),
50267
+ kind: string().optional()
50268
+ });
49780
50269
  var LinkStateSchema = _enum([
49781
50270
  "unlinked",
49782
50271
  "linked",
@@ -49827,10 +50316,14 @@ var ExposedDeviceSchema = object({
49827
50316
  capabilities: array(string()).optional()
49828
50317
  });
49829
50318
  var ExposeInputSchema = object({
50319
+ instanceId: InstanceIdSchema$1,
49830
50320
  deviceId: string(),
49831
50321
  capabilities: array(string()).optional()
49832
50322
  });
49833
- var UnexposeInputSchema = object({ deviceId: string() });
50323
+ var UnexposeInputSchema = object({
50324
+ instanceId: InstanceIdSchema$1,
50325
+ deviceId: string()
50326
+ });
49834
50327
  /**
49835
50328
  * What an exporter may expose besides devices (D968). An action and a
49836
50329
  * notification target are LIST ENTRIES, never devices (D954): they enter an
@@ -49859,15 +50352,21 @@ var ExposedEntitySchema = object({
49859
50352
  entity: ExportEntityRefSchema,
49860
50353
  exposedAs: string().optional()
49861
50354
  });
49862
- var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
49863
- var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
49864
- method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
50355
+ var ExposeEntityInputSchema = object({
50356
+ instanceId: InstanceIdSchema$1,
50357
+ entity: ExportEntityRefSchema
50358
+ });
50359
+ var UnexposeEntityInputSchema = object({
50360
+ instanceId: InstanceIdSchema$1,
50361
+ entity: ExportEntityRefSchema
50362
+ });
50363
+ method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
49865
50364
  kind: "mutation",
49866
50365
  auth: "admin"
49867
50366
  }), method(UnexposeInputSchema, _void(), {
49868
50367
  kind: "mutation",
49869
50368
  auth: "admin"
49870
- }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
50369
+ }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
49871
50370
  kind: "mutation",
49872
50371
  auth: "admin"
49873
50372
  }), optionalMethod(UnexposeEntityInputSchema, _void(), {
@@ -51747,7 +52246,7 @@ object({
51747
52246
  /** Ms epoch of the last value received from the appliance; 0 = never. */
51748
52247
  lastFetchedAt: number()
51749
52248
  });
51750
- var DeviceIdSchema$1 = number().int().nonnegative();
52249
+ var DeviceIdSchema$2 = number().int().nonnegative();
51751
52250
  /** A value a program option or setting can take. */
51752
52251
  var DishwasherValueSchema = union([
51753
52252
  boolean(),
@@ -51811,77 +52310,77 @@ var DishwasherOptionsSchema = object({
51811
52310
  options: array(DishwasherOptionDescriptorSchema)
51812
52311
  });
51813
52312
  DeviceType.Dishwasher, method(object({
51814
- deviceId: DeviceIdSchema$1,
52313
+ deviceId: DeviceIdSchema$2,
51815
52314
  program: string().min(1).optional(),
51816
52315
  options: record(string(), DishwasherValueSchema).optional()
51817
52316
  }), _void(), {
51818
52317
  kind: "mutation",
51819
52318
  auth: "protected"
51820
- }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
52319
+ }), method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
51821
52320
  kind: "mutation",
51822
52321
  auth: "protected"
51823
- }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
52322
+ }), method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
51824
52323
  kind: "mutation",
51825
52324
  auth: "protected"
51826
- }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
52325
+ }), method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
51827
52326
  kind: "mutation",
51828
52327
  auth: "protected"
51829
52328
  }), method(object({
51830
- deviceId: DeviceIdSchema$1,
52329
+ deviceId: DeviceIdSchema$2,
51831
52330
  program: string().min(1)
51832
52331
  }), _void(), {
51833
52332
  kind: "mutation",
51834
52333
  auth: "protected"
51835
52334
  }), method(object({
51836
- deviceId: DeviceIdSchema$1,
52335
+ deviceId: DeviceIdSchema$2,
51837
52336
  on: boolean()
51838
52337
  }), _void(), {
51839
52338
  kind: "mutation",
51840
52339
  auth: "protected"
51841
52340
  }), method(object({
51842
- deviceId: DeviceIdSchema$1,
52341
+ deviceId: DeviceIdSchema$2,
51843
52342
  on: boolean()
51844
52343
  }), _void(), {
51845
52344
  kind: "mutation",
51846
52345
  auth: "protected"
51847
52346
  }), method(object({
51848
- deviceId: DeviceIdSchema$1,
52347
+ deviceId: DeviceIdSchema$2,
51849
52348
  on: boolean()
51850
52349
  }), _void(), {
51851
52350
  kind: "mutation",
51852
52351
  auth: "protected"
51853
52352
  }), method(object({
51854
- deviceId: DeviceIdSchema$1,
52353
+ deviceId: DeviceIdSchema$2,
51855
52354
  on: boolean()
51856
52355
  }), _void(), {
51857
52356
  kind: "mutation",
51858
52357
  auth: "protected"
51859
52358
  }), method(object({
51860
- deviceId: DeviceIdSchema$1,
52359
+ deviceId: DeviceIdSchema$2,
51861
52360
  on: boolean()
51862
52361
  }), _void(), {
51863
52362
  kind: "mutation",
51864
52363
  auth: "protected"
51865
52364
  }), method(object({
51866
- deviceId: DeviceIdSchema$1,
52365
+ deviceId: DeviceIdSchema$2,
51867
52366
  on: boolean()
51868
52367
  }), _void(), {
51869
52368
  kind: "mutation",
51870
52369
  auth: "protected"
51871
52370
  }), method(object({
51872
- deviceId: DeviceIdSchema$1,
52371
+ deviceId: DeviceIdSchema$2,
51873
52372
  on: boolean()
51874
52373
  }), _void(), {
51875
52374
  kind: "mutation",
51876
52375
  auth: "protected"
51877
52376
  }), method(object({
51878
- deviceId: DeviceIdSchema$1,
52377
+ deviceId: DeviceIdSchema$2,
51879
52378
  key: string().min(1),
51880
52379
  value: DishwasherValueSchema
51881
52380
  }), _void(), {
51882
52381
  kind: "mutation",
51883
52382
  auth: "protected"
51884
- }), method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" });
52383
+ }), method(object({ deviceId: DeviceIdSchema$2 }), DishwasherOptionsSchema, { auth: "protected" });
51885
52384
  /**
51886
52385
  * Palette entry ids (D939). Audio is the one gradient; `audio-*` leaves are
51887
52386
  * not entries. `heatmapScrub` is a mark, not an event kind.
@@ -53068,15 +53567,15 @@ var FanPresetCaptureResultSchema = object({
53068
53567
  items: array(FanCaptureItemSchema),
53069
53568
  omitted: array(FanCaptureOmissionSchema)
53070
53569
  });
53071
- var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
53570
+ var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
53072
53571
  DeviceType.Fan;
53073
- method(deviceInput$1, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$1.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
53572
+ method(deviceInput$2, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$2.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
53074
53573
  kind: "mutation",
53075
53574
  auth: "admin"
53076
- }), method(deviceInput$1.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
53575
+ }), method(deviceInput$2.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
53077
53576
  kind: "mutation",
53078
53577
  auth: "protected"
53079
- }), method(deviceInput$1.extend({
53578
+ }), method(deviceInput$2.extend({
53080
53579
  fields: array(FanCaptureFieldSchema).optional(),
53081
53580
  steps: array(ActionStepSchema).max(20).default([])
53082
53581
  }), FanPresetCaptureResultSchema, { auth: "protected" });
@@ -53600,13 +54099,65 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
53600
54099
  */
53601
54100
  var ConfigSchemaPassthrough$3 = custom();
53602
54101
  var IntegrationInstanceIdSchema = string().min(1);
54102
+ var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
54103
+ /**
54104
+ * One kind of instance a provider can create (an adapter, an account type). The sign-in and
54105
+ * connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
54106
+ * hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
54107
+ * interactive account link (popup) and may carry `{{brokerId}}`, which the UI replaces with the
54108
+ * instance id.
54109
+ */
54110
+ var IntegrationInstanceKindSchema = object({
54111
+ /** Stable id of the kind, sent back as `createInstance.kind`. */
54112
+ id: string().min(1),
54113
+ label: string().min(1),
54114
+ /** The kind signs in through a server-held interactive session. */
54115
+ signIn: object({
54116
+ basePath: string().min(1),
54117
+ methods: array(_enum(["device-code", "proxy"])).optional()
54118
+ }).optional(),
54119
+ /** Relative hub path to start an interactive account link of an instance of this kind. */
54120
+ connectPath: string().min(1).optional(),
54121
+ /** Relative hub path to disconnect that account. */
54122
+ disconnectPath: string().min(1).optional()
54123
+ });
53603
54124
  var IntegrationInstanceDescriptionSchema = object({
54125
+ /** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
54126
+ role: _enum([
54127
+ "devices",
54128
+ "services",
54129
+ "both"
54130
+ ]),
54131
+ /** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
54132
+ discovery: object({
54133
+ mode: _enum([
54134
+ "none",
54135
+ "lan",
54136
+ "account"
54137
+ ]),
54138
+ scopedToInstance: boolean()
54139
+ }).optional(),
53604
54140
  /** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
53605
- instanceMode: _enum(["single", "multiple"]),
54141
+ instanceMode: IntegrationInstanceModeSchema,
53606
54142
  /** What the instance is, for the UI (`destination`, `relay`, `account`). */
53607
54143
  kind: string(),
53608
54144
  /** True when `testInstance` really probes the remote. */
53609
- canTest: boolean()
54145
+ canTest: boolean(),
54146
+ /**
54147
+ * The kinds an instance can be of. Absent or one entry: nothing to choose; more than one: the
54148
+ * Add flow asks which first and sends it as `createInstance.kind`.
54149
+ */
54150
+ kinds: array(IntegrationInstanceKindSchema).optional(),
54151
+ /**
54152
+ * The provider accepts `createInstance.id` for an id that once existed (an adopted id, because
54153
+ * devices carry it). The UI then offers "restore under its old id" on Add. Absent means no.
54154
+ */
54155
+ allowRestoreId: boolean().optional(),
54156
+ /**
54157
+ * `createInstance` without an `id` works: the provider mints it. Absent means the caller must
54158
+ * send an id (the UI then mints an opaque one).
54159
+ */
54160
+ mintsId: boolean().optional()
53610
54161
  });
53611
54162
  var IntegrationInstanceStatusSchema = object({
53612
54163
  state: _enum([
@@ -53615,7 +54166,9 @@ var IntegrationInstanceStatusSchema = object({
53615
54166
  "error",
53616
54167
  "unknown"
53617
54168
  ]),
53618
- detail: string().optional()
54169
+ detail: string().optional(),
54170
+ /** A short human word for the state ("Signed in", "Token expired"); shown instead of `state`. */
54171
+ label: string().optional()
53619
54172
  });
53620
54173
  var IntegrationInstanceSchema = object({
53621
54174
  id: IntegrationInstanceIdSchema,
@@ -53623,13 +54176,18 @@ var IntegrationInstanceSchema = object({
53623
54176
  /** The addon's own rule (an URL is present, a client id is present...). */
53624
54177
  configured: boolean(),
53625
54178
  /**
53626
- * The instance's own on/off switch (D996): always present, derived from the record body
53627
- * (`enabled !== false`, so a row written before the field existed is on).
54179
+ * The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
54180
+ * so a row written before the field existed is on). Absent when the `integrations` registry
54181
+ * owns the switch (`describe().role` is `devices`): read it from the registry row.
53628
54182
  */
53629
- enabled: boolean(),
54183
+ enabled: boolean().optional(),
54184
+ /** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
54185
+ kind: string().optional(),
53630
54186
  /** Values for the instance form; a secret is a `SecretFieldView`, never text. */
53631
54187
  formValues: record(string(), unknown()),
53632
- status: IntegrationInstanceStatusSchema.optional()
54188
+ status: IntegrationInstanceStatusSchema.optional(),
54189
+ /** Epoch ms of the last probe of the remote that produced `status`; absent when never checked. */
54190
+ lastCheckedAt: number().optional()
53633
54191
  });
53634
54192
  var IntegrationInstancePatchSchema = object({
53635
54193
  id: IntegrationInstanceIdSchema,
@@ -53647,10 +54205,14 @@ var integrationInstancesCapability = {
53647
54205
  instanceFormSchema: method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }),
53648
54206
  listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
53649
54207
  getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
53650
- /** Insert the typed record under the given id (a duplicate id is refused). */
54208
+ /**
54209
+ * Insert the typed record. With `id` it is stored under that id (a duplicate id is refused);
54210
+ * without one the PROVIDER mints it and answers it. `kind` picks one of `describe().kinds`.
54211
+ */
53651
54212
  createInstance: method(object({
53652
- id: IntegrationInstanceIdSchema,
53653
- name: string().min(1)
54213
+ id: IntegrationInstanceIdSchema.optional(),
54214
+ name: string().min(1),
54215
+ kind: string().min(1).optional()
53654
54216
  }), object({ id: string() }), {
53655
54217
  kind: "mutation",
53656
54218
  auth: "admin"
@@ -55482,6 +56044,11 @@ var LogEntrySchema = object({
55482
56044
  * flight, and not asking for the next slice ends the scan.
55483
56045
  */
55484
56046
  var LogReadQuerySchema = object({
56047
+ /**
56048
+ * Which destination INSTANCE answers (a destination addon may hold several records). Absent ⇒
56049
+ * the provider's first readable one, so a caller written before instances existed is unchanged.
56050
+ */
56051
+ instanceId: string().min(1).max(200).optional(),
55485
56052
  /** Correlates `cancelRead`. Caller-chosen, unique per in-flight read. */
55486
56053
  requestId: string().min(1).max(64).optional(),
55487
56054
  /** The device the line is about (`tags.deviceId`, numeric). */
@@ -55544,6 +56111,8 @@ var LogReadResultSchema = object({
55544
56111
  /** True when `cancelRead` ended this slice early. */
55545
56112
  cancelled: boolean().optional()
55546
56113
  });
56114
+ /** Names one destination instance; absent ⇒ the provider's first readable one. */
56115
+ var LogInstanceSelectorSchema = object({ instanceId: string().min(1).max(200).optional() });
55547
56116
  var LogReadCapabilitiesSchema = object({
55548
56117
  /** False ⇒ this destination only ships lines (console, forwarder, an unconfigured Loki). */
55549
56118
  readable: boolean(),
@@ -55562,7 +56131,7 @@ var LogReadCapabilitiesSchema = object({
55562
56131
  /** How far back the store reaches, when the provider knows (days). */
55563
56132
  retentionDays: number().nullable()
55564
56133
  });
55565
- method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(_void(), LogReadCapabilitiesSchema, { auth: "admin" }), optionalMethod(LogReadQuerySchema, LogReadResultSchema, { auth: "admin" }), optionalMethod(object({ requestId: string().min(1).max(64) }), object({ cancelled: boolean() }), {
56134
+ method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(LogInstanceSelectorSchema, LogReadCapabilitiesSchema, { auth: "admin" }), optionalMethod(LogReadQuerySchema, LogReadResultSchema, { auth: "admin" }), optionalMethod(object({ requestId: string().min(1).max(64) }), object({ cancelled: boolean() }), {
55566
56135
  kind: "mutation",
55567
56136
  auth: "admin"
55568
56137
  });
@@ -55666,164 +56235,6 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
55666
56235
  ]);
55667
56236
  method(_void(), array(LoginMethodContributionSchema).readonly());
55668
56237
  /**
55669
- * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
55670
- * Apple TV, Spotify, Roku, generic OTT receivers, …) on
55671
- * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
55672
- * surface — those use `DeviceType.Camera` + the broker pipeline.
55673
- *
55674
- * The cap declares a wide method surface; not every backend supports
55675
- * every method. Drivers signal sub-capability via the
55676
- * `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
55677
- * corresponding method) so the UI gates buttons without a slice
55678
- * fetch — same pattern as `PanTiltZoom` for cameras.
55679
- */
55680
- var MediaPlayerStateSchema = _enum([
55681
- "off",
55682
- "on",
55683
- "idle",
55684
- "playing",
55685
- "paused",
55686
- "buffering",
55687
- "standby"
55688
- ]);
55689
- var MediaPlayerRepeatSchema = _enum([
55690
- "off",
55691
- "all",
55692
- "one"
55693
- ]);
55694
- var MediaInfoSchema = object({
55695
- /** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
55696
- * `podcast`, …). */
55697
- type: string(),
55698
- /** Human-readable title. */
55699
- title: string(),
55700
- /** Optional artist / channel / station label. */
55701
- artist: string().optional(),
55702
- /** Optional album / season / show name. */
55703
- album: string().optional(),
55704
- /** Optional cover-art / thumbnail URL. */
55705
- imageUrl: string().optional()
55706
- });
55707
- /**
55708
- * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
55709
- * the device said so. `assumed`: the driver has no feedback (infrared) and
55710
- * tracks what it last SENT; it moves only after a send was accepted, and a
55711
- * human at the real remote makes it wrong. A UI shows an assumed value as a
55712
- * guess, never as a reading.
55713
- */
55714
- var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
55715
- /** The fields of a media-player status a driver can only guess. */
55716
- var MediaPlayerAssumedFieldSchema = _enum([
55717
- "state",
55718
- "volumeLevel",
55719
- "isMuted",
55720
- "source"
55721
- ]);
55722
- object({
55723
- /** Playback lifecycle state. */
55724
- state: MediaPlayerStateSchema,
55725
- /**
55726
- * Whether the state fields are readings or the driver's own bookkeeping.
55727
- * A slice written before this field existed parses as `reported`.
55728
- */
55729
- stateSource: MediaPlayerStateSourceSchema.default("reported"),
55730
- /**
55731
- * Which fields are the driver's own guess. `stateSource` is `assumed` when
55732
- * this is not empty; a field not listed here is a reading even then (power
55733
- * from a running-state source, mute guessed from the last send).
55734
- */
55735
- assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
55736
- /** Volume as 0..100 inclusive. Null when the device has no volume
55737
- * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
55738
- volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
55739
- /** Mute toggle distinct from volume=0. Null when no mute surface.
55740
- * Pair with `DeviceFeature.MediaPlayerMute`. */
55741
- isMuted: boolean().nullable(),
55742
- /** Active source / input. Empty when no source surface. */
55743
- source: string(),
55744
- /** Selectable sources. Empty when no source surface.
55745
- * Pair with `DeviceFeature.MediaPlayerSelectSource`. */
55746
- availableSources: array(string()),
55747
- /** Currently-playing media info. Null when nothing is playing. */
55748
- currentMedia: MediaInfoSchema.nullable(),
55749
- /** Current playback position in ms. Null when not seekable or
55750
- * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
55751
- position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
55752
- /** Total duration of the current media in ms. Null when unknown
55753
- * (live stream) or nothing is playing. */
55754
- duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
55755
- /** Shuffle toggle. Null when no shuffle surface.
55756
- * Pair with `DeviceFeature.MediaPlayerShuffle`. */
55757
- shuffle: boolean().nullable(),
55758
- /** Repeat mode. Null when no repeat surface.
55759
- * Pair with `DeviceFeature.MediaPlayerRepeat`. */
55760
- repeat: MediaPlayerRepeatSchema.nullable(),
55761
- /** Ms epoch when the slice was last updated. */
55762
- lastChangedAt: number()
55763
- });
55764
- DeviceType.MediaPlayer, method(object({ deviceId: number().int().nonnegative() }), _void(), {
55765
- kind: "mutation",
55766
- auth: "protected"
55767
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55768
- kind: "mutation",
55769
- auth: "protected"
55770
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55771
- kind: "mutation",
55772
- auth: "protected"
55773
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55774
- kind: "mutation",
55775
- auth: "protected"
55776
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
55777
- kind: "mutation",
55778
- auth: "protected"
55779
- }), method(object({
55780
- deviceId: number().int().nonnegative(),
55781
- position: number().int().nonnegative()
55782
- }), _void(), {
55783
- kind: "mutation",
55784
- auth: "protected"
55785
- }), method(object({
55786
- deviceId: number().int().nonnegative(),
55787
- volumeLevel: number().min(0).max(100)
55788
- }), _void(), {
55789
- kind: "mutation",
55790
- auth: "protected"
55791
- }), method(object({
55792
- deviceId: number().int().nonnegative(),
55793
- muted: boolean()
55794
- }), _void(), {
55795
- kind: "mutation",
55796
- auth: "protected"
55797
- }), method(object({
55798
- deviceId: number().int().nonnegative(),
55799
- shuffle: boolean()
55800
- }), _void(), {
55801
- kind: "mutation",
55802
- auth: "protected"
55803
- }), method(object({
55804
- deviceId: number().int().nonnegative(),
55805
- repeat: MediaPlayerRepeatSchema
55806
- }), _void(), {
55807
- kind: "mutation",
55808
- auth: "protected"
55809
- }), method(object({
55810
- deviceId: number().int().nonnegative(),
55811
- source: string().min(1)
55812
- }), _void(), {
55813
- kind: "mutation",
55814
- auth: "protected"
55815
- }), method(object({
55816
- deviceId: number().int().nonnegative(),
55817
- /** Media identifier / URL. */
55818
- mediaId: string().min(1),
55819
- /** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
55820
- * passes it through to the upstream service. */
55821
- mediaType: string().min(1)
55822
- }), _void(), {
55823
- kind: "mutation",
55824
- auth: "protected"
55825
- });
55826
- /**
55827
56238
  * mesh-network — collection cap for mesh-VPN providers.
55828
56239
  *
55829
56240
  * Different shape from `network-access` (public tunnels):
@@ -64116,6 +64527,21 @@ var NcTemplateVarKindSchema = _enum([
64116
64527
  "sound",
64117
64528
  "system"
64118
64529
  ]);
64530
+ /**
64531
+ * The default title/body a rule of one SITUATION is delivered with when the
64532
+ * operator wrote no template — served so an editor can pre-fill it (and the
64533
+ * operator can rebuild it from placeholders) instead of starting from a blank
64534
+ * box that, once saved, silently replaces it. `id` names the situation inside
64535
+ * a rule `kind`: `appliance-cycle` is a `sensor` rule whose device is an
64536
+ * appliance finishing a cycle. Texts are raw templates (`{{var}}` intact), in
64537
+ * the hub's current language and with its overrides applied.
64538
+ */
64539
+ var NcTemplateDefaultSchema = object({
64540
+ id: _enum(["appliance-cycle"]),
64541
+ kind: NcRuleKindSchema,
64542
+ title: string(),
64543
+ body: string()
64544
+ });
64119
64545
  /** Which producer renders a template: an ordinary rule, a timelapse, a digest. */
64120
64546
  var NcTemplateFamilySchema = _enum([
64121
64547
  "rule",
@@ -64971,7 +65397,11 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
64971
65397
  }), method(object({}), object({
64972
65398
  catalog: array(NcConditionDescriptorSchema),
64973
65399
  taxonomy: NcTaxonomySchema.optional()
64974
- })), method(object({}), object({ vars: array(NcTemplateVarDescriptorSchema) })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
65400
+ })), method(object({}), object({
65401
+ vars: array(NcTemplateVarDescriptorSchema),
65402
+ /** Per-situation default texts; absent on a hub that predates them. */
65403
+ defaults: array(NcTemplateDefaultSchema)
65404
+ })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
64975
65405
  kind: "mutation",
64976
65406
  access: "view"
64977
65407
  }), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), NcRuleLastRunsSchema, { auth: "admin" }), method(object({ artifactId: string().min(1) }), object({ url: string().nullable() }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
@@ -65719,52 +66149,27 @@ object({
65719
66149
  empty: boolean().nullable(),
65720
66150
  /** True while the pump is running. Null while unknown. */
65721
66151
  pumpRunning: boolean().nullable(),
65722
- /** True when the pump works on an on/off duty cycle rather than continuously. */
65723
- pumpIntervalEnabled: boolean().nullable(),
65724
- /** How long the pump runs per cycle, in seconds. */
65725
- pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
65726
- /** How long the pump rests per cycle, in seconds. */
65727
- pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
65728
66152
  /** Water drunk in the last drink, in litres. */
65729
66153
  lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
65730
66154
  /** Water drunk in the current hour, in litres. */
65731
66155
  currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
65732
66156
  /** Water drunk over the last 24 hours (rolling), in litres. */
65733
66157
  last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
65734
- /** Water drunk since local midnight, in litres. */
66158
+ /** Water drunk since local midnight, in litres. Null when the fountain has no such sensor. */
65735
66159
  todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
65736
66160
  /** Decoded fault message. Null means healthy; a non-null string is an active fault. */
65737
66161
  error: string().nullable(),
65738
66162
  /** Ms epoch when the slice was last refreshed. */
65739
66163
  lastFetchedAt: number()
65740
66164
  });
65741
- var DeviceIdSchema = number().int().nonnegative();
65742
- var MinutesSchema = number().int().min(0);
66165
+ var DeviceIdSchema$1 = number().int().nonnegative();
65743
66166
  DeviceType.PetFountain, method(object({
65744
- deviceId: DeviceIdSchema,
65745
- on: boolean()
65746
- }), _void(), {
65747
- kind: "mutation",
65748
- auth: "protected"
65749
- }), method(object({
65750
- deviceId: DeviceIdSchema,
66167
+ deviceId: DeviceIdSchema$1,
65751
66168
  on: boolean()
65752
66169
  }), _void(), {
65753
66170
  kind: "mutation",
65754
66171
  auth: "protected"
65755
- }), method(object({
65756
- deviceId: DeviceIdSchema,
65757
- minutes: MinutesSchema
65758
- }), _void(), {
65759
- kind: "mutation",
65760
- auth: "protected"
65761
- }), method(object({
65762
- deviceId: DeviceIdSchema,
65763
- minutes: MinutesSchema
65764
- }), _void(), {
65765
- kind: "mutation",
65766
- auth: "protected"
65767
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
66172
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
65768
66173
  kind: "mutation",
65769
66174
  auth: "protected"
65770
66175
  });
@@ -67298,6 +67703,49 @@ object({
67298
67703
  DeviceType.PetFeeder,
67299
67704
  DeviceType.Heater
67300
67705
  ].filter((type) => type !== DeviceType.Sensor);
67706
+ var PowerSourceRefusalReasonSchema = _enum([
67707
+ "no-power-source",
67708
+ "switch-missing",
67709
+ "switch-offline",
67710
+ "switch-unknown",
67711
+ "restart-in-progress",
67712
+ "switch-call-failed"
67713
+ ]);
67714
+ var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
67715
+ accepted: literal(false),
67716
+ reason: PowerSourceRefusalReasonSchema,
67717
+ /** One line for the operator, naming the switch. */
67718
+ message: string()
67719
+ })]);
67720
+ var PowerSourceInfoSchema = object({
67721
+ /** False when the device has no power source linked; the rest is then empty. */
67722
+ linked: boolean(),
67723
+ switchDeviceId: number().nullable(),
67724
+ switchName: string().nullable(),
67725
+ /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
67726
+ switchOn: boolean().nullable(),
67727
+ switchOnline: boolean().nullable(),
67728
+ restartInProgress: boolean(),
67729
+ offSeconds: number().int()
67730
+ });
67731
+ object({
67732
+ /** Null while the switch has not reported: unknown is never `false` (D393). */
67733
+ switchOn: boolean().nullable(),
67734
+ offSeconds: number().int(),
67735
+ lastFetchedAt: number()
67736
+ });
67737
+ 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;
67738
+ var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
67739
+ systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
67740
+ kind: "mutation",
67741
+ auth: "admin"
67742
+ }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
67743
+ kind: "mutation",
67744
+ auth: "admin"
67745
+ }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
67746
+ kind: "mutation",
67747
+ auth: "admin"
67748
+ });
67301
67749
  /**
67302
67750
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
67303
67751
  * on `DeviceType.Presence`. Read-only — no setters: presence is
@@ -67724,6 +68172,37 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
67724
68172
  deviceId: number(),
67725
68173
  status: PtzAutotrackStatusSchema
67726
68174
  });
68175
+ object({
68176
+ /** True when the pump works on the on/off cycle, false when it runs continuously. Null while unknown. */
68177
+ enabled: boolean().nullable(),
68178
+ /** How long the pump runs per cycle, in seconds. Null while unknown. */
68179
+ onTime: number().min(0).nullable().meta(readingMeta("duration")),
68180
+ /** How long the pump rests per cycle, in seconds. Null while unknown. */
68181
+ offTime: number().min(0).nullable().meta(readingMeta("duration")),
68182
+ /** Ms epoch when the slice was last refreshed. */
68183
+ lastFetchedAt: number()
68184
+ });
68185
+ var DeviceIdSchema = number().int().nonnegative();
68186
+ var MinutesSchema = number().int().min(0);
68187
+ DeviceType.PetFountain, method(object({
68188
+ deviceId: DeviceIdSchema,
68189
+ on: boolean()
68190
+ }), _void(), {
68191
+ kind: "mutation",
68192
+ auth: "protected"
68193
+ }), method(object({
68194
+ deviceId: DeviceIdSchema,
68195
+ minutes: MinutesSchema
68196
+ }), _void(), {
68197
+ kind: "mutation",
68198
+ auth: "protected"
68199
+ }), method(object({
68200
+ deviceId: DeviceIdSchema,
68201
+ minutes: MinutesSchema
68202
+ }), _void(), {
68203
+ kind: "mutation",
68204
+ auth: "protected"
68205
+ });
67727
68206
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
67728
68207
  kind: "mutation",
67729
68208
  auth: "admin"
@@ -70437,7 +70916,13 @@ var RemoteMediaCommandSchema = _enum([
70437
70916
  "next",
70438
70917
  "previous",
70439
70918
  "setMute",
70440
- "selectSource"
70919
+ "selectSource",
70920
+ "turnOn",
70921
+ "turnOff",
70922
+ "volumeUp",
70923
+ "volumeDown",
70924
+ "channelUp",
70925
+ "channelDown"
70441
70926
  ]);
70442
70927
  /** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
70443
70928
  var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
@@ -70458,7 +70943,7 @@ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.e
70458
70943
  }), RemoteSendResultSchema, {
70459
70944
  kind: "mutation",
70460
70945
  auth: "protected"
70461
- }), method(deviceInput.extend({
70946
+ }), method(deviceInput, MediaPlayerStatusSchema.nullable(), { auth: "protected" }), method(deviceInput.extend({
70462
70947
  command: RemoteMediaCommandSchema,
70463
70948
  muted: boolean().optional(),
70464
70949
  source: string().optional(),
@@ -72299,7 +72784,11 @@ object({
72299
72784
  });
72300
72785
  Object.values(DeviceType), method(_void(), array(StreamSignalSchema).readonly());
72301
72786
  object({
72302
- on: boolean().meta({ label: "On" }),
72787
+ /**
72788
+ * `null` = the device has never reported its state (a measurement that
72789
+ * has not happened is unknown, never `false`).
72790
+ */
72791
+ on: boolean().nullable().meta({ label: "On" }),
72303
72792
  /** Ms epoch of the last state change. Useful for UI "X minutes ago". */
72304
72793
  lastChangedAt: number()
72305
72794
  });
@@ -72311,7 +72800,7 @@ DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceTyp
72311
72800
  auth: "protected"
72312
72801
  }), object({
72313
72802
  deviceId: number(),
72314
- on: boolean(),
72803
+ on: boolean().nullable(),
72315
72804
  lastChangedAt: number()
72316
72805
  });
72317
72806
  /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
@@ -75323,6 +75812,26 @@ function buildDescriptor(capName, kind) {
75323
75812
  * contributes one entry per bound cap present in this map.
75324
75813
  */
75325
75814
  var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
75815
+ /**
75816
+ * What a remote key is CALLED and what it LOOKS like, for every role (D963),
75817
+ * shared by the admin UI and the viewer. A role is data on the wire; each
75818
+ * client resolves its name from `label` (English and Italian side by side) and
75819
+ * its glyph from `icon`, a lucide id from the shared icon catalogue — the
75820
+ * server sends an id, a client maps it to its own icon set.
75821
+ *
75822
+ * `legend` is text printed on the key INSTEAD of a glyph (a digit has no
75823
+ * pictogram): a client that can draw text shows it, one that cannot falls back
75824
+ * to `icon`.
75825
+ */
75826
+ function digit(n) {
75827
+ return {
75828
+ label: t(n, n),
75829
+ icon: "hash",
75830
+ legend: n
75831
+ };
75832
+ }
75833
+ digit("0"), digit("1"), digit("2"), digit("3"), digit("4"), digit("5"), digit("6"), digit("7"), digit("8"), digit("9");
75834
+ DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
75326
75835
  var btn = (idSuffix, row, col, init) => ({
75327
75836
  idSuffix,
75328
75837
  row,
@@ -75449,6 +75958,34 @@ discriminatedUnion("kind", [object({
75449
75958
  kind: literal("pronto"),
75450
75959
  code: string()
75451
75960
  })]);
75961
+ /**
75962
+ * Propose a running threshold from a device's own power history (planning#405).
75963
+ *
75964
+ * Pure and deterministic: the same series gives the same answer. The series is
75965
+ * a HELD-VALUE step series — a point's value stands until the next point — which
75966
+ * is how device-history stores a gauge, so "time at a level" is exact, not a
75967
+ * count of samples.
75968
+ *
75969
+ * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
75970
+ * near F. A device that is not near one level most of the time has no floor.
75971
+ * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
75972
+ * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
75973
+ * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
75974
+ * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
75975
+ * grid a person would type; stop dwell covers the longest pause; start dwell
75976
+ * covers the longest standby blip above T.
75977
+ * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
75978
+ * run must hold exactly one true interval and nothing may flap outside.
75979
+ */
75980
+ var SECOND_MS = 1e3;
75981
+ var MINUTE_MS$1 = 60 * SECOND_MS;
75982
+ 24 * (60 * MINUTE_MS$1);
75983
+ 10 * MINUTE_MS$1;
75984
+ 30 * SECOND_MS;
75985
+ 60 * SECOND_MS;
75986
+ 20 * MINUTE_MS$1;
75987
+ 10 * SECOND_MS;
75988
+ 10 * SECOND_MS;
75452
75989
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
75453
75990
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
75454
75991
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
@@ -76430,6 +76967,27 @@ Object.freeze({
76430
76967
  access: "view",
76431
76968
  role: "viewer"
76432
76969
  },
76970
+ "boardKeySource.getBoardKey": {
76971
+ capName: "board-key-source",
76972
+ capScope: "system",
76973
+ addonId: null,
76974
+ access: "view",
76975
+ role: "admin"
76976
+ },
76977
+ "boardKeySource.listBoards": {
76978
+ capName: "board-key-source",
76979
+ capScope: "system",
76980
+ addonId: null,
76981
+ access: "view",
76982
+ role: "admin"
76983
+ },
76984
+ "boardKeySource.listSources": {
76985
+ capName: "board-key-source",
76986
+ capScope: "system",
76987
+ addonId: null,
76988
+ access: "view",
76989
+ role: "admin"
76990
+ },
76433
76991
  "brightness.getStatus": {
76434
76992
  capName: "brightness",
76435
76993
  capScope: "device",
@@ -76969,6 +77527,13 @@ Object.freeze({
76969
77527
  access: "create",
76970
77528
  role: "admin"
76971
77529
  },
77530
+ "coreBlocks.proposeRunningThreshold": {
77531
+ capName: "core-blocks",
77532
+ capScope: "system",
77533
+ addonId: null,
77534
+ access: "view",
77535
+ role: "admin"
77536
+ },
76972
77537
  "coreBlocks.reportAutomationRun": {
76973
77538
  capName: "core-blocks",
76974
77539
  capScope: "system",
@@ -77480,6 +78045,13 @@ Object.freeze({
77480
78045
  access: "view",
77481
78046
  role: "admin"
77482
78047
  },
78048
+ "deviceExport.listInstances": {
78049
+ capName: "device-export",
78050
+ capScope: "system",
78051
+ addonId: null,
78052
+ access: "view",
78053
+ role: "admin"
78054
+ },
77483
78055
  "deviceExport.listSupportedDeviceKinds": {
77484
78056
  capName: "device-export",
77485
78057
  capScope: "system",
@@ -79916,6 +80488,20 @@ Object.freeze({
79916
80488
  access: "view",
79917
80489
  role: "admin"
79918
80490
  },
80491
+ "mediaPlayer.channelDown": {
80492
+ capName: "media-player",
80493
+ capScope: "device",
80494
+ addonId: null,
80495
+ access: "create",
80496
+ role: "viewer"
80497
+ },
80498
+ "mediaPlayer.channelUp": {
80499
+ capName: "media-player",
80500
+ capScope: "device",
80501
+ addonId: null,
80502
+ access: "create",
80503
+ role: "viewer"
80504
+ },
79919
80505
  "mediaPlayer.getStatus": {
79920
80506
  capName: "media-player",
79921
80507
  capScope: "device",
@@ -80007,6 +80593,34 @@ Object.freeze({
80007
80593
  access: "create",
80008
80594
  role: "viewer"
80009
80595
  },
80596
+ "mediaPlayer.turnOff": {
80597
+ capName: "media-player",
80598
+ capScope: "device",
80599
+ addonId: null,
80600
+ access: "create",
80601
+ role: "viewer"
80602
+ },
80603
+ "mediaPlayer.turnOn": {
80604
+ capName: "media-player",
80605
+ capScope: "device",
80606
+ addonId: null,
80607
+ access: "create",
80608
+ role: "viewer"
80609
+ },
80610
+ "mediaPlayer.volumeDown": {
80611
+ capName: "media-player",
80612
+ capScope: "device",
80613
+ addonId: null,
80614
+ access: "create",
80615
+ role: "viewer"
80616
+ },
80617
+ "mediaPlayer.volumeUp": {
80618
+ capName: "media-player",
80619
+ capScope: "device",
80620
+ addonId: null,
80621
+ access: "create",
80622
+ role: "viewer"
80623
+ },
80010
80624
  "meshNetwork.getStatus": {
80011
80625
  capName: "mesh-network",
80012
80626
  capScope: "system",
@@ -81253,27 +81867,6 @@ Object.freeze({
81253
81867
  access: "create",
81254
81868
  role: "viewer"
81255
81869
  },
81256
- "petFountain.setPumpInterval": {
81257
- capName: "pet-fountain",
81258
- capScope: "device",
81259
- addonId: null,
81260
- access: "create",
81261
- role: "viewer"
81262
- },
81263
- "petFountain.setPumpOffMinutes": {
81264
- capName: "pet-fountain",
81265
- capScope: "device",
81266
- addonId: null,
81267
- access: "create",
81268
- role: "viewer"
81269
- },
81270
- "petFountain.setPumpOnMinutes": {
81271
- capName: "pet-fountain",
81272
- capScope: "device",
81273
- addonId: null,
81274
- access: "create",
81275
- role: "viewer"
81276
- },
81277
81870
  "pipelineAnalytics.addRetrainFrameFromRecording": {
81278
81871
  capName: "pipeline-analytics",
81279
81872
  capScope: "device",
@@ -83052,6 +83645,34 @@ Object.freeze({
83052
83645
  access: "create",
83053
83646
  role: "admin"
83054
83647
  },
83648
+ "pumpCycle.getStatus": {
83649
+ capName: "pump-cycle",
83650
+ capScope: "device",
83651
+ addonId: null,
83652
+ access: "view",
83653
+ role: "viewer"
83654
+ },
83655
+ "pumpCycle.setEnabled": {
83656
+ capName: "pump-cycle",
83657
+ capScope: "device",
83658
+ addonId: null,
83659
+ access: "create",
83660
+ role: "viewer"
83661
+ },
83662
+ "pumpCycle.setOffMinutes": {
83663
+ capName: "pump-cycle",
83664
+ capScope: "device",
83665
+ addonId: null,
83666
+ access: "create",
83667
+ role: "viewer"
83668
+ },
83669
+ "pumpCycle.setOnMinutes": {
83670
+ capName: "pump-cycle",
83671
+ capScope: "device",
83672
+ addonId: null,
83673
+ access: "create",
83674
+ role: "viewer"
83675
+ },
83055
83676
  "reboot.reboot": {
83056
83677
  capName: "reboot",
83057
83678
  capScope: "device",
@@ -83556,6 +84177,13 @@ Object.freeze({
83556
84177
  access: "create",
83557
84178
  role: "admin"
83558
84179
  },
84180
+ "remote.getAssumedStatus": {
84181
+ capName: "remote",
84182
+ capScope: "system",
84183
+ addonId: null,
84184
+ access: "view",
84185
+ role: "admin"
84186
+ },
83559
84187
  "remote.getConfig": {
83560
84188
  capName: "remote",
83561
84189
  capScope: "system",
@@ -86371,6 +86999,11 @@ Object.freeze({
86371
86999
  form: "single",
86372
87000
  optional: false
86373
87001
  }],
87002
+ "coreBlocks.proposeRunningThreshold": [{
87003
+ name: "deviceId",
87004
+ form: "single",
87005
+ optional: false
87006
+ }],
86374
87007
  "cover.close": [{
86375
87008
  name: "deviceId",
86376
87009
  form: "single",
@@ -87222,6 +87855,16 @@ Object.freeze({
87222
87855
  form: "single",
87223
87856
  optional: true
87224
87857
  }],
87858
+ "mediaPlayer.channelDown": [{
87859
+ name: "deviceId",
87860
+ form: "single",
87861
+ optional: false
87862
+ }],
87863
+ "mediaPlayer.channelUp": [{
87864
+ name: "deviceId",
87865
+ form: "single",
87866
+ optional: false
87867
+ }],
87225
87868
  "mediaPlayer.getStatus": [{
87226
87869
  name: "deviceId",
87227
87870
  form: "single",
@@ -87287,6 +87930,26 @@ Object.freeze({
87287
87930
  form: "single",
87288
87931
  optional: false
87289
87932
  }],
87933
+ "mediaPlayer.turnOff": [{
87934
+ name: "deviceId",
87935
+ form: "single",
87936
+ optional: false
87937
+ }],
87938
+ "mediaPlayer.turnOn": [{
87939
+ name: "deviceId",
87940
+ form: "single",
87941
+ optional: false
87942
+ }],
87943
+ "mediaPlayer.volumeDown": [{
87944
+ name: "deviceId",
87945
+ form: "single",
87946
+ optional: false
87947
+ }],
87948
+ "mediaPlayer.volumeUp": [{
87949
+ name: "deviceId",
87950
+ form: "single",
87951
+ optional: false
87952
+ }],
87290
87953
  "meter.getStatus": [{
87291
87954
  name: "deviceId",
87292
87955
  form: "single",
@@ -87596,21 +88259,6 @@ Object.freeze({
87596
88259
  form: "single",
87597
88260
  optional: false
87598
88261
  }],
87599
- "petFountain.setPumpInterval": [{
87600
- name: "deviceId",
87601
- form: "single",
87602
- optional: false
87603
- }],
87604
- "petFountain.setPumpOffMinutes": [{
87605
- name: "deviceId",
87606
- form: "single",
87607
- optional: false
87608
- }],
87609
- "petFountain.setPumpOnMinutes": [{
87610
- name: "deviceId",
87611
- form: "single",
87612
- optional: false
87613
- }],
87614
88262
  "pipelineAnalytics.addRetrainFrameFromRecording": [{
87615
88263
  name: "deviceId",
87616
88264
  form: "single",
@@ -88246,6 +88894,26 @@ Object.freeze({
88246
88894
  form: "single",
88247
88895
  optional: false
88248
88896
  }],
88897
+ "pumpCycle.getStatus": [{
88898
+ name: "deviceId",
88899
+ form: "single",
88900
+ optional: false
88901
+ }],
88902
+ "pumpCycle.setEnabled": [{
88903
+ name: "deviceId",
88904
+ form: "single",
88905
+ optional: false
88906
+ }],
88907
+ "pumpCycle.setOffMinutes": [{
88908
+ name: "deviceId",
88909
+ form: "single",
88910
+ optional: false
88911
+ }],
88912
+ "pumpCycle.setOnMinutes": [{
88913
+ name: "deviceId",
88914
+ form: "single",
88915
+ optional: false
88916
+ }],
88249
88917
  "reboot.reboot": [{
88250
88918
  name: "deviceId",
88251
88919
  form: "single",
@@ -88447,6 +89115,11 @@ Object.freeze({
88447
89115
  form: "single",
88448
89116
  optional: true
88449
89117
  }],
89118
+ "remote.getAssumedStatus": [{
89119
+ name: "deviceId",
89120
+ form: "single",
89121
+ optional: false
89122
+ }],
88450
89123
  "remote.getConfig": [{
88451
89124
  name: "deviceId",
88452
89125
  form: "single",
@@ -88994,99 +89667,6 @@ Object.freeze({
88994
89667
  "network-access": "ingress",
88995
89668
  "smtp-provider": "email"
88996
89669
  });
88997
- /**
88998
- * A saved Customize assignment (D936). The schema is the type: callers use
88999
- * `z.infer`, they do not restate the shape.
89000
- */
89001
- var OverrideSourceSchema = object({
89002
- addonId: string().min(1),
89003
- stableId: string().min(1),
89004
- cap: string().min(1),
89005
- fieldPath: string().min(1)
89006
- });
89007
- var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
89008
- object({ kind: literal("identity") }),
89009
- object({ kind: literal("invert") }),
89010
- object({
89011
- kind: literal("threshold"),
89012
- comparison: _enum(["gte", "lte"]),
89013
- threshold: number(),
89014
- startDwellMs: number().nonnegative(),
89015
- stopDwellMs: number().nonnegative()
89016
- }),
89017
- object({
89018
- kind: literal("linear"),
89019
- scale: number(),
89020
- offset: number(),
89021
- clampMin: number().optional(),
89022
- clampMax: number().optional()
89023
- }),
89024
- object({
89025
- kind: literal("enum-map"),
89026
- mapping: record(string(), string().nullable())
89027
- }),
89028
- object({ kind: literal("number") })
89029
- ]);
89030
- var ScalarOverrideDraftSchema = object({
89031
- kind: literal("scalar"),
89032
- cap: string().min(1),
89033
- targetId: string().min(1),
89034
- source: OverrideSourceSchema,
89035
- transform: CustomizeTransformDraftSchema
89036
- });
89037
- var KeyedArrayOverrideDraftSchema = object({
89038
- kind: literal("keyedArray"),
89039
- cap: string().min(1),
89040
- targetId: string().min(1),
89041
- key: string().min(1),
89042
- label: string().min(1),
89043
- fieldPath: string().min(1),
89044
- source: OverrideSourceSchema,
89045
- transform: CustomizeTransformDraftSchema
89046
- });
89047
- var PulseOverrideDraftSchema = object({
89048
- kind: literal("pulse"),
89049
- cap: string().min(1),
89050
- targetId: string().min(1),
89051
- enabled: boolean(),
89052
- debounceMs: number().nonnegative(),
89053
- edges: array(_enum([
89054
- "rising",
89055
- "button",
89056
- "native-event"
89057
- ])).min(1).optional(),
89058
- sources: array(OverrideSourceSchema).min(1)
89059
- });
89060
- var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
89061
- "in-addition",
89062
- "confirm",
89063
- "replace"
89064
- ]).optional() });
89065
- discriminatedUnion("kind", [
89066
- ScalarOverrideDraftSchema,
89067
- KeyedArrayOverrideDraftSchema,
89068
- PulseOverrideDraftSchema,
89069
- object({
89070
- kind: literal("span"),
89071
- cap: string().min(1),
89072
- targetId: string().min(1),
89073
- timeoutMs: number().nonnegative(),
89074
- cooldownMs: number().nonnegative().optional(),
89075
- sources: array(SpanSourceDraftSchema)
89076
- })
89077
- ]);
89078
- object({
89079
- targetId: string().min(1),
89080
- configured: boolean(),
89081
- source: _enum(["native", "assigned"]),
89082
- state: _enum([
89083
- "active",
89084
- "stale",
89085
- "released",
89086
- "unavailable"
89087
- ]),
89088
- reason: string().nullable()
89089
- });
89090
89670
  var G711_SCALE_CORRECTION_DB = {
89091
89671
  PCMU: 20 * Math.log10(4),
89092
89672
  PCMA: 20 * Math.log10(8)
@@ -89702,6 +90282,7 @@ var STATE_SUMMARY_VARS = [
89702
90282
  [...RULE_TEXT],
89703
90283
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
89704
90284
  ([...RULE_TEXT], [...COMBINED_ALARM]),
90285
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
89705
90286
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
89706
90287
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
89707
90288
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -145069,6 +145650,7 @@ var SmtpNodemailerAddon = class extends BaseAddon {
145069
145650
  });
145070
145651
  this.instances = new RecordIntegrationInstances(records, smtpRelaysKind, {
145071
145652
  description: {
145653
+ role: "services",
145072
145654
  instanceMode: "multiple",
145073
145655
  kind: "relays",
145074
145656
  canTest: true