@camstack/addon-import-alexa 0.2.197 → 0.2.199

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.
package/dist/addon.mjs CHANGED
@@ -5674,7 +5674,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5674
5674
  return EventCategory;
5675
5675
  }({});
5676
5676
  //#endregion
5677
- //#region ../types/dist/device-type-D1fH5kaC.mjs
5677
+ //#region ../types/dist/device-type-DxCT0krO.mjs
5678
5678
  /**
5679
5679
  import { errMsg } from '@camstack/types'
5680
5680
  * Extract a human-readable message from an unknown error value.
@@ -5836,6 +5836,13 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
5836
5836
  * filters that want "all devices with on-motion behaviour".
5837
5837
  */
5838
5838
  DeviceFeature["MotionTrigger"] = "motion-trigger";
5839
+ /**
5840
+ * Device can be run for a chosen time and then stopped. Mirrors the
5841
+ * `timed-run` wrapper cap — the cap is bound by the core-blocks addon, not by
5842
+ * a driver, so the flag is derived from the device's bindings
5843
+ * (`featuresOfBoundCaps`). Gates the quick-run control in the viewer.
5844
+ */
5845
+ DeviceFeature["TimedRun"] = "timed-run";
5839
5846
  /** Light supports rgb-triplet color via `color` cap. */
5840
5847
  DeviceFeature["LightColorRgb"] = "light-color-rgb";
5841
5848
  /** Light supports HSV color via `color` cap. */
@@ -6059,7 +6066,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
6059
6066
  return DeviceRole;
6060
6067
  }({});
6061
6068
  //#endregion
