@camstack/addon-provider-homeassistant 1.2.654 → 1.2.656
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/addon.js +313 -88
- package/dist/addon.mjs +310 -88
- package/dist/ha-export/ha-export.addon.js +528 -275
- package/dist/ha-export/ha-export.addon.mjs +515 -276
- package/dist/{ha-instance-settings-Bn_Trz6p.js → ha-instance-settings-Dm0tU8iv.js} +1045 -340
- package/dist/{ha-instance-settings-g5pwb3Fi.mjs → ha-instance-settings-EQ5Ey3Q-.mjs} +1000 -331
- package/package.json +6 -2
|
@@ -5981,7 +5981,7 @@ var ZodIssueCode = {
|
|
|
5981
5981
|
var ZodFirstPartyTypeKind;
|
|
5982
5982
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5983
5983
|
//#endregion
|
|
5984
|
-
//#region ../types/dist/sleep-
|
|
5984
|
+
//#region ../types/dist/sleep-BaWhXZFm.mjs
|
|
5985
5985
|
/**
|
|
5986
5986
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5987
5987
|
* window to float samples (D455).
|
|
@@ -6076,6 +6076,16 @@ var AddonMigrationsBlockedError = class extends Error {
|
|
|
6076
6076
|
this.migrationIds = migrationIds;
|
|
6077
6077
|
}
|
|
6078
6078
|
};
|
|
6079
|
+
/**
|
|
6080
|
+
* The value a redacted secret is replaced with on every read surface.
|
|
6081
|
+
*
|
|
6082
|
+
* It is also the WRITE-BACK token: an `upsertLocation` that sends this value
|
|
6083
|
+
* back for a key means "keep what is stored", which is what lets an operator
|
|
6084
|
+
* rename a location without retyping its password. A literal an operator could
|
|
6085
|
+
* plausibly choose as a real password would turn that convenience into a way
|
|
6086
|
+
* to lock yourself out, hence the sentinel shape.
|
|
6087
|
+
*/
|
|
6088
|
+
var REDACTED_SECRET = "__camstack_redacted__";
|
|
6079
6089
|
/** Is this field's stored value a credential? */
|
|
6080
6090
|
function isSecretConfigField(field) {
|
|
6081
6091
|
if (field.type === "password") return true;
|
|
@@ -6129,9 +6139,16 @@ function isSecretMarker(value) {
|
|
|
6129
6139
|
const ref = value["$secret"];
|
|
6130
6140
|
return typeof ref === "string" && ref.length > 0;
|
|
6131
6141
|
}
|
|
6142
|
+
/** Anything carrying a `$secret` key — a stored marker or a UI view of one. */
|
|
6143
|
+
function isSecretMarkerLike(value) {
|
|
6144
|
+
return isRecord$7(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
|
|
6145
|
+
}
|
|
6132
6146
|
function isSecretClear(value) {
|
|
6133
6147
|
return isRecord$7(value) && value["$secretClear"] === true;
|
|
6134
6148
|
}
|
|
6149
|
+
function isSecretFieldView(value) {
|
|
6150
|
+
return isRecord$7(value) && value["set"] === true && isSecretMarkerLike(value);
|
|
6151
|
+
}
|
|
6135
6152
|
/**
|
|
6136
6153
|
* The UI view of a stored secret field value: a marker becomes "set", inline
|
|
6137
6154
|
* plaintext becomes "set (inline)", nothing becomes `null`. A value never
|
|
@@ -6781,11 +6798,36 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6781
6798
|
}
|
|
6782
6799
|
return out;
|
|
6783
6800
|
}
|
|
6801
|
+
/**
|
|
6802
|
+
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6803
|
+
*
|
|
6804
|
+
* A ref is DETERMINISTIC from where the value lives — the owner addon, the
|
|
6805
|
+
* scope (global, one node, one device, one integration, one row of a business
|
|
6806
|
+
* collection) and the field path inside that scope — so saving the same field
|
|
6807
|
+
* twice updates the same ref, and the ref itself says which settings blob
|
|
6808
|
+
* points at it. It is also the AAD of the value's ciphertext: a ciphertext
|
|
6809
|
+
* copied onto another ref does not decrypt.
|
|
6810
|
+
*
|
|
6811
|
+
* Array items are addressed by ITEM ID, never by index
|
|
6812
|
+
* (`providers[authentik].clientSecret`), so reordering a list keeps every ref.
|
|
6813
|
+
*
|
|
6814
|
+
* Pure and browser-safe: no node builtins, no I/O.
|
|
6815
|
+
*/
|
|
6816
|
+
/** The URI scheme of every keychain ref. */
|
|
6817
|
+
var SECRET_REF_SCHEME = "secret://";
|
|
6784
6818
|
var GLOBAL_SCOPE = { kind: "global" };
|
|
6785
6819
|
/** The scope every addon-wide (not per-node, not per-device) setting lives in. */
|
|
6786
6820
|
function globalSecretScope() {
|
|
6787
6821
|
return GLOBAL_SCOPE;
|
|
6788
6822
|
}
|
|
6823
|
+
/**
|
|
6824
|
+
* An addon id as a ref owner. A hub in-process builtin's context id is
|
|
6825
|
+
* `addon:<id>`; the owner of its secrets is `<id>`, exactly as for the same
|
|
6826
|
+
* addon running forked.
|
|
6827
|
+
*/
|
|
6828
|
+
function normalizeSecretOwner(addonId) {
|
|
6829
|
+
return addonId.startsWith("addon:") ? addonId.slice(6) : addonId;
|
|
6830
|
+
}
|
|
6789
6831
|
function encodeSegment(value) {
|
|
6790
6832
|
return encodeURIComponent(value);
|
|
6791
6833
|
}
|
|
@@ -6796,6 +6838,16 @@ function decodeSegment(value) {
|
|
|
6796
6838
|
return null;
|
|
6797
6839
|
}
|
|
6798
6840
|
}
|
|
6841
|
+
/** The scope as it appears inside a ref (`global`, `node/<id>`, …). */
|
|
6842
|
+
function formatSecretScope(scope) {
|
|
6843
|
+
switch (scope.kind) {
|
|
6844
|
+
case "global": return "global";
|
|
6845
|
+
case "node": return `node/${encodeSegment(scope.nodeId)}`;
|
|
6846
|
+
case "device": return `device/${scope.deviceId}`;
|
|
6847
|
+
case "integration": return `integration/${encodeSegment(scope.integrationId)}`;
|
|
6848
|
+
case "row": return `row/${encodeSegment(scope.collection)}/${encodeSegment(scope.rowId)}`;
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6799
6851
|
/**
|
|
6800
6852
|
* The field path of a value inside an array item, addressed by the item's id:
|
|
6801
6853
|
* `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
|
|
@@ -6804,6 +6856,13 @@ function decodeSegment(value) {
|
|
|
6804
6856
|
function arrayItemFieldPath(arrayKey, itemId, fieldKey) {
|
|
6805
6857
|
return `${arrayKey}[${encodeSegment(itemId)}].${fieldKey}`;
|
|
6806
6858
|
}
|
|
6859
|
+
/** Build a ref. Throws on an empty owner or field path — both are bugs. */
|
|
6860
|
+
function buildSecretRef(parts) {
|
|
6861
|
+
const owner = normalizeSecretOwner(parts.owner);
|
|
6862
|
+
if (owner.length === 0) throw new Error("buildSecretRef: owner is empty");
|
|
6863
|
+
if (parts.fieldPath.length === 0) throw new Error("buildSecretRef: fieldPath is empty");
|
|
6864
|
+
return `${SECRET_REF_SCHEME}${encodeSegment(owner)}/${formatSecretScope(parts.scope)}/${parts.fieldPath}`;
|
|
6865
|
+
}
|
|
6807
6866
|
function parseScope(segments) {
|
|
6808
6867
|
switch (segments[0]) {
|
|
6809
6868
|
case "global": return {
|
|
@@ -8069,6 +8128,10 @@ var BaseAddon = class {
|
|
|
8069
8128
|
const stored = keychain ? await this.readAddonStoreWithRetry() : {};
|
|
8070
8129
|
const patch = {};
|
|
8071
8130
|
for (const key of keys) {
|
|
8131
|
+
if (tree.get(key)?.kind === "array") {
|
|
8132
|
+
patch[key] = [];
|
|
8133
|
+
continue;
|
|
8134
|
+
}
|
|
8072
8135
|
const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
|
|
8073
8136
|
patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
|
|
8074
8137
|
}
|
|
@@ -8720,6 +8783,13 @@ var RecordsError = class extends Error {
|
|
|
8720
8783
|
this.detail = detail;
|
|
8721
8784
|
}
|
|
8722
8785
|
};
|
|
8786
|
+
/** A write guarded by `ifRev` found another revision (or an existing row for `ifRev: 0`). */
|
|
8787
|
+
var RevConflictError = class extends RecordsError {
|
|
8788
|
+
constructor(detail) {
|
|
8789
|
+
super("rev-conflict", detail);
|
|
8790
|
+
this.name = "RevConflictError";
|
|
8791
|
+
}
|
|
8792
|
+
};
|
|
8723
8793
|
/** The keychain could not store or serve a value: explicit, never an empty string. */
|
|
8724
8794
|
var RecordSecretUnavailableError = class extends RecordsError {
|
|
8725
8795
|
constructor(detail) {
|
|
@@ -8757,11 +8827,17 @@ var RECORD_BATCH_MAX = 1e3;
|
|
|
8757
8827
|
var RECORD_QUERY_MAX = 1e3;
|
|
8758
8828
|
/** A kind in the `settings` file may not bound itself above this (D447: high volume is `events`). */
|
|
8759
8829
|
var RECORD_SETTINGS_MAX_ROWS = 5e4;
|
|
8830
|
+
/** The `Column`-free proof prefix: a ref collection for a kind's secret fields. */
|
|
8831
|
+
var RECORD_SECRET_COLLECTION_PREFIX = "records:";
|
|
8760
8832
|
/**
|
|
8761
8833
|
* Secret fields of a record, addressed the way a Zod schema declares them
|
|
8762
8834
|
* (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
|
|
8763
8835
|
* (`auth.accessToken`, `headers[].value`).
|
|
8764
8836
|
*
|
|
8837
|
+
* `{}` after a key means "each value of this map" (`applianceSecrets{}`): the map's own key
|
|
8838
|
+
* names the value (`applianceSecrets{fridge}`), so no id key is declared and a body that drops a
|
|
8839
|
+
* key releases exactly that key's secret.
|
|
8840
|
+
*
|
|
8765
8841
|
* Array elements are matched by a DECLARED id key (`secretArrayKeys`:
|
|
8766
8842
|
* `{ headers: 'name' }`), never by index, so reordering a list keeps every
|
|
8767
8843
|
* keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
|
|
@@ -8774,11 +8850,13 @@ function parseSecretPath(path) {
|
|
|
8774
8850
|
if (path.length === 0) throw new InvalidRecordError("an empty secret path");
|
|
8775
8851
|
return path.split(".").map((raw) => {
|
|
8776
8852
|
const each = raw.endsWith("[]");
|
|
8777
|
-
const
|
|
8853
|
+
const map = raw.endsWith("{}");
|
|
8854
|
+
const key = each || map ? raw.slice(0, -2) : raw;
|
|
8778
8855
|
if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
|
|
8779
8856
|
return {
|
|
8780
8857
|
key,
|
|
8781
|
-
each
|
|
8858
|
+
each,
|
|
8859
|
+
map
|
|
8782
8860
|
};
|
|
8783
8861
|
});
|
|
8784
8862
|
}
|
|
@@ -8793,6 +8871,128 @@ function secretArrayPrefixes(path) {
|
|
|
8793
8871
|
}
|
|
8794
8872
|
return prefixes;
|
|
8795
8873
|
}
|
|
8874
|
+
function isRecord$4(value) {
|
|
8875
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8876
|
+
}
|
|
8877
|
+
function elementId(element, idKey, declared, prefix) {
|
|
8878
|
+
const id = isRecord$4(element) ? element[idKey] : void 0;
|
|
8879
|
+
if (typeof id !== "string" || id.length === 0) throw new InvalidRecordError(`"${declared}": every element of "${prefix}" needs a non-empty string "${idKey}" to carry a secret`);
|
|
8880
|
+
return id;
|
|
8881
|
+
}
|
|
8882
|
+
/**
|
|
8883
|
+
* Visit every secret leaf PRESENT in `data` (an absent branch has nothing to
|
|
8884
|
+
* visit). `replace` returns the new leaf value, or {@link REMOVE_LEAF}; the
|
|
8885
|
+
* result is a new body, `data` is untouched.
|
|
8886
|
+
*/
|
|
8887
|
+
var REMOVE_LEAF = Symbol("remove-leaf");
|
|
8888
|
+
function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arrayKeys, visit) {
|
|
8889
|
+
if (!isRecord$4(node)) return node;
|
|
8890
|
+
const segment = segments[index];
|
|
8891
|
+
if (segment === void 0) return node;
|
|
8892
|
+
if (!Object.prototype.hasOwnProperty.call(node, segment.key)) return node;
|
|
8893
|
+
const child = node[segment.key];
|
|
8894
|
+
const keys = [...walkedKeys, segment.key];
|
|
8895
|
+
const last = index === segments.length - 1;
|
|
8896
|
+
if (segment.each) {
|
|
8897
|
+
if (!Array.isArray(child)) return node;
|
|
8898
|
+
const prefix = keys.join(".");
|
|
8899
|
+
const idKey = arrayKeys[prefix];
|
|
8900
|
+
if (idKey === void 0) throw new InvalidRecordError(`"${declared}": no id key declared for the array "${prefix}"`);
|
|
8901
|
+
const mapped = child.map((element) => {
|
|
8902
|
+
const id = elementId(element, idKey, declared, prefix);
|
|
8903
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}[${encodeURIComponent(id)}]`;
|
|
8904
|
+
return last ? element : mapAt(element, segments, index + 1, keys, concrete, declared, arrayKeys, visit);
|
|
8905
|
+
});
|
|
8906
|
+
return {
|
|
8907
|
+
...node,
|
|
8908
|
+
[segment.key]: mapped
|
|
8909
|
+
};
|
|
8910
|
+
}
|
|
8911
|
+
if (segment.map) {
|
|
8912
|
+
if (!isRecord$4(child)) return node;
|
|
8913
|
+
const kept = Object.entries(child).map(([mapKey, value]) => {
|
|
8914
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}{${encodeURIComponent(mapKey)}}`;
|
|
8915
|
+
if (!last) return [mapKey, mapAt(value, segments, index + 1, keys, concrete, declared, arrayKeys, visit)];
|
|
8916
|
+
return [mapKey, visit({
|
|
8917
|
+
declared,
|
|
8918
|
+
concrete,
|
|
8919
|
+
value
|
|
8920
|
+
})];
|
|
8921
|
+
}).filter(([, value]) => value !== REMOVE_LEAF);
|
|
8922
|
+
return {
|
|
8923
|
+
...node,
|
|
8924
|
+
[segment.key]: Object.fromEntries(kept)
|
|
8925
|
+
};
|
|
8926
|
+
}
|
|
8927
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
|
|
8928
|
+
if (last) {
|
|
8929
|
+
const replacement = visit({
|
|
8930
|
+
declared,
|
|
8931
|
+
concrete,
|
|
8932
|
+
value: child
|
|
8933
|
+
});
|
|
8934
|
+
if (replacement === REMOVE_LEAF) return Object.fromEntries(Object.entries(node).filter(([key]) => key !== segment.key));
|
|
8935
|
+
return {
|
|
8936
|
+
...node,
|
|
8937
|
+
[segment.key]: replacement
|
|
8938
|
+
};
|
|
8939
|
+
}
|
|
8940
|
+
return {
|
|
8941
|
+
...node,
|
|
8942
|
+
[segment.key]: mapAt(child, segments, index + 1, keys, concrete, declared, arrayKeys, visit)
|
|
8943
|
+
};
|
|
8944
|
+
}
|
|
8945
|
+
/** Replace/remove every present secret leaf; returns a NEW body. */
|
|
8946
|
+
function mapSecretLeaves(data, paths, arrayKeys, visit) {
|
|
8947
|
+
let current = data;
|
|
8948
|
+
for (const declared of paths) current = mapAt(current, parseSecretPath(declared), 0, [], "", declared, arrayKeys, visit);
|
|
8949
|
+
return current;
|
|
8950
|
+
}
|
|
8951
|
+
/**
|
|
8952
|
+
* The keychain collection name a kind's secrets live under. Prefixed so a
|
|
8953
|
+
* record's refs never collide with the refs a legacy collection of the same name
|
|
8954
|
+
* already holds during an adoption.
|
|
8955
|
+
*/
|
|
8956
|
+
function recordSecretCollection(kind) {
|
|
8957
|
+
return `${RECORD_SECRET_COLLECTION_PREFIX}${kind}`;
|
|
8958
|
+
}
|
|
8959
|
+
/** The ONE ref a row's secret field may carry: the engine and the facade both build it here. */
|
|
8960
|
+
function recordSecretRef(owner, kind, rowId, concretePath) {
|
|
8961
|
+
return buildSecretRef({
|
|
8962
|
+
owner,
|
|
8963
|
+
scope: {
|
|
8964
|
+
kind: "row",
|
|
8965
|
+
collection: recordSecretCollection(kind),
|
|
8966
|
+
rowId
|
|
8967
|
+
},
|
|
8968
|
+
fieldPath: concretePath
|
|
8969
|
+
});
|
|
8970
|
+
}
|
|
8971
|
+
/**
|
|
8972
|
+
* A stored secret, as an admin read shows it: a VIEW naming the row's own
|
|
8973
|
+
* keychain ref (`set: true`), never the value and no longer the bare redaction
|
|
8974
|
+
* token. The form hands it to the secret field, which reveals the value by ref
|
|
8975
|
+
* through the audited `keychain-admin.reveal`; sent back untouched it keeps the
|
|
8976
|
+
* stored secret (the records door reads a view as KEEP).
|
|
8977
|
+
*
|
|
8978
|
+
* ONE implementation for the entries service and for every addon that serves
|
|
8979
|
+
* its own instances (`RecordIntegrationInstances`).
|
|
8980
|
+
*/
|
|
8981
|
+
function isRecord$3(value) {
|
|
8982
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8983
|
+
}
|
|
8984
|
+
function secretViewsOf(owner, kind, id, decl, data) {
|
|
8985
|
+
if (decl.secretPaths.length === 0) return data;
|
|
8986
|
+
const shown = mapSecretLeaves(data, decl.secretPaths, decl.secretArrayKeys, (leaf) => {
|
|
8987
|
+
if (leaf.value !== "__camstack_redacted__") return leaf.value;
|
|
8988
|
+
return {
|
|
8989
|
+
$secret: recordSecretRef(owner, kind, id, leaf.concrete),
|
|
8990
|
+
v: 1,
|
|
8991
|
+
set: true
|
|
8992
|
+
};
|
|
8993
|
+
});
|
|
8994
|
+
return isRecord$3(shown) ? shown : data;
|
|
8995
|
+
}
|
|
8796
8996
|
/**
|
|
8797
8997
|
* The share-view scope kinds a share-link token can carry. The hub mints
|
|
8798
8998
|
* and validates them (`ShareViewGrantSchema` builds its
|
|
@@ -8865,7 +9065,8 @@ function isZodSecret(schema) {
|
|
|
8865
9065
|
}
|
|
8866
9066
|
/**
|
|
8867
9067
|
* Every declared secret path in a config schema: dotted keys, `[]` for "each
|
|
8868
|
-
* element of this array" (`connection.password`, `rows[].token`)
|
|
9068
|
+
* element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
|
|
9069
|
+
* map" (`applianceSecrets{}`). A declared
|
|
8869
9070
|
* object or array is reported as one path and not descended into — everything
|
|
8870
9071
|
* under it is secret.
|
|
8871
9072
|
*/
|
|
@@ -8876,6 +9077,7 @@ function collectZodSecretPaths(schema, prefix = "") {
|
|
|
8876
9077
|
const def = defOf(layer);
|
|
8877
9078
|
if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
|
|
8878
9079
|
else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
|
|
9080
|
+
else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
|
|
8879
9081
|
else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
|
|
8880
9082
|
else if (def.type === "intersection") {
|
|
8881
9083
|
if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
|
|
@@ -9015,7 +9217,7 @@ function defineRecordKinds(specs) {
|
|
|
9015
9217
|
retention: spec.retention,
|
|
9016
9218
|
deviceRemoval: spec.deviceRemoval,
|
|
9017
9219
|
maxDataBytes: spec.maxDataBytes,
|
|
9018
|
-
secretPaths: [...secretPaths],
|
|
9220
|
+
secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
|
|
9019
9221
|
secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
|
|
9020
9222
|
transactFamily: spec.transactFamily,
|
|
9021
9223
|
entry: spec.entry === void 0 ? void 0 : {
|
|
@@ -9060,6 +9262,7 @@ function defineEntryKind(name, spec) {
|
|
|
9060
9262
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9061
9263
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9062
9264
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
9265
|
+
...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
|
|
9063
9266
|
entry: {
|
|
9064
9267
|
title: spec.title,
|
|
9065
9268
|
...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
|
|
@@ -11787,7 +11990,7 @@ object({
|
|
|
11787
11990
|
params: record(string(), union([string(), number()])).optional()
|
|
11788
11991
|
});
|
|
11789
11992
|
//#endregion
|
|
11790
|
-
//#region ../types/dist/composition-
|
|
11993
|
+
//#region ../types/dist/composition-CRNwcFYY.mjs
|
|
11791
11994
|
/**
|
|
11792
11995
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11793
11996
|
*
|
|
@@ -12550,38 +12753,6 @@ function validateExpressionSource(src) {
|
|
|
12550
12753
|
return null;
|
|
12551
12754
|
}
|
|
12552
12755
|
/**
|
|
12553
|
-
* One power reading becomes a washer, dryer or dishwasher (D944 helper).
|
|
12554
|
-
*
|
|
12555
|
-
* Standby of the control board is under 5 W; a motor or a heater is far
|
|
12556
|
-
* above that, so 5 W is the line. A door lock or a short pump spike is not
|
|
12557
|
-
* a cycle, so running starts only after 30 s above the line.
|
|
12558
|
-
*
|
|
12559
|
-
* A wash, dry or dishwasher pause (soak, heat-wait, heater off between
|
|
12560
|
-
* bursts) lasts several minutes. Ending the cycle on the first dip would
|
|
12561
|
-
* report "finished" in the middle of the program, so the stop dwell is
|
|
12562
|
-
* 10 minutes on every type. The number is stored per type so one machine
|
|
12563
|
-
* can move later without moving the others.
|
|
12564
|
-
*/
|
|
12565
|
-
var APPLIANCE_TEMPLATE_ID = "appliance";
|
|
12566
|
-
var APPLIANCE_KINDS = [
|
|
12567
|
-
"washer",
|
|
12568
|
-
"dryer",
|
|
12569
|
-
"dishwasher"
|
|
12570
|
-
];
|
|
12571
|
-
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
12572
|
-
/**
|
|
12573
|
-
* A dumb plug-in stove or heater, built from a smart plug (a `Heater`).
|
|
12574
|
-
*
|
|
12575
|
-
* The plug is the actuator: `switch.setState` is a FORWARD to the plug's own
|
|
12576
|
-
* (D967), so the heater switches exactly as the plug does. Running is the
|
|
12577
|
-
* plug being on; when a power reading is also named, running is the draw
|
|
12578
|
-
* staying above the line instead (a plug left on with the stove's own
|
|
12579
|
-
* thermostat open is not heating). 50 W is far above any standby and below
|
|
12580
|
-
* the smallest radiator.
|
|
12581
|
-
*/
|
|
12582
|
-
var HEATER_TEMPLATE_ID = "heater";
|
|
12583
|
-
DeviceType.Heater;
|
|
12584
|
-
/**
|
|
12585
12756
|
* Statistics and integrals over a history read (D944).
|
|
12586
12757
|
*
|
|
12587
12758
|
* Pure: the same points always give the same answer, so a restart recomputes
|
|
@@ -12725,34 +12896,6 @@ var HelperSpecSchema = discriminatedUnion("kind", [
|
|
|
12725
12896
|
/** Actuator groups only. Readings leave this unset. */
|
|
12726
12897
|
active: ActiveCriterionSchema.optional()
|
|
12727
12898
|
}),
|
|
12728
|
-
object({
|
|
12729
|
-
kind: literal("appliance"),
|
|
12730
|
-
templateId: literal(APPLIANCE_TEMPLATE_ID),
|
|
12731
|
-
appliance: _enum(APPLIANCE_KINDS),
|
|
12732
|
-
thresholdWatts: number().positive(),
|
|
12733
|
-
startDwellMs: number().int().nonnegative(),
|
|
12734
|
-
stopDwellMs: number().int().nonnegative(),
|
|
12735
|
-
member: HelperMemberSchema,
|
|
12736
|
-
/**
|
|
12737
|
-
* The plug that powers the appliance (`switch.on`). Optional: when set, the
|
|
12738
|
-
* composition carries a `power-source` feature, so the hub can cut and restore
|
|
12739
|
-
* that plug (power-source link of a NEW-target composition).
|
|
12740
|
-
*/
|
|
12741
|
-
powerSwitch: HelperMemberSchema.optional(),
|
|
12742
|
-
/** How long a forced restart keeps the plug off; the cap's default when unset. */
|
|
12743
|
-
offSeconds: number().optional()
|
|
12744
|
-
}),
|
|
12745
|
-
object({
|
|
12746
|
-
kind: literal("heater"),
|
|
12747
|
-
templateId: literal(HEATER_TEMPLATE_ID),
|
|
12748
|
-
/** The plug, read and commanded at `switch.on`. */
|
|
12749
|
-
plug: HelperMemberSchema,
|
|
12750
|
-
/** The plug's power reading, when it has one: running is then the draw, not the switch. */
|
|
12751
|
-
meter: HelperMemberSchema.optional(),
|
|
12752
|
-
thresholdWatts: number().positive(),
|
|
12753
|
-
startDwellMs: number().int().nonnegative(),
|
|
12754
|
-
stopDwellMs: number().int().nonnegative()
|
|
12755
|
-
}),
|
|
12756
12899
|
object({
|
|
12757
12900
|
kind: literal("statistic"),
|
|
12758
12901
|
stat: _enum(STATISTIC_STATS),
|
|
@@ -12976,7 +13119,7 @@ var CompositionSchema = object({
|
|
|
12976
13119
|
kind: literal("existing"),
|
|
12977
13120
|
device: CompositionSourceRefSchema
|
|
12978
13121
|
})]),
|
|
12979
|
-
features: array(CompositionFeatureSchema).min(
|
|
13122
|
+
features: array(CompositionFeatureSchema).min(0).max(16),
|
|
12980
13123
|
/**
|
|
12981
13124
|
* Present when the block is a helper template. Absent on a free
|
|
12982
13125
|
* composition (the room example, device 4603). The editor reads this,
|
|
@@ -13311,6 +13454,205 @@ function buildCapturingReply(envelope) {
|
|
|
13311
13454
|
};
|
|
13312
13455
|
return wrapper;
|
|
13313
13456
|
}
|
|
13457
|
+
/**
|
|
13458
|
+
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13459
|
+
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13460
|
+
*
|
|
13461
|
+
* Every instance is one record of the addon's own kind, keyed by the
|
|
13462
|
+
* `integrations` registry id. The records door does the secret work: a field the
|
|
13463
|
+
* kind marks `.meta({ secret: true })` is a keychain marker bound to the row, so
|
|
13464
|
+
* deleting the row deletes the secret, and one row's value is never another's.
|
|
13465
|
+
*
|
|
13466
|
+
* Patch rules for a secret (`updateInstance`): omitted, `''`, the redaction
|
|
13467
|
+
* token or the view the form was given KEEP the stored secret;
|
|
13468
|
+
* `{ $secretClear: true }` clears it; any other string replaces it.
|
|
13469
|
+
*/
|
|
13470
|
+
/** The most instances one addon lists (an integrations page, not a table). */
|
|
13471
|
+
var INSTANCES_LIST_MAX = 1e3;
|
|
13472
|
+
/** The keys of the kind's secret fields that sit at the top level of the body. */
|
|
13473
|
+
function topLevelSecretKeys(paths) {
|
|
13474
|
+
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[") && !p.includes("{")));
|
|
13475
|
+
}
|
|
13476
|
+
/** The keys of the kind's top-level MAPS whose values are secrets (`applianceSecrets{}`). */
|
|
13477
|
+
function secretMapKeys(paths) {
|
|
13478
|
+
return new Set(paths.filter((p) => p.endsWith("{}") && !p.slice(0, -2).match(/[.[{]/)).map((p) => p.slice(0, -2)));
|
|
13479
|
+
}
|
|
13480
|
+
/**
|
|
13481
|
+
* A patch value for a map of secrets: an entry that is omitted, `''`, the redaction token or the
|
|
13482
|
+
* view the form was given KEEPS its stored secret (it is left out of the merge); `{ $secretClear }`
|
|
13483
|
+
* or `null` removes the entry; any other string replaces it. Null when nothing is left to merge.
|
|
13484
|
+
*/
|
|
13485
|
+
function secretMapPatch(value) {
|
|
13486
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
13487
|
+
const out = {};
|
|
13488
|
+
for (const [key, entry] of Object.entries(value)) if (entry === null || isSecretClear(entry)) out[key] = null;
|
|
13489
|
+
else if (typeof entry === "string" && entry !== "" && entry !== "__camstack_redacted__" && !isSecretFieldView(entry)) out[key] = entry;
|
|
13490
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
13491
|
+
}
|
|
13492
|
+
/** Does the kind's body schema carry an `enabled` field? */
|
|
13493
|
+
function declaresEnabled(kind) {
|
|
13494
|
+
const json = toJSONSchema(kind.schema, {
|
|
13495
|
+
unrepresentable: "any",
|
|
13496
|
+
io: "input"
|
|
13497
|
+
});
|
|
13498
|
+
const properties = Reflect.get(json, "properties");
|
|
13499
|
+
return typeof properties === "object" && properties !== null && "enabled" in properties;
|
|
13500
|
+
}
|
|
13501
|
+
var RecordIntegrationInstances = class {
|
|
13502
|
+
records;
|
|
13503
|
+
kind;
|
|
13504
|
+
hooks;
|
|
13505
|
+
handle;
|
|
13506
|
+
secretKeys;
|
|
13507
|
+
secretMaps;
|
|
13508
|
+
nameField;
|
|
13509
|
+
/** The `integrations` registry owns the on/off switch (`role: devices`); the body has none. */
|
|
13510
|
+
registryOwnsEnabled;
|
|
13511
|
+
constructor(records, kind, hooks) {
|
|
13512
|
+
this.records = records;
|
|
13513
|
+
this.kind = kind;
|
|
13514
|
+
this.hooks = hooks;
|
|
13515
|
+
this.handle = records.kind(kind);
|
|
13516
|
+
this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
|
|
13517
|
+
this.secretMaps = secretMapKeys(kind.declaration.secretPaths);
|
|
13518
|
+
this.nameField = kind.declaration.entry?.nameField ?? "name";
|
|
13519
|
+
this.registryOwnsEnabled = hooks.description.role === "devices";
|
|
13520
|
+
if (this.registryOwnsEnabled && declaresEnabled(kind)) throw new Error(`kind "${kind.name}": the integrations registry owns the on/off switch of a role "devices" addon, so the body must not declare "enabled"`);
|
|
13521
|
+
}
|
|
13522
|
+
/**
|
|
13523
|
+
* One instance read for the addon's own runtime; null when absent. When the keychain is
|
|
13524
|
+
* locked the row is served WITHOUT its secrets and says so (`secrets: 'unavailable'`), so the
|
|
13525
|
+
* caller can keep what it had instead of reconfiguring with an empty secret (D49).
|
|
13526
|
+
*/
|
|
13527
|
+
async readResolved(id) {
|
|
13528
|
+
try {
|
|
13529
|
+
const record = await this.handle.getResolved(id);
|
|
13530
|
+
return record === null ? null : {
|
|
13531
|
+
id,
|
|
13532
|
+
body: record.data,
|
|
13533
|
+
enabled: record.data.enabled !== false,
|
|
13534
|
+
secrets: "resolved"
|
|
13535
|
+
};
|
|
13536
|
+
} catch (err) {
|
|
13537
|
+
if (!(err instanceof RecordSecretUnavailableError)) throw err;
|
|
13538
|
+
const stored = await this.handle.get(id);
|
|
13539
|
+
if (stored === null) return null;
|
|
13540
|
+
return {
|
|
13541
|
+
id,
|
|
13542
|
+
body: Object.fromEntries(Object.entries(stored.data).filter(([key]) => !this.secretKeys.has(key))),
|
|
13543
|
+
enabled: stored.data.enabled !== false,
|
|
13544
|
+
secrets: "unavailable"
|
|
13545
|
+
};
|
|
13546
|
+
}
|
|
13547
|
+
}
|
|
13548
|
+
/** The body of {@link readResolved} alone; a locked keychain yields the row without secrets. */
|
|
13549
|
+
async getResolved(id) {
|
|
13550
|
+
return (await this.readResolved(id))?.body ?? null;
|
|
13551
|
+
}
|
|
13552
|
+
/** Every instance, secrets resolved where the keychain allows; an absent one is skipped. */
|
|
13553
|
+
async listResolved() {
|
|
13554
|
+
const page = await this.handle.query({ limit: INSTANCES_LIST_MAX });
|
|
13555
|
+
const out = [];
|
|
13556
|
+
for (const record of page.records) {
|
|
13557
|
+
const read = await this.readResolved(record.id);
|
|
13558
|
+
if (read !== null) out.push(read);
|
|
13559
|
+
}
|
|
13560
|
+
return out;
|
|
13561
|
+
}
|
|
13562
|
+
viewOf(record) {
|
|
13563
|
+
const shown = secretViewsOf(this.records.owner, this.kind.name, record.id, this.kind.declaration, record.data);
|
|
13564
|
+
const rawName = record.data[this.nameField];
|
|
13565
|
+
const kind = this.hooks.kindOf?.(record.data);
|
|
13566
|
+
const health = this.hooks.health?.(record.data, record.id);
|
|
13567
|
+
return {
|
|
13568
|
+
id: record.id,
|
|
13569
|
+
name: typeof rawName === "string" ? rawName : record.id,
|
|
13570
|
+
configured: this.hooks.configured(record.data),
|
|
13571
|
+
...this.registryOwnsEnabled ? {} : { enabled: record.data.enabled !== false },
|
|
13572
|
+
formValues: shown,
|
|
13573
|
+
...kind !== void 0 ? { kind } : {},
|
|
13574
|
+
...health?.status !== void 0 ? { status: health.status } : {},
|
|
13575
|
+
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13576
|
+
};
|
|
13577
|
+
}
|
|
13578
|
+
describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
|
|
13579
|
+
...this.hooks.description,
|
|
13580
|
+
mintsId: true
|
|
13581
|
+
};
|
|
13582
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13583
|
+
listInstances = async () => {
|
|
13584
|
+
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13585
|
+
};
|
|
13586
|
+
getInstance = async ({ id }) => {
|
|
13587
|
+
const record = await this.handle.get(id);
|
|
13588
|
+
return record === null ? null : this.viewOf(record);
|
|
13589
|
+
};
|
|
13590
|
+
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13591
|
+
createInstance = async ({ id, name, kind }) => {
|
|
13592
|
+
const minted = id ?? this.hooks.mintId?.({
|
|
13593
|
+
name,
|
|
13594
|
+
...kind !== void 0 ? { kind } : {}
|
|
13595
|
+
});
|
|
13596
|
+
if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
|
|
13597
|
+
await this.handle.put({
|
|
13598
|
+
id: minted,
|
|
13599
|
+
data: this.hooks.initialBody(name, kind)
|
|
13600
|
+
}, { ifRev: 0 });
|
|
13601
|
+
await this.hooks.onChanged?.(minted, "created");
|
|
13602
|
+
return { id: minted };
|
|
13603
|
+
};
|
|
13604
|
+
updateInstance = async ({ id, name, values }) => {
|
|
13605
|
+
const patch = {};
|
|
13606
|
+
for (const [key, value] of Object.entries(values)) {
|
|
13607
|
+
if (value === void 0) continue;
|
|
13608
|
+
if (this.secretMaps.has(key)) {
|
|
13609
|
+
const entries = secretMapPatch(value);
|
|
13610
|
+
if (entries !== null) patch[key] = entries;
|
|
13611
|
+
} else if (!this.secretKeys.has(key)) patch[key] = value;
|
|
13612
|
+
else if (isSecretClear(value)) patch[key] = null;
|
|
13613
|
+
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13614
|
+
}
|
|
13615
|
+
if (name !== void 0) patch[this.nameField] = name;
|
|
13616
|
+
await this.handle.patch(id, patch);
|
|
13617
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13618
|
+
return { ok: true };
|
|
13619
|
+
};
|
|
13620
|
+
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13621
|
+
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13622
|
+
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
13623
|
+
await this.handle.patch(id, { enabled });
|
|
13624
|
+
await this.hooks.onChanged?.(id, "enabled");
|
|
13625
|
+
return { ok: true };
|
|
13626
|
+
};
|
|
13627
|
+
/** The row and its keychain refs go together (the records door). */
|
|
13628
|
+
deleteInstance = async ({ id }) => {
|
|
13629
|
+
await this.handle.delete(id);
|
|
13630
|
+
await this.hooks.onChanged?.(id, "deleted");
|
|
13631
|
+
return { ok: true };
|
|
13632
|
+
};
|
|
13633
|
+
testInstance = async (input) => {
|
|
13634
|
+
if (this.hooks.test === void 0) return {
|
|
13635
|
+
success: false,
|
|
13636
|
+
status: "unsupported",
|
|
13637
|
+
error: "This integration declares no connection test"
|
|
13638
|
+
};
|
|
13639
|
+
return this.hooks.test(input);
|
|
13640
|
+
};
|
|
13641
|
+
/** The flat provider object to hand to `registerProvider`. */
|
|
13642
|
+
toProvider() {
|
|
13643
|
+
return {
|
|
13644
|
+
describe: this.describe,
|
|
13645
|
+
instanceFormSchema: this.instanceFormSchema,
|
|
13646
|
+
listInstances: this.listInstances,
|
|
13647
|
+
getInstance: this.getInstance,
|
|
13648
|
+
createInstance: this.createInstance,
|
|
13649
|
+
updateInstance: this.updateInstance,
|
|
13650
|
+
setInstanceEnabled: this.setInstanceEnabled,
|
|
13651
|
+
deleteInstance: this.deleteInstance,
|
|
13652
|
+
testInstance: this.testInstance
|
|
13653
|
+
};
|
|
13654
|
+
}
|
|
13655
|
+
};
|
|
13314
13656
|
var EncodeProfileSchema = object({
|
|
13315
13657
|
video: object({
|
|
13316
13658
|
codec: _enum([
|
|
@@ -14416,6 +14758,58 @@ var MigrationOwnerStepSchema = object({
|
|
|
14416
14758
|
/** The ledger state the engine found: `pending` has no row yet. */
|
|
14417
14759
|
state: union([literal("pending"), MigrationStatusSchema])
|
|
14418
14760
|
});
|
|
14761
|
+
function isPlain(value) {
|
|
14762
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14763
|
+
}
|
|
14764
|
+
async function flattenNestedRecords(ports, spec) {
|
|
14765
|
+
const ids = await ports.listNested(spec.legacyKey);
|
|
14766
|
+
if (ids.length === 0) return {
|
|
14767
|
+
outcome: "already-done",
|
|
14768
|
+
summary: { converted: 0 }
|
|
14769
|
+
};
|
|
14770
|
+
const rows = /* @__PURE__ */ new Map();
|
|
14771
|
+
for (const id of ids) try {
|
|
14772
|
+
rows.set(id, await ports.readResolved(id));
|
|
14773
|
+
} catch (err) {
|
|
14774
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
14775
|
+
throw new Error(`record '${id}': cannot be read with its secrets (${why}) - nothing moved`);
|
|
14776
|
+
}
|
|
14777
|
+
for (const [id, row] of rows) {
|
|
14778
|
+
const legacy = row[spec.legacyKey];
|
|
14779
|
+
if (!isPlain(legacy)) throw new Error(`record '${id}': '${spec.legacyKey}' is not an object - nothing moved`);
|
|
14780
|
+
const flat = spec.toFlat(legacy, row);
|
|
14781
|
+
await ports.patch(id, flat);
|
|
14782
|
+
const back = await ports.readResolved(id);
|
|
14783
|
+
const wrong = spec.flatSecretKeys.filter((key) => {
|
|
14784
|
+
const sent = flat[key];
|
|
14785
|
+
const got = back[key];
|
|
14786
|
+
return (typeof sent === "string" ? sent : "") !== (typeof got === "string" ? got : "");
|
|
14787
|
+
});
|
|
14788
|
+
if (wrong.length > 0) {
|
|
14789
|
+
await ports.patch(id, Object.fromEntries(Object.keys(flat).map((key) => [key, null])));
|
|
14790
|
+
throw new Error(`record '${id}': ${wrong.join(", ")} did not read back from its new home - the row is unchanged`);
|
|
14791
|
+
}
|
|
14792
|
+
await ports.patch(id, { [spec.legacyKey]: null });
|
|
14793
|
+
}
|
|
14794
|
+
return {
|
|
14795
|
+
outcome: "done",
|
|
14796
|
+
summary: { converted: rows.size }
|
|
14797
|
+
};
|
|
14798
|
+
}
|
|
14799
|
+
/** The ports over one kind's records handle. */
|
|
14800
|
+
function flattenPortsOver(handle) {
|
|
14801
|
+
return {
|
|
14802
|
+
listNested: async (legacyKey) => (await handle.query({ limit: 1e3 })).records.filter((r) => legacyKey in r.data).map((r) => r.id),
|
|
14803
|
+
readResolved: async (id) => {
|
|
14804
|
+
const row = await handle.getResolved(id);
|
|
14805
|
+
if (row === null) throw new Error("the record is gone");
|
|
14806
|
+
return row.data;
|
|
14807
|
+
},
|
|
14808
|
+
patch: async (id, patch) => {
|
|
14809
|
+
await handle.patch(id, patch);
|
|
14810
|
+
}
|
|
14811
|
+
};
|
|
14812
|
+
}
|
|
14419
14813
|
/**
|
|
14420
14814
|
* The trainer contract — what the hub hands the cloud trainer and what it
|
|
14421
14815
|
* gets back (cloud training, D805+).
|
|
@@ -50288,117 +50682,6 @@ function actuator(input) {
|
|
|
50288
50682
|
};
|
|
50289
50683
|
}
|
|
50290
50684
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
50291
|
-
var PowerSourceRefusalReasonSchema = _enum([
|
|
50292
|
-
"no-power-source",
|
|
50293
|
-
"switch-missing",
|
|
50294
|
-
"switch-offline",
|
|
50295
|
-
"switch-unknown",
|
|
50296
|
-
"restart-in-progress",
|
|
50297
|
-
"switch-call-failed"
|
|
50298
|
-
]);
|
|
50299
|
-
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
50300
|
-
accepted: literal(false),
|
|
50301
|
-
reason: PowerSourceRefusalReasonSchema,
|
|
50302
|
-
/** One line for the operator, naming the switch. */
|
|
50303
|
-
message: string()
|
|
50304
|
-
})]);
|
|
50305
|
-
var PowerSourceInfoSchema = object({
|
|
50306
|
-
/** False when the device has no power source linked; the rest is then empty. */
|
|
50307
|
-
linked: boolean(),
|
|
50308
|
-
switchDeviceId: number().nullable(),
|
|
50309
|
-
switchName: string().nullable(),
|
|
50310
|
-
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
50311
|
-
switchOn: boolean().nullable(),
|
|
50312
|
-
switchOnline: boolean().nullable(),
|
|
50313
|
-
restartInProgress: boolean(),
|
|
50314
|
-
offSeconds: number().int()
|
|
50315
|
-
});
|
|
50316
|
-
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
50317
|
-
var PowerSourceStateSchema = object({
|
|
50318
|
-
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
50319
|
-
switchOn: boolean().nullable(),
|
|
50320
|
-
offSeconds: number().int(),
|
|
50321
|
-
lastFetchedAt: number()
|
|
50322
|
-
});
|
|
50323
|
-
var POWER_SOURCE_DEVICE_TYPES = [
|
|
50324
|
-
DeviceType.Gateway,
|
|
50325
|
-
DeviceType.Hub,
|
|
50326
|
-
DeviceType.Camera,
|
|
50327
|
-
DeviceType.Sensor,
|
|
50328
|
-
DeviceType.MediaPlayer,
|
|
50329
|
-
DeviceType.Vacuum,
|
|
50330
|
-
DeviceType.Generic,
|
|
50331
|
-
DeviceType.Washer,
|
|
50332
|
-
DeviceType.Dryer,
|
|
50333
|
-
DeviceType.Dishwasher,
|
|
50334
|
-
DeviceType.PetFountain,
|
|
50335
|
-
DeviceType.Fan,
|
|
50336
|
-
DeviceType.Humidifier,
|
|
50337
|
-
DeviceType.Climate,
|
|
50338
|
-
DeviceType.WaterHeater
|
|
50339
|
-
];
|
|
50340
|
-
var powerSourceCustomize = {
|
|
50341
|
-
version: 1,
|
|
50342
|
-
targets: [{
|
|
50343
|
-
id: "switch",
|
|
50344
|
-
cardinality: "scalar",
|
|
50345
|
-
vocabulary: {
|
|
50346
|
-
cap: "power-source",
|
|
50347
|
-
stateField: "switchOn"
|
|
50348
|
-
},
|
|
50349
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
50350
|
-
apply: "add",
|
|
50351
|
-
path: "switchOn",
|
|
50352
|
-
sources: [{
|
|
50353
|
-
kind: "switch",
|
|
50354
|
-
transforms: [{ kind: "identity" }]
|
|
50355
|
-
}],
|
|
50356
|
-
companions: [{
|
|
50357
|
-
kind: "constant",
|
|
50358
|
-
path: "offSeconds",
|
|
50359
|
-
value: 10,
|
|
50360
|
-
when: "add"
|
|
50361
|
-
}, {
|
|
50362
|
-
kind: "clock",
|
|
50363
|
-
path: "lastFetchedAt",
|
|
50364
|
-
when: "always"
|
|
50365
|
-
}],
|
|
50366
|
-
sourceReport: { status: "assignment-status" }
|
|
50367
|
-
}]
|
|
50368
|
-
};
|
|
50369
|
-
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
50370
|
-
var powerSourceCapability = {
|
|
50371
|
-
name: "power-source",
|
|
50372
|
-
scope: "device",
|
|
50373
|
-
mode: "singleton",
|
|
50374
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
50375
|
-
methods: {
|
|
50376
|
-
getStatus: systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "admin" }),
|
|
50377
|
-
powerOn: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
50378
|
-
kind: "mutation",
|
|
50379
|
-
auth: "admin"
|
|
50380
|
-
}),
|
|
50381
|
-
powerOff: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
50382
|
-
kind: "mutation",
|
|
50383
|
-
auth: "admin"
|
|
50384
|
-
}),
|
|
50385
|
-
forceRestart: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
50386
|
-
kind: "mutation",
|
|
50387
|
-
auth: "admin"
|
|
50388
|
-
})
|
|
50389
|
-
},
|
|
50390
|
-
runtimeState: PowerSourceStateSchema,
|
|
50391
|
-
customize: powerSourceCustomize,
|
|
50392
|
-
/**
|
|
50393
|
-
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
50394
|
-
* its next report; a restored value would outlive the reading it names.
|
|
50395
|
-
*
|
|
50396
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
50397
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
50398
|
-
*/
|
|
50399
|
-
durability: "session",
|
|
50400
|
-
history: "none"
|
|
50401
|
-
};
|
|
50402
50685
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
50403
50686
|
DeviceType.Camera;
|
|
50404
50687
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
@@ -50426,6 +50709,165 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
|
|
|
50426
50709
|
}).optional()
|
|
50427
50710
|
})) });
|
|
50428
50711
|
/**
|
|
50712
|
+
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
50713
|
+
* `z.infer`, they do not restate the shape.
|
|
50714
|
+
*/
|
|
50715
|
+
var OverrideSourceSchema = object({
|
|
50716
|
+
addonId: string().min(1),
|
|
50717
|
+
stableId: string().min(1),
|
|
50718
|
+
cap: string().min(1),
|
|
50719
|
+
fieldPath: string().min(1)
|
|
50720
|
+
});
|
|
50721
|
+
var IdentityTransformDraftSchema = object({ kind: literal("identity") });
|
|
50722
|
+
var InvertTransformDraftSchema = object({ kind: literal("invert") });
|
|
50723
|
+
var ThresholdTransformDraftSchema = object({
|
|
50724
|
+
kind: literal("threshold"),
|
|
50725
|
+
comparison: _enum(["gte", "lte"]),
|
|
50726
|
+
threshold: number(),
|
|
50727
|
+
startDwellMs: number().nonnegative(),
|
|
50728
|
+
stopDwellMs: number().nonnegative()
|
|
50729
|
+
});
|
|
50730
|
+
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
50731
|
+
IdentityTransformDraftSchema,
|
|
50732
|
+
InvertTransformDraftSchema,
|
|
50733
|
+
ThresholdTransformDraftSchema,
|
|
50734
|
+
object({
|
|
50735
|
+
kind: literal("linear"),
|
|
50736
|
+
scale: number(),
|
|
50737
|
+
offset: number(),
|
|
50738
|
+
clampMin: number().optional(),
|
|
50739
|
+
clampMax: number().optional()
|
|
50740
|
+
}),
|
|
50741
|
+
object({
|
|
50742
|
+
kind: literal("enum-map"),
|
|
50743
|
+
mapping: record(string(), string().nullable())
|
|
50744
|
+
}),
|
|
50745
|
+
object({ kind: literal("number") })
|
|
50746
|
+
]);
|
|
50747
|
+
var ScalarOverrideDraftSchema = object({
|
|
50748
|
+
kind: literal("scalar"),
|
|
50749
|
+
cap: string().min(1),
|
|
50750
|
+
targetId: string().min(1),
|
|
50751
|
+
source: OverrideSourceSchema,
|
|
50752
|
+
transform: CustomizeTransformDraftSchema
|
|
50753
|
+
});
|
|
50754
|
+
var KeyedArrayOverrideDraftSchema = object({
|
|
50755
|
+
kind: literal("keyedArray"),
|
|
50756
|
+
cap: string().min(1),
|
|
50757
|
+
targetId: string().min(1),
|
|
50758
|
+
key: string().min(1),
|
|
50759
|
+
label: string().min(1),
|
|
50760
|
+
fieldPath: string().min(1),
|
|
50761
|
+
source: OverrideSourceSchema,
|
|
50762
|
+
transform: CustomizeTransformDraftSchema
|
|
50763
|
+
});
|
|
50764
|
+
var PulseOverrideDraftSchema = object({
|
|
50765
|
+
kind: literal("pulse"),
|
|
50766
|
+
cap: string().min(1),
|
|
50767
|
+
targetId: string().min(1),
|
|
50768
|
+
enabled: boolean(),
|
|
50769
|
+
debounceMs: number().nonnegative(),
|
|
50770
|
+
edges: array(_enum([
|
|
50771
|
+
"rising",
|
|
50772
|
+
"button",
|
|
50773
|
+
"native-event"
|
|
50774
|
+
])).min(1).optional(),
|
|
50775
|
+
sources: array(OverrideSourceSchema).min(1)
|
|
50776
|
+
});
|
|
50777
|
+
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
50778
|
+
"in-addition",
|
|
50779
|
+
"confirm",
|
|
50780
|
+
"replace"
|
|
50781
|
+
]).optional() });
|
|
50782
|
+
discriminatedUnion("kind", [
|
|
50783
|
+
ScalarOverrideDraftSchema,
|
|
50784
|
+
KeyedArrayOverrideDraftSchema,
|
|
50785
|
+
PulseOverrideDraftSchema,
|
|
50786
|
+
object({
|
|
50787
|
+
kind: literal("span"),
|
|
50788
|
+
cap: string().min(1),
|
|
50789
|
+
targetId: string().min(1),
|
|
50790
|
+
timeoutMs: number().nonnegative(),
|
|
50791
|
+
cooldownMs: number().nonnegative().optional(),
|
|
50792
|
+
sources: array(SpanSourceDraftSchema)
|
|
50793
|
+
})
|
|
50794
|
+
]);
|
|
50795
|
+
object({
|
|
50796
|
+
targetId: string().min(1),
|
|
50797
|
+
configured: boolean(),
|
|
50798
|
+
source: _enum(["native", "assigned"]),
|
|
50799
|
+
state: _enum([
|
|
50800
|
+
"active",
|
|
50801
|
+
"stale",
|
|
50802
|
+
"released",
|
|
50803
|
+
"unavailable"
|
|
50804
|
+
]),
|
|
50805
|
+
reason: string().nullable()
|
|
50806
|
+
});
|
|
50807
|
+
/**
|
|
50808
|
+
* What a running-threshold proposal says (planning#405). The schema is the
|
|
50809
|
+
* type: the algorithm, the capability method and the UI all read `z.infer`.
|
|
50810
|
+
*
|
|
50811
|
+
* A proposal is read-only advice: it carries the numbers it was derived from
|
|
50812
|
+
* (`evidence`) so the operator can judge it, and an `insufficient` answer names
|
|
50813
|
+
* WHY with a typed reason and the catalog defaults, never an invented number.
|
|
50814
|
+
*/
|
|
50815
|
+
var RunningProposalTierSchema = _enum(["raw", "5m"]);
|
|
50816
|
+
var RunningProposalConfidenceSchema = _enum([
|
|
50817
|
+
"high",
|
|
50818
|
+
"medium",
|
|
50819
|
+
"low"
|
|
50820
|
+
]);
|
|
50821
|
+
var RunningProposalInsufficientReasonSchema = _enum([
|
|
50822
|
+
"no-history",
|
|
50823
|
+
"span-too-short",
|
|
50824
|
+
"truncated",
|
|
50825
|
+
"no-idle-floor",
|
|
50826
|
+
"no-runs",
|
|
50827
|
+
"no-separation"
|
|
50828
|
+
]);
|
|
50829
|
+
var RunningProposalReplaySchema = object({
|
|
50830
|
+
/** Intervals the real dwell expression held true over the window. */
|
|
50831
|
+
intervals: number().int().nonnegative(),
|
|
50832
|
+
/** Intervals outside every run, or a second interval inside one. */
|
|
50833
|
+
extra: number().int().nonnegative(),
|
|
50834
|
+
/** Runs without an interval of their own (none, or merged into a neighbour). */
|
|
50835
|
+
missed: number().int().nonnegative()
|
|
50836
|
+
});
|
|
50837
|
+
var RunningProposalEvidenceSchema = object({
|
|
50838
|
+
tier: RunningProposalTierSchema,
|
|
50839
|
+
spanMs: number().nonnegative(),
|
|
50840
|
+
from: number(),
|
|
50841
|
+
to: number(),
|
|
50842
|
+
points: number().int().nonnegative(),
|
|
50843
|
+
runsFound: number().int().nonnegative(),
|
|
50844
|
+
pausesFound: number().int().nonnegative(),
|
|
50845
|
+
longestPauseMs: number().nonnegative(),
|
|
50846
|
+
idleFloorW: number(),
|
|
50847
|
+
noiseCeilingW: number(),
|
|
50848
|
+
plateauW: number(),
|
|
50849
|
+
replay: RunningProposalReplaySchema
|
|
50850
|
+
});
|
|
50851
|
+
var RunningProposalInsufficientEvidenceSchema = object({
|
|
50852
|
+
spanMs: number().nonnegative(),
|
|
50853
|
+
points: number().int().nonnegative(),
|
|
50854
|
+
runsFound: number().int().nonnegative()
|
|
50855
|
+
});
|
|
50856
|
+
var RunningThresholdProposalSchema = discriminatedUnion("state", [object({
|
|
50857
|
+
state: literal("proposal"),
|
|
50858
|
+
transform: ThresholdTransformDraftSchema,
|
|
50859
|
+
evidence: RunningProposalEvidenceSchema,
|
|
50860
|
+
confidence: RunningProposalConfidenceSchema,
|
|
50861
|
+
/** Why the confidence is not higher; empty when nothing held it back. */
|
|
50862
|
+
notes: array(string())
|
|
50863
|
+
}), object({
|
|
50864
|
+
state: literal("insufficient"),
|
|
50865
|
+
reason: RunningProposalInsufficientReasonSchema,
|
|
50866
|
+
evidence: RunningProposalInsufficientEvidenceSchema,
|
|
50867
|
+
/** The catalog defaults, offered as what stands when nothing better is known. */
|
|
50868
|
+
fallback: ThresholdTransformDraftSchema
|
|
50869
|
+
})]);
|
|
50870
|
+
/**
|
|
50429
50871
|
* The report of a one-shot "move this owner's plaintext secrets into the
|
|
50430
50872
|
* keychain" run (D878). COUNTS only - never a value, never a ref.
|
|
50431
50873
|
*
|
|
@@ -50715,6 +51157,14 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
50715
51157
|
kind: "mutation",
|
|
50716
51158
|
auth: "admin",
|
|
50717
51159
|
access: "view"
|
|
51160
|
+
}), method(object({
|
|
51161
|
+
deviceId: number().int(),
|
|
51162
|
+
cap: string().min(1),
|
|
51163
|
+
fieldPath: string().min(1),
|
|
51164
|
+
deviceType: _enum(DeviceType).optional()
|
|
51165
|
+
}), RunningThresholdProposalSchema, {
|
|
51166
|
+
auth: "admin",
|
|
51167
|
+
access: "view"
|
|
50718
51168
|
}), method(object({
|
|
50719
51169
|
blockId: string().min(1),
|
|
50720
51170
|
trace: AutomationRunTraceSchema.optional(),
|
|
@@ -51691,7 +52141,46 @@ method(object({
|
|
|
51691
52141
|
* exposes only the COMMON surface: link state, supported device
|
|
51692
52142
|
* kinds (so the UI can filter the device picker), and per-device
|
|
51693
52143
|
* expose/unexpose.
|
|
52144
|
+
*
|
|
52145
|
+
* INSTANCES. One export addon can serve several independent targets (a
|
|
52146
|
+
* HomeKit bridge, a Home Assistant broker). `listInstances` names them.
|
|
52147
|
+
* `instanceId` on a READ is optional: absent = the whole addon (linked if any
|
|
52148
|
+
* enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
|
|
52149
|
+
* default instance's); present = that instance; an id the provider does not
|
|
52150
|
+
* serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
|
|
52151
|
+
* never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
|
|
52152
|
+
* change always names the target it changes. A single-instance provider
|
|
52153
|
+
* accepts an absent id on a read or its own id, and refuses any other.
|
|
52154
|
+
*/
|
|
52155
|
+
/** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
|
|
52156
|
+
var InstanceIdSchema$1 = string().min(1);
|
|
52157
|
+
var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
|
|
52158
|
+
/** One independent export target of an addon. */
|
|
52159
|
+
var ExportInstanceSchema = object({
|
|
52160
|
+
instanceId: InstanceIdSchema$1,
|
|
52161
|
+
name: string(),
|
|
52162
|
+
enabled: boolean(),
|
|
52163
|
+
kind: string().optional()
|
|
52164
|
+
});
|
|
52165
|
+
var EXPORT_INSTANCE_UNKNOWN = "export-instance-unknown";
|
|
52166
|
+
/** The named failure of an `instanceId` the provider does not serve. The message starts with the code. */
|
|
52167
|
+
var ExportInstanceUnknownError = class extends Error {
|
|
52168
|
+
code = EXPORT_INSTANCE_UNKNOWN;
|
|
52169
|
+
instanceId;
|
|
52170
|
+
constructor(addon, instanceId) {
|
|
52171
|
+
super(`${EXPORT_INSTANCE_UNKNOWN}: ${addon} serves no instance "${instanceId}"`);
|
|
52172
|
+
this.name = "ExportInstanceUnknownError";
|
|
52173
|
+
this.instanceId = instanceId;
|
|
52174
|
+
}
|
|
52175
|
+
};
|
|
52176
|
+
/**
|
|
52177
|
+
* The instance a `getStatus` call names. The cap's `status` block injects a per-device
|
|
52178
|
+
* `getStatus({ deviceId })` beside the method's own `{ instanceId? }`, so the provider sees
|
|
52179
|
+
* both shapes; only the second names an instance (a per-device status read is addon-wide).
|
|
51694
52180
|
*/
|
|
52181
|
+
function statusInstanceOf(input) {
|
|
52182
|
+
return "instanceId" in input ? input.instanceId : void 0;
|
|
52183
|
+
}
|
|
51695
52184
|
var LinkStateSchema = _enum([
|
|
51696
52185
|
"unlinked",
|
|
51697
52186
|
"linked",
|
|
@@ -51742,10 +52231,14 @@ var ExposedDeviceSchema = object({
|
|
|
51742
52231
|
capabilities: array(string()).optional()
|
|
51743
52232
|
});
|
|
51744
52233
|
var ExposeInputSchema = object({
|
|
52234
|
+
instanceId: InstanceIdSchema$1,
|
|
51745
52235
|
deviceId: string(),
|
|
51746
52236
|
capabilities: array(string()).optional()
|
|
51747
52237
|
});
|
|
51748
|
-
var UnexposeInputSchema = object({
|
|
52238
|
+
var UnexposeInputSchema = object({
|
|
52239
|
+
instanceId: InstanceIdSchema$1,
|
|
52240
|
+
deviceId: string()
|
|
52241
|
+
});
|
|
51749
52242
|
/**
|
|
51750
52243
|
* What an exporter may expose besides devices (D968). An action and a
|
|
51751
52244
|
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
@@ -51774,8 +52267,14 @@ var ExposedEntitySchema = object({
|
|
|
51774
52267
|
entity: ExportEntityRefSchema,
|
|
51775
52268
|
exposedAs: string().optional()
|
|
51776
52269
|
});
|
|
51777
|
-
var ExposeEntityInputSchema = object({
|
|
51778
|
-
|
|
52270
|
+
var ExposeEntityInputSchema = object({
|
|
52271
|
+
instanceId: InstanceIdSchema$1,
|
|
52272
|
+
entity: ExportEntityRefSchema
|
|
52273
|
+
});
|
|
52274
|
+
var UnexposeEntityInputSchema = object({
|
|
52275
|
+
instanceId: InstanceIdSchema$1,
|
|
52276
|
+
entity: ExportEntityRefSchema
|
|
52277
|
+
});
|
|
51779
52278
|
var deviceExportCapability = {
|
|
51780
52279
|
name: "device-export",
|
|
51781
52280
|
scope: "system",
|
|
@@ -51795,9 +52294,10 @@ var deviceExportCapability = {
|
|
|
51795
52294
|
*/
|
|
51796
52295
|
exposesDeviceSettings: true,
|
|
51797
52296
|
methods: {
|
|
51798
|
-
getStatus: method(
|
|
51799
|
-
|
|
51800
|
-
|
|
52297
|
+
getStatus: method(InstancePinSchema, DeviceExportStatusSchema),
|
|
52298
|
+
listInstances: method(_void(), array(ExportInstanceSchema)),
|
|
52299
|
+
listSupportedDeviceKinds: method(InstancePinSchema, array(DeviceKindSchema)),
|
|
52300
|
+
listExposedDevices: method(InstancePinSchema, array(ExposedDeviceSchema)),
|
|
51801
52301
|
exposeDevice: method(ExposeInputSchema, _void(), {
|
|
51802
52302
|
kind: "mutation",
|
|
51803
52303
|
auth: "admin"
|
|
@@ -51807,7 +52307,7 @@ var deviceExportCapability = {
|
|
|
51807
52307
|
auth: "admin"
|
|
51808
52308
|
}),
|
|
51809
52309
|
listSupportedEntityKinds: optionalMethod(_void(), array(ExportEntityKindSchema)),
|
|
51810
|
-
listExposedEntities: optionalMethod(
|
|
52310
|
+
listExposedEntities: optionalMethod(InstancePinSchema, array(ExposedEntitySchema)),
|
|
51811
52311
|
exposeEntity: optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
51812
52312
|
kind: "mutation",
|
|
51813
52313
|
auth: "admin"
|
|
@@ -55412,15 +55912,15 @@ var FanPresetCaptureResultSchema = object({
|
|
|
55412
55912
|
items: array(FanCaptureItemSchema),
|
|
55413
55913
|
omitted: array(FanCaptureOmissionSchema)
|
|
55414
55914
|
});
|
|
55415
|
-
var deviceInput$
|
|
55915
|
+
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
55416
55916
|
DeviceType.Fan;
|
|
55417
|
-
method(deviceInput$
|
|
55917
|
+
method(deviceInput$2, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$2.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
|
|
55418
55918
|
kind: "mutation",
|
|
55419
55919
|
auth: "admin"
|
|
55420
|
-
}), method(deviceInput$
|
|
55920
|
+
}), method(deviceInput$2.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
|
|
55421
55921
|
kind: "mutation",
|
|
55422
55922
|
auth: "protected"
|
|
55423
|
-
}), method(deviceInput$
|
|
55923
|
+
}), method(deviceInput$2.extend({
|
|
55424
55924
|
fields: array(FanCaptureFieldSchema).optional(),
|
|
55425
55925
|
steps: array(ActionStepSchema).max(20).default([])
|
|
55426
55926
|
}), FanPresetCaptureResultSchema, { auth: "protected" });
|
|
@@ -56331,13 +56831,65 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56331
56831
|
*/
|
|
56332
56832
|
var ConfigSchemaPassthrough$3 = custom();
|
|
56333
56833
|
var IntegrationInstanceIdSchema = string().min(1);
|
|
56834
|
+
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
56835
|
+
/**
|
|
56836
|
+
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
56837
|
+
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
56838
|
+
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
56839
|
+
* interactive account link (popup) and may carry `{{brokerId}}`, which the UI replaces with the
|
|
56840
|
+
* instance id.
|
|
56841
|
+
*/
|
|
56842
|
+
var IntegrationInstanceKindSchema = object({
|
|
56843
|
+
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
56844
|
+
id: string().min(1),
|
|
56845
|
+
label: string().min(1),
|
|
56846
|
+
/** The kind signs in through a server-held interactive session. */
|
|
56847
|
+
signIn: object({
|
|
56848
|
+
basePath: string().min(1),
|
|
56849
|
+
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
56850
|
+
}).optional(),
|
|
56851
|
+
/** Relative hub path to start an interactive account link of an instance of this kind. */
|
|
56852
|
+
connectPath: string().min(1).optional(),
|
|
56853
|
+
/** Relative hub path to disconnect that account. */
|
|
56854
|
+
disconnectPath: string().min(1).optional()
|
|
56855
|
+
});
|
|
56334
56856
|
var IntegrationInstanceDescriptionSchema = object({
|
|
56857
|
+
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
56858
|
+
role: _enum([
|
|
56859
|
+
"devices",
|
|
56860
|
+
"services",
|
|
56861
|
+
"both"
|
|
56862
|
+
]),
|
|
56863
|
+
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
56864
|
+
discovery: object({
|
|
56865
|
+
mode: _enum([
|
|
56866
|
+
"none",
|
|
56867
|
+
"lan",
|
|
56868
|
+
"account"
|
|
56869
|
+
]),
|
|
56870
|
+
scopedToInstance: boolean()
|
|
56871
|
+
}).optional(),
|
|
56335
56872
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
56336
|
-
instanceMode:
|
|
56873
|
+
instanceMode: IntegrationInstanceModeSchema,
|
|
56337
56874
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
56338
56875
|
kind: string(),
|
|
56339
56876
|
/** True when `testInstance` really probes the remote. */
|
|
56340
|
-
canTest: boolean()
|
|
56877
|
+
canTest: boolean(),
|
|
56878
|
+
/**
|
|
56879
|
+
* The kinds an instance can be of. Absent or one entry: nothing to choose; more than one: the
|
|
56880
|
+
* Add flow asks which first and sends it as `createInstance.kind`.
|
|
56881
|
+
*/
|
|
56882
|
+
kinds: array(IntegrationInstanceKindSchema).optional(),
|
|
56883
|
+
/**
|
|
56884
|
+
* The provider accepts `createInstance.id` for an id that once existed (an adopted id, because
|
|
56885
|
+
* devices carry it). The UI then offers "restore under its old id" on Add. Absent means no.
|
|
56886
|
+
*/
|
|
56887
|
+
allowRestoreId: boolean().optional(),
|
|
56888
|
+
/**
|
|
56889
|
+
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
56890
|
+
* send an id (the UI then mints an opaque one).
|
|
56891
|
+
*/
|
|
56892
|
+
mintsId: boolean().optional()
|
|
56341
56893
|
});
|
|
56342
56894
|
var IntegrationInstanceStatusSchema = object({
|
|
56343
56895
|
state: _enum([
|
|
@@ -56346,7 +56898,9 @@ var IntegrationInstanceStatusSchema = object({
|
|
|
56346
56898
|
"error",
|
|
56347
56899
|
"unknown"
|
|
56348
56900
|
]),
|
|
56349
|
-
detail: string().optional()
|
|
56901
|
+
detail: string().optional(),
|
|
56902
|
+
/** A short human word for the state ("Signed in", "Token expired"); shown instead of `state`. */
|
|
56903
|
+
label: string().optional()
|
|
56350
56904
|
});
|
|
56351
56905
|
var IntegrationInstanceSchema = object({
|
|
56352
56906
|
id: IntegrationInstanceIdSchema,
|
|
@@ -56354,13 +56908,18 @@ var IntegrationInstanceSchema = object({
|
|
|
56354
56908
|
/** The addon's own rule (an URL is present, a client id is present...). */
|
|
56355
56909
|
configured: boolean(),
|
|
56356
56910
|
/**
|
|
56357
|
-
* The instance's own on/off switch (D996)
|
|
56358
|
-
*
|
|
56911
|
+
* The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
|
|
56912
|
+
* so a row written before the field existed is on). Absent when the `integrations` registry
|
|
56913
|
+
* owns the switch (`describe().role` is `devices`): read it from the registry row.
|
|
56359
56914
|
*/
|
|
56360
|
-
enabled: boolean(),
|
|
56915
|
+
enabled: boolean().optional(),
|
|
56916
|
+
/** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
|
|
56917
|
+
kind: string().optional(),
|
|
56361
56918
|
/** Values for the instance form; a secret is a `SecretFieldView`, never text. */
|
|
56362
56919
|
formValues: record(string(), unknown()),
|
|
56363
|
-
status: IntegrationInstanceStatusSchema.optional()
|
|
56920
|
+
status: IntegrationInstanceStatusSchema.optional(),
|
|
56921
|
+
/** Epoch ms of the last probe of the remote that produced `status`; absent when never checked. */
|
|
56922
|
+
lastCheckedAt: number().optional()
|
|
56364
56923
|
});
|
|
56365
56924
|
var IntegrationInstancePatchSchema = object({
|
|
56366
56925
|
id: IntegrationInstanceIdSchema,
|
|
@@ -56368,31 +56927,57 @@ var IntegrationInstancePatchSchema = object({
|
|
|
56368
56927
|
/** Omitted / '' / redaction token keeps a secret; `{ $secretClear: true }` clears it. */
|
|
56369
56928
|
values: record(string(), unknown())
|
|
56370
56929
|
});
|
|
56371
|
-
|
|
56372
|
-
|
|
56373
|
-
|
|
56374
|
-
|
|
56375
|
-
|
|
56376
|
-
|
|
56377
|
-
|
|
56378
|
-
|
|
56379
|
-
|
|
56380
|
-
|
|
56381
|
-
|
|
56382
|
-
|
|
56383
|
-
|
|
56384
|
-
|
|
56385
|
-
|
|
56386
|
-
|
|
56387
|
-
|
|
56388
|
-
|
|
56389
|
-
}),
|
|
56390
|
-
|
|
56391
|
-
|
|
56392
|
-
}),
|
|
56393
|
-
|
|
56394
|
-
|
|
56395
|
-
|
|
56930
|
+
var integrationInstancesCapability = {
|
|
56931
|
+
name: "integration-instances",
|
|
56932
|
+
scope: "system",
|
|
56933
|
+
mode: "collection",
|
|
56934
|
+
methods: {
|
|
56935
|
+
describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
|
|
56936
|
+
/** The form; `id: null` is the create form. */
|
|
56937
|
+
instanceFormSchema: method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
56938
|
+
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
56939
|
+
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
56940
|
+
/**
|
|
56941
|
+
* Insert the typed record. With `id` it is stored under that id (a duplicate id is refused);
|
|
56942
|
+
* without one the PROVIDER mints it and answers it. `kind` picks one of `describe().kinds`.
|
|
56943
|
+
*/
|
|
56944
|
+
createInstance: method(object({
|
|
56945
|
+
id: IntegrationInstanceIdSchema.optional(),
|
|
56946
|
+
name: string().min(1),
|
|
56947
|
+
kind: string().min(1).optional()
|
|
56948
|
+
}), object({ id: string() }), {
|
|
56949
|
+
kind: "mutation",
|
|
56950
|
+
auth: "admin"
|
|
56951
|
+
}),
|
|
56952
|
+
updateInstance: method(IntegrationInstancePatchSchema, object({ ok: literal(true) }), {
|
|
56953
|
+
kind: "mutation",
|
|
56954
|
+
auth: "admin"
|
|
56955
|
+
}),
|
|
56956
|
+
/**
|
|
56957
|
+
* Switch ONE instance on or off. A row control, not a form field: `updateInstance.values` is the
|
|
56958
|
+
* form patch (D62, D996). An off instance keeps its record and secrets and does no work.
|
|
56959
|
+
*/
|
|
56960
|
+
setInstanceEnabled: method(object({
|
|
56961
|
+
id: IntegrationInstanceIdSchema,
|
|
56962
|
+
enabled: boolean()
|
|
56963
|
+
}), object({ ok: literal(true) }), {
|
|
56964
|
+
kind: "mutation",
|
|
56965
|
+
auth: "admin"
|
|
56966
|
+
}),
|
|
56967
|
+
/** Delete the record and its keychain refs together. */
|
|
56968
|
+
deleteInstance: method(object({ id: IntegrationInstanceIdSchema }), object({ ok: literal(true) }), {
|
|
56969
|
+
kind: "mutation",
|
|
56970
|
+
auth: "admin"
|
|
56971
|
+
}),
|
|
56972
|
+
testInstance: optionalMethod(object({
|
|
56973
|
+
id: IntegrationInstanceIdSchema.nullable(),
|
|
56974
|
+
values: record(string(), unknown())
|
|
56975
|
+
}), TestConnectionResultSchema$1, {
|
|
56976
|
+
kind: "mutation",
|
|
56977
|
+
auth: "admin"
|
|
56978
|
+
})
|
|
56979
|
+
}
|
|
56980
|
+
};
|
|
56396
56981
|
/**
|
|
56397
56982
|
* Two-way audio / talk-back cap. Clients open a WebRTC audio-only
|
|
56398
56983
|
* session (server-initiated offer pattern, identical to the main
|
|
@@ -58410,6 +58995,11 @@ var LogEntrySchema = object({
|
|
|
58410
58995
|
* flight, and not asking for the next slice ends the scan.
|
|
58411
58996
|
*/
|
|
58412
58997
|
var LogReadQuerySchema = object({
|
|
58998
|
+
/**
|
|
58999
|
+
* Which destination INSTANCE answers (a destination addon may hold several records). Absent ⇒
|
|
59000
|
+
* the provider's first readable one, so a caller written before instances existed is unchanged.
|
|
59001
|
+
*/
|
|
59002
|
+
instanceId: string().min(1).max(200).optional(),
|
|
58413
59003
|
/** Correlates `cancelRead`. Caller-chosen, unique per in-flight read. */
|
|
58414
59004
|
requestId: string().min(1).max(64).optional(),
|
|
58415
59005
|
/** The device the line is about (`tags.deviceId`, numeric). */
|
|
@@ -58472,6 +59062,8 @@ var LogReadResultSchema = object({
|
|
|
58472
59062
|
/** True when `cancelRead` ended this slice early. */
|
|
58473
59063
|
cancelled: boolean().optional()
|
|
58474
59064
|
});
|
|
59065
|
+
/** Names one destination instance; absent ⇒ the provider's first readable one. */
|
|
59066
|
+
var LogInstanceSelectorSchema = object({ instanceId: string().min(1).max(200).optional() });
|
|
58475
59067
|
var LogReadCapabilitiesSchema = object({
|
|
58476
59068
|
/** False ⇒ this destination only ships lines (console, forwarder, an unconfigured Loki). */
|
|
58477
59069
|
readable: boolean(),
|
|
@@ -58490,7 +59082,7 @@ var LogReadCapabilitiesSchema = object({
|
|
|
58490
59082
|
/** How far back the store reaches, when the provider knows (days). */
|
|
58491
59083
|
retentionDays: number().nullable()
|
|
58492
59084
|
});
|
|
58493
|
-
method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(
|
|
59085
|
+
method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(LogInstanceSelectorSchema, LogReadCapabilitiesSchema, { auth: "admin" }), optionalMethod(LogReadQuerySchema, LogReadResultSchema, { auth: "admin" }), optionalMethod(object({ requestId: string().min(1).max(64) }), object({ cancelled: boolean() }), {
|
|
58494
59086
|
kind: "mutation",
|
|
58495
59087
|
auth: "admin"
|
|
58496
59088
|
});
|
|
@@ -70833,6 +71425,14 @@ var powerMeterCapability = {
|
|
|
70833
71425
|
deviceNative: true,
|
|
70834
71426
|
mode: "singleton",
|
|
70835
71427
|
deviceTypes: POWER_METER_DEVICE_TYPES,
|
|
71428
|
+
deviceConfig: { ui: {
|
|
71429
|
+
kind: "widget",
|
|
71430
|
+
widgetId: "host/power-meter-line",
|
|
71431
|
+
tab: "power",
|
|
71432
|
+
placement: "hero",
|
|
71433
|
+
label: "Power",
|
|
71434
|
+
order: 4
|
|
71435
|
+
} },
|
|
70836
71436
|
methods: {},
|
|
70837
71437
|
status: {
|
|
70838
71438
|
schema: PowerMeterStatusSchema,
|
|
@@ -70892,6 +71492,117 @@ var powerMeterCapability = {
|
|
|
70892
71492
|
volatileStateFields: ["lastFetchedAt"],
|
|
70893
71493
|
customize: powerMeterCustomize
|
|
70894
71494
|
};
|
|
71495
|
+
var PowerSourceRefusalReasonSchema = _enum([
|
|
71496
|
+
"no-power-source",
|
|
71497
|
+
"switch-missing",
|
|
71498
|
+
"switch-offline",
|
|
71499
|
+
"switch-unknown",
|
|
71500
|
+
"restart-in-progress",
|
|
71501
|
+
"switch-call-failed"
|
|
71502
|
+
]);
|
|
71503
|
+
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
71504
|
+
accepted: literal(false),
|
|
71505
|
+
reason: PowerSourceRefusalReasonSchema,
|
|
71506
|
+
/** One line for the operator, naming the switch. */
|
|
71507
|
+
message: string()
|
|
71508
|
+
})]);
|
|
71509
|
+
var PowerSourceInfoSchema = object({
|
|
71510
|
+
/** False when the device has no power source linked; the rest is then empty. */
|
|
71511
|
+
linked: boolean(),
|
|
71512
|
+
switchDeviceId: number().nullable(),
|
|
71513
|
+
switchName: string().nullable(),
|
|
71514
|
+
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
71515
|
+
switchOn: boolean().nullable(),
|
|
71516
|
+
switchOnline: boolean().nullable(),
|
|
71517
|
+
restartInProgress: boolean(),
|
|
71518
|
+
offSeconds: number().int()
|
|
71519
|
+
});
|
|
71520
|
+
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
71521
|
+
var PowerSourceStateSchema = object({
|
|
71522
|
+
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
71523
|
+
switchOn: boolean().nullable(),
|
|
71524
|
+
offSeconds: number().int(),
|
|
71525
|
+
lastFetchedAt: number()
|
|
71526
|
+
});
|
|
71527
|
+
var POWER_SOURCE_DEVICE_TYPES = [
|
|
71528
|
+
DeviceType.Gateway,
|
|
71529
|
+
DeviceType.Hub,
|
|
71530
|
+
DeviceType.Camera,
|
|
71531
|
+
DeviceType.Sensor,
|
|
71532
|
+
DeviceType.MediaPlayer,
|
|
71533
|
+
DeviceType.Vacuum,
|
|
71534
|
+
DeviceType.Generic,
|
|
71535
|
+
DeviceType.Washer,
|
|
71536
|
+
DeviceType.Dryer,
|
|
71537
|
+
DeviceType.Dishwasher,
|
|
71538
|
+
DeviceType.PetFountain,
|
|
71539
|
+
DeviceType.Fan,
|
|
71540
|
+
DeviceType.Humidifier,
|
|
71541
|
+
DeviceType.Climate,
|
|
71542
|
+
DeviceType.WaterHeater
|
|
71543
|
+
];
|
|
71544
|
+
var powerSourceCustomize = {
|
|
71545
|
+
version: 1,
|
|
71546
|
+
targets: [{
|
|
71547
|
+
id: "switch",
|
|
71548
|
+
cardinality: "scalar",
|
|
71549
|
+
vocabulary: {
|
|
71550
|
+
cap: "power-source",
|
|
71551
|
+
stateField: "switchOn"
|
|
71552
|
+
},
|
|
71553
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
71554
|
+
apply: "add",
|
|
71555
|
+
path: "switchOn",
|
|
71556
|
+
sources: [{
|
|
71557
|
+
kind: "switch",
|
|
71558
|
+
transforms: [{ kind: "identity" }]
|
|
71559
|
+
}],
|
|
71560
|
+
companions: [{
|
|
71561
|
+
kind: "constant",
|
|
71562
|
+
path: "offSeconds",
|
|
71563
|
+
value: 10,
|
|
71564
|
+
when: "add"
|
|
71565
|
+
}, {
|
|
71566
|
+
kind: "clock",
|
|
71567
|
+
path: "lastFetchedAt",
|
|
71568
|
+
when: "always"
|
|
71569
|
+
}],
|
|
71570
|
+
sourceReport: { status: "assignment-status" }
|
|
71571
|
+
}]
|
|
71572
|
+
};
|
|
71573
|
+
var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
|
|
71574
|
+
var powerSourceCapability = {
|
|
71575
|
+
name: "power-source",
|
|
71576
|
+
scope: "device",
|
|
71577
|
+
mode: "singleton",
|
|
71578
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
71579
|
+
methods: {
|
|
71580
|
+
getStatus: systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }),
|
|
71581
|
+
powerOn: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
71582
|
+
kind: "mutation",
|
|
71583
|
+
auth: "admin"
|
|
71584
|
+
}),
|
|
71585
|
+
powerOff: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
71586
|
+
kind: "mutation",
|
|
71587
|
+
auth: "admin"
|
|
71588
|
+
}),
|
|
71589
|
+
forceRestart: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
71590
|
+
kind: "mutation",
|
|
71591
|
+
auth: "admin"
|
|
71592
|
+
})
|
|
71593
|
+
},
|
|
71594
|
+
runtimeState: PowerSourceStateSchema,
|
|
71595
|
+
customize: powerSourceCustomize,
|
|
71596
|
+
/**
|
|
71597
|
+
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
71598
|
+
* its next report; a restored value would outlive the reading it names.
|
|
71599
|
+
*
|
|
71600
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
71601
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
71602
|
+
*/
|
|
71603
|
+
durability: "session",
|
|
71604
|
+
history: "none"
|
|
71605
|
+
};
|
|
70895
71606
|
/**
|
|
70896
71607
|
* Presence cap. Models HA `person.*` and `device_tracker.*` entities
|
|
70897
71608
|
* on `DeviceType.Presence`. Read-only — no setters: presence is
|
|
@@ -80387,6 +81098,25 @@ function buildDescriptor(capName, kind) {
|
|
|
80387
81098
|
* contributes one entry per bound cap present in this map.
|
|
80388
81099
|
*/
|
|
80389
81100
|
var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
|
|
81101
|
+
/**
|
|
81102
|
+
* What a remote key is CALLED and what it LOOKS like, for every role (D963),
|
|
81103
|
+
* shared by the admin UI and the viewer. A role is data on the wire; each
|
|
81104
|
+
* client resolves its name from `label` (English and Italian side by side) and
|
|
81105
|
+
* its glyph from `icon`, a lucide id from the shared icon catalogue — the
|
|
81106
|
+
* server sends an id, a client maps it to its own icon set.
|
|
81107
|
+
*
|
|
81108
|
+
* `legend` is text printed on the key INSTEAD of a glyph (a digit has no
|
|
81109
|
+
* pictogram): a client that can draw text shows it, one that cannot falls back
|
|
81110
|
+
* to `icon`.
|
|
81111
|
+
*/
|
|
81112
|
+
function digit(n) {
|
|
81113
|
+
return {
|
|
81114
|
+
label: t(n, n),
|
|
81115
|
+
icon: "hash",
|
|
81116
|
+
legend: n
|
|
81117
|
+
};
|
|
81118
|
+
}
|
|
81119
|
+
digit("0"), digit("1"), digit("2"), digit("3"), digit("4"), digit("5"), digit("6"), digit("7"), digit("8"), digit("9");
|
|
80390
81120
|
DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
|
|
80391
81121
|
var btn = (idSuffix, row, col, init) => ({
|
|
80392
81122
|
idSuffix,
|
|
@@ -80551,6 +81281,34 @@ function runningFromMediaPlayer(state, previous, fetchedAt, transitionAt) {
|
|
|
80551
81281
|
if (state === "paused") return nativeSlice(false, "paused", previous, transitionAtMs, fetchedAt, null);
|
|
80552
81282
|
return nativeSlice(false, state, previous, transitionAtMs, fetchedAt, null);
|
|
80553
81283
|
}
|
|
81284
|
+
/**
|
|
81285
|
+
* Propose a running threshold from a device's own power history (planning#405).
|
|
81286
|
+
*
|
|
81287
|
+
* Pure and deterministic: the same series gives the same answer. The series is
|
|
81288
|
+
* a HELD-VALUE step series — a point's value stands until the next point — which
|
|
81289
|
+
* is how device-history stores a gauge, so "time at a level" is exact, not a
|
|
81290
|
+
* count of samples.
|
|
81291
|
+
*
|
|
81292
|
+
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
81293
|
+
* near F. A device that is not near one level most of the time has no floor.
|
|
81294
|
+
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
81295
|
+
* 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
|
|
81296
|
+
* the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
|
|
81297
|
+
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
81298
|
+
* grid a person would type; stop dwell covers the longest pause; start dwell
|
|
81299
|
+
* covers the longest standby blip above T.
|
|
81300
|
+
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
81301
|
+
* run must hold exactly one true interval and nothing may flap outside.
|
|
81302
|
+
*/
|
|
81303
|
+
var SECOND_MS = 1e3;
|
|
81304
|
+
var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
81305
|
+
24 * (60 * MINUTE_MS$1);
|
|
81306
|
+
10 * MINUTE_MS$1;
|
|
81307
|
+
30 * SECOND_MS;
|
|
81308
|
+
60 * SECOND_MS;
|
|
81309
|
+
20 * MINUTE_MS$1;
|
|
81310
|
+
10 * SECOND_MS;
|
|
81311
|
+
10 * SECOND_MS;
|
|
80554
81312
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
80555
81313
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
80556
81314
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
@@ -83656,6 +84414,13 @@ Object.freeze({
|
|
|
83656
84414
|
access: "create",
|
|
83657
84415
|
role: "admin"
|
|
83658
84416
|
},
|
|
84417
|
+
"coreBlocks.proposeRunningThreshold": {
|
|
84418
|
+
capName: "core-blocks",
|
|
84419
|
+
capScope: "system",
|
|
84420
|
+
addonId: null,
|
|
84421
|
+
access: "view",
|
|
84422
|
+
role: "admin"
|
|
84423
|
+
},
|
|
83659
84424
|
"coreBlocks.reportAutomationRun": {
|
|
83660
84425
|
capName: "core-blocks",
|
|
83661
84426
|
capScope: "system",
|
|
@@ -84167,6 +84932,13 @@ Object.freeze({
|
|
|
84167
84932
|
access: "view",
|
|
84168
84933
|
role: "admin"
|
|
84169
84934
|
},
|
|
84935
|
+
"deviceExport.listInstances": {
|
|
84936
|
+
capName: "device-export",
|
|
84937
|
+
capScope: "system",
|
|
84938
|
+
addonId: null,
|
|
84939
|
+
access: "view",
|
|
84940
|
+
role: "admin"
|
|
84941
|
+
},
|
|
84170
84942
|
"deviceExport.listSupportedDeviceKinds": {
|
|
84171
84943
|
capName: "device-export",
|
|
84172
84944
|
capScope: "system",
|
|
@@ -93125,6 +93897,11 @@ Object.freeze({
|
|
|
93125
93897
|
form: "single",
|
|
93126
93898
|
optional: false
|
|
93127
93899
|
}],
|
|
93900
|
+
"coreBlocks.proposeRunningThreshold": [{
|
|
93901
|
+
name: "deviceId",
|
|
93902
|
+
form: "single",
|
|
93903
|
+
optional: false
|
|
93904
|
+
}],
|
|
93128
93905
|
"cover.close": [{
|
|
93129
93906
|
name: "deviceId",
|
|
93130
93907
|
form: "single",
|
|
@@ -95788,99 +96565,6 @@ Object.freeze({
|
|
|
95788
96565
|
"network-access": "ingress",
|
|
95789
96566
|
"smtp-provider": "email"
|
|
95790
96567
|
});
|
|
95791
|
-
/**
|
|
95792
|
-
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
95793
|
-
* `z.infer`, they do not restate the shape.
|
|
95794
|
-
*/
|
|
95795
|
-
var OverrideSourceSchema = object({
|
|
95796
|
-
addonId: string().min(1),
|
|
95797
|
-
stableId: string().min(1),
|
|
95798
|
-
cap: string().min(1),
|
|
95799
|
-
fieldPath: string().min(1)
|
|
95800
|
-
});
|
|
95801
|
-
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
95802
|
-
object({ kind: literal("identity") }),
|
|
95803
|
-
object({ kind: literal("invert") }),
|
|
95804
|
-
object({
|
|
95805
|
-
kind: literal("threshold"),
|
|
95806
|
-
comparison: _enum(["gte", "lte"]),
|
|
95807
|
-
threshold: number(),
|
|
95808
|
-
startDwellMs: number().nonnegative(),
|
|
95809
|
-
stopDwellMs: number().nonnegative()
|
|
95810
|
-
}),
|
|
95811
|
-
object({
|
|
95812
|
-
kind: literal("linear"),
|
|
95813
|
-
scale: number(),
|
|
95814
|
-
offset: number(),
|
|
95815
|
-
clampMin: number().optional(),
|
|
95816
|
-
clampMax: number().optional()
|
|
95817
|
-
}),
|
|
95818
|
-
object({
|
|
95819
|
-
kind: literal("enum-map"),
|
|
95820
|
-
mapping: record(string(), string().nullable())
|
|
95821
|
-
}),
|
|
95822
|
-
object({ kind: literal("number") })
|
|
95823
|
-
]);
|
|
95824
|
-
var ScalarOverrideDraftSchema = object({
|
|
95825
|
-
kind: literal("scalar"),
|
|
95826
|
-
cap: string().min(1),
|
|
95827
|
-
targetId: string().min(1),
|
|
95828
|
-
source: OverrideSourceSchema,
|
|
95829
|
-
transform: CustomizeTransformDraftSchema
|
|
95830
|
-
});
|
|
95831
|
-
var KeyedArrayOverrideDraftSchema = object({
|
|
95832
|
-
kind: literal("keyedArray"),
|
|
95833
|
-
cap: string().min(1),
|
|
95834
|
-
targetId: string().min(1),
|
|
95835
|
-
key: string().min(1),
|
|
95836
|
-
label: string().min(1),
|
|
95837
|
-
fieldPath: string().min(1),
|
|
95838
|
-
source: OverrideSourceSchema,
|
|
95839
|
-
transform: CustomizeTransformDraftSchema
|
|
95840
|
-
});
|
|
95841
|
-
var PulseOverrideDraftSchema = object({
|
|
95842
|
-
kind: literal("pulse"),
|
|
95843
|
-
cap: string().min(1),
|
|
95844
|
-
targetId: string().min(1),
|
|
95845
|
-
enabled: boolean(),
|
|
95846
|
-
debounceMs: number().nonnegative(),
|
|
95847
|
-
edges: array(_enum([
|
|
95848
|
-
"rising",
|
|
95849
|
-
"button",
|
|
95850
|
-
"native-event"
|
|
95851
|
-
])).min(1).optional(),
|
|
95852
|
-
sources: array(OverrideSourceSchema).min(1)
|
|
95853
|
-
});
|
|
95854
|
-
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
95855
|
-
"in-addition",
|
|
95856
|
-
"confirm",
|
|
95857
|
-
"replace"
|
|
95858
|
-
]).optional() });
|
|
95859
|
-
discriminatedUnion("kind", [
|
|
95860
|
-
ScalarOverrideDraftSchema,
|
|
95861
|
-
KeyedArrayOverrideDraftSchema,
|
|
95862
|
-
PulseOverrideDraftSchema,
|
|
95863
|
-
object({
|
|
95864
|
-
kind: literal("span"),
|
|
95865
|
-
cap: string().min(1),
|
|
95866
|
-
targetId: string().min(1),
|
|
95867
|
-
timeoutMs: number().nonnegative(),
|
|
95868
|
-
cooldownMs: number().nonnegative().optional(),
|
|
95869
|
-
sources: array(SpanSourceDraftSchema)
|
|
95870
|
-
})
|
|
95871
|
-
]);
|
|
95872
|
-
object({
|
|
95873
|
-
targetId: string().min(1),
|
|
95874
|
-
configured: boolean(),
|
|
95875
|
-
source: _enum(["native", "assigned"]),
|
|
95876
|
-
state: _enum([
|
|
95877
|
-
"active",
|
|
95878
|
-
"stale",
|
|
95879
|
-
"released",
|
|
95880
|
-
"unavailable"
|
|
95881
|
-
]),
|
|
95882
|
-
reason: string().nullable()
|
|
95883
|
-
});
|
|
95884
96568
|
var G711_SCALE_CORRECTION_DB = {
|
|
95885
96569
|
PCMU: 20 * Math.log10(4),
|
|
95886
96570
|
PCMA: 20 * Math.log10(8)
|
|
@@ -97711,21 +98395,6 @@ function haInstanceSettingsPortsOver(api) {
|
|
|
97711
98395
|
}
|
|
97712
98396
|
};
|
|
97713
98397
|
}
|
|
97714
|
-
/** The brokerId → settings view of the instances: what a reader keeps per broker. */
|
|
97715
|
-
function settingsByBroker(rows) {
|
|
97716
|
-
const out = /* @__PURE__ */ new Map();
|
|
97717
|
-
for (const row of rows) if (row.brokerId !== null) out.set(row.brokerId, row.settings);
|
|
97718
|
-
return out;
|
|
97719
|
-
}
|
|
97720
|
-
/** Read the instances, or report why not and answer nothing (a reader falls back to the legacy key). */
|
|
97721
|
-
async function listInstancesOrNothing(ports, onError) {
|
|
97722
|
-
try {
|
|
97723
|
-
return await ports.listInstances();
|
|
97724
|
-
} catch (err) {
|
|
97725
|
-
onError(errMsg(err));
|
|
97726
|
-
return [];
|
|
97727
|
-
}
|
|
97728
|
-
}
|
|
97729
98398
|
//#endregion
|
|
97730
98399
|
Object.defineProperty(exports, "AlarmArmModeSchema", {
|
|
97731
98400
|
enumerable: true,
|
|
@@ -97817,6 +98486,12 @@ Object.defineProperty(exports, "EventCategory", {
|
|
|
97817
98486
|
return EventCategory;
|
|
97818
98487
|
}
|
|
97819
98488
|
});
|
|
98489
|
+
Object.defineProperty(exports, "ExportInstanceUnknownError", {
|
|
98490
|
+
enumerable: true,
|
|
98491
|
+
get: function() {
|
|
98492
|
+
return ExportInstanceUnknownError;
|
|
98493
|
+
}
|
|
98494
|
+
});
|
|
97820
98495
|
Object.defineProperty(exports, "FanDirectionSchema", {
|
|
97821
98496
|
enumerable: true,
|
|
97822
98497
|
get: function() {
|
|
@@ -97853,18 +98528,36 @@ Object.defineProperty(exports, "PLUG_APPLIANCE_TYPES", {
|
|
|
97853
98528
|
return PLUG_APPLIANCE_TYPES;
|
|
97854
98529
|
}
|
|
97855
98530
|
});
|
|
98531
|
+
Object.defineProperty(exports, "REDACTED_SECRET", {
|
|
98532
|
+
enumerable: true,
|
|
98533
|
+
get: function() {
|
|
98534
|
+
return REDACTED_SECRET;
|
|
98535
|
+
}
|
|
98536
|
+
});
|
|
97856
98537
|
Object.defineProperty(exports, "RUNNING_STATE_COLD", {
|
|
97857
98538
|
enumerable: true,
|
|
97858
98539
|
get: function() {
|
|
97859
98540
|
return RUNNING_STATE_COLD;
|
|
97860
98541
|
}
|
|
97861
98542
|
});
|
|
98543
|
+
Object.defineProperty(exports, "RecordIntegrationInstances", {
|
|
98544
|
+
enumerable: true,
|
|
98545
|
+
get: function() {
|
|
98546
|
+
return RecordIntegrationInstances;
|
|
98547
|
+
}
|
|
98548
|
+
});
|
|
97862
98549
|
Object.defineProperty(exports, "RecordSecretUnavailableError", {
|
|
97863
98550
|
enumerable: true,
|
|
97864
98551
|
get: function() {
|
|
97865
98552
|
return RecordSecretUnavailableError;
|
|
97866
98553
|
}
|
|
97867
98554
|
});
|
|
98555
|
+
Object.defineProperty(exports, "RevConflictError", {
|
|
98556
|
+
enumerable: true,
|
|
98557
|
+
get: function() {
|
|
98558
|
+
return RevConflictError;
|
|
98559
|
+
}
|
|
98560
|
+
});
|
|
97868
98561
|
Object.defineProperty(exports, "TargetSchema", {
|
|
97869
98562
|
enumerable: true,
|
|
97870
98563
|
get: function() {
|
|
@@ -98123,6 +98816,18 @@ Object.defineProperty(exports, "fanControlCapability", {
|
|
|
98123
98816
|
return fanControlCapability;
|
|
98124
98817
|
}
|
|
98125
98818
|
});
|
|
98819
|
+
Object.defineProperty(exports, "flattenNestedRecords", {
|
|
98820
|
+
enumerable: true,
|
|
98821
|
+
get: function() {
|
|
98822
|
+
return flattenNestedRecords;
|
|
98823
|
+
}
|
|
98824
|
+
});
|
|
98825
|
+
Object.defineProperty(exports, "flattenPortsOver", {
|
|
98826
|
+
enumerable: true,
|
|
98827
|
+
get: function() {
|
|
98828
|
+
return flattenPortsOver;
|
|
98829
|
+
}
|
|
98830
|
+
});
|
|
98126
98831
|
Object.defineProperty(exports, "floodCapability", {
|
|
98127
98832
|
enumerable: true,
|
|
98128
98833
|
get: function() {
|
|
@@ -98165,6 +98870,12 @@ Object.defineProperty(exports, "ingestReading", {
|
|
|
98165
98870
|
return ingestReading;
|
|
98166
98871
|
}
|
|
98167
98872
|
});
|
|
98873
|
+
Object.defineProperty(exports, "integrationInstancesCapability", {
|
|
98874
|
+
enumerable: true,
|
|
98875
|
+
get: function() {
|
|
98876
|
+
return integrationInstancesCapability;
|
|
98877
|
+
}
|
|
98878
|
+
});
|
|
98168
98879
|
Object.defineProperty(exports, "isReadingQuantity", {
|
|
98169
98880
|
enumerable: true,
|
|
98170
98881
|
get: function() {
|
|
@@ -98183,12 +98894,6 @@ Object.defineProperty(exports, "listHomeAssistantBrokers", {
|
|
|
98183
98894
|
return listHomeAssistantBrokers;
|
|
98184
98895
|
}
|
|
98185
98896
|
});
|
|
98186
|
-
Object.defineProperty(exports, "listInstancesOrNothing", {
|
|
98187
|
-
enumerable: true,
|
|
98188
|
-
get: function() {
|
|
98189
|
-
return listInstancesOrNothing;
|
|
98190
|
-
}
|
|
98191
|
-
});
|
|
98192
98897
|
Object.defineProperty(exports, "literal", {
|
|
98193
98898
|
enumerable: true,
|
|
98194
98899
|
get: function() {
|
|
@@ -98369,16 +99074,16 @@ Object.defineProperty(exports, "sendToExportedTarget", {
|
|
|
98369
99074
|
return sendToExportedTarget;
|
|
98370
99075
|
}
|
|
98371
99076
|
});
|
|
98372
|
-
Object.defineProperty(exports, "
|
|
99077
|
+
Object.defineProperty(exports, "smokeCapability", {
|
|
98373
99078
|
enumerable: true,
|
|
98374
99079
|
get: function() {
|
|
98375
|
-
return
|
|
99080
|
+
return smokeCapability;
|
|
98376
99081
|
}
|
|
98377
99082
|
});
|
|
98378
|
-
Object.defineProperty(exports, "
|
|
99083
|
+
Object.defineProperty(exports, "statusInstanceOf", {
|
|
98379
99084
|
enumerable: true,
|
|
98380
99085
|
get: function() {
|
|
98381
|
-
return
|
|
99086
|
+
return statusInstanceOf;
|
|
98382
99087
|
}
|
|
98383
99088
|
});
|
|
98384
99089
|
Object.defineProperty(exports, "string", {
|