open-agents-ai 0.51.0 → 0.53.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.
Files changed (2) hide show
  1. package/dist/index.js +148 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -17284,6 +17284,8 @@ function renderSlashHelp() {
17284
17284
  ["/listen manual", "Manual mode \u2014 press Enter to submit (turns off auto)"],
17285
17285
  ["/listen auto", "Auto-submit after 3s silence (blinking \u25CF indicator)"],
17286
17286
  ["/listen stop", "Stop listening"],
17287
+ ["/call", "Start voice call session (cloudflared tunnel + PCM frontend)"],
17288
+ ["/call stop", "Stop active call session"],
17287
17289
  ["/cost", "Show session token cost breakdown"],
17288
17290
  ["/evaluate", "Evaluate last completed task (LLM quality scoring)"],
17289
17291
  ["/task-type", "Set task type (code, document, analysis, plan, general, auto)"],
@@ -17672,6 +17674,7 @@ var init_render = __esm({
17672
17674
  "/update",
17673
17675
  "/voice",
17674
17676
  "/listen",
17677
+ "/call",
17675
17678
  "/stream",
17676
17679
  "/verbose",
17677
17680
  "/dream",
@@ -20711,6 +20714,41 @@ async function handleSlashCommand(input, ctx) {
20711
20714
  renderInfo(msg);
20712
20715
  return "handled";
20713
20716
  }
20717
+ case "call": {
20718
+ if (!ctx.callStart) {
20719
+ renderWarning("Call mode not available in this context.");
20720
+ return "handled";
20721
+ }
20722
+ if (arg === "stop" || arg === "off") {
20723
+ if (ctx.isCallActive?.()) {
20724
+ await ctx.callStop?.();
20725
+ renderInfo("Call session ended.");
20726
+ } else {
20727
+ renderWarning("No active call session.");
20728
+ }
20729
+ return "handled";
20730
+ }
20731
+ if (ctx.isCallActive?.()) {
20732
+ const url = ctx.getCallUrl?.();
20733
+ if (url) {
20734
+ renderInfo(`Call session active: ${c2.bold(url)}`);
20735
+ } else {
20736
+ renderWarning("Call session active but no URL available yet.");
20737
+ }
20738
+ return "handled";
20739
+ }
20740
+ try {
20741
+ const url = await ctx.callStart();
20742
+ if (url) {
20743
+ renderInfo(`Call session started: ${c2.bold(url)}`);
20744
+ } else {
20745
+ renderWarning("Failed to start call session.");
20746
+ }
20747
+ } catch (err) {
20748
+ renderError(`Call error: ${err instanceof Error ? err.message : String(err)}`);
20749
+ }
20750
+ return "handled";
20751
+ }
20714
20752
  case "bruteforce":
20715
20753
  case "brute": {
20716
20754
  const isOn = ctx.bruteForceToggle();
@@ -27416,6 +27454,10 @@ with summary "no_reply" to silently skip without responding.
27416
27454
  toolPolicyConfig;
27417
27455
  /** Command handler for admin DM slash commands (wired from interactive.ts) */
27418
27456
  commandHandler = null;
27457
+ /** Callback to get active call session URL (wired from interactive.ts) */
27458
+ callUrlGetter = null;
27459
+ /** Callback to write content into the scrollable TUI waterfall area (wired from interactive.ts) */
27460
+ writeContent = null;
27419
27461
  /** Media cache — fileUniqueId → cache entry */
27420
27462
  mediaCache = /* @__PURE__ */ new Map();
27421
27463
  /** Media cache directory */
@@ -27449,6 +27491,14 @@ with summary "no_reply" to silently skip without responding.
27449
27491
  setCommandHandler(handler) {
27450
27492
  this.commandHandler = handler;
27451
27493
  }
27494
+ /** Register callback to get active call URL for /call button forwarding */
27495
+ setCallUrlGetter(getter) {
27496
+ this.callUrlGetter = getter;
27497
+ }
27498
+ /** Register callback to write content into the scrollable TUI area (status bar guard) */
27499
+ setWriteContent(fn) {
27500
+ this.writeContent = fn;
27501
+ }
27452
27502
  /** Register event handler for sub-agent activity (waterfall view) */
27453
27503
  setOnSubAgentEvent(handler) {
27454
27504
  this.onSubAgentEvent = handler;
@@ -27462,6 +27512,14 @@ with summary "no_reply" to silently skip without responding.
27462
27512
  get botUsername() {
27463
27513
  return this.state.botUsername;
27464
27514
  }
27515
+ /** Write to the scrollable TUI waterfall area (respects status bar scroll region) */
27516
+ tuiWrite(fn) {
27517
+ if (this.writeContent) {
27518
+ this.writeContent(fn);
27519
+ } else {
27520
+ fn();
27521
+ }
27522
+ }
27465
27523
  /** Start polling for Telegram messages */
27466
27524
  async start() {
27467
27525
  if (this.polling)
@@ -27597,10 +27655,21 @@ with summary "no_reply" to silently skip without responding.
27597
27655
  const isAdmin = this.isAdminUser(msg);
27598
27656
  const toolContext = this.resolveToolContext(msg, isAdmin);
27599
27657
  const isAdminDM = toolContext === "telegram-admin-dm";
27658
+ if (msg.text.trim().toLowerCase() === "/call" && this.callUrlGetter) {
27659
+ const callUrl = this.callUrlGetter();
27660
+ if (callUrl) {
27661
+ await this.sendCallButton(msg.chatId, callUrl);
27662
+ return;
27663
+ }
27664
+ if (!isAdmin) {
27665
+ await this.sendMessage(msg.chatId, "No active call session. Ask the admin to start one with /call.");
27666
+ return;
27667
+ }
27668
+ }
27600
27669
  if (isAdminDM && msg.text.startsWith("/") && this.commandHandler) {
27601
27670
  const cmdName = msg.text.split(/\s+/)[0].slice(1).toLowerCase();
27602
27671
  if (cmdName !== "start") {
27603
- renderTelegramSubAgentEvent(msg.username, `command: ${msg.text}`);
27672
+ this.tuiWrite(() => renderTelegramSubAgentEvent(msg.username, `command: ${msg.text}`));
27604
27673
  try {
27605
27674
  const result = await this.commandHandler(msg.text);
27606
27675
  if (result) {
@@ -27620,7 +27689,7 @@ with summary "no_reply" to silently skip without responding.
27620
27689
  const existing = this.subAgents.get(msg.chatId);
27621
27690
  if (existing && !existing.aborted) {
27622
27691
  existing.runner.injectUserMessage(msg.text);
27623
- renderTelegramSubAgentEvent(msg.username, "mid-conversation steering injected");
27692
+ this.tuiWrite(() => renderTelegramSubAgentEvent(msg.username, "mid-conversation steering injected"));
27624
27693
  return;
27625
27694
  }
27626
27695
  const subAgent = {
@@ -27638,7 +27707,7 @@ with summary "no_reply" to silently skip without responding.
27638
27707
  this.subAgents.set(msg.chatId, subAgent);
27639
27708
  this.state.activeSubAgents = this.subAgents.size;
27640
27709
  subAgent.typingInterval = this.startTypingIndicator(msg.chatId);
27641
- renderTelegramSubAgentStart(msg.username, msg.text, isAdminDM);
27710
+ this.tuiWrite(() => renderTelegramSubAgentStart(msg.username, msg.text, isAdminDM));
27642
27711
  try {
27643
27712
  if (isAdminDM) {
27644
27713
  const msgId = await this.sendLiveMessage(msg.chatId, "<i>\u23F3 Processing...</i>");
@@ -27654,7 +27723,7 @@ with summary "no_reply" to silently skip without responding.
27654
27723
  subAgent.typingInterval = null;
27655
27724
  }
27656
27725
  if (result === "no_reply" || result === "") {
27657
- renderTelegramSubAgentEvent(msg.username, "discretion: skipped reply");
27726
+ this.tuiWrite(() => renderTelegramSubAgentEvent(msg.username, "discretion: skipped reply"));
27658
27727
  return;
27659
27728
  }
27660
27729
  const finalText = result || "I couldn't generate a response. Please try again.";
@@ -27665,14 +27734,14 @@ with summary "no_reply" to silently skip without responding.
27665
27734
  } else {
27666
27735
  await this.sendMessageHTML(msg.chatId, finalHtml, msg.chatType !== "private" ? msg.messageId : void 0);
27667
27736
  }
27668
- renderTelegramSubAgentComplete(msg.username, finalText);
27737
+ this.tuiWrite(() => renderTelegramSubAgentComplete(msg.username, finalText));
27669
27738
  } catch (err) {
27670
27739
  if (subAgent.typingInterval) {
27671
27740
  clearInterval(subAgent.typingInterval);
27672
27741
  subAgent.typingInterval = null;
27673
27742
  }
27674
27743
  const errMsg = err instanceof Error ? err.message : String(err);
27675
- renderTelegramSubAgentError(msg.username, errMsg);
27744
+ this.tuiWrite(() => renderTelegramSubAgentError(msg.username, errMsg));
27676
27745
  if (isAdminDM && subAgent.liveMessageId) {
27677
27746
  await this.editLiveMessage(msg.chatId, subAgent.liveMessageId, `\u274C Error: ${errMsg}`).catch(() => {
27678
27747
  });
@@ -27999,11 +28068,31 @@ Telegram admin: @${msg.username}` : `Telegram ${isGroup ? "group" : "public"} ch
27999
28068
  this.state.messagesSent++;
28000
28069
  return result.result?.message_id ?? null;
28001
28070
  } catch (err) {
28002
- renderWarning(`Failed to send Telegram message: ${err instanceof Error ? err.message : String(err)}`);
28071
+ this.tuiWrite(() => renderWarning(`Failed to send Telegram message: ${err instanceof Error ? err.message : String(err)}`));
28003
28072
  return null;
28004
28073
  }
28005
28074
  }
28006
28075
  }
28076
+ /** Send an inline keyboard button to start a voice call session */
28077
+ async sendCallButton(chatId, url) {
28078
+ try {
28079
+ const result = await this.apiCall("sendMessage", {
28080
+ chat_id: chatId,
28081
+ text: "\u{1F4DE} <b>Voice Call Session</b>\n\nTap the button below to join the live voice call with the agent.",
28082
+ parse_mode: "HTML",
28083
+ reply_markup: {
28084
+ inline_keyboard: [[
28085
+ { text: "\u{1F50A} Start Call", url }
28086
+ ]]
28087
+ }
28088
+ });
28089
+ this.state.messagesSent++;
28090
+ return result.result?.message_id ?? null;
28091
+ } catch (err) {
28092
+ this.tuiWrite(() => renderWarning(`Failed to send call button: ${err instanceof Error ? err.message : String(err)}`));
28093
+ return null;
28094
+ }
28095
+ }
28007
28096
  /**
28008
28097
  * Send a voice message (WAV/OGG) to a Telegram chat using multipart form-data.
28009
28098
  * Telegram requires OGG/Opus for sendVoice, but also accepts sendAudio with WAV.
@@ -28082,7 +28171,7 @@ ${caption}\r
28082
28171
  }
28083
28172
  return null;
28084
28173
  } catch (err) {
28085
- renderWarning(`Failed to send voice message: ${err instanceof Error ? err.message : String(err)}`);
28174
+ this.tuiWrite(() => renderWarning(`Failed to send voice message: ${err instanceof Error ? err.message : String(err)}`));
28086
28175
  return null;
28087
28176
  }
28088
28177
  }
@@ -28219,7 +28308,7 @@ ${caption}\r
28219
28308
  };
28220
28309
  if (this.agentConfig && this.repoRoot) {
28221
28310
  this.handleMessageWithSubAgent(msg).catch((err) => {
28222
- renderWarning(`Telegram sub-agent error: ${err instanceof Error ? err.message : String(err)}`);
28311
+ this.tuiWrite(() => renderWarning(`Telegram sub-agent error: ${err instanceof Error ? err.message : String(err)}`));
28223
28312
  });
28224
28313
  } else {
28225
28314
  this.onMessage(msg);
@@ -30683,6 +30772,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
30683
30772
  if (adminId) {
30684
30773
  telegramBridge.setAdmin(adminId);
30685
30774
  }
30775
+ telegramBridge.setWriteContent(writeContent);
30686
30776
  telegramBridge.setOnSubAgentEvent((chatId, username, event) => {
30687
30777
  if (event.type === "tool_call" && event.toolName) {
30688
30778
  const argsPreview = event.toolArgs ? JSON.stringify(event.toolArgs).slice(0, 60) : "";
@@ -30705,6 +30795,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
30705
30795
  telegramBridge.setVoiceEngine(voiceEngine);
30706
30796
  telegramBridge.voiceEnabled = true;
30707
30797
  }
30798
+ telegramBridge.setCallUrlGetter(() => voiceSession?.tunnelUrl ?? null);
30708
30799
  telegramBridge.setCommandHandler(async (input) => {
30709
30800
  const captured = [];
30710
30801
  const origWrite = process.stdout.write;
@@ -30867,6 +30958,54 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
30867
30958
  }
30868
30959
  return text ? `Stopped. Last transcript: "${text}"` : "Stopped listening.";
30869
30960
  },
30961
+ // Voice call session — standalone cloudflared tunnel for /call
30962
+ async callStart() {
30963
+ if (voiceSession?.isActive) {
30964
+ return voiceSession.tunnelUrl;
30965
+ }
30966
+ voiceSession = new VoiceSession();
30967
+ voiceSession.on("userConnected", (id, username) => {
30968
+ writeContent(() => renderVoiceSessionUser("connected", username));
30969
+ });
30970
+ voiceSession.on("userDisconnected", (id) => {
30971
+ writeContent(() => renderVoiceSessionUser("disconnected", id));
30972
+ });
30973
+ try {
30974
+ const tunnelUrl = await voiceSession.start();
30975
+ writeContent(() => renderVoiceSessionStart(tunnelUrl));
30976
+ if (telegramBridge?.isActive && savedSettings.telegramAdmin) {
30977
+ const adminChatId = parseInt(savedSettings.telegramAdmin, 10);
30978
+ if (!isNaN(adminChatId)) {
30979
+ telegramBridge.sendCallButton(adminChatId, tunnelUrl).catch(() => {
30980
+ });
30981
+ }
30982
+ }
30983
+ return tunnelUrl;
30984
+ } catch (err) {
30985
+ writeContent(() => renderWarning(`Voice session failed: ${err instanceof Error ? err.message : String(err)}`));
30986
+ voiceSession = null;
30987
+ return null;
30988
+ }
30989
+ },
30990
+ async callStop() {
30991
+ if (voiceSession?.isActive) {
30992
+ const runtime = voiceSession.runtime;
30993
+ await voiceSession.stop();
30994
+ writeContent(() => renderVoiceSessionStop(runtime));
30995
+ voiceSession = null;
30996
+ }
30997
+ },
30998
+ isCallActive() {
30999
+ return voiceSession?.isActive ?? false;
31000
+ },
31001
+ getCallUrl() {
31002
+ return voiceSession?.tunnelUrl ?? null;
31003
+ },
31004
+ async sendCallButton(chatId, url) {
31005
+ if (telegramBridge?.isActive) {
31006
+ await telegramBridge.sendCallButton(chatId, url);
31007
+ }
31008
+ },
30870
31009
  getEmojis: () => getEmojisEnabled(),
30871
31010
  setEmojis: (enabled) => setEmojisEnabled(enabled),
30872
31011
  getColors: () => getColorsEnabled(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",