6062
- //#region ../types/dist/sleep-CQP-REJp.mjs
6069
+ //#region ../types/dist/sleep-CGPZyzB5.mjs
6063
6070
  /**
6064
6071
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6065
6072
  * window to float samples (D455).
@@ -7291,6 +7298,7 @@ var RecordIntegrationInstances = class {
7291
7298
  this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
7292
7299
  this.secretMaps = secretMapKeys(kind.declaration.secretPaths);
7293
7300
  this.nameField = kind.declaration.entry?.nameField ?? "name";
7301
+ if (hooks.kindOf === void 0 && hooks.description.kinds.length !== 1) throw new Error(`record kind '${kind.name}': describe().kinds has ${hooks.description.kinds.length} entries, so kindOf is required to say which one an instance is`);
7294
7302
  }
7295
7303
  /**
7296
7304
  * One instance read for the addon's own runtime; null when absent. When the keychain is
@@ -7332,10 +7340,21 @@ var RecordIntegrationInstances = class {
7332
7340
  }
7333
7341
  return out;
7334
7342
  }
7343
+ /** The kind an instance body is of: the provider's `kindOf`, else its one declared kind. */
7344
+ kindOfBody(body) {
7345
+ if (this.hooks.kindOf !== void 0) {
7346
+ const id = this.hooks.kindOf(body);
7347
+ if (!this.hooks.description.kinds.some((declared) => declared.id === id)) throw new Error(`record kind '${this.kind.name}': kindOf returned '${id}', which describe().kinds does not declare (D1013)`);
7348
+ return id;
7349
+ }
7350
+ const only = this.hooks.description.kinds[0];
7351
+ if (only === void 0) throw new Error(`record kind '${this.kind.name}' declares no kinds`);
7352
+ return only.id;
7353
+ }
7335
7354
  viewOf(record) {
7336
7355
  const shown = secretViewsOf(this.records.owner, this.kind.name, record.id, this.kind.declaration, record.data);
7337
7356
  const rawName = record.data[this.nameField];
7338
- const kind = this.hooks.kindOf?.(record.data);
7357
+ const kind = this.kindOfBody(record.data);
7339
7358
  const health = this.hooks.health?.(record.data, record.id);
7340
7359
  return {
7341
7360
  id: record.id,
@@ -7343,7 +7362,7 @@ var RecordIntegrationInstances = class {
7343
7362
  configured: this.hooks.configured(record.data),
7344
7363
  enabled: isInstanceEnabled(record.data),
7345
7364
  formValues: shown,
7346
- ...kind !== void 0 ? { kind } : {},
7365
+ kind,
7347
7366
  ...health?.status !== void 0 ? { status: health.status } : {},
7348
7367
  ...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
7349
7368
  };
@@ -8444,7 +8463,7 @@ function boundOf(list) {
8444
8463
  * Base class for CamStack addons. Eliminates settings boilerplate:
8445
8464
  *
8446
8465
  * - Typed `config` property with automatic resolution from store + defaults
8447
- * - `getGlobalSettings()` / `updateGlobalSettings()` auto-implemented
8466
+ * - the scoped settings API (`clusterSettings` / `nodeSettings` / `deviceDefaults`) auto-implemented
8448
8467
  * - `getAddonSettings()` / `updateAddonSettings()` auto-implemented
8449
8468
  * - `getDeviceSettings()` / `updateDeviceSettings()` auto-implemented
8450
8469
  * - `ctx` accessor for the AddonContext (no manual `this.ctxRef` storage)
@@ -8454,7 +8473,7 @@ function boundOf(list) {
8454
8473
  * Subclasses override:
8455
8474
  * - `onInitialize()` — addon-specific init logic, return ProviderRegistration[]
8456
8475
  * - `onShutdown()` — cleanup
8457
- * - `globalSettingsSchema()` / `deviceSettingsSchema()` — UI schemas
8476
+ * - `clusterSettingsSchema()` / `nodeSettingsSchema()` / `deviceSettingsSchema()` — UI schemas
8458
8477
  *
8459
8478
  * @example
8460
8479
  * ```ts
@@ -8468,7 +8487,7 @@ function boundOf(list) {
8468
8487
  * super({ maxRetries: 3, endpoint: 'http://localhost' })
8469
8488
  * }
8470
8489
  *
8471
- * protected globalSettingsSchema() {
8490
+ * protected clusterSettingsSchema() {
8472
8491
  * return this.schema({
8473
8492
  * sections: [{
8474
8493
  * id: 'main', title: 'Settings',
@@ -8511,7 +8530,7 @@ var BaseAddon = class {
8511
8530
  defaults;
8512
8531
  /**
8513
8532
  * Hidden-state paths that hold secrets (D787) — values the addon writes
8514
- * itself (`updateGlobalSettings`, `state()`) that no form field declares.
8533
+ * itself (`state()`, the scoped writers) that no form field declares.
8515
8534
  * Stored in the keychain exactly like a `password` field. Syntax:
8516
8535
  * `'snapshotSigningKey'`, `'identity.pincode'`, `'tokens[userId].accessToken'`
8517
8536
  * (`key[<id field>]` addresses array items by id, never by index).
@@ -8521,7 +8540,7 @@ var BaseAddon = class {
8521
8540
  /**
8522
8541
  * D986 M4: the scoped schemas this addon declared, resolved once per capability
8523
8542
  * (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
8524
- * see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
8543
+ * see them. Empty until loaded.
8525
8544
  */
8526
8545
  _scopedByCap = /* @__PURE__ */ new Map();
8527
8546
  _nodeKeysByCap = /* @__PURE__ */ new Map();
@@ -8707,7 +8726,7 @@ var BaseAddon = class {
8707
8726
  /** Addon-specific cleanup. Override if needed. */
8708
8727
  async onShutdown() {}
8709
8728
  /**
8710
- * Called after config is resolved during updateGlobalSettings/updateAddonSettings.
8729
+ * Called after config is resolved during a settings write.
8711
8730
  * Override to react to config changes (e.g. restart a sampler, reconnect a service).
8712
8731
  * Not called during initialize() — use onInitialize() for initial setup.
8713
8732
  */
@@ -8736,82 +8755,12 @@ var BaseAddon = class {
8736
8755
  scopedSettingsCapabilities() {
8737
8756
  return [];
8738
8757
  }
8739
- /** Override to provide global-level settings UI schema. */
8740
- globalSettingsSchema(_cap) {
8741
- return null;
8742
- }
8743
8758
  /** Override to provide device-level settings UI schema. */
8744
8759
  deviceSettingsSchema() {
8745
8760
  return null;
8746
8761
  }
8747
8762
  /**
8748
- * INTEGRATION-LEVEL SETTINGS — declare which of this addon's global sections
8749
- * ARE the configuration of its integration.
8750
- *
8751
- * Return the `ConfigSection.id`s, from {@link globalSettingsSchema}, that an
8752
- * operator should find on the addon's integration page (System →
8753
- * Integrations → <name>) rather than only in the cluster-wide list of every
8754
- * addon. Empty (the default) means the addon has no integration-level
8755
- * settings and no such surface is offered — this is opt-in, because whether
8756
- * an addon's configuration IS its integration's configuration depends on the
8757
- * nature of the integration.
8758
- *
8759
- * WHAT THIS IS NOT. It is not a scope. The selected sections keep living in
8760
- * the ONE global schema, in the ONE addon store, written by the ONE
8761
- * `updateGlobalSettings` path. There is deliberately no
8762
- * `updateIntegrationSettings`: a second write path is how a surface acquires
8763
- * a second store key, and this repo has shipped that twice (`btmPath@hub`,
8764
- * D266). Selecting sections cannot introduce a key that selecting cannot.
8765
- *
8766
- * WHY IT IS A LIST OF SECTION IDS AND NOT A MARKER ON THE SECTION.
8767
- * `ConfigFieldBase` used to carry `scope?: 'device' | 'global'` and it was
8768
- * removed with the reason recorded at
8769
- * `packages/types/src/interfaces/config-ui.ts:249` — *"a field's scope is
8770
- * determined by WHICH schema it lives in, not by a field-level marker."* A
8771
- * marker sprinkled across sections also has to borrow a field that already
8772
- * means something else; borrowing `section.tab` put the literal word
8773
- * "integration" into an operator-facing tab bar, because `tab` means "how to
8774
- * GROUP this visually" and cannot also mean "where this lives" (D269
8775
- * supersedes D268). One declaration, in one place, next to the schema whose
8776
- * ids it names.
8777
- */
8778
- integrationSettingSections() {
8779
- return [];
8780
- }
8781
- async getGlobalSettings(overlay, cap, nodeId) {
8782
- await this.loadScopedSchemas(cap);
8783
- const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
8784
- if (!schema) return { sections: [] };
8785
- const projected = await this.readRawProjectedStore(nodeId, cap);
8786
- return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
8787
- }
8788
- /**
8789
- * The integration-level view of this addon's settings: exactly the sections
8790
- * named by {@link integrationSettingSections}, hydrated from the SAME store
8791
- * `getGlobalSettings` reads, and narrowed to cluster-scoped fields.
8792
- *
8793
- * Returns `null` when the addon declared nothing — an addon that opts out has
8794
- * no integration settings surface at all, rather than an empty one that reads
8795
- * as a failed load.
8796
- *
8797
- * Three properties hold BY CONSTRUCTION, which is why they are here in core
8798
- * and not in whichever UI happens to render this:
8799
- *
8800
- * 1. **One key.** The payload is a SUBSET of the global schema, so a field
8801
- * shown here is the same field, with the same bare key, that the addon's
8802
- * own page shows. There is no integration-specific writer — callers save
8803
- * through `updateGlobalSettings` — so a second store key is unreachable,
8804
- * not merely discouraged.
8805
- * 2. **No node scope.** `perNode: true` fields are DROPPED. Their store key
8806
- * is `<key>@<nodeId>` and an integration is not a node; whichever node
8807
- * such a field silently picked would be a wrong answer for the operator
8808
- * who opened the page (D266).
8809
- * 3. **No silent typo.** A declared id that names no section throws. The
8810
- * alternative — skip it — turns a rename into a surface that quietly
8811
- * empties, which looks exactly like an addon with nothing to configure.
8812
- */
8813
- /**
8814
- * The form schema of ONE integration instance, or `null` to use {@link globalSettingsSchema}.
8763
+ * The form schema of ONE integration instance, or `null` to use the scoped form.
8815
8764
  *
8816
8765
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
8817
8766
  * needs its connection fields, the default integration the addon's own sections. It is the
@@ -8984,6 +8933,19 @@ var BaseAddon = class {
8984
8933
  const overlay = await this.scopedSettingsOverlay(scope, cap, nodeId);
8985
8934
  return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(stored, overlay) : stored, { inlineSecretsAsViews: true });
8986
8935
  }
8936
+ /**
8937
+ * The scoped schemas this addon serves: one entry per loaded (capability, scope), capabilities
8938
+ * in declaration order, scopes in {@link SCOPED_SETTINGS_SCOPES} order. `[]` when it declares
8939
+ * nothing.
8940
+ */
8941
+ async listScopedSettings() {
8942
+ await this.loadScopedSchemas();
8943
+ return [...this._scopedByCap].flatMap(([cap, entries]) => entries.map((entry) => ({
8944
+ cap,
8945
+ scope: entry.scope,
8946
+ sectionIds: entry.schema.sections.map((section) => section.id)
8947
+ })));
8948
+ }
8987
8949
  /** The OPERATOR's write of declared keys of one scope (D787 secret semantics). */
8988
8950
  async updateScopedSettings(scope, cap, patch, nodeId) {
8989
8951
  this._operatorWriteDepth++;
@@ -9009,32 +8971,15 @@ var BaseAddon = class {
9009
8971
  */
9010
8972
  async getIntegrationFormSchema(instance, nodeId) {
9011
8973
  const scoped = await this.integrationFormSchema(instance);
9012
- if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
8974
+ if (scoped === null) return this.scopedForm(nodeId);
9013
8975
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
9014
8976
  }
9015
- async getIntegrationSettings(nodeId) {
9016
- const declared = this.integrationSettingSections();
9017
- if (declared.length === 0) return null;
9018
- const schema = this.globalSettingsSchema();
9019
- if (!schema) throw new Error(`${this.constructor.name}: integrationSettingSections() names [${declared.join(", ")}] but globalSettingsSchema() returns null.`);
9020
- const byId = new Map(schema.sections.map((section) => [section.id, section]));
9021
- const sections = [];
9022
- for (const id of declared) {
9023
- const section = byId.get(id);
9024
- if (!section) throw new Error(`${this.constructor.name}: integrationSettingSections() names unknown section "${id}". Known sections: [${[...byId.keys()].join(", ")}].`);
9025
- const fields = dropPerNodeFields(section.fields);
9026
- if (fields.length === 0) continue;
9027
- sections.push({
9028
- ...section,
9029
- fields
9030
- });
9031
- }
9032
- if (sections.length === 0) return null;
9033
- const projected = await this.readRawProjectedStore(nodeId);
9034
- return hydrateSchema({
9035
- ...schema,
9036
- sections
9037
- }, projected, { inlineSecretsAsViews: true });
8977
+ /** The form of the scoped schemas this addon declared, hydrated from its store. */
8978
+ async scopedForm(nodeId) {
8979
+ await this.loadScopedSchemas();
8980
+ const schema = this.scopedFormSchema();
8981
+ if (!schema) return { sections: [] };
8982
+ return hydrateSchema(schema, await this.readRawProjectedStore(nodeId), { inlineSecretsAsViews: true });
9038
8983
  }
9039
8984
  /**
9040
8985
  * The raw addon store PROJECTED onto the target node's bare per-node keys:
@@ -9043,8 +8988,8 @@ var BaseAddon = class {
9043
8988
  * bare fallback), all `@`-scoped and stray bare per-node keys are dropped.
9044
8989
  * A no-op passthrough when the schema declares no `perNode` field.
9045
8990
  *
9046
- * This is the sanctioned way for a `getGlobalSettings` OVERRIDE that needs
9047
- * the store for custom option logic (option narrowing, value snapping) to
8991
+ * This is the sanctioned way for an addon that needs
8992
+ * the store for custom logic (option narrowing, value snapping) to
9048
8993
  * read it per-node — never `ctx.settings.readAddonStore()` directly.
9049
8994
  * `nodeId` omitted ⇒ the local node (node-blind injection from ctx).
9050
8995
  *
@@ -9096,7 +9041,7 @@ var BaseAddon = class {
9096
9041
  }
9097
9042
  /**
9098
9043
  * Whether ONE global key of this addon's store was written, read three-way
9099
- * (D651). For a question a hydrated view cannot answer: `getGlobalSettings`
9044
+ * (D651). For a question a hydrated view cannot answer: a hydrated form
9100
9045
  * reports `value ?? default`, so "the operator chose the default" and "nobody
9101
9046
  * chose" look the same, and a failed read looks like both. A `perNode` key is
9102
9047
  * not supported here: its stored name is scoped.
@@ -9130,7 +9075,7 @@ var BaseAddon = class {
9130
9075
  }
9131
9076
  /**
9132
9077
  * The OPERATOR's write — a settings form or an admin API call, dispatched by
9133
- * the kernel. Identical to {@link updateGlobalSettings}, except that a secret
9078
+ * the kernel. The one in-process write of the addon blob that is not a scoped write; a secret
9134
9079
  * the operator sends is stored as sent even when it equals a pre-keychain
9135
9080
  * inline value: the operator re-entering a password is a change, an addon
9136
9081
  * writing back what it read is not (D787).
@@ -9143,9 +9088,6 @@ var BaseAddon = class {
9143
9088
  this._operatorWriteDepth--;
9144
9089
  }
9145
9090
  }
9146
- async updateGlobalSettings(patch, nodeId) {
9147
- await this.applyGlobalPatch(patch, nodeId);
9148
- }
9149
9091
  /**
9150
9092
  * REMOVE stored secrets — their keychain values and their markers (D787).
9151
9093
  *
@@ -9188,8 +9130,7 @@ var BaseAddon = class {
9188
9130
  await this.resolveConfig();
9189
9131
  await this.onConfigChanged();
9190
9132
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
9191
- const legacy = this.globalSettingsSchema();
9192
- this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
9133
+ this.maybeAutoRestart(Object.keys(barePatch), this.loadedScopedSchemas().map((entry) => entry.schema));
9193
9134
  return true;
9194
9135
  }
9195
9136
  /**
@@ -9227,10 +9168,7 @@ var BaseAddon = class {
9227
9168
  this._nodeKeysByCap = /* @__PURE__ */ new Map();
9228
9169
  this._addonSecretTree = null;
9229
9170
  }
9230
- /**
9231
- * The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
9232
- * sections of its scoped schemas, so dropping the old hook does not blank the form.
9233
- */
9171
+ /** The form of an addon's scoped schemas: their sections, each shown once. */
9234
9172
  scopedFormSchema(cap) {
9235
9173
  const byId = /* @__PURE__ */ new Map();
9236
9174
  for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
@@ -9242,8 +9180,7 @@ var BaseAddon = class {
9242
9180
  return [...this._scopedByCap.values()].flat();
9243
9181
  }
9244
9182
  /**
9245
- * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
9246
- * the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
9183
+ * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}. Derived once
9247
9184
  * per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
9248
9185
  * per-node code path is bypassed and the settings API behaves like the node-agnostic one.
9249
9186
  * Call {@link loadScopedSchemas} first (the async seams do).
@@ -9252,8 +9189,7 @@ var BaseAddon = class {
9252
9189
  const cacheKey = cap ?? "";
9253
9190
  const cached = this._nodeKeysByCap.get(cacheKey);
9254
9191
  if (cached) return cached;
9255
- const legacy = this.globalSettingsSchema(cap);
9256
- const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
9192
+ const keys = /* @__PURE__ */ new Set();
9257
9193
  for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
9258
9194
  if (scope !== "node") continue;
9259
9195
  for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
@@ -9471,8 +9407,6 @@ var BaseAddon = class {
9471
9407
  }
9472
9408
  declaredKeySet() {
9473
9409
  const keys = new Set(Object.keys(this.defaults));
9474
- const schema = this.globalSettingsSchema();
9475
- if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
9476
9410
  for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
9477
9411
  for (const key of this._declaredStateKeys) keys.add(key);
9478
9412
  for (const path of this.secretStatePaths) {
@@ -9570,7 +9504,7 @@ var BaseAddon = class {
9570
9504
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
9571
9505
  addonSecretTree() {
9572
9506
  if (this._addonSecretTree === null) {
9573
- const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
9507
+ const tree = /* @__PURE__ */ new Map();
9574
9508
  for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
9575
9509
  this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
9576
9510
  }
@@ -9782,47 +9716,6 @@ function classifyStoreError(err) {
9782
9716
  if (TRANSPORT_PHRASES.some((f) => message.includes(f))) return "transport";
9783
9717
  return "permanent";
9784
9718
  }
9785
- /**
9786
- * Collect the keys of every field marked `perNode: true`, recursing into
9787
- * layout containers (`group` fields and `sub-tabs` tabs) the same way
9788
- * `hydrateSchema` does. Valueless structural fields (separator/info/…)
9789
- * don't declare `perNode` and are excluded by the `in` narrowing.
9790
- */
9791
- /**
9792
- * The same fields with every `perNode: true` one removed, recursing into layout
9793
- * containers exactly as {@link collectPerNodeFieldKeys} does. A container left
9794
- * with no child is dropped rather than rendered empty.
9795
- *
9796
- * Used by `getIntegrationSettings`: an integration is not a node, so a field
9797
- * whose store key is `<key>@<nodeId>` has no node to belong to there.
9798
- */
9799
- function dropPerNodeFields(fields) {
9800
- const kept = [];
9801
- for (const field of fields) {
9802
- if (field.type === "group") {
9803
- const inner = dropPerNodeFields(field.fields);
9804
- if (inner.length > 0) kept.push({
9805
- ...field,
9806
- fields: inner
9807
- });
9808
- continue;
9809
- }
9810
- if (field.type === "sub-tabs") {
9811
- const tabs = field.tabs.map((tab) => ({
9812
- ...tab,
9813
- fields: dropPerNodeFields(tab.fields)
9814
- })).filter((tab) => tab.fields.length > 0);
9815
- if (tabs.length > 0) kept.push({
9816
- ...field,
9817
- tabs
9818
- });
9819
- continue;
9820
- }
9821
- if ("perNode" in field && field.perNode === true) continue;
9822
- kept.push(field);
9823
- }
9824
- return kept;
9825
- }
9826
9719
  /** The keys of the value-holding fields, recursing into layout containers. */
9827
9720
  function collectValueFieldKeys(fields) {
9828
9721
  const collected = [];
@@ -9855,21 +9748,6 @@ function collectFieldKeys(fields) {
9855
9748
  }
9856
9749
  return collected;
9857
9750
  }
9858
- function collectPerNodeFieldKeys(fields) {
9859
- const collected = [];
9860
- for (const field of fields) {
9861
- if (field.type === "group") {
9862
- collected.push(...collectPerNodeFieldKeys(field.fields));
9863
- continue;
9864
- }
9865
- if (field.type === "sub-tabs") {
9866
- for (const tab of field.tabs) collected.push(...collectPerNodeFieldKeys(tab.fields));
9867
- continue;
9868
- }
9869
- if ("perNode" in field && field.perNode === true) collected.push(field.key);
9870
- }
9871
- return collected;
9872
- }
9873
9751
  /**
9874
9752
  * Normalize an `ICamstackAddon.initialize()` return value into the
9875
9753
  * `AddonInitResult` envelope. Arrays are wrapped into `{ providers }`;
@@ -12630,13 +12508,25 @@ Object.freeze({
12630
12508
  "path": "temperature",
12631
12509
  "valueType": "number"
12632
12510
  }],
12633
- "timed-run": [{
12634
- "path": "activeUntil",
12635
- "valueType": "number"
12636
- }, {
12637
- "path": "requestedBy",
12638
- "valueType": "string"
12639
- }],
12511
+ "timed-run": [
12512
+ {
12513
+ "path": "activeUntil",
12514
+ "valueType": "number"
12515
+ },
12516
+ {
12517
+ "path": "requestedBy",
12518
+ "valueType": "string"
12519
+ },
12520
+ {
12521
+ "path": "quickRun.duration",
12522
+ "valueType": "number"
12523
+ },
12524
+ {
12525
+ "path": "quickRun.unit",
12526
+ "valueType": "enum",
12527
+ "enumValues": ["s", "min"]
12528
+ }
12529
+ ],
12640
12530
  "update": [
12641
12531
  {
12642
12532
  "path": "currentVersion",
@@ -12860,7 +12750,7 @@ object({
12860
12750
  params: record(string(), union([string(), number$1()])).optional()
12861
12751
  });
12862
12752
  //#endregion
12863
- //#region ../types/dist/composition-BmBHuDTQ.mjs
12753
+ //#region ../types/dist/composition-Bx0JvKUU.mjs
12864
12754
  /**
12865
12755
  * The ffmpeg build every CamStack node downloads, per platform and arch.
12866
12756
  *
@@ -14022,6 +13912,387 @@ var NodeColorIdSchema = _enum([
14022
13912
  "node-h"
14023
13913
  ]);
14024
13914
  //#endregion
13915
+ //#region ../types/dist/convert-BAzH15HU.mjs
13916
+ /**
13917
+ * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
13918
+ * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
13919
+ * `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
13920
+ * decimals). K derives from `°C = K − 273.15`.
13921
+ */
13922
+ var UNIT_TABLE = {
13923
+ "°C": {
13924
+ dimension: "temperature",
13925
+ factor: 1,
13926
+ offset: 0
13927
+ },
13928
+ "°F": {
13929
+ dimension: "temperature",
13930
+ factor: 5 / 9,
13931
+ offset: -160 / 9
13932
+ },
13933
+ K: {
13934
+ dimension: "temperature",
13935
+ factor: 1,
13936
+ offset: -273.15
13937
+ },
13938
+ hPa: {
13939
+ dimension: "pressure",
13940
+ factor: 1,
13941
+ offset: 0
13942
+ },
13943
+ kPa: {
13944
+ dimension: "pressure",
13945
+ factor: 10,
13946
+ offset: 0
13947
+ },
13948
+ Pa: {
13949
+ dimension: "pressure",
13950
+ factor: .01,
13951
+ offset: 0
13952
+ },
13953
+ mbar: {
13954
+ dimension: "pressure",
13955
+ factor: 1,
13956
+ offset: 0
13957
+ },
13958
+ bar: {
13959
+ dimension: "pressure",
13960
+ factor: 1e3,
13961
+ offset: 0
13962
+ },
13963
+ inHg: {
13964
+ dimension: "pressure",
13965
+ factor: 33.8639,
13966
+ offset: 0
13967
+ },
13968
+ mmHg: {
13969
+ dimension: "pressure",
13970
+ factor: 1.33322,
13971
+ offset: 0
13972
+ },
13973
+ psi: {
13974
+ dimension: "pressure",
13975
+ factor: 68.9476,
13976
+ offset: 0
13977
+ },
13978
+ "m/s": {
13979
+ dimension: "speed",
13980
+ factor: 1,
13981
+ offset: 0
13982
+ },
13983
+ "km/h": {
13984
+ dimension: "speed",
13985
+ factor: 1 / 3.6,
13986
+ offset: 0
13987
+ },
13988
+ mph: {
13989
+ dimension: "speed",
13990
+ factor: .44704,
13991
+ offset: 0
13992
+ },
13993
+ kn: {
13994
+ dimension: "speed",
13995
+ factor: .514444,
13996
+ offset: 0
13997
+ },
13998
+ "mm/h": {
13999
+ dimension: "precipitation-rate",
14000
+ factor: 1,
14001
+ offset: 0
14002
+ },
14003
+ "in/h": {
14004
+ dimension: "precipitation-rate",
14005
+ factor: 25.4,
14006
+ offset: 0
14007
+ },
14008
+ m: {
14009
+ dimension: "length",
14010
+ factor: 1,
14011
+ offset: 0
14012
+ },
14013
+ mm: {
14014
+ dimension: "length",
14015
+ factor: .001,
14016
+ offset: 0
14017
+ },
14018
+ cm: {
14019
+ dimension: "length",
14020
+ factor: .01,
14021
+ offset: 0
14022
+ },
14023
+ km: {
14024
+ dimension: "length",
14025
+ factor: 1e3,
14026
+ offset: 0
14027
+ },
14028
+ in: {
14029
+ dimension: "length",
14030
+ factor: .0254,
14031
+ offset: 0
14032
+ },
14033
+ ft: {
14034
+ dimension: "length",
14035
+ factor: .3048,
14036
+ offset: 0
14037
+ },
14038
+ mi: {
14039
+ dimension: "length",
14040
+ factor: 1609.344,
14041
+ offset: 0
14042
+ },
14043
+ "m²": {
14044
+ dimension: "area",
14045
+ factor: 1,
14046
+ offset: 0
14047
+ },
14048
+ lx: {
14049
+ dimension: "illuminance",
14050
+ factor: 1,
14051
+ offset: 0
14052
+ },
14053
+ "W/m²": {
14054
+ dimension: "irradiance",
14055
+ factor: 1,
14056
+ offset: 0
14057
+ },
14058
+ W: {
14059
+ dimension: "power",
14060
+ factor: 1,
14061
+ offset: 0
14062
+ },
14063
+ kW: {
14064
+ dimension: "power",
14065
+ factor: 1e3,
14066
+ offset: 0
14067
+ },
14068
+ VA: {
14069
+ dimension: "apparent-power",
14070
+ factor: 1,
14071
+ offset: 0
14072
+ },
14073
+ Wh: {
14074
+ dimension: "energy",
14075
+ factor: 1,
14076
+ offset: 0
14077
+ },
14078
+ kWh: {
14079
+ dimension: "energy",
14080
+ factor: 1e3,
14081
+ offset: 0
14082
+ },
14083
+ MWh: {
14084
+ dimension: "energy",
14085
+ factor: 1e6,
14086
+ offset: 0
14087
+ },
14088
+ V: {
14089
+ dimension: "voltage",
14090
+ factor: 1,
14091
+ offset: 0
14092
+ },
14093
+ mV: {
14094
+ dimension: "voltage",
14095
+ factor: .001,
14096
+ offset: 0
14097
+ },
14098
+ A: {
14099
+ dimension: "current",
14100
+ factor: 1,
14101
+ offset: 0
14102
+ },
14103
+ mA: {
14104
+ dimension: "current",
14105
+ factor: .001,
14106
+ offset: 0
14107
+ },
14108
+ ppm: {
14109
+ dimension: "concentration-volume",
14110
+ factor: 1,
14111
+ offset: 0
14112
+ },
14113
+ ppb: {
14114
+ dimension: "concentration-volume",
14115
+ factor: .001,
14116
+ offset: 0
14117
+ },
14118
+ "µg/m³": {
14119
+ dimension: "concentration-mass",
14120
+ factor: 1,
14121
+ offset: 0
14122
+ },
14123
+ "mg/m³": {
14124
+ dimension: "concentration-mass",
14125
+ factor: 1e3,
14126
+ offset: 0
14127
+ },
14128
+ kg: {
14129
+ dimension: "mass",
14130
+ factor: 1,
14131
+ offset: 0
14132
+ },
14133
+ g: {
14134
+ dimension: "mass",
14135
+ factor: .001,
14136
+ offset: 0
14137
+ },
14138
+ lb: {
14139
+ dimension: "mass",
14140
+ factor: .453592,
14141
+ offset: 0
14142
+ },
14143
+ oz: {
14144
+ dimension: "mass",
14145
+ factor: .0283495,
14146
+ offset: 0
14147
+ },
14148
+ "%": {
14149
+ dimension: "percentage",
14150
+ factor: 1,
14151
+ offset: 0
14152
+ },
14153
+ L: {
14154
+ dimension: "volume",
14155
+ factor: 1,
14156
+ offset: 0
14157
+ },
14158
+ mL: {
14159
+ dimension: "volume",
14160
+ factor: .001,
14161
+ offset: 0
14162
+ },
14163
+ gal: {
14164
+ dimension: "volume",
14165
+ factor: 3.785411784,
14166
+ offset: 0
14167
+ },
14168
+ s: {
14169
+ dimension: "duration",
14170
+ factor: 1,
14171
+ offset: 0
14172
+ },
14173
+ ms: {
14174
+ dimension: "duration",
14175
+ factor: .001,
14176
+ offset: 0
14177
+ },
14178
+ min: {
14179
+ dimension: "duration",
14180
+ factor: 60,
14181
+ offset: 0
14182
+ },
14183
+ h: {
14184
+ dimension: "duration",
14185
+ factor: 3600,
14186
+ offset: 0
14187
+ },
14188
+ d: {
14189
+ dimension: "duration",
14190
+ factor: 86400,
14191
+ offset: 0
14192
+ },
14193
+ B: {
14194
+ dimension: "information",
14195
+ factor: 1,
14196
+ offset: 0
14197
+ },
14198
+ KiB: {
14199
+ dimension: "information",
14200
+ factor: 1024,
14201
+ offset: 0
14202
+ },
14203
+ MiB: {
14204
+ dimension: "information",
14205
+ factor: 1024 ** 2,
14206
+ offset: 0
14207
+ },
14208
+ GiB: {
14209
+ dimension: "information",
14210
+ factor: 1024 ** 3,
14211
+ offset: 0
14212
+ },
14213
+ TiB: {
14214
+ dimension: "information",
14215
+ factor: 1024 ** 4,
14216
+ offset: 0
14217
+ },
14218
+ kB: {
14219
+ dimension: "information",
14220
+ factor: 1e3,
14221
+ offset: 0
14222
+ },
14223
+ MB: {
14224
+ dimension: "information",
14225
+ factor: 1e6,
14226
+ offset: 0
14227
+ },
14228
+ GB: {
14229
+ dimension: "information",
14230
+ factor: 1e9,
14231
+ offset: 0
14232
+ },
14233
+ "°": {
14234
+ dimension: "angle",
14235
+ factor: 1,
14236
+ offset: 0
14237
+ },
14238
+ dBFS: {
14239
+ dimension: "sound-level",
14240
+ factor: 1,
14241
+ offset: 0
14242
+ },
14243
+ dBm: {
14244
+ dimension: "signal-level",
14245
+ factor: 1,
14246
+ offset: 0
14247
+ },
14248
+ mired: {
14249
+ dimension: "colour-temperature",
14250
+ factor: 1,
14251
+ offset: 0
14252
+ },
14253
+ kbps: {
14254
+ dimension: "bitrate",
14255
+ factor: 1,
14256
+ offset: 0
14257
+ },
14258
+ Mbps: {
14259
+ dimension: "bitrate",
14260
+ factor: 1e3,
14261
+ offset: 0
14262
+ },
14263
+ fps: {
14264
+ dimension: "rate",
14265
+ factor: 1,
14266
+ offset: 0
14267
+ }
14268
+ };
14269
+ /** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
14270
+ function specFor(unit) {
14271
+ return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
14272
+ }
14273
+ /** Value in `spec`'s unit → its dimension's canonical unit. */
14274
+ function toCanonical(value, spec) {
14275
+ return value * spec.factor + spec.offset;
14276
+ }
14277
+ /** Canonical-unit value → the unit described by `spec`. */
14278
+ function fromCanonical(canonical, spec) {
14279
+ return (canonical - spec.offset) / spec.factor;
14280
+ }
14281
+ /**
14282
+ * Graceful variant for the render path: returns the converted value, or
14283
+ * `undefined` when the conversion is refused (unknown unit / cross-dimension).
14284
+ * Never throws — a render caller falls back to the raw value + source unit.
14285
+ */
14286
+ function tryConvertUnit(value, from, to) {
14287
+ if (from === to) return value;
14288
+ const fromSpec = specFor(from);
14289
+ if (!fromSpec) return void 0;
14290
+ const toSpec = specFor(to);
14291
+ if (!toSpec) return void 0;
14292
+ if (fromSpec.dimension !== toSpec.dimension) return void 0;
14293
+ return fromCanonical(toCanonical(value, fromSpec), toSpec);
14294
+ }
14295
+ //#endregion
14025
14296
  //#region ../types/dist/index.mjs
14026
14297
  var SMTP_INBOUND_DEFAULT_PORT = 2525;
14027
14298
  var SMTP_INBOUND_DEFAULT_DOMAIN = "camstack.local";
@@ -15089,43 +15360,44 @@ function notificationTargetParts(records, spec) {
15089
15360
  const defined = spec.kinds[notificationTargetKindName(descriptor.kind)];
15090
15361
  if (defined === void 0) return [];
15091
15362
  const canTest = spec.probe !== void 0 && spec.canProbe(descriptor.kind);
15092
- const instances = new RecordIntegrationInstances(records, defined, {
15093
- description: {
15094
- role: "services",
15095
- instanceMode: "multiple",
15096
- kind: "target",
15097
- canTest
15098
- },
15099
- formSchema: () => formOf(descriptor),
15100
- initialBody: (name) => ({ name }),
15101
- mintId: () => spec.mintId(),
15102
- configured: () => true,
15103
- validateWrite: ({ body }) => spec.validate?.({
15104
- kind: descriptor.kind,
15105
- config: body
15106
- }),
15107
- test: async ({ id, values }) => {
15108
- if (spec.probe === void 0 || !canTest) return {
15109
- success: false,
15110
- status: "unsupported",
15111
- error: "This kind cannot be tested"
15112
- };
15113
- return probeToTestResult(await spec.probe({
15114
- kind: descriptor.kind,
15115
- config: values,
15116
- targetId: id ?? void 0
15117
- }));
15118
- }
15119
- });
15363
+ const kind = {
15364
+ id: descriptor.kind,
15365
+ label: descriptor.label,
15366
+ functionCap: NOTIFICATION_OUTPUT_CAP,
15367
+ role: "services",
15368
+ canTest
15369
+ };
15120
15370
  return [{
15121
- kind: {
15122
- id: descriptor.kind,
15123
- label: descriptor.label,
15124
- functionCap: NOTIFICATION_OUTPUT_CAP,
15125
- role: "services",
15126
- canTest
15127
- },
15128
- provider: instances.toProvider()
15371
+ kind,
15372
+ provider: new RecordIntegrationInstances(records, defined, {
15373
+ description: {
15374
+ role: "services",
15375
+ instanceMode: "multiple",
15376
+ kind: "target",
15377
+ canTest,
15378
+ kinds: [kind]
15379
+ },
15380
+ formSchema: () => formOf(descriptor),
15381
+ initialBody: (name) => ({ name }),
15382
+ mintId: () => spec.mintId(),
15383
+ configured: () => true,
15384
+ validateWrite: ({ body }) => spec.validate?.({
15385
+ kind: descriptor.kind,
15386
+ config: body
15387
+ }),
15388
+ test: async ({ id, values }) => {
15389
+ if (spec.probe === void 0 || !canTest) return {
15390
+ success: false,
15391
+ status: "unsupported",
15392
+ error: "This kind cannot be tested"
15393
+ };
15394
+ return probeToTestResult(await spec.probe({
15395
+ kind: descriptor.kind,
15396
+ config: values,
15397
+ targetId: id ?? void 0
15398
+ }));
15399
+ }
15400
+ }).toProvider()
15129
15401
  }];
15130
15402
  });
15131
15403
  }
@@ -43486,6 +43758,7 @@ var ExpressionBindingSourceSchema = union([
43486
43758
  object({
43487
43759
  kind: literal("global"),
43488
43760
  sourceStableId: string(),
43761
+ addonId: string().optional(),
43489
43762
  cap: string(),
43490
43763
  fieldPath: string()
43491
43764
  })
@@ -43612,385 +43885,6 @@ function extractSourceInfoFromMetadata(metadata) {
43612
43885
  return parsed.data;
43613
43886
  }
43614
43887
  /**
43615
- * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
43616
- * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
43617
- * `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
43618
- * decimals). K derives from `°C = K − 273.15`.
43619
- */
43620
- var UNIT_TABLE = {
43621
- "°C": {
43622
- dimension: "temperature",
43623
- factor: 1,
43624
- offset: 0
43625
- },
43626
- "°F": {
43627
- dimension: "temperature",
43628
- factor: 5 / 9,
43629
- offset: -160 / 9
43630
- },
43631
- K: {
43632
- dimension: "temperature",
43633
- factor: 1,
43634
- offset: -273.15
43635
- },
43636
- hPa: {
43637
- dimension: "pressure",
43638
- factor: 1,
43639
- offset: 0
43640
- },
43641
- kPa: {
43642
- dimension: "pressure",
43643
- factor: 10,
43644
- offset: 0
43645
- },
43646
- Pa: {
43647
- dimension: "pressure",
43648
- factor: .01,
43649
- offset: 0
43650
- },
43651
- mbar: {
43652
- dimension: "pressure",
43653
- factor: 1,
43654
- offset: 0
43655
- },
43656
- bar: {
43657
- dimension: "pressure",
43658
- factor: 1e3,
43659
- offset: 0
43660
- },
43661
- inHg: {
43662
- dimension: "pressure",
43663
- factor: 33.8639,
43664
- offset: 0
43665
- },
43666
- mmHg: {
43667
- dimension: "pressure",
43668
- factor: 1.33322,
43669
- offset: 0
43670
- },
43671
- psi: {
43672
- dimension: "pressure",
43673
- factor: 68.9476,
43674
- offset: 0
43675
- },
43676
- "m/s": {
43677
- dimension: "speed",
43678
- factor: 1,
43679
- offset: 0
43680
- },
43681
- "km/h": {
43682
- dimension: "speed",
43683
- factor: 1 / 3.6,
43684
- offset: 0
43685
- },
43686
- mph: {
43687
- dimension: "speed",
43688
- factor: .44704,
43689
- offset: 0
43690
- },
43691
- kn: {
43692
- dimension: "speed",
43693
- factor: .514444,
43694
- offset: 0
43695
- },
43696
- "mm/h": {
43697
- dimension: "precipitation-rate",
43698
- factor: 1,
43699
- offset: 0
43700
- },
43701
- "in/h": {
43702
- dimension: "precipitation-rate",
43703
- factor: 25.4,
43704
- offset: 0
43705
- },
43706
- m: {
43707
- dimension: "length",
43708
- factor: 1,
43709
- offset: 0
43710
- },
43711
- mm: {
43712
- dimension: "length",
43713
- factor: .001,
43714
- offset: 0
43715
- },
43716
- cm: {
43717
- dimension: "length",
43718
- factor: .01,
43719
- offset: 0
43720
- },
43721
- km: {
43722
- dimension: "length",
43723
- factor: 1e3,
43724
- offset: 0
43725
- },
43726
- in: {
43727
- dimension: "length",
43728
- factor: .0254,
43729
- offset: 0
43730
- },
43731
- ft: {
43732
- dimension: "length",
43733
- factor: .3048,
43734
- offset: 0
43735
- },
43736
- mi: {
43737
- dimension: "length",
43738
- factor: 1609.344,
43739
- offset: 0
43740
- },
43741
- "m²": {
43742
- dimension: "area",
43743
- factor: 1,
43744
- offset: 0
43745
- },
43746
- lx: {
43747
- dimension: "illuminance",
43748
- factor: 1,
43749
- offset: 0
43750
- },
43751
- "W/m²": {
43752
- dimension: "irradiance",
43753
- factor: 1,
43754
- offset: 0
43755
- },
43756
- W: {
43757
- dimension: "power",
43758
- factor: 1,
43759
- offset: 0
43760
- },
43761
- kW: {
43762
- dimension: "power",
43763
- factor: 1e3,
43764
- offset: 0
43765
- },
43766
- VA: {
43767
- dimension: "apparent-power",
43768
- factor: 1,
43769
- offset: 0
43770
- },
43771
- Wh: {
43772
- dimension: "energy",
43773
- factor: 1,
43774
- offset: 0
43775
- },
43776
- kWh: {
43777
- dimension: "energy",
43778
- factor: 1e3,
43779
- offset: 0
43780
- },
43781
- MWh: {
43782
- dimension: "energy",
43783
- factor: 1e6,
43784
- offset: 0
43785
- },
43786
- V: {
43787
- dimension: "voltage",
43788
- factor: 1,
43789
- offset: 0
43790
- },
43791
- mV: {
43792
- dimension: "voltage",
43793
- factor: .001,
43794
- offset: 0
43795
- },
43796
- A: {
43797
- dimension: "current",
43798
- factor: 1,
43799
- offset: 0
43800
- },
43801
- mA: {
43802
- dimension: "current",
43803
- factor: .001,
43804
- offset: 0
43805
- },
43806
- ppm: {
43807
- dimension: "concentration-volume",
43808
- factor: 1,
43809
- offset: 0
43810
- },
43811
- ppb: {
43812
- dimension: "concentration-volume",
43813
- factor: .001,
43814
- offset: 0
43815
- },
43816
- "µg/m³": {
43817
- dimension: "concentration-mass",
43818
- factor: 1,
43819
- offset: 0
43820
- },
43821
- "mg/m³": {
43822
- dimension: "concentration-mass",
43823
- factor: 1e3,
43824
- offset: 0
43825
- },
43826
- kg: {
43827
- dimension: "mass",
43828
- factor: 1,
43829
- offset: 0
43830
- },
43831
- g: {
43832
- dimension: "mass",
43833
- factor: .001,
43834
- offset: 0
43835
- },
43836
- lb: {
43837
- dimension: "mass",
43838
- factor: .453592,
43839
- offset: 0
43840
- },
43841
- oz: {
43842
- dimension: "mass",
43843
- factor: .0283495,
43844
- offset: 0
43845
- },
43846
- "%": {
43847
- dimension: "percentage",
43848
- factor: 1,
43849
- offset: 0
43850
- },
43851
- L: {
43852
- dimension: "volume",
43853
- factor: 1,
43854
- offset: 0
43855
- },
43856
- mL: {
43857
- dimension: "volume",
43858
- factor: .001,
43859
- offset: 0
43860
- },
43861
- gal: {
43862
- dimension: "volume",
43863
- factor: 3.785411784,
43864
- offset: 0
43865
- },
43866
- s: {
43867
- dimension: "duration",
43868
- factor: 1,
43869
- offset: 0
43870
- },
43871
- ms: {
43872
- dimension: "duration",
43873
- factor: .001,
43874
- offset: 0
43875
- },
43876
- min: {
43877
- dimension: "duration",
43878
- factor: 60,
43879
- offset: 0
43880
- },
43881
- h: {
43882
- dimension: "duration",
43883
- factor: 3600,
43884
- offset: 0
43885
- },
43886
- d: {
43887
- dimension: "duration",
43888
- factor: 86400,
43889
- offset: 0
43890
- },
43891
- B: {
43892
- dimension: "information",
43893
- factor: 1,
43894
- offset: 0
43895
- },
43896
- KiB: {
43897
- dimension: "information",
43898
- factor: 1024,
43899
- offset: 0
43900
- },
43901
- MiB: {
43902
- dimension: "information",
43903
- factor: 1024 ** 2,
43904
- offset: 0
43905
- },
43906
- GiB: {
43907
- dimension: "information",
43908
- factor: 1024 ** 3,
43909
- offset: 0
43910
- },
43911
- TiB: {
43912
- dimension: "information",
43913
- factor: 1024 ** 4,
43914
- offset: 0
43915
- },
43916
- kB: {
43917
- dimension: "information",
43918
- factor: 1e3,
43919
- offset: 0
43920
- },
43921
- MB: {
43922
- dimension: "information",
43923
- factor: 1e6,
43924
- offset: 0
43925
- },
43926
- GB: {
43927
- dimension: "information",
43928
- factor: 1e9,
43929
- offset: 0
43930
- },
43931
- "°": {
43932
- dimension: "angle",
43933
- factor: 1,
43934
- offset: 0
43935
- },
43936
- dBFS: {
43937
- dimension: "sound-level",
43938
- factor: 1,
43939
- offset: 0
43940
- },
43941
- dBm: {
43942
- dimension: "signal-level",
43943
- factor: 1,
43944
- offset: 0
43945
- },
43946
- mired: {
43947
- dimension: "colour-temperature",
43948
- factor: 1,
43949
- offset: 0
43950
- },
43951
- kbps: {
43952
- dimension: "bitrate",
43953
- factor: 1,
43954
- offset: 0
43955
- },
43956
- Mbps: {
43957
- dimension: "bitrate",
43958
- factor: 1e3,
43959
- offset: 0
43960
- },
43961
- fps: {
43962
- dimension: "rate",
43963
- factor: 1,
43964
- offset: 0
43965
- }
43966
- };
43967
- /** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
43968
- function specFor(unit) {
43969
- return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
43970
- }
43971
- /** Value in `spec`'s unit → its dimension's canonical unit. */
43972
- function toCanonical(value, spec) {
43973
- return value * spec.factor + spec.offset;
43974
- }
43975
- /** Canonical-unit value → the unit described by `spec`. */
43976
- function fromCanonical(canonical, spec) {
43977
- return (canonical - spec.offset) / spec.factor;
43978
- }
43979
- /**
43980
- * Graceful variant for the render path: returns the converted value, or
43981
- * `undefined` when the conversion is refused (unknown unit / cross-dimension).
43982
- * Never throws — a render caller falls back to the raw value + source unit.
43983
- */
43984
- function tryConvertUnit(value, from, to) {
43985
- if (from === to) return value;
43986
- const fromSpec = specFor(from);
43987
- if (!fromSpec) return void 0;
43988
- const toSpec = specFor(to);
43989
- if (!toSpec) return void 0;
43990
- if (fromSpec.dimension !== toSpec.dimension) return void 0;
43991
- return fromCanonical(toCanonical(value, fromSpec), toSpec);
43992
- }
43993
- /**
43994
43888
  * A number a person types for a physical quantity — the input side of the
43995
43889
  * value + unit contract (D972).
43996
43890
  *
@@ -45688,26 +45582,23 @@ var SettingsSchemaWithValuesSchema = object({
45688
45582
  var SettingsPatchSchema = record(string(), unknown());
45689
45583
  /** The scope of a scoped-settings call (D986): where the capability declared the values. */
45690
45584
  var ScopedSettingsScopeSchema = _enum(SCOPED_SETTINGS_SCOPES);
45585
+ /** One scoped schema an addon serves: its capability, scope and section ids. */
45586
+ var ScopedSettingsDeclarationSchema = object({
45587
+ cap: string(),
45588
+ scope: ScopedSettingsScopeSchema,
45589
+ sectionIds: array(string())
45590
+ });
45691
45591
  /** Standard success response for update operations. */
45692
45592
  var SettingsUpdateResultSchema = object({ success: literal(true) });
45693
45593
  method(object({
45694
- addonId: string(),
45695
- nodeId: string().optional(),
45696
- overlay: record(string(), unknown()).optional(),
45697
- cap: string().optional()
45698
- }), SettingsSchemaWithValuesSchema.nullable()), method(object({
45699
- addonId: string(),
45700
- nodeId: string().optional(),
45701
- patch: SettingsPatchSchema
45702
- }), SettingsUpdateResultSchema, {
45703
- kind: "mutation",
45704
- auth: "admin"
45705
- }), method(object({
45706
45594
  addonId: string(),
45707
45595
  cap: string(),
45708
45596
  scope: ScopedSettingsScopeSchema,
45709
45597
  nodeId: string().optional()
45710
45598
  }), SettingsSchemaWithValuesSchema.nullable()), method(object({
45599
+ addonId: string(),
45600
+ nodeId: string().optional()
45601
+ }), array(ScopedSettingsDeclarationSchema).nullable()), method(object({
45711
45602
  addonId: string(),
45712
45603
  cap: string(),
45713
45604
  scope: ScopedSettingsScopeSchema,
@@ -45722,9 +45613,6 @@ method(object({
45722
45613
  scope: ScopedSettingsScopeSchema,
45723
45614
  nodeId: string().optional()
45724
45615
  }), record(string(), unknown()).nullable()), method(object({
45725
- addonId: string(),
45726
- nodeId: string().optional()
45727
- }), SettingsSchemaWithValuesSchema.nullable()), method(object({
45728
45616
  addonId: string(),
45729
45617
  nodeId: string().optional(),
45730
45618
  instance: IntegrationInstanceRefSchema
@@ -47287,7 +47175,7 @@ var BackupDestinationInfoSchema = object({
47287
47175
  id: string(),
47288
47176
  /**
47289
47177
  * Manifest id of the owning addon. The Settings button on the
47290
- * destination card opens that addon's `globalSettingsSchema` panel.
47178
+ * destination card opens that addon's scoped settings panel.
47291
47179
  */
47292
47180
  addonId: string(),
47293
47181
  /** Operator-toggled enable flag from the destination-policy table. */
@@ -50746,7 +50634,7 @@ var mediaPlayerCapability = {
50746
50634
  isMuted: { kind: "boolean" }
50747
50635
  } }
50748
50636
  };
50749
- 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;
50637
+ 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, DeviceType.MediaPlayer;
50750
50638
  var NUMBER = {
50751
50639
  kind: "fixed",
50752
50640
  result: ["number"]
@@ -52764,6 +52652,7 @@ method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(Expor
52764
52652
  kind: "mutation",
52765
52653
  auth: "admin"
52766
52654
  });
52655
+ var DeviceAliasSchema = string().max(64).regex(/^[a-z][a-z0-9_]*$/);
52767
52656
  /**
52768
52657
  * A SITE ZONE — a named part of the property that several locations belong to.
52769
52658
  *
@@ -53250,6 +53139,12 @@ var DeviceInfoSchema = object({
53250
53139
  addonId: string(),
53251
53140
  type: _enum(DeviceType),
53252
53141
  name: string(),
53142
+ /** The name a value template uses for this device (`{{ stufa_veranda.temperature }}`),
53143
+ * entity_id style. Generated once from the name at first registration, unique
53144
+ * cluster-wide, editable through `setAlias`, NOT changed by a rename (D1021).
53145
+ * Optional: a record produced by an older build omits it until the boot
53146
+ * backfill has run. */
53147
+ alias: DeviceAliasSchema.optional(),
53253
53148
  /** Operator-organisational location label. `null` when unset. */
53254
53149
  location: string().nullable(),
53255
53150
  /** Soft-disabled flag. */
@@ -53569,6 +53464,25 @@ method(object({
53569
53464
  }), _void(), {
53570
53465
  kind: "mutation",
53571
53466
  auth: "admin"
53467
+ }), method(object({
53468
+ deviceId: number$1(),
53469
+ alias: string()
53470
+ }), discriminatedUnion("ok", [object({
53471
+ ok: literal(true),
53472
+ alias: DeviceAliasSchema
53473
+ }), object({
53474
+ ok: literal(false),
53475
+ reason: _enum([
53476
+ "empty",
53477
+ "too-long",
53478
+ "grammar",
53479
+ "reserved",
53480
+ "taken"
53481
+ ]),
53482
+ takenBy: number$1().optional()
53483
+ })]), {
53484
+ kind: "mutation",
53485
+ auth: "admin"
53572
53486
  }), method(object({
53573
53487
  deviceId: number$1(),
53574
53488
  location: string().nullable()
@@ -57374,10 +57288,11 @@ var IntegrationInstanceDescriptionSchema = object({
57374
57288
  /** True when `testInstance` really probes the remote. */
57375
57289
  canTest: boolean(),
57376
57290
  /**
57377
- * The kinds an instance can be of. Absent or one entry: nothing to choose; more than one: the
57378
- * Add flow asks which first and sends it as `createInstance.kind`.
57291
+ * The kinds an instance can be of (D1013 rule 1), each naming its `functionCap`. One entry:
57292
+ * nothing to choose; more than one: the Add flow asks which first and sends it as
57293
+ * `createInstance.kind`. Never empty: every instance belongs to exactly one kind.
57379
57294
  */
57380
- kinds: array(IntegrationInstanceKindSchema).optional(),
57295
+ kinds: array(IntegrationInstanceKindSchema).min(1),
57381
57296
  /**
57382
57297
  * The provider accepts `createInstance.id` for an id that once existed (an adopted id, because
57383
57298
  * devices carry it). The UI then offers "restore under its old id" on Add. Absent means no.
@@ -57413,8 +57328,8 @@ var IntegrationInstanceSchema = object({
57413
57328
  * the body is the one store, `setInstanceEnabled` the one writer (epic #440, step 3).
57414
57329
  */
57415
57330
  enabled: boolean().optional(),
57416
- /** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
57417
- kind: string().optional(),
57331
+ /** The kind of this instance: exactly one of `describe().kinds[].id` (D1013 rule 2). */
57332
+ kind: string().min(1),
57418
57333
  /** Values for the instance form; a secret is a `SecretFieldView`, never text. */
57419
57334
  formValues: record(string(), unknown()),
57420
57335
  status: IntegrationInstanceStatusSchema.optional(),
@@ -67669,6 +67584,11 @@ var NcScheduleSchema = object({
67669
67584
  * as a STATE SUMMARY at the end of each window of `schedule`.
67670
67585
  */
67671
67586
  var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
67587
+ _enum([
67588
+ "activation",
67589
+ "deactivation",
67590
+ "schedule"
67591
+ ]);
67672
67592
  /**
67673
67593
  * How a summary window is SENT (D698): `rule` (absent) = ONE notification with
67674
67594
  * every section; `section` = one notification per section with something to
@@ -67685,11 +67605,26 @@ var NcStateSummarySplitSchema = _enum(["rule", "section"]);
67685
67605
  * midnight crossing); the send time is each window's END. Refused on the
67686
67606
  * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
67687
67607
  */
67688
- var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
67689
- mode: literal("summary"),
67690
- schedule: NcScheduleSchema,
67691
- split: NcStateSummarySplitSchema.optional()
67692
- })]);
67608
+ var NcSystemEventDeliverySchema = discriminatedUnion("mode", [
67609
+ object({ mode: literal("instant") }),
67610
+ object({
67611
+ mode: literal("summary"),
67612
+ schedule: NcScheduleSchema,
67613
+ split: NcStateSummarySplitSchema.optional()
67614
+ }),
67615
+ object({
67616
+ mode: literal("modes"),
67617
+ /** The bad edge, and every kind that has a single edge. */
67618
+ onActivation: boolean(),
67619
+ /** The recovery edges of the classes of the selected kinds (derived, never stored per kind). */
67620
+ onDeactivation: boolean(),
67621
+ /** A state summary at the end of each window; absent = no summary. */
67622
+ onSchedule: object({
67623
+ schedule: NcScheduleSchema,
67624
+ grouping: NcStateSummarySplitSchema
67625
+ }).optional()
67626
+ })
67627
+ ]);
67693
67628
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
67694
67629
  var NcPlateMatcherSchema = object({
67695
67630
  /**
@@ -75763,6 +75698,16 @@ var RemoteLayoutComponentSchema = object({
75763
75698
  method(object({}), array(RemoteLayoutComponentSchema), { auth: "protected" });
75764
75699
  var RUNNING_POWER_START_DELAY_MS = 3e4;
75765
75700
  var RUNNING_POWER_STOP_DELAY_MS = 18e4;
75701
+ /**
75702
+ * Per-type overrides of the dwell defaults. A media player draws power the
75703
+ * moment it is on and stops drawing the moment it is off, so it needs neither
75704
+ * a long dwell nor a second sample. A type absent here keeps the global values.
75705
+ */
75706
+ var RUNNING_POWER_DEFAULTS_BY_TYPE = { [DeviceType.MediaPlayer]: {
75707
+ startDwellMs: 1e4,
75708
+ stopDwellMs: 1e4,
75709
+ minSamples: 1
75710
+ } };
75766
75711
  var RunningStateSourceSchema = _enum(["native", "assigned"]);
75767
75712
  var RunningStateStatusSchema = object({
75768
75713
  /** null = unknown — never coerced to false (D393). */
@@ -75780,93 +75725,27 @@ var RunningFlipEventDataSchema = object({
75780
75725
  phase: string().nullable()
75781
75726
  });
75782
75727
  var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
75783
- /** Generic Customize target for the running boolean (D936). */
75784
- var runningCustomize = {
75785
- version: 1,
75786
- overridable: [{
75787
- path: "running",
75788
- by: "running"
75789
- }],
75790
- targets: [{
75791
- id: "running",
75792
- cardinality: "scalar",
75793
- vocabulary: {
75794
- cap: "running-state",
75795
- stateField: "running"
75796
- },
75797
- deviceTypes: [
75798
- DeviceType.Washer,
75799
- DeviceType.Dryer,
75800
- DeviceType.Dishwasher,
75801
- DeviceType.Vacuum,
75802
- DeviceType.LawnMower,
75803
- DeviceType.MediaPlayer,
75804
- DeviceType.WaterHeater,
75805
- DeviceType.Humidifier,
75806
- DeviceType.Fan,
75807
- DeviceType.Climate,
75808
- DeviceType.Thermostat,
75809
- DeviceType.PetFeeder,
75810
- DeviceType.Heater
75811
- ],
75812
- apply: "add",
75813
- path: "running",
75814
- sources: [
75815
- {
75816
- kind: "switch",
75817
- transforms: [{ kind: "identity" }]
75818
- },
75819
- {
75820
- kind: "binary",
75821
- transforms: [{ kind: "identity" }, { kind: "invert" }]
75822
- },
75823
- {
75824
- kind: "numeric",
75825
- requiredUnit: "W",
75826
- transforms: [{
75827
- kind: "threshold",
75828
- comparisons: ["gte", "lte"],
75829
- comparison: "gte",
75830
- defaultThreshold: 5,
75831
- defaultStartDwellMs: RUNNING_POWER_START_DELAY_MS,
75832
- defaultStopDwellMs: RUNNING_POWER_STOP_DELAY_MS,
75833
- thresholdUnit: "W"
75834
- }]
75835
- }
75836
- ],
75837
- companions: [
75838
- {
75839
- kind: "constant",
75840
- path: "source",
75841
- value: "assigned",
75842
- when: "always"
75843
- },
75844
- {
75845
- kind: "constant",
75846
- path: "phase",
75847
- value: null,
75848
- when: "add"
75849
- },
75850
- {
75851
- kind: "clock",
75852
- path: "lastFetchedAt",
75853
- when: "always"
75854
- },
75855
- {
75856
- kind: "transition-time",
75857
- path: "since",
75858
- sourceChangedAt: "prefer"
75859
- }
75860
- ],
75861
- sourceReport: {
75862
- status: "assignment-status",
75863
- runtimeField: "source",
75864
- nativeValue: "native",
75865
- assignedValue: "assigned"
75866
- },
75867
- advancedExpression: true
75868
- }]
75869
- };
75728
+ /**
75729
+ * Customize offers "running" only where a job makes sense (D933): a cycle,
75730
+ * a clean, a mow, playback, or another appliance that is on and working.
75731
+ * The cap itself stays on every type — a camera can still report running
75732
+ * natively; it just has no card.
75733
+ */
75734
+ var RUNNING_CUSTOMIZE_DEVICE_TYPES = [
75735
+ DeviceType.Washer,
75736
+ DeviceType.Dryer,
75737
+ DeviceType.Dishwasher,
75738
+ DeviceType.Vacuum,
75739
+ DeviceType.LawnMower,
75740
+ DeviceType.MediaPlayer,
75741
+ DeviceType.WaterHeater,
75742
+ DeviceType.Humidifier,
75743
+ DeviceType.Fan,
75744
+ DeviceType.Climate,
75745
+ DeviceType.Thermostat,
75746
+ DeviceType.PetFeeder,
75747
+ DeviceType.Heater
75748
+ ];
75870
75749
  var runningStateCapability = {
75871
75750
  name: "running-state",
75872
75751
  scope: "device",
@@ -75886,7 +75765,79 @@ var runningStateCapability = {
75886
75765
  durability: "restored",
75887
75766
  history: { fields: { running: { kind: "boolean" } } },
75888
75767
  volatileStateFields: ["lastFetchedAt"],
75889
- customize: runningCustomize
75768
+ customize: {
75769
+ version: 1,
75770
+ overridable: [{
75771
+ path: "running",
75772
+ by: "running"
75773
+ }],
75774
+ targets: [{
75775
+ id: "running",
75776
+ cardinality: "scalar",
75777
+ vocabulary: {
75778
+ cap: "running-state",
75779
+ stateField: "running"
75780
+ },
75781
+ deviceTypes: RUNNING_CUSTOMIZE_DEVICE_TYPES,
75782
+ apply: "add",
75783
+ path: "running",
75784
+ sources: [
75785
+ {
75786
+ kind: "switch",
75787
+ transforms: [{ kind: "identity" }]
75788
+ },
75789
+ {
75790
+ kind: "binary",
75791
+ transforms: [{ kind: "identity" }, { kind: "invert" }]
75792
+ },
75793
+ {
75794
+ kind: "numeric",
75795
+ requiredUnit: "W",
75796
+ transforms: [{
75797
+ kind: "threshold",
75798
+ comparisons: ["gte", "lte"],
75799
+ comparison: "gte",
75800
+ defaultThreshold: 5,
75801
+ defaultStartDwellMs: RUNNING_POWER_START_DELAY_MS,
75802
+ defaultStopDwellMs: RUNNING_POWER_STOP_DELAY_MS,
75803
+ deviceTypeDefaults: RUNNING_POWER_DEFAULTS_BY_TYPE,
75804
+ thresholdUnit: "W"
75805
+ }]
75806
+ }
75807
+ ],
75808
+ companions: [
75809
+ {
75810
+ kind: "constant",
75811
+ path: "source",
75812
+ value: "assigned",
75813
+ when: "always"
75814
+ },
75815
+ {
75816
+ kind: "constant",
75817
+ path: "phase",
75818
+ value: null,
75819
+ when: "add"
75820
+ },
75821
+ {
75822
+ kind: "clock",
75823
+ path: "lastFetchedAt",
75824
+ when: "always"
75825
+ },
75826
+ {
75827
+ kind: "transition-time",
75828
+ path: "since",
75829
+ sourceChangedAt: "prefer"
75830
+ }
75831
+ ],
75832
+ sourceReport: {
75833
+ status: "assignment-status",
75834
+ runtimeField: "source",
75835
+ nativeValue: "native",
75836
+ assignedValue: "assigned"
75837
+ },
75838
+ advancedExpression: true
75839
+ }]
75840
+ }
75890
75841
  };
75891
75842
  /**
75892
75843
  * scene-monitor — device-scoped reference-region state cap. An operator marks
@@ -78820,6 +78771,11 @@ var TimedRunUnitSchema = _enum(["s", "min"]);
78820
78771
  _enum(["switch", "pet-fountain"]);
78821
78772
  DeviceType.Heater, DeviceType.PetFountain;
78822
78773
  var TIMED_RUN_DEVICE_TYPES = [DeviceType.Heater, DeviceType.PetFountain];
78774
+ /** One duration, as the one-tap run asks for it. */
78775
+ var TimedRunQuickRunSchema = object({
78776
+ duration: number$1().positive().finite(),
78777
+ unit: TimedRunUnitSchema
78778
+ });
78823
78779
  /** Why a request was not carried out. Typed, so a caller can say it in words. */
78824
78780
  var TimedRunRefusalSchema = _enum([
78825
78781
  "unsupported-device",
@@ -78840,8 +78796,23 @@ var TimedRunStatusSchema = object({
78840
78796
  activeUntil: number$1().nullable(),
78841
78797
  /** Who asked for the run (a user id or an addon id); `null` when nothing is armed or it did not say. */
78842
78798
  requestedBy: string().nullable(),
78799
+ /**
78800
+ * The one-tap run of THIS device: what a "Run" button on any surface starts. `null` when
78801
+ * the device is gone or its type has no timed run; a status from before this field
78802
+ * existed has none, and a surface falls back to the type's default.
78803
+ */
78804
+ quickRun: TimedRunQuickRunSchema.nullable(),
78843
78805
  lastFetchedAt: number$1()
78844
78806
  });
78807
+ /** What the settings form needs: the unit, the bounds and the type's default. */
78808
+ var TimedRunOptionsSchema = object({
78809
+ unit: TimedRunUnitSchema,
78810
+ /** The longest run, in `unit`. */
78811
+ maxDuration: number$1(),
78812
+ defaultDuration: number$1()
78813
+ });
78814
+ /** The one-tap run duration, in the type's unit; `null` returns to the type's default. */
78815
+ var TimedRunSettingsPatchSchema = object({ quickRunDuration: number$1().positive().finite().nullable().optional() });
78845
78816
  var timedRunCapability = {
78846
78817
  name: "timed-run",
78847
78818
  scope: "device",
@@ -78849,7 +78820,24 @@ var timedRunCapability = {
78849
78820
  kind: "wrapper",
78850
78821
  defaultActive: true,
78851
78822
  deviceTypes: TIMED_RUN_DEVICE_TYPES,
78823
+ deviceConfig: { ui: {
78824
+ kind: "derived-form",
78825
+ builderId: "timed-run",
78826
+ tab: "general"
78827
+ } },
78852
78828
  methods: {
78829
+ getOptions: method(object({ deviceId: number$1().int() }), TimedRunOptionsSchema),
78830
+ /**
78831
+ * The one-tap run of this device. A duration above the type's maximum is
78832
+ * REFUSED (thrown), like `runFor`: the maximum is a safety bound.
78833
+ */
78834
+ setSettings: method(object({
78835
+ deviceId: number$1().int(),
78836
+ settings: TimedRunSettingsPatchSchema
78837
+ }), _void(), {
78838
+ kind: "mutation",
78839
+ auth: "admin"
78840
+ }),
78853
78841
  /**
78854
78842
  * Turn the device on and arm its off. A run already armed is replaced (the
78855
78843
  * new deadline wins). Refuses with a typed reason above the type's maximum.
@@ -83550,13 +83538,6 @@ Object.freeze({
83550
83538
  access: "view",
83551
83539
  role: "admin"
83552
83540
  },
83553
- "addonSettings.getGlobalSettings": {
83554
- capName: "addon-settings",
83555
- capScope: "system",
83556
- addonId: null,
83557
- access: "view",
83558
- role: "admin"
83559
- },
83560
83541
  "addonSettings.getIntegrationFormSchema": {
83561
83542
  capName: "addon-settings",
83562
83543
  capScope: "system",
@@ -83564,14 +83545,14 @@ Object.freeze({
83564
83545
  access: "view",
83565
83546
  role: "admin"
83566
83547
  },
83567
- "addonSettings.getIntegrationSettings": {
83548
+ "addonSettings.getScopedSettings": {
83568
83549
  capName: "addon-settings",
83569
83550
  capScope: "system",
83570
83551
  addonId: null,
83571
83552
  access: "view",
83572
83553
  role: "admin"
83573
83554
  },
83574
- "addonSettings.getScopedSettings": {
83555
+ "addonSettings.listScopedSettings": {
83575
83556
  capName: "addon-settings",
83576
83557
  capScope: "system",
83577
83558
  addonId: null,
@@ -83599,13 +83580,6 @@ Object.freeze({
83599
83580
  access: "create",
83600
83581
  role: "admin"
83601
83582
  },
83602
- "addonSettings.updateGlobalSettings": {
83603
- capName: "addon-settings",
83604
- capScope: "system",
83605
- addonId: null,
83606
- access: "create",
83607
- role: "admin"
83608
- },
83609
83583
  "addonSettings.updateScopedSettings": {
83610
83584
  capName: "addon-settings",
83611
83585
  capScope: "system",
@@ -85776,6 +85750,13 @@ Object.freeze({
85776
85750
  access: "create",
85777
85751
  role: "admin"
85778
85752
  },
85753
+ "deviceManager.setAlias": {
85754
+ capName: "device-manager",
85755
+ capScope: "system",
85756
+ addonId: null,
85757
+ access: "create",
85758
+ role: "admin"
85759
+ },
85779
85760
  "deviceManager.setChildLayout": {
85780
85761
  capName: "device-manager",
85781
85762
  capScope: "system",
@@ -92559,6 +92540,13 @@ Object.freeze({
92559
92540
  access: "create",
92560
92541
  role: "viewer"
92561
92542
  },
92543
+ "timedRun.getOptions": {
92544
+ capName: "timed-run",
92545
+ capScope: "device",
92546
+ addonId: null,
92547
+ access: "view",
92548
+ role: "viewer"
92549
+ },
92562
92550
  "timedRun.getStatus": {
92563
92551
  capName: "timed-run",
92564
92552
  capScope: "device",
@@ -92573,6 +92561,13 @@ Object.freeze({
92573
92561
  access: "create",
92574
92562
  role: "viewer"
92575
92563
  },
92564
+ "timedRun.setSettings": {
92565
+ capName: "timed-run",
92566
+ capScope: "device",
92567
+ addonId: null,
92568
+ access: "create",
92569
+ role: "admin"
92570
+ },
92576
92571
  "toast.onToast": {
92577
92572
  capName: "toast",
92578
92573
  capScope: "system",
@@ -93503,6 +93498,7 @@ function offered(received) {
93503
93498
  };
93504
93499
  }
93505
93500
  offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
93501
+ string().min(1).max(4096);
93506
93502
  Object.freeze({
93507
93503
  "accessories.getStatus": [{
93508
93504
  name: "deviceId",
@@ -94186,6 +94182,11 @@ Object.freeze({
94186
94182
  form: "single",
94187
94183
  optional: false
94188
94184
  }],
94185
+ "deviceManager.setAlias": [{
94186
+ name: "deviceId",
94187
+ form: "single",
94188
+ optional: false
94189
+ }],
94189
94190
  "deviceManager.setChildLayout": [{
94190
94191
  name: "deviceId",
94191
94192
  form: "single",
@@ -96250,6 +96251,11 @@ Object.freeze({
96250
96251
  form: "single",
96251
96252
  optional: false
96252
96253
  }],
96254
+ "timedRun.getOptions": [{
96255
+ name: "deviceId",
96256
+ form: "single",
96257
+ optional: false
96258
+ }],
96253
96259
  "timedRun.getStatus": [{
96254
96260
  name: "deviceId",
96255
96261
  form: "single",
@@ -96260,6 +96266,11 @@ Object.freeze({
96260
96266
  form: "single",
96261
96267
  optional: false
96262
96268
  }],
96269
+ "timedRun.setSettings": [{
96270
+ name: "deviceId",
96271
+ form: "single",
96272
+ optional: false
96273
+ }],
96263
96274
  "update.getStatus": [{
96264
96275
  name: "deviceId",
96265
96276
  form: "single",
@@ -97329,6 +97340,7 @@ var STATE_SUMMARY_VARS = [
97329
97340
  [...RULE_TEXT],
97330
97341
  [...RULE_TEXT],
97331
97342
  [...RULE_TEXT],
97343
+ [...RULE_TEXT],
97332
97344
  [...RULE_BODY_ONLY],
97333
97345
  [...RULE_TEXT],
97334
97346
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
@@ -145192,7 +145204,14 @@ function buildAlexaAccountInstances(records, deps) {
145192
145204
  instanceMode: "multiple",
145193
145205
  kind: "account",
145194
145206
  canTest: true,
145195
- allowRestoreId: true
145207
+ allowRestoreId: true,
145208
+ kinds: [{
145209
+ id: ALEXA_ACCOUNT_KIND,
145210
+ label: "Alexa account",
145211
+ functionCap: "broker",
145212
+ role: "services",
145213
+ brokerKind: ALEXA_BROKER_KIND
145214
+ }]
145196
145215
  },
145197
145216
  formSchema: () => buildConnectionFormSchema(),
145198
145217
  initialBody: (name) => ({
@@ -145246,7 +145265,13 @@ function buildAlexaInstances(records) {
145246
145265
  role: "devices",
145247
145266
  instanceMode: "multiple",
145248
145267
  kind: "connection",
145249
- canTest: false
145268
+ canTest: false,
145269
+ kinds: [{
145270
+ id: "integration",
145271
+ label: "Alexa",
145272
+ functionCap: "device-provider",
145273
+ role: "devices"
145274
+ }]
145250
145275
  },
145251
145276
  labels: { brokerId: { label: "Alexa account" } }
145252
145277
  });