omnius 1.0.438 → 1.0.440

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
@@ -603492,8 +603492,8 @@ function purgeVenvTorch(pip) {
603492
603492
  );
603493
603493
  return `${purge.stdout || ""}${purge.stderr || ""}`;
603494
603494
  }
603495
- function jetsonTorchInstallSource() {
603496
- const wheel = discoverJetsonTorchWheel();
603495
+ function jetsonTorchInstallSource(py = getVenvPython()) {
603496
+ const wheel = discoverJetsonTorchWheel(py);
603497
603497
  if (wheel) {
603498
603498
  return {
603499
603499
  source: wheel,
@@ -603526,11 +603526,16 @@ function torchCudaProbe(py = getVenvPython()) {
603526
603526
  { encoding: "utf8", timeout: 3e4 }
603527
603527
  );
603528
603528
  const log22 = `${probe.stdout || ""}${probe.stderr || ""}`.trim();
603529
- return { ok: probe.status === 0, log: log22 };
603529
+ try {
603530
+ return { ok: probe.status === 0, log: log22, json: JSON.parse(probe.stdout || "{}") };
603531
+ } catch {
603532
+ return { ok: probe.status === 0, log: log22 };
603533
+ }
603530
603534
  }
603531
603535
  function ensureEmbedDeps() {
603532
603536
  const venv = getVenvDir();
603533
603537
  let log22 = "";
603538
+ const diagnostics = [];
603534
603539
  try {
603535
603540
  mkdirSync67(venv, { recursive: true });
603536
603541
  } catch {
@@ -603542,6 +603547,7 @@ function ensureEmbedDeps() {
603542
603547
  }
603543
603548
  if (isJetsonHost2() && cudaHardwarePresent() && !asrCpuAllowed()) {
603544
603549
  enableVenvSystemSitePackages(venv);
603550
+ diagnostics.push(`Jetson ASR CUDA repair: python=${getVenvPython()} abi=${pythonAbiTag()} cuda=${detectCudaToolkitVersion() || "unknown"} jp_dirs=${jetsonPytorchDirs().join(",")}`);
603545
603551
  }
603546
603552
  if (venvExisted && !(process.env["OMNIUS_INSTALL_FULL_EMBED_DEPS"] === "1")) {
603547
603553
  const check = spawnSync8(
@@ -603557,6 +603563,7 @@ function ensureEmbedDeps() {
603557
603563
  if (cudaProbe.ok) {
603558
603564
  return { ok: true, log: `embed deps already satisfied; torch CUDA OK ${cudaProbe.log}` };
603559
603565
  }
603566
+ diagnostics.push(`Initial venv torch CUDA failed: ${cudaProbe.log}`);
603560
603567
  log22 += `embed deps import OK but torch CUDA probe failed; repairing ASR torch. ${cudaProbe.log}
603561
603568
  `;
603562
603569
  }
@@ -603568,8 +603575,10 @@ function ensureEmbedDeps() {
603568
603575
  if (cudaHardwarePresent() && !asrCpuAllowed()) {
603569
603576
  const before = torchCudaProbe();
603570
603577
  torchOk = before.ok;
603578
+ if (!torchOk) diagnostics.push(`Pre-repair torch probe: ${before.log}`);
603571
603579
  if (!torchOk && isJetsonHost2()) {
603572
603580
  const systemTorch = torchCudaProbe("python3");
603581
+ diagnostics.push(`System python torch probe: ${systemTorch.log || "unavailable"}`);
603573
603582
  if (systemTorch.ok) {
603574
603583
  log22 += `Venv torch CUDA probe failed, but system Jetson torch CUDA works; enabling system site packages and removing venv-local torch.
603575
603584
  venv=${before.log}
@@ -603597,19 +603606,25 @@ system=${systemTorch.log}
603597
603606
  );
603598
603607
  log22 += torchDeps.stdout + torchDeps.stderr;
603599
603608
  const torchSource = jetsonTorchInstallSource();
603609
+ diagnostics.push(`Jetson torch install source: ${torchSource.source || "not found"}`);
603600
603610
  log22 += `Torch CUDA probe failed on Jetson; installing JetPack-compatible PyTorch from ${torchSource.source}
603601
603611
  ${before.log}
603602
603612
  `;
603603
- const torchInstall = spawnSync8(
603604
- pip,
603605
- torchSource.args,
603606
- { encoding: "utf8", timeout: 9e5 }
603607
- );
603608
- log22 += torchInstall.stdout + torchInstall.stderr;
603609
- const after = torchCudaProbe();
603610
- torchOk = after.ok;
603611
- log22 += `Torch CUDA probe after Jetson install: ${after.log}
603613
+ if (!torchSource.source) {
603614
+ torchOk = false;
603615
+ } else {
603616
+ const torchInstall = spawnSync8(
603617
+ pip,
603618
+ torchSource.args,
603619
+ { encoding: "utf8", timeout: 9e5 }
603620
+ );
603621
+ log22 += torchInstall.stdout + torchInstall.stderr;
603622
+ const after = torchCudaProbe();
603623
+ torchOk = after.ok;
603624
+ diagnostics.push(`Post Jetson torch install probe: ${after.log}`);
603625
+ log22 += `Torch CUDA probe after Jetson install: ${after.log}
603612
603626
  `;
603627
+ }
603613
603628
  } else if (!torchOk) {
603614
603629
  log22 += `Torch CUDA probe failed on CUDA host; not allowing ASR CPU fallback. ${before.log}
603615
603630
  `;
@@ -603649,20 +603664,27 @@ ${before.log}
603649
603664
  if (!torchOk) {
603650
603665
  log22 += `Torch CUDA probe still failed after ASR dependency install. ${finalTorch.log}
603651
603666
  `;
603667
+ diagnostics.push(`Post ASR dependency torch probe: ${finalTorch.log}`);
603652
603668
  if (isJetsonHost2()) {
603653
603669
  log22 += "Retrying Jetson torch repair after ASR dependency install to remove any incompatible torch resolver side effects.\n";
603654
603670
  log22 += purgeVenvTorch(pip);
603655
603671
  const torchSource = jetsonTorchInstallSource();
603656
- const retry = spawnSync8(
603657
- pip,
603658
- torchSource.args,
603659
- { encoding: "utf8", timeout: 9e5 }
603660
- );
603661
- log22 += retry.stdout + retry.stderr;
603662
- const repaired = torchCudaProbe();
603663
- torchOk = repaired.ok;
603664
- log22 += `Torch CUDA probe after final Jetson repair from ${torchSource.source}: ${repaired.log}
603672
+ diagnostics.push(`Final Jetson torch repair source: ${torchSource.source || "not found"}`);
603673
+ if (!torchSource.source) {
603674
+ torchOk = false;
603675
+ } else {
603676
+ const retry = spawnSync8(
603677
+ pip,
603678
+ torchSource.args,
603679
+ { encoding: "utf8", timeout: 9e5 }
603680
+ );
603681
+ log22 += retry.stdout + retry.stderr;
603682
+ const repaired = torchCudaProbe();
603683
+ torchOk = repaired.ok;
603684
+ diagnostics.push(`Final repaired torch probe: ${repaired.log}`);
603685
+ log22 += `Torch CUDA probe after final Jetson repair from ${torchSource.source}: ${repaired.log}
603665
603686
  `;
603687
+ }
603666
603688
  }
603667
603689
  }
603668
603690
  }
@@ -603682,6 +603704,21 @@ ${before.log}
603682
603704
  fullOk = full.status === 0;
603683
603705
  }
603684
603706
  const ok3 = asrDepsOk && up.status === 0 && fullOk && torchOk;
603707
+ if (!ok3) {
603708
+ const compact4 = [
603709
+ "ASR dependency setup failed.",
603710
+ ...diagnostics,
603711
+ `pip_base_ok=${up.status === 0}`,
603712
+ `asr_deps_ok=${asrDepsOk}`,
603713
+ `full_embed_deps_ok=${fullOk}`,
603714
+ `torch_cuda_ok=${torchOk}`,
603715
+ "Set OMNIUS_JETSON_TORCH_WHEEL=/path/or/url/to/torch-*-linux_aarch64.whl to override auto-discovery."
603716
+ ].join("\n");
603717
+ return { ok: ok3, log: `${compact4}
603718
+
603719
+ --- raw pip tail ---
603720
+ ${log22.slice(-2e3)}` };
603721
+ }
603685
603722
  return { ok: ok3, log: log22 };
603686
603723
  }
603687
603724
  function runEmbedImage(input) {
@@ -603790,6 +603827,56 @@ function updateListenLiveState(patch) {
603790
603827
  function getListenLiveState() {
603791
603828
  return listenLiveState;
603792
603829
  }
603830
+ function clamp0110(value2) {
603831
+ if (!Number.isFinite(value2)) return 0;
603832
+ return Math.max(0, Math.min(1, value2));
603833
+ }
603834
+ function micColor(text2, level) {
603835
+ const idx = Math.round(clamp0110(level) * (MIC_GRADIENT_256.length - 1));
603836
+ const code8 = MIC_GRADIENT_256[idx] ?? 21;
603837
+ return `\x1B[38;5;${code8}m${text2}\x1B[0m`;
603838
+ }
603839
+ function micLevelChar(level) {
603840
+ const idx = Math.round(clamp0110(level) * (MIC_LEVEL_CHARS.length - 1));
603841
+ return MIC_LEVEL_CHARS[idx] ?? "▁";
603842
+ }
603843
+ function micSpectrumChar(level) {
603844
+ const idx = Math.round(clamp0110(level) * (MIC_SPECTRUM_CHARS.length - 1));
603845
+ return MIC_SPECTRUM_CHARS[idx] ?? " ";
603846
+ }
603847
+ function renderMicWaveform(bucketSquares, bucketCounts, peak) {
603848
+ return bucketSquares.map((sum, idx) => {
603849
+ const count = Math.max(1, bucketCounts[idx] ?? 1);
603850
+ const level = Math.sqrt(sum / count);
603851
+ const normalized = clamp0110(level / Math.max(0.02, peak || 0.02));
603852
+ return micColor(micLevelChar(normalized), normalized);
603853
+ }).join("");
603854
+ }
603855
+ function renderMicSpectrum(samples) {
603856
+ if (samples.length < 16) return "";
603857
+ const n2 = Math.min(256, samples.length);
603858
+ const src2 = samples.slice(0, n2);
603859
+ const bands = [];
603860
+ const maxK = Math.max(2, Math.floor(n2 / 2) - 1);
603861
+ for (let band = 0; band < MIC_SPECTRUM_BANDS; band++) {
603862
+ const t2 = MIC_SPECTRUM_BANDS <= 1 ? 0 : band / (MIC_SPECTRUM_BANDS - 1);
603863
+ const k = Math.max(1, Math.min(maxK, Math.round(1 + (Math.exp(t2 * Math.log(maxK)) - 1))));
603864
+ let re = 0;
603865
+ let im = 0;
603866
+ for (let i2 = 0; i2 < n2; i2++) {
603867
+ const w = 0.5 - 0.5 * Math.cos(2 * Math.PI * i2 / Math.max(1, n2 - 1));
603868
+ const angle = 2 * Math.PI * k * i2 / n2;
603869
+ const sample = (src2[i2] ?? 0) * w;
603870
+ re += sample * Math.cos(angle);
603871
+ im -= sample * Math.sin(angle);
603872
+ }
603873
+ const mag = Math.sqrt(re * re + im * im) / n2;
603874
+ const db = 20 * Math.log10(Math.max(1e-7, mag));
603875
+ const normalized = clamp0110((db + 75) / 75);
603876
+ bands.push(micColor(micSpectrumChar(normalized), normalized));
603877
+ }
603878
+ return bands.join("");
603879
+ }
603793
603880
  async function resolveMicSource() {
603794
603881
  const configured = String(process.env["OMNIUS_MIC_DEVICE"] ?? "").trim();
603795
603882
  if (configured.startsWith("hw:") || configured.startsWith("plughw:")) {
@@ -604228,7 +604315,7 @@ function getListenEngine(config) {
604228
604315
  }
604229
604316
  return _engine;
604230
604317
  }
604231
- var MANAGED_TRANSCRIBE_CLI_DIR2, AUDIO_EXTENSIONS, VIDEO_EXTENSIONS2, listenLiveState, lastHardwareVadAt, WhisperFallbackTranscriber, ListenEngine, _bgInstallPromise, _managedTranscribeCliInstallPromise, _engine;
604318
+ var MANAGED_TRANSCRIBE_CLI_DIR2, AUDIO_EXTENSIONS, VIDEO_EXTENSIONS2, listenLiveState, lastHardwareVadAt, MIC_WAVEFORM_BINS, MIC_SPECTRUM_BANDS, MIC_WATERFALL_ROWS, MIC_LEVEL_CHARS, MIC_SPECTRUM_CHARS, MIC_GRADIENT_256, WhisperFallbackTranscriber, ListenEngine, _bgInstallPromise, _managedTranscribeCliInstallPromise, _engine;
604232
604319
  var init_listen = __esm({
604233
604320
  "packages/cli/src/tui/listen.ts"() {
604234
604321
  "use strict";
@@ -604265,6 +604352,9 @@ var init_listen = __esm({
604265
604352
  micLevelDb: null,
604266
604353
  noiseFloorDb: null,
604267
604354
  speechActive: false,
604355
+ waveform: "",
604356
+ spectrum: "",
604357
+ waterfall: [],
604268
604358
  doa: null,
604269
604359
  lastTranscript: "",
604270
604360
  lastTranscriptAt: 0,
@@ -604274,6 +604364,33 @@ var init_listen = __esm({
604274
604364
  updatedAt: 0
604275
604365
  };
604276
604366
  lastHardwareVadAt = 0;
604367
+ MIC_WAVEFORM_BINS = 56;
604368
+ MIC_SPECTRUM_BANDS = 28;
604369
+ MIC_WATERFALL_ROWS = 8;
604370
+ MIC_LEVEL_CHARS = "▁▂▃▄▅▆▇█";
604371
+ MIC_SPECTRUM_CHARS = " .:-=+*#%@";
604372
+ MIC_GRADIENT_256 = [
604373
+ 21,
604374
+ 27,
604375
+ 33,
604376
+ 39,
604377
+ 45,
604378
+ 51,
604379
+ 50,
604380
+ 49,
604381
+ 48,
604382
+ 47,
604383
+ 82,
604384
+ 118,
604385
+ 154,
604386
+ 190,
604387
+ 226,
604388
+ 220,
604389
+ 214,
604390
+ 208,
604391
+ 202,
604392
+ 196
604393
+ ];
604277
604394
  WhisperFallbackTranscriber = class extends EventEmitter6 {
604278
604395
  constructor(model, scriptPath2) {
604279
604396
  super();
@@ -604285,6 +604402,8 @@ var init_listen = __esm({
604285
604402
  process = null;
604286
604403
  _ready = false;
604287
604404
  registeredBrokerName = null;
604405
+ lastVadSpeech = null;
604406
+ lastVisibleVadAt = 0;
604288
604407
  get ready() {
604289
604408
  return this._ready;
604290
604409
  }
@@ -604298,7 +604417,7 @@ var init_listen = __esm({
604298
604417
  const res = ensure();
604299
604418
  if (res?.log) this.emit("status", res.log.slice(-500));
604300
604419
  if (res && res.ok === false) {
604301
- throw new Error(`ASR Python dependency setup failed: ${String(res.log ?? "").slice(-1200)}`);
604420
+ throw new Error(String(res.log ?? "ASR Python dependency setup failed").slice(0, 2400));
604302
604421
  }
604303
604422
  }
604304
604423
  if (typeof getPy === "function") {
@@ -604318,7 +604437,7 @@ var init_listen = __esm({
604318
604437
  const consensusEnv = String(process.env["OMNIUS_ASR_CONSENSUS"] ?? "").trim().toLowerCase();
604319
604438
  const consensusModel = consensusEnv === "0" || consensusEnv === "off" || consensusEnv === "false" ? "off" : consensusEnv || defaultConsensusModel(this.model);
604320
604439
  const consensusThreshold = String(process.env["OMNIUS_ASR_CONSENSUS_THRESHOLD"] ?? "0.45");
604321
- const silenceMs = String(process.env["OMNIUS_ASR_SILENCE_MS"] ?? "800");
604440
+ const silenceMs = String(process.env["OMNIUS_ASR_SILENCE_MS"] ?? "3000");
604322
604441
  this.process = spawn29(
604323
604442
  pyPath,
604324
604443
  [
@@ -604386,9 +604505,15 @@ var init_listen = __esm({
604386
604505
  case "vad": {
604387
604506
  lastHardwareVadAt = Date.now();
604388
604507
  const doa = Number(evt.doa);
604389
- this.emit("status", `Whisper VAD: ${evt.speech === true ? "speech" : "silence"}`);
604508
+ const speech = evt.speech === true;
604509
+ const now2 = Date.now();
604510
+ if (process.env["OMNIUS_ASR_VERBOSE_VAD"] === "1" && (speech !== this.lastVadSpeech || now2 - this.lastVisibleVadAt > 1e4)) {
604511
+ this.emit("status", `Whisper VAD: ${speech ? "speech" : "silence"}`);
604512
+ this.lastVisibleVadAt = now2;
604513
+ }
604514
+ this.lastVadSpeech = speech;
604390
604515
  updateListenLiveState({
604391
- speechActive: evt.speech === true,
604516
+ speechActive: speech,
604392
604517
  doa: Number.isFinite(doa) ? doa : null
604393
604518
  });
604394
604519
  break;
@@ -604557,6 +604682,7 @@ var init_listen = __esm({
604557
604682
  micNoiseFloorDb = null;
604558
604683
  micSpeechActive = false;
604559
604684
  lastLevelEmit = 0;
604685
+ micWaterfall = [];
604560
604686
  /**
604561
604687
  * Spawn the mic capture process and wire PCM piping + level metering.
604562
604688
  * Shared by start() and resume() so both paths meter identically.
@@ -604596,9 +604722,20 @@ var init_listen = __esm({
604596
604722
  let sumSquares = 0;
604597
604723
  const stride = samples > 4096 ? 4 : 1;
604598
604724
  let counted = 0;
604725
+ const bucketSquares = new Array(MIC_WAVEFORM_BINS).fill(0);
604726
+ const bucketCounts = new Array(MIC_WAVEFORM_BINS).fill(0);
604727
+ const spectrumSamples = [];
604728
+ const spectrumDecimate = Math.max(1, Math.floor(samples / 256));
604599
604729
  for (let i2 = 0; i2 + 1 < chunk.length; i2 += 2 * stride) {
604600
604730
  const v = chunk.readInt16LE(i2) / 32768;
604601
604731
  sumSquares += v * v;
604732
+ const sampleIndex = Math.floor(i2 / 2);
604733
+ const bucket = Math.min(MIC_WAVEFORM_BINS - 1, Math.floor(sampleIndex / Math.max(1, samples) * MIC_WAVEFORM_BINS));
604734
+ bucketSquares[bucket] += v * v;
604735
+ bucketCounts[bucket] += 1;
604736
+ if (sampleIndex % spectrumDecimate === 0 && spectrumSamples.length < 256) {
604737
+ spectrumSamples.push(v);
604738
+ }
604602
604739
  counted++;
604603
604740
  }
604604
604741
  if (counted === 0) return;
@@ -604613,18 +604750,33 @@ var init_listen = __esm({
604613
604750
  const speech = hardwareFresh ? listenLiveState.speechActive : this.micLevelDb > Math.max(-58, floor + 6);
604614
604751
  const changed = speech !== this.micSpeechActive;
604615
604752
  this.micSpeechActive = speech;
604753
+ const peak = Math.sqrt(Math.max(...bucketSquares.map((sum, idx) => sum / Math.max(1, bucketCounts[idx] ?? 1)), 0));
604754
+ const waveform = renderMicWaveform(bucketSquares, bucketCounts, peak);
604755
+ const spectrum = renderMicSpectrum(spectrumSamples);
604756
+ if (spectrum) {
604757
+ this.micWaterfall.push(spectrum);
604758
+ if (this.micWaterfall.length > MIC_WATERFALL_ROWS) {
604759
+ this.micWaterfall = this.micWaterfall.slice(-MIC_WATERFALL_ROWS);
604760
+ }
604761
+ }
604616
604762
  const now2 = Date.now();
604617
604763
  if (changed || now2 - this.lastLevelEmit >= 300) {
604618
604764
  this.lastLevelEmit = now2;
604619
604765
  updateListenLiveState({
604620
604766
  micLevelDb: Number(this.micLevelDb.toFixed(1)),
604621
604767
  noiseFloorDb: Number(floor.toFixed(1)),
604622
- speechActive: speech
604768
+ speechActive: speech,
604769
+ waveform,
604770
+ spectrum,
604771
+ waterfall: [...this.micWaterfall]
604623
604772
  });
604624
604773
  this.emit("level", {
604625
604774
  levelDb: this.micLevelDb,
604626
604775
  noiseFloorDb: floor,
604627
- speechActive: speech
604776
+ speechActive: speech,
604777
+ waveform,
604778
+ spectrum,
604779
+ waterfall: [...this.micWaterfall]
604628
604780
  });
604629
604781
  }
604630
604782
  }
@@ -604684,12 +604836,16 @@ var init_listen = __esm({
604684
604836
  */
604685
604837
  async start() {
604686
604838
  if (this.active) return "Already listening.";
604839
+ this.micWaterfall = [];
604687
604840
  updateListenLiveState({
604688
604841
  phase: "bootstrapping",
604689
604842
  model: this.config.model,
604690
604843
  backend: "",
604691
604844
  lastStatus: "starting ASR backend...",
604692
- speechActive: false
604845
+ speechActive: false,
604846
+ waveform: "",
604847
+ spectrum: "",
604848
+ waterfall: []
604693
604849
  });
604694
604850
  const micCmd = await findMicCaptureCommand();
604695
604851
  if (!micCmd) {
@@ -604896,7 +605052,8 @@ transcribe-cli error: ${transcribeCliError}` : "";
604896
605052
  this.active = false;
604897
605053
  this.owners.clear();
604898
605054
  this.blinkState = false;
604899
- updateListenLiveState({ phase: "idle", speechActive: false, micLevelDb: null, lastStatus: "stopped" });
605055
+ this.micWaterfall = [];
605056
+ updateListenLiveState({ phase: "idle", speechActive: false, micLevelDb: null, waveform: "", spectrum: "", waterfall: [], lastStatus: "stopped" });
604900
605057
  if (this.blinkTimer) {
604901
605058
  clearInterval(this.blinkTimer);
604902
605059
  this.blinkTimer = null;
@@ -604957,7 +605114,7 @@ transcribe-cli error: ${transcribeCliError}` : "";
604957
605114
  }
604958
605115
  this.micProcess = null;
604959
605116
  }
604960
- updateListenLiveState({ phase: "paused", speechActive: false, lastStatus: "mic paused (TTS speaking)" });
605117
+ updateListenLiveState({ phase: "paused", speechActive: false, waveform: "", spectrum: "", lastStatus: "mic paused (TTS speaking)" });
604961
605118
  this.emit("paused");
604962
605119
  this.emit("recording", false);
604963
605120
  }
@@ -606981,6 +607138,7 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
606981
607138
  };
606982
607139
  const asrMicBits = asr.micLevelDb !== null ? ` mic=${asr.micLevelDb.toFixed(1)}dB${asr.speechActive ? " ●speech" : ""}${asr.doa !== null ? ` dir=${asr.doa}°` : ""}` : "";
606983
607140
  const asrLine = asr.phase !== "idle" ? `│ ├─ whisper: ${[asr.backend, asr.model].filter(Boolean).join(" ") || "starting"} — ${asrPhaseLabel[asr.phase] ?? asr.phase}${asrMicBits}` : "";
607141
+ const asrStateLine = asr.phase !== "idle" ? `│ ├─ state: phase=${asr.phase} backend=${asr.backend || "unknown"} model=${asr.model || "unknown"} vad=${asr.speechActive ? "speech" : "quiet"} floor=${asr.noiseFloorDb !== null ? `${asr.noiseFloorDb.toFixed(1)}dB` : "n/a"}` : "";
606984
607142
  const asrFinalAge = asr.lastTranscriptAt > 0 ? Math.max(0, Math.round((now2 - asr.lastTranscriptAt) / 1e3)) : null;
606985
607143
  const asrPartialAge = asr.lastPartialTranscriptAt > 0 ? Math.max(0, Math.round((now2 - asr.lastPartialTranscriptAt) / 1e3)) : null;
606986
607144
  const showPartial = asr.lastPartialTranscript && (!asr.lastTranscriptAt || asr.lastPartialTranscriptAt >= asr.lastTranscriptAt);
@@ -606990,14 +607148,23 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
606990
607148
  const audioBits = [
606991
607149
  `├─ input: ${audioSnapshot?.input || snapshot.config.selectedAudioInput || "none"}`,
606992
607150
  asrLine,
607151
+ asrStateLine,
606993
607152
  asrStatusLine,
606994
607153
  asrHeardLine,
607154
+ asr.waveform ? `│ ├─ waveform: ${asr.waveform}` : "",
607155
+ asr.spectrum ? `│ ├─ spectrum: ${asr.spectrum} ${dashboardDim("low→high freq")}` : "",
606995
607156
  audioSnapshot?.activity ? `│ ├─ activity: ${audioSnapshot.activity.waveform} rms=${audioSnapshot.activity.rmsDb.toFixed(1)}dB active=${Math.round(audioSnapshot.activity.activeRatio * 100)}%` : "",
606996
607157
  audioError ? `│ ├─ error: ${trimOneLine(audioError, 140)}` : "",
606997
607158
  transcript ? `│ ├─ asr${audioSnapshot?.asrBackend ? ` (${audioSnapshot.asrBackend})` : ""}: ${trimOneLine(transcript, 140)}` : "",
606998
607159
  audioSnapshot?.intake ? `│ ├─ intake: ${audioSnapshot.intake.intendedForAgent ? "for-agent" : "ambient"} conf=${audioSnapshot.intake.confidence.toFixed(2)} action=${audioSnapshot.intake.action}` : ""
606999
607160
  ].filter(Boolean);
607000
- for (const item of audioBits.slice(0, 9)) pushDashboardWrapped(lines, item, width);
607161
+ for (const item of audioBits.slice(0, 11)) pushDashboardWrapped(lines, item, width);
607162
+ if (asr.waterfall.length > 0) {
607163
+ lines.push(`│${truncateAnsiCell(` ├─ speech waterfall ${dashboardDim("blue quiet → red loud")}`, width - 2)}│`);
607164
+ for (const row of asr.waterfall.slice(-8)) {
607165
+ lines.push(`│${truncateAnsiCell(` │ ${row}`, width - 2)}│`);
607166
+ }
607167
+ }
607001
607168
  if (coloredSoundSummary) {
607002
607169
  lines.push(`│${truncateAnsiCell(` └─ ${coloredSoundSummary}`, width - 2)}│`);
607003
607170
  }
@@ -668510,7 +668677,7 @@ function normalizePersonName(name10) {
668510
668677
  function personKey(name10) {
668511
668678
  return `person:${normalizePersonName(name10)}`;
668512
668679
  }
668513
- function clamp0110(value2, fallback = 0) {
668680
+ function clamp0111(value2, fallback = 0) {
668514
668681
  if (typeof value2 !== "number" || !Number.isFinite(value2)) return fallback;
668515
668682
  return Math.max(0, Math.min(1, value2));
668516
668683
  }
@@ -668527,7 +668694,7 @@ function parseStructuredIdentifyResult(result) {
668527
668694
  const matches = faces.filter((face) => face["identified"] === true && typeof face["name"] === "string" && String(face["name"]).trim()).map((face) => ({
668528
668695
  name: String(face["name"]).trim(),
668529
668696
  personId: typeof face["person_id"] === "string" ? face["person_id"] : void 0,
668530
- confidence: clamp0110(face["confidence"], 0),
668697
+ confidence: clamp0111(face["confidence"], 0),
668531
668698
  margin: typeof face["margin"] === "number" ? face["margin"] : void 0,
668532
668699
  bbox: Array.isArray(face["bbox"]) ? face["bbox"].map((n2) => Number(n2)).filter(Number.isFinite) : void 0
668533
668700
  }));
@@ -668610,7 +668777,7 @@ function activePendingVisualIdentities(store2, scope, sessionId, limit = 3) {
668610
668777
  pendingId,
668611
668778
  name: name10,
668612
668779
  relation: String(meta["relation"] || "depicts"),
668613
- confidence: clamp0110(meta["confidence"], 0.92),
668780
+ confidence: clamp0111(meta["confidence"], 0.92),
668614
668781
  note: typeof meta["note"] === "string" ? meta["note"] : void 0,
668615
668782
  episodeId: ep.id,
668616
668783
  createdAt: ep.timestamp,
@@ -668719,7 +668886,7 @@ function stageVisualIdentityAssertion(options2) {
668719
668886
  target: "next_visual_media",
668720
668887
  name: name10,
668721
668888
  relation: options2.relation || "depicts",
668722
- confidence: clamp0110(options2.confidence, 0.92),
668889
+ confidence: clamp0111(options2.confidence, 0.92),
668723
668890
  note: options2.note,
668724
668891
  createdAt: Date.now(),
668725
668892
  expiresAt
@@ -668727,7 +668894,7 @@ function stageVisualIdentityAssertion(options2) {
668727
668894
  identityAssertions: [{
668728
668895
  name: name10,
668729
668896
  relation: options2.relation || "named_as",
668730
- confidence: clamp0110(options2.confidence, 0.92),
668897
+ confidence: clamp0111(options2.confidence, 0.92),
668731
668898
  assertedBy: options2.sender,
668732
668899
  note: options2.note || "Explicit user-provided identity staged for the next visual media."
668733
668900
  }],
@@ -668758,7 +668925,7 @@ function stageVisualIdentityAssertion(options2) {
668758
668925
  target: "next_visual_media",
668759
668926
  name: name10,
668760
668927
  relation: options2.relation || "depicts",
668761
- confidence: clamp0110(options2.confidence, 0.92),
668928
+ confidence: clamp0111(options2.confidence, 0.92),
668762
668929
  note: options2.note,
668763
668930
  createdAt: Date.now(),
668764
668931
  expiresAt
@@ -668767,7 +668934,7 @@ function stageVisualIdentityAssertion(options2) {
668767
668934
  identityAssertions: [{
668768
668935
  name: name10,
668769
668936
  relation: options2.relation || "named_as",
668770
- confidence: clamp0110(options2.confidence, 0.92),
668937
+ confidence: clamp0111(options2.confidence, 0.92),
668771
668938
  assertedBy: options2.sender,
668772
668939
  note: options2.note || "Explicit user-provided identity staged for the next visual media."
668773
668940
  }]
@@ -677563,7 +677730,7 @@ ${result.output}`,
677563
677730
  });
677564
677731
 
677565
677732
  // packages/cli/src/tui/stimulation.ts
677566
- function clamp0111(value2) {
677733
+ function clamp0112(value2) {
677567
677734
  return Math.max(0, Math.min(1, value2));
677568
677735
  }
677569
677736
  function cloneState(state) {
@@ -677619,7 +677786,7 @@ var init_stimulation = __esm({
677619
677786
  ...DEFAULT_STATE,
677620
677787
  ...state,
677621
677788
  phase: normalizePhase(state.phase) ?? DEFAULT_STATE.phase,
677622
- attention: clamp0111(Number.isFinite(state.attention) ? Number(state.attention) : DEFAULT_STATE.attention),
677789
+ attention: clamp0112(Number.isFinite(state.attention) ? Number(state.attention) : DEFAULT_STATE.attention),
677623
677790
  updatedAtMs: Number.isFinite(state.updatedAtMs) ? Number(state.updatedAtMs) : now2,
677624
677791
  lastStimulusAtMs: Number.isFinite(state.lastStimulusAtMs) ? Number(state.lastStimulusAtMs) : now2,
677625
677792
  messagesSinceAnalysis: Math.max(0, Math.floor(Number(state.messagesSinceAnalysis ?? 0))),
@@ -677660,11 +677827,11 @@ var init_stimulation = __esm({
677660
677827
  applyAgentDecision(channelId, decision2, nowMs = Date.now()) {
677661
677828
  const state = this.stateFor(channelId, nowMs);
677662
677829
  if (Number.isFinite(decision2.attentionScore)) {
677663
- state.attention = clamp0111(Number(decision2.attentionScore));
677830
+ state.attention = clamp0112(Number(decision2.attentionScore));
677664
677831
  } else if (Number.isFinite(decision2.attentionDelta)) {
677665
- state.attention = clamp0111(state.attention + Number(decision2.attentionDelta));
677832
+ state.attention = clamp0112(state.attention + Number(decision2.attentionDelta));
677666
677833
  } else {
677667
- state.attention = clamp0111(state.attention + (decision2.shouldReply ? 0.22 : -0.1));
677834
+ state.attention = clamp0112(state.attention + (decision2.shouldReply ? 0.22 : -0.1));
677668
677835
  }
677669
677836
  if (decision2.phase) {
677670
677837
  state.attention = Math.max(state.attention, PHASE_FLOORS[decision2.phase]);
@@ -677720,7 +677887,7 @@ var init_stimulation = __esm({
677720
677887
  });
677721
677888
 
677722
677889
  // packages/cli/src/tui/pid-controller.ts
677723
- function clamp0112(x) {
677890
+ function clamp0113(x) {
677724
677891
  if (!Number.isFinite(x)) return 0;
677725
677892
  if (x < 0) return 0;
677726
677893
  if (x > 1) return 1;
@@ -677784,7 +677951,7 @@ var init_pid_controller = __esm({
677784
677951
  const dt = st.lastSampleAt > 0 ? now2 - st.lastSampleAt : 1e3;
677785
677952
  const derivative = dt > 0 ? (error - st.lastError) / dt : 0;
677786
677953
  const u = st.config.kp * error + st.config.ki * st.integral + st.config.kd * derivative;
677787
- st.output = clamp0112(st.output + u);
677954
+ st.output = clamp0113(st.output + u);
677788
677955
  st.lastError = error;
677789
677956
  st.lastSampleAt = now2;
677790
677957
  st.samples += 1;
@@ -678313,7 +678480,7 @@ function buildReplyOpportunities(input, openQuestions) {
678313
678480
  function daydreamOpportunityId(input, trigger) {
678314
678481
  return createHash39("sha1").update(`${input.sessionKey}:${input.generatedAtMs}:${trigger}`).digest("hex").slice(0, 16);
678315
678482
  }
678316
- function clamp0113(value2) {
678483
+ function clamp0114(value2) {
678317
678484
  if (!Number.isFinite(value2)) return 0;
678318
678485
  return Math.max(0, Math.min(1, value2));
678319
678486
  }
@@ -678330,7 +678497,7 @@ function pushStimulationSignal(signals, signal, source, weight) {
678330
678497
  signals.push({
678331
678498
  signal: cleanSignal,
678332
678499
  source: cleanSource,
678333
- weight: clamp0113(weight)
678500
+ weight: clamp0114(weight)
678334
678501
  });
678335
678502
  }
678336
678503
  function buildMetaAnalysisSignals(input) {
@@ -678460,7 +678627,7 @@ function buildCuriosityThreads(input, openQuestions, stimulationSignals) {
678460
678627
  question: text2.endsWith("?") || text2.endsWith("?") ? text2 : `What should be learned or clarified from: ${text2 || entry.mediaSummary || "recent media"}?`,
678461
678628
  rationale: "Human curiosity, uncertainty, or multimodal content makes this a useful idle exploration target.",
678462
678629
  sourceMessages: messageId,
678463
- intensity: clamp0113(0.5 + replyBoost + mediaBoost + questionBoost)
678630
+ intensity: clamp0114(0.5 + replyBoost + mediaBoost + questionBoost)
678464
678631
  });
678465
678632
  }
678466
678633
  for (const question of openQuestions.slice(-4)) {
@@ -678483,7 +678650,7 @@ function buildCuriosityThreads(input, openQuestions, stimulationSignals) {
678483
678650
  question: `Is there a useful clarification or memory consolidation around ${strongest.source}?`,
678484
678651
  rationale: "Strongest stimulation signal can seed a low-intrusion reflection target.",
678485
678652
  sourceMessages: [],
678486
- intensity: clamp0113(strongest.weight * 0.72)
678653
+ intensity: clamp0114(strongest.weight * 0.72)
678487
678654
  });
678488
678655
  }
678489
678656
  return threads.sort((a2, b) => b.intensity - a2.intensity).slice(0, 8);
@@ -678563,7 +678730,7 @@ function buildOutreachPlans(input, curiosityThreads) {
678563
678730
  purpose: "Continue the public thread only when the live model judges that the group would benefit from a concise follow-up.",
678564
678731
  draftIntent: "Ask one concrete clarification, offer one useful synthesis, or stay silent if the room has moved on.",
678565
678732
  gate: "model_decision",
678566
- confidence: clamp0113(thread.intensity * 0.86)
678733
+ confidence: clamp0114(thread.intensity * 0.86)
678567
678734
  });
678568
678735
  const participant = participantForThread(input, thread);
678569
678736
  if (!participant) continue;
@@ -678575,7 +678742,7 @@ function buildOutreachPlans(input, curiosityThreads) {
678575
678742
  purpose: "Offer a one-to-one follow-up only if private contact is allowed and the issue is personal, unresolved, or better handled outside the group.",
678576
678743
  draftIntent: "Reference the public thread briefly, ask permission to continue privately, and do not reveal hidden meta-analysis.",
678577
678744
  gate: "admin_review",
678578
- confidence: clamp0113(thread.intensity * 0.58)
678745
+ confidence: clamp0114(thread.intensity * 0.58)
678579
678746
  });
678580
678747
  }
678581
678748
  return plans.slice(0, 8);
@@ -679907,7 +680074,7 @@ function numberOr(value2, fallback) {
679907
680074
  function isNumber(value2) {
679908
680075
  return typeof value2 === "number" && Number.isFinite(value2);
679909
680076
  }
679910
- function clamp0114(value2) {
680077
+ function clamp0115(value2) {
679911
680078
  return Math.max(0, Math.min(1, Number.isFinite(value2) ? value2 : 0));
679912
680079
  }
679913
680080
  function iso(ts) {
@@ -680091,8 +680258,8 @@ function normalizeRelationship(raw) {
680091
680258
  kind: value2.kind,
680092
680259
  fromKey: String(value2.fromKey),
680093
680260
  toKey: String(value2.toKey),
680094
- confidence: clamp0114(numberOr(value2.confidence, 0)),
680095
- weight: clamp0114(numberOr(value2.weight, 0)),
680261
+ confidence: clamp0115(numberOr(value2.confidence, 0)),
680262
+ weight: clamp0115(numberOr(value2.weight, 0)),
680096
680263
  firstSeenAt: numberOr(value2.firstSeenAt, Date.now()),
680097
680264
  lastSeenAt: numberOr(value2.lastSeenAt, Date.now()),
680098
680265
  evidenceMessageIds: Array.isArray(value2.evidenceMessageIds) ? value2.evidenceMessageIds.filter(isNumber).slice(-40) : [],
@@ -680111,7 +680278,7 @@ function normalizePreferences(raw) {
680111
680278
  if (!evidence || typeof evidence !== "object") continue;
680112
680279
  out[actorKey][key] = {
680113
680280
  value: Math.max(-1, Math.min(1, numberOr(evidence.value, 0))),
680114
- confidence: clamp0114(numberOr(evidence.confidence, 0)),
680281
+ confidence: clamp0115(numberOr(evidence.confidence, 0)),
680115
680282
  updatedAt: numberOr(evidence.updatedAt, Date.now()),
680116
680283
  evidenceMessageIds: Array.isArray(evidence.evidenceMessageIds) ? evidence.evidenceMessageIds.filter(isNumber).slice(-12) : [],
680117
680284
  note: compactOptional(evidence.note, 220)
@@ -680125,7 +680292,7 @@ function normalizePreferences(raw) {
680125
680292
  out[actorKey].replyMode = {
680126
680293
  mode,
680127
680294
  scope: normalizeReplyPreferenceScope(record["scope"]),
680128
- confidence: clamp0114(numberOr(record["confidence"], 0.8)),
680295
+ confidence: clamp0115(numberOr(record["confidence"], 0.8)),
680129
680296
  updatedAt: numberOr(record["updatedAt"], Date.now()),
680130
680297
  evidenceMessageIds: Array.isArray(record["evidenceMessageIds"]) ? record["evidenceMessageIds"].filter(isNumber).slice(-12) : [],
680131
680298
  note: compactOptional(record["note"], 220),
@@ -680215,7 +680382,7 @@ function normalizeOutcome2(raw) {
680215
680382
  replyToMessageId: typeof value2.replyToMessageId === "number" ? value2.replyToMessageId : void 0,
680216
680383
  route: value2.route === "action" ? "action" : "chat",
680217
680384
  shouldReply: value2.shouldReply === true,
680218
- confidence: clamp0114(numberOr(value2.confidence, 0)),
680385
+ confidence: clamp0115(numberOr(value2.confidence, 0)),
680219
680386
  reason: compact3(value2.reason || "", 280),
680220
680387
  source: compact3(value2.source || "unknown", 80),
680221
680388
  silentDisposition: compactOptional(value2.silentDisposition, 280),
@@ -680227,7 +680394,7 @@ function normalizeOutcome2(raw) {
680227
680394
  scenarioNote: compactOptional(value2.scenarioNote, 360),
680228
680395
  scenarioId: compactOptional(value2.scenarioId, 160),
680229
680396
  scenarioLabel: compactOptional(value2.scenarioLabel, 160),
680230
- scenarioConfidence: typeof value2.scenarioConfidence === "number" && Number.isFinite(value2.scenarioConfidence) ? clamp0114(value2.scenarioConfidence) : void 0,
680397
+ scenarioConfidence: typeof value2.scenarioConfidence === "number" && Number.isFinite(value2.scenarioConfidence) ? clamp0115(value2.scenarioConfidence) : void 0,
680231
680398
  scenarioObjective: compactOptional(value2.scenarioObjective, 360),
680232
680399
  scenarioStateLoop: compactOptional(value2.scenarioStateLoop, 360),
680233
680400
  salienceSignals: Array.isArray(value2.salienceSignals) ? value2.salienceSignals.map(String).slice(0, 16) : [],
@@ -680245,7 +680412,7 @@ function normalizeDaydreamOpportunity(raw) {
680245
680412
  artifactId: String(value2.artifactId || "unknown"),
680246
680413
  generatedAt: String(value2.generatedAt || (/* @__PURE__ */ new Date()).toISOString()),
680247
680414
  trigger: compact3(value2.trigger || "", 240),
680248
- confidence: clamp0114(numberOr(value2.confidence, 0)),
680415
+ confidence: clamp0115(numberOr(value2.confidence, 0)),
680249
680416
  lifecycle,
680250
680417
  firstSeenAt: numberOr(value2.firstSeenAt, Date.now()),
680251
680418
  updatedAt: numberOr(value2.updatedAt, Date.now()),
@@ -680302,7 +680469,7 @@ function commitTelegramSocialDecision(state, input) {
680302
680469
  replyToMessageId: input.replyToMessageId,
680303
680470
  route: input.route,
680304
680471
  shouldReply: input.shouldReply,
680305
- confidence: clamp0114(input.confidence),
680472
+ confidence: clamp0115(input.confidence),
680306
680473
  reason: compact3(input.reason, 280),
680307
680474
  source: compact3(input.source, 80),
680308
680475
  silentDisposition: compactOptional(input.silentDisposition, 280),
@@ -680314,7 +680481,7 @@ function commitTelegramSocialDecision(state, input) {
680314
680481
  scenarioNote: compactOptional(input.scenarioNote, 360),
680315
680482
  scenarioId: compactOptional(input.scenarioId, 160),
680316
680483
  scenarioLabel: compactOptional(input.scenarioLabel, 160),
680317
- scenarioConfidence: input.scenarioConfidence === void 0 ? void 0 : clamp0114(input.scenarioConfidence),
680484
+ scenarioConfidence: input.scenarioConfidence === void 0 ? void 0 : clamp0115(input.scenarioConfidence),
680318
680485
  scenarioObjective: compactOptional(input.scenarioObjective, 360),
680319
680486
  scenarioStateLoop: compactOptional(input.scenarioStateLoop, 360),
680320
680487
  salienceSignals: [...new Set((input.salienceSignals ?? []).map(String))].slice(0, 16),
@@ -680338,7 +680505,7 @@ function registerDaydreamOpportunities(state, opportunities, now2 = Date.now())
680338
680505
  artifactId: opportunity.artifactId || "unknown",
680339
680506
  generatedAt: opportunity.generatedAt || new Date(now2).toISOString(),
680340
680507
  trigger: compact3(opportunity.trigger, 240),
680341
- confidence: clamp0114(opportunity.confidence),
680508
+ confidence: clamp0115(opportunity.confidence),
680342
680509
  lifecycle: "proposed",
680343
680510
  firstSeenAt: now2,
680344
680511
  updatedAt: now2,
@@ -680348,7 +680515,7 @@ function registerDaydreamOpportunities(state, opportunities, now2 = Date.now())
680348
680515
  };
680349
680516
  if (existing) {
680350
680517
  item.trigger = compact3(opportunity.trigger, 240) || item.trigger;
680351
- item.confidence = clamp0114(opportunity.confidence);
680518
+ item.confidence = clamp0115(opportunity.confidence);
680352
680519
  item.updatedAt = now2;
680353
680520
  }
680354
680521
  state.daydreamOpportunities[id2] = item;
@@ -680375,7 +680542,7 @@ function setTelegramReplyModePreference(state, input) {
680375
680542
  const next = {
680376
680543
  mode: input.mode,
680377
680544
  scope: input.scope,
680378
- confidence: Math.max(existing?.confidence ?? 0, clamp0114(input.confidence ?? 0.84)),
680545
+ confidence: Math.max(existing?.confidence ?? 0, clamp0115(input.confidence ?? 0.84)),
680379
680546
  updatedAt: now2,
680380
680547
  evidenceMessageIds: appendUnique(existing?.evidenceMessageIds ?? [], input.messageId, 12),
680381
680548
  note: compactOptional(input.note, 220),
@@ -680555,8 +680722,8 @@ function upsertRelationship(state, kind, fromKey, toKey, messageId, confidence2,
680555
680722
  evidenceMessageIds: [],
680556
680723
  source
680557
680724
  };
680558
- edge.confidence = Math.max(edge.confidence, clamp0114(confidence2));
680559
- edge.weight = Math.min(1, edge.weight + 0.12 + clamp0114(confidence2) * 0.2);
680725
+ edge.confidence = Math.max(edge.confidence, clamp0115(confidence2));
680726
+ edge.weight = Math.min(1, edge.weight + 0.12 + clamp0115(confidence2) * 0.2);
680560
680727
  edge.lastSeenAt = now2;
680561
680728
  edge.evidenceMessageIds = appendUnique(edge.evidenceMessageIds, messageId, 40);
680562
680729
  edge.note = compactOptional(note, 260) || edge.note;
@@ -680598,7 +680765,7 @@ function setPreference(vector, key, value2, confidence2, messageId, now2, note)
680598
680765
  const existing = vector[key];
680599
680766
  vector[key] = {
680600
680767
  value: existing ? existing.value * 0.7 + value2 * 0.3 : value2,
680601
- confidence: Math.max(existing?.confidence ?? 0, clamp0114(confidence2)),
680768
+ confidence: Math.max(existing?.confidence ?? 0, clamp0115(confidence2)),
680602
680769
  updatedAt: now2,
680603
680770
  evidenceMessageIds: appendUnique(existing?.evidenceMessageIds ?? [], messageId, 12),
680604
680771
  note
@@ -680780,7 +680947,7 @@ async function queryVisionModel(modelName, imagePath, prompt = "Describe what yo
680780
680947
  return "";
680781
680948
  }
680782
680949
  }
680783
- function clamp0115(value2, fallback = 0) {
680950
+ function clamp0116(value2, fallback = 0) {
680784
680951
  if (typeof value2 !== "number" || !Number.isFinite(value2)) return fallback;
680785
680952
  return Math.max(0, Math.min(1, value2));
680786
680953
  }
@@ -680797,9 +680964,9 @@ function parseVisualMemoryRecognize(raw) {
680797
680964
  label: String(match.label).trim(),
680798
680965
  objectId: typeof match.object_id === "string" ? match.object_id : void 0,
680799
680966
  matchedAlias: typeof match.matched_alias === "string" ? match.matched_alias : void 0,
680800
- blendedScore: clamp0115(match.blended_score, 0),
680801
- imageSimilarity: typeof match.image_similarity === "number" ? clamp0115(match.image_similarity, 0) : void 0,
680802
- textSimilarity: typeof match.text_similarity === "number" ? clamp0115(match.text_similarity, 0) : void 0
680967
+ blendedScore: clamp0116(match.blended_score, 0),
680968
+ imageSimilarity: typeof match.image_similarity === "number" ? clamp0116(match.image_similarity, 0) : void 0,
680969
+ textSimilarity: typeof match.text_similarity === "number" ? clamp0116(match.text_similarity, 0) : void 0
680803
680970
  })).sort((a2, b) => b.blendedScore - a2.blendedScore).slice(0, 5);
680804
680971
  }
680805
680972
  function formatVisualFamiliarityContext(result) {
@@ -699750,7 +699917,7 @@ __export(voicechat_exports, {
699750
699917
  isLikelyAgentSpeechEcho: () => isLikelyAgentSpeechEcho
699751
699918
  });
699752
699919
  import { EventEmitter as EventEmitter12 } from "node:events";
699753
- function clamp0116(x) {
699920
+ function clamp0117(x) {
699754
699921
  return x < 0 ? 0 : x > 1 ? 1 : x;
699755
699922
  }
699756
699923
  function alnumRatio(s2) {
@@ -699803,9 +699970,9 @@ function computeSignalFromText(text2, confidence2) {
699803
699970
  score -= repeatingCharPenalty(t2) * 0.4;
699804
699971
  score -= gibberishTokenPenalty(t2) * 0.8;
699805
699972
  if (typeof confidence2 === "number" && !Number.isNaN(confidence2)) {
699806
- score = 0.7 * score + 0.3 * clamp0116(confidence2);
699973
+ score = 0.7 * score + 0.3 * clamp0117(confidence2);
699807
699974
  }
699808
- return clamp0116(score);
699975
+ return clamp0117(score);
699809
699976
  }
699810
699977
  function truncateForLog(s2, n2) {
699811
699978
  return s2.length <= n2 ? s2 : s2.slice(0, n2 - 1) + "…";
@@ -700297,7 +700464,7 @@ Rules:
700297
700464
  }
700298
700465
  }, MAX_SEGMENT_MS);
700299
700466
  }
700300
- const signalScore = typeof snr === "number" && !Number.isNaN(snr) ? clamp0116(snr) : computeSignalFromText(text2, confidence2);
700467
+ const signalScore = typeof snr === "number" && !Number.isNaN(snr) ? clamp0117(snr) : computeSignalFromText(text2, confidence2);
700301
700468
  this.lastSignalScore = signalScore;
700302
700469
  if (consensus !== void 0) {
700303
700470
  this.consensusMetadataSeen = true;
@@ -739376,7 +739543,16 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
739376
739543
  if (!available) {
739377
739544
  return "ASR runtime unavailable. Omnius attempted managed transcribe-cli and Whisper fallback setup; check ~/.omnius/runtimes/asr and ~/.omnius/venv.";
739378
739545
  }
739546
+ let lastRenderedTranscript = "";
739547
+ let lastRenderedTranscriptAt = 0;
739379
739548
  engine.on("transcript", (text2, isFinal) => {
739549
+ const normalizedTranscript = text2.trim();
739550
+ const now2 = Date.now();
739551
+ if (normalizedTranscript && normalizedTranscript === lastRenderedTranscript && now2 - lastRenderedTranscriptAt < 2500) {
739552
+ return;
739553
+ }
739554
+ lastRenderedTranscript = normalizedTranscript;
739555
+ lastRenderedTranscriptAt = now2;
739380
739556
  if (engine.currentMode === "confirm") {
739381
739557
  writeContent(
739382
739558
  () => renderInfo(`Heard: "${text2}" ${c3.dim("(press Enter to submit)")}`)
@@ -739801,7 +739977,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
739801
739977
  statusBar.setMicActivity(evt.speechActive, evt.levelDb);
739802
739978
  });
739803
739979
  listenEng.on("info", (msg) => {
739804
- if (/consensus rejected|loading whisper|whisper model loaded|Whisper VAD|no accepted speech text|very short utterance/i.test(msg)) {
739980
+ if (/consensus rejected|loading whisper|whisper model loaded/i.test(msg)) {
739805
739981
  writeContent(() => renderInfo(`[voicechat/asr] ${msg}`));
739806
739982
  }
739807
739983
  });
@@ -454,7 +454,7 @@ def main():
454
454
  help="Second whisper model run in parallel on finalized utterances; transcripts are only emitted when both models agree. 'off' disables.")
455
455
  parser.add_argument("--consensus-threshold", type=float, default=0.55,
456
456
  help="Token similarity (0-1) required between the two models' transcripts.")
457
- parser.add_argument("--silence-ms", type=float, default=900,
457
+ parser.add_argument("--silence-ms", type=float, default=3000,
458
458
  help="Sustained silence that finalizes an utterance.")
459
459
  parser.add_argument("--preroll-ms", type=float, default=600,
460
460
  help="Audio kept from before speech onset.")
@@ -577,7 +577,6 @@ def main():
577
577
  def transcribe_final(samples):
578
578
  nonlocal last_final_text
579
579
  if len(samples) < int(0.4 * SAMPLE_RATE):
580
- emit_status("Whisper ignored very short utterance.")
581
580
  return
582
581
  samples = amplify(samples)
583
582
  try:
@@ -585,7 +584,6 @@ def main():
585
584
 
586
585
  text = _segment_filtered_text(result)
587
586
  if not text:
588
- emit_status("Whisper final produced no accepted speech text.")
589
587
  return
590
588
  if text == last_final_text:
591
589
  return
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.438",
3
+ "version": "1.0.440",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.438",
9
+ "version": "1.0.440",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.438",
3
+ "version": "1.0.440",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",