open-agents-ai 0.187.583 → 0.187.584

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/README.md CHANGED
@@ -3365,7 +3365,7 @@ All settings commands accept `--local` to save to project `.oa/settings.json` in
3365
3365
 
3366
3366
  `/platforms menu` opens a keyboard-driven connectivity surface for platform status and onboarding. The same surface is available through `/gateway menu`.
3367
3367
 
3368
- Supported adapter IDs are `telegram`, `discord`, `slack`, `matrix`, and `webhook`. Telegram is wired into the existing bridge controls, so the menu can save the bot token, save the admin user ID, start the bridge, stop it, and show status. The other platform adapters expose the shared connector configuration surface: enable/disable, token environment variable, base URL or webhook URL, default target/channel/room, polling vs webhook mode, redacted config display, and status/health reporting.
3368
+ Supported adapter IDs are `telegram`, `discord`, `slack`, `matrix`, and `webhook`. Telegram is wired into the existing bridge controls, so the menu can save the bot token, save the admin user ID, set the interaction mode, start the bridge, stop it, and show status. The other platform adapters expose the shared connector configuration surface: enable/disable, token environment variable, base URL or webhook URL, default target/channel/room, polling vs webhook mode, redacted config display, and status/health reporting.
3369
3369
 
3370
3370
  Use environment variables for credentials:
3371
3371
 
@@ -3416,7 +3416,7 @@ The steering sub-agent uses the same model and backend as the main agent with `m
3416
3416
 
3417
3417
  <div align="right"><a href="#top">back to top</a></div>
3418
3418
 
3419
- Connect the agent to a Telegram bot. Each incoming message spawns a dedicated sub-agent that handles the conversation independently — visible in the terminal waterfall alongside other agent activity.
3419
+ Connect the agent to a Telegram bot. Telegram can run in auto, chat, or action mode: conversational messages get rapid streamed replies in chat mode, while codebase/file/run requests use dedicated action sub-agents that are visible in the terminal waterfall alongside other agent activity.
3420
3420
 
3421
3421
  ```bash
3422
3422
  /telegram --key <token> # Save bot token (persisted to .oa/settings.json)
@@ -3424,6 +3424,7 @@ Connect the agent to a Telegram bot. Each incoming message spawns a dedicated su
3424
3424
  /telegram # Toggle bridge on/off (uses saved key)
3425
3425
  /telegram status # Show connection status + active sub-agents
3426
3426
  /telegram stop # Disconnect and kill all sub-agents
3427
+ /telegram mode auto|chat|action # Set interaction routing profile
3427
3428
  /telegram auth # Show a one-time TUI-only admin auth code
3428
3429
  /telegram auth cancel # Cancel the pending admin auth code
3429
3430
  /telegram bot <username> <text> # Send bot-to-bot message by @username
@@ -3438,7 +3439,17 @@ Connect the agent to a Telegram bot. Each incoming message spawns a dedicated su
3438
3439
  /telegram delete-reactions <chat> --user <id> # Delete recent reactions
