omnius 1.0.409 → 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,
@@ -601886,7 +602339,8 @@ __export(image_ascii_preview_exports, {
601886
602339
  buildImageAsciiPreview: () => buildImageAsciiPreview,
601887
602340
  defaultAsciiPreviewSize: () => defaultAsciiPreviewSize,
601888
602341
  extractSavedImagePath: () => extractSavedImagePath,
601889
- formatImageAsciiContext: () => formatImageAsciiContext
602342
+ formatImageAsciiContext: () => formatImageAsciiContext,
602343
+ isLowInformationAsciiPreview: () => isLowInformationAsciiPreview
601890
602344
  });
601891
602345
  import { createRequire as createRequire5 } from "node:module";
601892
602346
  import { existsSync as existsSync108, readFileSync as readFileSync87, statSync as statSync42 } from "node:fs";
@@ -602054,6 +602508,15 @@ function normalizeImageToAsciiResult(converted, width, height) {
602054
602508
  if (!ascii2) return { ascii: null, error: "empty normalized renderer output" };
602055
602509
  return { ascii: ascii2, plainAscii: plainAscii || void 0 };
602056
602510
  }
602511
+ function isLowInformationAsciiPreview(ascii2) {
602512
+ const plain = stripAsciiAnsi(String(ascii2 ?? "")).replace(/\r/g, "").split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).join("\n");
602513
+ if (!plain) return true;
602514
+ const visible = plain.replace(/\s/g, "");
602515
+ if (visible.length < 12) return true;
602516
+ const unique2 = new Set(Array.from(visible));
602517
+ if (unique2.size <= 2 && /[█▓▒░#@]/u.test(visible)) return true;
602518
+ return false;
602519
+ }
602057
602520
  function previewFailure(message2, width) {
602058
602521
  const clean5 = message2.replace(/\s+/g, " ").trim();
602059
602522
  const text2 = `[image-to-ascii preview failed: ${clean5 || "unknown error"}]`;
@@ -602172,13 +602635,19 @@ async function buildImageAsciiPreview(inputPath, options2 = {}) {
602172
602635
  if (options2.preferPackage !== false) {
602173
602636
  const result = await convertWithImageToAscii(imagePath, width, height, timeoutMs);
602174
602637
  if (result.ascii) {
602638
+ let plainAscii = result.plainAscii;
602639
+ if (isLowInformationAsciiPreview(plainAscii)) {
602640
+ plainAscii = await convertWithFfmpeg(imagePath, width, height, timeoutMs) ?? plainAscii;
602641
+ }
602175
602642
  return {
602176
602643
  path: imagePath,
602177
602644
  ascii: result.ascii,
602178
- plainAscii: result.plainAscii,
602645
+ plainAscii,
602179
602646
  renderer: "image-to-ascii",
602180
602647
  width,
602181
- height
602648
+ height,
602649
+ imageWidth: dimensions?.width,
602650
+ imageHeight: dimensions?.height
602182
602651
  };
602183
602652
  }
602184
602653
  return {
@@ -602186,11 +602655,24 @@ async function buildImageAsciiPreview(inputPath, options2 = {}) {
602186
602655
  ascii: previewFailure(result.error || "renderer unavailable", width),
602187
602656
  renderer: "image-to-ascii",
602188
602657
  width,
602189
- height
602658
+ height,
602659
+ imageWidth: dimensions?.width,
602660
+ imageHeight: dimensions?.height
602190
602661
  };
602191
602662
  }
602192
602663
  const fallback = await convertWithFfmpeg(imagePath, width, height, timeoutMs);
602193
- 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
+ }
602194
602676
  return null;
602195
602677
  }
602196
602678
  function formatImageAsciiContext(preview, label) {
@@ -602309,10 +602791,45 @@ function parseJsonObjectFromText(value2) {
602309
602791
  const direct = parseJsonObject3(value2);
602310
602792
  if (direct) return direct;
602311
602793
  const text2 = String(value2 ?? "");
602312
- const start2 = text2.indexOf("{");
602313
- const end = text2.lastIndexOf("}");
602314
- if (start2 < 0 || end <= start2) return null;
602315
- 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;
602316
602833
  }
602317
602834
  function normalizeLiveCameraRotation(value2) {
602318
602835
  if (value2 === void 0 || value2 === null || value2 === "") return 0;
@@ -602404,32 +602921,88 @@ function truncateCell(value2, width) {
602404
602921
  function stripDashboardAnsi(value2) {
602405
602922
  return value2.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
602406
602923
  }
602407
- function dashboardPreviewLines(camera) {
602408
- const raw = camera.plainAscii || camera.ascii || "";
602409
- return raw.replace(/\r/g, "").split("\n").map((line) => stripDashboardAnsi(line).replace(/\s+$/g, "")).filter((line) => line.trim().length > 0).slice(0, 10);
602924
+ function dashboardVisibleWidth(value2) {
602925
+ return stripDashboardAnsi(value2).length;
602410
602926
  }
602411
- function centerDashboardCell(value2, width) {
602412
- const text2 = stripDashboardAnsi(String(value2 ?? "")).replace(/\r?\n/g, " ");
602413
- if (width <= 1) return text2.slice(0, Math.max(0, width));
602414
- const clipped = text2.length > width ? `${text2.slice(0, Math.max(1, width - 1))}…` : text2;
602415
- const left = Math.floor((width - clipped.length) / 2);
602416
- return `${" ".repeat(Math.max(0, left))}${clipped}${" ".repeat(Math.max(0, width - clipped.length - left))}`;
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
+ }
602954
+ function dashboardPreviewLines(camera) {
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);
602417
602957
  }
602418
602958
  function dashboardPreviewAspect(lines) {
602419
602959
  const widths = lines.map((line) => stripDashboardAnsi(line).length).filter((n2) => n2 > 0);
602420
602960
  const maxWidth = Math.max(1, ...widths);
602421
602961
  return Math.max(0.12, Math.min(1.6, lines.length / maxWidth));
602422
602962
  }
602423
- function fitDashboardPane(lines, paneWidth, paneHeight) {
602424
- const source = lines.length > 0 ? lines : ["preview pending"];
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
+ }
602971
+ function fitDashboardPane(lines, paneWidth, paneHeight, fallback) {
602972
+ const source = lines.length > 0 ? lines : [fallback];
602425
602973
  const normalized = [];
602426
602974
  for (let i2 = 0; i2 < paneHeight; i2++) {
602427
602975
  const sourceIndex = source.length <= paneHeight ? i2 : Math.min(source.length - 1, Math.floor(i2 * source.length / paneHeight));
602428
- normalized.push(truncateCell(source[sourceIndex] ?? "", paneWidth));
602976
+ normalized.push(truncateAnsiCell(source[sourceIndex] ?? "", paneWidth));
602429
602977
  }
602430
602978
  while (normalized.length < paneHeight) normalized.push(" ".repeat(paneWidth));
602431
602979
  return normalized;
602432
602980
  }
602981
+ function liveDashboardPaneLayout(width, cameraCount) {
602982
+ const innerWidth = Math.max(10, width - 2);
602983
+ const paneCount = Math.max(1, Math.min(cameraCount || 1, width >= 92 ? 2 : 1));
602984
+ const gutter = paneCount - 1;
602985
+ const paneWidth = Math.max(28, Math.floor((innerWidth - gutter) / paneCount));
602986
+ return { innerWidth, paneCount, paneWidth, paneContentWidth: Math.max(10, paneWidth - 2) };
602987
+ }
602988
+ function liveDashboardPreviewWidthForSources(sourceCount, termWidth = process.stdout.columns || 100) {
602989
+ const { paneContentWidth } = liveDashboardPaneLayout(Math.max(60, termWidth), sourceCount);
602990
+ return Math.max(42, Math.min(120, paneContentWidth));
602991
+ }
602992
+ function formatCameraPane(camera, paneWidth, paneHeight, now2) {
602993
+ const contentWidth = Math.max(10, paneWidth - 2);
602994
+ const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
602995
+ const title = `${compactSourceId(camera.source)} ${age}s${cameraPaneBadge(camera)}`;
602996
+ const topTitle = ` ${title} `;
602997
+ const left = Math.max(0, Math.floor((contentWidth - topTitle.length) / 2));
602998
+ const right = Math.max(0, contentWidth - topTitle.length - left);
602999
+ const top = `╭${"─".repeat(left)}${topTitle.slice(0, contentWidth)}${"─".repeat(right)}╮`;
603000
+ const bottom = `╰${"─".repeat(contentWidth)}╯`;
603001
+ const previewLines = dashboardPreviewLines(camera);
603002
+ const fallback = camera.framePath ? "preview refreshing" : camera.error ? "capture failed" : "awaiting frame";
603003
+ const body = fitDashboardPane(previewLines, contentWidth, paneHeight, fallback);
603004
+ return [top, ...body.map((line) => `│${truncateAnsiCell(line, contentWidth)}│`), bottom];
603005
+ }
602433
603006
  function pushDashboardWrapped(lines, value2, width) {
602434
603007
  const contentWidth = Math.max(10, width - 2);
602435
603008
  const words = stripDashboardAnsi(value2).replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
@@ -602457,6 +603030,160 @@ function isAudioFailureText(value2) {
602457
603030
  if (!text2) return false;
602458
603031
  return /^(?:ASR|VAD|Transcription)\s+(?:failed|unavailable)|No module named ['"]?transcribe_cli|torchcodec|torchaudio version/i.test(text2);
602459
603032
  }
603033
+ function readPcm16WavActivity(filePath, bins = 36) {
603034
+ try {
603035
+ const buf = readFileSync88(filePath);
603036
+ if (buf.length < 44 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") return void 0;
603037
+ let offset = 12;
603038
+ let channels = 1;
603039
+ let bitsPerSample = 16;
603040
+ let dataStart = -1;
603041
+ let dataSize = 0;
603042
+ while (offset + 8 <= buf.length) {
603043
+ const id2 = buf.toString("ascii", offset, offset + 4);
603044
+ const size = buf.readUInt32LE(offset + 4);
603045
+ const start2 = offset + 8;
603046
+ if (id2 === "fmt " && start2 + 16 <= buf.length) {
603047
+ channels = Math.max(1, buf.readUInt16LE(start2 + 2));
603048
+ bitsPerSample = buf.readUInt16LE(start2 + 14);
603049
+ } else if (id2 === "data") {
603050
+ dataStart = start2;
603051
+ dataSize = Math.min(size, buf.length - start2);
603052
+ break;
603053
+ }
603054
+ offset = start2 + size + size % 2;
603055
+ }
603056
+ if (dataStart < 0 || dataSize <= 0 || bitsPerSample !== 16) return void 0;
603057
+ const frameBytes = channels * 2;
603058
+ const frames = Math.floor(dataSize / frameBytes);
603059
+ if (frames <= 0) return void 0;
603060
+ let sumSquares = 0;
603061
+ let peak = 0;
603062
+ let active = 0;
603063
+ const bucketSquares = new Array(Math.max(1, bins)).fill(0);
603064
+ const bucketCounts = new Array(Math.max(1, bins)).fill(0);
603065
+ for (let i2 = 0; i2 < frames; i2++) {
603066
+ let sampleSum = 0;
603067
+ for (let ch = 0; ch < channels; ch++) {
603068
+ sampleSum += buf.readInt16LE(dataStart + i2 * frameBytes + ch * 2) / 32768;
603069
+ }
603070
+ const sample = sampleSum / channels;
603071
+ const abs = Math.abs(sample);
603072
+ peak = Math.max(peak, abs);
603073
+ sumSquares += sample * sample;
603074
+ if (abs > 0.018) active++;
603075
+ const b = Math.min(bucketSquares.length - 1, Math.floor(i2 / frames * bucketSquares.length));
603076
+ bucketSquares[b] += sample * sample;
603077
+ bucketCounts[b] += 1;
603078
+ }
603079
+ const rms = Math.sqrt(sumSquares / frames);
603080
+ const chars = "▁▂▃▄▅▆▇█";
603081
+ const waveform = bucketSquares.map((sum, idx) => {
603082
+ const count = Math.max(1, bucketCounts[idx] ?? 1);
603083
+ const level = Math.sqrt(sum / count);
603084
+ const normalized = Math.max(0, Math.min(1, level / Math.max(0.02, peak || 0.02)));
603085
+ return chars[Math.min(chars.length - 1, Math.round(normalized * (chars.length - 1)))] ?? "▁";
603086
+ }).join("");
603087
+ return {
603088
+ rms: Number(rms.toFixed(4)),
603089
+ peak: Number(peak.toFixed(4)),
603090
+ rmsDb: Number((20 * Math.log10(Math.max(1e-6, rms))).toFixed(1)),
603091
+ activeRatio: Number((active / frames).toFixed(3)),
603092
+ waveform
603093
+ };
603094
+ } catch {
603095
+ return void 0;
603096
+ }
603097
+ }
603098
+ function extractAsrBackend(output) {
603099
+ const match = String(output ?? "").match(/^Backend:\s*(.+)$/m);
603100
+ if (!match?.[1]) return void 0;
603101
+ const backend = match[1].trim();
603102
+ if (/managed openai-whisper|transcribe-cli|whisper/i.test(backend)) return backend;
603103
+ return backend.slice(0, 80);
603104
+ }
603105
+ function parseLiveAudioIntakeDecision(value2) {
603106
+ const parsed = parseJsonObjectFromText(value2);
603107
+ if (!parsed) return null;
603108
+ const intended = parsed["intended_for_agent"] ?? parsed["intendedForAgent"] ?? parsed["addressed_to_agent"];
603109
+ const confidence2 = Math.max(0, Math.min(1, Number(parsed["confidence"] ?? 0)));
603110
+ if (typeof intended !== "boolean" || !Number.isFinite(confidence2)) return null;
603111
+ const actionRaw = String(parsed["action"] ?? (intended ? "respond" : "ignore")).toLowerCase();
603112
+ const action = actionRaw === "respond" || actionRaw === "monitor" || actionRaw === "ignore" ? actionRaw : intended ? "respond" : "ignore";
603113
+ return {
603114
+ intendedForAgent: intended,
603115
+ confidence: confidence2,
603116
+ action,
603117
+ reason: trimOneLine(parsed["reason"] ?? "", 220) || "intake classifier decision"
603118
+ };
603119
+ }
603120
+ function fallbackLiveAudioIntakeDecision(transcript) {
603121
+ const text2 = transcript.toLowerCase();
603122
+ const addressed = /\b(omnius|hey\s+omnius|assistant|agent|computer)\b/.test(text2) || /\?$/.test(transcript.trim());
603123
+ return {
603124
+ intendedForAgent: addressed,
603125
+ confidence: addressed ? 0.58 : 0.35,
603126
+ action: addressed ? "respond" : "monitor",
603127
+ reason: addressed ? "fallback detected direct address or question shape" : "fallback could not establish that ambient speech was addressed to Omnius"
603128
+ };
603129
+ }
603130
+ async function classifyLiveAudioIntake(transcript, config) {
603131
+ const now2 = Date.now();
603132
+ const clean5 = transcript.trim();
603133
+ if (!clean5) {
603134
+ return {
603135
+ intendedForAgent: false,
603136
+ confidence: 1,
603137
+ action: "ignore",
603138
+ reason: "empty transcript",
603139
+ source: "fallback",
603140
+ updatedAt: now2
603141
+ };
603142
+ }
603143
+ if (config?.backendUrl && config.model) {
603144
+ const url = `${config.backendUrl.replace(/\/+$/, "")}/v1/chat/completions`;
603145
+ const body = {
603146
+ model: config.model,
603147
+ temperature: 0,
603148
+ max_tokens: 180,
603149
+ think: false,
603150
+ messages: [
603151
+ {
603152
+ role: "system",
603153
+ content: [
603154
+ "You are a live-audio intake classifier for Omnius.",
603155
+ "Decide whether a transcribed utterance is intended for the agent or is ambient/background speech.",
603156
+ 'Return only JSON: {"intended_for_agent": boolean, "confidence": 0.0-1.0, "action": "respond"|"ignore"|"monitor", "reason": "brief evidence"}.',
603157
+ "Do not answer the utterance. Do not treat all room speech as addressed to the agent."
603158
+ ].join("\n")
603159
+ },
603160
+ { role: "user", content: clean5.slice(0, 1200) }
603161
+ ]
603162
+ };
603163
+ try {
603164
+ const controller = new AbortController();
603165
+ const timer = setTimeout(() => controller.abort(), 6e3).unref();
603166
+ const resp = await fetch(url, {
603167
+ method: "POST",
603168
+ headers: {
603169
+ "Content-Type": "application/json",
603170
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
603171
+ },
603172
+ body: JSON.stringify(body),
603173
+ signal: controller.signal
603174
+ });
603175
+ clearTimeout(timer);
603176
+ if (resp.ok) {
603177
+ const json = await resp.json();
603178
+ const content = json.choices?.[0]?.message?.content ?? "";
603179
+ const parsed = parseLiveAudioIntakeDecision(content);
603180
+ if (parsed) return { ...parsed, source: "intake-agent", updatedAt: now2 };
603181
+ }
603182
+ } catch {
603183
+ }
603184
+ }
603185
+ return { ...fallbackLiveAudioIntakeDecision(clean5), source: "fallback", updatedAt: now2 };
603186
+ }
602460
603187
  function summarizeInference(value2) {
602461
603188
  if (!value2) return "objects: pending";
602462
603189
  const lines = value2.split("\n").map((line) => line.trim()).filter(Boolean);
@@ -602468,9 +603195,152 @@ function summarizeInference(value2) {
602468
603195
  const sampled = lines.find((line) => /^objects:/i.test(line));
602469
603196
  return sampled ? trimOneLine(sampled, 160) : trimOneLine(lines.slice(0, 4).join("; "), 160);
602470
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
+ }
602471
603341
  function cameraSnapshotSummary(camera) {
602472
603342
  if (camera.error) return `error: ${trimOneLine(camera.error, 160)}`;
602473
- return camera.summary || summarizeInference(camera.inference);
603343
+ return camera.summary || (camera.classifications?.length ? `classifications: ${cameraClassificationsSummary(camera)}` : "") || summarizeInference(camera.inference);
602474
603344
  }
602475
603345
  function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
602476
603346
  const width = Math.max(60, opts.width ?? 100);
@@ -602491,24 +603361,19 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
602491
603361
  }
602492
603362
  if (cameras.length > 0) {
602493
603363
  lines.push(`├${horizontal}┤`);
602494
- const paneCount = Math.min(cameras.length, width >= 116 ? 3 : width >= 78 ? 2 : 1);
602495
- const innerWidth = width - 2;
602496
- const gutter = paneCount - 1;
602497
- const paneWidth = Math.max(24, Math.floor((innerWidth - gutter) / paneCount));
603364
+ const { innerWidth, paneCount, paneWidth, paneContentWidth } = liveDashboardPaneLayout(width, cameras.length);
602498
603365
  for (let start2 = 0; start2 < cameras.length; start2 += paneCount) {
602499
603366
  const group = cameras.slice(start2, start2 + paneCount);
602500
603367
  const sourceLines = group.map((camera) => dashboardPreviewLines(camera));
602501
- const paneHeight = Math.max(7, Math.min(22, Math.round(paneWidth * Math.max(...sourceLines.map(dashboardPreviewAspect)))));
602502
- const panes = sourceLines.map((previewLines) => fitDashboardPane(previewLines, paneWidth, paneHeight));
602503
- for (let row = 0; row < paneHeight; row++) {
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)));
603370
+ const paneHeight = Math.max(10, Math.min(32, Math.round(paneContentWidth * aspect)));
603371
+ const panes = group.map((camera) => formatCameraPane(camera, paneWidth, paneHeight, now2));
603372
+ const rowCount = Math.max(...panes.map((pane) => pane.length));
603373
+ for (let row = 0; row < rowCount; row++) {
602504
603374
  const body = panes.map((pane) => pane[row] ?? " ".repeat(paneWidth)).join(" ");
602505
- lines.push(`│${truncateCell(body, innerWidth)}│`);
603375
+ lines.push(`│${truncateAnsiCell(body, innerWidth)}│`);
602506
603376
  }
602507
- const labels = group.map((camera) => {
602508
- const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
602509
- return centerDashboardCell(`${compactSourceId(camera.source)} age=${age}s`, paneWidth);
602510
- }).join(" ");
602511
- lines.push(`│${truncateCell(labels, innerWidth)}│`);
602512
603377
  }
602513
603378
  }
602514
603379
  if (cameras.length > 0) {
@@ -602518,8 +603383,10 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
602518
603383
  for (const camera of cameras) {
602519
603384
  const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
602520
603385
  const rotation = camera.orientation ? formatCameraRotation(camera.orientation.rotation) : "upright/no correction";
603386
+ const classifications = cameraClassificationsSummary(camera);
602521
603387
  const detailLines = [
602522
603388
  `${compactSourceId(camera.source)} age=${age}s rotation=${rotation}`,
603389
+ classifications ? `classifications: ${classifications}` : "",
602523
603390
  cameraSnapshotSummary(camera),
602524
603391
  camera.framePath ? `frame: ${camera.displayPath || camera.framePath}` : "",
602525
603392
  camera.orientation?.evidence ? `orientation: ${trimOneLine(camera.orientation.evidence, 120)}` : ""
@@ -602535,11 +603402,13 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
602535
603402
  const audioError = [snapshot.audio.error, transcriptFailure].filter(Boolean).join(" | ");
602536
603403
  const audioBits = [
602537
603404
  `audio input=${snapshot.audio.input || snapshot.config.selectedAudioInput || "none"}`,
603405
+ snapshot.audio.activity ? `activity ${snapshot.audio.activity.waveform} rms=${snapshot.audio.activity.rmsDb.toFixed(1)}dB active=${Math.round(snapshot.audio.activity.activeRatio * 100)}%` : "",
602538
603406
  audioError ? `error=${trimOneLine(audioError, 120)}` : "",
602539
- transcript ? `asr=${trimOneLine(transcript, 120)}` : "",
603407
+ transcript ? `asr${snapshot.audio.asrBackend ? `(${snapshot.audio.asrBackend})` : ""}=${trimOneLine(transcript, 120)}` : "",
603408
+ snapshot.audio.intake ? `intake=${snapshot.audio.intake.intendedForAgent ? "for-agent" : "ambient"} conf=${snapshot.audio.intake.confidence.toFixed(2)} ${snapshot.audio.intake.action}` : "",
602540
603409
  snapshot.audio.analysis ? `sound=${trimOneLine(snapshot.audio.analysis, 120)}` : ""
602541
603410
  ].filter(Boolean);
602542
- for (const item of audioBits.slice(0, 4)) lines.push(`│${truncateCell(` ${item}`, width - 2)}│`);
603411
+ for (const item of audioBits.slice(0, 6)) lines.push(`│${truncateCell(` ${item}`, width - 2)}│`);
602543
603412
  }
602544
603413
  lines.push(`╰${horizontal}╯`);
602545
603414
  return lines;
@@ -602547,7 +603416,7 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
602547
603416
  function parseLiveMediaPayload(value2) {
602548
603417
  const parsed = parseJsonObject3(value2);
602549
603418
  if (!parsed) return null;
602550
- 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;
602551
603420
  return parsed;
602552
603421
  }
602553
603422
  function observedAtFromOffset(sampledAt, payload, offsetSec) {
@@ -602644,6 +603513,21 @@ function observationsFromLiveMediaPayload(payload, sampledAt, fallbackSource) {
602644
603513
  evidence: identity3.evidence
602645
603514
  });
602646
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
+ }
602647
603531
  const faceCount = people.length;
602648
603532
  if (faceCount === 0) {
602649
603533
  const personTracks = (payload.tracks ?? []).filter((track) => String(track.label || "").toLowerCase() === "person").slice(0, 8);
@@ -602769,8 +603653,8 @@ function loadLiveSensorConfig(repoRoot) {
602769
603653
  ...DEFAULT_CONFIG7,
602770
603654
  ...parsed,
602771
603655
  cameraOrientation: sanitizeCameraOrientation(parsed.cameraOrientation),
602772
- videoIntervalMs: Math.max(1500, Number(parsed.videoIntervalMs ?? DEFAULT_CONFIG7.videoIntervalMs)),
602773
- 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))
602774
603658
  };
602775
603659
  } catch {
602776
603660
  return { ...DEFAULT_CONFIG7 };
@@ -602814,6 +603698,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
602814
603698
  const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
602815
603699
  const rotation = camera.orientation ? formatCameraRotation(camera.orientation.rotation) : "upright/no correction";
602816
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}`);
602817
603703
  const summary = cameraSnapshotSummary(camera);
602818
603704
  if (summary) lines.push(` summary: ${summary}`);
602819
603705
  }
@@ -602868,6 +603754,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
602868
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" : ""}`);
602869
603755
  if (snapshot.video.error) lines.push(`error: ${snapshot.video.error}`);
602870
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}`);
602871
603759
  if (snapshot.video.asciiContext) lines.push(snapshot.video.asciiContext);
602872
603760
  if (snapshot.video.inference) {
602873
603761
  lines.push("Video inference:");
@@ -602884,6 +603772,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
602884
603772
  lines.push(`timestamp: ${nowIso3(camera.updatedAt)} age=${Math.max(0, Math.round(age / 1e3))}s rotation=${rotation}${age > maxAgeMs ? " STALE" : ""}`);
602885
603773
  if (camera.error) lines.push(`error: ${camera.error}`);
602886
603774
  if (camera.framePath) lines.push(`frame: ${camera.framePath}`);
603775
+ const classifications = cameraClassificationsSummary(camera);
603776
+ if (classifications) lines.push(`classifications: ${classifications}`);
602887
603777
  lines.push(`summary: ${cameraSnapshotSummary(camera)}`);
602888
603778
  }
602889
603779
  }
@@ -602909,10 +603799,17 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
602909
603799
  lines.push(`timestamp: ${nowIso3(snapshot.audio.updatedAt)} age=${Math.max(0, Math.round(age / 1e3))}s${age > maxAgeMs ? " STALE" : ""}`);
602910
603800
  if (audioError) lines.push(`error: ${audioError}`);
602911
603801
  if (snapshot.audio.recordingPath) lines.push(`recording: ${snapshot.audio.recordingPath}`);
603802
+ if (snapshot.audio.activity) {
603803
+ lines.push(`activity: ${snapshot.audio.activity.waveform} rms_db=${snapshot.audio.activity.rmsDb.toFixed(1)} peak=${snapshot.audio.activity.peak.toFixed(3)} active=${Math.round(snapshot.audio.activity.activeRatio * 100)}%`);
603804
+ }
602912
603805
  if (transcript) {
602913
- lines.push("Live ASR:");
603806
+ lines.push(`Live ASR${snapshot.audio.asrBackend ? ` (${snapshot.audio.asrBackend})` : ""}:`);
602914
603807
  lines.push(cleanPreview(transcript, 1200));
602915
603808
  }
603809
+ if (snapshot.audio.intake) {
603810
+ lines.push(`Live audio intake: intended_for_agent=${snapshot.audio.intake.intendedForAgent ? "true" : "false"} confidence=${snapshot.audio.intake.confidence.toFixed(2)} action=${snapshot.audio.intake.action} source=${snapshot.audio.intake.source}`);
603811
+ lines.push(`intake_reason: ${snapshot.audio.intake.reason}`);
603812
+ }
602916
603813
  if (snapshot.audio.analysis) {
602917
603814
  lines.push("Live sound analysis:");
602918
603815
  lines.push(cleanPreview(snapshot.audio.analysis, 1600));
@@ -603127,13 +604024,17 @@ function formatLiveStatus(snapshot) {
603127
604024
  }
603128
604025
  return lines.join("\n");
603129
604026
  }
603130
- 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;
603131
604028
  var init_live_sensors = __esm({
603132
604029
  "packages/cli/src/tui/live-sensors.ts"() {
603133
604030
  "use strict";
603134
604031
  init_dist5();
603135
604032
  init_async_process();
603136
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;
603137
604038
  DEFAULT_CONFIG7 = {
603138
604039
  videoEnabled: false,
603139
604040
  audioEnabled: false,
@@ -603143,10 +604044,11 @@ var init_live_sensors = __esm({
603143
604044
  asrEnabled: false,
603144
604045
  audioAnalysisEnabled: false,
603145
604046
  cameraOrientation: {},
603146
- videoIntervalMs: 5e3,
603147
- audioIntervalMs: 9e3
604047
+ videoIntervalMs: 1e3,
604048
+ audioIntervalMs: 6e3
603148
604049
  };
603149
604050
  managers = /* @__PURE__ */ new Map();
604051
+ DASHBOARD_ANSI_STICKY_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/y;
603150
604052
  LiveSensorManager = class {
603151
604053
  constructor(repoRoot) {
603152
604054
  this.repoRoot = repoRoot;
@@ -603174,6 +604076,7 @@ var init_live_sensors = __esm({
603174
604076
  lastAgentReviewAt = 0;
603175
604077
  autoOrientationInFlight = /* @__PURE__ */ new Set();
603176
604078
  lastFeedbackAt = /* @__PURE__ */ new Map();
604079
+ intakeAgentConfig;
603177
604080
  setStatusSink(sink) {
603178
604081
  this.statusSink = sink ?? null;
603179
604082
  this.emitStatus();
@@ -603184,6 +604087,9 @@ var init_live_sensors = __esm({
603184
604087
  setAgentActionSink(sink) {
603185
604088
  this.agentActionSink = sink ?? null;
603186
604089
  }
604090
+ setAudioIntakeAgentConfig(config) {
604091
+ this.intakeAgentConfig = config && config.backendUrl && config.model ? { backendUrl: config.backendUrl, model: config.model, apiKey: config.apiKey } : void 0;
604092
+ }
603187
604093
  getConfig() {
603188
604094
  return { ...this.config };
603189
604095
  }
@@ -603348,10 +604254,11 @@ var init_live_sensors = __esm({
603348
604254
  this.config = {
603349
604255
  ...this.config,
603350
604256
  ...patch,
603351
- videoIntervalMs: Math.max(1500, Number(patch.videoIntervalMs ?? this.config.videoIntervalMs)),
603352
- 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))
603353
604259
  };
603354
604260
  this.persist();
604261
+ this.clearLoopTimers();
603355
604262
  this.ensureLoops();
603356
604263
  this.emitStatus();
603357
604264
  return this.getConfig();
@@ -603377,8 +604284,8 @@ var init_live_sensors = __esm({
603377
604284
  audioEnabled: true,
603378
604285
  asrEnabled: true,
603379
604286
  audioAnalysisEnabled: true,
603380
- videoIntervalMs: options2.lowLatency === false ? this.config.videoIntervalMs : Math.min(this.config.videoIntervalMs, 1500),
603381
- 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)
603382
604289
  });
603383
604290
  for (const source of this.activeVideoSources()) {
603384
604291
  const current = this.getCameraOrientation(source);
@@ -603479,7 +604386,7 @@ var init_live_sensors = __esm({
603479
604386
  const framePath = extractSavedImagePath(result.output, this.repoRoot);
603480
604387
  if (!framePath) return { ok: false, message: "Camera capture succeeded but no saved image path was returned." };
603481
604388
  const displayPath = relative11(this.repoRoot, framePath).startsWith("..") ? framePath : relative11(this.repoRoot, framePath);
603482
- const previewWidth = Math.max(48, Math.min(96, (process.stdout.columns || 100) - 14));
604389
+ const previewWidth = Math.max(42, Math.min(120, Math.floor(Number(options2.previewWidth ?? (process.stdout.columns || 100) - 14))));
603483
604390
  const preview = await buildImageAsciiPreview(framePath, { width: previewWidth });
603484
604391
  const asciiContext = preview ? formatImageAsciiContext(preview, displayPath) : void 0;
603485
604392
  const observedAt = Date.now();
@@ -603491,6 +604398,10 @@ var init_live_sensors = __esm({
603491
604398
  ascii: preview?.ascii,
603492
604399
  plainAscii: preview?.plainAscii,
603493
604400
  renderer: preview?.renderer,
604401
+ previewWidth: preview?.width,
604402
+ previewHeight: preview?.height,
604403
+ frameWidth: preview?.imageWidth,
604404
+ frameHeight: preview?.imageHeight,
603494
604405
  asciiContext,
603495
604406
  rotation,
603496
604407
  orientation
@@ -603501,7 +604412,8 @@ var init_live_sensors = __esm({
603501
604412
  [source]: {
603502
604413
  ...cameraView,
603503
604414
  summary: this.snapshot.cameras?.[source]?.summary,
603504
- inference: this.snapshot.cameras?.[source]?.inference
604415
+ inference: this.snapshot.cameras?.[source]?.inference,
604416
+ classifications: this.snapshot.cameras?.[source]?.classifications
603505
604417
  }
603506
604418
  };
603507
604419
  this.snapshot.events = mergeRecentById(this.snapshot.events, [{
@@ -603523,6 +604435,7 @@ var init_live_sensors = __esm({
603523
604435
  imagePath: framePath,
603524
604436
  displayPath,
603525
604437
  ascii: preview?.ascii,
604438
+ plainAscii: preview?.plainAscii,
603526
604439
  renderer: preview?.renderer
603527
604440
  });
603528
604441
  }
@@ -603532,7 +604445,12 @@ var init_live_sensors = __esm({
603532
604445
  framePath,
603533
604446
  displayPath,
603534
604447
  ascii: preview?.ascii,
603535
- renderer: preview?.renderer
604448
+ plainAscii: preview?.plainAscii,
604449
+ renderer: preview?.renderer,
604450
+ frameWidth: preview?.imageWidth,
604451
+ frameHeight: preview?.imageHeight,
604452
+ previewWidth: preview?.width,
604453
+ previewHeight: preview?.height
603536
604454
  };
603537
604455
  }
603538
604456
  async recognizeFrameObjects(framePath, source, observedAt = Date.now(), options2 = {}) {
@@ -603577,8 +604495,8 @@ var init_live_sensors = __esm({
603577
604495
  return message2;
603578
604496
  }
603579
604497
  }
603580
- async sampleSingleVideoNow(source, forceInfer) {
603581
- const preview = await this.previewCamera(source, { emit: false });
604498
+ async sampleSingleVideoNow(source, forceInfer, previewWidth) {
604499
+ const preview = await this.previewCamera(source, { emit: false, previewWidth });
603582
604500
  let inference = "";
603583
604501
  let clipSummary = "";
603584
604502
  const orientation = this.getCameraOrientation(source);
@@ -603589,20 +604507,27 @@ var init_live_sensors = __esm({
603589
604507
  action: "watch",
603590
604508
  source_kind: "camera",
603591
604509
  camera: source,
603592
- duration_sec: 2.5,
603593
- sample_interval_sec: 1,
603594
- 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,
603595
604516
  rotate_degrees: orientation?.rotation ?? 0,
603596
604517
  auto_bootstrap: true,
603597
604518
  audio_mode: "none",
603598
604519
  detect_objects: true,
603599
604520
  track_objects: true,
604521
+ segment_objects: true,
604522
+ extract_segments: true,
604523
+ max_segments_per_frame: 8,
603600
604524
  crop_faces: true,
603601
604525
  recognize_faces: true
603602
604526
  });
603603
604527
  inference = result.success ? String(result.output || result.llmContent || "") : `Video inference failed: ${result.error || result.output || "unknown error"}`;
603604
604528
  const livePayload = result.success ? parseLiveMediaPayload(result.llmContent || result.output) : null;
603605
604529
  const observations = observationsFromLiveMediaPayload(livePayload, sampledAt, source);
604530
+ const classifications = classificationsFromLiveMediaPayload(livePayload, sampledAt, source);
603606
604531
  this.snapshot.events = mergeRecentById(this.snapshot.events, observations.events, 50);
603607
604532
  this.snapshot.people = mergeRecentById(this.snapshot.people, observations.people, 24);
603608
604533
  const existing = this.snapshot.cameras?.[source] ?? this.snapshot.video ?? { updatedAt: Date.now(), source };
@@ -603612,6 +604537,7 @@ var init_live_sensors = __esm({
603612
604537
  source,
603613
604538
  inference,
603614
604539
  summary: summarizeInference(inference),
604540
+ classifications: mergeCameraClassifications(existing.classifications, classifications),
603615
604541
  rotation: orientation?.rotation ?? 0,
603616
604542
  orientation,
603617
604543
  ...preview.ok ? { error: void 0 } : { error: preview.message }
@@ -603650,6 +604576,10 @@ var init_live_sensors = __esm({
603650
604576
  const current = this.snapshot.cameras?.[source];
603651
604577
  if (current) {
603652
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
+ );
603653
604583
  this.snapshot.cameras = { ...this.snapshot.cameras ?? {}, [source]: current };
603654
604584
  if (this.snapshot.video?.source === source) this.snapshot.video = current;
603655
604585
  this.persist();
@@ -603667,8 +604597,9 @@ ${clipSummary}` : ""].filter(Boolean).join("\n");
603667
604597
  const sources = this.activeVideoSources();
603668
604598
  if (sources.length === 0) return "No camera selected. Use /live refresh after connecting hardware.";
603669
604599
  const outputs = [];
604600
+ const previewWidth = liveDashboardPreviewWidthForSources(sources.length);
603670
604601
  for (const source of sources) {
603671
- outputs.push(await this.sampleSingleVideoNow(source, forceInfer));
604602
+ outputs.push(await this.sampleSingleVideoNow(source, forceInfer, previewWidth));
603672
604603
  }
603673
604604
  this.emitDashboard();
603674
604605
  return outputs.map((output, index) => `## Camera ${sources[index]}
@@ -603686,7 +604617,9 @@ ${output}`).join("\n\n");
603686
604617
  ensureLiveDir(this.repoRoot);
603687
604618
  const recordingPath = join124(outputDir2, `audio-${Date.now()}.wav`);
603688
604619
  let transcript = "";
604620
+ let asrBackend = "";
603689
604621
  let analysis = "";
604622
+ let intake;
603690
604623
  const audioErrors = [];
603691
604624
  const capture = await recordAudioSampleWithRecovery(input, this.devices.audioInputs, 3, recordingPath);
603692
604625
  if (!capture.ok) {
@@ -603710,11 +604643,13 @@ ${output}`).join("\n\n");
603710
604643
  if (capture.device !== this.config.selectedAudioInput) {
603711
604644
  this.config.selectedAudioInput = capture.device;
603712
604645
  }
604646
+ const activity = readPcm16WavActivity(recordingPath);
603713
604647
  if (this.config.asrEnabled) {
603714
604648
  try {
603715
604649
  const asr = await new TranscribeFileTool(this.repoRoot).execute({ path: recordingPath, model: "tiny" });
603716
604650
  if (asr.success) {
603717
604651
  transcript = cleanPreview(typeof asr.llmContent === "string" ? asr.llmContent : "", 1600);
604652
+ asrBackend = extractAsrBackend(asr.output) ?? "whisper";
603718
604653
  } else {
603719
604654
  audioErrors.push(`ASR unavailable: ${asr.error || asr.output || "unknown error"}`);
603720
604655
  }
@@ -603740,13 +604675,19 @@ ${output}`).join("\n\n");
603740
604675
  }
603741
604676
  analysis = parts.filter(Boolean).join("\n");
603742
604677
  }
