@camstack/addon-post-analysis 1.2.653 → 1.2.654

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.
Files changed (16) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/{clip-model-registry-BrazZJvr.mjs → clip-model-registry-BX6j2lnF.mjs} +997 -147
  3. package/dist/{clip-model-registry-Bz1uQvQ8.js → clip-model-registry-CFQBxtE2.js} +997 -147
  4. package/dist/embedding-encoder/index.js +1 -1
  5. package/dist/embedding-encoder/index.mjs +1 -1
  6. package/dist/pipeline-analytics/_stub.js +2 -2
  7. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D6DnF6Vk.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Vag6rhEh.mjs} +2 -2
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BUwWKfHC.mjs +26 -0
  9. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DXQUFp7P.mjs +26 -0
  10. package/dist/pipeline-analytics/{hostInit-DtrcY1yz.mjs → hostInit-CnvnZsmk.mjs} +2 -2
  11. package/dist/pipeline-analytics/index.js +164 -10
  12. package/dist/pipeline-analytics/index.mjs +164 -10
  13. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  14. package/package.json +1 -1
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BKMr4IqL.mjs +0 -26
  16. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CtNyIYsM.mjs +0 -26
@@ -5676,7 +5676,7 @@ var ZodIssueCode = {
5676
5676
  var ZodFirstPartyTypeKind;
5677
5677
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5678
5678
  //#endregion
5679
- //#region ../types/dist/sleep-63gM_1JR.mjs
5679
+ //#region ../types/dist/sleep-CeyhrcOk.mjs
5680
5680
  /**
5681
5681
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5682
5682
  * window to float samples (D455).
@@ -7743,6 +7743,7 @@ var BaseAddon = class {
7743
7743
  * a warning and falls back rather than crashing boot.
7744
7744
  */
7745
7745
  state(key, schema, fallback) {
7746
+ this._declaredStateKeys.add(key);
7746
7747
  return createDurableState({
7747
7748
  key,
7748
7749
  schema,
@@ -7758,6 +7759,96 @@ var BaseAddon = class {
7758
7759
  } })
7759
7760
  });
7760
7761
  }
