@camstack/addon-pipeline-orchestrator 1.2.640 → 1.2.641
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 +7 -0
- package/dist/_stub.js +354 -310
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-DgqFPt2m.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-DwQA2kWO.mjs} +2 -2
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D6nTaHNp.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DNaztdEQ.mjs +26 -0
- package/dist/{hostInit-DD8GvZ4U.mjs → hostInit-BFSvtn_R.mjs} +2 -2
- package/dist/index.js +1256 -124
- package/dist/index.mjs +1256 -124
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D0_XCtEI.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DuREsGpg.mjs +0 -26
package/dist/index.js
CHANGED
|
@@ -5619,7 +5619,7 @@ var ZodIssueCode = {
|
|
|
5619
5619
|
var ZodFirstPartyTypeKind;
|
|
5620
5620
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5621
5621
|
//#endregion
|
|
5622
|
-
//#region ../types/dist/sleep-
|
|
5622
|
+
//#region ../types/dist/sleep-CeyhrcOk.mjs
|
|
5623
5623
|
/**
|
|
5624
5624
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5625
5625
|
* window to float samples (D455).
|
|
@@ -7702,6 +7702,7 @@ var BaseAddon = class {
|
|
|
7702
7702
|
* a warning and falls back rather than crashing boot.
|
|
7703
7703
|
*/
|
|
7704
7704
|
state(key, schema, fallback) {
|
|
7705
|
+
this._declaredStateKeys.add(key);
|
|
7705
7706
|
return createDurableState({
|
|
7706
7707
|
key,
|
|
7707
7708
|
schema,
|
|
@@ -7717,6 +7718,96 @@ var BaseAddon = class {
|
|
|
7717
7718
|
} })
|
|
7718
7719
|
});
|
|
7719
7720
|
}
|
|
7721
|
+
/** Durable-state keys this process registered through {@link state}. */
|
|
7722
|
+
_declaredStateKeys = /* @__PURE__ */ new Set();
|
|
7723
|
+
/**
|
|
7724
|
+
* Stale-data audit: every global-settings key this addon still declares —
|
|
7725
|
+
* constructor defaults, settings-schema fields (layout containers included),
|
|
7726
|
+
* durable-state keys and the root of each `secretStatePaths` entry. Answered
|
|
7727
|
+
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
7728
|
+
*/
|
|
7729
|
+
async getDeclaredSettingsKeys() {
|
|
7730
|
+
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
7731
|
+
}
|
|
7732
|
+
declaredKeySet() {
|
|
7733
|
+
const keys = new Set(Object.keys(this.defaults));
|
|
7734
|
+
const schema = this.globalSettingsSchema();
|
|
7735
|
+
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
7736
|
+
for (const key of this._declaredStateKeys) keys.add(key);
|
|
7737
|
+
for (const path of this.secretStatePaths) {
|
|
7738
|
+
const root = path.split(/[.[]/, 1)[0];
|
|
7739
|
+
if (root) keys.add(root);
|
|
7740
|
+
}
|
|
7741
|
+
return keys;
|
|
7742
|
+
}
|
|
7743
|
+
/**
|
|
7744
|
+
* Stale-data audit: remove stored global-settings keys the addon no longer
|
|
7745
|
+
* declares, and clear the keychain values their markers held — except a value
|
|
7746
|
+
* a surviving key still points at. A declared or absent key is refused.
|
|
7747
|
+
*/
|
|
7748
|
+
async unsetStoredSettingsKeys(keys) {
|
|
7749
|
+
const settings = this._ctx?.settings;
|
|
7750
|
+
const raw = this.rawSettings();
|
|
7751
|
+
if (!settings || !raw) return {
|
|
7752
|
+
removed: [],
|
|
7753
|
+
refused: keys.map((key) => ({
|
|
7754
|
+
key,
|
|
7755
|
+
reason: "no settings store"
|
|
7756
|
+
})),
|
|
7757
|
+
secretsCleared: 0
|
|
7758
|
+
};
|
|
7759
|
+
let stored;
|
|
7760
|
+
try {
|
|
7761
|
+
stored = await raw.readAddonStore();
|
|
7762
|
+
} catch (error) {
|
|
7763
|
+
const reason = `store unreadable: ${error instanceof Error ? error.message : "unknown"}`;
|
|
7764
|
+
return {
|
|
7765
|
+
removed: [],
|
|
7766
|
+
refused: keys.map((key) => ({
|
|
7767
|
+
key,
|
|
7768
|
+
reason
|
|
7769
|
+
})),
|
|
7770
|
+
secretsCleared: 0
|
|
7771
|
+
};
|
|
7772
|
+
}
|
|
7773
|
+
const declared = this.declaredKeySet();
|
|
7774
|
+
const removed = [];
|
|
7775
|
+
const refused = [];
|
|
7776
|
+
for (const key of keys) {
|
|
7777
|
+
const base = key.split("@", 1)[0] ?? key;
|
|
7778
|
+
if (declared.has(base)) refused.push({
|
|
7779
|
+
key,
|
|
7780
|
+
reason: "still declared by the addon"
|
|
7781
|
+
});
|
|
7782
|
+
else if (!(key in stored)) refused.push({
|
|
7783
|
+
key,
|
|
7784
|
+
reason: "not stored"
|
|
7785
|
+
});
|
|
7786
|
+
else removed.push(key);
|
|
7787
|
+
}
|
|
7788
|
+
if (removed.length === 0) return {
|
|
7789
|
+
removed,
|
|
7790
|
+
refused,
|
|
7791
|
+
secretsCleared: 0
|
|
7792
|
+
};
|
|
7793
|
+
const kept = /* @__PURE__ */ new Set();
|
|
7794
|
+
const held = /* @__PURE__ */ new Set();
|
|
7795
|
+
for (const [key, value] of Object.entries(stored)) {
|
|
7796
|
+
const target = removed.includes(key) ? held : kept;
|
|
7797
|
+
for (const ref of collectSecretMarkerRefs(value)) target.add(ref);
|
|
7798
|
+
}
|
|
7799
|
+
const refsToClear = [...held].filter((ref) => !kept.has(ref));
|
|
7800
|
+
const patch = {};
|
|
7801
|
+
for (const key of removed) patch[key] = void 0;
|
|
7802
|
+
await settings.writeAddonStore(patch);
|
|
7803
|
+
const secretsCleared = refsToClear.length > 0 && this._ctx?.secrets ? await this._ctx.secrets.delete(refsToClear) : 0;
|
|
7804
|
+
await this.resolveConfig();
|
|
7805
|
+
return {
|
|
7806
|
+
removed,
|
|
7807
|
+
refused,
|
|
7808
|
+
secretsCleared
|
|
7809
|
+
};
|
|
7810
|
+
}
|
|
7720
7811
|
/** Per-device variant of {@link state}, backed by the per-device store. */
|
|
7721
7812
|
deviceState(deviceId, key, schema, fallback) {
|
|
7722
7813
|
return createDurableState({
|
|
@@ -7987,6 +8078,21 @@ function dropPerNodeFields(fields) {
|
|
|
7987
8078
|
}
|
|
7988
8079
|
return kept;
|
|
7989
8080
|
}
|
|
8081
|
+
function collectFieldKeys(fields) {
|
|
8082
|
+
const collected = [];
|
|
8083
|
+
for (const field of fields) {
|
|
8084
|
+
if (field.type === "group") {
|
|
8085
|
+
collected.push(...collectFieldKeys(field.fields));
|
|
8086
|
+
continue;
|
|
8087
|
+
}
|
|
8088
|
+
if (field.type === "sub-tabs") {
|
|
8089
|
+
for (const tab of field.tabs) collected.push(...collectFieldKeys(tab.fields));
|
|
8090
|
+
continue;
|
|
8091
|
+
}
|
|
8092
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8093
|
+
}
|
|
8094
|
+
return collected;
|
|
8095
|
+
}
|
|
7990
8096
|
function collectPerNodeFieldKeys(fields) {
|
|
7991
8097
|
const collected = [];
|
|
7992
8098
|
for (const field of fields) {
|
|
@@ -9667,6 +9773,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9667
9773
|
"recording-signal",
|
|
9668
9774
|
"records",
|
|
9669
9775
|
"remote",
|
|
9776
|
+
"remote-layout-components",
|
|
9670
9777
|
"running-state",
|
|
9671
9778
|
"scene-monitor",
|
|
9672
9779
|
"script-runner",
|
|
@@ -11536,7 +11643,7 @@ function sleep(ms) {
|
|
|
11536
11643
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11537
11644
|
}
|
|
11538
11645
|
//#endregion
|
|
11539
|
-
//#region ../types/dist/err-msg-
|
|
11646
|
+
//#region ../types/dist/err-msg-BD5buxvT.mjs
|
|
11540
11647
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
11541
11648
|
DeviceType["Camera"] = "camera";
|
|
11542
11649
|
DeviceType["Hub"] = "hub";
|
|
@@ -11642,6 +11749,12 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
11642
11749
|
DeviceType["Washer"] = "washer";
|
|
11643
11750
|
/** Tumble dryer. Same plug-built running surface as {@link Washer}. */
|
|
11644
11751
|
DeviceType["Dryer"] = "dryer";
|
|
11752
|
+
/** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
|
|
11753
|
+
* other radio-to-IP bridges). It carries the box's own facts (firmware,
|
|
11754
|
+
* address, connection state) and the actions the box really supports
|
|
11755
|
+
* (`reboot`, `device-admin-link`). Products are NOT its children — they are
|
|
11756
|
+
* adopted beside it — and its connection settings stay on the integration. */
|
|
11757
|
+
DeviceType["Gateway"] = "gateway";
|
|
11645
11758
|
return DeviceType;
|
|
11646
11759
|
}({});
|
|
11647
11760
|
var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
@@ -11902,7 +12015,7 @@ function errMsg$2(err) {
|
|
|
11902
12015
|
return String(err);
|
|
11903
12016
|
}
|
|
11904
12017
|
//#endregion
|
|
11905
|
-
//#region ../types/dist/composition-
|
|
12018
|
+
//#region ../types/dist/composition-C7WdThmP.mjs
|
|
11906
12019
|
/**
|
|
11907
12020
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11908
12021
|
*
|
|
@@ -17371,8 +17484,172 @@ object({
|
|
|
17371
17484
|
quantity: ReadingQuantitySchema,
|
|
17372
17485
|
unit: string().min(1)
|
|
17373
17486
|
});
|
|
17374
|
-
|
|
17375
|
-
|
|
17487
|
+
new Set([
|
|
17488
|
+
{
|
|
17489
|
+
id: "person",
|
|
17490
|
+
name: "Person"
|
|
17491
|
+
},
|
|
17492
|
+
{
|
|
17493
|
+
id: "vehicle",
|
|
17494
|
+
name: "Vehicle"
|
|
17495
|
+
},
|
|
17496
|
+
{
|
|
17497
|
+
id: "animal",
|
|
17498
|
+
name: "Animal"
|
|
17499
|
+
},
|
|
17500
|
+
{
|
|
17501
|
+
id: "package",
|
|
17502
|
+
name: "Package"
|
|
17503
|
+
}
|
|
17504
|
+
].map((l) => l.id));
|
|
17505
|
+
var COCO_TO_MACRO = {
|
|
17506
|
+
mapping: {
|
|
17507
|
+
person: "person",
|
|
17508
|
+
bicycle: "vehicle",
|
|
17509
|
+
car: "vehicle",
|
|
17510
|
+
motorcycle: "vehicle",
|
|
17511
|
+
airplane: "vehicle",
|
|
17512
|
+
bus: "vehicle",
|
|
17513
|
+
train: "vehicle",
|
|
17514
|
+
truck: "vehicle",
|
|
17515
|
+
boat: "vehicle",
|
|
17516
|
+
bird: "animal",
|
|
17517
|
+
cat: "animal",
|
|
17518
|
+
dog: "animal",
|
|
17519
|
+
horse: "animal",
|
|
17520
|
+
sheep: "animal",
|
|
17521
|
+
cow: "animal",
|
|
17522
|
+
elephant: "animal",
|
|
17523
|
+
bear: "animal",
|
|
17524
|
+
zebra: "animal",
|
|
17525
|
+
giraffe: "animal",
|
|
17526
|
+
suitcase: "package",
|
|
17527
|
+
backpack: "package",
|
|
17528
|
+
handbag: "package"
|
|
17529
|
+
},
|
|
17530
|
+
preserveOriginal: false
|
|
17531
|
+
};
|
|
17532
|
+
/**
|
|
17533
|
+
* WHAT a detection-shaped leaf is about: one MACRO class (person, vehicle,
|
|
17534
|
+
* animal, package) plus an OPTIONAL label that narrows it.
|
|
17535
|
+
*
|
|
17536
|
+
* The taxonomy is `COCO_TO_MACRO` (and `CLASS_MAP_MACRO_TARGETS`) — never a
|
|
17537
|
+
* second list. The label that fits depends on the macro class:
|
|
17538
|
+
*
|
|
17539
|
+
* - `animal` → a sub-class (`dog`, `cat`, …);
|
|
17540
|
+
* - `person` → a known face (an identity of the `face-gallery`);
|
|
17541
|
+
* - `vehicle` → a plate (its text) or a sub-class (`car`, `truck`, …);
|
|
17542
|
+
* - `package` → a sub-class.
|
|
17543
|
+
*
|
|
17544
|
+
* `other` exists only so a class the taxonomy does not know (a custom model's
|
|
17545
|
+
* `forklift`) survives the conversion of a stored recipe without widening.
|
|
17546
|
+
*
|
|
17547
|
+
* A label is decided LATE (D683): face recognition and LPR land after the
|
|
17548
|
+
* detection starts. The track-lifecycle `update` event carries the newly
|
|
17549
|
+
* resolved `label` / `plateText`, which is why a leaf with a label fires when
|
|
17550
|
+
* the subject GETS it — not at the first detection.
|
|
17551
|
+
*/
|
|
17552
|
+
var SubClassLabelSchema = object({
|
|
17553
|
+
kind: literal("sub-class"),
|
|
17554
|
+
className: string().min(1)
|
|
17555
|
+
});
|
|
17556
|
+
var IdentityLabelSchema = object({
|
|
17557
|
+
kind: literal("identity"),
|
|
17558
|
+
/** The `face-gallery` identity id (stable across a rename). */
|
|
17559
|
+
identityId: string().min(1),
|
|
17560
|
+
/** The identity's name — what a track-lifecycle event carries as `label`. */
|
|
17561
|
+
name: string().min(1)
|
|
17562
|
+
});
|
|
17563
|
+
var PlateLabelSchema = object({
|
|
17564
|
+
kind: literal("plate"),
|
|
17565
|
+
text: string().min(1)
|
|
17566
|
+
});
|
|
17567
|
+
function macroOf$1(className) {
|
|
17568
|
+
return COCO_TO_MACRO.mapping[className.toLowerCase()] ?? className.toLowerCase();
|
|
17569
|
+
}
|
|
17570
|
+
/** A sub-class label must belong to the macro it is under. */
|
|
17571
|
+
function subClassBelongs(macro, label) {
|
|
17572
|
+
return label === void 0 || macroOf$1(label.className) === macro;
|
|
17573
|
+
}
|
|
17574
|
+
var subClassOf = (macro) => SubClassLabelSchema.refine((l) => subClassBelongs(macro, l), { message: `that class is not a kind of ${macro} in the detection taxonomy` });
|
|
17575
|
+
var DetectionSubjectSchema = discriminatedUnion("macro", [
|
|
17576
|
+
object({
|
|
17577
|
+
macro: literal("animal"),
|
|
17578
|
+
label: subClassOf("animal").optional()
|
|
17579
|
+
}),
|
|
17580
|
+
object({
|
|
17581
|
+
macro: literal("person"),
|
|
17582
|
+
label: IdentityLabelSchema.optional()
|
|
17583
|
+
}),
|
|
17584
|
+
object({
|
|
17585
|
+
macro: literal("vehicle"),
|
|
17586
|
+
label: discriminatedUnion("kind", [SubClassLabelSchema.refine((l) => subClassBelongs("vehicle", l), { message: "that class is not a kind of vehicle in the detection taxonomy" }), PlateLabelSchema]).optional()
|
|
17587
|
+
}),
|
|
17588
|
+
object({
|
|
17589
|
+
macro: literal("package"),
|
|
17590
|
+
label: subClassOf("package").optional()
|
|
17591
|
+
}),
|
|
17592
|
+
object({
|
|
17593
|
+
macro: literal("other"),
|
|
17594
|
+
className: string().min(1)
|
|
17595
|
+
})
|
|
17596
|
+
]);
|
|
17597
|
+
/**
|
|
17598
|
+
* The subject of a PARKED object. The stationary registry's event carries a
|
|
17599
|
+
* class and a zone, no face and no plate — an identity or plate label there
|
|
17600
|
+
* could never match, so the schema refuses it instead of letting it sit dead.
|
|
17601
|
+
*/
|
|
17602
|
+
var StationarySubjectSchema = DetectionSubjectSchema.refine((s) => s.macro === "other" || s.label === void 0 || s.label.kind === "sub-class", { message: "a parked object carries no face or plate — only a class can narrow it" });
|
|
17603
|
+
/**
|
|
17604
|
+
* A stored class string as a subject: a macro name stays a macro; a raw class
|
|
17605
|
+
* the taxonomy files under a macro becomes that macro plus the sub-class label
|
|
17606
|
+
* (`dog` → animal / dog); anything else is `other`, kept verbatim.
|
|
17607
|
+
*/
|
|
17608
|
+
function subjectFromClassName(className) {
|
|
17609
|
+
const name = className.trim().toLowerCase();
|
|
17610
|
+
const macro = COCO_TO_MACRO.mapping[name];
|
|
17611
|
+
if (new Set(Object.values(COCO_TO_MACRO.mapping)).has(name)) return macroOnly(name);
|
|
17612
|
+
if (macro === void 0) return {
|
|
17613
|
+
macro: "other",
|
|
17614
|
+
className
|
|
17615
|
+
};
|
|
17616
|
+
const label = {
|
|
17617
|
+
kind: "sub-class",
|
|
17618
|
+
className: name
|
|
17619
|
+
};
|
|
17620
|
+
switch (macro) {
|
|
17621
|
+
case "animal": return {
|
|
17622
|
+
macro: "animal",
|
|
17623
|
+
label
|
|
17624
|
+
};
|
|
17625
|
+
case "vehicle": return {
|
|
17626
|
+
macro: "vehicle",
|
|
17627
|
+
label
|
|
17628
|
+
};
|
|
17629
|
+
case "package": return {
|
|
17630
|
+
macro: "package",
|
|
17631
|
+
label
|
|
17632
|
+
};
|
|
17633
|
+
default: return {
|
|
17634
|
+
macro: "other",
|
|
17635
|
+
className
|
|
17636
|
+
};
|
|
17637
|
+
}
|
|
17638
|
+
}
|
|
17639
|
+
function macroOnly(macro) {
|
|
17640
|
+
switch (macro) {
|
|
17641
|
+
case "animal": return { macro: "animal" };
|
|
17642
|
+
case "person": return { macro: "person" };
|
|
17643
|
+
case "vehicle": return { macro: "vehicle" };
|
|
17644
|
+
case "package": return { macro: "package" };
|
|
17645
|
+
default: return {
|
|
17646
|
+
macro: "other",
|
|
17647
|
+
className: macro
|
|
17648
|
+
};
|
|
17649
|
+
}
|
|
17650
|
+
}
|
|
17651
|
+
/** How a state leaf compares a device field to a value. Derived from the
|
|
17652
|
+
* field's `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
|
|
17376
17653
|
var AutomationConditionOperatorSchema = _enum([
|
|
17377
17654
|
"eq",
|
|
17378
17655
|
"ne",
|
|
@@ -17383,12 +17660,76 @@ var AutomationConditionOperatorSchema = _enum([
|
|
|
17383
17660
|
"contains",
|
|
17384
17661
|
"in"
|
|
17385
17662
|
]);
|
|
17386
|
-
|
|
17387
|
-
|
|
17663
|
+
/**
|
|
17664
|
+
* D8 compliance, and it is the reason a "becomes" leaf is not merely an event
|
|
17665
|
+
* subscription: it evaluates against the **state mirror**, which is reconciled,
|
|
17666
|
+
* and an event only WAKES the evaluation. A dropped event therefore DELAYS a
|
|
17667
|
+
* fire; it does not lose it.
|
|
17668
|
+
*/
|
|
17669
|
+
var DeviceBecomesLeafSchema = object({
|
|
17670
|
+
kind: literal("device-becomes"),
|
|
17671
|
+
deviceId: number().int().nonnegative(),
|
|
17672
|
+
cap: string().min(1),
|
|
17673
|
+
fieldPath: string().min(1),
|
|
17674
|
+
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
17675
|
+
becomes: union([
|
|
17676
|
+
string(),
|
|
17677
|
+
number(),
|
|
17678
|
+
boolean()
|
|
17679
|
+
]).optional(),
|
|
17680
|
+
/** Only on a CHANGE of value, not on every re-report. */
|
|
17681
|
+
edge: boolean().optional(),
|
|
17682
|
+
/** The new value must hold this long before it counts. */
|
|
17683
|
+
forMs: number().int().min(0).max(864e5).optional(),
|
|
17684
|
+
/** Collapse a burst into one occurrence. */
|
|
17685
|
+
debounceMs: number().int().min(0).max(6e5).optional()
|
|
17686
|
+
});
|
|
17687
|
+
var DeviceEventLeafSchema = object({
|
|
17688
|
+
kind: literal("device-event"),
|
|
17689
|
+
/** An `EventCategory` value. */
|
|
17690
|
+
category: string().min(1),
|
|
17691
|
+
deviceId: number().int().nonnegative().optional()
|
|
17692
|
+
});
|
|
17693
|
+
var ScheduleLeafSchema = object({
|
|
17694
|
+
kind: literal("schedule"),
|
|
17695
|
+
cron: string().min(1).max(120)
|
|
17696
|
+
});
|
|
17697
|
+
var ManualLeafSchema = object({ kind: literal("manual") });
|
|
17698
|
+
var IntervalLeafSchema = object({
|
|
17699
|
+
kind: literal("interval"),
|
|
17700
|
+
minutes: number().int().min(1).max(1440)
|
|
17701
|
+
});
|
|
17702
|
+
var DetectionLeafSchema = object({
|
|
17703
|
+
kind: literal("detection"),
|
|
17704
|
+
/** One macro class each, optionally narrowed by a label; a track of any listed subject fires. */
|
|
17705
|
+
subjects: array(DetectionSubjectSchema).min(1),
|
|
17706
|
+
deviceIds: array(number().int().nonnegative()).min(1),
|
|
17707
|
+
/** The track must have visited at least one of these zones. */
|
|
17708
|
+
zones: array(string().min(1)).min(1).optional(),
|
|
17709
|
+
/** Best-so-far confidence the track must reach. */
|
|
17710
|
+
minScore: number().min(0).max(1).optional()
|
|
17711
|
+
});
|
|
17712
|
+
var StationaryLeafSchema = object({
|
|
17713
|
+
kind: literal("stationary"),
|
|
17714
|
+
deviceIds: array(number().int().nonnegative()).min(1),
|
|
17715
|
+
subjects: array(StationarySubjectSchema).min(1).optional(),
|
|
17716
|
+
zones: array(string().min(1)).min(1).optional(),
|
|
17717
|
+
/** `appeared`: it became parked. `departed`: the registry retired it. */
|
|
17718
|
+
phase: _enum(["appeared", "departed"])
|
|
17719
|
+
});
|
|
17720
|
+
/** "is": one device field compared to one value, NOW. */
|
|
17721
|
+
var DeviceIsLeafSchema = object({
|
|
17722
|
+
kind: literal("device-is"),
|
|
17388
17723
|
deviceId: number().int().nonnegative(),
|
|
17389
17724
|
cap: string().min(1),
|
|
17390
17725
|
fieldPath: string().min(1),
|
|
17391
17726
|
operator: AutomationConditionOperatorSchema,
|
|
17727
|
+
/**
|
|
17728
|
+
* The value. A number on a quantity field is stored in the field's base unit
|
|
17729
|
+
* (D944); a quantity value is the WRITE form, which the save boundary
|
|
17730
|
+
* replaces with the converted number and `unit`. A leaf that still carries
|
|
17731
|
+
* one is never evaluated (it fails).
|
|
17732
|
+
*/
|
|
17392
17733
|
value: union([
|
|
17393
17734
|
string(),
|
|
17394
17735
|
number(),
|
|
@@ -17396,24 +17737,26 @@ var AutomationConditionLeafSchema = object({
|
|
|
17396
17737
|
array(union([string(), number()])),
|
|
17397
17738
|
QuantityValueSchema
|
|
17398
17739
|
]),
|
|
17740
|
+
/** The unit the person picked for `value` (display only; `value` is in the base unit). */
|
|
17399
17741
|
unit: string().min(1).optional()
|
|
17400
17742
|
});
|
|
17401
|
-
|
|
17743
|
+
/**
|
|
17744
|
+
* True when the camera currently has a PARKED object matching `subjects` in at
|
|
17745
|
+
* least one of `zones`. Reads the zone-analytics snapshot through the same
|
|
17746
|
+
* mirror as every other state leaf, so a dropped event never decides it (D49).
|
|
17747
|
+
*/
|
|
17748
|
+
var StationaryPresentLeafSchema = object({
|
|
17402
17749
|
kind: literal("stationary-present"),
|
|
17403
17750
|
deviceId: number().int().nonnegative(),
|
|
17404
|
-
|
|
17405
|
-
classes: array(string().min(1)).min(1).optional(),
|
|
17406
|
-
/** The parked object must be in at least one of these zones. */
|
|
17751
|
+
subjects: array(StationarySubjectSchema).min(1).optional(),
|
|
17407
17752
|
zones: array(string().min(1)).min(1).optional()
|
|
17408
17753
|
});
|
|
17409
17754
|
/**
|
|
17410
|
-
* The
|
|
17411
|
-
*
|
|
17412
|
-
*
|
|
17413
|
-
* validation is the SAME function `ExpressionSourceSchema` runs, so the two
|
|
17414
|
-
* cannot drift — an expression that one accepts, the other accepts.
|
|
17755
|
+
* The escape hatch: the bounded expression engine over named device bindings.
|
|
17756
|
+
* Declared as a plain object so a discriminated union can read `kind`; the
|
|
17757
|
+
* author-time validation is the SAME function `ExpressionSourceSchema` runs.
|
|
17415
17758
|
*/
|
|
17416
|
-
var
|
|
17759
|
+
var ExpressionLeafSchema = object({
|
|
17417
17760
|
kind: literal("expression"),
|
|
17418
17761
|
expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
17419
17762
|
bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
@@ -17425,55 +17768,62 @@ var AutomationConditionExpressionSchema = object({
|
|
|
17425
17768
|
path: ["expr"]
|
|
17426
17769
|
});
|
|
17427
17770
|
});
|
|
17428
|
-
var
|
|
17771
|
+
var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
|
|
17429
17772
|
object({
|
|
17430
17773
|
kind: literal("all"),
|
|
17431
|
-
children: array(
|
|
17774
|
+
children: array(AutomationWhenSchema)
|
|
17432
17775
|
}),
|
|
17433
17776
|
object({
|
|
17434
17777
|
kind: literal("any"),
|
|
17435
|
-
children: array(
|
|
17778
|
+
children: array(AutomationWhenSchema)
|
|
17436
17779
|
}),
|
|
17437
17780
|
object({
|
|
17438
17781
|
kind: literal("not"),
|
|
17439
|
-
child:
|
|
17782
|
+
child: AutomationWhenSchema
|
|
17440
17783
|
}),
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
17784
|
+
DeviceBecomesLeafSchema,
|
|
17785
|
+
DeviceEventLeafSchema,
|
|
17786
|
+
ScheduleLeafSchema,
|
|
17787
|
+
ManualLeafSchema,
|
|
17788
|
+
IntervalLeafSchema,
|
|
17789
|
+
DetectionLeafSchema,
|
|
17790
|
+
StationaryLeafSchema,
|
|
17791
|
+
DeviceIsLeafSchema,
|
|
17792
|
+
StationaryPresentLeafSchema,
|
|
17793
|
+
ExpressionLeafSchema
|
|
17444
17794
|
]));
|
|
17445
17795
|
/**
|
|
17446
|
-
*
|
|
17447
|
-
*
|
|
17448
|
-
*
|
|
17449
|
-
*
|
|
17450
|
-
*
|
|
17451
|
-
*
|
|
17452
|
-
*
|
|
17453
|
-
*
|
|
17454
|
-
|
|
17455
|
-
|
|
17796
|
+
* The recipe shape before the single `when` tree: a flat list of triggers
|
|
17797
|
+
* (OR'd, each an instant) and an optional condition tree. Stored recipes —
|
|
17798
|
+
* the `AUTOMATION_RECIPE` literal of every block saved so far, and whatever an
|
|
17799
|
+
* older client still posts — are in this shape, so it stays READABLE here, and
|
|
17800
|
+
* {@link convertLegacyRecipe} turns it into the new one, losslessly and one
|
|
17801
|
+
* way. Nothing writes this shape any more.
|
|
17802
|
+
*
|
|
17803
|
+
* `{ triggers: [t1…tn], conditions?: C }`
|
|
17804
|
+
* → `when = all(any(t1…tn), C)`, or `any(t1…tn)` without `C`;
|
|
17805
|
+
* a single trigger is not wrapped in `any`.
|
|
17806
|
+
* a `condition` leaf → a `device-is` leaf (the "is" verb);
|
|
17807
|
+
* a `device-state` trigger → a `device-becomes` leaf (the "becomes" verb);
|
|
17808
|
+
* `classes: ['dog']` → the macro `animal` with the label `dog`.
|
|
17809
|
+
*/
|
|
17810
|
+
var LegacyTriggerSchema = discriminatedUnion("kind", [
|
|
17456
17811
|
object({
|
|
17457
17812
|
kind: literal("device-state"),
|
|
17458
17813
|
deviceId: number().int().nonnegative(),
|
|
17459
17814
|
cap: string().min(1),
|
|
17460
17815
|
fieldPath: string().min(1),
|
|
17461
|
-
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
17462
17816
|
becomes: union([
|
|
17463
17817
|
string(),
|
|
17464
17818
|
number(),
|
|
17465
17819
|
boolean()
|
|
17466
17820
|
]).optional(),
|
|
17467
|
-
/** Only on a CHANGE of value, not on every re-report. */
|
|
17468
17821
|
edge: boolean().optional(),
|
|
17469
|
-
/** The condition must hold this long before the run starts. */
|
|
17470
17822
|
forMs: number().int().min(0).max(864e5).optional(),
|
|
17471
|
-
/** Collapse a burst into one run. */
|
|
17472
17823
|
debounceMs: number().int().min(0).max(6e5).optional()
|
|
17473
17824
|
}),
|
|
17474
17825
|
object({
|
|
17475
17826
|
kind: literal("device-event"),
|
|
17476
|
-
/** An `EventCategory` value. */
|
|
17477
17827
|
category: string().min(1),
|
|
17478
17828
|
deviceId: number().int().nonnegative().optional()
|
|
17479
17829
|
}),
|
|
@@ -17484,25 +17834,16 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
|
|
|
17484
17834
|
object({ kind: literal("manual") }),
|
|
17485
17835
|
object({
|
|
17486
17836
|
kind: literal("detection"),
|
|
17487
|
-
/** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
|
|
17488
|
-
* matches every raw class that maps to it; any other name matches
|
|
17489
|
-
* exactly. */
|
|
17490
17837
|
classes: array(string().min(1)).min(1),
|
|
17491
17838
|
deviceIds: array(number().int().nonnegative()).min(1),
|
|
17492
|
-
/** The track must have visited at least one of these zones. */
|
|
17493
17839
|
zones: array(string().min(1)).min(1).optional(),
|
|
17494
|
-
/** Best-so-far confidence the track must reach. */
|
|
17495
17840
|
minScore: number().min(0).max(1).optional()
|
|
17496
17841
|
}),
|
|
17497
17842
|
object({
|
|
17498
17843
|
kind: literal("stationary"),
|
|
17499
17844
|
deviceIds: array(number().int().nonnegative()).min(1),
|
|
17500
|
-
/** Class names; a macro class matches like the detection trigger. */
|
|
17501
17845
|
classes: array(string().min(1)).min(1).optional(),
|
|
17502
|
-
/** The parked object must be in at least one of these zones. */
|
|
17503
17846
|
zones: array(string().min(1)).min(1).optional(),
|
|
17504
|
-
/** `appeared`: it became parked. `departed`: the registry retired it.
|
|
17505
|
-
* The vocabulary of `PipelineAnalyticsStationaryChanged`. */
|
|
17506
17847
|
phase: _enum(["appeared", "departed"])
|
|
17507
17848
|
}),
|
|
17508
17849
|
object({
|
|
@@ -17510,6 +17851,143 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
|
|
|
17510
17851
|
minutes: number().int().min(1).max(1440)
|
|
17511
17852
|
})
|
|
17512
17853
|
]);
|
|
17854
|
+
var LegacyConditionLeafSchema = object({
|
|
17855
|
+
kind: literal("condition"),
|
|
17856
|
+
deviceId: number().int().nonnegative(),
|
|
17857
|
+
cap: string().min(1),
|
|
17858
|
+
fieldPath: string().min(1),
|
|
17859
|
+
operator: AutomationConditionOperatorSchema,
|
|
17860
|
+
value: union([
|
|
17861
|
+
string(),
|
|
17862
|
+
number(),
|
|
17863
|
+
boolean(),
|
|
17864
|
+
array(union([string(), number()])),
|
|
17865
|
+
QuantityValueSchema
|
|
17866
|
+
]),
|
|
17867
|
+
unit: string().min(1).optional()
|
|
17868
|
+
});
|
|
17869
|
+
var LegacyStationaryPresentSchema = object({
|
|
17870
|
+
kind: literal("stationary-present"),
|
|
17871
|
+
deviceId: number().int().nonnegative(),
|
|
17872
|
+
classes: array(string().min(1)).min(1).optional(),
|
|
17873
|
+
zones: array(string().min(1)).min(1).optional()
|
|
17874
|
+
});
|
|
17875
|
+
var LegacyExpressionSchema = object({
|
|
17876
|
+
kind: literal("expression"),
|
|
17877
|
+
expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
17878
|
+
bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
17879
|
+
}).superRefine((src, ctx) => {
|
|
17880
|
+
const err = validateExpressionSource(src);
|
|
17881
|
+
if (err !== null) ctx.addIssue({
|
|
17882
|
+
code: "custom",
|
|
17883
|
+
message: err,
|
|
17884
|
+
path: ["expr"]
|
|
17885
|
+
});
|
|
17886
|
+
});
|
|
17887
|
+
var LegacyConditionSchema = lazy(() => discriminatedUnion("kind", [
|
|
17888
|
+
object({
|
|
17889
|
+
kind: literal("all"),
|
|
17890
|
+
children: array(LegacyConditionSchema)
|
|
17891
|
+
}),
|
|
17892
|
+
object({
|
|
17893
|
+
kind: literal("any"),
|
|
17894
|
+
children: array(LegacyConditionSchema)
|
|
17895
|
+
}),
|
|
17896
|
+
object({
|
|
17897
|
+
kind: literal("not"),
|
|
17898
|
+
child: LegacyConditionSchema
|
|
17899
|
+
}),
|
|
17900
|
+
LegacyConditionLeafSchema,
|
|
17901
|
+
LegacyExpressionSchema,
|
|
17902
|
+
LegacyStationaryPresentSchema
|
|
17903
|
+
]));
|
|
17904
|
+
function convertTrigger(trigger) {
|
|
17905
|
+
switch (trigger.kind) {
|
|
17906
|
+
case "device-state": return {
|
|
17907
|
+
...trigger,
|
|
17908
|
+
kind: "device-becomes"
|
|
17909
|
+
};
|
|
17910
|
+
case "detection": {
|
|
17911
|
+
const { classes, ...rest } = trigger;
|
|
17912
|
+
return {
|
|
17913
|
+
...rest,
|
|
17914
|
+
subjects: classes.map(subjectFromClassName)
|
|
17915
|
+
};
|
|
17916
|
+
}
|
|
17917
|
+
case "stationary": {
|
|
17918
|
+
const { classes, ...rest } = trigger;
|
|
17919
|
+
return {
|
|
17920
|
+
...rest,
|
|
17921
|
+
...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
|
|
17922
|
+
};
|
|
17923
|
+
}
|
|
17924
|
+
default: return trigger;
|
|
17925
|
+
}
|
|
17926
|
+
}
|
|
17927
|
+
/** An old condition tree as a `when` subtree: `condition` → "is", classes → subjects. */
|
|
17928
|
+
function convertLegacyCondition(condition) {
|
|
17929
|
+
switch (condition.kind) {
|
|
17930
|
+
case "condition": return {
|
|
17931
|
+
...condition,
|
|
17932
|
+
kind: "device-is"
|
|
17933
|
+
};
|
|
17934
|
+
case "stationary-present": {
|
|
17935
|
+
const { classes, ...rest } = condition;
|
|
17936
|
+
return {
|
|
17937
|
+
...rest,
|
|
17938
|
+
...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
|
|
17939
|
+
};
|
|
17940
|
+
}
|
|
17941
|
+
case "expression": return condition;
|
|
17942
|
+
case "not": return {
|
|
17943
|
+
kind: "not",
|
|
17944
|
+
child: convertLegacyCondition(condition.child)
|
|
17945
|
+
};
|
|
17946
|
+
case "all":
|
|
17947
|
+
case "any": return {
|
|
17948
|
+
kind: condition.kind,
|
|
17949
|
+
children: condition.children.map(convertLegacyCondition)
|
|
17950
|
+
};
|
|
17951
|
+
}
|
|
17952
|
+
}
|
|
17953
|
+
/** `all(any(t1…tn), C)`, or `any(t1…tn)`; a single trigger is not wrapped in `any`. */
|
|
17954
|
+
function convertLegacyWhen(half) {
|
|
17955
|
+
const leaves = half.triggers.map(convertTrigger);
|
|
17956
|
+
const first = leaves[0];
|
|
17957
|
+
const triggers = leaves.length === 1 && first !== void 0 ? first : {
|
|
17958
|
+
kind: "any",
|
|
17959
|
+
children: leaves
|
|
17960
|
+
};
|
|
17961
|
+
if (half.conditions === void 0) return triggers;
|
|
17962
|
+
return {
|
|
17963
|
+
kind: "all",
|
|
17964
|
+
children: [triggers, convertLegacyCondition(half.conditions)]
|
|
17965
|
+
};
|
|
17966
|
+
}
|
|
17967
|
+
/**
|
|
17968
|
+
* The automation RECIPE — what a visually-authored automation is, before it
|
|
17969
|
+
* becomes code.
|
|
17970
|
+
*
|
|
17971
|
+
* This is the data half of `docs/design/2026-08-08-automation-scripts-custom-devices.md`
|
|
17972
|
+
* Phase 6. A recipe is not an engine and never evaluates anything: it is a
|
|
17973
|
+
* declarative description that {@link generateAutomationBlock} compiles to the
|
|
17974
|
+
* TypeScript of an ordinary core block. There is therefore no second rule
|
|
17975
|
+
* engine, no second store and no second runtime — the block runner is the
|
|
17976
|
+
* runtime, `core_blocks` is the store (§2.1).
|
|
17977
|
+
*
|
|
17978
|
+
* Two parts:
|
|
17979
|
+
*
|
|
17980
|
+
* - **`when`** — ONE tree (`./when.ts`) of all/any/not over event leaves
|
|
17981
|
+
* (instants) and state leaves (durations). It replaces the flat trigger list
|
|
17982
|
+
* plus the separate condition tree; the firing rule is in that file and in
|
|
17983
|
+
* ADR "one tree".
|
|
17984
|
+
* - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
|
|
17985
|
+
* fragment of §3.2.2. See {@link AutomationActionSchema} for the ONE place
|
|
17986
|
+
* this had to diverge from "verbatim", and why.
|
|
17987
|
+
*
|
|
17988
|
+
* A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
|
|
17989
|
+
* {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
|
|
17990
|
+
*/
|
|
17513
17991
|
/**
|
|
17514
17992
|
* One action step.
|
|
17515
17993
|
*
|
|
@@ -17612,69 +18090,49 @@ var AutomationRunModeSchema = discriminatedUnion("mode", [
|
|
|
17612
18090
|
minutes: number().int().min(1).max(1440)
|
|
17613
18091
|
})
|
|
17614
18092
|
]);
|
|
17615
|
-
var
|
|
17616
|
-
|
|
17617
|
-
conditions: AutomationConditionSchema.optional(),
|
|
18093
|
+
var CurrentAutomationRecipeSchema = object({
|
|
18094
|
+
when: AutomationWhenSchema,
|
|
17618
18095
|
actions: array(AutomationActionSchema),
|
|
17619
18096
|
runMode: AutomationRunModeSchema.optional(),
|
|
17620
18097
|
/** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
|
|
17621
18098
|
keepTraces: number().int().min(1).max(100).optional()
|
|
17622
18099
|
});
|
|
17623
|
-
|
|
17624
|
-
|
|
17625
|
-
|
|
17626
|
-
|
|
17627
|
-
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
bus: "vehicle",
|
|
17649
|
-
train: "vehicle",
|
|
17650
|
-
truck: "vehicle",
|
|
17651
|
-
boat: "vehicle",
|
|
17652
|
-
bird: "animal",
|
|
17653
|
-
cat: "animal",
|
|
17654
|
-
dog: "animal",
|
|
17655
|
-
horse: "animal",
|
|
17656
|
-
sheep: "animal",
|
|
17657
|
-
cow: "animal",
|
|
17658
|
-
elephant: "animal",
|
|
17659
|
-
bear: "animal",
|
|
17660
|
-
zebra: "animal",
|
|
17661
|
-
giraffe: "animal",
|
|
17662
|
-
suitcase: "package",
|
|
17663
|
-
backpack: "package",
|
|
17664
|
-
handbag: "package"
|
|
17665
|
-
},
|
|
17666
|
-
preserveOriginal: false
|
|
17667
|
-
};
|
|
18100
|
+
/** A recipe as it was stored before the single tree. Read-only: nothing writes this. */
|
|
18101
|
+
var LegacyAutomationRecipeSchema = object({
|
|
18102
|
+
triggers: array(LegacyTriggerSchema),
|
|
18103
|
+
conditions: LegacyConditionSchema.optional(),
|
|
18104
|
+
actions: array(AutomationActionSchema),
|
|
18105
|
+
runMode: AutomationRunModeSchema.optional(),
|
|
18106
|
+
keepTraces: number().int().min(1).max(100).optional()
|
|
18107
|
+
});
|
|
18108
|
+
/** The old shape as the new one, lossless. */
|
|
18109
|
+
function convertLegacyRecipe(legacy) {
|
|
18110
|
+
const { triggers, conditions, ...rest } = legacy;
|
|
18111
|
+
return {
|
|
18112
|
+
when: convertLegacyWhen({
|
|
18113
|
+
triggers,
|
|
18114
|
+
conditions
|
|
18115
|
+
}),
|
|
18116
|
+
...rest
|
|
18117
|
+
};
|
|
18118
|
+
}
|
|
18119
|
+
/**
|
|
18120
|
+
* Where recipes are PARSED (the core-blocks cap, the MCP tool, the literal a
|
|
18121
|
+
* saved block carries): the new shape as it is, or the old shape converted to
|
|
18122
|
+
* it. The output type is always the new one.
|
|
18123
|
+
*/
|
|
18124
|
+
var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
|
|
17668
18125
|
new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
17669
18126
|
/**
|
|
17670
18127
|
* Run traces — what an automation (or, later, an Action run) DID, node by node,
|
|
17671
18128
|
* so the editor can paint every block green or red with its reason.
|
|
17672
18129
|
*
|
|
17673
|
-
* A trace is data, not a log: it covers the WHOLE tree — the
|
|
17674
|
-
* every
|
|
17675
|
-
* short-circuit never reached), the run-mode verdict and every action
|
|
17676
|
-
* each addressed by a stable path (`
|
|
17677
|
-
* for an Action run).
|
|
18130
|
+
* A trace is data, not a log: it covers the WHOLE tree — the event leaf that
|
|
18131
|
+
* fired, every node of the `when` tree (containers and leaves, including the
|
|
18132
|
+
* ones a short-circuit never reached), the run-mode verdict and every action
|
|
18133
|
+
* step — each addressed by a stable path (`when.0.1`, `actions.2`, or `steps.N`
|
|
18134
|
+
* for an Action run). Traces written before the single tree carry
|
|
18135
|
+
* `conditions.*` paths and still parse. Skipped
|
|
17678
18136
|
* evaluations are traced too, and identical consecutive skips collapse into a
|
|
17679
18137
|
* repeat count so a trigger that fails every 5 minutes cannot flush real runs.
|
|
17680
18138
|
*
|
|
@@ -17706,6 +18164,8 @@ var TraceNodeOutcomeSchema = _enum([
|
|
|
17706
18164
|
]);
|
|
17707
18165
|
var AutomationTriggerTraceSchema = object({
|
|
17708
18166
|
kind: string().min(1),
|
|
18167
|
+
/** The event leaf that fired (`when`, `when.0.1`); absent on traces written before the single tree. */
|
|
18168
|
+
path: string().min(1).optional(),
|
|
17709
18169
|
outcome: _enum(["pass", "fail"]),
|
|
17710
18170
|
/** Why an event was rejected ("class person not in [dog]") or what fired. */
|
|
17711
18171
|
reason: string(),
|
|
@@ -17713,15 +18173,23 @@ var AutomationTriggerTraceSchema = object({
|
|
|
17713
18173
|
vars: record(string(), TraceValueSchema)
|
|
17714
18174
|
});
|
|
17715
18175
|
var AutomationConditionTraceNodeSchema = object({
|
|
17716
|
-
/** `
|
|
18176
|
+
/** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
|
|
17717
18177
|
path: string().min(1),
|
|
17718
18178
|
kind: _enum([
|
|
17719
18179
|
"all",
|
|
17720
18180
|
"any",
|
|
17721
18181
|
"not",
|
|
17722
18182
|
"condition",
|
|
18183
|
+
"device-is",
|
|
17723
18184
|
"expression",
|
|
17724
|
-
"stationary-present"
|
|
18185
|
+
"stationary-present",
|
|
18186
|
+
"device-becomes",
|
|
18187
|
+
"device-event",
|
|
18188
|
+
"schedule",
|
|
18189
|
+
"interval",
|
|
18190
|
+
"manual",
|
|
18191
|
+
"detection",
|
|
18192
|
+
"stationary"
|
|
17725
18193
|
]),
|
|
17726
18194
|
outcome: TraceNodeOutcomeSchema,
|
|
17727
18195
|
reason: string(),
|
|
@@ -17755,6 +18223,12 @@ var AutomationStepTraceSchema = object({
|
|
|
17755
18223
|
deviceId: number().int().optional(),
|
|
17756
18224
|
startedAt: number().optional(),
|
|
17757
18225
|
endedAt: number().optional(),
|
|
18226
|
+
/**
|
|
18227
|
+
* The parameters the step SENT, after `{{vars.x}}` substitution — what the
|
|
18228
|
+
* device was actually asked. Bounded like trigger `vars`; a secret parameter
|
|
18229
|
+
* is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
|
|
18230
|
+
*/
|
|
18231
|
+
args: record(string(), TraceValueSchema).optional(),
|
|
17758
18232
|
outcome: _enum([
|
|
17759
18233
|
"ok",
|
|
17760
18234
|
"error",
|
|
@@ -18504,6 +18978,23 @@ method(object({
|
|
|
18504
18978
|
addonId: string(),
|
|
18505
18979
|
nodeId: string().optional()
|
|
18506
18980
|
}), SettingsSchemaWithValuesSchema.nullable()), method(object({
|
|
18981
|
+
addonId: string(),
|
|
18982
|
+
nodeId: string().optional()
|
|
18983
|
+
}), object({ keys: array(string()) }).nullable(), { auth: "admin" }), method(object({
|
|
18984
|
+
addonId: string(),
|
|
18985
|
+
nodeId: string().optional(),
|
|
18986
|
+
keys: array(string())
|
|
18987
|
+
}), object({
|
|
18988
|
+
removed: array(string()),
|
|
18989
|
+
refused: array(object({
|
|
18990
|
+
key: string(),
|
|
18991
|
+
reason: string()
|
|
18992
|
+
})),
|
|
18993
|
+
secretsCleared: number()
|
|
18994
|
+
}), {
|
|
18995
|
+
kind: "mutation",
|
|
18996
|
+
auth: "admin"
|
|
18997
|
+
}), method(object({
|
|
18507
18998
|
addonId: string(),
|
|
18508
18999
|
deviceId: number(),
|
|
18509
19000
|
nodeId: string().optional()
|
|
@@ -19958,8 +20449,21 @@ var BrokerProviderInfoSchema = object({
|
|
|
19958
20449
|
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
19959
20450
|
* offers it as the default path; the fields the session fills are
|
|
19960
20451
|
* marked `filledBySignIn` in the kind's settings schema.
|
|
20452
|
+
*
|
|
20453
|
+
* `methods` lists the ways the session can sign in, recommended first:
|
|
20454
|
+
* `'device-code'` adds `POST {basePath}/code/start` (body: the form's
|
|
20455
|
+
* settings) answering `{ sessionId, userCode, verificationUrl,
|
|
20456
|
+
* expiresAt }` — the operator types `userCode` at `verificationUrl` on
|
|
20457
|
+
* any device, and the same `status` / `commit` / `cancel` routes follow
|
|
20458
|
+
* (status then carries `method`, `userCode`, `verificationUrl`,
|
|
20459
|
+
* `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
|
|
20460
|
+
* `'proxy'` is the `{basePath}/start` flow above. Absent means
|
|
20461
|
+
* `['proxy']`.
|
|
19961
20462
|
*/
|
|
19962
|
-
signIn: object({
|
|
20463
|
+
signIn: object({
|
|
20464
|
+
basePath: string().min(1),
|
|
20465
|
+
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
20466
|
+
}).optional()
|
|
19963
20467
|
}))
|
|
19964
20468
|
});
|
|
19965
20469
|
var ListInputSchema = object({
|
|
@@ -22471,6 +22975,8 @@ var StorageKindSchema = object({
|
|
|
22471
22975
|
"orphaned",
|
|
22472
22976
|
"dropped"
|
|
22473
22977
|
]),
|
|
22978
|
+
/** An owner declared this kind since the engine started. */
|
|
22979
|
+
declaredThisBoot: boolean(),
|
|
22474
22980
|
/** `null` when the caller asked for no counts (`counts: false`). */
|
|
22475
22981
|
rows: number().int().nonnegative().nullable(),
|
|
22476
22982
|
/** Rows x the average size of a sample; null for an empty kind. */
|
|
@@ -22482,6 +22988,51 @@ var StorageKindSchema = object({
|
|
|
22482
22988
|
/** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
|
|
22483
22989
|
declaration: RecordKindDeclarationSchema
|
|
22484
22990
|
});
|
|
22991
|
+
_enum([
|
|
22992
|
+
"orphaned",
|
|
22993
|
+
"stale",
|
|
22994
|
+
"unknown"
|
|
22995
|
+
]);
|
|
22996
|
+
/** A record kind nobody declared since its owner started, or whose owner is gone. */
|
|
22997
|
+
var StaleKindFindingSchema = object({
|
|
22998
|
+
owner: string(),
|
|
22999
|
+
kind: string(),
|
|
23000
|
+
/** `unknown`: the owner could not be judged (not running, or its state unreadable) - never dropped. */
|
|
23001
|
+
verdict: _enum(["orphaned", "unknown"]),
|
|
23002
|
+
reason: string(),
|
|
23003
|
+
rows: number().int().nonnegative().nullable(),
|
|
23004
|
+
/** local-auth's tables are never dropped by this audit. */
|
|
23005
|
+
protected: boolean()
|
|
23006
|
+
});
|
|
23007
|
+
/** A stored global-settings key the running addon no longer declares. */
|
|
23008
|
+
var StaleSettingsKeyFindingSchema = object({
|
|
23009
|
+
addonId: string(),
|
|
23010
|
+
/** The stored key as written, a per-node `<key>@<nodeId>` suffix included. */
|
|
23011
|
+
key: string(),
|
|
23012
|
+
/**
|
|
23013
|
+
* Keychain refs whose ONLY marker sits under this key: in use by stale data,
|
|
23014
|
+
* not orphans yet. Removing the key releases them.
|
|
23015
|
+
*/
|
|
23016
|
+
secretRefs: array(string())
|
|
23017
|
+
});
|
|
23018
|
+
/** An addon the audit could not judge, with the reason. Never reported as stale. */
|
|
23019
|
+
var StaleUnknownSchema = object({
|
|
23020
|
+
subject: string(),
|
|
23021
|
+
reason: string()
|
|
23022
|
+
});
|
|
23023
|
+
var StaleDataReportSchema = object({
|
|
23024
|
+
/** Epoch ms of the last finished scan; `null` before the first. */
|
|
23025
|
+
scannedAt: number().nullable(),
|
|
23026
|
+
scanning: boolean(),
|
|
23027
|
+
trigger: _enum([
|
|
23028
|
+
"boot",
|
|
23029
|
+
"interval",
|
|
23030
|
+
"manual"
|
|
23031
|
+
]).nullable(),
|
|
23032
|
+
kinds: array(StaleKindFindingSchema),
|
|
23033
|
+
settingsKeys: array(StaleSettingsKeyFindingSchema),
|
|
23034
|
+
unknown: array(StaleUnknownSchema)
|
|
23035
|
+
});
|
|
22485
23036
|
method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
|
|
22486
23037
|
owner: string().min(1).optional(),
|
|
22487
23038
|
/** `false` skips the per-kind row count (a catalog read only). Default true. */
|
|
@@ -22489,7 +23040,30 @@ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: arr
|
|
|
22489
23040
|
}), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
|
|
22490
23041
|
owner: string().min(1),
|
|
22491
23042
|
kind: RecordKindNameSchema
|
|
22492
|
-
}), RecordQueryResultSchema, { auth: "admin" })
|
|
23043
|
+
}), RecordQueryResultSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, {
|
|
23044
|
+
kind: "mutation",
|
|
23045
|
+
auth: "admin"
|
|
23046
|
+
}), method(object({
|
|
23047
|
+
owner: string().min(1),
|
|
23048
|
+
kind: RecordKindNameSchema
|
|
23049
|
+
}), object({
|
|
23050
|
+
deleted: number().int().nonnegative(),
|
|
23051
|
+
secretsReleased: number().int().nonnegative()
|
|
23052
|
+
}), {
|
|
23053
|
+
kind: "mutation",
|
|
23054
|
+
auth: "admin",
|
|
23055
|
+
caller: "required"
|
|
23056
|
+
}), method(object({
|
|
23057
|
+
addonId: string().min(1),
|
|
23058
|
+
key: string().min(1)
|
|
23059
|
+
}), object({
|
|
23060
|
+
removed: boolean(),
|
|
23061
|
+
secretsCleared: number().int().nonnegative()
|
|
23062
|
+
}), {
|
|
23063
|
+
kind: "mutation",
|
|
23064
|
+
auth: "admin",
|
|
23065
|
+
caller: "required"
|
|
23066
|
+
});
|
|
22493
23067
|
/**
|
|
22494
23068
|
* record-entries - the ONE admin surface for every list of user-created entries
|
|
22495
23069
|
* (D881): notification targets, brokers, LLM profiles, anything an addon
|
|
@@ -22628,6 +23202,12 @@ method(object({
|
|
|
22628
23202
|
}), method(object({ deviceId: number().int() }), object({ refs: array(string()).readonly() }), {
|
|
22629
23203
|
kind: "mutation",
|
|
22630
23204
|
auth: "admin"
|
|
23205
|
+
}), method(object({
|
|
23206
|
+
refs: RefListSchema,
|
|
23207
|
+
onBehalfOf: string().min(1)
|
|
23208
|
+
}), object({ released: number().int() }), {
|
|
23209
|
+
kind: "mutation",
|
|
23210
|
+
auth: "admin"
|
|
22631
23211
|
}), method(object({}), array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }), method(object({}), KeychainStateSchema, { auth: "admin" });
|
|
22632
23212
|
/**
|
|
22633
23213
|
* device-history — the ONE mechanism for numeric history (D850).
|
|
@@ -23417,7 +23997,9 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
|
|
|
23417
23997
|
/** Upstream-system identity + rendering hints for the candidate. */
|
|
23418
23998
|
sourceInfo: SourceInfoSchema.optional(),
|
|
23419
23999
|
/** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
|
|
23420
|
-
children: array(DiscoveredChildDeviceSchema).readonly().optional()
|
|
24000
|
+
children: array(DiscoveredChildDeviceSchema).readonly().optional(),
|
|
24001
|
+
/** `childNativeId` of the gateway candidate in the same list this device sits behind. */
|
|
24002
|
+
parentStableId: string().optional()
|
|
23421
24003
|
}));
|
|
23422
24004
|
/**
|
|
23423
24005
|
* Status block — populated by the integration after each
|
|
@@ -43732,6 +44314,79 @@ object({
|
|
|
43732
44314
|
metadata: record(string(), unknown()).optional()
|
|
43733
44315
|
});
|
|
43734
44316
|
/**
|
|
44317
|
+
* HOW a provider can be found on the network. The provider only DECLARES the
|
|
44318
|
+
* mode; the `local-network` scan engine runs it once for every provider that
|
|
44319
|
+
* asked (one multicast browse, one host sweep) and hands the raw hits back to
|
|
44320
|
+
* `classifyDiscoveryHits`. A provider never opens a discovery socket itself,
|
|
44321
|
+
* except through a `custom` mode.
|
|
44322
|
+
*/
|
|
44323
|
+
var DiscoveryModeSchema = discriminatedUnion("kind", [
|
|
44324
|
+
object({
|
|
44325
|
+
kind: literal("mdns"),
|
|
44326
|
+
/** DNS-SD service type, e.g. `_esphomelib._tcp`. */
|
|
44327
|
+
serviceType: string().min(1),
|
|
44328
|
+
/** Regular expression an instance name must match (case-insensitive). */
|
|
44329
|
+
instancePattern: string().optional()
|
|
44330
|
+
}),
|
|
44331
|
+
object({
|
|
44332
|
+
kind: literal("ws-discovery"),
|
|
44333
|
+
/** Probe types (e.g. `dn:NetworkVideoTransmitter`); omitted = any device. */
|
|
44334
|
+
types: array(string()).optional()
|
|
44335
|
+
}),
|
|
44336
|
+
object({
|
|
44337
|
+
kind: literal("ssdp"),
|
|
44338
|
+
searchTarget: string().min(1)
|
|
44339
|
+
}),
|
|
44340
|
+
object({
|
|
44341
|
+
kind: literal("udp-broadcast"),
|
|
44342
|
+
port: number().int().min(1).max(65535),
|
|
44343
|
+
payloadHex: string().regex(/^([0-9a-fA-F]{2})*$/),
|
|
44344
|
+
/** Port replies arrive on, when the device answers somewhere other than the source port. */
|
|
44345
|
+
listenPort: number().int().min(1).max(65535).optional()
|
|
44346
|
+
}),
|
|
44347
|
+
object({
|
|
44348
|
+
kind: literal("http-probe"),
|
|
44349
|
+
port: number().int().min(1).max(65535),
|
|
44350
|
+
path: string().startsWith("/"),
|
|
44351
|
+
method: _enum(["GET", "POST"]).default("GET"),
|
|
44352
|
+
bodyText: string().optional(),
|
|
44353
|
+
contentType: string().optional(),
|
|
44354
|
+
tls: boolean().default(false),
|
|
44355
|
+
/** Status codes that count as a hit. Omitted = any HTTP answer. */
|
|
44356
|
+
acceptStatus: array(number().int()).optional()
|
|
44357
|
+
}),
|
|
44358
|
+
object({
|
|
44359
|
+
kind: literal("custom"),
|
|
44360
|
+
/** Why the engine cannot do it (an allowlisted exception, e.g. a vendor UDP protocol). */
|
|
44361
|
+
reason: string().min(1)
|
|
44362
|
+
})
|
|
44363
|
+
]);
|
|
44364
|
+
/** What the engine saw on the wire for ONE declared mode; the provider classifies it. */
|
|
44365
|
+
var DiscoveryHitSchema = object({
|
|
44366
|
+
/** Index into the provider's own `getDiscoveryModes()` array. */
|
|
44367
|
+
modeIndex: number().int().min(0),
|
|
44368
|
+
host: string(),
|
|
44369
|
+
port: number().int(),
|
|
44370
|
+
http: object({
|
|
44371
|
+
status: number().int(),
|
|
44372
|
+
headers: record(string(), string()),
|
|
44373
|
+
/** At most 16 KiB. */
|
|
44374
|
+
bodyText: string()
|
|
44375
|
+
}).optional(),
|
|
44376
|
+
mdns: object({
|
|
44377
|
+
instance: string(),
|
|
44378
|
+
host: string(),
|
|
44379
|
+
port: number().int(),
|
|
44380
|
+
txt: record(string(), string())
|
|
44381
|
+
}).optional(),
|
|
44382
|
+
udp: object({ payloadHex: string() }).optional(),
|
|
44383
|
+
wsd: object({
|
|
44384
|
+
xaddrs: array(string()),
|
|
44385
|
+
types: array(string()),
|
|
44386
|
+
scopes: array(string())
|
|
44387
|
+
}).optional()
|
|
44388
|
+
});
|
|
44389
|
+
/**
|
|
43735
44390
|
* Candidate handed back from discovery and accepted by
|
|
43736
44391
|
* `adoptDiscoveredDevice`. Shape mirrors the in-process
|
|
43737
44392
|
* `DiscoveredDevice` interface used by `DeviceDiscovery`.
|
|
@@ -43766,7 +44421,17 @@ var DiscoveryCandidateSchema = object({
|
|
|
43766
44421
|
/** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
|
|
43767
44422
|
onboardedDeviceId: number().optional(),
|
|
43768
44423
|
/** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
|
|
43769
|
-
onboardedName: string().optional()
|
|
44424
|
+
onboardedName: string().optional(),
|
|
44425
|
+
/**
|
|
44426
|
+
* This candidate is an INTEGRATION to set up (a gateway, a hub, a controller),
|
|
44427
|
+
* not a device to adopt. Replaces the `prefilledConfig.setupIntegration` marker,
|
|
44428
|
+
* which is still read for one release.
|
|
44429
|
+
*/
|
|
44430
|
+
setupIntegration: boolean().optional(),
|
|
44431
|
+
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
44432
|
+
parentStableId: string().optional(),
|
|
44433
|
+
/** `false` = shown for context but not pickable. Default true. */
|
|
44434
|
+
selectable: boolean().optional()
|
|
43770
44435
|
});
|
|
43771
44436
|
/**
|
|
43772
44437
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -43819,6 +44484,12 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
|
|
|
43819
44484
|
}), method(object({}), boolean()), method(object({ params: record(string(), unknown()).optional() }), array(DiscoveryCandidateSchema), {
|
|
43820
44485
|
kind: "mutation",
|
|
43821
44486
|
auth: "admin"
|
|
44487
|
+
}), method(object({}), array(DiscoveryModeSchema)), method(object({
|
|
44488
|
+
hits: array(DiscoveryHitSchema),
|
|
44489
|
+
params: record(string(), unknown()).optional()
|
|
44490
|
+
}), array(DiscoveryCandidateSchema), {
|
|
44491
|
+
kind: "mutation",
|
|
44492
|
+
auth: "admin"
|
|
43822
44493
|
}), method(object({}), CreationSchemaOutputSchema), method(object({}), object({ deviceType: _enum(DeviceType).nullable() })), method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
|
|
43823
44494
|
kind: "mutation",
|
|
43824
44495
|
auth: "admin"
|
|
@@ -44546,17 +45217,23 @@ method(object({
|
|
|
44546
45217
|
}), method(object({}), object({ providers: array(object({
|
|
44547
45218
|
addonId: string(),
|
|
44548
45219
|
label: string()
|
|
44549
|
-
})).readonly() }), { auth: "admin" }), method(
|
|
44550
|
-
|
|
44551
|
-
|
|
44552
|
-
|
|
44553
|
-
|
|
44554
|
-
|
|
44555
|
-
|
|
44556
|
-
|
|
44557
|
-
}),
|
|
45220
|
+
})).readonly() }), { auth: "admin" }), method(
|
|
45221
|
+
/** `subnets`: extra CIDRs for THIS scan, on top of the hub's own and the saved list. */
|
|
45222
|
+
object({ subnets: array(string()).optional() }),
|
|
45223
|
+
object({ groups: array(object({
|
|
45224
|
+
addonId: string(),
|
|
45225
|
+
label: string(),
|
|
45226
|
+
candidates: array(DiscoveryCandidateSchema).readonly(),
|
|
45227
|
+
error: string().nullable()
|
|
45228
|
+
})).readonly() }),
|
|
45229
|
+
{
|
|
45230
|
+
kind: "mutation",
|
|
45231
|
+
auth: "admin"
|
|
45232
|
+
}
|
|
45233
|
+
), method(object({
|
|
44558
45234
|
addonId: string(),
|
|
44559
|
-
params: record(string(), unknown()).optional()
|
|
45235
|
+
params: record(string(), unknown()).optional(),
|
|
45236
|
+
subnets: array(string()).optional()
|
|
44560
45237
|
}), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
|
|
44561
45238
|
kind: "mutation",
|
|
44562
45239
|
auth: "admin"
|
|
@@ -58292,6 +58969,12 @@ var TerminalInstanceInfoSchema = object({
|
|
|
58292
58969
|
instanceId: string(),
|
|
58293
58970
|
cameraStableId: string(),
|
|
58294
58971
|
nodeId: string(),
|
|
58972
|
+
/**
|
|
58973
|
+
* False while the node the instance is bound to is not in the cluster
|
|
58974
|
+
* topology (forgotten, renamed, re-paired). The instance is kept; the
|
|
58975
|
+
* operator re-points it with `updateInstance({ targetNodeId })`.
|
|
58976
|
+
*/
|
|
58977
|
+
nodeInCluster: boolean(),
|
|
58295
58978
|
profileId: string(),
|
|
58296
58979
|
profileLabel: string(),
|
|
58297
58980
|
name: string(),
|
|
@@ -58341,6 +59024,8 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
|
|
|
58341
59024
|
auth: "admin"
|
|
58342
59025
|
}), method(object({
|
|
58343
59026
|
instanceId: string().min(1),
|
|
59027
|
+
/** Re-point the instance to another node; the profile must exist there. */
|
|
59028
|
+
targetNodeId: string().min(1).max(256).optional(),
|
|
58344
59029
|
name: string().trim().min(1).max(160).optional(),
|
|
58345
59030
|
executable: string().max(1024).optional(),
|
|
58346
59031
|
args: array(string().max(2048)).max(64).optional(),
|
|
@@ -60860,6 +61545,30 @@ object({
|
|
|
60860
61545
|
at: number()
|
|
60861
61546
|
}).nullable()
|
|
60862
61547
|
});
|
|
61548
|
+
var RemoteButtonShapeSchema = _enum([
|
|
61549
|
+
"rect",
|
|
61550
|
+
"rounded",
|
|
61551
|
+
"round",
|
|
61552
|
+
"pill"
|
|
61553
|
+
]);
|
|
61554
|
+
var RemoteButtonToneSchema = _enum([
|
|
61555
|
+
"neutral",
|
|
61556
|
+
"accent",
|
|
61557
|
+
"danger",
|
|
61558
|
+
"red",
|
|
61559
|
+
"green",
|
|
61560
|
+
"yellow",
|
|
61561
|
+
"blue"
|
|
61562
|
+
]);
|
|
61563
|
+
var RemoteGroupFrameSchema = _enum([
|
|
61564
|
+
"none",
|
|
61565
|
+
"panel",
|
|
61566
|
+
"divider",
|
|
61567
|
+
"dpad",
|
|
61568
|
+
"rocker-v",
|
|
61569
|
+
"rocker-h",
|
|
61570
|
+
"keypad"
|
|
61571
|
+
]);
|
|
60863
61572
|
var RemoteLayoutButtonSchema = object({
|
|
60864
61573
|
id: string().min(1).max(40),
|
|
60865
61574
|
/** 0-based grid position. */
|
|
@@ -60869,12 +61578,94 @@ var RemoteLayoutButtonSchema = object({
|
|
|
60869
61578
|
colSpan: number().int().min(1).max(24),
|
|
60870
61579
|
icon: string().min(1).max(60).optional(),
|
|
60871
61580
|
label: string().min(1).max(40).optional(),
|
|
60872
|
-
role: RemoteRoleSchema
|
|
61581
|
+
role: RemoteRoleSchema,
|
|
61582
|
+
/** Absent = the plain rendering every layout had before shapes existed. */
|
|
61583
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
61584
|
+
tone: RemoteButtonToneSchema.optional(),
|
|
61585
|
+
/** The group this button belongs to; it must lie inside that group's rectangle. */
|
|
61586
|
+
groupId: string().min(1).max(40).optional()
|
|
61587
|
+
});
|
|
61588
|
+
/**
|
|
61589
|
+
* A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
|
|
61590
|
+
* titled panel, a section divider, a D-pad ring. `componentId` names the palette
|
|
61591
|
+
* entry it came from (informational: the layout is self-contained).
|
|
61592
|
+
*/
|
|
61593
|
+
var RemoteLayoutGroupSchema = object({
|
|
61594
|
+
id: string().min(1).max(40),
|
|
61595
|
+
componentId: string().min(1).max(80),
|
|
61596
|
+
frame: RemoteGroupFrameSchema,
|
|
61597
|
+
row: number().int().min(0).max(24),
|
|
61598
|
+
col: number().int().min(0).max(24),
|
|
61599
|
+
rowSpan: number().int().min(1).max(24),
|
|
61600
|
+
colSpan: number().int().min(1).max(24),
|
|
61601
|
+
title: string().min(1).max(40).optional()
|
|
60873
61602
|
});
|
|
61603
|
+
function rectsOverlap(a, b) {
|
|
61604
|
+
return a.row < b.row + b.rowSpan && b.row < a.row + a.rowSpan && a.col < b.col + b.colSpan && b.col < a.col + a.colSpan;
|
|
61605
|
+
}
|
|
61606
|
+
function rectContains(outer, inner) {
|
|
61607
|
+
return inner.row >= outer.row && inner.col >= outer.col && inner.row + inner.rowSpan <= outer.row + outer.rowSpan && inner.col + inner.colSpan <= outer.col + outer.colSpan;
|
|
61608
|
+
}
|
|
61609
|
+
/**
|
|
61610
|
+
* The group rules of a layout: inside the grid, no two groups overlap, a
|
|
61611
|
+
* member lies inside its group, a divider holds nothing, and a button that is
|
|
61612
|
+
* not a member never sits inside someone else's rectangle (a group moves as
|
|
61613
|
+
* one thing, so a stray key under it would be left behind).
|
|
61614
|
+
*/
|
|
61615
|
+
function refineGroups(grid, ctx) {
|
|
61616
|
+
const groups = grid.groups ?? [];
|
|
61617
|
+
const byId = /* @__PURE__ */ new Map();
|
|
61618
|
+
const fail = (message, path) => {
|
|
61619
|
+
ctx.addIssue({
|
|
61620
|
+
code: "custom",
|
|
61621
|
+
message,
|
|
61622
|
+
path
|
|
61623
|
+
});
|
|
61624
|
+
};
|
|
61625
|
+
groups.forEach((group, index) => {
|
|
61626
|
+
if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
|
|
61627
|
+
"groups",
|
|
61628
|
+
index,
|
|
61629
|
+
"id"
|
|
61630
|
+
]);
|
|
61631
|
+
byId.set(group.id, group);
|
|
61632
|
+
if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
|
|
61633
|
+
for (let other = 0; other < index; other += 1) {
|
|
61634
|
+
const earlier = groups[other];
|
|
61635
|
+
if (earlier !== void 0 && rectsOverlap(earlier, group)) {
|
|
61636
|
+
fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
|
|
61637
|
+
return;
|
|
61638
|
+
}
|
|
61639
|
+
}
|
|
61640
|
+
});
|
|
61641
|
+
grid.buttons.forEach((button, index) => {
|
|
61642
|
+
if (button.groupId !== void 0) {
|
|
61643
|
+
const group = byId.get(button.groupId);
|
|
61644
|
+
if (group === void 0) {
|
|
61645
|
+
fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
|
|
61646
|
+
"buttons",
|
|
61647
|
+
index,
|
|
61648
|
+
"groupId"
|
|
61649
|
+
]);
|
|
61650
|
+
return;
|
|
61651
|
+
}
|
|
61652
|
+
if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
|
|
61653
|
+
"buttons",
|
|
61654
|
+
index,
|
|
61655
|
+
"groupId"
|
|
61656
|
+
]);
|
|
61657
|
+
if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
|
|
61658
|
+
return;
|
|
61659
|
+
}
|
|
61660
|
+
const foreign = groups.find((group) => rectsOverlap(group, button));
|
|
61661
|
+
if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
|
|
61662
|
+
});
|
|
61663
|
+
}
|
|
60874
61664
|
object({
|
|
60875
61665
|
columns: number().int().min(1).max(24),
|
|
60876
61666
|
rows: number().int().min(1).max(24),
|
|
60877
|
-
buttons: array(RemoteLayoutButtonSchema).max(200)
|
|
61667
|
+
buttons: array(RemoteLayoutButtonSchema).max(200),
|
|
61668
|
+
groups: array(RemoteLayoutGroupSchema).max(50).optional()
|
|
60878
61669
|
}).superRefine((grid, ctx) => {
|
|
60879
61670
|
const ids = /* @__PURE__ */ new Set();
|
|
60880
61671
|
const taken = /* @__PURE__ */ new Map();
|
|
@@ -60911,6 +61702,7 @@ object({
|
|
|
60911
61702
|
taken.set(cell, button.id);
|
|
60912
61703
|
}
|
|
60913
61704
|
});
|
|
61705
|
+
refineGroups(grid, ctx);
|
|
60914
61706
|
});
|
|
60915
61707
|
var RemoteBoundCommandSchema = object({
|
|
60916
61708
|
role: RemoteRoleSchema,
|
|
@@ -61042,6 +61834,91 @@ method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSche
|
|
|
61042
61834
|
kind: "mutation",
|
|
61043
61835
|
auth: "admin"
|
|
61044
61836
|
});
|
|
61837
|
+
var RemoteComponentCategorySchema = _enum([
|
|
61838
|
+
"power",
|
|
61839
|
+
"navigation",
|
|
61840
|
+
"volume",
|
|
61841
|
+
"numbers",
|
|
61842
|
+
"media",
|
|
61843
|
+
"colour",
|
|
61844
|
+
"apps",
|
|
61845
|
+
"layout"
|
|
61846
|
+
]);
|
|
61847
|
+
/** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
|
|
61848
|
+
var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
|
|
61849
|
+
var RemoteComponentButtonSchema = object({
|
|
61850
|
+
/** Joined to the instance id when the component is inserted. */
|
|
61851
|
+
idSuffix: string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
|
|
61852
|
+
row: number().int().min(0).max(24),
|
|
61853
|
+
col: number().int().min(0).max(24),
|
|
61854
|
+
rowSpan: number().int().min(1).max(24),
|
|
61855
|
+
colSpan: number().int().min(1).max(24),
|
|
61856
|
+
role: RemoteRoleSchema,
|
|
61857
|
+
icon: string().min(1).max(60).optional(),
|
|
61858
|
+
label: string().min(1).max(40).optional(),
|
|
61859
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
61860
|
+
tone: RemoteButtonToneSchema.optional()
|
|
61861
|
+
});
|
|
61862
|
+
var RemoteLayoutComponentSchema = object({
|
|
61863
|
+
id: string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
|
|
61864
|
+
name: string().min(1).max(60),
|
|
61865
|
+
nameI18n: object({
|
|
61866
|
+
en: string().min(1).max(60),
|
|
61867
|
+
it: string().min(1).max(60)
|
|
61868
|
+
}).optional(),
|
|
61869
|
+
/** A display icon key, resolved by the UI like a button icon. */
|
|
61870
|
+
icon: string().min(1).max(60),
|
|
61871
|
+
category: RemoteComponentCategorySchema,
|
|
61872
|
+
size: object({
|
|
61873
|
+
rows: number().int().min(1).max(24),
|
|
61874
|
+
cols: number().int().min(1).max(24)
|
|
61875
|
+
}),
|
|
61876
|
+
/** The frame drawn around the whole component, or null for loose buttons. */
|
|
61877
|
+
group: object({
|
|
61878
|
+
frame: RemoteGroupFrameSchema,
|
|
61879
|
+
title: string().min(1).max(40).optional()
|
|
61880
|
+
}).nullable(),
|
|
61881
|
+
buttons: array(RemoteComponentButtonSchema).max(40)
|
|
61882
|
+
}).superRefine((component, ctx) => {
|
|
61883
|
+
const suffixes = /* @__PURE__ */ new Set();
|
|
61884
|
+
component.buttons.forEach((button, index) => {
|
|
61885
|
+
if (suffixes.has(button.idSuffix)) ctx.addIssue({
|
|
61886
|
+
code: "custom",
|
|
61887
|
+
message: `button "${button.idSuffix}" is listed twice`,
|
|
61888
|
+
path: [
|
|
61889
|
+
"buttons",
|
|
61890
|
+
index,
|
|
61891
|
+
"idSuffix"
|
|
61892
|
+
]
|
|
61893
|
+
});
|
|
61894
|
+
suffixes.add(button.idSuffix);
|
|
61895
|
+
if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
|
|
61896
|
+
ctx.addIssue({
|
|
61897
|
+
code: "custom",
|
|
61898
|
+
message: `button "${button.idSuffix}" leaves the component`,
|
|
61899
|
+
path: ["buttons", index]
|
|
61900
|
+
});
|
|
61901
|
+
return;
|
|
61902
|
+
}
|
|
61903
|
+
for (let other = 0; other < index; other += 1) {
|
|
61904
|
+
const earlier = component.buttons[other];
|
|
61905
|
+
if (earlier !== void 0 && rectsOverlap(earlier, button)) {
|
|
61906
|
+
ctx.addIssue({
|
|
61907
|
+
code: "custom",
|
|
61908
|
+
message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
|
|
61909
|
+
path: ["buttons", index]
|
|
61910
|
+
});
|
|
61911
|
+
return;
|
|
61912
|
+
}
|
|
61913
|
+
}
|
|
61914
|
+
});
|
|
61915
|
+
if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
|
|
61916
|
+
code: "custom",
|
|
61917
|
+
message: "a divider component holds no buttons",
|
|
61918
|
+
path: ["buttons"]
|
|
61919
|
+
});
|
|
61920
|
+
});
|
|
61921
|
+
method(object({}), array(RemoteLayoutComponentSchema), { auth: "protected" });
|
|
61045
61922
|
DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
61046
61923
|
kind: "mutation",
|
|
61047
61924
|
auth: "protected"
|
|
@@ -63183,7 +64060,9 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
63183
64060
|
id: string(),
|
|
63184
64061
|
settings: record(string(), unknown()),
|
|
63185
64062
|
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
63186
|
-
secretKeys: array(string()).optional()
|
|
64063
|
+
secretKeys: array(string()).optional(),
|
|
64064
|
+
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
64065
|
+
skipRestart: boolean().optional()
|
|
63187
64066
|
}), object({ success: boolean() }), {
|
|
63188
64067
|
kind: "mutation",
|
|
63189
64068
|
auth: "admin"
|
|
@@ -63660,6 +64539,39 @@ var TlsStatusSchema = object({
|
|
|
63660
64539
|
hasCa: boolean()
|
|
63661
64540
|
}).nullable()
|
|
63662
64541
|
});
|
|
64542
|
+
/** One provider's ask of the scan engine: the modes it declared. */
|
|
64543
|
+
var ScanRequestSchema = object({
|
|
64544
|
+
providerId: string().min(1),
|
|
64545
|
+
modes: array(DiscoveryModeSchema)
|
|
64546
|
+
});
|
|
64547
|
+
var ScanErrorSchema = object({
|
|
64548
|
+
/** Absent for a failure that is the scan's own (a bad subnet, a socket that would not open). */
|
|
64549
|
+
providerId: string().optional(),
|
|
64550
|
+
/** The mode family that failed (`mdns`, `http-probe`, …). */
|
|
64551
|
+
mode: string().optional(),
|
|
64552
|
+
message: string()
|
|
64553
|
+
});
|
|
64554
|
+
/** What one scan did, per mode family; the engine logs the same numbers once. */
|
|
64555
|
+
var ScanStatsSchema = object({
|
|
64556
|
+
hostsProbed: number().int(),
|
|
64557
|
+
hits: number().int(),
|
|
64558
|
+
timeouts: number().int(),
|
|
64559
|
+
byMode: record(string(), object({
|
|
64560
|
+
probed: number().int(),
|
|
64561
|
+
hits: number().int(),
|
|
64562
|
+
timeouts: number().int()
|
|
64563
|
+
}))
|
|
64564
|
+
});
|
|
64565
|
+
var ScanNetworkResultSchema = object({
|
|
64566
|
+
hits: array(object({
|
|
64567
|
+
providerId: string(),
|
|
64568
|
+
hit: DiscoveryHitSchema
|
|
64569
|
+
})),
|
|
64570
|
+
/** CIDRs actually covered: the hub's own /24s, the saved list and the per-call list. */
|
|
64571
|
+
subnetsScanned: array(string()),
|
|
64572
|
+
errors: array(ScanErrorSchema),
|
|
64573
|
+
stats: ScanStatsSchema
|
|
64574
|
+
});
|
|
63663
64575
|
method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
|
|
63664
64576
|
/**
|
|
63665
64577
|
* LEGACY HINT — do not send from new code. Kept optional so clients
|
|
@@ -63714,6 +64626,16 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
|
|
|
63714
64626
|
}), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
|
|
63715
64627
|
kind: "mutation",
|
|
63716
64628
|
auth: "admin"
|
|
64629
|
+
}), method(object({
|
|
64630
|
+
requests: array(ScanRequestSchema),
|
|
64631
|
+
subnets: array(string()).optional()
|
|
64632
|
+
}), ScanNetworkResultSchema, {
|
|
64633
|
+
kind: "mutation",
|
|
64634
|
+
auth: "admin",
|
|
64635
|
+
timeoutMs: 9e4
|
|
64636
|
+
}), method(_void(), object({ subnets: array(string()) }), { auth: "admin" }), method(object({ subnets: array(string()) }), object({ subnets: array(string()) }), {
|
|
64637
|
+
kind: "mutation",
|
|
64638
|
+
auth: "admin"
|
|
63717
64639
|
}), method(_void(), TlsStatusSchema, {
|
|
63718
64640
|
kind: "mutation",
|
|
63719
64641
|
auth: "admin"
|
|
@@ -66997,7 +67919,7 @@ object({
|
|
|
66997
67919
|
});
|
|
66998
67920
|
Object.values(DeviceType);
|
|
66999
67921
|
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
67000
|
-
DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
|
|
67922
|
+
DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
|
|
67001
67923
|
kind: "mutation",
|
|
67002
67924
|
auth: "admin"
|
|
67003
67925
|
});
|
|
@@ -70821,6 +71743,124 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
70821
71743
|
kind: "mutation",
|
|
70822
71744
|
auth: "admin"
|
|
70823
71745
|
});
|
|
71746
|
+
var btn = (idSuffix, row, col, init) => ({
|
|
71747
|
+
idSuffix,
|
|
71748
|
+
row,
|
|
71749
|
+
col,
|
|
71750
|
+
rowSpan: 1,
|
|
71751
|
+
colSpan: 1,
|
|
71752
|
+
...init
|
|
71753
|
+
});
|
|
71754
|
+
function rocker(id, name, it, frame, title, buttons, size) {
|
|
71755
|
+
return {
|
|
71756
|
+
id,
|
|
71757
|
+
name,
|
|
71758
|
+
nameI18n: {
|
|
71759
|
+
en: name,
|
|
71760
|
+
it
|
|
71761
|
+
},
|
|
71762
|
+
icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
|
|
71763
|
+
category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
|
|
71764
|
+
size,
|
|
71765
|
+
group: title === null ? { frame } : {
|
|
71766
|
+
frame,
|
|
71767
|
+
title
|
|
71768
|
+
},
|
|
71769
|
+
buttons
|
|
71770
|
+
};
|
|
71771
|
+
}
|
|
71772
|
+
btn("power", 0, 0, {
|
|
71773
|
+
role: "power",
|
|
71774
|
+
shape: "round",
|
|
71775
|
+
tone: "danger"
|
|
71776
|
+
}), btn("up", 0, 1, {
|
|
71777
|
+
role: "up",
|
|
71778
|
+
icon: "chevron-up"
|
|
71779
|
+
}), btn("left", 1, 0, {
|
|
71780
|
+
role: "left",
|
|
71781
|
+
icon: "chevron-left"
|
|
71782
|
+
}), btn("ok", 1, 1, {
|
|
71783
|
+
role: "ok",
|
|
71784
|
+
shape: "round",
|
|
71785
|
+
tone: "accent"
|
|
71786
|
+
}), btn("right", 1, 2, {
|
|
71787
|
+
role: "right",
|
|
71788
|
+
icon: "chevron-right"
|
|
71789
|
+
}), btn("down", 2, 1, {
|
|
71790
|
+
role: "down",
|
|
71791
|
+
icon: "chevron-down"
|
|
71792
|
+
}), rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
|
|
71793
|
+
role: "volume_up",
|
|
71794
|
+
icon: "plus",
|
|
71795
|
+
shape: "pill"
|
|
71796
|
+
}), btn("down", 1, 0, {
|
|
71797
|
+
role: "volume_down",
|
|
71798
|
+
icon: "minus",
|
|
71799
|
+
shape: "pill"
|
|
71800
|
+
})], {
|
|
71801
|
+
rows: 2,
|
|
71802
|
+
cols: 1
|
|
71803
|
+
}), rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
|
|
71804
|
+
role: "channel_up",
|
|
71805
|
+
icon: "chevron-up",
|
|
71806
|
+
shape: "pill"
|
|
71807
|
+
}), btn("down", 1, 0, {
|
|
71808
|
+
role: "channel_down",
|
|
71809
|
+
icon: "chevron-down",
|
|
71810
|
+
shape: "pill"
|
|
71811
|
+
})], {
|
|
71812
|
+
rows: 2,
|
|
71813
|
+
cols: 1
|
|
71814
|
+
}), rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
|
|
71815
|
+
role: "custom:minus",
|
|
71816
|
+
label: "Minus",
|
|
71817
|
+
icon: "minus",
|
|
71818
|
+
shape: "pill"
|
|
71819
|
+
}), btn("plus", 0, 1, {
|
|
71820
|
+
role: "custom:plus",
|
|
71821
|
+
label: "Plus",
|
|
71822
|
+
icon: "plus",
|
|
71823
|
+
shape: "pill"
|
|
71824
|
+
})], {
|
|
71825
|
+
rows: 1,
|
|
71826
|
+
cols: 2
|
|
71827
|
+
}), [...Array.from({ length: 9 }, (_, i) => btn(`d${i + 1}`, Math.floor(i / 3), i % 3, { role: `digit_${i + 1}` })), btn("d0", 3, 1, { role: "digit_0" })], btn("red", 0, 0, {
|
|
71828
|
+
role: "custom:color_red",
|
|
71829
|
+
label: "Red",
|
|
71830
|
+
tone: "red"
|
|
71831
|
+
}), btn("green", 0, 1, {
|
|
71832
|
+
role: "custom:color_green",
|
|
71833
|
+
label: "Green",
|
|
71834
|
+
tone: "green"
|
|
71835
|
+
}), btn("yellow", 0, 2, {
|
|
71836
|
+
role: "custom:color_yellow",
|
|
71837
|
+
label: "Yellow",
|
|
71838
|
+
tone: "yellow"
|
|
71839
|
+
}), btn("blue", 0, 3, {
|
|
71840
|
+
role: "custom:color_blue",
|
|
71841
|
+
label: "Blue",
|
|
71842
|
+
tone: "blue"
|
|
71843
|
+
}), btn("rewind", 0, 0, {
|
|
71844
|
+
role: "rewind",
|
|
71845
|
+
icon: "rewind"
|
|
71846
|
+
}), btn("play", 0, 1, {
|
|
71847
|
+
role: "play",
|
|
71848
|
+
icon: "play"
|
|
71849
|
+
}), btn("pause", 0, 2, {
|
|
71850
|
+
role: "pause",
|
|
71851
|
+
icon: "pause"
|
|
71852
|
+
}), btn("forward", 0, 3, {
|
|
71853
|
+
role: "fast_forward",
|
|
71854
|
+
icon: "fast-forward"
|
|
71855
|
+
}), btn("stop", 0, 4, {
|
|
71856
|
+
role: "stop",
|
|
71857
|
+
icon: "square"
|
|
71858
|
+
}), [
|
|
71859
|
+
1,
|
|
71860
|
+
2,
|
|
71861
|
+
3,
|
|
71862
|
+
4
|
|
71863
|
+
].map((n) => btn(`app${n}`, 0, n - 1, { role: `custom:app_slot_${n}` })), btn("back", 0, 0, { role: "back" }), btn("home", 0, 1, { role: "home" }), btn("menu", 0, 2, { role: "menu" });
|
|
70824
71864
|
discriminatedUnion("kind", [object({
|
|
70825
71865
|
kind: literal("raw"),
|
|
70826
71866
|
timings: array(number().int()),
|
|
@@ -70831,7 +71871,7 @@ discriminatedUnion("kind", [object({
|
|
|
70831
71871
|
})]);
|
|
70832
71872
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
70833
71873
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
70834
|
-
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
71874
|
+
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
70835
71875
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
70836
71876
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
70837
71877
|
new Set(CAP_NAMES);
|
|
@@ -70884,7 +71924,7 @@ var AccessoryKind = {
|
|
|
70884
71924
|
PrivacyMask: DeviceRole.PrivacyMask
|
|
70885
71925
|
};
|
|
70886
71926
|
AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
|
|
70887
|
-
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
71927
|
+
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
|
|
70888
71928
|
new Set(Object.values(DeviceType));
|
|
70889
71929
|
Object.freeze({
|
|
70890
71930
|
"accessories.getStatus": {
|
|
@@ -71272,6 +72312,13 @@ Object.freeze({
|
|
|
71272
72312
|
access: "create",
|
|
71273
72313
|
role: "admin"
|
|
71274
72314
|
},
|
|
72315
|
+
"addonSettings.getDeclaredSettingsKeys": {
|
|
72316
|
+
capName: "addon-settings",
|
|
72317
|
+
capScope: "system",
|
|
72318
|
+
addonId: null,
|
|
72319
|
+
access: "view",
|
|
72320
|
+
role: "admin"
|
|
72321
|
+
},
|
|
71275
72322
|
"addonSettings.getDeviceSettings": {
|
|
71276
72323
|
capName: "addon-settings",
|
|
71277
72324
|
capScope: "system",
|
|
@@ -71293,6 +72340,13 @@ Object.freeze({
|
|
|
71293
72340
|
access: "view",
|
|
71294
72341
|
role: "admin"
|
|
71295
72342
|
},
|
|
72343
|
+
"addonSettings.unsetStoredSettingsKeys": {
|
|
72344
|
+
capName: "addon-settings",
|
|
72345
|
+
capScope: "system",
|
|
72346
|
+
addonId: null,
|
|
72347
|
+
access: "create",
|
|
72348
|
+
role: "admin"
|
|
72349
|
+
},
|
|
71296
72350
|
"addonSettings.updateDeviceSettings": {
|
|
71297
72351
|
capName: "addon-settings",
|
|
71298
72352
|
capScope: "system",
|
|
@@ -73708,6 +74762,13 @@ Object.freeze({
|
|
|
73708
74762
|
access: "create",
|
|
73709
74763
|
role: "admin"
|
|
73710
74764
|
},
|
|
74765
|
+
"deviceProvider.classifyDiscoveryHits": {
|
|
74766
|
+
capName: "device-provider",
|
|
74767
|
+
capScope: "system",
|
|
74768
|
+
addonId: null,
|
|
74769
|
+
access: "create",
|
|
74770
|
+
role: "admin"
|
|
74771
|
+
},
|
|
73711
74772
|
"deviceProvider.createDevice": {
|
|
73712
74773
|
capName: "device-provider",
|
|
73713
74774
|
capScope: "system",
|
|
@@ -73736,6 +74797,13 @@ Object.freeze({
|
|
|
73736
74797
|
access: "view",
|
|
73737
74798
|
role: "admin"
|
|
73738
74799
|
},
|
|
74800
|
+
"deviceProvider.getDiscoveryModes": {
|
|
74801
|
+
capName: "device-provider",
|
|
74802
|
+
capScope: "system",
|
|
74803
|
+
addonId: null,
|
|
74804
|
+
access: "view",
|
|
74805
|
+
role: "admin"
|
|
74806
|
+
},
|
|
73739
74807
|
"deviceProvider.getDiscoveryParamsSchema": {
|
|
73740
74808
|
capName: "device-provider",
|
|
73741
74809
|
capScope: "system",
|
|
@@ -74513,6 +75581,13 @@ Object.freeze({
|
|
|
74513
75581
|
access: "create",
|
|
74514
75582
|
role: "admin"
|
|
74515
75583
|
},
|
|
75584
|
+
"keychain.releaseRecordRefs": {
|
|
75585
|
+
capName: "keychain",
|
|
75586
|
+
capScope: "system",
|
|
75587
|
+
addonId: null,
|
|
75588
|
+
access: "create",
|
|
75589
|
+
role: "admin"
|
|
75590
|
+
},
|
|
74516
75591
|
"keychain.resolve": {
|
|
74517
75592
|
capName: "keychain",
|
|
74518
75593
|
capScope: "system",
|
|
@@ -74933,6 +76008,13 @@ Object.freeze({
|
|
|
74933
76008
|
access: "view",
|
|
74934
76009
|
role: "admin"
|
|
74935
76010
|
},
|
|
76011
|
+
"localNetwork.getScanSubnets": {
|
|
76012
|
+
capName: "local-network",
|
|
76013
|
+
capScope: "system",
|
|
76014
|
+
addonId: null,
|
|
76015
|
+
access: "view",
|
|
76016
|
+
role: "admin"
|
|
76017
|
+
},
|
|
74936
76018
|
"localNetwork.getTlsStatus": {
|
|
74937
76019
|
capName: "local-network",
|
|
74938
76020
|
capScope: "system",
|
|
@@ -74982,6 +76064,13 @@ Object.freeze({
|
|
|
74982
76064
|
access: "create",
|
|
74983
76065
|
role: "admin"
|
|
74984
76066
|
},
|
|
76067
|
+
"localNetwork.scanNetwork": {
|
|
76068
|
+
capName: "local-network",
|
|
76069
|
+
capScope: "system",
|
|
76070
|
+
addonId: null,
|
|
76071
|
+
access: "create",
|
|
76072
|
+
role: "admin"
|
|
76073
|
+
},
|
|
74985
76074
|
"localNetwork.setAddressRole": {
|
|
74986
76075
|
capName: "local-network",
|
|
74987
76076
|
capScope: "system",
|
|
@@ -75003,6 +76092,13 @@ Object.freeze({
|
|
|
75003
76092
|
access: "create",
|
|
75004
76093
|
role: "admin"
|
|
75005
76094
|
},
|
|
76095
|
+
"localNetwork.setScanSubnets": {
|
|
76096
|
+
capName: "local-network",
|
|
76097
|
+
capScope: "system",
|
|
76098
|
+
addonId: null,
|
|
76099
|
+
access: "create",
|
|
76100
|
+
role: "admin"
|
|
76101
|
+
},
|
|
75006
76102
|
"localNetwork.setViewerEndpoints": {
|
|
75007
76103
|
capName: "local-network",
|
|
75008
76104
|
capScope: "system",
|
|
@@ -78685,6 +79781,13 @@ Object.freeze({
|
|
|
78685
79781
|
access: "create",
|
|
78686
79782
|
role: "admin"
|
|
78687
79783
|
},
|
|
79784
|
+
"remoteLayoutComponents.listComponents": {
|
|
79785
|
+
capName: "remote-layout-components",
|
|
79786
|
+
capScope: "system",
|
|
79787
|
+
addonId: null,
|
|
79788
|
+
access: "view",
|
|
79789
|
+
role: "admin"
|
|
79790
|
+
},
|
|
78688
79791
|
"runningState.getStatus": {
|
|
78689
79792
|
capName: "running-state",
|
|
78690
79793
|
capScope: "device",
|
|
@@ -79217,6 +80320,13 @@ Object.freeze({
|
|
|
79217
80320
|
access: "create",
|
|
79218
80321
|
role: "admin"
|
|
79219
80322
|
},
|
|
80323
|
+
"storageAdmin.dropOrphanedKind": {
|
|
80324
|
+
capName: "storage-admin",
|
|
80325
|
+
capScope: "system",
|
|
80326
|
+
addonId: null,
|
|
80327
|
+
access: "delete",
|
|
80328
|
+
role: "admin"
|
|
80329
|
+
},
|
|
79220
80330
|
"storageAdmin.listKinds": {
|
|
79221
80331
|
capName: "storage-admin",
|
|
79222
80332
|
capScope: "system",
|
|
@@ -79238,6 +80348,27 @@ Object.freeze({
|
|
|
79238
80348
|
access: "view",
|
|
79239
80349
|
role: "admin"
|
|
79240
80350
|
},
|
|
80351
|
+
"storageAdmin.scanStaleData": {
|
|
80352
|
+
capName: "storage-admin",
|
|
80353
|
+
capScope: "system",
|
|
80354
|
+
addonId: null,
|
|
80355
|
+
access: "create",
|
|
80356
|
+
role: "admin"
|
|
80357
|
+
},
|
|
80358
|
+
"storageAdmin.staleDataReport": {
|
|
80359
|
+
capName: "storage-admin",
|
|
80360
|
+
capScope: "system",
|
|
80361
|
+
addonId: null,
|
|
80362
|
+
access: "view",
|
|
80363
|
+
role: "admin"
|
|
80364
|
+
},
|
|
80365
|
+
"storageAdmin.unsetStaleSettingsKey": {
|
|
80366
|
+
capName: "storage-admin",
|
|
80367
|
+
capScope: "system",
|
|
80368
|
+
addonId: null,
|
|
80369
|
+
access: "create",
|
|
80370
|
+
role: "admin"
|
|
80371
|
+
},
|
|
79241
80372
|
"storageEvictable.evict": {
|
|
79242
80373
|
capName: "storage-evictable",
|
|
79243
80374
|
capScope: "system",
|
|
@@ -84861,6 +85992,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
84861
85992
|
"recording-export": system("recording exports — requested from the timeline, not an automation step"),
|
|
84862
85993
|
records: system(INTERNAL),
|
|
84863
85994
|
remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
|
|
85995
|
+
"remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
|
|
84864
85996
|
"server-management": system(CLUSTER),
|
|
84865
85997
|
"settings-store": system(INTERNAL),
|
|
84866
85998
|
"smtp-provider": system(INTERNAL),
|
|
@@ -84933,7 +86065,7 @@ function projectAction(cap, action) {
|
|
|
84933
86065
|
};
|
|
84934
86066
|
}
|
|
84935
86067
|
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
84936
|
-
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
86068
|
+
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
|
|
84937
86069
|
/**
|
|
84938
86070
|
* Heuristic check — `device.features?.includes(profile.when.hasFeature)`.
|
|
84939
86071
|
*/
|