camstack 1.2.211 → 1.2.213
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 +13 -0
- package/dist/{chunk-NAUJQNCX.js → chunk-MJJWVAEM.js} +966 -175
- package/dist/cli.js +4 -4
- package/dist/{launcher-UXJ4WU3U.js → launcher-6TBFW332.js} +3834 -1129
- package/dist/{server-OYJZ5RCY.js → server-EFXGQ7FD.js} +1926 -705
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// ../types/dist/err-msg-
|
|
3
|
+
// ../types/dist/err-msg-BD5buxvT.mjs
|
|
4
4
|
var DeviceType = /* @__PURE__ */ (function(DeviceType2) {
|
|
5
5
|
DeviceType2["Camera"] = "camera";
|
|
6
6
|
DeviceType2["Hub"] = "hub";
|
|
@@ -37,6 +37,7 @@ var DeviceType = /* @__PURE__ */ (function(DeviceType2) {
|
|
|
37
37
|
DeviceType2["Dishwasher"] = "dishwasher";
|
|
38
38
|
DeviceType2["Washer"] = "washer";
|
|
39
39
|
DeviceType2["Dryer"] = "dryer";
|
|
40
|
+
DeviceType2["Gateway"] = "gateway";
|
|
40
41
|
return DeviceType2;
|
|
41
42
|
})({});
|
|
42
43
|
var DeviceFeature = /* @__PURE__ */ (function(DeviceFeature2) {
|
|
@@ -154,7 +155,7 @@ var DeviceRole = /* @__PURE__ */ (function(DeviceRole2) {
|
|
|
154
155
|
return DeviceRole2;
|
|
155
156
|
})({});
|
|
156
157
|
|
|
157
|
-
// ../types/dist/composition-
|
|
158
|
+
// ../types/dist/composition-C7WdThmP.mjs
|
|
158
159
|
import { z } from "zod";
|
|
159
160
|
import { createHash } from "crypto";
|
|
160
161
|
var RESTREAM_INPUT_PROBE = {
|
|
@@ -1403,7 +1404,7 @@ var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
|
|
|
1403
1404
|
return EventCategory2;
|
|
1404
1405
|
})({});
|
|
1405
1406
|
|
|
1406
|
-
// ../types/dist/sleep-
|
|
1407
|
+
// ../types/dist/sleep-CeyhrcOk.mjs
|
|
1407
1408
|
import { z as z2 } from "zod";
|
|
1408
1409
|
var AUDIO_CHUNK_FORMATS = [
|
|
1409
1410
|
"f32le",
|
|
@@ -7307,6 +7308,149 @@ function metaOf(schema) {
|
|
|
7307
7308
|
function paramMetaOf(field) {
|
|
7308
7309
|
return metaOf(field) ?? metaOf(unwrapSchema(field));
|
|
7309
7310
|
}
|
|
7311
|
+
var MACRO_LABELS = [
|
|
7312
|
+
{
|
|
7313
|
+
id: "person",
|
|
7314
|
+
name: "Person"
|
|
7315
|
+
},
|
|
7316
|
+
{
|
|
7317
|
+
id: "vehicle",
|
|
7318
|
+
name: "Vehicle"
|
|
7319
|
+
},
|
|
7320
|
+
{
|
|
7321
|
+
id: "animal",
|
|
7322
|
+
name: "Animal"
|
|
7323
|
+
},
|
|
7324
|
+
{
|
|
7325
|
+
id: "package",
|
|
7326
|
+
name: "Package"
|
|
7327
|
+
}
|
|
7328
|
+
];
|
|
7329
|
+
var DETECTION_MACRO_CLASSES = new Set(MACRO_LABELS.map((l) => l.id));
|
|
7330
|
+
var COCO_TO_MACRO = {
|
|
7331
|
+
mapping: {
|
|
7332
|
+
person: "person",
|
|
7333
|
+
bicycle: "vehicle",
|
|
7334
|
+
car: "vehicle",
|
|
7335
|
+
motorcycle: "vehicle",
|
|
7336
|
+
airplane: "vehicle",
|
|
7337
|
+
bus: "vehicle",
|
|
7338
|
+
train: "vehicle",
|
|
7339
|
+
truck: "vehicle",
|
|
7340
|
+
boat: "vehicle",
|
|
7341
|
+
bird: "animal",
|
|
7342
|
+
cat: "animal",
|
|
7343
|
+
dog: "animal",
|
|
7344
|
+
horse: "animal",
|
|
7345
|
+
sheep: "animal",
|
|
7346
|
+
cow: "animal",
|
|
7347
|
+
elephant: "animal",
|
|
7348
|
+
bear: "animal",
|
|
7349
|
+
zebra: "animal",
|
|
7350
|
+
giraffe: "animal",
|
|
7351
|
+
suitcase: "package",
|
|
7352
|
+
backpack: "package",
|
|
7353
|
+
handbag: "package"
|
|
7354
|
+
},
|
|
7355
|
+
preserveOriginal: false
|
|
7356
|
+
};
|
|
7357
|
+
var SubClassLabelSchema = z4.object({
|
|
7358
|
+
kind: z4.literal("sub-class"),
|
|
7359
|
+
className: z4.string().min(1)
|
|
7360
|
+
});
|
|
7361
|
+
var IdentityLabelSchema = z4.object({
|
|
7362
|
+
kind: z4.literal("identity"),
|
|
7363
|
+
/** The `face-gallery` identity id (stable across a rename). */
|
|
7364
|
+
identityId: z4.string().min(1),
|
|
7365
|
+
/** The identity's name — what a track-lifecycle event carries as `label`. */
|
|
7366
|
+
name: z4.string().min(1)
|
|
7367
|
+
});
|
|
7368
|
+
var PlateLabelSchema = z4.object({
|
|
7369
|
+
kind: z4.literal("plate"),
|
|
7370
|
+
text: z4.string().min(1)
|
|
7371
|
+
});
|
|
7372
|
+
function macroOf$1(className) {
|
|
7373
|
+
return COCO_TO_MACRO.mapping[className.toLowerCase()] ?? className.toLowerCase();
|
|
7374
|
+
}
|
|
7375
|
+
function subClassBelongs(macro2, label) {
|
|
7376
|
+
return label === void 0 || macroOf$1(label.className) === macro2;
|
|
7377
|
+
}
|
|
7378
|
+
var subClassOf = (macro2) => SubClassLabelSchema.refine((l) => subClassBelongs(macro2, l), { message: `that class is not a kind of ${macro2} in the detection taxonomy` });
|
|
7379
|
+
var DetectionSubjectSchema = z4.discriminatedUnion("macro", [
|
|
7380
|
+
z4.object({
|
|
7381
|
+
macro: z4.literal("animal"),
|
|
7382
|
+
label: subClassOf("animal").optional()
|
|
7383
|
+
}),
|
|
7384
|
+
z4.object({
|
|
7385
|
+
macro: z4.literal("person"),
|
|
7386
|
+
label: IdentityLabelSchema.optional()
|
|
7387
|
+
}),
|
|
7388
|
+
z4.object({
|
|
7389
|
+
macro: z4.literal("vehicle"),
|
|
7390
|
+
label: z4.discriminatedUnion("kind", [SubClassLabelSchema.refine((l) => subClassBelongs("vehicle", l), { message: "that class is not a kind of vehicle in the detection taxonomy" }), PlateLabelSchema]).optional()
|
|
7391
|
+
}),
|
|
7392
|
+
z4.object({
|
|
7393
|
+
macro: z4.literal("package"),
|
|
7394
|
+
label: subClassOf("package").optional()
|
|
7395
|
+
}),
|
|
7396
|
+
z4.object({
|
|
7397
|
+
macro: z4.literal("other"),
|
|
7398
|
+
className: z4.string().min(1)
|
|
7399
|
+
})
|
|
7400
|
+
]);
|
|
7401
|
+
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 \u2014 only a class can narrow it" });
|
|
7402
|
+
function subjectFromClassName(className) {
|
|
7403
|
+
const name = className.trim().toLowerCase();
|
|
7404
|
+
const macro2 = COCO_TO_MACRO.mapping[name];
|
|
7405
|
+
if (new Set(Object.values(COCO_TO_MACRO.mapping)).has(name)) return macroOnly(name);
|
|
7406
|
+
if (macro2 === void 0) return {
|
|
7407
|
+
macro: "other",
|
|
7408
|
+
className
|
|
7409
|
+
};
|
|
7410
|
+
const label = {
|
|
7411
|
+
kind: "sub-class",
|
|
7412
|
+
className: name
|
|
7413
|
+
};
|
|
7414
|
+
switch (macro2) {
|
|
7415
|
+
case "animal":
|
|
7416
|
+
return {
|
|
7417
|
+
macro: "animal",
|
|
7418
|
+
label
|
|
7419
|
+
};
|
|
7420
|
+
case "vehicle":
|
|
7421
|
+
return {
|
|
7422
|
+
macro: "vehicle",
|
|
7423
|
+
label
|
|
7424
|
+
};
|
|
7425
|
+
case "package":
|
|
7426
|
+
return {
|
|
7427
|
+
macro: "package",
|
|
7428
|
+
label
|
|
7429
|
+
};
|
|
7430
|
+
default:
|
|
7431
|
+
return {
|
|
7432
|
+
macro: "other",
|
|
7433
|
+
className
|
|
7434
|
+
};
|
|
7435
|
+
}
|
|
7436
|
+
}
|
|
7437
|
+
function macroOnly(macro2) {
|
|
7438
|
+
switch (macro2) {
|
|
7439
|
+
case "animal":
|
|
7440
|
+
return { macro: "animal" };
|
|
7441
|
+
case "person":
|
|
7442
|
+
return { macro: "person" };
|
|
7443
|
+
case "vehicle":
|
|
7444
|
+
return { macro: "vehicle" };
|
|
7445
|
+
case "package":
|
|
7446
|
+
return { macro: "package" };
|
|
7447
|
+
default:
|
|
7448
|
+
return {
|
|
7449
|
+
macro: "other",
|
|
7450
|
+
className: macro2
|
|
7451
|
+
};
|
|
7452
|
+
}
|
|
7453
|
+
}
|
|
7310
7454
|
var AutomationConditionOperatorSchema = z4.enum([
|
|
7311
7455
|
"eq",
|
|
7312
7456
|
"ne",
|
|
@@ -7317,12 +7461,69 @@ var AutomationConditionOperatorSchema = z4.enum([
|
|
|
7317
7461
|
"contains",
|
|
7318
7462
|
"in"
|
|
7319
7463
|
]);
|
|
7320
|
-
var
|
|
7321
|
-
kind: z4.literal("
|
|
7464
|
+
var DeviceBecomesLeafSchema = z4.object({
|
|
7465
|
+
kind: z4.literal("device-becomes"),
|
|
7466
|
+
deviceId: z4.number().int().nonnegative(),
|
|
7467
|
+
cap: z4.string().min(1),
|
|
7468
|
+
fieldPath: z4.string().min(1),
|
|
7469
|
+
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
7470
|
+
becomes: z4.union([
|
|
7471
|
+
z4.string(),
|
|
7472
|
+
z4.number(),
|
|
7473
|
+
z4.boolean()
|
|
7474
|
+
]).optional(),
|
|
7475
|
+
/** Only on a CHANGE of value, not on every re-report. */
|
|
7476
|
+
edge: z4.boolean().optional(),
|
|
7477
|
+
/** The new value must hold this long before it counts. */
|
|
7478
|
+
forMs: z4.number().int().min(0).max(864e5).optional(),
|
|
7479
|
+
/** Collapse a burst into one occurrence. */
|
|
7480
|
+
debounceMs: z4.number().int().min(0).max(6e5).optional()
|
|
7481
|
+
});
|
|
7482
|
+
var DeviceEventLeafSchema = z4.object({
|
|
7483
|
+
kind: z4.literal("device-event"),
|
|
7484
|
+
/** An `EventCategory` value. */
|
|
7485
|
+
category: z4.string().min(1),
|
|
7486
|
+
deviceId: z4.number().int().nonnegative().optional()
|
|
7487
|
+
});
|
|
7488
|
+
var ScheduleLeafSchema = z4.object({
|
|
7489
|
+
kind: z4.literal("schedule"),
|
|
7490
|
+
cron: z4.string().min(1).max(120)
|
|
7491
|
+
});
|
|
7492
|
+
var ManualLeafSchema = z4.object({ kind: z4.literal("manual") });
|
|
7493
|
+
var IntervalLeafSchema = z4.object({
|
|
7494
|
+
kind: z4.literal("interval"),
|
|
7495
|
+
minutes: z4.number().int().min(1).max(1440)
|
|
7496
|
+
});
|
|
7497
|
+
var DetectionLeafSchema = z4.object({
|
|
7498
|
+
kind: z4.literal("detection"),
|
|
7499
|
+
/** One macro class each, optionally narrowed by a label; a track of any listed subject fires. */
|
|
7500
|
+
subjects: z4.array(DetectionSubjectSchema).min(1),
|
|
7501
|
+
deviceIds: z4.array(z4.number().int().nonnegative()).min(1),
|
|
7502
|
+
/** The track must have visited at least one of these zones. */
|
|
7503
|
+
zones: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7504
|
+
/** Best-so-far confidence the track must reach. */
|
|
7505
|
+
minScore: z4.number().min(0).max(1).optional()
|
|
7506
|
+
});
|
|
7507
|
+
var StationaryLeafSchema = z4.object({
|
|
7508
|
+
kind: z4.literal("stationary"),
|
|
7509
|
+
deviceIds: z4.array(z4.number().int().nonnegative()).min(1),
|
|
7510
|
+
subjects: z4.array(StationarySubjectSchema).min(1).optional(),
|
|
7511
|
+
zones: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7512
|
+
/** `appeared`: it became parked. `departed`: the registry retired it. */
|
|
7513
|
+
phase: z4.enum(["appeared", "departed"])
|
|
7514
|
+
});
|
|
7515
|
+
var DeviceIsLeafSchema = z4.object({
|
|
7516
|
+
kind: z4.literal("device-is"),
|
|
7322
7517
|
deviceId: z4.number().int().nonnegative(),
|
|
7323
7518
|
cap: z4.string().min(1),
|
|
7324
7519
|
fieldPath: z4.string().min(1),
|
|
7325
7520
|
operator: AutomationConditionOperatorSchema,
|
|
7521
|
+
/**
|
|
7522
|
+
* The value. A number on a quantity field is stored in the field's base unit
|
|
7523
|
+
* (D944); a quantity value is the WRITE form, which the save boundary
|
|
7524
|
+
* replaces with the converted number and `unit`. A leaf that still carries
|
|
7525
|
+
* one is never evaluated (it fails).
|
|
7526
|
+
*/
|
|
7326
7527
|
value: z4.union([
|
|
7327
7528
|
z4.string(),
|
|
7328
7529
|
z4.number(),
|
|
@@ -7330,17 +7531,16 @@ var AutomationConditionLeafSchema = z4.object({
|
|
|
7330
7531
|
z4.array(z4.union([z4.string(), z4.number()])),
|
|
7331
7532
|
QuantityValueSchema
|
|
7332
7533
|
]),
|
|
7534
|
+
/** The unit the person picked for `value` (display only; `value` is in the base unit). */
|
|
7333
7535
|
unit: z4.string().min(1).optional()
|
|
7334
7536
|
});
|
|
7335
|
-
var
|
|
7537
|
+
var StationaryPresentLeafSchema = z4.object({
|
|
7336
7538
|
kind: z4.literal("stationary-present"),
|
|
7337
7539
|
deviceId: z4.number().int().nonnegative(),
|
|
7338
|
-
|
|
7339
|
-
classes: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7340
|
-
/** The parked object must be in at least one of these zones. */
|
|
7540
|
+
subjects: z4.array(StationarySubjectSchema).min(1).optional(),
|
|
7341
7541
|
zones: z4.array(z4.string().min(1)).min(1).optional()
|
|
7342
7542
|
});
|
|
7343
|
-
var
|
|
7543
|
+
var ExpressionLeafSchema = z4.object({
|
|
7344
7544
|
kind: z4.literal("expression"),
|
|
7345
7545
|
expr: z4.string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
7346
7546
|
bindings: z4.record(z4.string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
@@ -7352,45 +7552,150 @@ var AutomationConditionExpressionSchema = z4.object({
|
|
|
7352
7552
|
path: ["expr"]
|
|
7353
7553
|
});
|
|
7354
7554
|
});
|
|
7355
|
-
var
|
|
7555
|
+
var AutomationWhenSchema = z4.lazy(() => z4.discriminatedUnion("kind", [
|
|
7356
7556
|
z4.object({
|
|
7357
7557
|
kind: z4.literal("all"),
|
|
7358
|
-
children: z4.array(
|
|
7558
|
+
children: z4.array(AutomationWhenSchema)
|
|
7359
7559
|
}),
|
|
7360
7560
|
z4.object({
|
|
7361
7561
|
kind: z4.literal("any"),
|
|
7362
|
-
children: z4.array(
|
|
7562
|
+
children: z4.array(AutomationWhenSchema)
|
|
7363
7563
|
}),
|
|
7364
7564
|
z4.object({
|
|
7365
7565
|
kind: z4.literal("not"),
|
|
7366
|
-
child:
|
|
7566
|
+
child: AutomationWhenSchema
|
|
7367
7567
|
}),
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7568
|
+
DeviceBecomesLeafSchema,
|
|
7569
|
+
DeviceEventLeafSchema,
|
|
7570
|
+
ScheduleLeafSchema,
|
|
7571
|
+
ManualLeafSchema,
|
|
7572
|
+
IntervalLeafSchema,
|
|
7573
|
+
DetectionLeafSchema,
|
|
7574
|
+
StationaryLeafSchema,
|
|
7575
|
+
DeviceIsLeafSchema,
|
|
7576
|
+
StationaryPresentLeafSchema,
|
|
7577
|
+
ExpressionLeafSchema
|
|
7371
7578
|
]));
|
|
7372
|
-
var
|
|
7579
|
+
var EVENT_KINDS = /* @__PURE__ */ new Set([
|
|
7580
|
+
"device-becomes",
|
|
7581
|
+
"device-event",
|
|
7582
|
+
"schedule",
|
|
7583
|
+
"manual",
|
|
7584
|
+
"interval",
|
|
7585
|
+
"detection",
|
|
7586
|
+
"stationary"
|
|
7587
|
+
]);
|
|
7588
|
+
function isEventLeaf(node) {
|
|
7589
|
+
return EVENT_KINDS.has(node.kind);
|
|
7590
|
+
}
|
|
7591
|
+
function isLeaf(node) {
|
|
7592
|
+
return node.kind !== "all" && node.kind !== "any" && node.kind !== "not";
|
|
7593
|
+
}
|
|
7594
|
+
function whenDepth(node) {
|
|
7595
|
+
if (isLeaf(node)) return 1;
|
|
7596
|
+
if (node.kind === "not") return 1 + whenDepth(node.child);
|
|
7597
|
+
let deepest = 0;
|
|
7598
|
+
for (const child of node.children) deepest = Math.max(deepest, whenDepth(child));
|
|
7599
|
+
return 1 + deepest;
|
|
7600
|
+
}
|
|
7601
|
+
function countWhenLeaves(node) {
|
|
7602
|
+
if (isLeaf(node)) return 1;
|
|
7603
|
+
if (node.kind === "not") return countWhenLeaves(node.child);
|
|
7604
|
+
let total = 0;
|
|
7605
|
+
for (const child of node.children) total += countWhenLeaves(child);
|
|
7606
|
+
return total;
|
|
7607
|
+
}
|
|
7608
|
+
function forEachLeaf(node, visit, path2 = "when") {
|
|
7609
|
+
if (isLeaf(node)) {
|
|
7610
|
+
visit(node, path2);
|
|
7611
|
+
return;
|
|
7612
|
+
}
|
|
7613
|
+
if (node.kind === "not") {
|
|
7614
|
+
forEachLeaf(node.child, visit, `${path2}.0`);
|
|
7615
|
+
return;
|
|
7616
|
+
}
|
|
7617
|
+
node.children.forEach((child, i) => {
|
|
7618
|
+
forEachLeaf(child, visit, `${path2}.${i}`);
|
|
7619
|
+
});
|
|
7620
|
+
}
|
|
7621
|
+
function hasEventLeaves(node) {
|
|
7622
|
+
let found = false;
|
|
7623
|
+
forEachLeaf(node, (leaf) => {
|
|
7624
|
+
if (isEventLeaf(leaf)) found = true;
|
|
7625
|
+
});
|
|
7626
|
+
return found;
|
|
7627
|
+
}
|
|
7628
|
+
function hasEmptyContainer(node) {
|
|
7629
|
+
if (isLeaf(node)) return false;
|
|
7630
|
+
if (node.kind === "not") return hasEmptyContainer(node.child);
|
|
7631
|
+
if (node.children.length === 0) return true;
|
|
7632
|
+
return node.children.some(hasEmptyContainer);
|
|
7633
|
+
}
|
|
7634
|
+
var LEAF_WORDS = {
|
|
7635
|
+
"device-becomes": "a device value that becomes",
|
|
7636
|
+
"device-event": "a device event",
|
|
7637
|
+
schedule: "a schedule",
|
|
7638
|
+
manual: "a manual run",
|
|
7639
|
+
interval: "an interval",
|
|
7640
|
+
detection: "a detection",
|
|
7641
|
+
stationary: "a parked object appearing or leaving"
|
|
7642
|
+
};
|
|
7643
|
+
function say(kind) {
|
|
7644
|
+
return LEAF_WORDS[kind] ?? kind;
|
|
7645
|
+
}
|
|
7646
|
+
function eventNeed(node, path2, negated) {
|
|
7647
|
+
if (isLeaf(node)) {
|
|
7648
|
+
if (!isEventLeaf(node)) return { paths: [] };
|
|
7649
|
+
if (negated) return { error: `"not" is applied to ${say(node.kind)} (${path2}) \u2014 an event is an instant, so "it did not happen" is true at every other moment and can never start a run. Negate a state ("is") instead, or remove the "not"` };
|
|
7650
|
+
return { paths: [{
|
|
7651
|
+
path: path2,
|
|
7652
|
+
kind: node.kind
|
|
7653
|
+
}] };
|
|
7654
|
+
}
|
|
7655
|
+
if (node.kind === "not") return eventNeed(node.child, `${path2}.0`, !negated);
|
|
7656
|
+
const conjunctive = node.kind === "all" !== negated;
|
|
7657
|
+
const parts = [];
|
|
7658
|
+
for (let i = 0; i < node.children.length; i += 1) {
|
|
7659
|
+
const child = node.children[i];
|
|
7660
|
+
if (child === void 0) continue;
|
|
7661
|
+
const need = eventNeed(child, `${path2}.${i}`, negated);
|
|
7662
|
+
if ("error" in need) return need;
|
|
7663
|
+
parts.push(need);
|
|
7664
|
+
}
|
|
7665
|
+
if (conjunctive) return { paths: parts.flatMap((p) => p.paths) };
|
|
7666
|
+
let best;
|
|
7667
|
+
for (const part of parts) if (best === void 0 || part.paths.length < best.paths.length) best = part;
|
|
7668
|
+
return best ?? { paths: [] };
|
|
7669
|
+
}
|
|
7670
|
+
function validateWhenTree(when) {
|
|
7671
|
+
if (hasEmptyContainer(when)) return "a group is empty \u2014 group existing rows instead of leaving a container to fill";
|
|
7672
|
+
const depth = whenDepth(when);
|
|
7673
|
+
if (depth > 4) return `the rules nest ${depth} levels deep \u2014 the limit is 4. "not (any of \u2026)" is the same as "all of (not \u2026)", which is one level shallower`;
|
|
7674
|
+
const leaves = countWhenLeaves(when);
|
|
7675
|
+
if (leaves > 32) return `the rules use ${leaves} rows \u2014 the limit is 32`;
|
|
7676
|
+
const need = eventNeed(when, "when", false);
|
|
7677
|
+
if ("error" in need) return need.error;
|
|
7678
|
+
const [first, second] = need.paths;
|
|
7679
|
+
if (first !== void 0 && second !== void 0) return `${say(first.kind)} (${first.path}) and ${say(second.kind)} (${second.path}) would both have to happen at the same instant, and an event happens at one moment \u2014 this can never run. Use "any of" to start on either, or turn one of them into a state ("is") that must already hold`;
|
|
7680
|
+
return null;
|
|
7681
|
+
}
|
|
7682
|
+
var LegacyTriggerSchema = z4.discriminatedUnion("kind", [
|
|
7373
7683
|
z4.object({
|
|
7374
7684
|
kind: z4.literal("device-state"),
|
|
7375
7685
|
deviceId: z4.number().int().nonnegative(),
|
|
7376
7686
|
cap: z4.string().min(1),
|
|
7377
7687
|
fieldPath: z4.string().min(1),
|
|
7378
|
-
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
7379
7688
|
becomes: z4.union([
|
|
7380
7689
|
z4.string(),
|
|
7381
7690
|
z4.number(),
|
|
7382
7691
|
z4.boolean()
|
|
7383
7692
|
]).optional(),
|
|
7384
|
-
/** Only on a CHANGE of value, not on every re-report. */
|
|
7385
7693
|
edge: z4.boolean().optional(),
|
|
7386
|
-
/** The condition must hold this long before the run starts. */
|
|
7387
7694
|
forMs: z4.number().int().min(0).max(864e5).optional(),
|
|
7388
|
-
/** Collapse a burst into one run. */
|
|
7389
7695
|
debounceMs: z4.number().int().min(0).max(6e5).optional()
|
|
7390
7696
|
}),
|
|
7391
7697
|
z4.object({
|
|
7392
7698
|
kind: z4.literal("device-event"),
|
|
7393
|
-
/** An `EventCategory` value. */
|
|
7394
7699
|
category: z4.string().min(1),
|
|
7395
7700
|
deviceId: z4.number().int().nonnegative().optional()
|
|
7396
7701
|
}),
|
|
@@ -7401,25 +7706,16 @@ var AutomationTriggerSchema = z4.discriminatedUnion("kind", [
|
|
|
7401
7706
|
z4.object({ kind: z4.literal("manual") }),
|
|
7402
7707
|
z4.object({
|
|
7403
7708
|
kind: z4.literal("detection"),
|
|
7404
|
-
/** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
|
|
7405
|
-
* matches every raw class that maps to it; any other name matches
|
|
7406
|
-
* exactly. */
|
|
7407
7709
|
classes: z4.array(z4.string().min(1)).min(1),
|
|
7408
7710
|
deviceIds: z4.array(z4.number().int().nonnegative()).min(1),
|
|
7409
|
-
/** The track must have visited at least one of these zones. */
|
|
7410
7711
|
zones: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7411
|
-
/** Best-so-far confidence the track must reach. */
|
|
7412
7712
|
minScore: z4.number().min(0).max(1).optional()
|
|
7413
7713
|
}),
|
|
7414
7714
|
z4.object({
|
|
7415
7715
|
kind: z4.literal("stationary"),
|
|
7416
7716
|
deviceIds: z4.array(z4.number().int().nonnegative()).min(1),
|
|
7417
|
-
/** Class names; a macro class matches like the detection trigger. */
|
|
7418
7717
|
classes: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7419
|
-
/** The parked object must be in at least one of these zones. */
|
|
7420
7718
|
zones: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7421
|
-
/** `appeared`: it became parked. `departed`: the registry retired it.
|
|
7422
|
-
* The vocabulary of `PipelineAnalyticsStationaryChanged`. */
|
|
7423
7719
|
phase: z4.enum(["appeared", "departed"])
|
|
7424
7720
|
}),
|
|
7425
7721
|
z4.object({
|
|
@@ -7427,6 +7723,123 @@ var AutomationTriggerSchema = z4.discriminatedUnion("kind", [
|
|
|
7427
7723
|
minutes: z4.number().int().min(1).max(1440)
|
|
7428
7724
|
})
|
|
7429
7725
|
]);
|
|
7726
|
+
var LegacyConditionLeafSchema = z4.object({
|
|
7727
|
+
kind: z4.literal("condition"),
|
|
7728
|
+
deviceId: z4.number().int().nonnegative(),
|
|
7729
|
+
cap: z4.string().min(1),
|
|
7730
|
+
fieldPath: z4.string().min(1),
|
|
7731
|
+
operator: AutomationConditionOperatorSchema,
|
|
7732
|
+
value: z4.union([
|
|
7733
|
+
z4.string(),
|
|
7734
|
+
z4.number(),
|
|
7735
|
+
z4.boolean(),
|
|
7736
|
+
z4.array(z4.union([z4.string(), z4.number()])),
|
|
7737
|
+
QuantityValueSchema
|
|
7738
|
+
]),
|
|
7739
|
+
unit: z4.string().min(1).optional()
|
|
7740
|
+
});
|
|
7741
|
+
var LegacyStationaryPresentSchema = z4.object({
|
|
7742
|
+
kind: z4.literal("stationary-present"),
|
|
7743
|
+
deviceId: z4.number().int().nonnegative(),
|
|
7744
|
+
classes: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7745
|
+
zones: z4.array(z4.string().min(1)).min(1).optional()
|
|
7746
|
+
});
|
|
7747
|
+
var LegacyExpressionSchema = z4.object({
|
|
7748
|
+
kind: z4.literal("expression"),
|
|
7749
|
+
expr: z4.string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
7750
|
+
bindings: z4.record(z4.string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
7751
|
+
}).superRefine((src, ctx) => {
|
|
7752
|
+
const err = validateExpressionSource(src);
|
|
7753
|
+
if (err !== null) ctx.addIssue({
|
|
7754
|
+
code: "custom",
|
|
7755
|
+
message: err,
|
|
7756
|
+
path: ["expr"]
|
|
7757
|
+
});
|
|
7758
|
+
});
|
|
7759
|
+
var LegacyConditionSchema = z4.lazy(() => z4.discriminatedUnion("kind", [
|
|
7760
|
+
z4.object({
|
|
7761
|
+
kind: z4.literal("all"),
|
|
7762
|
+
children: z4.array(LegacyConditionSchema)
|
|
7763
|
+
}),
|
|
7764
|
+
z4.object({
|
|
7765
|
+
kind: z4.literal("any"),
|
|
7766
|
+
children: z4.array(LegacyConditionSchema)
|
|
7767
|
+
}),
|
|
7768
|
+
z4.object({
|
|
7769
|
+
kind: z4.literal("not"),
|
|
7770
|
+
child: LegacyConditionSchema
|
|
7771
|
+
}),
|
|
7772
|
+
LegacyConditionLeafSchema,
|
|
7773
|
+
LegacyExpressionSchema,
|
|
7774
|
+
LegacyStationaryPresentSchema
|
|
7775
|
+
]));
|
|
7776
|
+
function convertTrigger(trigger) {
|
|
7777
|
+
switch (trigger.kind) {
|
|
7778
|
+
case "device-state":
|
|
7779
|
+
return {
|
|
7780
|
+
...trigger,
|
|
7781
|
+
kind: "device-becomes"
|
|
7782
|
+
};
|
|
7783
|
+
case "detection": {
|
|
7784
|
+
const { classes, ...rest } = trigger;
|
|
7785
|
+
return {
|
|
7786
|
+
...rest,
|
|
7787
|
+
subjects: classes.map(subjectFromClassName)
|
|
7788
|
+
};
|
|
7789
|
+
}
|
|
7790
|
+
case "stationary": {
|
|
7791
|
+
const { classes, ...rest } = trigger;
|
|
7792
|
+
return {
|
|
7793
|
+
...rest,
|
|
7794
|
+
...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
|
|
7795
|
+
};
|
|
7796
|
+
}
|
|
7797
|
+
default:
|
|
7798
|
+
return trigger;
|
|
7799
|
+
}
|
|
7800
|
+
}
|
|
7801
|
+
function convertLegacyCondition(condition) {
|
|
7802
|
+
switch (condition.kind) {
|
|
7803
|
+
case "condition":
|
|
7804
|
+
return {
|
|
7805
|
+
...condition,
|
|
7806
|
+
kind: "device-is"
|
|
7807
|
+
};
|
|
7808
|
+
case "stationary-present": {
|
|
7809
|
+
const { classes, ...rest } = condition;
|
|
7810
|
+
return {
|
|
7811
|
+
...rest,
|
|
7812
|
+
...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
|
|
7813
|
+
};
|
|
7814
|
+
}
|
|
7815
|
+
case "expression":
|
|
7816
|
+
return condition;
|
|
7817
|
+
case "not":
|
|
7818
|
+
return {
|
|
7819
|
+
kind: "not",
|
|
7820
|
+
child: convertLegacyCondition(condition.child)
|
|
7821
|
+
};
|
|
7822
|
+
case "all":
|
|
7823
|
+
case "any":
|
|
7824
|
+
return {
|
|
7825
|
+
kind: condition.kind,
|
|
7826
|
+
children: condition.children.map(convertLegacyCondition)
|
|
7827
|
+
};
|
|
7828
|
+
}
|
|
7829
|
+
}
|
|
7830
|
+
function convertLegacyWhen(half) {
|
|
7831
|
+
const leaves = half.triggers.map(convertTrigger);
|
|
7832
|
+
const first = leaves[0];
|
|
7833
|
+
const triggers = leaves.length === 1 && first !== void 0 ? first : {
|
|
7834
|
+
kind: "any",
|
|
7835
|
+
children: leaves
|
|
7836
|
+
};
|
|
7837
|
+
if (half.conditions === void 0) return triggers;
|
|
7838
|
+
return {
|
|
7839
|
+
kind: "all",
|
|
7840
|
+
children: [triggers, convertLegacyCondition(half.conditions)]
|
|
7841
|
+
};
|
|
7842
|
+
}
|
|
7430
7843
|
var AutomationWaitStepSchema = z4.object({
|
|
7431
7844
|
kind: z4.literal("wait"),
|
|
7432
7845
|
seconds: z4.number().min(0).max(300)
|
|
@@ -7494,54 +7907,34 @@ var AutomationRunModeSchema = z4.discriminatedUnion("mode", [
|
|
|
7494
7907
|
minutes: z4.number().int().min(1).max(1440)
|
|
7495
7908
|
})
|
|
7496
7909
|
]);
|
|
7497
|
-
var
|
|
7498
|
-
|
|
7499
|
-
conditions: AutomationConditionSchema.optional(),
|
|
7910
|
+
var CurrentAutomationRecipeSchema = z4.object({
|
|
7911
|
+
when: AutomationWhenSchema,
|
|
7500
7912
|
actions: z4.array(AutomationActionSchema),
|
|
7501
7913
|
runMode: AutomationRunModeSchema.optional(),
|
|
7502
7914
|
/** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
|
|
7503
7915
|
keepTraces: z4.number().int().min(1).max(100).optional()
|
|
7504
7916
|
});
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
if (isLeaf(cond)) return 1;
|
|
7522
|
-
if (cond.kind === "not") return countConditionLeaves(cond.child);
|
|
7523
|
-
let total = 0;
|
|
7524
|
-
for (const child of cond.children) total += countConditionLeaves(child);
|
|
7525
|
-
return total;
|
|
7526
|
-
}
|
|
7527
|
-
function findEmptyContainer(cond) {
|
|
7528
|
-
if (isLeaf(cond)) return false;
|
|
7529
|
-
if (cond.kind === "not") return findEmptyContainer(cond.child);
|
|
7530
|
-
if (cond.children.length === 0) return true;
|
|
7531
|
-
return cond.children.some(findEmptyContainer);
|
|
7917
|
+
var LegacyAutomationRecipeSchema = z4.object({
|
|
7918
|
+
triggers: z4.array(LegacyTriggerSchema),
|
|
7919
|
+
conditions: LegacyConditionSchema.optional(),
|
|
7920
|
+
actions: z4.array(AutomationActionSchema),
|
|
7921
|
+
runMode: AutomationRunModeSchema.optional(),
|
|
7922
|
+
keepTraces: z4.number().int().min(1).max(100).optional()
|
|
7923
|
+
});
|
|
7924
|
+
function convertLegacyRecipe(legacy) {
|
|
7925
|
+
const { triggers, conditions, ...rest } = legacy;
|
|
7926
|
+
return {
|
|
7927
|
+
when: convertLegacyWhen({
|
|
7928
|
+
triggers,
|
|
7929
|
+
conditions
|
|
7930
|
+
}),
|
|
7931
|
+
...rest
|
|
7932
|
+
};
|
|
7532
7933
|
}
|
|
7934
|
+
var AutomationRecipeSchema = z4.union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
|
|
7533
7935
|
function validateRecipeBounds(recipe) {
|
|
7534
|
-
if (recipe.triggers.length === 0) return "an automation needs at least one trigger \u2014 without one nothing can start a run";
|
|
7535
7936
|
if (recipe.actions.length === 0) return "an automation needs at least one action \u2014 a rule that does nothing is not a rule";
|
|
7536
|
-
|
|
7537
|
-
if (cond !== void 0) {
|
|
7538
|
-
if (findEmptyContainer(cond)) return "a condition group is empty \u2014 group existing rows instead of leaving a container to fill";
|
|
7539
|
-
const depth = conditionDepth(cond);
|
|
7540
|
-
if (depth > 3) return `conditions nest ${depth} levels deep \u2014 the limit is 3. "not (any of \u2026)" is the same as "all of (not \u2026)", which is one level shallower`;
|
|
7541
|
-
const leaves = countConditionLeaves(cond);
|
|
7542
|
-
if (leaves > 32) return `conditions use ${leaves} leaves \u2014 the limit is 32`;
|
|
7543
|
-
}
|
|
7544
|
-
return null;
|
|
7937
|
+
return validateWhenTree(recipe.when);
|
|
7545
7938
|
}
|
|
7546
7939
|
var RECONCILE_MS = 15e3;
|
|
7547
7940
|
function stepMetaOf(action) {
|
|
@@ -7564,10 +7957,11 @@ function stepMetaOf(action) {
|
|
|
7564
7957
|
function j(value) {
|
|
7565
7958
|
return JSON.stringify(value);
|
|
7566
7959
|
}
|
|
7567
|
-
function
|
|
7960
|
+
function emitLeaf(trigger, path2) {
|
|
7961
|
+
const at = j(path2);
|
|
7568
7962
|
switch (trigger.kind) {
|
|
7569
7963
|
case "schedule":
|
|
7570
|
-
return ` //
|
|
7964
|
+
return ` // event leaf ${path2}: schedule \u2014 armed to the next minute boundary and
|
|
7571
7965
|
// RE-ARMED from the wall clock every tick, so it cannot drift the way a
|
|
7572
7966
|
// setInterval(24h) does. No cron library: a block cannot resolve one.
|
|
7573
7967
|
{
|
|
@@ -7575,7 +7969,7 @@ function emitTrigger(trigger, index) {
|
|
|
7575
7969
|
const arm = (): void => {
|
|
7576
7970
|
timer = setTimeout(() => {
|
|
7577
7971
|
if (cronMatches(${j(trigger.cron)}, new Date())) {
|
|
7578
|
-
void run({ kind: 'schedule', at: Date.now() })
|
|
7972
|
+
void run({ kind: 'schedule', leaf: ${at}, at: Date.now() })
|
|
7579
7973
|
}
|
|
7580
7974
|
arm()
|
|
7581
7975
|
}, nextMinuteBoundary())
|
|
@@ -7584,13 +7978,14 @@ function emitTrigger(trigger, index) {
|
|
|
7584
7978
|
disposers.push(() => { if (timer !== undefined) clearTimeout(timer) })
|
|
7585
7979
|
}`;
|
|
7586
7980
|
case "device-event":
|
|
7587
|
-
return ` //
|
|
7981
|
+
return ` // event leaf ${path2}: device event
|
|
7588
7982
|
disposers.push(
|
|
7589
7983
|
ctx.eventBus.subscribe(
|
|
7590
7984
|
{ category: ${j(trigger.category)}${trigger.deviceId === void 0 ? "" : `, deviceId: ${j(trigger.deviceId)}`} },
|
|
7591
7985
|
(event) => {
|
|
7592
7986
|
void run({
|
|
7593
7987
|
kind: 'device-event',
|
|
7988
|
+
leaf: ${at},
|
|
7594
7989
|
at: Date.now(),
|
|
7595
7990
|
deviceId: event.deviceId,
|
|
7596
7991
|
category: ${j(trigger.category)},
|
|
@@ -7598,9 +7993,9 @@ function emitTrigger(trigger, index) {
|
|
|
7598
7993
|
},
|
|
7599
7994
|
),
|
|
7600
7995
|
)`;
|
|
7601
|
-
case "device-
|
|
7996
|
+
case "device-becomes": {
|
|
7602
7997
|
const becomes = trigger.becomes === void 0 ? "undefined" : j(trigger.becomes);
|
|
7603
|
-
return ` //
|
|
7998
|
+
return ` // event leaf ${path2}: device state \u2014 the mirror is the authority, the
|
|
7604
7999
|
// event only WAKES the evaluation, so a dropped event delays and never loses.
|
|
7605
8000
|
{
|
|
7606
8001
|
let previous: unknown = undefined
|
|
@@ -7621,7 +8016,8 @@ function emitTrigger(trigger, index) {
|
|
|
7621
8016
|
}
|
|
7622
8017
|
heldSince = undefined
|
|
7623
8018
|
void run({
|
|
7624
|
-
kind: 'device-
|
|
8019
|
+
kind: 'device-becomes',
|
|
8020
|
+
leaf: ${at},
|
|
7625
8021
|
at: Date.now(),
|
|
7626
8022
|
deviceId: ${j(trigger.deviceId)},
|
|
7627
8023
|
cap: ${j(trigger.cap)},
|
|
@@ -7641,17 +8037,17 @@ function emitTrigger(trigger, index) {
|
|
|
7641
8037
|
}`;
|
|
7642
8038
|
}
|
|
7643
8039
|
case "manual":
|
|
7644
|
-
return ` //
|
|
8040
|
+
return ` // event leaf ${path2}: manual \u2014 fired by automation-control.trigger
|
|
7645
8041
|
// ("Run actions"), which runs the THEN half with an EMPTY vars bag. A manual
|
|
7646
8042
|
// run bypasses only the cooldown wait, never the single/queued limits.`;
|
|
7647
8043
|
case "interval":
|
|
7648
|
-
return ` //
|
|
8044
|
+
return ` // event leaf ${path2}: interval \u2014 every ${trigger.minutes} min, first run jittered so
|
|
7649
8045
|
// blocks restarted together do not all fire on the same second.
|
|
7650
8046
|
{
|
|
7651
8047
|
let interval: ReturnType<typeof setInterval> | undefined
|
|
7652
8048
|
const first = setTimeout(() => {
|
|
7653
|
-
void run({ kind: 'interval', at: Date.now() })
|
|
7654
|
-
interval = setInterval(() => { void run({ kind: 'interval', at: Date.now() }) }, ${j(trigger.minutes * 6e4)})
|
|
8049
|
+
void run({ kind: 'interval', leaf: ${at}, at: Date.now() })
|
|
8050
|
+
interval = setInterval(() => { void run({ kind: 'interval', leaf: ${at}, at: Date.now() }) }, ${j(trigger.minutes * 6e4)})
|
|
7655
8051
|
}, Math.floor(Math.random() * Math.min(${j(trigger.minutes * 6e4)}, 30_000)) + 1)
|
|
7656
8052
|
disposers.push(() => {
|
|
7657
8053
|
clearTimeout(first)
|
|
@@ -7659,7 +8055,7 @@ function emitTrigger(trigger, index) {
|
|
|
7659
8055
|
})
|
|
7660
8056
|
}`;
|
|
7661
8057
|
case "detection":
|
|
7662
|
-
return ` //
|
|
8058
|
+
return ` // event leaf ${path2}: detection \u2014 ONE fire per (device, track); a rejected event is
|
|
7663
8059
|
// traced with its reason and never fires.
|
|
7664
8060
|
{
|
|
7665
8061
|
const filter = new DetectionTriggerFilter(${j(trigger)})
|
|
@@ -7676,7 +8072,7 @@ function emitTrigger(trigger, index) {
|
|
|
7676
8072
|
})
|
|
7677
8073
|
}
|
|
7678
8074
|
void run(
|
|
7679
|
-
{ kind: 'detection', at: Date.now(), ...decision.vars },
|
|
8075
|
+
{ kind: 'detection', leaf: ${at}, at: Date.now(), ...decision.vars },
|
|
7680
8076
|
decision.reason,
|
|
7681
8077
|
decision.kind === 'fire',
|
|
7682
8078
|
)
|
|
@@ -7685,9 +8081,9 @@ function emitTrigger(trigger, index) {
|
|
|
7685
8081
|
)
|
|
7686
8082
|
}`;
|
|
7687
8083
|
case "stationary":
|
|
7688
|
-
return ` //
|
|
8084
|
+
return ` // event leaf ${path2}: stationary \u2014 a parked object appeared / departed. Stateless:
|
|
7689
8085
|
// the registry emits one event per transition. A dropped event only misses a fire;
|
|
7690
|
-
// the stationary-present
|
|
8086
|
+
// the stationary-present STATE leaf reads the registry, not this event.
|
|
7691
8087
|
{
|
|
7692
8088
|
const trigger = ${j(trigger)} as const
|
|
7693
8089
|
disposers.push(
|
|
@@ -7703,7 +8099,7 @@ function emitTrigger(trigger, index) {
|
|
|
7703
8099
|
})
|
|
7704
8100
|
}
|
|
7705
8101
|
void run(
|
|
7706
|
-
{ kind: 'stationary', at: Date.now(), ...decision.vars },
|
|
8102
|
+
{ kind: 'stationary', leaf: ${at}, at: Date.now(), ...decision.vars },
|
|
7707
8103
|
decision.reason,
|
|
7708
8104
|
decision.kind === 'fire',
|
|
7709
8105
|
)
|
|
@@ -7726,11 +8122,11 @@ ${body}
|
|
|
7726
8122
|
case "cap":
|
|
7727
8123
|
if (Array.isArray(action.deviceId)) return step(` for (const deviceId of ${j(action.deviceId)}) {
|
|
7728
8124
|
scope.setDeviceId(deviceId)
|
|
7729
|
-
await actuate(deviceId, ${j(action.cap)}, ${j(action.method)}, ${j(action.args ?? {})}, vars)
|
|
8125
|
+
await actuate(scope, deviceId, ${j(action.cap)}, ${j(action.method)}, ${j(action.args ?? {})}, vars)
|
|
7730
8126
|
}`);
|
|
7731
8127
|
return step(` const deviceId = ${typeof action.deviceId === "number" ? j(action.deviceId) : `resolveTarget(vars, ${j(action.deviceId.$var)})`}
|
|
7732
8128
|
scope.setDeviceId(deviceId)
|
|
7733
|
-
await actuate(deviceId, ${j(action.cap)}, ${j(action.method)}, ${j(action.args ?? {})}, vars)`);
|
|
8129
|
+
await actuate(scope, deviceId, ${j(action.cap)}, ${j(action.method)}, ${j(action.args ?? {})}, vars)`);
|
|
7734
8130
|
case "action":
|
|
7735
8131
|
return step(` // an Action run, awaited: the step takes THAT run's outcome
|
|
7736
8132
|
const result = await ctx.api.actions.run.mutate({
|
|
@@ -7755,6 +8151,13 @@ function generateAutomationBlock(input) {
|
|
|
7755
8151
|
const { name, recipe } = input;
|
|
7756
8152
|
const runMode = recipe.runMode ?? { mode: "single" };
|
|
7757
8153
|
const stepMeta = recipe.actions.map(stepMetaOf);
|
|
8154
|
+
const eventLeaves = [];
|
|
8155
|
+
forEachLeaf(recipe.when, (leaf, path2) => {
|
|
8156
|
+
if (isEventLeaf(leaf)) eventLeaves.push({
|
|
8157
|
+
leaf,
|
|
8158
|
+
path: path2
|
|
8159
|
+
});
|
|
8160
|
+
});
|
|
7758
8161
|
return `import type {
|
|
7759
8162
|
AddonContext,
|
|
7760
8163
|
AutomationConditionTraceNode,
|
|
@@ -7762,6 +8165,7 @@ function generateAutomationBlock(input) {
|
|
|
7762
8165
|
AutomationRunVerdict,
|
|
7763
8166
|
AutomationStepTrace,
|
|
7764
8167
|
RunnableStep,
|
|
8168
|
+
StepScope,
|
|
7765
8169
|
} from '@camstack/types'
|
|
7766
8170
|
import {
|
|
7767
8171
|
ConditionMirror,
|
|
@@ -7769,9 +8173,9 @@ import {
|
|
|
7769
8173
|
EventCategory,
|
|
7770
8174
|
RunGate,
|
|
7771
8175
|
buildTriggerTrace,
|
|
7772
|
-
|
|
8176
|
+
collectWhenMirrorSources,
|
|
7773
8177
|
coreBlockIdFromAddonId,
|
|
7774
|
-
|
|
8178
|
+
evaluateWhenTree,
|
|
7775
8179
|
evaluateStationaryTrigger,
|
|
7776
8180
|
isSkippedTrace,
|
|
7777
8181
|
mergeSkipTraces,
|
|
@@ -7799,7 +8203,11 @@ const RUN_MODE = ${j(runMode)} as const
|
|
|
7799
8203
|
/** Run traces kept (a ring, held in the hub's runs ledger). */
|
|
7800
8204
|
const KEEP_TRACES = ${recipe.keepTraces ?? 20}
|
|
7801
8205
|
|
|
7802
|
-
|
|
8206
|
+
/** The ONE tree: event leaves (instants) and state leaves (durations). */
|
|
8207
|
+
const WHEN = AUTOMATION_RECIPE.when
|
|
8208
|
+
|
|
8209
|
+
/** No event leaf: the automation fires when the tree goes false \u2192 true, never because it was already true. */
|
|
8210
|
+
const STATE_ONLY = ${!hasEventLeaves(recipe.when)}
|
|
7803
8211
|
|
|
7804
8212
|
/** Skips that repeat are folded into one trace; it goes to the hub after this. */
|
|
7805
8213
|
const SKIP_FLUSH_MS = 5_000
|
|
@@ -7858,7 +8266,8 @@ function matchField(field: string, value: number): boolean {
|
|
|
7858
8266
|
export default function block(ctx: AddonContext) {
|
|
7859
8267
|
const disposers: Array<() => void> = []
|
|
7860
8268
|
const gate = new RunGate(RUN_MODE)
|
|
7861
|
-
const
|
|
8269
|
+
const MIRROR_SOURCES = collectWhenMirrorSources(WHEN)
|
|
8270
|
+
const mirror = new ConditionMirror(MIRROR_SOURCES)
|
|
7862
8271
|
const BLOCK_ID = coreBlockIdFromAddonId(ctx.id)
|
|
7863
8272
|
/** The cooldown stamp: memory is the authority at fire time (D49); the hub ledger is read once at start. */
|
|
7864
8273
|
let lastRunStartedAt: number | null = null
|
|
@@ -8024,6 +8433,7 @@ export default function block(ctx: AddonContext) {
|
|
|
8024
8433
|
}
|
|
8025
8434
|
|
|
8026
8435
|
async function actuate(
|
|
8436
|
+
scope: StepScope,
|
|
8027
8437
|
deviceId: number,
|
|
8028
8438
|
cap: string,
|
|
8029
8439
|
method: string,
|
|
@@ -8033,6 +8443,7 @@ export default function block(ctx: AddonContext) {
|
|
|
8033
8443
|
const substituted = substitute(args, vars)
|
|
8034
8444
|
const resolved: Record<string, unknown> =
|
|
8035
8445
|
substituted !== null && typeof substituted === 'object' ? { ...substituted } : {}
|
|
8446
|
+
scope.setArgs(resolved)
|
|
8036
8447
|
// Forked-addon ctx.api namespaces are tRPC Proxies over a noop function
|
|
8037
8448
|
// (typeof === 'function'), not plain objects \u2014 reject only null/undefined.
|
|
8038
8449
|
const client: unknown = Reflect.get(ctx.api, capToRouterKey(cap))
|
|
@@ -8121,10 +8532,13 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
8121
8532
|
let nodes: AutomationConditionTraceNode[] = []
|
|
8122
8533
|
let steps: AutomationStepTrace[] = []
|
|
8123
8534
|
try {
|
|
8124
|
-
const
|
|
8535
|
+
const firedLeaf = trigger['leaf']
|
|
8536
|
+
const evaluation = evaluateWhenTree(WHEN, mirror, Date.now(), {
|
|
8537
|
+
...(typeof firedLeaf === 'string' ? { firedPath: firedLeaf } : {}),
|
|
8538
|
+
})
|
|
8125
8539
|
nodes = [...evaluation.nodes]
|
|
8126
8540
|
for (const node of nodes) {
|
|
8127
|
-
if (node.kind === '
|
|
8541
|
+
if (node.kind === 'device-is' && node.observed === null && node.deviceId !== undefined) {
|
|
8128
8542
|
ctx.logger.warn('condition leaf value unknown \u2014 treated as false', {
|
|
8129
8543
|
tags: { deviceId: node.deviceId },
|
|
8130
8544
|
path: node.path,
|
|
@@ -8135,7 +8549,7 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
8135
8549
|
}
|
|
8136
8550
|
if (!evaluation.pass) {
|
|
8137
8551
|
outcome = 'skipped-conditions'
|
|
8138
|
-
ctx.logger.debug('automation did not run \u2014
|
|
8552
|
+
ctx.logger.debug('automation did not run \u2014 the rule tree was false at that instant', { trigger, ...deviceTags(trigger) })
|
|
8139
8553
|
return
|
|
8140
8554
|
}
|
|
8141
8555
|
const letThrough = stampUnknown && RUN_MODE.mode === 'cooldown'
|
|
@@ -8234,9 +8648,30 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
8234
8648
|
const ready: Promise<void> = (async () => {
|
|
8235
8649
|
await loadState()
|
|
8236
8650
|
await refreshMirror()
|
|
8651
|
+
stateWatching = true
|
|
8652
|
+
evaluateStateTree()
|
|
8237
8653
|
})()
|
|
8238
8654
|
|
|
8239
|
-
|
|
8655
|
+
/**
|
|
8656
|
+
* Rule 2 \u2014 a tree of states only. It fires when the tree goes false \u2192 true.
|
|
8657
|
+
* The FIRST evaluation only seeds \`treeWasTrue\` (a tree that is already true
|
|
8658
|
+
* when the hub or runner starts must not fire), and an evaluation that read an
|
|
8659
|
+
* unknown value neither seeds nor moves it: unknown is not false.
|
|
8660
|
+
*/
|
|
8661
|
+
let treeWasTrue: boolean | undefined
|
|
8662
|
+
let stateWatching = false
|
|
8663
|
+
function evaluateStateTree(): void {
|
|
8664
|
+
if (!STATE_ONLY || !stateWatching || stopped) return
|
|
8665
|
+
const evaluation = evaluateWhenTree(WHEN, mirror, Date.now())
|
|
8666
|
+
if (evaluation.unknown) return
|
|
8667
|
+
const was = treeWasTrue
|
|
8668
|
+
treeWasTrue = evaluation.pass
|
|
8669
|
+
if (was === false && evaluation.pass) {
|
|
8670
|
+
void run({ kind: 'state', leaf: 'when', at: Date.now() }, 'the rules became true', true)
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8673
|
+
|
|
8674
|
+
if (MIRROR_SOURCES.length > 0) {
|
|
8240
8675
|
disposers.push(
|
|
8241
8676
|
ctx.eventBus.subscribe({ category: EventCategory.DeviceStateChanged }, (event) => {
|
|
8242
8677
|
const data = event.data
|
|
@@ -8244,14 +8679,17 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
8244
8679
|
const capName = data['capName']
|
|
8245
8680
|
if (typeof deviceId === 'number' && typeof capName === 'string') {
|
|
8246
8681
|
mirror.apply(deviceId, capName, data['slice'])
|
|
8682
|
+
evaluateStateTree()
|
|
8247
8683
|
}
|
|
8248
8684
|
}),
|
|
8249
8685
|
)
|
|
8250
|
-
const mirrorReconcile = setInterval(() => {
|
|
8686
|
+
const mirrorReconcile = setInterval(() => {
|
|
8687
|
+
void refreshMirror().then(evaluateStateTree)
|
|
8688
|
+
}, RECONCILE_MS)
|
|
8251
8689
|
disposers.push(() => clearInterval(mirrorReconcile))
|
|
8252
8690
|
}
|
|
8253
8691
|
|
|
8254
|
-
${
|
|
8692
|
+
${eventLeaves.map(({ leaf, path: path2 }) => emitLeaf(leaf, path2)).join("\n\n")}
|
|
8255
8693
|
|
|
8256
8694
|
return () => {
|
|
8257
8695
|
stopped = true
|
|
@@ -8266,52 +8704,6 @@ ${recipe.triggers.map((t2, i) => emitTrigger(t2, i)).join("\n\n")}
|
|
|
8266
8704
|
}
|
|
8267
8705
|
`;
|
|
8268
8706
|
}
|
|
8269
|
-
var MACRO_LABELS = [
|
|
8270
|
-
{
|
|
8271
|
-
id: "person",
|
|
8272
|
-
name: "Person"
|
|
8273
|
-
},
|
|
8274
|
-
{
|
|
8275
|
-
id: "vehicle",
|
|
8276
|
-
name: "Vehicle"
|
|
8277
|
-
},
|
|
8278
|
-
{
|
|
8279
|
-
id: "animal",
|
|
8280
|
-
name: "Animal"
|
|
8281
|
-
},
|
|
8282
|
-
{
|
|
8283
|
-
id: "package",
|
|
8284
|
-
name: "Package"
|
|
8285
|
-
}
|
|
8286
|
-
];
|
|
8287
|
-
var DETECTION_MACRO_CLASSES = new Set(MACRO_LABELS.map((l) => l.id));
|
|
8288
|
-
var COCO_TO_MACRO = {
|
|
8289
|
-
mapping: {
|
|
8290
|
-
person: "person",
|
|
8291
|
-
bicycle: "vehicle",
|
|
8292
|
-
car: "vehicle",
|
|
8293
|
-
motorcycle: "vehicle",
|
|
8294
|
-
airplane: "vehicle",
|
|
8295
|
-
bus: "vehicle",
|
|
8296
|
-
train: "vehicle",
|
|
8297
|
-
truck: "vehicle",
|
|
8298
|
-
boat: "vehicle",
|
|
8299
|
-
bird: "animal",
|
|
8300
|
-
cat: "animal",
|
|
8301
|
-
dog: "animal",
|
|
8302
|
-
horse: "animal",
|
|
8303
|
-
sheep: "animal",
|
|
8304
|
-
cow: "animal",
|
|
8305
|
-
elephant: "animal",
|
|
8306
|
-
bear: "animal",
|
|
8307
|
-
zebra: "animal",
|
|
8308
|
-
giraffe: "animal",
|
|
8309
|
-
suitcase: "package",
|
|
8310
|
-
backpack: "package",
|
|
8311
|
-
handbag: "package"
|
|
8312
|
-
},
|
|
8313
|
-
preserveOriginal: false
|
|
8314
|
-
};
|
|
8315
8707
|
var MACRO_NAMES = new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
8316
8708
|
var TraceValueSchema = z4.union([
|
|
8317
8709
|
z4.string(),
|
|
@@ -8336,6 +8728,8 @@ var TraceNodeOutcomeSchema = z4.enum([
|
|
|
8336
8728
|
]);
|
|
8337
8729
|
var AutomationTriggerTraceSchema = z4.object({
|
|
8338
8730
|
kind: z4.string().min(1),
|
|
8731
|
+
/** The event leaf that fired (`when`, `when.0.1`); absent on traces written before the single tree. */
|
|
8732
|
+
path: z4.string().min(1).optional(),
|
|
8339
8733
|
outcome: z4.enum(["pass", "fail"]),
|
|
8340
8734
|
/** Why an event was rejected ("class person not in [dog]") or what fired. */
|
|
8341
8735
|
reason: z4.string(),
|
|
@@ -8343,15 +8737,23 @@ var AutomationTriggerTraceSchema = z4.object({
|
|
|
8343
8737
|
vars: z4.record(z4.string(), TraceValueSchema)
|
|
8344
8738
|
});
|
|
8345
8739
|
var AutomationConditionTraceNodeSchema = z4.object({
|
|
8346
|
-
/** `
|
|
8740
|
+
/** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
|
|
8347
8741
|
path: z4.string().min(1),
|
|
8348
8742
|
kind: z4.enum([
|
|
8349
8743
|
"all",
|
|
8350
8744
|
"any",
|
|
8351
8745
|
"not",
|
|
8352
8746
|
"condition",
|
|
8747
|
+
"device-is",
|
|
8353
8748
|
"expression",
|
|
8354
|
-
"stationary-present"
|
|
8749
|
+
"stationary-present",
|
|
8750
|
+
"device-becomes",
|
|
8751
|
+
"device-event",
|
|
8752
|
+
"schedule",
|
|
8753
|
+
"interval",
|
|
8754
|
+
"manual",
|
|
8755
|
+
"detection",
|
|
8756
|
+
"stationary"
|
|
8355
8757
|
]),
|
|
8356
8758
|
outcome: TraceNodeOutcomeSchema,
|
|
8357
8759
|
reason: z4.string(),
|
|
@@ -8384,6 +8786,12 @@ var AutomationStepTraceSchema = z4.object({
|
|
|
8384
8786
|
deviceId: z4.number().int().optional(),
|
|
8385
8787
|
startedAt: z4.number().optional(),
|
|
8386
8788
|
endedAt: z4.number().optional(),
|
|
8789
|
+
/**
|
|
8790
|
+
* The parameters the step SENT, after `{{vars.x}}` substitution — what the
|
|
8791
|
+
* device was actually asked. Bounded like trigger `vars`; a secret parameter
|
|
8792
|
+
* is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
|
|
8793
|
+
*/
|
|
8794
|
+
args: z4.record(z4.string(), TraceValueSchema).optional(),
|
|
8387
8795
|
outcome: z4.enum([
|
|
8388
8796
|
"ok",
|
|
8389
8797
|
"error",
|
|
@@ -8651,9 +9059,9 @@ function normalizeLeaf(leaf, resolver, path2) {
|
|
|
8651
9059
|
})]
|
|
8652
9060
|
};
|
|
8653
9061
|
}
|
|
8654
|
-
function normalizeConditionUnits(condition, resolver, path2 = "
|
|
9062
|
+
function normalizeConditionUnits(condition, resolver, path2 = "when") {
|
|
8655
9063
|
switch (condition.kind) {
|
|
8656
|
-
case "
|
|
9064
|
+
case "device-is": {
|
|
8657
9065
|
const { leaf, warnings } = normalizeLeaf(condition, resolver, path2);
|
|
8658
9066
|
return {
|
|
8659
9067
|
value: leaf,
|
|
@@ -8744,20 +9152,20 @@ function normalizeActionUnits(actions, resolver, path2 = "steps") {
|
|
|
8744
9152
|
};
|
|
8745
9153
|
}
|
|
8746
9154
|
function normalizeRecipeUnits(recipe, resolver) {
|
|
8747
|
-
const
|
|
9155
|
+
const when = normalizeConditionUnits(recipe.when, resolver, "when");
|
|
8748
9156
|
const actions = normalizeActionUnits(recipe.actions, resolver, "actions");
|
|
8749
9157
|
return {
|
|
8750
9158
|
value: {
|
|
8751
9159
|
...recipe,
|
|
8752
|
-
|
|
9160
|
+
when: when.value,
|
|
8753
9161
|
actions: actions.value
|
|
8754
9162
|
},
|
|
8755
|
-
warnings: [...
|
|
9163
|
+
warnings: [...when.warnings, ...actions.warnings]
|
|
8756
9164
|
};
|
|
8757
9165
|
}
|
|
8758
9166
|
function stampCondition(condition, resolver) {
|
|
8759
9167
|
switch (condition.kind) {
|
|
8760
|
-
case "
|
|
9168
|
+
case "device-is": {
|
|
8761
9169
|
if (typeof condition.value !== "number" || condition.unit !== void 0) return condition;
|
|
8762
9170
|
const quantity = resolver.fieldQuantity(condition.cap, condition.fieldPath);
|
|
8763
9171
|
if (quantity === void 0 || unitsForQuantity(quantity).length < 2) return condition;
|
|
@@ -8798,7 +9206,7 @@ function stampAction(action, resolver) {
|
|
|
8798
9206
|
function prepareRecipeUnits(recipe, resolver) {
|
|
8799
9207
|
const stamped = {
|
|
8800
9208
|
...recipe,
|
|
8801
|
-
|
|
9209
|
+
when: stampCondition(recipe.when, resolver),
|
|
8802
9210
|
actions: recipe.actions.map((action) => stampAction(action, resolver))
|
|
8803
9211
|
};
|
|
8804
9212
|
try {
|
|
@@ -9287,6 +9695,25 @@ var addonSettingsCapability = {
|
|
|
9287
9695
|
addonId: z4.string(),
|
|
9288
9696
|
nodeId: z4.string().optional()
|
|
9289
9697
|
}), SettingsSchemaWithValuesSchema.nullable()),
|
|
9698
|
+
getDeclaredSettingsKeys: method(z4.object({
|
|
9699
|
+
addonId: z4.string(),
|
|
9700
|
+
nodeId: z4.string().optional()
|
|
9701
|
+
}), z4.object({ keys: z4.array(z4.string()) }).nullable(), { auth: "admin" }),
|
|
9702
|
+
unsetStoredSettingsKeys: method(z4.object({
|
|
9703
|
+
addonId: z4.string(),
|
|
9704
|
+
nodeId: z4.string().optional(),
|
|
9705
|
+
keys: z4.array(z4.string())
|
|
9706
|
+
}), z4.object({
|
|
9707
|
+
removed: z4.array(z4.string()),
|
|
9708
|
+
refused: z4.array(z4.object({
|
|
9709
|
+
key: z4.string(),
|
|
9710
|
+
reason: z4.string()
|
|
9711
|
+
})),
|
|
9712
|
+
secretsCleared: z4.number()
|
|
9713
|
+
}), {
|
|
9714
|
+
kind: "mutation",
|
|
9715
|
+
auth: "admin"
|
|
9716
|
+
}),
|
|
9290
9717
|
getDeviceSettings: method(z4.object({
|
|
9291
9718
|
addonId: z4.string(),
|
|
9292
9719
|
deviceId: z4.number(),
|
|
@@ -10846,8 +11273,21 @@ var BrokerProviderInfoSchema = z4.object({
|
|
|
10846
11273
|
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
10847
11274
|
* offers it as the default path; the fields the session fills are
|
|
10848
11275
|
* marked `filledBySignIn` in the kind's settings schema.
|
|
11276
|
+
*
|
|
11277
|
+
* `methods` lists the ways the session can sign in, recommended first:
|
|
11278
|
+
* `'device-code'` adds `POST {basePath}/code/start` (body: the form's
|
|
11279
|
+
* settings) answering `{ sessionId, userCode, verificationUrl,
|
|
11280
|
+
* expiresAt }` — the operator types `userCode` at `verificationUrl` on
|
|
11281
|
+
* any device, and the same `status` / `commit` / `cancel` routes follow
|
|
11282
|
+
* (status then carries `method`, `userCode`, `verificationUrl`,
|
|
11283
|
+
* `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
|
|
11284
|
+
* `'proxy'` is the `{basePath}/start` flow above. Absent means
|
|
11285
|
+
* `['proxy']`.
|
|
10849
11286
|
*/
|
|
10850
|
-
signIn: z4.object({
|
|
11287
|
+
signIn: z4.object({
|
|
11288
|
+
basePath: z4.string().min(1),
|
|
11289
|
+
methods: z4.array(z4.enum(["device-code", "proxy"])).optional()
|
|
11290
|
+
}).optional()
|
|
10851
11291
|
}))
|
|
10852
11292
|
});
|
|
10853
11293
|
var ListInputSchema = z4.object({
|
|
@@ -14140,6 +14580,8 @@ var StorageKindSchema = z4.object({
|
|
|
14140
14580
|
"orphaned",
|
|
14141
14581
|
"dropped"
|
|
14142
14582
|
]),
|
|
14583
|
+
/** An owner declared this kind since the engine started. */
|
|
14584
|
+
declaredThisBoot: z4.boolean(),
|
|
14143
14585
|
/** `null` when the caller asked for no counts (`counts: false`). */
|
|
14144
14586
|
rows: z4.number().int().nonnegative().nullable(),
|
|
14145
14587
|
/** Rows x the average size of a sample; null for an empty kind. */
|
|
@@ -14151,6 +14593,48 @@ var StorageKindSchema = z4.object({
|
|
|
14151
14593
|
/** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
|
|
14152
14594
|
declaration: RecordKindDeclarationSchema
|
|
14153
14595
|
});
|
|
14596
|
+
z4.enum([
|
|
14597
|
+
"orphaned",
|
|
14598
|
+
"stale",
|
|
14599
|
+
"unknown"
|
|
14600
|
+
]);
|
|
14601
|
+
var StaleKindFindingSchema = z4.object({
|
|
14602
|
+
owner: z4.string(),
|
|
14603
|
+
kind: z4.string(),
|
|
14604
|
+
/** `unknown`: the owner could not be judged (not running, or its state unreadable) - never dropped. */
|
|
14605
|
+
verdict: z4.enum(["orphaned", "unknown"]),
|
|
14606
|
+
reason: z4.string(),
|
|
14607
|
+
rows: z4.number().int().nonnegative().nullable(),
|
|
14608
|
+
/** local-auth's tables are never dropped by this audit. */
|
|
14609
|
+
protected: z4.boolean()
|
|
14610
|
+
});
|
|
14611
|
+
var StaleSettingsKeyFindingSchema = z4.object({
|
|
14612
|
+
addonId: z4.string(),
|
|
14613
|
+
/** The stored key as written, a per-node `<key>@<nodeId>` suffix included. */
|
|
14614
|
+
key: z4.string(),
|
|
14615
|
+
/**
|
|
14616
|
+
* Keychain refs whose ONLY marker sits under this key: in use by stale data,
|
|
14617
|
+
* not orphans yet. Removing the key releases them.
|
|
14618
|
+
*/
|
|
14619
|
+
secretRefs: z4.array(z4.string())
|
|
14620
|
+
});
|
|
14621
|
+
var StaleUnknownSchema = z4.object({
|
|
14622
|
+
subject: z4.string(),
|
|
14623
|
+
reason: z4.string()
|
|
14624
|
+
});
|
|
14625
|
+
var StaleDataReportSchema = z4.object({
|
|
14626
|
+
/** Epoch ms of the last finished scan; `null` before the first. */
|
|
14627
|
+
scannedAt: z4.number().nullable(),
|
|
14628
|
+
scanning: z4.boolean(),
|
|
14629
|
+
trigger: z4.enum([
|
|
14630
|
+
"boot",
|
|
14631
|
+
"interval",
|
|
14632
|
+
"manual"
|
|
14633
|
+
]).nullable(),
|
|
14634
|
+
kinds: z4.array(StaleKindFindingSchema),
|
|
14635
|
+
settingsKeys: z4.array(StaleSettingsKeyFindingSchema),
|
|
14636
|
+
unknown: z4.array(StaleUnknownSchema)
|
|
14637
|
+
});
|
|
14154
14638
|
var storageAdminCapability = {
|
|
14155
14639
|
name: "storage-admin",
|
|
14156
14640
|
scope: "system",
|
|
@@ -14175,7 +14659,49 @@ var storageAdminCapability = {
|
|
|
14175
14659
|
readRecords: method(RecordQueryInputSchema.extend({
|
|
14176
14660
|
owner: z4.string().min(1),
|
|
14177
14661
|
kind: RecordKindNameSchema
|
|
14178
|
-
}), RecordQueryResultSchema, { auth: "admin" })
|
|
14662
|
+
}), RecordQueryResultSchema, { auth: "admin" }),
|
|
14663
|
+
/**
|
|
14664
|
+
* The last stale-data scan (read-only detection): record kinds left behind
|
|
14665
|
+
* and settings keys no running addon declares. Held in memory; a scan runs
|
|
14666
|
+
* at hub boot, every 24 h and on `scanStaleData`.
|
|
14667
|
+
*/
|
|
14668
|
+
staleDataReport: method(z4.object({}), StaleDataReportSchema, { auth: "admin" }),
|
|
14669
|
+
/** Scan now. Detection only: it logs findings (owner and key, never values) and changes nothing. */
|
|
14670
|
+
scanStaleData: method(z4.object({}), StaleDataReportSchema, {
|
|
14671
|
+
kind: "mutation",
|
|
14672
|
+
auth: "admin"
|
|
14673
|
+
}),
|
|
14674
|
+
/**
|
|
14675
|
+
* Drop an ORPHANED kind: its rows, their keychain values, then mark it
|
|
14676
|
+
* dropped. The verdict is re-checked at call time; anything not still
|
|
14677
|
+
* orphaned (or a protected owner) is refused.
|
|
14678
|
+
*/
|
|
14679
|
+
dropOrphanedKind: method(z4.object({
|
|
14680
|
+
owner: z4.string().min(1),
|
|
14681
|
+
kind: RecordKindNameSchema
|
|
14682
|
+
}), z4.object({
|
|
14683
|
+
deleted: z4.number().int().nonnegative(),
|
|
14684
|
+
secretsReleased: z4.number().int().nonnegative()
|
|
14685
|
+
}), {
|
|
14686
|
+
kind: "mutation",
|
|
14687
|
+
auth: "admin",
|
|
14688
|
+
caller: "required"
|
|
14689
|
+
}),
|
|
14690
|
+
/**
|
|
14691
|
+
* Unset one stale settings key on a running addon (an owner-side write that
|
|
14692
|
+
* also clears the keychain values the key held). Re-checked at call time.
|
|
14693
|
+
*/
|
|
14694
|
+
unsetStaleSettingsKey: method(z4.object({
|
|
14695
|
+
addonId: z4.string().min(1),
|
|
14696
|
+
key: z4.string().min(1)
|
|
14697
|
+
}), z4.object({
|
|
14698
|
+
removed: z4.boolean(),
|
|
14699
|
+
secretsCleared: z4.number().int().nonnegative()
|
|
14700
|
+
}), {
|
|
14701
|
+
kind: "mutation",
|
|
14702
|
+
auth: "admin",
|
|
14703
|
+
caller: "required"
|
|
14704
|
+
})
|
|
14179
14705
|
}
|
|
14180
14706
|
};
|
|
14181
14707
|
var EntryKindInfoSchema = z4.object({
|
|
@@ -14307,6 +14833,19 @@ var keychainCapability = {
|
|
|
14307
14833
|
kind: "mutation",
|
|
14308
14834
|
auth: "admin"
|
|
14309
14835
|
}),
|
|
14836
|
+
/**
|
|
14837
|
+
* Release the values rows of a DROPPED record kind held, whatever the kind's
|
|
14838
|
+
* owner (stale-data audit). Reserved to `sqlite-settings`, the engine that
|
|
14839
|
+
* just deleted the rows: only refs scoped `row/records:<kind>/...` are
|
|
14840
|
+
* accepted, and the audit names the operator it acted for. Answers a count.
|
|
14841
|
+
*/
|
|
14842
|
+
releaseRecordRefs: method(z4.object({
|
|
14843
|
+
refs: RefListSchema,
|
|
14844
|
+
onBehalfOf: z4.string().min(1)
|
|
14845
|
+
}), z4.object({ released: z4.number().int() }), {
|
|
14846
|
+
kind: "mutation",
|
|
14847
|
+
auth: "admin"
|
|
14848
|
+
}),
|
|
14310
14849
|
/** The caller's own refs, metadata only. */
|
|
14311
14850
|
listMeta: method(z4.object({}), z4.array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }),
|
|
14312
14851
|
/** Whether values can be served — the save path refuses a write while locked. */
|
|
@@ -15201,7 +15740,9 @@ var DiscoveredChildDeviceSchema = z4.lazy(() => z4.object({
|
|
|
15201
15740
|
/** Upstream-system identity + rendering hints for the candidate. */
|
|
15202
15741
|
sourceInfo: SourceInfoSchema.optional(),
|
|
15203
15742
|
/** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
|
|
15204
|
-
children: z4.array(DiscoveredChildDeviceSchema).readonly().optional()
|
|
15743
|
+
children: z4.array(DiscoveredChildDeviceSchema).readonly().optional(),
|
|
15744
|
+
/** `childNativeId` of the gateway candidate in the same list this device sits behind. */
|
|
15745
|
+
parentStableId: z4.string().optional()
|
|
15205
15746
|
}));
|
|
15206
15747
|
var DeviceDiscoveryStatusSchema = z4.object({
|
|
15207
15748
|
discovered: z4.array(DiscoveredChildDeviceSchema),
|
|
@@ -35419,6 +35960,71 @@ var DiscoveredDeviceSchema = z4.object({
|
|
|
35419
35960
|
type: z4.string(),
|
|
35420
35961
|
metadata: z4.record(z4.string(), z4.unknown()).optional()
|
|
35421
35962
|
});
|
|
35963
|
+
var DiscoveryModeSchema = z4.discriminatedUnion("kind", [
|
|
35964
|
+
z4.object({
|
|
35965
|
+
kind: z4.literal("mdns"),
|
|
35966
|
+
/** DNS-SD service type, e.g. `_esphomelib._tcp`. */
|
|
35967
|
+
serviceType: z4.string().min(1),
|
|
35968
|
+
/** Regular expression an instance name must match (case-insensitive). */
|
|
35969
|
+
instancePattern: z4.string().optional()
|
|
35970
|
+
}),
|
|
35971
|
+
z4.object({
|
|
35972
|
+
kind: z4.literal("ws-discovery"),
|
|
35973
|
+
/** Probe types (e.g. `dn:NetworkVideoTransmitter`); omitted = any device. */
|
|
35974
|
+
types: z4.array(z4.string()).optional()
|
|
35975
|
+
}),
|
|
35976
|
+
z4.object({
|
|
35977
|
+
kind: z4.literal("ssdp"),
|
|
35978
|
+
searchTarget: z4.string().min(1)
|
|
35979
|
+
}),
|
|
35980
|
+
z4.object({
|
|
35981
|
+
kind: z4.literal("udp-broadcast"),
|
|
35982
|
+
port: z4.number().int().min(1).max(65535),
|
|
35983
|
+
payloadHex: z4.string().regex(/^([0-9a-fA-F]{2})*$/),
|
|
35984
|
+
/** Port replies arrive on, when the device answers somewhere other than the source port. */
|
|
35985
|
+
listenPort: z4.number().int().min(1).max(65535).optional()
|
|
35986
|
+
}),
|
|
35987
|
+
z4.object({
|
|
35988
|
+
kind: z4.literal("http-probe"),
|
|
35989
|
+
port: z4.number().int().min(1).max(65535),
|
|
35990
|
+
path: z4.string().startsWith("/"),
|
|
35991
|
+
method: z4.enum(["GET", "POST"]).default("GET"),
|
|
35992
|
+
bodyText: z4.string().optional(),
|
|
35993
|
+
contentType: z4.string().optional(),
|
|
35994
|
+
tls: z4.boolean().default(false),
|
|
35995
|
+
/** Status codes that count as a hit. Omitted = any HTTP answer. */
|
|
35996
|
+
acceptStatus: z4.array(z4.number().int()).optional()
|
|
35997
|
+
}),
|
|
35998
|
+
z4.object({
|
|
35999
|
+
kind: z4.literal("custom"),
|
|
36000
|
+
/** Why the engine cannot do it (an allowlisted exception, e.g. a vendor UDP protocol). */
|
|
36001
|
+
reason: z4.string().min(1)
|
|
36002
|
+
})
|
|
36003
|
+
]);
|
|
36004
|
+
var DiscoveryHitSchema = z4.object({
|
|
36005
|
+
/** Index into the provider's own `getDiscoveryModes()` array. */
|
|
36006
|
+
modeIndex: z4.number().int().min(0),
|
|
36007
|
+
host: z4.string(),
|
|
36008
|
+
port: z4.number().int(),
|
|
36009
|
+
http: z4.object({
|
|
36010
|
+
status: z4.number().int(),
|
|
36011
|
+
headers: z4.record(z4.string(), z4.string()),
|
|
36012
|
+
/** At most 16 KiB. */
|
|
36013
|
+
bodyText: z4.string()
|
|
36014
|
+
}).optional(),
|
|
36015
|
+
mdns: z4.object({
|
|
36016
|
+
instance: z4.string(),
|
|
36017
|
+
host: z4.string(),
|
|
36018
|
+
port: z4.number().int(),
|
|
36019
|
+
txt: z4.record(z4.string(), z4.string())
|
|
36020
|
+
}).optional(),
|
|
36021
|
+
udp: z4.object({ payloadHex: z4.string() }).optional(),
|
|
36022
|
+
wsd: z4.object({
|
|
36023
|
+
xaddrs: z4.array(z4.string()),
|
|
36024
|
+
types: z4.array(z4.string()),
|
|
36025
|
+
scopes: z4.array(z4.string())
|
|
36026
|
+
}).optional()
|
|
36027
|
+
});
|
|
35422
36028
|
var DiscoveryCandidateSchema = z4.object({
|
|
35423
36029
|
stableId: z4.string(),
|
|
35424
36030
|
type: z4.enum(DeviceType),
|
|
@@ -35449,7 +36055,17 @@ var DiscoveryCandidateSchema = z4.object({
|
|
|
35449
36055
|
/** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
|
|
35450
36056
|
onboardedDeviceId: z4.number().optional(),
|
|
35451
36057
|
/** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
|
|
35452
|
-
onboardedName: z4.string().optional()
|
|
36058
|
+
onboardedName: z4.string().optional(),
|
|
36059
|
+
/**
|
|
36060
|
+
* This candidate is an INTEGRATION to set up (a gateway, a hub, a controller),
|
|
36061
|
+
* not a device to adopt. Replaces the `prefilledConfig.setupIntegration` marker,
|
|
36062
|
+
* which is still read for one release.
|
|
36063
|
+
*/
|
|
36064
|
+
setupIntegration: z4.boolean().optional(),
|
|
36065
|
+
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
36066
|
+
parentStableId: z4.string().optional(),
|
|
36067
|
+
/** `false` = shown for context but not pickable. Default true. */
|
|
36068
|
+
selectable: z4.boolean().optional()
|
|
35453
36069
|
});
|
|
35454
36070
|
var DeviceSummarySchema = z4.object({
|
|
35455
36071
|
id: z4.number(),
|
|
@@ -35527,6 +36143,24 @@ var deviceProviderCapability = {
|
|
|
35527
36143
|
auth: "admin"
|
|
35528
36144
|
}),
|
|
35529
36145
|
/**
|
|
36146
|
+
* How this provider is found on the network. `[]` = the provider has no
|
|
36147
|
+
* declarative mode and keeps answering `discoverDevices` itself.
|
|
36148
|
+
*/
|
|
36149
|
+
getDiscoveryModes: method(z4.object({}), z4.array(DiscoveryModeSchema)),
|
|
36150
|
+
/**
|
|
36151
|
+
* Turn the engine's raw hits into candidates. The provider may make
|
|
36152
|
+
* follow-up UNICAST requests to a hit host (an authenticated call, an
|
|
36153
|
+
* identity check) and never sweeps a subnet. `params` is the per-scan form
|
|
36154
|
+
* from `getDiscoveryParamsSchema`.
|
|
36155
|
+
*/
|
|
36156
|
+
classifyDiscoveryHits: method(z4.object({
|
|
36157
|
+
hits: z4.array(DiscoveryHitSchema),
|
|
36158
|
+
params: z4.record(z4.string(), z4.unknown()).optional()
|
|
36159
|
+
}), z4.array(DiscoveryCandidateSchema), {
|
|
36160
|
+
kind: "mutation",
|
|
36161
|
+
auth: "admin"
|
|
36162
|
+
}),
|
|
36163
|
+
/**
|
|
35530
36164
|
* Optional form schema (`ConfigUISchema`) for the EXTRA per-scan inputs a
|
|
35531
36165
|
* provider accepts (e.g. Gree's broadcast address for a different subnet).
|
|
35532
36166
|
* `null` when the provider takes no extra scan params — the generic
|
|
@@ -36782,20 +37416,26 @@ var deviceManagerCapability = {
|
|
|
36782
37416
|
label: z4.string()
|
|
36783
37417
|
})).readonly() }), { auth: "admin" }),
|
|
36784
37418
|
/** Scan EVERY discovery-capable provider; returns candidates grouped by addon. */
|
|
36785
|
-
discoverAllProviders: method(
|
|
36786
|
-
|
|
36787
|
-
|
|
36788
|
-
|
|
36789
|
-
|
|
36790
|
-
|
|
36791
|
-
|
|
36792
|
-
|
|
36793
|
-
|
|
37419
|
+
discoverAllProviders: method(
|
|
37420
|
+
/** `subnets`: extra CIDRs for THIS scan, on top of the hub's own and the saved list. */
|
|
37421
|
+
z4.object({ subnets: z4.array(z4.string()).optional() }),
|
|
37422
|
+
z4.object({ groups: z4.array(z4.object({
|
|
37423
|
+
addonId: z4.string(),
|
|
37424
|
+
label: z4.string(),
|
|
37425
|
+
candidates: z4.array(DiscoveryCandidateSchema).readonly(),
|
|
37426
|
+
error: z4.string().nullable()
|
|
37427
|
+
})).readonly() }),
|
|
37428
|
+
{
|
|
37429
|
+
kind: "mutation",
|
|
37430
|
+
auth: "admin"
|
|
37431
|
+
}
|
|
37432
|
+
),
|
|
36794
37433
|
/** Scan ONE provider with optional provider-specific params (subnet /
|
|
36795
37434
|
* broadcast addr) — the per-integration "Scan network" surface. */
|
|
36796
37435
|
discoverProvider: method(z4.object({
|
|
36797
37436
|
addonId: z4.string(),
|
|
36798
|
-
params: z4.record(z4.string(), z4.unknown()).optional()
|
|
37437
|
+
params: z4.record(z4.string(), z4.unknown()).optional(),
|
|
37438
|
+
subnets: z4.array(z4.string()).optional()
|
|
36799
37439
|
}), z4.object({ candidates: z4.array(DiscoveryCandidateSchema).readonly() }), {
|
|
36800
37440
|
kind: "mutation",
|
|
36801
37441
|
auth: "admin"
|
|
@@ -56163,7 +56803,9 @@ var integrationsCapability = {
|
|
|
56163
56803
|
id: z4.string(),
|
|
56164
56804
|
settings: z4.record(z4.string(), z4.unknown()),
|
|
56165
56805
|
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56166
|
-
secretKeys: z4.array(z4.string()).optional()
|
|
56806
|
+
secretKeys: z4.array(z4.string()).optional(),
|
|
56807
|
+
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56808
|
+
skipRestart: z4.boolean().optional()
|
|
56167
56809
|
}), z4.object({ success: z4.boolean() }), {
|
|
56168
56810
|
kind: "mutation",
|
|
56169
56811
|
auth: "admin"
|
|
@@ -56699,6 +57341,37 @@ var TlsStatusSchema = z4.object({
|
|
|
56699
57341
|
hasCa: z4.boolean()
|
|
56700
57342
|
}).nullable()
|
|
56701
57343
|
});
|
|
57344
|
+
var ScanRequestSchema = z4.object({
|
|
57345
|
+
providerId: z4.string().min(1),
|
|
57346
|
+
modes: z4.array(DiscoveryModeSchema)
|
|
57347
|
+
});
|
|
57348
|
+
var ScanErrorSchema = z4.object({
|
|
57349
|
+
/** Absent for a failure that is the scan's own (a bad subnet, a socket that would not open). */
|
|
57350
|
+
providerId: z4.string().optional(),
|
|
57351
|
+
/** The mode family that failed (`mdns`, `http-probe`, …). */
|
|
57352
|
+
mode: z4.string().optional(),
|
|
57353
|
+
message: z4.string()
|
|
57354
|
+
});
|
|
57355
|
+
var ScanStatsSchema = z4.object({
|
|
57356
|
+
hostsProbed: z4.number().int(),
|
|
57357
|
+
hits: z4.number().int(),
|
|
57358
|
+
timeouts: z4.number().int(),
|
|
57359
|
+
byMode: z4.record(z4.string(), z4.object({
|
|
57360
|
+
probed: z4.number().int(),
|
|
57361
|
+
hits: z4.number().int(),
|
|
57362
|
+
timeouts: z4.number().int()
|
|
57363
|
+
}))
|
|
57364
|
+
});
|
|
57365
|
+
var ScanNetworkResultSchema = z4.object({
|
|
57366
|
+
hits: z4.array(z4.object({
|
|
57367
|
+
providerId: z4.string(),
|
|
57368
|
+
hit: DiscoveryHitSchema
|
|
57369
|
+
})),
|
|
57370
|
+
/** CIDRs actually covered: the hub's own /24s, the saved list and the per-call list. */
|
|
57371
|
+
subnetsScanned: z4.array(z4.string()),
|
|
57372
|
+
errors: z4.array(ScanErrorSchema),
|
|
57373
|
+
stats: ScanStatsSchema
|
|
57374
|
+
});
|
|
56702
57375
|
var localNetworkCapability = {
|
|
56703
57376
|
name: "local-network",
|
|
56704
57377
|
scope: "system",
|
|
@@ -56912,6 +57585,34 @@ var localNetworkCapability = {
|
|
|
56912
57585
|
kind: "mutation",
|
|
56913
57586
|
auth: "admin"
|
|
56914
57587
|
}),
|
|
57588
|
+
/**
|
|
57589
|
+
* The LAN discovery engine. Every provider that declared discovery modes
|
|
57590
|
+
* (`device-provider.getDiscoveryModes`) is scanned in ONE pass: a single
|
|
57591
|
+
* mDNS browse for the union of service types, one WS-Discovery probe, one
|
|
57592
|
+
* SSDP search per target and one sweep that visits each host once and runs
|
|
57593
|
+
* every http-probe of every provider. Subnets scanned = the hub's own
|
|
57594
|
+
* non-internal IPv4 /24s ∪ the saved list (`getScanSubnets`) ∪ `subnets`.
|
|
57595
|
+
* A bad CIDR, or more than 4096 hosts in total, refuses the scan.
|
|
57596
|
+
*/
|
|
57597
|
+
scanNetwork: method(z4.object({
|
|
57598
|
+
requests: z4.array(ScanRequestSchema),
|
|
57599
|
+
subnets: z4.array(z4.string()).optional()
|
|
57600
|
+
}), ScanNetworkResultSchema, {
|
|
57601
|
+
kind: "mutation",
|
|
57602
|
+
auth: "admin",
|
|
57603
|
+
timeoutMs: 9e4
|
|
57604
|
+
}),
|
|
57605
|
+
/** Extra subnets every scan covers besides the hub's own, as CIDR strings. */
|
|
57606
|
+
getScanSubnets: method(z4.void(), z4.object({ subnets: z4.array(z4.string()) }), { auth: "admin" }),
|
|
57607
|
+
/**
|
|
57608
|
+
* Replace the saved extra subnets (a cluster setting). Each entry is IPv4
|
|
57609
|
+
* CIDR, at most a /22; the list is refused when it is invalid or its hosts
|
|
57610
|
+
* would exceed the scan cap.
|
|
57611
|
+
*/
|
|
57612
|
+
setScanSubnets: method(z4.object({ subnets: z4.array(z4.string()) }), z4.object({ subnets: z4.array(z4.string()) }), {
|
|
57613
|
+
kind: "mutation",
|
|
57614
|
+
auth: "admin"
|
|
57615
|
+
}),
|
|
56915
57616
|
/** Return to the generated local CA. The uploaded certificate stays stored (D873). */
|
|
56916
57617
|
revertToGeneratedCertificate: method(z4.void(), TlsStatusSchema, {
|
|
56917
57618
|
kind: "mutation",
|
|
@@ -60924,7 +61625,8 @@ var rebootCapability = {
|
|
|
60924
61625
|
deviceTypes: [
|
|
60925
61626
|
DeviceType.Camera,
|
|
60926
61627
|
DeviceType.Sensor,
|
|
60927
|
-
DeviceType.Switch
|
|
61628
|
+
DeviceType.Switch,
|
|
61629
|
+
DeviceType.Gateway
|
|
60928
61630
|
],
|
|
60929
61631
|
methods: { reboot: method(z4.object({ deviceId: z4.number().int().nonnegative() }), z4.object({ success: z4.literal(true) }), {
|
|
60930
61632
|
kind: "mutation",
|
|
@@ -65450,7 +66152,8 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
65450
66152
|
DeviceType.PetFountain,
|
|
65451
66153
|
DeviceType.Dishwasher,
|
|
65452
66154
|
DeviceType.Washer,
|
|
65453
|
-
DeviceType.Dryer
|
|
66155
|
+
DeviceType.Dryer,
|
|
66156
|
+
DeviceType.Gateway
|
|
65454
66157
|
],
|
|
65455
66158
|
apply: "add",
|
|
65456
66159
|
vocabularyCap: "consumables",
|
|
@@ -65951,7 +66654,8 @@ var DEVICE_TYPE_CONTROL_KIND = {
|
|
|
65951
66654
|
[DeviceType.PetFountain]: "pet-fountain",
|
|
65952
66655
|
[DeviceType.Dishwasher]: "dishwasher",
|
|
65953
66656
|
[DeviceType.Washer]: "dummy",
|
|
65954
|
-
[DeviceType.Dryer]: "dummy"
|
|
66657
|
+
[DeviceType.Dryer]: "dummy",
|
|
66658
|
+
[DeviceType.Gateway]: "dummy"
|
|
65955
66659
|
};
|
|
65956
66660
|
var DEVICE_TYPE_VALUES = new Set(Object.values(DeviceType));
|
|
65957
66661
|
var METHOD_ACCESS_MAP = Object.freeze({
|
|
@@ -66340,6 +67044,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
66340
67044
|
access: "create",
|
|
66341
67045
|
role: "admin"
|
|
66342
67046
|
},
|
|
67047
|
+
"addonSettings.getDeclaredSettingsKeys": {
|
|
67048
|
+
capName: "addon-settings",
|
|
67049
|
+
capScope: "system",
|
|
67050
|
+
addonId: null,
|
|
67051
|
+
access: "view",
|
|
67052
|
+
role: "admin"
|
|
67053
|
+
},
|
|
66343
67054
|
"addonSettings.getDeviceSettings": {
|
|
66344
67055
|
capName: "addon-settings",
|
|
66345
67056
|
capScope: "system",
|
|
@@ -66361,6 +67072,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
66361
67072
|
access: "view",
|
|
66362
67073
|
role: "admin"
|
|
66363
67074
|
},
|
|
67075
|
+
"addonSettings.unsetStoredSettingsKeys": {
|
|
67076
|
+
capName: "addon-settings",
|
|
67077
|
+
capScope: "system",
|
|
67078
|
+
addonId: null,
|
|
67079
|
+
access: "create",
|
|
67080
|
+
role: "admin"
|
|
67081
|
+
},
|
|
66364
67082
|
"addonSettings.updateDeviceSettings": {
|
|
66365
67083
|
capName: "addon-settings",
|
|
66366
67084
|
capScope: "system",
|
|
@@ -68776,6 +69494,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
68776
69494
|
access: "create",
|
|
68777
69495
|
role: "admin"
|
|
68778
69496
|
},
|
|
69497
|
+
"deviceProvider.classifyDiscoveryHits": {
|
|
69498
|
+
capName: "device-provider",
|
|
69499
|
+
capScope: "system",
|
|
69500
|
+
addonId: null,
|
|
69501
|
+
access: "create",
|
|
69502
|
+
role: "admin"
|
|
69503
|
+
},
|
|
68779
69504
|
"deviceProvider.createDevice": {
|
|
68780
69505
|
capName: "device-provider",
|
|
68781
69506
|
capScope: "system",
|
|
@@ -68804,6 +69529,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
68804
69529
|
access: "view",
|
|
68805
69530
|
role: "admin"
|
|
68806
69531
|
},
|
|
69532
|
+
"deviceProvider.getDiscoveryModes": {
|
|
69533
|
+
capName: "device-provider",
|
|
69534
|
+
capScope: "system",
|
|
69535
|
+
addonId: null,
|
|
69536
|
+
access: "view",
|
|
69537
|
+
role: "admin"
|
|
69538
|
+
},
|
|
68807
69539
|
"deviceProvider.getDiscoveryParamsSchema": {
|
|
68808
69540
|
capName: "device-provider",
|
|
68809
69541
|
capScope: "system",
|
|
@@ -69581,6 +70313,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
69581
70313
|
access: "create",
|
|
69582
70314
|
role: "admin"
|
|
69583
70315
|
},
|
|
70316
|
+
"keychain.releaseRecordRefs": {
|
|
70317
|
+
capName: "keychain",
|
|
70318
|
+
capScope: "system",
|
|
70319
|
+
addonId: null,
|
|
70320
|
+
access: "create",
|
|
70321
|
+
role: "admin"
|
|
70322
|
+
},
|
|
69584
70323
|
"keychain.resolve": {
|
|
69585
70324
|
capName: "keychain",
|
|
69586
70325
|
capScope: "system",
|
|
@@ -70001,6 +70740,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
70001
70740
|
access: "view",
|
|
70002
70741
|
role: "admin"
|
|
70003
70742
|
},
|
|
70743
|
+
"localNetwork.getScanSubnets": {
|
|
70744
|
+
capName: "local-network",
|
|
70745
|
+
capScope: "system",
|
|
70746
|
+
addonId: null,
|
|
70747
|
+
access: "view",
|
|
70748
|
+
role: "admin"
|
|
70749
|
+
},
|
|
70004
70750
|
"localNetwork.getTlsStatus": {
|
|
70005
70751
|
capName: "local-network",
|
|
70006
70752
|
capScope: "system",
|
|
@@ -70050,6 +70796,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
70050
70796
|
access: "create",
|
|
70051
70797
|
role: "admin"
|
|
70052
70798
|
},
|
|
70799
|
+
"localNetwork.scanNetwork": {
|
|
70800
|
+
capName: "local-network",
|
|
70801
|
+
capScope: "system",
|
|
70802
|
+
addonId: null,
|
|
70803
|
+
access: "create",
|
|
70804
|
+
role: "admin"
|
|
70805
|
+
},
|
|
70053
70806
|
"localNetwork.setAddressRole": {
|
|
70054
70807
|
capName: "local-network",
|
|
70055
70808
|
capScope: "system",
|
|
@@ -70071,6 +70824,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
70071
70824
|
access: "create",
|
|
70072
70825
|
role: "admin"
|
|
70073
70826
|
},
|
|
70827
|
+
"localNetwork.setScanSubnets": {
|
|
70828
|
+
capName: "local-network",
|
|
70829
|
+
capScope: "system",
|
|
70830
|
+
addonId: null,
|
|
70831
|
+
access: "create",
|
|
70832
|
+
role: "admin"
|
|
70833
|
+
},
|
|
70074
70834
|
"localNetwork.setViewerEndpoints": {
|
|
70075
70835
|
capName: "local-network",
|
|
70076
70836
|
capScope: "system",
|
|
@@ -74292,6 +75052,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
74292
75052
|
access: "create",
|
|
74293
75053
|
role: "admin"
|
|
74294
75054
|
},
|
|
75055
|
+
"storageAdmin.dropOrphanedKind": {
|
|
75056
|
+
capName: "storage-admin",
|
|
75057
|
+
capScope: "system",
|
|
75058
|
+
addonId: null,
|
|
75059
|
+
access: "delete",
|
|
75060
|
+
role: "admin"
|
|
75061
|
+
},
|
|
74295
75062
|
"storageAdmin.listKinds": {
|
|
74296
75063
|
capName: "storage-admin",
|
|
74297
75064
|
capScope: "system",
|
|
@@ -74313,6 +75080,27 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
74313
75080
|
access: "view",
|
|
74314
75081
|
role: "admin"
|
|
74315
75082
|
},
|
|
75083
|
+
"storageAdmin.scanStaleData": {
|
|
75084
|
+
capName: "storage-admin",
|
|
75085
|
+
capScope: "system",
|
|
75086
|
+
addonId: null,
|
|
75087
|
+
access: "create",
|
|
75088
|
+
role: "admin"
|
|
75089
|
+
},
|
|
75090
|
+
"storageAdmin.staleDataReport": {
|
|
75091
|
+
capName: "storage-admin",
|
|
75092
|
+
capScope: "system",
|
|
75093
|
+
addonId: null,
|
|
75094
|
+
access: "view",
|
|
75095
|
+
role: "admin"
|
|
75096
|
+
},
|
|
75097
|
+
"storageAdmin.unsetStaleSettingsKey": {
|
|
75098
|
+
capName: "storage-admin",
|
|
75099
|
+
capScope: "system",
|
|
75100
|
+
addonId: null,
|
|
75101
|
+
access: "create",
|
|
75102
|
+
role: "admin"
|
|
75103
|
+
},
|
|
74316
75104
|
"storageEvictable.evict": {
|
|
74317
75105
|
capName: "storage-evictable",
|
|
74318
75106
|
capScope: "system",
|
|
@@ -80013,7 +80801,8 @@ var DEVICE_TYPE_DO_CAPS = {
|
|
|
80013
80801
|
[DeviceType.PetFountain]: ["pet-fountain"],
|
|
80014
80802
|
[DeviceType.Dishwasher]: ["dishwasher"],
|
|
80015
80803
|
[DeviceType.Washer]: [],
|
|
80016
|
-
[DeviceType.Dryer]: []
|
|
80804
|
+
[DeviceType.Dryer]: [],
|
|
80805
|
+
[DeviceType.Gateway]: []
|
|
80017
80806
|
};
|
|
80018
80807
|
var NUMERIC_ABOVE = t("When {quantity} of {device} rises above {threshold}", "Quando {quantity} di {device} sale sopra {threshold}");
|
|
80019
80808
|
var NUMERIC_BELOW = t("When {quantity} of {device} falls below {threshold}", "Quando {quantity} di {device} scende sotto {threshold}");
|
|
@@ -85323,10 +86112,12 @@ function describeHub(hub) {
|
|
|
85323
86112
|
export {
|
|
85324
86113
|
isReadingQuantity,
|
|
85325
86114
|
FFMPEG_PATH_ENV,
|
|
86115
|
+
MACRO_LABELS,
|
|
86116
|
+
countWhenLeaves,
|
|
86117
|
+
convertLegacyWhen,
|
|
85326
86118
|
AutomationRecipeSchema,
|
|
85327
86119
|
validateRecipeBounds,
|
|
85328
86120
|
generateAutomationBlock,
|
|
85329
|
-
MACRO_LABELS,
|
|
85330
86121
|
unitsForQuantity,
|
|
85331
86122
|
capUnitResolver,
|
|
85332
86123
|
prepareRecipeUnits,
|