604678
+ if (transcript) {
604679
+ intake = await classifyLiveAudioIntake(transcript, this.intakeAgentConfig);
604680
+ }
603743
604681
  this.snapshot.audio = {
603744
604682
  updatedAt: Date.now(),
603745
604683
  input: capture.device,
603746
604684
  output: this.config.selectedAudioOutput,
603747
604685
  recordingPath,
603748
604686
  transcript,
604687
+ asrBackend: asrBackend || void 0,
603749
604688
  analysis,
604689
+ activity,
604690
+ intake,
603750
604691
  error: audioErrors.length > 0 ? audioErrors.join("\n") : void 0
603751
604692
  };
603752
604693
  const audioEvents = [];
@@ -603774,12 +604715,20 @@ ${output}`).join("\n\n");
603774
604715
  }
603775
604716
  this.snapshot.events = mergeRecentById(this.snapshot.events, audioEvents, 50);
603776
604717
  this.persist();
604718
+ if (transcript && intake?.intendedForAgent) {
604719
+ this.maybeRequestAgentReview(
604720
+ "Live audio intake classified speech as intended for Omnius",
604721
+ `transcript=${transcript}; confidence=${intake.confidence.toFixed(2)}; reason=${intake.reason}`
604722
+ );
604723
+ }
603777
604724
  this.emitDashboard();
603778
604725
  return [
603779
604726
  `Audio sample captured from ${capture.device}${capture.device !== input ? ` (recovered from ${input})` : ""}: ${recordingPath}`,
603780
604727
  transcript ? `
