@camstack/types 1.2.404 → 1.2.406

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 (58) hide show
  1. package/dist/addon.js +21 -2
  2. package/dist/addon.mjs +21 -2
  3. package/dist/capabilities/index.d.ts +6 -3
  4. package/dist/capabilities/notification-rules.cap.d.ts +3 -78
  5. package/dist/capabilities/osd-manager.cap.d.ts +0 -18
  6. package/dist/capabilities/pipeline-analytics.cap.d.ts +24 -0
  7. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +36 -0
  8. package/dist/capabilities/platform-probe.cap.d.ts +34 -0
  9. package/dist/capabilities/running-state.cap.d.ts +76 -0
  10. package/dist/capabilities/schemas/package-llm-verdict.d.ts +14 -0
  11. package/dist/capabilities/schemas/zone-rule.d.ts +4 -0
  12. package/dist/capabilities/zone-rules.cap.d.ts +10 -0
  13. package/dist/client.js +10 -62
  14. package/dist/client.mjs +10 -62
  15. package/dist/{composition-eoYYVfOp.js → composition-RmnnN39T.js} +86 -2
  16. package/dist/{composition-CB6PMT9p.mjs → composition-xz-ud5EQ.mjs} +86 -2
  17. package/dist/device/cap-vocabulary/entries/appliances.d.ts +1 -1
  18. package/dist/enums/event-category.d.ts +4 -0
  19. package/dist/enums.js +1 -1
  20. package/dist/enums.mjs +1 -1
  21. package/dist/{event-category-fDPduoeC.mjs → event-category-Ceif8n-v.mjs} +4 -0
  22. package/dist/{event-category-CXrwjC3e.js → event-category-DG6Vir8Z.js} +4 -0
  23. package/dist/expression/builtins.d.ts +7 -18
  24. package/dist/expression/expression-source.d.ts +14 -2
  25. package/dist/generated/addon-api.d.ts +14 -0
  26. package/dist/generated/addon-cap-facts.d.ts +1 -1
  27. package/dist/generated/cap-status-types.d.ts +3 -1
  28. package/dist/generated/capability-router-map.d.ts +5 -2
  29. package/dist/generated/device-local-state.d.ts +3 -0
  30. package/dist/generated/device-proxy.d.ts +3 -0
  31. package/dist/generated/device-scoped-caps.d.ts +1 -1
  32. package/dist/generated/history-catalog.d.ts +1 -1
  33. package/dist/generated/method-access-map.d.ts +1 -1
  34. package/dist/generated/method-device-selectors.d.ts +1 -1
  35. package/dist/generated/runtime-state-durability.d.ts +1 -1
  36. package/dist/index.d.ts +4 -0
  37. package/dist/index.js +2534 -1415
  38. package/dist/index.mjs +2471 -1415
  39. package/dist/interfaces/config-ui.d.ts +69 -2
  40. package/dist/interfaces/platform.d.ts +7 -0
  41. package/dist/node-color.d.ts +1 -1
  42. package/dist/node.js +1 -1
  43. package/dist/node.mjs +1 -1
  44. package/dist/pipeline/auto-tune/accelerator-compute.d.ts +116 -0
  45. package/dist/pipeline/auto-tune/detector-performance.d.ts +5 -0
  46. package/dist/pipeline/auto-tune/hardware-class.d.ts +5 -4
  47. package/dist/pipeline/auto-tune/index.d.ts +1 -0
  48. package/dist/pipeline/auto-tune/plan-schemas.d.ts +100 -0
  49. package/dist/pipeline/auto-tune/recommend.d.ts +31 -9
  50. package/dist/pipeline/second-pass/index.d.ts +2 -0
  51. package/dist/pipeline/second-pass/motion-crop-plan.d.ts +183 -0
  52. package/dist/pipeline/second-pass/small-subject-recovery.d.ts +216 -0
  53. package/dist/running-state/assignment.d.ts +34 -0
  54. package/dist/running-state/defaults.d.ts +3 -0
  55. package/dist/running-state/from-operation.d.ts +14 -0
  56. package/dist/{sleep-CHIEEJkY.mjs → sleep-CYfIZzGv.mjs} +7 -3
  57. package/dist/{sleep-BKS4OPMo.js → sleep-UP964hNP.js} +7 -3
  58. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-BKS4OPMo.js");
3
- const require_event_category = require("./event-category-CXrwjC3e.js");
4
- const require_composition = require("./composition-eoYYVfOp.js");
2
+ const require_sleep = require("./sleep-UP964hNP.js");
3
+ const require_event_category = require("./event-category-DG6Vir8Z.js");
4
+ const require_composition = require("./composition-RmnnN39T.js");
5
5
  const require_node_color = require("./node-color.js");
6
6
  const require_err_msg = require("./err-msg-BhVbr0NX.js");
7
7
  const require_enums = require("./enums.js");
@@ -12871,7 +12871,15 @@ var BUILTIN_RESULT_KINDS = Object.freeze({
12871
12871
  kind: "fixed",
12872
12872
  result: ["number", "null"]
12873
12873
  },
12874
- count: NUMBER
12874
+ count: NUMBER,
12875
+ dwell: {
12876
+ kind: "fixed",
12877
+ result: ["boolean", "null"]
12878
+ },
12879
+ latchedAt: {
12880
+ kind: "fixed",
12881
+ result: ["number", "null"]
12882
+ }
12875
12883
  });
12876
12884
  var KindInferenceError = class extends Error {};
