omnius 1.0.430 → 1.0.431
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 +259 -102
- package/dist/scripts/live-whisper.py +78 -11
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -330392,7 +330392,7 @@ ${lanes.join("\n")}
|
|
|
330392
330392
|
}
|
|
330393
330393
|
return false;
|
|
330394
330394
|
}
|
|
330395
|
-
function createPropertyDescriptor(attributes,
|
|
330395
|
+
function createPropertyDescriptor(attributes, singleLine2) {
|
|
330396
330396
|
const properties = [];
|
|
330397
330397
|
tryAddPropertyAssignment(properties, "enumerable", asExpression(attributes.enumerable));
|
|
330398
330398
|
tryAddPropertyAssignment(properties, "configurable", asExpression(attributes.configurable));
|
|
@@ -330401,7 +330401,7 @@ ${lanes.join("\n")}
|
|
|
330401
330401
|
let isAccessor2 = tryAddPropertyAssignment(properties, "get", attributes.get);
|
|
330402
330402
|
isAccessor2 = tryAddPropertyAssignment(properties, "set", attributes.set) || isAccessor2;
|
|
330403
330403
|
Debug.assert(!(isData && isAccessor2), "A PropertyDescriptor may not be both an accessor descriptor and a data descriptor.");
|
|
330404
|
-
return createObjectLiteralExpression(properties, !
|
|
330404
|
+
return createObjectLiteralExpression(properties, !singleLine2);
|
|
330405
330405
|
}
|
|
330406
330406
|
function updateOuterExpression(outerExpression, expression) {
|
|
330407
330407
|
switch (outerExpression.kind) {
|
|
@@ -343871,9 +343871,9 @@ ${lanes.join("\n")}
|
|
|
343871
343871
|
}
|
|
343872
343872
|
return;
|
|
343873
343873
|
}
|
|
343874
|
-
const
|
|
343875
|
-
if (
|
|
343876
|
-
return addPragmaForMatch(pragmas, range, 2,
|
|
343874
|
+
const singleLine2 = range.kind === 2 && singleLinePragmaRegEx.exec(text2);
|
|
343875
|
+
if (singleLine2) {
|
|
343876
|
+
return addPragmaForMatch(pragmas, range, 2, singleLine2);
|
|
343877
343877
|
}
|
|
343878
343878
|
if (range.kind === 3) {
|
|
343879
343879
|
const multiLinePragmaRegEx = /@(\S+)(\s+(?:\S.*)?)?$/gm;
|
|
@@ -419394,7 +419394,7 @@ ${lanes.join("\n")}
|
|
|
419394
419394
|
}
|
|
419395
419395
|
function transformFunctionBody2(node) {
|
|
419396
419396
|
let multiLine = false;
|
|
419397
|
-
let
|
|
419397
|
+
let singleLine2 = false;
|
|
419398
419398
|
let statementsLocation;
|
|
419399
419399
|
let closeBraceLocation;
|
|
419400
419400
|
const prologue = [];
|
|
@@ -419436,7 +419436,7 @@ ${lanes.join("\n")}
|
|
|
419436
419436
|
const equalsGreaterThanToken = node.equalsGreaterThanToken;
|
|
419437
419437
|
if (!nodeIsSynthesized(equalsGreaterThanToken) && !nodeIsSynthesized(body)) {
|
|
419438
419438
|
if (rangeEndIsOnSameLineAsRangeStart(equalsGreaterThanToken, body, currentSourceFile)) {
|
|
419439
|
-
|
|
419439
|
+
singleLine2 = true;
|
|
419440
419440
|
} else {
|
|
419441
419441
|
multiLine = true;
|
|
419442
419442
|
}
|
|
@@ -419465,7 +419465,7 @@ ${lanes.join("\n")}
|
|
|
419465
419465
|
}
|
|
419466
419466
|
const block = factory2.createBlock(setTextRange(factory2.createNodeArray(statements), statementsLocation), multiLine);
|
|
419467
419467
|
setTextRange(block, node.body);
|
|
419468
|
-
if (!multiLine &&
|
|
419468
|
+
if (!multiLine && singleLine2) {
|
|
419469
419469
|
setEmitFlags(
|
|
419470
419470
|
block,
|
|
419471
419471
|
1
|
|
@@ -603393,6 +603393,10 @@ function defaultConsensusModel(primaryModel) {
|
|
|
603393
603393
|
if (model === "large-v3" || model === "large") return "small";
|
|
603394
603394
|
return "base";
|
|
603395
603395
|
}
|
|
603396
|
+
function asrConsensusEnabled() {
|
|
603397
|
+
const consensusEnv = String(process.env["OMNIUS_ASR_CONSENSUS"] ?? "").trim().toLowerCase();
|
|
603398
|
+
return !(consensusEnv === "0" || consensusEnv === "off" || consensusEnv === "false");
|
|
603399
|
+
}
|
|
603396
603400
|
async function ensureVenvForTranscribeCli() {
|
|
603397
603401
|
const bin = process.platform === "win32" ? "Scripts" : "bin";
|
|
603398
603402
|
const exe = process.platform === "win32" ? "python.exe" : "python3";
|
|
@@ -603895,8 +603899,19 @@ var init_listen = __esm({
|
|
|
603895
603899
|
updateListenLiveState({ phase: "error", lastStatus: "no mic capture tool (arecord/sox/ffmpeg)" });
|
|
603896
603900
|
return "No microphone capture tool found. Install arecord (Linux), sox (macOS), or ffmpeg.";
|
|
603897
603901
|
}
|
|
603898
|
-
|
|
603899
|
-
if (
|
|
603902
|
+
const preferWhisperFallback = asrConsensusEnabled();
|
|
603903
|
+
if (preferWhisperFallback) {
|
|
603904
|
+
this.emit(
|
|
603905
|
+
"info",
|
|
603906
|
+
"ASR consensus enabled — using live-whisper.py (CUDA when available)."
|
|
603907
|
+
);
|
|
603908
|
+
updateListenLiveState({
|
|
603909
|
+
backend: "openai-whisper",
|
|
603910
|
+
lastStatus: "starting consensus Whisper backend..."
|
|
603911
|
+
});
|
|
603912
|
+
}
|
|
603913
|
+
let tc = preferWhisperFallback ? null : await this.loadTranscribeCli();
|
|
603914
|
+
if (!preferWhisperFallback && !tc) {
|
|
603900
603915
|
if (_bgInstallPromise) {
|
|
603901
603916
|
this.emit("info", "Waiting for transcribe-cli install...");
|
|
603902
603917
|
await _bgInstallPromise;
|
|
@@ -604174,13 +604189,14 @@ transcribe-cli error: ${transcribeCliError}` : "";
|
|
|
604174
604189
|
* Caller is responsible for calling .stop() when done.
|
|
604175
604190
|
*/
|
|
604176
604191
|
async createCallTranscriber() {
|
|
604177
|
-
|
|
604178
|
-
|
|
604192
|
+
const requireConsensus = asrConsensusEnabled();
|
|
604193
|
+
let tc = requireConsensus ? null : await this.loadTranscribeCli();
|
|
604194
|
+
if (!requireConsensus && !tc && _bgInstallPromise) {
|
|
604179
604195
|
await _bgInstallPromise;
|
|
604180
604196
|
this.transcribeCliAvailable = null;
|
|
604181
604197
|
tc = await this.loadTranscribeCli();
|
|
604182
604198
|
}
|
|
604183
|
-
if (!tc) {
|
|
604199
|
+
if (!requireConsensus && !tc) {
|
|
604184
604200
|
try {
|
|
604185
604201
|
await ensureManagedTranscribeCliNode();
|
|
604186
604202
|
this.transcribeCliAvailable = null;
|
|
@@ -604188,7 +604204,7 @@ transcribe-cli error: ${transcribeCliError}` : "";
|
|
|
604188
604204
|
} catch {
|
|
604189
604205
|
}
|
|
604190
604206
|
}
|
|
604191
|
-
if (tc?.TranscribeLive && await ensureVenvForTranscribeCli()) {
|
|
604207
|
+
if (!requireConsensus && tc?.TranscribeLive && await ensureVenvForTranscribeCli()) {
|
|
604192
604208
|
try {
|
|
604193
604209
|
const transcriber = new tc.TranscribeLive({
|
|
604194
604210
|
model: this.config.model,
|
|
@@ -617275,6 +617291,7 @@ let micStream = null;
|
|
|
617275
617291
|
let scriptProcessor = null;
|
|
617276
617292
|
let micActive = false;
|
|
617277
617293
|
let playbackQueue = [];
|
|
617294
|
+
let pendingTtsHeader = null;
|
|
617278
617295
|
let isPlaying = false;
|
|
617279
617296
|
let reconnectDelay = 1000;
|
|
617280
617297
|
let reconnectTimer = null;
|
|
@@ -617334,10 +617351,14 @@ function connect() {
|
|
|
617334
617351
|
waveState = 'speaking';
|
|
617335
617352
|
} else if (msg.type === 'speaking_end') {
|
|
617336
617353
|
waveState = micActive ? 'listening' : 'idle';
|
|
617354
|
+
} else if (msg.type === 'tts_header') {
|
|
617355
|
+
pendingTtsHeader = { sampleRate: msg.sampleRate || 22050 };
|
|
617337
617356
|
}
|
|
617338
617357
|
} catch {}
|
|
617339
617358
|
} else {
|
|
617340
|
-
|
|
617359
|
+
const sampleRate = (pendingTtsHeader && pendingTtsHeader.sampleRate) || 22050;
|
|
617360
|
+
pendingTtsHeader = null;
|
|
617361
|
+
playbackQueue.push({ pcm: new Int16Array(evt.data), sampleRate });
|
|
617341
617362
|
if (!isPlaying) drainPlayback();
|
|
617342
617363
|
}
|
|
617343
617364
|
};
|
|
@@ -617356,15 +617377,17 @@ function escapeHtml(s) {
|
|
|
617356
617377
|
}
|
|
617357
617378
|
|
|
617358
617379
|
async function drainPlayback() {
|
|
617359
|
-
if (!playbackCtx) playbackCtx = new AudioContext(
|
|
617380
|
+
if (!playbackCtx) playbackCtx = new AudioContext();
|
|
617360
617381
|
isPlaying = true;
|
|
617361
617382
|
while (playbackQueue.length > 0) {
|
|
617362
|
-
const
|
|
617383
|
+
const frame = playbackQueue.shift();
|
|
617384
|
+
const int16 = frame.pcm || frame;
|
|
617385
|
+
const sampleRate = frame.sampleRate || 22050;
|
|
617363
617386
|
const float32 = new Float32Array(int16.length);
|
|
617364
617387
|
for (let i = 0; i < int16.length; i++) {
|
|
617365
617388
|
float32[i] = int16[i] < 0 ? int16[i] / 0x8000 : int16[i] / 0x7FFF;
|
|
617366
617389
|
}
|
|
617367
|
-
const buf = playbackCtx.createBuffer(1, float32.length,
|
|
617390
|
+
const buf = playbackCtx.createBuffer(1, float32.length, sampleRate);
|
|
617368
617391
|
buf.getChannelData(0).set(float32);
|
|
617369
617392
|
const src = playbackCtx.createBufferSource();
|
|
617370
617393
|
src.buffer = buf;
|
|
@@ -618013,11 +618036,15 @@ var init_voice_session = __esm({
|
|
|
618013
618036
|
/**
|
|
618014
618037
|
* Send TTS audio (PCM Int16 @ model sample rate) to all connected clients.
|
|
618015
618038
|
*/
|
|
618016
|
-
sendAudioToClients(pcmInt16) {
|
|
618039
|
+
sendAudioToClients(pcmInt16, sampleRate = 22050) {
|
|
618017
618040
|
this.ttsSpeaking = true;
|
|
618041
|
+
const header = JSON.stringify({ type: "tts_header", sampleRate, bytes: pcmInt16.length });
|
|
618018
618042
|
for (const ws of this.wsClients.values()) {
|
|
618019
618043
|
try {
|
|
618020
|
-
if (ws.readyState === import_websocket5.default.OPEN)
|
|
618044
|
+
if (ws.readyState === import_websocket5.default.OPEN) {
|
|
618045
|
+
ws.send(header);
|
|
618046
|
+
ws.send(pcmInt16);
|
|
618047
|
+
}
|
|
618021
618048
|
} catch {
|
|
618022
618049
|
}
|
|
618023
618050
|
}
|
|
@@ -618025,11 +618052,15 @@ var init_voice_session = __esm({
|
|
|
618025
618052
|
/**
|
|
618026
618053
|
* Send TTS audio to a specific client by clientId.
|
|
618027
618054
|
*/
|
|
618028
|
-
sendAudioToClient(clientId, pcmInt16) {
|
|
618055
|
+
sendAudioToClient(clientId, pcmInt16, sampleRate = 22050) {
|
|
618056
|
+
this.ttsSpeaking = true;
|
|
618029
618057
|
const ws = this.wsClients.get(clientId);
|
|
618030
618058
|
if (ws) {
|
|
618031
618059
|
try {
|
|
618032
|
-
if (ws.readyState === import_websocket5.default.OPEN)
|
|
618060
|
+
if (ws.readyState === import_websocket5.default.OPEN) {
|
|
618061
|
+
ws.send(JSON.stringify({ type: "tts_header", sampleRate, bytes: pcmInt16.length }));
|
|
618062
|
+
ws.send(pcmInt16);
|
|
618063
|
+
}
|
|
618033
618064
|
} catch {
|
|
618034
618065
|
}
|
|
618035
618066
|
}
|
|
@@ -618038,6 +618069,7 @@ var init_voice_session = __esm({
|
|
|
618038
618069
|
* Send speaking state change to a specific client.
|
|
618039
618070
|
*/
|
|
618040
618071
|
sendSpeakingStateToClient(clientId, speaking) {
|
|
618072
|
+
this.ttsSpeaking = speaking;
|
|
618041
618073
|
const ws = this.wsClients.get(clientId);
|
|
618042
618074
|
if (ws) {
|
|
618043
618075
|
const msg = JSON.stringify({ type: speaking ? "speaking_start" : "speaking_end" });
|
|
@@ -673216,6 +673248,147 @@ var init_emotion_engine = __esm({
|
|
|
673216
673248
|
}
|
|
673217
673249
|
});
|
|
673218
673250
|
|
|
673251
|
+
// packages/cli/src/tui/conversation-context.ts
|
|
673252
|
+
import { statfsSync as statfsSync8 } from "node:fs";
|
|
673253
|
+
function buildConversationRuntimeContext(now2 = /* @__PURE__ */ new Date(), repoRoot) {
|
|
673254
|
+
const date = new Intl.DateTimeFormat("en-US", {
|
|
673255
|
+
weekday: "long",
|
|
673256
|
+
year: "numeric",
|
|
673257
|
+
month: "long",
|
|
673258
|
+
day: "numeric"
|
|
673259
|
+
}).format(now2);
|
|
673260
|
+
const time = new Intl.DateTimeFormat("en-US", {
|
|
673261
|
+
hour: "numeric",
|
|
673262
|
+
minute: "2-digit",
|
|
673263
|
+
second: "2-digit",
|
|
673264
|
+
timeZoneName: "short"
|
|
673265
|
+
}).format(now2);
|
|
673266
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || process.env["TZ"] || "system";
|
|
673267
|
+
let diskLine = "";
|
|
673268
|
+
if (repoRoot) {
|
|
673269
|
+
try {
|
|
673270
|
+
const stats = statfsSync8(repoRoot);
|
|
673271
|
+
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
673272
|
+
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
673273
|
+
const usedBytes = totalBytes - freeBytes;
|
|
673274
|
+
const totalGB = Math.round(totalBytes / 1024 ** 3);
|
|
673275
|
+
const freeGB = Math.round(freeBytes / 1024 ** 3);
|
|
673276
|
+
const usedGB = Math.round(usedBytes / 1024 ** 3);
|
|
673277
|
+
const usedPct = totalBytes > 0 ? Math.round(usedBytes / totalBytes * 100) : 0;
|
|
673278
|
+
diskLine = `Disk: disk_available_gb=${freeGB} disk_used_gb=${usedGB} disk_total_gb=${totalGB} disk_used_pct=${usedPct} path=${repoRoot}`;
|
|
673279
|
+
} catch {
|
|
673280
|
+
}
|
|
673281
|
+
}
|
|
673282
|
+
return [
|
|
673283
|
+
`Current date: ${date}`,
|
|
673284
|
+
`Current time: ${time}`,
|
|
673285
|
+
`Current ISO timestamp: ${now2.toISOString()}`,
|
|
673286
|
+
`Timezone: ${timezone}`,
|
|
673287
|
+
repoRoot ? `Working directory: ${repoRoot}` : "",
|
|
673288
|
+
diskLine
|
|
673289
|
+
].filter(Boolean).join("\n");
|
|
673290
|
+
}
|
|
673291
|
+
function quoteConversationBlock(value2, maxChars = 2400) {
|
|
673292
|
+
const text2 = String(value2 ?? "").replace(/\r\n/g, "\n").trim();
|
|
673293
|
+
if (!text2) return "> (empty)";
|
|
673294
|
+
const clipped = text2.length > maxChars ? `${text2.slice(0, Math.max(0, maxChars - 1))}...` : text2;
|
|
673295
|
+
return clipped.split("\n").map((line) => `> ${line}`).join("\n");
|
|
673296
|
+
}
|
|
673297
|
+
function buildScopedConversationMessages(turns, options2) {
|
|
673298
|
+
const pinned = [];
|
|
673299
|
+
const dynamic = [];
|
|
673300
|
+
let latestContextFrame = null;
|
|
673301
|
+
for (const turn of turns) {
|
|
673302
|
+
if (options2.isPinnedSystemTurn(turn) && !pinned.some((p2) => p2.content === turn.content)) {
|
|
673303
|
+
pinned.push(turn);
|
|
673304
|
+
} else if (options2.isContextFrameTurn?.(turn)) {
|
|
673305
|
+
latestContextFrame = turn;
|
|
673306
|
+
} else {
|
|
673307
|
+
dynamic.push(turn);
|
|
673308
|
+
}
|
|
673309
|
+
}
|
|
673310
|
+
if (latestContextFrame) {
|
|
673311
|
+
if (options2.placeContextFrameBeforeLastUser !== false) {
|
|
673312
|
+
let lastUserIdx = -1;
|
|
673313
|
+
for (let i2 = dynamic.length - 1; i2 >= 0; i2--) {
|
|
673314
|
+
if (dynamic[i2].role === "user") {
|
|
673315
|
+
lastUserIdx = i2;
|
|
673316
|
+
break;
|
|
673317
|
+
}
|
|
673318
|
+
}
|
|
673319
|
+
if (lastUserIdx >= 0) dynamic.splice(lastUserIdx, 0, latestContextFrame);
|
|
673320
|
+
else dynamic.push(latestContextFrame);
|
|
673321
|
+
} else {
|
|
673322
|
+
dynamic.push(latestContextFrame);
|
|
673323
|
+
}
|
|
673324
|
+
}
|
|
673325
|
+
const available = Math.max(0, options2.maxMessages - pinned.length);
|
|
673326
|
+
let windowed = dynamic.slice(-available);
|
|
673327
|
+
if (latestContextFrame && available > 0 && !windowed.includes(latestContextFrame)) {
|
|
673328
|
+
windowed = [latestContextFrame, ...windowed.slice(-(available - 1))];
|
|
673329
|
+
}
|
|
673330
|
+
return [...pinned, ...windowed];
|
|
673331
|
+
}
|
|
673332
|
+
function buildVoiceSessionContext(args) {
|
|
673333
|
+
const maxTranscriptTurns = Math.max(1, args.maxTranscriptTurns ?? 8);
|
|
673334
|
+
const voiceLines = (args.voiceTranscript ?? []).slice(-maxTranscriptTurns).map((turn) => `${formatClock(turn.ts)} ${turn.role}: ${singleLine(turn.content, 360)}`);
|
|
673335
|
+
const relayLines = (args.relayTranscript ?? []).slice(-maxTranscriptTurns).map((turn) => {
|
|
673336
|
+
const dir = turn.dir === "toMain" ? "voice->main" : "main->voice";
|
|
673337
|
+
return `${formatClock(turn.ts)} ${dir}: ${singleLine(turn.content, 360)}`;
|
|
673338
|
+
});
|
|
673339
|
+
const taskLine = args.activeTask ? `active=${args.activeTask.active ? "yes" : "no"} tool_calls=${args.activeTask.toolCalls ?? 0} files_touched=${args.activeTask.filesTouched ?? 0}` : "active=unknown";
|
|
673340
|
+
const sections = [
|
|
673341
|
+
`## Runtime Context
|
|
673342
|
+
|
|
673343
|
+
${buildConversationRuntimeContext(/* @__PURE__ */ new Date(), args.repoRoot)}`,
|
|
673344
|
+
[
|
|
673345
|
+
"## Voice/Live Session Contract",
|
|
673346
|
+
"",
|
|
673347
|
+
`surface=voicechat session_id=${args.sessionId} turn=${args.turnCount}`,
|
|
673348
|
+
`main_agent_task=${taskLine}`,
|
|
673349
|
+
"Latest user utterance remains the active request. Earlier ASR transcript turns are conversational history, not new instructions.",
|
|
673350
|
+
"ASR text can be noisy. Prefer consensus-filtered final turns, resolve pronouns from recent context, and ask for clarification rather than filling gaps.",
|
|
673351
|
+
"Live perception is evidence for the current reply, not a topic to narrate unless the user asks about it."
|
|
673352
|
+
].join("\n")
|
|
673353
|
+
];
|
|
673354
|
+
if (voiceLines.length > 0) {
|
|
673355
|
+
sections.push(`## Recent Voice Conversation
|
|
673356
|
+
|
|
673357
|
+
${quoteConversationBlock(voiceLines.join("\n"))}`);
|
|
673358
|
+
}
|
|
673359
|
+
if (relayLines.length > 0) {
|
|
673360
|
+
sections.push(`## Main Agent Relay Stream
|
|
673361
|
+
|
|
673362
|
+
${quoteConversationBlock(relayLines.join("\n"))}`);
|
|
673363
|
+
}
|
|
673364
|
+
if (args.liveContext?.trim()) {
|
|
673365
|
+
sections.push(`## Live Context Frame
|
|
673366
|
+
|
|
673367
|
+
${args.liveContext.trim()}`);
|
|
673368
|
+
}
|
|
673369
|
+
return sections.join("\n\n");
|
|
673370
|
+
}
|
|
673371
|
+
function singleLine(value2, maxChars) {
|
|
673372
|
+
const clean5 = value2.replace(/\s+/g, " ").trim();
|
|
673373
|
+
return clean5.length > maxChars ? `${clean5.slice(0, Math.max(0, maxChars - 1))}...` : clean5;
|
|
673374
|
+
}
|
|
673375
|
+
function formatClock(ts) {
|
|
673376
|
+
try {
|
|
673377
|
+
return new Date(ts).toLocaleTimeString("en-US", {
|
|
673378
|
+
hour: "2-digit",
|
|
673379
|
+
minute: "2-digit",
|
|
673380
|
+
second: "2-digit"
|
|
673381
|
+
});
|
|
673382
|
+
} catch {
|
|
673383
|
+
return "unknown-time";
|
|
673384
|
+
}
|
|
673385
|
+
}
|
|
673386
|
+
var init_conversation_context = __esm({
|
|
673387
|
+
"packages/cli/src/tui/conversation-context.ts"() {
|
|
673388
|
+
"use strict";
|
|
673389
|
+
}
|
|
673390
|
+
});
|
|
673391
|
+
|
|
673219
673392
|
// packages/cli/src/tui/telegram-format.ts
|
|
673220
673393
|
function escapeHtml2(text2) {
|
|
673221
673394
|
return String(text2 ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -678572,7 +678745,6 @@ import {
|
|
|
678572
678745
|
unlinkSync as unlinkSync34,
|
|
678573
678746
|
readdirSync as readdirSync55,
|
|
678574
678747
|
statSync as statSync56,
|
|
678575
|
-
statfsSync as statfsSync8,
|
|
678576
678748
|
readFileSync as readFileSync126,
|
|
678577
678749
|
writeFileSync as writeFileSync82,
|
|
678578
678750
|
appendFileSync as appendFileSync18
|
|
@@ -679829,42 +680001,7 @@ function telegramMemoryTags(text2, mediaSummary) {
|
|
|
679829
680001
|
return tokens.filter((token) => !/^\d+$/.test(token)).slice(0, 10);
|
|
679830
680002
|
}
|
|
679831
680003
|
function buildTelegramRuntimeContext(now2 = /* @__PURE__ */ new Date(), repoRoot) {
|
|
679832
|
-
|
|
679833
|
-
weekday: "long",
|
|
679834
|
-
year: "numeric",
|
|
679835
|
-
month: "long",
|
|
679836
|
-
day: "numeric"
|
|
679837
|
-
}).format(now2);
|
|
679838
|
-
const time = new Intl.DateTimeFormat("en-US", {
|
|
679839
|
-
hour: "numeric",
|
|
679840
|
-
minute: "2-digit",
|
|
679841
|
-
second: "2-digit",
|
|
679842
|
-
timeZoneName: "short"
|
|
679843
|
-
}).format(now2);
|
|
679844
|
-
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || process.env["TZ"] || "system";
|
|
679845
|
-
let diskLine = "";
|
|
679846
|
-
if (repoRoot) {
|
|
679847
|
-
try {
|
|
679848
|
-
const stats = statfsSync8(repoRoot);
|
|
679849
|
-
const totalBytes = stats.blocks * stats.bsize;
|
|
679850
|
-
const freeBytes = stats.bavail * stats.bsize;
|
|
679851
|
-
const usedBytes = totalBytes - freeBytes;
|
|
679852
|
-
const totalGB = Math.round(totalBytes / 1024 ** 3);
|
|
679853
|
-
const freeGB = Math.round(freeBytes / 1024 ** 3);
|
|
679854
|
-
const usedGB = Math.round(usedBytes / 1024 ** 3);
|
|
679855
|
-
const usedPct = totalBytes > 0 ? Math.round(usedBytes / totalBytes * 100) : 0;
|
|
679856
|
-
diskLine = `Disk: disk_available_gb=${freeGB} disk_used_gb=${usedGB} disk_total_gb=${totalGB} disk_used_pct=${usedPct} path=${repoRoot}`;
|
|
679857
|
-
} catch {
|
|
679858
|
-
}
|
|
679859
|
-
}
|
|
679860
|
-
return [
|
|
679861
|
-
`Current date: ${date}`,
|
|
679862
|
-
`Current time: ${time}`,
|
|
679863
|
-
`Current ISO timestamp: ${now2.toISOString()}`,
|
|
679864
|
-
`Timezone: ${timezone}`,
|
|
679865
|
-
repoRoot ? `Working directory: ${repoRoot}` : "",
|
|
679866
|
-
diskLine
|
|
679867
|
-
].filter(Boolean).join("\n");
|
|
680004
|
+
return buildConversationRuntimeContext(now2, repoRoot);
|
|
679868
680005
|
}
|
|
679869
680006
|
function telegramSessionIdFromKey(sessionKey) {
|
|
679870
680007
|
return `telegram-${createHash42("sha1").update(sessionKey).digest("hex").slice(0, 16)}`;
|
|
@@ -681026,6 +681163,7 @@ var TELEGRAM_TOOL_ACTION_GROUPS, TELEGRAM_TOOL_ACTION_GROUP, TELEGRAM_TOOL_MUTAT
|
|
|
681026
681163
|
var init_telegram_bridge = __esm({
|
|
681027
681164
|
"packages/cli/src/tui/telegram-bridge.ts"() {
|
|
681028
681165
|
"use strict";
|
|
681166
|
+
init_conversation_context();
|
|
681029
681167
|
init_telegram_format();
|
|
681030
681168
|
init_async_process();
|
|
681031
681169
|
init_dist8();
|
|
@@ -697648,43 +697786,21 @@ function isContextSnapshotTurn(turn) {
|
|
|
697648
697786
|
return turn.role === "system" && turn.content.startsWith(CONTEXT_SNAPSHOT_PREFIX);
|
|
697649
697787
|
}
|
|
697650
697788
|
function isPinnedSystemTurn(turn) {
|
|
697651
|
-
return turn.role === "system" && (turn.content === SYSTEM_PROMPT2 || turn.content.startsWith("Available tools
|
|
697789
|
+
return turn.role === "system" && (turn.content === SYSTEM_PROMPT2 || turn.content.startsWith("Available tools."));
|
|
697652
697790
|
}
|
|
697653
697791
|
function buildRealtimeVoiceMessages(turns, maxMessages = MAX_REALTIME_MODEL_MESSAGES) {
|
|
697654
|
-
|
|
697655
|
-
|
|
697656
|
-
|
|
697657
|
-
|
|
697658
|
-
|
|
697659
|
-
|
|
697660
|
-
} else if (isContextSnapshotTurn(turn)) {
|
|
697661
|
-
latestSnapshot = turn;
|
|
697662
|
-
} else {
|
|
697663
|
-
dynamic.push(turn);
|
|
697664
|
-
}
|
|
697665
|
-
}
|
|
697666
|
-
if (latestSnapshot) {
|
|
697667
|
-
let lastUserIdx = -1;
|
|
697668
|
-
for (let i2 = dynamic.length - 1; i2 >= 0; i2--) {
|
|
697669
|
-
if (dynamic[i2].role === "user") {
|
|
697670
|
-
lastUserIdx = i2;
|
|
697671
|
-
break;
|
|
697672
|
-
}
|
|
697673
|
-
}
|
|
697674
|
-
if (lastUserIdx >= 0) dynamic.splice(lastUserIdx, 0, latestSnapshot);
|
|
697675
|
-
else dynamic.push(latestSnapshot);
|
|
697676
|
-
}
|
|
697677
|
-
const available = Math.max(0, maxMessages - pinned.length);
|
|
697678
|
-
let windowed = dynamic.slice(-available);
|
|
697679
|
-
if (latestSnapshot && available > 0 && !windowed.includes(latestSnapshot)) {
|
|
697680
|
-
windowed = [latestSnapshot, ...windowed.slice(-(available - 1))];
|
|
697681
|
-
}
|
|
697682
|
-
return [...pinned, ...windowed];
|
|
697792
|
+
return buildScopedConversationMessages(turns, {
|
|
697793
|
+
maxMessages,
|
|
697794
|
+
isPinnedSystemTurn,
|
|
697795
|
+
isContextFrameTurn: isContextSnapshotTurn,
|
|
697796
|
+
placeContextFrameBeforeLastUser: true
|
|
697797
|
+
});
|
|
697683
697798
|
}
|
|
697684
697799
|
var VAD_SILENCE_MS, MAX_SEGMENT_MS, SUMMARY_INJECTION_INTERVAL, MAX_CONTEXT_TURNS, MAX_REALTIME_MODEL_MESSAGES, CONTEXT_SNAPSHOT_PREFIX, CONTEXT_SNAPSHOT_SCOPE, MAX_VOICE_REPLY_CHARS, AGENT_ECHO_WINDOW_MS, AGENT_ECHO_SIMILARITY, SYSTEM_PROMPT2, MIN_SIGNAL_SCORE, NOISE_ONLY_RE, VoiceChatSession;
|
|
697685
697800
|
var init_voicechat = __esm({
|
|
697686
697801
|
"packages/cli/src/tui/voicechat.ts"() {
|
|
697687
697802
|
"use strict";
|
|
697803
|
+
init_conversation_context();
|
|
697688
697804
|
VAD_SILENCE_MS = 3e3;
|
|
697689
697805
|
MAX_SEGMENT_MS = 6500;
|
|
697690
697806
|
SUMMARY_INJECTION_INTERVAL = 4;
|
|
@@ -697699,6 +697815,7 @@ var init_voicechat = __esm({
|
|
|
697699
697815
|
|
|
697700
697816
|
Rules:
|
|
697701
697817
|
- ALWAYS answer the user's most recent message directly. The live context snapshot is background perception — never the topic. Do not describe cameras, feeds, or surroundings unless the user's latest message asks about them.
|
|
697818
|
+
- Treat this as one continuous conversation thread. Resolve pronouns from recent voice turns and main-agent relay context, but let the latest user utterance override earlier transcript noise.
|
|
697702
697819
|
- Never repeat your previous answer. If you already described the scene, don't describe it again unless explicitly asked again — respond to what the user just said.
|
|
697703
697820
|
- Live perception: each turn you may receive a read-only "[ACTIVE CONTEXT FRAME]" system message containing what the camera currently sees (objects, people, classifications, recent visual events) and what is currently heard (sound scene, recent sounds, transcripts). Treat it as your own live sight and hearing. When the user asks what you SEE, answer from the camera fields; when the user asks what you HEAR, answer from the audio/sound fields. If the snapshot is missing or stale, say you can't see/hear clearly right now — do not invent observations.
|
|
697704
697821
|
- Never invent environment facts (cwd, OS, specs, repo state). If you need a precise fact from the main agent, request a tool by outputting on a single line EXACTLY one JSON object: {"tool": string, "args": object} and nothing else. Then wait for the tool result before answering.
|
|
@@ -698022,13 +698139,19 @@ Rules:
|
|
|
698022
698139
|
const snap = await Promise.resolve(this.toolRelay.contextSnapshot());
|
|
698023
698140
|
if (snap && snap.trim()) {
|
|
698024
698141
|
this.dropTransientSystemTurns();
|
|
698142
|
+
const contextFrame = buildVoiceSessionContext({
|
|
698143
|
+
sessionId: "voicechat",
|
|
698144
|
+
turnCount: this.turnCount,
|
|
698145
|
+
voiceTranscript: this.voiceTranscript,
|
|
698146
|
+
relayTranscript: this.relayTranscript,
|
|
698147
|
+
liveContext: `${CONTEXT_SNAPSHOT_SCOPE}
|
|
698148
|
+
|
|
698149
|
+
${snap.trim()}`
|
|
698150
|
+
});
|
|
698025
698151
|
this.context.push({
|
|
698026
698152
|
role: "system",
|
|
698027
698153
|
content: `${CONTEXT_SNAPSHOT_PREFIX}
|
|
698028
|
-
|
|
698029
|
-
${CONTEXT_SNAPSHOT_SCOPE}
|
|
698030
|
-
|
|
698031
|
-
${snap.trim()}`
|
|
698154
|
+
${contextFrame}`
|
|
698032
698155
|
});
|
|
698033
698156
|
this.trimContext();
|
|
698034
698157
|
}
|
|
@@ -737112,6 +737235,32 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
737112
737235
|
sharedTranscriber: null
|
|
737113
737236
|
};
|
|
737114
737237
|
const engine = getListenEngine();
|
|
737238
|
+
const callConsensusRequired = (() => {
|
|
737239
|
+
const env2 = String(process.env["OMNIUS_ASR_CONSENSUS"] ?? "").trim().toLowerCase();
|
|
737240
|
+
return !(env2 === "0" || env2 === "off" || env2 === "false");
|
|
737241
|
+
})();
|
|
737242
|
+
const recentCallFinals = [];
|
|
737243
|
+
const normalizeCallTranscript = (value2) => value2.toLowerCase().replace(/[^\p{L}\p{N}\s']/gu, " ").replace(/\s+/g, " ").trim();
|
|
737244
|
+
const isLikelyCallAsrHallucination = (normalized) => /^(thanks|thank you) for watching(?: this video)?$/.test(normalized) || /^thanks for watching and/.test(normalized) || /\bdon't forget to (like|subscribe)\b/.test(normalized);
|
|
737245
|
+
const shouldDropCallFinalTranscript = (text2, evt) => {
|
|
737246
|
+
if (callConsensusRequired && evt.consensus !== true) {
|
|
737247
|
+
return evt.consensus === false ? "consensus rejected" : "missing consensus metadata";
|
|
737248
|
+
}
|
|
737249
|
+
const normalized = normalizeCallTranscript(text2);
|
|
737250
|
+
if (!normalized) return "empty transcript";
|
|
737251
|
+
if (isLikelyCallAsrHallucination(normalized)) {
|
|
737252
|
+
return "known whisper outro hallucination";
|
|
737253
|
+
}
|
|
737254
|
+
const now2 = Date.now();
|
|
737255
|
+
while (recentCallFinals.length > 0 && now2 - recentCallFinals[0].ts > 15e3) {
|
|
737256
|
+
recentCallFinals.shift();
|
|
737257
|
+
}
|
|
737258
|
+
if (recentCallFinals.some((entry) => entry.normalized === normalized)) {
|
|
737259
|
+
return "duplicate final transcript";
|
|
737260
|
+
}
|
|
737261
|
+
recentCallFinals.push({ normalized, ts: now2 });
|
|
737262
|
+
return null;
|
|
737263
|
+
};
|
|
737115
737264
|
voiceSession.onUserAudio = (pcmChunk, userId) => {
|
|
737116
737265
|
if (callState.sharedTranscriber)
|
|
737117
737266
|
callState.sharedTranscriber.write(pcmChunk);
|
|
@@ -737142,7 +737291,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
737142
737291
|
if (result && session?.isActive) {
|
|
737143
737292
|
const { pcm, sampleRate } = result;
|
|
737144
737293
|
session.sendSpeakingStateToClient(id2, true);
|
|
737145
|
-
session.sendAudioToClient(id2, pcm);
|
|
737294
|
+
session.sendAudioToClient(id2, pcm, sampleRate);
|
|
737146
737295
|
const durationMs = pcm.length / 2 / sampleRate * 1e3;
|
|
737147
737296
|
setTimeout(
|
|
737148
737297
|
() => session.sendSpeakingStateToClient(id2, false),
|
|
@@ -737177,17 +737326,25 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
737177
737326
|
(evt) => {
|
|
737178
737327
|
if (!evt.text?.trim() || !voiceSession?.isActive) return;
|
|
737179
737328
|
const text2 = evt.text.trim();
|
|
737329
|
+
if (!evt.isFinal) return;
|
|
737330
|
+
const dropReason = shouldDropCallFinalTranscript(text2, evt);
|
|
737331
|
+
if (dropReason) {
|
|
737332
|
+
writeContent(
|
|
737333
|
+
() => renderInfo(
|
|
737334
|
+
`[call/asr] dropped ${dropReason}: "${text2.slice(0, 80)}"`
|
|
737335
|
+
)
|
|
737336
|
+
);
|
|
737337
|
+
return;
|
|
737338
|
+
}
|
|
737180
737339
|
writeContent(
|
|
737181
737340
|
() => renderVoiceSessionTranscript("user", text2)
|
|
737182
737341
|
);
|
|
737183
737342
|
voiceSession?.sendTranscript("user", text2);
|
|
737184
|
-
|
|
737185
|
-
|
|
737186
|
-
|
|
737187
|
-
|
|
737188
|
-
|
|
737189
|
-
rl.feed(text2 + "\n");
|
|
737190
|
-
}
|
|
737343
|
+
for (const [clientId, agent] of callSubAgents) {
|
|
737344
|
+
agent.handleTranscript(text2);
|
|
737345
|
+
}
|
|
737346
|
+
if (callSubAgents.size === 0) {
|
|
737347
|
+
rl.feed(text2 + "\n");
|
|
737191
737348
|
}
|
|
737192
737349
|
}
|
|
737193
737350
|
);
|
|
@@ -737250,7 +737407,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
737250
737407
|
voiceEngine.onPCMOutput = (pcm, sampleRate) => {
|
|
737251
737408
|
if (session?.isActive) {
|
|
737252
737409
|
session.sendSpeakingState(true);
|
|
737253
|
-
session.sendAudioToClients(pcm);
|
|
737410
|
+
session.sendAudioToClients(pcm, sampleRate);
|
|
737254
737411
|
const durationMs = pcm.length / 2 / sampleRate * 1e3;
|
|
737255
737412
|
setTimeout(() => session.sendSpeakingState(false), durationMs);
|
|
737256
737413
|
}
|
|
@@ -84,6 +84,76 @@ def emit_transcript(text: str, is_final: bool = False, doa=None, consensus=None)
|
|
|
84
84
|
event["consensus"] = bool(consensus)
|
|
85
85
|
emit(event)
|
|
86
86
|
|
|
87
|
+
|
|
88
|
+
def host_cuda_hardware_present() -> bool:
|
|
89
|
+
"""Best-effort CUDA hardware hint, including Jetson/Orin nodes."""
|
|
90
|
+
for path in (
|
|
91
|
+
"/dev/nvidiactl",
|
|
92
|
+
"/dev/nvidia0",
|
|
93
|
+
"/dev/nvhost-gpu",
|
|
94
|
+
"/proc/driver/nvidia/gpus",
|
|
95
|
+
"/sys/devices/gpu.0",
|
|
96
|
+
):
|
|
97
|
+
if os.path.exists(path):
|
|
98
|
+
return True
|
|
99
|
+
visible = os.environ.get("NVIDIA_VISIBLE_DEVICES", "").strip().lower()
|
|
100
|
+
return bool(visible and visible not in ("none", "void", "no"))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def select_whisper_device() -> str:
|
|
104
|
+
"""Force Whisper onto CUDA whenever PyTorch can see a CUDA device.
|
|
105
|
+
|
|
106
|
+
CPU is only allowed when CUDA is genuinely unavailable to this Python
|
|
107
|
+
runtime. If CUDA is visible but unusable, fail loudly instead of silently
|
|
108
|
+
degrading into slow CPU transcription.
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
import torch
|
|
112
|
+
except Exception as e:
|
|
113
|
+
if host_cuda_hardware_present():
|
|
114
|
+
emit_error(f"CUDA hardware appears present, but PyTorch could not be imported for CUDA Whisper ({e}); refusing CPU fallback.")
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
emit_status(f"PyTorch unavailable for CUDA probe ({e}); using CPU because CUDA is not available to Whisper.")
|
|
117
|
+
return "cpu"
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
cuda_count = int(torch.cuda.device_count())
|
|
121
|
+
cuda_available = bool(torch.cuda.is_available() and cuda_count > 0)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
if host_cuda_hardware_present():
|
|
124
|
+
emit_error(f"CUDA hardware appears present, but PyTorch CUDA probe failed ({e}); refusing CPU fallback.")
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
emit_status(f"CUDA probe failed ({e}); using CPU because CUDA is not available to Whisper.")
|
|
127
|
+
return "cpu"
|
|
128
|
+
|
|
129
|
+
if not cuda_available:
|
|
130
|
+
cuda_version = getattr(getattr(torch, "version", None), "cuda", None)
|
|
131
|
+
if host_cuda_hardware_present():
|
|
132
|
+
emit_error(f"CUDA hardware appears present, but PyTorch reports CUDA unavailable (torch.version.cuda={cuda_version}, device_count={cuda_count}); refusing CPU fallback.")
|
|
133
|
+
sys.exit(1)
|
|
134
|
+
emit_status(f"CUDA unavailable to PyTorch (torch.version.cuda={cuda_version}, device_count={cuda_count}); using CPU fallback.")
|
|
135
|
+
return "cpu"
|
|
136
|
+
|
|
137
|
+
raw_index = os.environ.get("OMNIUS_ASR_CUDA_DEVICE", "0").strip() or "0"
|
|
138
|
+
try:
|
|
139
|
+
device_index = int(raw_index)
|
|
140
|
+
except ValueError:
|
|
141
|
+
emit_error(f"Invalid OMNIUS_ASR_CUDA_DEVICE={raw_index!r}; refusing CPU fallback because CUDA is available.")
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
if device_index < 0 or device_index >= cuda_count:
|
|
144
|
+
emit_error(f"OMNIUS_ASR_CUDA_DEVICE={device_index} outside available CUDA device range 0..{cuda_count - 1}; refusing CPU fallback.")
|
|
145
|
+
sys.exit(1)
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
torch.cuda.set_device(device_index)
|
|
149
|
+
name = torch.cuda.get_device_name(device_index)
|
|
150
|
+
except Exception as e:
|
|
151
|
+
emit_error(f"CUDA is available but cuda:{device_index} could not be selected ({e}); refusing CPU fallback.")
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
emit_status(f"CUDA available; forcing Whisper onto cuda:{device_index} ({name}).")
|
|
155
|
+
return f"cuda:{device_index}"
|
|
156
|
+
|
|
87
157
|
# ---------------------------------------------------------------------------
|
|
88
158
|
# Venv bootstrap
|
|
89
159
|
# ---------------------------------------------------------------------------
|
|
@@ -344,18 +414,15 @@ def main():
|
|
|
344
414
|
|
|
345
415
|
import whisper
|
|
346
416
|
|
|
347
|
-
|
|
417
|
+
device = select_whisper_device()
|
|
418
|
+
emit_status(f"Loading Whisper {args.model} model on {device}...")
|
|
348
419
|
try:
|
|
349
|
-
device = "cpu"
|
|
350
|
-
try:
|
|
351
|
-
import torch
|
|
352
|
-
if torch.cuda.is_available():
|
|
353
|
-
device = "cuda"
|
|
354
|
-
except ImportError:
|
|
355
|
-
pass
|
|
356
420
|
model = whisper.load_model(args.model, device=device)
|
|
357
421
|
except Exception as e:
|
|
358
|
-
|
|
422
|
+
if str(device).startswith("cuda"):
|
|
423
|
+
emit_error(f"Failed to load model on {device}; refusing CPU fallback because CUDA is available: {e}")
|
|
424
|
+
else:
|
|
425
|
+
emit_error(f"Failed to load model on CPU fallback: {e}")
|
|
359
426
|
sys.exit(1)
|
|
360
427
|
|
|
361
428
|
# Consensus model: hallucinations (mixed-language garbage on noisy or
|
|
@@ -363,7 +430,7 @@ def main():
|
|
|
363
430
|
consensus_model = None
|
|
364
431
|
consensus_name = (args.consensus_model or "off").strip().lower()
|
|
365
432
|
if consensus_name not in ("off", "none", "", args.model):
|
|
366
|
-
emit_status(f"Loading consensus Whisper {consensus_name} model...")
|
|
433
|
+
emit_status(f"Loading consensus Whisper {consensus_name} model on {device}...")
|
|
367
434
|
try:
|
|
368
435
|
consensus_model = whisper.load_model(consensus_name, device=device)
|
|
369
436
|
except Exception as e:
|
|
@@ -377,7 +444,7 @@ def main():
|
|
|
377
444
|
|
|
378
445
|
emit({"type": "ready"})
|
|
379
446
|
|
|
380
|
-
fp16 = (device
|
|
447
|
+
fp16 = str(device).startswith("cuda")
|
|
381
448
|
|
|
382
449
|
def run_transcribe(m, samples):
|
|
383
450
|
return m.transcribe(
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnius",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.431",
|
|
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.431",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"image-to-ascii"
|
|
12
12
|
],
|
package/package.json
CHANGED