603781
- ASR:
604728
+ ASR${asrBackend ? ` (${asrBackend})` : ""}:
603782
604729
  ${transcript}` : "",
604730
+ intake ? `
604731
+ Intake: ${intake.intendedForAgent ? "intended for agent" : "ambient/monitor"} confidence=${intake.confidence.toFixed(2)} (${intake.source})` : "",
603783
604732
  analysis ? `
603784
604733
  Sound analysis:
603785
604734
  ${analysis}` : ""
@@ -603788,30 +604737,63 @@ ${analysis}` : ""
603788
604737
  this.audioInFlight = false;
603789
604738
  }
603790
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
+ }
603791
604785
  ensureLoops() {
603792
604786
  if (this.config.videoEnabled && !this.videoTimer) {
603793
- void this.sampleVideoNow(this.config.inferEnabled || this.config.clipEnabled).catch(() => {
603794
- });
603795
- this.videoTimer = setInterval(() => {
603796
- void this.sampleVideoNow(this.config.inferEnabled || this.config.clipEnabled).catch(() => {
603797
- });
603798
- }, this.config.videoIntervalMs);
603799
- this.videoTimer.unref?.();
604787
+ this.scheduleVideoLoop(0);
603800
604788
  } else if (!this.config.videoEnabled && this.videoTimer) {
603801
- clearInterval(this.videoTimer);
604789
+ clearTimeout(this.videoTimer);
603802
604790
  this.videoTimer = null;
603803
604791
  }
603804
604792
  const wantsAudio = this.config.audioEnabled && (this.config.asrEnabled || this.config.audioAnalysisEnabled);
603805
604793
  if (wantsAudio && !this.audioTimer) {
603806
- void this.sampleAudioNow().catch(() => {
603807
- });
603808
- this.audioTimer = setInterval(() => {
603809
- void this.sampleAudioNow().catch(() => {
603810
- });
603811
- }, this.config.audioIntervalMs);
603812
- this.audioTimer.unref?.();
604794
+ this.scheduleAudioLoop(0);
603813
604795
  } else if (!wantsAudio && this.audioTimer) {
603814
- clearInterval(this.audioTimer);
604796
+ clearTimeout(this.audioTimer);
603815
604797
  this.audioTimer = null;
603816
604798
  }
603817
604799
  }
