omnius 1.0.410 → 1.0.412
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 +1244 -97
- package/npm-shrinkwrap.json +14 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -41456,17 +41456,30 @@ function isTranscribable(path12) {
|
|
|
41456
41456
|
const ext = extname4(path12).toLowerCase();
|
|
41457
41457
|
return AUDIO_EXTS.has(ext) || VIDEO_EXTS.has(ext);
|
|
41458
41458
|
}
|
|
41459
|
+
async function requireTranscribeCliFrom(packageDir) {
|
|
41460
|
+
const { createRequire: createRequire11 } = await import("node:module");
|
|
41461
|
+
const req3 = createRequire11(import.meta.url);
|
|
41462
|
+
const distPath = join40(packageDir, "dist", "index.js");
|
|
41463
|
+
if (existsSync38(distPath))
|
|
41464
|
+
return req3(distPath);
|
|
41465
|
+
return req3(packageDir);
|
|
41466
|
+
}
|
|
41459
41467
|
async function loadTranscribeCli() {
|
|
41460
41468
|
if (_tcChecked)
|
|
41461
41469
|
return _tcModule;
|
|
41462
41470
|
_tcChecked = true;
|
|
41471
|
+
try {
|
|
41472
|
+
const { createRequire: createRequire11 } = await import("node:module");
|
|
41473
|
+
const req3 = createRequire11(import.meta.url);
|
|
41474
|
+
_tcModule = req3("transcribe-cli");
|
|
41475
|
+
return _tcModule;
|
|
41476
|
+
} catch {
|
|
41477
|
+
}
|
|
41463
41478
|
try {
|
|
41464
41479
|
const globalRoot = (await execFileText("npm", ["root", "-g"], { timeout: 5e3 })).trim();
|
|
41465
41480
|
const tcPath = join40(globalRoot, "transcribe-cli");
|
|
41466
41481
|
if (existsSync38(join40(tcPath, "dist", "index.js"))) {
|
|
41467
|
-
|
|
41468
|
-
const req3 = createRequire11(import.meta.url);
|
|
41469
|
-
_tcModule = req3(join40(tcPath, "dist", "index.js"));
|
|
41482
|
+
_tcModule = await requireTranscribeCliFrom(tcPath);
|
|
41470
41483
|
return _tcModule;
|
|
41471
41484
|
}
|
|
41472
41485
|
} catch {
|
|
@@ -41478,9 +41491,7 @@ async function loadTranscribeCli() {
|
|
|
41478
41491
|
for (const ver of readdirSync61(nvmBase)) {
|
|
41479
41492
|
const tcPath = join40(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
|
|
41480
41493
|
if (existsSync38(join40(tcPath, "dist", "index.js"))) {
|
|
41481
|
-
|
|
41482
|
-
const req3 = createRequire11(import.meta.url);
|
|
41483
|
-
_tcModule = req3(join40(tcPath, "dist", "index.js"));
|
|
41494
|
+
_tcModule = await requireTranscribeCliFrom(tcPath);
|
|
41484
41495
|
return _tcModule;
|
|
41485
41496
|
}
|
|
41486
41497
|
}
|
|
@@ -41489,6 +41500,58 @@ async function loadTranscribeCli() {
|
|
|
41489
41500
|
}
|
|
41490
41501
|
return null;
|
|
41491
41502
|
}
|
|
41503
|
+
async function ensureManagedTranscribeCliRuntime() {
|
|
41504
|
+
const packageDir = join40(MANAGED_TRANSCRIBE_CLI_DIR, "node_modules", "transcribe-cli");
|
|
41505
|
+
if (existsSync38(join40(packageDir, "dist", "index.js"))) {
|
|
41506
|
+
try {
|
|
41507
|
+
_managedTranscribeCliReady = true;
|
|
41508
|
+
_tcModule = await requireTranscribeCliFrom(packageDir);
|
|
41509
|
+
_tcChecked = true;
|
|
41510
|
+
return _tcModule;
|
|
41511
|
+
} catch {
|
|
41512
|
+
}
|
|
41513
|
+
}
|
|
41514
|
+
if (_managedTranscribeCliChecked && !_managedTranscribeCliReady)
|
|
41515
|
+
return null;
|
|
41516
|
+
_managedTranscribeCliChecked = true;
|
|
41517
|
+
try {
|
|
41518
|
+
mkdirSync22(MANAGED_TRANSCRIBE_CLI_DIR, { recursive: true });
|
|
41519
|
+
if (!existsSync38(join40(MANAGED_TRANSCRIBE_CLI_DIR, "package.json"))) {
|
|
41520
|
+
writeFileSync21(join40(MANAGED_TRANSCRIBE_CLI_DIR, "package.json"), JSON.stringify({ private: true, dependencies: {} }, null, 2), "utf8");
|
|
41521
|
+
}
|
|
41522
|
+
await execFileText("npm", ["install", "--prefix", MANAGED_TRANSCRIBE_CLI_DIR, "transcribe-cli@^2.0.1"], {
|
|
41523
|
+
timeout: 18e4,
|
|
41524
|
+
env: transcriptionPythonEnv()
|
|
41525
|
+
});
|
|
41526
|
+
_managedTranscribeCliReady = true;
|
|
41527
|
+
_tcModule = await requireTranscribeCliFrom(packageDir);
|
|
41528
|
+
_tcChecked = true;
|
|
41529
|
+
return _tcModule;
|
|
41530
|
+
} catch {
|
|
41531
|
+
_managedTranscribeCliReady = false;
|
|
41532
|
+
return null;
|
|
41533
|
+
}
|
|
41534
|
+
}
|
|
41535
|
+
async function ensureTranscribeCliPythonModule(python) {
|
|
41536
|
+
if (_transcribeCliPythonModuleChecked)
|
|
41537
|
+
return;
|
|
41538
|
+
_transcribeCliPythonModuleChecked = true;
|
|
41539
|
+
try {
|
|
41540
|
+
await execFileText(python, ["-c", "import transcribe_cli"], {
|
|
41541
|
+
timeout: 1e4,
|
|
41542
|
+
env: transcriptionPythonEnv()
|
|
41543
|
+
});
|
|
41544
|
+
return;
|
|
41545
|
+
} catch {
|
|
41546
|
+
}
|
|
41547
|
+
try {
|
|
41548
|
+
await execFileText(python, ["-m", "pip", "install", "-U", "transcribe-cli"], {
|
|
41549
|
+
timeout: 3e5,
|
|
41550
|
+
env: transcriptionPythonEnv()
|
|
41551
|
+
});
|
|
41552
|
+
} catch {
|
|
41553
|
+
}
|
|
41554
|
+
}
|
|
41492
41555
|
function isYouTubeUrl(url) {
|
|
41493
41556
|
return /(?:youtube\.com\/(?:watch|shorts|live|embed|v\/)|youtu\.be\/)/i.test(url);
|
|
41494
41557
|
}
|
|
@@ -41519,7 +41582,7 @@ function formatTime(seconds) {
|
|
|
41519
41582
|
const s2 = Math.floor(seconds % 60);
|
|
41520
41583
|
return `${String(m2).padStart(2, "0")}:${String(s2).padStart(2, "0")}`;
|
|
41521
41584
|
}
|
|
41522
|
-
var AUDIO_EXTS, VIDEO_EXTS, MAX_TRANSCRIBE_URL_BYTES, MANAGED_ASR_VENV, _tcModule, _tcChecked, _managedAsrReady, TranscribeFileTool, YT_DLP_VENV, TranscribeUrlTool, YouTubeDownloadTool;
|
|
41585
|
+
var AUDIO_EXTS, VIDEO_EXTS, MAX_TRANSCRIBE_URL_BYTES, MANAGED_ASR_VENV, MANAGED_TRANSCRIBE_CLI_DIR, _tcModule, _tcChecked, _managedAsrReady, _managedTranscribeCliChecked, _managedTranscribeCliReady, _transcribeCliPythonModuleChecked, TranscribeFileTool, YT_DLP_VENV, TranscribeUrlTool, YouTubeDownloadTool;
|
|
41523
41586
|
var init_transcribe_tool = __esm({
|
|
41524
41587
|
"packages/execution/dist/tools/transcribe-tool.js"() {
|
|
41525
41588
|
"use strict";
|
|
@@ -41551,9 +41614,13 @@ var init_transcribe_tool = __esm({
|
|
|
41551
41614
|
]);
|
|
41552
41615
|
MAX_TRANSCRIBE_URL_BYTES = 100 * 1024 * 1024;
|
|
41553
41616
|
MANAGED_ASR_VENV = join40(homedir11(), ".omnius", "runtimes", "asr", ".venv-whisper");
|
|
41617
|
+
MANAGED_TRANSCRIBE_CLI_DIR = join40(homedir11(), ".omnius", "runtimes", "asr", "transcribe-cli-node");
|
|
41554
41618
|
_tcModule = null;
|
|
41555
41619
|
_tcChecked = false;
|
|
41556
41620
|
_managedAsrReady = false;
|
|
41621
|
+
_managedTranscribeCliChecked = false;
|
|
41622
|
+
_managedTranscribeCliReady = false;
|
|
41623
|
+
_transcribeCliPythonModuleChecked = false;
|
|
41557
41624
|
TranscribeFileTool = class {
|
|
41558
41625
|
name = "transcribe_file";
|
|
41559
41626
|
description = "Transcribe a local audio or video file to text using Whisper (faster-whisper). Supports MP3, WAV, FLAC, AAC, M4A, OGG, MP4, MKV, AVI, MOV, WebM. Returns the full transcription text with optional speaker diarization. Transcription is 100% local — no API keys needed.";
|
|
@@ -41628,7 +41695,15 @@ var init_transcribe_tool = __esm({
|
|
|
41628
41695
|
if (effectiveModel !== askedModel)
|
|
41629
41696
|
model = effectiveModel;
|
|
41630
41697
|
const failures = [];
|
|
41631
|
-
|
|
41698
|
+
let managedPython = "";
|
|
41699
|
+
try {
|
|
41700
|
+
managedPython = await this.ensureManagedWhisperRuntime();
|
|
41701
|
+
process.env.TRANSCRIBE_PYTHON = managedPython;
|
|
41702
|
+
await ensureTranscribeCliPythonModule(managedPython);
|
|
41703
|
+
} catch (err) {
|
|
41704
|
+
failures.push(`managed ASR runtime bootstrap: ${err instanceof Error ? err.message : String(err)}`);
|
|
41705
|
+
}
|
|
41706
|
+
const tc = await loadTranscribeCli() ?? await ensureManagedTranscribeCliRuntime();
|
|
41632
41707
|
if (tc) {
|
|
41633
41708
|
try {
|
|
41634
41709
|
const result = await withProcessEnv(transcriptionPythonEnv(), () => tc.transcribe(filePath, {
|
|
@@ -41641,6 +41716,8 @@ var init_transcribe_tool = __esm({
|
|
|
41641
41716
|
} catch (err) {
|
|
41642
41717
|
failures.push(`transcribe-cli module: ${err instanceof Error ? err.message : String(err)}`);
|
|
41643
41718
|
}
|
|
41719
|
+
} else {
|
|
41720
|
+
failures.push("transcribe-cli npm package unavailable after managed auto-install");
|
|
41644
41721
|
}
|
|
41645
41722
|
const cli = await this.execViaCli(filePath, model, diarize, start2);
|
|
41646
41723
|
if (cli.success)
|
|
@@ -555654,6 +555731,169 @@ function buildYolo26BootstrapPlan(model = DEFAULT_YOLO26_MODEL) {
|
|
|
555654
555731
|
packages: ["ultralytics>=8.4.0", "opencv-python-headless", "yt-dlp", "numpy"]
|
|
555655
555732
|
};
|
|
555656
555733
|
}
|
|
555734
|
+
function buildYolo26DeploymentOptions() {
|
|
555735
|
+
const yolo26Models = YOLO26_SCALES.flatMap((scale) => Object.values(YOLO26_TASKS).map((task) => `yolo26${scale}${task.suffix}.pt`));
|
|
555736
|
+
const yoloeModels = YOLO26_SCALES.flatMap((scale) => [`yoloe-26${scale}-seg.pt`, `yoloe-26${scale}-seg-pf.pt`]);
|
|
555737
|
+
return {
|
|
555738
|
+
defaultModel: DEFAULT_YOLO26_MODEL,
|
|
555739
|
+
scales: [...YOLO26_SCALES],
|
|
555740
|
+
tasks: YOLO26_TASKS,
|
|
555741
|
+
families: {
|
|
555742
|
+
yolo26: {
|
|
555743
|
+
description: "YOLO26 closed-vocabulary models for detection, segmentation, semantic segmentation, pose, OBB, and classification.",
|
|
555744
|
+
models: yolo26Models
|
|
555745
|
+
},
|
|
555746
|
+
"yoloe-26": {
|
|
555747
|
+
description: "YOLOE-26 open-vocabulary segmentation models for text, visual, or prompt-free detection/segmentation workflows.",
|
|
555748
|
+
models: yoloeModels
|
|
555749
|
+
}
|
|
555750
|
+
},
|
|
555751
|
+
yoloePromptModes: [
|
|
555752
|
+
{ mode: "text", description: "Set open-vocabulary classes with model.set_classes([...]) before prediction or export." },
|
|
555753
|
+
{ mode: "prompt_free", description: "Use YOLOE prompt-free weights such as yoloe-26s-seg-pf.pt for built-in large-vocabulary discovery." },
|
|
555754
|
+
{ mode: "visual", description: "Use YOLOE visual prompts where supported by the installed Ultralytics version; exported prompts are static." }
|
|
555755
|
+
],
|
|
555756
|
+
exportFormats: YOLO26_EXPORT_FORMATS,
|
|
555757
|
+
headModes: [
|
|
555758
|
+
{ value: true, name: "end2end", description: "YOLO26 default one-to-one head; NMS-free output optimized for deployment latency." },
|
|
555759
|
+
{ value: false, name: "one-to-many", description: "Traditional output requiring NMS post-processing; useful for compatibility or accuracy-sensitive pipelines." }
|
|
555760
|
+
],
|
|
555761
|
+
exportArguments: [
|
|
555762
|
+
"format",
|
|
555763
|
+
"imgsz",
|
|
555764
|
+
"quantize",
|
|
555765
|
+
"dynamic",
|
|
555766
|
+
"simplify",
|
|
555767
|
+
"opset",
|
|
555768
|
+
"workspace",
|
|
555769
|
+
"nms",
|
|
555770
|
+
"batch",
|
|
555771
|
+
"device",
|
|
555772
|
+
"data",
|
|
555773
|
+
"fraction",
|
|
555774
|
+
"end2end",
|
|
555775
|
+
"optimize",
|
|
555776
|
+
"keras",
|
|
555777
|
+
"name"
|
|
555778
|
+
]
|
|
555779
|
+
};
|
|
555780
|
+
}
|
|
555781
|
+
function normalizeYolo26Scale(value2) {
|
|
555782
|
+
const scale = String(value2 ?? "n").trim().toLowerCase();
|
|
555783
|
+
return YOLO26_SCALES.includes(scale) ? scale : "n";
|
|
555784
|
+
}
|
|
555785
|
+
function normalizeYolo26Task(value2) {
|
|
555786
|
+
const raw = String(value2 ?? "detect").trim().toLowerCase();
|
|
555787
|
+
const aliases = {
|
|
555788
|
+
detection: "detect",
|
|
555789
|
+
det: "detect",
|
|
555790
|
+
instance: "segment",
|
|
555791
|
+
segmentation: "segment",
|
|
555792
|
+
seg: "segment",
|
|
555793
|
+
sem: "semantic",
|
|
555794
|
+
semantic_segmentation: "semantic",
|
|
555795
|
+
keypoint: "pose",
|
|
555796
|
+
keypoints: "pose",
|
|
555797
|
+
classify: "classify",
|
|
555798
|
+
classification: "classify",
|
|
555799
|
+
cls: "classify",
|
|
555800
|
+
oriented: "obb",
|
|
555801
|
+
oriented_detection: "obb"
|
|
555802
|
+
};
|
|
555803
|
+
const normalized = aliases[raw] ?? raw;
|
|
555804
|
+
return Object.prototype.hasOwnProperty.call(YOLO26_TASKS, normalized) ? normalized : "detect";
|
|
555805
|
+
}
|
|
555806
|
+
function normalizeYolo26Family(value2) {
|
|
555807
|
+
const raw = String(value2 ?? "yolo26").trim().toLowerCase();
|
|
555808
|
+
if (["yoloe", "yoloe26", "yoloe-26", "open-vocabulary", "open_vocab"].includes(raw))
|
|
555809
|
+
return "yoloe-26";
|
|
555810
|
+
return "yolo26";
|
|
555811
|
+
}
|
|
555812
|
+
function resolveYolo26Model(args) {
|
|
555813
|
+
const explicit = args["yolo_model"] ?? args["model"];
|
|
555814
|
+
if (typeof explicit === "string" && explicit.trim())
|
|
555815
|
+
return explicit.trim();
|
|
555816
|
+
const family = normalizeYolo26Family(args["yolo_family"] ?? args["family"] ?? args["variant"]);
|
|
555817
|
+
const scale = normalizeYolo26Scale(args["yolo_scale"] ?? args["scale"]);
|
|
555818
|
+
const promptMode = String(args["yoloe_prompt_mode"] ?? args["prompt_mode"] ?? "").trim().toLowerCase();
|
|
555819
|
+
const promptFree = promptMode === "prompt_free" || promptMode === "prompt-free" || promptMode === "pf" || args["prompt_free"] === true;
|
|
555820
|
+
if (family === "yoloe-26")
|
|
555821
|
+
return `yoloe-26${scale}-seg${promptFree ? "-pf" : ""}.pt`;
|
|
555822
|
+
const task = normalizeYolo26Task(args["yolo_task"] ?? args["task"]);
|
|
555823
|
+
return `yolo26${scale}${YOLO26_TASKS[task].suffix}.pt`;
|
|
555824
|
+
}
|
|
555825
|
+
function normalizeYolo26ExportFormat(value2) {
|
|
555826
|
+
const raw = String(value2 ?? "onnx").trim().toLowerCase().replace(/-/g, "_");
|
|
555827
|
+
const aliases = {
|
|
555828
|
+
trt: "engine",
|
|
555829
|
+
tensorrt: "engine",
|
|
555830
|
+
tf_saved_model: "saved_model",
|
|
555831
|
+
savedmodel: "saved_model",
|
|
555832
|
+
tensorflow_saved_model: "saved_model",
|
|
555833
|
+
graphdef: "pb",
|
|
555834
|
+
edge_tpu: "edgetpu",
|
|
555835
|
+
tflite: "litert",
|
|
555836
|
+
tensorflow_lite: "litert",
|
|
555837
|
+
lite_rt: "litert",
|
|
555838
|
+
torch: "pytorch",
|
|
555839
|
+
pt: "pytorch"
|
|
555840
|
+
};
|
|
555841
|
+
const normalized = aliases[raw] ?? raw;
|
|
555842
|
+
if (YOLO26_EXPORT_FORMATS.some((entry) => entry.format === normalized))
|
|
555843
|
+
return normalized;
|
|
555844
|
+
throw new Error(`Unsupported YOLO26 export format: ${String(value2)}. Use one of: ${YOLO26_EXPORT_FORMATS.map((entry) => entry.format).join(", ")}`);
|
|
555845
|
+
}
|
|
555846
|
+
function optionalBoolean(args, key) {
|
|
555847
|
+
if (!(key in args) || args[key] === void 0 || args[key] === null || args[key] === "")
|
|
555848
|
+
return void 0;
|
|
555849
|
+
if (typeof args[key] === "boolean")
|
|
555850
|
+
return args[key];
|
|
555851
|
+
const raw = String(args[key]).trim().toLowerCase();
|
|
555852
|
+
if (["1", "true", "yes", "on"].includes(raw))
|
|
555853
|
+
return true;
|
|
555854
|
+
if (["0", "false", "no", "off"].includes(raw))
|
|
555855
|
+
return false;
|
|
555856
|
+
return void 0;
|
|
555857
|
+
}
|
|
555858
|
+
function optionalNumber(args, key) {
|
|
555859
|
+
if (!(key in args) || args[key] === void 0 || args[key] === null || args[key] === "")
|
|
555860
|
+
return void 0;
|
|
555861
|
+
const n2 = Number(args[key]);
|
|
555862
|
+
return Number.isFinite(n2) ? n2 : void 0;
|
|
555863
|
+
}
|
|
555864
|
+
function exportOptionsFromArgs(args, format3) {
|
|
555865
|
+
const options2 = { format: format3 };
|
|
555866
|
+
for (const key of ["imgsz", "opset", "workspace", "batch", "fraction"]) {
|
|
555867
|
+
const n2 = optionalNumber(args, key);
|
|
555868
|
+
if (n2 !== void 0)
|
|
555869
|
+
options2[key] = n2;
|
|
555870
|
+
}
|
|
555871
|
+
for (const key of ["dynamic", "simplify", "nms", "optimize", "keras", "end2end"]) {
|
|
555872
|
+
const b = optionalBoolean(args, key);
|
|
555873
|
+
if (b !== void 0)
|
|
555874
|
+
options2[key] = b;
|
|
555875
|
+
}
|
|
555876
|
+
const quantize = args["quantize"] ?? args["precision"];
|
|
555877
|
+
if (quantize !== void 0 && quantize !== null && String(quantize).trim()) {
|
|
555878
|
+
const asNumber2 = Number(quantize);
|
|
555879
|
+
options2["quantize"] = Number.isFinite(asNumber2) ? asNumber2 : String(quantize).trim();
|
|
555880
|
+
}
|
|
555881
|
+
for (const key of ["device", "data", "name"]) {
|
|
555882
|
+
const value2 = args[key];
|
|
555883
|
+
if (typeof value2 === "string" && value2.trim())
|
|
555884
|
+
options2[key] = value2.trim();
|
|
555885
|
+
}
|
|
555886
|
+
return options2;
|
|
555887
|
+
}
|
|
555888
|
+
function stringListFromArg(value2) {
|
|
555889
|
+
if (Array.isArray(value2)) {
|
|
555890
|
+
return value2.map((item) => String(item).trim()).filter(Boolean);
|
|
555891
|
+
}
|
|
555892
|
+
if (typeof value2 === "string") {
|
|
555893
|
+
return value2.split(/[,;\n]/).map((item) => item.trim()).filter(Boolean);
|
|
555894
|
+
}
|
|
555895
|
+
return [];
|
|
555896
|
+
}
|
|
555657
555897
|
function sourceKind(args) {
|
|
555658
555898
|
const explicit = String(args["source_kind"] ?? args["source_type"] ?? "").toLowerCase();
|
|
555659
555899
|
if (["camera", "youtube", "file", "direct"].includes(explicit))
|
|
@@ -555797,6 +556037,59 @@ async function ensureRuntime(autoBootstrap) {
|
|
|
555797
556037
|
return { ok: false, python: plan.python, error: err instanceof Error ? err.message : String(err) };
|
|
555798
556038
|
}
|
|
555799
556039
|
}
|
|
556040
|
+
async function exportYolo26Model(python, model, options2, yoloeClasses = []) {
|
|
556041
|
+
const cfgPath = join92(tmpdir19(), `omnius-yolo26-export-${Date.now()}.json`);
|
|
556042
|
+
const scriptPath2 = join92(tmpdir19(), `omnius-yolo26-export-${Date.now()}.py`);
|
|
556043
|
+
writeFileSync39(cfgPath, JSON.stringify({ model, options: options2, yoloeClasses }, null, 2), "utf8");
|
|
556044
|
+
writeFileSync39(scriptPath2, String.raw`
|
|
556045
|
+
import json, sys
|
|
556046
|
+
from ultralytics import YOLO
|
|
556047
|
+
try:
|
|
556048
|
+
from ultralytics import YOLOE
|
|
556049
|
+
except Exception:
|
|
556050
|
+
YOLOE = None
|
|
556051
|
+
|
|
556052
|
+
cfg_path = sys.argv[1]
|
|
556053
|
+
with open(cfg_path, "r", encoding="utf-8") as fh:
|
|
556054
|
+
cfg = json.load(fh)
|
|
556055
|
+
|
|
556056
|
+
model_name = cfg["model"]
|
|
556057
|
+
options = cfg["options"]
|
|
556058
|
+
yoloe_classes = [str(x).strip() for x in cfg.get("yoloeClasses", []) if str(x).strip()]
|
|
556059
|
+
model = YOLOE(model_name) if str(model_name).startswith("yoloe-") and YOLOE is not None else YOLO(model_name)
|
|
556060
|
+
if yoloe_classes and hasattr(model, "set_classes"):
|
|
556061
|
+
model.set_classes(yoloe_classes)
|
|
556062
|
+
artifact = model.export(**options)
|
|
556063
|
+
print(json.dumps({
|
|
556064
|
+
"success": True,
|
|
556065
|
+
"model": model_name,
|
|
556066
|
+
"format": options.get("format"),
|
|
556067
|
+
"artifact": str(artifact),
|
|
556068
|
+
"options": options,
|
|
556069
|
+
"yoloe_classes": yoloe_classes,
|
|
556070
|
+
}))
|
|
556071
|
+
`, "utf8");
|
|
556072
|
+
try {
|
|
556073
|
+
const raw = await execFileText(python, [scriptPath2, cfgPath], {
|
|
556074
|
+
timeout: 30 * 6e4,
|
|
556075
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
556076
|
+
env: { ...process.env, PYTHONUNBUFFERED: "1" }
|
|
556077
|
+
});
|
|
556078
|
+
const jsonLine2 = raw.trim().split("\n").reverse().find((line) => line.trim().startsWith("{") && line.trim().endsWith("}"));
|
|
556079
|
+
if (!jsonLine2)
|
|
556080
|
+
throw new Error(`YOLO26 export returned no JSON result; stdout tail: ${raw.slice(-1e3)}`);
|
|
556081
|
+
return JSON.parse(jsonLine2);
|
|
556082
|
+
} finally {
|
|
556083
|
+
try {
|
|
556084
|
+
rmSync10(cfgPath, { force: true });
|
|
556085
|
+
} catch {
|
|
556086
|
+
}
|
|
556087
|
+
try {
|
|
556088
|
+
rmSync10(scriptPath2, { force: true });
|
|
556089
|
+
} catch {
|
|
556090
|
+
}
|
|
556091
|
+
}
|
|
556092
|
+
}
|
|
555800
556093
|
function makePythonScript() {
|
|
555801
556094
|
return String.raw`
|
|
555802
556095
|
import json, os, sys, time
|
|
@@ -555818,6 +556111,8 @@ def main():
|
|
|
555818
556111
|
os.makedirs(out_dir, exist_ok=True)
|
|
555819
556112
|
crop_dir = os.path.join(out_dir, "crops")
|
|
555820
556113
|
os.makedirs(crop_dir, exist_ok=True)
|
|
556114
|
+
segment_dir = os.path.join(out_dir, "segments")
|
|
556115
|
+
os.makedirs(segment_dir, exist_ok=True)
|
|
555821
556116
|
source_kind = cfg["source_kind"]
|
|
555822
556117
|
source = cfg["source"]
|
|
555823
556118
|
media_path = None
|
|
@@ -555827,6 +556122,10 @@ def main():
|
|
|
555827
556122
|
import cv2
|
|
555828
556123
|
import numpy as np
|
|
555829
556124
|
from ultralytics import YOLO
|
|
556125
|
+
try:
|
|
556126
|
+
from ultralytics import YOLOE
|
|
556127
|
+
except Exception:
|
|
556128
|
+
YOLOE = None
|
|
555830
556129
|
except Exception as exc:
|
|
555831
556130
|
print(json.dumps({"success": False, "error": "python imports failed: " + str(exc)}))
|
|
555832
556131
|
return
|
|
@@ -555869,16 +556168,26 @@ def main():
|
|
|
555869
556168
|
return
|
|
555870
556169
|
|
|
555871
556170
|
yolo_model = cfg.get("yolo_model") or "yolo26n.pt"
|
|
555872
|
-
|
|
556171
|
+
yoloe_classes = [str(x).strip() for x in (cfg.get("yoloe_classes") or []) if str(x).strip()]
|
|
556172
|
+
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")
|
|
556173
|
+
use_yoloe = str(yolo_model).startswith("yoloe-") and YOLOE is not None
|
|
556174
|
+
model = (YOLOE(yolo_model) if use_yoloe else YOLO(yolo_model)) if cfg.get("detect_objects", True) else None
|
|
556175
|
+
if model is not None and yoloe_classes and hasattr(model, "set_classes"):
|
|
556176
|
+
try:
|
|
556177
|
+
model.set_classes(yoloe_classes)
|
|
556178
|
+
except TypeError:
|
|
556179
|
+
model.set_classes(list(yoloe_classes))
|
|
555873
556180
|
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
|
|
555874
556181
|
objects = []
|
|
555875
556182
|
faces = []
|
|
556183
|
+
segments = []
|
|
555876
556184
|
track_stats = {}
|
|
555877
556185
|
max_frames = int(cfg.get("max_frames") or 8)
|
|
555878
556186
|
interval = float(cfg.get("sample_interval_sec") or 1.0)
|
|
555879
556187
|
duration = float(cfg.get("duration_sec") or 8.0)
|
|
555880
556188
|
conf = float(cfg.get("confidence") or 0.25)
|
|
555881
556189
|
imgsz = int(cfg.get("imgsz") or 640)
|
|
556190
|
+
end2end = cfg.get("end2end", None)
|
|
555882
556191
|
start_wall = time.time()
|
|
555883
556192
|
frame_index = 0
|
|
555884
556193
|
|
|
@@ -555902,10 +556211,17 @@ def main():
|
|
|
555902
556211
|
|
|
555903
556212
|
if model is not None:
|
|
555904
556213
|
try:
|
|
556214
|
+
predict_kwargs = {"verbose": False, "conf": conf, "imgsz": imgsz}
|
|
556215
|
+
if end2end is not None:
|
|
556216
|
+
predict_kwargs["end2end"] = bool(end2end)
|
|
556217
|
+
if cfg.get("agnostic_nms", None) is not None:
|
|
556218
|
+
predict_kwargs["agnostic_nms"] = bool(cfg.get("agnostic_nms"))
|
|
556219
|
+
elif use_yoloe:
|
|
556220
|
+
predict_kwargs["agnostic_nms"] = True
|
|
555905
556221
|
if cfg.get("track_objects", True):
|
|
555906
|
-
results = model.track(frame, persist=True,
|
|
556222
|
+
results = model.track(frame, persist=True, **predict_kwargs)
|
|
555907
556223
|
else:
|
|
555908
|
-
results = model.predict(frame,
|
|
556224
|
+
results = model.predict(frame, **predict_kwargs)
|
|
555909
556225
|
r = results[0] if results else None
|
|
555910
556226
|
if r is not None and getattr(r, "boxes", None) is not None:
|
|
555911
556227
|
boxes = r.boxes
|
|
@@ -555935,6 +556251,44 @@ def main():
|
|
|
555935
556251
|
st["last_seen_sec"] = max(st["last_seen_sec"], ts)
|
|
555936
556252
|
st["observations"] += 1
|
|
555937
556253
|
st["confidence_sum"] += c
|
|
556254
|
+
want_segments = bool(cfg.get("segment_objects") or cfg.get("extract_segments") or use_yoloe)
|
|
556255
|
+
if want_segments and getattr(r, "masks", None) is not None and getattr(r.masks, "data", None) is not None:
|
|
556256
|
+
mask_data = r.masks.data.cpu().numpy()
|
|
556257
|
+
max_segments = int(cfg.get("max_segments_per_frame") or 8)
|
|
556258
|
+
for i, raw_mask in enumerate(mask_data[:max_segments]):
|
|
556259
|
+
label = str(names.get(int(cls[i]), int(cls[i]))) if i < len(cls) else "object"
|
|
556260
|
+
c = float(confs[i]) if i < len(confs) else 0.0
|
|
556261
|
+
box = xyxy[i] if i < len(xyxy) else [0, 0, frame.shape[1], frame.shape[0]]
|
|
556262
|
+
mask = (raw_mask > 0.5).astype("uint8") * 255
|
|
556263
|
+
if mask.shape[:2] != frame.shape[:2]:
|
|
556264
|
+
mask = cv2.resize(mask, (frame.shape[1], frame.shape[0]), interpolation=cv2.INTER_NEAREST)
|
|
556265
|
+
x1, y1, x2, y2 = [int(max(0, round(float(v)))) for v in box]
|
|
556266
|
+
x1 = min(x1, frame.shape[1] - 1)
|
|
556267
|
+
x2 = min(max(x2, x1 + 1), frame.shape[1])
|
|
556268
|
+
y1 = min(y1, frame.shape[0] - 1)
|
|
556269
|
+
y2 = min(max(y2, y1 + 1), frame.shape[0])
|
|
556270
|
+
mask_path = os.path.join(segment_dir, f"seg_f{frame_index:04d}_{i:02d}_mask.png")
|
|
556271
|
+
cv2.imwrite(mask_path, mask)
|
|
556272
|
+
crop_path = None
|
|
556273
|
+
if cfg.get("extract_segments", False):
|
|
556274
|
+
crop_bgr = frame[y1:y2, x1:x2]
|
|
556275
|
+
crop_alpha = mask[y1:y2, x1:x2]
|
|
556276
|
+
if crop_bgr.size > 0 and crop_alpha.size > 0:
|
|
556277
|
+
crop_rgba = cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2BGRA)
|
|
556278
|
+
crop_rgba[:, :, 3] = crop_alpha
|
|
556279
|
+
crop_path = os.path.join(segment_dir, f"seg_f{frame_index:04d}_{i:02d}_{label.replace('/', '_')}.png")
|
|
556280
|
+
cv2.imwrite(crop_path, crop_rgba)
|
|
556281
|
+
area_ratio = float((mask > 0).sum()) / max(1.0, float(frame.shape[0] * frame.shape[1]))
|
|
556282
|
+
segments.append({
|
|
556283
|
+
"frame_index": frame_index,
|
|
556284
|
+
"timestamp_sec": round(float(ts), 3),
|
|
556285
|
+
"label": label,
|
|
556286
|
+
"confidence": c,
|
|
556287
|
+
"bbox_xyxy": [int(x1), int(y1), int(x2), int(y2)],
|
|
556288
|
+
"mask_path": mask_path,
|
|
556289
|
+
"crop_path": crop_path,
|
|
556290
|
+
"area_ratio": round(area_ratio, 6),
|
|
556291
|
+
})
|
|
555938
556292
|
except Exception as exc:
|
|
555939
556293
|
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
556294
|
|
|
@@ -555984,6 +556338,9 @@ def main():
|
|
|
555984
556338
|
"objects": objects,
|
|
555985
556339
|
"tracks": tracks,
|
|
555986
556340
|
"faces": faces,
|
|
556341
|
+
"segments": segments,
|
|
556342
|
+
"yoloe_classes": yoloe_classes,
|
|
556343
|
+
"yoloe_prompt_mode": yoloe_prompt_mode,
|
|
555987
556344
|
}))
|
|
555988
556345
|
|
|
555989
556346
|
if __name__ == "__main__":
|
|
@@ -555995,8 +556352,13 @@ function mockResult(args, workingDir) {
|
|
|
555995
556352
|
const rotation = normalizeCameraRotationDegrees(args["rotate_degrees"] ?? args["rotation_degrees"] ?? args["rotate"]);
|
|
555996
556353
|
const outDir = join92(workingDir, ".omnius", "live-media", `mock-${Date.now().toString(36)}`);
|
|
555997
556354
|
mkdirSync47(join92(outDir, "crops"), { recursive: true });
|
|
556355
|
+
mkdirSync47(join92(outDir, "segments"), { recursive: true });
|
|
555998
556356
|
const facePath = join92(outDir, "crops", "face_f0000_00.jpg");
|
|
556357
|
+
const maskPath = join92(outDir, "segments", "seg_f0001_00_mask.png");
|
|
556358
|
+
const segmentPath = join92(outDir, "segments", "seg_f0001_00_red_cup.png");
|
|
555999
556359
|
writeFileSync39(facePath, "mock face crop", "utf8");
|
|
556360
|
+
writeFileSync39(maskPath, "mock segment mask", "utf8");
|
|
556361
|
+
writeFileSync39(segmentPath, "mock extracted segment", "utf8");
|
|
556000
556362
|
return {
|
|
556001
556363
|
success: true,
|
|
556002
556364
|
source_kind: kind,
|
|
@@ -556016,10 +556378,22 @@ function mockResult(args, workingDir) {
|
|
|
556016
556378
|
{ track_id: "1", label: "person", first_seen_sec: 0, last_seen_sec: 0, observations: 1, mean_confidence: 0.91 },
|
|
556017
556379
|
{ track_id: "2", label: "red cup", first_seen_sec: 1, last_seen_sec: 2, observations: 2, mean_confidence: 0.84 }
|
|
556018
556380
|
],
|
|
556019
|
-
faces: [{ frame_index: 0, timestamp_sec: 0, crop_path: facePath, bbox_xyxy: [20, 30, 80, 90], confidence: 0.5 }]
|
|
556381
|
+
faces: [{ frame_index: 0, timestamp_sec: 0, crop_path: facePath, bbox_xyxy: [20, 30, 80, 90], confidence: 0.5 }],
|
|
556382
|
+
segments: [{
|
|
556383
|
+
frame_index: 1,
|
|
556384
|
+
timestamp_sec: 1,
|
|
556385
|
+
label: "red cup",
|
|
556386
|
+
confidence: 0.84,
|
|
556387
|
+
bbox_xyxy: [130, 140, 180, 210],
|
|
556388
|
+
mask_path: maskPath,
|
|
556389
|
+
crop_path: segmentPath,
|
|
556390
|
+
area_ratio: 0.012
|
|
556391
|
+
}],
|
|
556392
|
+
yoloe_classes: stringListFromArg(args["yoloe_classes"] ?? args["classes"]),
|
|
556393
|
+
yoloe_prompt_mode: String(args["yoloe_prompt_mode"] ?? "closed_set")
|
|
556020
556394
|
};
|
|
556021
556395
|
}
|
|
556022
|
-
var LIVE_MEDIA_ROOT, YOLO26_VENV, DEFAULT_YOLO26_MODEL, LiveMediaLoopTool;
|
|
556396
|
+
var LIVE_MEDIA_ROOT, YOLO26_VENV, DEFAULT_YOLO26_MODEL, YOLO26_SCALES, YOLO26_TASKS, YOLO26_EXPORT_FORMATS, LiveMediaLoopTool;
|
|
556023
556397
|
var init_live_media_loop = __esm({
|
|
556024
556398
|
"packages/execution/dist/tools/live-media-loop.js"() {
|
|
556025
556399
|
"use strict";
|
|
@@ -556033,14 +556407,44 @@ var init_live_media_loop = __esm({
|
|
|
556033
556407
|
LIVE_MEDIA_ROOT = join92(homedir28(), ".omnius", "runtimes", "live-media");
|
|
556034
556408
|
YOLO26_VENV = join92(LIVE_MEDIA_ROOT, ".venv-yolo26");
|
|
556035
556409
|
DEFAULT_YOLO26_MODEL = "yolo26n.pt";
|
|
556410
|
+
YOLO26_SCALES = ["n", "s", "m", "l", "x"];
|
|
556411
|
+
YOLO26_TASKS = {
|
|
556412
|
+
detect: { suffix: "", description: "Object detection" },
|
|
556413
|
+
segment: { suffix: "-seg", description: "Instance segmentation" },
|
|
556414
|
+
semantic: { suffix: "-sem", description: "Semantic segmentation" },
|
|
556415
|
+
pose: { suffix: "-pose", description: "Pose/keypoints" },
|
|
556416
|
+
obb: { suffix: "-obb", description: "Oriented object detection" },
|
|
556417
|
+
classify: { suffix: "-cls", description: "Image classification" }
|
|
556418
|
+
};
|
|
556419
|
+
YOLO26_EXPORT_FORMATS = [
|
|
556420
|
+
{ format: "pytorch", artifact: "yolo26n.pt", description: "Native PyTorch checkpoint", arguments: [], precisions: ["fp32"] },
|
|
556421
|
+
{ format: "torchscript", artifact: "yolo26n.torchscript", description: "TorchScript runtime artifact", arguments: ["imgsz", "quantize", "dynamic", "optimize", "nms", "batch", "device"], precisions: ["fp32", "fp16"] },
|
|
556422
|
+
{ 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"] },
|
|
556423
|
+
{ 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"] },
|
|
556424
|
+
{ format: "engine", artifact: "yolo26n.engine", description: "NVIDIA TensorRT engine", arguments: ["imgsz", "quantize", "dynamic", "simplify", "workspace", "nms", "batch", "data", "fraction", "device"], precisions: ["fp32", "fp16", "int8"] },
|
|
556425
|
+
{ format: "coreml", artifact: "yolo26n.mlpackage", description: "Apple CoreML deployment", arguments: ["imgsz", "dynamic", "quantize", "nms", "batch", "device"], precisions: ["fp32", "fp16", "int8", "w8a16"] },
|
|
556426
|
+
{ format: "saved_model", artifact: "yolo26n_saved_model/", description: "TensorFlow SavedModel", arguments: ["imgsz", "keras", "quantize", "nms", "batch", "data", "fraction", "device"], precisions: ["fp32", "int8"] },
|
|
556427
|
+
{ format: "pb", artifact: "yolo26n.pb", description: "TensorFlow GraphDef", arguments: ["imgsz", "batch", "device"], precisions: ["fp32"] },
|
|
556428
|
+
{ format: "edgetpu", artifact: "yolo26n_edgetpu.tflite", description: "Google Coral Edge TPU", arguments: ["imgsz", "quantize", "data", "fraction", "device"], precisions: ["int8"] },
|
|
556429
|
+
{ format: "paddle", artifact: "yolo26n_paddle_model/", description: "PaddlePaddle", arguments: ["imgsz", "batch", "device"], precisions: ["fp32"] },
|
|
556430
|
+
{ format: "mnn", artifact: "yolo26n.mnn", description: "MNN mobile/embedded runtime", arguments: ["imgsz", "batch", "quantize", "device"], precisions: ["fp32", "fp16", "int8"] },
|
|
556431
|
+
{ format: "ncnn", artifact: "yolo26n_ncnn_model/", description: "Tencent NCNN mobile/embedded runtime", arguments: ["imgsz", "quantize", "batch", "device"], precisions: ["fp32", "fp16"] },
|
|
556432
|
+
{ format: "imx", artifact: "yolo26n_imx_model/", description: "Sony IMX500", arguments: ["imgsz", "quantize", "data", "fraction", "nms", "device"], precisions: ["int8", "w8a16"] },
|
|
556433
|
+
{ format: "rknn", artifact: "yolo26n_rknn_model/", description: "Rockchip RKNN", arguments: ["imgsz", "batch", "name", "quantize", "data", "fraction", "device"], precisions: ["fp16", "int8"] },
|
|
556434
|
+
{ format: "executorch", artifact: "yolo26n_executorch_model/", description: "PyTorch ExecuTorch", arguments: ["imgsz", "batch", "device"], precisions: ["fp32"] },
|
|
556435
|
+
{ format: "axelera", artifact: "yolo26n_axelera_model/", description: "Axelera AI deployment", arguments: ["imgsz", "batch", "quantize", "data", "fraction", "device"], precisions: ["int8"] },
|
|
556436
|
+
{ format: "deepx", artifact: "yolo26n_deepx_model/", description: "DEEPX accelerator deployment", arguments: ["imgsz", "quantize", "data", "optimize", "device"], precisions: ["int8"] },
|
|
556437
|
+
{ format: "qnn", artifact: "yolo26n_qnn.onnx", description: "Qualcomm QNN HTP", arguments: ["imgsz", "batch", "name", "quantize", "data", "fraction", "device"], precisions: ["w8a16"] },
|
|
556438
|
+
{ format: "litert", artifact: "yolo26n.tflite", description: "Google LiteRT / TensorFlow Lite successor", arguments: ["imgsz", "quantize", "batch", "data", "fraction", "device"], precisions: ["fp32", "int8", "w8a16", "w8a32"] }
|
|
556439
|
+
];
|
|
556036
556440
|
LiveMediaLoopTool = class {
|
|
556037
556441
|
workingDir;
|
|
556038
556442
|
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'.";
|
|
556443
|
+
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
556444
|
parameters = {
|
|
556041
556445
|
type: "object",
|
|
556042
556446
|
properties: {
|
|
556043
|
-
action: { type: "string", enum: ["watch", "analyze", "ensure", "status"] },
|
|
556447
|
+
action: { type: "string", enum: ["watch", "analyze", "ensure", "status", "deployment_options", "options", "export"] },
|
|
556044
556448
|
source_kind: { type: "string", enum: ["youtube", "camera", "file", "direct"] },
|
|
556045
556449
|
url: { type: "string", description: "YouTube or direct video URL" },
|
|
556046
556450
|
path: { type: "string", description: "Local media path" },
|
|
@@ -556053,6 +556457,36 @@ var init_live_media_loop = __esm({
|
|
|
556053
556457
|
sample_interval_sec: { type: "number", description: "Seconds between sampled frames, default 1" },
|
|
556054
556458
|
max_frames: { type: "number", description: "Max sampled frames, default 8" },
|
|
556055
556459
|
yolo_model: { type: "string", description: "Ultralytics model, default yolo26n.pt" },
|
|
556460
|
+
yolo_family: { type: "string", enum: ["yolo26", "yoloe-26"], description: "Model family when yolo_model is omitted." },
|
|
556461
|
+
yolo_task: { type: "string", enum: ["detect", "segment", "semantic", "pose", "obb", "classify"], description: "YOLO26 task suffix when yolo_model is omitted." },
|
|
556462
|
+
yolo_scale: { type: "string", enum: ["n", "s", "m", "l", "x"], description: "YOLO26 model scale when yolo_model is omitted." },
|
|
556463
|
+
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." },
|
|
556464
|
+
prompt_mode: { type: "string", description: "Alias for yoloe_prompt_mode." },
|
|
556465
|
+
prompt_free: { type: "boolean", description: "Alias for yoloe_prompt_mode=prompt_free." },
|
|
556466
|
+
yoloe_classes: { type: ["array", "string"], description: "YOLOE text prompt classes, e.g. ['person','bus'] or comma-separated string." },
|
|
556467
|
+
classes: { type: ["array", "string"], description: "Alias for yoloe_classes." },
|
|
556468
|
+
segment_objects: { type: "boolean", description: "Capture instance segmentation masks when model returns masks. Enabled automatically for YOLOE." },
|
|
556469
|
+
extract_segments: { type: "boolean", description: "Save transparent RGBA crops for segmented objects." },
|
|
556470
|
+
max_segments_per_frame: { type: "number", description: "Limit saved mask/crop artifacts per sampled frame, default 8." },
|
|
556471
|
+
agnostic_nms: { type: "boolean", description: "Override YOLOE agnostic NMS behavior." },
|
|
556472
|
+
export_format: { type: "string", description: "YOLO26 export format for action=export, e.g. onnx, engine, openvino, coreml, saved_model, rknn, ncnn, litert." },
|
|
556473
|
+
format: { type: "string", description: "Alias for export_format when action=export." },
|
|
556474
|
+
quantize: { type: ["string", "number"], description: "Export precision: 8/int8/w8a8, 16/fp16/w16a16, 32/fp32, w8a16, or w8a32 where supported." },
|
|
556475
|
+
precision: { type: ["string", "number"], description: "Alias for quantize." },
|
|
556476
|
+
end2end: { type: "boolean", description: "YOLO26 one-to-one NMS-free head when true; set false for traditional one-to-many/NMS pipeline." },
|
|
556477
|
+
imgsz: { type: "number", description: "YOLO input/export image size, default 640." },
|
|
556478
|
+
dynamic: { type: "boolean", description: "Enable dynamic input size for supported export formats." },
|
|
556479
|
+
simplify: { type: "boolean", description: "Simplify ONNX/TensorRT export graphs when supported." },
|
|
556480
|
+
opset: { type: "number", description: "ONNX opset version." },
|
|
556481
|
+
workspace: { type: "number", description: "TensorRT workspace size in GiB." },
|
|
556482
|
+
nms: { type: "boolean", description: "Embed NMS in supported exports. Not available for YOLO26 end2end exports." },
|
|
556483
|
+
batch: { type: "number", description: "Export batch size." },
|
|
556484
|
+
device: { type: "string", description: "Export/inference device, e.g. cpu, 0, mps, npu:0, dla:0." },
|
|
556485
|
+
data: { type: "string", description: "Calibration dataset yaml for INT8/W8A16 exports." },
|
|
556486
|
+
fraction: { type: "number", description: "Calibration data fraction for quantized export." },
|
|
556487
|
+
optimize: { type: "boolean", description: "Export optimization for supported targets." },
|
|
556488
|
+
keras: { type: "boolean", description: "Keras SavedModel export option." },
|
|
556489
|
+
name: { type: "string", description: "Target/chip/name option for formats such as RKNN or QNN." },
|
|
556056
556490
|
auto_bootstrap: { type: "boolean", description: "Create managed YOLO26 venv if missing, default true" },
|
|
556057
556491
|
detect_objects: { type: "boolean" },
|
|
556058
556492
|
track_objects: { type: "boolean" },
|
|
@@ -556071,9 +556505,27 @@ var init_live_media_loop = __esm({
|
|
|
556071
556505
|
async execute(args) {
|
|
556072
556506
|
const start2 = performance.now();
|
|
556073
556507
|
const action = String(args["action"] ?? "watch").toLowerCase();
|
|
556074
|
-
const yoloModel =
|
|
556508
|
+
const yoloModel = resolveYolo26Model(args);
|
|
556075
556509
|
const rotation = normalizeCameraRotationDegrees(args["rotate_degrees"] ?? args["rotation_degrees"] ?? args["rotate"]);
|
|
556076
556510
|
const plan = buildYolo26BootstrapPlan(yoloModel);
|
|
556511
|
+
if (action === "deployment_options" || action === "options") {
|
|
556512
|
+
const options2 = buildYolo26DeploymentOptions();
|
|
556513
|
+
const text2 = [
|
|
556514
|
+
"YOLO26 / YOLOE deployment options",
|
|
556515
|
+
`default model: ${options2.defaultModel}`,
|
|
556516
|
+
`scales: ${options2.scales.join(", ")}`,
|
|
556517
|
+
`tasks: ${Object.entries(options2.tasks).map(([task, meta]) => `${task}${meta.suffix}`).join(", ")}`,
|
|
556518
|
+
`formats: ${options2.exportFormats.map((entry) => entry.format).join(", ")}`,
|
|
556519
|
+
`YOLOE prompt modes: ${options2.yoloePromptModes.map((entry) => entry.mode).join(", ")}`,
|
|
556520
|
+
"head modes: end2end=true for default NMS-free one-to-one head; end2end=false for traditional one-to-many/NMS pipeline"
|
|
556521
|
+
].join("\n");
|
|
556522
|
+
return {
|
|
556523
|
+
success: true,
|
|
556524
|
+
output: text2,
|
|
556525
|
+
llmContent: JSON.stringify(options2, null, 2),
|
|
556526
|
+
durationMs: performance.now() - start2
|
|
556527
|
+
};
|
|
556528
|
+
}
|
|
556077
556529
|
if (action === "status" || action === "ensure") {
|
|
556078
556530
|
const runtime = action === "ensure" ? await ensureRuntime(args["auto_bootstrap"] !== false) : await ensureRuntime(false);
|
|
556079
556531
|
return {
|
|
@@ -556084,13 +556536,69 @@ var init_live_media_loop = __esm({
|
|
|
556084
556536
|
`python: ${plan.python}`,
|
|
556085
556537
|
`model: ${plan.model}`,
|
|
556086
556538
|
`packages: ${plan.packages.join(", ")}`,
|
|
556539
|
+
`deployment formats: ${YOLO26_EXPORT_FORMATS.map((entry) => entry.format).join(", ")}`,
|
|
556087
556540
|
runtime.error ? `error: ${runtime.error}` : ""
|
|
556088
556541
|
].filter(Boolean).join("\n"),
|
|
556089
556542
|
error: runtime.ok ? void 0 : runtime.error,
|
|
556090
|
-
llmContent: JSON.stringify({ ...plan, ready: runtime.ok }, null, 2),
|
|
556543
|
+
llmContent: JSON.stringify({ ...plan, ready: runtime.ok, deploymentOptions: buildYolo26DeploymentOptions() }, null, 2),
|
|
556091
556544
|
durationMs: performance.now() - start2
|
|
556092
556545
|
};
|
|
556093
556546
|
}
|
|
556547
|
+
if (action === "export") {
|
|
556548
|
+
let format3;
|
|
556549
|
+
try {
|
|
556550
|
+
format3 = normalizeYolo26ExportFormat(args["export_format"] ?? args["format"] ?? args["deploy_format"]);
|
|
556551
|
+
} catch (err) {
|
|
556552
|
+
return { success: false, output: "", error: err instanceof Error ? err.message : String(err), durationMs: performance.now() - start2 };
|
|
556553
|
+
}
|
|
556554
|
+
if (format3 === "pytorch") {
|
|
556555
|
+
const payload = { success: true, model: yoloModel, format: format3, artifact: yoloModel, options: { format: format3 } };
|
|
556556
|
+
return {
|
|
556557
|
+
success: true,
|
|
556558
|
+
output: `YOLO26 native PyTorch model selected: ${yoloModel}`,
|
|
556559
|
+
llmContent: JSON.stringify(payload, null, 2),
|
|
556560
|
+
durationMs: performance.now() - start2
|
|
556561
|
+
};
|
|
556562
|
+
}
|
|
556563
|
+
const exportOptions = exportOptionsFromArgs(args, format3);
|
|
556564
|
+
const yoloeClasses = stringListFromArg(args["yoloe_classes"] ?? args["classes"]);
|
|
556565
|
+
if (Boolean(args["mock"]) || process.env["OMNIUS_LIVE_MEDIA_MOCK"] === "1") {
|
|
556566
|
+
const payload = {
|
|
556567
|
+
success: true,
|
|
556568
|
+
model: yoloModel,
|
|
556569
|
+
format: format3,
|
|
556570
|
+
artifact: `${basename19(yoloModel, ".pt")}.${format3 === "engine" ? "engine" : format3}`,
|
|
556571
|
+
options: exportOptions,
|
|
556572
|
+
yoloe_classes: yoloeClasses,
|
|
556573
|
+
mock: true
|
|
556574
|
+
};
|
|
556575
|
+
return {
|
|
556576
|
+
success: true,
|
|
556577
|
+
output: `YOLO26 export plan (mock): ${yoloModel} -> ${format3}`,
|
|
556578
|
+
llmContent: JSON.stringify(payload, null, 2),
|
|
556579
|
+
durationMs: performance.now() - start2
|
|
556580
|
+
};
|
|
556581
|
+
}
|
|
556582
|
+
const runtime = await ensureRuntime(args["auto_bootstrap"] !== false);
|
|
556583
|
+
if (!runtime.ok)
|
|
556584
|
+
return { success: false, output: "", error: runtime.error, durationMs: performance.now() - start2 };
|
|
556585
|
+
try {
|
|
556586
|
+
const exported = await exportYolo26Model(runtime.python, yoloModel, exportOptions, yoloeClasses);
|
|
556587
|
+
return {
|
|
556588
|
+
success: true,
|
|
556589
|
+
output: [
|
|
556590
|
+
`YOLO26 export complete: ${yoloModel}`,
|
|
556591
|
+
`format: ${format3}`,
|
|
556592
|
+
`artifact: ${String(exported["artifact"] ?? "")}`,
|
|
556593
|
+
`options: ${JSON.stringify(exportOptions)}`
|
|
556594
|
+
].join("\n"),
|
|
556595
|
+
llmContent: JSON.stringify(exported, null, 2),
|
|
556596
|
+
durationMs: performance.now() - start2
|
|
556597
|
+
};
|
|
556598
|
+
} catch (err) {
|
|
556599
|
+
return { success: false, output: "", error: err instanceof Error ? err.message : String(err), durationMs: performance.now() - start2 };
|
|
556600
|
+
}
|
|
556601
|
+
}
|
|
556094
556602
|
const mock = Boolean(args["mock"]) || process.env["OMNIUS_LIVE_MEDIA_MOCK"] === "1";
|
|
556095
556603
|
let result;
|
|
556096
556604
|
if (mock) {
|
|
@@ -556118,6 +556626,13 @@ var init_live_media_loop = __esm({
|
|
|
556118
556626
|
sample_interval_sec: Number(args["sample_interval_sec"] ?? 1),
|
|
556119
556627
|
max_frames: Number(args["max_frames"] ?? 8),
|
|
556120
556628
|
rotate_degrees: rotation,
|
|
556629
|
+
end2end: optionalBoolean(args, "end2end"),
|
|
556630
|
+
yoloe_classes: stringListFromArg(args["yoloe_classes"] ?? args["classes"]),
|
|
556631
|
+
yoloe_prompt_mode: String(args["yoloe_prompt_mode"] ?? args["prompt_mode"] ?? ""),
|
|
556632
|
+
segment_objects: args["segment_objects"] !== false,
|
|
556633
|
+
extract_segments: args["extract_segments"] !== false,
|
|
556634
|
+
max_segments_per_frame: Number(args["max_segments_per_frame"] ?? 8),
|
|
556635
|
+
agnostic_nms: optionalBoolean(args, "agnostic_nms"),
|
|
556121
556636
|
detect_objects: args["detect_objects"] !== false,
|
|
556122
556637
|
track_objects: args["track_objects"] !== false,
|
|
556123
556638
|
crop_faces: args["crop_faces"] !== false,
|
|
@@ -556215,10 +556730,13 @@ var init_live_media_loop = __esm({
|
|
|
556215
556730
|
if (result.media_path)
|
|
556216
556731
|
lines.push(`media: ${result.media_path}`);
|
|
556217
556732
|
lines.push(`YOLO model: ${result.yolo_model} (${result.yolo_live ? "live" : "mock/harness"})`);
|
|
556733
|
+
if (result.yoloe_prompt_mode && result.yoloe_prompt_mode !== "closed_set") {
|
|
556734
|
+
lines.push(`YOLOE prompt mode: ${result.yoloe_prompt_mode}${result.yoloe_classes?.length ? ` classes=${result.yoloe_classes.join(", ")}` : ""}`);
|
|
556735
|
+
}
|
|
556218
556736
|
if (result.rotate_degrees)
|
|
556219
556737
|
lines.push(`frame rotation correction: ${result.rotate_degrees} degrees clockwise`);
|
|
556220
556738
|
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}`);
|
|
556739
|
+
lines.push(`objects: ${result.objects.length} detection(s), tracks: ${result.tracks.length}, face crops: ${result.faces.length}, segments: ${result.segments?.length ?? 0}`);
|
|
556222
556740
|
const objectSummary = summarizeObjects(result.objects);
|
|
556223
556741
|
if (objectSummary.length) {
|
|
556224
556742
|
lines.push("");
|
|
@@ -556240,6 +556758,13 @@ var init_live_media_loop = __esm({
|
|
|
556240
556758
|
lines.push(`- ${basename19(face.crop_path)} at ${face.timestamp_sec.toFixed(1)}s ${face.crop_path}`);
|
|
556241
556759
|
}
|
|
556242
556760
|
}
|
|
556761
|
+
if (result.segments?.length) {
|
|
556762
|
+
lines.push("");
|
|
556763
|
+
lines.push("## Segments");
|
|
556764
|
+
for (const segment of result.segments.slice(0, 12)) {
|
|
556765
|
+
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)}`);
|
|
556766
|
+
}
|
|
556767
|
+
}
|
|
556243
556768
|
if (audioEvidence.length) {
|
|
556244
556769
|
lines.push("");
|
|
556245
556770
|
lines.push("## Audio Evidence");
|
|
@@ -556263,11 +556788,14 @@ var init_live_media_loop = __esm({
|
|
|
556263
556788
|
media_path: result.media_path,
|
|
556264
556789
|
yolo_model: result.yolo_model,
|
|
556265
556790
|
yolo_live: result.yolo_live,
|
|
556791
|
+
yoloe_classes: result.yoloe_classes ?? [],
|
|
556792
|
+
yoloe_prompt_mode: result.yoloe_prompt_mode,
|
|
556266
556793
|
rotate_degrees: result.rotate_degrees ?? 0,
|
|
556267
556794
|
frames_sampled: result.frames_sampled,
|
|
556268
556795
|
objects: result.objects.slice(0, 80),
|
|
556269
556796
|
tracks: result.tracks.slice(0, 40),
|
|
556270
556797
|
faces: result.faces.slice(0, 20),
|
|
556798
|
+
segments: (result.segments ?? []).slice(0, 40),
|
|
556271
556799
|
audio_evidence: audioEvidence,
|
|
556272
556800
|
trigger_hits: hits,
|
|
556273
556801
|
artifacts_dir: result.output_dir
|
|
@@ -557548,6 +558076,7 @@ __export(dist_exports2, {
|
|
|
557548
558076
|
buildToolManifestFromModule: () => buildToolManifestFromModule,
|
|
557549
558077
|
buildWorkboardSynthesisContext: () => buildWorkboardSynthesisContext,
|
|
557550
558078
|
buildYolo26BootstrapPlan: () => buildYolo26BootstrapPlan,
|
|
558079
|
+
buildYolo26DeploymentOptions: () => buildYolo26DeploymentOptions,
|
|
557551
558080
|
builtInMediaModelCatalog: () => builtInMediaModelCatalog,
|
|
557552
558081
|
canInvokeTool: () => canInvokeTool,
|
|
557553
558082
|
checkConstraints: () => checkConstraints,
|
|
@@ -557781,6 +558310,7 @@ __export(dist_exports2, {
|
|
|
557781
558310
|
resolveMediaCudaVisibleDevicesForEnv: () => resolveMediaCudaVisibleDevicesForEnv,
|
|
557782
558311
|
resolveMediaModel: () => resolveMediaModel,
|
|
557783
558312
|
resolveSecret: () => resolveSecret,
|
|
558313
|
+
resolveYolo26Model: () => resolveYolo26Model,
|
|
557784
558314
|
revokeSecret: () => revokeSecret,
|
|
557785
558315
|
runBenchmark: () => runBenchmark,
|
|
557786
558316
|
runBuild: () => runBuild,
|
|
@@ -602192,7 +602722,9 @@ async function buildImageAsciiPreview(inputPath, options2 = {}) {
|
|
|
602192
602722
|
plainAscii,
|
|
602193
602723
|
renderer: "image-to-ascii",
|
|
602194
602724
|
width,
|
|
602195
|
-
height
|
|
602725
|
+
height,
|
|
602726
|
+
imageWidth: dimensions?.width,
|
|
602727
|
+
imageHeight: dimensions?.height
|
|
602196
602728
|
};
|
|
602197
602729
|
}
|
|
602198
602730
|
return {
|
|
@@ -602200,11 +602732,24 @@ async function buildImageAsciiPreview(inputPath, options2 = {}) {
|
|
|
602200
602732
|
ascii: previewFailure(result.error || "renderer unavailable", width),
|
|
602201
602733
|
renderer: "image-to-ascii",
|
|
602202
602734
|
width,
|
|
602203
|
-
height
|
|
602735
|
+
height,
|
|
602736
|
+
imageWidth: dimensions?.width,
|
|
602737
|
+
imageHeight: dimensions?.height
|
|
602204
602738
|
};
|
|
602205
602739
|
}
|
|
602206
602740
|
const fallback = await convertWithFfmpeg(imagePath, width, height, timeoutMs);
|
|
602207
|
-
if (fallback)
|
|
602741
|
+
if (fallback) {
|
|
602742
|
+
return {
|
|
602743
|
+
path: imagePath,
|
|
602744
|
+
ascii: fallback,
|
|
602745
|
+
plainAscii: fallback,
|
|
602746
|
+
renderer: "ffmpeg",
|
|
602747
|
+
width,
|
|
602748
|
+
height,
|
|
602749
|
+
imageWidth: dimensions?.width,
|
|
602750
|
+
imageHeight: dimensions?.height
|
|
602751
|
+
};
|
|
602752
|
+
}
|
|
602208
602753
|
return null;
|
|
602209
602754
|
}
|
|
602210
602755
|
function formatImageAsciiContext(preview, label) {
|
|
@@ -602323,10 +602868,45 @@ function parseJsonObjectFromText(value2) {
|
|
|
602323
602868
|
const direct = parseJsonObject3(value2);
|
|
602324
602869
|
if (direct) return direct;
|
|
602325
602870
|
const text2 = String(value2 ?? "");
|
|
602326
|
-
const
|
|
602327
|
-
|
|
602328
|
-
|
|
602329
|
-
|
|
602871
|
+
const candidates = [];
|
|
602872
|
+
let depth = 0;
|
|
602873
|
+
let start2 = -1;
|
|
602874
|
+
let inString = false;
|
|
602875
|
+
let escaped = false;
|
|
602876
|
+
for (let i2 = 0; i2 < text2.length; i2++) {
|
|
602877
|
+
const ch = text2[i2];
|
|
602878
|
+
if (inString) {
|
|
602879
|
+
if (escaped) {
|
|
602880
|
+
escaped = false;
|
|
602881
|
+
} else if (ch === "\\") {
|
|
602882
|
+
escaped = true;
|
|
602883
|
+
} else if (ch === '"') {
|
|
602884
|
+
inString = false;
|
|
602885
|
+
}
|
|
602886
|
+
continue;
|
|
602887
|
+
}
|
|
602888
|
+
if (ch === '"') {
|
|
602889
|
+
inString = true;
|
|
602890
|
+
continue;
|
|
602891
|
+
}
|
|
602892
|
+
if (ch === "{") {
|
|
602893
|
+
if (depth === 0) start2 = i2;
|
|
602894
|
+
depth++;
|
|
602895
|
+
continue;
|
|
602896
|
+
}
|
|
602897
|
+
if (ch === "}" && depth > 0) {
|
|
602898
|
+
depth--;
|
|
602899
|
+
if (depth === 0 && start2 >= 0) {
|
|
602900
|
+
candidates.push(text2.slice(start2, i2 + 1));
|
|
602901
|
+
start2 = -1;
|
|
602902
|
+
}
|
|
602903
|
+
}
|
|
602904
|
+
}
|
|
602905
|
+
for (const candidate of candidates.reverse()) {
|
|
602906
|
+
const parsed = parseJsonObject3(candidate);
|
|
602907
|
+
if (parsed) return parsed;
|
|
602908
|
+
}
|
|
602909
|
+
return null;
|
|
602330
602910
|
}
|
|
602331
602911
|
function normalizeLiveCameraRotation(value2) {
|
|
602332
602912
|
if (value2 === void 0 || value2 === null || value2 === "") return 0;
|
|
@@ -602361,9 +602941,9 @@ function parseCameraOrientationDecision(value2) {
|
|
|
602361
602941
|
}
|
|
602362
602942
|
function formatCameraRotation(rotation) {
|
|
602363
602943
|
if (rotation === 0) return "upright/no correction";
|
|
602364
|
-
if (rotation === 90) return "90 degrees clockwise";
|
|
602365
|
-
if (rotation === 180) return "180 degrees";
|
|
602366
|
-
return "90 degrees counter-clockwise";
|
|
602944
|
+
if (rotation === 90) return "90 degrees clockwise correction";
|
|
602945
|
+
if (rotation === 180) return "180 degrees correction";
|
|
602946
|
+
return "90 degrees counter-clockwise correction";
|
|
602367
602947
|
}
|
|
602368
602948
|
function sanitizeCameraOrientation(value2) {
|
|
602369
602949
|
if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) return {};
|
|
@@ -602418,21 +602998,59 @@ function truncateCell(value2, width) {
|
|
|
602418
602998
|
function stripDashboardAnsi(value2) {
|
|
602419
602999
|
return value2.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
602420
603000
|
}
|
|
603001
|
+
function dashboardVisibleWidth(value2) {
|
|
603002
|
+
return stripDashboardAnsi(value2).length;
|
|
603003
|
+
}
|
|
603004
|
+
function truncateAnsiCell(value2, width) {
|
|
603005
|
+
value2 = String(value2 ?? "").replace(/\r?\n/g, " ");
|
|
603006
|
+
if (width <= 0) return "";
|
|
603007
|
+
let out = "";
|
|
603008
|
+
let visible = 0;
|
|
603009
|
+
let i2 = 0;
|
|
603010
|
+
let sawAnsi = false;
|
|
603011
|
+
while (i2 < value2.length && visible < width) {
|
|
603012
|
+
DASHBOARD_ANSI_STICKY_PATTERN.lastIndex = i2;
|
|
603013
|
+
const ansi5 = DASHBOARD_ANSI_STICKY_PATTERN.exec(value2);
|
|
603014
|
+
if (ansi5) {
|
|
603015
|
+
sawAnsi = true;
|
|
603016
|
+
out += ansi5[0];
|
|
603017
|
+
i2 = DASHBOARD_ANSI_STICKY_PATTERN.lastIndex;
|
|
603018
|
+
continue;
|
|
603019
|
+
}
|
|
603020
|
+
const codePoint = value2.codePointAt(i2);
|
|
603021
|
+
if (codePoint == null) break;
|
|
603022
|
+
const ch = String.fromCodePoint(codePoint);
|
|
603023
|
+
out += ch;
|
|
603024
|
+
i2 += ch.length;
|
|
603025
|
+
visible++;
|
|
603026
|
+
}
|
|
603027
|
+
const currentWidth = dashboardVisibleWidth(out);
|
|
603028
|
+
if (currentWidth < width) out += " ".repeat(width - currentWidth);
|
|
603029
|
+
return sawAnsi ? `${out}\x1B[0m` : out;
|
|
603030
|
+
}
|
|
602421
603031
|
function dashboardPreviewLines(camera) {
|
|
602422
|
-
const raw = camera.
|
|
602423
|
-
return raw.replace(/\r/g, "").split("\n").map((line) =>
|
|
603032
|
+
const raw = camera.ascii || camera.plainAscii || "";
|
|
603033
|
+
return raw.replace(/\r/g, "").split("\n").map((line) => line.replace(/\s+$/g, "")).filter((line) => stripDashboardAnsi(line).trim().length > 0);
|
|
602424
603034
|
}
|
|
602425
603035
|
function dashboardPreviewAspect(lines) {
|
|
602426
603036
|
const widths = lines.map((line) => stripDashboardAnsi(line).length).filter((n2) => n2 > 0);
|
|
602427
603037
|
const maxWidth = Math.max(1, ...widths);
|
|
602428
603038
|
return Math.max(0.12, Math.min(1.6, lines.length / maxWidth));
|
|
602429
603039
|
}
|
|
603040
|
+
function cameraDashboardAspect(camera, lines) {
|
|
603041
|
+
const frameWidth = Number(camera.frameWidth ?? 0);
|
|
603042
|
+
const frameHeight = Number(camera.frameHeight ?? 0);
|
|
603043
|
+
if (frameWidth > 0 && frameHeight > 0) {
|
|
603044
|
+
return Math.max(0.12, Math.min(1.4, frameHeight / frameWidth * 0.52));
|
|
603045
|
+
}
|
|
603046
|
+
return dashboardPreviewAspect(lines);
|
|
603047
|
+
}
|
|
602430
603048
|
function fitDashboardPane(lines, paneWidth, paneHeight, fallback) {
|
|
602431
603049
|
const source = lines.length > 0 ? lines : [fallback];
|
|
602432
603050
|
const normalized = [];
|
|
602433
603051
|
for (let i2 = 0; i2 < paneHeight; i2++) {
|
|
602434
603052
|
const sourceIndex = source.length <= paneHeight ? i2 : Math.min(source.length - 1, Math.floor(i2 * source.length / paneHeight));
|
|
602435
|
-
normalized.push(
|
|
603053
|
+
normalized.push(truncateAnsiCell(source[sourceIndex] ?? "", paneWidth));
|
|
602436
603054
|
}
|
|
602437
603055
|
while (normalized.length < paneHeight) normalized.push(" ".repeat(paneWidth));
|
|
602438
603056
|
return normalized;
|
|
@@ -602451,7 +603069,7 @@ function liveDashboardPreviewWidthForSources(sourceCount, termWidth = process.st
|
|
|
602451
603069
|
function formatCameraPane(camera, paneWidth, paneHeight, now2) {
|
|
602452
603070
|
const contentWidth = Math.max(10, paneWidth - 2);
|
|
602453
603071
|
const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
|
|
602454
|
-
const title = `${compactSourceId(camera.source)} ${age}s`;
|
|
603072
|
+
const title = `${compactSourceId(camera.source)} ${age}s${cameraPaneBadge(camera)}`;
|
|
602455
603073
|
const topTitle = ` ${title} `;
|
|
602456
603074
|
const left = Math.max(0, Math.floor((contentWidth - topTitle.length) / 2));
|
|
602457
603075
|
const right = Math.max(0, contentWidth - topTitle.length - left);
|
|
@@ -602460,7 +603078,7 @@ function formatCameraPane(camera, paneWidth, paneHeight, now2) {
|
|
|
602460
603078
|
const previewLines = dashboardPreviewLines(camera);
|
|
602461
603079
|
const fallback = camera.framePath ? "preview refreshing" : camera.error ? "capture failed" : "awaiting frame";
|
|
602462
603080
|
const body = fitDashboardPane(previewLines, contentWidth, paneHeight, fallback);
|
|
602463
|
-
return [top, ...body.map((line) => `│${
|
|
603081
|
+
return [top, ...body.map((line) => `│${truncateAnsiCell(line, contentWidth)}│`), bottom];
|
|
602464
603082
|
}
|
|
602465
603083
|
function pushDashboardWrapped(lines, value2, width) {
|
|
602466
603084
|
const contentWidth = Math.max(10, width - 2);
|
|
@@ -602586,6 +603204,116 @@ function fallbackLiveAudioIntakeDecision(transcript) {
|
|
|
602586
603204
|
reason: addressed ? "fallback detected direct address or question shape" : "fallback could not establish that ambient speech was addressed to Omnius"
|
|
602587
603205
|
};
|
|
602588
603206
|
}
|
|
603207
|
+
function parseLiveSpeechDecision(value2) {
|
|
603208
|
+
const parsed = parseJsonObjectFromText(value2);
|
|
603209
|
+
if (!parsed) return null;
|
|
603210
|
+
const speak = parsed["speak"];
|
|
603211
|
+
const confidence2 = Math.max(0, Math.min(1, Number(parsed["confidence"] ?? 0)));
|
|
603212
|
+
if (typeof speak !== "boolean" || !Number.isFinite(confidence2)) return null;
|
|
603213
|
+
const actionRaw = String(parsed["action"] ?? (speak ? "speak" : "silent")).toLowerCase();
|
|
603214
|
+
const action = actionRaw === "speak" || actionRaw === "silent" || actionRaw === "queue_review" ? actionRaw : speak ? "speak" : "silent";
|
|
603215
|
+
return {
|
|
603216
|
+
speak,
|
|
603217
|
+
text: trimOneLine(parsed["text"] ?? "", 180),
|
|
603218
|
+
confidence: confidence2,
|
|
603219
|
+
action,
|
|
603220
|
+
reason: trimOneLine(parsed["reason"] ?? "", 220) || "live speech arbiter decision"
|
|
603221
|
+
};
|
|
603222
|
+
}
|
|
603223
|
+
function fallbackLiveSpeechDecision(proposal) {
|
|
603224
|
+
if (proposal.kind === "unknown_person") {
|
|
603225
|
+
return {
|
|
603226
|
+
speak: true,
|
|
603227
|
+
text: proposal.defaultText,
|
|
603228
|
+
confidence: 0.72,
|
|
603229
|
+
action: "speak",
|
|
603230
|
+
reason: "unknown person visible and identity clarification is useful"
|
|
603231
|
+
};
|
|
603232
|
+
}
|
|
603233
|
+
if (proposal.kind === "audio_intent") {
|
|
603234
|
+
return {
|
|
603235
|
+
speak: true,
|
|
603236
|
+
text: proposal.defaultText,
|
|
603237
|
+
confidence: 0.68,
|
|
603238
|
+
action: "speak",
|
|
603239
|
+
reason: "audio intake classified speech as addressed to Omnius"
|
|
603240
|
+
};
|
|
603241
|
+
}
|
|
603242
|
+
if (proposal.urgency === "high") {
|
|
603243
|
+
return {
|
|
603244
|
+
speak: true,
|
|
603245
|
+
text: proposal.defaultText,
|
|
603246
|
+
confidence: 0.6,
|
|
603247
|
+
action: "speak",
|
|
603248
|
+
reason: "high-urgency live trigger"
|
|
603249
|
+
};
|
|
603250
|
+
}
|
|
603251
|
+
return {
|
|
603252
|
+
speak: false,
|
|
603253
|
+
text: "",
|
|
603254
|
+
confidence: 0.55,
|
|
603255
|
+
action: "silent",
|
|
603256
|
+
reason: "ambient event did not justify interrupting with speech"
|
|
603257
|
+
};
|
|
603258
|
+
}
|
|
603259
|
+
async function classifyLiveSpeech(proposal, config) {
|
|
603260
|
+
const now2 = Date.now();
|
|
603261
|
+
if (config?.backendUrl && config.model) {
|
|
603262
|
+
const url = `${config.backendUrl.replace(/\/+$/, "")}/v1/chat/completions`;
|
|
603263
|
+
const body = {
|
|
603264
|
+
model: config.model,
|
|
603265
|
+
temperature: 0,
|
|
603266
|
+
max_tokens: 180,
|
|
603267
|
+
think: false,
|
|
603268
|
+
messages: [
|
|
603269
|
+
{
|
|
603270
|
+
role: "system",
|
|
603271
|
+
content: [
|
|
603272
|
+
"You are Omnius' live audio/video speech arbiter.",
|
|
603273
|
+
"Decide whether the agent should speak aloud into the physical environment right now.",
|
|
603274
|
+
"Speak rarely. Do not narrate routine detections or every frame.",
|
|
603275
|
+
"Speak when directly addressed, when an unknown person should be politely identified, or when an important visual/audio trigger needs immediate clarification.",
|
|
603276
|
+
"Never invent a person's identity. If asking identity, ask one short natural question.",
|
|
603277
|
+
"Keep spoken text under 16 words.",
|
|
603278
|
+
'Return only JSON: {"speak": boolean, "text": "short utterance", "confidence": 0.0-1.0, "action": "speak"|"silent"|"queue_review", "reason": "brief evidence"}.'
|
|
603279
|
+
].join("\n")
|
|
603280
|
+
},
|
|
603281
|
+
{
|
|
603282
|
+
role: "user",
|
|
603283
|
+
content: [
|
|
603284
|
+
`kind=${proposal.kind}`,
|
|
603285
|
+
`source=${proposal.source}`,
|
|
603286
|
+
`urgency=${proposal.urgency}`,
|
|
603287
|
+
`summary=${proposal.summary}`,
|
|
603288
|
+
`evidence=${proposal.evidence}`,
|
|
603289
|
+
`default_utterance=${proposal.defaultText}`
|
|
603290
|
+
].join("\n").slice(0, 1600)
|
|
603291
|
+
}
|
|
603292
|
+
]
|
|
603293
|
+
};
|
|
603294
|
+
try {
|
|
603295
|
+
const controller = new AbortController();
|
|
603296
|
+
const timer = setTimeout(() => controller.abort(), 5e3).unref();
|
|
603297
|
+
const resp = await fetch(url, {
|
|
603298
|
+
method: "POST",
|
|
603299
|
+
headers: {
|
|
603300
|
+
"Content-Type": "application/json",
|
|
603301
|
+
...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
|
|
603302
|
+
},
|
|
603303
|
+
body: JSON.stringify(body),
|
|
603304
|
+
signal: controller.signal
|
|
603305
|
+
});
|
|
603306
|
+
clearTimeout(timer);
|
|
603307
|
+
if (resp.ok) {
|
|
603308
|
+
const json = await resp.json();
|
|
603309
|
+
const parsed = parseLiveSpeechDecision(json.choices?.[0]?.message?.content ?? "");
|
|
603310
|
+
if (parsed) return { ...parsed, source: "live-arbiter", updatedAt: now2 };
|
|
603311
|
+
}
|
|
603312
|
+
} catch {
|
|
603313
|
+
}
|
|
603314
|
+
}
|
|
603315
|
+
return { ...fallbackLiveSpeechDecision(proposal), source: "fallback", updatedAt: now2 };
|
|
603316
|
+
}
|
|
602589
603317
|
async function classifyLiveAudioIntake(transcript, config) {
|
|
602590
603318
|
const now2 = Date.now();
|
|
602591
603319
|
const clean5 = transcript.trim();
|
|
@@ -602654,9 +603382,152 @@ function summarizeInference(value2) {
|
|
|
602654
603382
|
const sampled = lines.find((line) => /^objects:/i.test(line));
|
|
602655
603383
|
return sampled ? trimOneLine(sampled, 160) : trimOneLine(lines.slice(0, 4).join("; "), 160);
|
|
602656
603384
|
}
|
|
603385
|
+
function boundedConfidence(value2) {
|
|
603386
|
+
const n2 = Number(value2);
|
|
603387
|
+
return Number.isFinite(n2) ? Math.max(0, Math.min(1, n2)) : void 0;
|
|
603388
|
+
}
|
|
603389
|
+
function mergeClassificationInto(map2, item) {
|
|
603390
|
+
const label = trimOneLine(item.label || item.kind, 80) || item.kind;
|
|
603391
|
+
const key = `${item.kind}:${label}:${item.trackId ?? ""}:${item.evidencePath ?? ""}`;
|
|
603392
|
+
const existing = map2.get(key);
|
|
603393
|
+
const count = Math.max(1, Number(item.count ?? 1));
|
|
603394
|
+
if (!existing) {
|
|
603395
|
+
map2.set(key, { ...item, label, count });
|
|
603396
|
+
return;
|
|
603397
|
+
}
|
|
603398
|
+
const total = existing.count + count;
|
|
603399
|
+
const confidence2 = existing.confidence !== void 0 || item.confidence !== void 0 ? ((existing.confidence ?? 0) * existing.count + (item.confidence ?? 0) * count) / total : void 0;
|
|
603400
|
+
map2.set(key, {
|
|
603401
|
+
...existing,
|
|
603402
|
+
count: total,
|
|
603403
|
+
confidence: confidence2,
|
|
603404
|
+
updatedAt: Math.max(existing.updatedAt, item.updatedAt)
|
|
603405
|
+
});
|
|
603406
|
+
}
|
|
603407
|
+
function classificationsFromLiveMediaPayload(payload, sampledAt, fallbackSource) {
|
|
603408
|
+
if (!payload) return [];
|
|
603409
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
603410
|
+
void fallbackSource;
|
|
603411
|
+
for (const object of payload.objects ?? []) {
|
|
603412
|
+
const label = trimOneLine(object.label || "object", 80) || "object";
|
|
603413
|
+
mergeClassificationInto(map2, {
|
|
603414
|
+
kind: "object",
|
|
603415
|
+
label,
|
|
603416
|
+
count: 1,
|
|
603417
|
+
confidence: boundedConfidence(object.confidence),
|
|
603418
|
+
trackId: object.track_id === void 0 || object.track_id === null ? void 0 : String(object.track_id),
|
|
603419
|
+
updatedAt: observedAtFromOffset(sampledAt, payload, Number(object.timestamp_sec ?? 0))
|
|
603420
|
+
});
|
|
603421
|
+
}
|
|
603422
|
+
for (const track of payload.tracks ?? []) {
|
|
603423
|
+
const label = trimOneLine(track.label || "object", 80) || "object";
|
|
603424
|
+
mergeClassificationInto(map2, {
|
|
603425
|
+
kind: "track",
|
|
603426
|
+
label,
|
|
603427
|
+
count: 1,
|
|
603428
|
+
confidence: boundedConfidence(track.mean_confidence),
|
|
603429
|
+
trackId: track.track_id === void 0 || track.track_id === null ? void 0 : String(track.track_id),
|
|
603430
|
+
updatedAt: observedAtFromOffset(sampledAt, payload, Number(track.last_seen_sec ?? track.first_seen_sec ?? 0))
|
|
603431
|
+
});
|
|
603432
|
+
}
|
|
603433
|
+
for (const face of payload.faces ?? []) {
|
|
603434
|
+
const identity3 = parseFaceIdentity(face);
|
|
603435
|
+
const label = identity3.status === "known" && identity3.name ? identity3.name : "unknown face";
|
|
603436
|
+
mergeClassificationInto(map2, {
|
|
603437
|
+
kind: identity3.status === "known" ? "identity" : "face",
|
|
603438
|
+
label,
|
|
603439
|
+
count: 1,
|
|
603440
|
+
confidence: boundedConfidence(identity3.confidence ?? face.confidence),
|
|
603441
|
+
evidencePath: typeof face.crop_path === "string" ? face.crop_path : void 0,
|
|
603442
|
+
updatedAt: observedAtFromOffset(sampledAt, payload, Number(face.timestamp_sec ?? 0))
|
|
603443
|
+
});
|
|
603444
|
+
}
|
|
603445
|
+
for (const segment of payload.segments ?? []) {
|
|
603446
|
+
const label = trimOneLine(segment.label || "segment", 80) || "segment";
|
|
603447
|
+
mergeClassificationInto(map2, {
|
|
603448
|
+
kind: "segment",
|
|
603449
|
+
label,
|
|
603450
|
+
count: 1,
|
|
603451
|
+
confidence: boundedConfidence(segment.confidence),
|
|
603452
|
+
evidencePath: typeof segment.crop_path === "string" && segment.crop_path ? segment.crop_path : typeof segment.mask_path === "string" ? segment.mask_path : void 0,
|
|
603453
|
+
updatedAt: observedAtFromOffset(sampledAt, payload, Number(segment.timestamp_sec ?? 0))
|
|
603454
|
+
});
|
|
603455
|
+
}
|
|
603456
|
+
for (const hit of payload.trigger_hits ?? []) {
|
|
603457
|
+
const label = trimOneLine(hit.triggerName || hit.matched || "visual trigger", 80) || "visual trigger";
|
|
603458
|
+
mergeClassificationInto(map2, {
|
|
603459
|
+
kind: "trigger",
|
|
603460
|
+
label,
|
|
603461
|
+
count: 1,
|
|
603462
|
+
confidence: boundedConfidence(hit.confidence),
|
|
603463
|
+
updatedAt: sampledAt
|
|
603464
|
+
});
|
|
603465
|
+
}
|
|
603466
|
+
return [...map2.values()].sort((a2, b) => {
|
|
603467
|
+
const aPriority = a2.kind === "face" || a2.kind === "identity" ? 0 : a2.kind === "segment" ? 1 : a2.kind === "object" ? 2 : 3;
|
|
603468
|
+
const bPriority = b.kind === "face" || b.kind === "identity" ? 0 : b.kind === "segment" ? 1 : b.kind === "object" ? 2 : 3;
|
|
603469
|
+
if (aPriority !== bPriority) return aPriority - bPriority;
|
|
603470
|
+
return (b.confidence ?? 0) - (a2.confidence ?? 0);
|
|
603471
|
+
}).slice(0, 24).map((item) => ({ ...item }));
|
|
603472
|
+
}
|
|
603473
|
+
function classificationsFromClipSummary(summary, observedAt, evidencePath) {
|
|
603474
|
+
const parsed = parseJsonObjectFromText(summary);
|
|
603475
|
+
const matches = Array.isArray(parsed?.["matches"]) ? parsed["matches"] : [];
|
|
603476
|
+
return matches.slice(0, 8).map((match) => {
|
|
603477
|
+
const label = trimOneLine(
|
|
603478
|
+
match["label"] ?? match["name"] ?? match["title"] ?? match["id"] ?? "visual-memory match",
|
|
603479
|
+
80
|
|
603480
|
+
) || "visual-memory match";
|
|
603481
|
+
return {
|
|
603482
|
+
kind: "clip",
|
|
603483
|
+
label,
|
|
603484
|
+
count: 1,
|
|
603485
|
+
confidence: boundedConfidence(match["score"] ?? match["similarity"] ?? match["confidence"]),
|
|
603486
|
+
evidencePath,
|
|
603487
|
+
updatedAt: observedAt
|
|
603488
|
+
};
|
|
603489
|
+
});
|
|
603490
|
+
}
|
|
603491
|
+
function mergeCameraClassifications(existing, incoming, limit = 24) {
|
|
603492
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
603493
|
+
for (const item of [...existing ?? [], ...incoming]) {
|
|
603494
|
+
mergeClassificationInto(map2, item);
|
|
603495
|
+
}
|
|
603496
|
+
const merged = [...map2.values()].sort((a2, b) => b.updatedAt - a2.updatedAt || (b.confidence ?? 0) - (a2.confidence ?? 0)).slice(0, limit);
|
|
603497
|
+
return merged.length > 0 ? merged : existing;
|
|
603498
|
+
}
|
|
603499
|
+
function cameraClassificationsSummary(camera, maxChars = 220) {
|
|
603500
|
+
const items = (camera.classifications ?? []).sort((a2, b) => {
|
|
603501
|
+
const aPriority = a2.kind === "face" || a2.kind === "identity" ? 0 : a2.kind === "segment" ? 1 : a2.kind === "object" ? 2 : a2.kind === "track" ? 3 : 4;
|
|
603502
|
+
const bPriority = b.kind === "face" || b.kind === "identity" ? 0 : b.kind === "segment" ? 1 : b.kind === "object" ? 2 : b.kind === "track" ? 3 : 4;
|
|
603503
|
+
if (aPriority !== bPriority) return aPriority - bPriority;
|
|
603504
|
+
return (b.confidence ?? 0) - (a2.confidence ?? 0);
|
|
603505
|
+
}).slice(0, 8).map((item) => {
|
|
603506
|
+
const count = item.count > 1 ? ` x${item.count}` : "";
|
|
603507
|
+
const confidence2 = item.confidence !== void 0 ? ` ${item.confidence.toFixed(2)}` : "";
|
|
603508
|
+
return `${item.kind}:${item.label}${count}${confidence2}`;
|
|
603509
|
+
});
|
|
603510
|
+
return trimOneLine(items.join("; "), maxChars);
|
|
603511
|
+
}
|
|
603512
|
+
function cameraPaneBadge(camera) {
|
|
603513
|
+
const classifications = camera.classifications ?? [];
|
|
603514
|
+
const faceCount = classifications.filter((item) => item.kind === "face" || item.kind === "identity").reduce((sum, item) => sum + Math.max(1, item.count), 0);
|
|
603515
|
+
const objectCount = classifications.filter((item) => item.kind === "object").reduce((sum, item) => sum + Math.max(1, item.count), 0);
|
|
603516
|
+
const segmentCount = classifications.filter((item) => item.kind === "segment").reduce((sum, item) => sum + Math.max(1, item.count), 0);
|
|
603517
|
+
const trackCount = classifications.filter((item) => item.kind === "track").reduce((sum, item) => sum + Math.max(1, item.count), 0);
|
|
603518
|
+
const triggerCount = classifications.filter((item) => item.kind === "trigger").reduce((sum, item) => sum + Math.max(1, item.count), 0);
|
|
603519
|
+
const parts = [
|
|
603520
|
+
faceCount > 0 ? `F${faceCount}` : "",
|
|
603521
|
+
segmentCount > 0 ? `S${segmentCount}` : "",
|
|
603522
|
+
objectCount > 0 ? `O${objectCount}` : "",
|
|
603523
|
+
trackCount > 0 ? `T${trackCount}` : "",
|
|
603524
|
+
triggerCount > 0 ? `!${triggerCount}` : ""
|
|
603525
|
+
].filter(Boolean);
|
|
603526
|
+
return parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
603527
|
+
}
|
|
602657
603528
|
function cameraSnapshotSummary(camera) {
|
|
602658
603529
|
if (camera.error) return `error: ${trimOneLine(camera.error, 160)}`;
|
|
602659
|
-
return camera.summary || summarizeInference(camera.inference);
|
|
603530
|
+
return camera.summary || (camera.classifications?.length ? `classifications: ${cameraClassificationsSummary(camera)}` : "") || summarizeInference(camera.inference);
|
|
602660
603531
|
}
|
|
602661
603532
|
function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
|
|
602662
603533
|
const width = Math.max(60, opts.width ?? 100);
|
|
@@ -602681,13 +603552,14 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
|
|
|
602681
603552
|
for (let start2 = 0; start2 < cameras.length; start2 += paneCount) {
|
|
602682
603553
|
const group = cameras.slice(start2, start2 + paneCount);
|
|
602683
603554
|
const sourceLines = group.map((camera) => dashboardPreviewLines(camera));
|
|
602684
|
-
const
|
|
603555
|
+
const aspects = group.map((camera, idx) => cameraDashboardAspect(camera, sourceLines[idx] ?? []));
|
|
603556
|
+
const aspect = Math.max(0.18, Math.min(0.92, Math.max(...aspects)));
|
|
602685
603557
|
const paneHeight = Math.max(10, Math.min(32, Math.round(paneContentWidth * aspect)));
|
|
602686
603558
|
const panes = group.map((camera) => formatCameraPane(camera, paneWidth, paneHeight, now2));
|
|
602687
603559
|
const rowCount = Math.max(...panes.map((pane) => pane.length));
|
|
602688
603560
|
for (let row = 0; row < rowCount; row++) {
|
|
602689
603561
|
const body = panes.map((pane) => pane[row] ?? " ".repeat(paneWidth)).join(" ");
|
|
602690
|
-
lines.push(`│${
|
|
603562
|
+
lines.push(`│${truncateAnsiCell(body, innerWidth)}│`);
|
|
602691
603563
|
}
|
|
602692
603564
|
}
|
|
602693
603565
|
}
|
|
@@ -602698,8 +603570,10 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
|
|
|
602698
603570
|
for (const camera of cameras) {
|
|
602699
603571
|
const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
|
|
602700
603572
|
const rotation = camera.orientation ? formatCameraRotation(camera.orientation.rotation) : "upright/no correction";
|
|
603573
|
+
const classifications = cameraClassificationsSummary(camera);
|
|
602701
603574
|
const detailLines = [
|
|
602702
603575
|
`${compactSourceId(camera.source)} age=${age}s rotation=${rotation}`,
|
|
603576
|
+
classifications ? `classifications: ${classifications}` : "",
|
|
602703
603577
|
cameraSnapshotSummary(camera),
|
|
602704
603578
|
camera.framePath ? `frame: ${camera.displayPath || camera.framePath}` : "",
|
|
602705
603579
|
camera.orientation?.evidence ? `orientation: ${trimOneLine(camera.orientation.evidence, 120)}` : ""
|
|
@@ -602729,7 +603603,7 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
|
|
|
602729
603603
|
function parseLiveMediaPayload(value2) {
|
|
602730
603604
|
const parsed = parseJsonObject3(value2);
|
|
602731
603605
|
if (!parsed) return null;
|
|
602732
|
-
if (!Array.isArray(parsed["objects"]) && !Array.isArray(parsed["tracks"]) && !Array.isArray(parsed["faces"])) return null;
|
|
603606
|
+
if (!Array.isArray(parsed["objects"]) && !Array.isArray(parsed["tracks"]) && !Array.isArray(parsed["faces"]) && !Array.isArray(parsed["segments"])) return null;
|
|
602733
603607
|
return parsed;
|
|
602734
603608
|
}
|
|
602735
603609
|
function observedAtFromOffset(sampledAt, payload, offsetSec) {
|
|
@@ -602826,6 +603700,21 @@ function observationsFromLiveMediaPayload(payload, sampledAt, fallbackSource) {
|
|
|
602826
603700
|
evidence: identity3.evidence
|
|
602827
603701
|
});
|
|
602828
603702
|
}
|
|
603703
|
+
for (const segment of (payload.segments ?? []).slice(0, 24)) {
|
|
603704
|
+
const label = String(segment.label || "segment");
|
|
603705
|
+
const observedAt = observedAtFromOffset(sampledAt, payload, Number(segment.timestamp_sec ?? 0));
|
|
603706
|
+
const evidencePath = typeof segment.crop_path === "string" && segment.crop_path ? segment.crop_path : typeof segment.mask_path === "string" ? segment.mask_path : void 0;
|
|
603707
|
+
events.push({
|
|
603708
|
+
id: stableEventId("camera_segment", source, observedAt, `${label}:${segment.frame_index ?? ""}:${evidencePath ?? ""}`),
|
|
603709
|
+
kind: "camera_segment",
|
|
603710
|
+
observedAt,
|
|
603711
|
+
source,
|
|
603712
|
+
title: `Segmented ${label}`,
|
|
603713
|
+
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 ?? [])}`,
|
|
603714
|
+
confidence: typeof segment.confidence === "number" ? segment.confidence : void 0,
|
|
603715
|
+
evidencePath
|
|
603716
|
+
});
|
|
603717
|
+
}
|
|
602829
603718
|
const faceCount = people.length;
|
|
602830
603719
|
if (faceCount === 0) {
|
|
602831
603720
|
const personTracks = (payload.tracks ?? []).filter((track) => String(track.label || "").toLowerCase() === "person").slice(0, 8);
|
|
@@ -602951,8 +603840,8 @@ function loadLiveSensorConfig(repoRoot) {
|
|
|
602951
603840
|
...DEFAULT_CONFIG7,
|
|
602952
603841
|
...parsed,
|
|
602953
603842
|
cameraOrientation: sanitizeCameraOrientation(parsed.cameraOrientation),
|
|
602954
|
-
videoIntervalMs: Math.max(
|
|
602955
|
-
audioIntervalMs: Math.max(
|
|
603843
|
+
videoIntervalMs: Math.max(MIN_VIDEO_INTERVAL_MS, Number(parsed.videoIntervalMs ?? DEFAULT_CONFIG7.videoIntervalMs)),
|
|
603844
|
+
audioIntervalMs: Math.max(MIN_AUDIO_INTERVAL_MS, Number(parsed.audioIntervalMs ?? DEFAULT_CONFIG7.audioIntervalMs))
|
|
602956
603845
|
};
|
|
602957
603846
|
} catch {
|
|
602958
603847
|
return { ...DEFAULT_CONFIG7 };
|
|
@@ -602977,6 +603866,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
|
|
|
602977
603866
|
lines.push("<live-sensor-context>");
|
|
602978
603867
|
lines.push("## LIVE SENSOR STATUS");
|
|
602979
603868
|
lines.push("Live sensor context is operator-enabled ambient input. Treat it as current perceptual evidence for this turn; if it is stale, missing, or insufficient, use camera/audio tools to refresh before answering what you see or hear.");
|
|
603869
|
+
lines.push('Live world-model rule: when the user asks what is around, who is present, what changed, what is heard, or asks a deictic question like "what do you see", treat the live camera/audio fields as first-class context. Be curious and tool-capable: refresh stale sensors, inspect frame paths, query vision/CLIP/visual_memory/audio tools, and combine metadata into a concise answer. Do not rely only on summaries when the task requires current perception.');
|
|
603870
|
+
lines.push("Live speech rule: in low-latency voice/live modes, short spoken replies are appropriate for direct address, identity clarification, or important visual/audio triggers. Do not narrate every frame.");
|
|
602980
603871
|
lines.push("Identity rule: never invent names for observed people. If a person is unknown and identity matters, ask the user or the person for their name, then store the association with visual_memory(action='enroll', image='<face crop>', name='<name>') when a face crop is available, or multimodal_memory(action='meet', person_name='<name>') when live voice/name context is available.");
|
|
602981
603872
|
lines.push(`Snapshot file: ${liveSnapshotPath(snapshot.repoRoot)}`);
|
|
602982
603873
|
lines.push(`Streams: video=${snapshot.config.videoEnabled ? "on" : "off"} infer=${snapshot.config.inferEnabled ? "on" : "off"} clip=${snapshot.config.clipEnabled ? "on" : "off"} audio=${snapshot.config.audioEnabled ? "on" : "off"} asr=${snapshot.config.asrEnabled ? "on" : "off"} sounds=${snapshot.config.audioAnalysisEnabled ? "on" : "off"} output-monitor=${snapshot.config.audioOutputEnabled ? "on" : "off"}`);
|
|
@@ -602996,6 +603887,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
|
|
|
602996
603887
|
const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
|
|
602997
603888
|
const rotation = camera.orientation ? formatCameraRotation(camera.orientation.rotation) : "upright/no correction";
|
|
602998
603889
|
lines.push(`- ${camera.source} age=${age}s rotation=${rotation}${camera.framePath ? ` frame=${camera.framePath}` : ""}${camera.error ? ` error=${trimOneLine(camera.error, 240)}` : ""}`);
|
|
603890
|
+
const classifications = cameraClassificationsSummary(camera);
|
|
603891
|
+
if (classifications) lines.push(` classifications: ${classifications}`);
|
|
602999
603892
|
const summary = cameraSnapshotSummary(camera);
|
|
603000
603893
|
if (summary) lines.push(` summary: ${summary}`);
|
|
603001
603894
|
}
|
|
@@ -603050,6 +603943,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
|
|
|
603050
603943
|
lines.push(`timestamp: ${nowIso3(snapshot.video.updatedAt)} age=${Math.max(0, Math.round(age / 1e3))}s source=${snapshot.video.source}${snapshot.video.orientation ? ` rotation=${formatCameraRotation(snapshot.video.orientation.rotation)}` : ""}${age > maxAgeMs ? " STALE" : ""}`);
|
|
603051
603944
|
if (snapshot.video.error) lines.push(`error: ${snapshot.video.error}`);
|
|
603052
603945
|
if (snapshot.video.framePath) lines.push(`frame: ${snapshot.video.framePath}`);
|
|
603946
|
+
const classifications = cameraClassificationsSummary(snapshot.video);
|
|
603947
|
+
if (classifications) lines.push(`Live classifications: ${classifications}`);
|
|
603053
603948
|
if (snapshot.video.asciiContext) lines.push(snapshot.video.asciiContext);
|
|
603054
603949
|
if (snapshot.video.inference) {
|
|
603055
603950
|
lines.push("Video inference:");
|
|
@@ -603066,6 +603961,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
|
|
|
603066
603961
|
lines.push(`timestamp: ${nowIso3(camera.updatedAt)} age=${Math.max(0, Math.round(age / 1e3))}s rotation=${rotation}${age > maxAgeMs ? " STALE" : ""}`);
|
|
603067
603962
|
if (camera.error) lines.push(`error: ${camera.error}`);
|
|
603068
603963
|
if (camera.framePath) lines.push(`frame: ${camera.framePath}`);
|
|
603964
|
+
const classifications = cameraClassificationsSummary(camera);
|
|
603965
|
+
if (classifications) lines.push(`classifications: ${classifications}`);
|
|
603069
603966
|
lines.push(`summary: ${cameraSnapshotSummary(camera)}`);
|
|
603070
603967
|
}
|
|
603071
603968
|
}
|
|
@@ -603117,6 +604014,16 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
|
|
|
603117
604014
|
function formatLiveSensorContext(repoRoot) {
|
|
603118
604015
|
return formatLiveSensorContextFromSnapshot(readLiveSensorSnapshot(repoRoot));
|
|
603119
604016
|
}
|
|
604017
|
+
function isLiveSensorSnapshotActive(snapshot) {
|
|
604018
|
+
const cfg = snapshot?.config;
|
|
604019
|
+
if (!cfg) return false;
|
|
604020
|
+
return Boolean(
|
|
604021
|
+
cfg.videoEnabled || cfg.audioEnabled || cfg.audioOutputEnabled || cfg.inferEnabled || cfg.clipEnabled || cfg.asrEnabled || cfg.audioAnalysisEnabled
|
|
604022
|
+
);
|
|
604023
|
+
}
|
|
604024
|
+
function isLiveSensorActiveForRepo(repoRoot) {
|
|
604025
|
+
return isLiveSensorSnapshotActive(readLiveSensorSnapshot(repoRoot));
|
|
604026
|
+
}
|
|
603120
604027
|
async function discoverAudioInputs() {
|
|
603121
604028
|
const devices = [];
|
|
603122
604029
|
const errors = [];
|
|
@@ -603250,31 +604157,101 @@ print(json.dumps({"ok": True, "scores": scores}))
|
|
|
603250
604157
|
async function tryRecordAudioDevice(device, durationSec, outputPath3) {
|
|
603251
604158
|
await recordAudioSample(device, durationSec, outputPath3);
|
|
603252
604159
|
}
|
|
604160
|
+
function hasLiveAudioSignal(activity) {
|
|
604161
|
+
if (!activity) return false;
|
|
604162
|
+
return activity.peak >= 0.025 || activity.rmsDb > -48 || activity.activeRatio >= 0.025;
|
|
604163
|
+
}
|
|
604164
|
+
function audioCaptureMethod(device) {
|
|
604165
|
+
return device.startsWith("pulse:") ? "pulse/ffmpeg" : "arecord";
|
|
604166
|
+
}
|
|
603253
604167
|
async function recordAudioSampleWithRecovery(preferred, devices, durationSec, outputPath3) {
|
|
603254
604168
|
const ordered = [];
|
|
603255
604169
|
const add3 = (id2) => {
|
|
603256
604170
|
const clean5 = String(id2 ?? "").trim();
|
|
603257
604171
|
if (clean5 && !ordered.includes(clean5)) ordered.push(clean5);
|
|
603258
604172
|
};
|
|
603259
|
-
|
|
603260
|
-
|
|
604173
|
+
const preferredInput = preferredLiveAudioInputId(devices, preferred);
|
|
604174
|
+
add3(preferredInput);
|
|
604175
|
+
if (preferred && !isAudioOutputMonitorId(preferred)) add3(preferred);
|
|
604176
|
+
for (const device of rankLiveAudioInputDevices(devices, preferred).filter((entry) => !isAudioOutputMonitorDevice(entry))) add3(device.id);
|
|
603261
604177
|
add3("pulse:default");
|
|
603262
604178
|
add3("default");
|
|
603263
|
-
for (const device of devices.filter((entry) => entry
|
|
604179
|
+
for (const device of rankLiveAudioInputDevices(devices, preferred).filter((entry) => isAudioOutputMonitorDevice(entry))) add3(device.id);
|
|
603264
604180
|
const errors = [];
|
|
604181
|
+
const attempts = [];
|
|
604182
|
+
let firstQuietCapture = null;
|
|
603265
604183
|
for (const candidate of ordered) {
|
|
604184
|
+
attempts.push(candidate);
|
|
603266
604185
|
try {
|
|
603267
604186
|
await tryRecordAudioDevice(candidate, durationSec, outputPath3);
|
|
603268
|
-
|
|
604187
|
+
const activity = readPcm16WavActivity(outputPath3);
|
|
604188
|
+
const method = audioCaptureMethod(candidate);
|
|
604189
|
+
if (hasLiveAudioSignal(activity)) {
|
|
604190
|
+
return { ok: true, device: candidate, method, errors, attempts, activity, signalDetected: true };
|
|
604191
|
+
}
|
|
604192
|
+
if (!firstQuietCapture) {
|
|
604193
|
+
firstQuietCapture = {
|
|
604194
|
+
device: candidate,
|
|
604195
|
+
method,
|
|
604196
|
+
activity,
|
|
604197
|
+
bytes: readFileSync88(outputPath3),
|
|
604198
|
+
errors: [...errors]
|
|
604199
|
+
};
|
|
604200
|
+
}
|
|
604201
|
+
errors.push(`${candidate}: captured but no live input signal detected`);
|
|
603269
604202
|
} catch (err) {
|
|
603270
604203
|
errors.push(`${candidate}: ${err instanceof Error ? err.message : String(err)}`.slice(0, 360));
|
|
603271
604204
|
}
|
|
603272
604205
|
}
|
|
603273
|
-
|
|
604206
|
+
if (firstQuietCapture) {
|
|
604207
|
+
writeFileSync56(outputPath3, firstQuietCapture.bytes);
|
|
604208
|
+
return {
|
|
604209
|
+
ok: true,
|
|
604210
|
+
device: firstQuietCapture.device,
|
|
604211
|
+
method: firstQuietCapture.method,
|
|
604212
|
+
errors,
|
|
604213
|
+
attempts,
|
|
604214
|
+
activity: firstQuietCapture.activity,
|
|
604215
|
+
signalDetected: false
|
|
604216
|
+
};
|
|
604217
|
+
}
|
|
604218
|
+
return { ok: false, device: preferredInput || preferred || "default", method: "none", errors, attempts };
|
|
603274
604219
|
}
|
|
603275
604220
|
function firstDeviceId(devices) {
|
|
603276
604221
|
return devices.find((device) => device.id && !/metadata\/non-capture/i.test(device.detail ?? ""))?.id ?? devices[0]?.id;
|
|
603277
604222
|
}
|
|
604223
|
+
function isAudioOutputMonitorId(id2) {
|
|
604224
|
+
return /(?:\.monitor\b|sink\.monitor|alsa_output|output\.monitor)/i.test(String(id2 ?? ""));
|
|
604225
|
+
}
|
|
604226
|
+
function isAudioOutputMonitorDevice(device) {
|
|
604227
|
+
if (!device) return false;
|
|
604228
|
+
return isAudioOutputMonitorId(device.id) || isAudioOutputMonitorId(device.label) || isAudioOutputMonitorId(device.detail);
|
|
604229
|
+
}
|
|
604230
|
+
function preferredLiveAudioInputId(devices, configured) {
|
|
604231
|
+
const usable = devices.filter((device) => device.id && !isAudioOutputMonitorDevice(device));
|
|
604232
|
+
if (configured && usable.some((device) => device.id === configured)) return configured;
|
|
604233
|
+
return rankLiveAudioInputDevices(devices, configured)[0]?.id ?? firstDeviceId(devices);
|
|
604234
|
+
}
|
|
604235
|
+
function liveAudioDeviceRank(device, configured) {
|
|
604236
|
+
let rank = 0;
|
|
604237
|
+
if (device.id === configured) rank -= 40;
|
|
604238
|
+
if (isAudioOutputMonitorDevice(device)) rank += 500;
|
|
604239
|
+
if (device.source === "pulse" || device.source === "pipewire") rank -= 20;
|
|
604240
|
+
if (device.source === "alsa") rank -= 10;
|
|
604241
|
+
if (device.source === "default") rank += 30;
|
|
604242
|
+
if (/metadata\/non-capture/i.test(device.detail ?? "")) rank += 300;
|
|
604243
|
+
return rank;
|
|
604244
|
+
}
|
|
604245
|
+
function rankLiveAudioInputDevices(devices, configured) {
|
|
604246
|
+
const seen = /* @__PURE__ */ new Set();
|
|
604247
|
+
const ranked = [];
|
|
604248
|
+
for (const device of devices) {
|
|
604249
|
+
if (!device.id || seen.has(device.id)) continue;
|
|
604250
|
+
seen.add(device.id);
|
|
604251
|
+
ranked.push(device);
|
|
604252
|
+
}
|
|
604253
|
+
return ranked.sort((a2, b) => liveAudioDeviceRank(a2, configured) - liveAudioDeviceRank(b, configured));
|
|
604254
|
+
}
|
|
603278
604255
|
function getLiveSensorManager(repoRoot) {
|
|
603279
604256
|
const key = repoRoot;
|
|
603280
604257
|
let manager = managers.get(key);
|
|
@@ -603316,13 +604293,17 @@ function formatLiveStatus(snapshot) {
|
|
|
603316
604293
|
}
|
|
603317
604294
|
return lines.join("\n");
|
|
603318
604295
|
}
|
|
603319
|
-
var DEFAULT_CONFIG7, managers, LiveSensorManager;
|
|
604296
|
+
var MIN_VIDEO_INTERVAL_MS, LOW_LATENCY_VIDEO_INTERVAL_MS, MIN_AUDIO_INTERVAL_MS, LOW_LATENCY_AUDIO_INTERVAL_MS, DEFAULT_CONFIG7, managers, DASHBOARD_ANSI_STICKY_PATTERN, LiveSensorManager;
|
|
603320
604297
|
var init_live_sensors = __esm({
|
|
603321
604298
|
"packages/cli/src/tui/live-sensors.ts"() {
|
|
603322
604299
|
"use strict";
|
|
603323
604300
|
init_dist5();
|
|
603324
604301
|
init_async_process();
|
|
603325
604302
|
init_image_ascii_preview();
|
|
604303
|
+
MIN_VIDEO_INTERVAL_MS = 250;
|
|
604304
|
+
LOW_LATENCY_VIDEO_INTERVAL_MS = 250;
|
|
604305
|
+
MIN_AUDIO_INTERVAL_MS = 1500;
|
|
604306
|
+
LOW_LATENCY_AUDIO_INTERVAL_MS = 2e3;
|
|
603326
604307
|
DEFAULT_CONFIG7 = {
|
|
603327
604308
|
videoEnabled: false,
|
|
603328
604309
|
audioEnabled: false,
|
|
@@ -603332,10 +604313,11 @@ var init_live_sensors = __esm({
|
|
|
603332
604313
|
asrEnabled: false,
|
|
603333
604314
|
audioAnalysisEnabled: false,
|
|
603334
604315
|
cameraOrientation: {},
|
|
603335
|
-
videoIntervalMs:
|
|
603336
|
-
audioIntervalMs:
|
|
604316
|
+
videoIntervalMs: 1e3,
|
|
604317
|
+
audioIntervalMs: 6e3
|
|
603337
604318
|
};
|
|
603338
604319
|
managers = /* @__PURE__ */ new Map();
|
|
604320
|
+
DASHBOARD_ANSI_STICKY_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/y;
|
|
603339
604321
|
LiveSensorManager = class {
|
|
603340
604322
|
constructor(repoRoot) {
|
|
603341
604323
|
this.repoRoot = repoRoot;
|
|
@@ -603363,6 +604345,13 @@ var init_live_sensors = __esm({
|
|
|
603363
604345
|
lastAgentReviewAt = 0;
|
|
603364
604346
|
autoOrientationInFlight = /* @__PURE__ */ new Set();
|
|
603365
604347
|
lastFeedbackAt = /* @__PURE__ */ new Map();
|
|
604348
|
+
lastClipAt = /* @__PURE__ */ new Map();
|
|
604349
|
+
nextVideoSourceIndex = 0;
|
|
604350
|
+
liveSpeechSink = null;
|
|
604351
|
+
liveSpeechEnabled = false;
|
|
604352
|
+
lastLiveSpeechAt = 0;
|
|
604353
|
+
lastLiveSpeechByKey = /* @__PURE__ */ new Map();
|
|
604354
|
+
liveSpeechInFlight = /* @__PURE__ */ new Set();
|
|
603366
604355
|
intakeAgentConfig;
|
|
603367
604356
|
setStatusSink(sink) {
|
|
603368
604357
|
this.statusSink = sink ?? null;
|
|
@@ -603374,6 +604363,12 @@ var init_live_sensors = __esm({
|
|
|
603374
604363
|
setAgentActionSink(sink) {
|
|
603375
604364
|
this.agentActionSink = sink ?? null;
|
|
603376
604365
|
}
|
|
604366
|
+
setLiveSpeechSink(sink) {
|
|
604367
|
+
this.liveSpeechSink = sink ?? null;
|
|
604368
|
+
}
|
|
604369
|
+
setLiveSpeechEnabled(enabled2) {
|
|
604370
|
+
this.liveSpeechEnabled = enabled2;
|
|
604371
|
+
}
|
|
603377
604372
|
setAudioIntakeAgentConfig(config) {
|
|
603378
604373
|
this.intakeAgentConfig = config && config.backendUrl && config.model ? { backendUrl: config.backendUrl, model: config.model, apiKey: config.apiKey } : void 0;
|
|
603379
604374
|
}
|
|
@@ -603469,16 +604464,14 @@ var init_live_sensors = __esm({
|
|
|
603469
604464
|
if (!framePath) return `Camera orientation detection captured ${target}, but no frame path was returned.`;
|
|
603470
604465
|
const cvScores = await scoreCameraOrientationWithOpenCv(framePath);
|
|
603471
604466
|
const cvDecision = cvScores ? chooseCameraOrientationFromScores(cvScores) : null;
|
|
603472
|
-
if (cvDecision && cvDecision.confidence >= 0.68) {
|
|
603473
|
-
const orientation2 = this.setCameraRotation(target, cvDecision.rotation, "auto", cvDecision.reason, cvDecision.confidence);
|
|
603474
|
-
return orientation2 ? `Auto orientation for ${target}: ${describeCameraOrientation(orientation2)}` : `Auto orientation for ${target} could not be persisted.`;
|
|
603475
|
-
}
|
|
603476
604467
|
const prompt = [
|
|
603477
604468
|
"Determine whether this camera frame is upright for a human viewer.",
|
|
603478
604469
|
"Return only JSON with this schema:",
|
|
603479
604470
|
'{"rotation_degrees":0|90|180|270,"confidence":0.0-1.0,"reason":"brief visual evidence"}',
|
|
603480
604471
|
"rotation_degrees is the clockwise correction to apply to future frames so they are upright.",
|
|
603481
|
-
"Use 90 if the image must rotate clockwise; use 270 if it must rotate counter-clockwise; use 180 if upside down; use 0 if already upright or inconclusive."
|
|
604472
|
+
"Use 90 if the image must rotate clockwise; use 270 if it must rotate counter-clockwise; use 180 if upside down; use 0 if already upright or inconclusive.",
|
|
604473
|
+
"If the visible scene is sideways, choose the correction direction that makes people, faces, screens, and text upright; if the opposite direction would make them sideways, do not choose it.",
|
|
604474
|
+
cvDecision ? `Computer-vision candidate: ${formatCameraRotation(cvDecision.rotation)} confidence=${cvDecision.confidence.toFixed(2)} evidence=${cvDecision.reason}` : "Computer-vision candidate: unavailable."
|
|
603482
604475
|
].join("\n");
|
|
603483
604476
|
const vision = await new VisionTool(this.repoRoot).execute({
|
|
603484
604477
|
action: "query",
|
|
@@ -603501,9 +604494,10 @@ var init_live_sensors = __esm({
|
|
|
603501
604494
|
}
|
|
603502
604495
|
return `Camera orientation detection for ${target} was inconclusive. Vision output did not contain a structured rotation decision.`;
|
|
603503
604496
|
}
|
|
603504
|
-
const
|
|
603505
|
-
const
|
|
603506
|
-
const
|
|
604497
|
+
const visionConfidence = decision2.confidence ?? 0;
|
|
604498
|
+
const useCv = Boolean(cvDecision) && (visionConfidence < 0.55 || cvDecision.rotation === decision2.rotation && cvDecision.confidence > visionConfidence + 0.18);
|
|
604499
|
+
const selected = useCv && cvDecision ? cvDecision : decision2;
|
|
604500
|
+
const evidence = useCv ? cvDecision?.reason ?? "" : `${decision2.reason || boundedText(vision.llmContent || vision.output, 320)}${cvDecision ? `; cv=${cvDecision.reason}` : ""}`;
|
|
603507
604501
|
const orientation = this.setCameraRotation(target, selected.rotation, "auto", evidence, selected.confidence);
|
|
603508
604502
|
return orientation ? `Auto orientation for ${target}: ${describeCameraOrientation(orientation)}` : `Auto orientation for ${target} could not be persisted.`;
|
|
603509
604503
|
} finally {
|
|
@@ -603532,7 +604526,10 @@ var init_live_sensors = __esm({
|
|
|
603532
604526
|
errors
|
|
603533
604527
|
};
|
|
603534
604528
|
if (!this.config.selectedCamera) this.config.selectedCamera = firstDeviceId(video);
|
|
603535
|
-
|
|
604529
|
+
const preferredInput = preferredLiveAudioInputId(inputs.devices, this.config.selectedAudioInput);
|
|
604530
|
+
if (!this.config.selectedAudioInput || isAudioOutputMonitorId(this.config.selectedAudioInput) && preferredInput && !isAudioOutputMonitorId(preferredInput)) {
|
|
604531
|
+
this.config.selectedAudioInput = preferredInput;
|
|
604532
|
+
}
|
|
603536
604533
|
if (!this.config.selectedAudioOutput) this.config.selectedAudioOutput = firstDeviceId(outputs.devices);
|
|
603537
604534
|
this.persist();
|
|
603538
604535
|
return this.devices;
|
|
@@ -603541,16 +604538,18 @@ var init_live_sensors = __esm({
|
|
|
603541
604538
|
this.config = {
|
|
603542
604539
|
...this.config,
|
|
603543
604540
|
...patch,
|
|
603544
|
-
videoIntervalMs: Math.max(
|
|
603545
|
-
audioIntervalMs: Math.max(
|
|
604541
|
+
videoIntervalMs: Math.max(MIN_VIDEO_INTERVAL_MS, Number(patch.videoIntervalMs ?? this.config.videoIntervalMs)),
|
|
604542
|
+
audioIntervalMs: Math.max(MIN_AUDIO_INTERVAL_MS, Number(patch.audioIntervalMs ?? this.config.audioIntervalMs))
|
|
603546
604543
|
};
|
|
603547
604544
|
this.persist();
|
|
604545
|
+
this.clearLoopTimers();
|
|
603548
604546
|
this.ensureLoops();
|
|
603549
604547
|
this.emitStatus();
|
|
603550
604548
|
return this.getConfig();
|
|
603551
604549
|
}
|
|
603552
604550
|
stopAll() {
|
|
603553
604551
|
this.agentActionEnabled = false;
|
|
604552
|
+
this.liveSpeechEnabled = false;
|
|
603554
604553
|
this.configure({
|
|
603555
604554
|
videoEnabled: false,
|
|
603556
604555
|
audioEnabled: false,
|
|
@@ -603563,6 +604562,7 @@ var init_live_sensors = __esm({
|
|
|
603563
604562
|
}
|
|
603564
604563
|
startRunMode(options2 = {}) {
|
|
603565
604564
|
this.agentActionEnabled = Boolean(options2.agent);
|
|
604565
|
+
this.liveSpeechEnabled = Boolean(options2.speech ?? options2.agent);
|
|
603566
604566
|
this.configure({
|
|
603567
604567
|
videoEnabled: true,
|
|
603568
604568
|
inferEnabled: true,
|
|
@@ -603570,8 +604570,8 @@ var init_live_sensors = __esm({
|
|
|
603570
604570
|
audioEnabled: true,
|
|
603571
604571
|
asrEnabled: true,
|
|
603572
604572
|
audioAnalysisEnabled: true,
|
|
603573
|
-
videoIntervalMs: options2.lowLatency === false ? this.config.videoIntervalMs : Math.min(this.config.videoIntervalMs,
|
|
603574
|
-
audioIntervalMs: options2.lowLatency === false ? this.config.audioIntervalMs : Math.min(this.config.audioIntervalMs,
|
|
604573
|
+
videoIntervalMs: options2.lowLatency === false ? this.config.videoIntervalMs : Math.min(this.config.videoIntervalMs, LOW_LATENCY_VIDEO_INTERVAL_MS),
|
|
604574
|
+
audioIntervalMs: options2.lowLatency === false ? this.config.audioIntervalMs : Math.min(this.config.audioIntervalMs, LOW_LATENCY_AUDIO_INTERVAL_MS)
|
|
603575
604575
|
});
|
|
603576
604576
|
for (const source of this.activeVideoSources()) {
|
|
603577
604577
|
const current = this.getCameraOrientation(source);
|
|
@@ -603594,7 +604594,7 @@ var init_live_sensors = __esm({
|
|
|
603594
604594
|
}, 6e4);
|
|
603595
604595
|
});
|
|
603596
604596
|
}
|
|
603597
|
-
void this.sampleVideoNow(true).catch((err) => {
|
|
604597
|
+
void this.sampleVideoNow(true, { mode: "all" }).catch((err) => {
|
|
603598
604598
|
this.emitFeedback({
|
|
603599
604599
|
kind: "error",
|
|
603600
604600
|
title: "Live video sample failed",
|
|
@@ -603684,6 +604684,10 @@ var init_live_sensors = __esm({
|
|
|
603684
604684
|
ascii: preview?.ascii,
|
|
603685
604685
|
plainAscii: preview?.plainAscii,
|
|
603686
604686
|
renderer: preview?.renderer,
|
|
604687
|
+
previewWidth: preview?.width,
|
|
604688
|
+
previewHeight: preview?.height,
|
|
604689
|
+
frameWidth: preview?.imageWidth,
|
|
604690
|
+
frameHeight: preview?.imageHeight,
|
|
603687
604691
|
asciiContext,
|
|
603688
604692
|
rotation,
|
|
603689
604693
|
orientation
|
|
@@ -603694,7 +604698,8 @@ var init_live_sensors = __esm({
|
|
|
603694
604698
|
[source]: {
|
|
603695
604699
|
...cameraView,
|
|
603696
604700
|
summary: this.snapshot.cameras?.[source]?.summary,
|
|
603697
|
-
inference: this.snapshot.cameras?.[source]?.inference
|
|
604701
|
+
inference: this.snapshot.cameras?.[source]?.inference,
|
|
604702
|
+
classifications: this.snapshot.cameras?.[source]?.classifications
|
|
603698
604703
|
}
|
|
603699
604704
|
};
|
|
603700
604705
|
this.snapshot.events = mergeRecentById(this.snapshot.events, [{
|
|
@@ -603727,7 +604732,11 @@ var init_live_sensors = __esm({
|
|
|
603727
604732
|
displayPath,
|
|
603728
604733
|
ascii: preview?.ascii,
|
|
603729
604734
|
plainAscii: preview?.plainAscii,
|
|
603730
|
-
renderer: preview?.renderer
|
|
604735
|
+
renderer: preview?.renderer,
|
|
604736
|
+
frameWidth: preview?.imageWidth,
|
|
604737
|
+
frameHeight: preview?.imageHeight,
|
|
604738
|
+
previewWidth: preview?.width,
|
|
604739
|
+
previewHeight: preview?.height
|
|
603731
604740
|
};
|
|
603732
604741
|
}
|
|
603733
604742
|
async recognizeFrameObjects(framePath, source, observedAt = Date.now(), options2 = {}) {
|
|
@@ -603780,24 +604789,33 @@ var init_live_sensors = __esm({
|
|
|
603780
604789
|
const sampledAt = Date.now();
|
|
603781
604790
|
if (forceInfer && source) {
|
|
603782
604791
|
const loop = new LiveMediaLoopTool(this.repoRoot);
|
|
604792
|
+
const inferFromCapturedFrame = preview.ok && Boolean(preview.framePath);
|
|
603783
604793
|
const result = await loop.execute({
|
|
603784
604794
|
action: "watch",
|
|
603785
|
-
source_kind: "camera",
|
|
603786
|
-
camera: source,
|
|
603787
|
-
|
|
603788
|
-
|
|
603789
|
-
|
|
603790
|
-
|
|
604795
|
+
source_kind: inferFromCapturedFrame ? "file" : "camera",
|
|
604796
|
+
...inferFromCapturedFrame ? { path: preview.framePath } : { camera: source },
|
|
604797
|
+
yolo_family: "yoloe-26",
|
|
604798
|
+
yolo_scale: "n",
|
|
604799
|
+
yoloe_prompt_mode: "prompt_free",
|
|
604800
|
+
duration_sec: inferFromCapturedFrame ? 0.1 : 0.75,
|
|
604801
|
+
sample_interval_sec: inferFromCapturedFrame ? 0.1 : 0.25,
|
|
604802
|
+
max_frames: 1,
|
|
604803
|
+
rotate_degrees: inferFromCapturedFrame ? 0 : orientation?.rotation ?? 0,
|
|
603791
604804
|
auto_bootstrap: true,
|
|
603792
604805
|
audio_mode: "none",
|
|
603793
604806
|
detect_objects: true,
|
|
603794
604807
|
track_objects: true,
|
|
604808
|
+
segment_objects: true,
|
|
604809
|
+
extract_segments: true,
|
|
604810
|
+
max_segments_per_frame: 8,
|
|
603795
604811
|
crop_faces: true,
|
|
603796
604812
|
recognize_faces: true
|
|
603797
604813
|
});
|
|
603798
604814
|
inference = result.success ? String(result.output || result.llmContent || "") : `Video inference failed: ${result.error || result.output || "unknown error"}`;
|
|
603799
604815
|
const livePayload = result.success ? parseLiveMediaPayload(result.llmContent || result.output) : null;
|
|
604816
|
+
if (livePayload) livePayload.source = source;
|
|
603800
604817
|
const observations = observationsFromLiveMediaPayload(livePayload, sampledAt, source);
|
|
604818
|
+
const classifications = classificationsFromLiveMediaPayload(livePayload, sampledAt, source);
|
|
603801
604819
|
this.snapshot.events = mergeRecentById(this.snapshot.events, observations.events, 50);
|
|
603802
604820
|
this.snapshot.people = mergeRecentById(this.snapshot.people, observations.people, 24);
|
|
603803
604821
|
const existing = this.snapshot.cameras?.[source] ?? this.snapshot.video ?? { updatedAt: Date.now(), source };
|
|
@@ -603807,6 +604825,7 @@ var init_live_sensors = __esm({
|
|
|
603807
604825
|
source,
|
|
603808
604826
|
inference,
|
|
603809
604827
|
summary: summarizeInference(inference),
|
|
604828
|
+
classifications: mergeCameraClassifications(existing.classifications, classifications),
|
|
603810
604829
|
rotation: orientation?.rotation ?? 0,
|
|
603811
604830
|
orientation,
|
|
603812
604831
|
...preview.ok ? { error: void 0 } : { error: preview.message }
|
|
@@ -603831,6 +604850,16 @@ var init_live_sensors = __esm({
|
|
|
603831
604850
|
`${unknownPeople.length} unknown individual(s) observed on live camera ${source}`,
|
|
603832
604851
|
unknownPeople.map((entry) => `${entry.cropPath || entry.trackId || entry.source} ${entry.evidence || ""}`).join("; ")
|
|
603833
604852
|
);
|
|
604853
|
+
this.maybeSpeakLive({
|
|
604854
|
+
key: `unknown-person:${source}`,
|
|
604855
|
+
kind: "unknown_person",
|
|
604856
|
+
source,
|
|
604857
|
+
summary: `${unknownPeople.length} unknown individual(s) observed on live camera ${source}`,
|
|
604858
|
+
evidence: unknownPeople.map((entry) => `${entry.cropPath || entry.trackId || entry.source} ${entry.evidence || ""}`).join("; "),
|
|
604859
|
+
defaultText: "I see someone here, but I do not know who. Who is there?",
|
|
604860
|
+
urgency: "high",
|
|
604861
|
+
observedAt: sampledAt
|
|
604862
|
+
}, 9e4);
|
|
603834
604863
|
}
|
|
603835
604864
|
const triggerEvents = observations.events.filter((entry) => entry.kind === "visual_trigger");
|
|
603836
604865
|
if (triggerEvents.length > 0) {
|
|
@@ -603838,13 +604867,29 @@ var init_live_sensors = __esm({
|
|
|
603838
604867
|
`Visual trigger hit in live camera stream ${source}`,
|
|
603839
604868
|
triggerEvents.map((entry) => entry.summary).join("; ")
|
|
603840
604869
|
);
|
|
604870
|
+
this.maybeSpeakLive({
|
|
604871
|
+
key: `visual-trigger:${source}:${triggerEvents[0]?.title ?? "trigger"}`,
|
|
604872
|
+
kind: "visual_trigger",
|
|
604873
|
+
source,
|
|
604874
|
+
summary: `Visual trigger hit in live camera stream ${source}`,
|
|
604875
|
+
evidence: triggerEvents.map((entry) => entry.summary).join("; "),
|
|
604876
|
+
defaultText: "I noticed something important in view. Should I inspect it?",
|
|
604877
|
+
urgency: "high",
|
|
604878
|
+
observedAt: sampledAt
|
|
604879
|
+
}, 45e3);
|
|
603841
604880
|
}
|
|
603842
604881
|
}
|
|
603843
|
-
|
|
604882
|
+
const lastClip = this.lastClipAt.get(source) ?? 0;
|
|
604883
|
+
if (this.config.clipEnabled && preview.ok && preview.framePath && source && sampledAt - lastClip >= 5e3) {
|
|
604884
|
+
this.lastClipAt.set(source, sampledAt);
|
|
603844
604885
|
clipSummary = await this.recognizeFrameObjects(preview.framePath, source, sampledAt, { emit: false });
|
|
603845
604886
|
const current = this.snapshot.cameras?.[source];
|
|
603846
604887
|
if (current) {
|
|
603847
604888
|
current.summary = [current.summary, clipSummary ? `clip: ${trimOneLine(clipSummary, 160)}` : ""].filter(Boolean).join(" | ");
|
|
604889
|
+
current.classifications = mergeCameraClassifications(
|
|
604890
|
+
current.classifications,
|
|
604891
|
+
classificationsFromClipSummary(clipSummary, sampledAt, preview.framePath)
|
|
604892
|
+
);
|
|
603848
604893
|
this.snapshot.cameras = { ...this.snapshot.cameras ?? {}, [source]: current };
|
|
603849
604894
|
if (this.snapshot.video?.source === source) this.snapshot.video = current;
|
|
603850
604895
|
this.persist();
|
|
@@ -603855,14 +604900,16 @@ ${inference}` : "", clipSummary ? `
|
|
|
603855
604900
|
CLIP visual memory:
|
|
603856
604901
|
${clipSummary}` : ""].filter(Boolean).join("\n");
|
|
603857
604902
|
}
|
|
603858
|
-
async sampleVideoNow(forceInfer = this.config.inferEnabled) {
|
|
604903
|
+
async sampleVideoNow(forceInfer = this.config.inferEnabled, options2 = {}) {
|
|
603859
604904
|
if (this.videoInFlight) return "Video sampler is already running.";
|
|
603860
604905
|
this.videoInFlight = true;
|
|
603861
604906
|
try {
|
|
603862
|
-
const
|
|
604907
|
+
const allSources = this.activeVideoSources();
|
|
604908
|
+
if (allSources.length === 0) return "No camera selected. Use /live refresh after connecting hardware.";
|
|
604909
|
+
const sources = options2.mode === "round-robin" ? [allSources[this.nextVideoSourceIndex++ % allSources.length]] : allSources;
|
|
603863
604910
|
if (sources.length === 0) return "No camera selected. Use /live refresh after connecting hardware.";
|
|
603864
604911
|
const outputs = [];
|
|
603865
|
-
const previewWidth = liveDashboardPreviewWidthForSources(
|
|
604912
|
+
const previewWidth = liveDashboardPreviewWidthForSources(allSources.length);
|
|
603866
604913
|
for (const source of sources) {
|
|
603867
604914
|
outputs.push(await this.sampleSingleVideoNow(source, forceInfer, previewWidth));
|
|
603868
604915
|
}
|
|
@@ -603877,7 +604924,9 @@ ${output}`).join("\n\n");
|
|
|
603877
604924
|
if (this.audioInFlight) return "Audio sampler is already running.";
|
|
603878
604925
|
this.audioInFlight = true;
|
|
603879
604926
|
try {
|
|
603880
|
-
const
|
|
604927
|
+
const preferredInput = preferredLiveAudioInputId(this.devices.audioInputs, this.config.selectedAudioInput) || "default";
|
|
604928
|
+
const input = this.config.selectedAudioInput && !isAudioOutputMonitorId(this.config.selectedAudioInput) ? this.config.selectedAudioInput : preferredInput;
|
|
604929
|
+
if (input !== this.config.selectedAudioInput) this.config.selectedAudioInput = input;
|
|
603881
604930
|
const outputDir2 = liveDir(this.repoRoot);
|
|
603882
604931
|
ensureLiveDir(this.repoRoot);
|
|
603883
604932
|
const recordingPath = join124(outputDir2, `audio-${Date.now()}.wav`);
|
|
@@ -603908,7 +604957,10 @@ ${output}`).join("\n\n");
|
|
|
603908
604957
|
if (capture.device !== this.config.selectedAudioInput) {
|
|
603909
604958
|
this.config.selectedAudioInput = capture.device;
|
|
603910
604959
|
}
|
|
603911
|
-
const activity = readPcm16WavActivity(recordingPath);
|
|
604960
|
+
const activity = capture.activity ?? readPcm16WavActivity(recordingPath);
|
|
604961
|
+
if (capture.ok && capture.signalDetected === false) {
|
|
604962
|
+
audioErrors.push(`No live input signal detected after probing ${capture.attempts?.length ?? 1} audio source(s); using quiet capture from ${capture.device}`);
|
|
604963
|
+
}
|
|
603912
604964
|
if (this.config.asrEnabled) {
|
|
603913
604965
|
try {
|
|
603914
604966
|
const asr = await new TranscribeFileTool(this.repoRoot).execute({ path: recordingPath, model: "tiny" });
|
|
@@ -603985,6 +605037,16 @@ ${output}`).join("\n\n");
|
|
|
603985
605037
|
"Live audio intake classified speech as intended for Omnius",
|
|
603986
605038
|
`transcript=${transcript}; confidence=${intake.confidence.toFixed(2)}; reason=${intake.reason}`
|
|
603987
605039
|
);
|
|
605040
|
+
this.maybeSpeakLive({
|
|
605041
|
+
key: `audio-intent:${capture.device}:${transcript.slice(0, 48)}`,
|
|
605042
|
+
kind: "audio_intent",
|
|
605043
|
+
source: capture.device,
|
|
605044
|
+
summary: "Live audio intake classified speech as intended for Omnius",
|
|
605045
|
+
evidence: `transcript=${transcript}; confidence=${intake.confidence.toFixed(2)}; reason=${intake.reason}`,
|
|
605046
|
+
defaultText: "I heard you. I am checking the live context now.",
|
|
605047
|
+
urgency: "high",
|
|
605048
|
+
observedAt: this.snapshot.audio.updatedAt
|
|
605049
|
+
}, 2e4);
|
|
603988
605050
|
}
|
|
603989
605051
|
this.emitDashboard();
|
|
603990
605052
|
return [
|
|
@@ -604002,30 +605064,63 @@ ${analysis}` : ""
|
|
|
604002
605064
|
this.audioInFlight = false;
|
|
604003
605065
|
}
|
|
604004
605066
|
}
|
|
605067
|
+
clearLoopTimers() {
|
|
605068
|
+
if (this.videoTimer) {
|
|
605069
|
+
clearTimeout(this.videoTimer);
|
|
605070
|
+
this.videoTimer = null;
|
|
605071
|
+
}
|
|
605072
|
+
if (this.audioTimer) {
|
|
605073
|
+
clearTimeout(this.audioTimer);
|
|
605074
|
+
this.audioTimer = null;
|
|
605075
|
+
}
|
|
605076
|
+
}
|
|
605077
|
+
scheduleVideoLoop(delayMs) {
|
|
605078
|
+
if (!this.config.videoEnabled || this.videoTimer) return;
|
|
605079
|
+
this.videoTimer = setTimeout(async () => {
|
|
605080
|
+
this.videoTimer = null;
|
|
605081
|
+
if (!this.config.videoEnabled) return;
|
|
605082
|
+
try {
|
|
605083
|
+
await this.sampleVideoNow(this.config.inferEnabled || this.config.clipEnabled, { mode: "round-robin" });
|
|
605084
|
+
} catch {
|
|
605085
|
+
} finally {
|
|
605086
|
+
if (this.config.videoEnabled) {
|
|
605087
|
+
this.scheduleVideoLoop(this.config.videoIntervalMs);
|
|
605088
|
+
}
|
|
605089
|
+
}
|
|
605090
|
+
}, Math.max(0, delayMs));
|
|
605091
|
+
this.videoTimer.unref?.();
|
|
605092
|
+
}
|
|
605093
|
+
scheduleAudioLoop(delayMs) {
|
|
605094
|
+
const wantsAudio = this.config.audioEnabled && (this.config.asrEnabled || this.config.audioAnalysisEnabled);
|
|
605095
|
+
if (!wantsAudio || this.audioTimer) return;
|
|
605096
|
+
this.audioTimer = setTimeout(async () => {
|
|
605097
|
+
this.audioTimer = null;
|
|
605098
|
+
const stillWantsAudio = this.config.audioEnabled && (this.config.asrEnabled || this.config.audioAnalysisEnabled);
|
|
605099
|
+
if (!stillWantsAudio) return;
|
|
605100
|
+
try {
|
|
605101
|
+
await this.sampleAudioNow();
|
|
605102
|
+
} catch {
|
|
605103
|
+
} finally {
|
|
605104
|
+
const wantsNext = this.config.audioEnabled && (this.config.asrEnabled || this.config.audioAnalysisEnabled);
|
|
605105
|
+
if (wantsNext) {
|
|
605106
|
+
this.scheduleAudioLoop(this.config.audioIntervalMs);
|
|
605107
|
+
}
|
|
605108
|
+
}
|
|
605109
|
+
}, Math.max(0, delayMs));
|
|
605110
|
+
this.audioTimer.unref?.();
|
|
605111
|
+
}
|
|
604005
605112
|
ensureLoops() {
|
|
604006
605113
|
if (this.config.videoEnabled && !this.videoTimer) {
|
|
604007
|
-
|
|
604008
|
-
});
|
|
604009
|
-
this.videoTimer = setInterval(() => {
|
|
604010
|
-
void this.sampleVideoNow(this.config.inferEnabled || this.config.clipEnabled).catch(() => {
|
|
604011
|
-
});
|
|
604012
|
-
}, this.config.videoIntervalMs);
|
|
604013
|
-
this.videoTimer.unref?.();
|
|
605114
|
+
this.scheduleVideoLoop(0);
|
|
604014
605115
|
} else if (!this.config.videoEnabled && this.videoTimer) {
|
|
604015
|
-
|
|
605116
|
+
clearTimeout(this.videoTimer);
|
|
604016
605117
|
this.videoTimer = null;
|
|
604017
605118
|
}
|
|
604018
605119
|
const wantsAudio = this.config.audioEnabled && (this.config.asrEnabled || this.config.audioAnalysisEnabled);
|
|
604019
605120
|
if (wantsAudio && !this.audioTimer) {
|
|
604020
|
-
|
|
604021
|
-
});
|
|
604022
|
-
this.audioTimer = setInterval(() => {
|
|
604023
|
-
void this.sampleAudioNow().catch(() => {
|
|
604024
|
-
});
|
|
604025
|
-
}, this.config.audioIntervalMs);
|
|
604026
|
-
this.audioTimer.unref?.();
|
|
605121
|
+
this.scheduleAudioLoop(0);
|
|
604027
605122
|
} else if (!wantsAudio && this.audioTimer) {
|
|
604028
|
-
|
|
605123
|
+
clearTimeout(this.audioTimer);
|
|
604029
605124
|
this.audioTimer = null;
|
|
604030
605125
|
}
|
|
604031
605126
|
}
|
|
@@ -604060,7 +605155,7 @@ ${analysis}` : ""
|
|
|
604060
605155
|
maybeRequestAgentReview(reason, evidence) {
|
|
604061
605156
|
if (!this.agentActionEnabled || !this.agentActionSink) return;
|
|
604062
605157
|
const now2 = Date.now();
|
|
604063
|
-
if (now2 - this.lastAgentReviewAt <
|
|
605158
|
+
if (now2 - this.lastAgentReviewAt < 6e4) return;
|
|
604064
605159
|
this.lastAgentReviewAt = now2;
|
|
604065
605160
|
const prompt = [
|
|
604066
605161
|
"Live environment PFC review.",
|
|
@@ -604083,6 +605178,38 @@ ${analysis}` : ""
|
|
|
604083
605178
|
} catch {
|
|
604084
605179
|
}
|
|
604085
605180
|
}
|
|
605181
|
+
maybeSpeakLive(proposal, perKeyCooldownMs) {
|
|
605182
|
+
if (!this.liveSpeechEnabled || !this.liveSpeechSink) return;
|
|
605183
|
+
const now2 = Date.now();
|
|
605184
|
+
if (now2 - this.lastLiveSpeechAt < 12e3) return;
|
|
605185
|
+
const lastForKey = this.lastLiveSpeechByKey.get(proposal.key) ?? 0;
|
|
605186
|
+
if (now2 - lastForKey < perKeyCooldownMs) return;
|
|
605187
|
+
if (this.liveSpeechInFlight.has(proposal.key)) return;
|
|
605188
|
+
this.liveSpeechInFlight.add(proposal.key);
|
|
605189
|
+
void classifyLiveSpeech(proposal, this.intakeAgentConfig).then((decision2) => {
|
|
605190
|
+
this.lastLiveSpeechByKey.set(proposal.key, Date.now());
|
|
605191
|
+
if (!decision2.speak || !decision2.text.trim()) return;
|
|
605192
|
+
this.lastLiveSpeechAt = Date.now();
|
|
605193
|
+
const text2 = trimOneLine(decision2.text, 180);
|
|
605194
|
+
this.liveSpeechSink?.(text2, decision2);
|
|
605195
|
+
this.emitFeedbackThrottled(`speech:${proposal.key}`, {
|
|
605196
|
+
kind: "speech",
|
|
605197
|
+
title: "Live speech emitted",
|
|
605198
|
+
observedAt: decision2.updatedAt,
|
|
605199
|
+
message: `${text2}
|
|
605200
|
+
reason=${decision2.reason} confidence=${decision2.confidence.toFixed(2)} source=${decision2.source}`
|
|
605201
|
+
}, 15e3);
|
|
605202
|
+
}).catch((err) => {
|
|
605203
|
+
this.emitFeedbackThrottled(`speech-error:${proposal.key}`, {
|
|
605204
|
+
kind: "error",
|
|
605205
|
+
title: "Live speech arbiter failed",
|
|
605206
|
+
observedAt: Date.now(),
|
|
605207
|
+
message: err instanceof Error ? err.message : String(err)
|
|
605208
|
+
}, 6e4);
|
|
605209
|
+
}).finally(() => {
|
|
605210
|
+
this.liveSpeechInFlight.delete(proposal.key);
|
|
605211
|
+
});
|
|
605212
|
+
}
|
|
604086
605213
|
persist() {
|
|
604087
605214
|
ensureLiveDir(this.repoRoot);
|
|
604088
605215
|
writeFileSync56(liveConfigPath(this.repoRoot), JSON.stringify(this.config, null, 2), "utf8");
|
|
@@ -638440,6 +639567,10 @@ async function showPruneMenu(options2) {
|
|
|
638440
639567
|
});
|
|
638441
639568
|
if (!result.confirmed || !result.key || result.key === "back") return;
|
|
638442
639569
|
const start2 = (label, o2) => {
|
|
639570
|
+
if (isLiveSensorActiveForRepo(workingDir)) {
|
|
639571
|
+
renderWarning("Live audio/video mode is active; pruning is deferred so sensor, ASR, vision, and reasoning inference stay available. Stop live mode before running prune.");
|
|
639572
|
+
return;
|
|
639573
|
+
}
|
|
638443
639574
|
renderInfo(`${label} (running in background — summary will follow)…`);
|
|
638444
639575
|
runPruneWorkerOnce({
|
|
638445
639576
|
repoRoot: workingDir,
|
|
@@ -638475,6 +639606,7 @@ var init_prune_menu = __esm({
|
|
|
638475
639606
|
init_render();
|
|
638476
639607
|
init_kg_prune();
|
|
638477
639608
|
init_memory_maintenance();
|
|
639609
|
+
init_live_sensors();
|
|
638478
639610
|
import_chalk2 = __toESM(require_source(), 1);
|
|
638479
639611
|
}
|
|
638480
639612
|
});
|
|
@@ -647474,6 +648606,7 @@ async function handleSlashCommand(input, ctx3) {
|
|
|
647474
648606
|
case "prune": {
|
|
647475
648607
|
const sub2 = (arg?.trim().split(/\s+/)[0] ?? "").toLowerCase();
|
|
647476
648608
|
const workingDir = ctx3.repoRoot || process.cwd();
|
|
648609
|
+
const force = /\b--force\b/i.test(arg ?? "");
|
|
647477
648610
|
const targetMatch = arg?.match(/--target\s+(\d+)/);
|
|
647478
648611
|
const targetNodes = targetMatch ? parseInt(targetMatch[1], 10) : void 0;
|
|
647479
648612
|
const { knowledgeGraphStats: knowledgeGraphStats2, formatKgStatsLine: formatKgStatsLine2 } = await Promise.resolve().then(() => (init_kg_prune(), kg_prune_exports));
|
|
@@ -647482,6 +648615,10 @@ async function handleSlashCommand(input, ctx3) {
|
|
|
647482
648615
|
renderInfo(summary);
|
|
647483
648616
|
};
|
|
647484
648617
|
const startBackground = (label, o2) => {
|
|
648618
|
+
if (isLiveSensorActiveForRepo(workingDir) && !force) {
|
|
648619
|
+
renderWarning("Live audio/video mode is active; pruning is deferred so sensor, ASR, vision, and reasoning inference stay available. Stop live mode first, or rerun with --force if you intentionally want pruning to compete with live inference.");
|
|
648620
|
+
return;
|
|
648621
|
+
}
|
|
647485
648622
|
renderInfo(`${label} (running in background — summary will follow)…`);
|
|
647486
648623
|
runPruneWorkerOnce2({
|
|
647487
648624
|
repoRoot: workingDir,
|
|
@@ -655805,6 +656942,12 @@ function attachLiveSinks(ctx3, manager, agentEnabled = false) {
|
|
|
655805
656942
|
model: ctx3.config.model,
|
|
655806
656943
|
apiKey: ctx3.config.apiKey
|
|
655807
656944
|
});
|
|
656945
|
+
const speechEnabled = agentEnabled && Boolean(ctx3.voiceSpeak);
|
|
656946
|
+
manager.setLiveSpeechEnabled(speechEnabled);
|
|
656947
|
+
manager.setLiveSpeechSink(speechEnabled ? (text2) => {
|
|
656948
|
+
if (ctx3.voiceIsEnabled && !ctx3.voiceIsEnabled()) return;
|
|
656949
|
+
ctx3.voiceSpeak?.(text2);
|
|
656950
|
+
} : void 0);
|
|
655808
656951
|
if (agentEnabled && ctx3.startBackgroundPrompt) {
|
|
655809
656952
|
manager.setAgentActionSink((prompt) => {
|
|
655810
656953
|
const id2 = ctx3.startBackgroundPrompt?.(prompt);
|
|
@@ -655818,13 +656961,14 @@ async function startLiveVoicechat(ctx3, manager) {
|
|
|
655818
656961
|
const agentEnabled = Boolean(ctx3.startBackgroundPrompt);
|
|
655819
656962
|
attachLiveSinks(ctx3, manager, agentEnabled);
|
|
655820
656963
|
await ensureLiveInventory(manager);
|
|
655821
|
-
renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true }));
|
|
655822
656964
|
if (!ctx3.voiceChatStart) {
|
|
656965
|
+
renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true, speech: false }));
|
|
655823
656966
|
renderWarning("Voice chat is not available in this context; live audio/video feedback remains active.");
|
|
655824
656967
|
return;
|
|
655825
656968
|
}
|
|
655826
656969
|
ctx3.voiceSetMode?.("voicechat");
|
|
655827
656970
|
if (ctx3.isVoiceChatActive?.()) {
|
|
656971
|
+
renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true, speech: Boolean(ctx3.voiceSpeak) }));
|
|
655828
656972
|
renderInfo("Voice chat is already active. Live audio/video feedback remains attached.");
|
|
655829
656973
|
return;
|
|
655830
656974
|
}
|
|
@@ -655835,6 +656979,7 @@ async function startLiveVoicechat(ctx3, manager) {
|
|
|
655835
656979
|
try {
|
|
655836
656980
|
renderInfo("Starting live voicechat with audio/video context...");
|
|
655837
656981
|
await ctx3.voiceChatStart();
|
|
656982
|
+
renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true, speech: Boolean(ctx3.voiceSpeak) }));
|
|
655838
656983
|
renderInfo("Live voicechat active: low-latency audio/video context is updating while voice conversation continues.");
|
|
655839
656984
|
} catch (err) {
|
|
655840
656985
|
renderError(`Live voicechat failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -655895,7 +657040,7 @@ async function handleLiveCommand(ctx3, arg) {
|
|
|
655895
657040
|
renderWarning("Live PFC agent review is unavailable in this context; starting visual/audio feedback only.");
|
|
655896
657041
|
}
|
|
655897
657042
|
await ensureLiveInventory(manager);
|
|
655898
|
-
renderInfo(manager.startRunMode({ agent: effectiveAgent, lowLatency: true }));
|
|
657043
|
+
renderInfo(manager.startRunMode({ agent: effectiveAgent, lowLatency: true, speech: effectiveAgent && Boolean(ctx3.voiceSpeak) }));
|
|
655899
657044
|
return;
|
|
655900
657045
|
}
|
|
655901
657046
|
if (sub2 === "chat" || sub2 === "voicechat" || sub2 === "voice") {
|
|
@@ -656150,14 +657295,14 @@ async function showLiveMenu(ctx3, manager) {
|
|
|
656150
657295
|
continue;
|
|
656151
657296
|
case "run-live":
|
|
656152
657297
|
attachLiveSinks(ctx3, manager, false);
|
|
656153
|
-
renderInfo(manager.startRunMode({ agent: false, lowLatency: true }));
|
|
657298
|
+
renderInfo(manager.startRunMode({ agent: false, lowLatency: true, speech: false }));
|
|
656154
657299
|
continue;
|
|
656155
657300
|
case "run-agent":
|
|
656156
657301
|
attachLiveSinks(ctx3, manager, true);
|
|
656157
657302
|
if (!ctx3.startBackgroundPrompt) {
|
|
656158
657303
|
renderWarning("Live PFC agent review is unavailable in this context; starting visual/audio feedback only.");
|
|
656159
657304
|
}
|
|
656160
|
-
renderInfo(manager.startRunMode({ agent: Boolean(ctx3.startBackgroundPrompt), lowLatency: true }));
|
|
657305
|
+
renderInfo(manager.startRunMode({ agent: Boolean(ctx3.startBackgroundPrompt), lowLatency: true, speech: Boolean(ctx3.startBackgroundPrompt && ctx3.voiceSpeak) }));
|
|
656161
657306
|
continue;
|
|
656162
657307
|
case "run-chat":
|
|
656163
657308
|
await startLiveVoicechat(ctx3, manager);
|
|
@@ -731385,9 +732530,10 @@ Rationale: ${proposal.rationale}${provenanceNote}${dmnDevDiscipline(proposal.cat
|
|
|
731385
732530
|
}, delayMs);
|
|
731386
732531
|
}
|
|
731387
732532
|
let activeTask = null;
|
|
732533
|
+
const liveSensorsBusy = () => isLiveSensorActiveForRepo(repoRoot);
|
|
731388
732534
|
idleMemoryMaintenance = startIdleMemoryMaintenance({
|
|
731389
732535
|
repoRoot,
|
|
731390
|
-
isBusy: () => Boolean(activeTask) || _interactiveSessionActive,
|
|
732536
|
+
isBusy: () => Boolean(activeTask) || _interactiveSessionActive || liveSensorsBusy(),
|
|
731391
732537
|
onSummary: (summary) => {
|
|
731392
732538
|
if (!summary) return;
|
|
731393
732539
|
writeContent(() => renderInfo(summary));
|
|
@@ -733941,6 +735087,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
733941
735087
|
},
|
|
733942
735088
|
setLiveMediaStatus(status) {
|
|
733943
735089
|
statusBar.setLiveMediaStatus(status);
|
|
735090
|
+
if (status.audio || status.video) idleMemoryMaintenance?.notifyActivity();
|
|
733944
735091
|
},
|
|
733945
735092
|
liveDynamicBlockHost: {
|
|
733946
735093
|
registerDynamicBlock: (id2, render2) => statusBar.registerDynamicBlock(id2, render2),
|