omnius 1.0.411 → 1.0.412

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -41456,17 +41456,30 @@ function isTranscribable(path12) {
41456
41456
  const ext = extname4(path12).toLowerCase();
41457
41457
  return AUDIO_EXTS.has(ext) || VIDEO_EXTS.has(ext);
41458
41458
  }
41459
+ async function requireTranscribeCliFrom(packageDir) {
41460
+ const { createRequire: createRequire11 } = await import("node:module");
41461
+ const req3 = createRequire11(import.meta.url);
41462
+ const distPath = join40(packageDir, "dist", "index.js");
41463
+ if (existsSync38(distPath))
41464
+ return req3(distPath);
41465
+ return req3(packageDir);
41466
+ }
41459
41467
  async function loadTranscribeCli() {
41460
41468
  if (_tcChecked)
41461
41469
  return _tcModule;
41462
41470
  _tcChecked = true;
41471
+ try {
41472
+ const { createRequire: createRequire11 } = await import("node:module");
41473
+ const req3 = createRequire11(import.meta.url);
41474
+ _tcModule = req3("transcribe-cli");
41475
+ return _tcModule;
41476
+ } catch {
41477
+ }
41463
41478
  try {
41464
41479
  const globalRoot = (await execFileText("npm", ["root", "-g"], { timeout: 5e3 })).trim();
41465
41480
  const tcPath = join40(globalRoot, "transcribe-cli");
41466
41481
  if (existsSync38(join40(tcPath, "dist", "index.js"))) {
41467
- const { createRequire: createRequire11 } = await import("node:module");
41468
- const req3 = createRequire11(import.meta.url);
41469
- _tcModule = req3(join40(tcPath, "dist", "index.js"));
41482
+ _tcModule = await requireTranscribeCliFrom(tcPath);
41470
41483
  return _tcModule;
41471
41484
  }
41472
41485
  } catch {
@@ -41478,9 +41491,7 @@ async function loadTranscribeCli() {
41478
41491
  for (const ver of readdirSync61(nvmBase)) {
41479
41492
  const tcPath = join40(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
41480
41493
  if (existsSync38(join40(tcPath, "dist", "index.js"))) {
41481
- const { createRequire: createRequire11 } = await import("node:module");
41482
- const req3 = createRequire11(import.meta.url);
41483
- _tcModule = req3(join40(tcPath, "dist", "index.js"));
41494
+ _tcModule = await requireTranscribeCliFrom(tcPath);
41484
41495
  return _tcModule;
41485
41496
  }
41486
41497
  }
@@ -41489,6 +41500,58 @@ async function loadTranscribeCli() {
41489
41500
  }
41490
41501
  return null;
41491
41502
  }
41503
+ async function ensureManagedTranscribeCliRuntime() {
41504
+ const packageDir = join40(MANAGED_TRANSCRIBE_CLI_DIR, "node_modules", "transcribe-cli");
41505
+ if (existsSync38(join40(packageDir, "dist", "index.js"))) {
41506
+ try {
41507
+ _managedTranscribeCliReady = true;
41508
+ _tcModule = await requireTranscribeCliFrom(packageDir);
41509
+ _tcChecked = true;
41510
+ return _tcModule;
41511
+ } catch {
41512
+ }
41513
+ }
41514
+ if (_managedTranscribeCliChecked && !_managedTranscribeCliReady)
41515
+ return null;
41516
+ _managedTranscribeCliChecked = true;
41517
+ try {
41518
+ mkdirSync22(MANAGED_TRANSCRIBE_CLI_DIR, { recursive: true });
41519
+ if (!existsSync38(join40(MANAGED_TRANSCRIBE_CLI_DIR, "package.json"))) {
41520
+ writeFileSync21(join40(MANAGED_TRANSCRIBE_CLI_DIR, "package.json"), JSON.stringify({ private: true, dependencies: {} }, null, 2), "utf8");
41521
+ }
41522
+ await execFileText("npm", ["install", "--prefix", MANAGED_TRANSCRIBE_CLI_DIR, "transcribe-cli@^2.0.1"], {
41523
+ timeout: 18e4,
41524
+ env: transcriptionPythonEnv()
41525
+ });
41526
+ _managedTranscribeCliReady = true;
41527
+ _tcModule = await requireTranscribeCliFrom(packageDir);
41528
+ _tcChecked = true;
41529
+ return _tcModule;
41530
+ } catch {
41531
+ _managedTranscribeCliReady = false;
41532
+ return null;
41533
+ }
41534
+ }
41535
+ async function ensureTranscribeCliPythonModule(python) {
41536
+ if (_transcribeCliPythonModuleChecked)
41537
+ return;
41538
+ _transcribeCliPythonModuleChecked = true;
41539
+ try {
41540
+ await execFileText(python, ["-c", "import transcribe_cli"], {
41541
+ timeout: 1e4,
41542
+ env: transcriptionPythonEnv()
41543
+ });
41544
+ return;
41545
+ } catch {
41546
+ }
41547
+ try {
41548
+ await execFileText(python, ["-m", "pip", "install", "-U", "transcribe-cli"], {
41549
+ timeout: 3e5,
41550
+ env: transcriptionPythonEnv()
41551
+ });
41552
+ } catch {
41553
+ }
41554
+ }
41492
41555
  function isYouTubeUrl(url) {
41493
41556
  return /(?:youtube\.com\/(?:watch|shorts|live|embed|v\/)|youtu\.be\/)/i.test(url);
41494
41557
  }
@@ -41519,7 +41582,7 @@ function formatTime(seconds) {
41519
41582
  const s2 = Math.floor(seconds % 60);
41520
41583
  return `${String(m2).padStart(2, "0")}:${String(s2).padStart(2, "0")}`;
41521
41584
  }
41522
- var AUDIO_EXTS, VIDEO_EXTS, MAX_TRANSCRIBE_URL_BYTES, MANAGED_ASR_VENV, _tcModule, _tcChecked, _managedAsrReady, TranscribeFileTool, YT_DLP_VENV, TranscribeUrlTool, YouTubeDownloadTool;
41585
+ var AUDIO_EXTS, VIDEO_EXTS, MAX_TRANSCRIBE_URL_BYTES, MANAGED_ASR_VENV, MANAGED_TRANSCRIBE_CLI_DIR, _tcModule, _tcChecked, _managedAsrReady, _managedTranscribeCliChecked, _managedTranscribeCliReady, _transcribeCliPythonModuleChecked, TranscribeFileTool, YT_DLP_VENV, TranscribeUrlTool, YouTubeDownloadTool;
41523
41586
  var init_transcribe_tool = __esm({
41524
41587
  "packages/execution/dist/tools/transcribe-tool.js"() {
41525
41588
  "use strict";
@@ -41551,9 +41614,13 @@ var init_transcribe_tool = __esm({
41551
41614
  ]);
41552
41615
  MAX_TRANSCRIBE_URL_BYTES = 100 * 1024 * 1024;
41553
41616
  MANAGED_ASR_VENV = join40(homedir11(), ".omnius", "runtimes", "asr", ".venv-whisper");
41617
+ MANAGED_TRANSCRIBE_CLI_DIR = join40(homedir11(), ".omnius", "runtimes", "asr", "transcribe-cli-node");
41554
41618
  _tcModule = null;
41555
41619
  _tcChecked = false;
41556
41620
  _managedAsrReady = false;
41621
+ _managedTranscribeCliChecked = false;
41622
+ _managedTranscribeCliReady = false;
41623
+ _transcribeCliPythonModuleChecked = false;
41557
41624
  TranscribeFileTool = class {
41558
41625
  name = "transcribe_file";
41559
41626
  description = "Transcribe a local audio or video file to text using Whisper (faster-whisper). Supports MP3, WAV, FLAC, AAC, M4A, OGG, MP4, MKV, AVI, MOV, WebM. Returns the full transcription text with optional speaker diarization. Transcription is 100% local — no API keys needed.";
@@ -41628,7 +41695,15 @@ var init_transcribe_tool = __esm({
41628
41695
  if (effectiveModel !== askedModel)
41629
41696
  model = effectiveModel;
41630
41697
  const failures = [];
41631
- const tc = await loadTranscribeCli();
41698
+ let managedPython = "";
41699
+ try {
41700
+ managedPython = await this.ensureManagedWhisperRuntime();
41701
+ process.env.TRANSCRIBE_PYTHON = managedPython;
41702
+ await ensureTranscribeCliPythonModule(managedPython);
41703
+ } catch (err) {
41704
+ failures.push(`managed ASR runtime bootstrap: ${err instanceof Error ? err.message : String(err)}`);
41705
+ }
41706
+ const tc = await loadTranscribeCli() ?? await ensureManagedTranscribeCliRuntime();
41632
41707
  if (tc) {
41633
41708
  try {
41634
41709
  const result = await withProcessEnv(transcriptionPythonEnv(), () => tc.transcribe(filePath, {
@@ -41641,6 +41716,8 @@ var init_transcribe_tool = __esm({
41641
41716
  } catch (err) {
41642
41717
  failures.push(`transcribe-cli module: ${err instanceof Error ? err.message : String(err)}`);
41643
41718
  }
41719
+ } else {
41720
+ failures.push("transcribe-cli npm package unavailable after managed auto-install");
41644
41721
  }
41645
41722
  const cli = await this.execViaCli(filePath, model, diarize, start2);
41646
41723
  if (cli.success)
@@ -602864,9 +602941,9 @@ function parseCameraOrientationDecision(value2) {
602864
602941
  }
602865
602942
  function formatCameraRotation(rotation) {
602866
602943
  if (rotation === 0) return "upright/no correction";
602867
- if (rotation === 90) return "90 degrees clockwise";
602868
- if (rotation === 180) return "180 degrees";
602869
- return "90 degrees counter-clockwise";
602944
+ if (rotation === 90) return "90 degrees clockwise correction";
602945
+ if (rotation === 180) return "180 degrees correction";
602946
+ return "90 degrees counter-clockwise correction";
602870
602947
  }
602871
602948
  function sanitizeCameraOrientation(value2) {
602872
602949
  if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) return {};
@@ -603127,6 +603204,116 @@ function fallbackLiveAudioIntakeDecision(transcript) {
603127
603204
  reason: addressed ? "fallback detected direct address or question shape" : "fallback could not establish that ambient speech was addressed to Omnius"
603128
603205
  };
603129
603206
  }
603207
+ function parseLiveSpeechDecision(value2) {
603208
+ const parsed = parseJsonObjectFromText(value2);
603209
+ if (!parsed) return null;
603210
+ const speak = parsed["speak"];
603211
+ const confidence2 = Math.max(0, Math.min(1, Number(parsed["confidence"] ?? 0)));
603212
+ if (typeof speak !== "boolean" || !Number.isFinite(confidence2)) return null;
603213
+ const actionRaw = String(parsed["action"] ?? (speak ? "speak" : "silent")).toLowerCase();
603214
+ const action = actionRaw === "speak" || actionRaw === "silent" || actionRaw === "queue_review" ? actionRaw : speak ? "speak" : "silent";
603215
+ return {
603216
+ speak,
603217
+ text: trimOneLine(parsed["text"] ?? "", 180),
603218
+ confidence: confidence2,
603219
+ action,
603220
+ reason: trimOneLine(parsed["reason"] ?? "", 220) || "live speech arbiter decision"
603221
+ };
603222
+ }
603223
+ function fallbackLiveSpeechDecision(proposal) {
603224
+ if (proposal.kind === "unknown_person") {
603225
+ return {
603226
+ speak: true,
603227
+ text: proposal.defaultText,
603228
+ confidence: 0.72,
603229
+ action: "speak",
603230
+ reason: "unknown person visible and identity clarification is useful"
603231
+ };
603232
+ }
603233
+ if (proposal.kind === "audio_intent") {
603234
+ return {
603235
+ speak: true,
603236
+ text: proposal.defaultText,
603237
+ confidence: 0.68,
603238
+ action: "speak",
603239
+ reason: "audio intake classified speech as addressed to Omnius"
603240
+ };
603241
+ }
603242
+ if (proposal.urgency === "high") {
603243
+ return {
603244
+ speak: true,
603245
+ text: proposal.defaultText,
603246
+ confidence: 0.6,
603247
+ action: "speak",
603248
+ reason: "high-urgency live trigger"
603249
+ };
603250
+ }
603251
+ return {
603252
+ speak: false,
603253
+ text: "",
603254
+ confidence: 0.55,
603255
+ action: "silent",
603256
+ reason: "ambient event did not justify interrupting with speech"
603257
+ };
603258
+ }
603259
+ async function classifyLiveSpeech(proposal, config) {
603260
+ const now2 = Date.now();
603261
+ if (config?.backendUrl && config.model) {
603262
+ const url = `${config.backendUrl.replace(/\/+$/, "")}/v1/chat/completions`;
603263
+ const body = {
603264
+ model: config.model,
603265
+ temperature: 0,
603266
+ max_tokens: 180,
603267
+ think: false,
603268
+ messages: [
603269
+ {
603270
+ role: "system",
603271
+ content: [
603272
+ "You are Omnius' live audio/video speech arbiter.",
603273
+ "Decide whether the agent should speak aloud into the physical environment right now.",
603274
+ "Speak rarely. Do not narrate routine detections or every frame.",
603275
+ "Speak when directly addressed, when an unknown person should be politely identified, or when an important visual/audio trigger needs immediate clarification.",
603276
+ "Never invent a person's identity. If asking identity, ask one short natural question.",
603277
+ "Keep spoken text under 16 words.",
603278
+ 'Return only JSON: {"speak": boolean, "text": "short utterance", "confidence": 0.0-1.0, "action": "speak"|"silent"|"queue_review", "reason": "brief evidence"}.'
603279
+ ].join("\n")
603280
+ },
603281
+ {
603282
+ role: "user",
603283
+ content: [
603284
+ `kind=${proposal.kind}`,
603285
+ `source=${proposal.source}`,
603286
+ `urgency=${proposal.urgency}`,
603287
+ `summary=${proposal.summary}`,
603288
+ `evidence=${proposal.evidence}`,
603289
+ `default_utterance=${proposal.defaultText}`
603290
+ ].join("\n").slice(0, 1600)
603291
+ }
603292
+ ]
603293
+ };
603294
+ try {
603295
+ const controller = new AbortController();
603296
+ const timer = setTimeout(() => controller.abort(), 5e3).unref();
603297
+ const resp = await fetch(url, {
603298
+ method: "POST",
603299
+ headers: {
603300
+ "Content-Type": "application/json",
603301
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
603302
+ },
603303
+ body: JSON.stringify(body),
603304
+ signal: controller.signal
603305
+ });
603306
+ clearTimeout(timer);
603307
+ if (resp.ok) {
603308
+ const json = await resp.json();
603309
+ const parsed = parseLiveSpeechDecision(json.choices?.[0]?.message?.content ?? "");
603310
+ if (parsed) return { ...parsed, source: "live-arbiter", updatedAt: now2 };
603311
+ }
603312
+ } catch {
603313
+ }
603314
+ }
603315
+ return { ...fallbackLiveSpeechDecision(proposal), source: "fallback", updatedAt: now2 };
603316
+ }
603130
603317
  async function classifyLiveAudioIntake(transcript, config) {
603131
603318
  const now2 = Date.now();
603132
603319
  const clean5 = transcript.trim();
@@ -603679,6 +603866,8 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
603679
603866
  lines.push("<live-sensor-context>");
603680
603867
  lines.push("## LIVE SENSOR STATUS");
603681
603868
  lines.push("Live sensor context is operator-enabled ambient input. Treat it as current perceptual evidence for this turn; if it is stale, missing, or insufficient, use camera/audio tools to refresh before answering what you see or hear.");
603869
+ lines.push('Live world-model rule: when the user asks what is around, who is present, what changed, what is heard, or asks a deictic question like "what do you see", treat the live camera/audio fields as first-class context. Be curious and tool-capable: refresh stale sensors, inspect frame paths, query vision/CLIP/visual_memory/audio tools, and combine metadata into a concise answer. Do not rely only on summaries when the task requires current perception.');
603870
+ lines.push("Live speech rule: in low-latency voice/live modes, short spoken replies are appropriate for direct address, identity clarification, or important visual/audio triggers. Do not narrate every frame.");
603682
603871
  lines.push("Identity rule: never invent names for observed people. If a person is unknown and identity matters, ask the user or the person for their name, then store the association with visual_memory(action='enroll', image='<face crop>', name='<name>') when a face crop is available, or multimodal_memory(action='meet', person_name='<name>') when live voice/name context is available.");
603683
603872
  lines.push(`Snapshot file: ${liveSnapshotPath(snapshot.repoRoot)}`);
603684
603873
  lines.push(`Streams: video=${snapshot.config.videoEnabled ? "on" : "off"} infer=${snapshot.config.inferEnabled ? "on" : "off"} clip=${snapshot.config.clipEnabled ? "on" : "off"} audio=${snapshot.config.audioEnabled ? "on" : "off"} asr=${snapshot.config.asrEnabled ? "on" : "off"} sounds=${snapshot.config.audioAnalysisEnabled ? "on" : "off"} output-monitor=${snapshot.config.audioOutputEnabled ? "on" : "off"}`);
@@ -603825,6 +604014,16 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
603825
604014
  function formatLiveSensorContext(repoRoot) {
603826
604015
  return formatLiveSensorContextFromSnapshot(readLiveSensorSnapshot(repoRoot));
603827
604016
  }
604017
+ function isLiveSensorSnapshotActive(snapshot) {
604018
+ const cfg = snapshot?.config;
604019
+ if (!cfg) return false;
604020
+ return Boolean(
604021
+ cfg.videoEnabled || cfg.audioEnabled || cfg.audioOutputEnabled || cfg.inferEnabled || cfg.clipEnabled || cfg.asrEnabled || cfg.audioAnalysisEnabled
604022
+ );
604023
+ }
604024
+ function isLiveSensorActiveForRepo(repoRoot) {
604025
+ return isLiveSensorSnapshotActive(readLiveSensorSnapshot(repoRoot));
604026
+ }
603828
604027
  async function discoverAudioInputs() {
603829
604028
  const devices = [];
603830
604029
  const errors = [];
@@ -603958,31 +604157,101 @@ print(json.dumps({"ok": True, "scores": scores}))
603958
604157
  async function tryRecordAudioDevice(device, durationSec, outputPath3) {
603959
604158
  await recordAudioSample(device, durationSec, outputPath3);
603960
604159
  }
604160
+ function hasLiveAudioSignal(activity) {
604161
+ if (!activity) return false;
604162
+ return activity.peak >= 0.025 || activity.rmsDb > -48 || activity.activeRatio >= 0.025;
604163
+ }
604164
+ function audioCaptureMethod(device) {
604165
+ return device.startsWith("pulse:") ? "pulse/ffmpeg" : "arecord";
604166
+ }
603961
604167
  async function recordAudioSampleWithRecovery(preferred, devices, durationSec, outputPath3) {
603962
604168
  const ordered = [];
603963
604169
  const add3 = (id2) => {
603964
604170
  const clean5 = String(id2 ?? "").trim();
603965
604171
  if (clean5 && !ordered.includes(clean5)) ordered.push(clean5);
603966
604172
  };
603967
- add3(preferred);
603968
- for (const device of devices.filter((entry) => entry.source === "pulse" || entry.source === "pipewire")) add3(device.id);
604173
+ const preferredInput = preferredLiveAudioInputId(devices, preferred);
604174
+ add3(preferredInput);
604175
+ if (preferred && !isAudioOutputMonitorId(preferred)) add3(preferred);
604176
+ for (const device of rankLiveAudioInputDevices(devices, preferred).filter((entry) => !isAudioOutputMonitorDevice(entry))) add3(device.id);
603969
604177
  add3("pulse:default");
603970
604178
  add3("default");
603971
- for (const device of devices.filter((entry) => entry.source !== "pulse" && entry.source !== "pipewire")) add3(device.id);
604179
+ for (const device of rankLiveAudioInputDevices(devices, preferred).filter((entry) => isAudioOutputMonitorDevice(entry))) add3(device.id);
603972
604180
  const errors = [];
604181
+ const attempts = [];
604182
+ let firstQuietCapture = null;
603973
604183
  for (const candidate of ordered) {
604184
+ attempts.push(candidate);
603974
604185
  try {
603975
604186
  await tryRecordAudioDevice(candidate, durationSec, outputPath3);
603976
- return { ok: true, device: candidate, method: candidate.startsWith("pulse:") ? "pulse/ffmpeg" : "arecord", errors };
604187
+ const activity = readPcm16WavActivity(outputPath3);
604188
+ const method = audioCaptureMethod(candidate);
604189
+ if (hasLiveAudioSignal(activity)) {
604190
+ return { ok: true, device: candidate, method, errors, attempts, activity, signalDetected: true };
604191
+ }
604192
+ if (!firstQuietCapture) {
604193
+ firstQuietCapture = {
604194
+ device: candidate,
604195
+ method,
604196
+ activity,
604197
+ bytes: readFileSync88(outputPath3),
604198
+ errors: [...errors]
604199
+ };
604200
+ }
604201
+ errors.push(`${candidate}: captured but no live input signal detected`);
603977
604202
  } catch (err) {
603978
604203
  errors.push(`${candidate}: ${err instanceof Error ? err.message : String(err)}`.slice(0, 360));
603979
604204
  }
603980
604205
  }
603981
- return { ok: false, device: preferred || "default", method: "none", errors };
604206
+ if (firstQuietCapture) {
604207
+ writeFileSync56(outputPath3, firstQuietCapture.bytes);
604208
+ return {
604209
+ ok: true,
604210
+ device: firstQuietCapture.device,
604211
+ method: firstQuietCapture.method,
604212
+ errors,
604213
+ attempts,
604214
+ activity: firstQuietCapture.activity,
604215
+ signalDetected: false
604216
+ };
604217
+ }
604218
+ return { ok: false, device: preferredInput || preferred || "default", method: "none", errors, attempts };
603982
604219
  }
603983
604220
  function firstDeviceId(devices) {
603984
604221
  return devices.find((device) => device.id && !/metadata\/non-capture/i.test(device.detail ?? ""))?.id ?? devices[0]?.id;
603985
604222
  }
604223
+ function isAudioOutputMonitorId(id2) {
604224
+ return /(?:\.monitor\b|sink\.monitor|alsa_output|output\.monitor)/i.test(String(id2 ?? ""));
604225
+ }
604226
+ function isAudioOutputMonitorDevice(device) {
604227
+ if (!device) return false;
604228
+ return isAudioOutputMonitorId(device.id) || isAudioOutputMonitorId(device.label) || isAudioOutputMonitorId(device.detail);
604229
+ }
604230
+ function preferredLiveAudioInputId(devices, configured) {
604231
+ const usable = devices.filter((device) => device.id && !isAudioOutputMonitorDevice(device));
604232
+ if (configured && usable.some((device) => device.id === configured)) return configured;
604233
+ return rankLiveAudioInputDevices(devices, configured)[0]?.id ?? firstDeviceId(devices);
604234
+ }
604235
+ function liveAudioDeviceRank(device, configured) {
604236
+ let rank = 0;
604237
+ if (device.id === configured) rank -= 40;
604238
+ if (isAudioOutputMonitorDevice(device)) rank += 500;
604239
+ if (device.source === "pulse" || device.source === "pipewire") rank -= 20;
604240
+ if (device.source === "alsa") rank -= 10;
604241
+ if (device.source === "default") rank += 30;
604242
+ if (/metadata\/non-capture/i.test(device.detail ?? "")) rank += 300;
604243
+ return rank;
604244
+ }
604245
+ function rankLiveAudioInputDevices(devices, configured) {
604246
+ const seen = /* @__PURE__ */ new Set();
604247
+ const ranked = [];
604248
+ for (const device of devices) {
604249
+ if (!device.id || seen.has(device.id)) continue;
604250
+ seen.add(device.id);
604251
+ ranked.push(device);
604252
+ }
604253
+ return ranked.sort((a2, b) => liveAudioDeviceRank(a2, configured) - liveAudioDeviceRank(b, configured));
604254
+ }
603986
604255
  function getLiveSensorManager(repoRoot) {
603987
604256
  const key = repoRoot;
603988
604257
  let manager = managers.get(key);
@@ -604076,6 +604345,13 @@ var init_live_sensors = __esm({
604076
604345
  lastAgentReviewAt = 0;
604077
604346
  autoOrientationInFlight = /* @__PURE__ */ new Set();
604078
604347
  lastFeedbackAt = /* @__PURE__ */ new Map();
604348
+ lastClipAt = /* @__PURE__ */ new Map();
604349
+ nextVideoSourceIndex = 0;
604350
+ liveSpeechSink = null;
604351
+ liveSpeechEnabled = false;
604352
+ lastLiveSpeechAt = 0;
604353
+ lastLiveSpeechByKey = /* @__PURE__ */ new Map();
604354
+ liveSpeechInFlight = /* @__PURE__ */ new Set();
604079
604355
  intakeAgentConfig;
604080
604356
  setStatusSink(sink) {
604081
604357
  this.statusSink = sink ?? null;
@@ -604087,6 +604363,12 @@ var init_live_sensors = __esm({
604087
604363
  setAgentActionSink(sink) {
604088
604364
  this.agentActionSink = sink ?? null;
604089
604365
  }
604366
+ setLiveSpeechSink(sink) {
604367
+ this.liveSpeechSink = sink ?? null;
604368
+ }
604369
+ setLiveSpeechEnabled(enabled2) {
604370
+ this.liveSpeechEnabled = enabled2;
604371
+ }
604090
604372
  setAudioIntakeAgentConfig(config) {
604091
604373
  this.intakeAgentConfig = config && config.backendUrl && config.model ? { backendUrl: config.backendUrl, model: config.model, apiKey: config.apiKey } : void 0;
604092
604374
  }
@@ -604182,16 +604464,14 @@ var init_live_sensors = __esm({
604182
604464
  if (!framePath) return `Camera orientation detection captured ${target}, but no frame path was returned.`;
604183
604465
  const cvScores = await scoreCameraOrientationWithOpenCv(framePath);
604184
604466
  const cvDecision = cvScores ? chooseCameraOrientationFromScores(cvScores) : null;
604185
- if (cvDecision && cvDecision.confidence >= 0.68) {
604186
- const orientation2 = this.setCameraRotation(target, cvDecision.rotation, "auto", cvDecision.reason, cvDecision.confidence);
604187
- return orientation2 ? `Auto orientation for ${target}: ${describeCameraOrientation(orientation2)}` : `Auto orientation for ${target} could not be persisted.`;
604188
- }
604189
604467
  const prompt = [
604190
604468
  "Determine whether this camera frame is upright for a human viewer.",
604191
604469
  "Return only JSON with this schema:",
604192
604470
  '{"rotation_degrees":0|90|180|270,"confidence":0.0-1.0,"reason":"brief visual evidence"}',
604193
604471
  "rotation_degrees is the clockwise correction to apply to future frames so they are upright.",
604194
- "Use 90 if the image must rotate clockwise; use 270 if it must rotate counter-clockwise; use 180 if upside down; use 0 if already upright or inconclusive."
604472
+ "Use 90 if the image must rotate clockwise; use 270 if it must rotate counter-clockwise; use 180 if upside down; use 0 if already upright or inconclusive.",
604473
+ "If the visible scene is sideways, choose the correction direction that makes people, faces, screens, and text upright; if the opposite direction would make them sideways, do not choose it.",
604474
+ cvDecision ? `Computer-vision candidate: ${formatCameraRotation(cvDecision.rotation)} confidence=${cvDecision.confidence.toFixed(2)} evidence=${cvDecision.reason}` : "Computer-vision candidate: unavailable."
604195
604475
  ].join("\n");
604196
604476
  const vision = await new VisionTool(this.repoRoot).execute({
604197
604477
  action: "query",
@@ -604214,9 +604494,10 @@ var init_live_sensors = __esm({
604214
604494
  }
604215
604495
  return `Camera orientation detection for ${target} was inconclusive. Vision output did not contain a structured rotation decision.`;
604216
604496
  }
604217
- const useCv = cvDecision && (cvDecision.confidence > (decision2.confidence ?? 0) + 0.12 || cvDecision.rotation !== decision2.rotation && cvDecision.confidence >= 0.62 && (decision2.confidence ?? 0) < 0.76);
604218
- const selected = useCv ? cvDecision : decision2;
604219
- const evidence = useCv ? cvDecision.reason : `${decision2.reason || boundedText(vision.llmContent || vision.output, 320)}${cvDecision ? `; cv=${cvDecision.reason}` : ""}`;
604497
+ const visionConfidence = decision2.confidence ?? 0;
604498
+ const useCv = Boolean(cvDecision) && (visionConfidence < 0.55 || cvDecision.rotation === decision2.rotation && cvDecision.confidence > visionConfidence + 0.18);
604499
+ const selected = useCv && cvDecision ? cvDecision : decision2;
604500
+ const evidence = useCv ? cvDecision?.reason ?? "" : `${decision2.reason || boundedText(vision.llmContent || vision.output, 320)}${cvDecision ? `; cv=${cvDecision.reason}` : ""}`;
604220
604501
  const orientation = this.setCameraRotation(target, selected.rotation, "auto", evidence, selected.confidence);
604221
604502
  return orientation ? `Auto orientation for ${target}: ${describeCameraOrientation(orientation)}` : `Auto orientation for ${target} could not be persisted.`;
604222
604503
  } finally {
@@ -604245,7 +604526,10 @@ var init_live_sensors = __esm({
604245
604526
  errors
604246
604527
  };
604247
604528
  if (!this.config.selectedCamera) this.config.selectedCamera = firstDeviceId(video);
604248
- if (!this.config.selectedAudioInput) this.config.selectedAudioInput = firstDeviceId(inputs.devices);
604529
+ const preferredInput = preferredLiveAudioInputId(inputs.devices, this.config.selectedAudioInput);
604530
+ if (!this.config.selectedAudioInput || isAudioOutputMonitorId(this.config.selectedAudioInput) && preferredInput && !isAudioOutputMonitorId(preferredInput)) {
604531
+ this.config.selectedAudioInput = preferredInput;
604532
+ }
604249
604533
  if (!this.config.selectedAudioOutput) this.config.selectedAudioOutput = firstDeviceId(outputs.devices);
604250
604534
  this.persist();
604251
604535
  return this.devices;
@@ -604265,6 +604549,7 @@ var init_live_sensors = __esm({
604265
604549
  }
604266
604550
  stopAll() {
604267
604551
  this.agentActionEnabled = false;
604552
+ this.liveSpeechEnabled = false;
604268
604553
  this.configure({
604269
604554
  videoEnabled: false,
604270
604555
  audioEnabled: false,
@@ -604277,6 +604562,7 @@ var init_live_sensors = __esm({
604277
604562
  }
604278
604563
  startRunMode(options2 = {}) {
604279
604564
  this.agentActionEnabled = Boolean(options2.agent);
604565
+ this.liveSpeechEnabled = Boolean(options2.speech ?? options2.agent);
604280
604566
  this.configure({
604281
604567
  videoEnabled: true,
604282
604568
  inferEnabled: true,
@@ -604308,7 +604594,7 @@ var init_live_sensors = __esm({
604308
604594
  }, 6e4);
604309
604595
  });
604310
604596
  }
604311
- void this.sampleVideoNow(true).catch((err) => {
604597
+ void this.sampleVideoNow(true, { mode: "all" }).catch((err) => {
604312
604598
  this.emitFeedback({
604313
604599
  kind: "error",
604314
604600
  title: "Live video sample failed",
@@ -604503,17 +604789,18 @@ var init_live_sensors = __esm({
604503
604789
  const sampledAt = Date.now();
604504
604790
  if (forceInfer && source) {
604505
604791
  const loop = new LiveMediaLoopTool(this.repoRoot);
604792
+ const inferFromCapturedFrame = preview.ok && Boolean(preview.framePath);
604506
604793
  const result = await loop.execute({
604507
604794
  action: "watch",
604508
- source_kind: "camera",
604509
- camera: source,
604795
+ source_kind: inferFromCapturedFrame ? "file" : "camera",
604796
+ ...inferFromCapturedFrame ? { path: preview.framePath } : { camera: source },
604510
604797
  yolo_family: "yoloe-26",
604511
604798
  yolo_scale: "n",
604512
604799
  yoloe_prompt_mode: "prompt_free",
604513
- duration_sec: 0.75,
604514
- sample_interval_sec: 0.25,
604800
+ duration_sec: inferFromCapturedFrame ? 0.1 : 0.75,
604801
+ sample_interval_sec: inferFromCapturedFrame ? 0.1 : 0.25,
604515
604802
  max_frames: 1,
604516
- rotate_degrees: orientation?.rotation ?? 0,
604803
+ rotate_degrees: inferFromCapturedFrame ? 0 : orientation?.rotation ?? 0,
604517
604804
  auto_bootstrap: true,
604518
604805
  audio_mode: "none",
604519
604806
  detect_objects: true,
@@ -604526,6 +604813,7 @@ var init_live_sensors = __esm({
604526
604813
  });
604527
604814
  inference = result.success ? String(result.output || result.llmContent || "") : `Video inference failed: ${result.error || result.output || "unknown error"}`;
604528
604815
  const livePayload = result.success ? parseLiveMediaPayload(result.llmContent || result.output) : null;
604816
+ if (livePayload) livePayload.source = source;
604529
604817
  const observations = observationsFromLiveMediaPayload(livePayload, sampledAt, source);
604530
604818
  const classifications = classificationsFromLiveMediaPayload(livePayload, sampledAt, source);
604531
604819
  this.snapshot.events = mergeRecentById(this.snapshot.events, observations.events, 50);
@@ -604562,6 +604850,16 @@ var init_live_sensors = __esm({
604562
604850
  `${unknownPeople.length} unknown individual(s) observed on live camera ${source}`,
604563
604851
  unknownPeople.map((entry) => `${entry.cropPath || entry.trackId || entry.source} ${entry.evidence || ""}`).join("; ")
604564
604852
  );
604853
+ this.maybeSpeakLive({
604854
+ key: `unknown-person:${source}`,
604855
+ kind: "unknown_person",
604856
+ source,
604857
+ summary: `${unknownPeople.length} unknown individual(s) observed on live camera ${source}`,
604858
+ evidence: unknownPeople.map((entry) => `${entry.cropPath || entry.trackId || entry.source} ${entry.evidence || ""}`).join("; "),
604859
+ defaultText: "I see someone here, but I do not know who. Who is there?",
604860
+ urgency: "high",
604861
+ observedAt: sampledAt
604862
+ }, 9e4);
604565
604863
  }
604566
604864
  const triggerEvents = observations.events.filter((entry) => entry.kind === "visual_trigger");
604567
604865
  if (triggerEvents.length > 0) {
@@ -604569,9 +604867,21 @@ var init_live_sensors = __esm({
604569
604867
  `Visual trigger hit in live camera stream ${source}`,
604570
604868
  triggerEvents.map((entry) => entry.summary).join("; ")
604571
604869
  );
604870
+ this.maybeSpeakLive({
604871
+ key: `visual-trigger:${source}:${triggerEvents[0]?.title ?? "trigger"}`,
604872
+ kind: "visual_trigger",
604873
+ source,
604874
+ summary: `Visual trigger hit in live camera stream ${source}`,
604875
+ evidence: triggerEvents.map((entry) => entry.summary).join("; "),
604876
+ defaultText: "I noticed something important in view. Should I inspect it?",
604877
+ urgency: "high",
604878
+ observedAt: sampledAt
604879
+ }, 45e3);
604572
604880
  }
604573
604881
  }
604574
- if (this.config.clipEnabled && preview.ok && preview.framePath && source) {
604882
+ const lastClip = this.lastClipAt.get(source) ?? 0;
604883
+ if (this.config.clipEnabled && preview.ok && preview.framePath && source && sampledAt - lastClip >= 5e3) {
604884
+ this.lastClipAt.set(source, sampledAt);
604575
604885
  clipSummary = await this.recognizeFrameObjects(preview.framePath, source, sampledAt, { emit: false });
604576
604886
  const current = this.snapshot.cameras?.[source];
604577
604887
  if (current) {
@@ -604590,14 +604900,16 @@ ${inference}` : "", clipSummary ? `
604590
604900
  CLIP visual memory:
604591
604901
  ${clipSummary}` : ""].filter(Boolean).join("\n");
604592
604902
  }
604593
- async sampleVideoNow(forceInfer = this.config.inferEnabled) {
604903
+ async sampleVideoNow(forceInfer = this.config.inferEnabled, options2 = {}) {
604594
604904
  if (this.videoInFlight) return "Video sampler is already running.";
604595
604905
  this.videoInFlight = true;
604596
604906
  try {
604597
- const sources = this.activeVideoSources();
604907
+ const allSources = this.activeVideoSources();
604908
+ if (allSources.length === 0) return "No camera selected. Use /live refresh after connecting hardware.";
604909
+ const sources = options2.mode === "round-robin" ? [allSources[this.nextVideoSourceIndex++ % allSources.length]] : allSources;
604598
604910
  if (sources.length === 0) return "No camera selected. Use /live refresh after connecting hardware.";
604599
604911
  const outputs = [];
604600
- const previewWidth = liveDashboardPreviewWidthForSources(sources.length);
604912
+ const previewWidth = liveDashboardPreviewWidthForSources(allSources.length);
604601
604913
  for (const source of sources) {
604602
604914
  outputs.push(await this.sampleSingleVideoNow(source, forceInfer, previewWidth));
604603
604915
  }
@@ -604612,7 +604924,9 @@ ${output}`).join("\n\n");
604612
604924
  if (this.audioInFlight) return "Audio sampler is already running.";
604613
604925
  this.audioInFlight = true;
604614
604926
  try {
604615
- const input = this.config.selectedAudioInput || firstDeviceId(this.devices.audioInputs) || "default";
604927
+ const preferredInput = preferredLiveAudioInputId(this.devices.audioInputs, this.config.selectedAudioInput) || "default";
604928
+ const input = this.config.selectedAudioInput && !isAudioOutputMonitorId(this.config.selectedAudioInput) ? this.config.selectedAudioInput : preferredInput;
604929
+ if (input !== this.config.selectedAudioInput) this.config.selectedAudioInput = input;
604616
604930
  const outputDir2 = liveDir(this.repoRoot);
604617
604931
  ensureLiveDir(this.repoRoot);
604618
604932
  const recordingPath = join124(outputDir2, `audio-${Date.now()}.wav`);
@@ -604643,7 +604957,10 @@ ${output}`).join("\n\n");
604643
604957
  if (capture.device !== this.config.selectedAudioInput) {
604644
604958
  this.config.selectedAudioInput = capture.device;
604645
604959
  }
604646
- const activity = readPcm16WavActivity(recordingPath);
604960
+ const activity = capture.activity ?? readPcm16WavActivity(recordingPath);
604961
+ if (capture.ok && capture.signalDetected === false) {
604962
+ audioErrors.push(`No live input signal detected after probing ${capture.attempts?.length ?? 1} audio source(s); using quiet capture from ${capture.device}`);
604963
+ }
604647
604964
  if (this.config.asrEnabled) {
604648
604965
  try {
604649
604966
  const asr = await new TranscribeFileTool(this.repoRoot).execute({ path: recordingPath, model: "tiny" });
@@ -604720,6 +605037,16 @@ ${output}`).join("\n\n");
604720
605037
  "Live audio intake classified speech as intended for Omnius",
604721
605038
  `transcript=${transcript}; confidence=${intake.confidence.toFixed(2)}; reason=${intake.reason}`
604722
605039
  );
605040
+ this.maybeSpeakLive({
605041
+ key: `audio-intent:${capture.device}:${transcript.slice(0, 48)}`,
605042
+ kind: "audio_intent",
605043
+ source: capture.device,
605044
+ summary: "Live audio intake classified speech as intended for Omnius",
605045
+ evidence: `transcript=${transcript}; confidence=${intake.confidence.toFixed(2)}; reason=${intake.reason}`,
605046
+ defaultText: "I heard you. I am checking the live context now.",
605047
+ urgency: "high",
605048
+ observedAt: this.snapshot.audio.updatedAt
605049
+ }, 2e4);
604723
605050
  }
604724
605051
  this.emitDashboard();
604725
605052
  return [
@@ -604753,7 +605080,7 @@ ${analysis}` : ""
604753
605080
  this.videoTimer = null;
604754
605081
  if (!this.config.videoEnabled) return;
604755
605082
  try {
604756
- await this.sampleVideoNow(this.config.inferEnabled || this.config.clipEnabled);
605083
+ await this.sampleVideoNow(this.config.inferEnabled || this.config.clipEnabled, { mode: "round-robin" });
604757
605084
  } catch {
604758
605085
  } finally {
604759
605086
  if (this.config.videoEnabled) {
@@ -604828,7 +605155,7 @@ ${analysis}` : ""
604828
605155
  maybeRequestAgentReview(reason, evidence) {
604829
605156
  if (!this.agentActionEnabled || !this.agentActionSink) return;
604830
605157
  const now2 = Date.now();
604831
- if (now2 - this.lastAgentReviewAt < 2e4) return;
605158
+ if (now2 - this.lastAgentReviewAt < 6e4) return;
604832
605159
  this.lastAgentReviewAt = now2;
604833
605160
  const prompt = [
604834
605161
  "Live environment PFC review.",
@@ -604851,6 +605178,38 @@ ${analysis}` : ""
604851
605178
  } catch {
604852
605179
  }
604853
605180
  }
605181
+ maybeSpeakLive(proposal, perKeyCooldownMs) {
605182
+ if (!this.liveSpeechEnabled || !this.liveSpeechSink) return;
605183
+ const now2 = Date.now();
605184
+ if (now2 - this.lastLiveSpeechAt < 12e3) return;
605185
+ const lastForKey = this.lastLiveSpeechByKey.get(proposal.key) ?? 0;
605186
+ if (now2 - lastForKey < perKeyCooldownMs) return;
605187
+ if (this.liveSpeechInFlight.has(proposal.key)) return;
605188
+ this.liveSpeechInFlight.add(proposal.key);
605189
+ void classifyLiveSpeech(proposal, this.intakeAgentConfig).then((decision2) => {
605190
+ this.lastLiveSpeechByKey.set(proposal.key, Date.now());
605191
+ if (!decision2.speak || !decision2.text.trim()) return;
605192
+ this.lastLiveSpeechAt = Date.now();
605193
+ const text2 = trimOneLine(decision2.text, 180);
605194
+ this.liveSpeechSink?.(text2, decision2);
605195
+ this.emitFeedbackThrottled(`speech:${proposal.key}`, {
605196
+ kind: "speech",
605197
+ title: "Live speech emitted",
605198
+ observedAt: decision2.updatedAt,
605199
+ message: `${text2}
605200
+ reason=${decision2.reason} confidence=${decision2.confidence.toFixed(2)} source=${decision2.source}`
605201
+ }, 15e3);
605202
+ }).catch((err) => {
605203
+ this.emitFeedbackThrottled(`speech-error:${proposal.key}`, {
605204
+ kind: "error",
605205
+ title: "Live speech arbiter failed",
605206
+ observedAt: Date.now(),
605207
+ message: err instanceof Error ? err.message : String(err)
605208
+ }, 6e4);
605209
+ }).finally(() => {
605210
+ this.liveSpeechInFlight.delete(proposal.key);
605211
+ });
605212
+ }
604854
605213
  persist() {
604855
605214
  ensureLiveDir(this.repoRoot);
604856
605215
  writeFileSync56(liveConfigPath(this.repoRoot), JSON.stringify(this.config, null, 2), "utf8");
@@ -639208,6 +639567,10 @@ async function showPruneMenu(options2) {
639208
639567
  });
639209
639568
  if (!result.confirmed || !result.key || result.key === "back") return;
639210
639569
  const start2 = (label, o2) => {
639570
+ if (isLiveSensorActiveForRepo(workingDir)) {
639571
+ renderWarning("Live audio/video mode is active; pruning is deferred so sensor, ASR, vision, and reasoning inference stay available. Stop live mode before running prune.");
639572
+ return;
639573
+ }
639211
639574
  renderInfo(`${label} (running in background — summary will follow)…`);
639212
639575
  runPruneWorkerOnce({
639213
639576
  repoRoot: workingDir,
@@ -639243,6 +639606,7 @@ var init_prune_menu = __esm({
639243
639606
  init_render();
639244
639607
  init_kg_prune();
639245
639608
  init_memory_maintenance();
639609
+ init_live_sensors();
639246
639610
  import_chalk2 = __toESM(require_source(), 1);
639247
639611
  }
639248
639612
  });
@@ -648242,6 +648606,7 @@ async function handleSlashCommand(input, ctx3) {
648242
648606
  case "prune": {
648243
648607
  const sub2 = (arg?.trim().split(/\s+/)[0] ?? "").toLowerCase();
648244
648608
  const workingDir = ctx3.repoRoot || process.cwd();
648609
+ const force = /\b--force\b/i.test(arg ?? "");
648245
648610
  const targetMatch = arg?.match(/--target\s+(\d+)/);
648246
648611
  const targetNodes = targetMatch ? parseInt(targetMatch[1], 10) : void 0;
648247
648612
  const { knowledgeGraphStats: knowledgeGraphStats2, formatKgStatsLine: formatKgStatsLine2 } = await Promise.resolve().then(() => (init_kg_prune(), kg_prune_exports));
@@ -648250,6 +648615,10 @@ async function handleSlashCommand(input, ctx3) {
648250
648615
  renderInfo(summary);
648251
648616
  };
648252
648617
  const startBackground = (label, o2) => {
648618
+ if (isLiveSensorActiveForRepo(workingDir) && !force) {
648619
+ renderWarning("Live audio/video mode is active; pruning is deferred so sensor, ASR, vision, and reasoning inference stay available. Stop live mode first, or rerun with --force if you intentionally want pruning to compete with live inference.");
648620
+ return;
648621
+ }
648253
648622
  renderInfo(`${label} (running in background — summary will follow)…`);
648254
648623
  runPruneWorkerOnce2({
648255
648624
  repoRoot: workingDir,
@@ -656573,6 +656942,12 @@ function attachLiveSinks(ctx3, manager, agentEnabled = false) {
656573
656942
  model: ctx3.config.model,
656574
656943
  apiKey: ctx3.config.apiKey
656575
656944
  });
656945
+ const speechEnabled = agentEnabled && Boolean(ctx3.voiceSpeak);
656946
+ manager.setLiveSpeechEnabled(speechEnabled);
656947
+ manager.setLiveSpeechSink(speechEnabled ? (text2) => {
656948
+ if (ctx3.voiceIsEnabled && !ctx3.voiceIsEnabled()) return;
656949
+ ctx3.voiceSpeak?.(text2);
656950
+ } : void 0);
656576
656951
  if (agentEnabled && ctx3.startBackgroundPrompt) {
656577
656952
  manager.setAgentActionSink((prompt) => {
656578
656953
  const id2 = ctx3.startBackgroundPrompt?.(prompt);
@@ -656586,13 +656961,14 @@ async function startLiveVoicechat(ctx3, manager) {
656586
656961
  const agentEnabled = Boolean(ctx3.startBackgroundPrompt);
656587
656962
  attachLiveSinks(ctx3, manager, agentEnabled);
656588
656963
  await ensureLiveInventory(manager);
656589
- renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true }));
656590
656964
  if (!ctx3.voiceChatStart) {
656965
+ renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true, speech: false }));
656591
656966
  renderWarning("Voice chat is not available in this context; live audio/video feedback remains active.");
656592
656967
  return;
656593
656968
  }
656594
656969
  ctx3.voiceSetMode?.("voicechat");
656595
656970
  if (ctx3.isVoiceChatActive?.()) {
656971
+ renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true, speech: Boolean(ctx3.voiceSpeak) }));
656596
656972
  renderInfo("Voice chat is already active. Live audio/video feedback remains attached.");
656597
656973
  return;
656598
656974
  }
@@ -656603,6 +656979,7 @@ async function startLiveVoicechat(ctx3, manager) {
656603
656979
  try {
656604
656980
  renderInfo("Starting live voicechat with audio/video context...");
656605
656981
  await ctx3.voiceChatStart();
656982
+ renderInfo(manager.startRunMode({ agent: agentEnabled, lowLatency: true, speech: Boolean(ctx3.voiceSpeak) }));
656606
656983
  renderInfo("Live voicechat active: low-latency audio/video context is updating while voice conversation continues.");
656607
656984
  } catch (err) {
656608
656985
  renderError(`Live voicechat failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -656663,7 +657040,7 @@ async function handleLiveCommand(ctx3, arg) {
656663
657040
  renderWarning("Live PFC agent review is unavailable in this context; starting visual/audio feedback only.");
656664
657041
  }
656665
657042
  await ensureLiveInventory(manager);
656666
- renderInfo(manager.startRunMode({ agent: effectiveAgent, lowLatency: true }));
657043
+ renderInfo(manager.startRunMode({ agent: effectiveAgent, lowLatency: true, speech: effectiveAgent && Boolean(ctx3.voiceSpeak) }));
656667
657044
  return;
656668
657045
  }
656669
657046
  if (sub2 === "chat" || sub2 === "voicechat" || sub2 === "voice") {
@@ -656918,14 +657295,14 @@ async function showLiveMenu(ctx3, manager) {
656918
657295
  continue;
656919
657296
  case "run-live":
656920
657297
  attachLiveSinks(ctx3, manager, false);
656921
- renderInfo(manager.startRunMode({ agent: false, lowLatency: true }));
657298
+ renderInfo(manager.startRunMode({ agent: false, lowLatency: true, speech: false }));
656922
657299
  continue;
656923
657300
  case "run-agent":
656924
657301
  attachLiveSinks(ctx3, manager, true);
656925
657302
  if (!ctx3.startBackgroundPrompt) {
656926
657303
  renderWarning("Live PFC agent review is unavailable in this context; starting visual/audio feedback only.");
656927
657304
  }
656928
- renderInfo(manager.startRunMode({ agent: Boolean(ctx3.startBackgroundPrompt), lowLatency: true }));
657305
+ renderInfo(manager.startRunMode({ agent: Boolean(ctx3.startBackgroundPrompt), lowLatency: true, speech: Boolean(ctx3.startBackgroundPrompt && ctx3.voiceSpeak) }));
656929
657306
  continue;
656930
657307
  case "run-chat":
656931
657308
  await startLiveVoicechat(ctx3, manager);
@@ -732153,9 +732530,10 @@ Rationale: ${proposal.rationale}${provenanceNote}${dmnDevDiscipline(proposal.cat
732153
732530
  }, delayMs);
732154
732531
  }
732155
732532
  let activeTask = null;
732533
+ const liveSensorsBusy = () => isLiveSensorActiveForRepo(repoRoot);
732156
732534
  idleMemoryMaintenance = startIdleMemoryMaintenance({
732157
732535
  repoRoot,
732158
- isBusy: () => Boolean(activeTask) || _interactiveSessionActive,
732536
+ isBusy: () => Boolean(activeTask) || _interactiveSessionActive || liveSensorsBusy(),
732159
732537
  onSummary: (summary) => {
732160
732538
  if (!summary) return;
732161
732539
  writeContent(() => renderInfo(summary));
@@ -734709,6 +735087,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
734709
735087
  },
734710
735088
  setLiveMediaStatus(status) {
734711
735089
  statusBar.setLiveMediaStatus(status);
735090
+ if (status.audio || status.video) idleMemoryMaintenance?.notifyActivity();
734712
735091
  },
734713
735092
  liveDynamicBlockHost: {
734714
735093
  registerDynamicBlock: (id2, render2) => statusBar.registerDynamicBlock(id2, render2),
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.411",
3
+ "version": "1.0.412",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.411",
9
+ "version": "1.0.412",
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.411",
3
+ "version": "1.0.412",
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",