7762
+ /** Durable-state keys this process registered through {@link state}. */
7763
+ _declaredStateKeys = /* @__PURE__ */ new Set();
7764
+ /**
7765
+ * Stale-data audit: every global-settings key this addon still declares —
7766
+ * constructor defaults, settings-schema fields (layout containers included),
7767
+ * durable-state keys and the root of each `secretStatePaths` entry. Answered
7768
+ * here, in the addon's own process, so the hub keeps no copy of it.
7769
+ */
7770
+ async getDeclaredSettingsKeys() {
7771
+ return { keys: [...this.declaredKeySet()].toSorted() };
7772
+ }
7773
+ declaredKeySet() {
7774
+ const keys = new Set(Object.keys(this.defaults));
7775
+ const schema = this.globalSettingsSchema();
7776
+ if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
7777
+ for (const key of this._declaredStateKeys) keys.add(key);
7778
+ for (const path of this.secretStatePaths) {
7779
+ const root = path.split(/[.[]/, 1)[0];
7780
+ if (root) keys.add(root);
7781
+ }
7782
+ return keys;
7783
+ }
7784
+ /**
7785
+ * Stale-data audit: remove stored global-settings keys the addon no longer
7786
+ * declares, and clear the keychain values their markers held — except a value
7787
+ * a surviving key still points at. A declared or absent key is refused.
7788
+ */
7789
+ async unsetStoredSettingsKeys(keys) {
7790
+ const settings = this._ctx?.settings;
7791
+ const raw = this.rawSettings();
7792
+ if (!settings || !raw) return {
7793
+ removed: [],
7794
+ refused: keys.map((key) => ({
7795
+ key,
7796
+ reason: "no settings store"
7797
+ })),
7798
+ secretsCleared: 0
7799
+ };
7800
+ let stored;
7801
+ try {
7802
+ stored = await raw.readAddonStore();
7803
+ } catch (error) {
7804
+ const reason = `store unreadable: ${error instanceof Error ? error.message : "unknown"}`;
7805
+ return {
7806
+ removed: [],
7807
+ refused: keys.map((key) => ({
7808
+ key,
7809
+ reason
7810
+ })),
7811
+ secretsCleared: 0
7812
+ };
7813
+ }
7814
+ const declared = this.declaredKeySet();
7815
+ const removed = [];
7816
+ const refused = [];
7817
+ for (const key of keys) {
7818
+ const base = key.split("@", 1)[0] ?? key;
7819
+ if (declared.has(base)) refused.push({
7820
+ key,
7821
+ reason: "still declared by the addon"
7822
+ });
7823
+ else if (!(key in stored)) refused.push({
7824
+ key,
7825
+ reason: "not stored"
7826
+ });
7827
+ else removed.push(key);
7828
+ }
7829
+ if (removed.length === 0) return {
7830
+ removed,
7831
+ refused,
7832
+ secretsCleared: 0
7833
+ };
7834
+ const kept = /* @__PURE__ */ new Set();
7835
+ const held = /* @__PURE__ */ new Set();
7836
+ for (const [key, value] of Object.entries(stored)) {
7837
+ const target = removed.includes(key) ? held : kept;
7838
+ for (const ref of collectSecretMarkerRefs(value)) target.add(ref);
7839
+ }
7840
+ const refsToClear = [...held].filter((ref) => !kept.has(ref));
7841
+ const patch = {};
7842
+ for (const key of removed) patch[key] = void 0;
7843
+ await settings.writeAddonStore(patch);
7844
+ const secretsCleared = refsToClear.length > 0 && this._ctx?.secrets ? await this._ctx.secrets.delete(refsToClear) : 0;
7845
+ await this.resolveConfig();
7846
+ return {
7847
+ removed,
7848
+ refused,
7849
+ secretsCleared
7850
+ };
7851
+ }
7761
7852
  /** Per-device variant of {@link state}, backed by the per-device store. */
7762
7853
  deviceState(deviceId, key, schema, fallback) {
7763
7854
  return createDurableState({
@@ -8028,6 +8119,21 @@ function dropPerNodeFields(fields) {
8028
8119
  }
8029
8120
  return kept;
8030
8121
  }
8122
+ function collectFieldKeys(fields) {
8123
+ const collected = [];
8124
+ for (const field of fields) {
8125
+ if (field.type === "group") {
8126
+ collected.push(...collectFieldKeys(field.fields));
8127
+ continue;
8128
+ }
8129
+ if (field.type === "sub-tabs") {
8130
+ for (const tab of field.tabs) collected.push(...collectFieldKeys(tab.fields));
8131
+ continue;
8132
+ }
8133
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8134
+ }
8135
+ return collected;
8136
+ }
8031
8137
  function collectPerNodeFieldKeys(fields) {
8032
8138
  const collected = [];
8033
8139
  for (const field of fields) {
@@ -11231,7 +11337,7 @@ function sleep(ms) {
11231
11337
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
11232
11338
  }
11233
11339
  //#endregion
11234
- //#region ../types/dist/err-msg-DWXWc0Di.mjs
11340
+ //#region ../types/dist/err-msg-BD5buxvT.mjs
11235
11341
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
11236
11342
  DeviceType["Camera"] = "camera";
11237
11343
  DeviceType["Hub"] = "hub";
@@ -11337,6 +11443,12 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
11337
11443
  DeviceType["Washer"] = "washer";
11338
11444
  /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
11339
11445
  DeviceType["Dryer"] = "dryer";
11446
+ /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
11447
+ * other radio-to-IP bridges). It carries the box's own facts (firmware,
11448
+ * address, connection state) and the actions the box really supports
11449
+ * (`reboot`, `device-admin-link`). Products are NOT its children — they are
11450
+ * adopted beside it — and its connection settings stay on the integration. */
11451
+ DeviceType["Gateway"] = "gateway";
11340
11452
  return DeviceType;
11341
11453
  }({});
11342
11454
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -11597,7 +11709,7 @@ function errMsg(err) {
11597
11709
  return String(err);
11598
11710
  }
11599
11711
  //#endregion
11600
- //#region ../types/dist/composition-kSuOifuG.mjs
11712
+ //#region ../types/dist/composition-C7WdThmP.mjs
11601
11713
  /**
11602
11714
  * Deterministic SHA-256 hash of an arbitrary serialisable value. The
11603
11715
  * canonical form sorts object keys alphabetically at every depth so two
@@ -17566,8 +17678,194 @@ object({
17566
17678
  quantity: ReadingQuantitySchema,
17567
17679
  unit: string().min(1)
17568
17680
  });
17569
- /** How a leaf compares a device field to a value. Derived from the field's
17570
- * `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
17681
+ /**
17682
+ * The class names a per-class COUNT can ever be keyed on — the macro ids, and
17683
+ * only those.
17684
+ *
17685
+ * `preserveOriginal: false` below is the whole reason this set exists: the
17686
+ * executor maps each raw COCO label onto its macro and DROPS everything else,
17687
+ * so a tracked detection's `className` is always one of {@link MACRO_LABELS}.
17688
+ * Any surface that indexes counts by class name — the ZoneAnalytics occupancy
17689
+ * snapshot's `byClass`, and therefore every occupancy rule — can match nothing
17690
+ * else. Derived from the catalog, never hand-listed, so a new macro joins it by
17691
+ * being declared once.
17692
+ *
17693
+ * Cost of not having it, 2026-08-12: the notification rule editor offered the
17694
+ * full taxonomy for an occupancy class (occupancy is the one condition that
17695
+ * does not go through the engine's macro-expanding `expandClassSelector`), two
17696
+ * live rules were authored on `car`, and both counters read 0 forever.
17697
+ */
17698
+ var DETECTION_MACRO_CLASSES = new Set([
17699
+ {
17700
+ id: "person",
17701
+ name: "Person"
17702
+ },
17703
+ {
17704
+ id: "vehicle",
17705
+ name: "Vehicle"
17706
+ },
17707
+ {
17708
+ id: "animal",
17709
+ name: "Animal"
17710
+ },
17711
+ {
17712
+ id: "package",
17713
+ name: "Package"
17714
+ }
17715
+ ].map((l) => l.id));
17716
+ /** True when `className` is a macro the detector can actually emit (and hence a
17717
+ * class a count can be keyed on) — see {@link DETECTION_MACRO_CLASSES}. */
17718
+ function isDetectionMacroClass(className) {
17719
+ return DETECTION_MACRO_CLASSES.has(className.trim().toLowerCase());
17720
+ }
17721
+ var COCO_TO_MACRO = {
17722
+ mapping: {
17723
+ person: "person",
17724
+ bicycle: "vehicle",
17725
+ car: "vehicle",
17726
+ motorcycle: "vehicle",
17727
+ airplane: "vehicle",
17728
+ bus: "vehicle",
17729
+ train: "vehicle",
17730
+ truck: "vehicle",
17731
+ boat: "vehicle",
17732
+ bird: "animal",
17733
+ cat: "animal",
17734
+ dog: "animal",
17735
+ horse: "animal",
17736
+ sheep: "animal",
17737
+ cow: "animal",
17738
+ elephant: "animal",
17739
+ bear: "animal",
17740
+ zebra: "animal",
17741
+ giraffe: "animal",
17742
+ suitcase: "package",
17743
+ backpack: "package",
17744
+ handbag: "package"
17745
+ },
17746
+ preserveOriginal: false
17747
+ };
17748
+ /**
17749
+ * WHAT a detection-shaped leaf is about: one MACRO class (person, vehicle,
17750
+ * animal, package) plus an OPTIONAL label that narrows it.
17751
+ *
17752
+ * The taxonomy is `COCO_TO_MACRO` (and `CLASS_MAP_MACRO_TARGETS`) — never a
17753
+ * second list. The label that fits depends on the macro class:
17754
+ *
17755
+ * - `animal` → a sub-class (`dog`, `cat`, …);
17756
+ * - `person` → a known face (an identity of the `face-gallery`);
17757
+ * - `vehicle` → a plate (its text) or a sub-class (`car`, `truck`, …);
17758
+ * - `package` → a sub-class.
17759
+ *
17760
+ * `other` exists only so a class the taxonomy does not know (a custom model's
17761
+ * `forklift`) survives the conversion of a stored recipe without widening.
17762
+ *
17763
+ * A label is decided LATE (D683): face recognition and LPR land after the
17764
+ * detection starts. The track-lifecycle `update` event carries the newly
17765
+ * resolved `label` / `plateText`, which is why a leaf with a label fires when
17766
+ * the subject GETS it — not at the first detection.
17767
+ */
17768
+ var SubClassLabelSchema = object({
17769
+ kind: literal("sub-class"),
17770
+ className: string().min(1)
17771
+ });
17772
+ var IdentityLabelSchema = object({
17773
+ kind: literal("identity"),
17774
+ /** The `face-gallery` identity id (stable across a rename). */
17775
+ identityId: string().min(1),
17776
+ /** The identity's name — what a track-lifecycle event carries as `label`. */
17777
+ name: string().min(1)
17778
+ });
17779
+ var PlateLabelSchema = object({
17780
+ kind: literal("plate"),
17781
+ text: string().min(1)
17782
+ });
17783
+ function macroOf$1(className) {
17784
+ return COCO_TO_MACRO.mapping[className.toLowerCase()] ?? className.toLowerCase();
17785
+ }
17786
+ /** A sub-class label must belong to the macro it is under. */
17787
+ function subClassBelongs(macro, label) {
17788
+ return label === void 0 || macroOf$1(label.className) === macro;
17789
+ }
17790
+ var subClassOf = (macro) => SubClassLabelSchema.refine((l) => subClassBelongs(macro, l), { message: `that class is not a kind of ${macro} in the detection taxonomy` });
17791
+ var DetectionSubjectSchema = discriminatedUnion("macro", [
17792
+ object({
17793
+ macro: literal("animal"),
17794
+ label: subClassOf("animal").optional()
17795
+ }),
17796
+ object({
17797
+ macro: literal("person"),
17798
+ label: IdentityLabelSchema.optional()
17799
+ }),
17800
+ object({
17801
+ macro: literal("vehicle"),
17802
+ label: discriminatedUnion("kind", [SubClassLabelSchema.refine((l) => subClassBelongs("vehicle", l), { message: "that class is not a kind of vehicle in the detection taxonomy" }), PlateLabelSchema]).optional()
17803
+ }),
17804
+ object({
17805
+ macro: literal("package"),
17806
+ label: subClassOf("package").optional()
17807
+ }),
17808
+ object({
17809
+ macro: literal("other"),
17810
+ className: string().min(1)
17811
+ })
17812
+ ]);
17813
+ /**
17814
+ * The subject of a PARKED object. The stationary registry's event carries a
17815
+ * class and a zone, no face and no plate — an identity or plate label there
17816
+ * could never match, so the schema refuses it instead of letting it sit dead.
17817
+ */
17818
+ var StationarySubjectSchema = DetectionSubjectSchema.refine((s) => s.macro === "other" || s.label === void 0 || s.label.kind === "sub-class", { message: "a parked object carries no face or plate — only a class can narrow it" });
17819
+ /**
17820
+ * A stored class string as a subject: a macro name stays a macro; a raw class
17821
+ * the taxonomy files under a macro becomes that macro plus the sub-class label
17822
+ * (`dog` → animal / dog); anything else is `other`, kept verbatim.
17823
+ */
17824
+ function subjectFromClassName(className) {
17825
+ const name = className.trim().toLowerCase();
17826
+ const macro = COCO_TO_MACRO.mapping[name];
17827
+ if (new Set(Object.values(COCO_TO_MACRO.mapping)).has(name)) return macroOnly(name);
17828
+ if (macro === void 0) return {
17829
+ macro: "other",
17830
+ className
17831
+ };
17832
+ const label = {
17833
+ kind: "sub-class",
17834
+ className: name
17835
+ };
17836
+ switch (macro) {
17837
+ case "animal": return {
17838
+ macro: "animal",
17839
+ label
17840
+ };
17841
+ case "vehicle": return {
17842
+ macro: "vehicle",
17843
+ label
17844
+ };
17845
+ case "package": return {
17846
+ macro: "package",
17847
+ label
17848
+ };
17849
+ default: return {
17850
+ macro: "other",
17851
+ className
17852
+ };
17853
+ }
17854
+ }
17855
+ function macroOnly(macro) {
17856
+ switch (macro) {
17857
+ case "animal": return { macro: "animal" };
17858
+ case "person": return { macro: "person" };
17859
+ case "vehicle": return { macro: "vehicle" };
17860
+ case "package": return { macro: "package" };
17861
+ default: return {
17862
+ macro: "other",
17863
+ className: macro
17864
+ };
17865
+ }
17866
+ }
17867
+ /** How a state leaf compares a device field to a value. Derived from the
17868
+ * field's `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
17571
17869
  var AutomationConditionOperatorSchema = _enum([
17572
17870
  "eq",
17573
17871
  "ne",
@@ -17578,12 +17876,76 @@ var AutomationConditionOperatorSchema = _enum([
17578
17876
  "contains",
17579
17877
  "in"
17580
17878
  ]);
17581
- var AutomationConditionLeafSchema = object({
17582
- kind: literal("condition"),
17879
+ /**
17880
+ * D8 compliance, and it is the reason a "becomes" leaf is not merely an event
17881
+ * subscription: it evaluates against the **state mirror**, which is reconciled,
17882
+ * and an event only WAKES the evaluation. A dropped event therefore DELAYS a
17883
+ * fire; it does not lose it.
17884
+ */
17885
+ var DeviceBecomesLeafSchema = object({
17886
+ kind: literal("device-becomes"),
17887
+ deviceId: number().int().nonnegative(),
17888
+ cap: string().min(1),
17889
+ fieldPath: string().min(1),
17890
+ /** Fire when the field takes this value. Omit to fire on any change. */
17891
+ becomes: union([
17892
+ string(),
17893
+ number(),
17894
+ boolean()
17895
+ ]).optional(),
17896
+ /** Only on a CHANGE of value, not on every re-report. */
17897
+ edge: boolean().optional(),
17898
+ /** The new value must hold this long before it counts. */
17899
+ forMs: number().int().min(0).max(864e5).optional(),
17900
+ /** Collapse a burst into one occurrence. */
17901
+ debounceMs: number().int().min(0).max(6e5).optional()
17902
+ });
17903
+ var DeviceEventLeafSchema = object({
17904
+ kind: literal("device-event"),
17905
+ /** An `EventCategory` value. */
17906
+ category: string().min(1),
17907
+ deviceId: number().int().nonnegative().optional()
17908
+ });
17909
+ var ScheduleLeafSchema = object({
17910
+ kind: literal("schedule"),
17911
+ cron: string().min(1).max(120)
17912
+ });
17913
+ var ManualLeafSchema = object({ kind: literal("manual") });
17914
+ var IntervalLeafSchema = object({
17915
+ kind: literal("interval"),
17916
+ minutes: number().int().min(1).max(1440)
17917
+ });
17918
+ var DetectionLeafSchema = object({
17919
+ kind: literal("detection"),
17920
+ /** One macro class each, optionally narrowed by a label; a track of any listed subject fires. */
17921
+ subjects: array(DetectionSubjectSchema).min(1),
17922
+ deviceIds: array(number().int().nonnegative()).min(1),
17923
+ /** The track must have visited at least one of these zones. */
17924
+ zones: array(string().min(1)).min(1).optional(),
17925
+ /** Best-so-far confidence the track must reach. */
17926
+ minScore: number().min(0).max(1).optional()
17927
+ });
17928
+ var StationaryLeafSchema = object({
17929
+ kind: literal("stationary"),
17930
+ deviceIds: array(number().int().nonnegative()).min(1),
17931
+ subjects: array(StationarySubjectSchema).min(1).optional(),
17932
+ zones: array(string().min(1)).min(1).optional(),
17933
+ /** `appeared`: it became parked. `departed`: the registry retired it. */
17934
+ phase: _enum(["appeared", "departed"])
17935
+ });
17936
+ /** "is": one device field compared to one value, NOW. */
17937
+ var DeviceIsLeafSchema = object({
17938
+ kind: literal("device-is"),
17583
17939
  deviceId: number().int().nonnegative(),
17584
17940
  cap: string().min(1),
17585
17941
  fieldPath: string().min(1),
17586
17942
  operator: AutomationConditionOperatorSchema,
17943
+ /**
17944
+ * The value. A number on a quantity field is stored in the field's base unit
17945
+ * (D944); a quantity value is the WRITE form, which the save boundary
17946
+ * replaces with the converted number and `unit`. A leaf that still carries
17947
+ * one is never evaluated (it fails).
17948
+ */
17587
17949
  value: union([
17588
17950
  string(),
17589
17951
  number(),
@@ -17591,24 +17953,26 @@ var AutomationConditionLeafSchema = object({
17591
17953
  array(union([string(), number()])),
17592
17954
  QuantityValueSchema
17593
17955
  ]),
17956
+ /** The unit the person picked for `value` (display only; `value` is in the base unit). */
17594
17957
  unit: string().min(1).optional()
17595
17958
  });
17596
- var AutomationConditionStationaryPresentSchema = object({
17959
+ /**
17960
+ * True when the camera currently has a PARKED object matching `subjects` in at
17961
+ * least one of `zones`. Reads the zone-analytics snapshot through the same
17962
+ * mirror as every other state leaf, so a dropped event never decides it (D49).
17963
+ */
17964
+ var StationaryPresentLeafSchema = object({
17597
17965
  kind: literal("stationary-present"),
17598
17966
  deviceId: number().int().nonnegative(),
17599
- /** Class names; a macro class matches every raw class that maps to it. */
17600
- classes: array(string().min(1)).min(1).optional(),
17601
- /** The parked object must be in at least one of these zones. */
17967
+ subjects: array(StationarySubjectSchema).min(1).optional(),
17602
17968
  zones: array(string().min(1)).min(1).optional()
17603
17969
  });
17604
17970
  /**
17605
- * The expression leaf, declared as a plain object rather than an intersection
17606
- * with {@link ExpressionSourceSchema}: a discriminated union has to be able to
17607
- * read `kind` off each option, and an intersection hides it. The author-time
17608
- * validation is the SAME function `ExpressionSourceSchema` runs, so the two
17609
- * cannot drift — an expression that one accepts, the other accepts.
17971
+ * The escape hatch: the bounded expression engine over named device bindings.
17972
+ * Declared as a plain object so a discriminated union can read `kind`; the
17973
+ * author-time validation is the SAME function `ExpressionSourceSchema` runs.
17610
17974
  */
17611
- var AutomationConditionExpressionSchema = object({
17975
+ var ExpressionLeafSchema = object({
17612
17976
  kind: literal("expression"),
17613
17977
  expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
17614
17978
  bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
@@ -17620,55 +17984,62 @@ var AutomationConditionExpressionSchema = object({
17620
17984
  path: ["expr"]
17621
17985
  });
17622
17986
  });
17623
- var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
17987
+ var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
17624
17988
  object({
17625
17989
  kind: literal("all"),
17626
- children: array(AutomationConditionSchema)
17990
+ children: array(AutomationWhenSchema)
17627
17991
  }),
17628
17992
  object({
17629
17993
  kind: literal("any"),
17630
- children: array(AutomationConditionSchema)
17994
+ children: array(AutomationWhenSchema)
17631
17995
  }),
17632
17996
  object({
17633
17997
  kind: literal("not"),
17634
- child: AutomationConditionSchema
17998
+ child: AutomationWhenSchema
17635
17999
  }),
17636
- AutomationConditionLeafSchema,
17637
- AutomationConditionExpressionSchema,
17638
- AutomationConditionStationaryPresentSchema
18000
+ DeviceBecomesLeafSchema,
18001
+ DeviceEventLeafSchema,
18002
+ ScheduleLeafSchema,
18003
+ ManualLeafSchema,
18004
+ IntervalLeafSchema,
18005
+ DetectionLeafSchema,
18006
+ StationaryLeafSchema,
18007
+ DeviceIsLeafSchema,
18008
+ StationaryPresentLeafSchema,
18009
+ ExpressionLeafSchema
17639
18010
  ]));
17640
18011
  /**
17641
- * What starts a run.
17642
- *
17643
- * D8 compliance, and it is the reason `device-state` is not merely an event
17644
- * subscription: the trigger evaluates against the **state mirror**, which is
17645
- * reconciled, and an event only WAKES the evaluation. A dropped event therefore
17646
- * DELAYS a trigger; it does not lose it. `schedule` uses `croner` — the one
17647
- * already in the repo — because `setInterval(24h)` drifts and "at 23:30" does
17648
- * not.
17649
- */
17650
- var AutomationTriggerSchema = discriminatedUnion("kind", [
18012
+ * The recipe shape before the single `when` tree: a flat list of triggers
18013
+ * (OR'd, each an instant) and an optional condition tree. Stored recipes —
18014
+ * the `AUTOMATION_RECIPE` literal of every block saved so far, and whatever an
18015
+ * older client still posts — are in this shape, so it stays READABLE here, and
18016
+ * {@link convertLegacyRecipe} turns it into the new one, losslessly and one
18017
+ * way. Nothing writes this shape any more.
18018
+ *
18019
+ * `{ triggers: [t1…tn], conditions?: C }`
18020
+ * → `when = all(any(t1…tn), C)`, or `any(t1…tn)` without `C`;
18021
+ * a single trigger is not wrapped in `any`.
18022
+ * a `condition` leaf → a `device-is` leaf (the "is" verb);
18023
+ * a `device-state` trigger → a `device-becomes` leaf (the "becomes" verb);
18024
+ * `classes: ['dog']` → the macro `animal` with the label `dog`.
18025
+ */
18026
+ var LegacyTriggerSchema = discriminatedUnion("kind", [
17651
18027
  object({
17652
18028
  kind: literal("device-state"),
17653
18029
  deviceId: number().int().nonnegative(),
17654
18030
  cap: string().min(1),
17655
18031
  fieldPath: string().min(1),
17656
- /** Fire when the field takes this value. Omit to fire on any change. */
17657
18032
  becomes: union([
17658
18033
  string(),
17659
18034
  number(),
17660
18035
  boolean()
17661
18036
  ]).optional(),
17662
- /** Only on a CHANGE of value, not on every re-report. */
17663
18037
  edge: boolean().optional(),
17664
- /** The condition must hold this long before the run starts. */
17665
18038
  forMs: number().int().min(0).max(864e5).optional(),
17666
- /** Collapse a burst into one run. */
17667
18039
  debounceMs: number().int().min(0).max(6e5).optional()
17668
18040
  }),
17669
18041
  object({
17670
18042
  kind: literal("device-event"),
17671
- /** An `EventCategory` value. */
17672
18043
  category: string().min(1),
17673
18044
  deviceId: number().int().nonnegative().optional()
17674
18045
  }),
@@ -17679,25 +18050,16 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
17679
18050
  object({ kind: literal("manual") }),
17680
18051
  object({
17681
18052
  kind: literal("detection"),
17682
- /** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
17683
- * matches every raw class that maps to it; any other name matches
17684
- * exactly. */
17685
18053
  classes: array(string().min(1)).min(1),
17686
18054
  deviceIds: array(number().int().nonnegative()).min(1),
17687
- /** The track must have visited at least one of these zones. */
17688
18055
  zones: array(string().min(1)).min(1).optional(),
17689
- /** Best-so-far confidence the track must reach. */
17690
18056
  minScore: number().min(0).max(1).optional()
17691
18057
  }),
17692
18058
  object({
17693
18059
  kind: literal("stationary"),
17694
18060
  deviceIds: array(number().int().nonnegative()).min(1),
17695
- /** Class names; a macro class matches like the detection trigger. */
17696
18061
  classes: array(string().min(1)).min(1).optional(),
17697
- /** The parked object must be in at least one of these zones. */
17698
18062
  zones: array(string().min(1)).min(1).optional(),
17699
- /** `appeared`: it became parked. `departed`: the registry retired it.
17700
- * The vocabulary of `PipelineAnalyticsStationaryChanged`. */
17701
18063
  phase: _enum(["appeared", "departed"])
17702
18064
  }),
17703
18065
  object({
@@ -17705,6 +18067,143 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
17705
18067
  minutes: number().int().min(1).max(1440)
17706
18068
  })
17707
18069
  ]);
18070
+ var LegacyConditionLeafSchema = object({
18071
+ kind: literal("condition"),
18072
+ deviceId: number().int().nonnegative(),
18073
+ cap: string().min(1),
18074
+ fieldPath: string().min(1),
18075
+ operator: AutomationConditionOperatorSchema,
18076
+ value: union([
18077
+ string(),
18078
+ number(),
18079
+ boolean(),
18080
+ array(union([string(), number()])),
18081
+ QuantityValueSchema
18082
+ ]),
18083
+ unit: string().min(1).optional()
18084
+ });
18085
+ var LegacyStationaryPresentSchema = object({
18086
+ kind: literal("stationary-present"),
18087
+ deviceId: number().int().nonnegative(),
18088
+ classes: array(string().min(1)).min(1).optional(),
18089
+ zones: array(string().min(1)).min(1).optional()
18090
+ });
18091
+ var LegacyExpressionSchema = object({
18092
+ kind: literal("expression"),
18093
+ expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
18094
+ bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
18095
+ }).superRefine((src, ctx) => {
18096
+ const err = validateExpressionSource(src);
18097
+ if (err !== null) ctx.addIssue({
18098
+ code: "custom",
18099
+ message: err,
18100
+ path: ["expr"]
18101
+ });
18102
+ });
18103
+ var LegacyConditionSchema = lazy(() => discriminatedUnion("kind", [
18104
+ object({
18105
+ kind: literal("all"),
18106
+ children: array(LegacyConditionSchema)
18107
+ }),
18108
+ object({
18109
+ kind: literal("any"),
18110
+ children: array(LegacyConditionSchema)
18111
+ }),
18112
+ object({
18113
+ kind: literal("not"),
18114
+ child: LegacyConditionSchema
18115
+ }),
18116
+ LegacyConditionLeafSchema,
18117
+ LegacyExpressionSchema,
18118
+ LegacyStationaryPresentSchema
18119
+ ]));
18120
+ function convertTrigger(trigger) {
18121
+ switch (trigger.kind) {
18122
+ case "device-state": return {
18123
+ ...trigger,
18124
+ kind: "device-becomes"
18125
+ };
18126
+ case "detection": {
18127
+ const { classes, ...rest } = trigger;
18128
+ return {
18129
+ ...rest,
18130
+ subjects: classes.map(subjectFromClassName)
18131
+ };
18132
+ }
18133
+ case "stationary": {
18134
+ const { classes, ...rest } = trigger;
18135
+ return {
18136
+ ...rest,
18137
+ ...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
18138
+ };
18139
+ }
18140
+ default: return trigger;
18141
+ }
18142
+ }
18143
+ /** An old condition tree as a `when` subtree: `condition` → "is", classes → subjects. */
18144
+ function convertLegacyCondition(condition) {
18145
+ switch (condition.kind) {
18146
+ case "condition": return {
18147
+ ...condition,
18148
+ kind: "device-is"
18149
+ };
18150
+ case "stationary-present": {
18151
+ const { classes, ...rest } = condition;
18152
+ return {
18153
+ ...rest,
18154
+ ...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
18155
+ };
18156
+ }
18157
+ case "expression": return condition;
18158
+ case "not": return {
18159
+ kind: "not",
18160
+ child: convertLegacyCondition(condition.child)
18161
+ };
18162
+ case "all":
18163
+ case "any": return {
18164
+ kind: condition.kind,
18165
+ children: condition.children.map(convertLegacyCondition)
18166
+ };
18167
+ }
18168
+ }
18169
+ /** `all(any(t1…tn), C)`, or `any(t1…tn)`; a single trigger is not wrapped in `any`. */
18170
+ function convertLegacyWhen(half) {
18171
+ const leaves = half.triggers.map(convertTrigger);
18172
+ const first = leaves[0];
18173
+ const triggers = leaves.length === 1 && first !== void 0 ? first : {
18174
+ kind: "any",
18175
+ children: leaves
18176
+ };
18177
+ if (half.conditions === void 0) return triggers;
18178
+ return {
18179
+ kind: "all",
18180
+ children: [triggers, convertLegacyCondition(half.conditions)]
18181
+ };
18182
+ }
18183
+ /**
18184
+ * The automation RECIPE — what a visually-authored automation is, before it
18185
+ * becomes code.
18186
+ *
18187
+ * This is the data half of `docs/design/2026-08-08-automation-scripts-custom-devices.md`
18188
+ * Phase 6. A recipe is not an engine and never evaluates anything: it is a
18189
+ * declarative description that {@link generateAutomationBlock} compiles to the
18190
+ * TypeScript of an ordinary core block. There is therefore no second rule
18191
+ * engine, no second store and no second runtime — the block runner is the
18192
+ * runtime, `core_blocks` is the store (§2.1).
18193
+ *
18194
+ * Two parts:
18195
+ *
18196
+ * - **`when`** — ONE tree (`./when.ts`) of all/any/not over event leaves
18197
+ * (instants) and state leaves (durations). It replaces the flat trigger list
18198
+ * plus the separate condition tree; the firing rule is in that file and in
18199
+ * ADR "one tree".
18200
+ * - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
18201
+ * fragment of §3.2.2. See {@link AutomationActionSchema} for the ONE place
18202
+ * this had to diverge from "verbatim", and why.
18203
+ *
18204
+ * A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
18205
+ * {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
18206
+ */
17708
18207
  /**
17709
18208
  * One action step.
17710
18209
  *
@@ -17807,91 +18306,49 @@ var AutomationRunModeSchema = discriminatedUnion("mode", [
17807
18306
  minutes: number().int().min(1).max(1440)
17808
18307
  })
17809
18308
  ]);
17810
- var AutomationRecipeSchema = object({
17811
- triggers: array(AutomationTriggerSchema),
17812
- conditions: AutomationConditionSchema.optional(),
18309
+ var CurrentAutomationRecipeSchema = object({
18310
+ when: AutomationWhenSchema,
17813
18311
  actions: array(AutomationActionSchema),
17814
18312
  runMode: AutomationRunModeSchema.optional(),
17815
18313
  /** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
17816
18314
  keepTraces: number().int().min(1).max(100).optional()
17817
18315
  });
18316
+ /** A recipe as it was stored before the single tree. Read-only: nothing writes this. */
18317
+ var LegacyAutomationRecipeSchema = object({
18318
+ triggers: array(LegacyTriggerSchema),
18319
+ conditions: LegacyConditionSchema.optional(),
18320
+ actions: array(AutomationActionSchema),
18321
+ runMode: AutomationRunModeSchema.optional(),
18322
+ keepTraces: number().int().min(1).max(100).optional()
18323
+ });
18324
+ /** The old shape as the new one, lossless. */
18325
+ function convertLegacyRecipe(legacy) {
18326
+ const { triggers, conditions, ...rest } = legacy;
18327
+ return {
18328
+ when: convertLegacyWhen({
18329
+ triggers,
18330
+ conditions
18331
+ }),
18332
+ ...rest
18333
+ };
18334
+ }
17818
18335
  /**
17819
- * The class names a per-class COUNT can ever be keyed on — the macro ids, and
17820
- * only those.
17821
- *
17822
- * `preserveOriginal: false` below is the whole reason this set exists: the
17823
- * executor maps each raw COCO label onto its macro and DROPS everything else,
17824
- * so a tracked detection's `className` is always one of {@link MACRO_LABELS}.
17825
- * Any surface that indexes counts by class name — the ZoneAnalytics occupancy
17826
- * snapshot's `byClass`, and therefore every occupancy rule — can match nothing
17827
- * else. Derived from the catalog, never hand-listed, so a new macro joins it by
17828
- * being declared once.
17829
- *
17830
- * Cost of not having it, 2026-08-12: the notification rule editor offered the
17831
- * full taxonomy for an occupancy class (occupancy is the one condition that
17832
- * does not go through the engine's macro-expanding `expandClassSelector`), two
17833
- * live rules were authored on `car`, and both counters read 0 forever.
18336
+ * Where recipes are PARSED (the core-blocks cap, the MCP tool, the literal a
18337
+ * saved block carries): the new shape as it is, or the old shape converted to
18338
+ * it. The output type is always the new one.
17834
18339
  */
17835
- var DETECTION_MACRO_CLASSES = new Set([
17836
- {
17837
- id: "person",
17838
- name: "Person"
17839
- },
17840
- {
17841
- id: "vehicle",
17842
- name: "Vehicle"
17843
- },
17844
- {
17845
- id: "animal",
17846
- name: "Animal"
17847
- },
17848
- {
17849
- id: "package",
17850
- name: "Package"
17851
- }
17852
- ].map((l) => l.id));
17853
- /** True when `className` is a macro the detector can actually emit (and hence a
17854
- * class a count can be keyed on) — see {@link DETECTION_MACRO_CLASSES}. */
17855
- function isDetectionMacroClass(className) {
17856
- return DETECTION_MACRO_CLASSES.has(className.trim().toLowerCase());
17857
- }
17858
- var COCO_TO_MACRO = {
17859
- mapping: {
17860
- person: "person",
17861
- bicycle: "vehicle",
17862
- car: "vehicle",
17863
- motorcycle: "vehicle",
17864
- airplane: "vehicle",
17865
- bus: "vehicle",
17866
- train: "vehicle",
17867
- truck: "vehicle",
17868
- boat: "vehicle",
17869
- bird: "animal",
17870
- cat: "animal",
17871
- dog: "animal",
17872
- horse: "animal",
17873
- sheep: "animal",
17874
- cow: "animal",
17875
- elephant: "animal",
17876
- bear: "animal",
17877
- zebra: "animal",
17878
- giraffe: "animal",
17879
- suitcase: "package",
17880
- backpack: "package",
17881
- handbag: "package"
17882
- },
17883
- preserveOriginal: false
17884
- };
18340
+ var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
17885
18341
  new Set(Object.values(COCO_TO_MACRO.mapping));
17886
18342
  /**
17887
18343
  * Run traces — what an automation (or, later, an Action run) DID, node by node,
17888
18344
  * so the editor can paint every block green or red with its reason.
17889
18345
  *
17890
- * A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
17891
- * every condition node (containers and leaves, including the ones a
17892
- * short-circuit never reached), the run-mode verdict and every action step —
17893
- * each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
17894
- * for an Action run). Skipped
18346
+ * A trace is data, not a log: it covers the WHOLE tree — the event leaf that
18347
+ * fired, every node of the `when` tree (containers and leaves, including the
18348
+ * ones a short-circuit never reached), the run-mode verdict and every action
18349
+ * step — each addressed by a stable path (`when.0.1`, `actions.2`, or `steps.N`
18350
+ * for an Action run). Traces written before the single tree carry
18351
+ * `conditions.*` paths and still parse. Skipped
17895
18352
  * evaluations are traced too, and identical consecutive skips collapse into a
17896
18353
  * repeat count so a trigger that fails every 5 minutes cannot flush real runs.
17897
18354
  *
@@ -17923,6 +18380,8 @@ var TraceNodeOutcomeSchema = _enum([
17923
18380
  ]);
17924
18381
  var AutomationTriggerTraceSchema = object({
17925
18382
  kind: string().min(1),
18383
+ /** The event leaf that fired (`when`, `when.0.1`); absent on traces written before the single tree. */
18384
+ path: string().min(1).optional(),
17926
18385
  outcome: _enum(["pass", "fail"]),
17927
18386
  /** Why an event was rejected ("class person not in [dog]") or what fired. */
17928
18387
  reason: string(),
@@ -17930,15 +18389,23 @@ var AutomationTriggerTraceSchema = object({
17930
18389
  vars: record(string(), TraceValueSchema)
17931
18390
  });
17932
18391
  var AutomationConditionTraceNodeSchema = object({
17933
- /** `conditions` for the root, `conditions.<i>` for its i-th child, … */
18392
+ /** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
17934
18393
  path: string().min(1),
17935
18394
  kind: _enum([
17936
18395
  "all",
17937
18396
  "any",
17938
18397
  "not",
17939
18398
  "condition",
18399
+ "device-is",
17940
18400
  "expression",
17941
- "stationary-present"
18401
+ "stationary-present",
18402
+ "device-becomes",
18403
+ "device-event",
18404
+ "schedule",
18405
+ "interval",
18406
+ "manual",
18407
+ "detection",
18408
+ "stationary"
17942
18409
  ]),
17943
18410
  outcome: TraceNodeOutcomeSchema,
17944
18411
  reason: string(),
@@ -17972,6 +18439,12 @@ var AutomationStepTraceSchema = object({
17972
18439
  deviceId: number().int().optional(),
17973
18440
  startedAt: number().optional(),
17974
18441
  endedAt: number().optional(),
18442
+ /**
18443
+ * The parameters the step SENT, after `{{vars.x}}` substitution — what the
18444
+ * device was actually asked. Bounded like trigger `vars`; a secret parameter
18445
+ * is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
18446
+ */
18447
+ args: record(string(), TraceValueSchema).optional(),
17975
18448
  outcome: _enum([
17976
18449
  "ok",
17977
18450
  "error",
@@ -18721,6 +19194,23 @@ method(object({
18721
19194
  addonId: string(),
18722
19195
  nodeId: string().optional()
18723
19196
  }), SettingsSchemaWithValuesSchema.nullable()), method(object({
19197
+ addonId: string(),
19198
+ nodeId: string().optional()
19199
+ }), object({ keys: array(string()) }).nullable(), { auth: "admin" }), method(object({
19200
+ addonId: string(),
19201
+ nodeId: string().optional(),
19202
+ keys: array(string())
19203
+ }), object({
19204
+ removed: array(string()),
19205
+ refused: array(object({
19206
+ key: string(),
19207
+ reason: string()
19208
+ })),
19209
+ secretsCleared: number()
19210
+ }), {
19211
+ kind: "mutation",
19212
+ auth: "admin"
19213
+ }), method(object({
18724
19214
  addonId: string(),
18725
19215
  deviceId: number(),
18726
19216
  nodeId: string().optional()
@@ -20175,8 +20665,21 @@ var BrokerProviderInfoSchema = object({
20175
20665
  * (`{ sessionId }`) drops the session. The dialog that edits a broker
20176
20666
  * offers it as the default path; the fields the session fills are
20177
20667
  * marked `filledBySignIn` in the kind's settings schema.
20668
+ *
20669
+ * `methods` lists the ways the session can sign in, recommended first:
20670
+ * `'device-code'` adds `POST {basePath}/code/start` (body: the form's
20671
+ * settings) answering `{ sessionId, userCode, verificationUrl,
20672
+ * expiresAt }` — the operator types `userCode` at `verificationUrl` on
20673
+ * any device, and the same `status` / `commit` / `cancel` routes follow
20674
+ * (status then carries `method`, `userCode`, `verificationUrl`,
20675
+ * `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
20676
+ * `'proxy'` is the `{basePath}/start` flow above. Absent means
20677
+ * `['proxy']`.
20178
20678
  */
20179
- signIn: object({ basePath: string().min(1) }).optional()
20679
+ signIn: object({
20680
+ basePath: string().min(1),
20681
+ methods: array(_enum(["device-code", "proxy"])).optional()
20682
+ }).optional()
20180
20683
  }))
20181
20684
  });
20182
20685
  var ListInputSchema = object({
@@ -22767,6 +23270,8 @@ var StorageKindSchema = object({
22767
23270
  "orphaned",
22768
23271
  "dropped"
22769
23272
  ]),
23273
+ /** An owner declared this kind since the engine started. */
23274
+ declaredThisBoot: boolean(),
22770
23275
  /** `null` when the caller asked for no counts (`counts: false`). */
22771
23276
  rows: number().int().nonnegative().nullable(),
22772
23277
  /** Rows x the average size of a sample; null for an empty kind. */
@@ -22778,6 +23283,51 @@ var StorageKindSchema = object({
22778
23283
  /** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
22779
23284
  declaration: RecordKindDeclarationSchema
22780
23285
  });
23286
+ _enum([
23287
+ "orphaned",
23288
+ "stale",
23289
+ "unknown"
23290
+ ]);
23291
+ /** A record kind nobody declared since its owner started, or whose owner is gone. */
23292
+ var StaleKindFindingSchema = object({
23293
+ owner: string(),
23294
+ kind: string(),
23295
+ /** `unknown`: the owner could not be judged (not running, or its state unreadable) - never dropped. */
23296
+ verdict: _enum(["orphaned", "unknown"]),
23297
+ reason: string(),
23298
+ rows: number().int().nonnegative().nullable(),
23299
+ /** local-auth's tables are never dropped by this audit. */
23300
+ protected: boolean()
23301
+ });
23302
+ /** A stored global-settings key the running addon no longer declares. */
23303
+ var StaleSettingsKeyFindingSchema = object({
23304
+ addonId: string(),
23305
+ /** The stored key as written, a per-node `<key>@<nodeId>` suffix included. */
23306
+ key: string(),
23307
+ /**
23308
+ * Keychain refs whose ONLY marker sits under this key: in use by stale data,
23309
+ * not orphans yet. Removing the key releases them.
23310
+ */
23311
+ secretRefs: array(string())
23312
+ });
23313
+ /** An addon the audit could not judge, with the reason. Never reported as stale. */
23314
+ var StaleUnknownSchema = object({
23315
+ subject: string(),
23316
+ reason: string()
23317
+ });
23318
+ var StaleDataReportSchema = object({
23319
+ /** Epoch ms of the last finished scan; `null` before the first. */
23320
+ scannedAt: number().nullable(),
23321
+ scanning: boolean(),
23322
+ trigger: _enum([
23323
+ "boot",
23324
+ "interval",
23325
+ "manual"
23326
+ ]).nullable(),
23327
+ kinds: array(StaleKindFindingSchema),
23328
+ settingsKeys: array(StaleSettingsKeyFindingSchema),
23329
+ unknown: array(StaleUnknownSchema)
23330
+ });
22781
23331
  method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
22782
23332
  owner: string().min(1).optional(),
22783
23333
  /** `false` skips the per-kind row count (a catalog read only). Default true. */
@@ -22785,7 +23335,30 @@ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: arr
22785
23335
  }), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
22786
23336
  owner: string().min(1),
22787
23337
  kind: RecordKindNameSchema
22788
- }), RecordQueryResultSchema, { auth: "admin" });
23338
+ }), RecordQueryResultSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, {
23339
+ kind: "mutation",
23340
+ auth: "admin"
23341
+ }), method(object({
23342
+ owner: string().min(1),
23343
+ kind: RecordKindNameSchema
23344
+ }), object({
23345
+ deleted: number().int().nonnegative(),
23346
+ secretsReleased: number().int().nonnegative()
23347
+ }), {
23348
+ kind: "mutation",
23349
+ auth: "admin",
23350
+ caller: "required"
23351
+ }), method(object({
23352
+ addonId: string().min(1),
23353
+ key: string().min(1)
23354
+ }), object({
23355
+ removed: boolean(),
23356
+ secretsCleared: number().int().nonnegative()
23357
+ }), {
23358
+ kind: "mutation",
23359
+ auth: "admin",
23360
+ caller: "required"
23361
+ });
22789
23362
  /**
22790
23363
  * record-entries - the ONE admin surface for every list of user-created entries
22791
23364
  * (D881): notification targets, brokers, LLM profiles, anything an addon
@@ -22924,6 +23497,12 @@ method(object({
22924
23497
  }), method(object({ deviceId: number().int() }), object({ refs: array(string()).readonly() }), {
22925
23498
  kind: "mutation",
22926
23499
  auth: "admin"
23500
+ }), method(object({
23501
+ refs: RefListSchema,
23502
+ onBehalfOf: string().min(1)
23503
+ }), object({ released: number().int() }), {
23504
+ kind: "mutation",
23505
+ auth: "admin"
22927
23506
  }), method(object({}), array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }), method(object({}), KeychainStateSchema, { auth: "admin" });
22928
23507
  /**
22929
23508
  * device-history — the ONE mechanism for numeric history (D850).
@@ -23713,7 +24292,9 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
23713
24292
  /** Upstream-system identity + rendering hints for the candidate. */
23714
24293
  sourceInfo: SourceInfoSchema.optional(),
23715
24294
  /** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
23716
- children: array(DiscoveredChildDeviceSchema).readonly().optional()
24295
+ children: array(DiscoveredChildDeviceSchema).readonly().optional(),
24296
+ /** `childNativeId` of the gateway candidate in the same list this device sits behind. */
24297
+ parentStableId: string().optional()
23717
24298
  }));
23718
24299
  /**
23719
24300
  * Status block — populated by the integration after each
@@ -44079,6 +44660,79 @@ object({
44079
44660
  metadata: record(string(), unknown()).optional()
44080
44661
  });
44081
44662
  /**
44663
+ * HOW a provider can be found on the network. The provider only DECLARES the
44664
+ * mode; the `local-network` scan engine runs it once for every provider that
44665
+ * asked (one multicast browse, one host sweep) and hands the raw hits back to
44666
+ * `classifyDiscoveryHits`. A provider never opens a discovery socket itself,
44667
+ * except through a `custom` mode.
44668
+ */
44669
+ var DiscoveryModeSchema = discriminatedUnion("kind", [
44670
+ object({
44671
+ kind: literal("mdns"),
44672
+ /** DNS-SD service type, e.g. `_esphomelib._tcp`. */
44673
+ serviceType: string().min(1),
44674
+ /** Regular expression an instance name must match (case-insensitive). */
44675
+ instancePattern: string().optional()
44676
+ }),
44677
+ object({
44678
+ kind: literal("ws-discovery"),
44679
+ /** Probe types (e.g. `dn:NetworkVideoTransmitter`); omitted = any device. */
44680
+ types: array(string()).optional()
44681
+ }),
44682
+ object({
44683
+ kind: literal("ssdp"),
44684
+ searchTarget: string().min(1)
44685
+ }),
44686
+ object({
44687
+ kind: literal("udp-broadcast"),
44688
+ port: number().int().min(1).max(65535),
44689
+ payloadHex: string().regex(/^([0-9a-fA-F]{2})*$/),
44690
+ /** Port replies arrive on, when the device answers somewhere other than the source port. */
44691
+ listenPort: number().int().min(1).max(65535).optional()
44692
+ }),
44693
+ object({
44694
+ kind: literal("http-probe"),
44695
+ port: number().int().min(1).max(65535),
44696
+ path: string().startsWith("/"),
44697
+ method: _enum(["GET", "POST"]).default("GET"),
44698
+ bodyText: string().optional(),
44699
+ contentType: string().optional(),
44700
+ tls: boolean().default(false),
44701
+ /** Status codes that count as a hit. Omitted = any HTTP answer. */
44702
+ acceptStatus: array(number().int()).optional()
44703
+ }),
44704
+ object({
44705
+ kind: literal("custom"),
44706
+ /** Why the engine cannot do it (an allowlisted exception, e.g. a vendor UDP protocol). */
44707
+ reason: string().min(1)
44708
+ })
44709
+ ]);
44710
+ /** What the engine saw on the wire for ONE declared mode; the provider classifies it. */
44711
+ var DiscoveryHitSchema = object({
44712
+ /** Index into the provider's own `getDiscoveryModes()` array. */
44713
+ modeIndex: number().int().min(0),
44714
+ host: string(),
44715
+ port: number().int(),
44716
+ http: object({
44717
+ status: number().int(),
44718
+ headers: record(string(), string()),
44719
+ /** At most 16 KiB. */
44720
+ bodyText: string()
44721
+ }).optional(),
44722
+ mdns: object({
44723
+ instance: string(),
44724
+ host: string(),
44725
+ port: number().int(),
44726
+ txt: record(string(), string())
44727
+ }).optional(),
44728
+ udp: object({ payloadHex: string() }).optional(),
44729
+ wsd: object({
44730
+ xaddrs: array(string()),
44731
+ types: array(string()),
44732
+ scopes: array(string())
44733
+ }).optional()
44734
+ });
44735
+ /**
44082
44736
  * Candidate handed back from discovery and accepted by
44083
44737
  * `adoptDiscoveredDevice`. Shape mirrors the in-process
44084
44738
  * `DiscoveredDevice` interface used by `DeviceDiscovery`.
@@ -44113,7 +44767,17 @@ var DiscoveryCandidateSchema = object({
44113
44767
  /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
44114
44768
  onboardedDeviceId: number().optional(),
44115
44769
  /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
44116
- onboardedName: string().optional()
44770
+ onboardedName: string().optional(),
44771
+ /**
44772
+ * This candidate is an INTEGRATION to set up (a gateway, a hub, a controller),
44773
+ * not a device to adopt. Replaces the `prefilledConfig.setupIntegration` marker,
44774
+ * which is still read for one release.
44775
+ */
44776
+ setupIntegration: boolean().optional(),
44777
+ /** `stableId` of the gateway candidate this device was found BEHIND. */
44778
+ parentStableId: string().optional(),
44779
+ /** `false` = shown for context but not pickable. Default true. */
44780
+ selectable: boolean().optional()
44117
44781
  });
44118
44782
  /**
44119
44783
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -44166,6 +44830,12 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
44166
44830
  }), method(object({}), boolean()), method(object({ params: record(string(), unknown()).optional() }), array(DiscoveryCandidateSchema), {
44167
44831
  kind: "mutation",
44168
44832
  auth: "admin"
44833
+ }), method(object({}), array(DiscoveryModeSchema)), method(object({
44834
+ hits: array(DiscoveryHitSchema),
44835
+ params: record(string(), unknown()).optional()
44836
+ }), array(DiscoveryCandidateSchema), {
44837
+ kind: "mutation",
44838
+ auth: "admin"
44169
44839
  }), method(object({}), CreationSchemaOutputSchema), method(object({}), object({ deviceType: _enum(DeviceType).nullable() })), method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
44170
44840
  kind: "mutation",
44171
44841
  auth: "admin"
@@ -44897,17 +45567,23 @@ method(object({
44897
45567
  }), method(object({}), object({ providers: array(object({
44898
45568
  addonId: string(),
44899
45569
  label: string()
44900
- })).readonly() }), { auth: "admin" }), method(object({}), object({ groups: array(object({
44901
- addonId: string(),
44902
- label: string(),
44903
- candidates: array(DiscoveryCandidateSchema).readonly(),
44904
- error: string().nullable()
44905
- })).readonly() }), {
44906
- kind: "mutation",
44907
- auth: "admin"
44908
- }), method(object({
45570
+ })).readonly() }), { auth: "admin" }), method(
45571
+ /** `subnets`: extra CIDRs for THIS scan, on top of the hub's own and the saved list. */
45572
+ object({ subnets: array(string()).optional() }),
45573
+ object({ groups: array(object({
45574
+ addonId: string(),
45575
+ label: string(),
45576
+ candidates: array(DiscoveryCandidateSchema).readonly(),
45577
+ error: string().nullable()
45578
+ })).readonly() }),
45579
+ {
45580
+ kind: "mutation",
45581
+ auth: "admin"
45582
+ }
45583
+ ), method(object({
44909
45584
  addonId: string(),
44910
- params: record(string(), unknown()).optional()
45585
+ params: record(string(), unknown()).optional(),
45586
+ subnets: array(string()).optional()
44911
45587
  }), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
44912
45588
  kind: "mutation",
44913
45589
  auth: "admin"
@@ -59615,6 +60291,12 @@ var TerminalInstanceInfoSchema = object({
59615
60291
  instanceId: string(),
59616
60292
  cameraStableId: string(),
59617
60293
  nodeId: string(),
60294
+ /**
60295
+ * False while the node the instance is bound to is not in the cluster
60296
+ * topology (forgotten, renamed, re-paired). The instance is kept; the
60297
+ * operator re-points it with `updateInstance({ targetNodeId })`.
60298
+ */
60299
+ nodeInCluster: boolean(),
59618
60300
  profileId: string(),
59619
60301
  profileLabel: string(),
59620
60302
  name: string(),
@@ -59664,6 +60346,8 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
59664
60346
  auth: "admin"
59665
60347
  }), method(object({
59666
60348
  instanceId: string().min(1),
60349
+ /** Re-point the instance to another node; the profile must exist there. */
60350
+ targetNodeId: string().min(1).max(256).optional(),
59667
60351
  name: string().trim().min(1).max(160).optional(),
59668
60352
  executable: string().max(1024).optional(),
59669
60353
  args: array(string().max(2048)).max(64).optional(),
@@ -66399,7 +67083,9 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
66399
67083
  id: string(),
66400
67084
  settings: record(string(), unknown()),
66401
67085
  /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
66402
- secretKeys: array(string()).optional()
67086
+ secretKeys: array(string()).optional(),
67087
+ /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
67088
+ skipRestart: boolean().optional()
66403
67089
  }), object({ success: boolean() }), {
66404
67090
  kind: "mutation",
66405
67091
  auth: "admin"
@@ -67027,6 +67713,39 @@ var TlsStatusSchema = object({
67027
67713
  hasCa: boolean()
67028
67714
  }).nullable()
67029
67715
  });
67716
+ /** One provider's ask of the scan engine: the modes it declared. */
67717
+ var ScanRequestSchema = object({
67718
+ providerId: string().min(1),
67719
+ modes: array(DiscoveryModeSchema)
67720
+ });
67721
+ var ScanErrorSchema = object({
67722
+ /** Absent for a failure that is the scan's own (a bad subnet, a socket that would not open). */
67723
+ providerId: string().optional(),
67724
+ /** The mode family that failed (`mdns`, `http-probe`, …). */
67725
+ mode: string().optional(),
67726
+ message: string()
67727
+ });
67728
+ /** What one scan did, per mode family; the engine logs the same numbers once. */
67729
+ var ScanStatsSchema = object({
67730
+ hostsProbed: number().int(),
67731
+ hits: number().int(),
67732
+ timeouts: number().int(),
67733
+ byMode: record(string(), object({
67734
+ probed: number().int(),
67735
+ hits: number().int(),
67736
+ timeouts: number().int()
67737
+ }))
67738
+ });
67739
+ var ScanNetworkResultSchema = object({
67740
+ hits: array(object({
67741
+ providerId: string(),
67742
+ hit: DiscoveryHitSchema
67743
+ })),
67744
+ /** CIDRs actually covered: the hub's own /24s, the saved list and the per-call list. */
67745
+ subnetsScanned: array(string()),
67746
+ errors: array(ScanErrorSchema),
67747
+ stats: ScanStatsSchema
67748
+ });
67030
67749
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
67031
67750
  /**
67032
67751
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -67081,6 +67800,16 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
67081
67800
  }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
67082
67801
  kind: "mutation",
67083
67802
  auth: "admin"
67803
+ }), method(object({
67804
+ requests: array(ScanRequestSchema),
67805
+ subnets: array(string()).optional()
67806
+ }), ScanNetworkResultSchema, {
67807
+ kind: "mutation",
67808
+ auth: "admin",
67809
+ timeoutMs: 9e4
67810
+ }), method(_void(), object({ subnets: array(string()) }), { auth: "admin" }), method(object({ subnets: array(string()) }), object({ subnets: array(string()) }), {
67811
+ kind: "mutation",
67812
+ auth: "admin"
67084
67813
  }), method(_void(), TlsStatusSchema, {
67085
67814
  kind: "mutation",
67086
67815
  auth: "admin"
@@ -70881,7 +71610,7 @@ var runningStateCapability = {
70881
71610
  volatileStateFields: ["lastFetchedAt"],
70882
71611
  customize: runningCustomize
70883
71612
  };
70884
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
71613
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
70885
71614
  kind: "mutation",
70886
71615
  auth: "admin"
70887
71616
  });
@@ -75574,52 +76303,89 @@ btn("power", 0, 0, {
75574
76303
  role: "power",
75575
76304
  shape: "round",
75576
76305
  tone: "danger"
75577
- }), btn("up", 0, 1, { role: "up" }), btn("left", 1, 0, { role: "left" }), btn("ok", 1, 1, {
76306
+ }), btn("up", 0, 1, {
76307
+ role: "up",
76308
+ icon: "chevron-up"
76309
+ }), btn("left", 1, 0, {
76310
+ role: "left",
76311
+ icon: "chevron-left"
76312
+ }), btn("ok", 1, 1, {
75578
76313
  role: "ok",
75579
76314
  shape: "round",
75580
76315
  tone: "accent"
75581
- }), btn("right", 1, 2, { role: "right" }), btn("down", 2, 1, { role: "down" }), rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
76316
+ }), btn("right", 1, 2, {
76317
+ role: "right",
76318
+ icon: "chevron-right"
76319
+ }), btn("down", 2, 1, {
76320
+ role: "down",
76321
+ icon: "chevron-down"
76322
+ }), rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
75582
76323
  role: "volume_up",
76324
+ icon: "plus",
75583
76325
  shape: "pill"
75584
76326
  }), btn("down", 1, 0, {
75585
76327
  role: "volume_down",
76328
+ icon: "minus",
75586
76329
  shape: "pill"
75587
76330
  })], {
75588
76331
  rows: 2,
75589
76332
  cols: 1
75590
76333
  }), rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
75591
76334
  role: "channel_up",
76335
+ icon: "chevron-up",
75592
76336
  shape: "pill"
75593
76337
  }), btn("down", 1, 0, {
75594
76338
  role: "channel_down",
76339
+ icon: "chevron-down",
75595
76340
  shape: "pill"
75596
76341
  })], {
75597
76342
  rows: 2,
75598
76343
  cols: 1
75599
76344
  }), rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
75600
76345
  role: "custom:minus",
75601
- label: "−",
76346
+ label: "Minus",
76347
+ icon: "minus",
75602
76348
  shape: "pill"
75603
76349
  }), btn("plus", 0, 1, {
75604
76350
  role: "custom:plus",
75605
- label: "+",
76351
+ label: "Plus",
76352
+ icon: "plus",
75606
76353
  shape: "pill"
75607
76354
  })], {
75608
76355
  rows: 1,
75609
76356
  cols: 2
75610
76357
  }), [...Array.from({ length: 9 }, (_, i) => btn(`d${i + 1}`, Math.floor(i / 3), i % 3, { role: `digit_${i + 1}` })), btn("d0", 3, 1, { role: "digit_0" })], btn("red", 0, 0, {
75611
76358
  role: "custom:color_red",
76359
+ label: "Red",
75612
76360
  tone: "red"
75613
76361
  }), btn("green", 0, 1, {
75614
76362
  role: "custom:color_green",
76363
+ label: "Green",
75615
76364
  tone: "green"
75616
76365
  }), btn("yellow", 0, 2, {
75617
76366
  role: "custom:color_yellow",
76367
+ label: "Yellow",
75618
76368
  tone: "yellow"
75619
76369
  }), btn("blue", 0, 3, {
75620
76370
  role: "custom:color_blue",
76371
+ label: "Blue",
75621
76372
  tone: "blue"
75622
- }), btn("rewind", 0, 0, { role: "rewind" }), btn("play", 0, 1, { role: "play" }), btn("pause", 0, 2, { role: "pause" }), btn("forward", 0, 3, { role: "fast_forward" }), btn("stop", 0, 4, { role: "stop" }), [
76373
+ }), btn("rewind", 0, 0, {
76374
+ role: "rewind",
76375
+ icon: "rewind"
76376
+ }), btn("play", 0, 1, {
76377
+ role: "play",
76378
+ icon: "play"
76379
+ }), btn("pause", 0, 2, {
76380
+ role: "pause",
76381
+ icon: "pause"
76382
+ }), btn("forward", 0, 3, {
76383
+ role: "fast_forward",
76384
+ icon: "fast-forward"
76385
+ }), btn("stop", 0, 4, {
76386
+ role: "stop",
76387
+ icon: "square"
76388
+ }), [
75623
76389
  1,
75624
76390
  2,
75625
76391
  3,
@@ -75635,7 +76401,7 @@ discriminatedUnion("kind", [object({
75635
76401
  })]);
75636
76402
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
75637
76403
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
75638
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
76404
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
75639
76405
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
75640
76406
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
75641
76407
  new Set(CAP_NAMES);
@@ -76807,7 +77573,7 @@ var DeclaredDevices = class {
76807
77573
  return removed;
76808
77574
  }
76809
77575
  };
76810
- DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
77576
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
76811
77577
  new Set(Object.values(DeviceType));
76812
77578
  Object.freeze({
76813
77579
  "accessories.getStatus": {
@@ -77195,6 +77961,13 @@ Object.freeze({
77195
77961
  access: "create",
77196
77962
  role: "admin"
77197
77963
  },
77964
+ "addonSettings.getDeclaredSettingsKeys": {
77965
+ capName: "addon-settings",
77966
+ capScope: "system",
77967
+ addonId: null,
77968
+ access: "view",
77969
+ role: "admin"
77970
+ },
77198
77971
  "addonSettings.getDeviceSettings": {
77199
77972
  capName: "addon-settings",
77200
77973
  capScope: "system",
@@ -77216,6 +77989,13 @@ Object.freeze({
77216
77989
  access: "view",
77217
77990
  role: "admin"
77218
77991
  },
77992
+ "addonSettings.unsetStoredSettingsKeys": {
77993
+ capName: "addon-settings",
77994
+ capScope: "system",
77995
+ addonId: null,
77996
+ access: "create",
77997
+ role: "admin"
77998
+ },
77219
77999
  "addonSettings.updateDeviceSettings": {
77220
78000
  capName: "addon-settings",
77221
78001
  capScope: "system",
@@ -79631,6 +80411,13 @@ Object.freeze({
79631
80411
  access: "create",
79632
80412
  role: "admin"
79633
80413
  },
80414
+ "deviceProvider.classifyDiscoveryHits": {
80415
+ capName: "device-provider",
80416
+ capScope: "system",
80417
+ addonId: null,
80418
+ access: "create",
80419
+ role: "admin"
80420
+ },
79634
80421
  "deviceProvider.createDevice": {
79635
80422
  capName: "device-provider",
79636
80423
  capScope: "system",
@@ -79659,6 +80446,13 @@ Object.freeze({
79659
80446
  access: "view",
79660
80447
  role: "admin"
79661
80448
  },
80449
+ "deviceProvider.getDiscoveryModes": {
80450
+ capName: "device-provider",
80451
+ capScope: "system",
80452
+ addonId: null,
80453
+ access: "view",
80454
+ role: "admin"
80455
+ },
79662
80456
  "deviceProvider.getDiscoveryParamsSchema": {
79663
80457
  capName: "device-provider",
79664
80458
  capScope: "system",
@@ -80436,6 +81230,13 @@ Object.freeze({
80436
81230
  access: "create",
80437
81231
  role: "admin"
80438
81232
  },
81233
+ "keychain.releaseRecordRefs": {
81234
+ capName: "keychain",
81235
+ capScope: "system",
81236
+ addonId: null,
81237
+ access: "create",
81238
+ role: "admin"
81239
+ },
80439
81240
  "keychain.resolve": {
80440
81241
  capName: "keychain",
80441
81242
  capScope: "system",
@@ -80856,6 +81657,13 @@ Object.freeze({
80856
81657
  access: "view",
80857
81658
  role: "admin"
80858
81659
  },
81660
+ "localNetwork.getScanSubnets": {
81661
+ capName: "local-network",
81662
+ capScope: "system",
81663
+ addonId: null,
81664
+ access: "view",
81665
+ role: "admin"
81666
+ },
80859
81667
  "localNetwork.getTlsStatus": {
80860
81668
  capName: "local-network",
80861
81669
  capScope: "system",
@@ -80905,6 +81713,13 @@ Object.freeze({
80905
81713
  access: "create",
80906
81714
  role: "admin"
80907
81715
  },
81716
+ "localNetwork.scanNetwork": {
81717
+ capName: "local-network",
81718
+ capScope: "system",
81719
+ addonId: null,
81720
+ access: "create",
81721
+ role: "admin"
81722
+ },
80908
81723
  "localNetwork.setAddressRole": {
80909
81724
  capName: "local-network",
80910
81725
  capScope: "system",
@@ -80926,6 +81741,13 @@ Object.freeze({
80926
81741
  access: "create",
80927
81742
  role: "admin"
80928
81743
  },
81744
+ "localNetwork.setScanSubnets": {
81745
+ capName: "local-network",
81746
+ capScope: "system",
81747
+ addonId: null,
81748
+ access: "create",
81749
+ role: "admin"
81750
+ },
80929
81751
  "localNetwork.setViewerEndpoints": {
80930
81752
  capName: "local-network",
80931
81753
  capScope: "system",
@@ -85147,6 +85969,13 @@ Object.freeze({
85147
85969
  access: "create",
85148
85970
  role: "admin"
85149
85971
  },
85972
+ "storageAdmin.dropOrphanedKind": {
85973
+ capName: "storage-admin",
85974
+ capScope: "system",
85975
+ addonId: null,
85976
+ access: "delete",
85977
+ role: "admin"
85978
+ },
85150
85979
  "storageAdmin.listKinds": {
85151
85980
  capName: "storage-admin",
85152
85981
  capScope: "system",
@@ -85168,6 +85997,27 @@ Object.freeze({
85168
85997
  access: "view",
85169
85998
  role: "admin"
85170
85999
  },
86000
+ "storageAdmin.scanStaleData": {
86001
+ capName: "storage-admin",
86002
+ capScope: "system",
86003
+ addonId: null,
86004
+ access: "create",
86005
+ role: "admin"
86006
+ },
86007
+ "storageAdmin.staleDataReport": {
86008
+ capName: "storage-admin",
86009
+ capScope: "system",
86010
+ addonId: null,
86011
+ access: "view",
86012
+ role: "admin"
86013
+ },
86014
+ "storageAdmin.unsetStaleSettingsKey": {
86015
+ capName: "storage-admin",
86016
+ capScope: "system",
86017
+ addonId: null,
86018
+ access: "create",
86019
+ role: "admin"
86020
+ },
85171
86021
  "storageEvictable.evict": {
85172
86022
  capName: "storage-evictable",
85173
86023
  capScope: "system",
@@ -90864,7 +91714,7 @@ function projectAction(cap, action) {
90864
91714
  };
90865
91715
  }
90866
91716
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
90867
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
91717
+ DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
90868
91718
  DeviceFeature.BatteryOperated;
90869
91719
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
90870
91720
  var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";