camstack 1.2.212 → 1.2.214
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +6 -0
- package/dist/chunk-37A556YF.js +822 -0
- package/dist/{chunk-MLWXP2SI.js → chunk-4Y3CBGOY.js} +972 -255
- package/dist/chunk-BYNP7S4W.js +56 -0
- package/dist/chunk-JPGJK5DC.js +75 -0
- package/dist/cli.js +127 -70
- package/dist/doctor-command-S7Q2U6HO.js +227 -0
- package/dist/{launcher-EACOVUIT.js → launcher-JAOMC5YY.js} +4417 -1279
- package/dist/{server-2GOCBDEY.js → server-TZCZTLBW.js} +1076 -1372
- package/package.json +1 -1
|
@@ -1404,7 +1404,7 @@ var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
|
|
|
1404
1404
|
return EventCategory2;
|
|
1405
1405
|
})({});
|
|
1406
1406
|
|
|
1407
|
-
// ../types/dist/sleep-
|
|
1407
|
+
// ../types/dist/sleep-CeyhrcOk.mjs
|
|
1408
1408
|
import { z as z2 } from "zod";
|
|
1409
1409
|
var AUDIO_CHUNK_FORMATS = [
|
|
1410
1410
|
"f32le",
|
|
@@ -6783,17 +6783,17 @@ function enumerateSchemaFields(schema, prefix = "") {
|
|
|
6783
6783
|
const out = [];
|
|
6784
6784
|
const shape = root.shape;
|
|
6785
6785
|
for (const [key, field] of Object.entries(shape)) {
|
|
6786
|
-
const
|
|
6786
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
6787
6787
|
const inner = unwrapSchema(field);
|
|
6788
6788
|
if (inner instanceof z4.ZodObject) {
|
|
6789
|
-
out.push(...enumerateSchemaFields(inner,
|
|
6789
|
+
out.push(...enumerateSchemaFields(inner, path));
|
|
6790
6790
|
continue;
|
|
6791
6791
|
}
|
|
6792
6792
|
const leaf = leafKind(field);
|
|
6793
6793
|
if (leaf) {
|
|
6794
6794
|
const quantity = quantityOfField(field);
|
|
6795
6795
|
out.push({
|
|
6796
|
-
path
|
|
6796
|
+
path,
|
|
6797
6797
|
kind: leaf.kind,
|
|
6798
6798
|
...leaf.enumValues ? { enumValues: leaf.enumValues } : {},
|
|
6799
6799
|
...quantity !== void 0 ? { quantity } : {}
|
|
@@ -7308,6 +7308,149 @@ function metaOf(schema) {
|
|
|
7308
7308
|
function paramMetaOf(field) {
|
|
7309
7309
|
return metaOf(field) ?? metaOf(unwrapSchema(field));
|
|
7310
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
|
+
}
|
|
7311
7454
|
var AutomationConditionOperatorSchema = z4.enum([
|
|
7312
7455
|
"eq",
|
|
7313
7456
|
"ne",
|
|
@@ -7318,12 +7461,69 @@ var AutomationConditionOperatorSchema = z4.enum([
|
|
|
7318
7461
|
"contains",
|
|
7319
7462
|
"in"
|
|
7320
7463
|
]);
|
|
7321
|
-
var
|
|
7322
|
-
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"),
|
|
7323
7517
|
deviceId: z4.number().int().nonnegative(),
|
|
7324
7518
|
cap: z4.string().min(1),
|
|
7325
7519
|
fieldPath: z4.string().min(1),
|
|
7326
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
|
+
*/
|
|
7327
7527
|
value: z4.union([
|
|
7328
7528
|
z4.string(),
|
|
7329
7529
|
z4.number(),
|
|
@@ -7331,17 +7531,16 @@ var AutomationConditionLeafSchema = z4.object({
|
|
|
7331
7531
|
z4.array(z4.union([z4.string(), z4.number()])),
|
|
7332
7532
|
QuantityValueSchema
|
|
7333
7533
|
]),
|
|
7534
|
+
/** The unit the person picked for `value` (display only; `value` is in the base unit). */
|
|
7334
7535
|
unit: z4.string().min(1).optional()
|
|
7335
7536
|
});
|
|
7336
|
-
var
|
|
7537
|
+
var StationaryPresentLeafSchema = z4.object({
|
|
7337
7538
|
kind: z4.literal("stationary-present"),
|
|
7338
7539
|
deviceId: z4.number().int().nonnegative(),
|
|
7339
|
-
|
|
7340
|
-
classes: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7341
|
-
/** The parked object must be in at least one of these zones. */
|
|
7540
|
+
subjects: z4.array(StationarySubjectSchema).min(1).optional(),
|
|
7342
7541
|
zones: z4.array(z4.string().min(1)).min(1).optional()
|
|
7343
7542
|
});
|
|
7344
|
-
var
|
|
7543
|
+
var ExpressionLeafSchema = z4.object({
|
|
7345
7544
|
kind: z4.literal("expression"),
|
|
7346
7545
|
expr: z4.string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
7347
7546
|
bindings: z4.record(z4.string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
@@ -7353,45 +7552,150 @@ var AutomationConditionExpressionSchema = z4.object({
|
|
|
7353
7552
|
path: ["expr"]
|
|
7354
7553
|
});
|
|
7355
7554
|
});
|
|
7356
|
-
var
|
|
7555
|
+
var AutomationWhenSchema = z4.lazy(() => z4.discriminatedUnion("kind", [
|
|
7357
7556
|
z4.object({
|
|
7358
7557
|
kind: z4.literal("all"),
|
|
7359
|
-
children: z4.array(
|
|
7558
|
+
children: z4.array(AutomationWhenSchema)
|
|
7360
7559
|
}),
|
|
7361
7560
|
z4.object({
|
|
7362
7561
|
kind: z4.literal("any"),
|
|
7363
|
-
children: z4.array(
|
|
7562
|
+
children: z4.array(AutomationWhenSchema)
|
|
7364
7563
|
}),
|
|
7365
7564
|
z4.object({
|
|
7366
7565
|
kind: z4.literal("not"),
|
|
7367
|
-
child:
|
|
7566
|
+
child: AutomationWhenSchema
|
|
7368
7567
|
}),
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7568
|
+
DeviceBecomesLeafSchema,
|
|
7569
|
+
DeviceEventLeafSchema,
|
|
7570
|
+
ScheduleLeafSchema,
|
|
7571
|
+
ManualLeafSchema,
|
|
7572
|
+
IntervalLeafSchema,
|
|
7573
|
+
DetectionLeafSchema,
|
|
7574
|
+
StationaryLeafSchema,
|
|
7575
|
+
DeviceIsLeafSchema,
|
|
7576
|
+
StationaryPresentLeafSchema,
|
|
7577
|
+
ExpressionLeafSchema
|
|
7372
7578
|
]));
|
|
7373
|
-
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, path = "when") {
|
|
7609
|
+
if (isLeaf(node)) {
|
|
7610
|
+
visit(node, path);
|
|
7611
|
+
return;
|
|
7612
|
+
}
|
|
7613
|
+
if (node.kind === "not") {
|
|
7614
|
+
forEachLeaf(node.child, visit, `${path}.0`);
|
|
7615
|
+
return;
|
|
7616
|
+
}
|
|
7617
|
+
node.children.forEach((child, i) => {
|
|
7618
|
+
forEachLeaf(child, visit, `${path}.${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, path, negated) {
|
|
7647
|
+
if (isLeaf(node)) {
|
|
7648
|
+
if (!isEventLeaf(node)) return { paths: [] };
|
|
7649
|
+
if (negated) return { error: `"not" is applied to ${say(node.kind)} (${path}) \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,
|
|
7652
|
+
kind: node.kind
|
|
7653
|
+
}] };
|
|
7654
|
+
}
|
|
7655
|
+
if (node.kind === "not") return eventNeed(node.child, `${path}.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, `${path}.${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", [
|
|
7374
7683
|
z4.object({
|
|
7375
7684
|
kind: z4.literal("device-state"),
|
|
7376
7685
|
deviceId: z4.number().int().nonnegative(),
|
|
7377
7686
|
cap: z4.string().min(1),
|
|
7378
7687
|
fieldPath: z4.string().min(1),
|
|
7379
|
-
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
7380
7688
|
becomes: z4.union([
|
|
7381
7689
|
z4.string(),
|
|
7382
7690
|
z4.number(),
|
|
7383
7691
|
z4.boolean()
|
|
7384
7692
|
]).optional(),
|
|
7385
|
-
/** Only on a CHANGE of value, not on every re-report. */
|
|
7386
7693
|
edge: z4.boolean().optional(),
|
|
7387
|
-
/** The condition must hold this long before the run starts. */
|
|
7388
7694
|
forMs: z4.number().int().min(0).max(864e5).optional(),
|
|
7389
|
-
/** Collapse a burst into one run. */
|
|
7390
7695
|
debounceMs: z4.number().int().min(0).max(6e5).optional()
|
|
7391
7696
|
}),
|
|
7392
7697
|
z4.object({
|
|
7393
7698
|
kind: z4.literal("device-event"),
|
|
7394
|
-
/** An `EventCategory` value. */
|
|
7395
7699
|
category: z4.string().min(1),
|
|
7396
7700
|
deviceId: z4.number().int().nonnegative().optional()
|
|
7397
7701
|
}),
|
|
@@ -7402,25 +7706,16 @@ var AutomationTriggerSchema = z4.discriminatedUnion("kind", [
|
|
|
7402
7706
|
z4.object({ kind: z4.literal("manual") }),
|
|
7403
7707
|
z4.object({
|
|
7404
7708
|
kind: z4.literal("detection"),
|
|
7405
|
-
/** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
|
|
7406
|
-
* matches every raw class that maps to it; any other name matches
|
|
7407
|
-
* exactly. */
|
|
7408
7709
|
classes: z4.array(z4.string().min(1)).min(1),
|
|
7409
7710
|
deviceIds: z4.array(z4.number().int().nonnegative()).min(1),
|
|
7410
|
-
/** The track must have visited at least one of these zones. */
|
|
7411
7711
|
zones: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7412
|
-
/** Best-so-far confidence the track must reach. */
|
|
7413
7712
|
minScore: z4.number().min(0).max(1).optional()
|
|
7414
7713
|
}),
|
|
7415
7714
|
z4.object({
|
|
7416
7715
|
kind: z4.literal("stationary"),
|
|
7417
7716
|
deviceIds: z4.array(z4.number().int().nonnegative()).min(1),
|
|
7418
|
-
/** Class names; a macro class matches like the detection trigger. */
|
|
7419
7717
|
classes: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7420
|
-
/** The parked object must be in at least one of these zones. */
|
|
7421
7718
|
zones: z4.array(z4.string().min(1)).min(1).optional(),
|
|
7422
|
-
/** `appeared`: it became parked. `departed`: the registry retired it.
|
|
7423
|
-
* The vocabulary of `PipelineAnalyticsStationaryChanged`. */
|
|
7424
7719
|
phase: z4.enum(["appeared", "departed"])
|
|
7425
7720
|
}),
|
|
7426
7721
|
z4.object({
|
|
@@ -7428,6 +7723,123 @@ var AutomationTriggerSchema = z4.discriminatedUnion("kind", [
|
|
|
7428
7723
|
minutes: z4.number().int().min(1).max(1440)
|
|
7429
7724
|
})
|
|
7430
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
|
+
}
|
|
7431
7843
|
var AutomationWaitStepSchema = z4.object({
|
|
7432
7844
|
kind: z4.literal("wait"),
|
|
7433
7845
|
seconds: z4.number().min(0).max(300)
|
|
@@ -7495,54 +7907,34 @@ var AutomationRunModeSchema = z4.discriminatedUnion("mode", [
|
|
|
7495
7907
|
minutes: z4.number().int().min(1).max(1440)
|
|
7496
7908
|
})
|
|
7497
7909
|
]);
|
|
7498
|
-
var
|
|
7499
|
-
|
|
7500
|
-
conditions: AutomationConditionSchema.optional(),
|
|
7910
|
+
var CurrentAutomationRecipeSchema = z4.object({
|
|
7911
|
+
when: AutomationWhenSchema,
|
|
7501
7912
|
actions: z4.array(AutomationActionSchema),
|
|
7502
7913
|
runMode: AutomationRunModeSchema.optional(),
|
|
7503
7914
|
/** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
|
|
7504
7915
|
keepTraces: z4.number().int().min(1).max(100).optional()
|
|
7505
7916
|
});
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
if (isLeaf(cond)) return 1;
|
|
7523
|
-
if (cond.kind === "not") return countConditionLeaves(cond.child);
|
|
7524
|
-
let total = 0;
|
|
7525
|
-
for (const child of cond.children) total += countConditionLeaves(child);
|
|
7526
|
-
return total;
|
|
7527
|
-
}
|
|
7528
|
-
function findEmptyContainer(cond) {
|
|
7529
|
-
if (isLeaf(cond)) return false;
|
|
7530
|
-
if (cond.kind === "not") return findEmptyContainer(cond.child);
|
|
7531
|
-
if (cond.children.length === 0) return true;
|
|
7532
|
-
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
|
+
};
|
|
7533
7933
|
}
|
|
7934
|
+
var AutomationRecipeSchema = z4.union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
|
|
7534
7935
|
function validateRecipeBounds(recipe) {
|
|
7535
|
-
if (recipe.triggers.length === 0) return "an automation needs at least one trigger \u2014 without one nothing can start a run";
|
|
7536
7936
|
if (recipe.actions.length === 0) return "an automation needs at least one action \u2014 a rule that does nothing is not a rule";
|
|
7537
|
-
|
|
7538
|
-
if (cond !== void 0) {
|
|
7539
|
-
if (findEmptyContainer(cond)) return "a condition group is empty \u2014 group existing rows instead of leaving a container to fill";
|
|
7540
|
-
const depth = conditionDepth(cond);
|
|
7541
|
-
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`;
|
|
7542
|
-
const leaves = countConditionLeaves(cond);
|
|
7543
|
-
if (leaves > 32) return `conditions use ${leaves} leaves \u2014 the limit is 32`;
|
|
7544
|
-
}
|
|
7545
|
-
return null;
|
|
7937
|
+
return validateWhenTree(recipe.when);
|
|
7546
7938
|
}
|
|
7547
7939
|
var RECONCILE_MS = 15e3;
|
|
7548
7940
|
function stepMetaOf(action) {
|
|
@@ -7565,10 +7957,11 @@ function stepMetaOf(action) {
|
|
|
7565
7957
|
function j(value) {
|
|
7566
7958
|
return JSON.stringify(value);
|
|
7567
7959
|
}
|
|
7568
|
-
function
|
|
7960
|
+
function emitLeaf(trigger, path) {
|
|
7961
|
+
const at = j(path);
|
|
7569
7962
|
switch (trigger.kind) {
|
|
7570
7963
|
case "schedule":
|
|
7571
|
-
return ` //
|
|
7964
|
+
return ` // event leaf ${path}: schedule \u2014 armed to the next minute boundary and
|
|
7572
7965
|
// RE-ARMED from the wall clock every tick, so it cannot drift the way a
|
|
7573
7966
|
// setInterval(24h) does. No cron library: a block cannot resolve one.
|
|
7574
7967
|
{
|
|
@@ -7576,7 +7969,7 @@ function emitTrigger(trigger, index) {
|
|
|
7576
7969
|
const arm = (): void => {
|
|
7577
7970
|
timer = setTimeout(() => {
|
|
7578
7971
|
if (cronMatches(${j(trigger.cron)}, new Date())) {
|
|
7579
|
-
void run({ kind: 'schedule', at: Date.now() })
|
|
7972
|
+
void run({ kind: 'schedule', leaf: ${at}, at: Date.now() })
|
|
7580
7973
|
}
|
|
7581
7974
|
arm()
|
|
7582
7975
|
}, nextMinuteBoundary())
|
|
@@ -7585,13 +7978,14 @@ function emitTrigger(trigger, index) {
|
|
|
7585
7978
|
disposers.push(() => { if (timer !== undefined) clearTimeout(timer) })
|
|
7586
7979
|
}`;
|
|
7587
7980
|
case "device-event":
|
|
7588
|
-
return ` //
|
|
7981
|
+
return ` // event leaf ${path}: device event
|
|
7589
7982
|
disposers.push(
|
|
7590
7983
|
ctx.eventBus.subscribe(
|
|
7591
7984
|
{ category: ${j(trigger.category)}${trigger.deviceId === void 0 ? "" : `, deviceId: ${j(trigger.deviceId)}`} },
|
|
7592
7985
|
(event) => {
|
|
7593
7986
|
void run({
|
|
7594
7987
|
kind: 'device-event',
|
|
7988
|
+
leaf: ${at},
|
|
7595
7989
|
at: Date.now(),
|
|
7596
7990
|
deviceId: event.deviceId,
|
|
7597
7991
|
category: ${j(trigger.category)},
|
|
@@ -7599,9 +7993,9 @@ function emitTrigger(trigger, index) {
|
|
|
7599
7993
|
},
|
|
7600
7994
|
),
|
|
7601
7995
|
)`;
|
|
7602
|
-
case "device-
|
|
7996
|
+
case "device-becomes": {
|
|
7603
7997
|
const becomes = trigger.becomes === void 0 ? "undefined" : j(trigger.becomes);
|
|
7604
|
-
return ` //
|
|
7998
|
+
return ` // event leaf ${path}: device state \u2014 the mirror is the authority, the
|
|
7605
7999
|
// event only WAKES the evaluation, so a dropped event delays and never loses.
|
|
7606
8000
|
{
|
|
7607
8001
|
let previous: unknown = undefined
|
|
@@ -7622,7 +8016,8 @@ function emitTrigger(trigger, index) {
|
|
|
7622
8016
|
}
|
|
7623
8017
|
heldSince = undefined
|
|
7624
8018
|
void run({
|
|
7625
|
-
kind: 'device-
|
|
8019
|
+
kind: 'device-becomes',
|
|
8020
|
+
leaf: ${at},
|
|
7626
8021
|
at: Date.now(),
|
|
7627
8022
|
deviceId: ${j(trigger.deviceId)},
|
|
7628
8023
|
cap: ${j(trigger.cap)},
|
|
@@ -7642,17 +8037,17 @@ function emitTrigger(trigger, index) {
|
|
|
7642
8037
|
}`;
|
|
7643
8038
|
}
|
|
7644
8039
|
case "manual":
|
|
7645
|
-
return ` //
|
|
8040
|
+
return ` // event leaf ${path}: manual \u2014 fired by automation-control.trigger
|
|
7646
8041
|
// ("Run actions"), which runs the THEN half with an EMPTY vars bag. A manual
|
|
7647
8042
|
// run bypasses only the cooldown wait, never the single/queued limits.`;
|
|
7648
8043
|
case "interval":
|
|
7649
|
-
return ` //
|
|
8044
|
+
return ` // event leaf ${path}: interval \u2014 every ${trigger.minutes} min, first run jittered so
|
|
7650
8045
|
// blocks restarted together do not all fire on the same second.
|
|
7651
8046
|
{
|
|
7652
8047
|
let interval: ReturnType<typeof setInterval> | undefined
|
|
7653
8048
|
const first = setTimeout(() => {
|
|
7654
|
-
void run({ kind: 'interval', at: Date.now() })
|
|
7655
|
-
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)})
|
|
7656
8051
|
}, Math.floor(Math.random() * Math.min(${j(trigger.minutes * 6e4)}, 30_000)) + 1)
|
|
7657
8052
|
disposers.push(() => {
|
|
7658
8053
|
clearTimeout(first)
|
|
@@ -7660,7 +8055,7 @@ function emitTrigger(trigger, index) {
|
|
|
7660
8055
|
})
|
|
7661
8056
|
}`;
|
|
7662
8057
|
case "detection":
|
|
7663
|
-
return ` //
|
|
8058
|
+
return ` // event leaf ${path}: detection \u2014 ONE fire per (device, track); a rejected event is
|
|
7664
8059
|
// traced with its reason and never fires.
|
|
7665
8060
|
{
|
|
7666
8061
|
const filter = new DetectionTriggerFilter(${j(trigger)})
|
|
@@ -7677,7 +8072,7 @@ function emitTrigger(trigger, index) {
|
|
|
7677
8072
|
})
|
|
7678
8073
|
}
|
|
7679
8074
|
void run(
|
|
7680
|
-
{ kind: 'detection', at: Date.now(), ...decision.vars },
|
|
8075
|
+
{ kind: 'detection', leaf: ${at}, at: Date.now(), ...decision.vars },
|
|
7681
8076
|
decision.reason,
|
|
7682
8077
|
decision.kind === 'fire',
|
|
7683
8078
|
)
|
|
@@ -7686,9 +8081,9 @@ function emitTrigger(trigger, index) {
|
|
|
7686
8081
|
)
|
|
7687
8082
|
}`;
|
|
7688
8083
|
case "stationary":
|
|
7689
|
-
return ` //
|
|
8084
|
+
return ` // event leaf ${path}: stationary \u2014 a parked object appeared / departed. Stateless:
|
|
7690
8085
|
// the registry emits one event per transition. A dropped event only misses a fire;
|
|
7691
|
-
// the stationary-present
|
|
8086
|
+
// the stationary-present STATE leaf reads the registry, not this event.
|
|
7692
8087
|
{
|
|
7693
8088
|
const trigger = ${j(trigger)} as const
|
|
7694
8089
|
disposers.push(
|
|
@@ -7704,7 +8099,7 @@ function emitTrigger(trigger, index) {
|
|
|
7704
8099
|
})
|
|
7705
8100
|
}
|
|
7706
8101
|
void run(
|
|
7707
|
-
{ kind: 'stationary', at: Date.now(), ...decision.vars },
|
|
8102
|
+
{ kind: 'stationary', leaf: ${at}, at: Date.now(), ...decision.vars },
|
|
7708
8103
|
decision.reason,
|
|
7709
8104
|
decision.kind === 'fire',
|
|
7710
8105
|
)
|
|
@@ -7727,11 +8122,11 @@ ${body}
|
|
|
7727
8122
|
case "cap":
|
|
7728
8123
|
if (Array.isArray(action.deviceId)) return step(` for (const deviceId of ${j(action.deviceId)}) {
|
|
7729
8124
|
scope.setDeviceId(deviceId)
|
|
7730
|
-
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)
|
|
7731
8126
|
}`);
|
|
7732
8127
|
return step(` const deviceId = ${typeof action.deviceId === "number" ? j(action.deviceId) : `resolveTarget(vars, ${j(action.deviceId.$var)})`}
|
|
7733
8128
|
scope.setDeviceId(deviceId)
|
|
7734
|
-
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)`);
|
|
7735
8130
|
case "action":
|
|
7736
8131
|
return step(` // an Action run, awaited: the step takes THAT run's outcome
|
|
7737
8132
|
const result = await ctx.api.actions.run.mutate({
|
|
@@ -7756,6 +8151,13 @@ function generateAutomationBlock(input) {
|
|
|
7756
8151
|
const { name, recipe } = input;
|
|
7757
8152
|
const runMode = recipe.runMode ?? { mode: "single" };
|
|
7758
8153
|
const stepMeta = recipe.actions.map(stepMetaOf);
|
|
8154
|
+
const eventLeaves = [];
|
|
8155
|
+
forEachLeaf(recipe.when, (leaf, path) => {
|
|
8156
|
+
if (isEventLeaf(leaf)) eventLeaves.push({
|
|
8157
|
+
leaf,
|
|
8158
|
+
path
|
|
8159
|
+
});
|
|
8160
|
+
});
|
|
7759
8161
|
return `import type {
|
|
7760
8162
|
AddonContext,
|
|
7761
8163
|
AutomationConditionTraceNode,
|
|
@@ -7763,6 +8165,7 @@ function generateAutomationBlock(input) {
|
|
|
7763
8165
|
AutomationRunVerdict,
|
|
7764
8166
|
AutomationStepTrace,
|
|
7765
8167
|
RunnableStep,
|
|
8168
|
+
StepScope,
|
|
7766
8169
|
} from '@camstack/types'
|
|
7767
8170
|
import {
|
|
7768
8171
|
ConditionMirror,
|
|
@@ -7770,9 +8173,9 @@ import {
|
|
|
7770
8173
|
EventCategory,
|
|
7771
8174
|
RunGate,
|
|
7772
8175
|
buildTriggerTrace,
|
|
7773
|
-
|
|
8176
|
+
collectWhenMirrorSources,
|
|
7774
8177
|
coreBlockIdFromAddonId,
|
|
7775
|
-
|
|
8178
|
+
evaluateWhenTree,
|
|
7776
8179
|
evaluateStationaryTrigger,
|
|
7777
8180
|
isSkippedTrace,
|
|
7778
8181
|
mergeSkipTraces,
|
|
@@ -7800,7 +8203,11 @@ const RUN_MODE = ${j(runMode)} as const
|
|
|
7800
8203
|
/** Run traces kept (a ring, held in the hub's runs ledger). */
|
|
7801
8204
|
const KEEP_TRACES = ${recipe.keepTraces ?? 20}
|
|
7802
8205
|
|
|
7803
|
-
|
|
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)}
|
|
7804
8211
|
|
|
7805
8212
|
/** Skips that repeat are folded into one trace; it goes to the hub after this. */
|
|
7806
8213
|
const SKIP_FLUSH_MS = 5_000
|
|
@@ -7859,7 +8266,8 @@ function matchField(field: string, value: number): boolean {
|
|
|
7859
8266
|
export default function block(ctx: AddonContext) {
|
|
7860
8267
|
const disposers: Array<() => void> = []
|
|
7861
8268
|
const gate = new RunGate(RUN_MODE)
|
|
7862
|
-
const
|
|
8269
|
+
const MIRROR_SOURCES = collectWhenMirrorSources(WHEN)
|
|
8270
|
+
const mirror = new ConditionMirror(MIRROR_SOURCES)
|
|
7863
8271
|
const BLOCK_ID = coreBlockIdFromAddonId(ctx.id)
|
|
7864
8272
|
/** The cooldown stamp: memory is the authority at fire time (D49); the hub ledger is read once at start. */
|
|
7865
8273
|
let lastRunStartedAt: number | null = null
|
|
@@ -8025,6 +8433,7 @@ export default function block(ctx: AddonContext) {
|
|
|
8025
8433
|
}
|
|
8026
8434
|
|
|
8027
8435
|
async function actuate(
|
|
8436
|
+
scope: StepScope,
|
|
8028
8437
|
deviceId: number,
|
|
8029
8438
|
cap: string,
|
|
8030
8439
|
method: string,
|
|
@@ -8034,6 +8443,7 @@ export default function block(ctx: AddonContext) {
|
|
|
8034
8443
|
const substituted = substitute(args, vars)
|
|
8035
8444
|
const resolved: Record<string, unknown> =
|
|
8036
8445
|
substituted !== null && typeof substituted === 'object' ? { ...substituted } : {}
|
|
8446
|
+
scope.setArgs(resolved)
|
|
8037
8447
|
// Forked-addon ctx.api namespaces are tRPC Proxies over a noop function
|
|
8038
8448
|
// (typeof === 'function'), not plain objects \u2014 reject only null/undefined.
|
|
8039
8449
|
const client: unknown = Reflect.get(ctx.api, capToRouterKey(cap))
|
|
@@ -8122,10 +8532,13 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
8122
8532
|
let nodes: AutomationConditionTraceNode[] = []
|
|
8123
8533
|
let steps: AutomationStepTrace[] = []
|
|
8124
8534
|
try {
|
|
8125
|
-
const
|
|
8535
|
+
const firedLeaf = trigger['leaf']
|
|
8536
|
+
const evaluation = evaluateWhenTree(WHEN, mirror, Date.now(), {
|
|
8537
|
+
...(typeof firedLeaf === 'string' ? { firedPath: firedLeaf } : {}),
|
|
8538
|
+
})
|
|
8126
8539
|
nodes = [...evaluation.nodes]
|
|
8127
8540
|
for (const node of nodes) {
|
|
8128
|
-
if (node.kind === '
|
|
8541
|
+
if (node.kind === 'device-is' && node.observed === null && node.deviceId !== undefined) {
|
|
8129
8542
|
ctx.logger.warn('condition leaf value unknown \u2014 treated as false', {
|
|
8130
8543
|
tags: { deviceId: node.deviceId },
|
|
8131
8544
|
path: node.path,
|
|
@@ -8136,7 +8549,7 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
8136
8549
|
}
|
|
8137
8550
|
if (!evaluation.pass) {
|
|
8138
8551
|
outcome = 'skipped-conditions'
|
|
8139
|
-
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) })
|
|
8140
8553
|
return
|
|
8141
8554
|
}
|
|
8142
8555
|
const letThrough = stampUnknown && RUN_MODE.mode === 'cooldown'
|
|
@@ -8235,9 +8648,30 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
8235
8648
|
const ready: Promise<void> = (async () => {
|
|
8236
8649
|
await loadState()
|
|
8237
8650
|
await refreshMirror()
|
|
8651
|
+
stateWatching = true
|
|
8652
|
+
evaluateStateTree()
|
|
8238
8653
|
})()
|
|
8239
8654
|
|
|
8240
|
-
|
|
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) {
|
|
8241
8675
|
disposers.push(
|
|
8242
8676
|
ctx.eventBus.subscribe({ category: EventCategory.DeviceStateChanged }, (event) => {
|
|
8243
8677
|
const data = event.data
|
|
@@ -8245,14 +8679,17 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
8245
8679
|
const capName = data['capName']
|
|
8246
8680
|
if (typeof deviceId === 'number' && typeof capName === 'string') {
|
|
8247
8681
|
mirror.apply(deviceId, capName, data['slice'])
|
|
8682
|
+
evaluateStateTree()
|
|
8248
8683
|
}
|
|
8249
8684
|
}),
|
|
8250
8685
|
)
|
|
8251
|
-
const mirrorReconcile = setInterval(() => {
|
|
8686
|
+
const mirrorReconcile = setInterval(() => {
|
|
8687
|
+
void refreshMirror().then(evaluateStateTree)
|
|
8688
|
+
}, RECONCILE_MS)
|
|
8252
8689
|
disposers.push(() => clearInterval(mirrorReconcile))
|
|
8253
8690
|
}
|
|
8254
8691
|
|
|
8255
|
-
${
|
|
8692
|
+
${eventLeaves.map(({ leaf, path }) => emitLeaf(leaf, path)).join("\n\n")}
|
|
8256
8693
|
|
|
8257
8694
|
return () => {
|
|
8258
8695
|
stopped = true
|
|
@@ -8267,52 +8704,6 @@ ${recipe.triggers.map((t2, i) => emitTrigger(t2, i)).join("\n\n")}
|
|
|
8267
8704
|
}
|
|
8268
8705
|
`;
|
|
8269
8706
|
}
|
|
8270
|
-
var MACRO_LABELS = [
|
|
8271
|
-
{
|
|
8272
|
-
id: "person",
|
|
8273
|
-
name: "Person"
|
|
8274
|
-
},
|
|
8275
|
-
{
|
|
8276
|
-
id: "vehicle",
|
|
8277
|
-
name: "Vehicle"
|
|
8278
|
-
},
|
|
8279
|
-
{
|
|
8280
|
-
id: "animal",
|
|
8281
|
-
name: "Animal"
|
|
8282
|
-
},
|
|
8283
|
-
{
|
|
8284
|
-
id: "package",
|
|
8285
|
-
name: "Package"
|
|
8286
|
-
}
|
|
8287
|
-
];
|
|
8288
|
-
var DETECTION_MACRO_CLASSES = new Set(MACRO_LABELS.map((l) => l.id));
|
|
8289
|
-
var COCO_TO_MACRO = {
|
|
8290
|
-
mapping: {
|
|
8291
|
-
person: "person",
|
|
8292
|
-
bicycle: "vehicle",
|
|
8293
|
-
car: "vehicle",
|
|
8294
|
-
motorcycle: "vehicle",
|
|
8295
|
-
airplane: "vehicle",
|
|
8296
|
-
bus: "vehicle",
|
|
8297
|
-
train: "vehicle",
|
|
8298
|
-
truck: "vehicle",
|
|
8299
|
-
boat: "vehicle",
|
|
8300
|
-
bird: "animal",
|
|
8301
|
-
cat: "animal",
|
|
8302
|
-
dog: "animal",
|
|
8303
|
-
horse: "animal",
|
|
8304
|
-
sheep: "animal",
|
|
8305
|
-
cow: "animal",
|
|
8306
|
-
elephant: "animal",
|
|
8307
|
-
bear: "animal",
|
|
8308
|
-
zebra: "animal",
|
|
8309
|
-
giraffe: "animal",
|
|
8310
|
-
suitcase: "package",
|
|
8311
|
-
backpack: "package",
|
|
8312
|
-
handbag: "package"
|
|
8313
|
-
},
|
|
8314
|
-
preserveOriginal: false
|
|
8315
|
-
};
|
|
8316
8707
|
var MACRO_NAMES = new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
8317
8708
|
var TraceValueSchema = z4.union([
|
|
8318
8709
|
z4.string(),
|
|
@@ -8337,6 +8728,8 @@ var TraceNodeOutcomeSchema = z4.enum([
|
|
|
8337
8728
|
]);
|
|
8338
8729
|
var AutomationTriggerTraceSchema = z4.object({
|
|
8339
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(),
|
|
8340
8733
|
outcome: z4.enum(["pass", "fail"]),
|
|
8341
8734
|
/** Why an event was rejected ("class person not in [dog]") or what fired. */
|
|
8342
8735
|
reason: z4.string(),
|
|
@@ -8344,15 +8737,23 @@ var AutomationTriggerTraceSchema = z4.object({
|
|
|
8344
8737
|
vars: z4.record(z4.string(), TraceValueSchema)
|
|
8345
8738
|
});
|
|
8346
8739
|
var AutomationConditionTraceNodeSchema = z4.object({
|
|
8347
|
-
/** `
|
|
8740
|
+
/** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
|
|
8348
8741
|
path: z4.string().min(1),
|
|
8349
8742
|
kind: z4.enum([
|
|
8350
8743
|
"all",
|
|
8351
8744
|
"any",
|
|
8352
8745
|
"not",
|
|
8353
8746
|
"condition",
|
|
8747
|
+
"device-is",
|
|
8354
8748
|
"expression",
|
|
8355
|
-
"stationary-present"
|
|
8749
|
+
"stationary-present",
|
|
8750
|
+
"device-becomes",
|
|
8751
|
+
"device-event",
|
|
8752
|
+
"schedule",
|
|
8753
|
+
"interval",
|
|
8754
|
+
"manual",
|
|
8755
|
+
"detection",
|
|
8756
|
+
"stationary"
|
|
8356
8757
|
]),
|
|
8357
8758
|
outcome: TraceNodeOutcomeSchema,
|
|
8358
8759
|
reason: z4.string(),
|
|
@@ -8385,6 +8786,12 @@ var AutomationStepTraceSchema = z4.object({
|
|
|
8385
8786
|
deviceId: z4.number().int().optional(),
|
|
8386
8787
|
startedAt: z4.number().optional(),
|
|
8387
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(),
|
|
8388
8795
|
outcome: z4.enum([
|
|
8389
8796
|
"ok",
|
|
8390
8797
|
"error",
|
|
@@ -8600,7 +9007,7 @@ function checkPickedUnit(unit, target) {
|
|
|
8600
9007
|
target
|
|
8601
9008
|
});
|
|
8602
9009
|
}
|
|
8603
|
-
function normalizeLeaf(leaf, resolver,
|
|
9010
|
+
function normalizeLeaf(leaf, resolver, path) {
|
|
8604
9011
|
const quantity = resolver.fieldQuantity(leaf.cap, leaf.fieldPath);
|
|
8605
9012
|
const base = quantity === void 0 ? void 0 : baseUnitForQuantity(quantity);
|
|
8606
9013
|
const wantsUnit = isQuantityValue(leaf.value) || leaf.unit !== void 0;
|
|
@@ -8644,7 +9051,7 @@ function normalizeLeaf(leaf, resolver, path2) {
|
|
|
8644
9051
|
return {
|
|
8645
9052
|
leaf,
|
|
8646
9053
|
warnings: [bareWarning({
|
|
8647
|
-
path
|
|
9054
|
+
path,
|
|
8648
9055
|
deviceId: leaf.deviceId,
|
|
8649
9056
|
field: leaf.fieldPath,
|
|
8650
9057
|
value: leaf.value,
|
|
@@ -8652,17 +9059,17 @@ function normalizeLeaf(leaf, resolver, path2) {
|
|
|
8652
9059
|
})]
|
|
8653
9060
|
};
|
|
8654
9061
|
}
|
|
8655
|
-
function normalizeConditionUnits(condition, resolver,
|
|
9062
|
+
function normalizeConditionUnits(condition, resolver, path = "when") {
|
|
8656
9063
|
switch (condition.kind) {
|
|
8657
|
-
case "
|
|
8658
|
-
const { leaf, warnings } = normalizeLeaf(condition, resolver,
|
|
9064
|
+
case "device-is": {
|
|
9065
|
+
const { leaf, warnings } = normalizeLeaf(condition, resolver, path);
|
|
8659
9066
|
return {
|
|
8660
9067
|
value: leaf,
|
|
8661
9068
|
warnings
|
|
8662
9069
|
};
|
|
8663
9070
|
}
|
|
8664
9071
|
case "not": {
|
|
8665
|
-
const inner = normalizeConditionUnits(condition.child, resolver, `${
|
|
9072
|
+
const inner = normalizeConditionUnits(condition.child, resolver, `${path}.child`);
|
|
8666
9073
|
return {
|
|
8667
9074
|
value: {
|
|
8668
9075
|
...condition,
|
|
@@ -8673,7 +9080,7 @@ function normalizeConditionUnits(condition, resolver, path2 = "conditions") {
|
|
|
8673
9080
|
}
|
|
8674
9081
|
case "all":
|
|
8675
9082
|
case "any": {
|
|
8676
|
-
const children = condition.children.map((child, i) => normalizeConditionUnits(child, resolver, `${
|
|
9083
|
+
const children = condition.children.map((child, i) => normalizeConditionUnits(child, resolver, `${path}.children.${i}`));
|
|
8677
9084
|
return {
|
|
8678
9085
|
value: {
|
|
8679
9086
|
...condition,
|
|
@@ -8689,7 +9096,7 @@ function normalizeConditionUnits(condition, resolver, path2 = "conditions") {
|
|
|
8689
9096
|
};
|
|
8690
9097
|
}
|
|
8691
9098
|
}
|
|
8692
|
-
function normalizeCapStep(step, resolver,
|
|
9099
|
+
function normalizeCapStep(step, resolver, path) {
|
|
8693
9100
|
const deviceId = typeof step.deviceId === "number" ? step.deviceId : void 0;
|
|
8694
9101
|
const args = { ...step.args };
|
|
8695
9102
|
const argUnits = { ...step.argUnits };
|
|
@@ -8710,7 +9117,7 @@ function normalizeCapStep(step, resolver, path2) {
|
|
|
8710
9117
|
});
|
|
8711
9118
|
argUnits[arg] = pickedUnit(given.unit);
|
|
8712
9119
|
} else if (typeof given === "number" && argUnits[arg] === void 0 && hasUnitChoice(meta.quantity)) warnings.push(bareWarning({
|
|
8713
|
-
path: `${
|
|
9120
|
+
path: `${path}.args.${arg}`,
|
|
8714
9121
|
...deviceId === void 0 ? {} : { deviceId },
|
|
8715
9122
|
field: arg,
|
|
8716
9123
|
value: given,
|
|
@@ -8727,7 +9134,7 @@ function normalizeCapStep(step, resolver, path2) {
|
|
|
8727
9134
|
warnings
|
|
8728
9135
|
};
|
|
8729
9136
|
}
|
|
8730
|
-
function normalizeActionUnits(actions, resolver,
|
|
9137
|
+
function normalizeActionUnits(actions, resolver, path = "steps") {
|
|
8731
9138
|
const out = [];
|
|
8732
9139
|
const warnings = [];
|
|
8733
9140
|
actions.forEach((action, i) => {
|
|
@@ -8735,7 +9142,7 @@ function normalizeActionUnits(actions, resolver, path2 = "steps") {
|
|
|
8735
9142
|
out.push(action);
|
|
8736
9143
|
return;
|
|
8737
9144
|
}
|
|
8738
|
-
const normalized = normalizeCapStep(action, resolver, `${
|
|
9145
|
+
const normalized = normalizeCapStep(action, resolver, `${path}.${i}`);
|
|
8739
9146
|
out.push(normalized.value);
|
|
8740
9147
|
warnings.push(...normalized.warnings);
|
|
8741
9148
|
});
|
|
@@ -8745,20 +9152,20 @@ function normalizeActionUnits(actions, resolver, path2 = "steps") {
|
|
|
8745
9152
|
};
|
|
8746
9153
|
}
|
|
8747
9154
|
function normalizeRecipeUnits(recipe, resolver) {
|
|
8748
|
-
const
|
|
9155
|
+
const when = normalizeConditionUnits(recipe.when, resolver, "when");
|
|
8749
9156
|
const actions = normalizeActionUnits(recipe.actions, resolver, "actions");
|
|
8750
9157
|
return {
|
|
8751
9158
|
value: {
|
|
8752
9159
|
...recipe,
|
|
8753
|
-
|
|
9160
|
+
when: when.value,
|
|
8754
9161
|
actions: actions.value
|
|
8755
9162
|
},
|
|
8756
|
-
warnings: [...
|
|
9163
|
+
warnings: [...when.warnings, ...actions.warnings]
|
|
8757
9164
|
};
|
|
8758
9165
|
}
|
|
8759
9166
|
function stampCondition(condition, resolver) {
|
|
8760
9167
|
switch (condition.kind) {
|
|
8761
|
-
case "
|
|
9168
|
+
case "device-is": {
|
|
8762
9169
|
if (typeof condition.value !== "number" || condition.unit !== void 0) return condition;
|
|
8763
9170
|
const quantity = resolver.fieldQuantity(condition.cap, condition.fieldPath);
|
|
8764
9171
|
if (quantity === void 0 || unitsForQuantity(quantity).length < 2) return condition;
|
|
@@ -8799,7 +9206,7 @@ function stampAction(action, resolver) {
|
|
|
8799
9206
|
function prepareRecipeUnits(recipe, resolver) {
|
|
8800
9207
|
const stamped = {
|
|
8801
9208
|
...recipe,
|
|
8802
|
-
|
|
9209
|
+
when: stampCondition(recipe.when, resolver),
|
|
8803
9210
|
actions: recipe.actions.map((action) => stampAction(action, resolver))
|
|
8804
9211
|
};
|
|
8805
9212
|
try {
|
|
@@ -9288,6 +9695,25 @@ var addonSettingsCapability = {
|
|
|
9288
9695
|
addonId: z4.string(),
|
|
9289
9696
|
nodeId: z4.string().optional()
|
|
9290
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
|
+
}),
|
|
9291
9717
|
getDeviceSettings: method(z4.object({
|
|
9292
9718
|
addonId: z4.string(),
|
|
9293
9719
|
deviceId: z4.number(),
|
|
@@ -14154,6 +14580,8 @@ var StorageKindSchema = z4.object({
|
|
|
14154
14580
|
"orphaned",
|
|
14155
14581
|
"dropped"
|
|
14156
14582
|
]),
|
|
14583
|
+
/** An owner declared this kind since the engine started. */
|
|
14584
|
+
declaredThisBoot: z4.boolean(),
|
|
14157
14585
|
/** `null` when the caller asked for no counts (`counts: false`). */
|
|
14158
14586
|
rows: z4.number().int().nonnegative().nullable(),
|
|
14159
14587
|
/** Rows x the average size of a sample; null for an empty kind. */
|
|
@@ -14165,6 +14593,48 @@ var StorageKindSchema = z4.object({
|
|
|
14165
14593
|
/** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
|
|
14166
14594
|
declaration: RecordKindDeclarationSchema
|
|
14167
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
|
+
});
|
|
14168
14638
|
var storageAdminCapability = {
|
|
14169
14639
|
name: "storage-admin",
|
|
14170
14640
|
scope: "system",
|
|
@@ -14189,7 +14659,49 @@ var storageAdminCapability = {
|
|
|
14189
14659
|
readRecords: method(RecordQueryInputSchema.extend({
|
|
14190
14660
|
owner: z4.string().min(1),
|
|
14191
14661
|
kind: RecordKindNameSchema
|
|
14192
|
-
}), 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
|
+
})
|
|
14193
14705
|
}
|
|
14194
14706
|
};
|
|
14195
14707
|
var EntryKindInfoSchema = z4.object({
|
|
@@ -14321,6 +14833,19 @@ var keychainCapability = {
|
|
|
14321
14833
|
kind: "mutation",
|
|
14322
14834
|
auth: "admin"
|
|
14323
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
|
+
}),
|
|
14324
14849
|
/** The caller's own refs, metadata only. */
|
|
14325
14850
|
listMeta: method(z4.object({}), z4.array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }),
|
|
14326
14851
|
/** Whether values can be served — the save path refuses a write while locked. */
|
|
@@ -15215,7 +15740,9 @@ var DiscoveredChildDeviceSchema = z4.lazy(() => z4.object({
|
|
|
15215
15740
|
/** Upstream-system identity + rendering hints for the candidate. */
|
|
15216
15741
|
sourceInfo: SourceInfoSchema.optional(),
|
|
15217
15742
|
/** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
|
|
15218
|
-
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()
|
|
15219
15746
|
}));
|
|
15220
15747
|
var DeviceDiscoveryStatusSchema = z4.object({
|
|
15221
15748
|
discovered: z4.array(DiscoveredChildDeviceSchema),
|
|
@@ -35433,6 +35960,71 @@ var DiscoveredDeviceSchema = z4.object({
|
|
|
35433
35960
|
type: z4.string(),
|
|
35434
35961
|
metadata: z4.record(z4.string(), z4.unknown()).optional()
|
|
35435
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
|
+
});
|
|
35436
36028
|
var DiscoveryCandidateSchema = z4.object({
|
|
35437
36029
|
stableId: z4.string(),
|
|
35438
36030
|
type: z4.enum(DeviceType),
|
|
@@ -35463,7 +36055,17 @@ var DiscoveryCandidateSchema = z4.object({
|
|
|
35463
36055
|
/** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
|
|
35464
36056
|
onboardedDeviceId: z4.number().optional(),
|
|
35465
36057
|
/** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
|
|
35466
|
-
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()
|
|
35467
36069
|
});
|
|
35468
36070
|
var DeviceSummarySchema = z4.object({
|
|
35469
36071
|
id: z4.number(),
|
|
@@ -35541,6 +36143,24 @@ var deviceProviderCapability = {
|
|
|
35541
36143
|
auth: "admin"
|
|
35542
36144
|
}),
|
|
35543
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
|
+
/**
|
|
35544
36164
|
* Optional form schema (`ConfigUISchema`) for the EXTRA per-scan inputs a
|
|
35545
36165
|
* provider accepts (e.g. Gree's broadcast address for a different subnet).
|
|
35546
36166
|
* `null` when the provider takes no extra scan params — the generic
|
|
@@ -36796,20 +37416,26 @@ var deviceManagerCapability = {
|
|
|
36796
37416
|
label: z4.string()
|
|
36797
37417
|
})).readonly() }), { auth: "admin" }),
|
|
36798
37418
|
/** Scan EVERY discovery-capable provider; returns candidates grouped by addon. */
|
|
36799
|
-
discoverAllProviders: method(
|
|
36800
|
-
|
|
36801
|
-
|
|
36802
|
-
|
|
36803
|
-
|
|
36804
|
-
|
|
36805
|
-
|
|
36806
|
-
|
|
36807
|
-
|
|
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
|
+
),
|
|
36808
37433
|
/** Scan ONE provider with optional provider-specific params (subnet /
|
|
36809
37434
|
* broadcast addr) — the per-integration "Scan network" surface. */
|
|
36810
37435
|
discoverProvider: method(z4.object({
|
|
36811
37436
|
addonId: z4.string(),
|
|
36812
|
-
params: z4.record(z4.string(), z4.unknown()).optional()
|
|
37437
|
+
params: z4.record(z4.string(), z4.unknown()).optional(),
|
|
37438
|
+
subnets: z4.array(z4.string()).optional()
|
|
36813
37439
|
}), z4.object({ candidates: z4.array(DiscoveryCandidateSchema).readonly() }), {
|
|
36814
37440
|
kind: "mutation",
|
|
36815
37441
|
auth: "admin"
|
|
@@ -49774,6 +50400,12 @@ var TerminalInstanceInfoSchema = z4.object({
|
|
|
49774
50400
|
instanceId: z4.string(),
|
|
49775
50401
|
cameraStableId: z4.string(),
|
|
49776
50402
|
nodeId: z4.string(),
|
|
50403
|
+
/**
|
|
50404
|
+
* False while the node the instance is bound to is not in the cluster
|
|
50405
|
+
* topology (forgotten, renamed, re-paired). The instance is kept; the
|
|
50406
|
+
* operator re-points it with `updateInstance({ targetNodeId })`.
|
|
50407
|
+
*/
|
|
50408
|
+
nodeInCluster: z4.boolean(),
|
|
49777
50409
|
profileId: z4.string(),
|
|
49778
50410
|
profileLabel: z4.string(),
|
|
49779
50411
|
name: z4.string(),
|
|
@@ -49833,6 +50465,8 @@ var terminalSessionCapability = {
|
|
|
49833
50465
|
}),
|
|
49834
50466
|
updateInstance: method(z4.object({
|
|
49835
50467
|
instanceId: z4.string().min(1),
|
|
50468
|
+
/** Re-point the instance to another node; the profile must exist there. */
|
|
50469
|
+
targetNodeId: z4.string().min(1).max(256).optional(),
|
|
49836
50470
|
name: z4.string().trim().min(1).max(160).optional(),
|
|
49837
50471
|
executable: z4.string().max(1024).optional(),
|
|
49838
50472
|
args: z4.array(z4.string().max(2048)).max(64).optional(),
|
|
@@ -52992,7 +53626,14 @@ var RemoteLayoutGroupSchema = z4.object({
|
|
|
52992
53626
|
col: z4.number().int().min(0).max(24),
|
|
52993
53627
|
rowSpan: z4.number().int().min(1).max(24),
|
|
52994
53628
|
colSpan: z4.number().int().min(1).max(24),
|
|
52995
|
-
title: z4.string().min(1).max(40).optional()
|
|
53629
|
+
title: z4.string().min(1).max(40).optional(),
|
|
53630
|
+
/**
|
|
53631
|
+
* How much room a divider takes: `compact` (the default) is a slim rule,
|
|
53632
|
+
* `normal` a full grid row. Other frames ignore it.
|
|
53633
|
+
*/
|
|
53634
|
+
size: z4.enum(["compact", "normal"]).optional(),
|
|
53635
|
+
/** Keep the title in the layout but draw only the rule. */
|
|
53636
|
+
hideTitle: z4.boolean().optional()
|
|
52996
53637
|
});
|
|
52997
53638
|
function rectsOverlap(a, b) {
|
|
52998
53639
|
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;
|
|
@@ -53003,11 +53644,11 @@ function rectContains(outer, inner) {
|
|
|
53003
53644
|
function refineGroups(grid, ctx) {
|
|
53004
53645
|
const groups = grid.groups ?? [];
|
|
53005
53646
|
const byId = /* @__PURE__ */ new Map();
|
|
53006
|
-
const fail = (message,
|
|
53647
|
+
const fail = (message, path) => {
|
|
53007
53648
|
ctx.addIssue({
|
|
53008
53649
|
code: "custom",
|
|
53009
53650
|
message,
|
|
53010
|
-
path
|
|
53651
|
+
path
|
|
53011
53652
|
});
|
|
53012
53653
|
};
|
|
53013
53654
|
groups.forEach((group, index) => {
|
|
@@ -56177,7 +56818,9 @@ var integrationsCapability = {
|
|
|
56177
56818
|
id: z4.string(),
|
|
56178
56819
|
settings: z4.record(z4.string(), z4.unknown()),
|
|
56179
56820
|
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56180
|
-
secretKeys: z4.array(z4.string()).optional()
|
|
56821
|
+
secretKeys: z4.array(z4.string()).optional(),
|
|
56822
|
+
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56823
|
+
skipRestart: z4.boolean().optional()
|
|
56181
56824
|
}), z4.object({ success: z4.boolean() }), {
|
|
56182
56825
|
kind: "mutation",
|
|
56183
56826
|
auth: "admin"
|
|
@@ -56713,6 +57356,37 @@ var TlsStatusSchema = z4.object({
|
|
|
56713
57356
|
hasCa: z4.boolean()
|
|
56714
57357
|
}).nullable()
|
|
56715
57358
|
});
|
|
57359
|
+
var ScanRequestSchema = z4.object({
|
|
57360
|
+
providerId: z4.string().min(1),
|
|
57361
|
+
modes: z4.array(DiscoveryModeSchema)
|
|
57362
|
+
});
|
|
57363
|
+
var ScanErrorSchema = z4.object({
|
|
57364
|
+
/** Absent for a failure that is the scan's own (a bad subnet, a socket that would not open). */
|
|
57365
|
+
providerId: z4.string().optional(),
|
|
57366
|
+
/** The mode family that failed (`mdns`, `http-probe`, …). */
|
|
57367
|
+
mode: z4.string().optional(),
|
|
57368
|
+
message: z4.string()
|
|
57369
|
+
});
|
|
57370
|
+
var ScanStatsSchema = z4.object({
|
|
57371
|
+
hostsProbed: z4.number().int(),
|
|
57372
|
+
hits: z4.number().int(),
|
|
57373
|
+
timeouts: z4.number().int(),
|
|
57374
|
+
byMode: z4.record(z4.string(), z4.object({
|
|
57375
|
+
probed: z4.number().int(),
|
|
57376
|
+
hits: z4.number().int(),
|
|
57377
|
+
timeouts: z4.number().int()
|
|
57378
|
+
}))
|
|
57379
|
+
});
|
|
57380
|
+
var ScanNetworkResultSchema = z4.object({
|
|
57381
|
+
hits: z4.array(z4.object({
|
|
57382
|
+
providerId: z4.string(),
|
|
57383
|
+
hit: DiscoveryHitSchema
|
|
57384
|
+
})),
|
|
57385
|
+
/** CIDRs actually covered: the hub's own /24s, the saved list and the per-call list. */
|
|
57386
|
+
subnetsScanned: z4.array(z4.string()),
|
|
57387
|
+
errors: z4.array(ScanErrorSchema),
|
|
57388
|
+
stats: ScanStatsSchema
|
|
57389
|
+
});
|
|
56716
57390
|
var localNetworkCapability = {
|
|
56717
57391
|
name: "local-network",
|
|
56718
57392
|
scope: "system",
|
|
@@ -56926,6 +57600,34 @@ var localNetworkCapability = {
|
|
|
56926
57600
|
kind: "mutation",
|
|
56927
57601
|
auth: "admin"
|
|
56928
57602
|
}),
|
|
57603
|
+
/**
|
|
57604
|
+
* The LAN discovery engine. Every provider that declared discovery modes
|
|
57605
|
+
* (`device-provider.getDiscoveryModes`) is scanned in ONE pass: a single
|
|
57606
|
+
* mDNS browse for the union of service types, one WS-Discovery probe, one
|
|
57607
|
+
* SSDP search per target and one sweep that visits each host once and runs
|
|
57608
|
+
* every http-probe of every provider. Subnets scanned = the hub's own
|
|
57609
|
+
* non-internal IPv4 /24s ∪ the saved list (`getScanSubnets`) ∪ `subnets`.
|
|
57610
|
+
* A bad CIDR, or more than 4096 hosts in total, refuses the scan.
|
|
57611
|
+
*/
|
|
57612
|
+
scanNetwork: method(z4.object({
|
|
57613
|
+
requests: z4.array(ScanRequestSchema),
|
|
57614
|
+
subnets: z4.array(z4.string()).optional()
|
|
57615
|
+
}), ScanNetworkResultSchema, {
|
|
57616
|
+
kind: "mutation",
|
|
57617
|
+
auth: "admin",
|
|
57618
|
+
timeoutMs: 9e4
|
|
57619
|
+
}),
|
|
57620
|
+
/** Extra subnets every scan covers besides the hub's own, as CIDR strings. */
|
|
57621
|
+
getScanSubnets: method(z4.void(), z4.object({ subnets: z4.array(z4.string()) }), { auth: "admin" }),
|
|
57622
|
+
/**
|
|
57623
|
+
* Replace the saved extra subnets (a cluster setting). Each entry is IPv4
|
|
57624
|
+
* CIDR, at most a /22; the list is refused when it is invalid or its hosts
|
|
57625
|
+
* would exceed the scan cap.
|
|
57626
|
+
*/
|
|
57627
|
+
setScanSubnets: method(z4.object({ subnets: z4.array(z4.string()) }), z4.object({ subnets: z4.array(z4.string()) }), {
|
|
57628
|
+
kind: "mutation",
|
|
57629
|
+
auth: "admin"
|
|
57630
|
+
}),
|
|
56929
57631
|
/** Return to the generated local CA. The uploaded certificate stays stored (D873). */
|
|
56930
57632
|
revertToGeneratedCertificate: method(z4.void(), TlsStatusSchema, {
|
|
56931
57633
|
kind: "mutation",
|
|
@@ -66357,6 +67059,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
66357
67059
|
access: "create",
|
|
66358
67060
|
role: "admin"
|
|
66359
67061
|
},
|
|
67062
|
+
"addonSettings.getDeclaredSettingsKeys": {
|
|
67063
|
+
capName: "addon-settings",
|
|
67064
|
+
capScope: "system",
|
|
67065
|
+
addonId: null,
|
|
67066
|
+
access: "view",
|
|
67067
|
+
role: "admin"
|
|
67068
|
+
},
|
|
66360
67069
|
"addonSettings.getDeviceSettings": {
|
|
66361
67070
|
capName: "addon-settings",
|
|
66362
67071
|
capScope: "system",
|
|
@@ -66378,6 +67087,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
66378
67087
|
access: "view",
|
|
66379
67088
|
role: "admin"
|
|
66380
67089
|
},
|
|
67090
|
+
"addonSettings.unsetStoredSettingsKeys": {
|
|
67091
|
+
capName: "addon-settings",
|
|
67092
|
+
capScope: "system",
|
|
67093
|
+
addonId: null,
|
|
67094
|
+
access: "create",
|
|
67095
|
+
role: "admin"
|
|
67096
|
+
},
|
|
66381
67097
|
"addonSettings.updateDeviceSettings": {
|
|
66382
67098
|
capName: "addon-settings",
|
|
66383
67099
|
capScope: "system",
|
|
@@ -68793,6 +69509,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
68793
69509
|
access: "create",
|
|
68794
69510
|
role: "admin"
|
|
68795
69511
|
},
|
|
69512
|
+
"deviceProvider.classifyDiscoveryHits": {
|
|
69513
|
+
capName: "device-provider",
|
|
69514
|
+
capScope: "system",
|
|
69515
|
+
addonId: null,
|
|
69516
|
+
access: "create",
|
|
69517
|
+
role: "admin"
|
|
69518
|
+
},
|
|
68796
69519
|
"deviceProvider.createDevice": {
|
|
68797
69520
|
capName: "device-provider",
|
|
68798
69521
|
capScope: "system",
|
|
@@ -68821,6 +69544,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
68821
69544
|
access: "view",
|
|
68822
69545
|
role: "admin"
|
|
68823
69546
|
},
|
|
69547
|
+
"deviceProvider.getDiscoveryModes": {
|
|
69548
|
+
capName: "device-provider",
|
|
69549
|
+
capScope: "system",
|
|
69550
|
+
addonId: null,
|
|
69551
|
+
access: "view",
|
|
69552
|
+
role: "admin"
|
|
69553
|
+
},
|
|
68824
69554
|
"deviceProvider.getDiscoveryParamsSchema": {
|
|
68825
69555
|
capName: "device-provider",
|
|
68826
69556
|
capScope: "system",
|
|
@@ -69598,6 +70328,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
69598
70328
|
access: "create",
|
|
69599
70329
|
role: "admin"
|
|
69600
70330
|
},
|
|
70331
|
+
"keychain.releaseRecordRefs": {
|
|
70332
|
+
capName: "keychain",
|
|
70333
|
+
capScope: "system",
|
|
70334
|
+
addonId: null,
|
|
70335
|
+
access: "create",
|
|
70336
|
+
role: "admin"
|
|
70337
|
+
},
|
|
69601
70338
|
"keychain.resolve": {
|
|
69602
70339
|
capName: "keychain",
|
|
69603
70340
|
capScope: "system",
|
|
@@ -70018,6 +70755,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
70018
70755
|
access: "view",
|
|
70019
70756
|
role: "admin"
|
|
70020
70757
|
},
|
|
70758
|
+
"localNetwork.getScanSubnets": {
|
|
70759
|
+
capName: "local-network",
|
|
70760
|
+
capScope: "system",
|
|
70761
|
+
addonId: null,
|
|
70762
|
+
access: "view",
|
|
70763
|
+
role: "admin"
|
|
70764
|
+
},
|
|
70021
70765
|
"localNetwork.getTlsStatus": {
|
|
70022
70766
|
capName: "local-network",
|
|
70023
70767
|
capScope: "system",
|
|
@@ -70067,6 +70811,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
70067
70811
|
access: "create",
|
|
70068
70812
|
role: "admin"
|
|
70069
70813
|
},
|
|
70814
|
+
"localNetwork.scanNetwork": {
|
|
70815
|
+
capName: "local-network",
|
|
70816
|
+
capScope: "system",
|
|
70817
|
+
addonId: null,
|
|
70818
|
+
access: "create",
|
|
70819
|
+
role: "admin"
|
|
70820
|
+
},
|
|
70070
70821
|
"localNetwork.setAddressRole": {
|
|
70071
70822
|
capName: "local-network",
|
|
70072
70823
|
capScope: "system",
|
|
@@ -70088,6 +70839,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
70088
70839
|
access: "create",
|
|
70089
70840
|
role: "admin"
|
|
70090
70841
|
},
|
|
70842
|
+
"localNetwork.setScanSubnets": {
|
|
70843
|
+
capName: "local-network",
|
|
70844
|
+
capScope: "system",
|
|
70845
|
+
addonId: null,
|
|
70846
|
+
access: "create",
|
|
70847
|
+
role: "admin"
|
|
70848
|
+
},
|
|
70091
70849
|
"localNetwork.setViewerEndpoints": {
|
|
70092
70850
|
capName: "local-network",
|
|
70093
70851
|
capScope: "system",
|
|
@@ -74309,6 +75067,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
74309
75067
|
access: "create",
|
|
74310
75068
|
role: "admin"
|
|
74311
75069
|
},
|
|
75070
|
+
"storageAdmin.dropOrphanedKind": {
|
|
75071
|
+
capName: "storage-admin",
|
|
75072
|
+
capScope: "system",
|
|
75073
|
+
addonId: null,
|
|
75074
|
+
access: "delete",
|
|
75075
|
+
role: "admin"
|
|
75076
|
+
},
|
|
74312
75077
|
"storageAdmin.listKinds": {
|
|
74313
75078
|
capName: "storage-admin",
|
|
74314
75079
|
capScope: "system",
|
|
@@ -74330,6 +75095,27 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
74330
75095
|
access: "view",
|
|
74331
75096
|
role: "admin"
|
|
74332
75097
|
},
|
|
75098
|
+
"storageAdmin.scanStaleData": {
|
|
75099
|
+
capName: "storage-admin",
|
|
75100
|
+
capScope: "system",
|
|
75101
|
+
addonId: null,
|
|
75102
|
+
access: "create",
|
|
75103
|
+
role: "admin"
|
|
75104
|
+
},
|
|
75105
|
+
"storageAdmin.staleDataReport": {
|
|
75106
|
+
capName: "storage-admin",
|
|
75107
|
+
capScope: "system",
|
|
75108
|
+
addonId: null,
|
|
75109
|
+
access: "view",
|
|
75110
|
+
role: "admin"
|
|
75111
|
+
},
|
|
75112
|
+
"storageAdmin.unsetStaleSettingsKey": {
|
|
75113
|
+
capName: "storage-admin",
|
|
75114
|
+
capScope: "system",
|
|
75115
|
+
addonId: null,
|
|
75116
|
+
access: "create",
|
|
75117
|
+
role: "admin"
|
|
75118
|
+
},
|
|
74333
75119
|
"storageEvictable.evict": {
|
|
74334
75120
|
capName: "storage-evictable",
|
|
74335
75121
|
capScope: "system",
|
|
@@ -85270,81 +86056,15 @@ var SmartIrDocumentSchema = z4.object({
|
|
|
85270
86056
|
commands: z4.record(z4.string(), z4.unknown())
|
|
85271
86057
|
});
|
|
85272
86058
|
|
|
85273
|
-
// src/discovery/hub-finder.ts
|
|
85274
|
-
import * as fs from "fs";
|
|
85275
|
-
import * as path from "path";
|
|
85276
|
-
import {
|
|
85277
|
-
defaultSessionDir,
|
|
85278
|
-
isSessionFile,
|
|
85279
|
-
resolveHubUrl,
|
|
85280
|
-
validateHubAddress
|
|
85281
|
-
} from "@camstack/develop";
|
|
85282
|
-
import {
|
|
85283
|
-
discoverHubs
|
|
85284
|
-
} from "@camstack/develop/hub-discovery";
|
|
85285
|
-
function validateManualHubUrl(value) {
|
|
85286
|
-
return validateHubAddress(value);
|
|
85287
|
-
}
|
|
85288
|
-
function knownHubCandidates(deps) {
|
|
85289
|
-
const dir = deps.sessionDir ?? defaultSessionDir();
|
|
85290
|
-
const env = deps.env ?? process.env;
|
|
85291
|
-
const out = [];
|
|
85292
|
-
let files = [];
|
|
85293
|
-
try {
|
|
85294
|
-
files = fs.readdirSync(dir);
|
|
85295
|
-
} catch {
|
|
85296
|
-
files = [];
|
|
85297
|
-
}
|
|
85298
|
-
for (const file of files) {
|
|
85299
|
-
if (file.endsWith(".json")) {
|
|
85300
|
-
try {
|
|
85301
|
-
const parsed = JSON.parse(fs.readFileSync(path.join(dir, file), "utf8"));
|
|
85302
|
-
if (isSessionFile(parsed)) {
|
|
85303
|
-
out.push({
|
|
85304
|
-
url: parsed.server,
|
|
85305
|
-
source: `saved session ${parsed.username}`
|
|
85306
|
-
});
|
|
85307
|
-
}
|
|
85308
|
-
} catch {
|
|
85309
|
-
}
|
|
85310
|
-
}
|
|
85311
|
-
}
|
|
85312
|
-
const fromEnv = resolveHubUrl({ env, session: null });
|
|
85313
|
-
if (fromEnv) {
|
|
85314
|
-
const name = (env["CAMSTACK_HUB_URL"] ?? "").trim() === "" ? "CAMSTACK_SERVER" : "CAMSTACK_HUB_URL";
|
|
85315
|
-
out.push({ url: fromEnv.url, source: name });
|
|
85316
|
-
}
|
|
85317
|
-
if (deps.workspaceRoot !== void 0) {
|
|
85318
|
-
const scratch = resolveHubUrl({
|
|
85319
|
-
env: {},
|
|
85320
|
-
workspaceRoot: deps.workspaceRoot,
|
|
85321
|
-
session: null
|
|
85322
|
-
});
|
|
85323
|
-
if (scratch) out.push({ url: scratch.url, source: "scratchpad/.hub-url" });
|
|
85324
|
-
}
|
|
85325
|
-
return out;
|
|
85326
|
-
}
|
|
85327
|
-
async function findHubs(deps = {}) {
|
|
85328
|
-
return discoverHubs({
|
|
85329
|
-
known: knownHubCandidates(deps),
|
|
85330
|
-
...deps.browse ? { browse: deps.browse } : {},
|
|
85331
|
-
...deps.probe ? { probe: deps.probe } : {},
|
|
85332
|
-
...deps.mdnsTimeoutMs !== void 0 ? { mdnsTimeoutMs: deps.mdnsTimeoutMs } : {},
|
|
85333
|
-
...deps.onProblem ? { onProblem: deps.onProblem } : {}
|
|
85334
|
-
});
|
|
85335
|
-
}
|
|
85336
|
-
function describeHub(hub) {
|
|
85337
|
-
const identity = [hub.nodeId, hub.version === null ? null : `v${hub.version}`].filter((p) => p !== null).join(" ");
|
|
85338
|
-
return `${hub.url}${identity === "" ? "" : ` ${identity}`} (${hub.sources.join(", ")})`;
|
|
85339
|
-
}
|
|
85340
|
-
|
|
85341
86059
|
export {
|
|
85342
86060
|
isReadingQuantity,
|
|
85343
86061
|
FFMPEG_PATH_ENV,
|
|
86062
|
+
MACRO_LABELS,
|
|
86063
|
+
countWhenLeaves,
|
|
86064
|
+
convertLegacyWhen,
|
|
85344
86065
|
AutomationRecipeSchema,
|
|
85345
86066
|
validateRecipeBounds,
|
|
85346
86067
|
generateAutomationBlock,
|
|
85347
|
-
MACRO_LABELS,
|
|
85348
86068
|
unitsForQuantity,
|
|
85349
86069
|
capUnitResolver,
|
|
85350
86070
|
prepareRecipeUnits,
|
|
@@ -85355,8 +86075,5 @@ export {
|
|
|
85355
86075
|
DETECTOR_PERF,
|
|
85356
86076
|
ALL_CAPABILITY_DEFINITIONS,
|
|
85357
86077
|
classifyInferenceHardware,
|
|
85358
|
-
acceleratorKindOf
|
|
85359
|
-
validateManualHubUrl,
|
|
85360
|
-
findHubs,
|
|
85361
|
-
describeHub
|
|
86078
|
+
acceleratorKindOf
|
|
85362
86079
|
};
|