omnius 1.0.426 → 1.0.427

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
@@ -603619,7 +603619,9 @@ var init_listen = __esm({
603619
603619
  case "transcript":
603620
603620
  this.emit("transcript", {
603621
603621
  text: evt.text,
603622
- isFinal: evt.isFinal ?? false
603622
+ isFinal: evt.isFinal ?? false,
603623
+ consensus: typeof evt.consensus === "boolean" ? evt.consensus : void 0,
603624
+ doa: typeof evt.doa === "number" ? evt.doa : void 0
603623
603625
  });
603624
603626
  break;
603625
603627
  case "consensus_rejected":
@@ -604016,7 +604018,7 @@ var init_listen = __esm({
604016
604018
  this.lastTranscriptTime = Date.now();
604017
604019
  this.pendingText = evt.text.trim();
604018
604020
  updateListenLiveState({ lastTranscript: this.pendingText, lastTranscriptAt: this.lastTranscriptTime });
604019
- this.emit("transcript", this.pendingText, evt.isFinal);
604021
+ this.emit("transcript", this.pendingText, evt.isFinal, { consensus: evt.consensus, doa: evt.doa });
604020
604022
  if (this.config.mode === "auto") this.resetSilenceTimer();
604021
604023
  });
604022
604024
  fallback.on("error", (err) => {
@@ -617229,6 +617231,25 @@ function rms(arr) {
617229
617231
  return Math.sqrt(sum / arr.length);
617230
617232
  }
617231
617233
 
617234
+ // Linear resampler: browsers frequently IGNORE the requested AudioContext
617235
+ // sample rate and run at the hardware rate (typically 48kHz). Sending those
617236
+ // frames as-if-16kHz made the audio unpack 3x slow/pitch-dropped server-side,
617237
+ // wrecking ASR. Always resample the actual context rate down to 16kHz.
617238
+ function resampleTo16k(input, srcRate) {
617239
+ if (srcRate === 16000) return input;
617240
+ const ratio = srcRate / 16000;
617241
+ const outLen = Math.floor(input.length / ratio);
617242
+ const out = new Float32Array(outLen);
617243
+ for (let i = 0; i < outLen; i++) {
617244
+ const pos = i * ratio;
617245
+ const i0 = Math.floor(pos);
617246
+ const i1 = Math.min(input.length - 1, i0 + 1);
617247
+ const frac = pos - i0;
617248
+ out[i] = input[i0] * (1 - frac) + input[i1] * frac;
617249
+ }
617250
+ return out;
617251
+ }
617252
+
617232
617253
  async function toggleMic() {
617233
617254
  if (micActive) { stopMic(); return; }
617234
617255
  try {
@@ -617237,17 +617258,19 @@ async function toggleMic() {
617237
617258
  audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true, noiseSuppression: true }
617238
617259
  });
617239
617260
  const source = micCtx.createMediaStreamSource(micStream);
617261
+ const actualRate = micCtx.sampleRate; // may be 48000 regardless of the request
617240
617262
  scriptProcessor = micCtx.createScriptProcessor(4096, 1, 1);
617241
617263
  scriptProcessor.onaudioprocess = (e) => {
617242
617264
  if (!micActive || !ws || ws.readyState !== 1) return;
617243
- const input = e.inputBuffer.getChannelData(0);
617265
+ const raw = e.inputBuffer.getChannelData(0);
617266
+ const input = resampleTo16k(raw, actualRate);
617244
617267
  const int16 = new Int16Array(input.length);
617245
617268
  for (let i = 0; i < input.length; i++) {
617246
617269
  const s = Math.max(-1, Math.min(1, input[i]));
617247
617270
  int16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
617248
617271
  }
617249
617272
  ws.send(int16.buffer);
617250
- micLevel = Math.min(1, rms(input) * 5);
617273
+ micLevel = Math.min(1, rms(raw) * 5);
617251
617274
  };
617252
617275
  source.connect(scriptProcessor);
617253
617276
  scriptProcessor.connect(micCtx.destination);
@@ -617256,7 +617279,8 @@ async function toggleMic() {
617256
617279
  micBtn.textContent = 'stop';
617257
617280
  micBtn.classList.add('active');
617258
617281
  hintEl.textContent = 'listening...';
617259
- ws.send(JSON.stringify({ type: 'mic_start', username: 'web-user' }));
617282
+ // Declare the post-resample rate (and the raw context rate for debugging)
617283
+ ws.send(JSON.stringify({ type: 'mic_start', username: 'web-user', sampleRate: 16000, contextRate: actualRate }));
617260
617284
  } catch (err) {
617261
617285
  hintEl.textContent = 'mic error: ' + err.message;
617262
617286
  }
@@ -617629,6 +617653,23 @@ connectPP();
617629
617653
  </body>
617630
617654
  </html>`;
617631
617655
  }
617656
+ function resamplePcm16Mono(pcm, srcRate, dstRate) {
617657
+ if (srcRate === dstRate || srcRate <= 0 || dstRate <= 0) return pcm;
617658
+ const srcSamples = Math.floor(pcm.length / 2);
617659
+ if (srcSamples === 0) return pcm;
617660
+ const ratio = srcRate / dstRate;
617661
+ const outSamples = Math.max(1, Math.floor(srcSamples / ratio));
617662
+ const out = Buffer.allocUnsafe(outSamples * 2);
617663
+ for (let i2 = 0; i2 < outSamples; i2++) {
617664
+ const pos = i2 * ratio;
617665
+ const i0 = Math.min(srcSamples - 1, Math.floor(pos));
617666
+ const i1 = Math.min(srcSamples - 1, i0 + 1);
617667
+ const frac = pos - i0;
617668
+ const sample = pcm.readInt16LE(i0 * 2) * (1 - frac) + pcm.readInt16LE(i1 * 2) * frac;
617669
+ out.writeInt16LE(Math.max(-32768, Math.min(32767, Math.round(sample))), i2 * 2);
617670
+ }
617671
+ return out;
617672
+ }
617632
617673
  function renderVoiceSessionStart(tunnelUrl) {
617633
617674
  renderBoxedBlock({
617634
617675
  title: "☁ Live Voice Session",
@@ -617936,17 +617977,26 @@ var init_voice_session = __esm({
617936
617977
  clearInterval(keepaliveInterval);
617937
617978
  }
617938
617979
  }, 1e4);
617980
+ let clientPcmRate = 16e3;
617939
617981
  ws.on("message", (data, isBinary) => {
617940
617982
  if (isBinary) {
617941
617983
  if (!this.ttsSpeaking) {
617942
- this.onUserAudio?.(Buffer.isBuffer(data) ? data : Buffer.from(data), clientId);
617984
+ let pcm = Buffer.isBuffer(data) ? data : Buffer.from(data);
617985
+ if (clientPcmRate !== 16e3 && clientPcmRate > 0) {
617986
+ pcm = resamplePcm16Mono(pcm, clientPcmRate, 16e3);
617987
+ }
617988
+ this.onUserAudio?.(pcm, clientId);
617943
617989
  }
617944
617990
  } else {
617945
617991
  try {
617946
617992
  const msg = JSON.parse(data.toString());
617947
- if (msg.type === "mic_start" && msg.username) {
617948
- const entry = this.state.connectedUsers.get(clientId);
617949
- if (entry) entry.username = msg.username;
617993
+ if (msg.type === "mic_start") {
617994
+ if (msg.username) {
617995
+ const entry = this.state.connectedUsers.get(clientId);
617996
+ if (entry) entry.username = msg.username;
617997
+ }
617998
+ const declared = Number(msg.sampleRate);
617999
+ clientPcmRate = Number.isFinite(declared) && declared >= 8e3 && declared <= 192e3 ? declared : 16e3;
617950
618000
  }
617951
618001
  } catch {
617952
618002
  }
@@ -697514,6 +697564,9 @@ Rules:
697514
697564
  // VAD segment capture
697515
697565
  captureBuffer = "";
697516
697566
  captureStartTime = 0;
697567
+ consensusMetadataSeen = false;
697568
+ consensusText = null;
697569
+ consensusSignalScore = null;
697517
697570
  silenceTimer = null;
697518
697571
  maxSegmentTimer = null;
697519
697572
  lastSignalScore = null;
@@ -697604,18 +697657,25 @@ Rules:
697604
697657
  let isFinal;
697605
697658
  let snr;
697606
697659
  let confidence2;
697660
+ let consensus;
697607
697661
  if (typeof args[0] === "object" && args[0] !== null) {
697608
697662
  const evt = args[0];
697609
697663
  text2 = evt.text ?? "";
697610
697664
  isFinal = evt.isFinal ?? false;
697611
697665
  snr = evt.snr;
697612
697666
  confidence2 = evt.confidence;
697667
+ consensus = typeof evt.consensus === "boolean" ? evt.consensus : void 0;
697613
697668
  } else {
697614
697669
  text2 = String(args[0] ?? "");
697615
697670
  isFinal = Boolean(args[1]);
697671
+ const meta = args[2];
697672
+ if (meta && typeof meta === "object") {
697673
+ const m2 = meta;
697674
+ if (typeof m2.consensus === "boolean") consensus = m2.consensus;
697675
+ }
697616
697676
  }
697617
697677
  if (!text2.trim()) return;
697618
- this.handleTranscript(text2.trim(), isFinal, snr, confidence2);
697678
+ this.handleTranscript(text2.trim(), isFinal, snr, confidence2, consensus);
697619
697679
  };
697620
697680
  this._onError = (err) => {
697621
697681
  const msg = err instanceof Error ? err.message : String(err);
@@ -697682,7 +697742,7 @@ Rules:
697682
697742
  // ---------------------------------------------------------------------------
697683
697743
  // Transcript handling — VAD-style segment capture (Voryn pattern)
697684
697744
  // ---------------------------------------------------------------------------
697685
- handleTranscript(text2, isFinal, snr, confidence2) {
697745
+ handleTranscript(text2, isFinal, snr, confidence2, consensus) {
697686
697746
  if (!this.active) return;
697687
697747
  if (this._state !== "LISTENING" && this._state !== "CAPTURING") {
697688
697748
  return;
@@ -697695,7 +697755,7 @@ Rules:
697695
697755
  }
697696
697756
  if (this._state === "LISTENING") {
697697
697757
  this.setState("CAPTURING");
697698
- this.captureBuffer = "";
697758
+ this.resetCaptureState();
697699
697759
  this.captureStartTime = Date.now();
697700
697760
  this.maxSegmentTimer = setTimeout(() => {
697701
697761
  if (this._state === "CAPTURING") {
@@ -697703,8 +697763,18 @@ Rules:
697703
697763
  }
697704
697764
  }, MAX_SEGMENT_MS);
697705
697765
  }
697706
- this.captureBuffer = text2;
697707
- this.lastSignalScore = typeof snr === "number" && !Number.isNaN(snr) ? clamp0116(snr) : computeSignalFromText(text2, confidence2);
697766
+ const signalScore = typeof snr === "number" && !Number.isNaN(snr) ? clamp0116(snr) : computeSignalFromText(text2, confidence2);
697767
+ this.lastSignalScore = signalScore;
697768
+ if (consensus !== void 0) {
697769
+ this.consensusMetadataSeen = true;
697770
+ if (consensus === true) {
697771
+ this.consensusText = text2;
697772
+ this.consensusSignalScore = signalScore;
697773
+ this.captureBuffer = text2;
697774
+ }
697775
+ } else {
697776
+ this.captureBuffer = text2;
697777
+ }
697708
697778
  this.emit("snr", { score: this.lastSignalScore });
697709
697779
  this.onPartialTranscript(text2);
697710
697780
  if (this.silenceTimer) clearTimeout(this.silenceTimer);
@@ -697715,11 +697785,22 @@ Rules:
697715
697785
  }
697716
697786
  }, waitMs);
697717
697787
  }
697788
+ resetCaptureState() {
697789
+ this.captureBuffer = "";
697790
+ this.captureStartTime = 0;
697791
+ this.lastSignalScore = null;
697792
+ this.consensusMetadataSeen = false;
697793
+ this.consensusText = null;
697794
+ this.consensusSignalScore = null;
697795
+ }
697718
697796
  // ---------------------------------------------------------------------------
697719
697797
  // Segment finalization → Transcribing → Thinking → Speaking
697720
697798
  // ---------------------------------------------------------------------------
697721
697799
  finalizeSegment() {
697722
- const text2 = this.captureBuffer.trim();
697800
+ const rawText = this.captureBuffer.trim();
697801
+ const requiresConsensus = this.consensusMetadataSeen;
697802
+ const text2 = requiresConsensus ? (this.consensusText ?? "").trim() : rawText;
697803
+ const signalScore = requiresConsensus ? this.consensusSignalScore ?? this.lastSignalScore : this.lastSignalScore;
697723
697804
  if (this.silenceTimer) {
697724
697805
  clearTimeout(this.silenceTimer);
697725
697806
  this.silenceTimer = null;
@@ -697728,18 +697809,20 @@ Rules:
697728
697809
  clearTimeout(this.maxSegmentTimer);
697729
697810
  this.maxSegmentTimer = null;
697730
697811
  }
697731
- this.captureBuffer = "";
697812
+ this.resetCaptureState();
697732
697813
  if (!text2) {
697814
+ if (requiresConsensus && rawText) {
697815
+ if (this.debugSnr) this.onStatus(`Ignoring utterance without dual-ASR consensus: ${truncateForLog(rawText, 48)}`);
697816
+ this.emit("consensusFiltered", { text: rawText });
697817
+ }
697733
697818
  this.setState("LISTENING");
697734
697819
  return;
697735
697820
  }
697736
- const score = Math.min(this.lastSignalScore ?? 1, computeSignalFromText(text2));
697821
+ const score = Math.min(signalScore ?? 1, computeSignalFromText(text2));
697737
697822
  if (score < MIN_SIGNAL_SCORE || NOISE_ONLY_RE.test(text2)) {
697738
697823
  if (this.debugSnr) this.onStatus(`Ignoring low-signal utterance (SNR:${score.toFixed(2)}): ${truncateForLog(text2, 48)}`);
697739
697824
  this.emit("snrFiltered", { score, text: text2 });
697740
697825
  this.setState("LISTENING");
697741
- this.captureBuffer = "";
697742
- this.lastSignalScore = null;
697743
697826
  return;
697744
697827
  }
697745
697828
  this.setState("TRANSCRIBING");
@@ -76,10 +76,12 @@ def emit_error(msg: str):
76
76
  emit({"type": "error", "message": msg})
77
77
 
78
78
 
79
- def emit_transcript(text: str, is_final: bool = False, doa=None):
79
+ def emit_transcript(text: str, is_final: bool = False, doa=None, consensus=None):
80
80
  event = {"type": "transcript", "text": text, "isFinal": is_final}
81
81
  if doa is not None:
82
82
  event["doa"] = doa
83
+ if consensus is not None:
84
+ event["consensus"] = bool(consensus)
83
85
  emit(event)
84
86
 
85
87
  # ---------------------------------------------------------------------------
@@ -441,7 +443,7 @@ def main():
441
443
  result = run_transcribe(model, amplify(samples))
442
444
  text = _segment_filtered_text(result)
443
445
  if text:
444
- emit_transcript(text, is_final=False)
446
+ emit_transcript(text, is_final=False, consensus=False)
445
447
  except Exception as e:
446
448
  emit_error(f"Transcription error: {e}")
447
449
 
@@ -481,7 +483,14 @@ def main():
481
483
  if text == last_final_text:
482
484
  return
483
485
  last_final_text = text
484
- emit_transcript(text, is_final=True, doa=tuning.doa() if tuning else None)
486
+ # consensus=True only when BOTH models ran and converged — the
487
+ # downstream voicechat gate requires convergence for pass-through.
488
+ emit_transcript(
489
+ text,
490
+ is_final=True,
491
+ doa=tuning.doa() if tuning else None,
492
+ consensus=consensus_model is not None,
493
+ )
485
494
  except Exception as e:
486
495
  emit_error(f"Transcription error: {e}")
487
496
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.426",
3
+ "version": "1.0.427",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.426",
9
+ "version": "1.0.427",
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.426",
3
+ "version": "1.0.427",
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",