omnius 1.0.412 → 1.0.414

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -41412,7 +41412,7 @@ ${content}`
41412
41412
  // packages/execution/dist/tools/transcribe-tool.js
41413
41413
  import { existsSync as existsSync38, mkdirSync as mkdirSync22, writeFileSync as writeFileSync21, readFileSync as readFileSync30, unlinkSync as unlinkSync5, readdirSync as readdirSync14 } from "node:fs";
41414
41414
  import { join as join40, basename as basename7, extname as extname4, resolve as resolve21 } from "node:path";
41415
- import { homedir as homedir11 } from "node:os";
41415
+ import { homedir as homedir11, tmpdir as tmpdir5 } from "node:os";
41416
41416
  function transcriptionPythonEnv(extra = {}) {
41417
41417
  const env2 = { ...process.env, ...extra };
41418
41418
  applyMediaCudaDeviceFilterToEnv(env2, "asr");
@@ -41639,6 +41639,11 @@ var init_transcribe_tool = __esm({
41639
41639
  diarize: {
41640
41640
  type: "boolean",
41641
41641
  description: "Enable speaker diarization (identify who said what). Default: false"
41642
+ },
41643
+ backend: {
41644
+ type: "string",
41645
+ description: "ASR backend preference. auto tries fast transcribe-cli first; managed-whisper uses Omnius' isolated Whisper venv directly.",
41646
+ enum: ["auto", "managed-whisper", "transcribe-cli"]
41642
41647
  }
41643
41648
  },
41644
41649
  required: ["path"]
@@ -41652,6 +41657,7 @@ var init_transcribe_tool = __esm({
41652
41657
  const filePath = resolve21(this.workingDir, String(args["path"] ?? ""));
41653
41658
  let model = String(args["model"] ?? "base");
41654
41659
  const diarize = Boolean(args["diarize"] ?? false);
41660
+ const backend = String(args["backend"] ?? "auto").toLowerCase();
41655
41661
  if (!existsSync38(filePath)) {
41656
41662
  return {
41657
41663
  success: false,
@@ -41694,6 +41700,17 @@ var init_transcribe_tool = __esm({
41694
41700
  }
41695
41701
  if (effectiveModel !== askedModel)
41696
41702
  model = effectiveModel;
41703
+ if (backend === "managed-whisper") {
41704
+ const managed2 = await this.execViaManagedWhisper(filePath, model, start2);
41705
+ if (managed2.success)
41706
+ return managed2;
41707
+ return {
41708
+ success: false,
41709
+ output: "",
41710
+ error: `Managed Whisper ASR failed. ${String(managed2.error || managed2.output || "unknown error").slice(0, 1200)}`,
41711
+ durationMs: performance.now() - start2
41712
+ };
41713
+ }
41697
41714
  const failures = [];
41698
41715
  let managedPython = "";
41699
41716
  try {
@@ -41704,7 +41721,7 @@ var init_transcribe_tool = __esm({
41704
41721
  failures.push(`managed ASR runtime bootstrap: ${err instanceof Error ? err.message : String(err)}`);
41705
41722
  }
41706
41723
  const tc = await loadTranscribeCli() ?? await ensureManagedTranscribeCliRuntime();
41707
- if (tc) {
41724
+ if (tc && backend !== "managed-whisper") {
41708
41725
  try {
41709
41726
  const result = await withProcessEnv(transcriptionPythonEnv(), () => tc.transcribe(filePath, {
41710
41727
  model,
@@ -41894,8 +41911,14 @@ var init_transcribe_tool = __esm({
41894
41911
  }
41895
41912
  }
41896
41913
  async execViaManagedWhisper(filePath, model, start2) {
41897
- const scriptPath2 = join40(this.workingDir, ".omnius", "tmp", `managed-whisper-${Date.now()}.py`);
41898
- mkdirSync22(join40(this.workingDir, ".omnius", "tmp"), { recursive: true });
41914
+ let scriptDir = join40(this.workingDir, ".omnius", "tmp");
41915
+ try {
41916
+ mkdirSync22(scriptDir, { recursive: true });
41917
+ } catch {
41918
+ scriptDir = join40(tmpdir5(), "omnius-asr");
41919
+ mkdirSync22(scriptDir, { recursive: true });
41920
+ }
41921
+ const scriptPath2 = join40(scriptDir, `managed-whisper-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.py`);
41899
41922
  const script = `
41900
41923
  import json, os, warnings
41901
41924
  warnings.filterwarnings("ignore")
@@ -41904,13 +41927,15 @@ audio_file = ${JSON.stringify(filePath)}
41904
41927
  model_name = ${JSON.stringify(model)}
41905
41928
  try:
41906
41929
  import whisper
41907
- device = "cpu"
41908
- try:
41909
- import torch
41910
- if torch.cuda.is_available():
41911
- device = "cuda"
41912
- except Exception:
41913
- pass
41930
+ device = os.environ.get("OMNIUS_ASR_DEVICE", "cpu").strip().lower() or "cpu"
41931
+ if device == "auto":
41932
+ device = "cpu"
41933
+ try:
41934
+ import torch
41935
+ if torch.cuda.is_available():
41936
+ device = "cuda"
41937
+ except Exception:
41938
+ pass
41914
41939
  m = whisper.load_model(model_name, device=device)
41915
41940
  result = m.transcribe(audio_file, fp16=(device == "cuda"), condition_on_previous_text=False)
41916
41941
  segments = []
