@grinev/opencode-telegram-bot 0.9.1 → 0.9.2

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
@@ -100,8 +100,6 @@ opencode-telegram config
100
100
  | `/stop` | Abort the current task |
101
101
  | `/sessions` | Browse and switch between recent sessions |
102
102
  | `/projects` | Switch between OpenCode projects |
103
- | `/model` | Choose a model from your favorites |
104
- | `/agent` | Switch agent mode (Plan / Build) |
105
103
  | `/rename` | Rename the current session |
106
104
  | `/opencode_start` | Start the OpenCode server remotely |
107
105
  | `/opencode_stop` | Stop the OpenCode server remotely |
@@ -184,7 +182,7 @@ The bot picks up your **favorite models** from OpenCode. To add a model to favor
184
182
  2. Go to model selection
185
183
  3. Hover over the model you want and press **Ctrl+F** to add it to favorites
186
184
 
187
- These favorites will appear in the `/model` command menu in Telegram.
185
+ These favorites will appear in the model picker opened from the bottom Telegram keyboard.
188
186
 
189
187
  A free model (`opencode/big-pickle`) is configured as the default fallback — if you haven't set up any favorites yet, the bot will use it automatically.
190
188
 
@@ -239,7 +237,7 @@ npm run dev
239
237
  - Ensure `opencode serve` is running in your project directory
240
238
  - Check that `OPENCODE_API_URL` points to the correct address (default: `http://localhost:4096`)
241
239
 
242
- **No models in `/model` menu**
240
+ **No models in model picker**
243
241
 
244
242
  - Add models to your OpenCode favorites: open OpenCode TUI, go to model selection, press **Ctrl+F** on desired models
245
243
 
