omnius 1.0.427 → 1.0.429

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
@@ -603384,6 +603384,15 @@ async function findLiveWhisperScript() {
603384
603384
  }
603385
603385
  return null;
603386
603386
  }
603387
+ function defaultConsensusModel(primaryModel) {
603388
+ const model = primaryModel.trim().toLowerCase();
603389
+ if (model === "tiny") return "base";
603390
+ if (model === "base") return "small";
603391
+ if (model === "small") return "base";
603392
+ if (model === "medium") return "base";
603393
+ if (model === "large-v3" || model === "large") return "small";
603394
+ return "base";
603395
+ }
603387
603396
  async function ensureVenvForTranscribeCli() {
603388
603397
  const bin = process.platform === "win32" ? "Scripts" : "bin";
603389
603398
  const exe = process.platform === "win32" ? "python.exe" : "python3";
@@ -603573,7 +603582,9 @@ var init_listen = __esm({
603573
603582
  lastStatus: `loading whisper ${this.model} model...`
603574
603583
  });
603575
603584
  const consensusEnv = String(process.env["OMNIUS_ASR_CONSENSUS"] ?? "").trim().toLowerCase();
603576
- const consensusModel = consensusEnv === "0" || consensusEnv === "off" || consensusEnv === "false" ? "off" : consensusEnv || (this.model === "tiny" ? "base" : "tiny");
603585
+ const consensusModel = consensusEnv === "0" || consensusEnv === "off" || consensusEnv === "false" ? "off" : consensusEnv || defaultConsensusModel(this.model);
603586
+ const consensusThreshold = String(process.env["OMNIUS_ASR_CONSENSUS_THRESHOLD"] ?? "0.45");
603587
+ const silenceMs = String(process.env["OMNIUS_ASR_SILENCE_MS"] ?? "800");
603577
603588
  this.process = spawn29(
603578
603589
  pyPath,
603579
603590
  [
@@ -603585,7 +603596,11 @@ var init_listen = __esm({
603585
603596
  "--window-seconds",
603586
603597
  "10",
603587
603598
  "--consensus-model",
603588
- consensusModel
603599
+ consensusModel,
603600
+ "--consensus-threshold",
603601
+ consensusThreshold,
603602
+ "--silence-ms",
603603
+ silenceMs
603589
603604
  ],
603590
603605
  {
603591
603606
  stdio: ["pipe", "pipe", "pipe"],
@@ -603625,13 +603640,18 @@ var init_listen = __esm({
603625
603640
  });
603626
603641
  break;
603627
603642
  case "consensus_rejected":
603628
- updateListenLiveState({
603629
- lastStatus: `consensus rejected (${evt.reason ?? "mismatch"}): ${String(evt.primary ?? "").slice(0, 80)}`
603630
- });
603643
+ {
603644
+ const message2 = `consensus rejected (${evt.reason ?? "mismatch"}): ${String(evt.primary ?? "").slice(0, 80)}`;
603645
+ updateListenLiveState({
603646
+ lastStatus: message2
603647
+ });
603648
+ this.emit("status", message2);
603649
+ }
603631
603650
  break;
603632
603651
  case "vad": {
603633
603652
  lastHardwareVadAt = Date.now();
603634
603653
  const doa = Number(evt.doa);
603654
+ this.emit("status", `Whisper VAD: ${evt.speech === true ? "speech" : "silence"}`);
603635
603655
  updateListenLiveState({
603636
603656
  speechActive: evt.speech === true,
603637
603657
  doa: Number.isFinite(doa) ? doa : null
@@ -604033,8 +604053,9 @@ transcribe-cli error: ${transcribeCliError}` : "";
604033
604053
  return `Failed to start live transcription: ${msg}${tcHint}`;
604034
604054
  }
604035
604055
  }
604036
- this.spawnMicProcess(micCmd);
604037
604056
  this.active = true;
604057
+ this.paused = false;
604058
+ this.spawnMicProcess(micCmd);
604038
604059
  this.blinkState = true;
604039
604060
  this.blinkTimer = setInterval(() => {
604040
604061
  this.blinkState = !this.blinkState;
@@ -606454,6 +606475,7 @@ var init_live_sensors = __esm({
606454
606475
  previewTickInFlight = false;
606455
606476
  lastStreamFrameMtime = /* @__PURE__ */ new Map();
606456
606477
  lastStreamPersistAt = 0;
606478
+ videoGeneration = 0;
606457
606479
  videoInFlight = false;
606458
606480
  audioInFlight = false;
606459
606481
  audioActivityInFlight = false;
@@ -606656,7 +606678,12 @@ var init_live_sensors = __esm({
606656
606678
  const next = (current + delta) % 360;
606657
606679
  return this.setCameraRotation(device, next, "manual", `manual ${direction === "cw" ? "clockwise" : "counter-clockwise"} dashboard override`);
606658
606680
  }
606681
+ isVideoWorkCurrent(generation) {
606682
+ return this.config.videoEnabled && generation === this.videoGeneration;
606683
+ }
606659
606684
  async autoOrientCamera(device = this.config.selectedCamera, options2 = {}) {
606685
+ const generation = this.videoGeneration;
606686
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
606660
606687
  const target = device || this.config.selectedCamera || firstDeviceId(this.devices.video);
606661
606688
  if (!target) return "No camera selected. Use /live camera <device> or select a camera in /live.";
606662
606689
  if (options2.onlyIfUnset && this.getCameraOrientation(target)) {
@@ -606673,17 +606700,20 @@ var init_live_sensors = __esm({
606673
606700
  framePath = streamFrame.path;
606674
606701
  }
606675
606702
  if (!framePath) {
606703
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
606676
606704
  const captured = await new CameraCaptureTool().execute({
606677
606705
  action: "capture",
606678
606706
  device: target,
606679
606707
  rotate_degrees: 0
606680
606708
  });
606709
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
606681
606710
  if (!captured.success) {
606682
606711
  return `Camera orientation detection could not capture ${target}: ${captured.error || captured.output || "unknown error"}`;
606683
606712
  }
606684
606713
  framePath = extractSavedImagePath(captured.output, this.repoRoot);
606685
606714
  }
606686
606715
  if (!framePath) return `Camera orientation detection captured ${target}, but no frame path was returned.`;
606716
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
606687
606717
  const cvScores = await scoreCameraOrientationWithOpenCv(framePath);
606688
606718
  const cvDecision = cvScores ? chooseCameraOrientationFromScores(cvScores) : null;
606689
606719
  const prompt = [
@@ -606701,6 +606731,7 @@ var init_live_sensors = __esm({
606701
606731
  prompt,
606702
606732
  model: "moondream3-preview"
606703
606733
  });
606734
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
606704
606735
  if (!vision.success) {
606705
606736
  if (cvDecision) {
606706
606737
  const orientation2 = this.setCameraRotation(target, cvDecision.rotation, "auto", cvDecision.reason, cvDecision.confidence);
@@ -606754,12 +606785,18 @@ var init_live_sensors = __esm({
606754
606785
  return this.devices;
606755
606786
  }
606756
606787
  configure(patch) {
606788
+ const wasVideoEnabled = this.config.videoEnabled;
606757
606789
  this.config = {
606758
606790
  ...this.config,
606759
606791
  ...patch,
606760
606792
  videoIntervalMs: Math.max(MIN_VIDEO_INTERVAL_MS, Number(patch.videoIntervalMs ?? this.config.videoIntervalMs)),
606761
606793
  audioIntervalMs: Math.max(MIN_AUDIO_INTERVAL_MS, Number(patch.audioIntervalMs ?? this.config.audioIntervalMs))
606762
606794
  };
606795
+ if (wasVideoEnabled && !this.config.videoEnabled) {
606796
+ this.videoGeneration++;
606797
+ this.lastStreamFrameMtime.clear();
606798
+ this.cameraStreamers?.stopAll();
606799
+ }
606763
606800
  this.persist();
606764
606801
  this.clearLoopTimers();
606765
606802
  this.ensureLoops();
@@ -606838,6 +606875,8 @@ var init_live_sensors = __esm({
606838
606875
  return options2.agent ? "Live PFC run enabled: visual/audio feedback is active and significant events can launch background agent review." : "Live run enabled: visual/audio feedback is active and live context will update each turn.";
606839
606876
  }
606840
606877
  async previewCamera(device = this.config.selectedCamera, options2 = {}) {
606878
+ const generation = options2.generation ?? this.videoGeneration;
606879
+ if (!this.isVideoWorkCurrent(generation)) return { ok: false, message: "Video context is disabled." };
606841
606880
  const source = device || firstDeviceId(this.devices.video);
606842
606881
  if (!source) return { ok: false, message: "No camera selected. Use /live to select a camera after device discovery." };
606843
606882
  const orientation = this.getCameraOrientation(source);
@@ -606847,6 +606886,7 @@ var init_live_sensors = __esm({
606847
606886
  const displayPath2 = relative11(this.repoRoot, streamFrame.path).startsWith("..") ? streamFrame.path : relative11(this.repoRoot, streamFrame.path);
606848
606887
  const previewWidth2 = Math.max(42, Math.min(120, Math.floor(Number(options2.previewWidth ?? (process.stdout.columns || 100) - 14))));
606849
606888
  const preview2 = await buildImageAsciiPreview(streamFrame.path, { width: previewWidth2 });
606889
+ if (!this.isVideoWorkCurrent(generation)) return { ok: false, message: "Video context is disabled." };
606850
606890
  if (preview2?.ascii || preview2?.plainAscii) {
606851
606891
  const observedAt2 = Date.now();
606852
606892
  const cameraView2 = {
@@ -606889,6 +606929,7 @@ var init_live_sensors = __esm({
606889
606929
  device: source,
606890
606930
  rotate_degrees: rotation
606891
606931
  });
606932
+ if (!this.isVideoWorkCurrent(generation)) return { ok: false, message: "Video context is disabled." };
606892
606933
  if (!result.success) {
606893
606934
  const error = result.error || result.output || "camera capture failed";
606894
606935
  const observedAt2 = Date.now();
@@ -606933,6 +606974,7 @@ var init_live_sensors = __esm({
606933
606974
  const displayPath = relative11(this.repoRoot, framePath).startsWith("..") ? framePath : relative11(this.repoRoot, framePath);
606934
606975
  const previewWidth = Math.max(42, Math.min(120, Math.floor(Number(options2.previewWidth ?? (process.stdout.columns || 100) - 14))));
606935
606976
  const preview = await buildImageAsciiPreview(framePath, { width: previewWidth });
606977
+ if (!this.isVideoWorkCurrent(generation)) return { ok: false, message: "Video context is disabled." };
606936
606978
  const asciiContext = preview ? formatImageAsciiContext(preview, displayPath) : void 0;
606937
606979
  const observedAt = Date.now();
606938
606980
  const cameraView = {
@@ -607041,7 +607083,10 @@ var init_live_sensors = __esm({
607041
607083
  }
607042
607084
  }
607043
607085
  async sampleSingleVideoNow(source, forceInfer, previewWidth, options2 = {}) {
607044
- const preview = await this.previewCamera(source, { emit: false, previewWidth });
607086
+ const generation = options2.generation ?? this.videoGeneration;
607087
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
607088
+ const preview = await this.previewCamera(source, { emit: false, previewWidth, generation });
607089
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
607045
607090
  void this.runLiveVlm(source, preview.ok ? preview.framePath : void 0).catch(() => {
607046
607091
  });
607047
607092
  let inference = "";
@@ -607076,6 +607121,7 @@ var init_live_sensors = __esm({
607076
607121
  crop_faces: true,
607077
607122
  recognize_faces: true
607078
607123
  });
607124
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
607079
607125
  inference = result.success ? String(result.output || result.llmContent || "") : `Video inference failed: ${result.error || result.output || "unknown error"}`;
607080
607126
  const livePayload = result.success ? parseLiveMediaPayload(result.llmContent || result.output) : null;
607081
607127
  if (livePayload) livePayload.source = source;
@@ -607148,6 +607194,7 @@ var init_live_sensors = __esm({
607148
607194
  if (this.config.clipEnabled && preview.ok && preview.framePath && source && sampledAt - lastClip >= 5e3) {
607149
607195
  this.lastClipAt.set(source, sampledAt);
607150
607196
  clipSummary = await this.recognizeFrameObjects(preview.framePath, source, sampledAt, { emit: false });
607197
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
607151
607198
  const current = this.snapshot.cameras?.[source];
607152
607199
  if (current) {
607153
607200
  const clipLine = formatClipDashboardSummary(clipSummary);
@@ -607176,9 +607223,12 @@ ${clipSummary}` : ""].filter(Boolean).join("\n");
607176
607223
  return result;
607177
607224
  }
607178
607225
  async sampleVideoNow(forceInfer = this.config.inferEnabled, options2 = {}) {
607226
+ const generation = options2.generation ?? this.videoGeneration;
607227
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
607179
607228
  if (this.videoInFlight) return "Video sampler is already running.";
607180
607229
  this.videoInFlight = true;
607181
607230
  try {
607231
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
607182
607232
  const allSources = this.activeVideoSources();
607183
607233
  if (allSources.length === 0) return "No camera selected. Use /live refresh after connecting hardware.";
607184
607234
  const sources = options2.mode === "round-robin" ? [allSources[this.nextVideoSourceIndex++ % allSources.length]] : allSources;
@@ -607186,8 +607236,10 @@ ${clipSummary}` : ""].filter(Boolean).join("\n");
607186
607236
  const outputs = [];
607187
607237
  const previewWidth = liveDashboardPreviewWidthForSources(allSources.length);
607188
607238
  for (const source of sources) {
607189
- outputs.push(await this.sampleSingleVideoNow(source, forceInfer, previewWidth, options2));
607239
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
607240
+ outputs.push(await this.sampleSingleVideoNow(source, forceInfer, previewWidth, { ...options2, generation }));
607190
607241
  }
607242
+ if (!this.isVideoWorkCurrent(generation)) return "Video context is disabled.";
607191
607243
  this.emitDashboard();
607192
607244
  return outputs.map((output, index) => `## Camera ${sources[index]}
607193
607245
  ${output}`).join("\n\n");
@@ -697778,7 +697830,7 @@ Rules:
697778
697830
  this.emit("snr", { score: this.lastSignalScore });
697779
697831
  this.onPartialTranscript(text2);
697780
697832
  if (this.silenceTimer) clearTimeout(this.silenceTimer);
697781
- const waitMs = this._vadSilenceMs ?? VAD_SILENCE_MS;
697833
+ const waitMs = isFinal ? 0 : this._vadSilenceMs ?? VAD_SILENCE_MS;
697782
697834
  this.silenceTimer = setTimeout(() => {
697783
697835
  if (this._state === "CAPTURING") {
697784
697836
  this.finalizeSegment();
@@ -697945,35 +697997,35 @@ ${toolOutput}` });
697945
697997
  const reply = extractVoiceModelReply(stripToolJsonLines(response.trim()));
697946
697998
  const finalSpoken = reply.text;
697947
697999
  if (!finalSpoken || reply.action === "silent") {
697948
- return;
697949
- }
697950
- if (typeof this.voice.waitUntilIdle === "function") {
697951
- try {
697952
- await this.voice.waitUntilIdle();
697953
- } catch {
698000
+ } else {
698001
+ if (typeof this.voice.waitUntilIdle === "function") {
698002
+ try {
698003
+ await this.voice.waitUntilIdle();
698004
+ } catch {
698005
+ }
697954
698006
  }
697955
- }
697956
- this.context.push({ role: "assistant", content: finalSpoken });
697957
- this.trimContext();
697958
- this.setState("SPEAKING");
697959
- this.onAgentSpeech(finalSpoken);
697960
- this.lastAgentSpeech = { text: finalSpoken, startedAt: Date.now() };
697961
- this.voice.speak(finalSpoken);
697962
- this.voiceTranscript.push({ role: "assistant", content: finalSpoken, ts: Date.now() });
697963
- if (this.runner && this.turnCount % SUMMARY_INJECTION_INTERVAL === 0) {
697964
- this.injectSummary();
697965
- }
697966
- if (typeof this.voice.waitUntilIdle === "function") {
697967
- try {
697968
- await this.voice.waitUntilIdle();
697969
- } catch {
698007
+ this.context.push({ role: "assistant", content: finalSpoken });
698008
+ this.trimContext();
698009
+ this.setState("SPEAKING");
698010
+ this.onAgentSpeech(finalSpoken);
698011
+ this.lastAgentSpeech = { text: finalSpoken, startedAt: Date.now() };
698012
+ this.voice.speak(finalSpoken);
698013
+ this.voiceTranscript.push({ role: "assistant", content: finalSpoken, ts: Date.now() });
698014
+ if (this.runner && this.turnCount % SUMMARY_INJECTION_INTERVAL === 0) {
698015
+ this.injectSummary();
698016
+ }
698017
+ if (typeof this.voice.waitUntilIdle === "function") {
698018
+ try {
698019
+ await this.voice.waitUntilIdle();
698020
+ } catch {
698021
+ }
698022
+ } else {
698023
+ const estimatedMs = Math.max(1500, finalSpoken.length / 5 * (6e4 / 150));
698024
+ await new Promise((r2) => setTimeout(r2, estimatedMs));
698025
+ }
698026
+ if (reply.action === "hangup") {
698027
+ this.active = false;
697970
698028
  }
697971
- } else {
697972
- const estimatedMs = Math.max(1500, finalSpoken.length / 5 * (6e4 / 150));
697973
- await new Promise((r2) => setTimeout(r2, estimatedMs));
697974
- }
697975
- if (reply.action === "hangup") {
697976
- this.active = false;
697977
698029
  }
697978
698030
  }
697979
698031
  } catch (err) {
@@ -737140,6 +737192,11 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
737140
737192
  listenEng.on("level", (evt) => {
737141
737193
  statusBar.setMicActivity(evt.speechActive, evt.levelDb);
737142
737194
  });
737195
+ listenEng.on("info", (msg) => {
737196
+ if (/consensus rejected|loading whisper|whisper model loaded|Whisper VAD|no accepted speech text|very short utterance/i.test(msg)) {
737197
+ writeContent(() => renderInfo(`[voicechat/asr] ${msg}`));
737198
+ }
737199
+ });
737143
737200
  const summaryRunner = {
737144
737201
  injectUserMessage(content) {
737145
737202
  if (activeTask?.runner) {
@@ -443,13 +443,18 @@ def main():
443
443
  result = run_transcribe(model, amplify(samples))
444
444
  text = _segment_filtered_text(result)
445
445
  if text:
446
- emit_transcript(text, is_final=False, consensus=False)
446
+ emit_transcript(
447
+ text,
448
+ is_final=False,
449
+ consensus=False if consensus_model is not None else None,
450
+ )
447
451
  except Exception as e:
448
452
  emit_error(f"Transcription error: {e}")
449
453
 
450
454
  def transcribe_final(samples):
451
455
  nonlocal last_final_text
452
456
  if len(samples) < int(0.4 * SAMPLE_RATE):
457
+ emit_status("Whisper ignored very short utterance.")
453
458
  return
454
459
  samples = amplify(samples)
455
460
  try:
@@ -466,6 +471,7 @@ def main():
466
471
 
467
472
  text = _segment_filtered_text(result)
468
473
  if not text:
474
+ emit_status("Whisper final produced no accepted speech text.")
469
475
  return
470
476
  if secondary is not None:
471
477
  secondary_text = _segment_filtered_text(secondary)
@@ -483,13 +489,15 @@ def main():
483
489
  if text == last_final_text:
484
490
  return
485
491
  last_final_text = text
486
- # consensus=True only when BOTH models ran and converged the
487
- # downstream voicechat gate requires convergence for pass-through.
492
+ # consensus=True only when BOTH models ran and converged. When the
493
+ # second model is off/unavailable, omit consensus metadata entirely
494
+ # so legacy single-model consumers do not interpret consensus=false
495
+ # as an explicit dual-ASR rejection.
488
496
  emit_transcript(
489
497
  text,
490
498
  is_final=True,
491
499
  doa=tuning.doa() if tuning else None,
492
- consensus=consensus_model is not None,
500
+ consensus=True if consensus_model is not None else None,
493
501
  )
494
502
  except Exception as e:
495
503
  emit_error(f"Transcription error: {e}")
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.427",
3
+ "version": "1.0.429",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.427",
9
+ "version": "1.0.429",
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.427",
3
+ "version": "1.0.429",
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",