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 +423 -44
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
603968
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
604218
|
-
const
|
|
604219
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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 <
|
|
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),
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnius",
|
|
3
|
-
"version": "1.0.
|
|
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.
|
|
9
|
+
"version": "1.0.412",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"image-to-ascii"
|
|
12
12
|
],
|
package/package.json
CHANGED