omnius 1.0.410 → 1.0.411

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -555654,6 +555654,169 @@ function buildYolo26BootstrapPlan(model = DEFAULT_YOLO26_MODEL) {
555654
555654
  packages: ["ultralytics>=8.4.0", "opencv-python-headless", "yt-dlp", "numpy"]
555655
555655
  };
555656
555656
  }
555657
+ function buildYolo26DeploymentOptions() {
555658
+ const yolo26Models = YOLO26_SCALES.flatMap((scale) => Object.values(YOLO26_TASKS).map((task) => `yolo26${scale}${task.suffix}.pt`));
555659
+ const yoloeModels = YOLO26_SCALES.flatMap((scale) => [`yoloe-26${scale}-seg.pt`, `yoloe-26${scale}-seg-pf.pt`]);
555660
+ return {
555661
+ defaultModel: DEFAULT_YOLO26_MODEL,
555662
+ scales: [...YOLO26_SCALES],
555663
+ tasks: YOLO26_TASKS,
555664
+ families: {
555665
+ yolo26: {
555666
+ description: "YOLO26 closed-vocabulary models for detection, segmentation, semantic segmentation, pose, OBB, and classification.",
555667
+ models: yolo26Models
555668
+ },
555669
+ "yoloe-26": {
555670
+ description: "YOLOE-26 open-vocabulary segmentation models for text, visual, or prompt-free detection/segmentation workflows.",
555671
+ models: yoloeModels
555672
+ }
555673
+ },
555674
+ yoloePromptModes: [
555675
+ { mode: "text", description: "Set open-vocabulary classes with model.set_classes([...]) before prediction or export." },
555676
+ { mode: "prompt_free", description: "Use YOLOE prompt-free weights such as yoloe-26s-seg-pf.pt for built-in large-vocabulary discovery." },
555677
+ { mode: "visual", description: "Use YOLOE visual prompts where supported by the installed Ultralytics version; exported prompts are static." }
555678
+ ],
555679
+ exportFormats: YOLO26_EXPORT_FORMATS,
555680
+ headModes: [
555681
+ { value: true, name: "end2end", description: "YOLO26 default one-to-one head; NMS-free output optimized for deployment latency." },
555682
+ { value: false, name: "one-to-many", description: "Traditional output requiring NMS post-processing; useful for compatibility or accuracy-sensitive pipelines." }
555683
+ ],
555684
+ exportArguments: [
555685
+ "format",
555686
+ "imgsz",
555687
+ "quantize",
555688
+ "dynamic",
555689
+ "simplify",
555690
+ "opset",
555691
+ "workspace",
555692
+ "nms",
555693
+ "batch",
555694
+ "device",
555695
+ "data",
555696
+ "fraction",
555697
+ "end2end",
555698
+ "optimize",
555699
+ "keras",
555700
+ "name"
555701
+ ]
555702
+ };
555703
+ }
555704
+ function normalizeYolo26Scale(value2) {
555705
+ const scale = String(value2 ?? "n").trim().toLowerCase();
555706
+ return YOLO26_SCALES.includes(scale) ? scale : "n";
555707
+ }
555708
+ function normalizeYolo26Task(value2) {
555709
+ const raw = String(value2 ?? "detect").trim().toLowerCase();
555710
+ const aliases = {
555711
+ detection: "detect",
555712
+ det: "detect",
555713
+ instance: "segment",
555714
+ segmentation: "segment",
555715
+ seg: "segment",
555716
+ sem: "semantic",
555717
+ semantic_segmentation: "semantic",
555718
+ keypoint: "pose",
555719
+ keypoints: "pose",
555720
+ classify: "classify",
555721
+ classification: "classify",
555722
+ cls: "classify",
555723
+ oriented: "obb",
555724
+ oriented_detection: "obb"
555725
+ };
555726
+ const normalized = aliases[raw] ?? raw;
555727
+ return Object.prototype.hasOwnProperty.call(YOLO26_TASKS, normalized) ? normalized : "detect";
555728
+ }
555729
+ function normalizeYolo26Family(value2) {
555730
+ const raw = String(value2 ?? "yolo26").trim().toLowerCase();
555731
+ if (["yoloe", "yoloe26", "yoloe-26", "open-vocabulary", "open_vocab"].includes(raw))
555732
+ return "yoloe-26";
555733
+ return "yolo26";
555734
+ }
555735
+ function resolveYolo26Model(args) {
555736
+ const explicit = args["yolo_model"] ?? args["model"];
555737
+ if (typeof explicit === "string" && explicit.trim())
555738
+ return explicit.trim();
555739
+ const family = normalizeYolo26Family(args["yolo_family"] ?? args["family"] ?? args["variant"]);
555740
+ const scale = normalizeYolo26Scale(args["yolo_scale"] ?? args["scale"]);
555741
+ const promptMode = String(args["yoloe_prompt_mode"] ?? args["prompt_mode"] ?? "").trim().toLowerCase();
555742
+ const promptFree = promptMode === "prompt_free" || promptMode === "prompt-free" || promptMode === "pf" || args["prompt_free"] === true;
555743
+ if (family === "yoloe-26")
555744
+ return `yoloe-26${scale}-seg${promptFree ? "-pf" : ""}.pt`;
555745
+ const task = normalizeYolo26Task(args["yolo_task"] ?? args["task"]);
555746
+ return `yolo26${scale}${YOLO26_TASKS[task].suffix}.pt`;
555747
+ }
555748
+ function normalizeYolo26ExportFormat(value2) {
555749
+ const raw = String(value2 ?? "onnx").trim().toLowerCase().replace(/-/g, "_");
555750
+ const aliases = {
555751
+ trt: "engine",
555752
+ tensorrt: "engine",
555753
+ tf_saved_model: "saved_model",
555754
+ savedmodel: "saved_model",
555755
+ tensorflow_saved_model: "saved_model",
555756
+ graphdef: "pb",
555757
+ edge_tpu: "edgetpu",
555758
+ tflite: "litert",
555759
+ tensorflow_lite: "litert",
555760
+ lite_rt: "litert",
555761
+ torch: "pytorch",
555762
+ pt: "pytorch"
555763
+ };
555764
+ const normalized = aliases[raw] ?? raw;
555765
+ if (YOLO26_EXPORT_FORMATS.some((entry) => entry.format === normalized))
555766
+ return normalized;
555767
+ throw new Error(`Unsupported YOLO26 export format: ${String(value2)}. Use one of: ${YOLO26_EXPORT_FORMATS.map((entry) => entry.format).join(", ")}`);
555768
+ }
555769
+ function optionalBoolean(args, key) {
555770
+ if (!(key in args) || args[key] === void 0 || args[key] === null || args[key] === "")
555771
+ return void 0;
555772
+ if (typeof args[key] === "boolean")
555773
+ return args[key];
555774
+ const raw = String(args[key]).trim().toLowerCase();
555775
+ if (["1", "true", "yes", "on"].includes(raw))
555776
+ return true;
555777
+ if (["0", "false", "no", "off"].includes(raw))
555778
+ return false;
555779
+ return void 0;
555780
+ }
555781
+ function optionalNumber(args, key) {
555782
+ if (!(key in args) || args[key] === void 0 || args[key] === null || args[key] === "")
555783
+ return void 0;
555784
+ const n2 = Number(args[key]);
555785
+ return Number.isFinite(n2) ? n2 : void 0;
555786
+ }
555787
+ function exportOptionsFromArgs(args, format3) {
555788
+ const options2 = { format: format3 };
555789
+ for (const key of ["imgsz", "opset", "workspace", "batch", "fraction"]) {
555790
+ const n2 = optionalNumber(args, key);
555791
+ if (n2 !== void 0)
555792
+ options2[key] = n2;
555793
+ }
555794
+ for (const key of ["dynamic", "simplify", "nms", "optimize", "keras", "end2end"]) {
555795
+ const b = optionalBoolean(args, key);
555796
+ if (b !== void 0)
555797
+ options2[key] = b;
555798
+ }
555799
+ const quantize = args["quantize"] ?? args["precision"];
555800
+ if (quantize !== void 0 && quantize !== null && String(quantize).trim()) {
555801
+ const asNumber2 = Number(quantize);
555802
+ options2["quantize"] = Number.isFinite(asNumber2) ? asNumber2 : String(quantize).trim();
555803
+ }
555804
+ for (const key of ["device", "data", "name"]) {
555805
+ const value2 = args[key];
555806
+ if (typeof value2 === "string" && value2.trim())
555807
+ options2[key] = value2.trim();
555808
+ }
555809
+ return options2;
555810
+ }
555811
+ function stringListFromArg(value2) {
555812
+ if (Array.isArray(value2)) {
555813
+ return value2.map((item) => String(item).trim()).filter(Boolean);
555814
+ }
555815
+ if (typeof value2 === "string") {
555816
+ return value2.split(/[,;\n]/).map((item) => item.trim()).filter(Boolean);
555817
+ }
555818
+ return [];
555819
+ }
555657
555820
  function sourceKind(args) {
555658
555821
  const explicit = String(args["source_kind"] ?? args["source_type"] ?? "").toLowerCase();
555659
555822
  if (["camera", "youtube", "file", "direct"].includes(explicit))
@@ -555797,6 +555960,59 @@ async function ensureRuntime(autoBootstrap) {
555797
555960
  return { ok: false, python: plan.python, error: err instanceof Error ? err.message : String(err) };
555798
555961
  }
555799
555962
  }
