@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.mjs
CHANGED
|
@@ -5591,7 +5591,7 @@ var ZodIssueCode = {
|
|
|
5591
5591
|
var ZodFirstPartyTypeKind;
|
|
5592
5592
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5593
5593
|
//#endregion
|
|
5594
|
-
//#region ../types/dist/sleep-
|
|
5594
|
+
//#region ../types/dist/sleep-CeyhrcOk.mjs
|
|
5595
5595
|
/**
|
|
5596
5596
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5597
5597
|
* window to float samples (D455).
|
|
@@ -7674,6 +7674,7 @@ var BaseAddon = class {
|
|
|
7674
7674
|
* a warning and falls back rather than crashing boot.
|
|
7675
7675
|
*/
|
|
7676
7676
|
state(key, schema, fallback) {
|
|
7677
|
+
this._declaredStateKeys.add(key);
|
|
7677
7678
|
return createDurableState({
|
|
7678
7679
|
key,
|
|
7679
7680
|
schema,
|
|
@@ -7689,6 +7690,96 @@ var BaseAddon = class {
|
|
|
7689
7690
|
} })
|
|
7690
7691
|
});
|
|
7691
7692
|
}
|
|
7693
|
+
/** Durable-state keys this process registered through {@link state}. */
|
|
7694
|
+
_declaredStateKeys = /* @__PURE__ */ new Set();
|
|
7695
|
+
/**
|
|
7696
|
+
* Stale-data audit: every global-settings key this addon still declares —
|
|
7697
|
+
* constructor defaults, settings-schema fields (layout containers included),
|
|
7698
|
+
* durable-state keys and the root of each `secretStatePaths` entry. Answered
|
|
7699
|
+
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
7700
|
+
*/
|
|
7701
|
+
async getDeclaredSettingsKeys() {
|
|
7702
|
+
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
7703
|
+
}
|
|
7704
|
+
declaredKeySet() {
|
|
7705
|
+
const keys = new Set(Object.keys(this.defaults));
|
|
7706
|
+
const schema = this.globalSettingsSchema();
|
|
7707
|
+
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
7708
|
+
for (const key of this._declaredStateKeys) keys.add(key);
|
|
7709
|
+
for (const path of this.secretStatePaths) {
|
|
7710
|
+
const root = path.split(/[.[]/, 1)[0];
|
|
7711
|
+
if (root) keys.add(root);
|
|
7712
|
+
}
|
|
7713
|
+
return keys;
|
|
7714
|
+
}
|
|
7715
|
+
/**
|
|
7716
|
+
* Stale-data audit: remove stored global-settings keys the addon no longer
|
|
7717
|
+
* declares, and clear the keychain values their markers held — except a value
|
|
7718
|
+
* a surviving key still points at. A declared or absent key is refused.
|
|
7719
|
+
*/
|
|
7720
|
+
async unsetStoredSettingsKeys(keys) {
|
|
7721
|
+
const settings = this._ctx?.settings;
|
|
7722
|
+
const raw = this.rawSettings();
|
|
7723
|
+
if (!settings || !raw) return {
|
|
7724
|
+
removed: [],
|
|
7725
|
+
refused: keys.map((key) => ({
|
|
7726
|
+
key,
|
|
7727
|
+
reason: "no settings store"
|
|
7728
|
+
})),
|
|
7729
|
+
secretsCleared: 0
|
|
7730
|
+
};
|
|
7731
|
+
let stored;
|
|
7732
|
+
try {
|
|
7733
|
+
stored = await raw.readAddonStore();
|
|
7734
|
+
} catch (error) {
|
|
7735
|
+
const reason = `store unreadable: ${error instanceof Error ? error.message : "unknown"}`;
|
|
7736
|
+
return {
|
|
7737
|
+
removed: [],
|
|
7738
|
+
refused: keys.map((key) => ({
|
|
7739
|
+
key,
|
|
7740
|
+
reason
|
|
7741
|
+
})),
|
|
7742
|
+
secretsCleared: 0
|
|
7743
|
+
};
|
|
7744
|
+
}
|
|
7745
|
+
const declared = this.declaredKeySet();
|
|
7746
|
+
const removed = [];
|
|
7747
|
+
const refused = [];
|
|
7748
|
+
for (const key of keys) {
|
|
7749
|
+
const base = key.split("@", 1)[0] ?? key;
|
|
7750
|
+
if (declared.has(base)) refused.push({
|
|
7751
|
+
key,
|
|
7752
|
+
reason: "still declared by the addon"
|
|
7753
|
+
});
|
|
7754
|
+
else if (!(key in stored)) refused.push({
|
|
7755
|
+
key,
|
|
7756
|
+
reason: "not stored"
|
|
7757
|
+
});
|
|
7758
|
+
else removed.push(key);
|
|
7759
|
+
}
|
|
7760
|
+
if (removed.length === 0) return {
|
|
7761
|
+
removed,
|
|
7762
|
+
refused,
|
|
7763
|
+
secretsCleared: 0
|
|
7764
|
+
};
|
|
7765
|
+
const kept = /* @__PURE__ */ new Set();
|
|
7766
|
+
const held = /* @__PURE__ */ new Set();
|
|
7767
|
+
for (const [key, value] of Object.entries(stored)) {
|
|
7768
|
+
const target = removed.includes(key) ? held : kept;
|
|
7769
|
+
for (const ref of collectSecretMarkerRefs(value)) target.add(ref);
|
|
7770
|
+
}
|
|
7771
|
+
const refsToClear = [...held].filter((ref) => !kept.has(ref));
|
|
7772
|
+
const patch = {};
|
|
7773
|
+
for (const key of removed) patch[key] = void 0;
|
|
7774
|
+
await settings.writeAddonStore(patch);
|
|
7775
|
+
const secretsCleared = refsToClear.length > 0 && this._ctx?.secrets ? await this._ctx.secrets.delete(refsToClear) : 0;
|
|
7776
|
+
await this.resolveConfig();
|
|
7777
|
+
return {
|
|
7778
|
+
removed,
|
|
7779
|
+
refused,
|
|
7780
|
+
secretsCleared
|
|
7781
|
+
};
|
|
7782
|
+
}
|
|
7692
7783
|
/** Per-device variant of {@link state}, backed by the per-device store. */
|
|
7693
7784
|
deviceState(deviceId, key, schema, fallback) {
|
|
7694
7785
|
return createDurableState({
|
|
@@ -7959,6 +8050,21 @@ function dropPerNodeFields(fields) {
|
|
|
7959
8050
|
}
|
|
7960
8051
|
return kept;
|
|
7961
8052
|
}
|
|
8053
|
+
function collectFieldKeys(fields) {
|
|
8054
|
+
const collected = [];
|
|
8055
|
+
for (const field of fields) {
|
|
8056
|
+
if (field.type === "group") {
|
|
8057
|
+
collected.push(...collectFieldKeys(field.fields));
|
|
8058
|
+
continue;
|
|
8059
|
+
}
|
|
8060
|
+
if (field.type === "sub-tabs") {
|
|
8061
|
+
for (const tab of field.tabs) collected.push(...collectFieldKeys(tab.fields));
|
|
8062
|
+
continue;
|
|
8063
|
+
}
|
|
8064
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8065
|
+
}
|
|
8066
|
+
return collected;
|
|
8067
|
+
}
|
|
7962
8068
|
function collectPerNodeFieldKeys(fields) {
|
|
7963
8069
|
const collected = [];
|
|
7964
8070
|
for (const field of fields) {
|
|
@@ -9639,6 +9745,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9639
9745
|
"recording-signal",
|
|
9640
9746
|
"records",
|
|
9641
9747
|
"remote",
|
|
9748
|
+
"remote-layout-components",
|
|
9642
9749
|
"running-state",
|
|
9643
9750
|
"scene-monitor",
|
|
9644
9751
|
"script-runner",
|
|
@@ -11508,7 +11615,7 @@ function sleep(ms) {
|
|
|
11508
11615
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11509
11616
|
}
|
|
11510
11617
|
//#endregion
|
|
11511
|
-
//#region ../types/dist/err-msg-
|
|
11618
|
+
//#region ../types/dist/err-msg-BD5buxvT.mjs
|
|
11512
11619
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
11513
11620
|
DeviceType["Camera"] = "camera";
|
|
11514
11621
|
DeviceType["Hub"] = "hub";
|
|
@@ -11614,6 +11721,12 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
11614
11721
|
DeviceType["Washer"] = "washer";
|
|
11615
11722
|
/** Tumble dryer. Same plug-built running surface as {@link Washer}. */
|
|
11616
11723
|
DeviceType["Dryer"] = "dryer";
|
|
11724
|
+
/** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
|
|
11725
|
+
* other radio-to-IP bridges). It carries the box's own facts (firmware,
|
|
11726
|
+
* address, connection state) and the actions the box really supports
|
|
11727
|
+
* (`reboot`, `device-admin-link`). Products are NOT its children — they are
|
|
11728
|
+
* adopted beside it — and its connection settings stay on the integration. */
|
|
11729
|
+
DeviceType["Gateway"] = "gateway";
|
|
11617
11730
|
return DeviceType;
|
|
11618
11731
|
}({});
|
|
11619
11732
|
var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
@@ -11874,7 +11987,7 @@ function errMsg$2(err) {
|
|
|
11874
11987
|
return String(err);
|
|
11875
11988
|
}
|
|
11876
11989
|
//#endregion
|
|
11877
|
-
//#region ../types/dist/composition-
|
|
11990
|
+
//#region ../types/dist/composition-C7WdThmP.mjs
|
|
11878
11991
|
/**
|
|
11879
11992
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11880
11993
|
*
|
|
@@ -17343,8 +17456,172 @@ object({
|
|
|
17343
17456
|
quantity: ReadingQuantitySchema,
|
|
17344
17457
|
unit: string().min(1)
|
|
17345
17458
|
});
|
|
17346
|
-
|
|
17347
|
-
|
|
17459
|
+
new Set([
|
|
17460
|
+
{
|
|
17461
|
+
id: "person",
|
|
17462
|
+
name: "Person"
|
|
17463
|
+
},
|
|
17464
|
+
{
|
|
17465
|
+
id: "vehicle",
|
|
17466
|
+
name: "Vehicle"
|
|
17467
|
+
},
|
|
17468
|
+
{
|
|
17469
|
+
id: "animal",
|
|
17470
|
+
name: "Animal"
|
|
17471
|
+
},
|
|
17472
|
+
{
|
|
17473
|
+
id: "package",
|
|
17474
|
+
name: "Package"
|
|
17475
|
+
}
|
|
17476
|
+
].map((l) => l.id));
|
|
17477
|
+
var COCO_TO_MACRO = {
|
|
17478
|
+
mapping: {
|
|
17479
|
+
person: "person",
|
|
17480
|
+
bicycle: "vehicle",
|
|
17481
|
+
car: "vehicle",
|
|
17482
|
+
motorcycle: "vehicle",
|
|
17483
|
+
airplane: "vehicle",
|
|
17484
|
+
bus: "vehicle",
|
|
17485
|
+
train: "vehicle",
|
|
17486
|
+
truck: "vehicle",
|
|
17487
|
+
boat: "vehicle",
|
|
17488
|
+
bird: "animal",
|
|
17489
|
+
cat: "animal",
|
|
17490
|
+
dog: "animal",
|
|
17491
|
+
horse: "animal",
|
|
17492
|
+
sheep: "animal",
|
|
17493
|
+
cow: "animal",
|
|
17494
|
+
elephant: "animal",
|
|
17495
|
+
bear: "animal",
|
|
17496
|
+
zebra: "animal",
|
|
17497
|
+
giraffe: "animal",
|
|
17498
|
+
suitcase: "package",
|
|
17499
|
+
backpack: "package",
|
|
17500
|
+
handbag: "package"
|
|
17501
|
+
},
|
|
17502
|
+
preserveOriginal: false
|
|
17503
|
+
};
|
|
17504
|
+
/**
|
|
17505
|
+
* WHAT a detection-shaped leaf is about: one MACRO class (person, vehicle,
|
|
17506
|
+
* animal, package) plus an OPTIONAL label that narrows it.
|
|
17507
|
+
*
|
|
17508
|
+
* The taxonomy is `COCO_TO_MACRO` (and `CLASS_MAP_MACRO_TARGETS`) — never a
|
|
17509
|
+
* second list. The label that fits depends on the macro class:
|
|
17510
|
+
*
|
|
17511
|
+
* - `animal` → a sub-class (`dog`, `cat`, …);
|
|
17512
|
+
* - `person` → a known face (an identity of the `face-gallery`);
|
|
17513
|
+
* - `vehicle` → a plate (its text) or a sub-class (`car`, `truck`, …);
|
|
17514
|
+
* - `package` → a sub-class.
|
|
17515
|
+
*
|
|
17516
|
+
* `other` exists only so a class the taxonomy does not know (a custom model's
|
|
17517
|
+
* `forklift`) survives the conversion of a stored recipe without widening.
|
|
17518
|
+
*
|
|
17519
|
+
* A label is decided LATE (D683): face recognition and LPR land after the
|
|
17520
|
+
* detection starts. The track-lifecycle `update` event carries the newly
|
|
17521
|
+
* resolved `label` / `plateText`, which is why a leaf with a label fires when
|
|
17522
|
+
* the subject GETS it — not at the first detection.
|
|
17523
|
+
*/
|
|
17524
|
+
var SubClassLabelSchema = object({
|
|
17525
|
+
kind: literal("sub-class"),
|
|
17526
|
+
className: string().min(1)
|
|
17527
|
+
});
|
|
17528
|
+
var IdentityLabelSchema = object({
|
|
17529
|
+
kind: literal("identity"),
|
|
17530
|
+
/** The `face-gallery` identity id (stable across a rename). */
|
|
17531
|
+
identityId: string().min(1),
|
|
17532
|
+
/** The identity's name — what a track-lifecycle event carries as `label`. */
|
|
17533
|
+
name: string().min(1)
|
|
17534
|
+
});
|
|
17535
|
+
var PlateLabelSchema = object({
|
|
17536
|
+
kind: literal("plate"),
|
|
17537
|
+
text: string().min(1)
|
|
17538
|
+
});
|
|
17539
|
+
function macroOf$1(className) {
|
|
17540
|
+
return COCO_TO_MACRO.mapping[className.toLowerCase()] ?? className.toLowerCase();
|
|
17541
|
+
}
|
|
17542
|
+
/** A sub-class label must belong to the macro it is under. */
|
|
17543
|
+
function subClassBelongs(macro, label) {
|
|
17544
|
+
return label === void 0 || macroOf$1(label.className) === macro;
|
|
17545
|
+
}
|
|
17546
|
+
var subClassOf = (macro) => SubClassLabelSchema.refine((l) => subClassBelongs(macro, l), { message: `that class is not a kind of ${macro} in the detection taxonomy` });
|
|
17547
|
+
var DetectionSubjectSchema = discriminatedUnion("macro", [
|
|
17548
|
+
object({
|
|
17549
|
+
macro: literal("animal"),
|
|
17550
|
+
label: subClassOf("animal").optional()
|
|
17551
|
+
}),
|
|
17552
|
+
object({
|
|
17553
|
+
macro: literal("person"),
|
|
17554
|
+
label: IdentityLabelSchema.optional()
|
|
17555
|
+
}),
|
|
17556
|
+
object({
|
|
17557
|
+
macro: literal("vehicle"),
|
|
17558
|
+
label: discriminatedUnion("kind", [SubClassLabelSchema.refine((l) => subClassBelongs("vehicle", l), { message: "that class is not a kind of vehicle in the detection taxonomy" }), PlateLabelSchema]).optional()
|
|
17559
|
+
}),
|
|
17560
|
+
object({
|
|
17561
|
+
macro: literal("package"),
|
|
17562
|
+
label: subClassOf("package").optional()
|
|
17563
|
+
}),
|
|
17564
|
+
object({
|
|
17565
|
+
macro: literal("other"),
|
|
17566
|
+
className: string().min(1)
|
|
17567
|
+
})
|
|
17568
|
+
]);
|
|
17569
|
+
/**
|
|
17570
|
+
* The subject of a PARKED object. The stationary registry's event carries a
|
|
17571
|
+
* class and a zone, no face and no plate — an identity or plate label there
|
|
17572
|
+
* could never match, so the schema refuses it instead of letting it sit dead.
|
|
17573
|
+
*/
|
|
17574
|
+
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" });
|
|
17575
|
+
/**
|
|
17576
|
+
* A stored class string as a subject: a macro name stays a macro; a raw class
|
|
17577
|
+
* the taxonomy files under a macro becomes that macro plus the sub-class label
|
|
17578
|
+
* (`dog` → animal / dog); anything else is `other`, kept verbatim.
|
|
17579
|
+
*/
|
|
17580
|
+
function subjectFromClassName(className) {
|
|
17581
|
+
const name = className.trim().toLowerCase();
|
|
17582
|
+
const macro = COCO_TO_MACRO.mapping[name];
|
|
17583
|
+
if (new Set(Object.values(COCO_TO_MACRO.mapping)).has(name)) return macroOnly(name);
|
|
17584
|
+
if (macro === void 0) return {
|
|
17585
|
+
macro: "other",
|
|
17586
|
+
className
|
|
17587
|
+
};
|
|
17588
|
+
const label = {
|
|
17589
|
+
kind: "sub-class",
|
|
17590
|
+
className: name
|
|
17591
|
+
};
|
|
17592
|
+
switch (macro) {
|
|
17593
|
+
case "animal": return {
|
|
17594
|
+
macro: "animal",
|
|
17595
|
+
label
|
|
17596
|
+
};
|
|
17597
|
+
case "vehicle": return {
|
|
17598
|
+
macro: "vehicle",
|
|
17599
|
+
label
|
|
17600
|
+
};
|
|
17601
|
+
case "package": return {
|
|
17602
|
+
macro: "package",
|
|
17603
|
+
label
|
|
17604
|
+
};
|
|
17605
|
+
default: return {
|
|
17606
|
+
macro: "other",
|
|
17607
|
+
className
|
|
17608
|
+
};
|
|
17609
|
+
}
|
|
17610
|
+
}
|
|
17611
|
+
function macroOnly(macro) {
|
|
17612
|
+
switch (macro) {
|
|
17613
|
+
case "animal": return { macro: "animal" };
|
|
17614
|
+
case "person": return { macro: "person" };
|
|
17615
|
+
case "vehicle": return { macro: "vehicle" };
|
|
17616
|
+
case "package": return { macro: "package" };
|
|
17617
|
+
default: return {
|
|
17618
|
+
macro: "other",
|
|
17619
|
+
className: macro
|
|
17620
|
+
};
|
|
17621
|
+
}
|
|
17622
|
+
}
|
|
17623
|
+
/** How a state leaf compares a device field to a value. Derived from the
|
|
17624
|
+
* field's `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
|
|
17348
17625
|
var AutomationConditionOperatorSchema = _enum([
|
|
17349
17626
|
"eq",
|
|
17350
17627
|
"ne",
|
|
@@ -17355,12 +17632,76 @@ var AutomationConditionOperatorSchema = _enum([
|
|
|
17355
17632
|
"contains",
|
|
17356
17633
|
"in"
|
|
17357
17634
|
]);
|
|
17358
|
-
|
|
17359
|
-
|
|
17635
|
+
/**
|
|
17636
|
+
* D8 compliance, and it is the reason a "becomes" leaf is not merely an event
|
|
17637
|
+
* subscription: it evaluates against the **state mirror**, which is reconciled,
|
|
17638
|
+
* and an event only WAKES the evaluation. A dropped event therefore DELAYS a
|
|
17639
|
+
* fire; it does not lose it.
|
|
17640
|
+
*/
|
|
17641
|
+
var DeviceBecomesLeafSchema = object({
|
|
17642
|
+
kind: literal("device-becomes"),
|
|
17643
|
+
deviceId: number().int().nonnegative(),
|
|
17644
|
+
cap: string().min(1),
|
|
17645
|
+
fieldPath: string().min(1),
|
|
17646
|
+
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
17647
|
+
becomes: union([
|
|
17648
|
+
string(),
|
|
17649
|
+
number(),
|
|
17650
|
+
boolean()
|
|
17651
|
+
]).optional(),
|
|
17652
|
+
/** Only on a CHANGE of value, not on every re-report. */
|
|
17653
|
+
edge: boolean().optional(),
|
|
17654
|
+
/** The new value must hold this long before it counts. */
|
|
17655
|
+
forMs: number().int().min(0).max(864e5).optional(),
|
|
17656
|
+
/** Collapse a burst into one occurrence. */
|
|
17657
|
+
debounceMs: number().int().min(0).max(6e5).optional()
|
|
17658
|
+
});
|
|
17659
|
+
var DeviceEventLeafSchema = object({
|
|
17660
|
+
kind: literal("device-event"),
|
|
17661
|
+
/** An `EventCategory` value. */
|
|
17662
|
+
category: string().min(1),
|
|
17663
|
+
deviceId: number().int().nonnegative().optional()
|
|
17664
|
+
});
|
|
17665
|
+
var ScheduleLeafSchema = object({
|
|
17666
|
+
kind: literal("schedule"),
|
|
17667
|
+
cron: string().min(1).max(120)
|
|
17668
|
+
});
|
|
17669
|
+
var ManualLeafSchema = object({ kind: literal("manual") });
|
|
17670
|
+
var IntervalLeafSchema = object({
|
|
17671
|
+
kind: literal("interval"),
|
|
17672
|
+
minutes: number().int().min(1).max(1440)
|
|
17673
|
+
});
|
|
17674
|
+
var DetectionLeafSchema = object({
|
|
17675
|
+
kind: literal("detection"),
|
|
17676
|
+
/** One macro class each, optionally narrowed by a label; a track of any listed subject fires. */
|
|
17677
|
+
subjects: array(DetectionSubjectSchema).min(1),
|
|
17678
|
+
deviceIds: array(number().int().nonnegative()).min(1),
|
|
17679
|
+
/** The track must have visited at least one of these zones. */
|
|
17680
|
+
zones: array(string().min(1)).min(1).optional(),
|
|
17681
|
+
/** Best-so-far confidence the track must reach. */
|
|
17682
|
+
minScore: number().min(0).max(1).optional()
|
|
17683
|
+
});
|
|
17684
|
+
var StationaryLeafSchema = object({
|
|
17685
|
+
kind: literal("stationary"),
|
|
17686
|
+
deviceIds: array(number().int().nonnegative()).min(1),
|
|
17687
|
+
subjects: array(StationarySubjectSchema).min(1).optional(),
|
|
17688
|
+
zones: array(string().min(1)).min(1).optional(),
|
|
17689
|
+
/** `appeared`: it became parked. `departed`: the registry retired it. */
|
|
17690
|
+
phase: _enum(["appeared", "departed"])
|
|
17691
|
+
});
|
|
17692
|
+
/** "is": one device field compared to one value, NOW. */
|
|
17693
|
+
var DeviceIsLeafSchema = object({
|
|
17694
|
+
kind: literal("device-is"),
|
|
17360
17695
|
deviceId: number().int().nonnegative(),
|
|
17361
17696
|
cap: string().min(1),
|
|
17362
17697
|
fieldPath: string().min(1),
|
|
17363
17698
|
operator: AutomationConditionOperatorSchema,
|
|
17699
|
+
/**
|
|
17700
|
+
* The value. A number on a quantity field is stored in the field's base unit
|
|
17701
|
+
* (D944); a quantity value is the WRITE form, which the save boundary
|
|
17702
|
+
* replaces with the converted number and `unit`. A leaf that still carries
|
|
17703
|
+
* one is never evaluated (it fails).
|
|
17704
|
+
*/
|
|
17364
17705
|
value: union([
|
|
17365
17706
|
string(),
|
|
17366
17707
|
number(),
|
|
@@ -17368,24 +17709,26 @@ var AutomationConditionLeafSchema = object({
|
|
|
17368
17709
|
array(union([string(), number()])),
|
|
17369
17710
|
QuantityValueSchema
|
|
17370
17711
|
]),
|
|
17712
|
+
/** The unit the person picked for `value` (display only; `value` is in the base unit). */
|
|
17371
17713
|
unit: string().min(1).optional()
|
|
17372
17714
|
});
|
|
17373
|
-
|
|
17715
|
+
/**
|
|
17716
|
+
* True when the camera currently has a PARKED object matching `subjects` in at
|
|
17717
|
+
* least one of `zones`. Reads the zone-analytics snapshot through the same
|
|
17718
|
+
* mirror as every other state leaf, so a dropped event never decides it (D49).
|
|
17719
|
+
*/
|
|
17720
|
+
var StationaryPresentLeafSchema = object({
|
|
17374
17721
|
kind: literal("stationary-present"),
|
|
17375
17722
|
deviceId: number().int().nonnegative(),
|
|
17376
|
-
|
|
17377
|
-
classes: array(string().min(1)).min(1).optional(),
|
|
17378
|
-
/** The parked object must be in at least one of these zones. */
|
|
17723
|
+
subjects: array(StationarySubjectSchema).min(1).optional(),
|
|
17379
17724
|
zones: array(string().min(1)).min(1).optional()
|
|
17380
17725
|
});
|
|
17381
17726
|
/**
|
|
17382
|
-
* The
|
|
17383
|
-
*
|
|
17384
|
-
*
|
|
17385
|
-
* validation is the SAME function `ExpressionSourceSchema` runs, so the two
|
|
17386
|
-
* cannot drift — an expression that one accepts, the other accepts.
|
|
17727
|
+
* The escape hatch: the bounded expression engine over named device bindings.
|
|
17728
|
+
* Declared as a plain object so a discriminated union can read `kind`; the
|
|
17729
|
+
* author-time validation is the SAME function `ExpressionSourceSchema` runs.
|
|
17387
17730
|
*/
|
|
17388
|
-
var
|
|
17731
|
+
var ExpressionLeafSchema = object({
|
|
17389
17732
|
kind: literal("expression"),
|
|
17390
17733
|
expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
17391
17734
|
bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
@@ -17397,55 +17740,62 @@ var AutomationConditionExpressionSchema = object({
|
|
|
17397
17740
|
path: ["expr"]
|
|
17398
17741
|
});
|
|
17399
17742
|
});
|
|
17400
|
-
var
|
|
17743
|
+
var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
|
|
17401
17744
|
object({
|
|
17402
17745
|
kind: literal("all"),
|
|
17403
|
-
children: array(
|
|
17746
|
+
children: array(AutomationWhenSchema)
|
|
17404
17747
|
}),
|
|
17405
17748
|
object({
|
|
17406
17749
|
kind: literal("any"),
|
|
17407
|
-
children: array(
|
|
17750
|
+
children: array(AutomationWhenSchema)
|
|
17408
17751
|
}),
|
|
17409
17752
|
object({
|
|
17410
17753
|
kind: literal("not"),
|
|
17411
|
-
child:
|
|
17754
|
+
child: AutomationWhenSchema
|
|
17412
17755
|
}),
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17756
|
+
DeviceBecomesLeafSchema,
|
|
17757
|
+
DeviceEventLeafSchema,
|
|
17758
|
+
ScheduleLeafSchema,
|
|
17759
|
+
ManualLeafSchema,
|
|
17760
|
+
IntervalLeafSchema,
|
|
17761
|
+
DetectionLeafSchema,
|
|
17762
|
+
StationaryLeafSchema,
|
|
17763
|
+
DeviceIsLeafSchema,
|
|
17764
|
+
StationaryPresentLeafSchema,
|
|
17765
|
+
ExpressionLeafSchema
|
|
17416
17766
|
]));
|
|
17417
17767
|
/**
|
|
17418
|
-
*
|
|
17419
|
-
*
|
|
17420
|
-
*
|
|
17421
|
-
*
|
|
17422
|
-
*
|
|
17423
|
-
*
|
|
17424
|
-
*
|
|
17425
|
-
*
|
|
17426
|
-
|
|
17427
|
-
|
|
17768
|
+
* The recipe shape before the single `when` tree: a flat list of triggers
|
|
17769
|
+
* (OR'd, each an instant) and an optional condition tree. Stored recipes —
|
|
17770
|
+
* the `AUTOMATION_RECIPE` literal of every block saved so far, and whatever an
|
|
17771
|
+
* older client still posts — are in this shape, so it stays READABLE here, and
|
|
17772
|
+
* {@link convertLegacyRecipe} turns it into the new one, losslessly and one
|
|
17773
|
+
* way. Nothing writes this shape any more.
|
|
17774
|
+
*
|
|
17775
|
+
* `{ triggers: [t1…tn], conditions?: C }`
|
|
17776
|
+
* → `when = all(any(t1…tn), C)`, or `any(t1…tn)` without `C`;
|
|
17777
|
+
* a single trigger is not wrapped in `any`.
|
|
17778
|
+
* a `condition` leaf → a `device-is` leaf (the "is" verb);
|
|
17779
|
+
* a `device-state` trigger → a `device-becomes` leaf (the "becomes" verb);
|
|
17780
|
+
* `classes: ['dog']` → the macro `animal` with the label `dog`.
|
|
17781
|
+
*/
|
|
17782
|
+
var LegacyTriggerSchema = discriminatedUnion("kind", [
|
|
17428
17783
|
object({
|
|
17429
17784
|
kind: literal("device-state"),
|
|
17430
17785
|
deviceId: number().int().nonnegative(),
|
|
17431
17786
|
cap: string().min(1),
|
|
17432
17787
|
fieldPath: string().min(1),
|
|
17433
|
-
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
17434
17788
|
becomes: union([
|
|
17435
17789
|
string(),
|
|
17436
17790
|
number(),
|
|
17437
17791
|
boolean()
|
|
17438
17792
|
]).optional(),
|
|
17439
|
-
/** Only on a CHANGE of value, not on every re-report. */
|
|
17440
17793
|
edge: boolean().optional(),
|
|
17441
|
-
/** The condition must hold this long before the run starts. */
|
|
17442
17794
|
forMs: number().int().min(0).max(864e5).optional(),
|
|
17443
|
-
/** Collapse a burst into one run. */
|
|
17444
17795
|
debounceMs: number().int().min(0).max(6e5).optional()
|
|
17445
17796
|
}),
|
|
17446
17797
|
object({
|
|
17447
17798
|
kind: literal("device-event"),
|
|
17448
|
-
/** An `EventCategory` value. */
|
|
17449
17799
|
category: string().min(1),
|
|
17450
17800
|
deviceId: number().int().nonnegative().optional()
|
|
17451
17801
|
}),
|
|
@@ -17456,25 +17806,16 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
|
|
|
17456
17806
|
object({ kind: literal("manual") }),
|
|
17457
17807
|
object({
|
|
17458
17808
|
kind: literal("detection"),
|
|
17459
|
-
/** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
|
|
17460
|
-
* matches every raw class that maps to it; any other name matches
|
|
17461
|
-
* exactly. */
|
|
17462
17809
|
classes: array(string().min(1)).min(1),
|
|
17463
17810
|
deviceIds: array(number().int().nonnegative()).min(1),
|
|
17464
|
-
/** The track must have visited at least one of these zones. */
|
|
17465
17811
|
zones: array(string().min(1)).min(1).optional(),
|
|
17466
|
-
/** Best-so-far confidence the track must reach. */
|
|
17467
17812
|
minScore: number().min(0).max(1).optional()
|
|
17468
17813
|
}),
|
|
17469
17814
|
object({
|
|
17470
17815
|
kind: literal("stationary"),
|
|
17471
17816
|
deviceIds: array(number().int().nonnegative()).min(1),
|
|
17472
|
-
/** Class names; a macro class matches like the detection trigger. */
|
|
17473
17817
|
classes: array(string().min(1)).min(1).optional(),
|
|
17474
|
-
/** The parked object must be in at least one of these zones. */
|
|
17475
17818
|
zones: array(string().min(1)).min(1).optional(),
|
|
17476
|
-
/** `appeared`: it became parked. `departed`: the registry retired it.
|
|
17477
|
-
* The vocabulary of `PipelineAnalyticsStationaryChanged`. */
|
|
17478
17819
|
phase: _enum(["appeared", "departed"])
|
|
17479
17820
|
}),
|
|
17480
17821
|
object({
|
|
@@ -17482,6 +17823,143 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
|
|
|
17482
17823
|
minutes: number().int().min(1).max(1440)
|
|
17483
17824
|
})
|
|
17484
17825
|
]);
|
|
17826
|
+
var LegacyConditionLeafSchema = object({
|
|
17827
|
+
kind: literal("condition"),
|
|
17828
|
+
deviceId: number().int().nonnegative(),
|
|
17829
|
+
cap: string().min(1),
|
|
17830
|
+
fieldPath: string().min(1),
|
|
17831
|
+
operator: AutomationConditionOperatorSchema,
|
|
17832
|
+
value: union([
|
|
17833
|
+
string(),
|
|
17834
|
+
number(),
|
|
17835
|
+
boolean(),
|
|
17836
|
+
array(union([string(), number()])),
|
|
17837
|
+
QuantityValueSchema
|
|
17838
|
+
]),
|
|
17839
|
+
unit: string().min(1).optional()
|
|
17840
|
+
});
|
|
17841
|
+
var LegacyStationaryPresentSchema = object({
|
|
17842
|
+
kind: literal("stationary-present"),
|
|
17843
|
+
deviceId: number().int().nonnegative(),
|
|
17844
|
+
classes: array(string().min(1)).min(1).optional(),
|
|
17845
|
+
zones: array(string().min(1)).min(1).optional()
|
|
17846
|
+
});
|
|
17847
|
+
var LegacyExpressionSchema = object({
|
|
17848
|
+
kind: literal("expression"),
|
|
17849
|
+
expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
17850
|
+
bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
17851
|
+
}).superRefine((src, ctx) => {
|
|
17852
|
+
const err = validateExpressionSource(src);
|
|
17853
|
+
if (err !== null) ctx.addIssue({
|
|
17854
|
+
code: "custom",
|
|
17855
|
+
message: err,
|
|
17856
|
+
path: ["expr"]
|
|
17857
|
+
});
|
|
17858
|
+
});
|
|
17859
|
+
var LegacyConditionSchema = lazy(() => discriminatedUnion("kind", [
|
|
17860
|
+
object({
|
|
17861
|
+
kind: literal("all"),
|
|
17862
|
+
children: array(LegacyConditionSchema)
|
|
17863
|
+
}),
|
|
17864
|
+
object({
|
|
17865
|
+
kind: literal("any"),
|
|
17866
|
+
children: array(LegacyConditionSchema)
|
|
17867
|
+
}),
|
|
17868
|
+
object({
|
|
17869
|
+
kind: literal("not"),
|
|
17870
|
+
child: LegacyConditionSchema
|
|
17871
|
+
}),
|
|
17872
|
+
LegacyConditionLeafSchema,
|
|
17873
|
+
LegacyExpressionSchema,
|
|
17874
|
+
LegacyStationaryPresentSchema
|
|
17875
|
+
]));
|
|
17876
|
+
function convertTrigger(trigger) {
|
|
17877
|
+
switch (trigger.kind) {
|
|
17878
|
+
case "device-state": return {
|
|
17879
|
+
...trigger,
|
|
17880
|
+
kind: "device-becomes"
|
|
17881
|
+
};
|
|
17882
|
+
case "detection": {
|
|
17883
|
+
const { classes, ...rest } = trigger;
|
|
17884
|
+
return {
|
|
17885
|
+
...rest,
|
|
17886
|
+
subjects: classes.map(subjectFromClassName)
|
|
17887
|
+
};
|
|
17888
|
+
}
|
|
17889
|
+
case "stationary": {
|
|
17890
|
+
const { classes, ...rest } = trigger;
|
|
17891
|
+
return {
|
|
17892
|
+
...rest,
|
|
17893
|
+
...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
|
|
17894
|
+
};
|
|
17895
|
+
}
|
|
17896
|
+
default: return trigger;
|
|
17897
|
+
}
|
|
17898
|
+
}
|
|
17899
|
+
/** An old condition tree as a `when` subtree: `condition` → "is", classes → subjects. */
|
|
17900
|
+
function convertLegacyCondition(condition) {
|
|
17901
|
+
switch (condition.kind) {
|
|
17902
|
+
case "condition": return {
|
|
17903
|
+
...condition,
|
|
17904
|
+
kind: "device-is"
|
|
17905
|
+
};
|
|
17906
|
+
case "stationary-present": {
|
|
17907
|
+
const { classes, ...rest } = condition;
|
|
17908
|
+
return {
|
|
17909
|
+
...rest,
|
|
17910
|
+
...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
|
|
17911
|
+
};
|
|
17912
|
+
}
|
|
17913
|
+
case "expression": return condition;
|
|
17914
|
+
case "not": return {
|
|
17915
|
+
kind: "not",
|
|
17916
|
+
child: convertLegacyCondition(condition.child)
|
|
17917
|
+
};
|
|
17918
|
+
case "all":
|
|
17919
|
+
case "any": return {
|
|
17920
|
+
kind: condition.kind,
|
|
17921
|
+
children: condition.children.map(convertLegacyCondition)
|
|
17922
|
+
};
|
|
17923
|
+
}
|
|
17924
|
+
}
|
|
17925
|
+
/** `all(any(t1…tn), C)`, or `any(t1…tn)`; a single trigger is not wrapped in `any`. */
|
|
17926
|
+
function convertLegacyWhen(half) {
|
|
17927
|
+
const leaves = half.triggers.map(convertTrigger);
|
|
17928
|
+
const first = leaves[0];
|
|
17929
|
+
const triggers = leaves.length === 1 && first !== void 0 ? first : {
|
|
17930
|
+
kind: "any",
|
|
17931
|
+
children: leaves
|
|
17932
|
+
};
|
|
17933
|
+
if (half.conditions === void 0) return triggers;
|
|
17934
|
+
return {
|
|
17935
|
+
kind: "all",
|
|
17936
|
+
children: [triggers, convertLegacyCondition(half.conditions)]
|
|
17937
|
+
};
|
|
17938
|
+
}
|
|
17939
|
+
/**
|
|
17940
|
+
* The automation RECIPE — what a visually-authored automation is, before it
|
|
17941
|
+
* becomes code.
|
|
17942
|
+
*
|
|
17943
|
+
* This is the data half of `docs/design/2026-08-08-automation-scripts-custom-devices.md`
|
|
17944
|
+
* Phase 6. A recipe is not an engine and never evaluates anything: it is a
|
|
17945
|
+
* declarative description that {@link generateAutomationBlock} compiles to the
|
|
17946
|
+
* TypeScript of an ordinary core block. There is therefore no second rule
|
|
17947
|
+
* engine, no second store and no second runtime — the block runner is the
|
|
17948
|
+
* runtime, `core_blocks` is the store (§2.1).
|
|
17949
|
+
*
|
|
17950
|
+
* Two parts:
|
|
17951
|
+
*
|
|
17952
|
+
* - **`when`** — ONE tree (`./when.ts`) of all/any/not over event leaves
|
|
17953
|
+
* (instants) and state leaves (durations). It replaces the flat trigger list
|
|
17954
|
+
* plus the separate condition tree; the firing rule is in that file and in
|
|
17955
|
+
* ADR "one tree".
|
|
17956
|
+
* - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
|
|
17957
|
+
* fragment of §3.2.2. See {@link AutomationActionSchema} for the ONE place
|
|
17958
|
+
* this had to diverge from "verbatim", and why.
|
|
17959
|
+
*
|
|
17960
|
+
* A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
|
|
17961
|
+
* {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
|
|
17962
|
+
*/
|
|
17485
17963
|
/**
|
|
17486
17964
|
* One action step.
|
|
17487
17965
|
*
|
|
@@ -17584,69 +18062,49 @@ var AutomationRunModeSchema = discriminatedUnion("mode", [
|
|
|
17584
18062
|
minutes: number().int().min(1).max(1440)
|
|
17585
18063
|
})
|
|
17586
18064
|
]);
|
|
17587
|
-
var
|
|
17588
|
-
|
|
17589
|
-
conditions: AutomationConditionSchema.optional(),
|
|
18065
|
+
var CurrentAutomationRecipeSchema = object({
|
|
18066
|
+
when: AutomationWhenSchema,
|
|
17590
18067
|
actions: array(AutomationActionSchema),
|
|
17591
18068
|
runMode: AutomationRunModeSchema.optional(),
|
|
17592
18069
|
/** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
|
|
17593
18070
|
keepTraces: number().int().min(1).max(100).optional()
|
|
17594
18071
|
});
|
|
17595
|
-
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
|
|
17601
|
-
|
|
17602
|
-
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
|
|
17620
|
-
bus: "vehicle",
|
|
17621
|
-
train: "vehicle",
|
|
17622
|
-
truck: "vehicle",
|
|
17623
|
-
boat: "vehicle",
|
|
17624
|
-
bird: "animal",
|
|
17625
|
-
cat: "animal",
|
|
17626
|
-
dog: "animal",
|
|
17627
|
-
horse: "animal",
|
|
17628
|
-
sheep: "animal",
|
|
17629
|
-
cow: "animal",
|
|
17630
|
-
elephant: "animal",
|
|
17631
|
-
bear: "animal",
|
|
17632
|
-
zebra: "animal",
|
|
17633
|
-
giraffe: "animal",
|
|
17634
|
-
suitcase: "package",
|
|
17635
|
-
backpack: "package",
|
|
17636
|
-
handbag: "package"
|
|
17637
|
-
},
|
|
17638
|
-
preserveOriginal: false
|
|
17639
|
-
};
|
|
18072
|
+
/** A recipe as it was stored before the single tree. Read-only: nothing writes this. */
|
|
18073
|
+
var LegacyAutomationRecipeSchema = object({
|
|
18074
|
+
triggers: array(LegacyTriggerSchema),
|
|
18075
|
+
conditions: LegacyConditionSchema.optional(),
|
|
18076
|
+
actions: array(AutomationActionSchema),
|
|
18077
|
+
runMode: AutomationRunModeSchema.optional(),
|
|
18078
|
+
keepTraces: number().int().min(1).max(100).optional()
|
|
18079
|
+
});
|
|
18080
|
+
/** The old shape as the new one, lossless. */
|
|
18081
|
+
function convertLegacyRecipe(legacy) {
|
|
18082
|
+
const { triggers, conditions, ...rest } = legacy;
|
|
18083
|
+
return {
|
|
18084
|
+
when: convertLegacyWhen({
|
|
18085
|
+
triggers,
|
|
18086
|
+
conditions
|
|
18087
|
+
}),
|
|
18088
|
+
...rest
|
|
18089
|
+
};
|
|
18090
|
+
}
|
|
18091
|
+
/**
|
|
18092
|
+
* Where recipes are PARSED (the core-blocks cap, the MCP tool, the literal a
|
|
18093
|
+
* saved block carries): the new shape as it is, or the old shape converted to
|
|
18094
|
+
* it. The output type is always the new one.
|
|
18095
|
+
*/
|
|
18096
|
+
var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
|
|
17640
18097
|
new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
17641
18098
|
/**
|
|
17642
18099
|
* Run traces — what an automation (or, later, an Action run) DID, node by node,
|
|
17643
18100
|
* so the editor can paint every block green or red with its reason.
|
|
17644
18101
|
*
|
|
17645
|
-
* A trace is data, not a log: it covers the WHOLE tree — the
|
|
17646
|
-
* every
|
|
17647
|
-
* short-circuit never reached), the run-mode verdict and every action
|
|
17648
|
-
* each addressed by a stable path (`
|
|
17649
|
-
* for an Action run).
|
|
18102
|
+
* A trace is data, not a log: it covers the WHOLE tree — the event leaf that
|
|
18103
|
+
* fired, every node of the `when` tree (containers and leaves, including the
|
|
18104
|
+
* ones a short-circuit never reached), the run-mode verdict and every action
|
|
18105
|
+
* step — each addressed by a stable path (`when.0.1`, `actions.2`, or `steps.N`
|
|
18106
|
+
* for an Action run). Traces written before the single tree carry
|
|
18107
|
+
* `conditions.*` paths and still parse. Skipped
|
|
17650
18108
|
* evaluations are traced too, and identical consecutive skips collapse into a
|
|
17651
18109
|
* repeat count so a trigger that fails every 5 minutes cannot flush real runs.
|
|
17652
18110
|
*
|
|
@@ -17678,6 +18136,8 @@ var TraceNodeOutcomeSchema = _enum([
|
|
|
17678
18136
|
]);
|
|
17679
18137
|
var AutomationTriggerTraceSchema = object({
|
|
17680
18138
|
kind: string().min(1),
|
|
18139
|
+
/** The event leaf that fired (`when`, `when.0.1`); absent on traces written before the single tree. */
|
|
18140
|
+
path: string().min(1).optional(),
|
|
17681
18141
|
outcome: _enum(["pass", "fail"]),
|
|
17682
18142
|
/** Why an event was rejected ("class person not in [dog]") or what fired. */
|
|
17683
18143
|
reason: string(),
|
|
@@ -17685,15 +18145,23 @@ var AutomationTriggerTraceSchema = object({
|
|
|
17685
18145
|
vars: record(string(), TraceValueSchema)
|
|
17686
18146
|
});
|
|
17687
18147
|
var AutomationConditionTraceNodeSchema = object({
|
|
17688
|
-
/** `
|
|
18148
|
+
/** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
|
|
17689
18149
|
path: string().min(1),
|
|
17690
18150
|
kind: _enum([
|
|
17691
18151
|
"all",
|
|
17692
18152
|
"any",
|
|
17693
18153
|
"not",
|
|
17694
18154
|
"condition",
|
|
18155
|
+
"device-is",
|
|
17695
18156
|
"expression",
|
|
17696
|
-
"stationary-present"
|
|
18157
|
+
"stationary-present",
|
|
18158
|
+
"device-becomes",
|
|
18159
|
+
"device-event",
|
|
18160
|
+
"schedule",
|
|
18161
|
+
"interval",
|
|
18162
|
+
"manual",
|
|
18163
|
+
"detection",
|
|
18164
|
+
"stationary"
|
|
17697
18165
|
]),
|
|
17698
18166
|
outcome: TraceNodeOutcomeSchema,
|
|
17699
18167
|
reason: string(),
|
|
@@ -17727,6 +18195,12 @@ var AutomationStepTraceSchema = object({
|
|
|
17727
18195
|
deviceId: number().int().optional(),
|
|
17728
18196
|
startedAt: number().optional(),
|
|
17729
18197
|
endedAt: number().optional(),
|
|
18198
|
+
/**
|
|
18199
|
+
* The parameters the step SENT, after `{{vars.x}}` substitution — what the
|
|
18200
|
+
* device was actually asked. Bounded like trigger `vars`; a secret parameter
|
|
18201
|
+
* is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
|
|
18202
|
+
*/
|
|
18203
|
+
args: record(string(), TraceValueSchema).optional(),
|
|
17730
18204
|
outcome: _enum([
|
|
17731
18205
|
"ok",
|
|
17732
18206
|
"error",
|
|
@@ -18476,6 +18950,23 @@ method(object({
|
|
|
18476
18950
|
addonId: string(),
|
|
18477
18951
|
nodeId: string().optional()
|
|
18478
18952
|
}), SettingsSchemaWithValuesSchema.nullable()), method(object({
|
|
18953
|
+
addonId: string(),
|
|
18954
|
+
nodeId: string().optional()
|
|
18955
|
+
}), object({ keys: array(string()) }).nullable(), { auth: "admin" }), method(object({
|
|
18956
|
+
addonId: string(),
|
|
18957
|
+
nodeId: string().optional(),
|
|
18958
|
+
keys: array(string())
|
|
18959
|
+
}), object({
|
|
18960
|
+
removed: array(string()),
|
|
18961
|
+
refused: array(object({
|
|
18962
|
+
key: string(),
|
|
18963
|
+
reason: string()
|
|
18964
|
+
})),
|
|
18965
|
+
secretsCleared: number()
|
|
18966
|
+
}), {
|
|
18967
|
+
kind: "mutation",
|
|
18968
|
+
auth: "admin"
|
|
18969
|
+
}), method(object({
|
|
18479
18970
|
addonId: string(),
|
|
18480
18971
|
deviceId: number(),
|
|
18481
18972
|
nodeId: string().optional()
|
|
@@ -19930,8 +20421,21 @@ var BrokerProviderInfoSchema = object({
|
|
|
19930
20421
|
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
19931
20422
|
* offers it as the default path; the fields the session fills are
|
|
19932
20423
|
* marked `filledBySignIn` in the kind's settings schema.
|
|
20424
|
+
*
|
|
20425
|
+
* `methods` lists the ways the session can sign in, recommended first:
|
|
20426
|
+
* `'device-code'` adds `POST {basePath}/code/start` (body: the form's
|
|
20427
|
+
* settings) answering `{ sessionId, userCode, verificationUrl,
|
|
20428
|
+
* expiresAt }` — the operator types `userCode` at `verificationUrl` on
|
|
20429
|
+
* any device, and the same `status` / `commit` / `cancel` routes follow
|
|
20430
|
+
* (status then carries `method`, `userCode`, `verificationUrl`,
|
|
20431
|
+
* `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
|
|
20432
|
+
* `'proxy'` is the `{basePath}/start` flow above. Absent means
|
|
20433
|
+
* `['proxy']`.
|
|
19933
20434
|
*/
|
|
19934
|
-
signIn: object({
|
|
20435
|
+
signIn: object({
|
|
20436
|
+
basePath: string().min(1),
|
|
20437
|
+
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
20438
|
+
}).optional()
|
|
19935
20439
|
}))
|
|
19936
20440
|
});
|
|
19937
20441
|
var ListInputSchema = object({
|
|
@@ -22443,6 +22947,8 @@ var StorageKindSchema = object({
|
|
|
22443
22947
|
"orphaned",
|
|
22444
22948
|
"dropped"
|
|
22445
22949
|
]),
|
|
22950
|
+
/** An owner declared this kind since the engine started. */
|
|
22951
|
+
declaredThisBoot: boolean(),
|
|
22446
22952
|
/** `null` when the caller asked for no counts (`counts: false`). */
|
|
22447
22953
|
rows: number().int().nonnegative().nullable(),
|
|
22448
22954
|
/** Rows x the average size of a sample; null for an empty kind. */
|
|
@@ -22454,6 +22960,51 @@ var StorageKindSchema = object({
|
|
|
22454
22960
|
/** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
|
|
22455
22961
|
declaration: RecordKindDeclarationSchema
|
|
22456
22962
|
});
|
|
22963
|
+
_enum([
|
|
22964
|
+
"orphaned",
|
|
22965
|
+
"stale",
|
|
22966
|
+
"unknown"
|
|
22967
|
+
]);
|
|
22968
|
+
/** A record kind nobody declared since its owner started, or whose owner is gone. */
|
|
22969
|
+
var StaleKindFindingSchema = object({
|
|
22970
|
+
owner: string(),
|
|
22971
|
+
kind: string(),
|
|
22972
|
+
/** `unknown`: the owner could not be judged (not running, or its state unreadable) - never dropped. */
|
|
22973
|
+
verdict: _enum(["orphaned", "unknown"]),
|
|
22974
|
+
reason: string(),
|
|
22975
|
+
rows: number().int().nonnegative().nullable(),
|
|
22976
|
+
/** local-auth's tables are never dropped by this audit. */
|
|
22977
|
+
protected: boolean()
|
|
22978
|
+
});
|
|
22979
|
+
/** A stored global-settings key the running addon no longer declares. */
|
|
22980
|
+
var StaleSettingsKeyFindingSchema = object({
|
|
22981
|
+
addonId: string(),
|
|
22982
|
+
/** The stored key as written, a per-node `<key>@<nodeId>` suffix included. */
|
|
22983
|
+
key: string(),
|
|
22984
|
+
/**
|
|
22985
|
+
* Keychain refs whose ONLY marker sits under this key: in use by stale data,
|
|
22986
|
+
* not orphans yet. Removing the key releases them.
|
|
22987
|
+
*/
|
|
22988
|
+
secretRefs: array(string())
|
|
22989
|
+
});
|
|
22990
|
+
/** An addon the audit could not judge, with the reason. Never reported as stale. */
|
|
22991
|
+
var StaleUnknownSchema = object({
|
|
22992
|
+
subject: string(),
|
|
22993
|
+
reason: string()
|
|
22994
|
+
});
|
|
22995
|
+
var StaleDataReportSchema = object({
|
|
22996
|
+
/** Epoch ms of the last finished scan; `null` before the first. */
|
|
22997
|
+
scannedAt: number().nullable(),
|
|
22998
|
+
scanning: boolean(),
|
|
22999
|
+
trigger: _enum([
|
|
23000
|
+
"boot",
|
|
23001
|
+
"interval",
|
|
23002
|
+
"manual"
|
|
23003
|
+
]).nullable(),
|
|
23004
|
+
kinds: array(StaleKindFindingSchema),
|
|
23005
|
+
settingsKeys: array(StaleSettingsKeyFindingSchema),
|
|
23006
|
+
unknown: array(StaleUnknownSchema)
|
|
23007
|
+
});
|
|
22457
23008
|
method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
|
|
22458
23009
|
owner: string().min(1).optional(),
|
|
22459
23010
|
/** `false` skips the per-kind row count (a catalog read only). Default true. */
|
|
@@ -22461,7 +23012,30 @@ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: arr
|
|
|
22461
23012
|
}), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
|
|
22462
23013
|
owner: string().min(1),
|
|
22463
23014
|
kind: RecordKindNameSchema
|
|
22464
|
-
}), RecordQueryResultSchema, { auth: "admin" })
|
|
23015
|
+
}), RecordQueryResultSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, {
|
|
23016
|
+
kind: "mutation",
|
|
23017
|
+
auth: "admin"
|
|
23018
|
+
}), method(object({
|
|
23019
|
+
owner: string().min(1),
|
|
23020
|
+
kind: RecordKindNameSchema
|
|
23021
|
+
}), object({
|
|
23022
|
+
deleted: number().int().nonnegative(),
|
|
23023
|
+
secretsReleased: number().int().nonnegative()
|
|
23024
|
+
}), {
|
|
23025
|
+
kind: "mutation",
|
|
23026
|
+
auth: "admin",
|
|
23027
|
+
caller: "required"
|
|
23028
|
+
}), method(object({
|
|
23029
|
+
addonId: string().min(1),
|
|
23030
|
+
key: string().min(1)
|
|
23031
|
+
}), object({
|
|
23032
|
+
removed: boolean(),
|
|
23033
|
+
secretsCleared: number().int().nonnegative()
|
|
23034
|
+
}), {
|
|
23035
|
+
kind: "mutation",
|
|
23036
|
+
auth: "admin",
|
|
23037
|
+
caller: "required"
|
|
23038
|
+
});
|
|
22465
23039
|
/**
|
|
22466
23040
|
* record-entries - the ONE admin surface for every list of user-created entries
|
|
22467
23041
|
* (D881): notification targets, brokers, LLM profiles, anything an addon
|
|
@@ -22600,6 +23174,12 @@ method(object({
|
|
|
22600
23174
|
}), method(object({ deviceId: number().int() }), object({ refs: array(string()).readonly() }), {
|
|
22601
23175
|
kind: "mutation",
|
|
22602
23176
|
auth: "admin"
|
|
23177
|
+
}), method(object({
|
|
23178
|
+
refs: RefListSchema,
|
|
23179
|
+
onBehalfOf: string().min(1)
|
|
23180
|
+
}), object({ released: number().int() }), {
|
|
23181
|
+
kind: "mutation",
|
|
23182
|
+
auth: "admin"
|
|
22603
23183
|
}), method(object({}), array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }), method(object({}), KeychainStateSchema, { auth: "admin" });
|
|
22604
23184
|
/**
|
|
22605
23185
|
* device-history — the ONE mechanism for numeric history (D850).
|
|
@@ -23389,7 +23969,9 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
|
|
|
23389
23969
|
/** Upstream-system identity + rendering hints for the candidate. */
|
|
23390
23970
|
sourceInfo: SourceInfoSchema.optional(),
|
|
23391
23971
|
/** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
|
|
23392
|
-
children: array(DiscoveredChildDeviceSchema).readonly().optional()
|
|
23972
|
+
children: array(DiscoveredChildDeviceSchema).readonly().optional(),
|
|
23973
|
+
/** `childNativeId` of the gateway candidate in the same list this device sits behind. */
|
|
23974
|
+
parentStableId: string().optional()
|
|
23393
23975
|
}));
|
|
23394
23976
|
/**
|
|
23395
23977
|
* Status block — populated by the integration after each
|
|
@@ -43704,6 +44286,79 @@ object({
|
|
|
43704
44286
|
metadata: record(string(), unknown()).optional()
|
|
43705
44287
|
});
|
|
43706
44288
|
/**
|
|
44289
|
+
* HOW a provider can be found on the network. The provider only DECLARES the
|
|
44290
|
+
* mode; the `local-network` scan engine runs it once for every provider that
|
|
44291
|
+
* asked (one multicast browse, one host sweep) and hands the raw hits back to
|
|
44292
|
+
* `classifyDiscoveryHits`. A provider never opens a discovery socket itself,
|
|
44293
|
+
* except through a `custom` mode.
|
|
44294
|
+
*/
|
|
44295
|
+
var DiscoveryModeSchema = discriminatedUnion("kind", [
|
|
44296
|
+
object({
|
|
44297
|
+
kind: literal("mdns"),
|
|
44298
|
+
/** DNS-SD service type, e.g. `_esphomelib._tcp`. */
|
|
44299
|
+
serviceType: string().min(1),
|
|
44300
|
+
/** Regular expression an instance name must match (case-insensitive). */
|
|
44301
|
+
instancePattern: string().optional()
|
|
44302
|
+
}),
|
|
44303
|
+
object({
|
|
44304
|
+
kind: literal("ws-discovery"),
|
|
44305
|
+
/** Probe types (e.g. `dn:NetworkVideoTransmitter`); omitted = any device. */
|
|
44306
|
+
types: array(string()).optional()
|
|
44307
|
+
}),
|
|
44308
|
+
object({
|
|
44309
|
+
kind: literal("ssdp"),
|
|
44310
|
+
searchTarget: string().min(1)
|
|
44311
|
+
}),
|
|
44312
|
+
object({
|
|
44313
|
+
kind: literal("udp-broadcast"),
|
|
44314
|
+
port: number().int().min(1).max(65535),
|
|
44315
|
+
payloadHex: string().regex(/^([0-9a-fA-F]{2})*$/),
|
|
44316
|
+
/** Port replies arrive on, when the device answers somewhere other than the source port. */
|
|
44317
|
+
listenPort: number().int().min(1).max(65535).optional()
|
|
44318
|
+
}),
|
|
44319
|
+
object({
|
|
44320
|
+
kind: literal("http-probe"),
|
|
44321
|
+
port: number().int().min(1).max(65535),
|
|
44322
|
+
path: string().startsWith("/"),
|
|
44323
|
+
method: _enum(["GET", "POST"]).default("GET"),
|
|
44324
|
+
bodyText: string().optional(),
|
|
44325
|
+
contentType: string().optional(),
|
|
44326
|
+
tls: boolean().default(false),
|
|
44327
|
+
/** Status codes that count as a hit. Omitted = any HTTP answer. */
|
|
44328
|
+
acceptStatus: array(number().int()).optional()
|
|
44329
|
+
}),
|
|
44330
|
+
object({
|
|
44331
|
+
kind: literal("custom"),
|
|
44332
|
+
/** Why the engine cannot do it (an allowlisted exception, e.g. a vendor UDP protocol). */
|
|
44333
|
+
reason: string().min(1)
|
|
44334
|
+
})
|
|
44335
|
+
]);
|
|
44336
|
+
/** What the engine saw on the wire for ONE declared mode; the provider classifies it. */
|
|
44337
|
+
var DiscoveryHitSchema = object({
|
|
44338
|
+
/** Index into the provider's own `getDiscoveryModes()` array. */
|
|
44339
|
+
modeIndex: number().int().min(0),
|
|
44340
|
+
host: string(),
|
|
44341
|
+
port: number().int(),
|
|
44342
|
+
http: object({
|
|
44343
|
+
status: number().int(),
|
|
44344
|
+
headers: record(string(), string()),
|
|
44345
|
+
/** At most 16 KiB. */
|
|
44346
|
+
bodyText: string()
|
|
44347
|
+
}).optional(),
|
|
44348
|
+
mdns: object({
|
|
44349
|
+
instance: string(),
|
|
44350
|
+
host: string(),
|
|
44351
|
+
port: number().int(),
|
|
44352
|
+
txt: record(string(), string())
|
|
44353
|
+
}).optional(),
|
|
44354
|
+
udp: object({ payloadHex: string() }).optional(),
|
|
44355
|
+
wsd: object({
|
|
44356
|
+
xaddrs: array(string()),
|
|
44357
|
+
types: array(string()),
|
|
44358
|
+
scopes: array(string())
|
|
44359
|
+
}).optional()
|
|
44360
|
+
});
|
|
44361
|
+
/**
|
|
43707
44362
|
* Candidate handed back from discovery and accepted by
|
|
43708
44363
|
* `adoptDiscoveredDevice`. Shape mirrors the in-process
|
|
43709
44364
|
* `DiscoveredDevice` interface used by `DeviceDiscovery`.
|
|
@@ -43738,7 +44393,17 @@ var DiscoveryCandidateSchema = object({
|
|
|
43738
44393
|
/** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
|
|
43739
44394
|
onboardedDeviceId: number().optional(),
|
|
43740
44395
|
/** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
|
|
43741
|
-
onboardedName: string().optional()
|
|
44396
|
+
onboardedName: string().optional(),
|
|
44397
|
+
/**
|
|
44398
|
+
* This candidate is an INTEGRATION to set up (a gateway, a hub, a controller),
|
|
44399
|
+
* not a device to adopt. Replaces the `prefilledConfig.setupIntegration` marker,
|
|
44400
|
+
* which is still read for one release.
|
|
44401
|
+
*/
|
|
44402
|
+
setupIntegration: boolean().optional(),
|
|
44403
|
+
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
44404
|
+
parentStableId: string().optional(),
|
|
44405
|
+
/** `false` = shown for context but not pickable. Default true. */
|
|
44406
|
+
selectable: boolean().optional()
|
|
43742
44407
|
});
|
|
43743
44408
|
/**
|
|
43744
44409
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -43791,6 +44456,12 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
|
|
|
43791
44456
|
}), method(object({}), boolean()), method(object({ params: record(string(), unknown()).optional() }), array(DiscoveryCandidateSchema), {
|
|
43792
44457
|
kind: "mutation",
|
|
43793
44458
|
auth: "admin"
|
|
44459
|
+
}), method(object({}), array(DiscoveryModeSchema)), method(object({
|
|
44460
|
+
hits: array(DiscoveryHitSchema),
|
|
44461
|
+
params: record(string(), unknown()).optional()
|
|
44462
|
+
}), array(DiscoveryCandidateSchema), {
|
|
44463
|
+
kind: "mutation",
|
|
44464
|
+
auth: "admin"
|
|
43794
44465
|
}), method(object({}), CreationSchemaOutputSchema), method(object({}), object({ deviceType: _enum(DeviceType).nullable() })), method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
|
|
43795
44466
|
kind: "mutation",
|
|
43796
44467
|
auth: "admin"
|
|
@@ -44518,17 +45189,23 @@ method(object({
|
|
|
44518
45189
|
}), method(object({}), object({ providers: array(object({
|
|
44519
45190
|
addonId: string(),
|
|
44520
45191
|
label: string()
|
|
44521
|
-
})).readonly() }), { auth: "admin" }), method(
|
|
44522
|
-
|
|
44523
|
-
|
|
44524
|
-
|
|
44525
|
-
|
|
44526
|
-
|
|
44527
|
-
|
|
44528
|
-
|
|
44529
|
-
}),
|
|
45192
|
+
})).readonly() }), { auth: "admin" }), method(
|
|
45193
|
+
/** `subnets`: extra CIDRs for THIS scan, on top of the hub's own and the saved list. */
|
|
45194
|
+
object({ subnets: array(string()).optional() }),
|
|
45195
|
+
object({ groups: array(object({
|
|
45196
|
+
addonId: string(),
|
|
45197
|
+
label: string(),
|
|
45198
|
+
candidates: array(DiscoveryCandidateSchema).readonly(),
|
|
45199
|
+
error: string().nullable()
|
|
45200
|
+
})).readonly() }),
|
|
45201
|
+
{
|
|
45202
|
+
kind: "mutation",
|
|
45203
|
+
auth: "admin"
|
|
45204
|
+
}
|
|
45205
|
+
), method(object({
|
|
44530
45206
|
addonId: string(),
|
|
44531
|
-
params: record(string(), unknown()).optional()
|
|
45207
|
+
params: record(string(), unknown()).optional(),
|
|
45208
|
+
subnets: array(string()).optional()
|
|
44532
45209
|
}), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
|
|
44533
45210
|
kind: "mutation",
|
|
44534
45211
|
auth: "admin"
|
|
@@ -58264,6 +58941,12 @@ var TerminalInstanceInfoSchema = object({
|
|
|
58264
58941
|
instanceId: string(),
|
|
58265
58942
|
cameraStableId: string(),
|
|
58266
58943
|
nodeId: string(),
|
|
58944
|
+
/**
|
|
58945
|
+
* False while the node the instance is bound to is not in the cluster
|
|
58946
|
+
* topology (forgotten, renamed, re-paired). The instance is kept; the
|
|
58947
|
+
* operator re-points it with `updateInstance({ targetNodeId })`.
|
|
58948
|
+
*/
|
|
58949
|
+
nodeInCluster: boolean(),
|
|
58267
58950
|
profileId: string(),
|
|
58268
58951
|
profileLabel: string(),
|
|
58269
58952
|
name: string(),
|
|
@@ -58313,6 +58996,8 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
|
|
|
58313
58996
|
auth: "admin"
|
|
58314
58997
|
}), method(object({
|
|
58315
58998
|
instanceId: string().min(1),
|
|
58999
|
+
/** Re-point the instance to another node; the profile must exist there. */
|
|
59000
|
+
targetNodeId: string().min(1).max(256).optional(),
|
|
58316
59001
|
name: string().trim().min(1).max(160).optional(),
|
|
58317
59002
|
executable: string().max(1024).optional(),
|
|
58318
59003
|
args: array(string().max(2048)).max(64).optional(),
|
|
@@ -60832,6 +61517,30 @@ object({
|
|
|
60832
61517
|
at: number()
|
|
60833
61518
|
}).nullable()
|
|
60834
61519
|
});
|
|
61520
|
+
var RemoteButtonShapeSchema = _enum([
|
|
61521
|
+
"rect",
|
|
61522
|
+
"rounded",
|
|
61523
|
+
"round",
|
|
61524
|
+
"pill"
|
|
61525
|
+
]);
|
|
61526
|
+
var RemoteButtonToneSchema = _enum([
|
|
61527
|
+
"neutral",
|
|
61528
|
+
"accent",
|
|
61529
|
+
"danger",
|
|
61530
|
+
"red",
|
|
61531
|
+
"green",
|
|
61532
|
+
"yellow",
|
|
61533
|
+
"blue"
|
|
61534
|
+
]);
|
|
61535
|
+
var RemoteGroupFrameSchema = _enum([
|
|
61536
|
+
"none",
|
|
61537
|
+
"panel",
|
|
61538
|
+
"divider",
|
|
61539
|
+
"dpad",
|
|
61540
|
+
"rocker-v",
|
|
61541
|
+
"rocker-h",
|
|
61542
|
+
"keypad"
|
|
61543
|
+
]);
|
|
60835
61544
|
var RemoteLayoutButtonSchema = object({
|
|
60836
61545
|
id: string().min(1).max(40),
|
|
60837
61546
|
/** 0-based grid position. */
|
|
@@ -60841,12 +61550,94 @@ var RemoteLayoutButtonSchema = object({
|
|
|
60841
61550
|
colSpan: number().int().min(1).max(24),
|
|
60842
61551
|
icon: string().min(1).max(60).optional(),
|
|
60843
61552
|
label: string().min(1).max(40).optional(),
|
|
60844
|
-
role: RemoteRoleSchema
|
|
61553
|
+
role: RemoteRoleSchema,
|
|
61554
|
+
/** Absent = the plain rendering every layout had before shapes existed. */
|
|
61555
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
61556
|
+
tone: RemoteButtonToneSchema.optional(),
|
|
61557
|
+
/** The group this button belongs to; it must lie inside that group's rectangle. */
|
|
61558
|
+
groupId: string().min(1).max(40).optional()
|
|
61559
|
+
});
|
|
61560
|
+
/**
|
|
61561
|
+
* A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
|
|
61562
|
+
* titled panel, a section divider, a D-pad ring. `componentId` names the palette
|
|
61563
|
+
* entry it came from (informational: the layout is self-contained).
|
|
61564
|
+
*/
|
|
61565
|
+
var RemoteLayoutGroupSchema = object({
|
|
61566
|
+
id: string().min(1).max(40),
|
|
61567
|
+
componentId: string().min(1).max(80),
|
|
61568
|
+
frame: RemoteGroupFrameSchema,
|
|
61569
|
+
row: number().int().min(0).max(24),
|
|
61570
|
+
col: number().int().min(0).max(24),
|
|
61571
|
+
rowSpan: number().int().min(1).max(24),
|
|
61572
|
+
colSpan: number().int().min(1).max(24),
|
|
61573
|
+
title: string().min(1).max(40).optional()
|
|
60845
61574
|
});
|
|
61575
|
+
function rectsOverlap(a, b) {
|
|
61576
|
+
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;
|
|
61577
|
+
}
|
|
61578
|
+
function rectContains(outer, inner) {
|
|
61579
|
+
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;
|
|
61580
|
+
}
|
|
61581
|
+
/**
|
|
61582
|
+
* The group rules of a layout: inside the grid, no two groups overlap, a
|
|
61583
|
+
* member lies inside its group, a divider holds nothing, and a button that is
|
|
61584
|
+
* not a member never sits inside someone else's rectangle (a group moves as
|
|
61585
|
+
* one thing, so a stray key under it would be left behind).
|
|
61586
|
+
*/
|
|
61587
|
+
function refineGroups(grid, ctx) {
|
|
61588
|
+
const groups = grid.groups ?? [];
|
|
61589
|
+
const byId = /* @__PURE__ */ new Map();
|
|
61590
|
+
const fail = (message, path) => {
|
|
61591
|
+
ctx.addIssue({
|
|
61592
|
+
code: "custom",
|
|
61593
|
+
message,
|
|
61594
|
+
path
|
|
61595
|
+
});
|
|
61596
|
+
};
|
|
61597
|
+
groups.forEach((group, index) => {
|
|
61598
|
+
if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
|
|
61599
|
+
"groups",
|
|
61600
|
+
index,
|
|
61601
|
+
"id"
|
|
61602
|
+
]);
|
|
61603
|
+
byId.set(group.id, group);
|
|
61604
|
+
if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
|
|
61605
|
+
for (let other = 0; other < index; other += 1) {
|
|
61606
|
+
const earlier = groups[other];
|
|
61607
|
+
if (earlier !== void 0 && rectsOverlap(earlier, group)) {
|
|
61608
|
+
fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
|
|
61609
|
+
return;
|
|
61610
|
+
}
|
|
61611
|
+
}
|
|
61612
|
+
});
|
|
61613
|
+
grid.buttons.forEach((button, index) => {
|
|
61614
|
+
if (button.groupId !== void 0) {
|
|
61615
|
+
const group = byId.get(button.groupId);
|
|
61616
|
+
if (group === void 0) {
|
|
61617
|
+
fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
|
|
61618
|
+
"buttons",
|
|
61619
|
+
index,
|
|
61620
|
+
"groupId"
|
|
61621
|
+
]);
|
|
61622
|
+
return;
|
|
61623
|
+
}
|
|
61624
|
+
if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
|
|
61625
|
+
"buttons",
|
|
61626
|
+
index,
|
|
61627
|
+
"groupId"
|
|
61628
|
+
]);
|
|
61629
|
+
if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
|
|
61630
|
+
return;
|
|
61631
|
+
}
|
|
61632
|
+
const foreign = groups.find((group) => rectsOverlap(group, button));
|
|
61633
|
+
if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
|
|
61634
|
+
});
|
|
61635
|
+
}
|
|
60846
61636
|
object({
|
|
60847
61637
|
columns: number().int().min(1).max(24),
|
|
60848
61638
|
rows: number().int().min(1).max(24),
|
|
60849
|
-
buttons: array(RemoteLayoutButtonSchema).max(200)
|
|
61639
|
+
buttons: array(RemoteLayoutButtonSchema).max(200),
|
|
61640
|
+
groups: array(RemoteLayoutGroupSchema).max(50).optional()
|
|
60850
61641
|
}).superRefine((grid, ctx) => {
|
|
60851
61642
|
const ids = /* @__PURE__ */ new Set();
|
|
60852
61643
|
const taken = /* @__PURE__ */ new Map();
|
|
@@ -60883,6 +61674,7 @@ object({
|
|
|
60883
61674
|
taken.set(cell, button.id);
|
|
60884
61675
|
}
|
|
60885
61676
|
});
|
|
61677
|
+
refineGroups(grid, ctx);
|
|
60886
61678
|
});
|
|
60887
61679
|
var RemoteBoundCommandSchema = object({
|
|
60888
61680
|
role: RemoteRoleSchema,
|
|
@@ -61014,6 +61806,91 @@ method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSche
|
|
|
61014
61806
|
kind: "mutation",
|
|
61015
61807
|
auth: "admin"
|
|
61016
61808
|
});
|
|
61809
|
+
var RemoteComponentCategorySchema = _enum([
|
|
61810
|
+
"power",
|
|
61811
|
+
"navigation",
|
|
61812
|
+
"volume",
|
|
61813
|
+
"numbers",
|
|
61814
|
+
"media",
|
|
61815
|
+
"colour",
|
|
61816
|
+
"apps",
|
|
61817
|
+
"layout"
|
|
61818
|
+
]);
|
|
61819
|
+
/** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
|
|
61820
|
+
var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
|
|
61821
|
+
var RemoteComponentButtonSchema = object({
|
|
61822
|
+
/** Joined to the instance id when the component is inserted. */
|
|
61823
|
+
idSuffix: string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
|
|
61824
|
+
row: number().int().min(0).max(24),
|
|
61825
|
+
col: number().int().min(0).max(24),
|
|
61826
|
+
rowSpan: number().int().min(1).max(24),
|
|
61827
|
+
colSpan: number().int().min(1).max(24),
|
|
61828
|
+
role: RemoteRoleSchema,
|
|
61829
|
+
icon: string().min(1).max(60).optional(),
|
|
61830
|
+
label: string().min(1).max(40).optional(),
|
|
61831
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
61832
|
+
tone: RemoteButtonToneSchema.optional()
|
|
61833
|
+
});
|
|
61834
|
+
var RemoteLayoutComponentSchema = object({
|
|
61835
|
+
id: string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
|
|
61836
|
+
name: string().min(1).max(60),
|
|
61837
|
+
nameI18n: object({
|
|
61838
|
+
en: string().min(1).max(60),
|
|
61839
|
+
it: string().min(1).max(60)
|
|
61840
|
+
}).optional(),
|
|
61841
|
+
/** A display icon key, resolved by the UI like a button icon. */
|
|
61842
|
+
icon: string().min(1).max(60),
|
|
61843
|
+
category: RemoteComponentCategorySchema,
|
|
61844
|
+
size: object({
|
|
61845
|
+
rows: number().int().min(1).max(24),
|
|
61846
|
+
cols: number().int().min(1).max(24)
|
|
61847
|
+
}),
|
|
61848
|
+
/** The frame drawn around the whole component, or null for loose buttons. */
|
|
61849
|
+
group: object({
|
|
61850
|
+
frame: RemoteGroupFrameSchema,
|
|
61851
|
+
title: string().min(1).max(40).optional()
|
|
61852
|
+
}).nullable(),
|
|
61853
|
+
buttons: array(RemoteComponentButtonSchema).max(40)
|
|
61854
|
+
}).superRefine((component, ctx) => {
|
|
61855
|
+
const suffixes = /* @__PURE__ */ new Set();
|
|
61856
|
+
component.buttons.forEach((button, index) => {
|
|
61857
|
+
if (suffixes.has(button.idSuffix)) ctx.addIssue({
|
|
61858
|
+
code: "custom",
|
|
61859
|
+
message: `button "${button.idSuffix}" is listed twice`,
|
|
61860
|
+
path: [
|
|
61861
|
+
"buttons",
|
|
61862
|
+
index,
|
|
61863
|
+
"idSuffix"
|
|
61864
|
+
]
|
|
61865
|
+
});
|
|
61866
|
+
suffixes.add(button.idSuffix);
|
|
61867
|
+
if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
|
|
61868
|
+
ctx.addIssue({
|
|
61869
|
+
code: "custom",
|
|
61870
|
+
message: `button "${button.idSuffix}" leaves the component`,
|
|
61871
|
+
path: ["buttons", index]
|
|
61872
|
+
});
|
|
61873
|
+
return;
|
|
61874
|
+
}
|
|
61875
|
+
for (let other = 0; other < index; other += 1) {
|
|
61876
|
+
const earlier = component.buttons[other];
|
|
61877
|
+
if (earlier !== void 0 && rectsOverlap(earlier, button)) {
|
|
61878
|
+
ctx.addIssue({
|
|
61879
|
+
code: "custom",
|
|
61880
|
+
message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
|
|
61881
|
+
path: ["buttons", index]
|
|
61882
|
+
});
|
|
61883
|
+
return;
|
|
61884
|
+
}
|
|
61885
|
+
}
|
|
61886
|
+
});
|
|
61887
|
+
if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
|
|
61888
|
+
code: "custom",
|
|
61889
|
+
message: "a divider component holds no buttons",
|
|
61890
|
+
path: ["buttons"]
|
|
61891
|
+
});
|
|
61892
|
+
});
|
|
61893
|
+
method(object({}), array(RemoteLayoutComponentSchema), { auth: "protected" });
|
|
61017
61894
|
DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
61018
61895
|
kind: "mutation",
|
|
61019
61896
|
auth: "protected"
|
|
@@ -63155,7 +64032,9 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
63155
64032
|
id: string(),
|
|
63156
64033
|
settings: record(string(), unknown()),
|
|
63157
64034
|
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
63158
|
-
secretKeys: array(string()).optional()
|
|
64035
|
+
secretKeys: array(string()).optional(),
|
|
64036
|
+
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
64037
|
+
skipRestart: boolean().optional()
|
|
63159
64038
|
}), object({ success: boolean() }), {
|
|
63160
64039
|
kind: "mutation",
|
|
63161
64040
|
auth: "admin"
|
|
@@ -63632,6 +64511,39 @@ var TlsStatusSchema = object({
|
|
|
63632
64511
|
hasCa: boolean()
|
|
63633
64512
|
}).nullable()
|
|
63634
64513
|
});
|
|
64514
|
+
/** One provider's ask of the scan engine: the modes it declared. */
|
|
64515
|
+
var ScanRequestSchema = object({
|
|
64516
|
+
providerId: string().min(1),
|
|
64517
|
+
modes: array(DiscoveryModeSchema)
|
|
64518
|
+
});
|
|
64519
|
+
var ScanErrorSchema = object({
|
|
64520
|
+
/** Absent for a failure that is the scan's own (a bad subnet, a socket that would not open). */
|
|
64521
|
+
providerId: string().optional(),
|
|
64522
|
+
/** The mode family that failed (`mdns`, `http-probe`, …). */
|
|
64523
|
+
mode: string().optional(),
|
|
64524
|
+
message: string()
|
|
64525
|
+
});
|
|
64526
|
+
/** What one scan did, per mode family; the engine logs the same numbers once. */
|
|
64527
|
+
var ScanStatsSchema = object({
|
|
64528
|
+
hostsProbed: number().int(),
|
|
64529
|
+
hits: number().int(),
|
|
64530
|
+
timeouts: number().int(),
|
|
64531
|
+
byMode: record(string(), object({
|
|
64532
|
+
probed: number().int(),
|
|
64533
|
+
hits: number().int(),
|
|
64534
|
+
timeouts: number().int()
|
|
64535
|
+
}))
|
|
64536
|
+
});
|
|
64537
|
+
var ScanNetworkResultSchema = object({
|
|
64538
|
+
hits: array(object({
|
|
64539
|
+
providerId: string(),
|
|
64540
|
+
hit: DiscoveryHitSchema
|
|
64541
|
+
})),
|
|
64542
|
+
/** CIDRs actually covered: the hub's own /24s, the saved list and the per-call list. */
|
|
64543
|
+
subnetsScanned: array(string()),
|
|
64544
|
+
errors: array(ScanErrorSchema),
|
|
64545
|
+
stats: ScanStatsSchema
|
|
64546
|
+
});
|
|
63635
64547
|
method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
|
|
63636
64548
|
/**
|
|
63637
64549
|
* LEGACY HINT — do not send from new code. Kept optional so clients
|
|
@@ -63686,6 +64598,16 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
|
|
|
63686
64598
|
}), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
|
|
63687
64599
|
kind: "mutation",
|
|
63688
64600
|
auth: "admin"
|
|
64601
|
+
}), method(object({
|
|
64602
|
+
requests: array(ScanRequestSchema),
|
|
64603
|
+
subnets: array(string()).optional()
|
|
64604
|
+
}), ScanNetworkResultSchema, {
|
|
64605
|
+
kind: "mutation",
|
|
64606
|
+
auth: "admin",
|
|
64607
|
+
timeoutMs: 9e4
|
|
64608
|
+
}), method(_void(), object({ subnets: array(string()) }), { auth: "admin" }), method(object({ subnets: array(string()) }), object({ subnets: array(string()) }), {
|
|
64609
|
+
kind: "mutation",
|
|
64610
|
+
auth: "admin"
|
|
63689
64611
|
}), method(_void(), TlsStatusSchema, {
|
|
63690
64612
|
kind: "mutation",
|
|
63691
64613
|
auth: "admin"
|
|
@@ -66969,7 +67891,7 @@ object({
|
|
|
66969
67891
|
});
|
|
66970
67892
|
Object.values(DeviceType);
|
|
66971
67893
|
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
66972
|
-
DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
|
|
67894
|
+
DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
|
|
66973
67895
|
kind: "mutation",
|
|
66974
67896
|
auth: "admin"
|
|
66975
67897
|
});
|
|
@@ -70793,6 +71715,124 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
70793
71715
|
kind: "mutation",
|
|
70794
71716
|
auth: "admin"
|
|
70795
71717
|
});
|
|
71718
|
+
var btn = (idSuffix, row, col, init) => ({
|
|
71719
|
+
idSuffix,
|
|
71720
|
+
row,
|
|
71721
|
+
col,
|
|
71722
|
+
rowSpan: 1,
|
|
71723
|
+
colSpan: 1,
|
|
71724
|
+
...init
|
|
71725
|
+
});
|
|
71726
|
+
function rocker(id, name, it, frame, title, buttons, size) {
|
|
71727
|
+
return {
|
|
71728
|
+
id,
|
|
71729
|
+
name,
|
|
71730
|
+
nameI18n: {
|
|
71731
|
+
en: name,
|
|
71732
|
+
it
|
|
71733
|
+
},
|
|
71734
|
+
icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
|
|
71735
|
+
category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
|
|
71736
|
+
size,
|
|
71737
|
+
group: title === null ? { frame } : {
|
|
71738
|
+
frame,
|
|
71739
|
+
title
|
|
71740
|
+
},
|
|
71741
|
+
buttons
|
|
71742
|
+
};
|
|
71743
|
+
}
|
|
71744
|
+
btn("power", 0, 0, {
|
|
71745
|
+
role: "power",
|
|
71746
|
+
shape: "round",
|
|
71747
|
+
tone: "danger"
|
|
71748
|
+
}), btn("up", 0, 1, {
|
|
71749
|
+
role: "up",
|
|
71750
|
+
icon: "chevron-up"
|
|
71751
|
+
}), btn("left", 1, 0, {
|
|
71752
|
+
role: "left",
|
|
71753
|
+
icon: "chevron-left"
|
|
71754
|
+
}), btn("ok", 1, 1, {
|
|
71755
|
+
role: "ok",
|
|
71756
|
+
shape: "round",
|
|
71757
|
+
tone: "accent"
|
|
71758
|
+
}), btn("right", 1, 2, {
|
|
71759
|
+
role: "right",
|
|
71760
|
+
icon: "chevron-right"
|
|
71761
|
+
}), btn("down", 2, 1, {
|
|
71762
|
+
role: "down",
|
|
71763
|
+
icon: "chevron-down"
|
|
71764
|
+
}), rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
|
|
71765
|
+
role: "volume_up",
|
|
71766
|
+
icon: "plus",
|
|
71767
|
+
shape: "pill"
|
|
71768
|
+
}), btn("down", 1, 0, {
|
|
71769
|
+
role: "volume_down",
|
|
71770
|
+
icon: "minus",
|
|
71771
|
+
shape: "pill"
|
|
71772
|
+
})], {
|
|
71773
|
+
rows: 2,
|
|
71774
|
+
cols: 1
|
|
71775
|
+
}), rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
|
|
71776
|
+
role: "channel_up",
|
|
71777
|
+
icon: "chevron-up",
|
|
71778
|
+
shape: "pill"
|
|
71779
|
+
}), btn("down", 1, 0, {
|
|
71780
|
+
role: "channel_down",
|
|
71781
|
+
icon: "chevron-down",
|
|
71782
|
+
shape: "pill"
|
|
71783
|
+
})], {
|
|
71784
|
+
rows: 2,
|
|
71785
|
+
cols: 1
|
|
71786
|
+
}), rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
|
|
71787
|
+
role: "custom:minus",
|
|
71788
|
+
label: "Minus",
|
|
71789
|
+
icon: "minus",
|
|
71790
|
+
shape: "pill"
|
|
71791
|
+
}), btn("plus", 0, 1, {
|
|
71792
|
+
role: "custom:plus",
|
|
71793
|
+
label: "Plus",
|
|
71794
|
+
icon: "plus",
|
|
71795
|
+
shape: "pill"
|
|
71796
|
+
})], {
|
|
71797
|
+
rows: 1,
|
|
71798
|
+
cols: 2
|
|
71799
|
+
}), [...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, {
|
|
71800
|
+
role: "custom:color_red",
|
|
71801
|
+
label: "Red",
|
|
71802
|
+
tone: "red"
|
|
71803
|
+
}), btn("green", 0, 1, {
|
|
71804
|
+
role: "custom:color_green",
|
|
71805
|
+
label: "Green",
|
|
71806
|
+
tone: "green"
|
|
71807
|
+
}), btn("yellow", 0, 2, {
|
|
71808
|
+
role: "custom:color_yellow",
|
|
71809
|
+
label: "Yellow",
|
|
71810
|
+
tone: "yellow"
|
|
71811
|
+
}), btn("blue", 0, 3, {
|
|
71812
|
+
role: "custom:color_blue",
|
|
71813
|
+
label: "Blue",
|
|
71814
|
+
tone: "blue"
|
|
71815
|
+
}), btn("rewind", 0, 0, {
|
|
71816
|
+
role: "rewind",
|
|
71817
|
+
icon: "rewind"
|
|
71818
|
+
}), btn("play", 0, 1, {
|
|
71819
|
+
role: "play",
|
|
71820
|
+
icon: "play"
|
|
71821
|
+
}), btn("pause", 0, 2, {
|
|
71822
|
+
role: "pause",
|
|
71823
|
+
icon: "pause"
|
|
71824
|
+
}), btn("forward", 0, 3, {
|
|
71825
|
+
role: "fast_forward",
|
|
71826
|
+
icon: "fast-forward"
|
|
71827
|
+
}), btn("stop", 0, 4, {
|
|
71828
|
+
role: "stop",
|
|
71829
|
+
icon: "square"
|
|
71830
|
+
}), [
|
|
71831
|
+
1,
|
|
71832
|
+
2,
|
|
71833
|
+
3,
|
|
71834
|
+
4
|
|
71835
|
+
].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" });
|
|
70796
71836
|
discriminatedUnion("kind", [object({
|
|
70797
71837
|
kind: literal("raw"),
|
|
70798
71838
|
timings: array(number().int()),
|
|
@@ -70803,7 +71843,7 @@ discriminatedUnion("kind", [object({
|
|
|
70803
71843
|
})]);
|
|
70804
71844
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
70805
71845
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
70806
|
-
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;
|
|
71846
|
+
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;
|
|
70807
71847
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
70808
71848
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
70809
71849
|
new Set(CAP_NAMES);
|
|
@@ -70856,7 +71896,7 @@ var AccessoryKind = {
|
|
|
70856
71896
|
PrivacyMask: DeviceRole.PrivacyMask
|
|
70857
71897
|
};
|
|
70858
71898
|
AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
|
|
70859
|
-
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;
|
|
71899
|
+
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;
|
|
70860
71900
|
new Set(Object.values(DeviceType));
|
|
70861
71901
|
Object.freeze({
|
|
70862
71902
|
"accessories.getStatus": {
|
|
@@ -71244,6 +72284,13 @@ Object.freeze({
|
|
|
71244
72284
|
access: "create",
|
|
71245
72285
|
role: "admin"
|
|
71246
72286
|
},
|
|
72287
|
+
"addonSettings.getDeclaredSettingsKeys": {
|
|
72288
|
+
capName: "addon-settings",
|
|
72289
|
+
capScope: "system",
|
|
72290
|
+
addonId: null,
|
|
72291
|
+
access: "view",
|
|
72292
|
+
role: "admin"
|
|
72293
|
+
},
|
|
71247
72294
|
"addonSettings.getDeviceSettings": {
|
|
71248
72295
|
capName: "addon-settings",
|
|
71249
72296
|
capScope: "system",
|
|
@@ -71265,6 +72312,13 @@ Object.freeze({
|
|
|
71265
72312
|
access: "view",
|
|
71266
72313
|
role: "admin"
|
|
71267
72314
|
},
|
|
72315
|
+
"addonSettings.unsetStoredSettingsKeys": {
|
|
72316
|
+
capName: "addon-settings",
|
|
72317
|
+
capScope: "system",
|
|
72318
|
+
addonId: null,
|
|
72319
|
+
access: "create",
|
|
72320
|
+
role: "admin"
|
|
72321
|
+
},
|
|
71268
72322
|
"addonSettings.updateDeviceSettings": {
|
|
71269
72323
|
capName: "addon-settings",
|
|
71270
72324
|
capScope: "system",
|
|
@@ -73680,6 +74734,13 @@ Object.freeze({
|
|
|
73680
74734
|
access: "create",
|
|
73681
74735
|
role: "admin"
|
|
73682
74736
|
},
|
|
74737
|
+
"deviceProvider.classifyDiscoveryHits": {
|
|
74738
|
+
capName: "device-provider",
|
|
74739
|
+
capScope: "system",
|
|
74740
|
+
addonId: null,
|
|
74741
|
+
access: "create",
|
|
74742
|
+
role: "admin"
|
|
74743
|
+
},
|
|
73683
74744
|
"deviceProvider.createDevice": {
|
|
73684
74745
|
capName: "device-provider",
|
|
73685
74746
|
capScope: "system",
|
|
@@ -73708,6 +74769,13 @@ Object.freeze({
|
|
|
73708
74769
|
access: "view",
|
|
73709
74770
|
role: "admin"
|
|
73710
74771
|
},
|
|
74772
|
+
"deviceProvider.getDiscoveryModes": {
|
|
74773
|
+
capName: "device-provider",
|
|
74774
|
+
capScope: "system",
|
|
74775
|
+
addonId: null,
|
|
74776
|
+
access: "view",
|
|
74777
|
+
role: "admin"
|
|
74778
|
+
},
|
|
73711
74779
|
"deviceProvider.getDiscoveryParamsSchema": {
|
|
73712
74780
|
capName: "device-provider",
|
|
73713
74781
|
capScope: "system",
|
|
@@ -74485,6 +75553,13 @@ Object.freeze({
|
|
|
74485
75553
|
access: "create",
|
|
74486
75554
|
role: "admin"
|
|
74487
75555
|
},
|
|
75556
|
+
"keychain.releaseRecordRefs": {
|
|
75557
|
+
capName: "keychain",
|
|
75558
|
+
capScope: "system",
|
|
75559
|
+
addonId: null,
|
|
75560
|
+
access: "create",
|
|
75561
|
+
role: "admin"
|
|
75562
|
+
},
|
|
74488
75563
|
"keychain.resolve": {
|
|
74489
75564
|
capName: "keychain",
|
|
74490
75565
|
capScope: "system",
|
|
@@ -74905,6 +75980,13 @@ Object.freeze({
|
|
|
74905
75980
|
access: "view",
|
|
74906
75981
|
role: "admin"
|
|
74907
75982
|
},
|
|
75983
|
+
"localNetwork.getScanSubnets": {
|
|
75984
|
+
capName: "local-network",
|
|
75985
|
+
capScope: "system",
|
|
75986
|
+
addonId: null,
|
|
75987
|
+
access: "view",
|
|
75988
|
+
role: "admin"
|
|
75989
|
+
},
|
|
74908
75990
|
"localNetwork.getTlsStatus": {
|
|
74909
75991
|
capName: "local-network",
|
|
74910
75992
|
capScope: "system",
|
|
@@ -74954,6 +76036,13 @@ Object.freeze({
|
|
|
74954
76036
|
access: "create",
|
|
74955
76037
|
role: "admin"
|
|
74956
76038
|
},
|
|
76039
|
+
"localNetwork.scanNetwork": {
|
|
76040
|
+
capName: "local-network",
|
|
76041
|
+
capScope: "system",
|
|
76042
|
+
addonId: null,
|
|
76043
|
+
access: "create",
|
|
76044
|
+
role: "admin"
|
|
76045
|
+
},
|
|
74957
76046
|
"localNetwork.setAddressRole": {
|
|
74958
76047
|
capName: "local-network",
|
|
74959
76048
|
capScope: "system",
|
|
@@ -74975,6 +76064,13 @@ Object.freeze({
|
|
|
74975
76064
|
access: "create",
|
|
74976
76065
|
role: "admin"
|
|
74977
76066
|
},
|
|
76067
|
+
"localNetwork.setScanSubnets": {
|
|
76068
|
+
capName: "local-network",
|
|
76069
|
+
capScope: "system",
|
|
76070
|
+
addonId: null,
|
|
76071
|
+
access: "create",
|
|
76072
|
+
role: "admin"
|
|
76073
|
+
},
|
|
74978
76074
|
"localNetwork.setViewerEndpoints": {
|
|
74979
76075
|
capName: "local-network",
|
|
74980
76076
|
capScope: "system",
|
|
@@ -78657,6 +79753,13 @@ Object.freeze({
|
|
|
78657
79753
|
access: "create",
|
|
78658
79754
|
role: "admin"
|
|
78659
79755
|
},
|
|
79756
|
+
"remoteLayoutComponents.listComponents": {
|
|
79757
|
+
capName: "remote-layout-components",
|
|
79758
|
+
capScope: "system",
|
|
79759
|
+
addonId: null,
|
|
79760
|
+
access: "view",
|
|
79761
|
+
role: "admin"
|
|
79762
|
+
},
|
|
78660
79763
|
"runningState.getStatus": {
|
|
78661
79764
|
capName: "running-state",
|
|
78662
79765
|
capScope: "device",
|
|
@@ -79189,6 +80292,13 @@ Object.freeze({
|
|
|
79189
80292
|
access: "create",
|
|
79190
80293
|
role: "admin"
|
|
79191
80294
|
},
|
|
80295
|
+
"storageAdmin.dropOrphanedKind": {
|
|
80296
|
+
capName: "storage-admin",
|
|
80297
|
+
capScope: "system",
|
|
80298
|
+
addonId: null,
|
|
80299
|
+
access: "delete",
|
|
80300
|
+
role: "admin"
|
|
80301
|
+
},
|
|
79192
80302
|
"storageAdmin.listKinds": {
|
|
79193
80303
|
capName: "storage-admin",
|
|
79194
80304
|
capScope: "system",
|
|
@@ -79210,6 +80320,27 @@ Object.freeze({
|
|
|
79210
80320
|
access: "view",
|
|
79211
80321
|
role: "admin"
|
|
79212
80322
|
},
|
|
80323
|
+
"storageAdmin.scanStaleData": {
|
|
80324
|
+
capName: "storage-admin",
|
|
80325
|
+
capScope: "system",
|
|
80326
|
+
addonId: null,
|
|
80327
|
+
access: "create",
|
|
80328
|
+
role: "admin"
|
|
80329
|
+
},
|
|
80330
|
+
"storageAdmin.staleDataReport": {
|
|
80331
|
+
capName: "storage-admin",
|
|
80332
|
+
capScope: "system",
|
|
80333
|
+
addonId: null,
|
|
80334
|
+
access: "view",
|
|
80335
|
+
role: "admin"
|
|
80336
|
+
},
|
|
80337
|
+
"storageAdmin.unsetStaleSettingsKey": {
|
|
80338
|
+
capName: "storage-admin",
|
|
80339
|
+
capScope: "system",
|
|
80340
|
+
addonId: null,
|
|
80341
|
+
access: "create",
|
|
80342
|
+
role: "admin"
|
|
80343
|
+
},
|
|
79213
80344
|
"storageEvictable.evict": {
|
|
79214
80345
|
capName: "storage-evictable",
|
|
79215
80346
|
capScope: "system",
|
|
@@ -84833,6 +85964,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
84833
85964
|
"recording-export": system("recording exports — requested from the timeline, not an automation step"),
|
|
84834
85965
|
records: system(INTERNAL),
|
|
84835
85966
|
remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
|
|
85967
|
+
"remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
|
|
84836
85968
|
"server-management": system(CLUSTER),
|
|
84837
85969
|
"settings-store": system(INTERNAL),
|
|
84838
85970
|
"smtp-provider": system(INTERNAL),
|
|
@@ -84905,7 +86037,7 @@ function projectAction(cap, action) {
|
|
|
84905
86037
|
};
|
|
84906
86038
|
}
|
|
84907
86039
|
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
84908
|
-
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;
|
|
86040
|
+
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;
|
|
84909
86041
|
/**
|
|
84910
86042
|
* Heuristic check — `device.features?.includes(profile.when.hasFeature)`.
|
|
84911
86043
|
*/
|