open-agents-ai 0.55.0 → 0.56.0
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 +204 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16726,6 +16726,62 @@ transcribe-cli error: ${transcribeCliError}` : "";
|
|
|
16726
16726
|
this.pendingText = "";
|
|
16727
16727
|
return text;
|
|
16728
16728
|
}
|
|
16729
|
+
/**
|
|
16730
|
+
* Create a standalone transcriber for call sessions.
|
|
16731
|
+
* No microphone capture — just an ASR engine you feed PCM chunks to.
|
|
16732
|
+
* Returns an EventEmitter with .write(pcm: Buffer) and 'transcript' events.
|
|
16733
|
+
* Caller is responsible for calling .stop() when done.
|
|
16734
|
+
*/
|
|
16735
|
+
async createCallTranscriber() {
|
|
16736
|
+
let tc = await this.loadTranscribeCli();
|
|
16737
|
+
if (!tc && _bgInstallPromise) {
|
|
16738
|
+
await _bgInstallPromise;
|
|
16739
|
+
this.transcribeCliAvailable = null;
|
|
16740
|
+
tc = await this.loadTranscribeCli();
|
|
16741
|
+
}
|
|
16742
|
+
if (!tc) {
|
|
16743
|
+
try {
|
|
16744
|
+
execSync18("npm i -g transcribe-cli", { stdio: "pipe", timeout: 18e4 });
|
|
16745
|
+
this.transcribeCliAvailable = null;
|
|
16746
|
+
tc = await this.loadTranscribeCli();
|
|
16747
|
+
} catch {
|
|
16748
|
+
}
|
|
16749
|
+
}
|
|
16750
|
+
if (tc?.TranscribeLive) {
|
|
16751
|
+
try {
|
|
16752
|
+
const transcriber = new tc.TranscribeLive({
|
|
16753
|
+
model: this.config.model,
|
|
16754
|
+
sampleRate: 16e3,
|
|
16755
|
+
channels: 1,
|
|
16756
|
+
sampleWidth: 2,
|
|
16757
|
+
chunkDuration: 3
|
|
16758
|
+
});
|
|
16759
|
+
await new Promise((resolve28, reject) => {
|
|
16760
|
+
const timeout = setTimeout(() => reject(new Error("Model load timeout (60s)")), 6e4);
|
|
16761
|
+
transcriber.on("ready", () => {
|
|
16762
|
+
clearTimeout(timeout);
|
|
16763
|
+
resolve28();
|
|
16764
|
+
});
|
|
16765
|
+
transcriber.on("error", (err) => {
|
|
16766
|
+
clearTimeout(timeout);
|
|
16767
|
+
reject(err);
|
|
16768
|
+
});
|
|
16769
|
+
});
|
|
16770
|
+
return transcriber;
|
|
16771
|
+
} catch {
|
|
16772
|
+
}
|
|
16773
|
+
}
|
|
16774
|
+
const scriptPath2 = findLiveWhisperScript();
|
|
16775
|
+
if (!scriptPath2)
|
|
16776
|
+
return null;
|
|
16777
|
+
try {
|
|
16778
|
+
const fallback = new WhisperFallbackTranscriber(this.config.model, scriptPath2);
|
|
16779
|
+
await fallback.start();
|
|
16780
|
+
return fallback;
|
|
16781
|
+
} catch {
|
|
16782
|
+
return null;
|
|
16783
|
+
}
|
|
16784
|
+
}
|
|
16729
16785
|
/**
|
|
16730
16786
|
* Transcribe a file (audio or video) using transcribe-cli.
|
|
16731
16787
|
* Returns the transcription result.
|
|
@@ -17284,8 +17340,8 @@ function renderSlashHelp() {
|
|
|
17284
17340
|
["/listen manual", "Manual mode \u2014 press Enter to submit (turns off auto)"],
|
|
17285
17341
|
["/listen auto", "Auto-submit after 3s silence (blinking \u25CF indicator)"],
|
|
17286
17342
|
["/listen stop", "Stop listening"],
|
|
17287
|
-
["/call", "Start voice call session (cloudflared tunnel +
|
|
17288
|
-
["/
|
|
17343
|
+
["/call", "Start voice call session (cloudflared tunnel + ASR/TTS)"],
|
|
17344
|
+
["/hangup", "End active call session"],
|
|
17289
17345
|
["/cost", "Show session token cost breakdown"],
|
|
17290
17346
|
["/evaluate", "Evaluate last completed task (LLM quality scoring)"],
|
|
17291
17347
|
["/task-type", "Set task type (code, document, analysis, plan, general, auto)"],
|
|
@@ -17675,6 +17731,7 @@ var init_render = __esm({
|
|
|
17675
17731
|
"/voice",
|
|
17676
17732
|
"/listen",
|
|
17677
17733
|
"/call",
|
|
17734
|
+
"/hangup",
|
|
17678
17735
|
"/stream",
|
|
17679
17736
|
"/verbose",
|
|
17680
17737
|
"/dream",
|
|
@@ -17878,6 +17935,11 @@ function connect() {
|
|
|
17878
17935
|
if (typeof evt.data === 'string') {
|
|
17879
17936
|
try {
|
|
17880
17937
|
const msg = JSON.parse(evt.data);
|
|
17938
|
+
if (msg.type === 'keepalive') {
|
|
17939
|
+
// Respond to keep connection alive through proxies
|
|
17940
|
+
if (ws.readyState === 1) ws.send(JSON.stringify({ type: 'pong' }));
|
|
17941
|
+
return;
|
|
17942
|
+
}
|
|
17881
17943
|
if (msg.type === 'transcript') {
|
|
17882
17944
|
addTranscript(msg.speaker, msg.text);
|
|
17883
17945
|
} else if (msg.type === 'speaking_start') {
|
|
@@ -18002,11 +18064,11 @@ function renderVoiceSessionStart(tunnelUrl) {
|
|
|
18002
18064
|
process.stdout.write(`
|
|
18003
18065
|
${c2.cyan("\u2601")} ${c2.bold("Live Voice Session")}
|
|
18004
18066
|
`);
|
|
18005
|
-
process.stdout.write(` ${c2.dim("\u23BF")}
|
|
18067
|
+
process.stdout.write(` ${c2.dim("\u23BF")} ${c2.cyan(tunnelUrl)}
|
|
18006
18068
|
`);
|
|
18007
18069
|
process.stdout.write(` ${c2.dim("\u23BF")} Bidirectional PCM audio + live transcription
|
|
18008
18070
|
`);
|
|
18009
|
-
process.stdout.write(` ${c2.dim("\u23BF")}
|
|
18071
|
+
process.stdout.write(` ${c2.dim("\u23BF")} /hangup to end session (auto-closes after 1 min idle)
|
|
18010
18072
|
|
|
18011
18073
|
`);
|
|
18012
18074
|
}
|
|
@@ -18023,6 +18085,12 @@ function renderVoiceSessionUser(action, username) {
|
|
|
18023
18085
|
process.stdout.write(` ${c2.dim("\u23BF")} ${c2.cyan("\u2601")} ${icon} ${username} ${action}
|
|
18024
18086
|
`);
|
|
18025
18087
|
}
|
|
18088
|
+
function renderVoiceSessionTranscript(speaker, text) {
|
|
18089
|
+
const label = speaker === "user" ? c2.yellow("user") : c2.cyan("agent");
|
|
18090
|
+
const preview = text.length > 80 ? text.slice(0, 77) + "..." : text;
|
|
18091
|
+
process.stdout.write(` ${c2.dim("\u23BF")} ${c2.cyan("\u2601")} [${label}] ${preview}
|
|
18092
|
+
`);
|
|
18093
|
+
}
|
|
18026
18094
|
var VoiceSession;
|
|
18027
18095
|
var init_voice_session = __esm({
|
|
18028
18096
|
"packages/cli/dist/tui/voice-session.js"() {
|
|
@@ -18034,7 +18102,10 @@ var init_voice_session = __esm({
|
|
|
18034
18102
|
cloudflaredProcess = null;
|
|
18035
18103
|
wsClients = /* @__PURE__ */ new Map();
|
|
18036
18104
|
runtimeTimer = null;
|
|
18105
|
+
idleTimer = null;
|
|
18037
18106
|
ttsSpeaking = false;
|
|
18107
|
+
/** Idle timeout before auto-closing (ms). Default 60s — no users connected → session ends. */
|
|
18108
|
+
idleTimeoutMs = 6e4;
|
|
18038
18109
|
/** Callback invoked when user audio chunk arrives (PCM 16kHz 16-bit mono) */
|
|
18039
18110
|
onUserAudio = null;
|
|
18040
18111
|
/** Callback invoked when user speech is transcribed */
|
|
@@ -18068,6 +18139,8 @@ var init_voice_session = __esm({
|
|
|
18068
18139
|
const port = await this.findFreePort();
|
|
18069
18140
|
this.state.localPort = port;
|
|
18070
18141
|
this.server = createServer((req, res) => this.handleHTTP(req, res));
|
|
18142
|
+
this.server.timeout = 0;
|
|
18143
|
+
this.server.keepAliveTimeout = 0;
|
|
18071
18144
|
this.server.on("upgrade", (req, socket, head) => {
|
|
18072
18145
|
if (req.url === "/ws") {
|
|
18073
18146
|
this.handleWebSocketUpgrade(req, socket, head);
|
|
@@ -18093,7 +18166,8 @@ var init_voice_session = __esm({
|
|
|
18093
18166
|
this.emit("tick", this.runtime);
|
|
18094
18167
|
}, 1e3);
|
|
18095
18168
|
this.emit("started", this.state.tunnelUrl);
|
|
18096
|
-
|
|
18169
|
+
this.resetIdleTimer();
|
|
18170
|
+
return this.state.tunnelUrl;
|
|
18097
18171
|
}
|
|
18098
18172
|
/**
|
|
18099
18173
|
* Stop the voice session.
|
|
@@ -18106,6 +18180,10 @@ var init_voice_session = __esm({
|
|
|
18106
18180
|
clearInterval(this.runtimeTimer);
|
|
18107
18181
|
this.runtimeTimer = null;
|
|
18108
18182
|
}
|
|
18183
|
+
if (this.idleTimer) {
|
|
18184
|
+
clearTimeout(this.idleTimer);
|
|
18185
|
+
this.idleTimer = null;
|
|
18186
|
+
}
|
|
18109
18187
|
for (const [id, socket] of this.wsClients) {
|
|
18110
18188
|
try {
|
|
18111
18189
|
const closeFrame = createWebSocketFrame(8, Buffer.alloc(0));
|
|
@@ -18199,7 +18277,8 @@ var init_voice_session = __esm({
|
|
|
18199
18277
|
const magic = "258EAFA5-E914-47DA-95CA-5AB9DC085B62";
|
|
18200
18278
|
const accept = createHash("sha1").update(key + magic).digest("base64");
|
|
18201
18279
|
socket.setNoDelay(true);
|
|
18202
|
-
socket.setKeepAlive(true,
|
|
18280
|
+
socket.setKeepAlive(true, 1e4);
|
|
18281
|
+
socket.setTimeout(0);
|
|
18203
18282
|
socket.write(`HTTP/1.1 101 Switching Protocols\r
|
|
18204
18283
|
Upgrade: websocket\r
|
|
18205
18284
|
Connection: Upgrade\r
|
|
@@ -18210,6 +18289,7 @@ Sec-WebSocket-Accept: ${accept}\r
|
|
|
18210
18289
|
this.wsClients.set(clientId, socket);
|
|
18211
18290
|
this.state.connectedUsers.set(clientId, { username: "web-user", connectedAt: Date.now() });
|
|
18212
18291
|
this.emit("userConnected", clientId, "web-user");
|
|
18292
|
+
this.resetIdleTimer();
|
|
18213
18293
|
try {
|
|
18214
18294
|
socket.write(createWebSocketFrame(9, Buffer.from("keepalive")));
|
|
18215
18295
|
} catch {
|
|
@@ -18221,10 +18301,11 @@ Sec-WebSocket-Accept: ${accept}\r
|
|
|
18221
18301
|
}
|
|
18222
18302
|
try {
|
|
18223
18303
|
socket.write(createWebSocketFrame(9, Buffer.from("keepalive")));
|
|
18304
|
+
socket.write(createWebSocketFrame(1, Buffer.from(JSON.stringify({ type: "keepalive" }))));
|
|
18224
18305
|
} catch {
|
|
18225
18306
|
clearInterval(pingInterval);
|
|
18226
18307
|
}
|
|
18227
|
-
},
|
|
18308
|
+
}, 5e3);
|
|
18228
18309
|
let frameBuffer = Buffer.alloc(0);
|
|
18229
18310
|
socket.on("data", (data) => {
|
|
18230
18311
|
frameBuffer = Buffer.concat([frameBuffer, data]);
|
|
@@ -18277,11 +18358,13 @@ Sec-WebSocket-Accept: ${accept}\r
|
|
|
18277
18358
|
this.wsClients.delete(clientId);
|
|
18278
18359
|
this.state.connectedUsers.delete(clientId);
|
|
18279
18360
|
this.emit("userDisconnected", clientId);
|
|
18361
|
+
this.resetIdleTimer();
|
|
18280
18362
|
});
|
|
18281
18363
|
socket.on("error", () => {
|
|
18282
18364
|
clearInterval(pingInterval);
|
|
18283
18365
|
this.wsClients.delete(clientId);
|
|
18284
18366
|
this.state.connectedUsers.delete(clientId);
|
|
18367
|
+
this.resetIdleTimer();
|
|
18285
18368
|
});
|
|
18286
18369
|
if (head.length > 0) {
|
|
18287
18370
|
socket.emit("data", head);
|
|
@@ -18324,6 +18407,27 @@ Sec-WebSocket-Accept: ${accept}\r
|
|
|
18324
18407
|
});
|
|
18325
18408
|
});
|
|
18326
18409
|
}
|
|
18410
|
+
// ── Idle timer ───────────────────────────────────────────────────────
|
|
18411
|
+
/**
|
|
18412
|
+
* Reset idle timer. Called when users connect/disconnect.
|
|
18413
|
+
* When no users are connected, starts a countdown to auto-close.
|
|
18414
|
+
* When a user connects, cancels any pending auto-close.
|
|
18415
|
+
*/
|
|
18416
|
+
resetIdleTimer() {
|
|
18417
|
+
if (this.idleTimer) {
|
|
18418
|
+
clearTimeout(this.idleTimer);
|
|
18419
|
+
this.idleTimer = null;
|
|
18420
|
+
}
|
|
18421
|
+
if (this.state.connectedUsers.size === 0) {
|
|
18422
|
+
this.idleTimer = setTimeout(() => {
|
|
18423
|
+
if (this.state.active && this.state.connectedUsers.size === 0) {
|
|
18424
|
+
this.emit("idle_timeout");
|
|
18425
|
+
this.stop().catch(() => {
|
|
18426
|
+
});
|
|
18427
|
+
}
|
|
18428
|
+
}, this.idleTimeoutMs);
|
|
18429
|
+
}
|
|
18430
|
+
}
|
|
18327
18431
|
// ── Helpers ───────────────────────────────────────────────────────────
|
|
18328
18432
|
findFreePort() {
|
|
18329
18433
|
return new Promise((resolve28, reject) => {
|
|
@@ -20782,6 +20886,19 @@ async function handleSlashCommand(input, ctx) {
|
|
|
20782
20886
|
}
|
|
20783
20887
|
return "handled";
|
|
20784
20888
|
}
|
|
20889
|
+
case "hangup": {
|
|
20890
|
+
if (!ctx.callStop) {
|
|
20891
|
+
renderWarning("Call mode not available in this context.");
|
|
20892
|
+
return "handled";
|
|
20893
|
+
}
|
|
20894
|
+
if (ctx.isCallActive?.()) {
|
|
20895
|
+
await ctx.callStop();
|
|
20896
|
+
renderInfo("Call session ended.");
|
|
20897
|
+
} else {
|
|
20898
|
+
renderWarning("No active call session.");
|
|
20899
|
+
}
|
|
20900
|
+
return "handled";
|
|
20901
|
+
}
|
|
20785
20902
|
case "bruteforce":
|
|
20786
20903
|
case "brute": {
|
|
20787
20904
|
const isOn = ctx.bruteForceToggle();
|
|
@@ -23168,6 +23285,12 @@ var init_voice = __esm({
|
|
|
23168
23285
|
enabled = false;
|
|
23169
23286
|
modelId = "glados";
|
|
23170
23287
|
ready = false;
|
|
23288
|
+
/**
|
|
23289
|
+
* Callback fired with PCM Int16 data whenever TTS synthesizes audio.
|
|
23290
|
+
* Used by VoiceSession to stream TTS output to WebSocket clients.
|
|
23291
|
+
* Set this to wire TTS → call session broadcast.
|
|
23292
|
+
*/
|
|
23293
|
+
onPCMOutput = null;
|
|
23171
23294
|
session = null;
|
|
23172
23295
|
// ort.InferenceSession
|
|
23173
23296
|
ort = null;
|
|
@@ -23399,6 +23522,14 @@ var init_voice = __esm({
|
|
|
23399
23522
|
const audioData = result["output"].data;
|
|
23400
23523
|
if (audioData.length === 0)
|
|
23401
23524
|
return;
|
|
23525
|
+
if (this.onPCMOutput) {
|
|
23526
|
+
const int16 = new Int16Array(audioData.length);
|
|
23527
|
+
for (let i = 0; i < audioData.length; i++) {
|
|
23528
|
+
const s = Math.max(-1, Math.min(1, audioData[i]));
|
|
23529
|
+
int16[i] = s < 0 ? s * 32768 : s * 32767;
|
|
23530
|
+
}
|
|
23531
|
+
this.onPCMOutput(Buffer.from(int16.buffer), this.config.audio.sample_rate);
|
|
23532
|
+
}
|
|
23402
23533
|
const wavPath = join34(tmpdir6(), `oa-voice-${Date.now()}.wav`);
|
|
23403
23534
|
this.writeWav(audioData, this.config.audio.sample_rate, wavPath);
|
|
23404
23535
|
await this.playWav(wavPath);
|
|
@@ -27495,6 +27626,8 @@ with summary "no_reply" to silently skip without responding.
|
|
|
27495
27626
|
callUrlGetter = null;
|
|
27496
27627
|
/** Callback to start a call session and return the URL (wired from interactive.ts) */
|
|
27497
27628
|
callStarter = null;
|
|
27629
|
+
/** Callback to stop a call session (wired from interactive.ts) */
|
|
27630
|
+
callStopper = null;
|
|
27498
27631
|
/** Callback to write content into the scrollable TUI waterfall area (wired from interactive.ts) */
|
|
27499
27632
|
writeContent = null;
|
|
27500
27633
|
/** Media cache — fileUniqueId → cache entry */
|
|
@@ -27538,6 +27671,10 @@ with summary "no_reply" to silently skip without responding.
|
|
|
27538
27671
|
setCallStarter(starter) {
|
|
27539
27672
|
this.callStarter = starter;
|
|
27540
27673
|
}
|
|
27674
|
+
/** Register callback to stop a call session */
|
|
27675
|
+
setCallStopper(stopper) {
|
|
27676
|
+
this.callStopper = stopper;
|
|
27677
|
+
}
|
|
27541
27678
|
/** Register callback to write content into the scrollable TUI area (status bar guard) */
|
|
27542
27679
|
setWriteContent(fn) {
|
|
27543
27680
|
this.writeContent = fn;
|
|
@@ -27698,6 +27835,16 @@ with summary "no_reply" to silently skip without responding.
|
|
|
27698
27835
|
const isAdmin = this.isAdminUser(msg);
|
|
27699
27836
|
const toolContext = this.resolveToolContext(msg, isAdmin);
|
|
27700
27837
|
const isAdminDM = toolContext === "telegram-admin-dm";
|
|
27838
|
+
if (msg.text.trim().toLowerCase() === "/hangup" && isAdmin && this.callStopper) {
|
|
27839
|
+
const callUrl = this.callUrlGetter?.();
|
|
27840
|
+
if (callUrl) {
|
|
27841
|
+
await this.callStopper();
|
|
27842
|
+
await this.sendMessage(msg.chatId, "Call session ended.");
|
|
27843
|
+
} else {
|
|
27844
|
+
await this.sendMessage(msg.chatId, "No active call session.");
|
|
27845
|
+
}
|
|
27846
|
+
return;
|
|
27847
|
+
}
|
|
27701
27848
|
if (msg.text.trim().toLowerCase() === "/call" && this.callUrlGetter) {
|
|
27702
27849
|
const callUrl = this.callUrlGetter();
|
|
27703
27850
|
if (callUrl) {
|
|
@@ -30854,6 +31001,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
30854
31001
|
}
|
|
30855
31002
|
telegramBridge.setCallUrlGetter(() => voiceSession?.tunnelUrl ?? null);
|
|
30856
31003
|
telegramBridge.setCallStarter(async () => commandCtx.callStart());
|
|
31004
|
+
telegramBridge.setCallStopper(async () => commandCtx.callStop());
|
|
30857
31005
|
telegramBridge.setCommandHandler(async (input) => {
|
|
30858
31006
|
const captured = [];
|
|
30859
31007
|
const origWrite = process.stdout.write;
|
|
@@ -31021,20 +31169,66 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
31021
31169
|
return text ? `Stopped. Last transcript: "${text}"` : "Stopped listening.";
|
|
31022
31170
|
},
|
|
31023
31171
|
// Voice call session — standalone cloudflared tunnel for /call
|
|
31172
|
+
// Wires: WebSocket audio → ASR transcription → agent input → TTS → WebSocket broadcast
|
|
31024
31173
|
async callStart() {
|
|
31025
31174
|
if (voiceSession?.isActive) {
|
|
31026
31175
|
return voiceSession.tunnelUrl;
|
|
31027
31176
|
}
|
|
31028
31177
|
voiceSession = new VoiceSession();
|
|
31178
|
+
const callState = { transcriber: null };
|
|
31179
|
+
const engine = getListenEngine();
|
|
31180
|
+
voiceSession.onUserAudio = (pcmChunk, userId) => {
|
|
31181
|
+
if (callState.transcriber)
|
|
31182
|
+
callState.transcriber.write(pcmChunk);
|
|
31183
|
+
};
|
|
31029
31184
|
voiceSession.on("userConnected", (id, username) => {
|
|
31030
31185
|
writeContent(() => renderVoiceSessionUser("connected", username));
|
|
31186
|
+
if (!callState.transcriber) {
|
|
31187
|
+
engine.createCallTranscriber().then((t) => {
|
|
31188
|
+
if (!t || !voiceSession?.isActive)
|
|
31189
|
+
return;
|
|
31190
|
+
callState.transcriber = t;
|
|
31191
|
+
callState.transcriber.on("transcript", (evt) => {
|
|
31192
|
+
if (!evt.text?.trim() || !voiceSession?.isActive)
|
|
31193
|
+
return;
|
|
31194
|
+
const text = evt.text.trim();
|
|
31195
|
+
writeContent(() => renderVoiceSessionTranscript("user", text));
|
|
31196
|
+
voiceSession?.sendTranscript("user", text);
|
|
31197
|
+
if (evt.isFinal) {
|
|
31198
|
+
rl.write(text + "\n");
|
|
31199
|
+
}
|
|
31200
|
+
});
|
|
31201
|
+
writeContent(() => renderInfo("Call ASR ready \u2014 listening for speech"));
|
|
31202
|
+
}).catch((err) => {
|
|
31203
|
+
writeContent(() => renderWarning(`Call ASR unavailable: ${err instanceof Error ? err.message : String(err)}`));
|
|
31204
|
+
});
|
|
31205
|
+
}
|
|
31031
31206
|
});
|
|
31032
31207
|
voiceSession.on("userDisconnected", (id) => {
|
|
31033
31208
|
writeContent(() => renderVoiceSessionUser("disconnected", id));
|
|
31034
31209
|
});
|
|
31210
|
+
voiceSession.on("idle_timeout", () => {
|
|
31211
|
+
writeContent(() => renderWarning("Call session auto-closed (1 min idle \u2014 no users connected)"));
|
|
31212
|
+
if (callState.transcriber) {
|
|
31213
|
+
callState.transcriber.stop();
|
|
31214
|
+
callState.transcriber = null;
|
|
31215
|
+
}
|
|
31216
|
+
voiceSession = null;
|
|
31217
|
+
});
|
|
31035
31218
|
try {
|
|
31036
31219
|
const tunnelUrl = await voiceSession.start();
|
|
31037
31220
|
writeContent(() => renderVoiceSessionStart(tunnelUrl));
|
|
31221
|
+
if (voiceEngine.enabled && voiceEngine.ready) {
|
|
31222
|
+
const session = voiceSession;
|
|
31223
|
+
voiceEngine.onPCMOutput = (pcm, sampleRate) => {
|
|
31224
|
+
if (session?.isActive) {
|
|
31225
|
+
session.sendSpeakingState(true);
|
|
31226
|
+
session.sendAudioToClients(pcm);
|
|
31227
|
+
const durationMs = pcm.length / 2 / sampleRate * 1e3;
|
|
31228
|
+
setTimeout(() => session.sendSpeakingState(false), durationMs);
|
|
31229
|
+
}
|
|
31230
|
+
};
|
|
31231
|
+
}
|
|
31038
31232
|
if (telegramBridge?.isActive && savedSettings.telegramAdmin) {
|
|
31039
31233
|
const adminChatId = parseInt(savedSettings.telegramAdmin, 10);
|
|
31040
31234
|
if (!isNaN(adminChatId)) {
|
|
@@ -31045,6 +31239,8 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
31045
31239
|
return tunnelUrl;
|
|
31046
31240
|
} catch (err) {
|
|
31047
31241
|
writeContent(() => renderWarning(`Voice session failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
31242
|
+
callState.transcriber?.stop();
|
|
31243
|
+
callState.transcriber = null;
|
|
31048
31244
|
voiceSession = null;
|
|
31049
31245
|
return null;
|
|
31050
31246
|
}
|
|
@@ -31055,6 +31251,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
31055
31251
|
await voiceSession.stop();
|
|
31056
31252
|
writeContent(() => renderVoiceSessionStop(runtime));
|
|
31057
31253
|
voiceSession = null;
|
|
31254
|
+
voiceEngine.onPCMOutput = null;
|
|
31058
31255
|
}
|
|
31059
31256
|
},
|
|
31060
31257
|
isCallActive() {
|
package/package.json
CHANGED