555963
+ async function exportYolo26Model(python, model, options2, yoloeClasses = []) {
555964
+ const cfgPath = join92(tmpdir19(), `omnius-yolo26-export-${Date.now()}.json`);
555965
+ const scriptPath2 = join92(tmpdir19(), `omnius-yolo26-export-${Date.now()}.py`);
555966
+ writeFileSync39(cfgPath, JSON.stringify({ model, options: options2, yoloeClasses }, null, 2), "utf8");
555967
+ writeFileSync39(scriptPath2, String.raw`
555968
+ import json, sys
555969
+ from ultralytics import YOLO
555970
+ try:
555971
+ from ultralytics import YOLOE
555972
+ except Exception:
555973
+ YOLOE = None
555974
+
555975
+ cfg_path = sys.argv[1]
555976
+ with open(cfg_path, "r", encoding="utf-8") as fh:
555977
+ cfg = json.load(fh)
555978
+
555979
+ model_name = cfg["model"]
555980
+ options = cfg["options"]
555981
+ yoloe_classes = [str(x).strip() for x in cfg.get("yoloeClasses", []) if str(x).strip()]
555982
+ model = YOLOE(model_name) if str(model_name).startswith("yoloe-") and YOLOE is not None else YOLO(model_name)
555983
+ if yoloe_classes and hasattr(model, "set_classes"):
555984
+ model.set_classes(yoloe_classes)
555985
+ artifact = model.export(**options)
555986
+ print(json.dumps({
555987
+ "success": True,
555988
+ "model": model_name,
555989
+ "format": options.get("format"),
555990
+ "artifact": str(artifact),
555991
+ "options": options,
555992
+ "yoloe_classes": yoloe_classes,
555993
+ }))
555994
+ `, "utf8");
555995
+ try {
555996
+ const raw = await execFileText(python, [scriptPath2, cfgPath], {
555997
+ timeout: 30 * 6e4,
555998
+ maxBuffer: 64 * 1024 * 1024,
555999
+ env: { ...process.env, PYTHONUNBUFFERED: "1" }
556000
+ });
556001
+ const jsonLine2 = raw.trim().split("\n").reverse().find((line) => line.trim().startsWith("{") && line.trim().endsWith("}"));
556002
+ if (!jsonLine2)
556003
+ throw new Error(`YOLO26 export returned no JSON result; stdout tail: ${raw.slice(-1e3)}`);
556004
+ return JSON.parse(jsonLine2);
556005
+ } finally {
556006
+ try {
556007
+ rmSync10(cfgPath, { force: true });
556008
+ } catch {
556009
+ }
556010
+ try {
556011
+ rmSync10(scriptPath2, { force: true });
556012
+ } catch {
556013
+ }
556014
+ }
556015
+ }
555800
556016
  function makePythonScript() {
555801
556017
  return String.raw`
555802
556018
  import json, os, sys, time
@@ -555818,6 +556034,8 @@ def main():
555818
556034
  os.makedirs(out_dir, exist_ok=True)
555819
556035
  crop_dir = os.path.join(out_dir, "crops")
555820
556036
  os.makedirs(crop_dir, exist_ok=True)
556037
+ segment_dir = os.path.join(out_dir, "segments")
556038
+ os.makedirs(segment_dir, exist_ok=True)
555821
556039
  source_kind = cfg["source_kind"]
555822
556040
  source = cfg["source"]
555823
556041
  media_path = None
@@ -555827,6 +556045,10 @@ def main():
555827
556045
  import cv2
555828
556046
  import numpy as np
555829
556047
  from ultralytics import YOLO
556048
+ try:
556049
+ from ultralytics import YOLOE
556050
+ except Exception:
556051
+ YOLOE = None
555830
556052
  except Exception as exc:
555831
556053
  print(json.dumps({"success": False, "error": "python imports failed: " + str(exc)}))
555832
556054
  return
@@ -555869,16 +556091,26 @@ def main():
555869
556091
  return
555870
556092
 
555871
556093
  yolo_model = cfg.get("yolo_model") or "yolo26n.pt"
555872
- model = YOLO(yolo_model) if cfg.get("detect_objects", True) else None
556094
+ yoloe_classes = [str(x).strip() for x in (cfg.get("yoloe_classes") or []) if str(x).strip()]
556095
+ yoloe_prompt_mode = cfg.get("yoloe_prompt_mode") or ("text" if yoloe_classes else "prompt_free" if str(yolo_model).endswith("-pf.pt") else "closed_set")
556096
+ use_yoloe = str(yolo_model).startswith("yoloe-") and YOLOE is not None
556097
+ model = (YOLOE(yolo_model) if use_yoloe else YOLO(yolo_model)) if cfg.get("detect_objects", True) else None
556098
+ if model is not None and yoloe_classes and hasattr(model, "set_classes"):
556099
+ try:
556100
+ model.set_classes(yoloe_classes)
556101
+ except TypeError:
556102
+ model.set_classes(list(yoloe_classes))
555873
556103
  face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
555874
556104
  objects = []
555875
556105
  faces = []
556106
+ segments = []
555876
556107
  track_stats = {}
555877
556108
  max_frames = int(cfg.get("max_frames") or 8)
555878
556109
  interval = float(cfg.get("sample_interval_sec") or 1.0)
555879
556110
  duration = float(cfg.get("duration_sec") or 8.0)
555880
556111
  conf = float(cfg.get("confidence") or 0.25)
555881
556112
  imgsz = int(cfg.get("imgsz") or 640)
556113
+ end2end = cfg.get("end2end", None)
555882
556114
  start_wall = time.time()
555883
556115
  frame_index = 0
555884
556116
 
@@ -555902,10 +556134,17 @@ def main():
555902
556134
 
555903
556135
  if model is not None:
555904
556136
  try:
556137
+ predict_kwargs = {"verbose": False, "conf": conf, "imgsz": imgsz}
556138
+ if end2end is not None:
556139
+ predict_kwargs["end2end"] = bool(end2end)
556140
+ if cfg.get("agnostic_nms", None) is not None:
556141
+ predict_kwargs["agnostic_nms"] = bool(cfg.get("agnostic_nms"))
556142
+ elif use_yoloe:
556143
+ predict_kwargs["agnostic_nms"] = True
555905
556144
  if cfg.get("track_objects", True):
555906
- results = model.track(frame, persist=True, verbose=False, conf=conf, imgsz=imgsz)
556145
+ results = model.track(frame, persist=True, **predict_kwargs)
555907
556146
  else:
555908
- results = model.predict(frame, verbose=False, conf=conf, imgsz=imgsz)
556147
+ results = model.predict(frame, **predict_kwargs)
555909
556148
  r = results[0] if results else None
555910
556149
  if r is not None and getattr(r, "boxes", None) is not None:
555911
556150
  boxes = r.boxes
@@ -555935,6 +556174,44 @@ def main():
555935
556174
  st["last_seen_sec"] = max(st["last_seen_sec"], ts)
555936
556175
  st["observations"] += 1
555937
556176
  st["confidence_sum"] += c
556177
+ want_segments = bool(cfg.get("segment_objects") or cfg.get("extract_segments") or use_yoloe)
556178
+ if want_segments and getattr(r, "masks", None) is not None and getattr(r.masks, "data", None) is not None:
556179
+ mask_data = r.masks.data.cpu().numpy()
556180
+ max_segments = int(cfg.get("max_segments_per_frame") or 8)
556181
+ for i, raw_mask in enumerate(mask_data[:max_segments]):
556182
+ label = str(names.get(int(cls[i]), int(cls[i]))) if i < len(cls) else "object"
556183
+ c = float(confs[i]) if i < len(confs) else 0.0
556184
+ box = xyxy[i] if i < len(xyxy) else [0, 0, frame.shape[1], frame.shape[0]]
556185
+ mask = (raw_mask > 0.5).astype("uint8") * 255
556186
+ if mask.shape[:2] != frame.shape[:2]:
556187
+ mask = cv2.resize(mask, (frame.shape[1], frame.shape[0]), interpolation=cv2.INTER_NEAREST)
556188
+ x1, y1, x2, y2 = [int(max(0, round(float(v)))) for v in box]
556189
+ x1 = min(x1, frame.shape[1] - 1)
556190
+ x2 = min(max(x2, x1 + 1), frame.shape[1])
556191
+ y1 = min(y1, frame.shape[0] - 1)
556192
+ y2 = min(max(y2, y1 + 1), frame.shape[0])
556193
+ mask_path = os.path.join(segment_dir, f"seg_f{frame_index:04d}_{i:02d}_mask.png")
556194
+ cv2.imwrite(mask_path, mask)
556195
+ crop_path = None
556196
+ if cfg.get("extract_segments", False):
556197
+ crop_bgr = frame[y1:y2, x1:x2]
556198
+ crop_alpha = mask[y1:y2, x1:x2]
556199
+ if crop_bgr.size > 0 and crop_alpha.size > 0:
556200
+ crop_rgba = cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2BGRA)
556201
+ crop_rgba[:, :, 3] = crop_alpha
556202
+ crop_path = os.path.join(segment_dir, f"seg_f{frame_index:04d}_{i:02d}_{label.replace('/', '_')}.png")
556203
+ cv2.imwrite(crop_path, crop_rgba)
556204
+ area_ratio = float((mask > 0).sum()) / max(1.0, float(frame.shape[0] * frame.shape[1]))
556205
+ segments.append({
556206
+ "frame_index": frame_index,
556207
+ "timestamp_sec": round(float(ts), 3),
556208
+ "label": label,
556209
+ "confidence": c,
556210
+ "bbox_xyxy": [int(x1), int(y1), int(x2), int(y2)],
556211
+ "mask_path": mask_path,
556212
+ "crop_path": crop_path,
556213
+ "area_ratio": round(area_ratio, 6),
556214
+ })
555938
556215
  except Exception as exc:
555939
556216
  objects.append({"frame_index": frame_index, "timestamp_sec": round(float(ts), 3), "label": "yolo_error", "confidence": 0.0, "bbox_xyxy": [0,0,0,0], "error": str(exc)})
555940
556217
 
@@ -555984,6 +556261,9 @@ def main():
555984
556261
  "objects": objects,
555985
556262
  "tracks": tracks,
555986
556263
  "faces": faces,
556264
+ "segments": segments,
556265
+ "yoloe_classes": yoloe_classes,
556266
+ "yoloe_prompt_mode": yoloe_prompt_mode,
555987
556267
  }))
555988
556268
 
555989
556269
  if __name__ == "__main__":
@@ -555995,8 +556275,13 @@ function mockResult(args, workingDir) {
555995
556275
  const rotation = normalizeCameraRotationDegrees(args["rotate_degrees"] ?? args["rotation_degrees"] ?? args["rotate"]);
555996
556276
  const outDir = join92(workingDir, ".omnius", "live-media", `mock-${Date.now().toString(36)}`);
555997
556277
  mkdirSync47(join92(outDir, "crops"), { recursive: true });
556278
+ mkdirSync47(join92(outDir, "segments"), { recursive: true });
555998
556279
  const facePath = join92(outDir, "crops", "face_f0000_00.jpg");
556280
+ const maskPath = join92(outDir, "segments", "seg_f0001_00_mask.png");
556281
+ const segmentPath = join92(outDir, "segments", "seg_f0001_00_red_cup.png");
555999
556282
  writeFileSync39(facePath, "mock face crop", "utf8");
556283
+ writeFileSync39(maskPath, "mock segment mask", "utf8");
556284
+ writeFileSync39(segmentPath, "mock extracted segment", "utf8");
556000
556285
  return {
556001
556286
  success: true,
556002
556287
  source_kind: kind,
@@ -556016,10 +556301,22 @@ function mockResult(args, workingDir) {
556016
556301
  { track_id: "1", label: "person", first_seen_sec: 0, last_seen_sec: 0, observations: 1, mean_confidence: 0.91 },
556017
556302
  { track_id: "2", label: "red cup", first_seen_sec: 1, last_seen_sec: 2, observations: 2, mean_confidence: 0.84 }
556018
556303
  ],
556019
- faces: [{ frame_index: 0, timestamp_sec: 0, crop_path: facePath, bbox_xyxy: [20, 30, 80, 90], confidence: 0.5 }]
556304
+ faces: [{ frame_index: 0, timestamp_sec: 0, crop_path: facePath, bbox_xyxy: [20, 30, 80, 90], confidence: 0.5 }],
556305
+ segments: [{
556306
+ frame_index: 1,
556307
+ timestamp_sec: 1,
556308
+ label: "red cup",
556309
+ confidence: 0.84,
556310
+ bbox_xyxy: [130, 140, 180, 210],
556311
+ mask_path: maskPath,
556312
+ crop_path: segmentPath,
556313
+ area_ratio: 0.012
556314
+ }],
556315
+ yoloe_classes: stringListFromArg(args["yoloe_classes"] ?? args["classes"]),
556316
+ yoloe_prompt_mode: String(args["yoloe_prompt_mode"] ?? "closed_set")
556020
556317
  };
556021
556318
  }
556022
- var LIVE_MEDIA_ROOT, YOLO26_VENV, DEFAULT_YOLO26_MODEL, LiveMediaLoopTool;
556319
+ var LIVE_MEDIA_ROOT, YOLO26_VENV, DEFAULT_YOLO26_MODEL, YOLO26_SCALES, YOLO26_TASKS, YOLO26_EXPORT_FORMATS, LiveMediaLoopTool;
556023
556320
  var init_live_media_loop = __esm({
556024
556321
  "packages/execution/dist/tools/live-media-loop.js"() {
556025
556322
  "use strict";
@@ -556033,14 +556330,44 @@ var init_live_media_loop = __esm({
556033
556330
  LIVE_MEDIA_ROOT = join92(homedir28(), ".omnius", "runtimes", "live-media");
556034
556331
  YOLO26_VENV = join92(LIVE_MEDIA_ROOT, ".venv-yolo26");
556035
556332
  DEFAULT_YOLO26_MODEL = "yolo26n.pt";
556333
+ YOLO26_SCALES = ["n", "s", "m", "l", "x"];
556334
+ YOLO26_TASKS = {
556335
+ detect: { suffix: "", description: "Object detection" },
556336
+ segment: { suffix: "-seg", description: "Instance segmentation" },
556337
+ semantic: { suffix: "-sem", description: "Semantic segmentation" },
556338
+ pose: { suffix: "-pose", description: "Pose/keypoints" },
556339
+ obb: { suffix: "-obb", description: "Oriented object detection" },
556340
+ classify: { suffix: "-cls", description: "Image classification" }
556341
+ };
556342
+ YOLO26_EXPORT_FORMATS = [
556343
+ { format: "pytorch", artifact: "yolo26n.pt", description: "Native PyTorch checkpoint", arguments: [], precisions: ["fp32"] },
556344
+ { format: "torchscript", artifact: "yolo26n.torchscript", description: "TorchScript runtime artifact", arguments: ["imgsz", "quantize", "dynamic", "optimize", "nms", "batch", "device"], precisions: ["fp32", "fp16"] },
556345
+ { format: "onnx", artifact: "yolo26n.onnx", description: "ONNX Runtime and cross-platform inference", arguments: ["imgsz", "quantize", "dynamic", "simplify", "opset", "nms", "batch", "data", "fraction", "device"], precisions: ["fp32", "fp16", "int8"] },
556346
+ { format: "openvino", artifact: "yolo26n_openvino_model/", description: "Intel OpenVINO CPU/iGPU/NPU deployment", arguments: ["imgsz", "quantize", "dynamic", "nms", "batch", "data", "fraction", "device"], precisions: ["fp32", "fp16", "int8"] },
556347
+ { format: "engine", artifact: "yolo26n.engine", description: "NVIDIA TensorRT engine", arguments: ["imgsz", "quantize", "dynamic", "simplify", "workspace", "nms", "batch", "data", "fraction", "device"], precisions: ["fp32", "fp16", "int8"] },
556348
+ { format: "coreml", artifact: "yolo26n.mlpackage", description: "Apple CoreML deployment", arguments: ["imgsz", "dynamic", "quantize", "nms", "batch", "device"], precisions: ["fp32", "fp16", "int8", "w8a16"] },
556349
+ { format: "saved_model", artifact: "yolo26n_saved_model/", description: "TensorFlow SavedModel", arguments: ["imgsz", "keras", "quantize", "nms", "batch", "data", "fraction", "device"], precisions: ["fp32", "int8"] },
556350
+ { format: "pb", artifact: "yolo26n.pb", description: "TensorFlow GraphDef", arguments: ["imgsz", "batch", "device"], precisions: ["fp32"] },
556351
+ { format: "edgetpu", artifact: "yolo26n_edgetpu.tflite", description: "Google Coral Edge TPU", arguments: ["imgsz", "quantize", "data", "fraction", "device"], precisions: ["int8"] },
556352
+ { format: "paddle", artifact: "yolo26n_paddle_model/", description: "PaddlePaddle", arguments: ["imgsz", "batch", "device"], precisions: ["fp32"] },
556353
+ { format: "mnn", artifact: "yolo26n.mnn", description: "MNN mobile/embedded runtime", arguments: ["imgsz", "batch", "quantize", "device"], precisions: ["fp32", "fp16", "int8"] },
556354
+ { format: "ncnn", artifact: "yolo26n_ncnn_model/", description: "Tencent NCNN mobile/embedded runtime", arguments: ["imgsz", "quantize", "batch", "device"], precisions: ["fp32", "fp16"] },
556355
+ { format: "imx", artifact: "yolo26n_imx_model/", description: "Sony IMX500", arguments: ["imgsz", "quantize", "data", "fraction", "nms", "device"], precisions: ["int8", "w8a16"] },
556356
+ { format: "rknn", artifact: "yolo26n_rknn_model/", description: "Rockchip RKNN", arguments: ["imgsz", "batch", "name", "quantize", "data", "fraction", "device"], precisions: ["fp16", "int8"] },
556357
+ { format: "executorch", artifact: "yolo26n_executorch_model/", description: "PyTorch ExecuTorch", arguments: ["imgsz", "batch", "device"], precisions: ["fp32"] },
556358
+ { format: "axelera", artifact: "yolo26n_axelera_model/", description: "Axelera AI deployment", arguments: ["imgsz", "batch", "quantize", "data", "fraction", "device"], precisions: ["int8"] },
556359
+ { format: "deepx", artifact: "yolo26n_deepx_model/", description: "DEEPX accelerator deployment", arguments: ["imgsz", "quantize", "data", "optimize", "device"], precisions: ["int8"] },
556360
+ { format: "qnn", artifact: "yolo26n_qnn.onnx", description: "Qualcomm QNN HTP", arguments: ["imgsz", "batch", "name", "quantize", "data", "fraction", "device"], precisions: ["w8a16"] },
556361
+ { format: "litert", artifact: "yolo26n.tflite", description: "Google LiteRT / TensorFlow Lite successor", arguments: ["imgsz", "quantize", "batch", "data", "fraction", "device"], precisions: ["fp32", "int8", "w8a16", "w8a32"] }
556362
+ ];
556036
556363
  LiveMediaLoopTool = class {
556037
556364
  workingDir;
556038
556365
  name = "live_media_loop";
556039
- description = "Watch a live or file media source in a bounded loop: YouTube/direct video/local file/camera, YOLO26 object detection/tracking, face cropping, optional face familiarity recall, ASR/audio comprehension, and visual trigger evaluation. Use this for live video input, object tracking, face crops, visual reminders/triggers, and 'watch this video/camera and react when X appears'.";
556366
+ description = "Watch a live or file media source in a bounded loop: YouTube/direct video/local file/camera, YOLO26/YOLOE object detection/tracking, open-vocabulary segmentation masks, extracted transparent object crops, face cropping, optional face familiarity recall, ASR/audio comprehension, and visual trigger evaluation. Use this for live video input, object tracking, object segmentation/extraction, face crops, visual reminders/triggers, and 'watch this video/camera and react when X appears'.";
556040
556367
  parameters = {
556041
556368
  type: "object",
556042
556369
  properties: {
556043
- action: { type: "string", enum: ["watch", "analyze", "ensure", "status"] },
556370
+ action: { type: "string", enum: ["watch", "analyze", "ensure", "status", "deployment_options", "options", "export"] },
556044
556371
  source_kind: { type: "string", enum: ["youtube", "camera", "file", "direct"] },
556045
556372
  url: { type: "string", description: "YouTube or direct video URL" },
556046
556373
  path: { type: "string", description: "Local media path" },
@@ -556053,6 +556380,36 @@ var init_live_media_loop = __esm({
556053
556380
  sample_interval_sec: { type: "number", description: "Seconds between sampled frames, default 1" },
556054
556381
  max_frames: { type: "number", description: "Max sampled frames, default 8" },
556055
556382
  yolo_model: { type: "string", description: "Ultralytics model, default yolo26n.pt" },
556383
+ yolo_family: { type: "string", enum: ["yolo26", "yoloe-26"], description: "Model family when yolo_model is omitted." },
556384
+ yolo_task: { type: "string", enum: ["detect", "segment", "semantic", "pose", "obb", "classify"], description: "YOLO26 task suffix when yolo_model is omitted." },
556385
+ yolo_scale: { type: "string", enum: ["n", "s", "m", "l", "x"], description: "YOLO26 model scale when yolo_model is omitted." },
556386
+ yoloe_prompt_mode: { type: "string", enum: ["closed_set", "text", "prompt_free", "visual"], description: "YOLOE prompting mode. text uses yoloe_classes; prompt_free uses *-seg-pf weights when no explicit yolo_model is set." },
556387
+ prompt_mode: { type: "string", description: "Alias for yoloe_prompt_mode." },
556388
+ prompt_free: { type: "boolean", description: "Alias for yoloe_prompt_mode=prompt_free." },
556389
+ yoloe_classes: { type: ["array", "string"], description: "YOLOE text prompt classes, e.g. ['person','bus'] or comma-separated string." },
556390
+ classes: { type: ["array", "string"], description: "Alias for yoloe_classes." },
556391
+ segment_objects: { type: "boolean", description: "Capture instance segmentation masks when model returns masks. Enabled automatically for YOLOE." },
556392
+ extract_segments: { type: "boolean", description: "Save transparent RGBA crops for segmented objects." },
556393
+ max_segments_per_frame: { type: "number", description: "Limit saved mask/crop artifacts per sampled frame, default 8." },
556394
+ agnostic_nms: { type: "boolean", description: "Override YOLOE agnostic NMS behavior." },
556395
+ export_format: { type: "string", description: "YOLO26 export format for action=export, e.g. onnx, engine, openvino, coreml, saved_model, rknn, ncnn, litert." },
556396
+ format: { type: "string", description: "Alias for export_format when action=export." },
556397
+ quantize: { type: ["string", "number"], description: "Export precision: 8/int8/w8a8, 16/fp16/w16a16, 32/fp32, w8a16, or w8a32 where supported." },
556398
+ precision: { type: ["string", "number"], description: "Alias for quantize." },
556399
+ end2end: { type: "boolean", description: "YOLO26 one-to-one NMS-free head when true; set false for traditional one-to-many/NMS pipeline." },
556400
+ imgsz: { type: "number", description: "YOLO input/export image size, default 640." },
556401
+ dynamic: { type: "boolean", description: "Enable dynamic input size for supported export formats." },
556402
+ simplify: { type: "boolean", description: "Simplify ONNX/TensorRT export graphs when supported." },
556403
+ opset: { type: "number", description: "ONNX opset version." },
556404
+ workspace: { type: "number", description: "TensorRT workspace size in GiB." },
556405
+ nms: { type: "boolean", description: "Embed NMS in supported exports. Not available for YOLO26 end2end exports." },
556406
+ batch: { type: "number", description: "Export batch size." },
556407
+ device: { type: "string", description: "Export/inference device, e.g. cpu, 0, mps, npu:0, dla:0." },
556408
+ data: { type: "string", description: "Calibration dataset yaml for INT8/W8A16 exports." },
556409
+ fraction: { type: "number", description: "Calibration data fraction for quantized export." },
556410
+ optimize: { type: "boolean", description: "Export optimization for supported targets." },
556411
+ keras: { type: "boolean", description: "Keras SavedModel export option." },
556412
+ name: { type: "string", description: "Target/chip/name option for formats such as RKNN or QNN." },
556056
556413
  auto_bootstrap: { type: "boolean", description: "Create managed YOLO26 venv if missing, default true" },
556057
556414
  detect_objects: { type: "boolean" },
556058
556415
  track_objects: { type: "boolean" },
@@ -556071,9 +556428,27 @@ var init_live_media_loop = __esm({
556071
556428
  async execute(args) {
556072
556429
  const start2 = performance.now();
556073
556430
  const action = String(args["action"] ?? "watch").toLowerCase();
556074
- const yoloModel = String(args["yolo_model"] ?? DEFAULT_YOLO26_MODEL);
556431
+ const yoloModel = resolveYolo26Model(args);
556075
556432
  const rotation = normalizeCameraRotationDegrees(args["rotate_degrees"] ?? args["rotation_degrees"] ?? args["rotate"]);
556076
556433
  const plan = buildYolo26BootstrapPlan(yoloModel);
556434
+ if (action === "deployment_options" || action === "options") {
556435
+ const options2 = buildYolo26DeploymentOptions();
556436
+ const text2 = [
556437
+ "YOLO26 / YOLOE deployment options",
556438
+ `default model: ${options2.defaultModel}`,
556439
+ `scales: ${options2.scales.join(", ")}`,
556440
+ `tasks: ${Object.entries(options2.tasks).map(([task, meta]) => `${task}${meta.suffix}`).join(", ")}`,
556441
+ `formats: ${options2.exportFormats.map((entry) => entry.format).join(", ")}`,
556442
+ `YOLOE prompt modes: ${options2.yoloePromptModes.map((entry) => entry.mode).join(", ")}`,
556443
+ "head modes: end2end=true for default NMS-free one-to-one head; end2end=false for traditional one-to-many/NMS pipeline"
556444
+ ].join("\n");
556445
+ return {
556446
+ success: true,
556447
+ output: text2,
556448
+ llmContent: JSON.stringify(options2, null, 2),
556449
+ durationMs: performance.now() - start2
556450
+ };
556451
+ }
556077
556452
  if (action === "status" || action === "ensure") {
556078
556453
  const runtime = action === "ensure" ? await ensureRuntime(args["auto_bootstrap"] !== false) : await ensureRuntime(false);
556079
556454
  return {
@@ -556084,13 +556459,69 @@ var init_live_media_loop = __esm({
556084
556459
  `python: ${plan.python}`,
556085
556460
  `model: ${plan.model}`,
556086
556461
  `packages: ${plan.packages.join(", ")}`,
556462
+ `deployment formats: ${YOLO26_EXPORT_FORMATS.map((entry) => entry.format).join(", ")}`,
556087
556463
  runtime.error ? `error: ${runtime.error}` : ""
556088
556464
  ].filter(Boolean).join("\n"),
556089
556465
  error: runtime.ok ? void 0 : runtime.error,
556090
- llmContent: JSON.stringify({ ...plan, ready: runtime.ok }, null, 2),
556466
+ llmContent: JSON.stringify({ ...plan, ready: runtime.ok, deploymentOptions: buildYolo26DeploymentOptions() }, null, 2),
556091
556467
  durationMs: performance.now() - start2
556092
556468
  };
556093
556469
  }
556470
+ if (action === "export") {
556471
+ let format3;
556472
+ try {
556473
+ format3 = normalizeYolo26ExportFormat(args["export_format"] ?? args["format"] ?? args["deploy_format"]);
556474
+ } catch (err) {
556475
+ return { success: false, output: "", error: err instanceof Error ? err.message : String(err), durationMs: performance.now() - start2 };
556476
+ }
556477
+ if (format3 === "pytorch") {
556478
+ const payload = { success: true, model: yoloModel, format: format3, artifact: yoloModel, options: { format: format3 } };
556479
+ return {
556480
+ success: true,
556481
+ output: `YOLO26 native PyTorch model selected: ${yoloModel}`,
556482
+ llmContent: JSON.stringify(payload, null, 2),
556483
+ durationMs: performance.now() - start2
556484
+ };
556485
+ }
556486
+ const exportOptions = exportOptionsFromArgs(args, format3);
556487
+ const yoloeClasses = stringListFromArg(args["yoloe_classes"] ?? args["classes"]);
556488
+ if (Boolean(args["mock"]) || process.env["OMNIUS_LIVE_MEDIA_MOCK"] === "1") {
556489
+ const payload = {
556490
+ success: true,
556491
+ model: yoloModel,
556492
+ format: format3,
556493
+ artifact: `${basename19(yoloModel, ".pt")}.${format3 === "engine" ? "engine" : format3}`,
556494
+ options: exportOptions,
556495
+ yoloe_classes: yoloeClasses,
556496
+ mock: true
556497
+ };
556498
+ return {
556499
+ success: true,
556500
+ output: `YOLO26 export plan (mock): ${yoloModel} -> ${format3}`,
556501
+ llmContent: JSON.stringify(payload, null, 2),
556502
+ durationMs: performance.now() - start2
556503
+ };
556504
+ }
556505
+ const runtime = await ensureRuntime(args["auto_bootstrap"] !== false);
556506
+ if (!runtime.ok)
556507
+ return { success: false, output: "", error: runtime.error, durationMs: performance.now() - start2 };
556508
+ try {
556509
+ const exported = await exportYolo26Model(runtime.python, yoloModel, exportOptions, yoloeClasses);
556510
+ return {
556511
+ success: true,
556512
+ output: [
556513
+ `YOLO26 export complete: ${yoloModel}`,
556514
+ `format: ${format3}`,
556515
+ `artifact: ${String(exported["artifact"] ?? "")}`,
556516
+ `options: ${JSON.stringify(exportOptions)}`
556517
+ ].join("\n"),
556518
+ llmContent: JSON.stringify(exported, null, 2),
556519
+ durationMs: performance.now() - start2
556520
+ };
556521
+ } catch (err) {
556522
+ return { success: false, output: "", error: err instanceof Error ? err.message : String(err), durationMs: performance.now() - start2 };
556523
+ }
556524
+ }
556094
556525
  const mock = Boolean(args["mock"]) || process.env["OMNIUS_LIVE_MEDIA_MOCK"] === "1";
556095
556526
  let result;
556096
556527
  if (mock) {
@@ -556118,6 +556549,13 @@ var init_live_media_loop = __esm({
556118
556549
  sample_interval_sec: Number(args["sample_interval_sec"] ?? 1),
556119
556550
  max_frames: Number(args["max_frames"] ?? 8),
556120
556551
  rotate_degrees: rotation,
556552
+ end2end: optionalBoolean(args, "end2end"),
556553
+ yoloe_classes: stringListFromArg(args["yoloe_classes"] ?? args["classes"]),
556554
+ yoloe_prompt_mode: String(args["yoloe_prompt_mode"] ?? args["prompt_mode"] ?? ""),
556555
+ segment_objects: args["segment_objects"] !== false,
556556
+ extract_segments: args["extract_segments"] !== false,
556557
+ max_segments_per_frame: Number(args["max_segments_per_frame"] ?? 8),
556558
+ agnostic_nms: optionalBoolean(args, "agnostic_nms"),
556121
556559
  detect_objects: args["detect_objects"] !== false,
556122
556560
  track_objects: args["track_objects"] !== false,
556123
556561
  crop_faces: args["crop_faces"] !== false,
@@ -556215,10 +556653,13 @@ var init_live_media_loop = __esm({
556215
556653
  if (result.media_path)
556216
556654
  lines.push(`media: ${result.media_path}`);
556217
556655
  lines.push(`YOLO model: ${result.yolo_model} (${result.yolo_live ? "live" : "mock/harness"})`);
556656
+ if (result.yoloe_prompt_mode && result.yoloe_prompt_mode !== "closed_set") {
556657
+ lines.push(`YOLOE prompt mode: ${result.yoloe_prompt_mode}${result.yoloe_classes?.length ? ` classes=${result.yoloe_classes.join(", ")}` : ""}`);
556658
+ }
556218
556659
  if (result.rotate_degrees)
556219
556660
  lines.push(`frame rotation correction: ${result.rotate_degrees} degrees clockwise`);
556220
556661
  lines.push(`sampled: ${result.frames_sampled} frame(s) over ${result.duration_observed_sec.toFixed(1)}s`);
556221
- lines.push(`objects: ${result.objects.length} detection(s), tracks: ${result.tracks.length}, face crops: ${result.faces.length}`);
556662
+ lines.push(`objects: ${result.objects.length} detection(s), tracks: ${result.tracks.length}, face crops: ${result.faces.length}, segments: ${result.segments?.length ?? 0}`);
556222
556663
  const objectSummary = summarizeObjects(result.objects);
556223
556664
  if (objectSummary.length) {
556224
556665
  lines.push("");
@@ -556240,6 +556681,13 @@ var init_live_media_loop = __esm({
556240
556681
  lines.push(`- ${basename19(face.crop_path)} at ${face.timestamp_sec.toFixed(1)}s ${face.crop_path}`);
556241
556682
  }
556242
556683
  }
556684
+ if (result.segments?.length) {
556685
+ lines.push("");
556686
+ lines.push("## Segments");
556687
+ for (const segment of result.segments.slice(0, 12)) {
556688
+ lines.push(`- ${segment.label} conf=${segment.confidence.toFixed(2)} mask=${segment.mask_path}${segment.crop_path ? ` crop=${segment.crop_path}` : ""} area=${(segment.area_ratio ?? 0).toFixed(4)}`);
556689
+ }
556690
+ }
556243
556691
  if (audioEvidence.length) {
556244
556692
  lines.push("");
556245
556693
  lines.push("## Audio Evidence");
@@ -556263,11 +556711,14 @@ var init_live_media_loop = __esm({
556263
556711
  media_path: result.media_path,
556264
556712
  yolo_model: result.yolo_model,
556265
556713
  yolo_live: result.yolo_live,
556714
+ yoloe_classes: result.yoloe_classes ?? [],
556715
+ yoloe_prompt_mode: result.yoloe_prompt_mode,
556266
556716
  rotate_degrees: result.rotate_degrees ?? 0,
556267
556717
  frames_sampled: result.frames_sampled,
556268
556718
  objects: result.objects.slice(0, 80),
556269
556719
  tracks: result.tracks.slice(0, 40),
556270
556720
  faces: result.faces.slice(0, 20),
556721
+ segments: (result.segments ?? []).slice(0, 40),
556271
556722
  audio_evidence: audioEvidence,
556272
556723
  trigger_hits: hits,
556273
556724
  artifacts_dir: result.output_dir
@@ -557548,6 +557999,7 @@ __export(dist_exports2, {
557548
557999
  buildToolManifestFromModule: () => buildToolManifestFromModule,
557549
558000
  buildWorkboardSynthesisContext: () => buildWorkboardSynthesisContext,
557550
558001
  buildYolo26BootstrapPlan: () => buildYolo26BootstrapPlan,
558002
+ buildYolo26DeploymentOptions: () => buildYolo26DeploymentOptions,
557551
558003
  builtInMediaModelCatalog: () => builtInMediaModelCatalog,
557552
558004
  canInvokeTool: () => canInvokeTool,
557553
558005
  checkConstraints: () => checkConstraints,
@@ -557781,6 +558233,7 @@ __export(dist_exports2, {
557781
558233
  resolveMediaCudaVisibleDevicesForEnv: () => resolveMediaCudaVisibleDevicesForEnv,
557782
558234
  resolveMediaModel: () => resolveMediaModel,
557783
558235
  resolveSecret: () => resolveSecret,
558236
+ resolveYolo26Model: () => resolveYolo26Model,
557784
558237
  revokeSecret: () => revokeSecret,
557785
558238
  runBenchmark: () => runBenchmark,
557786
558239
  runBuild: () => runBuild,
@@ -602192,7 +602645,9 @@ async function buildImageAsciiPreview(inputPath, options2 = {}) {
602192
602645
  plainAscii,
602193
602646
  renderer: "image-to-ascii",
602194
602647
  width,
602195
- height
602648
+ height,
602649
+ imageWidth: dimensions?.width,
602650
+ imageHeight: dimensions?.height
602196
602651
  };
602197
602652
  }
602198
602653
  return {
@@ -602200,11 +602655,24 @@ async function buildImageAsciiPreview(inputPath, options2 = {}) {
602200
602655
  ascii: previewFailure(result.error || "renderer unavailable", width),
602201
602656
  renderer: "image-to-ascii",
602202
602657
  width,
602203
- height
602658
+ height,
602659
+ imageWidth: dimensions?.width,
602660
+ imageHeight: dimensions?.height
602204
602661
  };
602205
602662
  }
602206
602663
  const fallback = await convertWithFfmpeg(imagePath, width, height, timeoutMs);
602207
- if (fallback) return { path: imagePath, ascii: fallback, plainAscii: fallback, renderer: "ffmpeg", width, height };
602664
+ if (fallback) {
602665
+ return {
602666
+ path: imagePath,
602667
+ ascii: fallback,
602668
+ plainAscii: fallback,
602669
+ renderer: "ffmpeg",
602670
+ width,
602671
+ height,
602672
+ imageWidth: dimensions?.width,
602673
+ imageHeight: dimensions?.height
602674
+ };
602675
+ }
602208
602676
  return null;
602209
602677
  }
602210
602678
  function formatImageAsciiContext(preview, label) {
@@ -602323,10 +602791,45 @@ function parseJsonObjectFromText(value2) {
602323
602791
  const direct = parseJsonObject3(value2);
602324
602792
  if (direct) return direct;
602325
602793
  const text2 = String(value2 ?? "");
602326
- const start2 = text2.indexOf("{");
602327
- const end = text2.lastIndexOf("}");
602328
- if (start2 < 0 || end <= start2) return null;
602329
- return parseJsonObject3(text2.slice(start2, end + 1));
602794
+ const candidates = [];
602795
+ let depth = 0;
602796
+ let start2 = -1;
602797
+ let inString = false;
602798
+ let escaped = false;
602799
+ for (let i2 = 0; i2 < text2.length; i2++) {
602800
+ const ch = text2[i2];
602801
+ if (inString) {
602802
+ if (escaped) {
602803
+ escaped = false;
602804
+ } else if (ch === "\\") {
602805
+ escaped = true;
602806
+ } else if (ch === '"') {
602807
+ inString = false;
602808
+ }
602809
+ continue;
602810
+ }
602811
+ if (ch === '"') {
602812
+ inString = true;
602813
+ continue;
602814
+ }
602815
+ if (ch === "{") {
602816
+ if (depth === 0) start2 = i2;
602817
+ depth++;
602818
+ continue;
602819
+ }
602820
+ if (ch === "}" && depth > 0) {
602821
+ depth--;
602822
+ if (depth === 0 && start2 >= 0) {
602823
+ candidates.push(text2.slice(start2, i2 + 1));
602824
+ start2 = -1;
602825
+ }
602826
+ }
602827
+ }
602828
+ for (const candidate of candidates.reverse()) {
602829
+ const parsed = parseJsonObject3(candidate);
602830
+ if (parsed) return parsed;
602831
+ }
602832
+ return null;
602330
602833
  }
602331
602834
  function normalizeLiveCameraRotation(value2) {
602332
602835
  if (value2 === void 0 || value2 === null || value2 === "") return 0;
@@ -602418,21 +602921,59 @@ function truncateCell(value2, width) {
602418
602921
  function stripDashboardAnsi(value2) {
602419
602922
  return value2.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
602420
602923
  }
602924
+ function dashboardVisibleWidth(value2) {
602925
+ return stripDashboardAnsi(value2).length;
602926
+ }
602927
+ function truncateAnsiCell(value2, width) {
602928
+ value2 = String(value2 ?? "").replace(/\r?\n/g, " ");
602929
+ if (width <= 0) return "";
602930
+ let out = "";
602931
+ let visible = 0;
602932
+ let i2 = 0;
602933
+ let sawAnsi = false;
602934
+ while (i2 < value2.length && visible < width) {
602935
+ DASHBOARD_ANSI_STICKY_PATTERN.lastIndex = i2;
602936
+ const ansi5 = DASHBOARD_ANSI_STICKY_PATTERN.exec(value2);
602937
+ if (ansi5) {
602938
+ sawAnsi = true;
602939
+ out += ansi5[0];
602940
+ i2 = DASHBOARD_ANSI_STICKY_PATTERN.lastIndex;
602941
+ continue;
602942
+ }
602943
+ const codePoint = value2.codePointAt(i2);
602944
+ if (codePoint == null) break;
602945
+ const ch = String.fromCodePoint(codePoint);
602946
+ out += ch;
602947
+ i2 += ch.length;
602948
+ visible++;
602949
+ }
602950
+ const currentWidth = dashboardVisibleWidth(out);
602951
+ if (currentWidth < width) out += " ".repeat(width - currentWidth);
602952
+ return sawAnsi ? `${out}\x1B[0m` : out;
602953
+ }
602421
602954
  function dashboardPreviewLines(camera) {
602422
- const raw = camera.plainAscii && !isLowInformationAsciiPreview(camera.plainAscii) ? camera.plainAscii : "";
602423
- return raw.replace(/\r/g, "").split("\n").map((line) => stripDashboardAnsi(line).replace(/\s+$/g, "")).filter((line) => line.trim().length > 0);
602955
+ const raw = camera.ascii || camera.plainAscii || "";
602956
+ return raw.replace(/\r/g, "").split("\n").map((line) => line.replace(/\s+$/g, "")).filter((line) => stripDashboardAnsi(line).trim().length > 0);
602424
602957
  }
602425
602958
  function dashboardPreviewAspect(lines) {
602426
602959
  const widths = lines.map((line) => stripDashboardAnsi(line).length).filter((n2) => n2 > 0);
602427
602960
  const maxWidth = Math.max(1, ...widths);
602428
602961
  return Math.max(0.12, Math.min(1.6, lines.length / maxWidth));
602429
602962
  }
602963
+ function cameraDashboardAspect(camera, lines) {
602964
+ const frameWidth = Number(camera.frameWidth ?? 0);
602965
+ const frameHeight = Number(camera.frameHeight ?? 0);
602966
+ if (frameWidth > 0 && frameHeight > 0) {
602967
+ return Math.max(0.12, Math.min(1.4, frameHeight / frameWidth * 0.52));
602968
+ }
602969
+ return dashboardPreviewAspect(lines);
602970
+ }
602430
602971
  function fitDashboardPane(lines, paneWidth, paneHeight, fallback) {
602431
602972
  const source = lines.length > 0 ? lines : [fallback];
602432
602973
  const normalized = [];
602433
602974
  for (let i2 = 0; i2 < paneHeight; i2++) {
602434
602975
  const sourceIndex = source.length <= paneHeight ? i2 : Math.min(source.length - 1, Math.floor(i2 * source.length / paneHeight));
602435
- normalized.push(truncateCell(source[sourceIndex] ?? "", paneWidth));
602976
+ normalized.push(truncateAnsiCell(source[sourceIndex] ?? "", paneWidth));
602436
602977
  }
602437
602978
  while (normalized.length < paneHeight) normalized.push(" ".repeat(paneWidth));
602438
602979
  return normalized;
@@ -602451,7 +602992,7 @@ function liveDashboardPreviewWidthForSources(sourceCount, termWidth = process.st
602451
602992
  function formatCameraPane(camera, paneWidth, paneHeight, now2) {
602452
602993
  const contentWidth = Math.max(10, paneWidth - 2);
602453
602994
  const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
602454
- const title = `${compactSourceId(camera.source)} ${age}s`;
602995
+ const title = `${compactSourceId(camera.source)} ${age}s${cameraPaneBadge(camera)}`;
602455
602996
  const topTitle = ` ${title} `;
602456
602997
  const left = Math.max(0, Math.floor((contentWidth - topTitle.length) / 2));
602457
602998
  const right = Math.max(0, contentWidth - topTitle.length - left);
@@ -602460,7 +603001,7 @@ function formatCameraPane(camera, paneWidth, paneHeight, now2) {
602460
603001
  const previewLines = dashboardPreviewLines(camera);
602461
603002
  const fallback = camera.framePath ? "preview refreshing" : camera.error ? "capture failed" : "awaiting frame";
602462
603003
  const body = fitDashboardPane(previewLines, contentWidth, paneHeight, fallback);
602463
- return [top, ...body.map((line) => `│${truncateCell(line, contentWidth)}│`), bottom];
603004
+ return [top, ...body.map((line) => `│${truncateAnsiCell(line, contentWidth)}│`), bottom];
602464
603005
  }
602465
603006
  function pushDashboardWrapped(lines, value2, width) {
602466
603007
  const contentWidth = Math.max(10, width - 2);
@@ -602654,9 +603195,152 @@ function summarizeInference(value2) {
602654
603195
  const sampled = lines.find((line) => /^objects:/i.test(line));
602655
603196
  return sampled ? trimOneLine(sampled, 160) : trimOneLine(lines.slice(0, 4).join("; "), 160);
602656
603197
  }
603198
+ function boundedConfidence(value2) {
603199
+ const n2 = Number(value2);
603200
+ return Number.isFinite(n2) ? Math.max(0, Math.min(1, n2)) : void 0;
603201
+ }
603202
+ function mergeClassificationInto(map2, item) {
603203
+ const label = trimOneLine(item.label || item.kind, 80) || item.kind;
603204
+ const key = `${item.kind}:${label}:${item.trackId ?? ""}:${item.evidencePath ?? ""}`;
603205
+ const existing = map2.get(key);
603206
+ const count = Math.max(1, Number(item.count ?? 1));
603207
+ if (!existing) {
603208
+ map2.set(key, { ...item, label, count });
603209
+ return;
603210
+ }
603211
+ const total = existing.count + count;
603212
+ const confidence2 = existing.confidence !== void 0 || item.confidence !== void 0 ? ((existing.confidence ?? 0) * existing.count + (item.confidence ?? 0) * count) / total : void 0;
603213
+ map2.set(key, {
603214
+ ...existing,
603215
+ count: total,
603216
+ confidence: confidence2,
603217
+ updatedAt: Math.max(existing.updatedAt, item.updatedAt)
603218
+ });
603219
+ }
603220
+ function classificationsFromLiveMediaPayload(payload, sampledAt, fallbackSource) {
603221
+ if (!payload) return [];
603222
+ const map2 = /* @__PURE__ */ new Map();
603223
+ void fallbackSource;
603224
+ for (const object of payload.objects ?? []) {
603225
+ const label = trimOneLine(object.label || "object", 80) || "object";
603226
+ mergeClassificationInto(map2, {
603227
+ kind: "object",
603228
+ label,
603229
+ count: 1,
603230
+ confidence: boundedConfidence(object.confidence),
603231
+ trackId: object.track_id === void 0 || object.track_id === null ? void 0 : String(object.track_id),
603232
+ updatedAt: observedAtFromOffset(sampledAt, payload, Number(object.timestamp_sec ?? 0))
603233
+ });
603234
+ }
603235
+ for (const track of payload.tracks ?? []) {
603236
+ const label = trimOneLine(track.label || "object", 80) || "object";
603237
+ mergeClassificationInto(map2, {
603238
+ kind: "track",
603239
+ label,
603240
+ count: 1,
603241
+ confidence: boundedConfidence(track.mean_confidence),
603242
+ trackId: track.track_id === void 0 || track.track_id === null ? void 0 : String(track.track_id),
603243
+ updatedAt: observedAtFromOffset(sampledAt, payload, Number(track.last_seen_sec ?? track.first_seen_sec ?? 0))
603244
+ });
603245
+ }
603246
+ for (const face of payload.faces ?? []) {
603247
+ const identity3 = parseFaceIdentity(face);
603248
+ const label = identity3.status === "known" && identity3.name ? identity3.name : "unknown face";
603249
+ mergeClassificationInto(map2, {
603250
+ kind: identity3.status === "known" ? "identity" : "face",
603251
+ label,
603252
+ count: 1,
603253
+ confidence: boundedConfidence(identity3.confidence ?? face.confidence),
603254
+ evidencePath: typeof face.crop_path === "string" ? face.crop_path : void 0,
603255
+ updatedAt: observedAtFromOffset(sampledAt, payload, Number(face.timestamp_sec ?? 0))
603256
+ });
603257
+ }
603258
+ for (const segment of payload.segments ?? []) {
603259
+ const label = trimOneLine(segment.label || "segment", 80) || "segment";
603260
+ mergeClassificationInto(map2, {
603261
+ kind: "segment",
603262
+ label,
603263
+ count: 1,
603264
+ confidence: boundedConfidence(segment.confidence),
603265
+ evidencePath: typeof segment.crop_path === "string" && segment.crop_path ? segment.crop_path : typeof segment.mask_path === "string" ? segment.mask_path : void 0,
603266
+ updatedAt: observedAtFromOffset(sampledAt, payload, Number(segment.timestamp_sec ?? 0))
603267
+ });
603268
+ }
603269
+ for (const hit of payload.trigger_hits ?? []) {
603270
+ const label = trimOneLine(hit.triggerName || hit.matched || "visual trigger", 80) || "visual trigger";
603271
+ mergeClassificationInto(map2, {
603272
+ kind: "trigger",
603273
+ label,
603274
+ count: 1,
603275
+ confidence: boundedConfidence(hit.confidence),
603276
+ updatedAt: sampledAt
603277
+ });
603278
+ }
603279
+ return [...map2.values()].sort((a2, b) => {
603280
+ const aPriority = a2.kind === "face" || a2.kind === "identity" ? 0 : a2.kind === "segment" ? 1 : a2.kind === "object" ? 2 : 3;
603281
+ const bPriority = b.kind === "face" || b.kind === "identity" ? 0 : b.kind === "segment" ? 1 : b.kind === "object" ? 2 : 3;
603282
+ if (aPriority !== bPriority) return aPriority - bPriority;
603283
+ return (b.confidence ?? 0) - (a2.confidence ?? 0);
603284
+ }).slice(0, 24).map((item) => ({ ...item }));
603285
+ }
603286
+ function classificationsFromClipSummary(summary, observedAt, evidencePath) {
603287
+ const parsed = parseJsonObjectFromText(summary);
603288
+ const matches = Array.isArray(parsed?.["matches"]) ? parsed["matches"] : [];
603289
+ return matches.slice(0, 8).map((match) => {
603290
+ const label = trimOneLine(
603291
+ match["label"] ?? match["name"] ?? match["title"] ?? match["id"] ?? "visual-memory match",
603292
+ 80
603293
+ ) || "visual-memory match";
603294
+ return {
603295
+ kind: "clip",
603296
+ label,
603297
+ count: 1,
603298
+ confidence: boundedConfidence(match["score"] ?? match["similarity"] ?? match["confidence"]),
603299
+ evidencePath,
603300
+ updatedAt: observedAt
603301
+ };
603302
+ });
603303
+ }
603304
+ function mergeCameraClassifications(existing, incoming, limit = 24) {
603305
+ const map2 = /* @__PURE__ */ new Map();
603306
+ for (const item of [...existing ?? [], ...incoming]) {
603307
+ mergeClassificationInto(map2, item);
603308
+ }
603309
+ const merged = [...map2.values()].sort((a2, b) => b.updatedAt - a2.updatedAt || (b.confidence ?? 0) - (a2.confidence ?? 0)).slice(0, limit);
603310
+ return merged.length > 0 ? merged : existing;
603311
+ }
603312
+ function cameraClassificationsSummary(camera, maxChars = 220) {
603313
+ const items = (camera.classifications ?? []).sort((a2, b) => {
603314
+ const aPriority = a2.kind === "face" || a2.kind === "identity" ? 0 : a2.kind === "segment" ? 1 : a2.kind === "object" ? 2 : a2.kind === "track" ? 3 : 4;
603315
+ const bPriority = b.kind === "face" || b.kind === "identity" ? 0 : b.kind === "segment" ? 1 : b.kind === "object" ? 2 : b.kind === "track" ? 3 : 4;
603316
+ if (aPriority !== bPriority) return aPriority - bPriority;
603317
+ return (b.confidence ?? 0) - (a2.confidence ?? 0);
603318
+ }).slice(0, 8).map((item) => {
603319
+ const count = item.count > 1 ? ` x${item.count}` : "";
603320
+ const confidence2 = item.confidence !== void 0 ? ` ${item.confidence.toFixed(2)}` : "";
603321
+ return `${item.kind}:${item.label}${count}${confidence2}`;
603322
+ });
603323
+ return trimOneLine(items.join("; "), maxChars);
603324
+ }
603325
+ function cameraPaneBadge(camera) {
603326
+ const classifications = camera.classifications ?? [];
603327
+ const faceCount = classifications.filter((item) => item.kind === "face" || item.kind === "identity").reduce((sum, item) => sum + Math.max(1, item.count), 0);
603328
+ const objectCount = classifications.filter((item) => item.kind === "object").reduce((sum, item) => sum + Math.max(1, item.count), 0);
603329
+ const segmentCount = classifications.filter((item) => item.kind === "segment").reduce((sum, item) => sum + Math.max(1, item.count), 0);
603330
+ const trackCount = classifications.filter((item) => item.kind === "track").reduce((sum, item) => sum + Math.max(1, item.count), 0);
603331
+ const triggerCount = classifications.filter((item) => item.kind === "trigger").reduce((sum, item) => sum + Math.max(1, item.count), 0);
603332
+ const parts = [
603333
+ faceCount > 0 ? `F${faceCount}` : "",
603334
+ segmentCount > 0 ? `S${segmentCount}` : "",
603335
+ objectCount > 0 ? `O${objectCount}` : "",
603336
+ trackCount > 0 ? `T${trackCount}` : "",
603337
+ triggerCount > 0 ? `!${triggerCount}` : ""
603338
+ ].filter(Boolean);
603339
+ return parts.length > 0 ? ` ${parts.join(" ")}` : "";
603340
+ }
602657
603341
  function cameraSnapshotSummary(camera) {
602658
603342
  if (camera.error) return `error: ${trimOneLine(camera.error, 160)}`;
602659
- return camera.summary || summarizeInference(camera.inference);
603343
+ return camera.summary || (camera.classifications?.length ? `classifications: ${cameraClassificationsSummary(camera)}` : "") || summarizeInference(camera.inference);
602660
603344
  }
602661
603345
  function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
602662
603346
  const width = Math.max(60, opts.width ?? 100);
@@ -602681,13 +603365,14 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
602681
603365
  for (let start2 = 0; start2 < cameras.length; start2 += paneCount) {
602682
603366
  const group = cameras.slice(start2, start2 + paneCount);
602683
603367
  const sourceLines = group.map((camera) => dashboardPreviewLines(camera));
602684
- const aspect = Math.max(0.22, Math.min(0.7, Math.max(...sourceLines.map(dashboardPreviewAspect))));
603368
+ const aspects = group.map((camera, idx) => cameraDashboardAspect(camera, sourceLines[idx] ?? []));
603369
+ const aspect = Math.max(0.18, Math.min(0.92, Math.max(...aspects)));
602685
603370
  const paneHeight = Math.max(10, Math.min(32, Math.round(paneContentWidth * aspect)));
602686
603371
  const panes = group.map((camera) => formatCameraPane(camera, paneWidth, paneHeight, now2));
602687
603372
  const rowCount = Math.max(...panes.map((pane) => pane.length));
602688
603373
  for (let row = 0; row < rowCount; row++) {
602689
603374
  const body = panes.map((pane) => pane[row] ?? " ".repeat(paneWidth)).join(" ");
602690
- lines.push(`│${truncateCell(body, innerWidth)}│`);
603375
+ lines.push(`│${truncateAnsiCell(body, innerWidth)}│`);
602691
603376
  }
602692
603377
  }
602693
603378
  }
@@ -602698,8 +603383,10 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
602698
603383
  for (const camera of cameras) {
602699
603384
  const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
602700
603385
  const rotation = camera.orientation ? formatCameraRotation(camera.orientation.rotation) : "upright/no correction";
603386
+ const classifications = cameraClassificationsSummary(camera);
602701
603387
  const detailLines = [
602702
603388
  `${compactSourceId(camera.source)} age=${age}s rotation=${rotation}`,
603389
+ classifications ? `classifications: ${classifications}` : "",
602703
603390
  cameraSnapshotSummary(camera),
602704
603391
  camera.framePath ? `frame: ${camera.displayPath || camera.framePath}` : "",
602705
603392
  camera.orientation?.evidence ? `orientation: ${trimOneLine(camera.orientation.evidence, 120)}` : ""
@@ -602729,7 +603416,7 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
602729
603416
  function parseLiveMediaPayload(value2) {
602730
603417
  const parsed = parseJsonObject3(value2);
602731
603418
  if (!parsed) return null;
602732
- if (!Array.isArray(parsed["objects"]) && !Array.isArray(parsed["tracks"]) && !Array.isArray(parsed["faces"])) return null;
603419
+ if (!Array.isArray(parsed["objects"]) && !Array.isArray(parsed["tracks"]) && !Array.isArray(parsed["faces"]) && !Array.isArray(parsed["segments"])) return null;
602733
603420
  return parsed;
602734
603421
  }
602735
603422
  function observedAtFromOffset(sampledAt, payload, offsetSec) {
@@ -602826,6 +603513,21 @@ function observationsFromLiveMediaPayload(payload, sampledAt, fallbackSource) {
602826
603513
  evidence: identity3.evidence
602827
603514
  });
602828
603515
  }
603516
+ for (const segment of (payload.segments ?? []).slice(0, 24)) {
603517
+ const label = String(segment.label || "segment");
603518
+ const observedAt = observedAtFromOffset(sampledAt, payload, Number(segment.timestamp_sec ?? 0));
603519
+ const evidencePath = typeof segment.crop_path === "string" && segment.crop_path ? segment.crop_path : typeof segment.mask_path === "string" ? segment.mask_path : void 0;
603520
+ events.push({
603521
+ id: stableEventId("camera_segment", source, observedAt, `${label}:${segment.frame_index ?? ""}:${evidencePath ?? ""}`),
603522
+ kind: "camera_segment",
603523
+ observedAt,
603524
+ source,
603525
+ title: `Segmented ${label}`,
603526
+ summary: `frame=${segment.frame_index ?? "?"} t=${Number(segment.timestamp_sec ?? 0).toFixed(1)}s conf=${Number(segment.confidence ?? 0).toFixed(2)} area=${Number(segment.area_ratio ?? 0).toFixed(4)} mask=${segment.mask_path ?? "none"} crop=${segment.crop_path ?? "none"} bbox=${JSON.stringify(segment.bbox_xyxy ?? [])}`,
603527
+ confidence: typeof segment.confidence === "number" ? segment.confidence : void 0,
603528
+ evidencePath
603529
+ });
603530
+ }
602829
603531
  const faceCount = people.length;
602830
603532
  if (faceCount === 0) {
602831
603533
  const personTracks = (payload.tracks ?? []).filter((track) => String(track.label || "").toLowerCase() === "person").slice(0, 8);
@@ -602951,8 +603653,8 @@ function loadLiveSensorConfig(repoRoot) {
602951
603653
  ...DEFAULT_CONFIG7,
602952
603654
  ...parsed,
602953
603655
  cameraOrientation: sanitizeCameraOrientation(parsed.cameraOrientation),
602954
- videoIntervalMs: Math.max(1500, Number(parsed.videoIntervalMs ?? DEFAULT_CONFIG7.videoIntervalMs)),
602955
- audioIntervalMs: Math.max(3e3, Number(parsed.audioIntervalMs ?? DEFAULT_CONFIG7.audioIntervalMs))
603656
+ videoIntervalMs: Math.max(MIN_VIDEO_INTERVAL_MS, Number(parsed.videoIntervalMs ?? DEFAULT_CONFIG7.videoIntervalMs)),
603657
+ audioIntervalMs: Math.max(MIN_AUDIO_INTERVAL_MS, Number(parsed.audioIntervalMs ?? DEFAULT_CONFIG7.audioIntervalMs))
602956
603658
  };
602957
603659
  } catch {
602958
603660
  return { ...DEFAULT_CONFIG7 };
@@ -602996,6 +603698,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
602996
603698
  const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
602997
603699
  const rotation = camera.orientation ? formatCameraRotation(camera.orientation.rotation) : "upright/no correction";
602998
603700
  lines.push(`- ${camera.source} age=${age}s rotation=${rotation}${camera.framePath ? ` frame=${camera.framePath}` : ""}${camera.error ? ` error=${trimOneLine(camera.error, 240)}` : ""}`);
603701
+ const classifications = cameraClassificationsSummary(camera);
603702
+ if (classifications) lines.push(` classifications: ${classifications}`);
602999
603703
  const summary = cameraSnapshotSummary(camera);
603000
603704
  if (summary) lines.push(` summary: ${summary}`);
603001
603705
  }
@@ -603050,6 +603754,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
603050
603754
  lines.push(`timestamp: ${nowIso3(snapshot.video.updatedAt)} age=${Math.max(0, Math.round(age / 1e3))}s source=${snapshot.video.source}${snapshot.video.orientation ? ` rotation=${formatCameraRotation(snapshot.video.orientation.rotation)}` : ""}${age > maxAgeMs ? " STALE" : ""}`);
603051
603755
  if (snapshot.video.error) lines.push(`error: ${snapshot.video.error}`);
603052
603756
  if (snapshot.video.framePath) lines.push(`frame: ${snapshot.video.framePath}`);
603757
+ const classifications = cameraClassificationsSummary(snapshot.video);
603758
+ if (classifications) lines.push(`Live classifications: ${classifications}`);
603053
603759
  if (snapshot.video.asciiContext) lines.push(snapshot.video.asciiContext);
603054
603760
  if (snapshot.video.inference) {
603055
603761
  lines.push("Video inference:");
@@ -603066,6 +603772,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
603066
603772
  lines.push(`timestamp: ${nowIso3(camera.updatedAt)} age=${Math.max(0, Math.round(age / 1e3))}s rotation=${rotation}${age > maxAgeMs ? " STALE" : ""}`);
603067
603773
  if (camera.error) lines.push(`error: ${camera.error}`);
603068
603774
  if (camera.framePath) lines.push(`frame: ${camera.framePath}`);
603775
+ const classifications = cameraClassificationsSummary(camera);
603776
+ if (classifications) lines.push(`classifications: ${classifications}`);
603069
603777
  lines.push(`summary: ${cameraSnapshotSummary(camera)}`);
603070
603778
  }
603071
603779
  }
@@ -603316,13 +604024,17 @@ function formatLiveStatus(snapshot) {
603316
604024
  }
603317
604025
  return lines.join("\n");
603318
604026
  }
603319
- var DEFAULT_CONFIG7, managers, LiveSensorManager;
604027
+ var MIN_VIDEO_INTERVAL_MS, LOW_LATENCY_VIDEO_INTERVAL_MS, MIN_AUDIO_INTERVAL_MS, LOW_LATENCY_AUDIO_INTERVAL_MS, DEFAULT_CONFIG7, managers, DASHBOARD_ANSI_STICKY_PATTERN, LiveSensorManager;
603320
604028
  var init_live_sensors = __esm({
603321
604029
  "packages/cli/src/tui/live-sensors.ts"() {
603322
604030
  "use strict";
603323
604031
  init_dist5();
603324
604032
  init_async_process();
603325
604033
  init_image_ascii_preview();
604034
+ MIN_VIDEO_INTERVAL_MS = 250;
604035
+ LOW_LATENCY_VIDEO_INTERVAL_MS = 250;
604036
+ MIN_AUDIO_INTERVAL_MS = 1500;
604037
+ LOW_LATENCY_AUDIO_INTERVAL_MS = 2e3;
603326
604038
  DEFAULT_CONFIG7 = {
603327
604039
  videoEnabled: false,
603328
604040
  audioEnabled: false,
@@ -603332,10 +604044,11 @@ var init_live_sensors = __esm({
603332
604044
  asrEnabled: false,
603333
604045
  audioAnalysisEnabled: false,
603334
604046
  cameraOrientation: {},
603335
- videoIntervalMs: 5e3,
603336
- audioIntervalMs: 9e3
604047
+ videoIntervalMs: 1e3,
604048
+ audioIntervalMs: 6e3
603337
604049
  };
603338
604050
  managers = /* @__PURE__ */ new Map();
604051
+ DASHBOARD_ANSI_STICKY_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/y;
603339
604052
  LiveSensorManager = class {
603340
604053
  constructor(repoRoot) {
603341
604054
  this.repoRoot = repoRoot;
@@ -603541,10 +604254,11 @@ var init_live_sensors = __esm({
603541
604254
  this.config = {
603542
604255
  ...this.config,
603543
604256
  ...patch,
603544
- videoIntervalMs: Math.max(1500, Number(patch.videoIntervalMs ?? this.config.videoIntervalMs)),
603545
- audioIntervalMs: Math.max(3e3, Number(patch.audioIntervalMs ?? this.config.audioIntervalMs))
604257
+ videoIntervalMs: Math.max(MIN_VIDEO_INTERVAL_MS, Number(patch.videoIntervalMs ?? this.config.videoIntervalMs)),
604258
+ audioIntervalMs: Math.max(MIN_AUDIO_INTERVAL_MS, Number(patch.audioIntervalMs ?? this.config.audioIntervalMs))
603546
604259
  };
603547
604260
  this.persist();
604261
+ this.clearLoopTimers();
603548
604262
  this.ensureLoops();
603549
604263
  this.emitStatus();
603550
604264
  return this.getConfig();
@@ -603570,8 +604284,8 @@ var init_live_sensors = __esm({
603570
604284
  audioEnabled: true,
603571
604285
  asrEnabled: true,
603572
604286
  audioAnalysisEnabled: true,
603573
- videoIntervalMs: options2.lowLatency === false ? this.config.videoIntervalMs : Math.min(this.config.videoIntervalMs, 1500),
603574
- audioIntervalMs: options2.lowLatency === false ? this.config.audioIntervalMs : Math.min(this.config.audioIntervalMs, 3e3)
604287
+ videoIntervalMs: options2.lowLatency === false ? this.config.videoIntervalMs : Math.min(this.config.videoIntervalMs, LOW_LATENCY_VIDEO_INTERVAL_MS),
604288
+ audioIntervalMs: options2.lowLatency === false ? this.config.audioIntervalMs : Math.min(this.config.audioIntervalMs, LOW_LATENCY_AUDIO_INTERVAL_MS)
603575
604289
  });
603576
604290
  for (const source of this.activeVideoSources()) {
603577
604291
  const current = this.getCameraOrientation(source);
@@ -603684,6 +604398,10 @@ var init_live_sensors = __esm({
603684
604398
  ascii: preview?.ascii,
603685
604399
  plainAscii: preview?.plainAscii,
603686
604400
  renderer: preview?.renderer,
604401
+ previewWidth: preview?.width,
604402
+ previewHeight: preview?.height,
604403
+ frameWidth: preview?.imageWidth,
604404
+ frameHeight: preview?.imageHeight,
603687
604405
  asciiContext,
603688
604406
  rotation,
603689
604407
  orientation
@@ -603694,7 +604412,8 @@ var init_live_sensors = __esm({
603694
604412
  [source]: {
603695
604413
  ...cameraView,
603696
604414
  summary: this.snapshot.cameras?.[source]?.summary,
603697
- inference: this.snapshot.cameras?.[source]?.inference
604415
+ inference: this.snapshot.cameras?.[source]?.inference,
604416
+ classifications: this.snapshot.cameras?.[source]?.classifications
603698
604417
  }
603699
604418
  };
603700
604419
  this.snapshot.events = mergeRecentById(this.snapshot.events, [{
@@ -603727,7 +604446,11 @@ var init_live_sensors = __esm({
603727
604446
  displayPath,
603728
604447
  ascii: preview?.ascii,
603729
604448
  plainAscii: preview?.plainAscii,
603730
- renderer: preview?.renderer
604449
+ renderer: preview?.renderer,
604450
+ frameWidth: preview?.imageWidth,
604451
+ frameHeight: preview?.imageHeight,
604452
+ previewWidth: preview?.width,
604453
+ previewHeight: preview?.height
603731
604454
  };
603732
604455
  }
603733
604456
  async recognizeFrameObjects(framePath, source, observedAt = Date.now(), options2 = {}) {
@@ -603784,20 +604507,27 @@ var init_live_sensors = __esm({
603784
604507
  action: "watch",
603785
604508
  source_kind: "camera",
603786
604509
  camera: source,
603787
- duration_sec: 2.5,
603788
- sample_interval_sec: 1,
603789
- max_frames: 2,
604510
+ yolo_family: "yoloe-26",
604511
+ yolo_scale: "n",
604512
+ yoloe_prompt_mode: "prompt_free",
604513
+ duration_sec: 0.75,
604514
+ sample_interval_sec: 0.25,
604515
+ max_frames: 1,
603790
604516
  rotate_degrees: orientation?.rotation ?? 0,
603791
604517
  auto_bootstrap: true,
603792
604518
  audio_mode: "none",
603793
604519
  detect_objects: true,
603794
604520
  track_objects: true,
604521
+ segment_objects: true,
604522
+ extract_segments: true,
604523
+ max_segments_per_frame: 8,
603795
604524
  crop_faces: true,
603796
604525
  recognize_faces: true
603797
604526
  });
603798
604527
  inference = result.success ? String(result.output || result.llmContent || "") : `Video inference failed: ${result.error || result.output || "unknown error"}`;
603799
604528
  const livePayload = result.success ? parseLiveMediaPayload(result.llmContent || result.output) : null;
603800
604529
  const observations = observationsFromLiveMediaPayload(livePayload, sampledAt, source);
604530
+ const classifications = classificationsFromLiveMediaPayload(livePayload, sampledAt, source);
603801
604531
  this.snapshot.events = mergeRecentById(this.snapshot.events, observations.events, 50);
603802
604532
  this.snapshot.people = mergeRecentById(this.snapshot.people, observations.people, 24);
603803
604533
  const existing = this.snapshot.cameras?.[source] ?? this.snapshot.video ?? { updatedAt: Date.now(), source };
@@ -603807,6 +604537,7 @@ var init_live_sensors = __esm({
603807
604537
  source,
603808
604538
  inference,
603809
604539
  summary: summarizeInference(inference),
604540
+ classifications: mergeCameraClassifications(existing.classifications, classifications),
603810
604541
  rotation: orientation?.rotation ?? 0,
603811
604542
  orientation,
603812
604543
  ...preview.ok ? { error: void 0 } : { error: preview.message }
@@ -603845,6 +604576,10 @@ var init_live_sensors = __esm({
603845
604576
  const current = this.snapshot.cameras?.[source];
603846
604577
  if (current) {
603847
604578
  current.summary = [current.summary, clipSummary ? `clip: ${trimOneLine(clipSummary, 160)}` : ""].filter(Boolean).join(" | ");
604579
+ current.classifications = mergeCameraClassifications(
604580
+ current.classifications,
604581
+ classificationsFromClipSummary(clipSummary, sampledAt, preview.framePath)
604582
+ );
603848
604583
  this.snapshot.cameras = { ...this.snapshot.cameras ?? {}, [source]: current };
603849
604584
  if (this.snapshot.video?.source === source) this.snapshot.video = current;
603850
604585
  this.persist();
@@ -604002,30 +604737,63 @@ ${analysis}` : ""
604002
604737
  this.audioInFlight = false;
604003
604738
  }
604004
604739
  }