12877
12885
  function kindOfValue(value) {
@@ -12883,7 +12891,7 @@ function kindOfValue(value) {
12883
12891
  function only(kind) {
12884
12892
  return new Set([kind]);
12885
12893
  }
12886
- function union(...sets) {
12894
+ function union$1(...sets) {
12887
12895
  return new Set(sets.flatMap((s) => [...s]));
12888
12896
  }
12889
12897
  function withoutNull(set) {
@@ -12911,8 +12919,8 @@ function callKinds(callee, args) {
12911
12919
  const rule = BUILTIN_RESULT_KINDS[callee];
12912
12920
  if (rule === void 0) throw new KindInferenceError(`no result-kind rule for builtin '${callee}'`);
12913
12921
  if (rule.kind === "fixed") return new Set(rule.result);
12914
- const nonNull = union(...args.map(withoutNull));
12915
- return args.every((a) => a.has("null")) ? union(nonNull, only("null")) : nonNull;
12922
+ const nonNull = union$1(...args.map(withoutNull));
12923
+ return args.every((a) => a.has("null")) ? union$1(nonNull, only("null")) : nonNull;
12916
12924
  }
12917
12925
  function kindsOf(node, bindings) {
12918
12926
  switch (node.kind) {
@@ -12925,8 +12933,8 @@ function kindsOf(node, bindings) {
12925
12933
  }
12926
12934
  case "unary": return only(node.op === "!" ? "boolean" : "number");
12927
12935
  case "binary": return binaryKinds(node.op, kindsOf(node.left, bindings), kindsOf(node.right, bindings));
12928
- case "logical": return union(kindsOf(node.left, bindings), kindsOf(node.right, bindings));
12929
- case "conditional": return union(kindsOf(node.consequent, bindings), kindsOf(node.alternate, bindings));
12936
+ case "logical": return union$1(kindsOf(node.left, bindings), kindsOf(node.right, bindings));
12937
+ case "conditional": return union$1(kindsOf(node.consequent, bindings), kindsOf(node.alternate, bindings));
12930
12938
  case "call": return callKinds(node.callee, node.args.map((a) => kindsOf(a, bindings)));
12931
12939
  }
12932
12940
  }
@@ -43726,9 +43734,6 @@ var NcSystemEventKindSchema = zod.z.enum([
43726
43734
  "device-battery-normal",
43727
43735
  "device-consumable-low",
43728
43736
  "device-consumable-normal",
43729
- "device-tank-low",
43730
- "device-tank-full",
43731
- "device-tank-normal",
43732
43737
  "stream-online",
43733
43738
  "stream-offline",
43734
43739
  "node-online",
@@ -43775,18 +43780,19 @@ var NcSystemEventKindSchema = zod.z.enum([
43775
43780
  "camera-enabled"
43776
43781
  ]);
43777
43782
  /** The legacy tail of {@link NcSystemEventKindSchema} — see its docblock. */
43778
- var NC_LEGACY_SYSTEM_EVENT_KINDS = new Set([
43783
+ var NC_LEGACY_SYSTEM_EVENT_KINDS = [
43779
43784
  "camera-online",
43780
43785
  "camera-offline",
43781
43786
  "camera-disabled",
43782
43787
  "camera-enabled"
43783
- ]);
43788
+ ];
43789
+ var NC_LEGACY_SYSTEM_EVENT_KIND_SET = new Set(NC_LEGACY_SYSTEM_EVENT_KINDS);
43784
43790
  /**
43785
43791
  * The kinds a rule may be AUTHORED with — every schema member except the
43786
43792
  * legacy tail. Editors render THIS list; the schema still parses the tail so a
43787
43793
  * durable row (and an unmigrated rule) survives being read.
43788
43794
  */
43789
- var NC_AUTHORABLE_SYSTEM_EVENT_KINDS = NcSystemEventKindSchema.options.filter((kind) => !NC_LEGACY_SYSTEM_EVENT_KINDS.has(kind));
43795
+ var NC_AUTHORABLE_SYSTEM_EVENT_KINDS = NcSystemEventKindSchema.options.filter((kind) => !NC_LEGACY_SYSTEM_EVENT_KIND_SET.has(kind));
43790
43796
  /**
43791
43797
  * The panel's transitions, as ONE list.
43792
43798
  *
@@ -43831,12 +43837,6 @@ var NC_BATTERY_CONSUMABLE_SYSTEM_EVENT_KINDS = [
43831
43837
  * and both editors read it.
43832
43838
  */
43833
43839
  var NC_CONSUMABLE_SYSTEM_EVENT_KINDS = ["device-consumable-low", "device-consumable-normal"];
43834
- /** A vacuum's tank alerts (D642) — raised on the provider's own verdict. */
43835
- var NC_TANK_SYSTEM_EVENT_KINDS = [
43836
- "device-tank-low",
43837
- "device-tank-full",
43838
- "device-tank-normal"
43839
- ];
43840
43840
  /**
43841
43841
  * The consumable threshold a rule that does not state one judges at (D642).
43842
43842
  * The operator's number ("default 15 %, configurable on the rule"), and the
@@ -45072,18 +45072,18 @@ var NcRuleSchema = NcRuleInputSchema.extend({
45072
45072
  * `addon-updates` the addons that are behind (D708 split them). `backups-exports` and `alarm` are not sections — a completed
45073
45073
  * job is not an open state, and the alarm stays instant.
45074
45074
  */
45075
- var NcStateSummarySectionIdSchema = zod.z.enum([
45075
+ var NC_STATE_SUMMARY_SECTION_IDS = [
45076
45076
  "device-state",
45077
45077
  "streams",
45078
45078
  "battery",
45079
45079
  "consumables",
45080
- "tanks",
45081
45080
  "detection-blind",
45082
45081
  "nodes",
45083
45082
  "addons",
45084
45083
  "addon-updates",
45085
45084
  "updates"
45086
- ]);
45085
+ ];
45086
+ var NcStateSummarySectionIdSchema = zod.z.enum(NC_STATE_SUMMARY_SECTION_IDS);
45087
45087
  /** One section of a previewed state summary. `unavailable` = its state could not be read. */
45088
45088
  var NcStateSummaryPreviewSectionSchema = zod.z.object({
45089
45089
  id: NcStateSummarySectionIdSchema,
@@ -45460,18 +45460,6 @@ var NC_CONDITION_CATALOG = [
45460
45460
  value: "device-consumable-normal",
45461
45461
  label: "Consumable replaced"
45462
45462
  },
45463
- {
45464
- value: "device-tank-low",
45465
- label: "Tank low (water, detergent)"
45466
- },
45467
- {
45468
- value: "device-tank-full",
45469
- label: "Tank full (dirty water, dust)"
45470
- },
45471
- {
45472
- value: "device-tank-normal",
45473
- label: "Tank back to normal"
45474
- },
45475
45463
  {
45476
45464
  value: "export-completed",
45477
45465
  label: "Export completed"
@@ -47289,6 +47277,20 @@ var CAMERA_SENSORS_NOT_LOADED = Object.freeze({
47289
47277
  reason: "camera sensor mappings not loaded"
47290
47278
  });
47291
47279
  //#endregion
47280
+ //#region src/capabilities/schemas/package-llm-verdict.ts
47281
+ /**
47282
+ * What the vision model must answer before a package event is emitted
47283
+ * when a packages rule has "Confirm with the LLM" on.
47284
+ *
47285
+ * `package` is the verdict. `confidence` is 0..1. `reason` is the model's
47286
+ * one-line explanation, stored on the event so a drop is not silent.
47287
+ */
47288
+ var PackageLlmVerdictSchema = zod.z.object({
47289
+ package: zod.z.boolean(),
47290
+ confidence: zod.z.number().min(0).max(1),
47291
+ reason: zod.z.string().min(1)
47292
+ });
47293
+ //#endregion
47292
47294
  //#region src/pipeline/cluster-model-scope.ts
47293
47295
  /**
47294
47296
  * A model choice has a SCOPE, and a scope change has a workflow.
@@ -48407,6 +48409,21 @@ var ObjectEventSchema = zod.z.object({
48407
48409
  * pick-up written before D684 — UNKNOWN, never 0 and never `timestamp`.
48408
48410
  */
48409
48411
  observedAt: zod.z.number().optional(),
48412
+ /**
48413
+ * The delivery was emitted without a vision-model yes (D931).
48414
+ *
48415
+ * Set only when the packages rule asked for an LLM confirm and the model
48416
+ * was unreachable, timed out, or answered something that did not parse.
48417
+ * A notification reads it so it can say "possible" instead of a definite
48418
+ * delivery. Absent on every other row, and on a confirm that said yes.
48419
+ */
48420
+ packagePossible: zod.z.boolean().optional(),
48421
+ /**
48422
+ * The vision model's verdict, when a packages rule asked for one and the
48423
+ * model answered the schema (D931). A "no" never becomes a row — the
48424
+ * detector drops it and logs the reason. Present on a yes.
48425
+ */
48426
+ packageLlmVerdict: PackageLlmVerdictSchema.optional(),
48410
48427
  /** The parent track's key-event importance [0,1], propagated to every object
48411
48428
  * event of the track (so an event row can be sorted by importance without a
48412
48429
  * track join). Absent on legacy rows / before the track was scored. */
@@ -51632,11 +51649,18 @@ var KeptReasonSchema = zod.z.enum(["custom-pin"]);
51632
51649
  * Where a choice's latency came from (D791): this node's own benchmark
51633
51650
  * result, or the hardware-class table. Measured on the node beats the table.
51634
51651
  */
51635
- var LatencySourceSchema = zod.z.discriminatedUnion("kind", [zod.z.object({ kind: zod.z.literal("table") }), zod.z.object({
51636
- kind: zod.z.literal("node-benchmark"),
51637
- resultId: zod.z.string(),
51638
- measuredAtMs: zod.z.number()
51639
- })]);
51652
+ var LatencySourceSchema = zod.z.discriminatedUnion("kind", [
51653
+ zod.z.object({ kind: zod.z.literal("table") }),
51654
+ zod.z.object({
51655
+ kind: zod.z.literal("node-benchmark"),
51656
+ resultId: zod.z.string(),
51657
+ measuredAtMs: zod.z.number()
51658
+ }),
51659
+ zod.z.object({
51660
+ kind: zod.z.literal("estimated"),
51661
+ estimatedFrom: HardwareClassIdSchema
51662
+ })
51663
+ ]);
51640
51664
  var DeviceModelChoiceSchema = zod.z.object({
51641
51665
  modelId: zod.z.string(),
51642
51666
  p50Ms: zod.z.number(),
@@ -51644,6 +51668,11 @@ var DeviceModelChoiceSchema = zod.z.object({
51644
51668
  measured: zod.z.boolean(),
51645
51669
  /** Absent on a plan from before D791 — read as the table. */
51646
51670
  source: LatencySourceSchema.optional(),
51671
+ /**
51672
+ * The measured class this latency was scaled from (D930). Absent when the
51673
+ * cell is the table or a node benchmark.
51674
+ */
51675
+ estimatedFrom: HardwareClassIdSchema.optional(),
51647
51676
  /** Parallel sessions at the camera rate with the headroom kept free: ⌊fps × (1 − headroom) / camera fps⌋. */
51648
51677
  capacitySessions: zod.z.number(),
51649
51678
  personRecall: zod.z.number()
@@ -67662,427 +67691,1201 @@ includeCrops: zod.z.boolean().optional() }).optional(), zod.z.array(VehicleSchem
67662
67691
  }
67663
67692
  };
67664
67693
  //#endregion
67665
- //#region src/capabilities/platform-probe.cap.ts
67666
- var ModelFormatSchema = zod.z.enum(require_sleep.MODEL_FORMATS);
67667
- var HwAccelBackendInputSchema = zod.z.enum([
67668
- "videotoolbox",
67669
- "cuda",
67670
- "nvdec",
67671
- "vaapi",
67672
- "qsv",
67673
- "d3d11va",
67674
- "dxva2",
67675
- "amf",
67676
- "vdpau",
67677
- "drm",
67678
- "coreml",
67679
- "tensorrt",
67680
- "openvino",
67681
- "directml",
67682
- "webgpu",
67683
- "none"
67684
- ]).nullable().optional();
67685
- var HwAccelResolutionSchema = zod.z.object({
67686
- preferred: zod.z.array(zod.z.string()).readonly(),
67687
- rationale: zod.z.string()
67688
- });
67689
- var HardwareEncoderIdSchema = zod.z.enum([
67690
- "h264_videotoolbox",
67691
- "hevc_videotoolbox",
67692
- "h264_vaapi",
67693
- "hevc_vaapi",
67694
- "h264_nvenc",
67695
- "hevc_nvenc",
67696
- "h264_qsv",
67697
- "hevc_qsv",
67698
- "h264_amf",
67699
- "hevc_amf",
67700
- "libx264",
67701
- "libx265"
67702
- ]);
67703
- var HardwareEncoderProbeSchema = zod.z.object({
67704
- encoder: HardwareEncoderIdSchema,
67705
- codec: zod.z.enum(["H264", "H265"]),
67706
- family: zod.z.enum([
67707
- "videotoolbox",
67708
- "vaapi",
67709
- "nvenc",
67710
- "qsv",
67711
- "amf",
67712
- "software"
67713
- ]),
67714
- available: zod.z.boolean(),
67715
- reason: zod.z.string().optional()
67716
- });
67717
- zod.z.object({
67718
- encoders: zod.z.array(HardwareEncoderProbeSchema).readonly(),
67719
- defaultH264: HardwareEncoderIdSchema,
67720
- defaultH265: HardwareEncoderIdSchema,
67721
- probedAt: zod.z.number()
67722
- });
67723
- zod.z.object({
67724
- methods: zod.z.array(zod.z.string()).readonly(),
67725
- probedAt: zod.z.number()
67726
- });
67727
- var HardwarePlatformSchema = zod.z.enum([
67728
- "darwin",
67729
- "linux",
67730
- "win32"
67731
- ]);
67732
- var HardwareArchSchema = zod.z.enum(["arm64", "x64"]);
67733
- var GpuInfoSchema = zod.z.object({
67734
- type: zod.z.enum([
67735
- "nvidia",
67736
- "amd",
67737
- "intel",
67738
- "apple"
67739
- ]),
67740
- name: zod.z.string(),
67741
- memoryMB: zod.z.number().optional()
67742
- });
67743
- var NpuInfoSchema = zod.z.object({
67744
- type: zod.z.enum([
67745
- "apple-ane",
67746
- "intel-npu",
67747
- "amd-xdna"
67748
- ]),
67749
- name: zod.z.string().optional(),
67750
- generation: zod.z.enum(["xdna1", "xdna2"]).optional()
67751
- });
67752
- var CoralBusSchema = zod.z.enum(["usb", "pci"]);
67753
- var CoralInfoSchema = zod.z.object({
67754
- type: zod.z.literal("coral-edgetpu"),
67755
- /** Legacy single-bus report (older nodes) — read via `coralUsableUnits`. */
67756
- bus: zod.z.string().optional(),
67757
- /** Legacy USB unit count (older nodes) — read via `coralUsableUnits`. */
67758
- count: zod.z.number().int().nonnegative().optional(),
67759
- /** Usable units per bus — each one is its own `edgetpu:<bus>[:N]` device. */
67760
- units: zod.z.array(zod.z.object({
67761
- bus: CoralBusSchema,
67762
- count: zod.z.number().int().nonnegative()
67763
- })).readonly().optional(),
67764
- /** Present-but-unusable units, each with its typed reason (never "absent"). */
67765
- unavailable: zod.z.array(zod.z.object({
67766
- bus: CoralBusSchema,
67767
- count: zod.z.number().int().nonnegative(),
67768
- reason: zod.z.enum(["driver-missing", "device-not-accessible"])
67769
- })).readonly().optional()
67770
- });
67771
- var HardwareInfoSchema = zod.z.object({
67772
- platform: HardwarePlatformSchema,
67773
- arch: HardwareArchSchema,
67774
- cpuModel: zod.z.string(),
67775
- cpuCores: zod.z.number(),
67776
- totalRAM_MB: zod.z.number(),
67777
- availableRAM_MB: zod.z.number(),
67778
- gpu: GpuInfoSchema.nullable(),
67779
- npu: NpuInfoSchema.nullable(),
67780
- coral: CoralInfoSchema.nullable().optional()
67781
- });
67782
- var PlatformScoreSchema = zod.z.object({
67783
- runtime: zod.z.literal("python"),
67784
- backend: zod.z.string(),
67785
- format: zod.z.enum([
67786
- "onnx",
67787
- "coreml",
67788
- "openvino",
67789
- "tflite"
67790
- ]),
67791
- score: zod.z.number(),
67792
- reason: zod.z.string(),
67793
- available: zod.z.boolean()
67794
- });
67795
- var InferenceDeviceDescriptorSchema = zod.z.object({
67796
- key: zod.z.string(),
67797
- backend: zod.z.string(),
67798
- device: zod.z.string(),
67799
- format: ModelFormatSchema,
67800
- runtime: zod.z.literal("python"),
67801
- score: zod.z.number(),
67802
- available: zod.z.boolean()
67803
- });
67804
- var PlatformCapabilitiesSchema = zod.z.object({
67805
- hardware: HardwareInfoSchema,
67806
- scores: zod.z.array(PlatformScoreSchema).readonly(),
67807
- bestScore: PlatformScoreSchema,
67808
- pythonPath: zod.z.string().nullable(),
67809
- devices: zod.z.array(InferenceDeviceDescriptorSchema).readonly()
67810
- });
67811
- var ModelRequirementSchema = zod.z.object({
67812
- modelId: zod.z.string(),
67813
- name: zod.z.string(),
67814
- minRAM_MB: zod.z.number(),
67815
- accuracyScore: zod.z.number(),
67816
- formats: zod.z.array(zod.z.string()).readonly()
67817
- });
67818
- var ResolvedInferenceConfigSchema = zod.z.object({
67819
- modelId: zod.z.string(),
67820
- runtime: zod.z.literal("python"),
67821
- backend: zod.z.string(),
67822
- format: ModelFormatSchema,
67823
- reason: zod.z.string()
67824
- });
67825
- /**
67826
- * The answer of an accelerator rescan (D919): the node's device list after
67827
- * the re-probe, what changed, and the evidence that decided the Coral answer
67828
- * (`usbBus` — could the USB bus be read at all; `decidedBy` — USB ids, the PCI
67829
- * bus, the installed-delegate fallback, or nothing).
67830
- */
67831
- var AcceleratorRescanSchema = zod.z.object({
67832
- devices: zod.z.array(InferenceDeviceDescriptorSchema).readonly(),
67833
- appeared: zod.z.array(zod.z.string()).readonly(),
67834
- disappeared: zod.z.array(zod.z.string()).readonly(),
67835
- usbBus: zod.z.enum(["observed", "unobservable"]),
67836
- decidedBy: zod.z.enum([
67837
- "usb-ids",
67838
- "pci",
67839
- "delegate-fallback",
67840
- "none"
67841
- ])
67842
- });
67843
- var platformProbeCapability = {
67844
- name: "platform-probe",
67845
- scope: "system",
67846
- mode: "singleton",
67847
- methods: {
67848
- getCapabilities: require_sleep.method(zod.z.void(), PlatformCapabilitiesSchema),
67849
- getHardware: require_sleep.method(zod.z.void(), HardwareInfoSchema),
67850
- resolveInferenceConfig: require_sleep.method(zod.z.object({ requirements: zod.z.array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema),
67851
- resolveHwAccel: require_sleep.method(zod.z.object({ prefer: HwAccelBackendInputSchema }), HwAccelResolutionSchema),
67852
- /**
67853
- * Re-probe this node's accelerators NOW (Coral USB ids + PCIe apex units;
67854
- * `/sys` and `/dev` reads, no model load) and rebuild the device list that
67855
- * `getCapabilities` serves. The same re-probe runs every 60 s on its own;
67856
- * this is the operator's "rescan inference devices". Target a node with the
67857
- * `{ nodeId }` routing input, like `getCapabilities`.
67858
- */
67859
- rescanAccelerators: require_sleep.method(zod.z.void(), AcceleratorRescanSchema, {
67860
- kind: "mutation",
67861
- auth: "admin"
67862
- })
67863
- }
67864
- };
67865
- //#endregion
67866
- //#region src/capabilities/power-meter.cap.ts
67867
- /**
67868
- * Multi-metric electrical meter. One slice can carry any combination
67869
- * of instantaneous power (W), cumulative energy (kWh), voltage (V),
67870
- * and current (A) — all fields optional so a single-metric source
67871
- * (HA `sensor` with `device_class: power`) populates only what it
67872
- * has and aggregators (energy dashboards, billing exports) compose
67873
- * across providers without needing one cap per metric.
67874
- *
67875
- * Trade-off acknowledged: a HomeKit export that wants to surface
67876
- * "power" and "energy" as separate services has to crack open the
67877
- * slice and emit two characteristics from one cap. Worth it to avoid
67878
- * exploding the cap catalog with `power-w` / `energy-kwh` /
67879
- * `voltage-v` / `current-a` quartets that always travel together
67880
- * in real-world devices.
67881
- */
67882
- var PowerMeterStatusSchema = zod.z.object({
67883
- /** Instantaneous power draw in watts. */
67884
- watts: zod.z.number().optional(),
67885
- /** Cumulative energy in kilowatt-hours since the meter was reset. */
67886
- kwhTotal: zod.z.number().optional(),
67887
- /** Voltage in volts. */
67888
- volts: zod.z.number().optional(),
67889
- /** Current in amperes. */
67890
- amps: zod.z.number().optional(),
67891
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
67892
- va: zod.z.number().optional(),
67893
- /** Power factor (0..1) if reported. */
67894
- powerFactor: zod.z.number().min(0).max(1).optional(),
67895
- /** Ms epoch when the slice was last updated. */
67896
- lastFetchedAt: zod.z.number(),
67897
- /** Live display unit of the single metric this slice carries (e.g. HA
67898
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
67899
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
67900
- * unit per slice is unambiguous. The UI prefers this over the role's
67901
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
67902
- unit: zod.z.string().optional(),
67903
- /** Suggested decimal places for numeric display.
67904
- * Populated live from the upstream source when provided (e.g. HA
67905
- * `attributes.suggested_display_precision`). Falls back to
67906
- * auto-formatting when absent. */
67907
- precision: zod.z.number().int().min(0).max(10).optional()
67908
- });
67909
- var powerMeterCapability = {
67910
- name: "power-meter",
67911
- scope: "device",
67912
- deviceNative: true,
67913
- mode: "singleton",
67914
- deviceTypes: [require_err_msg.DeviceType.Sensor],
67915
- methods: {},
67916
- status: {
67917
- schema: PowerMeterStatusSchema,
67918
- kind: "push"
67919
- },
67920
- runtimeState: PowerMeterStatusSchema,
67921
- /**
67922
- * Runtime-state durability: **restored** — as `numeric-sensor`; `kwhTotal` is an accumulator whose restored value is the baseline.
67923
- *
67924
- * See `RuntimeStateDurability`. Enforced by
67925
- * `scripts/check-runtime-state-durability.ts`.
67926
- */
67927
- durability: "restored",
67928
- /**
67929
- * History (D850): recorded series. Gauges are floored at 30 s; `kwhTotal` is the one monotonic meter (counter).
67930
- */
67931
- history: { fields: {
67932
- watts: {
67933
- kind: "gauge",
67934
- unit: "W",
67935
- deadband: 1,
67936
- minIntervalMs: 3e4
67937
- },
67938
- kwhTotal: {
67939
- kind: "counter",
67940
- unit: "kWh",
67941
- deadband: .01,
67942
- minIntervalMs: 6e4
67694
+ //#region src/pipeline/auto-tune/detector-performance.ts
67695
+ var DETECTOR_PERF = {
67696
+ "intel-core-ultra": {
67697
+ "intel-npu": {
67698
+ "yolov9t-320": {
67699
+ p50Ms: 2.9,
67700
+ fps: 383,
67701
+ measured: true
67702
+ },
67703
+ "yolov9t-320-int8": {
67704
+ p50Ms: 3.8,
67705
+ fps: 333,
67706
+ measured: true
67707
+ },
67708
+ "yolov9t-640": {
67709
+ p50Ms: 7.4,
67710
+ fps: 139,
67711
+ measured: true
67712
+ },
67713
+ "yolov9t-640-int8": {
67714
+ p50Ms: 7.5,
67715
+ fps: 144,
67716
+ measured: true
67717
+ },
67718
+ "yolov9s-320": {
67719
+ p50Ms: 4.4,
67720
+ fps: 241,
67721
+ measured: true
67722
+ },
67723
+ "yolov9s-320-int8": {
67724
+ p50Ms: 4.1,
67725
+ fps: 264,
67726
+ measured: true
67727
+ },
67728
+ "yolov9s-640": {
67729
+ p50Ms: 12.7,
67730
+ fps: 82,
67731
+ measured: true
67732
+ },
67733
+ "yolov9s-640-int8": {
67734
+ p50Ms: 10.7,
67735
+ fps: 99,
67736
+ measured: true
67737
+ },
67738
+ "yolov9m-320": {
67739
+ p50Ms: 8.4,
67740
+ fps: 123,
67741
+ measured: true
67742
+ },
67743
+ "yolov9m-320-int8": {
67744
+ p50Ms: 6.5,
67745
+ fps: 165,
67746
+ measured: true
67747
+ },
67748
+ "yolov9m-640": {
67749
+ p50Ms: 29.5,
67750
+ fps: 32,
67751
+ measured: true
67752
+ },
67753
+ "yolov9m-640-int8": {
67754
+ p50Ms: 19.5,
67755
+ fps: 52,
67756
+ measured: true
67757
+ }
67943
67758
  },
67944
- volts: {
67945
- kind: "gauge",
67946
- unit: "V",
67947
- deadband: 1,
67948
- minIntervalMs: 3e4
67759
+ "intel-igpu": {
67760
+ "yolov9t-320": {
67761
+ p50Ms: 2.6,
67762
+ fps: 405,
67763
+ measured: true
67764
+ },
67765
+ "yolov9t-320-int8": {
67766
+ p50Ms: 2.8,
67767
+ fps: 324,
67768
+ measured: true
67769
+ },
67770
+ "yolov9t-640": {
67771
+ p50Ms: 6.5,
67772
+ fps: 157,
67773
+ measured: true
67774
+ },
67775
+ "yolov9t-640-int8": {
67776
+ p50Ms: 5.6,
67777
+ fps: 185,
67778
+ measured: true
67779
+ },
67780
+ "yolov9s-320": {
67781
+ p50Ms: 4.6,
67782
+ fps: 200,
67783
+ measured: true
67784
+ },
67785
+ "yolov9s-320-int8": {
67786
+ p50Ms: 4.1,
67787
+ fps: 239,
67788
+ measured: true
67789
+ },
67790
+ "yolov9s-640": {
67791
+ p50Ms: 15.2,
67792
+ fps: 69,
67793
+ measured: true
67794
+ },
67795
+ "yolov9s-640-int8": {
67796
+ p50Ms: 10.5,
67797
+ fps: 99,
67798
+ measured: true
67799
+ },
67800
+ "yolov9m-320": {
67801
+ p50Ms: 11.4,
67802
+ fps: 88,
67803
+ measured: true
67804
+ },
67805
+ "yolov9m-320-int8": {
67806
+ p50Ms: 7,
67807
+ fps: 127,
67808
+ measured: true
67809
+ },
67810
+ "yolov9m-640": {
67811
+ p50Ms: 40,
67812
+ fps: 25,
67813
+ measured: true
67814
+ },
67815
+ "yolov9m-640-int8": {
67816
+ p50Ms: 23.5,
67817
+ fps: 44,
67818
+ measured: true
67819
+ }
67949
67820
  },
67950
- amps: {
67951
- kind: "gauge",
67952
- unit: "A",
67953
- deadband: .05,
67954
- minIntervalMs: 3e4
67821
+ "x86-cpu": {
67822
+ "yolov9t-320": {
67823
+ p50Ms: 6.9,
67824
+ fps: 145,
67825
+ measured: true
67826
+ },
67827
+ "yolov9t-320-int8": {
67828
+ p50Ms: 2.7,
67829
+ fps: 405,
67830
+ measured: true
67831
+ },
67832
+ "yolov9t-640": {
67833
+ p50Ms: 23,
67834
+ fps: 44,
67835
+ measured: true
67836
+ },
67837
+ "yolov9t-640-int8": {
67838
+ p50Ms: 8.8,
67839
+ fps: 124,
67840
+ measured: true
67841
+ },
67842
+ "yolov9s-320": {
67843
+ p50Ms: 15.8,
67844
+ fps: 63,
67845
+ measured: true
67846
+ },
67847
+ "yolov9s-320-int8": {
67848
+ p50Ms: 6.5,
67849
+ fps: 180,
67850
+ measured: true
67851
+ },
67852
+ "yolov9s-640": {
67853
+ p50Ms: 58.2,
67854
+ fps: 17,
67855
+ measured: true
67856
+ },
67857
+ "yolov9s-640-int8": {
67858
+ p50Ms: 20.1,
67859
+ fps: 52,
67860
+ measured: true
67861
+ },
67862
+ "yolov9m-320": {
67863
+ p50Ms: 40,
67864
+ fps: 25,
67865
+ measured: true
67866
+ },
67867
+ "yolov9m-320-int8": {
67868
+ p50Ms: 14.2,
67869
+ fps: 74,
67870
+ measured: true
67871
+ },
67872
+ "yolov9m-640": {
67873
+ p50Ms: 155.8,
67874
+ fps: 6,
67875
+ measured: true
67876
+ },
67877
+ "yolov9m-640-int8": {
67878
+ p50Ms: 52.4,
67879
+ fps: 19,
67880
+ measured: true
67881
+ }
67955
67882
  },
67956
- va: {
67957
- kind: "gauge",
67958
- unit: "VA",
67959
- deadband: 1,
67960
- minIntervalMs: 3e4
67883
+ coral: { "yolov9s-320-edgetpu": {
67884
+ p50Ms: 32,
67885
+ fps: 31,
67886
+ measured: false
67887
+ } }
67888
+ },
67889
+ "intel-n-series": {
67890
+ "intel-igpu": {
67891
+ "yolov9t-320": {
67892
+ p50Ms: 15,
67893
+ fps: 48,
67894
+ measured: true
67895
+ },
67896
+ "yolov9t-320-int8": {
67897
+ p50Ms: 14.1,
67898
+ fps: 67,
67899
+ measured: true
67900
+ },
67901
+ "yolov9t-640": {
67902
+ p50Ms: 38.5,
67903
+ fps: 26,
67904
+ measured: true
67905
+ },
67906
+ "yolov9t-640-int8": {
67907
+ p50Ms: 50,
67908
+ fps: 20,
67909
+ measured: true
67910
+ },
67911
+ "yolov9s-320": {
67912
+ p50Ms: 41.4,
67913
+ fps: 24,
67914
+ measured: true
67915
+ },
67916
+ "yolov9s-320-int8": {
67917
+ p50Ms: 23.3,
67918
+ fps: 34,
67919
+ measured: true
67920
+ },
67921
+ "yolov9s-640": {
67922
+ p50Ms: 127.4,
67923
+ fps: 8,
67924
+ measured: true
67925
+ },
67926
+ "yolov9s-640-int8": {
67927
+ p50Ms: 81,
67928
+ fps: 12,
67929
+ measured: true
67930
+ },
67931
+ "yolov9m-320": {
67932
+ p50Ms: 63.5,
67933
+ fps: 16,
67934
+ measured: true
67935
+ },
67936
+ "yolov9m-320-int8": {
67937
+ p50Ms: 66,
67938
+ fps: 15,
67939
+ measured: true
67940
+ },
67941
+ "yolov9m-640": {
67942
+ p50Ms: 215.5,
67943
+ fps: 5,
67944
+ measured: true
67945
+ },
67946
+ "yolov9m-640-int8": {
67947
+ p50Ms: 163,
67948
+ fps: 6,
67949
+ measured: true
67950
+ }
67961
67951
  },
67962
- powerFactor: {
67963
- kind: "gauge",
67964
- unit: "ratio",
67965
- deadband: .01,
67966
- minIntervalMs: 6e4
67967
- }
67968
- } },
67969
- /** Clock fields: written, but excluded from the compare that decides
67970
- * whether persisting is worth a SQLite commit. */
67971
- volatileStateFields: ["lastFetchedAt"]
67952
+ "x86-cpu": {
67953
+ "yolov9t-320": {
67954
+ p50Ms: 86.2,
67955
+ fps: 12,
67956
+ measured: true
67957
+ },
67958
+ "yolov9t-320-int8": {
67959
+ p50Ms: 52.7,
67960
+ fps: 20,
67961
+ measured: true
67962
+ },
67963
+ "yolov9t-640": {
67964
+ p50Ms: 322.1,
67965
+ fps: 3,
67966
+ measured: true
67967
+ },
67968
+ "yolov9t-640-int8": {
67969
+ p50Ms: 205.6,
67970
+ fps: 5,
67971
+ measured: true
67972
+ },
67973
+ "yolov9s-320": {
67974
+ p50Ms: 164.4,
67975
+ fps: 6,
67976
+ measured: true
67977
+ },
67978
+ "yolov9s-320-int8": {
67979
+ p50Ms: 86.9,
67980
+ fps: 12,
67981
+ measured: true
67982
+ },
67983
+ "yolov9s-640": {
67984
+ p50Ms: 341.3,
67985
+ fps: 3,
67986
+ measured: true
67987
+ },
67988
+ "yolov9s-640-int8": {
67989
+ p50Ms: 185.3,
67990
+ fps: 5,
67991
+ measured: true
67992
+ },
67993
+ "yolov9m-320": {
67994
+ p50Ms: 166.8,
67995
+ fps: 6,
67996
+ measured: true
67997
+ },
67998
+ "yolov9m-320-int8": {
67999
+ p50Ms: 226.3,
68000
+ fps: 4,
68001
+ measured: true
68002
+ },
68003
+ "yolov9m-640": {
68004
+ p50Ms: 663.5,
68005
+ fps: 2,
68006
+ measured: true
68007
+ },
68008
+ "yolov9m-640-int8": {
68009
+ p50Ms: 324.4,
68010
+ fps: 3,
68011
+ measured: true
68012
+ }
68013
+ },
68014
+ coral: { "yolov9s-320-edgetpu": {
68015
+ p50Ms: 32,
68016
+ fps: 31,
68017
+ measured: false
68018
+ } }
68019
+ },
68020
+ "amd-phoenix": {
68021
+ "x86-cpu": {
68022
+ "yolov9t-320": {
68023
+ p50Ms: 24,
68024
+ fps: 42,
68025
+ measured: true
68026
+ },
68027
+ "yolov9t-320-int8": {
68028
+ p50Ms: 12,
68029
+ fps: 83,
68030
+ measured: true
68031
+ },
68032
+ "yolov9t-640": {
68033
+ p50Ms: 50,
68034
+ fps: 20,
68035
+ measured: true
68036
+ },
68037
+ "yolov9t-640-int8": {
68038
+ p50Ms: 20,
68039
+ fps: 50,
68040
+ measured: true
68041
+ },
68042
+ "yolov9s-320": {
68043
+ p50Ms: 30,
68044
+ fps: 33,
68045
+ measured: true
68046
+ },
68047
+ "yolov9s-320-int8": {
68048
+ p50Ms: 15,
68049
+ fps: 67,
68050
+ measured: true
68051
+ },
68052
+ "yolov9s-640": {
68053
+ p50Ms: 92,
68054
+ fps: 11,
68055
+ measured: true
68056
+ },
68057
+ "yolov9s-640-int8": {
68058
+ p50Ms: 27,
68059
+ fps: 37,
68060
+ measured: true
68061
+ },
68062
+ "yolov9m-320": {
68063
+ p50Ms: 67,
68064
+ fps: 15,
68065
+ measured: true
68066
+ },
68067
+ "yolov9m-320-int8": {
68068
+ p50Ms: 21,
68069
+ fps: 48,
68070
+ measured: true
68071
+ },
68072
+ "yolov9m-640": {
68073
+ p50Ms: 229,
68074
+ fps: 4,
68075
+ measured: true
68076
+ },
68077
+ "yolov9m-640-int8": {
68078
+ p50Ms: 48,
68079
+ fps: 21,
68080
+ measured: true
68081
+ }
68082
+ },
68083
+ "amd-igpu": {
68084
+ "yolov9t-320": {
68085
+ p50Ms: 40,
68086
+ fps: 25,
68087
+ measured: true
68088
+ },
68089
+ "yolov9t-640": {
68090
+ p50Ms: 89,
68091
+ fps: 11,
68092
+ measured: true
68093
+ },
68094
+ "yolov9s-320": {
68095
+ p50Ms: 65,
68096
+ fps: 15,
68097
+ measured: true
68098
+ },
68099
+ "yolov9s-640": {
68100
+ p50Ms: 125,
68101
+ fps: 8,
68102
+ measured: true
68103
+ },
68104
+ "yolov9m-320": {
68105
+ p50Ms: 85,
68106
+ fps: 12,
68107
+ measured: true
68108
+ },
68109
+ "yolov9m-640": {
68110
+ p50Ms: 228,
68111
+ fps: 4,
68112
+ measured: true
68113
+ }
68114
+ },
68115
+ coral: { "yolov9s-320-edgetpu": {
68116
+ p50Ms: 32,
68117
+ fps: 31,
68118
+ measured: true
68119
+ } }
68120
+ },
68121
+ "apple-m-pro": {
68122
+ "apple-ane": {
68123
+ "yolov9t-320": {
68124
+ p50Ms: 1.1,
68125
+ fps: 952,
68126
+ measured: true
68127
+ },
68128
+ "yolov9t-640": {
68129
+ p50Ms: 3,
68130
+ fps: 333,
68131
+ measured: true
68132
+ },
68133
+ "yolov9s-320": {
68134
+ p50Ms: 1.5,
68135
+ fps: 667,
68136
+ measured: true
68137
+ },
68138
+ "yolov9s-640": {
68139
+ p50Ms: 5.1,
68140
+ fps: 196,
68141
+ measured: true
68142
+ },
68143
+ "yolov9m-320": {
68144
+ p50Ms: 3.2,
68145
+ fps: 312,
68146
+ measured: true
68147
+ },
68148
+ "yolov9m-640": {
68149
+ p50Ms: 10.2,
68150
+ fps: 98,
68151
+ measured: true
68152
+ }
68153
+ },
68154
+ coral: { "yolov9s-320-edgetpu": {
68155
+ p50Ms: 32,
68156
+ fps: 31,
68157
+ measured: false
68158
+ } }
68159
+ },
68160
+ "apple-m-base": {
68161
+ "apple-ane": {
68162
+ "yolov9t-320": {
68163
+ p50Ms: 1.32,
68164
+ fps: 758,
68165
+ measured: false
68166
+ },
68167
+ "yolov9t-640": {
68168
+ p50Ms: 3.45,
68169
+ fps: 290,
68170
+ measured: true
68171
+ },
68172
+ "yolov9s-320": {
68173
+ p50Ms: 1.8,
68174
+ fps: 556,
68175
+ measured: false
68176
+ },
68177
+ "yolov9s-640": {
68178
+ p50Ms: 5.46,
68179
+ fps: 183,
68180
+ measured: true
68181
+ },
68182
+ "yolov9m-320": {
68183
+ p50Ms: 3.79,
68184
+ fps: 264,
68185
+ measured: true
68186
+ },
68187
+ "yolov9m-640": {
68188
+ p50Ms: 10.34,
68189
+ fps: 97,
68190
+ measured: true
68191
+ }
68192
+ },
68193
+ coral: { "yolov9s-320-edgetpu": {
68194
+ p50Ms: 32,
68195
+ fps: 31,
68196
+ measured: false
68197
+ } }
68198
+ }
67972
68199
  };
67973
- //#endregion
67974
- //#region src/capabilities/presence.cap.ts
67975
- /**
67976
- * Presence cap. Models HA `person.*` and `device_tracker.*` entities
67977
- * on `DeviceType.Presence`. Read-only — no setters: presence is
67978
- * derived from upstream signals (mobile-app location, router DHCP
67979
- * lease, Bluetooth beacon, manual zone change) and pushed into the
67980
- * slice.
67981
- *
67982
- * `state` follows HA's canonical values — `home` / `not_home` /
67983
- * `<zone-name>` for any other named zone. The zone name is freeform
67984
- * because users define arbitrary zones (`work`, `gym`, `school`).
67985
- *
67986
- * GPS coordinates are nullable — tracker types without location
67987
- * (Bluetooth scanner, MQTT presence) omit them. The presence of
67988
- * GPS data is signaled by `DeviceFeature.PresenceGps` for fast
67989
- * UI map-pin rendering.
67990
- */
67991
- var GpsLocationSchema = zod.z.object({
67992
- /** Latitude in decimal degrees, -90..90. */
67993
- latitude: zod.z.number().min(-90).max(90),
67994
- /** Longitude in decimal degrees, -180..180. */
67995
- longitude: zod.z.number().min(-180).max(180),
67996
- /** Reported accuracy in meters (lower = better). */
67997
- accuracyMeters: zod.z.number().nonnegative()
67998
- });
67999
- var PresenceStatusSchema = zod.z.object({
68000
- /** `home` / `not_home` / any user-defined zone name. */
68001
- state: zod.z.string(),
68002
- /** Optional textual location label (zone name, city, address). Null
68003
- * when only the binary state is known. */
68004
- location: zod.z.string().nullable(),
68005
- /** GPS coordinates when available. Null otherwise. */
68006
- gps: GpsLocationSchema.nullable(),
68007
- /** Optional battery level of the tracking device (0..100). Null
68008
- * when not reported. */
68009
- batteryPercent: zod.z.number().min(0).max(100).nullable(),
68010
- /** Ms epoch when the slice was last updated. */
68011
- lastChangedAt: zod.z.number()
68012
- });
68013
- var presenceCapability = {
68014
- name: "presence",
68015
- scope: "device",
68016
- deviceNative: true,
68017
- mode: "singleton",
68018
- deviceTypes: [require_err_msg.DeviceType.Presence],
68019
- methods: {},
68020
- status: {
68021
- schema: PresenceStatusSchema,
68022
- kind: "push"
68200
+ var DETECTOR_ACCURACY = {
68201
+ "yolov9m-320": {
68202
+ personPrecision: .979,
68203
+ personRecall: .649
68023
68204
  },
68024
- /**
68025
- * Runtime-state slice — mirrored by the kernel. UI presence card
68026
- * reads `state` + `location` for the text label; iff `gps !== null`
68027
- * the map pin is rendered (use `DeviceFeature.PresenceGps` for the
68028
- * pre-fetch fast-path check).
68029
- */
68030
- runtimeState: PresenceStatusSchema,
68031
- /**
68032
- * Runtime-state durability: **restored** — occupancy-relevant: the restored state is what an occupancy rule compares the first post-restart observation against.
68033
- *
68034
- * See `RuntimeStateDurability`. Enforced by
68035
- * `scripts/check-runtime-state-durability.ts`.
68036
- */
68037
- durability: "restored",
68038
- /**
68039
- * History (D850): recorded series.
68040
- */
68041
- history: { fields: { batteryPercent: {
68042
- kind: "gauge",
68043
- unit: "%",
68044
- deadband: 1
68045
- } } },
68046
- /** Clock fields: written, but excluded from the compare that decides
68047
- * whether persisting is worth a SQLite commit. */
68048
- volatileStateFields: ["lastChangedAt"]
68205
+ "yolov9m-320-int8": {
68206
+ personPrecision: .974,
68207
+ personRecall: .684
68208
+ },
68209
+ "yolov9m-640": {
68210
+ personPrecision: .976,
68211
+ personRecall: .854
68212
+ },
68213
+ "yolov9m-640-int8": {
68214
+ personPrecision: .976,
68215
+ personRecall: .847
68216
+ },
68217
+ "yolov9s-320": {
68218
+ personPrecision: .958,
68219
+ personRecall: .479
68220
+ },
68221
+ "yolov9s-320-int8": {
68222
+ personPrecision: .969,
68223
+ personRecall: .438
68224
+ },
68225
+ "yolov9s-640": {
68226
+ personPrecision: .986,
68227
+ personRecall: .753
68228
+ },
68229
+ "yolov9s-640-int8": {
68230
+ personPrecision: .963,
68231
+ personRecall: .823
68232
+ },
68233
+ "yolov9t-320": {
68234
+ personPrecision: .946,
68235
+ personRecall: .368
68236
+ },
68237
+ "yolov9t-320-int8": {
68238
+ personPrecision: .943,
68239
+ personRecall: .347
68240
+ },
68241
+ "yolov9t-640": {
68242
+ personPrecision: .979,
68243
+ personRecall: .646
68244
+ },
68245
+ "yolov9t-640-int8": {
68246
+ personPrecision: .979,
68247
+ personRecall: .646
68248
+ }
68049
68249
  };
68050
68250
  //#endregion
68051
- //#region src/capabilities/pressure-sensor.cap.ts
68251
+ //#region src/pipeline/auto-tune/accelerator-compute.ts
68252
+ /**
68253
+ * Capacity of an accelerator we have not measured (D930).
68254
+ *
68255
+ * The latency table stays the measured columns. A device that reports compute
68256
+ * (OpenVINO GOPS, or execution units × clock, or compute units, or TOPS) is
68257
+ * estimated from the nearest measured column of the same accelerator kind,
68258
+ * scaled by the compute ratio and clamped. A node benchmark still replaces
68259
+ * the estimate. A device that reports neither compute nor a known class stays
68260
+ * `unknown`.
68261
+ *
68262
+ * A failed probe field is `null`, never `0` (D393): zero would be a compute
68263
+ * ratio of zero and would size the device as if it could not run.
68264
+ */
68265
+ /**
68266
+ * An unmeasured device is never estimated outside this band of its reference.
68267
+ *
68268
+ * Peak GOPS and detector latency do not move together: a 640 model is memory
68269
+ * bound, a 320 model less so, and the N100→N305 public EU×clock ratio is
68270
+ * about 2.2×. 2× is the widest that spread justifies without a measurement
68271
+ * on the device; 0.5× is the symmetric floor so a tiny iGPU is not told it
68272
+ * is many times slower than the weakest column we measured. A node benchmark
68273
+ * replaces the estimate and is not clamped.
68274
+ */
68275
+ var ESTIMATE_COMPUTE_RATIO_MIN = .5;
68276
+ var ESTIMATE_COMPUTE_RATIO_MAX = 2;
68277
+ /**
68278
+ * Within this of 1.0, a device whose class IS the reference is that measured
68279
+ * machine (a probe clock a few percent off the spec), not a different chip.
68280
+ * An unknown class is still estimated, so the plan says so.
68281
+ */
68282
+ var REFERENCE_MATCH_FRACTION = .05;
68283
+ /** N100 iGPU: 24 execution units, 750 MHz graphics max (Intel specification). */
68284
+ var N100_IGPU_EU = 24;
68285
+ var N100_IGPU_CLOCK_MHZ = 750;
68286
+ /**
68287
+ * Core Ultra 7 265K iGPU: 4 Xe-cores. OpenVINO's execution-unit count for
68288
+ * Xe-LP is 16 per Xe-core, and the graphics max dynamic frequency is 2000 MHz.
68289
+ * Used only to choose the nearest column for a chip that is not itself a
68290
+ * Core Ultra — that class is the measurement and is never scaled.
68291
+ */
68292
+ var CORE_ULTRA_265K_IGPU_EU = 64;
68293
+ var CORE_ULTRA_265K_IGPU_CLOCK_MHZ = 2e3;
68294
+ /** Phoenix / Hawk Point Radeon 780M: 12 compute units. */
68295
+ var PHOENIX_780M_CU = 12;
68296
+ var positiveOrNull = zod.z.number().nullable().transform((value) => value !== null && Number.isFinite(value) && value > 0 ? value : null);
68297
+ var textOrNull = zod.z.string().nullable().transform((value) => value !== null && value.trim() !== "" ? value : null);
68298
+ var PrecisionGopsSchema = zod.z.object({
68299
+ int8: positiveOrNull,
68300
+ fp16: positiveOrNull
68301
+ });
68302
+ /** What one accelerator device reported. Every failed query is null. */
68303
+ var AcceleratorComputeSchema = zod.z.object({
68304
+ deviceKey: zod.z.string(),
68305
+ fullDeviceName: textOrNull,
68306
+ executionUnits: positiveOrNull,
68307
+ clockMHz: positiveOrNull,
68308
+ gops: PrecisionGopsSchema.nullable(),
68309
+ architecture: textOrNull,
68310
+ npuName: textOrNull,
68311
+ tops: positiveOrNull,
68312
+ vendor: textOrNull,
68313
+ webgpuArchitecture: textOrNull,
68314
+ webgpuDevice: textOrNull,
68315
+ computeUnits: positiveOrNull
68316
+ });
68317
+ var AcceleratorFactsPayloadSchema = zod.z.object({ accelerators: zod.z.array(AcceleratorComputeSchema).nullable() });
68318
+ /**
68319
+ * Compute of the machines the latency columns were measured on.
68320
+ * GOPS is preferred when a column carries it; none of these do yet — the
68321
+ * boxes were measured before the probe reported `DEVICE_GOPS` — so the scale
68322
+ * uses execution units × clock (Intel) or compute units (AMD) until a
68323
+ * re-measure locks a GOPS figure on the column.
68324
+ */
68325
+ var REFERENCE_COMPUTE = [
68326
+ {
68327
+ hardwareClass: "intel-n-series",
68328
+ accelerator: "intel-igpu",
68329
+ score: {
68330
+ metric: "eu-mhz",
68331
+ value: 24 * 750
68332
+ }
68333
+ },
68334
+ {
68335
+ hardwareClass: "intel-core-ultra",
68336
+ accelerator: "intel-igpu",
68337
+ score: {
68338
+ metric: "eu-mhz",
68339
+ value: 64 * CORE_ULTRA_265K_IGPU_CLOCK_MHZ
68340
+ }
68341
+ },
68342
+ {
68343
+ hardwareClass: "amd-phoenix",
68344
+ accelerator: "amd-igpu",
68345
+ score: {
68346
+ metric: "cu",
68347
+ value: 12
68348
+ }
68349
+ }
68350
+ ];
68351
+ /** Parse a probe payload. `null` means the probe failed — not "no accelerators". */
68352
+ function parseAcceleratorFactsJson(raw) {
68353
+ const parsed = AcceleratorFactsPayloadSchema.safeParse(raw);
68354
+ if (!parsed.success || parsed.data.accelerators === null) return null;
68355
+ return parsed.data.accelerators;
68356
+ }
68357
+ function positive$1(value) {
68358
+ return value !== null && value > 0 ? value : null;
68359
+ }
68360
+ /** Scores in the order the estimate prefers. A non-positive field is absent. */
68361
+ function computeScores(report) {
68362
+ const scores = [];
68363
+ const int8 = positive$1(report.gops?.int8 ?? null);
68364
+ if (int8 !== null) scores.push({
68365
+ metric: "gops-int8",
68366
+ value: int8
68367
+ });
68368
+ const fp16 = positive$1(report.gops?.fp16 ?? null);
68369
+ if (fp16 !== null) scores.push({
68370
+ metric: "gops-fp16",
68371
+ value: fp16
68372
+ });
68373
+ const units = positive$1(report.executionUnits);
68374
+ const clock = positive$1(report.clockMHz);
68375
+ if (units !== null && clock !== null) scores.push({
68376
+ metric: "eu-mhz",
68377
+ value: units * clock
68378
+ });
68379
+ const computeUnits = positive$1(report.computeUnits);
68380
+ if (computeUnits !== null) scores.push({
68381
+ metric: "cu",
68382
+ value: computeUnits
68383
+ });
68384
+ const tops = positive$1(report.tops);
68385
+ if (tops !== null) scores.push({
68386
+ metric: "tops",
68387
+ value: tops
68388
+ });
68389
+ return scores;
68390
+ }
68391
+ function nearestReference(kind, score, references) {
68392
+ const same = references.filter((ref) => ref.accelerator === kind && ref.score.metric === score.metric && ref.score.value > 0);
68393
+ const first = same[0];
68394
+ if (first === void 0) return null;
68395
+ return same.reduce((best, ref) => {
68396
+ return Math.abs(Math.log(score.value / ref.score.value)) < Math.abs(Math.log(score.value / best.score.value)) ? ref : best;
68397
+ }, first);
68398
+ }
68399
+ function scoreAgainstReferences(report, kind, references) {
68400
+ for (const score of computeScores(report)) if (nearestReference(kind, score, references) !== null) return score;
68401
+ return null;
68402
+ }
68403
+ /** True when this report can scale a measured column of `kind`. */
68404
+ function reportIsScalable(report, kind, references = REFERENCE_COMPUTE) {
68405
+ return scoreAgainstReferences(report, kind, references) !== null;
68406
+ }
68407
+ function classColumn(perf, hardwareClass, accelerator) {
68408
+ if (hardwareClass === "unknown") return void 0;
68409
+ return perf[hardwareClass]?.[accelerator];
68410
+ }
68411
+ function round1(value) {
68412
+ return Math.round(value * 10) / 10;
68413
+ }
68414
+ function clampRatio(ratio) {
68415
+ return Math.min(2, Math.max(ESTIMATE_COMPUTE_RATIO_MIN, ratio));
68416
+ }
68417
+ function scaleColumn(column, ratio, estimatedFrom) {
68418
+ const out = {};
68419
+ for (const [modelId, cell] of Object.entries(column)) out[modelId] = {
68420
+ p50Ms: round1(cell.p50Ms / ratio),
68421
+ fps: Math.max(1, Math.round(cell.fps * ratio)),
68422
+ measured: false,
68423
+ estimatedFrom
68424
+ };
68425
+ return out;
68426
+ }
68052
68427
  /**
68053
- * Atmospheric pressure reading in hectopascals. Drives Home Assistant
68054
- * `sensor` entries with `device_class: pressure`.
68055
- *
68056
- * Unit normalisation: hPa. The canonical display unit (`hPa`) is a
68057
- * descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
68058
- * `sourceInfo`.
68428
+ * The latency column for one device. A measured class other than the N-series
68429
+ * spread is returned unchanged — one class there is one measured machine.
68430
+ * `intel-n-series` is the N100 column; a different reported compute scales it.
68431
+ * An unknown class scales from the nearest reference of the same kind.
68059
68432
  */
68060
- var PressureSensorStatusSchema = zod.z.object({
68061
- /** Current pressure in hPa. */
68062
- hpa: zod.z.number(),
68063
- /** Ms epoch when the slice was last updated. */
68064
- lastFetchedAt: zod.z.number(),
68065
- /** Live display unit from the upstream source (e.g. HA
68066
- * `attributes.unit_of_measurement`). The UI prefers this over the
68067
- * role's canonical unit. Absent → fall back to the canonical unit. */
68068
- unit: zod.z.string().optional(),
68069
- /** Suggested decimal places for numeric display.
68070
- * Populated live from the upstream source when provided (e.g. HA
68071
- * `attributes.suggested_display_precision`). Falls back to
68072
- * auto-formatting when absent. */
68073
- precision: zod.z.number().int().min(0).max(10).optional()
68074
- });
68075
- var pressureSensorCapability = {
68076
- name: "pressure-sensor",
68077
- scope: "device",
68078
- deviceNative: true,
68079
- mode: "singleton",
68080
- deviceTypes: [require_err_msg.DeviceType.Sensor],
68081
- methods: {},
68082
- status: {
68083
- schema: PressureSensorStatusSchema,
68084
- kind: "push"
68085
- },
68433
+ function resolveLatencyColumn(input) {
68434
+ const perf = input.perf ?? DETECTOR_PERF;
68435
+ const references = input.references ?? REFERENCE_COMPUTE;
68436
+ const measured = classColumn(perf, input.hardwareClass, input.accelerator);
68437
+ if (measured !== void 0 && input.hardwareClass !== "intel-n-series") return {
68438
+ column: measured,
68439
+ estimatedFrom: null
68440
+ };
68441
+ const score = input.report === null ? null : scoreAgainstReferences(input.report, input.accelerator, references);
68442
+ if (score === null) return measured === void 0 ? null : {
68443
+ column: measured,
68444
+ estimatedFrom: null
68445
+ };
68446
+ const nearest = nearestReference(input.accelerator, score, references);
68447
+ const base = nearest === null ? void 0 : classColumn(perf, nearest.hardwareClass, input.accelerator);
68448
+ if (nearest === null || base === void 0) return measured === void 0 ? null : {
68449
+ column: measured,
68450
+ estimatedFrom: null
68451
+ };
68452
+ const rawRatio = score.value / nearest.score.value;
68453
+ if (input.hardwareClass === nearest.hardwareClass && Math.abs(rawRatio - 1) <= .05) return {
68454
+ column: base,
68455
+ estimatedFrom: null
68456
+ };
68457
+ return {
68458
+ column: scaleColumn(base, clampRatio(rawRatio), nearest.hardwareClass),
68459
+ estimatedFrom: nearest.hardwareClass
68460
+ };
68461
+ }
68462
+ //#endregion
68463
+ //#region src/capabilities/platform-probe.cap.ts
68464
+ var ModelFormatSchema = zod.z.enum(require_sleep.MODEL_FORMATS);
68465
+ var HwAccelBackendInputSchema = zod.z.enum([
68466
+ "videotoolbox",
68467
+ "cuda",
68468
+ "nvdec",
68469
+ "vaapi",
68470
+ "qsv",
68471
+ "d3d11va",
68472
+ "dxva2",
68473
+ "amf",
68474
+ "vdpau",
68475
+ "drm",
68476
+ "coreml",
68477
+ "tensorrt",
68478
+ "openvino",
68479
+ "directml",
68480
+ "webgpu",
68481
+ "none"
68482
+ ]).nullable().optional();
68483
+ var HwAccelResolutionSchema = zod.z.object({
68484
+ preferred: zod.z.array(zod.z.string()).readonly(),
68485
+ rationale: zod.z.string()
68486
+ });
68487
+ var HardwareEncoderIdSchema = zod.z.enum([
68488
+ "h264_videotoolbox",
68489
+ "hevc_videotoolbox",
68490
+ "h264_vaapi",
68491
+ "hevc_vaapi",
68492
+ "h264_nvenc",
68493
+ "hevc_nvenc",
68494
+ "h264_qsv",
68495
+ "hevc_qsv",
68496
+ "h264_amf",
68497
+ "hevc_amf",
68498
+ "libx264",
68499
+ "libx265"
68500
+ ]);
68501
+ var HardwareEncoderProbeSchema = zod.z.object({
68502
+ encoder: HardwareEncoderIdSchema,
68503
+ codec: zod.z.enum(["H264", "H265"]),
68504
+ family: zod.z.enum([
68505
+ "videotoolbox",
68506
+ "vaapi",
68507
+ "nvenc",
68508
+ "qsv",
68509
+ "amf",
68510
+ "software"
68511
+ ]),
68512
+ available: zod.z.boolean(),
68513
+ reason: zod.z.string().optional()
68514
+ });
68515
+ zod.z.object({
68516
+ encoders: zod.z.array(HardwareEncoderProbeSchema).readonly(),
68517
+ defaultH264: HardwareEncoderIdSchema,
68518
+ defaultH265: HardwareEncoderIdSchema,
68519
+ probedAt: zod.z.number()
68520
+ });
68521
+ zod.z.object({
68522
+ methods: zod.z.array(zod.z.string()).readonly(),
68523
+ probedAt: zod.z.number()
68524
+ });
68525
+ var HardwarePlatformSchema = zod.z.enum([
68526
+ "darwin",
68527
+ "linux",
68528
+ "win32"
68529
+ ]);
68530
+ var HardwareArchSchema = zod.z.enum(["arm64", "x64"]);
68531
+ var GpuInfoSchema = zod.z.object({
68532
+ type: zod.z.enum([
68533
+ "nvidia",
68534
+ "amd",
68535
+ "intel",
68536
+ "apple"
68537
+ ]),
68538
+ name: zod.z.string(),
68539
+ memoryMB: zod.z.number().optional()
68540
+ });
68541
+ var NpuInfoSchema = zod.z.object({
68542
+ type: zod.z.enum([
68543
+ "apple-ane",
68544
+ "intel-npu",
68545
+ "amd-xdna"
68546
+ ]),
68547
+ name: zod.z.string().optional(),
68548
+ generation: zod.z.enum(["xdna1", "xdna2"]).optional()
68549
+ });
68550
+ var CoralBusSchema = zod.z.enum(["usb", "pci"]);
68551
+ var CoralInfoSchema = zod.z.object({
68552
+ type: zod.z.literal("coral-edgetpu"),
68553
+ /** Legacy single-bus report (older nodes) — read via `coralUsableUnits`. */
68554
+ bus: zod.z.string().optional(),
68555
+ /** Legacy USB unit count (older nodes) — read via `coralUsableUnits`. */
68556
+ count: zod.z.number().int().nonnegative().optional(),
68557
+ /** Usable units per bus — each one is its own `edgetpu:<bus>[:N]` device. */
68558
+ units: zod.z.array(zod.z.object({
68559
+ bus: CoralBusSchema,
68560
+ count: zod.z.number().int().nonnegative()
68561
+ })).readonly().optional(),
68562
+ /** Present-but-unusable units, each with its typed reason (never "absent"). */
68563
+ unavailable: zod.z.array(zod.z.object({
68564
+ bus: CoralBusSchema,
68565
+ count: zod.z.number().int().nonnegative(),
68566
+ reason: zod.z.enum(["driver-missing", "device-not-accessible"])
68567
+ })).readonly().optional()
68568
+ });
68569
+ var HardwareInfoSchema = zod.z.object({
68570
+ platform: HardwarePlatformSchema,
68571
+ arch: HardwareArchSchema,
68572
+ cpuModel: zod.z.string(),
68573
+ cpuCores: zod.z.number(),
68574
+ totalRAM_MB: zod.z.number(),
68575
+ availableRAM_MB: zod.z.number(),
68576
+ gpu: GpuInfoSchema.nullable(),
68577
+ npu: NpuInfoSchema.nullable(),
68578
+ coral: CoralInfoSchema.nullable().optional(),
68579
+ /**
68580
+ * Per-accelerator compute (D930). Omitted by a node that has not probed it.
68581
+ * `null` fields inside an entry are a failed query, never a zero.
68582
+ */
68583
+ accelerators: zod.z.array(AcceleratorComputeSchema).readonly().optional()
68584
+ });
68585
+ var PlatformScoreSchema = zod.z.object({
68586
+ runtime: zod.z.literal("python"),
68587
+ backend: zod.z.string(),
68588
+ format: zod.z.enum([
68589
+ "onnx",
68590
+ "coreml",
68591
+ "openvino",
68592
+ "tflite"
68593
+ ]),
68594
+ score: zod.z.number(),
68595
+ reason: zod.z.string(),
68596
+ available: zod.z.boolean()
68597
+ });
68598
+ var InferenceDeviceDescriptorSchema = zod.z.object({
68599
+ key: zod.z.string(),
68600
+ backend: zod.z.string(),
68601
+ device: zod.z.string(),
68602
+ format: ModelFormatSchema,
68603
+ runtime: zod.z.literal("python"),
68604
+ score: zod.z.number(),
68605
+ available: zod.z.boolean()
68606
+ });
68607
+ var PlatformCapabilitiesSchema = zod.z.object({
68608
+ hardware: HardwareInfoSchema,
68609
+ scores: zod.z.array(PlatformScoreSchema).readonly(),
68610
+ bestScore: PlatformScoreSchema,
68611
+ pythonPath: zod.z.string().nullable(),
68612
+ devices: zod.z.array(InferenceDeviceDescriptorSchema).readonly()
68613
+ });
68614
+ var ModelRequirementSchema = zod.z.object({
68615
+ modelId: zod.z.string(),
68616
+ name: zod.z.string(),
68617
+ minRAM_MB: zod.z.number(),
68618
+ accuracyScore: zod.z.number(),
68619
+ formats: zod.z.array(zod.z.string()).readonly()
68620
+ });
68621
+ var ResolvedInferenceConfigSchema = zod.z.object({
68622
+ modelId: zod.z.string(),
68623
+ runtime: zod.z.literal("python"),
68624
+ backend: zod.z.string(),
68625
+ format: ModelFormatSchema,
68626
+ reason: zod.z.string()
68627
+ });
68628
+ /**
68629
+ * The answer of an accelerator rescan (D919): the node's device list after
68630
+ * the re-probe, what changed, and the evidence that decided the Coral answer
68631
+ * (`usbBus` — could the USB bus be read at all; `decidedBy` — USB ids, the PCI
68632
+ * bus, the installed-delegate fallback, or nothing).
68633
+ */
68634
+ var AcceleratorRescanSchema = zod.z.object({
68635
+ devices: zod.z.array(InferenceDeviceDescriptorSchema).readonly(),
68636
+ appeared: zod.z.array(zod.z.string()).readonly(),
68637
+ disappeared: zod.z.array(zod.z.string()).readonly(),
68638
+ usbBus: zod.z.enum(["observed", "unobservable"]),
68639
+ decidedBy: zod.z.enum([
68640
+ "usb-ids",
68641
+ "pci",
68642
+ "delegate-fallback",
68643
+ "none"
68644
+ ])
68645
+ });
68646
+ var platformProbeCapability = {
68647
+ name: "platform-probe",
68648
+ scope: "system",
68649
+ mode: "singleton",
68650
+ methods: {
68651
+ getCapabilities: require_sleep.method(zod.z.void(), PlatformCapabilitiesSchema),
68652
+ getHardware: require_sleep.method(zod.z.void(), HardwareInfoSchema),
68653
+ resolveInferenceConfig: require_sleep.method(zod.z.object({ requirements: zod.z.array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema),
68654
+ resolveHwAccel: require_sleep.method(zod.z.object({ prefer: HwAccelBackendInputSchema }), HwAccelResolutionSchema),
68655
+ /**
68656
+ * Re-probe this node's accelerators NOW (Coral USB ids + PCIe apex units;
68657
+ * `/sys` and `/dev` reads, no model load) and rebuild the device list that
68658
+ * `getCapabilities` serves. The same re-probe runs every 60 s on its own;
68659
+ * this is the operator's "rescan inference devices". Target a node with the
68660
+ * `{ nodeId }` routing input, like `getCapabilities`.
68661
+ */
68662
+ rescanAccelerators: require_sleep.method(zod.z.void(), AcceleratorRescanSchema, {
68663
+ kind: "mutation",
68664
+ auth: "admin"
68665
+ })
68666
+ }
68667
+ };
68668
+ //#endregion
68669
+ //#region src/capabilities/power-meter.cap.ts
68670
+ /**
68671
+ * Multi-metric electrical meter. One slice can carry any combination
68672
+ * of instantaneous power (W), cumulative energy (kWh), voltage (V),
68673
+ * and current (A) — all fields optional so a single-metric source
68674
+ * (HA `sensor` with `device_class: power`) populates only what it
68675
+ * has and aggregators (energy dashboards, billing exports) compose
68676
+ * across providers without needing one cap per metric.
68677
+ *
68678
+ * Trade-off acknowledged: a HomeKit export that wants to surface
68679
+ * "power" and "energy" as separate services has to crack open the
68680
+ * slice and emit two characteristics from one cap. Worth it to avoid
68681
+ * exploding the cap catalog with `power-w` / `energy-kwh` /
68682
+ * `voltage-v` / `current-a` quartets that always travel together
68683
+ * in real-world devices.
68684
+ */
68685
+ var PowerMeterStatusSchema = zod.z.object({
68686
+ /** Instantaneous power draw in watts. */
68687
+ watts: zod.z.number().optional(),
68688
+ /** Cumulative energy in kilowatt-hours since the meter was reset. */
68689
+ kwhTotal: zod.z.number().optional(),
68690
+ /** Voltage in volts. */
68691
+ volts: zod.z.number().optional(),
68692
+ /** Current in amperes. */
68693
+ amps: zod.z.number().optional(),
68694
+ /** Apparent power in volt-amperes (W × power-factor reciprocal). */
68695
+ va: zod.z.number().optional(),
68696
+ /** Power factor (0..1) if reported. */
68697
+ powerFactor: zod.z.number().min(0).max(1).optional(),
68698
+ /** Ms epoch when the slice was last updated. */
68699
+ lastFetchedAt: zod.z.number(),
68700
+ /** Live display unit of the single metric this slice carries (e.g. HA
68701
+ * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
68702
+ * Each upstream `sensor.*` entity surfaces ONE device_class, so one
68703
+ * unit per slice is unambiguous. The UI prefers this over the role's
68704
+ * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
68705
+ unit: zod.z.string().optional(),
68706
+ /** Suggested decimal places for numeric display.
68707
+ * Populated live from the upstream source when provided (e.g. HA
68708
+ * `attributes.suggested_display_precision`). Falls back to
68709
+ * auto-formatting when absent. */
68710
+ precision: zod.z.number().int().min(0).max(10).optional()
68711
+ });
68712
+ var powerMeterCapability = {
68713
+ name: "power-meter",
68714
+ scope: "device",
68715
+ deviceNative: true,
68716
+ mode: "singleton",
68717
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
68718
+ methods: {},
68719
+ status: {
68720
+ schema: PowerMeterStatusSchema,
68721
+ kind: "push"
68722
+ },
68723
+ runtimeState: PowerMeterStatusSchema,
68724
+ /**
68725
+ * Runtime-state durability: **restored** — as `numeric-sensor`; `kwhTotal` is an accumulator whose restored value is the baseline.
68726
+ *
68727
+ * See `RuntimeStateDurability`. Enforced by
68728
+ * `scripts/check-runtime-state-durability.ts`.
68729
+ */
68730
+ durability: "restored",
68731
+ /**
68732
+ * History (D850): recorded series. Gauges are floored at 30 s; `kwhTotal` is the one monotonic meter (counter).
68733
+ */
68734
+ history: { fields: {
68735
+ watts: {
68736
+ kind: "gauge",
68737
+ unit: "W",
68738
+ deadband: 1,
68739
+ minIntervalMs: 3e4
68740
+ },
68741
+ kwhTotal: {
68742
+ kind: "counter",
68743
+ unit: "kWh",
68744
+ deadband: .01,
68745
+ minIntervalMs: 6e4
68746
+ },
68747
+ volts: {
68748
+ kind: "gauge",
68749
+ unit: "V",
68750
+ deadband: 1,
68751
+ minIntervalMs: 3e4
68752
+ },
68753
+ amps: {
68754
+ kind: "gauge",
68755
+ unit: "A",
68756
+ deadband: .05,
68757
+ minIntervalMs: 3e4
68758
+ },
68759
+ va: {
68760
+ kind: "gauge",
68761
+ unit: "VA",
68762
+ deadband: 1,
68763
+ minIntervalMs: 3e4
68764
+ },
68765
+ powerFactor: {
68766
+ kind: "gauge",
68767
+ unit: "ratio",
68768
+ deadband: .01,
68769
+ minIntervalMs: 6e4
68770
+ }
68771
+ } },
68772
+ /** Clock fields: written, but excluded from the compare that decides
68773
+ * whether persisting is worth a SQLite commit. */
68774
+ volatileStateFields: ["lastFetchedAt"]
68775
+ };
68776
+ //#endregion
68777
+ //#region src/capabilities/presence.cap.ts
68778
+ /**
68779
+ * Presence cap. Models HA `person.*` and `device_tracker.*` entities
68780
+ * on `DeviceType.Presence`. Read-only — no setters: presence is
68781
+ * derived from upstream signals (mobile-app location, router DHCP
68782
+ * lease, Bluetooth beacon, manual zone change) and pushed into the
68783
+ * slice.
68784
+ *
68785
+ * `state` follows HA's canonical values — `home` / `not_home` /
68786
+ * `<zone-name>` for any other named zone. The zone name is freeform
68787
+ * because users define arbitrary zones (`work`, `gym`, `school`).
68788
+ *
68789
+ * GPS coordinates are nullable — tracker types without location
68790
+ * (Bluetooth scanner, MQTT presence) omit them. The presence of
68791
+ * GPS data is signaled by `DeviceFeature.PresenceGps` for fast
68792
+ * UI map-pin rendering.
68793
+ */
68794
+ var GpsLocationSchema = zod.z.object({
68795
+ /** Latitude in decimal degrees, -90..90. */
68796
+ latitude: zod.z.number().min(-90).max(90),
68797
+ /** Longitude in decimal degrees, -180..180. */
68798
+ longitude: zod.z.number().min(-180).max(180),
68799
+ /** Reported accuracy in meters (lower = better). */
68800
+ accuracyMeters: zod.z.number().nonnegative()
68801
+ });
68802
+ var PresenceStatusSchema = zod.z.object({
68803
+ /** `home` / `not_home` / any user-defined zone name. */
68804
+ state: zod.z.string(),
68805
+ /** Optional textual location label (zone name, city, address). Null
68806
+ * when only the binary state is known. */
68807
+ location: zod.z.string().nullable(),
68808
+ /** GPS coordinates when available. Null otherwise. */
68809
+ gps: GpsLocationSchema.nullable(),
68810
+ /** Optional battery level of the tracking device (0..100). Null
68811
+ * when not reported. */
68812
+ batteryPercent: zod.z.number().min(0).max(100).nullable(),
68813
+ /** Ms epoch when the slice was last updated. */
68814
+ lastChangedAt: zod.z.number()
68815
+ });
68816
+ var presenceCapability = {
68817
+ name: "presence",
68818
+ scope: "device",
68819
+ deviceNative: true,
68820
+ mode: "singleton",
68821
+ deviceTypes: [require_err_msg.DeviceType.Presence],
68822
+ methods: {},
68823
+ status: {
68824
+ schema: PresenceStatusSchema,
68825
+ kind: "push"
68826
+ },
68827
+ /**
68828
+ * Runtime-state slice — mirrored by the kernel. UI presence card
68829
+ * reads `state` + `location` for the text label; iff `gps !== null`
68830
+ * the map pin is rendered (use `DeviceFeature.PresenceGps` for the
68831
+ * pre-fetch fast-path check).
68832
+ */
68833
+ runtimeState: PresenceStatusSchema,
68834
+ /**
68835
+ * Runtime-state durability: **restored** — occupancy-relevant: the restored state is what an occupancy rule compares the first post-restart observation against.
68836
+ *
68837
+ * See `RuntimeStateDurability`. Enforced by
68838
+ * `scripts/check-runtime-state-durability.ts`.
68839
+ */
68840
+ durability: "restored",
68841
+ /**
68842
+ * History (D850): recorded series.
68843
+ */
68844
+ history: { fields: { batteryPercent: {
68845
+ kind: "gauge",
68846
+ unit: "%",
68847
+ deadband: 1
68848
+ } } },
68849
+ /** Clock fields: written, but excluded from the compare that decides
68850
+ * whether persisting is worth a SQLite commit. */
68851
+ volatileStateFields: ["lastChangedAt"]
68852
+ };
68853
+ //#endregion
68854
+ //#region src/capabilities/pressure-sensor.cap.ts
68855
+ /**
68856
+ * Atmospheric pressure reading in hectopascals. Drives Home Assistant
68857
+ * `sensor` entries with `device_class: pressure`.
68858
+ *
68859
+ * Unit normalisation: hPa. The canonical display unit (`hPa`) is a
68860
+ * descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
68861
+ * `sourceInfo`.
68862
+ */
68863
+ var PressureSensorStatusSchema = zod.z.object({
68864
+ /** Current pressure in hPa. */
68865
+ hpa: zod.z.number(),
68866
+ /** Ms epoch when the slice was last updated. */
68867
+ lastFetchedAt: zod.z.number(),
68868
+ /** Live display unit from the upstream source (e.g. HA
68869
+ * `attributes.unit_of_measurement`). The UI prefers this over the
68870
+ * role's canonical unit. Absent → fall back to the canonical unit. */
68871
+ unit: zod.z.string().optional(),
68872
+ /** Suggested decimal places for numeric display.
68873
+ * Populated live from the upstream source when provided (e.g. HA
68874
+ * `attributes.suggested_display_precision`). Falls back to
68875
+ * auto-formatting when absent. */
68876
+ precision: zod.z.number().int().min(0).max(10).optional()
68877
+ });
68878
+ var pressureSensorCapability = {
68879
+ name: "pressure-sensor",
68880
+ scope: "device",
68881
+ deviceNative: true,
68882
+ mode: "singleton",
68883
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
68884
+ methods: {},
68885
+ status: {
68886
+ schema: PressureSensorStatusSchema,
68887
+ kind: "push"
68888
+ },
68086
68889
  runtimeState: PressureSensorStatusSchema,
68087
68890
  /**
68088
68891
  * Runtime-state durability: **restored** — as `numeric-sensor`.
@@ -68786,6 +69589,43 @@ var ptzAutotrackCapability = {
68786
69589
  history: "none"
68787
69590
  };
68788
69591
  //#endregion
69592
+ //#region src/capabilities/running-state.cap.ts
69593
+ var RunningStateSourceSchema = zod.z.enum(["native", "assigned"]);
69594
+ var RunningStateStatusSchema = zod.z.object({
69595
+ /** null = unknown — never coerced to false (D393). */
69596
+ running: zod.z.boolean().nullable(),
69597
+ /** Epoch ms the current boolean began; null while `running` is null. */
69598
+ since: zod.z.number().nullable(),
69599
+ source: RunningStateSourceSchema,
69600
+ phase: zod.z.string().nullable(),
69601
+ lastFetchedAt: zod.z.number()
69602
+ });
69603
+ var RunningFlipEventDataSchema = zod.z.object({
69604
+ deviceId: zod.z.number(),
69605
+ since: zod.z.number().nullable(),
69606
+ phase: zod.z.string().nullable()
69607
+ });
69608
+ var runningStateCapability = {
69609
+ name: "running-state",
69610
+ scope: "device",
69611
+ deviceNative: true,
69612
+ mode: "singleton",
69613
+ deviceTypes: Object.values(require_err_msg.DeviceType),
69614
+ methods: {},
69615
+ events: {
69616
+ onStarted: { data: RunningFlipEventDataSchema },
69617
+ onFinished: { data: RunningFlipEventDataSchema }
69618
+ },
69619
+ status: {
69620
+ schema: RunningStateStatusSchema,
69621
+ kind: "push"
69622
+ },
69623
+ runtimeState: RunningStateStatusSchema,
69624
+ durability: "restored",
69625
+ history: { fields: { running: { kind: "boolean" } } },
69626
+ volatileStateFields: ["lastFetchedAt"]
69627
+ };
69628
+ //#endregion
68789
69629
  //#region src/capabilities/reboot.cap.ts
68790
69630
  /**
68791
69631
  * reboot — device-scoped capability for "soft" device reboots (firmware
@@ -71686,7 +72526,20 @@ var ZoneRuleSchema = zod.z.object({
71686
72526
  * Defaults to `true` so operators creating a rule via the UI
71687
72527
  * see it active immediately.
71688
72528
  */
71689
- enabled: zod.z.boolean().default(true)
72529
+ enabled: zod.z.boolean().default(true),
72530
+ /**
72531
+ * Package stage only. When true, a package candidate may open from a
72532
+ * scene difference in this rule's zone after a person leaves. The
72533
+ * package model, when it boxes something overlapping, is extra evidence
72534
+ * and is not the gate. Absent or false keeps today's model path.
72535
+ */
72536
+ detectBySceneDifference: zod.z.boolean().optional(),
72537
+ /**
72538
+ * Package stage only. When true, a promoted package is shown to the
72539
+ * `llm` cap before the delivery event is emitted. Absent or false emits
72540
+ * as today. Usable with or without `detectBySceneDifference`.
72541
+ */
72542
+ confirmWithLlm: zod.z.boolean().optional()
71690
72543
  });
71691
72544
  /**
71692
72545
  * Convenience array schema — used by addon device-settings
@@ -74999,6 +75852,215 @@ var PIPELINE_OWNER_CAPABILITY_NAMES = [
74999
75852
  "pipeline-runner"
75000
75853
  ];
75001
75854
  //#endregion
75855
+ //#region src/running-state/assignment.ts
75856
+ function constant(literal) {
75857
+ return {
75858
+ kind: "expression",
75859
+ expr: literal,
75860
+ bindings: {}
75861
+ };
75862
+ }
75863
+ function fromRead(source, cap, fieldPath) {
75864
+ return {
75865
+ kind: "from",
75866
+ source,
75867
+ cap,
75868
+ fieldPath
75869
+ };
75870
+ }
75871
+ function sortKeys(value) {
75872
+ if (Array.isArray(value)) return value.map(sortKeys);
75873
+ if (typeof value !== "object" || value === null) return value;
75874
+ return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, inner]) => [key, sortKeys(inner)]));
75875
+ }
75876
+ function sameFeature(a, b) {
75877
+ return JSON.stringify(sortKeys(a)) === JSON.stringify(sortKeys(b));
75878
+ }
75879
+ function booleanRunningField(input) {
75880
+ if (input.source.kind !== "boolean") return null;
75881
+ const read = fromRead(input.source.source, input.source.cap, input.source.fieldPath);
75882
+ if (!input.invert) return read;
75883
+ return {
75884
+ kind: "expression",
75885
+ expr: "!x",
75886
+ bindings: { x: read }
75887
+ };
75888
+ }
75889
+ function sinceField(input) {
75890
+ if (input.source.kind === "boolean" && input.changedAtField !== null) return fromRead(input.source.source, input.source.cap, input.changedAtField);
75891
+ return {
75892
+ kind: "expression",
75893
+ expr: "latchedAt(running)",
75894
+ bindings: { running: fromRead(input.target, "running-state", "running") }
75895
+ };
75896
+ }
75897
+ function powerRunningField(input) {
75898
+ if (input.source.kind !== "number") return null;
75899
+ if (!Number.isFinite(input.thresholdWatts) || !Number.isFinite(input.startDelayMs) || !Number.isFinite(input.stopDelayMs) || input.thresholdWatts < 0 || input.startDelayMs < 0 || input.stopDelayMs < 0) return null;
75900
+ const read = fromRead(input.source.source, input.source.cap, input.source.fieldPath);
75901
+ return {
75902
+ kind: "expression",
75903
+ expr: `dwell(x >= ${input.thresholdWatts}, ${input.startDelayMs}, ${input.stopDelayMs})`,
75904
+ bindings: { x: read }
75905
+ };
75906
+ }
75907
+ function runningAssignmentFeature(input) {
75908
+ const running = input.source.kind === "number" ? powerRunningField(input) : booleanRunningField(input);
75909
+ if (running === null) return null;
75910
+ return {
75911
+ kind: "fields",
75912
+ cap: "running-state",
75913
+ fields: {
75914
+ source: constant("\"assigned\""),
75915
+ phase: constant("null"),
75916
+ lastFetchedAt: constant("now"),
75917
+ running,
75918
+ since: sinceField(input)
75919
+ }
75920
+ };
75921
+ }
75922
+ function booleanSourceFromRunningField(source) {
75923
+ if (source === void 0) return null;
75924
+ if (source.kind === "from") return {
75925
+ source: source.source,
75926
+ cap: source.cap,
75927
+ fieldPath: source.fieldPath,
75928
+ kind: "boolean"
75929
+ };
75930
+ if (source.kind !== "expression" || source.expr !== "!x") return null;
75931
+ const x = source.bindings["x"];
75932
+ if (x === void 0 || x.kind !== "from") return null;
75933
+ return {
75934
+ source: x.source,
75935
+ cap: x.cap,
75936
+ fieldPath: x.fieldPath,
75937
+ kind: "boolean"
75938
+ };
75939
+ }
75940
+ function recoverPowerRunning(running) {
75941
+ if (running === void 0 || running.kind !== "expression") return null;
75942
+ const match = /^dwell\(x >= ([\d.]+), (\d+), (\d+)\)$/.exec(running.expr);
75943
+ if (match === null) return null;
75944
+ const x = running.bindings["x"];
75945
+ if (x === void 0 || x.kind !== "from") return null;
75946
+ const thresholdWatts = Number(match[1]);
75947
+ const startDelayMs = Number(match[2]);
75948
+ const stopDelayMs = Number(match[3]);
75949
+ if (!Number.isFinite(thresholdWatts) || !Number.isFinite(startDelayMs) || !Number.isFinite(stopDelayMs)) return null;
75950
+ return {
75951
+ source: {
75952
+ ...x,
75953
+ kind: "number"
75954
+ },
75955
+ invert: false,
75956
+ thresholdWatts,
75957
+ startDelayMs,
75958
+ stopDelayMs
75959
+ };
75960
+ }
75961
+ /** Recover what {@link runningAssignmentFeature} wrote, else null. */
75962
+ function runningAssignmentRecovered(stored, target) {
75963
+ if (stored.cap !== "running-state" || stored.kind !== "fields") return null;
75964
+ const power = recoverPowerRunning(stored.fields["running"]);
75965
+ if (power !== null) return power;
75966
+ const booleanSource = booleanSourceFromRunningField(stored.fields["running"]);
75967
+ if (booleanSource === null) return null;
75968
+ const since = stored.fields["since"];
75969
+ let changedAtField = null;
75970
+ if (since?.kind === "from" && since.cap === booleanSource.cap && since.source.addonId === booleanSource.source.addonId && since.source.stableId === booleanSource.source.stableId) changedAtField = since.fieldPath;
75971
+ const rebuilt = runningAssignmentFeature({
75972
+ mode: "replace",
75973
+ target,
75974
+ source: booleanSource,
75975
+ changedAtField,
75976
+ invert: stored.fields["running"]?.kind === "expression" && stored.fields["running"].expr === "!x",
75977
+ thresholdWatts: 0,
75978
+ startDelayMs: 0,
75979
+ stopDelayMs: 0
75980
+ });
75981
+ if (rebuilt === null) return null;
75982
+ const { enabled: _e, ...rest } = stored;
75983
+ if (!sameFeature(rest, rebuilt)) return null;
75984
+ return {
75985
+ source: booleanSource,
75986
+ invert: stored.fields["running"]?.kind === "expression" && stored.fields["running"].expr === "!x",
75987
+ thresholdWatts: 0,
75988
+ startDelayMs: 0,
75989
+ stopDelayMs: 0
75990
+ };
75991
+ }
75992
+ function isRunningAssignmentFeature(stored, mode, target) {
75993
+ const recovered = runningAssignmentRecovered(stored, target);
75994
+ if (recovered === null) return false;
75995
+ const rebuilt = recovered.source.kind === "number" ? runningAssignmentFeature({
75996
+ mode,
75997
+ target,
75998
+ source: recovered.source,
75999
+ changedAtField: null,
76000
+ invert: false,
76001
+ thresholdWatts: recovered.thresholdWatts,
76002
+ startDelayMs: recovered.startDelayMs,
76003
+ stopDelayMs: recovered.stopDelayMs
76004
+ }) : runningAssignmentFeature({
76005
+ mode,
76006
+ target,
76007
+ source: recovered.source,
76008
+ changedAtField: stored.fields["since"]?.kind === "from" ? stored.fields["since"].fieldPath : null,
76009
+ invert: recovered.invert,
76010
+ thresholdWatts: 0,
76011
+ startDelayMs: 0,
76012
+ stopDelayMs: 0
76013
+ });
76014
+ if (rebuilt === null) return false;
76015
+ const { enabled: _enabled, ...rest } = stored;
76016
+ return sameFeature(rest, rebuilt);
76017
+ }
76018
+ //#endregion
76019
+ //#region src/running-state/defaults.ts
76020
+ var RUNNING_POWER_THRESHOLD_WATTS = 5;
76021
+ var RUNNING_POWER_START_DELAY_MS = 3e4;
76022
+ var RUNNING_POWER_STOP_DELAY_MS = 18e4;
76023
+ //#endregion
76024
+ //#region src/running-state/from-operation.ts
76025
+ function resolveSince(running, previous, sinceMs) {
76026
+ if (running === null) return null;
76027
+ if ((previous?.running ?? null) !== running) return sinceMs;
76028
+ return previous?.since ?? sinceMs;
76029
+ }
76030
+ function nativeSlice(running, phase, previous, sinceMs, now) {
76031
+ return {
76032
+ running,
76033
+ since: resolveSince(running, previous, sinceMs),
76034
+ source: "native",
76035
+ phase: running === null ? null : phase,
76036
+ lastFetchedAt: now
76037
+ };
76038
+ }
76039
+ function runningFromDishwasherOperation(operation, program, previous, now) {
76040
+ if (operation === null) return nativeSlice(null, null, previous, now, now);
76041
+ if (operation === "Run" || operation === "Aborting") return nativeSlice(true, program ?? operation, previous, now, now);
76042
+ if (operation === "Pause") return nativeSlice(false, "Pause", previous, now, now);
76043
+ return nativeSlice(false, operation, previous, now, now);
76044
+ }
76045
+ function runningFromVacuumState(state, previous, now) {
76046
+ if (state === null) return nativeSlice(null, null, previous, now, now);
76047
+ if (state === "cleaning" || state === "returning" || state === "drying") return nativeSlice(true, state, previous, now, now);
76048
+ if (state === "paused") return nativeSlice(false, "paused", previous, now, now);
76049
+ return nativeSlice(false, state, previous, now, now);
76050
+ }
76051
+ function runningFromLawnMower(activity, previous, now) {
76052
+ if (activity === null) return nativeSlice(null, null, previous, now, now);
76053
+ if (activity === "mowing") return nativeSlice(true, activity, previous, now, now);
76054
+ if (activity === "paused") return nativeSlice(false, "paused", previous, now, now);
76055
+ return nativeSlice(false, activity, previous, now, now);
76056
+ }
76057
+ function runningFromMediaPlayer(state, previous, now) {
76058
+ if (state === null) return nativeSlice(null, null, previous, now, now);
76059
+ if (state === "playing" || state === "buffering") return nativeSlice(true, state, previous, now, now);
76060
+ if (state === "paused") return nativeSlice(false, "paused", previous, now, now);
76061
+ return nativeSlice(false, state, previous, now, now);
76062
+ }
76063
+ //#endregion
75002
76064
  //#region src/clips/clip-event-labels.ts
75003
76065
  /**
75004
76066
  * Which of OUR object detections a clip window holds — the ONE overlap rule
@@ -75642,11 +76704,12 @@ function expressionOutcome(source, read, now, stateful) {
75642
76704
  return unavailable;
75643
76705
  }
75644
76706
  const deadlines = [];
76707
+ const hooks = {
76708
+ noteDeadline: (at) => deadlines.push(at),
76709
+ convert: (value, from, to) => tryConvertUnit(value, normalizeUnit(from) ?? from, normalizeUnit(to) ?? to) ?? null
76710
+ };
75645
76711
  const result = require_composition.evaluateExpressionSource(source.expr, values, now, {
75646
- hooks: {
75647
- noteDeadline: (at) => deadlines.push(at),
75648
- convert: (value, from, to) => tryConvertUnit(value, normalizeUnit(from) ?? from, normalizeUnit(to) ?? to) ?? null
75649
- },
76712
+ hooks,
75650
76713
  ...stateful === void 0 ? {} : { invokeStateful: (invocation) => require_composition.evaluateStatefulExpressionCall(stateful.callPathPrefix === void 0 ? invocation : {
75651
76714
  ...invocation,
75652
76715
  path: `${stateful.callPathPrefix}@${invocation.path}`
@@ -75654,7 +76717,7 @@ function expressionOutcome(source, read, now, stateful) {
75654
76717
  currentOutput: stateful.currentOutput,
75655
76718
  startedAtMs: stateful.startedAtMs,
75656
76719
  nowMs: now
75657
- }) }
76720
+ }, hooks) }
75658
76721
  });
75659
76722
  if (!result.ok) return {
75660
76723
  state: "unavailable",
@@ -76606,6 +77669,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
76606
77669
  ptzAutotrack: ptzAutotrackCapability,
76607
77670
  recordingOnboard: recordingOnboardCapability,
76608
77671
  recordingSignal: recordingSignalCapability,
77672
+ runningState: runningStateCapability,
76609
77673
  sceneMonitor: sceneMonitorCapability,
76610
77674
  scriptRunner: scriptRunnerCapability,
76611
77675
  smoke: smokeCapability,
@@ -86189,6 +87253,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
86189
87253
  access: "create",
86190
87254
  role: "admin"
86191
87255
  },
87256
+ "runningState.getStatus": {
87257
+ capName: "running-state",
87258
+ capScope: "device",
87259
+ addonId: null,
87260
+ access: "view",
87261
+ role: "viewer"
87262
+ },
86192
87263
  "sceneMonitor.captureReference": {
86193
87264
  capName: "scene-monitor",
86194
87265
  capScope: "device",
@@ -88478,6 +89549,7 @@ var KNOWN_CAP_NAMES = [
88478
89549
  "recording-onboard",
88479
89550
  "recording-signal",
88480
89551
  "records",
89552
+ "running-state",
88481
89553
  "scene-monitor",
88482
89554
  "script-runner",
88483
89555
  "server-management",
@@ -88592,6 +89664,7 @@ var DEVICE_CAP_NAMES = [
88592
89664
  "recording",
88593
89665
  "recording-onboard",
88594
89666
  "recording-signal",
89667
+ "running-state",
88595
89668
  "scene-monitor",
88596
89669
  "script-runner",
88597
89670
  "smoke",
@@ -91276,6 +92349,32 @@ var APPLIANCE_CAP_VOCABULARY = {
91276
92349
  }],
91277
92350
  events: { onSent: { notOperatorFacing: "a delivery receipt of a send this system made — not a fact about the world" } }
91278
92351
  },
92352
+ "running-state": {
92353
+ scope: "device",
92354
+ noun: t("Running", "In funzione"),
92355
+ state: [{
92356
+ kind: "boolean",
92357
+ field: "running",
92358
+ label: t("Running", "In funzione"),
92359
+ whenTrue: t("When {device} starts", "Quando {device} inizia"),
92360
+ whenFalse: t("When {device} finishes", "Quando {device} finisce"),
92361
+ icon: "play"
92362
+ }],
92363
+ events: {
92364
+ onStarted: {
92365
+ category: require_event_category.EventCategory.RunningStarted,
92366
+ label: t("Started running", "Ha iniziato a funzionare"),
92367
+ phrase: t("When {device} starts running", "Quando {device} inizia a funzionare"),
92368
+ icon: "play"
92369
+ },
92370
+ onFinished: {
92371
+ category: require_event_category.EventCategory.RunningFinished,
92372
+ label: t("Finished", "Ha finito"),
92373
+ phrase: t("When {device} finishes", "Quando {device} finisce"),
92374
+ icon: "square"
92375
+ }
92376
+ }
92377
+ },
91279
92378
  update: {
91280
92379
  scope: "device",
91281
92380
  noun: t("Firmware update", "Aggiornamento firmware"),
@@ -94216,6 +95315,7 @@ var CAP_NAMES_WITH_STATUS = [
94216
95315
  "recording-archive",
94217
95316
  "recording-onboard",
94218
95317
  "recording-signal",
95318
+ "running-state",
94219
95319
  "scene-monitor",
94220
95320
  "script-runner",
94221
95321
  "smoke",
@@ -94371,6 +95471,7 @@ var CAPABILITY_NAMES = {
94371
95471
  recordingOnboard: "recording-onboard",
94372
95472
  recordingSignal: "recording-signal",
94373
95473
  records: "records",
95474
+ runningState: "running-state",
94374
95475
  sceneMonitor: "scene-monitor",
94375
95476
  scriptRunner: "script-runner",
94376
95477
  serverManagement: "server-management",
@@ -94964,6 +96065,10 @@ var CAPABILITY_ROUTER_KEYS = [
94964
96065
  key: "records",
94965
96066
  name: "records"
94966
96067
  },
96068
+ {
96069
+ key: "runningState",
96070
+ name: "running-state"
96071
+ },
94967
96072
  {
94968
96073
  key: "sceneMonitor",
94969
96074
  name: "scene-monitor"
@@ -95280,6 +96385,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
95280
96385
  recordingOnboardCapability,
95281
96386
  recordingSignalCapability,
95282
96387
  recordsCapability,
96388
+ runningStateCapability,
95283
96389
  sceneMonitorCapability,
95284
96390
  scriptRunnerCapability,
95285
96391
  serverManagementCapability,
@@ -97596,6 +98702,11 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
97596
98702
  form: "single",
97597
98703
  optional: true
97598
98704
  }],
98705
+ "runningState.getStatus": [{
98706
+ name: "deviceId",
98707
+ form: "single",
98708
+ optional: false
98709
+ }],
97599
98710
  "sceneMonitor.captureReference": [{
97600
98711
  name: "deviceId",
97601
98712
  form: "single",
@@ -98297,7 +99408,7 @@ function getCapsByProviderKind(kind) {
98297
99408
  //#region src/generated/runtime-state-durability.ts
98298
99409
  /**
98299
99410
  * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
98300
- * `runtimeState`. 71 caps: 37 `restored`, 34 `session`.
99411
+ * `runtimeState`. 72 caps: 38 `restored`, 34 `session`.
98301
99412
  *
98302
99413
  * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
98303
99414
  * with `durability: 'session'` has one and has decided not to keep it.
@@ -98515,6 +99626,10 @@ var RUNTIME_STATE_POLICY = {
98515
99626
  durability: "session",
98516
99627
  volatileFields: []
98517
99628
  },
99629
+ "running-state": {
99630
+ durability: "restored",
99631
+ volatileFields: ["lastFetchedAt"]
99632
+ },
98518
99633
  "scene-monitor": {
98519
99634
  durability: "session",
98520
99635
  volatileFields: []
@@ -98637,6 +99752,7 @@ var RESTORED_CAP_NAMES = [
98637
99752
  "pressure-sensor",
98638
99753
  "privacy-mask",
98639
99754
  "recording-onboard",
99755
+ "running-state",
98640
99756
  "smoke",
98641
99757
  "stream-catalog",
98642
99758
  "stream-params",
@@ -98752,7 +99868,7 @@ var BucketAccumulator = class {
98752
99868
  //#region src/generated/history-catalog.ts
98753
99869
  /**
98754
99870
  * Cap history catalog, projected from every `*.cap.ts` `history` block.
98755
- * 40 caps with series (73 series), 30 caps answered `'none'`.
99871
+ * 41 caps with series (74 series), 30 caps answered `'none'`.
98756
99872
  * Field keys are `runtimeState` paths (`a.b` into fixed objects only).
98757
99873
  */
98758
99874
  var HISTORY_CATALOG = {
@@ -99029,6 +100145,7 @@ var HISTORY_CATALOG = {
99029
100145
  unit: "hPa",
99030
100146
  deadband: .1
99031
100147
  } },
100148
+ "running-state": { "running": { kind: "boolean" } },
99032
100149
  "smoke": { "detected": { kind: "boolean" } },
99033
100150
  "stream-params": {
99034
100151
  "main.bitrate": {
@@ -100832,6 +101949,7 @@ function prepareNotification(caps, n) {
100832
101949
  * narrows, and still fails CLOSED on a subject that cannot produce the identity
100833
101950
  * it tests (a device the mirror cannot name is not evidence of a camera).
100834
101951
  */
101952
+ var LEGACY_KINDS_WITHOUT_FILTERS = new Set(NC_LEGACY_SYSTEM_EVENT_KINDS);
100835
101953
  /**
100836
101954
  * Every filter, for the callers that must cover all of them (guards, tests).
100837
101955
  *
@@ -100855,6 +101973,7 @@ var NC_SYSTEM_EVENT_FILTER_KEYS = [
100855
101973
  * ignores it and an editor hides its input. It never means "excluded".
100856
101974
  */
100857
101975
  function systemEventFilterApplies(kind, filter) {
101976
+ if (LEGACY_KINDS_WITHOUT_FILTERS.has(kind)) return false;
100858
101977
  switch (filter) {
100859
101978
  case "deviceIds": return kind.startsWith("device-") || kind.startsWith("stream-") || kind === "detection-blind" || kind.startsWith("motion-") || kind.startsWith("recording-") || kind === "alarm-triggered";
100860
101979
  case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind" || kind.startsWith("motion-");
@@ -100909,9 +102028,6 @@ var NC_SYSTEM_EVENT_FAMILY = {
100909
102028
  "device-battery-normal": "device-events",
100910
102029
  "device-consumable-low": "device-events",
100911
102030
  "device-consumable-normal": "device-events",
100912
- "device-tank-low": "device-events",
100913
- "device-tank-full": "device-events",
100914
- "device-tank-normal": "device-events",
100915
102031
  "stream-online": "device-events",
100916
102032
  "stream-offline": "device-events",
100917
102033
  "detection-blind": "device-events",
@@ -101006,16 +102122,6 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
101006
102122
  label: "Consumables",
101007
102123
  kinds: ["device-consumable-low", "device-consumable-normal"]
101008
102124
  },
101009
- {
101010
- id: "tanks",
101011
- family: "device-events",
101012
- label: "Tanks",
101013
- kinds: [
101014
- "device-tank-low",
101015
- "device-tank-full",
101016
- "device-tank-normal"
101017
- ]
101018
- },
101019
102125
  {
101020
102126
  id: "detection-blind",
101021
102127
  family: "device-events",
@@ -102912,11 +104018,6 @@ var RULE_BODY_ONLY = ["body"];
102912
104018
  var RULE_TEXT = ["title", "body"];
102913
104019
  var BATTERY = ["device-battery-low", "device-battery-normal"];
102914
104020
  var CONSUMABLE = ["device-consumable-low", "device-consumable-normal"];
102915
- var TANK = [
102916
- "device-tank-low",
102917
- "device-tank-full",
102918
- "device-tank-normal"
102919
- ];
102920
104021
  var ALERT_EPISODE = [
102921
104022
  "alert-critical",
102922
104023
  "alert-error",
@@ -102948,7 +104049,6 @@ var DEVICE_EVENTS = [
102948
104049
  "device-enabled",
102949
104050
  ...BATTERY,
102950
104051
  ...CONSUMABLE,
102951
- ...TANK,
102952
104052
  "stream-online",
102953
104053
  "stream-offline",
102954
104054
  "detection-blind",
@@ -102994,12 +104094,6 @@ var STATE_SUMMARY_SECTION_VARS = [
102994
104094
  example: "Robot — filter 10 %",
102995
104095
  kinds: CONSUMABLE
102996
104096
  },
102997
- {
102998
- name: "summaryTanks",
102999
- label: "Summary: tanks",
103000
- example: "Robot — clean water low",
103001
- kinds: TANK
103002
- },
103003
104097
  {
103004
104098
  name: "summaryDetection",
103005
104099
  label: "Summary: cameras detecting nothing",
@@ -103408,18 +104502,7 @@ var NC_TEMPLATE_VARS = [
103408
104502
  families: ["rule"],
103409
104503
  fields: [...RULE_TEXT],
103410
104504
  kinds: ["system"],
103411
- systemEventKinds: [...CONSUMABLE, ...TANK]
103412
- },
103413
- {
103414
- name: "tank",
103415
- label: "Tank",
103416
- description: "Which tank, as a stable token: cleanWater, dirtyWater, detergent, dustBin, or the device key of any other supply (salt, rinse aid).",
103417
- example: "clean water",
103418
- group: "system",
103419
- families: ["rule"],
103420
- fields: [...RULE_TEXT],
103421
- kinds: ["system"],
103422
- systemEventKinds: [...TANK]
104505
+ systemEventKinds: [...CONSUMABLE]
103423
104506
  },
103424
104507
  {
103425
104508
  name: "profile",
@@ -104070,9 +105153,6 @@ var NC_TEMPLATE_VARS = [
104070
105153
  * delete it from here then, and the served catalog names it again.
104071
105154
  */
104072
105155
  var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
104073
- "device-tank-low",
104074
- "device-tank-full",
104075
- "device-tank-normal",
104076
105156
  "node-restarting",
104077
105157
  "node-updating",
104078
105158
  "alert-critical",
@@ -104234,15 +105314,6 @@ var NC_SYSTEM_EVENT_CLASS_SPECS = [
104234
105314
  snapshotLabel: "Consumables low now",
104235
105315
  snapshotSection: "consumables"
104236
105316
  },
104237
- {
104238
- id: "tanks",
104239
- family: "device-events",
104240
- label: "Tanks (low or full / back to normal)",
104241
- bad: ["device-tank-low", "device-tank-full"],
104242
- recovery: ["device-tank-normal"],
104243
- snapshotLabel: "Tanks low or full now",
104244
- snapshotSection: "tanks"
104245
- },
104246
105317
  {
104247
105318
  id: "motion-noise",
104248
105319
  family: "device-events",
@@ -105603,898 +106674,341 @@ function stepModelRunsOnFormat(models, modelId, format) {
105603
106674
  const named = models.find((m) => m.id === modelId);
105604
106675
  if (named !== void 0 && hasBuild(named, format)) return true;
105605
106676
  }
105606
- return models.some((m) => m.legacy !== true && hasBuild(m, format));
105607
- }
105608
- //#endregion
105609
- //#region src/pipeline/removed-model-ids.ts
105610
- /**
105611
- * Model ids DELETED from a step's catalog (D703, D710, D719), per step — and
105612
- * the one question asked of them: is a stored pin to this id a pin to a model
105613
- * that no longer exists? (D725)
105614
- *
105615
- * This is NOT a resolver input. `resolveModelForFormat` still returns an
105616
- * unknown id unchanged (a custom model), and a bulk pass pinned to one still
105617
- * refuses rather than substitutes (D54). The ONE reader is the orchestrator's
105618
- * stored-pin reset (`removed-model-pin-reset.ts`), which clears such a pin from
105619
- * the store so the step follows its default again — unless a registered custom
105620
- * model carries the same id.
105621
- *
105622
- * `removed-model-ids.spec.ts` (addon-pipeline) pins that every id here is
105623
- * really absent from its step's catalog. A model deleted from a catalog is
105624
- * added here in the same change, or its stored pins stay behind as custom ids.
105625
- */
105626
- /** `stepId → ids deleted from that step's catalog`. */
105627
- var REMOVED_MODEL_IDS = {
105628
- "object-detection": ["efficientdet-lite0-edgetpu", "ssdlite-mobiledet-coco-edgetpu"],
105629
- "face-detection": [
105630
- "scrypted-yolov9t-face",
105631
- "scrfd-2.5g",
105632
- "ssd-mobilenet-v2-face-edgetpu"
105633
- ],
105634
- "face-embedding": ["arcface-r100", "inception-resnet-v1"],
105635
- "plate-detection": ["yolov8n-plate"],
105636
- "plate-ocr": ["vgg-english-g2"],
105637
- "clip-embedding": [
105638
- "mobileclip-s0",
105639
- "mobileclip-s1",
105640
- "mobileclip-s2"
105641
- ],
105642
- "package-detection": ["yolov8n-package"],
105643
- "bird-classifier": ["bird-classifier-aiy-edgetpu", "bird-nabirds-404"],
105644
- "animal-classifier": ["animals-10"],
105645
- "vehicle-classifier": ["vehicle-type-efficientnet"]
105646
- };
105647
- /** True when `modelId` was deleted from `stepId`'s catalog. */
105648
- function isRemovedModelId(stepId, modelId) {
105649
- return REMOVED_MODEL_IDS[stepId]?.includes(modelId) ?? false;
105650
- }
105651
- //#endregion
105652
- //#region src/pipeline/model-bound-settings.ts
105653
- /** Suffix of the sibling key that names the model a stored value was made for. */
105654
- var MODEL_BINDING_SUFFIX = "ModelId";
105655
- /** `minConfidencePerson` → `minConfidencePersonModelId`. */
105656
- function modelBindingKey(key) {
105657
- return `${key}${MODEL_BINDING_SUFFIX}`;
105658
- }
105659
- /** The model a stored value was made for, or `undefined` when unbound. */
105660
- function storedBinding(settings, key) {
105661
- const binding = settings?.[modelBindingKey(key)];
105662
- return typeof binding === "string" && binding.length > 0 ? binding : void 0;
105663
- }
105664
- /**
105665
- * The effective value of `key` for the model that runs (`runningModelId`,
105666
- * `undefined` when unknown — it then matches no binding).
105667
- */
105668
- function resolveModelBoundSetting(settings, key, runningModelId, modelDefault, globalDefault) {
105669
- const stored = settings?.[key];
105670
- const binding = storedBinding(settings, key);
105671
- if (stored !== void 0 && binding === void 0) return {
105672
- value: stored,
105673
- source: "override"
105674
- };
105675
- if (stored !== void 0 && binding !== void 0 && binding === runningModelId) return {
105676
- value: stored,
105677
- source: "override",
105678
- boundModelId: binding
105679
- };
105680
- const inapplicable = stored !== void 0 && binding !== void 0 ? { inapplicableOverride: {
105681
- value: stored,
105682
- modelId: binding
105683
- } } : {};
105684
- if (modelDefault !== void 0) return {
105685
- value: modelDefault,
105686
- source: "model-default",
105687
- ...inapplicable
105688
- };
105689
- return {
105690
- value: globalDefault,
105691
- source: "global-default",
105692
- ...inapplicable
105693
- };
105694
- }
105695
- /**
105696
- * The OPERATOR layer of a step's settings as the running model sees it: every
105697
- * model-bound value made for another model is removed (so the default
105698
- * underneath — the running model's — wins the merge), and every binding key is
105699
- * stripped. Unbound values are kept. Returns a fresh object; `undefined` stays
105700
- * `undefined`.
105701
- */
105702
- function applyModelBoundSettings(settings, boundKeys, runningModelId) {
105703
- if (settings === void 0) return void 0;
105704
- const out = { ...settings };
105705
- for (const key of boundKeys) {
105706
- const binding = storedBinding(settings, key);
105707
- if (binding !== void 0 && binding !== runningModelId) delete out[key];
105708
- delete out[modelBindingKey(key)];
105709
- }
105710
- return out;
105711
- }
105712
- /** Store `value` for `key`, made for `modelId`. Returns a fresh object. */
105713
- function writeModelBoundSetting(settings, key, value, modelId) {
105714
- return {
105715
- ...settings,
105716
- [key]: value,
105717
- [modelBindingKey(key)]: modelId
105718
- };
105719
- }
105720
- /** Remove an override and its binding — "reset to the model default". */
105721
- function clearModelBoundSetting(settings, key) {
105722
- const out = { ...settings };
105723
- delete out[key];
105724
- delete out[modelBindingKey(key)];
105725
- return out;
105726
- }
105727
- /** True when any of `boundKeys` has an override IN FORCE for `modelId`. */
105728
- function hasModelBoundOverrides(settings, boundKeys, modelId) {
105729
- return boundKeys.some((key) => resolveModelBoundSetting(settings, key, modelId, void 0, void 0).source === "override");
105730
- }
105731
- /**
105732
- * "Reset thresholds to model defaults": remove every override of `boundKeys`
105733
- * that is in force for `modelId` (bound to it, or unbound), with its binding —
105734
- * one result, so the editor writes it in one save. An override stored for
105735
- * ANOTHER model is kept: it is not in force here, and it applies again when
105736
- * that model runs.
105737
- */
105738
- function clearModelBoundOverrides(settings, boundKeys, modelId) {
105739
- return boundKeys.reduce((acc, key) => resolveModelBoundSetting(acc, key, modelId, void 0, void 0).source === "override" ? clearModelBoundSetting(acc, key) : acc, { ...settings });
105740
- }
105741
- /**
105742
- * The keys of every `bindsToModel` field of a step's config schema, groups
105743
- * flattened (D738). One definition for the runner (which applies the binding)
105744
- * and the orchestrator's Auto tune (which clears those overrides, D745).
105745
- */
105746
- function collectModelBoundKeys(fields) {
105747
- return fields.flatMap((field) => {
105748
- if (field.type === "group") return collectModelBoundKeys(field.fields);
105749
- return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
105750
- });
105751
- }
105752
- //#endregion
105753
- //#region src/pipeline/step-settings-layers.ts
105754
- /**
105755
- * The layers of a detection step's settings, and the field-level edits on them
105756
- * (D754, D927).
105757
- *
105758
- * A step's settings reach a session through two stored layers, merged key by
105759
- * key — a key in the higher layer wins:
105760
- *
105761
- * node the node's base for the accelerator that runs the step
105762
- * camera `cameraSettings[cam].stepSettings[step]` — camera-semantic
105763
- * keys only, on every accelerator (never a model-bound field)
105764
- *
105765
- * Below them sits the default: the running model's (D738) or the field's own.
105766
- *
105767
- * Every layer is SPARSE and FIELD-LEVEL. A key present in a layer is an
105768
- * override; an absent key inherits. Two consequences that every editor must
105769
- * respect, and why this module exists:
105770
- *
105771
- * - A model-bound field (D738) occupies TWO keys, its value and
105772
- * `<key>ModelId`. They are written, compared and removed as one unit — a
105773
- * value without its binding over a lower layer's binding would be judged
105774
- * against the wrong model.
105775
- * - An editor writes a DIFF against the merged layers below, never a copy of
105776
- * them. A copy pins every field at the value it had that day, so later
105777
- * edits below never reach it (the full-copy "Override" this replaced).
105778
- */
105779
- /** Top-down order of the stored layers. */
105780
- var LAYER_ORDER = ["camera", "node"];
105781
- /**
105782
- * The keys one field occupies in a bag: its own, plus its binding key when it
105783
- * binds to a model. `boundKeys` = `collectModelBoundKeys(schema)`.
105784
- */
105785
- function fieldUnitKeys(key, boundKeys) {
105786
- return boundKeys.includes(key) ? [key, modelBindingKey(key)] : [key];
105787
- }
105788
- /** The field a stored key belongs to (`minConfidencePersonModelId` → `minConfidencePerson`). */
105789
- function unitOf(storedKey, boundKeys) {
105790
- return boundKeys.find((k) => modelBindingKey(k) === storedKey) ?? storedKey;
105791
- }
105792
- /** Structural equality for stored values (numbers, strings, booleans, arrays, plain objects). */
105793
- function settingValuesEqual(a, b) {
105794
- if (Object.is(a, b)) return true;
105795
- if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => settingValuesEqual(v, b[i]));
105796
- if (isPlainObject(a) && isPlainObject(b)) {
105797
- const ka = Object.keys(a);
105798
- const kb = Object.keys(b);
105799
- return ka.length === kb.length && ka.every((k) => settingValuesEqual(a[k], b[k]));
105800
- }
105801
- return false;
105802
- }
105803
- function isPlainObject(v) {
105804
- return typeof v === "object" && v !== null && !Array.isArray(v);
105805
- }
105806
- /** Merge bags bottom-up (later wins, per key). Fresh object. */
105807
- function mergeSettingsBags(...bags) {
105808
- return bags.reduce((acc, bag) => ({
105809
- ...acc,
105810
- ...bag
105811
- }), {});
105812
- }
105813
- /** The merged value of the layers BELOW `layer` (what removing a key from `layer` falls back to). */
105814
- function settingsBelow(layers, layer) {
105815
- return mergeSettingsBags(...LAYER_ORDER.slice(LAYER_ORDER.indexOf(layer) + 1).toReversed().map((id) => layers[id]));
105816
- }
105817
- /** The effective bag: every stored layer merged. */
105818
- function effectiveStepSettings(layers) {
105819
- return mergeSettingsBags(layers.node, layers.camera);
105820
- }
105821
- /** The highest stored layer that holds any key of `key`'s unit, or `default`. */
105822
- function stepSettingSource(layers, key, boundKeys) {
105823
- const unit = fieldUnitKeys(key, boundKeys);
105824
- for (const id of LAYER_ORDER) {
105825
- const bag = layers[id];
105826
- if (bag !== void 0 && unit.some((k) => bag[k] !== void 0)) return id;
105827
- }
105828
- return "default";
105829
- }
105830
- /**
105831
- * `layer` with `key`'s unit set from `values` (a bag carrying the unit's new
105832
- * value — e.g. the editor's whole next settings bag). A unit key that `values`
105833
- * does not carry is removed from the layer. Fresh object.
105834
- */
105835
- function withFieldFrom(layer, key, values, boundKeys) {
105836
- const out = { ...layer };
105837
- for (const k of fieldUnitKeys(key, boundKeys)) if (values[k] === void 0) delete out[k];
105838
- else out[k] = values[k];
105839
- return out;
105840
- }
105841
- /** `layer` without `key`'s unit — "reset to the next level". Fresh object. */
105842
- function withoutField(layer, key, boundKeys) {
105843
- const out = { ...layer };
105844
- for (const k of fieldUnitKeys(key, boundKeys)) delete out[k];
105845
- return out;
105846
- }
105847
- /** Every field unit stored in `layer`, by its field key. */
105848
- function unitsIn(layer, boundKeys) {
105849
- return [...new Set(Object.keys(layer).map((k) => unitOf(k, boundKeys)))];
105850
- }
105851
- /** True when every key of `key`'s unit has the same value in `layer` as in `below`. */
105852
- function unitEqualsBelow(layer, below, key, boundKeys) {
105853
- return fieldUnitKeys(key, boundKeys).every((k) => settingValuesEqual(layer[k], below[k]));
105854
- }
105855
- /**
105856
- * `layer` reduced to the fields that DIFFER from `below` — what an editor
105857
- * stores. A unit is kept or dropped whole. `undefined` when nothing is left.
105858
- */
105859
- function diffStepSettings(layer, below, boundKeys) {
105860
- if (layer === void 0) return void 0;
105861
- const out = {};
105862
- for (const unit of unitsIn(layer, boundKeys)) {
105863
- if (unitEqualsBelow(layer, below, unit, boundKeys)) continue;
105864
- for (const k of fieldUnitKeys(unit, boundKeys)) if (layer[k] !== void 0) out[k] = layer[k];
105865
- }
105866
- return Object.keys(out).length > 0 ? out : void 0;
105867
- }
105868
- /**
105869
- * The fields `layer` stores at exactly the value of the layers below — a
105870
- * legacy full copy's leftovers. They change nothing today and stop every
105871
- * later edit below from reaching this layer. Sorted.
105872
- */
105873
- function fieldsPinnedAtValueBelow(layer, below, boundKeys) {
105874
- if (layer === void 0) return [];
105875
- return unitsIn(layer, boundKeys).filter((unit) => unitEqualsBelow(layer, below, unit, boundKeys)).toSorted();
105876
- }
105877
- //#endregion
105878
- //#region src/pipeline/auto-tune/hardware-class.ts
105879
- var APPLE_CHIP = /^Apple M(\d+)(?:\s+(Pro|Max|Ultra))?\b/i;
105880
- var INTEL_CORE_ULTRA = /Core\(TM\)\s+Ultra|Core Ultra/i;
105881
- var INTEL_N_SERIES = /Intel\(R\)\s+N\d{2,3}\b|\bIntel\s+N\d{2,3}\b/i;
105882
- /** Phoenix / Hawk Point mobile (7040/8040) and Ryzen 8000G desktop APUs. */
105883
- var AMD_PHOENIX = /Ryzen \d+ (7[0-9]40|8[0-9]40|8[0-9]45)[HSU]|Ryzen \d+ 8[0-9]00G/i;
105884
- function parseAppleChip(cpuModel) {
105885
- const match = APPLE_CHIP.exec(cpuModel.trim());
105886
- if (match === null) return null;
105887
- const generation = Number(match[1]);
105888
- const variantRaw = (match[2] ?? "base").toLowerCase();
105889
- return {
105890
- generation,
105891
- variant: variantRaw === "pro" || variantRaw === "max" || variantRaw === "ultra" ? variantRaw : "base"
105892
- };
105893
- }
105894
- /**
105895
- * Classify a node. Apple base chips (M1–M4) map to the measured M2 column, the
105896
- * Pro/Max/Ultra chips to the measured M3 Pro column — the M2 was measured at
105897
- * 1.0–1.2× the M3 Pro on the Neural Engine, so the generation inside a variant
105898
- * matters less than the column's own error.
105899
- */
105900
- function classifyInferenceHardware(hw) {
105901
- const cpuModel = hw.cpuModel;
105902
- const apple = parseAppleChip(cpuModel);
105903
- if (apple !== null) return {
105904
- hardwareClass: apple.variant === "base" ? "apple-m-base" : "apple-m-pro",
105905
- cpuModel,
105906
- apple
105907
- };
105908
- if (INTEL_CORE_ULTRA.test(cpuModel)) return {
105909
- hardwareClass: "intel-core-ultra",
105910
- cpuModel
105911
- };
105912
- if (INTEL_N_SERIES.test(cpuModel)) return {
105913
- hardwareClass: "intel-n-series",
105914
- cpuModel
105915
- };
105916
- if (AMD_PHOENIX.test(cpuModel)) return {
105917
- hardwareClass: "amd-phoenix",
105918
- cpuModel
105919
- };
105920
- return {
105921
- hardwareClass: "unknown",
105922
- cpuModel
105923
- };
105924
- }
105925
- /**
105926
- * The accelerator kind of one of a node's device keys (`openvino:npu`,
105927
- * `openvino:gpu`, `coreml:all`, `edgetpu:usb`, `cpu`, `onnx:cpu`, …). `null`
105928
- * for a key this table has no column for.
105929
- */
105930
- function acceleratorKindOf(deviceKey, hw) {
105931
- const [backend = "", device = ""] = deviceKey.split(":");
105932
- if (backend === "edgetpu") return "coral";
105933
- if (backend === "coreml") return device === "cpu" ? "arm-cpu" : "apple-ane";
105934
- if (backend === "openvino" && device === "npu") return "intel-npu";
105935
- if (backend === "openvino" && device === "gpu") return "intel-igpu";
105936
- if (backend === "onnx" && device === "webgpu") return "amd-igpu";
105937
- if (backend === "vitisai" && device === "npu") return "amd-npu";
105938
- if (backend === "cpu" || device === "cpu") return hw.arch === "arm64" ? "arm-cpu" : "x86-cpu";
105939
- return null;
105940
- }
105941
- //#endregion
105942
- //#region src/pipeline/auto-tune/detector-performance.ts
105943
- var DETECTOR_PERF = {
105944
- "intel-core-ultra": {
105945
- "intel-npu": {
105946
- "yolov9t-320": {
105947
- p50Ms: 2.9,
105948
- fps: 383,
105949
- measured: true
105950
- },
105951
- "yolov9t-320-int8": {
105952
- p50Ms: 3.8,
105953
- fps: 333,
105954
- measured: true
105955
- },
105956
- "yolov9t-640": {
105957
- p50Ms: 7.4,
105958
- fps: 139,
105959
- measured: true
105960
- },
105961
- "yolov9t-640-int8": {
105962
- p50Ms: 7.5,
105963
- fps: 144,
105964
- measured: true
105965
- },
105966
- "yolov9s-320": {
105967
- p50Ms: 4.4,
105968
- fps: 241,
105969
- measured: true
105970
- },
105971
- "yolov9s-320-int8": {
105972
- p50Ms: 4.1,
105973
- fps: 264,
105974
- measured: true
105975
- },
105976
- "yolov9s-640": {
105977
- p50Ms: 12.7,
105978
- fps: 82,
105979
- measured: true
105980
- },
105981
- "yolov9s-640-int8": {
105982
- p50Ms: 10.7,
105983
- fps: 99,
105984
- measured: true
105985
- },
105986
- "yolov9m-320": {
105987
- p50Ms: 8.4,
105988
- fps: 123,
105989
- measured: true
105990
- },
105991
- "yolov9m-320-int8": {
105992
- p50Ms: 6.5,
105993
- fps: 165,
105994
- measured: true
105995
- },
105996
- "yolov9m-640": {
105997
- p50Ms: 29.5,
105998
- fps: 32,
105999
- measured: true
106000
- },
106001
- "yolov9m-640-int8": {
106002
- p50Ms: 19.5,
106003
- fps: 52,
106004
- measured: true
106005
- }
106006
- },
106007
- "intel-igpu": {
106008
- "yolov9t-320": {
106009
- p50Ms: 2.6,
106010
- fps: 405,
106011
- measured: true
106012
- },
106013
- "yolov9t-320-int8": {
106014
- p50Ms: 2.8,
106015
- fps: 324,
106016
- measured: true
106017
- },
106018
- "yolov9t-640": {
106019
- p50Ms: 6.5,
106020
- fps: 157,
106021
- measured: true
106022
- },
106023
- "yolov9t-640-int8": {
106024
- p50Ms: 5.6,
106025
- fps: 185,
106026
- measured: true
106027
- },
106028
- "yolov9s-320": {
106029
- p50Ms: 4.6,
106030
- fps: 200,
106031
- measured: true
106032
- },
106033
- "yolov9s-320-int8": {
106034
- p50Ms: 4.1,
106035
- fps: 239,
106036
- measured: true
106037
- },
106038
- "yolov9s-640": {
106039
- p50Ms: 15.2,
106040
- fps: 69,
106041
- measured: true
106042
- },
106043
- "yolov9s-640-int8": {
106044
- p50Ms: 10.5,
106045
- fps: 99,
106046
- measured: true
106047
- },
106048
- "yolov9m-320": {
106049
- p50Ms: 11.4,
106050
- fps: 88,
106051
- measured: true
106052
- },
106053
- "yolov9m-320-int8": {
106054
- p50Ms: 7,
106055
- fps: 127,
106056
- measured: true
106057
- },
106058
- "yolov9m-640": {
106059
- p50Ms: 40,
106060
- fps: 25,
106061
- measured: true
106062
- },
106063
- "yolov9m-640-int8": {
106064
- p50Ms: 23.5,
106065
- fps: 44,
106066
- measured: true
106067
- }
106068
- },
106069
- "x86-cpu": {
106070
- "yolov9t-320": {
106071
- p50Ms: 6.9,
106072
- fps: 145,
106073
- measured: true
106074
- },
106075
- "yolov9t-320-int8": {
106076
- p50Ms: 2.7,
106077
- fps: 405,
106078
- measured: true
106079
- },
106080
- "yolov9t-640": {
106081
- p50Ms: 23,
106082
- fps: 44,
106083
- measured: true
106084
- },
106085
- "yolov9t-640-int8": {
106086
- p50Ms: 8.8,
106087
- fps: 124,
106088
- measured: true
106089
- },
106090
- "yolov9s-320": {
106091
- p50Ms: 15.8,
106092
- fps: 63,
106093
- measured: true
106094
- },
106095
- "yolov9s-320-int8": {
106096
- p50Ms: 6.5,
106097
- fps: 180,
106098
- measured: true
106099
- },
106100
- "yolov9s-640": {
106101
- p50Ms: 58.2,
106102
- fps: 17,
106103
- measured: true
106104
- },
106105
- "yolov9s-640-int8": {
106106
- p50Ms: 20.1,
106107
- fps: 52,
106108
- measured: true
106109
- },
106110
- "yolov9m-320": {
106111
- p50Ms: 40,
106112
- fps: 25,
106113
- measured: true
106114
- },
106115
- "yolov9m-320-int8": {
106116
- p50Ms: 14.2,
106117
- fps: 74,
106118
- measured: true
106119
- },
106120
- "yolov9m-640": {
106121
- p50Ms: 155.8,
106122
- fps: 6,
106123
- measured: true
106124
- },
106125
- "yolov9m-640-int8": {
106126
- p50Ms: 52.4,
106127
- fps: 19,
106128
- measured: true
106129
- }
106130
- },
106131
- coral: { "yolov9s-320-edgetpu": {
106132
- p50Ms: 32,
106133
- fps: 31,
106134
- measured: false
106135
- } }
106136
- },
106137
- "intel-n-series": {
106138
- "intel-igpu": {
106139
- "yolov9t-320": {
106140
- p50Ms: 15,
106141
- fps: 48,
106142
- measured: true
106143
- },
106144
- "yolov9t-320-int8": {
106145
- p50Ms: 14.1,
106146
- fps: 67,
106147
- measured: true
106148
- },
106149
- "yolov9t-640": {
106150
- p50Ms: 38.5,
106151
- fps: 26,
106152
- measured: true
106153
- },
106154
- "yolov9t-640-int8": {
106155
- p50Ms: 33.1,
106156
- fps: 33,
106157
- measured: true
106158
- },
106159
- "yolov9s-320": {
106160
- p50Ms: 41.4,
106161
- fps: 24,
106162
- measured: true
106163
- },
106164
- "yolov9s-320-int8": {
106165
- p50Ms: 23.3,
106166
- fps: 34,
106167
- measured: true
106168
- },
106169
- "yolov9s-640": {
106170
- p50Ms: 127.4,
106171
- fps: 8,
106172
- measured: true
106173
- },
106174
- "yolov9s-640-int8": {
106175
- p50Ms: 92.9,
106176
- fps: 11,
106177
- measured: true
106178
- },
106179
- "yolov9m-320": {
106180
- p50Ms: 63.5,
106181
- fps: 16,
106182
- measured: true
106183
- },
106184
- "yolov9m-320-int8": {
106185
- p50Ms: 39.4,
106186
- fps: 26,
106187
- measured: true
106188
- },
106189
- "yolov9m-640": {
106190
- p50Ms: 215.5,
106191
- fps: 5,
106192
- measured: true
106193
- },
106194
- "yolov9m-640-int8": {
106195
- p50Ms: 139.1,
106196
- fps: 7,
106197
- measured: true
106198
- }
106199
- },
106200
- "x86-cpu": {
106201
- "yolov9t-320": {
106202
- p50Ms: 86.2,
106203
- fps: 12,
106204
- measured: true
106205
- },
106206
- "yolov9t-320-int8": {
106207
- p50Ms: 52.7,
106208
- fps: 20,
106209
- measured: true
106210
- },
106211
- "yolov9t-640": {
106212
- p50Ms: 322.1,
106213
- fps: 3,
106214
- measured: true
106215
- },
106216
- "yolov9t-640-int8": {
106217
- p50Ms: 205.6,
106218
- fps: 5,
106219
- measured: true
106220
- },
106221
- "yolov9s-320": {
106222
- p50Ms: 164.4,
106223
- fps: 6,
106224
- measured: true
106225
- },
106226
- "yolov9s-320-int8": {
106227
- p50Ms: 86.9,
106228
- fps: 12,
106229
- measured: true
106230
- },
106231
- "yolov9s-640": {
106232
- p50Ms: 341.3,
106233
- fps: 3,
106234
- measured: true
106235
- },
106236
- "yolov9s-640-int8": {
106237
- p50Ms: 185.3,
106238
- fps: 5,
106239
- measured: true
106240
- },
106241
- "yolov9m-320": {
106242
- p50Ms: 166.8,
106243
- fps: 6,
106244
- measured: true
106245
- },
106246
- "yolov9m-320-int8": {
106247
- p50Ms: 226.3,
106248
- fps: 4,
106249
- measured: true
106250
- },
106251
- "yolov9m-640": {
106252
- p50Ms: 663.5,
106253
- fps: 2,
106254
- measured: true
106255
- },
106256
- "yolov9m-640-int8": {
106257
- p50Ms: 324.4,
106258
- fps: 3,
106259
- measured: true
106260
- }
106261
- },
106262
- coral: { "yolov9s-320-edgetpu": {
106263
- p50Ms: 32,
106264
- fps: 31,
106265
- measured: false
106266
- } }
106267
- },
106268
- "amd-phoenix": {
106269
- "x86-cpu": {
106270
- "yolov9t-320": {
106271
- p50Ms: 24,
106272
- fps: 42,
106273
- measured: true
106274
- },
106275
- "yolov9t-320-int8": {
106276
- p50Ms: 12,
106277
- fps: 83,
106278
- measured: true
106279
- },
106280
- "yolov9t-640": {
106281
- p50Ms: 50,
106282
- fps: 20,
106283
- measured: true
106284
- },
106285
- "yolov9t-640-int8": {
106286
- p50Ms: 20,
106287
- fps: 50,
106288
- measured: true
106289
- },
106290
- "yolov9s-320": {
106291
- p50Ms: 30,
106292
- fps: 33,
106293
- measured: true
106294
- },
106295
- "yolov9s-320-int8": {
106296
- p50Ms: 15,
106297
- fps: 67,
106298
- measured: true
106299
- },
106300
- "yolov9s-640": {
106301
- p50Ms: 92,
106302
- fps: 11,
106303
- measured: true
106304
- },
106305
- "yolov9s-640-int8": {
106306
- p50Ms: 27,
106307
- fps: 37,
106308
- measured: true
106309
- },
106310
- "yolov9m-320": {
106311
- p50Ms: 67,
106312
- fps: 15,
106313
- measured: true
106314
- },
106315
- "yolov9m-320-int8": {
106316
- p50Ms: 21,
106317
- fps: 48,
106318
- measured: true
106319
- },
106320
- "yolov9m-640": {
106321
- p50Ms: 229,
106322
- fps: 4,
106323
- measured: true
106324
- },
106325
- "yolov9m-640-int8": {
106326
- p50Ms: 48,
106327
- fps: 21,
106328
- measured: true
106329
- }
106330
- },
106331
- "amd-igpu": {
106332
- "yolov9t-320": {
106333
- p50Ms: 40,
106334
- fps: 25,
106335
- measured: true
106336
- },
106337
- "yolov9t-640": {
106338
- p50Ms: 89,
106339
- fps: 11,
106340
- measured: true
106341
- },
106342
- "yolov9s-320": {
106343
- p50Ms: 65,
106344
- fps: 15,
106345
- measured: true
106346
- },
106347
- "yolov9s-640": {
106348
- p50Ms: 125,
106349
- fps: 8,
106350
- measured: true
106351
- },
106352
- "yolov9m-320": {
106353
- p50Ms: 85,
106354
- fps: 12,
106355
- measured: true
106356
- },
106357
- "yolov9m-640": {
106358
- p50Ms: 228,
106359
- fps: 4,
106360
- measured: true
106361
- }
106362
- },
106363
- coral: { "yolov9s-320-edgetpu": {
106364
- p50Ms: 32,
106365
- fps: 31,
106366
- measured: true
106367
- } }
106368
- },
106369
- "apple-m-pro": {
106370
- "apple-ane": {
106371
- "yolov9t-320": {
106372
- p50Ms: 1.1,
106373
- fps: 952,
106374
- measured: true
106375
- },
106376
- "yolov9t-640": {
106377
- p50Ms: 3,
106378
- fps: 333,
106379
- measured: true
106380
- },
106381
- "yolov9s-320": {
106382
- p50Ms: 1.5,
106383
- fps: 667,
106384
- measured: true
106385
- },
106386
- "yolov9s-640": {
106387
- p50Ms: 5.1,
106388
- fps: 196,
106389
- measured: true
106390
- },
106391
- "yolov9m-320": {
106392
- p50Ms: 3.2,
106393
- fps: 312,
106394
- measured: true
106395
- },
106396
- "yolov9m-640": {
106397
- p50Ms: 10.2,
106398
- fps: 98,
106399
- measured: true
106400
- }
106401
- },
106402
- coral: { "yolov9s-320-edgetpu": {
106403
- p50Ms: 32,
106404
- fps: 31,
106405
- measured: false
106406
- } }
106407
- },
106408
- "apple-m-base": {
106409
- "apple-ane": {
106410
- "yolov9t-320": {
106411
- p50Ms: 1.32,
106412
- fps: 758,
106413
- measured: false
106414
- },
106415
- "yolov9t-640": {
106416
- p50Ms: 3.45,
106417
- fps: 290,
106418
- measured: true
106419
- },
106420
- "yolov9s-320": {
106421
- p50Ms: 1.8,
106422
- fps: 556,
106423
- measured: false
106424
- },
106425
- "yolov9s-640": {
106426
- p50Ms: 5.46,
106427
- fps: 183,
106428
- measured: true
106429
- },
106430
- "yolov9m-320": {
106431
- p50Ms: 3.79,
106432
- fps: 264,
106433
- measured: true
106434
- },
106435
- "yolov9m-640": {
106436
- p50Ms: 10.34,
106437
- fps: 97,
106438
- measured: true
106439
- }
106440
- },
106441
- coral: { "yolov9s-320-edgetpu": {
106442
- p50Ms: 32,
106443
- fps: 31,
106444
- measured: false
106445
- } }
106446
- }
106677
+ return models.some((m) => m.legacy !== true && hasBuild(m, format));
106678
+ }
106679
+ //#endregion
106680
+ //#region src/pipeline/removed-model-ids.ts
106681
+ /**
106682
+ * Model ids DELETED from a step's catalog (D703, D710, D719), per step — and
106683
+ * the one question asked of them: is a stored pin to this id a pin to a model
106684
+ * that no longer exists? (D725)
106685
+ *
106686
+ * This is NOT a resolver input. `resolveModelForFormat` still returns an
106687
+ * unknown id unchanged (a custom model), and a bulk pass pinned to one still
106688
+ * refuses rather than substitutes (D54). The ONE reader is the orchestrator's
106689
+ * stored-pin reset (`removed-model-pin-reset.ts`), which clears such a pin from
106690
+ * the store so the step follows its default again — unless a registered custom
106691
+ * model carries the same id.
106692
+ *
106693
+ * `removed-model-ids.spec.ts` (addon-pipeline) pins that every id here is
106694
+ * really absent from its step's catalog. A model deleted from a catalog is
106695
+ * added here in the same change, or its stored pins stay behind as custom ids.
106696
+ */
106697
+ /** `stepId → ids deleted from that step's catalog`. */
106698
+ var REMOVED_MODEL_IDS = {
106699
+ "object-detection": ["efficientdet-lite0-edgetpu", "ssdlite-mobiledet-coco-edgetpu"],
106700
+ "face-detection": [
106701
+ "scrypted-yolov9t-face",
106702
+ "scrfd-2.5g",
106703
+ "ssd-mobilenet-v2-face-edgetpu"
106704
+ ],
106705
+ "face-embedding": ["arcface-r100", "inception-resnet-v1"],
106706
+ "plate-detection": ["yolov8n-plate"],
106707
+ "plate-ocr": ["vgg-english-g2"],
106708
+ "clip-embedding": [
106709
+ "mobileclip-s0",
106710
+ "mobileclip-s1",
106711
+ "mobileclip-s2"
106712
+ ],
106713
+ "package-detection": ["yolov8n-package"],
106714
+ "bird-classifier": ["bird-classifier-aiy-edgetpu", "bird-nabirds-404"],
106715
+ "animal-classifier": ["animals-10"],
106716
+ "vehicle-classifier": ["vehicle-type-efficientnet"]
106447
106717
  };
106448
- var DETECTOR_ACCURACY = {
106449
- "yolov9m-320": {
106450
- personPrecision: .979,
106451
- personRecall: .649
106452
- },
106453
- "yolov9m-320-int8": {
106454
- personPrecision: .974,
106455
- personRecall: .684
106456
- },
106457
- "yolov9m-640": {
106458
- personPrecision: .976,
106459
- personRecall: .854
106460
- },
106461
- "yolov9m-640-int8": {
106462
- personPrecision: .976,
106463
- personRecall: .847
106464
- },
106465
- "yolov9s-320": {
106466
- personPrecision: .958,
106467
- personRecall: .479
106468
- },
106469
- "yolov9s-320-int8": {
106470
- personPrecision: .969,
106471
- personRecall: .438
106472
- },
106473
- "yolov9s-640": {
106474
- personPrecision: .986,
106475
- personRecall: .753
106476
- },
106477
- "yolov9s-640-int8": {
106478
- personPrecision: .963,
106479
- personRecall: .823
106480
- },
106481
- "yolov9t-320": {
106482
- personPrecision: .946,
106483
- personRecall: .368
106484
- },
106485
- "yolov9t-320-int8": {
106486
- personPrecision: .943,
106487
- personRecall: .347
106488
- },
106489
- "yolov9t-640": {
106490
- personPrecision: .979,
106491
- personRecall: .646
106492
- },
106493
- "yolov9t-640-int8": {
106494
- personPrecision: .979,
106495
- personRecall: .646
106718
+ /** True when `modelId` was deleted from `stepId`'s catalog. */
106719
+ function isRemovedModelId(stepId, modelId) {
106720
+ return REMOVED_MODEL_IDS[stepId]?.includes(modelId) ?? false;
106721
+ }
106722
+ //#endregion
106723
+ //#region src/pipeline/model-bound-settings.ts
106724
+ /** Suffix of the sibling key that names the model a stored value was made for. */
106725
+ var MODEL_BINDING_SUFFIX = "ModelId";
106726
+ /** `minConfidencePerson` → `minConfidencePersonModelId`. */
106727
+ function modelBindingKey(key) {
106728
+ return `${key}${MODEL_BINDING_SUFFIX}`;
106729
+ }
106730
+ /** The model a stored value was made for, or `undefined` when unbound. */
106731
+ function storedBinding(settings, key) {
106732
+ const binding = settings?.[modelBindingKey(key)];
106733
+ return typeof binding === "string" && binding.length > 0 ? binding : void 0;
106734
+ }
106735
+ /**
106736
+ * The effective value of `key` for the model that runs (`runningModelId`,
106737
+ * `undefined` when unknown — it then matches no binding).
106738
+ */
106739
+ function resolveModelBoundSetting(settings, key, runningModelId, modelDefault, globalDefault) {
106740
+ const stored = settings?.[key];
106741
+ const binding = storedBinding(settings, key);
106742
+ if (stored !== void 0 && binding === void 0) return {
106743
+ value: stored,
106744
+ source: "override"
106745
+ };
106746
+ if (stored !== void 0 && binding !== void 0 && binding === runningModelId) return {
106747
+ value: stored,
106748
+ source: "override",
106749
+ boundModelId: binding
106750
+ };
106751
+ const inapplicable = stored !== void 0 && binding !== void 0 ? { inapplicableOverride: {
106752
+ value: stored,
106753
+ modelId: binding
106754
+ } } : {};
106755
+ if (modelDefault !== void 0) return {
106756
+ value: modelDefault,
106757
+ source: "model-default",
106758
+ ...inapplicable
106759
+ };
106760
+ return {
106761
+ value: globalDefault,
106762
+ source: "global-default",
106763
+ ...inapplicable
106764
+ };
106765
+ }
106766
+ /**
106767
+ * The OPERATOR layer of a step's settings as the running model sees it: every
106768
+ * model-bound value made for another model is removed (so the default
106769
+ * underneath — the running model's — wins the merge), and every binding key is
106770
+ * stripped. Unbound values are kept. Returns a fresh object; `undefined` stays
106771
+ * `undefined`.
106772
+ */
106773
+ function applyModelBoundSettings(settings, boundKeys, runningModelId) {
106774
+ if (settings === void 0) return void 0;
106775
+ const out = { ...settings };
106776
+ for (const key of boundKeys) {
106777
+ const binding = storedBinding(settings, key);
106778
+ if (binding !== void 0 && binding !== runningModelId) delete out[key];
106779
+ delete out[modelBindingKey(key)];
106496
106780
  }
106497
- };
106781
+ return out;
106782
+ }
106783
+ /** Store `value` for `key`, made for `modelId`. Returns a fresh object. */
106784
+ function writeModelBoundSetting(settings, key, value, modelId) {
106785
+ return {
106786
+ ...settings,
106787
+ [key]: value,
106788
+ [modelBindingKey(key)]: modelId
106789
+ };
106790
+ }
106791
+ /** Remove an override and its binding — "reset to the model default". */
106792
+ function clearModelBoundSetting(settings, key) {
106793
+ const out = { ...settings };
106794
+ delete out[key];
106795
+ delete out[modelBindingKey(key)];
106796
+ return out;
106797
+ }
106798
+ /** True when any of `boundKeys` has an override IN FORCE for `modelId`. */
106799
+ function hasModelBoundOverrides(settings, boundKeys, modelId) {
106800
+ return boundKeys.some((key) => resolveModelBoundSetting(settings, key, modelId, void 0, void 0).source === "override");
106801
+ }
106802
+ /**
106803
+ * "Reset thresholds to model defaults": remove every override of `boundKeys`
106804
+ * that is in force for `modelId` (bound to it, or unbound), with its binding —
106805
+ * one result, so the editor writes it in one save. An override stored for
106806
+ * ANOTHER model is kept: it is not in force here, and it applies again when
106807
+ * that model runs.
106808
+ */
106809
+ function clearModelBoundOverrides(settings, boundKeys, modelId) {
106810
+ return boundKeys.reduce((acc, key) => resolveModelBoundSetting(acc, key, modelId, void 0, void 0).source === "override" ? clearModelBoundSetting(acc, key) : acc, { ...settings });
106811
+ }
106812
+ /**
106813
+ * The keys of every `bindsToModel` field of a step's config schema, groups
106814
+ * flattened (D738). One definition for the runner (which applies the binding)
106815
+ * and the orchestrator's Auto tune (which clears those overrides, D745).
106816
+ */
106817
+ function collectModelBoundKeys(fields) {
106818
+ return fields.flatMap((field) => {
106819
+ if (field.type === "group") return collectModelBoundKeys(field.fields);
106820
+ return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
106821
+ });
106822
+ }
106823
+ //#endregion
106824
+ //#region src/pipeline/step-settings-layers.ts
106825
+ /**
106826
+ * The layers of a detection step's settings, and the field-level edits on them
106827
+ * (D754, D927).
106828
+ *
106829
+ * A step's settings reach a session through two stored layers, merged key by
106830
+ * key — a key in the higher layer wins:
106831
+ *
106832
+ * node the node's base for the accelerator that runs the step
106833
+ * camera `cameraSettings[cam].stepSettings[step]` — camera-semantic
106834
+ * keys only, on every accelerator (never a model-bound field)
106835
+ *
106836
+ * Below them sits the default: the running model's (D738) or the field's own.
106837
+ *
106838
+ * Every layer is SPARSE and FIELD-LEVEL. A key present in a layer is an
106839
+ * override; an absent key inherits. Two consequences that every editor must
106840
+ * respect, and why this module exists:
106841
+ *
106842
+ * - A model-bound field (D738) occupies TWO keys, its value and
106843
+ * `<key>ModelId`. They are written, compared and removed as one unit — a
106844
+ * value without its binding over a lower layer's binding would be judged
106845
+ * against the wrong model.
106846
+ * - An editor writes a DIFF against the merged layers below, never a copy of
106847
+ * them. A copy pins every field at the value it had that day, so later
106848
+ * edits below never reach it (the full-copy "Override" this replaced).
106849
+ */
106850
+ /** Top-down order of the stored layers. */
106851
+ var LAYER_ORDER = ["camera", "node"];
106852
+ /**
106853
+ * The keys one field occupies in a bag: its own, plus its binding key when it
106854
+ * binds to a model. `boundKeys` = `collectModelBoundKeys(schema)`.
106855
+ */
106856
+ function fieldUnitKeys(key, boundKeys) {
106857
+ return boundKeys.includes(key) ? [key, modelBindingKey(key)] : [key];
106858
+ }
106859
+ /** The field a stored key belongs to (`minConfidencePersonModelId` → `minConfidencePerson`). */
106860
+ function unitOf(storedKey, boundKeys) {
106861
+ return boundKeys.find((k) => modelBindingKey(k) === storedKey) ?? storedKey;
106862
+ }
106863
+ /** Structural equality for stored values (numbers, strings, booleans, arrays, plain objects). */
106864
+ function settingValuesEqual(a, b) {
106865
+ if (Object.is(a, b)) return true;
106866
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => settingValuesEqual(v, b[i]));
106867
+ if (isPlainObject(a) && isPlainObject(b)) {
106868
+ const ka = Object.keys(a);
106869
+ const kb = Object.keys(b);
106870
+ return ka.length === kb.length && ka.every((k) => settingValuesEqual(a[k], b[k]));
106871
+ }
106872
+ return false;
106873
+ }
106874
+ function isPlainObject(v) {
106875
+ return typeof v === "object" && v !== null && !Array.isArray(v);
106876
+ }
106877
+ /** Merge bags bottom-up (later wins, per key). Fresh object. */
106878
+ function mergeSettingsBags(...bags) {
106879
+ return bags.reduce((acc, bag) => ({
106880
+ ...acc,
106881
+ ...bag
106882
+ }), {});
106883
+ }
106884
+ /** The merged value of the layers BELOW `layer` (what removing a key from `layer` falls back to). */
106885
+ function settingsBelow(layers, layer) {
106886
+ return mergeSettingsBags(...LAYER_ORDER.slice(LAYER_ORDER.indexOf(layer) + 1).toReversed().map((id) => layers[id]));
106887
+ }
106888
+ /** The effective bag: every stored layer merged. */
106889
+ function effectiveStepSettings(layers) {
106890
+ return mergeSettingsBags(layers.node, layers.camera);
106891
+ }
106892
+ /** The highest stored layer that holds any key of `key`'s unit, or `default`. */
106893
+ function stepSettingSource(layers, key, boundKeys) {
106894
+ const unit = fieldUnitKeys(key, boundKeys);
106895
+ for (const id of LAYER_ORDER) {
106896
+ const bag = layers[id];
106897
+ if (bag !== void 0 && unit.some((k) => bag[k] !== void 0)) return id;
106898
+ }
106899
+ return "default";
106900
+ }
106901
+ /**
106902
+ * `layer` with `key`'s unit set from `values` (a bag carrying the unit's new
106903
+ * value — e.g. the editor's whole next settings bag). A unit key that `values`
106904
+ * does not carry is removed from the layer. Fresh object.
106905
+ */
106906
+ function withFieldFrom(layer, key, values, boundKeys) {
106907
+ const out = { ...layer };
106908
+ for (const k of fieldUnitKeys(key, boundKeys)) if (values[k] === void 0) delete out[k];
106909
+ else out[k] = values[k];
106910
+ return out;
106911
+ }
106912
+ /** `layer` without `key`'s unit — "reset to the next level". Fresh object. */
106913
+ function withoutField(layer, key, boundKeys) {
106914
+ const out = { ...layer };
106915
+ for (const k of fieldUnitKeys(key, boundKeys)) delete out[k];
106916
+ return out;
106917
+ }
106918
+ /** Every field unit stored in `layer`, by its field key. */
106919
+ function unitsIn(layer, boundKeys) {
106920
+ return [...new Set(Object.keys(layer).map((k) => unitOf(k, boundKeys)))];
106921
+ }
106922
+ /** True when every key of `key`'s unit has the same value in `layer` as in `below`. */
106923
+ function unitEqualsBelow(layer, below, key, boundKeys) {
106924
+ return fieldUnitKeys(key, boundKeys).every((k) => settingValuesEqual(layer[k], below[k]));
106925
+ }
106926
+ /**
106927
+ * `layer` reduced to the fields that DIFFER from `below` — what an editor
106928
+ * stores. A unit is kept or dropped whole. `undefined` when nothing is left.
106929
+ */
106930
+ function diffStepSettings(layer, below, boundKeys) {
106931
+ if (layer === void 0) return void 0;
106932
+ const out = {};
106933
+ for (const unit of unitsIn(layer, boundKeys)) {
106934
+ if (unitEqualsBelow(layer, below, unit, boundKeys)) continue;
106935
+ for (const k of fieldUnitKeys(unit, boundKeys)) if (layer[k] !== void 0) out[k] = layer[k];
106936
+ }
106937
+ return Object.keys(out).length > 0 ? out : void 0;
106938
+ }
106939
+ /**
106940
+ * The fields `layer` stores at exactly the value of the layers below — a
106941
+ * legacy full copy's leftovers. They change nothing today and stop every
106942
+ * later edit below from reaching this layer. Sorted.
106943
+ */
106944
+ function fieldsPinnedAtValueBelow(layer, below, boundKeys) {
106945
+ if (layer === void 0) return [];
106946
+ return unitsIn(layer, boundKeys).filter((unit) => unitEqualsBelow(layer, below, unit, boundKeys)).toSorted();
106947
+ }
106948
+ //#endregion
106949
+ //#region src/pipeline/auto-tune/hardware-class.ts
106950
+ var APPLE_CHIP = /^Apple M(\d+)(?:\s+(Pro|Max|Ultra))?\b/i;
106951
+ var INTEL_CORE_ULTRA = /Core\(TM\)\s+Ultra|Core Ultra/i;
106952
+ var INTEL_N_SERIES = /Intel\(R\)\s+N\d{2,3}\b|\bIntel\s+N\d{2,3}\b/i;
106953
+ /** Phoenix / Hawk Point mobile (7040/8040) and Ryzen 8000G desktop APUs. */
106954
+ var AMD_PHOENIX = /Ryzen \d+ (7[0-9]40|8[0-9]40|8[0-9]45)[HSU]|Ryzen \d+ 8[0-9]00G/i;
106955
+ function parseAppleChip(cpuModel) {
106956
+ const match = APPLE_CHIP.exec(cpuModel.trim());
106957
+ if (match === null) return null;
106958
+ const generation = Number(match[1]);
106959
+ const variantRaw = (match[2] ?? "base").toLowerCase();
106960
+ return {
106961
+ generation,
106962
+ variant: variantRaw === "pro" || variantRaw === "max" || variantRaw === "ultra" ? variantRaw : "base"
106963
+ };
106964
+ }
106965
+ /**
106966
+ * Classify a node. Apple base chips (M1–M4) map to the measured M2 column, the
106967
+ * Pro/Max/Ultra chips to the measured M3 Pro column — the M2 was measured at
106968
+ * 1.0–1.2× the M3 Pro on the Neural Engine, so the generation inside a variant
106969
+ * matters less than the column's own error.
106970
+ */
106971
+ function classifyInferenceHardware(hw) {
106972
+ const cpuModel = hw.cpuModel;
106973
+ const apple = parseAppleChip(cpuModel);
106974
+ if (apple !== null) return {
106975
+ hardwareClass: apple.variant === "base" ? "apple-m-base" : "apple-m-pro",
106976
+ cpuModel,
106977
+ apple
106978
+ };
106979
+ if (INTEL_CORE_ULTRA.test(cpuModel)) return {
106980
+ hardwareClass: "intel-core-ultra",
106981
+ cpuModel
106982
+ };
106983
+ if (INTEL_N_SERIES.test(cpuModel)) return {
106984
+ hardwareClass: "intel-n-series",
106985
+ cpuModel
106986
+ };
106987
+ if (AMD_PHOENIX.test(cpuModel)) return {
106988
+ hardwareClass: "amd-phoenix",
106989
+ cpuModel
106990
+ };
106991
+ return {
106992
+ hardwareClass: "unknown",
106993
+ cpuModel
106994
+ };
106995
+ }
106996
+ /**
106997
+ * The accelerator kind of one of a node's device keys (`openvino:npu`,
106998
+ * `openvino:gpu`, `coreml:all`, `edgetpu:usb`, `cpu`, `onnx:cpu`, …). `null`
106999
+ * for a key this table has no column for.
107000
+ */
107001
+ function acceleratorKindOf(deviceKey, hw) {
107002
+ const [backend = "", device = ""] = deviceKey.split(":");
107003
+ if (backend === "edgetpu") return "coral";
107004
+ if (backend === "coreml") return device === "cpu" ? "arm-cpu" : "apple-ane";
107005
+ if (backend === "openvino" && device === "npu") return "intel-npu";
107006
+ if (backend === "openvino" && device === "gpu") return "intel-igpu";
107007
+ if (backend === "onnx" && device === "webgpu") return "amd-igpu";
107008
+ if (backend === "vitisai" && device === "npu") return "amd-npu";
107009
+ if (backend === "cpu" || device === "cpu") return hw.arch === "arm64" ? "arm-cpu" : "x86-cpu";
107010
+ return null;
107011
+ }
106498
107012
  //#endregion
106499
107013
  //#region src/pipeline/auto-tune/recommend.ts
106500
107014
  /** The family the cluster runs. */
@@ -106510,10 +107024,35 @@ var RECALL_TIE = .02;
106510
107024
  var DEFAULT_HEADROOM = .4;
106511
107025
  /** Detection rate a session is counted at when no camera says otherwise. */
106512
107026
  var DEFAULT_CAMERA_FPS = 10;
106513
- /** Parallel sessions one device sustains at `fps` with `headroom` kept free. */
106514
- function capacitySessionsOf(fps, headroom, cameraFps) {
106515
- if (!(fps > 0) || !(cameraFps > 0)) return 0;
106516
- return Math.max(0, Math.floor(fps * (1 - headroom) / cameraFps + 1e-9));
107027
+ /**
107028
+ * Camera rate at which serial `1000/p50` matched the cameras that held
107029
+ * (N100 iGPU, 2026-10-24). Below this the second InferRequest's overlap
107030
+ * becomes extra sessions; at and above it the serial ceiling is the count.
107031
+ */
107032
+ var SERIAL_CAPACITY_CAMERA_FPS = 10;
107033
+ /**
107034
+ * Aggregate throughput ÷ serial throughput when the frame interval is wide
107035
+ * enough to hide the second request. intel-igpu is the N100 measurement
107036
+ * (~1.4× under LATENCY with 2 InferRequests). Every other kind stays 1
107037
+ * until it is measured the same way.
107038
+ */
107039
+ var AGGREGATE_THROUGHPUT_BY_KIND = { "intel-igpu": 1.4 };
107040
+ /** The overlap factor for this kind at this camera rate. 1 at {@link SERIAL_CAPACITY_CAMERA_FPS} and above. */
107041
+ function aggregateThroughputFactor(kind, cameraFps) {
107042
+ if (!(cameraFps > 0) || cameraFps >= 10) return 1;
107043
+ return AGGREGATE_THROUGHPUT_BY_KIND[kind] ?? 1;
107044
+ }
107045
+ /**
107046
+ * Parallel sessions one device sustains at `fps` with `headroom` kept free.
107047
+ * `aggregateFactor` is the kind's overlap (1 unless the caller measured
107048
+ * extra throughput at this camera rate).
107049
+ */
107050
+ function capacitySessionsOf(fps, headroom, cameraFps, aggregateFactor = 1) {
107051
+ if (!(fps > 0) || !(cameraFps > 0) || !(aggregateFactor > 0)) return 0;
107052
+ return Math.max(0, Math.floor(fps * aggregateFactor * (1 - headroom) / cameraFps + 1e-9));
107053
+ }
107054
+ function reportFor(hardware, deviceKey) {
107055
+ return hardware.accelerators?.find((report) => report.deviceKey === deviceKey) ?? null;
106517
107056
  }
106518
107057
  /** The table column with the node's own measurements laid over it. */
106519
107058
  function overlayMeasured(table, measured) {
@@ -106535,11 +107074,12 @@ function round(value, digits = 3) {
106535
107074
  const f = 10 ** digits;
106536
107075
  return Math.round(value * f) / f;
106537
107076
  }
106538
- function sessionsOn(cell, sizing) {
106539
- return cell === void 0 ? 0 : capacitySessionsOf(cell.fps, sizing.headroom, sizing.cameraFps);
107077
+ function sessionsOn(cell, sizing, kind) {
107078
+ if (cell === void 0) return 0;
107079
+ return capacitySessionsOf(cell.fps, sizing.headroom, sizing.cameraFps, aggregateThroughputFactor(kind, sizing.cameraFps));
106540
107080
  }
106541
- function fitsOn(cell, sizing) {
106542
- return sessionsOn(cell, sizing) >= sizing.minSessions;
107081
+ function fitsOn(ctx, modelId, sizing) {
107082
+ return sessionsOn(ctx.column[modelId], sizing, ctx.accelerator) >= sizing.minSessions;
106543
107083
  }
106544
107084
  function candidatesFor(contexts, formatsByModel) {
106545
107085
  return Object.keys(DETECTOR_ACCURACY).filter((id) => id.startsWith(AUTO_TUNE_FAMILY)).filter((id) => contexts.every((c) => c.column[id] !== void 0 && (formatsByModel[id] ?? []).includes(c.device.format))).map((id) => ({
@@ -106552,13 +107092,13 @@ function totalLatency(contexts, modelId) {
106552
107092
  }
106553
107093
  /** The fewest sessions any device of the group sustains with this model. */
106554
107094
  function worstSessions(contexts, modelId, sizing) {
106555
- return contexts.reduce((worst, c) => Math.min(worst, sessionsOn(c.column[modelId], sizing)), Number.POSITIVE_INFINITY);
107095
+ return contexts.reduce((worst, c) => Math.min(worst, sessionsOn(c.column[modelId], sizing, c.accelerator)), Number.POSITIVE_INFINITY);
106556
107096
  }
106557
107097
  /** One model for a group of devices that must agree (step 2–4 of the header). */
106558
107098
  function pickForGroup(contexts, formatsByModel, sizing) {
106559
107099
  const candidates = candidatesFor(contexts, formatsByModel);
106560
107100
  if (candidates.length === 0) return null;
106561
- const fitting = candidates.filter((c) => contexts.every((ctx) => fitsOn(ctx.column[c.modelId], sizing)));
107101
+ const fitting = candidates.filter((c) => contexts.every((ctx) => fitsOn(ctx, c.modelId, sizing)));
106562
107102
  if (fitting.length === 0) {
106563
107103
  const lightest = candidates.toSorted((a, b) => worstSessions(contexts, b.modelId, sizing) - worstSessions(contexts, a.modelId, sizing) || totalLatency(contexts, a.modelId) - totalLatency(contexts, b.modelId))[0];
106564
107104
  if (lightest === void 0) return null;
@@ -106577,7 +107117,7 @@ function pickForGroup(contexts, formatsByModel, sizing) {
106577
107117
  const reasons = [];
106578
107118
  const mostAccurate = candidates[0];
106579
107119
  if (mostAccurate !== void 0 && mostAccurate.modelId !== best.modelId) {
106580
- const limitedBy = contexts.find((ctx) => !fitsOn(ctx.column[mostAccurate.modelId], sizing))?.device.key ?? "";
107120
+ const limitedBy = contexts.find((ctx) => !fitsOn(ctx, mostAccurate.modelId, sizing))?.device.key ?? "";
106581
107121
  reasons.push({
106582
107122
  kind: "stepped-down",
106583
107123
  from: mostAccurate.modelId,
@@ -106598,13 +107138,19 @@ function pickForGroup(contexts, formatsByModel, sizing) {
106598
107138
  function choiceOn(ctx, modelId, sizing) {
106599
107139
  const cell = ctx.column[modelId];
106600
107140
  if (cell === void 0) return null;
107141
+ const estimatedFrom = cell.source?.kind === "node-benchmark" ? void 0 : cell.estimatedFrom;
107142
+ const source = cell.source ?? (estimatedFrom !== void 0 ? {
107143
+ kind: "estimated",
107144
+ estimatedFrom
107145
+ } : { kind: "table" });
106601
107146
  return {
106602
107147
  modelId,
106603
107148
  p50Ms: cell.p50Ms,
106604
107149
  fps: cell.fps,
106605
107150
  measured: cell.measured,
106606
- source: cell.source ?? { kind: "table" },
106607
- capacitySessions: sessionsOn(cell, sizing),
107151
+ source,
107152
+ ...estimatedFrom !== void 0 ? { estimatedFrom } : {},
107153
+ capacitySessions: sessionsOn(cell, sizing, ctx.accelerator),
106608
107154
  personRecall: DETECTOR_ACCURACY[modelId]?.personRecall ?? 0
106609
107155
  };
106610
107156
  }
@@ -106655,7 +107201,11 @@ function recommendNode(input) {
106655
107201
  const measured = input.measured ?? {};
106656
107202
  const hasMeasured = Object.values(measured).some((m) => Object.keys(m).length > 0);
106657
107203
  const knownTable = hardwareClass === "unknown" ? void 0 : perf[hardwareClass];
106658
- if (knownTable === void 0 && !hasMeasured) return skippedNode(node, {
107204
+ const canEstimate = (hardware.accelerators ?? []).some((report) => {
107205
+ const kind = acceleratorKindOf(report.deviceKey, hardware);
107206
+ return kind !== null && reportIsScalable(report, kind);
107207
+ });
107208
+ if (knownTable === void 0 && !hasMeasured && !canEstimate) return skippedNode(node, {
106659
107209
  kind: "no-measured-data",
106660
107210
  hardwareClass
106661
107211
  }, hardwareClass);
@@ -106691,7 +107241,12 @@ function recommendNode(input) {
106691
107241
  });
106692
107242
  continue;
106693
107243
  }
106694
- const column = accelerator === null ? void 0 : overlayMeasured(classTable[accelerator], measured[device.key]);
107244
+ const column = accelerator === null ? void 0 : overlayMeasured(resolveLatencyColumn({
107245
+ hardwareClass,
107246
+ accelerator,
107247
+ report: reportFor(hardware, device.key),
107248
+ perf
107249
+ })?.column, measured[device.key]);
106695
107250
  if (accelerator === null || column === void 0) {
106696
107251
  early.set(device.key, [{
106697
107252
  kind: "no-family-build",
@@ -107439,6 +107994,507 @@ function buildBenchStepTree(schema, rootModelId, enabledStepIds) {
107439
107994
  }];
107440
107995
  }
107441
107996
  //#endregion
107997
+ //#region src/pipeline/second-pass/small-subject-recovery.ts
107998
+ /** The object-detection step id — the first-level detector both recipes run. */
107999
+ var OBJECT_DETECTION_STEP_ID = "object-detection";
108000
+ /**
108001
+ * Smallest crop side, native pixels. Below this the model no longer
108002
+ * magnifies enough to matter on a 4K frame, and on a 1080p frame 640 is
108003
+ * already a third of the picture. Measured: 480–800 px all recover the dog.
108004
+ */
108005
+ var RECOVERY_CROP_MIN_SIDE_PX = 640;
108006
+ /**
108007
+ * Largest crop side. A 320 model on a 1024 px square is a 3.2× reduction and
108008
+ * scored the dog at 0.52–0.76; at 1280 px it was absent again. A region wider
108009
+ * than this is tiled, never zoomed out.
108010
+ */
108011
+ var RECOVERY_CROP_MAX_SIDE_PX = 800;
108012
+ /** Context around the region before squaring, as a fraction of its larger side. */
108013
+ var RECOVERY_CROP_PADDING_RATIO = .25;
108014
+ /**
108015
+ * The view the crop is resolved at, compressed. Never larger than the crop
108016
+ * (the floor is {@link RECOVERY_CROP_MIN_SIDE_PX}), so it never upscales, and
108017
+ * the model letterboxes it to its own input from here as it does the frame.
108018
+ */
108019
+ var RECOVERY_VIEW_PX = 640;
108020
+ /**
108021
+ * Crops per frame. Each is one more inference on the same pool (15–25 ms on
108022
+ * CoreML for a 640 view); two cover a person-and-dog blob and a second mover.
108023
+ */
108024
+ var RECOVERY_MAX_CROPS_PER_FRAME = 2;
108025
+ /**
108026
+ * The pass runs at most this often per camera — at 10 fps that is one frame
108027
+ * in five. A subject the full pass misses is missed for seconds, not for one
108028
+ * frame, so the recovery does not need every frame to find it.
108029
+ *
108030
+ * The floor under this number is the tracker: a track coasts for
108031
+ * `maxMissedMs` (4 000 ms by default) before it is dropped, so 2 recoveries a
108032
+ * second keep a subject the full-frame pass has lost ALIVE with 8× margin,
108033
+ * and reach `minHits` (3) in 1.5 s. Raising it past ~1 300 ms would start to
108034
+ * lose tracks between passes.
108035
+ */
108036
+ var RECOVERY_MIN_INTERVAL_MS = 500;
108037
+ /**
108038
+ * A region larger than this share of the frame is not a small subject: a
108039
+ * lighting change, a car filling the picture, a person at the lens. The
108040
+ * full-frame pass already sees those at scale.
108041
+ */
108042
+ var RECOVERY_MAX_REGION_FRAME_FRACTION = .25;
108043
+ /**
108044
+ * How much of a region must lie OUTSIDE every full-frame detection for the
108045
+ * pass to run on it. A person's own blob (dilated, with shadow) is covered
108046
+ * 70–90% by the person box; a person-and-dog blob is covered ~55% by it.
108047
+ */
108048
+ var RECOVERY_MIN_UNCOVERED_FRACTION = .35;
108049
+ /** Motion regions older than this describe a different frame. */
108050
+ var RECOVERY_MOTION_MAX_AGE_MS = 2e3;
108051
+ /** A recovered box overlapping a same-class full-frame box this much is the same subject. */
108052
+ var RECOVERY_FUSE_IOU = .5;
108053
+ /**
108054
+ * A recovered box CONTAINED this much in a same-class full-frame box is a part
108055
+ * of it — a torso the crop cut out of a person the full pass already has.
108056
+ */
108057
+ var RECOVERY_FUSE_CONTAINMENT = .7;
108058
+ /** A box within this many crop-view pixels of the crop edge was clipped by it. */
108059
+ var RECOVERY_EDGE_MARGIN_PX = 2;
108060
+ /** Area of a normalised rectangle, never negative. Shared with the D396 recipe. */
108061
+ function normalizedArea(r) {
108062
+ return Math.max(0, r.w) * Math.max(0, r.h);
108063
+ }
108064
+ var area = normalizedArea;
108065
+ function intersection(a, b) {
108066
+ return Math.max(0, Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y));
108067
+ }
108068
+ /**
108069
+ * Share of `region` not covered by any detection box. The sum of pairwise
108070
+ * intersections over-counts where detections overlap each other, which only
108071
+ * makes the pass MORE conservative; clipped at 1.
108072
+ */
108073
+ function uncoveredFraction(region, detections) {
108074
+ const regionArea = area(region);
108075
+ if (regionArea <= 0) return 0;
108076
+ let covered = 0;
108077
+ for (const detection of detections) covered += intersection(region, detection);
108078
+ return 1 - Math.min(1, covered / regionArea);
108079
+ }
108080
+ /**
108081
+ * THE square-placement rule of this pass — one owner, shared by both recipes.
108082
+ *
108083
+ * A crop is a square of `side` native pixels centred where it can be and slid
108084
+ * inside the frame where it cannot. Both region rules place their squares
108085
+ * through this function so a crop means the same rectangle whichever rule
108086
+ * chose the region.
108087
+ */
108088
+ function squareAround(centreX, centreY, side, native) {
108089
+ return {
108090
+ left: Math.round(Math.max(0, Math.min(native.width - side, centreX - side / 2))),
108091
+ top: Math.round(Math.max(0, Math.min(native.height - side, centreY - side / 2))),
108092
+ side
108093
+ };
108094
+ }
108095
+ function contains(crop, x, y) {
108096
+ return x >= crop.left && x <= crop.left + crop.side && y >= crop.top && y <= crop.top + crop.side;
108097
+ }
108098
+ /**
108099
+ * Squares for one region: one when it fits the largest side, otherwise up to
108100
+ * two tiles along its longer axis (a person-sized blob with something small
108101
+ * at either end). The padded region decides the side, never the frame.
108102
+ */
108103
+ function cropsForRegion(region, native) {
108104
+ const widthPx = region.w * native.width;
108105
+ const heightPx = region.h * native.height;
108106
+ const longest = Math.max(widthPx, heightPx);
108107
+ const padded = longest * 1.5;
108108
+ const side = Math.round(Math.min(Math.max(padded, 640), 800));
108109
+ const centreX = (region.x + region.w / 2) * native.width;
108110
+ const centreY = (region.y + region.h / 2) * native.height;
108111
+ if (padded <= 800) return [squareAround(centreX, centreY, side, native)];
108112
+ const alongX = widthPx >= heightPx;
108113
+ const quarter = longest / 4;
108114
+ return [squareAround(alongX ? centreX - quarter : centreX, alongX ? centreY : centreY - quarter, side, native), squareAround(alongX ? centreX + quarter : centreX, alongX ? centreY : centreY + quarter, side, native)];
108115
+ }
108116
+ /**
108117
+ * The ONE place the two region gates are decided. `planRecoveryCrops` filters
108118
+ * on it and {@link countRecoverySkips} counts it, so the report can never
108119
+ * describe a gate the planner does not actually apply.
108120
+ */
108121
+ function classifyRegion(region, uncovered) {
108122
+ if (area(region) > .25) return "too-large";
108123
+ if (uncovered < .35) return "covered";
108124
+ return "eligible";
108125
+ }
108126
+ /**
108127
+ * WHY A FRAME PLANNED NO CROP, AS A NUMBER (D378).
108128
+ *
108129
+ * The pass report counts the passes that RAN, so "the second pass stops running
108130
+ * when a person is beside the dog" — a plausible reading of
108131
+ * {@link RECOVERY_MAX_REGION_FRAME_FRACTION} and
108132
+ * {@link RECOVERY_MIN_UNCOVERED_FRACTION}, and a live hypothesis on camera 615
108133
+ * — could not be confirmed or killed from the logs at all. This classifies the
108134
+ * regions a frame offered, against the same predicate the planner filters on.
108135
+ */
108136
+ function countRecoverySkips(input) {
108137
+ let tooLarge = 0;
108138
+ let covered = 0;
108139
+ let eligible = 0;
108140
+ for (const region of input.regions) switch (classifyRegion(region, uncoveredFraction(region, input.detections))) {
108141
+ case "too-large":
108142
+ tooLarge += 1;
108143
+ break;
108144
+ case "covered":
108145
+ covered += 1;
108146
+ break;
108147
+ case "eligible":
108148
+ eligible += 1;
108149
+ break;
108150
+ }
108151
+ return {
108152
+ tooLarge,
108153
+ covered,
108154
+ eligible
108155
+ };
108156
+ }
108157
+ function planRecoveryCrops(input) {
108158
+ const { native } = input;
108159
+ if (Math.min(native.width, native.height) < 640) return [];
108160
+ const ranked = input.regions.map((region) => ({
108161
+ region,
108162
+ uncovered: uncoveredFraction(region, input.detections)
108163
+ })).filter(({ region, uncovered }) => classifyRegion(region, uncovered) === "eligible").toSorted((a, b) => area(b.region) * b.uncovered - area(a.region) * a.uncovered);
108164
+ const planned = [];
108165
+ for (const { region } of ranked) for (const crop of cropsForRegion(region, native)) {
108166
+ if (planned.length >= 2) return planned;
108167
+ const centreX = crop.left + crop.side / 2;
108168
+ const centreY = crop.top + crop.side / 2;
108169
+ if (planned.some((existing) => contains(existing, centreX, centreY))) continue;
108170
+ planned.push(crop);
108171
+ }
108172
+ return planned;
108173
+ }
108174
+ /**
108175
+ * Boxes the executor produced on the crop VIEW → the result's own space.
108176
+ *
108177
+ * Three spaces meet here: the view the model saw (`view`, ≤640 px), the native
108178
+ * square it was cut from (`crop`, native px) and the space the full-frame
108179
+ * result reports in (`result`, native px on the `frameRef` path, the inline
108180
+ * ≤640 view otherwise). A box touching the view edge was clipped by the crop
108181
+ * and is dropped: half a person is not a person, and the tracker would take
108182
+ * the half as the whole.
108183
+ */
108184
+ function remapCropDetections(detections, crop, view, result, native) {
108185
+ const viewToNative = crop.side / Math.max(1, view.width);
108186
+ const nativeToResultX = result.width / Math.max(1, native.width);
108187
+ const nativeToResultY = result.height / Math.max(1, native.height);
108188
+ const out = [];
108189
+ for (const detection of detections) {
108190
+ const { x, y, width, height } = detection.bbox;
108191
+ if (x <= 2 || y <= 2 || x + width >= view.width - 2 || y + height >= view.height - 2) continue;
108192
+ out.push({
108193
+ ...detection,
108194
+ nativeRecovery: {
108195
+ score: detection.score,
108196
+ cropSidePx: crop.side,
108197
+ viewPx: view.width
108198
+ },
108199
+ bbox: {
108200
+ x: (crop.left + x * viewToNative) * nativeToResultX,
108201
+ y: (crop.top + y * viewToNative) * nativeToResultY,
108202
+ width: width * viewToNative * nativeToResultX,
108203
+ height: height * viewToNative * nativeToResultY
108204
+ }
108205
+ });
108206
+ }
108207
+ return out;
108208
+ }
108209
+ function boxIntersection(a, b) {
108210
+ return Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
108211
+ }
108212
+ function sameSubject(recovered, existing) {
108213
+ const inter = boxIntersection(recovered, existing);
108214
+ if (inter <= 0) return false;
108215
+ const areaR = recovered.width * recovered.height;
108216
+ const union = areaR + existing.width * existing.height - inter;
108217
+ if (union > 0 && inter / union >= .5) return true;
108218
+ return areaR > 0 && inter / areaR >= .7;
108219
+ }
108220
+ /**
108221
+ * Class-aware NMS of the recovered boxes against the full-frame ones. Every
108222
+ * full-frame box is kept as it is — the second pass only ADDS. A recovered box
108223
+ * is added when no full-frame box of the same macro class is the same subject
108224
+ * (`RECOVERY_FUSE_IOU`) or contains it (`RECOVERY_FUSE_CONTAINMENT`). Ids are
108225
+ * re-minted so a crop's `d0` cannot collide with the frame's.
108226
+ */
108227
+ function fuseRecoveredDetections(full, recovered) {
108228
+ const accepted = [];
108229
+ for (const [index, candidate] of recovered.entries()) {
108230
+ if ([...full, ...accepted].some((existing) => existing.macroClass === candidate.macroClass && sameSubject(candidate.bbox, existing.bbox))) continue;
108231
+ accepted.push({
108232
+ ...candidate,
108233
+ id: `recovered-${index}-${candidate.id}`
108234
+ });
108235
+ }
108236
+ return [...full, ...accepted];
108237
+ }
108238
+ /**
108239
+ * The step the second pass runs: the enabled first-level object detector of
108240
+ * the camera's own tree, with its own model and no children. Same model as
108241
+ * the full pass so a recovered box is comparable to a full-frame one.
108242
+ */
108243
+ function selectRecoveryStep(steps) {
108244
+ const detector = steps.find((step) => step.addonId === "object-detection" && step.enabled !== false);
108245
+ if (!detector) return null;
108246
+ return {
108247
+ addonId: detector.addonId,
108248
+ ...detector.modelId !== void 0 ? { modelId: detector.modelId } : {},
108249
+ enabled: true
108250
+ };
108251
+ }
108252
+ //#endregion
108253
+ //#region src/pipeline/second-pass/motion-crop-plan.ts
108254
+ /**
108255
+ * Step-settings key of the opt-in, on the `object-detection` step — so it is
108256
+ * per camera through `pipelineOrchestrator.setCameraStepSettings` (a
108257
+ * camera-semantic key, D927), and it rides the same
108258
+ * `PipelineStepInputOutput.settings` the per-macro confidence floors ride.
108259
+ */
108260
+ var MOTION_CROP_DISPATCH_KEY = "motionCropDispatch";
108261
+ /**
108262
+ * Read the opt-in. Anything that is not the boolean `true` is the shipped
108263
+ * rule: a stored string or a number is a mis-write (this repo has shipped a
108264
+ * setting written into the wrong sqlite table before), and a mis-write must
108265
+ * not silently change what a camera detects on.
108266
+ */
108267
+ function readRecoveryRecipe(settings) {
108268
+ return settings?.["motionCropDispatch"] === true ? "motion-crop" : "uncovered-region";
108269
+ }
108270
+ /**
108271
+ * The recipe this camera's resolved step tree selects.
108272
+ *
108273
+ * Reads the ENABLED first-level detector's own settings, which is where
108274
+ * `pipelineOrchestrator.setCameraStepSettings` writes a per-camera value. A
108275
+ * tree with no enabled detector runs no second pass at all, so the answer
108276
+ * there is the shipped rule and it is never used.
108277
+ */
108278
+ function readRecoveryRecipeForSteps(steps) {
108279
+ return readRecoveryRecipe(steps.find((step) => step.addonId === "object-detection" && step.enabled !== false)?.settings);
108280
+ }
108281
+ /**
108282
+ * A motion box is grown ×1.5 about its centre before anything else.
108283
+ *
108284
+ * The motion step's box is the CHANGED pixels, which on a moving animal is the
108285
+ * leading edge plus its shadow, not the animal. The detector needs the subject
108286
+ * and the context it was trained on.
108287
+ */
108288
+ var MOTION_CROP_MOTION_PAD = 1.5;
108289
+ /**
108290
+ * An intersecting tracked box is grown ×1.25 — less, because it is already a
108291
+ * subject rectangle rather than a change mask.
108292
+ */
108293
+ var MOTION_CROP_TRACKED_PAD = 1.25;
108294
+ /**
108295
+ * Regions per frame, largest area first.
108296
+ *
108297
+ * Measured as NON-BINDING: over the 156 frames of the recall harness the cap
108298
+ * bound on 0 of them. It is the bound on a storm, not a tuning knob, and the
108299
+ * runner reports when it binds so a camera on which it starts to is visible.
108300
+ */
108301
+ var MOTION_CROP_MAX_REGIONS = 3;
108302
+ /**
108303
+ * The floor a `person` or `vehicle` box must clear when it came out of a CROP
108304
+ * rather than out of the full frame.
108305
+ *
108306
+ * Collected, not assumed: 3 085 quiet frames produced 27 motion-positive
108307
+ * frames and 28 crop invocations. The full-frame arm produced 0 boxes above
108308
+ * any gate; the crop arm produced 3, of which two are genuine false positives,
108309
+ * and both are night-IR corners upscaled into a 320 model — `vehicle 0.749`
108310
+ * and `person 0.701`. Zero false `animal` at any gate, which is why `animal`
108311
+ * is deliberately absent from this table: the class the pass exists to recover
108312
+ * is the class that never produced a false positive, and gating it would cost
108313
+ * the whole measured gain.
108314
+ *
108315
+ * This is a CROP-ONLY floor, applied on top of the per-macro floors the
108316
+ * executor already enforced. A full-frame `person` at 0.70 is untouched — the
108317
+ * full-frame arm is the one arm with ground truth behind its floor
108318
+ * (`minConfidencePerson` 0.65, raised on 2026-07-30 from a measured
108319
+ * distribution).
108320
+ *
108321
+ * The cost of this floor is stated rather than assumed away: it can also
108322
+ * remove a real distant person or vehicle recovered from a crop at 0.70–0.80.
108323
+ * Nothing in the quiet-footage sample measures that, so the floor is a
108324
+ * constant here rather than a slider, and the runner reports every drop it
108325
+ * makes per camera.
108326
+ */
108327
+ var CROP_CLASS_FLOORS = Object.freeze({
108328
+ person: .8,
108329
+ vehicle: .8
108330
+ });
108331
+ /**
108332
+ * Apply the crop-only class floors, REPORTING every drop.
108333
+ *
108334
+ * A class with no entry in {@link CROP_CLASS_FLOORS} is kept: a floor this
108335
+ * table cannot express is not a floor of zero, and silently dropping a class
108336
+ * nobody measured is the exact shape of silent drop this repo has paid for
108337
+ * three separate times.
108338
+ */
108339
+ function applyCropClassFloors(detections) {
108340
+ const kept = [];
108341
+ const dropped = [];
108342
+ for (const detection of detections) {
108343
+ const floor = CROP_CLASS_FLOORS[detection.macroClass];
108344
+ if (floor !== void 0 && detection.score < floor) {
108345
+ dropped.push({
108346
+ macroClass: detection.macroClass,
108347
+ score: detection.score,
108348
+ floor
108349
+ });
108350
+ continue;
108351
+ }
108352
+ kept.push(detection);
108353
+ }
108354
+ return {
108355
+ kept,
108356
+ dropped
108357
+ };
108358
+ }
108359
+ function scaleAbout(rect, factor) {
108360
+ const w = rect.w * factor;
108361
+ const h = rect.h * factor;
108362
+ return {
108363
+ x: rect.x + rect.w / 2 - w / 2,
108364
+ y: rect.y + rect.h / 2 - h / 2,
108365
+ w,
108366
+ h
108367
+ };
108368
+ }
108369
+ function overlaps(a, b) {
108370
+ return Math.min(a.x + a.w, b.x + b.w) > Math.max(a.x, b.x) && Math.min(a.y + a.h, b.y + b.h) > Math.max(a.y, b.y);
108371
+ }
108372
+ function union(a, b) {
108373
+ const x = Math.min(a.x, b.x);
108374
+ const y = Math.min(a.y, b.y);
108375
+ return {
108376
+ x,
108377
+ y,
108378
+ w: Math.max(a.x + a.w, b.x + b.w) - x,
108379
+ h: Math.max(a.y + a.h, b.y + b.h) - y
108380
+ };
108381
+ }
108382
+ function clampToFrame(rect) {
108383
+ const x = Math.max(0, rect.x);
108384
+ const y = Math.max(0, rect.y);
108385
+ return {
108386
+ x,
108387
+ y,
108388
+ w: Math.min(1, rect.x + rect.w) - x,
108389
+ h: Math.min(1, rect.y + rect.h) - y
108390
+ };
108391
+ }
108392
+ /**
108393
+ * Merge transitively every seed whose padded box overlaps another, and answer
108394
+ * the union rectangles.
108395
+ *
108396
+ * Transitive on purpose: a person, the dog beside them and the dog's shadow
108397
+ * are three components of one subject, and cutting three crops for them spends
108398
+ * two inferences on pixels the first one already showed the detector.
108399
+ */
108400
+ function mergeSeeds(seeds) {
108401
+ const merged = [];
108402
+ for (const seed of seeds) {
108403
+ let current = seed;
108404
+ let changed = true;
108405
+ while (changed) {
108406
+ changed = false;
108407
+ for (let i = merged.length - 1; i >= 0; i -= 1) {
108408
+ const other = merged[i];
108409
+ if (other === void 0 || !overlaps(current, other)) continue;
108410
+ current = union(current, other);
108411
+ merged.splice(i, 1);
108412
+ changed = true;
108413
+ }
108414
+ }
108415
+ merged.push(current);
108416
+ }
108417
+ return merged;
108418
+ }
108419
+ /**
108420
+ * The regions the measured recipe judges this frame on.
108421
+ *
108422
+ * Motion boxes padded {@link MOTION_CROP_MOTION_PAD}; every full-frame box
108423
+ * that intersects a RAW motion box padded {@link MOTION_CROP_TRACKED_PAD} and
108424
+ * added as a seed of its own; everything that overlaps merged; the result
108425
+ * clamped to the frame, ordered largest first and capped at
108426
+ * {@link MOTION_CROP_MAX_REGIONS}.
108427
+ *
108428
+ * There is deliberately no coverage gate and no size gate here. Those belong
108429
+ * to the shipped rule, and they are what makes it a rule about a subject
108430
+ * BESIDE a detected one; the measurement says the pass is worth running on the
108431
+ * detected subject's own region too.
108432
+ */
108433
+ function planMotionCropRegions(input) {
108434
+ if (input.regions.length === 0) return [];
108435
+ const seeds = input.regions.map((r) => scaleAbout(r, MOTION_CROP_MOTION_PAD));
108436
+ for (const detection of input.detections) {
108437
+ if (!input.regions.some((region) => overlaps(detection, region))) continue;
108438
+ seeds.push(scaleAbout(detection, MOTION_CROP_TRACKED_PAD));
108439
+ }
108440
+ return mergeSeeds(seeds).map(clampToFrame).filter((r) => r.w > 0 && r.h > 0).toSorted((a, b) => normalizedArea(b) - normalizedArea(a)).slice(0, 3);
108441
+ }
108442
+ /**
108443
+ * The square the detector is fed for one measured region.
108444
+ *
108445
+ * "Grow to the detector's input aspect ratio without ever shrinking" is a
108446
+ * SQUARE here, and that is a fact about the production detector rather than a
108447
+ * simplification: `yolov9m-320-int8` takes 320×320, as does every hardware
108448
+ * default in the catalog.
108449
+ *
108450
+ * The side is the region's longer side, floored at
108451
+ * {@link RECOVERY_CROP_MIN_SIDE_PX} — the growth is what magnifies the subject
108452
+ * — and ceilinged at {@link RECOVERY_CROP_MAX_SIDE_PX}. The ceiling is NOT a
108453
+ * simplification either: at 1024 px the same 320 model scored the reference
108454
+ * dog 0.52–0.76, and at 1280 px lost it entirely
108455
+ * (`docs/benchmarks/pipeline-frame-model-eval.md`, case `miss-dog-615`). A
108456
+ * region longer than the ceiling is TILED along its longer axis, never zoomed
108457
+ * out.
108458
+ */
108459
+ function motionCropsForRegion(region, native) {
108460
+ const widthPx = region.w * native.width;
108461
+ const heightPx = region.h * native.height;
108462
+ const longest = Math.max(widthPx, heightPx);
108463
+ const side = Math.round(Math.min(Math.max(longest, 640), 800));
108464
+ const centreX = (region.x + region.w / 2) * native.width;
108465
+ const centreY = (region.y + region.h / 2) * native.height;
108466
+ if (longest <= 800) return [squareAround(centreX, centreY, side, native)];
108467
+ const alongX = widthPx >= heightPx;
108468
+ const quarter = longest / 4;
108469
+ return [squareAround(alongX ? centreX - quarter : centreX, alongX ? centreY : centreY - quarter, side, native), squareAround(alongX ? centreX + quarter : centreX, alongX ? centreY : centreY + quarter, side, native)];
108470
+ }
108471
+ /** `true` when `inner` lies entirely inside `outer`. */
108472
+ function cropContains(outer, inner) {
108473
+ return inner.left >= outer.left && inner.top >= outer.top && inner.left + inner.side <= outer.left + outer.side && inner.top + inner.side <= outer.top + outer.side;
108474
+ }
108475
+ /**
108476
+ * The crops the measured recipe runs on this frame.
108477
+ *
108478
+ * Dedupe is by CONTAINMENT only. Two squares that merely overlap both run: the
108479
+ * pixels the second adds are pixels no other crop shows the detector, and the
108480
+ * fusion step downstream already refuses a duplicate BOX. Dropping a crop
108481
+ * because it overlapped would drop the subject sitting at its edge, which is
108482
+ * the whole population this pass exists for.
108483
+ *
108484
+ * A frame that cannot hold the smallest crop yields nothing: it is already
108485
+ * seen at scale.
108486
+ */
108487
+ function planMotionCrops(input) {
108488
+ const { native } = input;
108489
+ if (Math.min(native.width, native.height) < 640) return [];
108490
+ const planned = [];
108491
+ for (const region of planMotionCropRegions(input)) for (const crop of motionCropsForRegion(region, native)) {
108492
+ if (planned.some((existing) => cropContains(existing, crop))) continue;
108493
+ planned.push(crop);
108494
+ }
108495
+ return planned;
108496
+ }
108497
+ //#endregion
107442
108498
  //#region src/pipeline/root-face-crop.ts
107443
108499
  /** Side of the square, as a multiple of the face box's longer side. */
107444
108500
  var ROOT_FACE_REFINE_SCALE = 1.6;
@@ -109793,6 +110849,7 @@ exports.ADMIN_SCOPE = ADMIN_SCOPE;
109793
110849
  exports.AGENT_MDNS_QUERY_NAME = AGENT_MDNS_QUERY_NAME;
109794
110850
  exports.AGENT_MDNS_SERVICE_TYPE = AGENT_MDNS_SERVICE_TYPE;
109795
110851
  exports.AGENT_MDNS_TXT_KEYS = AGENT_MDNS_TXT_KEYS;
110852
+ exports.AGGREGATE_THROUGHPUT_BY_KIND = AGGREGATE_THROUGHPUT_BY_KIND;
109796
110853
  exports.ALEXA_EGRESS_PROFILE = ALEXA_EGRESS_PROFILE;
109797
110854
  exports.ALL_CAPABILITY_DEFINITIONS = ALL_CAPABILITY_DEFINITIONS;
109798
110855
  exports.ALL_METHOD_ACCESS = ALL_METHOD_ACCESS;
@@ -109820,6 +110877,7 @@ exports.AUDIO_SCALE_EPOCH_MS = AUDIO_SCALE_EPOCH_MS;
109820
110877
  exports.AUTOMATIC_HUB_ADDRESS_FLAGS = AUTOMATIC_HUB_ADDRESS_FLAGS;
109821
110878
  exports.AUTO_SHARE_HEADROOM = AUTO_SHARE_HEADROOM;
109822
110879
  exports.AUTO_TUNE_FAMILY = AUTO_TUNE_FAMILY;
110880
+ exports.AcceleratorComputeSchema = AcceleratorComputeSchema;
109823
110881
  exports.AcceleratorKindSchema = AcceleratorKindSchema;
109824
110882
  exports.AccessoriesStatusSchema = AccessoriesStatusSchema;
109825
110883
  exports.AccessoryKind = AccessoryKind;
@@ -110006,8 +111064,11 @@ exports.CONTAINER_CHILD_PRIORITY = CONTAINER_CHILD_PRIORITY;
110006
111064
  exports.CORAL_DEFAULT_MODEL_ID = CORAL_DEFAULT_MODEL_ID;
110007
111065
  exports.CORE_BLOCKS_ADDON_ID = CORE_BLOCKS_ADDON_ID;
110008
111066
  exports.CORE_BLOCK_ADDON_PREFIX = CORE_BLOCK_ADDON_PREFIX;
111067
+ exports.CORE_ULTRA_265K_IGPU_CLOCK_MHZ = CORE_ULTRA_265K_IGPU_CLOCK_MHZ;
111068
+ exports.CORE_ULTRA_265K_IGPU_EU = CORE_ULTRA_265K_IGPU_EU;
110009
111069
  exports.CREDENTIAL_TOKEN_AUDIENCE = CREDENTIAL_TOKEN_AUDIENCE;
110010
111070
  exports.CREDENTIAL_TOKEN_KIND = CREDENTIAL_TOKEN_KIND;
111071
+ exports.CROP_CLASS_FLOORS = CROP_CLASS_FLOORS;
110011
111072
  exports.CROP_SUPPLY_SOURCES = CROP_SUPPLY_SOURCES;
110012
111073
  exports.CROP_SUPPLY_STEP_IDS = CROP_SUPPLY_STEP_IDS;
110013
111074
  exports.CUSTOMIZATION_BLOCK_NAME_PREFIX = require_composition.CUSTOMIZATION_BLOCK_NAME_PREFIX;
@@ -110358,6 +111419,8 @@ exports.DoorbellStatusSchema = DoorbellStatusSchema;
110358
111419
  exports.EMBEDDING_MODEL_FAILED = EMBEDDING_MODEL_FAILED;
110359
111420
  exports.EMBEDDING_REQUIREMENTS_FAILED = EMBEDDING_REQUIREMENTS_FAILED;
110360
111421
  exports.EMBEDDING_REQUIREMENTS_UNAVAILABLE = EMBEDDING_REQUIREMENTS_UNAVAILABLE;
111422
+ exports.ESTIMATE_COMPUTE_RATIO_MAX = ESTIMATE_COMPUTE_RATIO_MAX;
111423
+ exports.ESTIMATE_COMPUTE_RATIO_MIN = ESTIMATE_COMPUTE_RATIO_MIN;
110361
111424
  exports.EVENTFUL_CAP_NAMES = EVENTFUL_CAP_NAMES;
110362
111425
  exports.EVENT_DENSITY_BATCH_MAX = EVENT_DENSITY_BATCH_MAX;
110363
111426
  exports.EVENT_KIND_BY_CAP = EVENT_KIND_BY_CAP;
@@ -110708,6 +111771,10 @@ exports.MODEL_FORMATS = require_sleep.MODEL_FORMATS;
110708
111771
  exports.MODEL_HOSTING = MODEL_HOSTING;
110709
111772
  exports.MODEL_PROVIDER_IDS = MODEL_PROVIDER_IDS;
110710
111773
  exports.MOTION_CLOSE_AFTER_MS = MOTION_CLOSE_AFTER_MS;
111774
+ exports.MOTION_CROP_DISPATCH_KEY = MOTION_CROP_DISPATCH_KEY;
111775
+ exports.MOTION_CROP_MAX_REGIONS = MOTION_CROP_MAX_REGIONS;
111776
+ exports.MOTION_CROP_MOTION_PAD = MOTION_CROP_MOTION_PAD;
111777
+ exports.MOTION_CROP_TRACKED_PAD = MOTION_CROP_TRACKED_PAD;
110711
111778
  exports.MOTION_SOURCE_LABELS = MOTION_SOURCE_LABELS;
110712
111779
  exports.MOTION_TRIGGER_FEATURE = MOTION_TRIGGER_FEATURE;
110713
111780
  exports.ManagedModelCatalogEntrySchema = ManagedModelCatalogEntrySchema;
@@ -110772,6 +111839,8 @@ exports.MotionZoneStatusSchema = MotionZoneStatusSchema;
110772
111839
  exports.MqttBrokerInfoSchema = BrokerInfoSchema$1;
110773
111840
  exports.MqttBrokerStatusSchema = StatusSchema;
110774
111841
  exports.MutationFilterSchema = MutationFilterSchema;
111842
+ exports.N100_IGPU_CLOCK_MHZ = N100_IGPU_CLOCK_MHZ;
111843
+ exports.N100_IGPU_EU = N100_IGPU_EU;
110775
111844
  exports.NATIVE_LEASE_ACTIVITY_FIELD = NATIVE_LEASE_ACTIVITY_FIELD;
110776
111845
  exports.NATIVE_LEASE_ACTIVITY_KEY = NATIVE_LEASE_ACTIVITY_KEY;
110777
111846
  exports.NATIVE_LEASE_ADMISSION_FIELD = NATIVE_LEASE_ADMISSION_FIELD;
@@ -110839,6 +111908,7 @@ exports.NC_GROUPING_MAX_SEC = NC_GROUPING_MAX_SEC;
110839
111908
  exports.NC_GROUPING_MIN_SEC = NC_GROUPING_MIN_SEC;
110840
111909
  exports.NC_HISTORY_LIMIT_DEFAULT = NC_HISTORY_LIMIT_DEFAULT;
110841
111910
  exports.NC_HISTORY_LIMIT_MAX = NC_HISTORY_LIMIT_MAX;
111911
+ exports.NC_LEGACY_SYSTEM_EVENT_KINDS = NC_LEGACY_SYSTEM_EVENT_KINDS;
110842
111912
  exports.NC_MAX_PER_TRACK_IMMEDIATE = NC_MAX_PER_TRACK_IMMEDIATE;
110843
111913
  exports.NC_MEDIA_IMAGE_MODES = NC_MEDIA_IMAGE_MODES;
110844
111914
  exports.NC_OCCUPANCY_DEFAULTS = NC_OCCUPANCY_DEFAULTS;
@@ -110853,6 +111923,7 @@ exports.NC_SENSOR_TRANSITION_NEVER_FIRES_WARNING = NC_SENSOR_TRANSITION_NEVER_FI
110853
111923
  exports.NC_SENSOR_TRANSITION_UNSUPPORTED_REASON = NC_SENSOR_TRANSITION_UNSUPPORTED_REASON;
110854
111924
  exports.NC_SERVER_PACKAGE_NAME = NC_SERVER_PACKAGE_NAME;
110855
111925
  exports.NC_SNOOZE_MAX_MINUTES = NC_SNOOZE_MAX_MINUTES;
111926
+ exports.NC_STATE_SUMMARY_SECTION_IDS = NC_STATE_SUMMARY_SECTION_IDS;
110856
111927
  exports.NC_SYSTEM_DELIVERY = NC_SYSTEM_DELIVERY;
110857
111928
  exports.NC_SYSTEM_EVENT_CLASS_SPECS = NC_SYSTEM_EVENT_CLASS_SPECS;
110858
111929
  exports.NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS = NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS;
@@ -110863,7 +111934,6 @@ exports.NC_SYSTEM_EVENT_FILTER_KEYS = NC_SYSTEM_EVENT_FILTER_KEYS;
110863
111934
  exports.NC_SYSTEM_EVENT_KIND_GROUP_SPECS = NC_SYSTEM_EVENT_KIND_GROUP_SPECS;
110864
111935
  exports.NC_SYSTEM_EVENT_SUMMARY_FAMILIES = NC_SYSTEM_EVENT_SUMMARY_FAMILIES;
110865
111936
  exports.NC_SYSTEM_RULE_KINDS = NC_SYSTEM_RULE_KINDS;
110866
- exports.NC_TANK_SYSTEM_EVENT_KINDS = NC_TANK_SYSTEM_EVENT_KINDS;
110867
111937
  exports.NC_TAXONOMY = NC_TAXONOMY;
110868
111938
  exports.NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS;
110869
111939
  exports.NC_TEMPLATE_VARS = NC_TEMPLATE_VARS;
@@ -111037,6 +112107,7 @@ exports.NotifierStatusSchema = NotifierStatusSchema;
111037
112107
  exports.NumericSensorStatusSchema = NumericSensorStatusSchema;
111038
112108
  exports.OAUTH_LANDING_PATH = OAUTH_LANDING_PATH;
111039
112109
  exports.OAUTH_LANDING_REASONS = OAUTH_LANDING_REASONS;
112110
+ exports.OBJECT_DETECTION_STEP_ID = OBJECT_DETECTION_STEP_ID;
111040
112111
  exports.OPERATOR_WRITTEN_STALE_MS = OPERATOR_WRITTEN_STALE_MS;
111041
112112
  exports.OPS_LOG_DEFAULT_LIMIT = OPS_LOG_DEFAULT_LIMIT;
111042
112113
  exports.OPS_LOG_RING_DEFAULT_MAX = OPS_LOG_RING_DEFAULT_MAX;
@@ -111087,6 +112158,7 @@ exports.PEER_BYTES_TICKET_TTL_MS = require_sleep.PEER_BYTES_TICKET_TTL_MS;
111087
112158
  exports.PENDING_BLOCK_ID = PENDING_BLOCK_ID;
111088
112159
  exports.PET_FEEDER_MANUAL_FEED_MAX = PET_FEEDER_MANUAL_FEED_MAX;
111089
112160
  exports.PET_FEEDER_MANUAL_FEED_MIN = PET_FEEDER_MANUAL_FEED_MIN;
112161
+ exports.PHOENIX_780M_CU = PHOENIX_780M_CU;
111090
112162
  exports.PIPELINE_ANALYTICS_CAP_NAME = PIPELINE_ANALYTICS_CAP_NAME;
111091
112163
  exports.PIPELINE_FLOW_CAPABILITY_NAMES = PIPELINE_FLOW_CAPABILITY_NAMES;
111092
112164
  exports.PIPELINE_OWNER_CAPABILITY_NAMES = PIPELINE_OWNER_CAPABILITY_NAMES;
@@ -111103,6 +112175,7 @@ exports.PUBLIC_ENDPOINT_HTTP_REASON = PUBLIC_ENDPOINT_HTTP_REASON;
111103
112175
  exports.PYTHON_SCRIPT = PYTHON_SCRIPT;
111104
112176
  exports.PackageInstallationSchema = PackageInstallationSchema;
111105
112177
  exports.PackageInstallationsSchema = PackageInstallationsSchema;
112178
+ exports.PackageLlmVerdictSchema = PackageLlmVerdictSchema;
111106
112179
  exports.PackageUpdateSchema = PackageUpdateSchema;
111107
112180
  exports.PackageVersionInfoSchema = PackageVersionInfoSchema;
111108
112181
  exports.ParkRefusalSchema = ParkRefusalSchema;
@@ -111135,6 +112208,7 @@ exports.PlaintextSecretError = require_sleep.PlaintextSecretError;
111135
112208
  exports.PolygonPointSchema = PolygonPointSchema;
111136
112209
  exports.PoolMemoryWatchdog = PoolMemoryWatchdog;
111137
112210
  exports.PowerMeterStatusSchema = PowerMeterStatusSchema;
112211
+ exports.PrecisionGopsSchema = PrecisionGopsSchema;
111138
112212
  exports.PreferenceDeviceIdSchema = PreferenceDeviceIdSchema;
111139
112213
  exports.PreferenceNamespaceSchema = PreferenceNamespaceSchema;
111140
112214
  exports.PreferenceRowSchema = PreferenceRowSchema;
@@ -111203,7 +112277,21 @@ exports.RECORD_QUERY_MAX = require_sleep.RECORD_QUERY_MAX;
111203
112277
  exports.RECORD_SECRET_COLLECTION_PREFIX = require_sleep.RECORD_SECRET_COLLECTION_PREFIX;
111204
112278
  exports.RECORD_SETTINGS_MAX_ROWS = require_sleep.RECORD_SETTINGS_MAX_ROWS;
111205
112279
  exports.RECORD_SWEEP_INTERVAL_MS = require_sleep.RECORD_SWEEP_INTERVAL_MS;
112280
+ exports.RECOVERY_CROP_MAX_SIDE_PX = RECOVERY_CROP_MAX_SIDE_PX;
112281
+ exports.RECOVERY_CROP_MIN_SIDE_PX = RECOVERY_CROP_MIN_SIDE_PX;
112282
+ exports.RECOVERY_CROP_PADDING_RATIO = RECOVERY_CROP_PADDING_RATIO;
112283
+ exports.RECOVERY_EDGE_MARGIN_PX = RECOVERY_EDGE_MARGIN_PX;
112284
+ exports.RECOVERY_FUSE_CONTAINMENT = RECOVERY_FUSE_CONTAINMENT;
112285
+ exports.RECOVERY_FUSE_IOU = RECOVERY_FUSE_IOU;
112286
+ exports.RECOVERY_MAX_CROPS_PER_FRAME = RECOVERY_MAX_CROPS_PER_FRAME;
112287
+ exports.RECOVERY_MAX_REGION_FRAME_FRACTION = RECOVERY_MAX_REGION_FRAME_FRACTION;
112288
+ exports.RECOVERY_MIN_INTERVAL_MS = RECOVERY_MIN_INTERVAL_MS;
112289
+ exports.RECOVERY_MIN_UNCOVERED_FRACTION = RECOVERY_MIN_UNCOVERED_FRACTION;
112290
+ exports.RECOVERY_MOTION_MAX_AGE_MS = RECOVERY_MOTION_MAX_AGE_MS;
112291
+ exports.RECOVERY_VIEW_PX = RECOVERY_VIEW_PX;
111206
112292
  exports.REDACTED_SECRET = require_sleep.REDACTED_SECRET;
112293
+ exports.REFERENCE_COMPUTE = REFERENCE_COMPUTE;
112294
+ exports.REFERENCE_MATCH_FRACTION = REFERENCE_MATCH_FRACTION;
111207
112295
  exports.REMOVAL_RECONCILE_INTERVAL_MS = REMOVAL_RECONCILE_INTERVAL_MS;
111208
112296
  exports.REMOVED_MODEL_IDS = REMOVED_MODEL_IDS;
111209
112297
  exports.REMOVE_LEAF = require_sleep.REMOVE_LEAF;
@@ -111223,6 +112311,9 @@ exports.ROOT_CHILD_CONTAINMENT_MIN = ROOT_CHILD_CONTAINMENT_MIN;
111223
112311
  exports.ROOT_FACE_REFINE_INPUT_PX = ROOT_FACE_REFINE_INPUT_PX;
111224
112312
  exports.ROOT_FACE_REFINE_SCALE = ROOT_FACE_REFINE_SCALE;
111225
112313
  exports.ROOT_SUPPLIABLE_STEP_IDS = ROOT_SUPPLIABLE_STEP_IDS;
112314
+ exports.RUNNING_POWER_START_DELAY_MS = RUNNING_POWER_START_DELAY_MS;
112315
+ exports.RUNNING_POWER_STOP_DELAY_MS = RUNNING_POWER_STOP_DELAY_MS;
112316
+ exports.RUNNING_POWER_THRESHOLD_WATTS = RUNNING_POWER_THRESHOLD_WATTS;
111226
112317
  exports.RUNTIME_DEFAULTS = require_sleep.RUNTIME_DEFAULTS;
111227
112318
  exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
111228
112319
  exports.RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS = RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS;
@@ -111374,6 +112465,8 @@ exports.RunnerJumpStepProvisioningSchema = RunnerJumpStepProvisioningSchema;
111374
112465
  exports.RunnerLocalLoadSchema = RunnerLocalLoadSchema;
111375
112466
  exports.RunnerLocalMetricsSchema = RunnerLocalMetricsSchema;
111376
112467
  exports.RunnerRefusalSchema = RunnerRefusalSchema;
112468
+ exports.RunningStateSourceSchema = RunningStateSourceSchema;
112469
+ exports.RunningStateStatusSchema = RunningStateStatusSchema;
111377
112470
  exports.SCENE_CONDITIONS = SCENE_CONDITIONS;
111378
112471
  exports.SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX = SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX;
111379
112472
  exports.SCENE_CONFIRM_DEFAULT_TIMEOUT_MS = SCENE_CONFIRM_DEFAULT_TIMEOUT_MS;
@@ -111390,6 +112483,7 @@ exports.SECRET_META = require_sleep.SECRET_META;
111390
112483
  exports.SECRET_REF_SCHEME = require_sleep.SECRET_REF_SCHEME;
111391
112484
  exports.SENSOR_FEATURES = SENSOR_FEATURES;
111392
112485
  exports.SENSOR_MAP = SENSOR_MAP;
112486
+ exports.SERIAL_CAPACITY_CAMERA_FPS = SERIAL_CAPACITY_CAMERA_FPS;
111393
112487
  exports.SERVER_NAME_MAX_LENGTH = SERVER_NAME_MAX_LENGTH;
111394
112488
  exports.SERVER_NAME_SETTING_KEY = SERVER_NAME_SETTING_KEY;
111395
112489
  exports.SESSION_PLACEMENT_SETTING_KEY = SESSION_PLACEMENT_SETTING_KEY;
@@ -111745,6 +112839,7 @@ exports.addonWidgetsSourceCapability = addonWidgetsSourceCapability;
111745
112839
  exports.addonsCapability = addonsCapability;
111746
112840
  exports.adminUiCapability = require_sleep.adminUiCapability;
111747
112841
  exports.advertisedHubOrigins = advertisedHubOrigins;
112842
+ exports.aggregateThroughputFactor = aggregateThroughputFactor;
111748
112843
  exports.airQualitySensorCapability = airQualitySensorCapability;
111749
112844
  exports.alarmDeviceBlocksArming = alarmDeviceBlocksArming;
111750
112845
  exports.alarmDeviceSecureOnArm = alarmDeviceSecureOnArm;
@@ -111762,6 +112857,7 @@ exports.ambientLightSensorCapability = ambientLightSensorCapability;
111762
112857
  exports.annotateClipsWithEventLabels = annotateClipsWithEventLabels;
111763
112858
  exports.appFlowLocation = appFlowLocation;
111764
112859
  exports.applyBlockingEdit = applyBlockingEdit;
112860
+ exports.applyCropClassFloors = applyCropClassFloors;
111765
112861
  exports.applyModeDelaysEdits = applyModeDelaysEdits;
111766
112862
  exports.applyModeDeviceListEdit = applyModeDeviceListEdit;
111767
112863
  exports.applyModelBoundSettings = applyModelBoundSettings;
@@ -111897,6 +112993,7 @@ exports.compositionSliceKey = require_composition.compositionSliceKey;
111897
112993
  exports.compositionSourceKey = require_composition.compositionSourceKey;
111898
112994
  exports.compositionSourceRefs = compositionSourceRefs;
111899
112995
  exports.computeCapacityShares = computeCapacityShares;
112996
+ exports.computeScores = computeScores;
111900
112997
  exports.conditionDepth = conditionDepth;
111901
112998
  exports.conditionExclusionReason = conditionExclusionReason;
111902
112999
  exports.conditionVisibleForKind = conditionVisibleForKind;
@@ -111919,6 +113016,7 @@ exports.coreBlockSourceKind = coreBlockSourceKind;
111919
113016
  exports.coreBlocksCapability = coreBlocksCapability;
111920
113017
  exports.cosineSimilarity = cosineSimilarity;
111921
113018
  exports.countConditionLeaves = countConditionLeaves;
113019
+ exports.countRecoverySkips = countRecoverySkips;
111922
113020
  exports.coverCapability = coverCapability;
111923
113021
  exports.createAlarmProfile = createAlarmProfile;
111924
113022
  exports.createDeviceExtensionProvider = createDeviceExtensionProvider;
@@ -112089,6 +113187,7 @@ exports.formatSecretScope = require_sleep.formatSecretScope;
112089
113187
  exports.freshResults = freshResults;
112090
113188
  exports.frigateDefaultClassFloors = frigateDefaultClassFloors;
112091
113189
  exports.frigateLabelFields = frigateLabelFields;
113190
+ exports.fuseRecoveredDetections = fuseRecoveredDetections;
112092
113191
  exports.gasCapability = gasCapability;
112093
113192
  exports.gbToBytes = gbToBytes;
112094
113193
  exports.generateAutomationBlock = generateAutomationBlock;
@@ -112181,6 +113280,7 @@ exports.isRemovedModelId = isRemovedModelId;
112181
113280
  exports.isRestoredCap = isRestoredCap;
112182
113281
  exports.isRetiredClearKey = isRetiredClearKey;
112183
113282
  exports.isRootSuppliableStepId = isRootSuppliableStepId;
113283
+ exports.isRunningAssignmentFeature = isRunningAssignmentFeature;
112184
113284
  exports.isSameAddonId = isSameAddonId;
112185
113285
  exports.isScheduleActive = isScheduleActive;
112186
113286
  exports.isSecretClear = require_sleep.isSecretClear;
@@ -112299,6 +113399,7 @@ exports.normalizeOrigin = normalizeOrigin;
112299
113399
  exports.normalizeSecretOwner = require_sleep.normalizeSecretOwner;
112300
113400
  exports.normalizeTokenScopes = normalizeTokenScopes;
112301
113401
  exports.normalizeUnit = normalizeUnit;
113402
+ exports.normalizedArea = normalizedArea;
112302
113403
  exports.notificationOutputCapability = notificationOutputCapability;
112303
113404
  exports.notificationRulesCapability = notificationRulesCapability;
112304
113405
  exports.notifierCapability = notifierCapability;
@@ -112316,6 +113417,7 @@ exports.osdCapability = osdCapability;
112316
113417
  exports.osdManagerCapability = osdManagerCapability;
112317
113418
  exports.overlayClusterStepSettings = overlayClusterStepSettings;
112318
113419
  exports.packagePickerItems = packagePickerItems;
113420
+ exports.parseAcceleratorFactsJson = parseAcceleratorFactsJson;
112319
113421
  exports.parseAdminBase = parseAdminBase;
112320
113422
  exports.parseAlertEpisodePayload = parseAlertEpisodePayload;
112321
113423
  exports.parseAppAuthCallback = parseAppAuthCallback;
@@ -112366,6 +113468,9 @@ exports.pipelineRunnerCapability = pipelineRunnerCapability;
112366
113468
  exports.planComposition = planComposition;
112367
113469
  exports.planCompositionEvents = planCompositionEvents;
112368
113470
  exports.planItems = planItems;
113471
+ exports.planMotionCropRegions = planMotionCropRegions;
113472
+ exports.planMotionCrops = planMotionCrops;
113473
+ exports.planRecoveryCrops = planRecoveryCrops;
112369
113474
  exports.plateGalleryCapability = plateGalleryCapability;
112370
113475
  exports.platformProbeCapability = platformProbeCapability;
112371
113476
  exports.poolMemoryThreshold = poolMemoryThreshold;
@@ -112394,6 +113499,8 @@ exports.readHubMotionStatus = readHubMotionStatus;
112394
113499
  exports.readMotionTriggeredBy = readMotionTriggeredBy;
112395
113500
  exports.readNativeLeaseOverride = readNativeLeaseOverride;
112396
113501
  exports.readNodePin = require_sleep.readNodePin;
113502
+ exports.readRecoveryRecipe = readRecoveryRecipe;
113503
+ exports.readRecoveryRecipeForSteps = readRecoveryRecipeForSteps;
112397
113504
  exports.readTimelapseGeneratedAt = readTimelapseGeneratedAt;
112398
113505
  exports.readUseRootModel = readUseRootModel;
112399
113506
  exports.readinessKey = require_sleep.readinessKey;
@@ -112415,9 +113522,11 @@ exports.recordsErrorFromMessage = require_sleep.recordsErrorFromMessage;
112415
113522
  exports.rectsToCells = rectsToCells;
112416
113523
  exports.reducePoints = reducePoints;
112417
113524
  exports.registerKnownSecret = require_sleep.registerKnownSecret;
113525
+ exports.remapCropDetections = remapCropDetections;
112418
113526
  exports.renameAlarmProfile = renameAlarmProfile;
112419
113527
  exports.renderVocabPhrase = renderVocabPhrase;
112420
113528
  exports.replacedFieldKeys = replacedFieldKeys;
113529
+ exports.reportIsScalable = reportIsScalable;
112421
113530
  exports.requestMountPrefix = requestMountPrefix;
112422
113531
  exports.requiredTopLevelKeys = requiredTopLevelKeys;
112423
113532
  exports.requiresPython = requiresPython;
@@ -112445,6 +113554,7 @@ exports.resolveHubAddressRoles = resolveHubAddressRoles;
112445
113554
  exports.resolveHydratedFieldValue = require_sleep.resolveHydratedFieldValue;
112446
113555
  exports.resolveIconKey = resolveIconKey;
112447
113556
  exports.resolveInternalAddress = resolveInternalAddress;
113557
+ exports.resolveLatencyColumn = resolveLatencyColumn;
112448
113558
  exports.resolveLocationMode = resolveLocationMode;
112449
113559
  exports.resolveMethodAuth = resolveMethodAuth;
112450
113560
  exports.resolveMethodRole = resolveMethodRole;
@@ -112488,6 +113598,13 @@ exports.ruleSectionOf = ruleSectionOf;
112488
113598
  exports.ruleSeedForSection = ruleSeedForSection;
112489
113599
  exports.ruleTriggerZoneIds = ruleTriggerZoneIds;
112490
113600
  exports.runInferenceStep = runInferenceStep;
113601
+ exports.runningAssignmentFeature = runningAssignmentFeature;
113602
+ exports.runningAssignmentRecovered = runningAssignmentRecovered;
113603
+ exports.runningFromDishwasherOperation = runningFromDishwasherOperation;
113604
+ exports.runningFromLawnMower = runningFromLawnMower;
113605
+ exports.runningFromMediaPlayer = runningFromMediaPlayer;
113606
+ exports.runningFromVacuumState = runningFromVacuumState;
113607
+ exports.runningStateCapability = runningStateCapability;
112491
113608
  exports.runtimeDevices = runtimeDevices;
112492
113609
  exports.runtimeStatePolicyFor = runtimeStatePolicyFor;
112493
113610
  exports.sameDoFamily = sameDoFamily;
@@ -112511,6 +113628,7 @@ exports.secretTreeFromSchema = require_sleep.secretTreeFromSchema;
112511
113628
  exports.secureActuationFor = secureActuationFor;
112512
113629
  exports.selectAppConnectionEndpoints = selectAppConnectionEndpoints;
112513
113630
  exports.selectAssignedProfileSlots = require_sleep.selectAssignedProfileSlots;
113631
+ exports.selectRecoveryStep = selectRecoveryStep;
112514
113632
  exports.sendTimesForEditor = sendTimesForEditor;
112515
113633
  exports.sendTimesOfSchedule = sendTimesOfSchedule;
112516
113634
  exports.sensorRaisedOf = sensorRaisedOf;
@@ -112537,6 +113655,7 @@ exports.smokeCapability = smokeCapability;
112537
113655
  exports.smtpProviderCapability = smtpProviderCapability;
112538
113656
  exports.snapshotCapability = snapshotCapability;
112539
113657
  exports.splitTranslationRef = splitTranslationRef;
113658
+ exports.squareAround = squareAround;
112540
113659
  exports.ssoBridgeCapability = ssoBridgeCapability;
112541
113660
  exports.startMoverUnderClearance = startMoverUnderClearance;
112542
113661
  exports.startReachabilityPoll = startReachabilityPoll;