@camstack/addon-mqtt-broker 1.2.155 → 1.2.156

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.
@@ -39,7 +39,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
40
40
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
41
41
  //#endregion
42
- //#region ../types/dist/event-category-BmPm98n0.mjs
42
+ //#region ../types/dist/event-category-78tcBemo.mjs
43
43
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
44
44
  EventCategory["SystemBoot"] = "system.boot";
45
45
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -160,6 +160,23 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
160
160
  */
161
161
  EventCategory["AddonLoadRecovered"] = "addon.load-recovered";
162
162
  /**
163
+ * An addon is RUNNING although its installed package is below what the
164
+ * running server requires (`camstack.requires.addons` of the server, or the
165
+ * addon's own `requires.server`). Raised once per streak by the unsupported-
166
+ * addon tracker, on the hub and on agents; AlertCenter turns it into one
167
+ * operator alert per node and package. The addon is never held back for it.
168
+ *
169
+ * Payload: `{ packageName, addonId, nodeId, installedVersion, requiredMinimum, serverVersion, unmet }`.
170
+ */
171
+ EventCategory["AddonUnsupportedVersion"] = "addon.unsupported-version";
172
+ /**
173
+ * The package of an {@link AddonUnsupportedVersion} alert reached a supported
174
+ * version, or is no longer installed on the node. AlertCenter dismisses the matching alert.
175
+ *
176
+ * Payload: `{ packageName, addonId, nodeId }`.
177
+ */
178
+ EventCategory["AddonUnsupportedResolved"] = "addon.unsupported-resolved";
179
+ /**
163
180
  * Monitor scheduled the next retry for a failed addon. Transient —
164
181
  * surfaced to the UI for live-updating the "next retry in Ns"
165
182
  * countdown on the Addons page row, NOT persisted as an alert.
@@ -270,7 +287,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
270
287
  EventCategory["ProcessRestarted"] = "process.restarted";
271
288
  /**
272
289
  * The SET of storage locations changed — one was created, edited, enabled,
273
- * disabled or deleted through `storage.upsertLocation` / `deleteLocation`.
290
+ * disabled or deleted through the location instances or the forced-delete action.
274
291
  *
275
292
  * Telemetry, not a transaction (D8/D11): every consumer that re-resolves on
276
293
  * it must also converge on its own periodic path, because a dropped event
@@ -825,7 +842,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
825
842
  return EventCategory;
826
843
  }({});
827
844
  //#endregion
828
- //#region ../types/dist/device-type-D1fH5kaC.mjs
845
+ //#region ../types/dist/device-type-DxCT0krO.mjs
829
846
  /**
830
847
  import { errMsg } from '@camstack/types'
831
848
  * Extract a human-readable message from an unknown error value.
@@ -987,6 +1004,13 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
987
1004
  * filters that want "all devices with on-motion behaviour".
988
1005
  */
989
1006
  DeviceFeature["MotionTrigger"] = "motion-trigger";
1007
+ /**
1008
+ * Device can be run for a chosen time and then stopped. Mirrors the
1009
+ * `timed-run` wrapper cap — the cap is bound by the core-blocks addon, not by
1010
+ * a driver, so the flag is derived from the device's bindings
1011
+ * (`featuresOfBoundCaps`). Gates the quick-run control in the viewer.
1012
+ */
1013
+ DeviceFeature["TimedRun"] = "timed-run";
990
1014
  /** Light supports rgb-triplet color via `color` cap. */
991
1015
  DeviceFeature["LightColorRgb"] = "light-color-rgb";
992
1016
  /** Light supports HSV color via `color` cap. */
@@ -6021,7 +6045,7 @@ var ZodIssueCode = {
6021
6045
  var ZodFirstPartyTypeKind;
6022
6046
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6023
6047
  //#endregion
6024
- //#region ../types/dist/sleep-D7ZKm4nb.mjs
6048
+ //#region ../types/dist/sleep-7El4r5Bl.mjs
6025
6049
  /**
6026
6050
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6027
6051
  * window to float samples (D455).
@@ -6385,6 +6409,59 @@ Object.fromEntries([
6385
6409
  function isValuelessField(field) {
6386
6410
  return field.type === "separator" || field.type === "info" || field.type === "qr-code" || field.type === "button" || field.type === "object-array" || field.type === "widget" || field.type === "addon-action-button" || field.type === "device-action-button" || field.type === "account-sign-in" || field.type === "secret-presence";
6387
6411
  }
6412
+ /**
6413
+ * Base collector: flatten a hydrated `ConfigUISchemaWithValues` into
6414
+ * `key → { value, default }`, recursing through `group` and `sub-tabs`
6415
+ * layout fields. Keeping `value` and `default` SEPARATE (rather than
6416
+ * pre-resolving) lets callers both read the effective value AND detect
6417
+ * overrides (`value` differs from `default`). Structural over `unknown`;
6418
+ * malformed input yields an empty map, never throws.
6419
+ */
6420
+ function collectHydratedFieldEntries(schema) {
6421
+ const out = /* @__PURE__ */ new Map();
6422
+ if (typeof schema !== "object" || schema === null) return out;
6423
+ const sections = schema.sections;
6424
+ if (!Array.isArray(sections)) return out;
6425
+ for (const section of sections) if (typeof section === "object" && section !== null) collectFieldEntriesInto(section.fields, out);
6426
+ return out;
6427
+ }
6428
+ /** Recursive field walker for {@link collectHydratedFieldEntries}. */
6429
+ function collectFieldEntriesInto(fields, into) {
6430
+ if (!Array.isArray(fields)) return;
6431
+ for (const field of fields) {
6432
+ if (typeof field !== "object" || field === null) continue;
6433
+ const type = field.type;
6434
+ if (type === "group") {
6435
+ collectFieldEntriesInto(field.fields, into);
6436
+ continue;
6437
+ }
6438
+ if (type === "sub-tabs") {
6439
+ const tabs = field.tabs;
6440
+ if (Array.isArray(tabs)) {
6441
+ for (const tab of tabs) if (typeof tab === "object" && tab !== null) collectFieldEntriesInto(tab.fields, into);
6442
+ }
6443
+ continue;
6444
+ }
6445
+ const key = field.key;
6446
+ if (typeof key !== "string" || key.length === 0) continue;
6447
+ if (!("value" in field)) continue;
6448
+ into.set(key, {
6449
+ value: field.value,
6450
+ default: field.default
6451
+ });
6452
+ }
6453
+ }
6454
+ /**
6455
+ * Flatten a hydrated settings payload into `key → EFFECTIVE value`
6456
+ * (`value ?? default`). The read-side inverse of `hydrateSchema` for the
6457
+ * common "just give me the resolved values" case. Built on
6458
+ * {@link collectHydratedFieldEntries}.
6459
+ */
6460
+ function collectHydratedFieldValues(schema) {
6461
+ const out = /* @__PURE__ */ new Map();
6462
+ for (const [key, entry] of collectHydratedFieldEntries(schema)) out.set(key, entry.value ?? entry.default);
6463
+ return out;
6464
+ }
6388
6465
  function hydrateSchema(schema, values, options = {}) {
6389
6466
  return {
6390
6467
  ...schema.tabs ? { tabs: [...schema.tabs] } : {},
@@ -7261,17 +7338,35 @@ var RecordIntegrationInstances = class {
7261
7338
  ...kind !== void 0 ? { kind } : {}
7262
7339
  });
7263
7340
  if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
7341
+ const initial = this.hooks.initialBody(name, kind);
7342
+ const patch = this.patchOf(values ?? {});
7343
+ if (Object.keys(patch).length > 0) Object.assign(patch, await this.hooks.validateWrite?.({
7344
+ kind,
7345
+ body: {
7346
+ ...initial,
7347
+ ...patch
7348
+ }
7349
+ }));
7264
7350
  await this.handle.put({
7265
7351
  id: minted,
7266
- data: this.hooks.initialBody(name, kind)
7352
+ data: initial
7267
7353
  }, { ifRev: 0 });
7268
- const patch = this.patchOf(values ?? {});
7269
7354
  if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
7270
7355
  await this.hooks.onChanged?.(minted, "created");
7271
7356
  return { id: minted };
7272
7357
  };
7273
7358
  updateInstance = async ({ id, name, values }) => {
7274
7359
  const patch = this.patchOf(values);
7360
+ if (this.hooks.validateWrite !== void 0 && Object.keys(patch).length > 0) {
7361
+ const stored = await this.handle.get(id);
7362
+ if (stored !== null) Object.assign(patch, await this.hooks.validateWrite({
7363
+ kind: void 0,
7364
+ body: {
7365
+ ...stored.data,
7366
+ ...patch
7367
+ }
7368
+ }));
7369
+ }
7275
7370
  if (name !== void 0) patch[this.nameField] = name;
7276
7371
  await this.handle.patch(id, patch);
7277
7372
  await this.hooks.onChanged?.(id, "updated");
@@ -7582,6 +7677,21 @@ var SCOPED_SETTINGS_SCOPES = [
7582
7677
  "device-defaults",
7583
7678
  "node"
7584
7679
  ];
7680
+ /** The one refusal of a key a scope did not declare. */
7681
+ function assertDeclaredKeys(keys, declared) {
7682
+ const undeclared = keys.filter((key) => !declared.has(key));
7683
+ if (undeclared.length > 0) throw new Error(`scoped settings: [${undeclared.join(", ")}] not declared for this scope (declared: [${[...declared].join(", ")}])`);
7684
+ }
7685
+ /** Build the handle over an addon's scoped read, write and unset. */
7686
+ function createScopedSettings(deps) {
7687
+ return {
7688
+ async get() {
7689
+ return deps.schema.parse(await deps.read());
7690
+ },
7691
+ patch: (patch) => deps.write(Object.fromEntries(Object.entries(patch))),
7692
+ unset: (keys) => deps.unset(keys)
7693
+ };
7694
+ }
7585
7695
  /** A write to a secret path could not be stored in the keychain, so it was refused. */
7586
7696
  var KeychainWriteRefusedError = class extends Error {
7587
7697
  fieldPath;
@@ -8157,7 +8267,16 @@ function createHandle(raw, opts) {
8157
8267
  return blob;
8158
8268
  }
8159
8269
  },
8160
- writeAddonStore: writeAddon
8270
+ writeAddonStore: writeAddon,
8271
+ removeClearedKeys: (keys) => serialize("addon", async () => {
8272
+ const stored = await readStoredAddonBlob(raw);
8273
+ index.seen("addon", stored);
8274
+ const removable = keys.filter((key) => collectSecretMarkerRefs(stored[key]).length === 0);
8275
+ if (removable.length === 0) return;
8276
+ const patch = Object.fromEntries(removable.map((key) => [key, void 0]));
8277
+ await raw.writeAddonStore(patch);
8278
+ index.wrote("addon", patch);
8279
+ })
8161
8280
  };
8162
8281
  }
8163
8282
  /** The context the addon sees, plus the private raw view and write outcome. */
