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.
@@ -1404,7 +1404,7 @@ var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
1404
1404
  return EventCategory2;
1405
1405
  })({});
1406
1406
 
1407
- // ../types/dist/sleep-63gM_1JR.mjs
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 path2 = prefix ? `${prefix}.${key}` : key;
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, path2));
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: path2,
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 AutomationConditionLeafSchema = z4.object({
7322
- kind: z4.literal("condition"),
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 AutomationConditionStationaryPresentSchema = z4.object({
7537
+ var StationaryPresentLeafSchema = z4.object({
7337
7538
  kind: z4.literal("stationary-present"),
7338
7539
  deviceId: z4.number().int().nonnegative(),
7339
- /** Class names; a macro class matches every raw class that maps to it. */
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 AutomationConditionExpressionSchema = z4.object({
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 AutomationConditionSchema = z4.lazy(() => z4.discriminatedUnion("kind", [
7555
+ var AutomationWhenSchema = z4.lazy(() => z4.discriminatedUnion("kind", [
7357
7556
  z4.object({
7358
7557
  kind: z4.literal("all"),
7359
- children: z4.array(AutomationConditionSchema)
7558
+ children: z4.array(AutomationWhenSchema)
7360
7559
  }),
7361
7560
  z4.object({
7362
7561
  kind: z4.literal("any"),
7363
- children: z4.array(AutomationConditionSchema)
7562
+ children: z4.array(AutomationWhenSchema)
7364
7563
  }),
7365
7564
  z4.object({
7366
7565
  kind: z4.literal("not"),
7367
- child: AutomationConditionSchema
7566
+ child: AutomationWhenSchema
7368
7567
  }),
7369
- AutomationConditionLeafSchema,
7370
- AutomationConditionExpressionSchema,
7371
- AutomationConditionStationaryPresentSchema
7568
+ DeviceBecomesLeafSchema,
7569
+ DeviceEventLeafSchema,
7570
+ ScheduleLeafSchema,
7571
+ ManualLeafSchema,
7572
+ IntervalLeafSchema,
7573
+ DetectionLeafSchema,
7574
+ StationaryLeafSchema,
7575
+ DeviceIsLeafSchema,
7576
+ StationaryPresentLeafSchema,
7577
+ ExpressionLeafSchema
7372
7578
  ]));
7373
- var AutomationTriggerSchema = z4.discriminatedUnion("kind", [
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 AutomationRecipeSchema = z4.object({
7499
- triggers: z4.array(AutomationTriggerSchema),
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
- function isLeaf(cond) {
7507
- return cond.kind === "condition" || cond.kind === "expression" || cond.kind === "stationary-present";
7508
- }
7509
- function conditionDepth(cond) {
7510
- if (isLeaf(cond)) return 1;
7511
- if (cond.kind === "not") return 1 + conditionDepth(cond.child);
7512
- const children = cond.children;
7513
- if (children.length === 0) return 1;
7514
- let deepest = 0;
7515
- for (const child of children) {
7516
- const d = conditionDepth(child);
7517
- if (d > deepest) deepest = d;
7518
- }
7519
- return 1 + deepest;
7520
- }
7521
- function countConditionLeaves(cond) {
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
- const cond = recipe.conditions;
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 emitTrigger(trigger, index) {
7960
+ function emitLeaf(trigger, path) {
7961
+ const at = j(path);
7569
7962
  switch (trigger.kind) {
7570
7963
  case "schedule":
7571
- return ` // trigger ${index}: schedule \u2014 armed to the next minute boundary and
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 ` // trigger ${index}: device event
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-state": {
7996
+ case "device-becomes": {
7603
7997
  const becomes = trigger.becomes === void 0 ? "undefined" : j(trigger.becomes);
7604
- return ` // trigger ${index}: device state \u2014 the mirror is the authority, the
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-state',
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 ` // trigger ${index}: manual \u2014 fired by automation-control.trigger
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 ` // trigger ${index}: interval \u2014 every ${trigger.minutes} min, first run jittered so
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 ` // trigger ${index}: detection \u2014 ONE fire per (device, track); a rejected event is
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 ` // trigger ${index}: stationary \u2014 a parked object appeared / departed. Stateless:
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 CONDITION reads the registry, not this event.
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
- collectMirrorSources,
8176
+ collectWhenMirrorSources,
7774
8177
  coreBlockIdFromAddonId,
7775
- evaluateConditionTree,
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
- const CONDITIONS = ${recipe.conditions === void 0 ? "undefined" : "AUTOMATION_RECIPE.conditions"}
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 mirror = new ConditionMirror(collectMirrorSources(CONDITIONS))
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 evaluation = evaluateConditionTree(CONDITIONS, mirror, Date.now())
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 === 'condition' && node.observed === null && node.deviceId !== undefined) {
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 conditions were false', { trigger, ...deviceTags(trigger) })
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
- if (CONDITIONS !== undefined) {
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(() => { void refreshMirror() }, RECONCILE_MS)
8686
+ const mirrorReconcile = setInterval(() => {
8687
+ void refreshMirror().then(evaluateStateTree)
8688
+ }, RECONCILE_MS)
8252
8689
  disposers.push(() => clearInterval(mirrorReconcile))
8253
8690
  }
8254
8691
 
8255
- ${recipe.triggers.map((t2, i) => emitTrigger(t2, i)).join("\n\n")}
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
- /** `conditions` for the root, `conditions.<i>` for its i-th child, … */
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, path2) {
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: path2,
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, path2 = "conditions") {
9062
+ function normalizeConditionUnits(condition, resolver, path = "when") {
8656
9063
  switch (condition.kind) {
8657
- case "condition": {
8658
- const { leaf, warnings } = normalizeLeaf(condition, resolver, path2);
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, `${path2}.child`);
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, `${path2}.children.${i}`));
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, path2) {
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: `${path2}.args.${arg}`,
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, path2 = "steps") {
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, `${path2}.${i}`);
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 conditions = recipe.conditions === void 0 ? void 0 : normalizeConditionUnits(recipe.conditions, resolver, "conditions");
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
- ...conditions === void 0 ? {} : { conditions: conditions.value },
9160
+ when: when.value,
8754
9161
  actions: actions.value
8755
9162
  },
8756
- warnings: [...conditions?.warnings ?? [], ...actions.warnings]
9163
+ warnings: [...when.warnings, ...actions.warnings]
8757
9164
  };
8758
9165
  }
8759
9166
  function stampCondition(condition, resolver) {
8760
9167
  switch (condition.kind) {
8761
- case "condition": {
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
- ...recipe.conditions === void 0 ? {} : { conditions: stampCondition(recipe.conditions, resolver) },
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(z4.object({}), z4.object({ groups: z4.array(z4.object({
36800
- addonId: z4.string(),
36801
- label: z4.string(),
36802
- candidates: z4.array(DiscoveryCandidateSchema).readonly(),
36803
- error: z4.string().nullable()
36804
- })).readonly() }), {
36805
- kind: "mutation",
36806
- auth: "admin"
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, path2) => {
53647
+ const fail = (message, path) => {
53007
53648
  ctx.addIssue({
53008
53649
  code: "custom",
53009
53650
  message,
53010
- path: path2
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
  };