@@ -624395,15 +625377,6 @@ var init_status_bar = __esm({
624395
625377
  const cmdPrefix = cmd.startsWith("view:") ? "omnius-view:" + cmd.slice(5) : "omnius-cmd:" + cmd;
624396
625378
  return `\x1B]8;;${cmdPrefix}\x07${label}\x1B]8;;\x07`;
624397
625379
  };
624398
- const buttonFg = (cmd) => {
624399
- let fg2 = TEXT_DIM;
624400
- if (cmd === "voice" && this._voiceActive) fg2 = 82;
624401
- if (cmd.startsWith("live") && (this._liveMediaStatus.audio || this._liveMediaStatus.video)) fg2 = 82;
624402
- if (cmd === "nexus") {
624403
- fg2 = this._nexusStatus === "connected" ? 82 : this._nexusStatus === "connecting" ? 208 : 196;
624404
- }
624405
- return fg2;
624406
- };
624407
625380
  const decorateMenuButton = (cmd, label) => {
624408
625381
  return `${HEADER_BUTTON_GLYPH_FG}${HEADER_BUTTON_LEFT}\x1B[0m${HEADER_BUTTON_BG}${HEADER_BUTTON_FG}${label}\x1B[0m${PANEL_BG_SEQ}${HEADER_BUTTON_GLYPH_FG}${HEADER_BUTTON_RIGHT}\x1B[0m${PANEL_BG_SEQ}`;
624409
625382
  };
@@ -624414,14 +625387,7 @@ var init_status_bar = __esm({
624414
625387
  return `\x1B[38;5;${color}m${HEADER_BUTTON_LEFT}\x1B[0m${weight}${fg2}${bg}${content}\x1B[0m${PANEL_BG_SEQ}\x1B[38;5;${color}m${HEADER_BUTTON_RIGHT}\x1B[0m${PANEL_BG_SEQ}`;
624415
625388
  };
624416
625389
  const renderBtn = (cmd, label) => {
624417
- const fg2 = buttonFg(cmd);
624418
- const btnW = label.length + 2;
624419
- const availW2 = getTermWidth() - identity3.width - 1;
624420
- if (btnW > availW2) return linkify(cmd, `\x1B[38;5;${fg2}m${label}`);
624421
- return linkify(
624422
- cmd,
624423
- `${PANEL_BG_SEQ}${HEADER_BUTTON_GLYPH_FG}${HEADER_BUTTON_LEFT}\x1B[0m${PANEL_BG_SEQ}\x1B[38;5;${fg2}m${label}\x1B[0m${PANEL_BG_SEQ}${HEADER_BUTTON_GLYPH_FG}${HEADER_BUTTON_RIGHT}\x1B[0m${PANEL_BG_SEQ}`
624424
- );
625390
+ return linkify(cmd, decorateMenuButton(cmd, label));
624425
625391
  };
624426
625392
  const identity3 = this.buildHeaderIdentityRender();
624427
625393
  const modelLabel = this.summarizeHeaderModelName() || "model";
@@ -624494,10 +625460,9 @@ var init_status_bar = __esm({
624494
625460
  };
624495
625461
  if (this._activeViewId !== "main") {
624496
625462
  const mainLabel = ` ↩ main `;
624497
- const mainColored = `\x1B[38;5;110m${mainLabel}\x1B[0m`;
624498
625463
  sysItems.push({
624499
- render: () => mainColored + " ",
624500
- w: mainLabel.length + 1
625464
+ render: () => linkify("view:main", decorateMenuButton("view:main", mainLabel)) + " ",
625465
+ w: mainLabel.length + 2 + 1
624501
625466
  });
624502
625467
  }
624503
625468
  if (this._agentViews.size > 1) {
@@ -624535,10 +625500,7 @@ var init_status_bar = __esm({
624535
625500
  const telegramDot = this._telegramStatus.active ? "●" : "○";
624536
625501
  const telegramLabel = this._telegramStatus.activeSubAgents > 0 ? ` ✈ tg ${this._telegramStatus.activeSubAgents} ` : " ✈ tg ";
624537
625502
  sysItems.push({
624538
- render: () => renderBtn(
624539
- "telegram",
624540
- `${HEADER_TELEGRAM_FG}${telegramDot}${telegramLabel}\x1B[0m`
624541
- ) + " ",
625503
+ render: () => renderBtn("telegram", `${telegramDot}${telegramLabel}`) + " ",
624542
625504
  w: telegramLabel.length + 2
624543
625505
  });
624544
625506
  const liveMediaActive = this._liveMediaStatus.audio || this._liveMediaStatus.video;
@@ -624683,7 +625645,7 @@ var init_status_bar = __esm({
624683
625645
  const trunc3 = (s2) => s2.trim().split(/\s+/).slice(0, 3).join(" ");
624684
625646
  if (this._activeViewId !== "main") {
624685
625647
  const mainLabel = ` ↩ main `;
624686
- zones.push({ w: mainLabel.length + 1, id: "main", render: () => "" });
625648
+ zones.push({ w: mainLabel.length + 2 + 1, id: "main", render: () => "" });
624687
625649
  }
624688
625650
  if (this._agentViews.size > 1) {
624689
625651
  for (const view of this._agentViews.values()) {
@@ -655606,6 +656568,11 @@ ${feedback.message}`);
655606
656568
  function attachLiveSinks(ctx3, manager, agentEnabled = false) {
655607
656569
  manager.setStatusSink(ctx3.setLiveMediaStatus);
655608
656570
  manager.setFeedbackSink((feedback) => renderLiveFeedback(ctx3, manager, feedback));
656571
+ manager.setAudioIntakeAgentConfig({
656572
+ backendUrl: ctx3.config.backendUrl,
656573
+ model: ctx3.config.model,
656574
+ apiKey: ctx3.config.apiKey
656575
+ });
655609
656576
  if (agentEnabled && ctx3.startBackgroundPrompt) {
655610
656577
  manager.setAgentActionSink((prompt) => {
655611
656578
  const id2 = ctx3.startBackgroundPrompt?.(prompt);