@@ -42491,7 +42516,7 @@ var init_structured_file = __esm({
42491
42516
  import { spawn as spawn7 } from "node:child_process";
42492
42517
  import { writeFile as writeFile9, mkdtemp, rm as rm2, readdir as readdir4, stat as stat4 } from "node:fs/promises";
42493
42518
  import { join as join41 } from "node:path";
42494
- import { tmpdir as tmpdir5 } from "node:os";
42519
+ import { tmpdir as tmpdir6 } from "node:os";
42495
42520
  function runProcess(cmd, args, options2) {
42496
42521
  return new Promise((resolve76) => {
42497
42522
  const proc = spawn7(cmd, args, {
@@ -42689,7 +42714,7 @@ ${result.filesCreated.join("\n")}`);
42689
42714
  // Subprocess mode — temp directory + separate process
42690
42715
  // -------------------------------------------------------------------------
42691
42716
  async #runSubprocess(code8, langConfig, timeoutMs, stdin) {
42692
- const sandboxDir = await mkdtemp(join41(tmpdir5(), "omnius-sandbox-"));
42717
+ const sandboxDir = await mkdtemp(join41(tmpdir6(), "omnius-sandbox-"));
42693
42718
  try {
42694
42719
  const scriptFile = join41(sandboxDir, `_sandbox_script${langConfig.ext}`);
42695
42720
  await writeFile9(scriptFile, code8, "utf-8");
@@ -42716,7 +42741,7 @@ ${result.filesCreated.join("\n")}`);
42716
42741
  bash: "bash:5"
42717
42742
  };
42718
42743
  const image = images[language] ?? "node:22-slim";
42719
- const sandboxDir = await mkdtemp(join41(tmpdir5(), "omnius-docker-sandbox-"));
42744
+ const sandboxDir = await mkdtemp(join41(tmpdir6(), "omnius-docker-sandbox-"));
42720
42745
  try {
42721
42746
  const scriptFile = `_sandbox_script${langConfig.ext}`;
42722
42747
  await writeFile9(join41(sandboxDir, scriptFile), code8, "utf-8");
@@ -274241,7 +274266,7 @@ New: ${newNarrative.slice(0, 200)}...`,
274241
274266
  import { spawn as spawn9 } from "node:child_process";
274242
274267
  import { createServer as createServer2 } from "node:net";
274243
274268
  import { join as join44 } from "node:path";
274244
- import { tmpdir as tmpdir6 } from "node:os";
274269
+ import { tmpdir as tmpdir7 } from "node:os";
274245
274270
  import { randomBytes as randomBytes11 } from "node:crypto";
274246
274271
  import { unlinkSync as unlinkSync6 } from "node:fs";
274247
274272
  var ReplTool;
@@ -274313,7 +274338,7 @@ var init_repl = __esm({
274313
274338
  if (!this.proc || this.proc.killed || this.proc.exitCode !== null) {
274314
274339
  await this.startProcess();
274315
274340
  }
274316
- const tempFile = join44(tmpdir6(), `omnius-repl-ctx-${randomBytes11(6).toString("hex")}.txt`);
274341
+ const tempFile = join44(tmpdir7(), `omnius-repl-ctx-${randomBytes11(6).toString("hex")}.txt`);
274317
274342
  const { writeFileSync: writeFs, unlinkSync: unlinkFs } = await import("node:fs");
274318
274343
  writeFs(tempFile, content, "utf8");
274319
274344
  const result = await this.executeCode(`with open(${JSON.stringify(tempFile)}, "r") as _f:
@@ -274540,7 +274565,7 @@ print("__OMNIUS_REPL_READY__")
274540
274565
  if (this.ipcServer)
274541
274566
  return;
274542
274567
  const sockId = randomBytes11(8).toString("hex");
274543
- this.ipcPath = join44(tmpdir6(), `omnius-repl-ipc-${sockId}.sock`);
274568
+ this.ipcPath = join44(tmpdir7(), `omnius-repl-ipc-${sockId}.sock`);
274544
274569
  return new Promise((resolve76, reject) => {
274545
274570
  this.ipcServer = createServer2((conn) => {
274546
274571
  let buffer2 = new Uint8Array(0);
@@ -287719,7 +287744,7 @@ ${parts.join("\n\n")}`,
287719
287744
  // packages/execution/dist/tools/desktop-click.js
287720
287745
  import { readFileSync as readFileSync35 } from "node:fs";
287721
287746
  import { execSync as execSync16 } from "node:child_process";
287722
- import { tmpdir as tmpdir7 } from "node:os";
287747
+ import { tmpdir as tmpdir8 } from "node:os";
287723
287748
  import { join as join57 } from "node:path";
287724
287749
  function getImageDimensions2(filePath) {
287725
287750
  try {
@@ -287936,7 +287961,7 @@ Button: ${button}, Type: ${clickType}`,
287936
287961
  durationMs: performance.now() - start2
287937
287962
  };
287938
287963
  }
287939
- const screenshotPath = join57(tmpdir7(), `omnius-desktop-click-${Date.now()}.png`);
287964
+ const screenshotPath = join57(tmpdir8(), `omnius-desktop-click-${Date.now()}.png`);
287940
287965
  const screenshotBackend = captureDesktopScreenshot(screenshotPath);
287941
287966
  const dims = getImageDimensions2(screenshotPath);
287942
287967
  if (!dims) {
@@ -288103,7 +288128,7 @@ Screenshot: ${screenshotPath}`,
288103
288128
  if (delayMs > 0) {
288104
288129
  await new Promise((r2) => setTimeout(r2, delayMs));
288105
288130
  }
288106
- const screenshotPath = join57(tmpdir7(), `omnius-desktop-describe-${Date.now()}.png`);
288131
+ const screenshotPath = join57(tmpdir8(), `omnius-desktop-describe-${Date.now()}.png`);
288107
288132
  const screenshotBackend = captureDesktopScreenshot(screenshotPath);
288108
288133
  const dims = getImageDimensions2(screenshotPath);
288109
288134
  const imageBuffer = readFileSync35(screenshotPath);
@@ -289307,7 +289332,7 @@ Language: ${language}
289307
289332
  import { existsSync as existsSync50, statSync as statSync21, readFileSync as readFileSync37, unlinkSync as unlinkSync8 } from "node:fs";
289308
289333
  import { resolve as resolve29, basename as basename10, join as join59 } from "node:path";
289309
289334
  import { execSync as execSync18 } from "node:child_process";
289310
- import { tmpdir as tmpdir8 } from "node:os";
289335
+ import { tmpdir as tmpdir9 } from "node:os";
289311
289336
  var PdfToTextTool;
289312
289337
  var init_pdf_to_text = __esm({
289313
289338
  "packages/execution/dist/tools/pdf-to-text.js"() {
@@ -289459,7 +289484,7 @@ ${text2}`,
289459
289484
  if (!ocrCheck.available || !tesCheck.available || !gsCheck.available) {
289460
289485
  return null;
289461
289486
  }
289462
- const tmpPdf = join59(tmpdir8(), `omnius-ocr-${Date.now()}.pdf`);
289487
+ const tmpPdf = join59(tmpdir9(), `omnius-ocr-${Date.now()}.pdf`);
289463
289488
  try {
289464
289489
  const ocrCmd = `ocrmypdf -l ${language} --skip-text ${JSON.stringify(inputPath)} ${JSON.stringify(tmpPdf)}`;
289465
289490
  execSync18(ocrCmd, { stdio: "pipe", timeout: 6e5 });
@@ -289493,7 +289518,7 @@ import { existsSync as existsSync51, mkdirSync as mkdirSync28, statSync as statS
289493
289518
  import { resolve as resolve30, basename as basename11, dirname as dirname16, join as join60 } from "node:path";
289494
289519
  import { execSync as execSync19 } from "node:child_process";
289495
289520
  import { fileURLToPath as fileURLToPath6 } from "node:url";
289496
- import { homedir as homedir13, tmpdir as tmpdir9 } from "node:os";
289521
+ import { homedir as homedir13, tmpdir as tmpdir10 } from "node:os";
289497
289522
  function findOcrScript() {
289498
289523
  const thisDir = dirname16(fileURLToPath6(import.meta.url));
289499
289524
  const devPath3 = resolve30(thisDir, "../../scripts/ocr-advanced.py");
@@ -289726,7 +289751,7 @@ var init_ocr_image_advanced = __esm({
289726
289751
  cmdParts.push("--output-dir", JSON.stringify(resolve30(this.workingDir, outputDir2)));
289727
289752
  let debugDir;
289728
289753
  if (debug) {
289729
- debugDir = join60(tmpdir9(), `omnius-ocr-debug-${Date.now()}`);
289754
+ debugDir = join60(tmpdir10(), `omnius-ocr-debug-${Date.now()}`);
289730
289755
  cmdParts.push("--debug-dir", debugDir);
289731
289756
  }
289732
289757
  try {
@@ -543195,7 +543220,7 @@ var init_process_health = __esm({
543195
543220
  // packages/execution/dist/tools/camera-capture.js
543196
543221
  import { accessSync, constants, readFileSync as readFileSync44, writeFileSync as writeFileSync28, unlinkSync as unlinkSync11, existsSync as existsSync60, mkdirSync as mkdirSync34, readdirSync as readdirSync21, renameSync as renameSync7 } from "node:fs";
543197
543222
  import { join as join75 } from "node:path";
543198
- import { tmpdir as tmpdir10 } from "node:os";
543223
+ import { tmpdir as tmpdir11 } from "node:os";
543199
543224
  function normalizeCameraRotationDegrees(value2) {
543200
543225
  if (value2 === void 0 || value2 === null || value2 === "")
543201
543226
  return 0;
@@ -543693,7 +543718,7 @@ ${formatRouterPlan(plan)}`,
543693
543718
  const height = args["height"] || 720;
543694
543719
  const outputPath3 = args["output_path"];
543695
543720
  const rotation = normalizeCameraRotationDegrees(args["rotate_degrees"] ?? args["rotation_degrees"] ?? args["rotate"]);
543696
- const captureDir = join75(tmpdir10(), "omnius-camera");
543721
+ const captureDir = join75(tmpdir11(), "omnius-camera");
543697
543722
  if (!existsSync60(captureDir))
543698
543723
  mkdirSync34(captureDir, { recursive: true });
543699
543724
  const tempFile = outputPath3 || join75(captureDir, `capture-${Date.now()}.jpg`);
@@ -544075,7 +544100,7 @@ Auto-installed camera dependencies: ${[
544075
544100
  } catch (err) {
544076
544101
  return { success: false, output: "", error: `QooCam OSC error: ${err instanceof Error ? err.message : String(err)}`, durationMs: performance.now() - start2 };
544077
544102
  }
544078
- const captureDir = join75(tmpdir10(), "omnius-camera");
544103
+ const captureDir = join75(tmpdir11(), "omnius-camera");
544079
544104
  if (!existsSync60(captureDir))
544080
544105
  mkdirSync34(captureDir, { recursive: true });
544081
544106
  const tempFile = outputPath3 || join75(captureDir, `qoocam-${Date.now()}.jpg`);
@@ -544225,7 +544250,7 @@ Saved to: ${outputPath3}`;
544225
544250
  import { execSync as execSync26 } from "node:child_process";
544226
544251
  import { readFileSync as readFileSync45, unlinkSync as unlinkSync12, existsSync as existsSync61, mkdirSync as mkdirSync35, statSync as statSync25 } from "node:fs";
544227
544252
  import { join as join76 } from "node:path";
544228
- import { tmpdir as tmpdir11 } from "node:os";
544253
+ import { tmpdir as tmpdir12 } from "node:os";
544229
544254
  var AudioCaptureTool;
544230
544255
  var init_audio_capture = __esm({
544231
544256
  "packages/execution/dist/tools/audio-capture.js"() {
@@ -544331,7 +544356,7 @@ ${devices.join("\n")}`,
544331
544356
  const channels = args["channels"] || 1;
544332
544357
  const format3 = args["format"] || "wav";
544333
544358
  const outputPath3 = args["output_path"];
544334
- const captureDir = join76(tmpdir11(), "omnius-audio");
544359
+ const captureDir = join76(tmpdir12(), "omnius-audio");
544335
544360
  if (!existsSync61(captureDir))
544336
544361
  mkdirSync35(captureDir, { recursive: true });
544337
544362
  const ext = format3 === "mp3" ? "mp3" : "wav";
@@ -544418,7 +544443,7 @@ Saved to: ${tempFile}`,
544418
544443
  }
544419
544444
  checkLevel(args, start2) {
544420
544445
  const device = args["device"] || "default";
544421
- const tempFile = join76(tmpdir11(), `omnius-level-${Date.now()}.raw`);
544446
+ const tempFile = join76(tmpdir12(), `omnius-level-${Date.now()}.raw`);
544422
544447
  try {
544423
544448
  execSync26(`arecord -D ${device} -f S16_LE -r 16000 -c 1 -d 1 -t raw -q ${tempFile}`, { timeout: 5e3, stdio: "pipe" });
544424
544449
  if (!existsSync61(tempFile)) {
@@ -544476,7 +544501,7 @@ Saved to: ${tempFile}`,
544476
544501
  import { execFileSync as execFileSync2, execSync as execSync27, spawn as spawn18 } from "node:child_process";
544477
544502
  import { copyFileSync as copyFileSync4, existsSync as existsSync62, statSync as statSync26, writeFileSync as writeFileSync29, mkdirSync as mkdirSync36, readdirSync as readdirSync22, writeSync, rmSync as rmSync7 } from "node:fs";
544478
544503
  import { basename as basename15, dirname as dirname21, extname as extname12, isAbsolute as isAbsolute4, join as join77, resolve as resolve39 } from "node:path";
544479
- import { homedir as homedir17, tmpdir as tmpdir12 } from "node:os";
544504
+ import { homedir as homedir17, tmpdir as tmpdir13 } from "node:os";
544480
544505
  function ttsPythonEnv(extra = {}) {
544481
544506
  const env2 = { ...process.env, ...extra };
544482
544507
  applyMediaCudaDeviceFilterToEnv(env2, "tts");
@@ -545357,7 +545382,7 @@ function ensureLuxttsDaemon() {
545357
545382
  }, 12e4);
545358
545383
  const daemon = spawn18(venvPy, [inferScript], {
545359
545384
  stdio: ["pipe", "pipe", "pipe"],
545360
- cwd: tmpdir12(),
545385
+ cwd: tmpdir13(),
545361
545386
  env: ttsPythonEnv({ LUXTTS_REPO_PATH: repoDir })
545362
545387
  });
545363
545388
  _luxttsDaemon = daemon;
@@ -545414,7 +545439,7 @@ function luxttsSynthesize(text2, cloneRef, outputPath3, speed = 1) {
545414
545439
  return;
545415
545440
  }
545416
545441
  const id2 = `tts-${++_luxttsRequestId}`;
545417
- const outPath = outputPath3 || join77(tmpdir12(), `omnius-luxtts-${Date.now()}.wav`);
545442
+ const outPath = outputPath3 || join77(tmpdir13(), `omnius-luxtts-${Date.now()}.wav`);
545418
545443
  const timeout2 = setTimeout(() => {
545419
545444
  _luxttsPending.delete(id2);
545420
545445
  reject(new Error("LuxTTS synthesis timeout"));
@@ -545557,7 +545582,7 @@ function ensureMisottsDaemon() {
545557
545582
  }, 12e4);
545558
545583
  const daemon = spawn18(venvPy, [inferScript], {
545559
545584
  stdio: ["pipe", "pipe", "pipe"],
545560
- cwd: tmpdir12(),
545585
+ cwd: tmpdir13(),
545561
545586
  env: ttsPythonEnv({ MISO_TTS_REPO_PATH: repoDir, NO_TORCH_COMPILE: "1" })
545562
545587
  });
545563
545588
  _misottsDaemon = daemon;
@@ -545614,7 +545639,7 @@ function misottsSynthesize(text2, cloneRef, outputPath3, maxAudioLengthMs = 3e4)
545614
545639
  return;
545615
545640
  }
545616
545641
  const id2 = `tts-${++_misottsRequestId}`;
545617
- const outPath = outputPath3 || join77(tmpdir12(), `omnius-misotts-${Date.now()}.wav`);
545642
+ const outPath = outputPath3 || join77(tmpdir13(), `omnius-misotts-${Date.now()}.wav`);
545618
545643
  const timeout2 = setTimeout(() => {
545619
545644
  _misottsPending.delete(id2);
545620
545645
  reject(new Error("MisoTTS synthesis timeout"));
@@ -546272,7 +546297,7 @@ ${tried.map((line) => `- ${line}`).join("\n")}`,
546272
546297
  ], {
546273
546298
  stdio: "pipe",
546274
546299
  timeout: 18e4,
546275
- cwd: tmpdir12()
546300
+ cwd: tmpdir13()
546276
546301
  });
546277
546302
  return `${voice} (${model})`;
546278
546303
  }
@@ -547069,7 +547094,7 @@ ${info}`, durationMs: performance.now() - start2 };
547069
547094
  import { execSync as execSync30 } from "node:child_process";
547070
547095
  import { readFileSync as readFileSync46, unlinkSync as unlinkSync13, existsSync as existsSync63, mkdirSync as mkdirSync37, statSync as statSync27 } from "node:fs";
547071
547096
  import { join as join78 } from "node:path";
547072
- import { tmpdir as tmpdir13 } from "node:os";
547097
+ import { tmpdir as tmpdir14 } from "node:os";
547073
547098
  var SdrScanTool;
547074
547099
  var init_sdr_scan = __esm({
547075
547100
  "packages/execution/dist/tools/sdr-scan.js"() {
@@ -547240,7 +547265,7 @@ Tools installed but rtl_test failed — device may be in use by another process.
547240
547265
  if (!await this.ensureSdrTools()) {
547241
547266
  return { success: false, output: "", error: "rtl-sdr tools not installed. Connect an RTL-SDR dongle and retry (will prompt for install).", durationMs: performance.now() - start2 };
547242
547267
  }
547243
- const captureDir = join78(tmpdir13(), "omnius-sdr");
547268
+ const captureDir = join78(tmpdir14(), "omnius-sdr");
547244
547269
  if (!existsSync63(captureDir))
547245
547270
  mkdirSync37(captureDir, { recursive: true });
547246
547271
  const outFile = join78(captureDir, `scan-${Date.now()}.csv`);
@@ -547324,7 +547349,7 @@ ${output.slice(0, 2e3)}`,
547324
547349
  } catch {
547325
547350
  return { success: false, output: "", error: "rtl_fm not installed. Run: sudo apt install rtl-sdr", durationMs: performance.now() - start2 };
547326
547351
  }
547327
- const captureDir = join78(tmpdir13(), "omnius-sdr");
547352
+ const captureDir = join78(tmpdir14(), "omnius-sdr");
547328
547353
  if (!existsSync63(captureDir))
547329
547354
  mkdirSync37(captureDir, { recursive: true });
547330
547355
  const outFile = join78(captureDir, `fm-${Date.now()}.wav`);
@@ -547781,7 +547806,7 @@ ${result.trim()}`, durationMs: performance.now() - start2 };
547781
547806
  // packages/execution/dist/tools/audio-analyze.js
547782
547807
  import { existsSync as existsSync65, mkdirSync as mkdirSync38, writeFileSync as writeFileSync30 } from "node:fs";
547783
547808
  import { basename as basename16, isAbsolute as isAbsolute5, join as join79, resolve as resolve40 } from "node:path";
547784
- import { homedir as homedir18, tmpdir as tmpdir14 } from "node:os";
547809
+ import { homedir as homedir18, tmpdir as tmpdir15 } from "node:os";
547785
547810
  import { fileURLToPath as fileURLToPath9 } from "node:url";
547786
547811
  function audioAnalysisPythonEnv(extra = {}) {
547787
547812
  const env2 = { ...process.env, ...extra };
@@ -548212,7 +548237,7 @@ except Exception as e:
548212
548237
  const contextDir = join79(homedir18(), ".omnius", "audio-context");
548213
548238
  if (!existsSync65(contextDir))
548214
548239
  mkdirSync38(contextDir, { recursive: true });
548215
- const audioFile = join79(tmpdir14(), `omnius-listen-${Date.now()}.wav`);
548240
+ const audioFile = join79(tmpdir15(), `omnius-listen-${Date.now()}.wav`);
548216
548241
  try {
548217
548242
  await execFileText("arecord", ["-D", "default", "-f", "S16_LE", "-r", "16000", "-c", "1", "-d", String(Math.min(duration, 60)), "-q", audioFile], {
548218
548243
  timeout: (duration + 10) * 1e3
@@ -548353,7 +548378,7 @@ ${e2.stderr ? String(e2.stderr) : ""}`.trim();
548353
548378
  return file;
548354
548379
  }
548355
548380
  const duration = args["duration"] || 5;
548356
- const outFile = join79(tmpdir14(), `omnius-analyze-${Date.now()}.wav`);
548381
+ const outFile = join79(tmpdir15(), `omnius-analyze-${Date.now()}.wav`);
548357
548382
  try {
548358
548383
  await execFileText("arecord", ["-D", "default", "-f", "S16_LE", "-r", "16000", "-c", "1", "-d", String(duration), "-q", outFile], {
548359
548384
  timeout: (duration + 10) * 1e3
@@ -548382,7 +548407,7 @@ ${e2.stderr ? String(e2.stderr) : ""}`.trim();
548382
548407
  }
548383
548408
  /** Run a Python script in the venv and parse JSON output */
548384
548409
  async runPythonScript(script, start2, label) {
548385
- const scriptFile = join79(tmpdir14(), `omnius-audio-${Date.now()}.py`);
548410
+ const scriptFile = join79(tmpdir15(), `omnius-audio-${Date.now()}.py`);
548386
548411
  writeFileSync30(scriptFile, script);
548387
548412
  try {
548388
548413
  const output = await execFileText(VENV_PYTHON, [scriptFile], {
@@ -548418,7 +548443,7 @@ ${output}`, durationMs: performance.now() - start2 };
548418
548443
  import { execSync as execSync33, spawnSync as spawnSync6 } from "node:child_process";
548419
548444
  import { existsSync as existsSync66, readFileSync as readFileSync47, writeFileSync as writeFileSync31, mkdirSync as mkdirSync39 } from "node:fs";
548420
548445
  import { join as join80 } from "node:path";
548421
- import { tmpdir as tmpdir15, homedir as homedir19 } from "node:os";
548446
+ import { tmpdir as tmpdir16, homedir as homedir19 } from "node:os";
548422
548447
  var GPS_USB_IDS, GpsLocationTool;
548423
548448
  var init_gps_location = __esm({
548424
548449
  "packages/execution/dist/tools/gps-location.js"() {
@@ -548543,7 +548568,7 @@ var init_gps_location = __esm({
548543
548568
  /** Run a Python GPS script using pyserial+pynmea2 and return JSON result */
548544
548569
  async runGpsPython(script, timeoutMs = 3e4) {
548545
548570
  await this.ensureGpsVenv();
548546
- const scriptFile = join80(tmpdir15(), `omnius-gps-${Date.now()}.py`);
548571
+ const scriptFile = join80(tmpdir16(), `omnius-gps-${Date.now()}.py`);
548547
548572
  writeFileSync31(scriptFile, script);
548548
548573
  try {
548549
548574
  const output = execSync33(`${this.GPS_PYTHON} ${scriptFile}`, {
@@ -549015,7 +549040,7 @@ Drift: ${drift != null ? drift + "ms" : "unknown"}`,
549015
549040
  // =========================================================================
549016
549041
  async recordTrack(args, start2) {
549017
549042
  const duration = args["duration"] || 60;
549018
- const outputPath3 = args["output_path"] || join80(tmpdir15(), `omnius-gps-track-${Date.now()}.gpx`);
549043
+ const outputPath3 = args["output_path"] || join80(tmpdir16(), `omnius-gps-track-${Date.now()}.gpx`);
549019
549044
  if (!await this.ensureGpsd(args)) {
549020
549045
  return { success: false, output: "", error: "gpsd required for track recording. Connect a GPS device.", durationMs: performance.now() - start2 };
549021
549046
  }
@@ -549248,7 +549273,7 @@ def _omnius_normalized_features(features):
549248
549273
  import { execFile as execFile6 } from "node:child_process";
549249
549274
  import { existsSync as existsSync67, mkdirSync as mkdirSync40, writeFileSync as writeFileSync32, readFileSync as readFileSync48 } from "node:fs";
549250
549275
  import { join as join81 } from "node:path";
549251
- import { homedir as homedir20, tmpdir as tmpdir16 } from "node:os";
549276
+ import { homedir as homedir20, tmpdir as tmpdir17 } from "node:os";
549252
549277
  function visualMemoryPythonEnv(extra = {}) {
549253
549278
  const env2 = { ...process.env, ...extra };
549254
549279
  applyMediaCudaDeviceFilterToEnv(env2, "vision");
@@ -550020,7 +550045,7 @@ ${objects.join("\n") || " (none taught)"}`,
550020
550045
  }
550021
550046
  }
550022
550047
  async runVisionPython(script, timeoutMs = 6e4) {
550023
- const scriptFile = join81(tmpdir16(), `omnius-vmem-${Date.now()}.py`);
550048
+ const scriptFile = join81(tmpdir17(), `omnius-vmem-${Date.now()}.py`);
550024
550049
  writeFileSync32(scriptFile, script);
550025
550050
  try {
550026
550051
  const { stdout: output } = await execFileText2(VENV_PY, [scriptFile], {
@@ -550053,7 +550078,7 @@ ${objects.join("\n") || " (none taught)"}`,
550053
550078
  import { execSync as execSync34 } from "node:child_process";
550054
550079
  import { appendFileSync as appendFileSync5, existsSync as existsSync68, mkdirSync as mkdirSync41, writeFileSync as writeFileSync33, readFileSync as readFileSync49, readdirSync as readdirSync23 } from "node:fs";
550055
550080
  import { join as join82 } from "node:path";
550056
- import { homedir as homedir21, tmpdir as tmpdir17 } from "node:os";
550081
+ import { homedir as homedir21, tmpdir as tmpdir18 } from "node:os";
550057
550082
  import { randomUUID as randomUUID15 } from "node:crypto";
550058
550083
  var MM_DIR, MM_INDEX, MultimodalMemoryTool;
550059
550084
  var init_multimodal_memory = __esm({
@@ -550172,7 +550197,7 @@ with torch.no_grad():
550172
550197
  features = _omnius_normalized_features(model.get_image_features(**inputs))
550173
550198
  print(json.dumps(features[0].cpu().numpy().tolist()))
550174
550199
  `;
550175
- const scriptFile = join82(tmpdir17(), `mm-clip-${Date.now()}.py`);
550200
+ const scriptFile = join82(tmpdir18(), `mm-clip-${Date.now()}.py`);
550176
550201
  writeFileSync33(scriptFile, clipScript);
550177
550202
  const clipOutput = execSync34(`${venvPy} ${scriptFile}`, { encoding: "utf8", timeout: 12e4, env: { ...process.env, PYTHONUNBUFFERED: "1" } });
550178
550203
  const embedding = JSON.parse(clipOutput.trim().split("\n").pop());
@@ -550194,7 +550219,7 @@ faces = app.get(img) if img is not None else []
550194
550219
  result = [{"confidence": float(f.det_score), "age": int(f.age) if hasattr(f, 'age') else None} for f in faces]
550195
550220
  print(json.dumps(result))
550196
550221
  `;
550197
- const scriptFile = join82(tmpdir17(), `mm-face-${Date.now()}.py`);
550222
+ const scriptFile = join82(tmpdir18(), `mm-face-${Date.now()}.py`);
550198
550223
  writeFileSync33(scriptFile, faceScript);
550199
550224
  const faceOutput = execSync34(`${venvPy} ${scriptFile}`, { encoding: "utf8", timeout: 6e4, env: { ...process.env, PYTHONUNBUFFERED: "1" } });
550200
550225
  const faces = JSON.parse(faceOutput.trim().split("\n").pop());
@@ -550246,7 +550271,7 @@ with open(model.class_map_path().numpy().decode()) as f:
550246
550271
  top=scores.numpy().mean(axis=0).argsort()[-1]
550247
550272
  print(classes[top])
550248
550273
  `;
550249
- const scriptFile = join82(tmpdir17(), `mm-yamnet-${Date.now()}.py`);
550274
+ const scriptFile = join82(tmpdir18(), `mm-yamnet-${Date.now()}.py`);
550250
550275
  writeFileSync33(scriptFile, classifyScript);
550251
550276
  const soundClass = execSync34(`${mlVenvPy} ${scriptFile}`, { encoding: "utf8", timeout: 12e4 }).trim().split("\n").pop();
550252
550277
  episode.audio.soundClass = soundClass;
@@ -550342,7 +550367,7 @@ if faces:
550342
550367
  else:
550343
550368
  print(json.dumps({"enrolled": False, "reason": "no face detected"}))
550344
550369
  `;
550345
- const scriptFile = join82(tmpdir17(), `mm-enroll-${Date.now()}.py`);
550370
+ const scriptFile = join82(tmpdir18(), `mm-enroll-${Date.now()}.py`);
550346
550371
  writeFileSync33(scriptFile, enrollScript);
550347
550372
  const enrollOutput = execSync34(`${venvPy} ${scriptFile}`, { encoding: "utf8", timeout: 6e4, env: { ...process.env, PYTHONUNBUFFERED: "1" } });
550348
550373
  const enrollResult = JSON.parse(enrollOutput.trim().split("\n").pop());
@@ -550397,7 +550422,7 @@ with torch.no_grad():
550397
550422
  features = _omnius_normalized_features(model.get_text_features(**inputs))
550398
550423
  print(json.dumps(features[0].cpu().numpy().tolist()))
550399
550424
  `;
550400
- const scriptFile = join82(tmpdir17(), `mm-clipq-${Date.now()}.py`);
550425
+ const scriptFile = join82(tmpdir18(), `mm-clipq-${Date.now()}.py`);
550401
550426
  writeFileSync33(scriptFile, clipTextScript);
550402
550427
  const output = execSync34(`${venvPy} ${scriptFile}`, { encoding: "utf8", timeout: 6e4, env: { ...process.env, PYTHONUNBUFFERED: "1" } });
550403
550428
  queryClipEmbedding = JSON.parse(output.trim().split("\n").pop());
@@ -550679,7 +550704,7 @@ ${lines.join("\n")}`,
550679
550704
  import { execSync as execSync35, spawnSync as spawnSync7 } from "node:child_process";
550680
550705
  import { existsSync as existsSync69, mkdirSync as mkdirSync42, writeFileSync as writeFileSync34, readFileSync as readFileSync50, unlinkSync as unlinkSync14 } from "node:fs";
550681
550706
  import { dirname as dirname22, join as join83, resolve as resolve41 } from "node:path";
550682
- import { tmpdir as tmpdir18, homedir as homedir22 } from "node:os";
550707
+ import { tmpdir as tmpdir19, homedir as homedir22 } from "node:os";
550683
550708
  import { fileURLToPath as fileURLToPath10 } from "node:url";
550684
550709
  function asrPythonEnv(extra = {}) {
550685
550710
  const env2 = { ...process.env, ...extra };
@@ -550794,7 +550819,7 @@ var init_asr_listen = __esm({
550794
550819
  listenAndTranscribe(args, start2) {
550795
550820
  const duration = args["duration"] || 8;
550796
550821
  const device = args["device"] || "default";
550797
- const captureDir = join83(tmpdir18(), "omnius-asr");
550822
+ const captureDir = join83(tmpdir19(), "omnius-asr");
550798
550823
  if (!existsSync69(captureDir))
550799
550824
  mkdirSync42(captureDir, { recursive: true });
550800
550825
  const audioFile = join83(captureDir, `listen-${Date.now()}.wav`);
@@ -550941,7 +550966,7 @@ except Exception:
550941
550966
 
550942
550967
  print(json.dumps({"ok": False, "error": "No whisper backend available"}))
550943
550968
  `;
550944
- const scriptFile = join83(tmpdir18(), `omnius-asr-whisper-${Date.now()}.py`);
550969
+ const scriptFile = join83(tmpdir19(), `omnius-asr-whisper-${Date.now()}.py`);
550945
550970
  writeFileSync34(scriptFile, whisperScript);
550946
550971
  const pyPaths = [
550947
550972
  join83(homedir22(), ".omnius", "venv", "bin", "python3"),
@@ -555719,7 +555744,7 @@ var init_visual_trigger = __esm({
555719
555744
 
555720
555745
  // packages/execution/dist/tools/live-media-loop.js
555721
555746
  import { existsSync as existsSync78, mkdirSync as mkdirSync47, readFileSync as readFileSync57, rmSync as rmSync10, writeFileSync as writeFileSync39 } from "node:fs";
555722
- import { homedir as homedir28, tmpdir as tmpdir19 } from "node:os";
555747
+ import { homedir as homedir28, tmpdir as tmpdir20 } from "node:os";
555723
555748
  import { basename as basename19, isAbsolute as isAbsolute8, join as join92, resolve as resolve46 } from "node:path";
555724
555749
  function buildYolo26BootstrapPlan(model = DEFAULT_YOLO26_MODEL) {
555725
555750
  return {
@@ -556038,8 +556063,8 @@ async function ensureRuntime(autoBootstrap) {
556038
556063
  }
556039
556064
  }
556040
556065
  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`);
556066
+ const cfgPath = join92(tmpdir20(), `omnius-yolo26-export-${Date.now()}.json`);
556067
+ const scriptPath2 = join92(tmpdir20(), `omnius-yolo26-export-${Date.now()}.py`);
556043
556068
  writeFileSync39(cfgPath, JSON.stringify({ model, options: options2, yoloeClasses }, null, 2), "utf8");
556044
556069
  writeFileSync39(scriptPath2, String.raw`
556045
556070
  import json, sys
@@ -556615,8 +556640,8 @@ var init_live_media_loop = __esm({
556615
556640
  return { success: false, output: "", error: runtime.error, durationMs: performance.now() - start2 };
556616
556641
  const outDir = join92(this.workingDir, ".omnius", "live-media", `run-${Date.now().toString(36)}`);
556617
556642
  mkdirSync47(outDir, { recursive: true });
556618
- const cfgPath = join92(tmpdir19(), `omnius-live-media-${Date.now()}.json`);
556619
- const scriptPath2 = join92(tmpdir19(), `omnius-live-media-${Date.now()}.py`);
556643
+ const cfgPath = join92(tmpdir20(), `omnius-live-media-${Date.now()}.json`);
556644
+ const scriptPath2 = join92(tmpdir20(), `omnius-live-media-${Date.now()}.py`);
556620
556645
  writeFileSync39(cfgPath, JSON.stringify({
556621
556646
  source_kind: sourceKind(args),
556622
556647
  source: src2,
@@ -593747,9 +593772,9 @@ ${result}`
593747
593772
  try {
593748
593773
  const { writeFileSync: writeFileSync95, readFileSync: readFileSync137, unlinkSync: unlinkSync37 } = await import("node:fs");
593749
593774
  const { join: join186 } = await import("node:path");
593750
- const { tmpdir: tmpdir25 } = await import("node:os");
593751
- const tmpIn = join186(tmpdir25(), `omnius_img_in_${Date.now()}.png`);
593752
- const tmpOut = join186(tmpdir25(), `omnius_img_out_${Date.now()}.jpg`);
593775
+ const { tmpdir: tmpdir26 } = await import("node:os");
593776
+ const tmpIn = join186(tmpdir26(), `omnius_img_in_${Date.now()}.png`);
593777
+ const tmpOut = join186(tmpdir26(), `omnius_img_out_${Date.now()}.jpg`);
593753
593778
  writeFileSync95(tmpIn, buffer2);
593754
593779
  const pyBin = process.platform === "win32" ? "python" : "python3";
593755
593780
  const resizeScript = [
@@ -595284,7 +595309,7 @@ var init_constraint_learner = __esm({
595284
595309
  import { existsSync as existsSync102, statSync as statSync39, openSync, readSync, closeSync, unlinkSync as unlinkSync18, writeFileSync as writeFileSync49 } from "node:fs";
595285
595310
  import { watch as fsWatch } from "node:fs";
595286
595311
  import { join as join113 } from "node:path";
595287
- import { tmpdir as tmpdir20 } from "node:os";
595312
+ import { tmpdir as tmpdir21 } from "node:os";
595288
595313
  import { randomBytes as randomBytes21 } from "node:crypto";
595289
595314
  var NexusAgenticBackend;
595290
595315
  var init_nexusBackend = __esm({
@@ -595486,7 +595511,7 @@ ${suffix}` } : m2);
595486
595511
  * Falls back to unary + word-split if streaming setup fails.
595487
595512
  */
595488
595513
  async *chatCompletionStream(request) {
595489
- const streamFile = join113(tmpdir20(), `nexus-stream-${randomBytes21(6).toString("hex")}.jsonl`);
595514
+ const streamFile = join113(tmpdir21(), `nexus-stream-${randomBytes21(6).toString("hex")}.jsonl`);
595490
595515
  writeFileSync49(streamFile, "", "utf8");
595491
595516
  const effectiveThink = this.effectiveThink(request);
595492
595517
  const daemonArgs = {
@@ -603069,7 +603094,7 @@ function liveDashboardPreviewWidthForSources(sourceCount, termWidth = process.st
603069
603094
  function formatCameraPane(camera, paneWidth, paneHeight, now2) {
603070
603095
  const contentWidth = Math.max(10, paneWidth - 2);
603071
603096
  const age = Math.max(0, Math.round((now2 - camera.updatedAt) / 1e3));
603072
- const title = `${compactSourceId(camera.source)} ${age}s${cameraPaneBadge(camera)}`;
603097
+ const title = `↺ ${compactSourceId(camera.source)} ${age}s${cameraPaneBadge(camera)} ↻`;
603073
603098
  const topTitle = ` ${title} `;
603074
603099
  const left = Math.max(0, Math.floor((contentWidth - topTitle.length) / 2));
603075
603100
  const right = Math.max(0, contentWidth - topTitle.length - left);
@@ -603080,6 +603105,30 @@ function formatCameraPane(camera, paneWidth, paneHeight, now2) {
603080
603105
  const body = fitDashboardPane(previewLines, contentWidth, paneHeight, fallback);
603081
603106
  return [top, ...body.map((line) => `│${truncateAnsiCell(line, contentWidth)}│`), bottom];
603082
603107
  }
603108
+ function liveDashboardRotationActionFromClick(snapshot, lineText, col) {
603109
+ const plain = stripDashboardAnsi(lineText);
603110
+ const idx = Math.max(0, col - 1);
603111
+ const clicked = plain[idx];
603112
+ if (clicked !== "↺" && clicked !== "↻") return null;
603113
+ const direction = clicked === "↺" ? "ccw" : "cw";
603114
+ const cameras = Object.values(snapshot.cameras ?? (snapshot.video ? { [snapshot.video.source]: snapshot.video } : {}));
603115
+ if (cameras.length === 0) return null;
603116
+ const leftPositions = [];
603117
+ for (let i2 = 0; i2 < plain.length; i2++) {
603118
+ if (plain[i2] === "↺") leftPositions.push(i2);
603119
+ }
603120
+ for (let i2 = 0; i2 < leftPositions.length; i2++) {
603121
+ const left = leftPositions[i2];
603122
+ const nextLeft = leftPositions[i2 + 1] ?? plain.length;
603123
+ const right = plain.indexOf("↻", left + 1);
603124
+ if (right < 0 || right >= nextLeft) continue;
603125
+ if (idx !== left && idx !== right) continue;
603126
+ const segment = plain.slice(left, right + 1);
603127
+ const camera = cameras.find((entry) => segment.includes(compactSourceId(entry.source)));
603128
+ if (camera) return { source: camera.source, direction };
603129
+ }
603130
+ return null;
603131
+ }
603083
603132
  function pushDashboardWrapped(lines, value2, width) {
603084
603133
  const contentWidth = Math.max(10, width - 2);
603085
603134
  const words = stripDashboardAnsi(value2).replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
@@ -603107,6 +603156,16 @@ function isAudioFailureText(value2) {
603107
603156
  if (!text2) return false;
603108
603157
  return /^(?:ASR|VAD|Transcription)\s+(?:failed|unavailable)|No module named ['"]?transcribe_cli|torchcodec|torchaudio version/i.test(text2);
603109
603158
  }
603159
+ function sanitizeLiveAudioError(value2) {
603160
+ const text2 = String(value2 ?? "").trim();
603161
+ if (!text2) return "";
603162
+ if (/transcribe-cli module:\s*No module named ['"]?transcribe_cli/i.test(text2) || /No module named ['"]?transcribe_cli/i.test(text2)) {
603163
+ const parts = text2.split(/\s*\|\s*/g).map((part) => part.trim()).filter((part) => part && !/transcribe-cli module:\s*No module named ['"]?transcribe_cli/i.test(part) && !/No module named ['"]?transcribe_cli/i.test(part));
603164
+ const preserved = parts.join(" | ").trim();
603165
+ return preserved || "ASR unavailable: managed Whisper runtime is still bootstrapping or failed; transcribe-cli is not required for live ASR.";
603166
+ }
603167
+ return text2;
603168
+ }
603110
603169
  function readPcm16WavActivity(filePath, bins = 36) {
603111
603170
  try {
603112
603171
  const buf = readFileSync88(filePath);
@@ -603221,39 +603280,12 @@ function parseLiveSpeechDecision(value2) {
603221
603280
  };
603222
603281
  }
603223
603282
  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
603283
  return {
603252
603284
  speak: false,
603253
603285
  text: "",
603254
603286
  confidence: 0.55,
603255
- action: "silent",
603256
- reason: "ambient event did not justify interrupting with speech"
603287
+ action: proposal.urgency === "high" ? "queue_review" : "silent",
603288
+ reason: "live speech arbiter unavailable; refusing canned vocal fallback"
603257
603289
  };
603258
603290
  }
603259
603291
  async function classifyLiveSpeech(proposal, config) {
@@ -603286,7 +603318,7 @@ async function classifyLiveSpeech(proposal, config) {
603286
603318
  `urgency=${proposal.urgency}`,
603287
603319
  `summary=${proposal.summary}`,
603288
603320
  `evidence=${proposal.evidence}`,
603289
- `default_utterance=${proposal.defaultText}`
603321
+ proposal.speechGoal ? `speech_goal=${proposal.speechGoal}` : ""
603290
603322
  ].join("\n").slice(0, 1600)
603291
603323
  }
603292
603324
  ]
@@ -603586,7 +603618,7 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
603586
603618
  lines.push(`├${horizontal}┤`);
603587
603619
  const transcriptFailure = isAudioFailureText(snapshot.audio.transcript) ? snapshot.audio.transcript : "";
603588
603620
  const transcript = transcriptFailure ? "" : snapshot.audio.transcript;
603589
- const audioError = [snapshot.audio.error, transcriptFailure].filter(Boolean).join(" | ");
603621
+ const audioError = sanitizeLiveAudioError([snapshot.audio.error, transcriptFailure].filter(Boolean).join(" | "));
603590
603622
  const audioBits = [
603591
603623
  `audio input=${snapshot.audio.input || snapshot.config.selectedAudioInput || "none"}`,
603592
603624
  snapshot.audio.activity ? `activity ${snapshot.audio.activity.waveform} rms=${snapshot.audio.activity.rmsDb.toFixed(1)}dB active=${Math.round(snapshot.audio.activity.activeRatio * 100)}%` : "",
@@ -603969,7 +604001,7 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
603969
604001
  if (snapshot.audio) {
603970
604002
  const transcriptFailure = isAudioFailureText(snapshot.audio.transcript) ? snapshot.audio.transcript : "";
603971
604003
  const transcript = transcriptFailure ? "" : snapshot.audio.transcript;
603972
- const audioError = [snapshot.audio.error, transcriptFailure].filter(Boolean).join(" | ");
604004
+ const audioError = sanitizeLiveAudioError([snapshot.audio.error, transcriptFailure].filter(Boolean).join(" | "));
603973
604005
  const audioEvents = (snapshot.events ?? []).filter((event) => event.kind === "audio_transcript" || event.kind === "audio_sound").sort((a2, b) => b.observedAt - a2.observedAt).slice(0, 8);
603974
604006
  if (audioEvents.length > 0) {
603975
604007
  lines.push("");
@@ -604346,12 +604378,15 @@ var init_live_sensors = __esm({
604346
604378
  autoOrientationInFlight = /* @__PURE__ */ new Set();
604347
604379
  lastFeedbackAt = /* @__PURE__ */ new Map();
604348
604380
  lastClipAt = /* @__PURE__ */ new Map();
604381
+ lastInferenceAt = /* @__PURE__ */ new Map();
604349
604382
  nextVideoSourceIndex = 0;
604350
604383
  liveSpeechSink = null;
604351
604384
  liveSpeechEnabled = false;
604352
604385
  lastLiveSpeechAt = 0;
604353
604386
  lastLiveSpeechByKey = /* @__PURE__ */ new Map();
604354
604387
  liveSpeechInFlight = /* @__PURE__ */ new Set();
604388
+ liveSpeechMutedUntil = 0;
604389
+ liveTtsSpeaking = null;
604355
604390
  intakeAgentConfig;
604356
604391
  setStatusSink(sink) {
604357
604392
  this.statusSink = sink ?? null;
@@ -604369,6 +604404,15 @@ var init_live_sensors = __esm({
604369
604404
  setLiveSpeechEnabled(enabled2) {
604370
604405
  this.liveSpeechEnabled = enabled2;
604371
604406
  }
604407
+ setLiveTtsSpeakingSource(source) {
604408
+ this.liveTtsSpeaking = source ?? null;
604409
+ }
604410
+ muteLiveAudioFor(ms) {
604411
+ this.liveSpeechMutedUntil = Math.max(this.liveSpeechMutedUntil, Date.now() + Math.max(0, ms));
604412
+ }
604413
+ isLiveAudioMutedForTts() {
604414
+ return Date.now() < this.liveSpeechMutedUntil || Boolean(this.liveTtsSpeaking?.());
604415
+ }
604372
604416
  setAudioIntakeAgentConfig(config) {
604373
604417
  this.intakeAgentConfig = config && config.backendUrl && config.model ? { backendUrl: config.backendUrl, model: config.model, apiKey: config.apiKey } : void 0;
604374
604418
  }
@@ -604401,6 +604445,10 @@ var init_live_sensors = __esm({
604401
604445
  setCameraRotation(device, rotation, source, evidence, confidence2) {
604402
604446
  const target = device || this.config.selectedCamera || firstDeviceId(this.devices.video);
604403
604447
  if (!target) return null;
604448
+ const current = this.config.cameraOrientation?.[target];
604449
+ if (source === "auto" && current?.source === "manual") {
604450
+ return current;
604451
+ }
604404
604452
  const orientation = {
604405
604453
  rotation,
604406
604454
  source,
@@ -604439,6 +604487,12 @@ var init_live_sensors = __esm({
604439
604487
  const next = current === 0 ? 90 : current === 90 ? 180 : current === 180 ? 270 : 0;
604440
604488
  return this.setCameraRotation(device, next, "manual", "manual cycle from /live menu");
604441
604489
  }
604490
+ rotateCameraRelative(device, direction) {
604491
+ const current = this.getCameraRotation(device);
604492
+ const delta = direction === "cw" ? 90 : 270;
604493
+ const next = (current + delta) % 360;
604494
+ return this.setCameraRotation(device, next, "manual", `manual ${direction === "cw" ? "clockwise" : "counter-clockwise"} dashboard override`);
604495
+ }
604442
604496
  async autoOrientCamera(device = this.config.selectedCamera, options2 = {}) {
604443
604497
  const target = device || this.config.selectedCamera || firstDeviceId(this.devices.video);
604444
604498
  if (!target) return "No camera selected. Use /live camera <device> or select a camera in /live.";
@@ -604594,7 +604648,7 @@ var init_live_sensors = __esm({
604594
604648
  }, 6e4);
604595
604649
  });
604596
604650
  }
604597
- void this.sampleVideoNow(true, { mode: "all" }).catch((err) => {
604651
+ void this.sampleVideoNow(true, { mode: "all", forceInferenceNow: true }).catch((err) => {
604598
604652
  this.emitFeedback({
604599
604653
  kind: "error",
604600
604654
  title: "Live video sample failed",
@@ -604781,13 +604835,17 @@ var init_live_sensors = __esm({
604781
604835
  return message2;
604782
604836
  }
604783
604837
  }
604784
- async sampleSingleVideoNow(source, forceInfer, previewWidth) {
604838
+ async sampleSingleVideoNow(source, forceInfer, previewWidth, options2 = {}) {
604785
604839
  const preview = await this.previewCamera(source, { emit: false, previewWidth });
604786
604840
  let inference = "";
604787
604841
  let clipSummary = "";
604788
604842
  const orientation = this.getCameraOrientation(source);
604789
604843
  const sampledAt = Date.now();
604790
- if (forceInfer && source) {
604844
+ const inferenceThrottleMs = Math.max(0, Number(options2.inferThrottleMs ?? 2500));
604845
+ const lastInferenceAt = this.lastInferenceAt.get(source) ?? 0;
604846
+ const shouldInfer = Boolean(forceInfer && source && (options2.forceInferenceNow || sampledAt - lastInferenceAt >= inferenceThrottleMs));
604847
+ if (shouldInfer) {
604848
+ this.lastInferenceAt.set(source, sampledAt);
604791
604849
  const loop = new LiveMediaLoopTool(this.repoRoot);
604792
604850
  const inferFromCapturedFrame = preview.ok && Boolean(preview.framePath);
604793
604851
  const result = await loop.execute({
@@ -604856,7 +604914,7 @@ var init_live_sensors = __esm({
604856
604914
  source,
604857
604915
  summary: `${unknownPeople.length} unknown individual(s) observed on live camera ${source}`,
604858
604916
  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?",
604917
+ speechGoal: "If appropriate, ask the visible unknown person for the name Omnius should associate with the face crop. Do not claim an identity.",
604860
604918
  urgency: "high",
604861
604919
  observedAt: sampledAt
604862
604920
  }, 9e4);
@@ -604873,7 +604931,7 @@ var init_live_sensors = __esm({
604873
604931
  source,
604874
604932
  summary: `Visual trigger hit in live camera stream ${source}`,
604875
604933
  evidence: triggerEvents.map((entry) => entry.summary).join("; "),
604876
- defaultText: "I noticed something important in view. Should I inspect it?",
604934
+ speechGoal: "If immediate clarification is warranted, ask whether Omnius should inspect the visible trigger.",
604877
604935
  urgency: "high",
604878
604936
  observedAt: sampledAt
604879
604937
  }, 45e3);
@@ -604900,6 +604958,15 @@ ${inference}` : "", clipSummary ? `
604900
604958
  CLIP visual memory:
604901
604959
  ${clipSummary}` : ""].filter(Boolean).join("\n");
604902
604960
  }
604961
+ async sampleCameraNow(source, forceInfer = this.config.inferEnabled || this.config.clipEnabled, options2 = {}) {
604962
+ const previewWidth = liveDashboardPreviewWidthForSources(this.activeVideoSources().length);
604963
+ const result = await this.sampleSingleVideoNow(source, forceInfer, previewWidth, {
604964
+ forceInferenceNow: options2.forceInferenceNow,
604965
+ inferThrottleMs: options2.forceInferenceNow ? 0 : 2500
604966
+ });
604967
+ this.emitDashboard();
604968
+ return result;
604969
+ }
604903
604970
  async sampleVideoNow(forceInfer = this.config.inferEnabled, options2 = {}) {
604904
604971
  if (this.videoInFlight) return "Video sampler is already running.";
604905
604972
  this.videoInFlight = true;
@@ -604911,7 +604978,7 @@ ${clipSummary}` : ""].filter(Boolean).join("\n");
604911
604978
  const outputs = [];
604912
604979
  const previewWidth = liveDashboardPreviewWidthForSources(allSources.length);
604913
604980
  for (const source of sources) {
604914
- outputs.push(await this.sampleSingleVideoNow(source, forceInfer, previewWidth));
604981
+ outputs.push(await this.sampleSingleVideoNow(source, forceInfer, previewWidth, options2));
604915
604982
  }
604916
604983
  this.emitDashboard();
604917
604984
  return outputs.map((output, index) => `## Camera ${sources[index]}
@@ -604927,6 +604994,20 @@ ${output}`).join("\n\n");
604927
604994
  const preferredInput = preferredLiveAudioInputId(this.devices.audioInputs, this.config.selectedAudioInput) || "default";
604928
604995
  const input = this.config.selectedAudioInput && !isAudioOutputMonitorId(this.config.selectedAudioInput) ? this.config.selectedAudioInput : preferredInput;
604929
604996
  if (input !== this.config.selectedAudioInput) this.config.selectedAudioInput = input;
604997
+ if (this.isLiveAudioMutedForTts()) {
604998
+ const now2 = Date.now();
604999
+ this.snapshot.audio = {
605000
+ updatedAt: now2,
605001
+ input,
605002
+ output: this.config.selectedAudioOutput,
605003
+ transcript: "",
605004
+ analysis: "",
605005
+ error: "Live audio capture muted during Omnius TTS playback to prevent echo."
605006
+ };
605007
+ this.persist();
605008
+ this.emitDashboard();
605009
+ return "Live audio capture muted during Omnius TTS playback to prevent echo.";
605010
+ }
604930
605011
  const outputDir2 = liveDir(this.repoRoot);
604931
605012
  ensureLiveDir(this.repoRoot);
604932
605013
  const recordingPath = join124(outputDir2, `audio-${Date.now()}.wav`);
@@ -604963,7 +605044,11 @@ ${output}`).join("\n\n");
604963
605044
  }
604964
605045
  if (this.config.asrEnabled) {
604965
605046
  try {
604966
- const asr = await new TranscribeFileTool(this.repoRoot).execute({ path: recordingPath, model: "tiny" });
605047
+ const asr = await new TranscribeFileTool(this.repoRoot).execute({
605048
+ path: recordingPath,
605049
+ model: "tiny",
605050
+ backend: "managed-whisper"
605051
+ });
604967
605052
  if (asr.success) {
604968
605053
  transcript = cleanPreview(typeof asr.llmContent === "string" ? asr.llmContent : "", 1600);
604969
605054
  asrBackend = extractAsrBackend(asr.output) ?? "whisper";
@@ -605043,7 +605128,7 @@ ${output}`).join("\n\n");
605043
605128
  source: capture.device,
605044
605129
  summary: "Live audio intake classified speech as intended for Omnius",
605045
605130
  evidence: `transcript=${transcript}; confidence=${intake.confidence.toFixed(2)}; reason=${intake.reason}`,
605046
- defaultText: "I heard you. I am checking the live context now.",
605131
+ speechGoal: "Respond naturally to the addressed utterance using live context; keep it brief.",
605047
605132
  urgency: "high",
605048
605133
  observedAt: this.snapshot.audio.updatedAt
605049
605134
  }, 2e4);
@@ -606316,7 +606401,7 @@ var init_listen = __esm({
606316
606401
  let caughtUncaught = null;
606317
606402
  const uncaughtHandler = (err) => {
606318
606403
  const msg = err?.message || String(err);
606319
- if (msg.includes("live_worker") || msg.includes("numpy") || msg.includes("Traceback")) {
606404
+ if (msg.includes("live_worker") || msg.includes("numpy") || msg.includes("transcribe_cli") || msg.includes("ModuleNotFoundError") || msg.includes("Traceback")) {
606320
606405
  caughtUncaught = err;
606321
606406
  } else {
606322
606407
  throw err;
@@ -625268,6 +625353,7 @@ var init_status_bar = __esm({
625268
625353
  * repaint, including selection updates and scroll events.
625269
625354
  */
625270
625355
  _dynamicBlocks = /* @__PURE__ */ new Map();
625356
+ _dynamicBlockClickHandlers = /* @__PURE__ */ new Map();
625271
625357
  /** Sentinel marker — used both for scrollback storage and reflow detection. */
625272
625358
  DYNAMIC_BLOCK_MARK_PREFIX = "DYNBLOCK:";
625273
625359
  DYNAMIC_BLOCK_MARK_SUFFIX = "";
@@ -625414,9 +625500,13 @@ var init_status_bar = __esm({
625414
625500
  this.invalidateRowCountCache();
625415
625501
  return `${this.DYNAMIC_BLOCK_MARK_PREFIX}${id2}${this.DYNAMIC_BLOCK_MARK_SUFFIX}`;
625416
625502
  }
625503
+ registerDynamicBlockClickHandler(id2, handler) {
625504
+ this._dynamicBlockClickHandlers.set(id2, handler);
625505
+ }
625417
625506
  /** Unregister a dynamic block. Existing sentinels in scrollback become inert (rendered as empty). */
625418
625507
  unregisterDynamicBlock(id2) {
625419
625508
  this._dynamicBlocks.delete(id2);
625509
+ this._dynamicBlockClickHandlers.delete(id2);
625420
625510
  this._dynamicBlockVersion++;
625421
625511
  this.invalidateRowCountCache();
625422
625512
  }
@@ -627119,9 +627209,23 @@ var init_status_bar = __esm({
627119
627209
  * other body rows are left alone so text selection still works. Returns true
627120
627210
  * when the click was consumed.
627121
627211
  */
627122
- handleContentBlockClick(screenRow) {
627212
+ handleContentBlockClick(screenRow, col) {
627123
627213
  const hit = this.hitTestContentBlock(screenRow);
627124
- if (!hit || !isCollapsibleBlock(hit.id)) return false;
627214
+ if (!hit) return false;
627215
+ const handler = this._dynamicBlockClickHandlers.get(hit.id);
627216
+ if (handler?.({
627217
+ id: hit.id,
627218
+ offset: hit.offset,
627219
+ col,
627220
+ row: screenRow,
627221
+ lineText: hit.lineText
627222
+ })) {
627223
+ if (this.active && !isOverlayActive() && !this._suspendContentLayer) {
627224
+ this.repaintContent();
627225
+ }
627226
+ return true;
627227
+ }
627228
+ if (!isCollapsibleBlock(hit.id)) return false;
627125
627229
  const plain = stripAnsi(hit.lineText);
627126
627230
  if (plain.includes(READ_MORE_LABEL) || plain.includes(READ_LESS_LABEL)) {
627127
627231
  toggleMore(hit.id);
@@ -627145,7 +627249,7 @@ var init_status_bar = __esm({
627145
627249
  if (!this.active) return;
627146
627250
  const w = termCols();
627147
627251
  if (type === "press" && row >= this.scrollRegionTop) {
627148
- if (this.handleContentBlockClick(row)) return;
627252
+ if (this.handleContentBlockClick(row, col)) return;
627149
627253
  }
627150
627254
  if (type === "press" && this._suggestions.length > 0) {
627151
627255
  if (this.suggestClickAt(row)) return;
@@ -638356,7 +638460,7 @@ var init_audio_waveform = __esm({
638356
638460
 
638357
638461
  // packages/cli/src/tui/neovim-mode.ts
638358
638462
  import { existsSync as existsSync129, unlinkSync as unlinkSync24 } from "node:fs";
638359
- import { tmpdir as tmpdir21 } from "node:os";
638463
+ import { tmpdir as tmpdir22 } from "node:os";
638360
638464
  import { join as join143 } from "node:path";
638361
638465
  function isNeovimActive() {
638362
638466
  return _state2 !== null && !_state2.cleanedUp;
@@ -638402,7 +638506,7 @@ async function startNeovimMode(opts) {
638402
638506
  );
638403
638507
  } catch {
638404
638508
  }
638405
- const socketPath = join143(tmpdir21(), `omnius-nvim-${process.pid}-${Date.now()}.sock`);
638509
+ const socketPath = join143(tmpdir22(), `omnius-nvim-${process.pid}-${Date.now()}.sock`);
638406
638510
  try {
638407
638511
  if (existsSync129(socketPath)) unlinkSync24(socketPath);
638408
638512
  } catch {
@@ -641943,7 +642047,7 @@ import {
641943
642047
  rmSync as rmSync11
641944
642048
  } from "node:fs";
641945
642049
  import { join as join150, dirname as dirname45, resolve as resolve61 } from "node:path";
641946
- import { homedir as homedir50, tmpdir as tmpdir22, platform as platform6 } from "node:os";
642050
+ import { homedir as homedir50, tmpdir as tmpdir23, platform as platform6 } from "node:os";
641947
642051
  import {
641948
642052
  spawn as nodeSpawn
641949
642053
  } from "node:child_process";
@@ -643639,6 +643743,9 @@ except Exception as exc:
643639
643743
  await this.sleep(100);
643640
643744
  }
643641
643745
  }
643746
+ isSpeaking() {
643747
+ return this.speaking || this.speakQueue.length > 0 || Boolean(this.currentPlayback);
643748
+ }
643642
643749
  enqueueSpeech(text2, volume, pitchFactor, speedFactor = 1, stereoDelayMs = 0.6, emotion) {
643643
643750
  if (!this.enabled || !this.ready) return;
643644
643751
  text2 = sanitizeForTTS(text2);
@@ -644036,7 +644143,7 @@ except Exception as exc:
644036
644143
  }
644037
644144
  this.onPCMOutput(Buffer.from(int16.buffer), sampleRate);
644038
644145
  }
644039
- const wavPath = join150(tmpdir22(), `omnius-voice-${Date.now()}.wav`);
644146
+ const wavPath = join150(tmpdir23(), `omnius-voice-${Date.now()}.wav`);
644040
644147
  this.writeStereoWav(stereo.left, stereo.right, sampleRate, wavPath);
644041
644148
  await this.playWav(wavPath);
644042
644149
  try {
@@ -644549,7 +644656,7 @@ except Exception as exc:
644549
644656
  const cleaned = injectExpressionTags ? applySupertonicExpressionTags(baseText, settings.expression, emotion) : baseText;
644550
644657
  if (!cleaned) return null;
644551
644658
  const wavPath = join150(
644552
- tmpdir22(),
644659
+ tmpdir23(),
644553
644660
  `omnius-supertonic3-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.wav`
644554
644661
  );
644555
644662
  try {
@@ -644620,7 +644727,7 @@ except Exception as exc:
644620
644727
  return new Promise((resolve76, reject) => {
644621
644728
  const proc = nodeSpawn("sh", ["-c", command], {
644622
644729
  stdio: ["ignore", "pipe", "pipe"],
644623
- cwd: tmpdir22(),
644730
+ cwd: tmpdir23(),
644624
644731
  env: voicePythonEnv()
644625
644732
  });
644626
644733
  let stdout = "";
@@ -644707,7 +644814,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`
644707
644814
  const mlxModelId = model.mlxModelId ?? "mlx-community/Kokoro-82M-bf16";
644708
644815
  const mlxVoice = model.mlxVoice ?? "af_heart";
644709
644816
  const mlxLangCode = model.mlxLangCode ?? "a";
644710
- const wavPath = join150(tmpdir22(), `omnius-mlx-${Date.now()}.wav`);
644817
+ const wavPath = join150(tmpdir23(), `omnius-mlx-${Date.now()}.wav`);
644711
644818
  const pyScript = [
644712
644819
  "import sys, json",
644713
644820
  "from mlx_audio.tts import generate as tts_gen",
@@ -644788,7 +644895,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`
644788
644895
  const mlxModelId = model.mlxModelId ?? "mlx-community/Kokoro-82M-bf16";
644789
644896
  const mlxVoice = model.mlxVoice ?? "af_heart";
644790
644897
  const mlxLangCode = model.mlxLangCode ?? "a";
644791
- const wavPath = join150(tmpdir22(), `omnius-mlx-buf-${Date.now()}.wav`);
644898
+ const wavPath = join150(tmpdir23(), `omnius-mlx-buf-${Date.now()}.wav`);
644792
644899
  const pyScript = [
644793
644900
  "import sys, json",
644794
644901
  "from mlx_audio.tts import generate as tts_gen",
@@ -645564,7 +645671,7 @@ if __name__ == '__main__':
645564
645671
  const env2 = voicePythonEnv({ LUXTTS_REPO_PATH: luxttsRepoDir2() });
645565
645672
  const daemon = nodeSpawn(venvPy, [luxttsInferScript2()], {
645566
645673
  stdio: ["pipe", "pipe", "pipe"],
645567
- cwd: tmpdir22(),
645674
+ cwd: tmpdir23(),
645568
645675
  env: env2
645569
645676
  });
645570
645677
  this._luxttsDaemon = daemon;
@@ -645654,7 +645761,7 @@ if __name__ == '__main__':
645654
645761
  const ready = await this.ensureLuxttsDaemon();
645655
645762
  if (!ready) return null;
645656
645763
  const wavPath = join150(
645657
- tmpdir22(),
645764
+ tmpdir23(),
645658
645765
  `omnius-luxtts-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.wav`
645659
645766
  );
645660
645767
  try {
@@ -645795,7 +645902,7 @@ if __name__ == '__main__':
645795
645902
  if (!cleaned) return null;
645796
645903
  const ready = await this.ensureLuxttsDaemon();
645797
645904
  if (!ready) return null;
645798
- const wavPath = join150(tmpdir22(), `omnius-luxtts-buf-${Date.now()}.wav`);
645905
+ const wavPath = join150(tmpdir23(), `omnius-luxtts-buf-${Date.now()}.wav`);
645799
645906
  try {
645800
645907
  await this.luxttsRequest({
645801
645908
  action: "synthesize",
@@ -645929,7 +646036,7 @@ if __name__ == "__main__":
645929
646036
  });
645930
646037
  const daemon = nodeSpawn(venvPy, [misottsInferScript2()], {
645931
646038
  stdio: ["pipe", "pipe", "pipe"],
645932
- cwd: tmpdir22(),
646039
+ cwd: tmpdir23(),
645933
646040
  env: env2
645934
646041
  });
645935
646042
  this._misottsDaemon = daemon;
@@ -646013,7 +646120,7 @@ if __name__ == "__main__":
646013
646120
  const ready = await this.ensureMisottsDaemon();
646014
646121
  if (!ready) return null;
646015
646122
  const wavPath = join150(
646016
- tmpdir22(),
646123
+ tmpdir23(),
646017
646124
  `omnius-misotts-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.wav`
646018
646125
  );
646019
646126
  try {
@@ -646163,7 +646270,7 @@ if __name__ == "__main__":
646163
646270
  if (!cleaned) return null;
646164
646271
  const ready = await this.ensureMisottsDaemon();
646165
646272
  if (!ready) return null;
646166
- const wavPath = join150(tmpdir22(), `omnius-misotts-buf-${Date.now()}.wav`);
646273
+ const wavPath = join150(tmpdir23(), `omnius-misotts-buf-${Date.now()}.wav`);
646167
646274
  try {
646168
646275
  const settings = this.getMisottsSettings();
646169
646276
  const cloneName = this.misottsCloneRef.split("/").pop() ?? "";
@@ -656904,6 +657011,15 @@ function renderLiveDashboard(ctx3, manager) {
656904
657011
  id2,
656905
657012
  (width) => formatLiveDashboardFromSnapshot(manager.getSnapshot(), { width })
656906
657013
  );
657014
+ host.registerDynamicBlockClickHandler?.(id2, ({ col, lineText }) => {
657015
+ const action = liveDashboardRotationActionFromClick(manager.getSnapshot(), lineText, col);
657016
+ if (!action) return false;
657017
+ manager.rotateCameraRelative(action.source, action.direction);
657018
+ void manager.sampleCameraNow(action.source, true, { forceInferenceNow: true }).catch((err) => {
657019
+ renderWarning(`Camera refresh failed after manual rotation: ${err instanceof Error ? err.message : String(err)}`);
657020
+ });
657021
+ return true;
657022
+ });
656907
657023
  host.appendDynamicBlock(id2);
656908
657024
  return;
656909
657025
  }
@@ -656934,7 +657050,7 @@ ${feedback.message}`);
656934
657050
  renderInfo(`[live ${stamp}] ${feedback.title}
656935
657051
  ${feedback.message}`);
656936
657052
  }
656937
- function attachLiveSinks(ctx3, manager, agentEnabled = false) {
657053
+ function attachLiveSinks(ctx3, manager, agentEnabled = false, speechEnabledOverride) {
656938
657054
  manager.setStatusSink(ctx3.setLiveMediaStatus);
656939
657055
  manager.setFeedbackSink((feedback) => renderLiveFeedback(ctx3, manager, feedback));
656940
657056
  manager.setAudioIntakeAgentConfig({
@@ -656942,10 +657058,13 @@ function attachLiveSinks(ctx3, manager, agentEnabled = false) {
656942
657058
  model: ctx3.config.model,
656943
657059
  apiKey: ctx3.config.apiKey
656944
657060
  });
656945
- const speechEnabled = agentEnabled && Boolean(ctx3.voiceSpeak);
657061
+ const speechEnabled = Boolean(speechEnabledOverride ?? agentEnabled) && Boolean(ctx3.voiceSpeak);
656946
657062
  manager.setLiveSpeechEnabled(speechEnabled);
657063
+ manager.setLiveTtsSpeakingSource(ctx3.voiceIsSpeaking);
656947
657064
  manager.setLiveSpeechSink(speechEnabled ? (text2) => {
656948
657065
  if (ctx3.voiceIsEnabled && !ctx3.voiceIsEnabled()) return;
657066
+ const estimatedSpeechMs = Math.max(1500, Math.min(15e3, text2.length * 85));
657067
+ manager.muteLiveAudioFor(estimatedSpeechMs + 1e3);
656949
657068
  ctx3.voiceSpeak?.(text2);
656950
657069
  } : void 0);
656951
657070
  if (agentEnabled && ctx3.startBackgroundPrompt) {
@@ -656958,17 +657077,17 @@ function attachLiveSinks(ctx3, manager, agentEnabled = false) {
656958
657077
  }
656959
657078
  }
656960
657079
  async function startLiveVoicechat(ctx3, manager) {
656961
- const agentEnabled = Boolean(ctx3.startBackgroundPrompt);
656962
- attachLiveSinks(ctx3, manager, agentEnabled);
657080
+ const speechEnabled = Boolean(ctx3.voiceSpeak);
657081
+ attachLiveSinks(ctx3, manager, false, speechEnabled);
656963
657082
  await ensureLiveInventory(manager);
656964
657083
  if (!ctx3.voiceChatStart) {
656965
- renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true, speech: false }));
657084
+ renderInfo(manager.startRunMode({ agent: false, lowLatency: true, speech: false }));
656966
657085
  renderWarning("Voice chat is not available in this context; live audio/video feedback remains active.");
656967
657086
  return;
656968
657087
  }
656969
657088
  ctx3.voiceSetMode?.("voicechat");
656970
657089
  if (ctx3.isVoiceChatActive?.()) {
656971
- renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true, speech: Boolean(ctx3.voiceSpeak) }));
657090
+ renderInfo(manager.startRunMode({ agent: false, lowLatency: true, speech: speechEnabled }));
656972
657091
  renderInfo("Voice chat is already active. Live audio/video feedback remains attached.");
656973
657092
  return;
656974
657093
  }
@@ -656979,7 +657098,7 @@ async function startLiveVoicechat(ctx3, manager) {
656979
657098
  try {
656980
657099
  renderInfo("Starting live voicechat with audio/video context...");
656981
657100
  await ctx3.voiceChatStart();
656982
- renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true, speech: Boolean(ctx3.voiceSpeak) }));
657101
+ renderInfo(manager.startRunMode({ agent: false, lowLatency: true, speech: speechEnabled }));
656983
657102
  renderInfo("Live voicechat active: low-latency audio/video context is updating while voice conversation continues.");
656984
657103
  } catch (err) {
656985
657104
  renderError(`Live voicechat failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -695342,15 +695461,15 @@ Content-Type: ${mimeForPath(pathOrFileId, field === "photo" ? "image" : "video")
695342
695461
  let filename = "response.wav";
695343
695462
  let mimeType = "audio/wav";
695344
695463
  try {
695345
- const { tmpdir: tmpdir25 } = await import("node:os");
695464
+ const { tmpdir: tmpdir26 } = await import("node:os");
695346
695465
  const { join: joinPath } = await import("node:path");
695347
695466
  const {
695348
695467
  writeFileSync: writeFs,
695349
695468
  readFileSync: readFs,
695350
695469
  unlinkSync: unlinkFs
695351
695470
  } = await import("node:fs");
695352
- const tmpWav = joinPath(tmpdir25(), `omnius-tg-voice-${Date.now()}.wav`);
695353
- const tmpOgg = joinPath(tmpdir25(), `omnius-tg-voice-${Date.now()}.ogg`);
695471
+ const tmpWav = joinPath(tmpdir26(), `omnius-tg-voice-${Date.now()}.wav`);
695472
+ const tmpOgg = joinPath(tmpdir26(), `omnius-tg-voice-${Date.now()}.ogg`);
695354
695473
  writeFs(tmpWav, wavBuffer);
695355
695474
  await execFileText4(
695356
695475
  "ffmpeg",
@@ -695735,7 +695854,10 @@ var init_task_manager_singleton = __esm({
695735
695854
  // packages/cli/src/tui/voicechat.ts
695736
695855
  var voicechat_exports = {};
695737
695856
  __export(voicechat_exports, {
695738
- VoiceChatSession: () => VoiceChatSession
695857
+ VoiceChatSession: () => VoiceChatSession,
695858
+ buildRealtimeVoiceMessages: () => buildRealtimeVoiceMessages,
695859
+ extractVoiceModelReply: () => extractVoiceModelReply,
695860
+ isLikelyAgentSpeechEcho: () => isLikelyAgentSpeechEcho
695739
695861
  });
695740
695862
  import { EventEmitter as EventEmitter12 } from "node:events";
695741
695863
  function clamp0116(x) {
@@ -695832,19 +695954,138 @@ function stripToolJsonLines(text2) {
695832
695954
  });
695833
695955
  return kept.join("\n").trim();
695834
695956
  }
695835
- var VAD_SILENCE_MS, MAX_SEGMENT_MS, MAX_CONTEXT_TURNS, SYSTEM_PROMPT2, MIN_SIGNAL_SCORE, NOISE_ONLY_RE, VoiceChatSession;
695957
+ function compactSpeechText(value2, maxLength = 8e3) {
695958
+ const text2 = String(value2 ?? "").replace(/\s+/g, " ").replace(/\s+([,.!?;:])/g, "$1").trim();
695959
+ return text2 ? text2.slice(0, maxLength) : "";
695960
+ }
695961
+ function comparableSpeechText(value2) {
695962
+ return String(value2 ?? "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
695963
+ }
695964
+ function comparableTokens(value2) {
695965
+ return comparableSpeechText(value2).split(/\s+/).filter(Boolean);
695966
+ }
695967
+ function jaccardSimilarity(aTokens, bTokens) {
695968
+ const a2 = new Set(aTokens);
695969
+ const b = new Set(bTokens);
695970
+ if (!a2.size || !b.size) return 0;
695971
+ let overlap = 0;
695972
+ for (const token of a2) if (b.has(token)) overlap++;
695973
+ return overlap / Math.max(1, Math.min(a2.size, b.size));
695974
+ }
695975
+ function isMostlyContainedInOrder(needleTokens, haystackTokens) {
695976
+ if (!needleTokens.length || !haystackTokens.length) return false;
695977
+ let index = 0;
695978
+ for (const token of haystackTokens) {
695979
+ if (token === needleTokens[index]) index++;
695980
+ if (index >= needleTokens.length) return true;
695981
+ }
695982
+ return false;
695983
+ }
695984
+ function isLikelyAgentSpeechEcho({
695985
+ heardText,
695986
+ agentText,
695987
+ elapsedMs: elapsedMs2
695988
+ }) {
695989
+ const heard = compactSpeechText(heardText, 1200);
695990
+ if (!heard) return { likelyEcho: false, echoSimilarity: 0, reason: "empty" };
695991
+ if (!Number.isFinite(elapsedMs2) || elapsedMs2 > AGENT_ECHO_WINDOW_MS) {
695992
+ return { likelyEcho: false, echoSimilarity: 0, reason: "outside_window" };
695993
+ }
695994
+ const heardTokens = comparableTokens(heard);
695995
+ const agentTokens = comparableTokens(agentText);
695996
+ if (!agentTokens.length) return { likelyEcho: false, echoSimilarity: 0, reason: "no_agent_speech" };
695997
+ const agentComparable = agentTokens.join(" ");
695998
+ const heardComparable = heardTokens.join(" ");
695999
+ const echoSimilarity = jaccardSimilarity(heardTokens, agentTokens);
696000
+ const exactEcho = Boolean(heardComparable && agentComparable && (agentComparable.includes(heardComparable) || heardComparable.includes(agentComparable)));
696001
+ const orderedEcho = heardTokens.length >= 3 && isMostlyContainedInOrder(heardTokens, agentTokens);
696002
+ const likelyEcho = echoSimilarity >= AGENT_ECHO_SIMILARITY || exactEcho || orderedEcho;
696003
+ return {
696004
+ likelyEcho,
696005
+ echoSimilarity,
696006
+ reason: likelyEcho ? "speaker_echo" : "pass"
696007
+ };
696008
+ }
696009
+ function clipVoiceReply(text2, maxLength = MAX_VOICE_REPLY_CHARS) {
696010
+ const cleaned = String(text2 || "").replace(/\s+/g, " ").replace(/^[`"']+|[`"']+$/g, "").trim();
696011
+ return cleaned ? cleaned.slice(0, maxLength) : "";
696012
+ }
696013
+ function tryParseJsonObject(value2) {
696014
+ const text2 = value2.trim();
696015
+ if (!text2.startsWith("{") || !text2.endsWith("}")) return null;
696016
+ try {
696017
+ const parsed = JSON.parse(text2);
696018
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
696019
+ } catch {
696020
+ return null;
696021
+ }
696022
+ }
696023
+ function extractVoiceModelReply(value2) {
696024
+ const cleaned = String(value2 ?? "").replace(/^\s*```(?:json)?/i, "").replace(/```\s*$/i, "").trim();
696025
+ const start2 = cleaned.indexOf("{");
696026
+ const end = cleaned.lastIndexOf("}");
696027
+ const embedded = tryParseJsonObject(cleaned) || (start2 >= 0 && end > start2 ? tryParseJsonObject(cleaned.slice(start2, end + 1)) : null);
696028
+ if (embedded && typeof embedded["text"] === "string") {
696029
+ const actionRaw = String(embedded["action"] ?? "speak").toLowerCase();
696030
+ const action = actionRaw === "hangup" ? "hangup" : actionRaw === "silent" || actionRaw === "none" ? "silent" : "speak";
696031
+ return {
696032
+ action,
696033
+ text: clipVoiceReply(embedded["text"]),
696034
+ structured: true,
696035
+ internalReason: typeof embedded["internal_reason"] === "string" ? clipVoiceReply(embedded["internal_reason"], 500) : typeof embedded["internalReason"] === "string" ? clipVoiceReply(embedded["internalReason"], 500) : null
696036
+ };
696037
+ }
696038
+ const text2 = cleaned.replace(/^\s*```(?:[a-z]+)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").replace(/^(?:agent|assistant|ai|voice|speaker|reply)\s*:\s*/i, "").trim();
696039
+ return {
696040
+ action: text2 ? "speak" : "silent",
696041
+ text: clipVoiceReply(text2),
696042
+ structured: false,
696043
+ internalReason: null
696044
+ };
696045
+ }
696046
+ function isContextSnapshotTurn(turn) {
696047
+ return turn.role === "system" && turn.content.startsWith(CONTEXT_SNAPSHOT_PREFIX);
696048
+ }
696049
+ function isPinnedSystemTurn(turn) {
696050
+ return turn.role === "system" && (turn.content === SYSTEM_PROMPT2 || turn.content.startsWith("Available tools (emit one-line JSON:"));
696051
+ }
696052
+ function buildRealtimeVoiceMessages(turns, maxMessages = MAX_REALTIME_MODEL_MESSAGES) {
696053
+ const pinned = [];
696054
+ const dynamic = [];
696055
+ let latestSnapshot = null;
696056
+ for (const turn of turns) {
696057
+ if (isPinnedSystemTurn(turn) && !pinned.some((p2) => p2.content === turn.content)) {
696058
+ pinned.push(turn);
696059
+ } else if (isContextSnapshotTurn(turn)) {
696060
+ latestSnapshot = turn;
696061
+ } else {
696062
+ dynamic.push(turn);
696063
+ }
696064
+ }
696065
+ if (latestSnapshot) dynamic.push(latestSnapshot);
696066
+ const available = Math.max(0, maxMessages - pinned.length);
696067
+ return [...pinned, ...dynamic.slice(-available)];
696068
+ }
696069
+ var VAD_SILENCE_MS, MAX_SEGMENT_MS, SUMMARY_INJECTION_INTERVAL, MAX_CONTEXT_TURNS, MAX_REALTIME_MODEL_MESSAGES, CONTEXT_SNAPSHOT_PREFIX, MAX_VOICE_REPLY_CHARS, AGENT_ECHO_WINDOW_MS, AGENT_ECHO_SIMILARITY, SYSTEM_PROMPT2, MIN_SIGNAL_SCORE, NOISE_ONLY_RE, VoiceChatSession;
695836
696070
  var init_voicechat = __esm({
695837
696071
  "packages/cli/src/tui/voicechat.ts"() {
695838
696072
  "use strict";
695839
696073
  VAD_SILENCE_MS = 3e3;
695840
696074
  MAX_SEGMENT_MS = 6500;
696075
+ SUMMARY_INJECTION_INTERVAL = 4;
695841
696076
  MAX_CONTEXT_TURNS = 20;
696077
+ MAX_REALTIME_MODEL_MESSAGES = 16;
696078
+ CONTEXT_SNAPSHOT_PREFIX = "Context snapshot (read-only):";
696079
+ MAX_VOICE_REPLY_CHARS = 700;
696080
+ AGENT_ECHO_WINDOW_MS = 12e3;
696081
+ AGENT_ECHO_SIMILARITY = 0.72;
695842
696082
  SYSTEM_PROMPT2 = `You are a voice assistant having a live spoken conversation. Keep responses extremely brief — 1-2 sentences max. You're speaking aloud, not writing. Be conversational, direct, and helpful. Don't use markdown or formatting — just natural speech.
695843
696083
 
695844
696084
  Rules:
695845
696085
  - Never invent environment facts (cwd, OS, specs, repo state). If you need a precise fact from the main agent, request a tool by outputting on a single line EXACTLY one JSON object: {"tool": string, "args": object} and nothing else. Then wait for the tool result before answering.
695846
696086
  - You may also request to relay a user task to the main agent by emitting {"tool":"voice_to_main","args":{"message":"...","start":true}}.
695847
- - Prefer tools for factual queries; otherwise, answer directly with a short reply.`;
696087
+ - Prefer tools for factual queries; otherwise, answer directly with a short reply.
696088
+ - Internal notes must never be spoken. If you need an internal reason, return compact JSON: {"action":"speak","text":"exact words to speak aloud","internal_reason":"brief nonspoken reason"}.`;
695848
696089
  MIN_SIGNAL_SCORE = 0.4;
695849
696090
  NOISE_ONLY_RE = /^(?:[.·…\s,;:!?\-–—_()\[\]{}"'`]+|(?:uh|um|erm|hmm|mm+|uhh+|umm+)[\s.!?]*)+$/i;
695850
696091
  VoiceChatSession = class extends EventEmitter12 {
@@ -695874,6 +696115,8 @@ Rules:
695874
696115
  silenceTimer = null;
695875
696116
  maxSegmentTimer = null;
695876
696117
  lastSignalScore = null;
696118
+ listenPausedForResponse = false;
696119
+ lastAgentSpeech = null;
695877
696120
  // Abort control for inference
695878
696121
  abortController = null;
695879
696122
  // Callbacks
@@ -695949,6 +696192,10 @@ Rules:
695949
696192
  this.context.push({ role: "system", content: this.toolCatalogNote });
695950
696193
  }
695951
696194
  this.turnCount = 0;
696195
+ this.voiceTranscript = [];
696196
+ this.relayTranscript = [];
696197
+ this.lastAgentSpeech = null;
696198
+ this.listenPausedForResponse = false;
695952
696199
  if (this.verbose) this.onStatus("VoiceChat active — LISTENING");
695953
696200
  this._onTranscript = (...args) => {
695954
696201
  let text2;
@@ -696038,6 +696285,12 @@ Rules:
696038
696285
  if (this._state !== "LISTENING" && this._state !== "CAPTURING") {
696039
696286
  return;
696040
696287
  }
696288
+ const echo = this.classifyAgentEcho(text2);
696289
+ if (echo.likelyEcho) {
696290
+ if (this.debugSnr) this.onStatus(`Ignoring likely agent-speaker echo (${echo.echoSimilarity.toFixed(2)}): ${truncateForLog(text2, 48)}`);
696291
+ this.emit("voiceEchoFiltered", echo);
696292
+ return;
696293
+ }
696041
696294
  if (this._state === "LISTENING") {
696042
696295
  this.setState("CAPTURING");
696043
696296
  this.captureBuffer = "";
@@ -696088,6 +696341,7 @@ Rules:
696088
696341
  return;
696089
696342
  }
696090
696343
  this.setState("TRANSCRIBING");
696344
+ this.pauseListenForResponse();
696091
696345
  this.onUserSpeech(text2);
696092
696346
  this.voiceTranscript.push({ role: "user", content: text2, ts: Date.now() });
696093
696347
  this.context.push({ role: "user", content: text2 });
@@ -696098,9 +696352,7 @@ Rules:
696098
696352
  } catch {
696099
696353
  }
696100
696354
  }
696101
- while (this.context.length > MAX_CONTEXT_TURNS + 1) {
696102
- this.context.splice(1, 1);
696103
- }
696355
+ this.trimContext();
696104
696356
  this.think();
696105
696357
  }
696106
696358
  // ---------------------------------------------------------------------------
@@ -696111,18 +696363,20 @@ Rules:
696111
696363
  this.setState("THINKING");
696112
696364
  if (this.verbose) this.onStatus("Thinking...");
696113
696365
  this.abortController = new AbortController();
696366
+ const lastUser = this.latestUserText();
696114
696367
  try {
696115
696368
  if (this.toolRelay?.contextSnapshot) {
696116
696369
  try {
696117
696370
  const snap = await Promise.resolve(this.toolRelay.contextSnapshot());
696118
696371
  if (snap && snap.trim()) {
696372
+ this.dropTransientSystemTurns();
696119
696373
  this.context.push({ role: "system", content: `Context snapshot (read-only):
696120
696374
  ${snap.trim()}` });
696375
+ this.trimContext();
696121
696376
  }
696122
696377
  } catch {
696123
696378
  }
696124
696379
  }
696125
- const lastUser = [...this.context].reverse().find((m2) => m2.role === "user")?.content || "";
696126
696380
  let preAnswered = false;
696127
696381
  if (this.heuristicsEnabled && this.toolRelay && lastUser) {
696128
696382
  const lower = lastUser.toLowerCase();
@@ -696176,6 +696430,7 @@ ${out}` });
696176
696430
  }
696177
696431
  this.context.push({ role: "system", content: `Tool ${name10} result (authoritative):
696178
696432
  ${toolOutput}` });
696433
+ this.trimContext();
696179
696434
  }
696180
696435
  if (!this.active) return;
696181
696436
  if (this.heuristicsEnabled && this.toolRelay && /\b(can't|cannot)\b/i.test(response) && this.toolCatalogNote) {
@@ -696183,18 +696438,25 @@ ${toolOutput}` });
696183
696438
  response = await this.streamOllamaInference(this.abortController.signal);
696184
696439
  }
696185
696440
  if (response.trim()) {
696186
- const finalSpoken = stripToolJsonLines(response.trim());
696441
+ const reply = extractVoiceModelReply(stripToolJsonLines(response.trim()));
696442
+ const finalSpoken = reply.text;
696443
+ if (!finalSpoken || reply.action === "silent") {
696444
+ return;
696445
+ }
696446
+ if (typeof this.voice.waitUntilIdle === "function") {
696447
+ try {
696448
+ await this.voice.waitUntilIdle();
696449
+ } catch {
696450
+ }
696451
+ }
696187
696452
  this.context.push({ role: "assistant", content: finalSpoken });
696453
+ this.trimContext();
696188
696454
  this.setState("SPEAKING");
696189
696455
  this.onAgentSpeech(finalSpoken);
696190
- try {
696191
- this.listen.pause();
696192
- } catch {
696193
- }
696456
+ this.lastAgentSpeech = { text: finalSpoken, startedAt: Date.now() };
696194
696457
  this.voice.speak(finalSpoken);
696195
696458
  this.voiceTranscript.push({ role: "assistant", content: finalSpoken, ts: Date.now() });
696196
- this.voiceTranscript.push({ role: "assistant", content: response.trim(), ts: Date.now() });
696197
- if (this.runner) {
696459
+ if (this.runner && this.turnCount % SUMMARY_INJECTION_INTERVAL === 0) {
696198
696460
  this.injectSummary();
696199
696461
  }
696200
696462
  if (typeof this.voice.waitUntilIdle === "function") {
@@ -696203,9 +696465,12 @@ ${toolOutput}` });
696203
696465
  } catch {
696204
696466
  }
696205
696467
  } else {
696206
- const estimatedMs = Math.max(1500, response.length / 5 * (6e4 / 150));
696468
+ const estimatedMs = Math.max(1500, finalSpoken.length / 5 * (6e4 / 150));
696207
696469
  await new Promise((r2) => setTimeout(r2, estimatedMs));
696208
696470
  }
696471
+ if (reply.action === "hangup") {
696472
+ this.active = false;
696473
+ }
696209
696474
  }
696210
696475
  } catch (err) {
696211
696476
  if (!this.active) return;
@@ -696217,14 +696482,44 @@ ${toolOutput}` });
696217
696482
  this.abortController = null;
696218
696483
  }
696219
696484
  if (this.active) {
696220
- try {
696221
- await this.listen.resume();
696222
- } catch {
696223
- }
696485
+ await this.resumeListenAfterResponse();
696224
696486
  this.setState("LISTENING");
696225
696487
  if (this.verbose) this.onStatus("LISTENING...");
696226
696488
  }
696227
696489
  }
696490
+ latestUserText() {
696491
+ return [...this.context].reverse().find((m2) => m2.role === "user")?.content || "";
696492
+ }
696493
+ pauseListenForResponse() {
696494
+ if (this.listenPausedForResponse) return;
696495
+ try {
696496
+ this.listen.pause();
696497
+ } catch {
696498
+ }
696499
+ this.listenPausedForResponse = true;
696500
+ }
696501
+ async resumeListenAfterResponse() {
696502
+ if (!this.listenPausedForResponse) return;
696503
+ try {
696504
+ await this.listen.resume();
696505
+ } catch {
696506
+ }
696507
+ this.listenPausedForResponse = false;
696508
+ }
696509
+ classifyAgentEcho(text2) {
696510
+ const agentText = this.lastAgentSpeech?.text || "";
696511
+ const elapsedMs2 = this.lastAgentSpeech ? Date.now() - this.lastAgentSpeech.startedAt : Number.POSITIVE_INFINITY;
696512
+ return isLikelyAgentSpeechEcho({ heardText: text2, agentText, elapsedMs: elapsedMs2 });
696513
+ }
696514
+ dropTransientSystemTurns() {
696515
+ this.context = this.context.filter((turn) => !isContextSnapshotTurn(turn));
696516
+ }
696517
+ trimContext() {
696518
+ this.context = buildRealtimeVoiceMessages(this.context, MAX_CONTEXT_TURNS + 2);
696519
+ }
696520
+ buildRealtimeMessagesForRequest() {
696521
+ return buildRealtimeVoiceMessages(this.context, MAX_REALTIME_MODEL_MESSAGES);
696522
+ }
696228
696523
  /**
696229
696524
  * Stream inference. Tries native Ollama /api/chat first (supports think:false
696230
696525
  * for reasoning models), falls back to OpenAI-compat /v1/chat/completions.
@@ -696236,11 +696531,11 @@ ${toolOutput}` });
696236
696531
  try {
696237
696532
  const nativeBody = JSON.stringify({
696238
696533
  model: this.model,
696239
- messages: this.context,
696534
+ messages: this.buildRealtimeMessagesForRequest(),
696240
696535
  stream: true,
696241
696536
  think: false,
696242
696537
  // Disable reasoning — voice chat needs fast, direct responses
696243
- options: { temperature: 0.7, num_predict: 256 }
696538
+ options: { temperature: 0.5, num_predict: 192 }
696244
696539
  });
696245
696540
  const res2 = await fetch(`${baseUrl2}/api/chat`, {
696246
696541
  method: "POST",
@@ -696257,10 +696552,10 @@ ${toolOutput}` });
696257
696552
  }
696258
696553
  const openaiBody = JSON.stringify({
696259
696554
  model: this.model,
696260
- messages: this.context,
696555
+ messages: this.buildRealtimeMessagesForRequest(),
696261
696556
  stream: true,
696262
- temperature: 0.7,
696263
- max_tokens: 1024
696557
+ temperature: 0.5,
696558
+ max_tokens: 384
696264
696559
  });
696265
696560
  const endpoint = baseUrl2.includes("/v1") ? `${baseUrl2}/chat/completions` : `${baseUrl2}/v1/chat/completions`;
696266
696561
  const res = await fetch(endpoint, { method: "POST", headers, body: openaiBody, signal });
@@ -700370,7 +700665,7 @@ __export(graphical_sudo_exports, {
700370
700665
  import { spawn as spawn35 } from "node:child_process";
700371
700666
  import { existsSync as existsSync161, mkdirSync as mkdirSync102, writeFileSync as writeFileSync87, chmodSync as chmodSync6 } from "node:fs";
700372
700667
  import { join as join174 } from "node:path";
700373
- import { tmpdir as tmpdir23 } from "node:os";
700668
+ import { tmpdir as tmpdir24 } from "node:os";
700374
700669
  function detectSudoHelper() {
700375
700670
  if (process.platform === "win32") return null;
700376
700671
  if (process.platform === "darwin") return "osascript";
@@ -700391,7 +700686,7 @@ function which2(cmd) {
700391
700686
  return null;
700392
700687
  }
700393
700688
  function ensureAskpassShim(helper, description) {
700394
- const shimDir = join174(tmpdir23(), "omnius-askpass");
700689
+ const shimDir = join174(tmpdir24(), "omnius-askpass");
700395
700690
  mkdirSync102(shimDir, { recursive: true });
700396
700691
  const shim = join174(shimDir, `${helper}.sh`);
700397
700692
  let body;
@@ -722734,11 +723029,11 @@ async function handleRequest(req3, res, ollamaUrl, verbose, runtimeDefaults = {}
722734
723029
  });
722735
723030
  return;
722736
723031
  }
722737
- const { tmpdir: tmpdir25 } = await import("node:os");
723032
+ const { tmpdir: tmpdir26 } = await import("node:os");
722738
723033
  const { writeFileSync: writeFileSync95, unlinkSync: unlinkSync37 } = await import("node:fs");
722739
723034
  const { join: pjoin } = await import("node:path");
722740
723035
  const tmpPath = pjoin(
722741
- tmpdir25(),
723036
+ tmpdir26(),
722742
723037
  `omnius-clone-upload-${Date.now()}-${safeName3}`
722743
723038
  );
722744
723039
  writeFileSync95(tmpPath, buf);
@@ -734213,6 +734508,9 @@ The user pasted a clipboard image saved at ${relPath}. Use the OCR, vision analy
734213
734508
  voiceIsEnabled() {
734214
734509
  return voiceEngine.enabled;
734215
734510
  },
734511
+ voiceIsSpeaking() {
734512
+ return voiceEngine.isSpeaking();
734513
+ },
734216
734514
  voiceGetModel() {
734217
734515
  return voiceEngine.modelId ?? "unknown";
734218
734516
  },
@@ -735092,6 +735390,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
735092
735390
  liveDynamicBlockHost: {
735093
735391
  registerDynamicBlock: (id2, render2) => statusBar.registerDynamicBlock(id2, render2),
735094
735392
  appendDynamicBlock: (id2) => statusBar.appendDynamicBlock(id2),
735393
+ registerDynamicBlockClickHandler: (id2, handler) => statusBar.registerDynamicBlockClickHandler(id2, handler),
735095
735394
  refreshDynamicBlocks: () => statusBar.refreshDynamicBlocks()
735096
735395
  },
735097
735396
  // Voice call session — standalone cloudflared tunnel for /call
@@ -738774,7 +739073,7 @@ __export(eval_exports, {
738774
739073
  evalCommand: () => evalCommand,
738775
739074
  expectedStatusesForEvalTask: () => expectedStatusesForEvalTask
738776
739075
  });
738777
- import { tmpdir as tmpdir24 } from "node:os";
739076
+ import { tmpdir as tmpdir25 } from "node:os";
738778
739077
  import { mkdirSync as mkdirSync110, writeFileSync as writeFileSync94 } from "node:fs";
738779
739078
  import { join as join184 } from "node:path";
738780
739079
  function expectedStatusesForEvalTask(task, live) {
@@ -738918,7 +739217,7 @@ async function evalCommand(opts, config) {
738918
739217
  process.exit(failed > 0 ? 1 : 0);
738919
739218
  }
738920
739219
  function createTempEvalRepo() {
738921
- const dir = join184(tmpdir24(), `omnius-eval-${Date.now()}`);
739220
+ const dir = join184(tmpdir25(), `omnius-eval-${Date.now()}`);
738922
739221
  mkdirSync110(dir, { recursive: true });
738923
739222
  mkdirSync110(join184(dir, "src"), { recursive: true });
738924
739223
  mkdirSync110(join184(dir, "tests"), { recursive: true });