604740
+ clearLoopTimers() {
604741
+ if (this.videoTimer) {
604742
+ clearTimeout(this.videoTimer);
604743
+ this.videoTimer = null;
604744
+ }
604745
+ if (this.audioTimer) {
604746
+ clearTimeout(this.audioTimer);
604747
+ this.audioTimer = null;
604748
+ }
604749
+ }
604750
+ scheduleVideoLoop(delayMs) {
604751
+ if (!this.config.videoEnabled || this.videoTimer) return;
604752
+ this.videoTimer = setTimeout(async () => {
604753
+ this.videoTimer = null;
604754
+ if (!this.config.videoEnabled) return;
604755
+ try {
604756
+ await this.sampleVideoNow(this.config.inferEnabled || this.config.clipEnabled);
604757
+ } catch {
604758
+ } finally {
604759
+ if (this.config.videoEnabled) {
604760
+ this.scheduleVideoLoop(this.config.videoIntervalMs);
604761
+ }
604762
+ }
604763
+ }, Math.max(0, delayMs));
604764
+ this.videoTimer.unref?.();
604765
+ }
604766
+ scheduleAudioLoop(delayMs) {
604767
+ const wantsAudio = this.config.audioEnabled && (this.config.asrEnabled || this.config.audioAnalysisEnabled);
604768
+ if (!wantsAudio || this.audioTimer) return;
604769
+ this.audioTimer = setTimeout(async () => {
604770
+ this.audioTimer = null;
604771
+ const stillWantsAudio = this.config.audioEnabled && (this.config.asrEnabled || this.config.audioAnalysisEnabled);
604772
+ if (!stillWantsAudio) return;
604773
+ try {
604774
+ await this.sampleAudioNow();
604775
+ } catch {
604776
+ } finally {
604777
+ const wantsNext = this.config.audioEnabled && (this.config.asrEnabled || this.config.audioAnalysisEnabled);
604778
+ if (wantsNext) {
604779
+ this.scheduleAudioLoop(this.config.audioIntervalMs);
604780
+ }
604781
+ }
604782
+ }, Math.max(0, delayMs));
604783
+ this.audioTimer.unref?.();
604784
+ }
604005
604785
  ensureLoops() {
604006
604786
  if (this.config.videoEnabled && !this.videoTimer) {
604007
- void this.sampleVideoNow(this.config.inferEnabled || this.config.clipEnabled).catch(() => {
604008
- });
604009
- this.videoTimer = setInterval(() => {
604010
- void this.sampleVideoNow(this.config.inferEnabled || this.config.clipEnabled).catch(() => {
604011
- });
604012
- }, this.config.videoIntervalMs);
604013
- this.videoTimer.unref?.();
604787
+ this.scheduleVideoLoop(0);
604014
604788
  } else if (!this.config.videoEnabled && this.videoTimer) {
604015
- clearInterval(this.videoTimer);
604789
+ clearTimeout(this.videoTimer);
604016
604790
  this.videoTimer = null;
604017
604791
  }
604018
604792
  const wantsAudio = this.config.audioEnabled && (this.config.asrEnabled || this.config.audioAnalysisEnabled);
604019
604793
  if (wantsAudio && !this.audioTimer) {
604020
- void this.sampleAudioNow().catch(() => {
604021
- });
604022
- this.audioTimer = setInterval(() => {
604023
- void this.sampleAudioNow().catch(() => {
604024
- });
604025
- }, this.config.audioIntervalMs);
604026
- this.audioTimer.unref?.();
604794
+ this.scheduleAudioLoop(0);
604027
604795
  } else if (!wantsAudio && this.audioTimer) {
604028
- clearInterval(this.audioTimer);
604796
+ clearTimeout(this.audioTimer);
604029
604797
  this.audioTimer = null;
604030
604798
  }
604031
604799
  }