@@ -8287,7 +8406,7 @@ function boundOf(list) {
8287
8406
  * Base class for CamStack addons. Eliminates settings boilerplate:
8288
8407
  *
8289
8408
  * - Typed `config` property with automatic resolution from store + defaults
8290
- * - `getGlobalSettings()` / `updateGlobalSettings()` auto-implemented
8409
+ * - the scoped settings API (`clusterSettings` / `nodeSettings` / `deviceDefaults`) auto-implemented
8291
8410
  * - `getAddonSettings()` / `updateAddonSettings()` auto-implemented
8292
8411
  * - `getDeviceSettings()` / `updateDeviceSettings()` auto-implemented
8293
8412
  * - `ctx` accessor for the AddonContext (no manual `this.ctxRef` storage)
@@ -8297,7 +8416,7 @@ function boundOf(list) {
8297
8416
  * Subclasses override:
8298
8417
  * - `onInitialize()` — addon-specific init logic, return ProviderRegistration[]
8299
8418
  * - `onShutdown()` — cleanup
8300
- * - `globalSettingsSchema()` / `deviceSettingsSchema()` — UI schemas
8419
+ * - `clusterSettingsSchema()` / `nodeSettingsSchema()` / `deviceSettingsSchema()` — UI schemas
8301
8420
  *
8302
8421
  * @example
8303
8422
  * ```ts
@@ -8311,7 +8430,7 @@ function boundOf(list) {
8311
8430
  * super({ maxRetries: 3, endpoint: 'http://localhost' })
8312
8431
  * }
8313
8432
  *
8314
- * protected globalSettingsSchema() {
8433
+ * protected clusterSettingsSchema() {
8315
8434
  * return this.schema({
8316
8435
  * sections: [{
8317
8436
  * id: 'main', title: 'Settings',
@@ -8354,7 +8473,7 @@ var BaseAddon = class {
8354
8473
  defaults;
8355
8474
  /**
8356
8475
  * Hidden-state paths that hold secrets (D787) — values the addon writes
8357
- * itself (`updateGlobalSettings`, `state()`) that no form field declares.
8476
+ * itself (`state()`, the scoped writers) that no form field declares.
8358
8477
  * Stored in the keychain exactly like a `password` field. Syntax:
8359
8478
  * `'snapshotSigningKey'`, `'identity.pincode'`, `'tokens[userId].accessToken'`
8360
8479
  * (`key[<id field>]` addresses array items by id, never by index).
@@ -8364,7 +8483,7 @@ var BaseAddon = class {
8364
8483
  /**
8365
8484
  * D986 M4: the scoped schemas this addon declared, resolved once per capability
8366
8485
  * (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
8367
- * see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
8486
+ * see them. Empty until loaded.
8368
8487
  */
8369
8488
  _scopedByCap = /* @__PURE__ */ new Map();
8370
8489
  _nodeKeysByCap = /* @__PURE__ */ new Map();
@@ -8550,7 +8669,7 @@ var BaseAddon = class {
8550
8669
  /** Addon-specific cleanup. Override if needed. */
8551
8670
  async onShutdown() {}
8552
8671
  /**
8553
- * Called after config is resolved during updateGlobalSettings/updateAddonSettings.
8672
+ * Called after config is resolved during a settings write.
8554
8673
  * Override to react to config changes (e.g. restart a sampler, reconnect a service).
8555
8674
  * Not called during initialize() — use onInitialize() for initial setup.
8556
8675
  */
@@ -8579,82 +8698,12 @@ var BaseAddon = class {
8579
8698
  scopedSettingsCapabilities() {
8580
8699
  return [];
8581
8700
  }
8582
- /** Override to provide global-level settings UI schema. */
8583
- globalSettingsSchema(_cap) {
8584
- return null;
8585
- }
8586
8701
  /** Override to provide device-level settings UI schema. */
8587
8702
  deviceSettingsSchema() {
8588
8703
  return null;
8589
8704
  }
8590
8705
  /**
8591
- * INTEGRATION-LEVEL SETTINGS — declare which of this addon's global sections
8592
- * ARE the configuration of its integration.
8593
- *
8594
- * Return the `ConfigSection.id`s, from {@link globalSettingsSchema}, that an
8595
- * operator should find on the addon's integration page (System →
8596
- * Integrations → <name>) rather than only in the cluster-wide list of every
8597
- * addon. Empty (the default) means the addon has no integration-level
8598
- * settings and no such surface is offered — this is opt-in, because whether
8599
- * an addon's configuration IS its integration's configuration depends on the
8600
- * nature of the integration.
8601
- *
8602
- * WHAT THIS IS NOT. It is not a scope. The selected sections keep living in
8603
- * the ONE global schema, in the ONE addon store, written by the ONE
8604
- * `updateGlobalSettings` path. There is deliberately no
8605
- * `updateIntegrationSettings`: a second write path is how a surface acquires
8606
- * a second store key, and this repo has shipped that twice (`btmPath@hub`,
8607
- * D266). Selecting sections cannot introduce a key that selecting cannot.
8608
- *
8609
- * WHY IT IS A LIST OF SECTION IDS AND NOT A MARKER ON THE SECTION.
8610
- * `ConfigFieldBase` used to carry `scope?: 'device' | 'global'` and it was
8611
- * removed with the reason recorded at
8612
- * `packages/types/src/interfaces/config-ui.ts:249` — *"a field's scope is
8613
- * determined by WHICH schema it lives in, not by a field-level marker."* A
8614
- * marker sprinkled across sections also has to borrow a field that already
8615
- * means something else; borrowing `section.tab` put the literal word
8616
- * "integration" into an operator-facing tab bar, because `tab` means "how to
8617
- * GROUP this visually" and cannot also mean "where this lives" (D269
8618
- * supersedes D268). One declaration, in one place, next to the schema whose
8619
- * ids it names.
8620
- */
8621
- integrationSettingSections() {
8622
- return [];
8623
- }
8624
- async getGlobalSettings(overlay, cap, nodeId) {
8625
- await this.loadScopedSchemas(cap);
8626
- const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
8627
- if (!schema) return { sections: [] };
8628
- const projected = await this.readRawProjectedStore(nodeId, cap);
8629
- return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
8630
- }
8631
- /**
8632
- * The integration-level view of this addon's settings: exactly the sections
8633
- * named by {@link integrationSettingSections}, hydrated from the SAME store
8634
- * `getGlobalSettings` reads, and narrowed to cluster-scoped fields.
8635
- *
8636
- * Returns `null` when the addon declared nothing — an addon that opts out has
8637
- * no integration settings surface at all, rather than an empty one that reads
8638
- * as a failed load.
8639
- *
8640
- * Three properties hold BY CONSTRUCTION, which is why they are here in core
8641
- * and not in whichever UI happens to render this:
8642
- *
8643
- * 1. **One key.** The payload is a SUBSET of the global schema, so a field
8644
- * shown here is the same field, with the same bare key, that the addon's
8645
- * own page shows. There is no integration-specific writer — callers save
8646
- * through `updateGlobalSettings` — so a second store key is unreachable,
8647
- * not merely discouraged.
8648
- * 2. **No node scope.** `perNode: true` fields are DROPPED. Their store key
8649
- * is `<key>@<nodeId>` and an integration is not a node; whichever node
8650
- * such a field silently picked would be a wrong answer for the operator
8651
- * who opened the page (D266).
8652
- * 3. **No silent typo.** A declared id that names no section throws. The
8653
- * alternative — skip it — turns a rename into a surface that quietly
8654
- * empties, which looks exactly like an addon with nothing to configure.
8655
- */
8656
- /**
8657
- * The form schema of ONE integration instance, or `null` to use {@link globalSettingsSchema}.
8706
+ * The form schema of ONE integration instance, or `null` to use the scoped form.
8658
8707
  *
8659
8708
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
8660
8709
  * needs its connection fields, the default integration the addon's own sections. It is the
@@ -8702,37 +8751,178 @@ var BaseAddon = class {
8702
8751
  }
8703
8752
  }
8704
8753
  /**
8754
+ * Hook: values layered over the stored ones when the FORM of one scope is served
8755
+ * ({@link getScopedSettings}) — a preview that derives dependent options without writing.
8756
+ * Never applied to {@link ScopedSettings.get} or to a write. `null` ⇒ no overlay.
8757
+ */
8758
+ async scopedSettingsOverlay(_scope, _cap, _nodeId) {
8759
+ return null;
8760
+ }
8761
+ /**
8762
+ * Hook: a write of one scope changed stored values (not called when the store reports no change,
8763
+ * e.g. the write-back of a stored secret). `keys` are the bare field keys of the write, node keys included.
8764
+ */
8765
+ async onScopedSettingsChanged(_scope, _cap, _keys) {}
8766
+ /** Typed handle over the CLUSTER settings `cap` declares (`clusterSettingsSchema`). */
8767
+ clusterSettings(cap, schema) {
8768
+ return this.scopedHandle("cluster", cap, schema);
8769
+ }
8770
+ /** Typed handle over the fleet defaults of the per-device settings `cap` declares. */
8771
+ deviceDefaults(cap, schema) {
8772
+ return this.scopedHandle("device-defaults", cap, schema);
8773
+ }
8774
+ /**
8775
+ * Typed handle over the settings `cap` owns PER NODE. `nodeId` omitted ⇒ the local node. A node
8776
+ * with no row yields the schema defaults — never a bare-key fallback.
8777
+ */
8778
+ nodeSettings(cap, schema, nodeId) {
8779
+ return this.scopedHandle("node", cap, schema, nodeId);
8780
+ }
8781
+ /**
8782
+ * {@link state} for a value that is kept per node: stored under `<key>@<nodeId>`, and the
8783
+ * settings store carries `nodeId` in the row data (D986 §1), so a forgotten node's rows go in one
8784
+ * central delete. `nodeId` omitted ⇒ the local node.
8785
+ */
8786
+ nodeState(key, schema, fallback, nodeId) {
8787
+ this._declaredStateKeys.add(key);
8788
+ return createDurableState({
8789
+ key: nodeScopedKey(key, nodeId ?? this._ctx?.kernel?.localNodeId ?? ""),
8790
+ schema,
8791
+ fallback,
8792
+ read: async () => this.resolveStoredSecrets(await this.readAddonStoreWithRetry()),
8793
+ write: async (patch) => {
8794
+ await this._ctx?.settings?.writeAddonStore(patch);
8795
+ },
8796
+ onParseError: (k, e) => this._ctx?.logger?.warn?.(`durable-state: stored "${k}" failed validation — using fallback`, { meta: {
8797
+ addonId: this._ctx?.id,
8798
+ key: k,
8799
+ error: String(e)
8800
+ } })
8801
+ });
8802
+ }
8803
+ /** The loaded schema `cap` declares for `scope`, or `null`. */
8804
+ async declaredScopedSchema(scope, cap) {
8805
+ await this.loadScopedSchemas(cap);
8806
+ return this.loadedScopedSchemas(cap).find((entry) => entry.scope === scope)?.schema ?? null;
8807
+ }
8808
+ async requireScopedSchema(scope, cap) {
8809
+ const schema = await this.declaredScopedSchema(scope, cap);
8810
+ if (schema === null) throw new Error(`${this.constructor.name}: cap "${cap}" declares no ${scope} settings`);
8811
+ return schema;
8812
+ }
8813
+ scopedKeys(schema) {
8814
+ return new Set(schema.sections.flatMap((section) => collectValueFieldKeys(section.fields)));
8815
+ }
8816
+ /** `store` narrowed to the keys `schema` declares. */
8817
+ narrowToScope(store, schema) {
8818
+ const keys = this.scopedKeys(schema);
8819
+ return Object.fromEntries(Object.entries(store).filter(([key]) => keys.has(key)));
8820
+ }
8821
+ scopedHandle(scope, cap, schema, nodeId) {
8822
+ return createScopedSettings({
8823
+ schema,
8824
+ read: async () => {
8825
+ const declared = await this.requireScopedSchema(scope, cap);
8826
+ const stored = this.narrowToScope(await this.resolveGlobalStore(nodeId, cap), declared);
8827
+ const effective = collectHydratedFieldValues(hydrateSchema(declared, stored));
8828
+ return {
8829
+ ...Object.fromEntries([...effective].filter(([, value]) => value !== void 0)),
8830
+ ...stored
8831
+ };
8832
+ },
8833
+ write: (patch) => this.writeScoped(scope, cap, patch, nodeId),
8834
+ unset: (keys) => this.unsetScoped(scope, cap, keys, nodeId)
8835
+ });
8836
+ }
8837
+ /** The one write path of the scoped API: declared keys only, then the global patch. */
8838
+ async writeScoped(scope, cap, patch, nodeId) {
8839
+ assertDeclaredKeys(Object.keys(patch), this.scopedKeys(await this.requireScopedSchema(scope, cap)));
8840
+ if (await this.applyGlobalPatch(patch, scope === "node" ? nodeId : void 0, cap)) await this.onScopedSettingsChanged(scope, cap, Object.keys(patch));
8841
+ }
8842
+ /**
8843
+ * Remove declared keys of one scope. A secret goes through {@link clearSecrets} — its keychain
8844
+ * value is released exactly as there — and the emptied row is then removed; every other key is
8845
+ * removed by an `undefined` patch (which a secret transform would read as "keep").
8846
+ */
8847
+ async unsetScoped(scope, cap, keys, nodeId) {
8848
+ assertDeclaredKeys(keys, this.scopedKeys(await this.requireScopedSchema(scope, cap)));
8849
+ const target = scope === "node" ? nodeId : void 0;
8850
+ const tree = this.addonSecretTree();
8851
+ const secretKeys = keys.filter((key) => tree.has(key));
8852
+ const plainKeys = keys.filter((key) => !tree.has(key));
8853
+ let changed = false;
8854
+ if (plainKeys.length > 0) {
8855
+ const patch = Object.fromEntries(plainKeys.map((key) => [key, void 0]));
8856
+ changed = await this.applyGlobalPatch(patch, target, cap) || changed;
8857
+ }
8858
+ if (secretKeys.length > 0) {
8859
+ await this.clearSecretKeys(secretKeys, target, cap);
8860
+ const node = normalizeNodeId(target ?? this._ctx?.kernel?.localNodeId);
8861
+ const rows = secretKeys.map((key) => scope === "node" ? nodeScopedKey(key, node) : key);
8862
+ if (this._secretSettings !== null) await this._secretSettings.removeClearedKeys(rows);
8863
+ else await this._ctx?.settings?.writeAddonStore(Object.fromEntries(rows.map((row) => [row, void 0])));
8864
+ changed = true;
8865
+ }
8866
+ if (changed) await this.onScopedSettingsChanged(scope, cap, [...keys]);
8867
+ }
8868
+ /**
8869
+ * The hydrated form of one scope of one capability's settings (any
8870
+ * {@link scopedSettingsOverlay} applied), or `null` when the capability declares nothing there.
8871
+ */
8872
+ async getScopedSettings(scope, cap, nodeId) {
8873
+ const schema = await this.declaredScopedSchema(scope, cap);
8874
+ if (schema === null) return null;
8875
+ const stored = this.narrowToScope(await this.readRawProjectedStore(nodeId, cap), schema);
8876
+ const overlay = await this.scopedSettingsOverlay(scope, cap, nodeId);
8877
+ return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(stored, overlay) : stored, { inlineSecretsAsViews: true });
8878
+ }
8879
+ /**
8880
+ * The scoped schemas this addon serves: one entry per loaded (capability, scope), capabilities
8881
+ * in declaration order, scopes in {@link SCOPED_SETTINGS_SCOPES} order. `[]` when it declares
8882
+ * nothing.
8883
+ */
8884
+ async listScopedSettings() {
8885
+ await this.loadScopedSchemas();
8886
+ return [...this._scopedByCap].flatMap(([cap, entries]) => entries.map((entry) => ({
8887
+ cap,
8888
+ scope: entry.scope,
8889
+ sectionIds: entry.schema.sections.map((section) => section.id)
8890
+ })));
8891
+ }
8892
+ /** The OPERATOR's write of declared keys of one scope (D787 secret semantics). */
8893
+ async updateScopedSettings(scope, cap, patch, nodeId) {
8894
+ this._operatorWriteDepth++;
8895
+ try {
8896
+ await this.writeScoped(scope, cap, patch, nodeId);
8897
+ } finally {
8898
+ this._operatorWriteDepth--;
8899
+ }
8900
+ }
8901
+ /**
8902
+ * The effective values (`stored ?? default`) of one scope, a secret as its `set` view — never
8903
+ * its value. `null` when the capability declares nothing for the scope.
8904
+ */
8905
+ async readScopedValues(scope, cap, nodeId) {
8906
+ const schema = await this.declaredScopedSchema(scope, cap);
8907
+ if (schema === null) return null;
8908
+ const form = hydrateSchema(schema, this.narrowToScope(await this.readRawProjectedStore(nodeId, cap), schema), { inlineSecretsAsViews: true });
8909
+ return Object.fromEntries([...collectHydratedFieldValues(form)].filter(([, value]) => value !== void 0));
8910
+ }
8911
+ /**
8705
8912
  * What the create wizard and the integration edit page render for one instance: the addon's
8706
8913
  * per-instance schema when it declares one, else its global schema (what they always showed).
8707
8914
  */
8708
8915
  async getIntegrationFormSchema(instance, nodeId) {
8709
8916
  const scoped = await this.integrationFormSchema(instance);
8710
- if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
8917
+ if (scoped === null) return this.scopedForm(nodeId);
8711
8918
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
8712
8919
  }
8713
- async getIntegrationSettings(nodeId) {
8714
- const declared = this.integrationSettingSections();
8715
- if (declared.length === 0) return null;
8716
- const schema = this.globalSettingsSchema();
8717
- if (!schema) throw new Error(`${this.constructor.name}: integrationSettingSections() names [${declared.join(", ")}] but globalSettingsSchema() returns null.`);
8718
- const byId = new Map(schema.sections.map((section) => [section.id, section]));
8719
- const sections = [];
8720
- for (const id of declared) {
8721
- const section = byId.get(id);
8722
- if (!section) throw new Error(`${this.constructor.name}: integrationSettingSections() names unknown section "${id}". Known sections: [${[...byId.keys()].join(", ")}].`);
8723
- const fields = dropPerNodeFields(section.fields);
8724
- if (fields.length === 0) continue;
8725
- sections.push({
8726
- ...section,
8727
- fields
8728
- });
8729
- }
8730
- if (sections.length === 0) return null;
8731
- const projected = await this.readRawProjectedStore(nodeId);
8732
- return hydrateSchema({
8733
- ...schema,
8734
- sections
8735
- }, projected, { inlineSecretsAsViews: true });
8920
+ /** The form of the scoped schemas this addon declared, hydrated from its store. */
8921
+ async scopedForm(nodeId) {
8922
+ await this.loadScopedSchemas();
8923
+ const schema = this.scopedFormSchema();
8924
+ if (!schema) return { sections: [] };
8925
+ return hydrateSchema(schema, await this.readRawProjectedStore(nodeId), { inlineSecretsAsViews: true });
8736
8926
  }
8737
8927
  /**
8738
8928
  * The raw addon store PROJECTED onto the target node's bare per-node keys:
@@ -8741,8 +8931,8 @@ var BaseAddon = class {
8741
8931
  * bare fallback), all `@`-scoped and stray bare per-node keys are dropped.
8742
8932
  * A no-op passthrough when the schema declares no `perNode` field.
8743
8933
  *
8744
- * This is the sanctioned way for a `getGlobalSettings` OVERRIDE that needs
8745
- * the store for custom option logic (option narrowing, value snapping) to
8934
+ * This is the sanctioned way for an addon that needs
8935
+ * the store for custom logic (option narrowing, value snapping) to
8746
8936
  * read it per-node — never `ctx.settings.readAddonStore()` directly.
8747
8937
  * `nodeId` omitted ⇒ the local node (node-blind injection from ctx).
8748
8938
  *
@@ -8794,7 +8984,7 @@ var BaseAddon = class {
8794
8984
  }
8795
8985
  /**
8796
8986
  * Whether ONE global key of this addon's store was written, read three-way
8797
- * (D651). For a question a hydrated view cannot answer: `getGlobalSettings`
8987
+ * (D651). For a question a hydrated view cannot answer: a hydrated form
8798
8988
  * reports `value ?? default`, so "the operator chose the default" and "nobody
8799
8989
  * chose" look the same, and a failed read looks like both. A `perNode` key is
8800
8990
  * not supported here: its stored name is scoped.
@@ -8828,7 +9018,7 @@ var BaseAddon = class {
8828
9018
  }
8829
9019
  /**
8830
9020
  * The OPERATOR's write — a settings form or an admin API call, dispatched by
8831
- * the kernel. Identical to {@link updateGlobalSettings}, except that a secret
9021
+ * the kernel. The one in-process write of the addon blob that is not a scoped write; a secret
8832
9022
  * the operator sends is stored as sent even when it equals a pre-keychain
8833
9023
  * inline value: the operator re-entering a password is a change, an addon
8834
9024
  * writing back what it read is not (D787).
@@ -8841,9 +9031,6 @@ var BaseAddon = class {
8841
9031
  this._operatorWriteDepth--;
8842
9032
  }
8843
9033
  }
8844
- async updateGlobalSettings(patch, nodeId) {
8845
- await this.applyGlobalPatch(patch, nodeId);
8846
- }
8847
9034
  /**
8848
9035
  * REMOVE stored secrets — their keychain values and their markers (D787).
8849
9036
  *
@@ -8853,6 +9040,9 @@ var BaseAddon = class {
8853
9040
  * a keychain (an older kernel) it writes `''`, as such code always did.
8854
9041
  */
8855
9042
  async clearSecrets(keys, nodeId) {
9043
+ await this.clearSecretKeys(keys, nodeId);
9044
+ }
9045
+ async clearSecretKeys(keys, nodeId, cap) {
8856
9046
  const tree = this.addonSecretTree();
8857
9047
  const keychain = this._ctx?.secrets !== void 0;
8858
9048
  const stored = keychain ? await this.readAddonStoreWithRetry() : {};
@@ -8865,25 +9055,26 @@ var BaseAddon = class {
8865
9055
  const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
8866
9056
  patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
8867
9057
  }
8868
- await this.applyGlobalPatch(patch, nodeId);
9058
+ await this.applyGlobalPatch(patch, nodeId, cap);
8869
9059
  }
8870
- async applyGlobalPatch(barePatch, nodeId) {
8871
- await this.loadScopedSchemas();
8872
- const keys = this.perNodeKeys();
9060
+ /** Resolves `true` when the write changed stored values. */
9061
+ async applyGlobalPatch(barePatch, nodeId, cap) {
9062
+ await this.loadScopedSchemas(cap);
9063
+ const keys = this.perNodeKeys(cap);
8873
9064
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
8874
9065
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
8875
9066
  const scoped = keys.size > 0 ? scopePatch(barePatch, keys, target) : barePatch;
8876
9067
  const handle = this._secretSettings;
8877
9068
  if (handle !== null) {
8878
9069
  const { changed } = await handle.writeAddonStore(scoped);
8879
- if (!changed) return;
9070
+ if (!changed) return false;
8880
9071
  } else await this._ctx?.settings?.writeAddonStore(scoped);
8881
- if (target !== localNode) return;
9072
+ if (target !== localNode) return true;
8882
9073
  await this.resolveConfig();
8883
9074
  await this.onConfigChanged();
8884
9075
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
8885
- const legacy = this.globalSettingsSchema();
8886
- this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
9076
+ this.maybeAutoRestart(Object.keys(barePatch), this.loadedScopedSchemas().map((entry) => entry.schema));
9077
+ return true;
8887
9078
  }
8888
9079
  /**
8889
9080
  * The set of field keys the global settings schema declares `perNode: true`
@@ -8920,10 +9111,7 @@ var BaseAddon = class {
8920
9111
  this._nodeKeysByCap = /* @__PURE__ */ new Map();
8921
9112
  this._addonSecretTree = null;
8922
9113
  }
8923
- /**
8924
- * The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
8925
- * sections of its scoped schemas, so dropping the old hook does not blank the form.
8926
- */
9114
+ /** The form of an addon's scoped schemas: their sections, each shown once. */
8927
9115
  scopedFormSchema(cap) {
8928
9116
  const byId = /* @__PURE__ */ new Map();
8929
9117
  for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
@@ -8935,8 +9123,7 @@ var BaseAddon = class {
8935
9123
  return [...this._scopedByCap.values()].flat();
8936
9124
  }
8937
9125
  /**
8938
- * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
8939
- * the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
9126
+ * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}. Derived once
8940
9127
  * per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
8941
9128
  * per-node code path is bypassed and the settings API behaves like the node-agnostic one.
8942
9129
  * Call {@link loadScopedSchemas} first (the async seams do).
@@ -8945,8 +9132,7 @@ var BaseAddon = class {
8945
9132
  const cacheKey = cap ?? "";
8946
9133
  const cached = this._nodeKeysByCap.get(cacheKey);
8947
9134
  if (cached) return cached;
8948
- const legacy = this.globalSettingsSchema(cap);
8949
- const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
9135
+ const keys = /* @__PURE__ */ new Set();
8950
9136
  for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
8951
9137
  if (scope !== "node") continue;
8952
9138
  for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
@@ -9164,8 +9350,6 @@ var BaseAddon = class {
9164
9350
  }
9165
9351
  declaredKeySet() {
9166
9352
  const keys = new Set(Object.keys(this.defaults));
9167
- const schema = this.globalSettingsSchema();
9168
- if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
9169
9353
  for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
9170
9354
  for (const key of this._declaredStateKeys) keys.add(key);
9171
9355
  for (const path of this.secretStatePaths) {
@@ -9263,7 +9447,7 @@ var BaseAddon = class {
9263
9447
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
9264
9448
  addonSecretTree() {
9265
9449
  if (this._addonSecretTree === null) {
9266
- const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
9450
+ const tree = /* @__PURE__ */ new Map();
9267
9451
  for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
9268
9452
  this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
9269
9453
  }
@@ -9475,47 +9659,6 @@ function classifyStoreError(err) {
9475
9659
  if (TRANSPORT_PHRASES.some((f) => message.includes(f))) return "transport";
9476
9660
  return "permanent";
9477
9661
  }
9478
- /**
9479
- * Collect the keys of every field marked `perNode: true`, recursing into
9480
- * layout containers (`group` fields and `sub-tabs` tabs) the same way
9481
- * `hydrateSchema` does. Valueless structural fields (separator/info/…)
9482
- * don't declare `perNode` and are excluded by the `in` narrowing.
9483
- */
9484
- /**
9485
- * The same fields with every `perNode: true` one removed, recursing into layout
9486
- * containers exactly as {@link collectPerNodeFieldKeys} does. A container left
9487
- * with no child is dropped rather than rendered empty.
9488
- *
9489
- * Used by `getIntegrationSettings`: an integration is not a node, so a field
9490
- * whose store key is `<key>@<nodeId>` has no node to belong to there.
9491
- */
9492
- function dropPerNodeFields(fields) {
9493
- const kept = [];
9494
- for (const field of fields) {
9495
- if (field.type === "group") {
9496
- const inner = dropPerNodeFields(field.fields);
9497
- if (inner.length > 0) kept.push({
9498
- ...field,
9499
- fields: inner
9500
- });
9501
- continue;
9502
- }
9503
- if (field.type === "sub-tabs") {
9504
- const tabs = field.tabs.map((tab) => ({
9505
- ...tab,
9506
- fields: dropPerNodeFields(tab.fields)
9507
- })).filter((tab) => tab.fields.length > 0);
9508
- if (tabs.length > 0) kept.push({
9509
- ...field,
9510
- tabs
9511
- });
9512
- continue;
9513
- }
9514
- if ("perNode" in field && field.perNode === true) continue;
9515
- kept.push(field);
9516
- }
9517
- return kept;
9518
- }
9519
9662
  /** The keys of the value-holding fields, recursing into layout containers. */
9520
9663
  function collectValueFieldKeys(fields) {
9521
9664
  const collected = [];
@@ -9548,21 +9691,6 @@ function collectFieldKeys(fields) {
9548
9691
  }
9549
9692
  return collected;
9550
9693
  }
9551
- function collectPerNodeFieldKeys(fields) {
9552
- const collected = [];
9553
- for (const field of fields) {
9554
- if (field.type === "group") {
9555
- collected.push(...collectPerNodeFieldKeys(field.fields));
9556
- continue;
9557
- }
9558
- if (field.type === "sub-tabs") {
9559
- for (const tab of field.tabs) collected.push(...collectPerNodeFieldKeys(tab.fields));
9560
- continue;
9561
- }
9562
- if ("perNode" in field && field.perNode === true) collected.push(field.key);
9563
- }
9564
- return collected;
9565
- }
9566
9694
  /**
9567
9695
  * Normalize an `ICamstackAddon.initialize()` return value into the
9568
9696
  * `AddonInitResult` envelope. Arrays are wrapped into `{ providers }`;
@@ -10613,7 +10741,6 @@ var CAP_NAMES = Object.freeze([
10613
10741
  "notifier",
10614
10742
  "numeric-sensor",
10615
10743
  "oauth-integration",
10616
- "oidc-connections",
10617
10744
  "osd",
10618
10745
  "osd-manager",
10619
10746
  "pet-feeder",
@@ -12320,13 +12447,25 @@ Object.freeze({
12320
12447
  "path": "temperature",
12321
12448
  "valueType": "number"
12322
12449
  }],
12323
- "timed-run": [{
12324
- "path": "activeUntil",
12325
- "valueType": "number"
12326
- }, {
12327
- "path": "requestedBy",
12328
- "valueType": "string"
12329
- }],
12450
+ "timed-run": [
12451
+ {
12452
+ "path": "activeUntil",
12453
+ "valueType": "number"
12454
+ },
12455
+ {
12456
+ "path": "requestedBy",
12457
+ "valueType": "string"
12458
+ },
12459
+ {
12460
+ "path": "quickRun.duration",
12461
+ "valueType": "number"
12462
+ },
12463
+ {
12464
+ "path": "quickRun.unit",
12465
+ "valueType": "enum",
12466
+ "enumValues": ["s", "min"]
12467
+ }
12468
+ ],
12330
12469
  "update": [
12331
12470
  {
12332
12471
  "path": "currentVersion",
@@ -12505,8 +12644,7 @@ Object.freeze({
12505
12644
  "device-provider": { "reloadDevice": 18e4 },
12506
12645
  "llm": {
12507
12646
  "generate": 6e5,
12508
- "generateVision": 6e5,
12509
- "testProfile": 6e5
12647
+ "generateVision": 6e5
12510
12648
  },
12511
12649
  "llm-runtime": {
12512
12650
  "complete": 6e5,
@@ -12551,7 +12689,7 @@ object({
12551
12689
  params: record(string(), union([string(), number()])).optional()
12552
12690
  });
12553
12691
  //#endregion
12554
- //#region ../types/dist/composition-BmBHuDTQ.mjs
12692
+ //#region ../types/dist/composition-Bx0JvKUU.mjs
12555
12693
  /**
12556
12694
  * The ffmpeg build every CamStack node downloads, per platform and arch.
12557
12695
  *
@@ -13713,6 +13851,387 @@ var NodeColorIdSchema = _enum([
13713
13851
  "node-h"
13714
13852
  ]);
13715
13853
  //#endregion
13854
+ //#region ../types/dist/convert-BAzH15HU.mjs
13855
+ /**
13856
+ * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
13857
+ * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
13858
+ * `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
13859
+ * decimals). K derives from `°C = K − 273.15`.
13860
+ */
13861
+ var UNIT_TABLE = {
13862
+ "°C": {
13863
+ dimension: "temperature",
13864
+ factor: 1,
13865
+ offset: 0
13866
+ },
13867
+ "°F": {
13868
+ dimension: "temperature",
13869
+ factor: 5 / 9,
13870
+ offset: -160 / 9
13871
+ },
13872
+ K: {
13873
+ dimension: "temperature",
13874
+ factor: 1,
13875
+ offset: -273.15
13876
+ },
13877
+ hPa: {
13878
+ dimension: "pressure",
13879
+ factor: 1,
13880
+ offset: 0
13881
+ },
13882
+ kPa: {
13883
+ dimension: "pressure",
13884
+ factor: 10,
13885
+ offset: 0
13886
+ },
13887
+ Pa: {
13888
+ dimension: "pressure",
13889
+ factor: .01,
13890
+ offset: 0
13891
+ },
13892
+ mbar: {
13893
+ dimension: "pressure",
13894
+ factor: 1,
13895
+ offset: 0
13896
+ },
13897
+ bar: {
13898
+ dimension: "pressure",
13899
+ factor: 1e3,
13900
+ offset: 0
13901
+ },
13902
+ inHg: {
13903
+ dimension: "pressure",
13904
+ factor: 33.8639,
13905
+ offset: 0
13906
+ },
13907
+ mmHg: {
13908
+ dimension: "pressure",
13909
+ factor: 1.33322,
13910
+ offset: 0
13911
+ },
13912
+ psi: {
13913
+ dimension: "pressure",
13914
+ factor: 68.9476,
13915
+ offset: 0
13916
+ },
13917
+ "m/s": {
13918
+ dimension: "speed",
13919
+ factor: 1,
13920
+ offset: 0
13921
+ },
13922
+ "km/h": {
13923
+ dimension: "speed",
13924
+ factor: 1 / 3.6,
13925
+ offset: 0
13926
+ },
13927
+ mph: {
13928
+ dimension: "speed",
13929
+ factor: .44704,
13930
+ offset: 0
13931
+ },
13932
+ kn: {
13933
+ dimension: "speed",
13934
+ factor: .514444,
13935
+ offset: 0
13936
+ },
13937
+ "mm/h": {
13938
+ dimension: "precipitation-rate",
13939
+ factor: 1,
13940
+ offset: 0
13941
+ },
13942
+ "in/h": {
13943
+ dimension: "precipitation-rate",
13944
+ factor: 25.4,
13945
+ offset: 0
13946
+ },
13947
+ m: {
13948
+ dimension: "length",
13949
+ factor: 1,
13950
+ offset: 0
13951
+ },
13952
+ mm: {
13953
+ dimension: "length",
13954
+ factor: .001,
13955
+ offset: 0
13956
+ },
13957
+ cm: {
13958
+ dimension: "length",
13959
+ factor: .01,
13960
+ offset: 0
13961
+ },
13962
+ km: {
13963
+ dimension: "length",
13964
+ factor: 1e3,
13965
+ offset: 0
13966
+ },
13967
+ in: {
13968
+ dimension: "length",
13969
+ factor: .0254,
13970
+ offset: 0
13971
+ },
13972
+ ft: {
13973
+ dimension: "length",
13974
+ factor: .3048,
13975
+ offset: 0
13976
+ },
13977
+ mi: {
13978
+ dimension: "length",
13979
+ factor: 1609.344,
13980
+ offset: 0
13981
+ },
13982
+ "m²": {
13983
+ dimension: "area",
13984
+ factor: 1,
13985
+ offset: 0
13986
+ },
13987
+ lx: {
13988
+ dimension: "illuminance",
13989
+ factor: 1,
13990
+ offset: 0
13991
+ },
13992
+ "W/m²": {
13993
+ dimension: "irradiance",
13994
+ factor: 1,
13995
+ offset: 0
13996
+ },
13997
+ W: {
13998
+ dimension: "power",
13999
+ factor: 1,
14000
+ offset: 0
14001
+ },
14002
+ kW: {
14003
+ dimension: "power",
14004
+ factor: 1e3,
14005
+ offset: 0
14006
+ },
14007
+ VA: {
14008
+ dimension: "apparent-power",
14009
+ factor: 1,
14010
+ offset: 0
14011
+ },
14012
+ Wh: {
14013
+ dimension: "energy",
14014
+ factor: 1,
14015
+ offset: 0
14016
+ },
14017
+ kWh: {
14018
+ dimension: "energy",
14019
+ factor: 1e3,
14020
+ offset: 0
14021
+ },
14022
+ MWh: {
14023
+ dimension: "energy",
14024
+ factor: 1e6,
14025
+ offset: 0
14026
+ },
14027
+ V: {
14028
+ dimension: "voltage",
14029
+ factor: 1,
14030
+ offset: 0
14031
+ },
14032
+ mV: {
14033
+ dimension: "voltage",
14034
+ factor: .001,
14035
+ offset: 0
14036
+ },
14037
+ A: {
14038
+ dimension: "current",
14039
+ factor: 1,
14040
+ offset: 0
14041
+ },
14042
+ mA: {
14043
+ dimension: "current",
14044
+ factor: .001,
14045
+ offset: 0
14046
+ },
14047
+ ppm: {
14048
+ dimension: "concentration-volume",
14049
+ factor: 1,
14050
+ offset: 0
14051
+ },
14052
+ ppb: {
14053
+ dimension: "concentration-volume",
14054
+ factor: .001,
14055
+ offset: 0
14056
+ },
14057
+ "µg/m³": {
14058
+ dimension: "concentration-mass",
14059
+ factor: 1,
14060
+ offset: 0
14061
+ },
14062
+ "mg/m³": {
14063
+ dimension: "concentration-mass",
14064
+ factor: 1e3,
14065
+ offset: 0
14066
+ },
14067
+ kg: {
14068
+ dimension: "mass",
14069
+ factor: 1,
14070
+ offset: 0
14071
+ },
14072
+ g: {
14073
+ dimension: "mass",
14074
+ factor: .001,
14075
+ offset: 0
14076
+ },
14077
+ lb: {
14078
+ dimension: "mass",
14079
+ factor: .453592,
14080
+ offset: 0
14081
+ },
14082
+ oz: {
14083
+ dimension: "mass",
14084
+ factor: .0283495,
14085
+ offset: 0
14086
+ },
14087
+ "%": {
14088
+ dimension: "percentage",
14089
+ factor: 1,
14090
+ offset: 0
14091
+ },
14092
+ L: {
14093
+ dimension: "volume",
14094
+ factor: 1,
14095
+ offset: 0
14096
+ },
14097
+ mL: {
14098
+ dimension: "volume",
14099
+ factor: .001,
14100
+ offset: 0
14101
+ },
14102
+ gal: {
14103
+ dimension: "volume",
14104
+ factor: 3.785411784,
14105
+ offset: 0
14106
+ },
14107
+ s: {
14108
+ dimension: "duration",
14109
+ factor: 1,
14110
+ offset: 0
14111
+ },
14112
+ ms: {
14113
+ dimension: "duration",
14114
+ factor: .001,
14115
+ offset: 0
14116
+ },
14117
+ min: {
14118
+ dimension: "duration",
14119
+ factor: 60,
14120
+ offset: 0
14121
+ },
14122
+ h: {
14123
+ dimension: "duration",
14124
+ factor: 3600,
14125
+ offset: 0
14126
+ },
14127
+ d: {
14128
+ dimension: "duration",
14129
+ factor: 86400,
14130
+ offset: 0
14131
+ },
14132
+ B: {
14133
+ dimension: "information",
14134
+ factor: 1,
14135
+ offset: 0
14136
+ },
14137
+ KiB: {
14138
+ dimension: "information",
14139
+ factor: 1024,
14140
+ offset: 0
14141
+ },
14142
+ MiB: {
14143
+ dimension: "information",
14144
+ factor: 1024 ** 2,
14145
+ offset: 0
14146
+ },
14147
+ GiB: {
14148
+ dimension: "information",
14149
+ factor: 1024 ** 3,
14150
+ offset: 0
14151
+ },
14152
+ TiB: {
14153
+ dimension: "information",
14154
+ factor: 1024 ** 4,
14155
+ offset: 0
14156
+ },
14157
+ kB: {
14158
+ dimension: "information",
14159
+ factor: 1e3,
14160
+ offset: 0
14161
+ },
14162
+ MB: {
14163
+ dimension: "information",
14164
+ factor: 1e6,
14165
+ offset: 0
14166
+ },
14167
+ GB: {
14168
+ dimension: "information",
14169
+ factor: 1e9,
14170
+ offset: 0
14171
+ },
14172
+ "°": {
14173
+ dimension: "angle",
14174
+ factor: 1,
14175
+ offset: 0
14176
+ },
14177
+ dBFS: {
14178
+ dimension: "sound-level",
14179
+ factor: 1,
14180
+ offset: 0
14181
+ },
14182
+ dBm: {
14183
+ dimension: "signal-level",
14184
+ factor: 1,
14185
+ offset: 0
14186
+ },
14187
+ mired: {
14188
+ dimension: "colour-temperature",
14189
+ factor: 1,
14190
+ offset: 0
14191
+ },
14192
+ kbps: {
14193
+ dimension: "bitrate",
14194
+ factor: 1,
14195
+ offset: 0
14196
+ },
14197
+ Mbps: {
14198
+ dimension: "bitrate",
14199
+ factor: 1e3,
14200
+ offset: 0
14201
+ },
14202
+ fps: {
14203
+ dimension: "rate",
14204
+ factor: 1,
14205
+ offset: 0
14206
+ }
14207
+ };
14208
+ /** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
14209
+ function specFor(unit) {
14210
+ return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
14211
+ }
14212
+ /** Value in `spec`'s unit → its dimension's canonical unit. */
14213
+ function toCanonical(value, spec) {
14214
+ return value * spec.factor + spec.offset;
14215
+ }
14216
+ /** Canonical-unit value → the unit described by `spec`. */
14217
+ function fromCanonical(canonical, spec) {
14218
+ return (canonical - spec.offset) / spec.factor;
14219
+ }
14220
+ /**
14221
+ * Graceful variant for the render path: returns the converted value, or
14222
+ * `undefined` when the conversion is refused (unknown unit / cross-dimension).
14223
+ * Never throws — a render caller falls back to the raw value + source unit.
14224
+ */
14225
+ function tryConvertUnit(value, from, to) {
14226
+ if (from === to) return value;
14227
+ const fromSpec = specFor(from);
14228
+ if (!fromSpec) return void 0;
14229
+ const toSpec = specFor(to);
14230
+ if (!toSpec) return void 0;
14231
+ if (fromSpec.dimension !== toSpec.dimension) return void 0;
14232
+ return fromCanonical(toCanonical(value, fromSpec), toSpec);
14233
+ }
14234
+ //#endregion
13716
14235
  //#region ../types/dist/index.mjs
13717
14236
  var SMTP_INBOUND_DEFAULT_PORT = 2525;
13718
14237
  var SMTP_INBOUND_DEFAULT_DOMAIN = "camstack.local";
@@ -13909,17 +14428,18 @@ var IntegrationInstanceRefSchema = object({
13909
14428
  * message to what the kind supports — callers never special-case a service.
13910
14429
  *
13911
14430
  * DESIGN DECISIONS (locked):
13912
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
13913
- * `setTargetEnabled`), each provider persisting via the `settings-store`
13914
- * cap. Rationale: the admin UI needs one uniform surface across the
13915
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
13916
- * alternative would fork the UI per addon and cannot host the
13917
- * discovery→adopt flow.
13918
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
13919
- * the generated cap-mount auto-`concatCollection`-fans them across every
13920
- * registered provider (notifiers addon + HA addon) so one catalog is
13921
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
13922
- * `addonId` the generated collection router extracts from the call input.
14431
+ * - A target is an INSTANCE of this function cap (epic #440, step 6): the
14432
+ * providers serve their targets as `integration-instances` records, one
14433
+ * record kind per target kind, and the admin lists, adds, edits, switches
14434
+ * and deletes them through that cap. This cap keeps what an instance cannot
14435
+ * say: the kind catalogue (`listTargetKinds`: capabilities, delivery
14436
+ * parameters, brand icon), dispatch (`send`, `testTarget`) and the form
14437
+ * helpers (`discoverTargets`, `probeTargetConfig`).
14438
+ * - `listTargetKinds` / `discoverTargets` return arrays → the generated
14439
+ * cap-mount auto-`concatCollection`-fans them across every registered
14440
+ * provider so one catalog is routable. `send` / `testTarget` /
14441
+ * `probeTargetConfig` route to ONE provider by the `addonId` the generated
14442
+ * collection router extracts from the call input.
13923
14443
  * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
13924
14444
  * transformer) + UDS MsgPack both round-trip typed arrays — already used by
13925
14445
  * `storage` / `storage-provider` / `recording` caps over the same path. No
@@ -14164,7 +14684,7 @@ var TargetKindCapsSchema = object({
14164
14684
  * the union is large and not meant for runtime validation here; the exported
14165
14685
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14166
14686
  */
14167
- var ConfigSchemaPassthrough$4 = unknown();
14687
+ var ConfigSchemaPassthrough$3 = unknown();
14168
14688
  var TargetKindSchema = object({
14169
14689
  kind: string(),
14170
14690
  label: string(),
@@ -14201,7 +14721,7 @@ var TargetKindSchema = object({
14201
14721
  * its raster path, which is the safe default for an unknown image.
14202
14722
  */
14203
14723
  iconMediaType: string().optional(),
14204
- configSchema: ConfigSchemaPassthrough$4,
14724
+ configSchema: ConfigSchemaPassthrough$3,
14205
14725
  /**
14206
14726
  * The config keys that decide WHERE this kind's secrets are sent (server,
14207
14727
  * base URL, instance, chat, recipient). A save that changes any of them
@@ -14223,9 +14743,11 @@ var TargetKindSchema = object({
14223
14743
  caps: TargetKindCapsSchema
14224
14744
  });
14225
14745
  /**
14226
- * A persisted target. `config` holds secrets; providers REDACT secret fields
14227
- * (return a presence marker only) when serving `listTargets` — never
14228
- * round-trip a stored secret to the UI.
14746
+ * A target as a provider's own code sees it (the notification-center bridge, the
14747
+ * send path): the flat record body split into the name, the switch and the
14748
+ * kind's fields. `config` holds secrets; a reader that serves it to a client
14749
+ * REDACTS secret fields (a presence marker only) — never round-trip a stored
14750
+ * secret to the UI.
14229
14751
  */
14230
14752
  var TargetSchema = object({
14231
14753
  id: string(),
@@ -14268,13 +14790,13 @@ var ProbeTargetConfigResultSchema = object({
14268
14790
  stateInstance: string().optional(),
14269
14791
  error: string().optional()
14270
14792
  });
14271
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
14793
+ method(object({}), array(TargetKindSchema)), method(object({
14272
14794
  kind: string(),
14273
14795
  config: record(string(), unknown()).optional(),
14274
14796
  /**
14275
14797
  * When editing / duplicating, the blank password field never round-trips
14276
14798
  * the stored token. Pass the source target id so the provider can restore
14277
- * secrets server-side before dialling (same merge as upsert).
14799
+ * secrets server-side before dialling.
14278
14800
  */
14279
14801
  targetId: string().optional()
14280
14802
  }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
@@ -14297,27 +14819,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
14297
14819
  }), TestResultSchema, {
14298
14820
  kind: "mutation",
14299
14821
  auth: "admin"
14300
- }), method(object({
14301
- target: TargetSchema,
14302
- /**
14303
- * Duplicate flow: create a NEW target id while copying secrets from an
14304
- * existing row. The UI never holds the stored token; blank / redacted
14305
- * password fields are restored from this source (D634 still applies if
14306
- * destinations diverge without a re-typed secret).
14307
- */
14308
- cloneSecretsFrom: string().optional()
14309
- }), TargetSchema, {
14310
- kind: "mutation",
14311
- auth: "admin"
14312
- }), method(object({ targetId: string() }), _void(), {
14313
- kind: "mutation",
14314
- auth: "admin"
14315
- }), method(object({
14316
- targetId: string(),
14317
- enabled: boolean()
14318
- }), _void(), {
14319
- kind: "mutation",
14320
- auth: "admin"
14321
14822
  });
14322
14823
  looseObject({
14323
14824
  name: string().min(1),
@@ -22585,7 +23086,6 @@ var SYSTEM_CAP_VOCABULARY = {
22585
23086
  "notification-output": system("notification delivery — reached through notifier devices and rules"),
22586
23087
  "notification-rules": system("notification rules — authored on their own page"),
22587
23088
  "oauth-integration": system(IDENTITY),
22588
- "oidc-connections": system(IDENTITY),
22589
23089
  "osd-manager": system(PIPELINE),
22590
23090
  "pipeline-executor": system(PIPELINE),
22591
23091
  "pipeline-orchestrator": system(PIPELINE),
@@ -42606,6 +43106,7 @@ var ExpressionBindingSourceSchema = union([
42606
43106
  object({
42607
43107
  kind: literal("global"),
42608
43108
  sourceStableId: string(),
43109
+ addonId: string().optional(),
42609
43110
  cap: string(),
42610
43111
  fieldPath: string()
42611
43112
  })
@@ -42672,385 +43173,6 @@ var SourceInfoSchema = object({
42672
43173
  raw: record(string(), unknown()).optional()
42673
43174
  });
42674
43175
  /**
42675
- * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
42676
- * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
42677
- * `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
42678
- * decimals). K derives from `°C = K − 273.15`.
42679
- */
42680
- var UNIT_TABLE = {
42681
- "°C": {
42682
- dimension: "temperature",
42683
- factor: 1,
42684
- offset: 0
42685
- },
42686
- "°F": {
42687
- dimension: "temperature",
42688
- factor: 5 / 9,
42689
- offset: -160 / 9
42690
- },
42691
- K: {
42692
- dimension: "temperature",
42693
- factor: 1,
42694
- offset: -273.15
42695
- },
42696
- hPa: {
42697
- dimension: "pressure",
42698
- factor: 1,
42699
- offset: 0
42700
- },
42701
- kPa: {
42702
- dimension: "pressure",
42703
- factor: 10,
42704
- offset: 0
42705
- },
42706
- Pa: {
42707
- dimension: "pressure",
42708
- factor: .01,
42709
- offset: 0
42710
- },
42711
- mbar: {
42712
- dimension: "pressure",
42713
- factor: 1,
42714
- offset: 0
42715
- },
42716
- bar: {
42717
- dimension: "pressure",
42718
- factor: 1e3,
42719
- offset: 0
42720
- },
42721
- inHg: {
42722
- dimension: "pressure",
42723
- factor: 33.8639,
42724
- offset: 0
42725
- },
42726
- mmHg: {
42727
- dimension: "pressure",
42728
- factor: 1.33322,
42729
- offset: 0
42730
- },
42731
- psi: {
42732
- dimension: "pressure",
42733
- factor: 68.9476,
42734
- offset: 0
42735
- },
42736
- "m/s": {
42737
- dimension: "speed",
42738
- factor: 1,
42739
- offset: 0
42740
- },
42741
- "km/h": {
42742
- dimension: "speed",
42743
- factor: 1 / 3.6,
42744
- offset: 0
42745
- },
42746
- mph: {
42747
- dimension: "speed",
42748
- factor: .44704,
42749
- offset: 0
42750
- },
42751
- kn: {
42752
- dimension: "speed",
42753
- factor: .514444,
42754
- offset: 0
42755
- },
42756
- "mm/h": {
42757
- dimension: "precipitation-rate",
42758
- factor: 1,
42759
- offset: 0
42760
- },
42761
- "in/h": {
42762
- dimension: "precipitation-rate",
42763
- factor: 25.4,
42764
- offset: 0
42765
- },
42766
- m: {
42767
- dimension: "length",
42768
- factor: 1,
42769
- offset: 0
42770
- },
42771
- mm: {
42772
- dimension: "length",
42773
- factor: .001,
42774
- offset: 0
42775
- },
42776
- cm: {
42777
- dimension: "length",
42778
- factor: .01,
42779
- offset: 0
42780
- },
42781
- km: {
42782
- dimension: "length",
42783
- factor: 1e3,
42784
- offset: 0
42785
- },
42786
- in: {
42787
- dimension: "length",
42788
- factor: .0254,
42789
- offset: 0
42790
- },
42791
- ft: {
42792
- dimension: "length",
42793
- factor: .3048,
42794
- offset: 0
42795
- },
42796
- mi: {
42797
- dimension: "length",
42798
- factor: 1609.344,
42799
- offset: 0
42800
- },
42801
- "m²": {
42802
- dimension: "area",
42803
- factor: 1,
42804
- offset: 0
42805
- },
42806
- lx: {
42807
- dimension: "illuminance",
42808
- factor: 1,
42809
- offset: 0
42810
- },
42811
- "W/m²": {
42812
- dimension: "irradiance",
42813
- factor: 1,
42814
- offset: 0
42815
- },
42816
- W: {
42817
- dimension: "power",
42818
- factor: 1,
42819
- offset: 0
42820
- },
42821
- kW: {
42822
- dimension: "power",
42823
- factor: 1e3,
42824
- offset: 0
42825
- },
42826
- VA: {
42827
- dimension: "apparent-power",
42828
- factor: 1,
42829
- offset: 0
42830
- },
42831
- Wh: {
42832
- dimension: "energy",
42833
- factor: 1,
42834
- offset: 0
42835
- },
42836
- kWh: {
42837
- dimension: "energy",
42838
- factor: 1e3,
42839
- offset: 0
42840
- },
42841
- MWh: {
42842
- dimension: "energy",
42843
- factor: 1e6,
42844
- offset: 0
42845
- },
42846
- V: {
42847
- dimension: "voltage",
42848
- factor: 1,
42849
- offset: 0
42850
- },
42851
- mV: {
42852
- dimension: "voltage",
42853
- factor: .001,
42854
- offset: 0
42855
- },
42856
- A: {
42857
- dimension: "current",
42858
- factor: 1,
42859
- offset: 0
42860
- },
42861
- mA: {
42862
- dimension: "current",
42863
- factor: .001,
42864
- offset: 0
42865
- },
42866
- ppm: {
42867
- dimension: "concentration-volume",
42868
- factor: 1,
42869
- offset: 0
42870
- },
42871
- ppb: {
42872
- dimension: "concentration-volume",
42873
- factor: .001,
42874
- offset: 0
42875
- },
42876
- "µg/m³": {
42877
- dimension: "concentration-mass",
42878
- factor: 1,
42879
- offset: 0
42880
- },
42881
- "mg/m³": {
42882
- dimension: "concentration-mass",
42883
- factor: 1e3,
42884
- offset: 0
42885
- },
42886
- kg: {
42887
- dimension: "mass",
42888
- factor: 1,
42889
- offset: 0
42890
- },
42891
- g: {
42892
- dimension: "mass",
42893
- factor: .001,
42894
- offset: 0
42895
- },
42896
- lb: {
42897
- dimension: "mass",
42898
- factor: .453592,
42899
- offset: 0
42900
- },
42901
- oz: {
42902
- dimension: "mass",
42903
- factor: .0283495,
42904
- offset: 0
42905
- },
42906
- "%": {
42907
- dimension: "percentage",
42908
- factor: 1,
42909
- offset: 0
42910
- },
42911
- L: {
42912
- dimension: "volume",
42913
- factor: 1,
42914
- offset: 0
42915
- },
42916
- mL: {
42917
- dimension: "volume",
42918
- factor: .001,
42919
- offset: 0
42920
- },
42921
- gal: {
42922
- dimension: "volume",
42923
- factor: 3.785411784,
42924
- offset: 0
42925
- },
42926
- s: {
42927
- dimension: "duration",
42928
- factor: 1,
42929
- offset: 0
42930
- },
42931
- ms: {
42932
- dimension: "duration",
42933
- factor: .001,
42934
- offset: 0
42935
- },
42936
- min: {
42937
- dimension: "duration",
42938
- factor: 60,
42939
- offset: 0
42940
- },
42941
- h: {
42942
- dimension: "duration",
42943
- factor: 3600,
42944
- offset: 0
42945
- },
42946
- d: {
42947
- dimension: "duration",
42948
- factor: 86400,
42949
- offset: 0
42950
- },
42951
- B: {
42952
- dimension: "information",
42953
- factor: 1,
42954
- offset: 0
42955
- },
42956
- KiB: {
42957
- dimension: "information",
42958
- factor: 1024,
42959
- offset: 0
42960
- },
42961
- MiB: {
42962
- dimension: "information",
42963
- factor: 1024 ** 2,
42964
- offset: 0
42965
- },
42966
- GiB: {
42967
- dimension: "information",
42968
- factor: 1024 ** 3,
42969
- offset: 0
42970
- },
42971
- TiB: {
42972
- dimension: "information",
42973
- factor: 1024 ** 4,
42974
- offset: 0
42975
- },
42976
- kB: {
42977
- dimension: "information",
42978
- factor: 1e3,
42979
- offset: 0
42980
- },
42981
- MB: {
42982
- dimension: "information",
42983
- factor: 1e6,
42984
- offset: 0
42985
- },
42986
- GB: {
42987
- dimension: "information",
42988
- factor: 1e9,
42989
- offset: 0
42990
- },
42991
- "°": {
42992
- dimension: "angle",
42993
- factor: 1,
42994
- offset: 0
42995
- },
42996
- dBFS: {
42997
- dimension: "sound-level",
42998
- factor: 1,
42999
- offset: 0
43000
- },
43001
- dBm: {
43002
- dimension: "signal-level",
43003
- factor: 1,
43004
- offset: 0
43005
- },
43006
- mired: {
43007
- dimension: "colour-temperature",
43008
- factor: 1,
43009
- offset: 0
43010
- },
43011
- kbps: {
43012
- dimension: "bitrate",
43013
- factor: 1,
43014
- offset: 0
43015
- },
43016
- Mbps: {
43017
- dimension: "bitrate",
43018
- factor: 1e3,
43019
- offset: 0
43020
- },
43021
- fps: {
43022
- dimension: "rate",
43023
- factor: 1,
43024
- offset: 0
43025
- }
43026
- };
43027
- /** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
43028
- function specFor(unit) {
43029
- return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
43030
- }
43031
- /** Value in `spec`'s unit → its dimension's canonical unit. */
43032
- function toCanonical(value, spec) {
43033
- return value * spec.factor + spec.offset;
43034
- }
43035
- /** Canonical-unit value → the unit described by `spec`. */
43036
- function fromCanonical(canonical, spec) {
43037
- return (canonical - spec.offset) / spec.factor;
43038
- }
43039
- /**
43040
- * Graceful variant for the render path: returns the converted value, or
43041
- * `undefined` when the conversion is refused (unknown unit / cross-dimension).
43042
- * Never throws — a render caller falls back to the raw value + source unit.
43043
- */
43044
- function tryConvertUnit(value, from, to) {
43045
- if (from === to) return value;
43046
- const fromSpec = specFor(from);
43047
- if (!fromSpec) return void 0;
43048
- const toSpec = specFor(to);
43049
- if (!toSpec) return void 0;
43050
- if (fromSpec.dimension !== toSpec.dimension) return void 0;
43051
- return fromCanonical(toCanonical(value, fromSpec), toSpec);
43052
- }
43053
- /**
43054
43176
  * A number a person types for a physical quantity — the input side of the
43055
43177
  * value + unit contract (D972).
43056
43178
  *
@@ -44727,15 +44849,28 @@ var SettingsSchemaWithValuesSchema = object({
44727
44849
  });
44728
44850
  /** Patch object — keys are field names, values are the new field values. */
44729
44851
  var SettingsPatchSchema = record(string(), unknown());
44852
+ /** The scope of a scoped-settings call (D986): where the capability declared the values. */
44853
+ var ScopedSettingsScopeSchema = _enum(SCOPED_SETTINGS_SCOPES);
44854
+ /** One scoped schema an addon serves: its capability, scope and section ids. */
44855
+ var ScopedSettingsDeclarationSchema = object({
44856
+ cap: string(),
44857
+ scope: ScopedSettingsScopeSchema,
44858
+ sectionIds: array(string())
44859
+ });
44730
44860
  /** Standard success response for update operations. */
44731
44861
  var SettingsUpdateResultSchema = object({ success: literal(true) });
44732
44862
  method(object({
44733
44863
  addonId: string(),
44734
- nodeId: string().optional(),
44735
- overlay: record(string(), unknown()).optional(),
44736
- cap: string().optional()
44864
+ cap: string(),
44865
+ scope: ScopedSettingsScopeSchema,
44866
+ nodeId: string().optional()
44737
44867
  }), SettingsSchemaWithValuesSchema.nullable()), method(object({
44738
44868
  addonId: string(),
44869
+ nodeId: string().optional()
44870
+ }), array(ScopedSettingsDeclarationSchema).nullable()), method(object({
44871
+ addonId: string(),
44872
+ cap: string(),
44873
+ scope: ScopedSettingsScopeSchema,
44739
44874
  nodeId: string().optional(),
44740
44875
  patch: SettingsPatchSchema
44741
44876
  }), SettingsUpdateResultSchema, {
@@ -44743,8 +44878,10 @@ method(object({
44743
44878
  auth: "admin"
44744
44879
  }), method(object({
44745
44880
  addonId: string(),
44881
+ cap: string(),
44882
+ scope: ScopedSettingsScopeSchema,
44746
44883
  nodeId: string().optional()
44747
- }), SettingsSchemaWithValuesSchema.nullable()), method(object({
44884
+ }), record(string(), unknown()).nullable()), method(object({
44748
44885
  addonId: string(),
44749
44886
  nodeId: string().optional(),
44750
44887
  instance: IntegrationInstanceRefSchema
@@ -46084,7 +46221,7 @@ var BackupDestinationInfoSchema = object({
46084
46221
  id: string(),
46085
46222
  /**
46086
46223
  * Manifest id of the owning addon. The Settings button on the
46087
- * destination card opens that addon's `globalSettingsSchema` panel.
46224
+ * destination card opens that addon's scoped settings panel.
46088
46225
  */
46089
46226
  addonId: string(),
46090
46227
  /** Operator-toggled enable flag from the destination-policy table. */
@@ -50554,6 +50691,7 @@ method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(Expor
50554
50691
  kind: "mutation",
50555
50692
  auth: "admin"
50556
50693
  });
50694
+ var DeviceAliasSchema = string().max(64).regex(/^[a-z][a-z0-9_]*$/);
50557
50695
  /**
50558
50696
  * A SITE ZONE — a named part of the property that several locations belong to.
50559
50697
  *
@@ -50947,6 +51085,12 @@ var DeviceInfoSchema = object({
50947
51085
  addonId: string(),
50948
51086
  type: _enum(DeviceType),
50949
51087
  name: string(),
51088
+ /** The name a value template uses for this device (`{{ stufa_veranda.temperature }}`),
51089
+ * entity_id style. Generated once from the name at first registration, unique
51090
+ * cluster-wide, editable through `setAlias`, NOT changed by a rename (D1021).
51091
+ * Optional: a record produced by an older build omits it until the boot
51092
+ * backfill has run. */
51093
+ alias: DeviceAliasSchema.optional(),
50950
51094
  /** Operator-organisational location label. `null` when unset. */
50951
51095
  location: string().nullable(),
50952
51096
  /** Soft-disabled flag. */
@@ -51266,6 +51410,25 @@ method(object({
51266
51410
  }), _void(), {
51267
51411
  kind: "mutation",
51268
51412
  auth: "admin"
51413
+ }), method(object({
51414
+ deviceId: number(),
51415
+ alias: string()
51416
+ }), discriminatedUnion("ok", [object({
51417
+ ok: literal(true),
51418
+ alias: DeviceAliasSchema
51419
+ }), object({
51420
+ ok: literal(false),
51421
+ reason: _enum([
51422
+ "empty",
51423
+ "too-long",
51424
+ "grammar",
51425
+ "reserved",
51426
+ "taken"
51427
+ ]),
51428
+ takenBy: number().optional()
51429
+ })]), {
51430
+ kind: "mutation",
51431
+ auth: "admin"
51269
51432
  }), method(object({
51270
51433
  deviceId: number(),
51271
51434
  location: string().nullable()
@@ -54291,7 +54454,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
54291
54454
  * or the redaction token KEEPS the stored secret; `{ $secretClear: true }`
54292
54455
  * clears it.
54293
54456
  */
54294
- var ConfigSchemaPassthrough$3 = custom();
54457
+ var ConfigSchemaPassthrough$2 = custom();
54295
54458
  var IntegrationInstanceIdSchema = string().min(1);
54296
54459
  var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
54297
54460
  /**
@@ -54337,7 +54500,16 @@ var IntegrationInstanceKindSchema = object({
54337
54500
  /** Relative hub path to start an interactive account link of an instance of this kind. */
54338
54501
  connectPath: string().min(1).optional(),
54339
54502
  /** Relative hub path to disconnect that account. */
54340
- disconnectPath: string().min(1).optional()
54503
+ disconnectPath: string().min(1).optional(),
54504
+ /**
54505
+ * Parts of the edit dialog the flat form cannot draw (a node picker, a model source), named by a
54506
+ * GENERIC widget id the client maps to a component; a client that knows none of them shows none.
54507
+ * `connection` widgets write connection values, `model` widgets own the model choice.
54508
+ */
54509
+ widgets: array(object({
54510
+ id: string().min(1),
54511
+ placement: _enum(["connection", "model"])
54512
+ })).optional()
54341
54513
  });
54342
54514
  /**
54343
54515
  * How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
@@ -54442,7 +54614,7 @@ var integrationInstancesCapability = {
54442
54614
  id: IntegrationInstanceIdSchema.nullable(),
54443
54615
  /** The kind of the create form (`id: null`) of a provider with several. */
54444
54616
  kind: string().min(1).optional()
54445
- }), ConfigSchemaPassthrough$3, { auth: "admin" }),
54617
+ }), ConfigSchemaPassthrough$2, { auth: "admin" }),
54446
54618
  listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
54447
54619
  getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
54448
54620
  /**
@@ -55393,12 +55565,12 @@ var LlmModelInfoSchema = object({
55393
55565
  });
55394
55566
  /**
55395
55567
  * The provider-specific part of a profile, validated by the provider that owns
55396
- * the kind (`upsertProfile` refuses what it does not accept). A field its
55568
+ * the kind (saving an instance refuses what it does not accept). A field its
55397
55569
  * `connectionSchema` declares `type: 'password'` is a secret: it is held in the
55398
55570
  * keychain, never in the row, and reads back as the redaction marker.
55399
55571
  */
55400
55572
  var LlmProviderConfigSchema = record(string(), unknown());
55401
- var LlmProfileSchema = object({
55573
+ object({
55402
55574
  id: string(),
55403
55575
  name: string(),
55404
55576
  /** The provider-declared kind (`LlmProfileKindDescriptor.kind`). */
@@ -55492,8 +55664,8 @@ var LlmProviderWidgetSchema = object({
55492
55664
  /** ConfigUISchema tree passed through untyped on the wire (the
55493
55665
  * notification-output `ConfigSchemaPassthrough` precedent at
55494
55666
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
55495
- var ConfigSchemaPassthrough$2 = unknown();
55496
- var LlmProfileKindDescriptorSchema = object({
55667
+ var ConfigSchemaPassthrough$1 = unknown();
55668
+ object({
55497
55669
  kind: LlmProfileKindSchema,
55498
55670
  label: string(),
55499
55671
  /** A generic icon name (`cloud`, `plug`, `cpu`) — never a glyph or a logo. */
@@ -55501,9 +55673,9 @@ var LlmProfileKindDescriptorSchema = object({
55501
55673
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
55502
55674
  addonId: string(),
55503
55675
  /** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
55504
- connectionSchema: ConfigSchemaPassthrough$2,
55676
+ connectionSchema: ConfigSchemaPassthrough$1,
55505
55677
  /** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
55506
- advancedSchema: ConfigSchemaPassthrough$2.optional(),
55678
+ advancedSchema: ConfigSchemaPassthrough$1.optional(),
55507
55679
  /** The kind's defaults; a model from `listModels` may override any of them. */
55508
55680
  features: LlmModelFeaturesSchema,
55509
55681
  /** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
@@ -55632,20 +55804,7 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
55632
55804
  }), method(object({
55633
55805
  addonId: string().optional(),
55634
55806
  requestId: string()
55635
- }), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
55636
- kind: "mutation",
55637
- auth: "admin"
55638
- }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
55639
- kind: "mutation",
55640
- auth: "admin"
55641
- }), method(ProfileRefInputSchema, _void(), {
55642
- kind: "mutation",
55643
- auth: "admin"
55644
- }), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
55645
- kind: "mutation",
55646
- auth: "admin",
55647
- timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
55648
- }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
55807
+ }), _void(), { kind: "mutation" }), method(LlmListModelsInputSchema, LlmListModelsResultSchema, {
55649
55808
  kind: "mutation",
55650
55809
  auth: "admin"
55651
55810
  }), method(object({}), array(LlmDefaultSchema)), method(object({
@@ -56640,20 +56799,7 @@ method(_void(), MeshStatusSchema), method(object({
56640
56799
  /** Optional hostname override the host should advertise once joined. */
56641
56800
  hostname: string().optional() }), object({
56642
56801
  /** Authentication URL the operator should open in a browser. */
56643
- loginUrl: string() }), { kind: "mutation" }), method(_void(), object({ left: literal(true) }), { kind: "mutation" }), method(_void(), object({ loggedOut: literal(true) }), { kind: "mutation" }), method(_void(), object({ peers: array(MeshPeerSchema).readonly() })), method(object({
56644
- /** Optional auth key — when provided, probes the key validity
56645
- * against the provider's API. Omit when already joined to
56646
- * just ping the daemon. */
56647
- authKey: string().optional() }), object({
56648
- ok: boolean(),
56649
- /** Provider-side identifier resolved by the probe (tailnet
56650
- * name for Tailscale, network id for ZeroTier, etc.). */
56651
- tenant: string().optional(),
56652
- /** Daemon binary version, when reachable. */
56653
- daemonVersion: string().optional(),
56654
- /** Human-readable error when `ok: false`. */
56655
- error: string().optional()
56656
- }), { kind: "mutation" });
56802
+ loginUrl: string() }), { kind: "mutation" }), method(_void(), object({ left: literal(true) }), { kind: "mutation" }), method(_void(), object({ loggedOut: literal(true) }), { kind: "mutation" }), method(_void(), object({ peers: array(MeshPeerSchema).readonly() }));
56657
56803
  object({
56658
56804
  value: number().nullable().meta(readingMeta("device", { label: "Total" })),
56659
56805
  lastFetchedAt: number()
@@ -64032,6 +64178,11 @@ var NcScheduleSchema = object({
64032
64178
  * as a STATE SUMMARY at the end of each window of `schedule`.
64033
64179
  */
64034
64180
  var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
64181
+ _enum([
64182
+ "activation",
64183
+ "deactivation",
64184
+ "schedule"
64185
+ ]);
64035
64186
  /**
64036
64187
  * How a summary window is SENT (D698): `rule` (absent) = ONE notification with
64037
64188
  * every section; `section` = one notification per section with something to
@@ -64048,11 +64199,26 @@ var NcStateSummarySplitSchema = _enum(["rule", "section"]);
64048
64199
  * midnight crossing); the send time is each window's END. Refused on the
64049
64200
  * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
64050
64201
  */
64051
- var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
64052
- mode: literal("summary"),
64053
- schedule: NcScheduleSchema,
64054
- split: NcStateSummarySplitSchema.optional()
64055
- })]);
64202
+ var NcSystemEventDeliverySchema = discriminatedUnion("mode", [
64203
+ object({ mode: literal("instant") }),
64204
+ object({
64205
+ mode: literal("summary"),
64206
+ schedule: NcScheduleSchema,
64207
+ split: NcStateSummarySplitSchema.optional()
64208
+ }),
64209
+ object({
64210
+ mode: literal("modes"),
64211
+ /** The bad edge, and every kind that has a single edge. */
64212
+ onActivation: boolean(),
64213
+ /** The recovery edges of the classes of the selected kinds (derived, never stored per kind). */
64214
+ onDeactivation: boolean(),
64215
+ /** A state summary at the end of each window; absent = no summary. */
64216
+ onSchedule: object({
64217
+ schedule: NcScheduleSchema,
64218
+ grouping: NcStateSummarySplitSchema
64219
+ }).optional()
64220
+ })
64221
+ ]);
64056
64222
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
64057
64223
  var NcPlateMatcherSchema = object({
64058
64224
  /**
@@ -66329,61 +66495,6 @@ var OauthIntegrationDescriptorSchema = object({
66329
66495
  });
66330
66496
  method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
66331
66497
  /**
66332
- * `oidc-connections` — CRUD for IdP rows under `auth-oidc` (D828 sibling).
66333
- *
66334
- * One hub-only singleton (`auth-oidc`). The Settings UI is a DataTable +
66335
- * ItemFormDialog; there is no `editable-array` in `globalSettingsSchema`.
66336
- * Operator-chosen `id` is the URL slug (`/addon/auth-oidc/<id>/…`) and is
66337
- * immutable after create.
66338
- */
66339
- var ConfigSchemaPassthrough$1 = unknown();
66340
- var OidcConnectionSchema = object({
66341
- id: string().min(1),
66342
- displayName: string(),
66343
- icon: string(),
66344
- issuerUrl: string(),
66345
- clientId: string(),
66346
- /** Client id + secret present — never the secret value. */
66347
- configured: boolean(),
66348
- /**
66349
- * Values for the connection form schema. `clientSecret` is a keychain
66350
- * {@link SecretFieldView} or `null` / `''`, never plaintext (D789).
66351
- */
66352
- formValues: record(string(), unknown()).optional()
66353
- });
66354
- /**
66355
- * Patch one connection. Omitted or empty `clientSecret` keeps the stored
66356
- * secret (D787). `{ $secretClear: true }` clears it. `id` is never renamed.
66357
- */
66358
- var OidcConnectionUpdateInputSchema = object({
66359
- id: string().min(1),
66360
- displayName: string().min(1),
66361
- icon: string().optional(),
66362
- issuerUrl: string().optional(),
66363
- clientId: string().optional(),
66364
- clientSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
66365
- redirectUri: string().optional(),
66366
- scopes: string().optional(),
66367
- usernameClaim: _enum([
66368
- "preferred_username",
66369
- "email",
66370
- "sub"
66371
- ]).optional()
66372
- });
66373
- method(object({}), ConfigSchemaPassthrough$1, { auth: "admin" }), method(object({}), array(OidcConnectionSchema).readonly(), { auth: "admin" }), method(object({
66374
- id: string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug"),
66375
- displayName: string().min(1)
66376
- }), object({ id: string() }), {
66377
- kind: "mutation",
66378
- auth: "admin"
66379
- }), method(OidcConnectionUpdateInputSchema, object({ ok: literal(true) }), {
66380
- kind: "mutation",
66381
- auth: "admin"
66382
- }), method(object({ id: string().min(1) }), object({ ok: literal(true) }), {
66383
- kind: "mutation",
66384
- auth: "admin"
66385
- });
66386
- /**
66387
66498
  * Generic on-screen-display (video overlay) cap. Each camera exposes
66388
66499
  * a firmware-defined list of overlays (channel name, timestamp,
66389
66500
  * watermark, logo, …); the cap models them as a homogeneous array of
@@ -72556,21 +72667,6 @@ method(object({
72556
72667
  */
72557
72668
  ttlSec: union([number().int().positive(), literal("never")]).optional()
72558
72669
  }), object({ token: string() }), { auth: "admin" }), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable(), { auth: "admin" });
72559
- var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
72560
- providerId: string().min(1),
72561
- displayName: string().min(1),
72562
- configSchema: unknown(),
72563
- nodeLocal: boolean(),
72564
- shouldSaveDiskSpace: literal(true),
72565
- minFreePercent: number().min(0).max(100)
72566
- }), object({
72567
- providerId: string().min(1),
72568
- displayName: string().min(1),
72569
- configSchema: unknown(),
72570
- nodeLocal: boolean(),
72571
- shouldSaveDiskSpace: literal(false),
72572
- minFreePercent: literal(null)
72573
- })]);
72574
72670
  method(object({
72575
72671
  location: StorageLocationRefSchema,
72576
72672
  relativePath: string()
@@ -72632,42 +72728,7 @@ method(object({
72632
72728
  }), _instanceof(Uint8Array), { auth: "admin" }), method(object({ downloadId: string() }), _void(), {
72633
72729
  kind: "mutation",
72634
72730
  auth: "admin"
72635
- }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
72636
- createdAt: true,
72637
- updatedAt: true
72638
- }), StorageLocationSchema, {
72639
- kind: "mutation",
72640
- auth: "admin"
72641
- }), method(object({
72642
- id: string(),
72643
- force: boolean().optional()
72644
- }), _void(), {
72645
- kind: "mutation",
72646
- auth: "admin"
72647
- }), method(_void(), array(StorageDrainProgressSchema).readonly()), method(object({ id: string() }), object({
72648
- ok: boolean(),
72649
- error: string().optional()
72650
- }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
72651
- providerId: string(),
72652
- /**
72653
- * The location this config is an UNSAVED edit of, when there is one.
72654
- *
72655
- * `listLocations` replaces every declared secret with the redaction
72656
- * sentinel, so the edit modal's form state holds the sentinel for any
72657
- * credential the operator did not retype — and posting that here
72658
- * without a way to resolve it makes the provider try to authenticate
72659
- * as `__camstack_redacted__` and report the operator's own working
72660
- * password as wrong. Given this id, the orchestrator restores each
72661
- * sentinel from the stored config (same rule as `upsertLocation`)
72662
- * before dispatching. Omitted by the "Add location" wizard, where
72663
- * every value was typed just now and nothing is stored yet.
72664
- */
72665
- locationId: string().optional(),
72666
- config: record(string(), unknown())
72667
- }), object({
72668
- ok: boolean(),
72669
- error: string().optional()
72670
- }), { auth: "admin" });
72731
+ }), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(_void(), array(StorageDrainProgressSchema).readonly());
72671
72732
  /** One table of the settings database. */
72672
72733
  var StorageTableSchema = object({
72673
72734
  /** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
@@ -73898,7 +73959,7 @@ var TerminalInstanceInfoSchema = object({
73898
73959
  /**
73899
73960
  * False while the node the instance is bound to is not in the cluster
73900
73961
  * topology (forgotten, renamed, re-paired). The instance is kept; the
73901
- * operator re-points it with `updateInstance({ targetNodeId })`.
73962
+ * operator re-points it on the Terminal page.
73902
73963
  */
73903
73964
  nodeInCluster: boolean(),
73904
73965
  profileId: string(),
@@ -73936,41 +73997,7 @@ var TerminalOutputBatchSchema = object({
73936
73997
  snapshot: string().optional(),
73937
73998
  events: array(TerminalOutputEventSchema).readonly()
73938
73999
  });
73939
- method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
73940
- targetNodeId: string().min(1),
73941
- profileId: string().min(1),
73942
- name: string().trim().min(1).max(160).optional(),
73943
- executable: string().max(1024).optional(),
73944
- args: array(string().max(2048)).max(64).optional(),
73945
- cwd: string().max(1024).optional(),
73946
- environment: array(string().max(4096)).max(64).optional(),
73947
- profileSettings: ProfileSettingsBagSchema.optional()
73948
- }), TerminalInstanceInfoSchema, {
73949
- kind: "mutation",
73950
- auth: "admin"
73951
- }), method(object({
73952
- instanceId: string().min(1),
73953
- /** Re-point the instance to another node; the profile must exist there. */
73954
- targetNodeId: string().min(1).max(256).optional(),
73955
- name: string().trim().min(1).max(160).optional(),
73956
- executable: string().max(1024).optional(),
73957
- args: array(string().max(2048)).max(64).optional(),
73958
- cwd: string().max(1024).optional(),
73959
- environment: array(string().max(4096)).max(64).optional(),
73960
- profileSettings: ProfileSettingsBagSchema.optional()
73961
- }), TerminalInstanceInfoSchema, {
73962
- kind: "mutation",
73963
- auth: "admin"
73964
- }), method(object({ instanceId: string().min(1) }), _void(), {
73965
- kind: "mutation",
73966
- auth: "admin"
73967
- }), method(object({
73968
- instanceId: string().min(1),
73969
- enabled: boolean()
73970
- }), TerminalInstanceInfoSchema, {
73971
- kind: "mutation",
73972
- auth: "admin"
73973
- }), method(_void(), array(TerminalLegacyCameraSchema).readonly(), { auth: "admin" }), method(object({
74000
+ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalLegacyCameraSchema).readonly(), { auth: "admin" }), method(object({
73974
74001
  stableId: string().min(1),
73975
74002
  name: string().trim().min(1).max(160).optional()
73976
74003
  }), TerminalInstanceInfoSchema, {
@@ -74039,6 +74066,11 @@ var TimedRunUnitSchema = _enum(["s", "min"]);
74039
74066
  _enum(["switch", "pet-fountain"]);
74040
74067
  DeviceType.Heater, DeviceType.PetFountain;
74041
74068
  DeviceType.Heater, DeviceType.PetFountain;
74069
+ /** One duration, as the one-tap run asks for it. */
74070
+ var TimedRunQuickRunSchema = object({
74071
+ duration: number().positive().finite(),
74072
+ unit: TimedRunUnitSchema
74073
+ });
74042
74074
  /** Why a request was not carried out. Typed, so a caller can say it in words. */
74043
74075
  var TimedRunRefusalSchema = _enum([
74044
74076
  "unsupported-device",
@@ -74059,9 +74091,30 @@ object({
74059
74091
  activeUntil: number().nullable(),
74060
74092
  /** Who asked for the run (a user id or an addon id); `null` when nothing is armed or it did not say. */
74061
74093
  requestedBy: string().nullable(),
74094
+ /**
74095
+ * The one-tap run of THIS device: what a "Run" button on any surface starts. `null` when
74096
+ * the device is gone or its type has no timed run; a status from before this field
74097
+ * existed has none, and a surface falls back to the type's default.
74098
+ */
74099
+ quickRun: TimedRunQuickRunSchema.nullable(),
74062
74100
  lastFetchedAt: number()
74063
74101
  });
74064
- method(object({
74102
+ /** What the settings form needs: the unit, the bounds and the type's default. */
74103
+ var TimedRunOptionsSchema = object({
74104
+ unit: TimedRunUnitSchema,
74105
+ /** The longest run, in `unit`. */
74106
+ maxDuration: number(),
74107
+ defaultDuration: number()
74108
+ });
74109
+ /** The one-tap run duration, in the type's unit; `null` returns to the type's default. */
74110
+ var TimedRunSettingsPatchSchema = object({ quickRunDuration: number().positive().finite().nullable().optional() });
74111
+ method(object({ deviceId: number().int() }), TimedRunOptionsSchema), method(object({
74112
+ deviceId: number().int(),
74113
+ settings: TimedRunSettingsPatchSchema
74114
+ }), _void(), {
74115
+ kind: "mutation",
74116
+ auth: "admin"
74117
+ }), method(object({
74065
74118
  deviceId: number().int(),
74066
74119
  duration: number().positive().finite(),
74067
74120
  unit: TimedRunUnitSchema
@@ -76767,21 +76820,28 @@ Object.freeze({
76767
76820
  access: "view",
76768
76821
  role: "admin"
76769
76822
  },
76770
- "addonSettings.getGlobalSettings": {
76823
+ "addonSettings.getIntegrationFormSchema": {
76824
+ capName: "addon-settings",
76825
+ capScope: "system",
76826
+ addonId: null,
76827
+ access: "view",
76828
+ role: "admin"
76829
+ },
76830
+ "addonSettings.getScopedSettings": {
76771
76831
  capName: "addon-settings",
76772
76832
  capScope: "system",
76773
76833
  addonId: null,
76774
76834
  access: "view",
76775
76835
  role: "admin"
76776
76836
  },
76777
- "addonSettings.getIntegrationFormSchema": {
76837
+ "addonSettings.listScopedSettings": {
76778
76838
  capName: "addon-settings",
76779
76839
  capScope: "system",
76780
76840
  addonId: null,
76781
76841
  access: "view",
76782
76842
  role: "admin"
76783
76843
  },
76784
- "addonSettings.getIntegrationSettings": {
76844
+ "addonSettings.readScopedValues": {
76785
76845
  capName: "addon-settings",
76786
76846
  capScope: "system",
76787
76847
  addonId: null,
@@ -76802,7 +76862,7 @@ Object.freeze({
76802
76862
  access: "create",
76803
76863
  role: "admin"
76804
76864
  },
76805
- "addonSettings.updateGlobalSettings": {
76865
+ "addonSettings.updateScopedSettings": {
76806
76866
  capName: "addon-settings",
76807
76867
  capScope: "system",
76808
76868
  addonId: null,
@@ -78972,6 +79032,13 @@ Object.freeze({
78972
79032
  access: "create",
78973
79033
  role: "admin"
78974
79034
  },
79035
+ "deviceManager.setAlias": {
79036
+ capName: "device-manager",
79037
+ capScope: "system",
79038
+ addonId: null,
79039
+ access: "create",
79040
+ role: "admin"
79041
+ },
78975
79042
  "deviceManager.setChildLayout": {
78976
79043
  capName: "device-manager",
78977
79044
  capScope: "system",
@@ -80260,13 +80327,6 @@ Object.freeze({
80260
80327
  access: "delete",
80261
80328
  role: "admin"
80262
80329
  },
80263
- "llm.deleteProfile": {
80264
- capName: "llm",
80265
- capScope: "system",
80266
- addonId: null,
80267
- access: "delete",
80268
- role: "admin"
80269
- },
80270
80330
  "llm.generate": {
80271
80331
  capName: "llm",
80272
80332
  capScope: "system",
@@ -80330,20 +80390,6 @@ Object.freeze({
80330
80390
  access: "view",
80331
80391
  role: "admin"
80332
80392
  },
80333
- "llm.listProfileKinds": {
80334
- capName: "llm",
80335
- capScope: "system",
80336
- addonId: null,
80337
- access: "view",
80338
- role: "admin"
80339
- },
80340
- "llm.listProfiles": {
80341
- capName: "llm",
80342
- capScope: "system",
80343
- addonId: null,
80344
- access: "view",
80345
- role: "admin"
80346
- },
80347
80393
  "llm.listRuntimeNodes": {
80348
80394
  capName: "llm",
80349
80395
  capScope: "system",
@@ -80351,13 +80397,6 @@ Object.freeze({
80351
80397
  access: "view",
80352
80398
  role: "admin"
80353
80399
  },
80354
- "llm.migrateRawSecrets": {
80355
- capName: "llm",
80356
- capScope: "system",
80357
- addonId: null,
80358
- access: "create",
80359
- role: "admin"
80360
- },
80361
80400
  "llm.resolveModelRef": {
80362
80401
  capName: "llm",
80363
80402
  capScope: "system",
@@ -80386,20 +80425,6 @@ Object.freeze({
80386
80425
  access: "create",
80387
80426
  role: "admin"
80388
80427
  },
80389
- "llm.testProfile": {
80390
- capName: "llm",
80391
- capScope: "system",
80392
- addonId: null,
80393
- access: "create",
80394
- role: "admin"
80395
- },
80396
- "llm.upsertProfile": {
80397
- capName: "llm",
80398
- capScope: "system",
80399
- addonId: null,
80400
- access: "create",
80401
- role: "admin"
80402
- },
80403
80428
  "llmRuntime.appleAvailability": {
80404
80429
  capName: "llm-runtime",
80405
80430
  capScope: "system",
@@ -80904,13 +80929,6 @@ Object.freeze({
80904
80929
  access: "create",
80905
80930
  role: "admin"
80906
80931
  },
80907
- "meshNetwork.testConnection": {
80908
- capName: "mesh-network",
80909
- capScope: "system",
80910
- addonId: null,
80911
- access: "create",
80912
- role: "admin"
80913
- },
80914
80932
  "meter.getStatus": {
80915
80933
  capName: "meter",
80916
80934
  capScope: "device",
@@ -81569,13 +81587,6 @@ Object.freeze({
81569
81587
  access: "create",
81570
81588
  role: "admin"
81571
81589
  },
81572
- "notificationOutput.deleteTarget": {
81573
- capName: "notification-output",
81574
- capScope: "system",
81575
- addonId: null,
81576
- access: "delete",
81577
- role: "admin"
81578
- },
81579
81590
  "notificationOutput.discoverTargets": {
81580
81591
  capName: "notification-output",
81581
81592
  capScope: "system",
@@ -81583,13 +81594,6 @@ Object.freeze({
81583
81594
  access: "view",
81584
81595
  role: "admin"
81585
81596
  },
81586
- "notificationOutput.getTarget": {
81587
- capName: "notification-output",
81588
- capScope: "system",
81589
- addonId: null,
81590
- access: "view",
81591
- role: "admin"
81592
- },
81593
81597
  "notificationOutput.listTargetKinds": {
81594
81598
  capName: "notification-output",
81595
81599
  capScope: "system",
@@ -81597,13 +81601,6 @@ Object.freeze({
81597
81601
  access: "view",
81598
81602
  role: "admin"
81599
81603
  },
81600
- "notificationOutput.listTargets": {
81601
- capName: "notification-output",
81602
- capScope: "system",
81603
- addonId: null,
81604
- access: "view",
81605
- role: "admin"
81606
- },
81607
81604
  "notificationOutput.probeTargetConfig": {
81608
81605
  capName: "notification-output",
81609
81606
  capScope: "system",
@@ -81618,13 +81615,6 @@ Object.freeze({
81618
81615
  access: "create",
81619
81616
  role: "admin"
81620
81617
  },
81621
- "notificationOutput.setTargetEnabled": {
81622
- capName: "notification-output",
81623
- capScope: "system",
81624
- addonId: null,
81625
- access: "create",
81626
- role: "admin"
81627
- },
81628
81618
  "notificationOutput.testTarget": {
81629
81619
  capName: "notification-output",
81630
81620
  capScope: "system",
@@ -81632,13 +81622,6 @@ Object.freeze({
81632
81622
  access: "create",
81633
81623
  role: "admin"
81634
81624
  },
81635
- "notificationOutput.upsertTarget": {
81636
- capName: "notification-output",
81637
- capScope: "system",
81638
- addonId: null,
81639
- access: "create",
81640
- role: "admin"
81641
- },
81642
81625
  "notificationRules.activateAlarmProfile": {
81643
81626
  capName: "notification-rules",
81644
81627
  capScope: "system",
@@ -81898,41 +81881,6 @@ Object.freeze({
81898
81881
  access: "view",
81899
81882
  role: "admin"
81900
81883
  },
81901
- "oidcConnections.connectionFormSchema": {
81902
- capName: "oidc-connections",
81903
- capScope: "system",
81904
- addonId: null,
81905
- access: "view",
81906
- role: "admin"
81907
- },
81908
- "oidcConnections.createConnection": {
81909
- capName: "oidc-connections",
81910
- capScope: "system",
81911
- addonId: null,
81912
- access: "create",
81913
- role: "admin"
81914
- },
81915
- "oidcConnections.deleteConnection": {
81916
- capName: "oidc-connections",
81917
- capScope: "system",
81918
- addonId: null,
81919
- access: "delete",
81920
- role: "admin"
81921
- },
81922
- "oidcConnections.listConnections": {
81923
- capName: "oidc-connections",
81924
- capScope: "system",
81925
- addonId: null,
81926
- access: "view",
81927
- role: "admin"
81928
- },
81929
- "oidcConnections.updateConnection": {
81930
- capName: "oidc-connections",
81931
- capScope: "system",
81932
- addonId: null,
81933
- access: "create",
81934
- role: "admin"
81935
- },
81936
81884
  "osd.getStatus": {
81937
81885
  capName: "osd",
81938
81886
  capScope: "device",
@@ -84964,13 +84912,6 @@ Object.freeze({
84964
84912
  access: "delete",
84965
84913
  role: "admin"
84966
84914
  },
84967
- "storage.deleteLocation": {
84968
- capName: "storage",
84969
- capScope: "system",
84970
- addonId: null,
84971
- access: "delete",
84972
- role: "admin"
84973
- },
84974
84915
  "storage.endDownload": {
84975
84916
  capName: "storage",
84976
84917
  capScope: "system",
@@ -85027,13 +84968,6 @@ Object.freeze({
85027
84968
  access: "view",
85028
84969
  role: "admin"
85029
84970
  },
85030
- "storage.listProviders": {
85031
- capName: "storage",
85032
- capScope: "system",
85033
- addonId: null,
85034
- access: "view",
85035
- role: "admin"
85036
- },
85037
84971
  "storage.read": {
85038
84972
  capName: "storage",
85039
84973
  capScope: "system",
@@ -85055,27 +84989,6 @@ Object.freeze({
85055
84989
  access: "view",
85056
84990
  role: "admin"
85057
84991
  },
85058
- "storage.testConfig": {
85059
- capName: "storage",
85060
- capScope: "system",
85061
- addonId: null,
85062
- access: "view",
85063
- role: "admin"
85064
- },
85065
- "storage.testLocation": {
85066
- capName: "storage",
85067
- capScope: "system",
85068
- addonId: null,
85069
- access: "view",
85070
- role: "admin"
85071
- },
85072
- "storage.upsertLocation": {
85073
- capName: "storage",
85074
- capScope: "system",
85075
- addonId: null,
85076
- access: "create",
85077
- role: "admin"
85078
- },
85079
84992
  "storage.write": {
85080
84993
  capName: "storage",
85081
84994
  capScope: "system",
@@ -85853,27 +85766,6 @@ Object.freeze({
85853
85766
  access: "create",
85854
85767
  role: "admin"
85855
85768
  },
85856
- "terminalSession.createInstance": {
85857
- capName: "terminal-session",
85858
- capScope: "system",
85859
- addonId: null,
85860
- access: "create",
85861
- role: "admin"
85862
- },
85863
- "terminalSession.deleteInstance": {
85864
- capName: "terminal-session",
85865
- capScope: "system",
85866
- addonId: null,
85867
- access: "delete",
85868
- role: "admin"
85869
- },
85870
- "terminalSession.listInstances": {
85871
- capName: "terminal-session",
85872
- capScope: "system",
85873
- addonId: null,
85874
- access: "view",
85875
- role: "admin"
85876
- },
85877
85769
  "terminalSession.listLegacyCameras": {
85878
85770
  capName: "terminal-session",
85879
85771
  capScope: "system",
@@ -85916,20 +85808,6 @@ Object.freeze({
85916
85808
  access: "create",
85917
85809
  role: "admin"
85918
85810
  },
85919
- "terminalSession.setInstanceEnabled": {
85920
- capName: "terminal-session",
85921
- capScope: "system",
85922
- addonId: null,
85923
- access: "create",
85924
- role: "admin"
85925
- },
85926
- "terminalSession.updateInstance": {
85927
- capName: "terminal-session",
85928
- capScope: "system",
85929
- addonId: null,
85930
- access: "create",
85931
- role: "admin"
85932
- },
85933
85811
  "terminalSession.writeInput": {
85934
85812
  capName: "terminal-session",
85935
85813
  capScope: "system",
@@ -85944,6 +85822,13 @@ Object.freeze({
85944
85822
  access: "create",
85945
85823
  role: "viewer"
85946
85824
  },
85825
+ "timedRun.getOptions": {
85826
+ capName: "timed-run",
85827
+ capScope: "device",
85828
+ addonId: null,
85829
+ access: "view",
85830
+ role: "viewer"
85831
+ },
85947
85832
  "timedRun.getStatus": {
85948
85833
  capName: "timed-run",
85949
85834
  capScope: "device",
@@ -85958,6 +85843,13 @@ Object.freeze({
85958
85843
  access: "create",
85959
85844
  role: "viewer"
85960
85845
  },
85846
+ "timedRun.setSettings": {
85847
+ capName: "timed-run",
85848
+ capScope: "device",
85849
+ addonId: null,
85850
+ access: "create",
85851
+ role: "admin"
85852
+ },
85961
85853
  "toast.onToast": {
85962
85854
  capName: "toast",
85963
85855
  capScope: "system",
@@ -86888,6 +86780,7 @@ function offered(received) {
86888
86780
  };
86889
86781
  }
86890
86782
  offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
86783
+ string().min(1).max(4096);
86891
86784
  Object.freeze({
86892
86785
  "accessories.getStatus": [{
86893
86786
  name: "deviceId",
@@ -87571,6 +87464,11 @@ Object.freeze({
87571
87464
  form: "single",
87572
87465
  optional: false
87573
87466
  }],
87467
+ "deviceManager.setAlias": [{
87468
+ name: "deviceId",
87469
+ form: "single",
87470
+ optional: false
87471
+ }],
87574
87472
  "deviceManager.setChildLayout": [{
87575
87473
  name: "deviceId",
87576
87474
  form: "single",
@@ -89635,6 +89533,11 @@ Object.freeze({
89635
89533
  form: "single",
89636
89534
  optional: false
89637
89535
  }],
89536
+ "timedRun.getOptions": [{
89537
+ name: "deviceId",
89538
+ form: "single",
89539
+ optional: false
89540
+ }],
89638
89541
  "timedRun.getStatus": [{
89639
89542
  name: "deviceId",
89640
89543
  form: "single",
@@ -89645,6 +89548,11 @@ Object.freeze({
89645
89548
  form: "single",
89646
89549
  optional: false
89647
89550
  }],
89551
+ "timedRun.setSettings": [{
89552
+ name: "deviceId",
89553
+ form: "single",
89554
+ optional: false
89555
+ }],
89648
89556
  "update.getStatus": [{
89649
89557
  name: "deviceId",
89650
89558
  form: "single",
@@ -90507,6 +90415,7 @@ var STATE_SUMMARY_VARS = [
90507
90415
  [...RULE_TEXT],
90508
90416
  [...RULE_TEXT],
90509
90417
  [...RULE_TEXT],
90418
+ [...RULE_TEXT],
90510
90419
  [...RULE_BODY_ONLY],
90511
90420
  [...RULE_TEXT],
90512
90421
  ([...RULE_TEXT], [...DEVICE_EVENTS]),