@@ -9,8 +9,6 @@ const COMMAND_DEFINITIONS = [
9
9
  { command: "stop", descriptionKey: "cmd.description.stop" },
10
10
  { command: "sessions", descriptionKey: "cmd.description.sessions" },
11
11
  { command: "projects", descriptionKey: "cmd.description.projects" },
12
- { command: "model", descriptionKey: "cmd.description.model" },
13
- { command: "agent", descriptionKey: "cmd.description.agent" },
14
12
  { command: "rename", descriptionKey: "cmd.description.rename" },
15
13
  { command: "opencode_start", descriptionKey: "cmd.description.opencode_start" },
16
14
  { command: "opencode_stop", descriptionKey: "cmd.description.opencode_stop" },
@@ -3,7 +3,7 @@ import { getLocalizedBotCommands } from "./definitions.js";
3
3
  function formatHelpText() {
4
4
  const commands = getLocalizedBotCommands();
5
5
  const lines = commands.map((item) => `/${item.command} - ${item.description}`);
6
- return `📖 ${t("cmd.description.help")}\n\n${lines.join("\n")}`;
6
+ return `📖 ${t("cmd.description.help")}\n\n${lines.join("\n")}\n\n${t("help.keyboard_hint")}`;
7
7
  }
8
8
  export async function helpCommand(ctx) {
9
9
  await ctx.reply(formatHelpText());
@@ -8,6 +8,7 @@ import { formatModelForDisplay } from "../../model/types.js";
8
8
  import { processManager } from "../../process/manager.js";
9
9
  import { logger } from "../../utils/logger.js";
10
10
  import { t } from "../../i18n/index.js";
11
+ import { sendMessageWithMarkdownFallback } from "../utils/send-with-markdown-fallback.js";
11
12
  export async function statusCommand(ctx) {
12
13
  try {
13
14
  const { data, error } = await opencodeClient.global.health();
@@ -58,7 +59,17 @@ export async function statusCommand(ctx) {
58
59
  message += `\n${t("status.session_not_selected")}\n`;
59
60
  message += t("status.session_hint");
60
61
  }
61
- await ctx.reply(message, { parse_mode: "Markdown" });
62
+ if (ctx.chat) {
63
+ await sendMessageWithMarkdownFallback({
64
+ api: ctx.api,
65
+ chatId: ctx.chat.id,
66
+ text: message,
67
+ parseMode: "Markdown",
68
+ });
69
+ }
70
+ else {
71
+ await ctx.reply(message);
72
+ }
62
73
  }
63
74
  catch (error) {
64
75
  logger.error("[Bot] Error checking server status:", error);
package/dist/bot/index.js CHANGED
@@ -19,8 +19,6 @@ import { projectsCommand, handleProjectSelect } from "./commands/projects.js";
19
19
  import { stopCommand } from "./commands/stop.js";
20
20
  import { opencodeStartCommand } from "./commands/opencode-start.js";
21
21
  import { opencodeStopCommand } from "./commands/opencode-stop.js";
22
- import { handleAgentCommand } from "./commands/agent.js";
23
- import { handleModelCommand } from "./commands/model.js";
24
22
  import { renameCommand, handleRenameCancel, handleRenameTextAnswer } from "./commands/rename.js";
25
23
  import { handleQuestionCallback, showCurrentQuestion, handleQuestionTextAnswer, } from "./handlers/question.js";
26
24
  import { handlePermissionCallback, showPermissionRequest } from "./handlers/permission.js";
@@ -53,6 +51,7 @@ let botInstance = null;
53
51
  let chatIdInstance = null;
54
52
  let commandsInitialized = false;
55
53
  const TELEGRAM_DOCUMENT_CAPTION_MAX_LENGTH = 1024;
54
+ const SESSION_RETRY_PREFIX = "🔁";
56
55
  const __filename = fileURLToPath(import.meta.url);
57
56
  const __dirname = path.dirname(__filename);
58
57
  const TEMP_DIR = path.join(__dirname, "..", ".tmp");
@@ -323,6 +322,21 @@ async function ensureEventSubscription(directory) {
323
322
  logger.error("[Bot] Failed to send session.error message:", err);
324
323
  });
325
324
  });
325
+ summaryAggregator.setOnSessionRetry(async ({ sessionId, message }) => {
326
+ if (!botInstance || !chatIdInstance) {
327
+ return;
328
+ }
329
+ const currentSession = getCurrentSession();
330
+ if (!currentSession || currentSession.id !== sessionId) {
331
+ return;
332
+ }
333
+ const normalizedMessage = message.trim() || t("common.unknown_error");
334
+ const truncatedMessage = normalizedMessage.length > 3500
335
+ ? `${normalizedMessage.slice(0, 3497)}...`
336
+ : normalizedMessage;
337
+ const retryMessage = t("bot.session_retry", { message: truncatedMessage });
338
+ toolMessageBatcher.enqueueUniqueByPrefix(sessionId, retryMessage, SESSION_RETRY_PREFIX);
339
+ });
326
340
  summaryAggregator.setOnSessionDiff(async (_sessionId, diffs) => {
327
341
  if (!pinnedMessageManager.isInitialized()) {
328
342
  return;
@@ -440,8 +454,6 @@ export function createBot() {
440
454
  bot.command("projects", projectsCommand);
441
455
  bot.command("sessions", sessionsCommand);
442
456
  bot.command("new", newCommand);
443
- bot.command("agent", handleAgentCommand);
444
- bot.command("model", handleModelCommand);
445
457
  bot.command("stop", stopCommand);
446
458
  bot.command("rename", renameCommand);
447
459
  bot.on("message:text", unknownCommandMiddleware);
@@ -52,7 +52,7 @@ export async function sendMessageWithMarkdownFallback({ api, chatId, text, optio
52
52
  if (!isTelegramMarkdownParseError(error)) {
53
53
  throw error;
54
54
  }
55
- logger.warn("[Bot] MarkdownV2 parse failed, retrying assistant message in raw mode", error);
55
+ logger.warn("[Bot] Markdown parse failed, retrying assistant message in raw mode", error);
56
56
  await api.sendMessage(chatId, text, options);
57
57
  }
58
58
  }
package/dist/i18n/de.js CHANGED
@@ -4,8 +4,6 @@ export const de = {
4
4
  "cmd.description.stop": "Aktuelle Aktion stoppen",
5
5
  "cmd.description.sessions": "Sitzungen auflisten",
6
6
  "cmd.description.projects": "Projekte auflisten",
7
- "cmd.description.model": "Modell auswählen",
8
- "cmd.description.agent": "Modus auswählen",
9
7
  "cmd.description.opencode_start": "OpenCode-Server starten",
10
8
  "cmd.description.opencode_stop": "OpenCode-Server stoppen",
11
9
  "cmd.description.help": "Hilfe",
@@ -31,7 +29,8 @@ export const de = {
31
29
  "inline.cancelled_callback": "Abgebrochen",
32
30
  "common.unknown": "unbekannt",
33
31
  "common.unknown_error": "unbekannter Fehler",
34
- "start.welcome": "👋 Willkommen beim OpenCode Telegram Bot!\n\nNutze Befehle:\n/projects — Projekt auswählen\n/sessions — Sitzungsliste\n/new — neue Sitzung\n/agentModus wechseln\n/modelModell auswählen\n/status Status\n/help Hilfe",
32
+ "start.welcome": "👋 Willkommen beim OpenCode Telegram Bot!\n\nNutze Befehle:\n/projects — Projekt auswählen\n/sessions — Sitzungsliste\n/new — neue Sitzung\n/statusStatus\n/helpHilfe\n\nNutze die unteren Buttons, um Modus, Modell und Variante zu wählen.",
33
+ "help.keyboard_hint": "💡 Nutze die unteren Buttons für Modus, Modell, Variante und Kontextaktionen.",
35
34
  "help.text": "📖 **Hilfe**\n\n/status - Serverstatus prüfen\n/sessions - Sitzungsliste\n/new - Neue Sitzung erstellen\n/help - Hilfe",
36
35
  "bot.thinking": "💭 Denke...",
37
36
  "bot.project_not_selected": "🏗 Projekt ist nicht ausgewählt.\n\nWähle zuerst ein Projekt mit /projects.",
@@ -42,6 +41,7 @@ export const de = {
42
41
  "bot.session_reset_project_mismatch": "⚠️ Die aktive Sitzung passt nicht zum ausgewählten Projekt und wurde daher zurückgesetzt. Nutze /sessions zur Auswahl oder /new, um eine neue Sitzung zu erstellen.",
43
42
  "bot.prompt_send_error": "Anfrage konnte nicht an OpenCode gesendet werden.",
44
43
  "bot.session_error": "🔴 OpenCode meldete einen Fehler: {message}",
44
+ "bot.session_retry": "🔁 {message}\n\nDer Provider liefert bei wiederholten Versuchen immer wieder denselben Fehler. Mit /stop abbrechen.",
45
45
  "bot.unknown_command": "⚠️ Unbekannter Befehl: {command}. Nutze /help, um verfügbare Befehle zu sehen.",
46
46
  "bot.photo_downloading": "⏳ Lade Foto herunter...",
47
47
  "bot.photo_too_large": "⚠️ Foto ist zu groß (max. {maxSizeMb}MB)",
package/dist/i18n/en.js CHANGED
@@ -4,8 +4,6 @@ export const en = {
4
4
  "cmd.description.stop": "Stop current action",
5
5
  "cmd.description.sessions": "List sessions",
6
6
  "cmd.description.projects": "List projects",
7
- "cmd.description.model": "Select model",
8
- "cmd.description.agent": "Select mode",
9
7
  "cmd.description.opencode_start": "Start OpenCode server",
10
8
  "cmd.description.opencode_stop": "Stop OpenCode server",
11
9
  "cmd.description.help": "Help",
@@ -31,7 +29,8 @@ export const en = {
31
29
  "inline.cancelled_callback": "Cancelled",
32
30
  "common.unknown": "unknown",
33
31
  "common.unknown_error": "unknown error",
34
- "start.welcome": "👋 Welcome to OpenCode Telegram Bot!\n\nUse commands:\n/projects — select project\n/sessions — session list\n/new — new session\n/agentswitch mode\n/modelselect model\n/status status\n/help help",
32
+ "start.welcome": "👋 Welcome to OpenCode Telegram Bot!\n\nUse commands:\n/projects — select project\n/sessions — session list\n/new — new session\n/statusstatus\n/helphelp\n\nUse the bottom buttons to select agent mode, model, and variant.",
33
+ "help.keyboard_hint": "💡 Use the bottom keyboard buttons for agent mode, model, variant, and context actions.",
35
34
  "help.text": "📖 **Help**\n\n/status - Check server status\n/sessions - Session list\n/new - Create new session\n/help - Help",
36
35
  "bot.thinking": "💭 Thinking...",
37
36
  "bot.project_not_selected": "🏗 Project is not selected.\n\nFirst select a project with /projects.",
@@ -42,6 +41,7 @@ export const en = {
42
41
  "bot.session_reset_project_mismatch": "⚠️ Active session does not match the selected project, so it was reset. Use /sessions to pick one or /new to create a new session.",
43
42
  "bot.prompt_send_error": "Failed to send request to OpenCode.",
44
43
  "bot.session_error": "🔴 OpenCode returned an error: {message}",
44
+ "bot.session_retry": "🔁 {message}\n\nProvider keeps returning the same error on repeated retries. Use /stop to abort.",
45
45
  "bot.unknown_command": "⚠️ Unknown command: {command}. Use /help to see available commands.",
46
46
  "bot.photo_downloading": "⏳ Downloading photo...",
47
47
  "bot.photo_too_large": "⚠️ Photo is too large (max {maxSizeMb}MB)",
package/dist/i18n/es.js CHANGED
@@ -4,8 +4,6 @@ export const es = {
4
4
  "cmd.description.stop": "Detener la acción actual",
5
5
  "cmd.description.sessions": "Listar sesiones",
6
6
  "cmd.description.projects": "Listar proyectos",
7
- "cmd.description.model": "Seleccionar modelo",
8
- "cmd.description.agent": "Seleccionar modo",
9
7
  "cmd.description.opencode_start": "Iniciar servidor OpenCode",
10
8
  "cmd.description.opencode_stop": "Detener servidor OpenCode",
11
9
  "cmd.description.help": "Ayuda",
@@ -31,7 +29,8 @@ export const es = {
31
29
  "inline.cancelled_callback": "Cancelado",
32
30
  "common.unknown": "desconocido",
33
31
  "common.unknown_error": "error desconocido",
34
- "start.welcome": "👋 ¡Bienvenido a OpenCode Telegram Bot!\n\nUsa los comandos:\n/projects — seleccionar proyecto\n/sessions — lista de sesiones\n/new — sesión nueva\n/agentcambiar modo\n/modelseleccionar modelo\n/status estado\n/help ayuda",
32
+ "start.welcome": "👋 ¡Bienvenido a OpenCode Telegram Bot!\n\nUsa los comandos:\n/projects — seleccionar proyecto\n/sessions — lista de sesiones\n/new — sesión nueva\n/statusestado\n/helpayuda\n\nUsa los botones inferiores para elegir modo, modelo y variante.",
33
+ "help.keyboard_hint": "💡 Usa los botones inferiores para modo del agente, modelo, variante y acciones de contexto.",
35
34
  "help.text": "📖 **Ayuda**\n\n/status - Ver estado del servidor\n/sessions - Lista de sesiones\n/new - Crear una sesión nueva\n/help - Ayuda",
36
35
  "bot.thinking": "💭 Pensando...",
37
36
  "bot.project_not_selected": "🏗 No hay un proyecto seleccionado.\n\nPrimero selecciona un proyecto con /projects.",
@@ -42,6 +41,7 @@ export const es = {
42
41
  "bot.session_reset_project_mismatch": "⚠️ La sesión activa no coincide con el proyecto seleccionado, así que se reinició. Usa /sessions para elegir una o /new para crear una nueva.",
43
42
  "bot.prompt_send_error": "No se pudo enviar la solicitud a OpenCode.",
44
43
  "bot.session_error": "🔴 OpenCode devolvió un error: {message}",
44
+ "bot.session_retry": "🔁 {message}\n\nEl proveedor devuelve el mismo error en intentos repetidos. Usa /stop para detenerlo.",
45
45
  "bot.unknown_command": "⚠️ Comando desconocido: {command}. Usa /help para ver los comandos disponibles.",
46
46
  "bot.photo_downloading": "⏳ Descargando foto...",
47
47
  "bot.photo_too_large": "⚠️ La foto es demasiado grande (max {maxSizeMb}MB)",
package/dist/i18n/ru.js CHANGED
@@ -4,8 +4,6 @@ export const ru = {
4
4
  "cmd.description.stop": "Прервать текущее действие",
5
5
  "cmd.description.sessions": "Список сессий",
6
6
  "cmd.description.projects": "Список проектов",
7
- "cmd.description.model": "Выбрать модель",
8
- "cmd.description.agent": "Выбрать режим работы",
9
7
  "cmd.description.opencode_start": "Запустить OpenCode сервер",
10
8
  "cmd.description.opencode_stop": "Остановить OpenCode сервер",
11
9
  "cmd.description.help": "Справка",
@@ -31,7 +29,8 @@ export const ru = {
31
29
  "inline.cancelled_callback": "Отменено",
32
30
  "common.unknown": "неизвестна",
33
31
  "common.unknown_error": "неизвестная ошибка",
34
- "start.welcome": "👋 Добро пожаловать в OpenCode Telegram Bot!\n\nИспользуйте команды:\n/projects — выбрать проект\n/sessions — список сессий\n/new — новая сессия\n/agentсменить режим\n/modelвыбрать модель\n/status статус\n/help справка",
32
+ "start.welcome": "👋 Добро пожаловать в OpenCode Telegram Bot!\n\nИспользуйте команды:\n/projects — выбрать проект\n/sessions — список сессий\n/new — новая сессия\n/statusстатус\n/helpсправка\n\nРежим, модель и вариант выбираются кнопками внизу.",
33
+ "help.keyboard_hint": "💡 Режим, модель, вариант и действия с контекстом доступны через нижние кнопки клавиатуры.",
35
34
  "help.text": "📖 **Справка**\n\n/status - Проверить статус сервера\n/sessions - Список сессий\n/new - Создать новую сессию\n/help - Справка",
36
35
  "bot.thinking": "💭 Думаю...",
37
36
  "bot.project_not_selected": "🏗 Проект не выбран.\n\nСначала выберите проект командой /projects.",
@@ -42,6 +41,7 @@ export const ru = {
42
41
  "bot.session_reset_project_mismatch": "⚠️ Активная сессия не соответствует выбранному проекту, поэтому была сброшена. Используйте /sessions для выбора или /new для создания новой сессии.",
43
42
  "bot.prompt_send_error": "Не удалось отправить запрос в OpenCode.",
44
43
  "bot.session_error": "🔴 OpenCode вернул ошибку: {message}",
44
+ "bot.session_retry": "🔁 {message}\n\nПровайдер возвращает одну и ту же ошибку при повторных запросах. Используйте /stop для остановки.",
45
45
  "bot.unknown_command": "⚠️ Неизвестная команда: {command}. Используйте /help для списка команд.",
46
46
  "bot.photo_downloading": "⏳ Скачиваю фото...",
47
47
  "bot.photo_too_large": "⚠️ Фото слишком большое (макс. {maxSizeMb}МБ)",
@@ -123,12 +123,12 @@ export const ru = {
123
123
  "model.menu.favorites_hint": "ℹ️ Список моделей формируется из favorites в OpenCode CLI.",
124
124
  "model.menu.error": "🔴 Не удалось получить список моделей",
125
125
  "variant.model_not_selected_callback": "Ошибка: модель не выбрана",
126
- "variant.changed_callback": "Variant изменен: {name}",
127
- "variant.changed_message": "✅ Variant изменен на: {name}",
128
- "variant.change_error_callback": "Ошибка при смене variant",
126
+ "variant.changed_callback": "Вариант изменен: {name}",
127
+ "variant.changed_message": "✅ Вариант изменен на: {name}",
128
+ "variant.change_error_callback": "Ошибка при смене варианта",
129
129
  "variant.select_model_first": "⚠️ Сначала выберите модель",
130
130
  "variant.menu.empty": "⚠️ Нет доступных вариантов",
131
- "variant.menu.current": "Текущий variant: {name}\n\nВыберите variant:",
131
+ "variant.menu.current": "Текущий вариант: {name}\n\nВыберите вариант:",
132
132
  "variant.menu.error": "🔴 Не удалось получить список вариантов",
133
133
  "context.button.confirm": "✅ Да, сжать контекст",
134
134
  "context.no_active_session": "⚠️ Нет активной сессии. Создайте сессию командой /new",
package/dist/i18n/zh.js CHANGED
@@ -4,8 +4,6 @@ export const zh = {
4
4
  "cmd.description.stop": "停止当前操作",
5
5
  "cmd.description.sessions": "列出会话",
6
6
  "cmd.description.projects": "列出项目",
7
- "cmd.description.model": "选择模型",
8
- "cmd.description.agent": "选择模式",
9
7
  "cmd.description.opencode_start": "启动 OpenCode 服务器",
10
8
  "cmd.description.opencode_stop": "停止 OpenCode 服务器",
11
9
  "cmd.description.help": "帮助",
@@ -31,7 +29,8 @@ export const zh = {
31
29
  "inline.cancelled_callback": "已取消",
32
30
  "common.unknown": "未知",
33
31
  "common.unknown_error": "未知错误",
34
- "start.welcome": "👋 欢迎使用 OpenCode Telegram Bot!\n\n可用命令:\n/projects — 选择项目\n/sessions — 会话列表\n/new — 新建会话\n/agent — 切换模式\n/model — 选择模型\n/status — 状态\n/help — 帮助",
32
+ "start.welcome": "👋 欢迎使用 OpenCode Telegram Bot!\n\n可用命令:\n/projects — 选择项目\n/sessions — 会话列表\n/new — 新建会话\n/status — 状态\n/help — 帮助\n\n请使用底部按钮选择模式、模型和变体。",
33
+ "help.keyboard_hint": "💡 代理模式、模型、变体和上下文操作请使用底部键盘按钮。",
35
34
  "help.text": "📖 **帮助**\n\n/status - 查看服务器状态\n/sessions - 会话列表\n/new - 创建新会话\n/help - 帮助",
36
35
  "bot.thinking": "💭 思考中...",
37
36
  "bot.project_not_selected": "🏗 未选择项目。\n\n请先使用 /projects 选择一个项目。",
@@ -42,6 +41,7 @@ export const zh = {
42
41
  "bot.session_reset_project_mismatch": "⚠️ 活动会话与所选项目不匹配,因此已重置。使用 /sessions 选择一个会话,或 /new 创建新会话。",
43
42
  "bot.prompt_send_error": "向 OpenCode 发送请求失败。",
44
43
  "bot.session_error": "🔴 OpenCode 返回错误:{message}",
44
+ "bot.session_retry": "🔁 {message}\n\n提供方在重复重试时持续返回同一错误。使用 /stop 可停止。",
45
45
  "bot.unknown_command": "⚠️ 未知命令:{command}。使用 /help 查看可用命令。",
46
46
  "bot.photo_downloading": "⏳ 正在下载照片...",
47
47
  "bot.photo_too_large": "⚠️ 照片过大(最大 {maxSizeMb}MB)",
@@ -40,6 +40,7 @@ class SummaryAggregator {
40
40
  onTokensCallback = null;
41
41
  onSessionCompactedCallback = null;
42
42
  onSessionErrorCallback = null;
43
+ onSessionRetryCallback = null;
43
44
  onPermissionCallback = null;
44
45
  onSessionDiffCallback = null;
45
46
  onFileChangeCallback = null;
@@ -81,6 +82,9 @@ class SummaryAggregator {
81
82
  setOnSessionError(callback) {
82
83
  this.onSessionErrorCallback = callback;
83
84
  }
85
+ setOnSessionRetry(callback) {
86
+ this.onSessionRetryCallback = callback;
87
+ }
84
88
  setOnPermission(callback) {
85
89
  this.onPermissionCallback = callback;
86
90
  }
@@ -434,10 +438,24 @@ class SummaryAggregator {
434
438
  return hash.toString(36);
435
439
  }
436
440
  handleSessionStatus(event) {
437
- const { sessionID } = event.properties;
441
+ const { sessionID, status } = event.properties;
438
442
  if (sessionID !== this.currentSessionId) {
439
443
  return;
440
444
  }
445
+ if (status?.type !== "retry" || !this.onSessionRetryCallback) {
446
+ return;
447
+ }
448
+ const callback = this.onSessionRetryCallback;
449
+ const message = status.message?.trim() || "Unknown retry error";
450
+ logger.warn(`[Aggregator] Session retry: session=${sessionID}, attempt=${status.attempt ?? "n/a"}, message=${message}`);
451
+ setImmediate(() => {
452
+ callback({
453
+ sessionId: sessionID,
454
+ attempt: status.attempt,
455
+ message,
456
+ next: status.next,
457
+ });
458
+ });
441
459
  }
442
460
  handleSessionIdle(event) {
443
461
  const { sessionID } = event.properties;
@@ -44,21 +44,10 @@ export class ToolMessageBatcher {
44
44
  return this.intervalSeconds;
45
45
  }
46
46
  enqueue(sessionId, message) {
47
- const normalizedMessage = message.trim();
48
- if (!sessionId || normalizedMessage.length === 0) {
49
- return;
50
- }
51
- if (this.intervalSeconds === 0) {
52
- const expectedGeneration = this.generation;
53
- logger.debug(`[ToolBatcher] Sending immediate text message: session=${sessionId}`);
54
- void this.enqueueTask(sessionId, () => this.sendTextSafe(sessionId, normalizedMessage, "immediate", expectedGeneration));
55
- return;
56
- }
57
- const queue = this.queues.get(sessionId) ?? [];
58
- queue.push({ kind: "text", text: normalizedMessage });
59
- this.queues.set(sessionId, queue);
60
- logger.debug(`[ToolBatcher] Queued text message: session=${sessionId}, queueSize=${queue.length}, interval=${this.intervalSeconds}s`);
61
- this.ensureTimer(sessionId);
47
+ this.enqueueTextInternal(sessionId, message);
48
+ }
49
+ enqueueUniqueByPrefix(sessionId, message, prefix) {
50
+ this.enqueueTextInternal(sessionId, message, prefix);
62
51
  }
63
52
  enqueueFile(sessionId, fileData) {
64
53
  if (!sessionId) {
@@ -142,6 +131,34 @@ export class ToolMessageBatcher {
142
131
  this.sessionTasks.set(sessionId, nextTask);
143
132
  return nextTask;
144
133
  }
134
+ enqueueTextInternal(sessionId, message, uniquePrefix) {
135
+ const normalizedMessage = message.trim();
136
+ if (!sessionId || normalizedMessage.length === 0) {
137
+ return;
138
+ }
139
+ if (this.intervalSeconds === 0) {
140
+ const expectedGeneration = this.generation;
141
+ logger.debug(`[ToolBatcher] Sending immediate text message: session=${sessionId}`);
142
+ void this.enqueueTask(sessionId, () => this.sendTextSafe(sessionId, normalizedMessage, "immediate", expectedGeneration));
143
+ return;
144
+ }
145
+ const normalizedPrefix = uniquePrefix?.trim();
146
+ const queue = this.queues.get(sessionId) ?? [];
147
+ if (normalizedPrefix) {
148
+ const existingUniqueMessage = queue.find((item) => item.kind === "text" && item.text.startsWith(normalizedPrefix));
149
+ if (existingUniqueMessage) {
150
+ existingUniqueMessage.text = normalizedMessage;
151
+ this.queues.set(sessionId, queue);
152
+ logger.debug(`[ToolBatcher] Updated queued unique text message: session=${sessionId}, prefix=${normalizedPrefix}, interval=${this.intervalSeconds}s`);
153
+ this.ensureTimer(sessionId);
154
+ return;
155
+ }
156
+ }
157
+ queue.push({ kind: "text", text: normalizedMessage });
158
+ this.queues.set(sessionId, queue);
159
+ logger.debug(`[ToolBatcher] Queued text message: session=${sessionId}, queueSize=${queue.length}, interval=${this.intervalSeconds}s`);
160
+ this.ensureTimer(sessionId);
161
+ }
145
162
  async flushSessionInternal(sessionId, reason) {
146
163
  const expectedGeneration = this.generation;
147
164
  this.clearTimer(sessionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grinev/opencode-telegram-bot",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Telegram bot client for OpenCode to run and monitor coding tasks from chat.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,17 +0,0 @@
1
- import { showAgentSelectionMenu } from "../handlers/agent.js";
2
- import { logger } from "../../utils/logger.js";
3
- import { t } from "../../i18n/index.js";
4
- /**
5
- * Handler for /agent command
6
- * Shows inline menu to select agent mode
7
- */
8
- export async function handleAgentCommand(ctx) {
9
- logger.debug("[AgentCommand] /agent command received");
10
- try {
11
- await showAgentSelectionMenu(ctx);
12
- }
13
- catch (err) {
14
- logger.error("[AgentCommand] Error showing agent menu:", err);
15
- await ctx.reply(t("error.load_agents"));
16
- }
17
- }
@@ -1,17 +0,0 @@
1
- import { showModelSelectionMenu } from "../handlers/model.js";
2
- import { logger } from "../../utils/logger.js";
3
- import { t } from "../../i18n/index.js";
4
- /**
5
- * Handler for /model command
6
- * Shows inline menu to select model from favorites
7
- */
8
- export async function handleModelCommand(ctx) {
9
- logger.debug("[ModelCommand] /model command received");
10
- try {
11
- await showModelSelectionMenu(ctx);
12
- }
13
- catch (err) {
14
- logger.error("[ModelCommand] Error showing model menu:", err);
15
- await ctx.reply(t("error.load_models"));
16
- }
17
- }