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