3439
3440
  ```
3440
3441
 
3441
- The bot token and admin ID are persisted to project settings, so you only need to set them once. After that, bare `/telegram` toggles the bridge on and off like a service watchdog.
3442
+ The bot token, admin ID, and interaction mode are persisted to settings, so you only need to set them once. After that, bare `/telegram` toggles the bridge on and off like a service watchdog.
3443
+
3444
+ ### Telegram Interaction Modes
3445
+
3446
+ Use `/telegram mode auto|chat|action` to control how inbound Telegram messages are routed:
3447
+
3448
+ - **auto** — short greetings, quick questions, playful messages, and conversational turns use fast streamed chat replies; explicit codebase/file/command/run/test requests use action sub-agents.
3449
+ - **chat** — every non-command message gets a direct quick-chat completion with no tool loop. This is best for rapid back-and-forth conversation.
3450
+ - **action** — every non-command message runs through the Telegram sub-agent path with the configured tool policy.
3451
+
3452
+ Telegram quick-chat views and action sub-agent views both appear as blue plane-labeled items in the systems header. Clicking one swaps the main scrollable window to that Telegram-only content buffer; press Esc to return to the primary view.
3442
3453
 
3443
3454
  ### Telegram Admin Authentication
3444
3455
 
@@ -3462,7 +3473,7 @@ The Telegram bridge handles modern Bot API traffic directly:
3462
3473
  - **Direct-message channels** — direct-message channel/topic metadata is preserved on normalized Telegram messages for routing and future adapter logic.
3463
3474
  - **Drafts and profile chat reads** — `/telegram draft` can set or clear message drafts, and `/telegram personal <user_id> <limit>` fetches recent messages from a user's profile personal chat.
3464
3475
  - **Reactions/admin helpers** — `/telegram delete-reaction` removes a message reaction, `/telegram delete-reactions` removes recent reactions by a user/chat, and `/telegram admins <chat> [--bots]` can include bot administrators.
3465
- - **TUI sub-agent visibility** — Telegram conversations register as blue plane-labeled sub-agent views in the systems panel and show an active plane indicator in the footer while the bridge or Telegram sub-agents are active.
3476
+ - **TUI Telegram visibility** — Telegram quick-chat and action conversations register as blue plane-labeled views in the systems panel; clicking a view swaps the main scrollable window to that Telegram-only buffer, and the footer shows an active plane indicator while the bridge or Telegram work is active.
3466
3477
 
3467
3478
  ### Admin Slash Command Passthrough
3468
3479
 
package/dist/index.js CHANGED
@@ -549063,6 +549063,7 @@ var init_command_registry = __esm({
549063
549063
  ["/telegram", "Toggle Telegram bridge on/off (uses saved key)"],
549064
549064
  ["/telegram status", "Show Telegram bridge status"],
549065
549065
  ["/telegram stop", "Disconnect Telegram bridge"],
549066
+ ["/telegram mode auto|chat|action", "Set Telegram interaction routing: auto, fast chat, or action sub-agent"],
549066
549067
  ["/telegram auth", "Show a TUI-only one-time code for Telegram admin authentication"],
549067
549068
  ["/telegram auth cancel", "Cancel the pending Telegram admin authentication code"],
549068
549069
  ["/telegram bot <username> <text>", "Send a Bot API bot-to-bot message by @username"],
@@ -558482,6 +558483,8 @@ var init_status_bar = __esm({
558482
558483
  zones.push({ w: 2, render: () => "" });
558483
558484
  const voiceLabel = this._voiceActive ? ` ${this._voiceModelId || "voice"} ` : " voice ";
558484
558485
  zones.push({ w: voiceLabel.length + 2, render: () => "" });
558486
+ const telegramLabel = this._telegramStatus.activeSubAgents > 0 ? ` ✈ tg ${this._telegramStatus.activeSubAgents} ` : " ✈ tg ";
558487
+ zones.push({ w: telegramLabel.length + 2, render: () => "" });
558485
558488
  zones.push({ w: 9, render: () => "" });
558486
558489
  let pages = [];
558487
558490
  let cur = [];
@@ -559608,7 +559611,7 @@ var init_status_bar = __esm({
559608
559611
  * If currently on the "systems" panel, re-render to show updated agent list. */
559609
559612
  renderAgentTabs() {
559610
559613
  if (!this.active) return;
559611
- if (this.currentHeaderPanel === "systems") {
559614
+ if (String(this.currentHeaderPanel).startsWith("sys-")) {
559612
559615
  this.refreshHeaderContent();
559613
559616
  }
559614
559617
  }
@@ -579313,6 +579316,7 @@ sleep 1
579313
579316
  case "telegram":
579314
579317
  case "tg": {
579315
579318
  const parts = arg ? arg.split(/\s+/) : [];
579319
+ const isLocal = hasLocal;
579316
579320
  if (parts[0] === "stop" || parts[0] === "off") {
579317
579321
  if (ctx3.isTelegramActive?.()) {
579318
579322
  ctx3.telegramStop?.();
@@ -579325,6 +579329,23 @@ sleep 1
579325
579329
  ctx3.telegramStatus?.();
579326
579330
  return "handled";
579327
579331
  }
579332
+ if (parts[0] === "mode" || parts[0] === "profile") {
579333
+ const requested = parts.slice(1).find((part) => !part.startsWith("--"));
579334
+ if (!requested) {
579335
+ const current = ctx3.getTelegramSettings?.()?.mode ?? "auto";
579336
+ renderInfo(`Telegram interaction mode: ${c3.bold(current)}`);
579337
+ renderInfo("Modes: auto (chat vs action), chat (fast replies), action (sub-agent tool loop).");
579338
+ return "handled";
579339
+ }
579340
+ if (requested !== "auto" && requested !== "chat" && requested !== "action") {
579341
+ renderWarning("Usage: /telegram mode auto|chat|action [--local]");
579342
+ return "handled";
579343
+ }
579344
+ ctx3.saveTelegramSettings?.({ mode: requested, local: isLocal });
579345
+ ctx3.telegramSetInteractionMode?.(requested);
579346
+ renderInfo(`Telegram interaction mode set to ${c3.bold(requested)}${isLocal ? " (project)" : " (global)"}.`);
579347
+ return "handled";
579348
+ }
579328
579349
  if (parts[0] === "auth" || parts[0] === "authenticate") {
579329
579350
  if (parts[1] === "cancel") {
579330
579351
  const cancelled = ctx3.telegramCancelAdminAuth?.() ?? false;
@@ -579500,7 +579521,6 @@ sleep 1
579500
579521
  }
579501
579522
  return "handled";
579502
579523
  }
579503
- const isLocal = parts.includes("--local");
579504
579524
  const keyIdx = parts.indexOf("--key");
579505
579525
  const adminIdx = parts.indexOf("--admin");
579506
579526
  if (keyIdx !== -1 || adminIdx !== -1) {
@@ -579570,6 +579590,7 @@ sleep 1
579570
579590
  renderInfo(" /telegram Toggle on/off");
579571
579591
  renderInfo(" /telegram stop Stop bridge");
579572
579592
  renderInfo(" /telegram status Show status");
579593
+ renderInfo(" /telegram mode auto|chat|action Set interaction routing profile");
579573
579594
  renderInfo(" /telegram auth Show one-time admin auth code");
579574
579595
  renderInfo(" /telegram auth cancel Cancel pending admin auth code");
579575
579596
  renderInfo(" /telegram bot <username> <text> Send bot-to-bot message");
@@ -580072,6 +580093,15 @@ async function showConfigEditor(ctx3) {
580072
580093
  value: String(merged.telegramAdmin ?? "[not set]"),
580073
580094
  detail: String(merged.telegramAdmin ?? "[not set]")
580074
580095
  },
580096
+ {
580097
+ key: "telegramMode",
580098
+ label: "telegramMode",
580099
+ kind: "enum",
580100
+ value: String(merged.telegramMode ?? "auto"),
580101
+ detail: String(merged.telegramMode ?? "auto"),
580102
+ options: ["auto", "chat", "action"],
580103
+ settingsKey: "telegramMode"
580104
+ },
580075
580105
  // -- Actions --
580076
580106
  {
580077
580107
  key: "__h_actions__",
@@ -580337,6 +580367,7 @@ async function showPlatformOnboardingMenu(ctx3, id) {
580337
580367
  { key: "telegram-status", label: "Status", detail: "Show bridge status" },
580338
580368
  { key: "telegram-token", label: "Set bot token", detail: "Saved to existing Telegram settings" },
580339
580369
  { key: "telegram-admin", label: "Set admin user id", detail: "Restricts remote admin controls" },
580370
+ { key: "telegram-mode", label: "Interaction mode", detail: ctx3.getTelegramSettings?.()?.mode ?? "auto" },
580340
580371
  { key: "telegram-start", label: "Start bridge", detail: "Uses saved token" },
580341
580372
  { key: "telegram-stop", label: "Stop bridge", detail: "Disconnect long polling" }
580342
580373
  ] : [
@@ -580415,7 +580446,13 @@ async function showPlatformOnboardingMenu(ctx3, id) {
580415
580446
  if (!result.confirmed || !result.key) return;
580416
580447
  if (id === "telegram") {
580417
580448
  if (result.key === "telegram-status") ctx3.telegramStatus?.();
580418
- else if (result.key === "telegram-start") {
580449
+ else if (result.key === "telegram-mode") {
580450
+ const current2 = ctx3.getTelegramSettings?.()?.mode ?? "auto";
580451
+ const next = current2 === "auto" ? "chat" : current2 === "chat" ? "action" : "auto";
580452
+ ctx3.saveTelegramSettings?.({ mode: next });
580453
+ ctx3.telegramSetInteractionMode?.(next);
580454
+ renderInfo(`Telegram interaction mode set to ${next}.`);
580455
+ } else if (result.key === "telegram-start") {
580419
580456
  const settings = ctx3.getTelegramSettings?.() ?? {};
580420
580457
  if (!settings.key) renderWarning("No Telegram bot token configured.");
580421
580458
  else await ctx3.telegramStart?.(settings.key, settings.admin);
@@ -590857,6 +590894,28 @@ import { mkdirSync as mkdirSync58, existsSync as existsSync100, unlinkSync as un
590857
590894
  import { join as join117, resolve as resolve35, basename as basename21 } from "node:path";
590858
590895
  import { writeFile as writeFileAsync } from "node:fs/promises";
590859
590896
  import { createHash as createHash18, randomInt } from "node:crypto";
590897
+ function classifyTelegramInteraction(text, mode = "auto", options2 = {}) {
590898
+ if (mode === "chat") return "chat";
590899
+ if (mode === "action") return "action";
590900
+ const trimmed = text.trim();
590901
+ if (!trimmed) return "chat";
590902
+ if (options2.isSlashCommand || trimmed.startsWith("/")) return "action";
590903
+ if (options2.hasMedia) return "action";
590904
+ const hasAction = TELEGRAM_ACTION_INTENT_RE.test(trimmed);
590905
+ const hasCodebaseContext = TELEGRAM_CODEBASE_CONTEXT_RE.test(trimmed) || TELEGRAM_COMMANDISH_RE.test(trimmed) || /[`'"]?[\w./-]+\.(?:ts|tsx|js|jsx|mjs|cjs|json|md|py|rs|go|java|css|scss|html|yml|yaml|toml|sh|sql)[`'"]?/i.test(trimmed);
590906
+ if (hasAction && hasCodebaseContext) return "action";
590907
+ if (/^(please\s+|pls\s+|can you\s+|could you\s+|we need to\s+|i need you to\s+)?(fix|implement|patch|edit|refactor|run|test|build|install|debug|commit|deploy|publish)\b/i.test(trimmed)) {
590908
+ return "action";
590909
+ }
590910
+ if (TELEGRAM_COMMANDISH_RE.test(trimmed)) return "action";
590911
+ if (trimmed.includes("```") && hasAction) return "action";
590912
+ if (trimmed.length > 360 && hasAction) return "action";
590913
+ if (/\b(write|draft)\b.*\b(poem|story|joke|email|message|caption|song|haiku)\b/i.test(trimmed)) return "chat";
590914
+ if (TELEGRAM_CHAT_INTENT_RE.test(trimmed)) return "chat";
590915
+ if (/[??]\s*$/.test(trimmed) && !hasAction) return "chat";
590916
+ if (trimmed.length <= 180 && !hasCodebaseContext && !hasAction) return "chat";
590917
+ return hasAction ? "action" : "chat";
590918
+ }
590860
590919
  function convertMarkdownToTelegramHTML(md) {
590861
590920
  let html = md;
590862
590921
  html = html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -591160,11 +591219,13 @@ function mimeForPath(path11, fallbackKind) {
591160
591219
  if (fallbackKind === "video") return "video/mp4";
591161
591220
  return "application/octet-stream";
591162
591221
  }
591163
- function renderTelegramStart(botUsername, adminId) {
591222
+ function renderTelegramStart(botUsername, adminId, mode = "auto") {
591164
591223
  process.stdout.write(`
591165
591224
  ${c3.cyan("✈")} ${c3.bold("Telegram Bridge")} connected as @${botUsername}
591166
591225
  `);
591167
- process.stdout.write(` ${c3.dim("Sub-agent mode: each message spawns a dedicated agent")}
591226
+ process.stdout.write(` ${c3.dim(`Interaction mode: ${mode}`)}
591227
+ `);
591228
+ process.stdout.write(` ${c3.dim("Auto mode uses quick chat for conversational turns and sub-agents for action requests")}
591168
591229
  `);
591169
591230
  if (adminId) {
591170
591231
  process.stdout.write(` ${c3.dim(`Admin: ${adminId} (full memory + tools)`)}
@@ -591178,17 +591239,19 @@ function renderTelegramStart(botUsername, adminId) {
591178
591239
 
591179
591240
  `);
591180
591241
  }
591181
- function renderTelegramStatus(active, botUsername, adminId, activeSubAgents) {
591242
+ function renderTelegramStatus(active, botUsername, adminId, activeSubAgents, mode = "auto") {
591182
591243
  if (active) {
591183
591244
  process.stdout.write(`
591184
591245
  ${c3.green("●")} Telegram bridge: ${c3.bold("ACTIVE")} (@${botUsername ?? "?"})
591246
+ `);
591247
+ process.stdout.write(` Mode: ${mode}
591185
591248
  `);
591186
591249
  if (adminId) {
591187
591250
  process.stdout.write(` Admin: ${adminId}
591188
591251
  `);
591189
591252
  }
591190
591253
  if (activeSubAgents && activeSubAgents > 0) {
591191
- process.stdout.write(` Active sub-agents: ${activeSubAgents}
591254
+ process.stdout.write(` Active Telegram work: ${activeSubAgents}
591192
591255
  `);
591193
591256
  }
591194
591257
  process.stdout.write(` ${c3.dim("Use /telegram to toggle off")}
@@ -591246,7 +591309,7 @@ function renderTelegramSubAgentError(username, error) {
591246
591309
  process.stdout.write(` ${c3.dim("⎿")} ${c3.red("✘")} @${username}: ${c3.dim(preview)}
591247
591310
  `);
591248
591311
  }
591249
- var TELEGRAM_SAFETY_PROMPT, ADMIN_DM_PROMPT, ADMIN_GROUP_PROMPT, GROUP_REPLY_DISCRETION_PROMPT, MEDIA_CACHE_TTL_MS, TelegramBridge;
591312
+ var TELEGRAM_SAFETY_PROMPT, ADMIN_DM_PROMPT, ADMIN_GROUP_PROMPT, GROUP_REPLY_DISCRETION_PROMPT, TELEGRAM_CHAT_MODE_PROMPT, TELEGRAM_ACTION_INTENT_RE, TELEGRAM_CODEBASE_CONTEXT_RE, TELEGRAM_COMMANDISH_RE, TELEGRAM_CHAT_INTENT_RE, MEDIA_CACHE_TTL_MS, TelegramBridge;
591250
591313
  var init_telegram_bridge = __esm({
591251
591314
  "packages/cli/src/tui/telegram-bridge.ts"() {
591252
591315
  "use strict";
@@ -591306,6 +591369,20 @@ REPLY DISCRETION: You are in a group chat. Only respond if:
591306
591369
  If the message is casual group chatter not directed at you, use task_complete
591307
591370
  with summary "no_reply" to silently skip without responding.
591308
591371
  `.trim();
591372
+ TELEGRAM_CHAT_MODE_PROMPT = `
591373
+ You are Open Agents replying in Telegram quick-chat mode.
591374
+
591375
+ Rules:
591376
+ 1. Reply directly to the Telegram user, conversationally and concisely.
591377
+ 2. Do not inspect, summarize, or expose local files, paths, secrets, tool output, or runtime internals.
591378
+ 3. Do not claim that you changed code, ran commands, or checked the workspace in quick-chat mode.
591379
+ 4. If the user asks for codebase action while quick-chat mode is forced, tell them to switch Telegram to action mode or send a concrete action request in auto mode.
591380
+ 5. For ordinary chat, status questions, greetings, quick explanations, and playful messages, answer immediately without tool-use narration.
591381
+ `.trim();
591382
+ TELEGRAM_ACTION_INTENT_RE = /\b(implement|fix|patch|edit|write|create|delete|remove|refactor|run|execute|test|build|install|debug|investigate|diagnose|validate|commit|push|pull|merge|rebase|deploy|publish|read|open|inspect|grep|search|find|list|apply|change|update)\b/i;
591383
+ TELEGRAM_CODEBASE_CONTEXT_RE = /\b(repo|repository|codebase|workspace|working directory|file|files|folder|directory|src|source|test|tests|package|pnpm|npm|node|git|branch|commit|pr|pull request|issue|shell|terminal|cli|command|function|class|component|endpoint|api)\b/i;
591384
+ TELEGRAM_COMMANDISH_RE = /(^|\s)(pnpm|npm|node|git|rg|grep|sed|cat|ls|cd|mkdir|rm|mv|cp|curl|docker|pytest|vitest|tsc)\b/i;
591385
+ TELEGRAM_CHAT_INTENT_RE = /\b(hi|hello|hey|thanks|thank you|lol|haha|joke|how are you|what's up|whats up|can you hear|are you there|explain|what is|what are|why|how does|tell me|opinion|quick question)\b/i;
591309
591386
  MEDIA_CACHE_TTL_MS = 30 * 60 * 1e3;
591310
591387
  TelegramBridge = class {
591311
591388
  constructor(botToken, onMessage, agentConfig, repoRoot, toolPolicyConfig) {
@@ -591327,6 +591404,7 @@ with summary "no_reply" to silently skip without responding.
591327
591404
  active: false,
591328
591405
  botUsername: "",
591329
591406
  supportsGuestQueries: false,
591407
+ interactionMode: "auto",
591330
591408
  startedAt: "",
591331
591409
  messagesReceived: 0,
591332
591410
  messagesSent: 0,
@@ -591336,6 +591414,12 @@ with summary "no_reply" to silently skip without responding.
591336
591414
  adminUserId = null;
591337
591415
  /** Active sub-agents by chat/guest session key */
591338
591416
  subAgents = /* @__PURE__ */ new Map();
591417
+ /** Active direct chat completions, counted with Telegram activity in the TUI */
591418
+ activeChatViews = /* @__PURE__ */ new Set();
591419
+ /** Lightweight chat history by chat/guest session key */
591420
+ chatHistory = /* @__PURE__ */ new Map();
591421
+ /** Telegram interaction routing profile */
591422
+ interactionMode = "auto";
591339
591423
  /** Event handler for forwarding sub-agent events to parent TUI */
591340
591424
  onSubAgentEvent = null;
591341
591425
  /** Tool policy config — user overrides from config */
@@ -591375,6 +591459,13 @@ with summary "no_reply" to silently skip without responding.
591375
591459
  setAdmin(userId) {
591376
591460
  this.adminUserId = userId;
591377
591461
  }
591462
+ setInteractionMode(mode) {
591463
+ this.interactionMode = mode;
591464
+ this.state.interactionMode = mode;
591465
+ }
591466
+ getInteractionMode() {
591467
+ return this.interactionMode;
591468
+ }
591378
591469
  /** Update tool policy config at runtime (e.g., from /disable command) */
591379
591470
  setToolPolicyConfig(config) {
591380
591471
  this.toolPolicyConfig = config;
@@ -591418,7 +591509,7 @@ with summary "no_reply" to silently skip without responding.
591418
591509
  return this.polling;
591419
591510
  }
591420
591511
  get stats() {
591421
- return { ...this.state, activeSubAgents: this.subAgents.size };
591512
+ return { ...this.state, activeSubAgents: this.activeTelegramInteractionCount() };
591422
591513
  }
591423
591514
  get botUsername() {
591424
591515
  return this.state.botUsername;
@@ -591476,9 +591567,28 @@ with summary "no_reply" to silently skip without responding.
591476
591567
  if (msg.guestQueryId) return `guest:${msg.guestQueryId}`;
591477
591568
  return `chat:${String(msg.chatId)}`;
591478
591569
  }
591570
+ activeTelegramInteractionCount() {
591571
+ return this.subAgents.size + this.activeChatViews.size;
591572
+ }
591573
+ refreshActiveTelegramInteractionCount() {
591574
+ this.state.activeSubAgents = this.activeTelegramInteractionCount();
591575
+ }
591479
591576
  canUseChatActions(msg) {
591480
591577
  return !msg.guestQueryId && (typeof msg.chatId === "number" || String(msg.chatId).startsWith("@"));
591481
591578
  }
591579
+ recordChatHistory(sessionKey, entry) {
591580
+ const existing = this.chatHistory.get(sessionKey) ?? [];
591581
+ existing.push(entry);
591582
+ if (existing.length > 16) existing.splice(0, existing.length - 16);
591583
+ this.chatHistory.set(sessionKey, existing);
591584
+ }
591585
+ shouldFastChatReplyInGroup(msg) {
591586
+ if (msg.chatType === "private" || msg.guestQueryId) return true;
591587
+ const lower = msg.text.toLowerCase();
591588
+ const botMention = this.state.botUsername ? lower.includes(`@${this.state.botUsername.toLowerCase()}`) : false;
591589
+ if (botMention || msg.replyToMessageId) return true;
591590
+ return /[??]/.test(msg.text) || /\b(help|question|how|what|why|when|where|can you|could you|explain)\b/i.test(msg.text);
591591
+ }
591482
591592
  async handleAdminAuthCommand(msg) {
591483
591593
  const normalized = msg.text.trim();
591484
591594
  const lower = normalized.toLowerCase();
@@ -591542,6 +591652,7 @@ with summary "no_reply" to silently skip without responding.
591542
591652
  active: true,
591543
591653
  botUsername: me.result?.username ?? "unknown",
591544
591654
  supportsGuestQueries: Boolean(me.result?.supports_guest_queries),
591655
+ interactionMode: this.interactionMode,
591545
591656
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
591546
591657
  messagesReceived: 0,
591547
591658
  messagesSent: 0,
@@ -591571,6 +591682,8 @@ with summary "no_reply" to silently skip without responding.
591571
591682
  if (agent.typingInterval) clearInterval(agent.typingInterval);
591572
591683
  }
591573
591684
  this.subAgents.clear();
591685
+ this.activeChatViews.clear();
591686
+ this.refreshActiveTelegramInteractionCount();
591574
591687
  }
591575
591688
  // ── Typing indicator ──────────────────────────────────────────────────
591576
591689
  /** Start sending "typing" indicator every 4 seconds */
@@ -591761,6 +591874,14 @@ Join: ${newUrl}`);
591761
591874
  }
591762
591875
  return;
591763
591876
  }
591877
+ const route = classifyTelegramInteraction(msg.text, this.interactionMode, {
591878
+ hasMedia: !!msg.media || !!msg.poll || !!msg.livePhoto,
591879
+ isSlashCommand: msg.text.trim().startsWith("/")
591880
+ });
591881
+ if (route === "chat") {
591882
+ await this.handleTelegramChatCompletion(msg, toolContext);
591883
+ return;
591884
+ }
591764
591885
  const subAgent = {
591765
591886
  chatId: msg.chatId,
591766
591887
  username: msg.username,
@@ -591776,7 +591897,7 @@ Join: ${newUrl}`);
591776
591897
  pendingMessages: []
591777
591898
  };
591778
591899
  this.subAgents.set(sessionKey, subAgent);
591779
- this.state.activeSubAgents = this.subAgents.size;
591900
+ this.refreshActiveTelegramInteractionCount();
591780
591901
  this.subAgentViewCallbacks?.onRegister(
591781
591902
  subAgent.viewId,
591782
591903
  `✈ @${msg.username || "telegram"}`,
@@ -591846,10 +591967,173 @@ Join: ${newUrl}`);
591846
591967
  }
591847
591968
  } finally {
591848
591969
  this.subAgents.delete(sessionKey);
591849
- this.state.activeSubAgents = this.subAgents.size;
591970
+ this.refreshActiveTelegramInteractionCount();
591850
591971
  this.subAgentViewCallbacks?.onComplete(subAgent.viewId);
591851
591972
  }
591852
591973
  }
591974
+ /** Fast Telegram chat path: direct streamed completion, no tool loop. */
591975
+ async handleTelegramChatCompletion(msg, toolContext) {
591976
+ if (!this.shouldFastChatReplyInGroup(msg)) {
591977
+ this.tuiWrite(() => renderTelegramSubAgentEvent(msg.username, "chat-mode discretion: skipped group chatter"));
591978
+ return;
591979
+ }
591980
+ const sessionKey = this.sessionKeyForMessage(msg);
591981
+ const viewId = `${this.viewIdForMessage(msg)}-chat`;
591982
+ let typingInterval = null;
591983
+ let liveMessageId = null;
591984
+ let accumulated = "";
591985
+ let lastEditMs = 0;
591986
+ let lastViewWriteMs = 0;
591987
+ const progressLines = [`💬 Quick chat mode (${this.interactionMode})`];
591988
+ this.activeChatViews.add(viewId);
591989
+ this.refreshActiveTelegramInteractionCount();
591990
+ this.subAgentViewCallbacks?.onRegister(
591991
+ viewId,
591992
+ `✈ @${msg.username || "telegram"}`,
591993
+ `Telegram quick chat: ${msg.text.slice(0, 160)}`
591994
+ );
591995
+ this.subAgentViewCallbacks?.onWrite(viewId, `✈ Telegram quick chat from @${msg.username}: ${msg.text}`);
591996
+ this.subAgentViewCallbacks?.onWrite(viewId, `route: chat (${this.interactionMode})`);
591997
+ this.subAgentViewCallbacks?.onStatus(viewId, "running");
591998
+ if (this.canUseChatActions(msg)) {
591999
+ typingInterval = this.startTypingIndicator(msg.chatId);
592000
+ }
592001
+ this.tuiWrite(() => renderTelegramSubAgentEvent(msg.username, `chat-mode fast reply (${this.interactionMode})`));
592002
+ try {
592003
+ if (!msg.guestQueryId) {
592004
+ liveMessageId = await this.sendLiveMessage(
592005
+ msg.chatId,
592006
+ renderTelegramLiveProgressHTML(progressLines, ""),
592007
+ msg.chatType !== "private" ? msg.messageId : void 0
592008
+ );
592009
+ }
592010
+ const mediaContext = msg.media || msg.livePhoto ? "Attachment received. Quick-chat mode does not inspect media; use action mode for media analysis." : "";
592011
+ const finalText = await this.runTelegramChatCompletion(
592012
+ msg,
592013
+ toolContext,
592014
+ mediaContext,
592015
+ (nextText) => {
592016
+ accumulated = nextText;
592017
+ const now = Date.now();
592018
+ if (now - lastViewWriteMs > 900) {
592019
+ lastViewWriteMs = now;
592020
+ this.subAgentViewCallbacks?.onWrite(viewId, `stream: ${sanitizeTelegramProgressText(nextText, 180)}`);
592021
+ }
592022
+ if (liveMessageId && !msg.guestQueryId && now - lastEditMs > 900) {
592023
+ lastEditMs = now;
592024
+ this.editLiveMessage(
592025
+ msg.chatId,
592026
+ liveMessageId,
592027
+ renderTelegramLiveProgressHTML(progressLines, accumulated)
592028
+ ).catch(() => {
592029
+ });
592030
+ }
592031
+ }
592032
+ );
592033
+ if (typingInterval) {
592034
+ clearInterval(typingInterval);
592035
+ typingInterval = null;
592036
+ }
592037
+ const cleaned = stripTelegramHiddenThinking(finalText || accumulated).trim() || "I heard you.";
592038
+ this.recordChatHistory(sessionKey, { role: "user", text: msg.text });
592039
+ this.recordChatHistory(sessionKey, { role: "assistant", text: cleaned });
592040
+ const finalHtml = convertMarkdownToTelegramHTML(cleaned);
592041
+ if (liveMessageId && !msg.guestQueryId) {
592042
+ await this.editLiveMessage(msg.chatId, liveMessageId, finalHtml);
592043
+ } else {
592044
+ await this.replyToTelegramMessage(msg, finalHtml, {
592045
+ html: true,
592046
+ replyToMessageId: msg.chatType !== "private" ? msg.messageId : void 0
592047
+ });
592048
+ }
592049
+ this.subAgentViewCallbacks?.onWrite(viewId, `completed: ${cleaned}`);
592050
+ this.subAgentViewCallbacks?.onStatus(viewId, "completed");
592051
+ } catch (err) {
592052
+ if (typingInterval) {
592053
+ clearInterval(typingInterval);
592054
+ typingInterval = null;
592055
+ }
592056
+ const errMsg = err instanceof Error ? err.message : String(err);
592057
+ this.tuiWrite(() => renderTelegramSubAgentError(msg.username, errMsg));
592058
+ this.subAgentViewCallbacks?.onWrite(viewId, `error: ${errMsg}`);
592059
+ this.subAgentViewCallbacks?.onStatus(viewId, "failed");
592060
+ if (liveMessageId && !msg.guestQueryId) {
592061
+ await this.editLiveMessage(msg.chatId, liveMessageId, `❌ Error: ${escapeTelegramHTML(errMsg)}`).catch(() => {
592062
+ });
592063
+ } else {
592064
+ await this.replyToTelegramMessage(msg, "Sorry, I couldn't process that quick chat message.").catch(() => {
592065
+ });
592066
+ }
592067
+ } finally {
592068
+ this.activeChatViews.delete(viewId);
592069
+ this.refreshActiveTelegramInteractionCount();
592070
+ this.subAgentViewCallbacks?.onComplete(viewId);
592071
+ }
592072
+ }
592073
+ buildTelegramChatMessages(msg, toolContext, mediaContext = "") {
592074
+ const isAdminDM = toolContext === "telegram-admin-dm";
592075
+ const isAdminGroup = toolContext === "telegram-admin-group";
592076
+ const isGroup = msg.chatType !== "private";
592077
+ const sessionKey = this.sessionKeyForMessage(msg);
592078
+ const history = this.chatHistory.get(sessionKey) ?? [];
592079
+ const safety = isAdminDM ? "Admin private DM. The user is trusted, but quick-chat mode still has no tool access." : isAdminGroup ? ADMIN_GROUP_PROMPT : TELEGRAM_SAFETY_PROMPT;
592080
+ const groupHint = isGroup ? `Telegram group: ${msg.chatTitle || "unknown"}. Keep the reply short and relevant.` : "Telegram private chat.";
592081
+ const messages2 = [
592082
+ {
592083
+ role: "system",
592084
+ content: `${TELEGRAM_CHAT_MODE_PROMPT}
592085
+
592086
+ ${safety}
592087
+
592088
+ ${groupHint}`
592089
+ },
592090
+ ...history.slice(-8).map((entry) => ({
592091
+ role: entry.role,
592092
+ content: entry.text
592093
+ }))
592094
+ ];
592095
+ const chatLabel = isGroup ? ` in group "${msg.chatTitle || "unknown"}"` : "";
592096
+ messages2.push({
592097
+ role: "user",
592098
+ content: `Telegram message from @${msg.username}${chatLabel}:
592099
+ ${msg.text}${mediaContext ? `
592100
+
592101
+ ${mediaContext}` : ""}`
592102
+ });
592103
+ return messages2;
592104
+ }
592105
+ async runTelegramChatCompletion(msg, toolContext, mediaContext, onToken) {
592106
+ const config = this.agentConfig;
592107
+ const backend = new OllamaAgenticBackend(
592108
+ config.backendUrl,
592109
+ config.model,
592110
+ config.apiKey
592111
+ );
592112
+ const request = {
592113
+ messages: this.buildTelegramChatMessages(msg, toolContext, mediaContext),
592114
+ tools: [],
592115
+ temperature: 0.4,
592116
+ maxTokens: 700,
592117
+ timeoutMs: Math.min(config.timeoutMs ?? 3e4, 3e4),
592118
+ think: false
592119
+ };
592120
+ let accumulated = "";
592121
+ const streamable = backend;
592122
+ if (typeof streamable.chatCompletionStream === "function") {
592123
+ for await (const chunk of streamable.chatCompletionStream(request)) {
592124
+ if (chunk.type === "content" && !chunk.thinking && chunk.content) {
592125
+ accumulated += chunk.content;
592126
+ onToken(accumulated);
592127
+ }
592128
+ }
592129
+ }
592130
+ if (!accumulated.trim()) {
592131
+ const result = await backend.chatCompletion(request);
592132
+ accumulated = result.choices[0]?.message?.content ?? "";
592133
+ if (accumulated) onToken(accumulated);
592134
+ }
592135
+ return stripTelegramHiddenThinking(accumulated).trim();
592136
+ }
591853
592137
  /** Run a sub-agent for a Telegram message */
591854
592138
  async runSubAgent(msg, subAgent, mediaContext = "") {
591855
592139
  const config = this.agentConfig;
@@ -622073,6 +622357,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
622073
622357
  currentConfig,
622074
622358
  repoRoot
622075
622359
  );
622360
+ telegramBridge.setInteractionMode(savedSettings.telegramMode ?? "auto");
622076
622361
  if (adminId) {
622077
622362
  telegramBridge.setAdmin(adminId);
622078
622363
  }
@@ -622185,7 +622470,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
622185
622470
  );
622186
622471
  });
622187
622472
  writeContent(
622188
- () => renderTelegramStart(telegramBridge.botUsername, adminId)
622473
+ () => renderTelegramStart(telegramBridge.botUsername, adminId, savedSettings.telegramMode ?? "auto")
622189
622474
  );
622190
622475
  showPrompt();
622191
622476
  },
@@ -622204,7 +622489,8 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
622204
622489
  getTelegramSettings() {
622205
622490
  return {
622206
622491
  key: savedSettings.telegramKey,
622207
- admin: savedSettings.telegramAdmin
622492
+ admin: savedSettings.telegramAdmin,
622493
+ mode: savedSettings.telegramMode
622208
622494
  };
622209
622495
  },
622210
622496
  saveTelegramSettings(settings) {
@@ -622214,9 +622500,13 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
622214
622500
  if (settings.admin !== void 0) {
622215
622501
  savedSettings.telegramAdmin = settings.admin;
622216
622502
  }
622503
+ if (settings.mode !== void 0) {
622504
+ savedSettings.telegramMode = settings.mode;
622505
+ }
622217
622506
  const payload = {
622218
622507
  ...settings.key !== void 0 ? { telegramKey: settings.key } : {},
622219
- ...settings.admin !== void 0 ? { telegramAdmin: settings.admin } : {}
622508
+ ...settings.admin !== void 0 ? { telegramAdmin: settings.admin } : {},
622509
+ ...settings.mode !== void 0 ? { telegramMode: settings.mode } : {}
622220
622510
  };
622221
622511
  if (settings.local) {
622222
622512
  saveProjectSettings(repoRoot, payload);
@@ -622224,6 +622514,10 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
622224
622514
  saveGlobalSettings(payload);
622225
622515
  }
622226
622516
  },
622517
+ telegramSetInteractionMode(mode) {
622518
+ savedSettings.telegramMode = mode;
622519
+ telegramBridge?.setInteractionMode(mode);
622520
+ },
622227
622521
  telegramStatus() {
622228
622522
  const active = telegramBridge?.isActive ?? false;
622229
622523
  const botUser = active ? telegramBridge?.botUsername : void 0;
@@ -622233,7 +622527,8 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
622233
622527
  active,
622234
622528
  botUser,
622235
622529
  savedSettings.telegramAdmin,
622236
- subAgents
622530
+ subAgents,
622531
+ savedSettings.telegramMode ?? "auto"
622237
622532
  )
622238
622533
  );
622239
622534
  },
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.583",
3
+ "version": "0.187.584",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.583",
9
+ "version": "0.187.584",
10
10
  "hasInstallScript": true,
11
11
  "license": "CC-BY-NC-4.0",
12
12
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.583",
3
+ "version": "0.187.584",
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",