talon-agent 3.12.1 → 3.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talon-agent",
3
- "version": "3.12.1",
3
+ "version": "3.12.2",
4
4
  "description": "Multi-frontend AI agent with full tool access, streaming, cron jobs, and plugin system",
5
5
  "author": "Dylan Neve",
6
6
  "license": "MIT",
@@ -102,7 +102,7 @@
102
102
  "@grammyjs/transformer-throttler": "^1.2.1",
103
103
  "@kilocode/sdk": "^7.2.22",
104
104
  "@modelcontextprotocol/sdk": "^1.29.0",
105
- "@openai/agents": "^0.13.0",
105
+ "@openai/agents": "^0.14.0",
106
106
  "@openai/codex-sdk": "^0.145.0",
107
107
  "@opencode-ai/sdk": "^1.17.4",
108
108
  "@playwright/mcp": "0.0.78",
@@ -15,7 +15,12 @@ import {
15
15
  MAX_OVERDUE_MS,
16
16
  type ScheduledMessage,
17
17
  } from "../../../storage/scheduled-store.js";
18
- import { sendText, toPositiveId } from "./shared.js";
18
+ import {
19
+ noteRichMessageFailure,
20
+ richMessagesAvailable,
21
+ sendText,
22
+ toPositiveId,
23
+ } from "./shared.js";
19
24
  import { TELEGRAM_MAX_TEXT, type TelegramActionHandlers } from "./types.js";
20
25
 
21
26
  // ── Inline keyboards ─────────────────────────────────────────────────────────
@@ -135,10 +140,8 @@ async function fireScheduled(bot: Bot, entry: ScheduledMessage): Promise<void> {
135
140
  return;
136
141
  }
137
142
  const keyboard = built.keyboard;
138
- await bot.api.sendMessage(chatId, markdownToTelegramHtml(entry.text), {
139
- parse_mode: "HTML",
140
- reply_markup: { inline_keyboard: keyboard },
141
- reply_parameters: replyTo ? { message_id: replyTo } : undefined,
143
+ await sendText(bot, chatId, entry.text, replyTo, {
144
+ inline_keyboard: keyboard,
142
145
  });
143
146
  } else {
144
147
  await sendText(bot, chatId, entry.text, replyTo);
@@ -246,12 +249,24 @@ export const messagingHandlers: TelegramActionHandlers = {
246
249
  ok: false,
247
250
  error: `Text too long (max ${TELEGRAM_MAX_TEXT})`,
248
251
  };
249
- const html = markdownToTelegramHtml(text);
250
252
  await withRetry(async () => {
253
+ if (richMessagesAvailable()) {
254
+ try {
255
+ await bot.api.editMessageText(chatId, Number(body.message_id), {
256
+ markdown: text,
257
+ });
258
+ return;
259
+ } catch (richErr) {
260
+ noteRichMessageFailure(richErr, `edit chat=${chatId}`);
261
+ }
262
+ }
251
263
  try {
252
- await bot.api.editMessageText(chatId, Number(body.message_id), html, {
253
- parse_mode: "HTML",
254
- });
264
+ await bot.api.editMessageText(
265
+ chatId,
266
+ Number(body.message_id),
267
+ markdownToTelegramHtml(text),
268
+ { parse_mode: "HTML" },
269
+ );
255
270
  } catch {
256
271
  await bot.api.editMessageText(chatId, Number(body.message_id), text);
257
272
  }
@@ -309,24 +324,15 @@ export const messagingHandlers: TelegramActionHandlers = {
309
324
  const text = String(body.text ?? "");
310
325
  if (text.length > TELEGRAM_MAX_TEXT)
311
326
  return { ok: false, error: `Text too long` };
312
- const html = markdownToTelegramHtml(text);
313
327
  const rows = body.rows as ButtonSpec[][];
314
328
  const built = buildInlineKeyboard(rows);
315
329
  if ("error" in built) return { ok: false, error: built.error };
316
330
  const keyboard = built.keyboard;
317
331
  gateway.incrementMessages(chatId);
318
- try {
319
- const sent = await bot.api.sendMessage(chatId, html, {
320
- parse_mode: "HTML",
321
- reply_markup: { inline_keyboard: keyboard },
322
- });
323
- return { ok: true, message_id: sent.message_id };
324
- } catch {
325
- const sent = await bot.api.sendMessage(chatId, text, {
326
- reply_markup: { inline_keyboard: keyboard },
327
- });
328
- return { ok: true, message_id: sent.message_id };
329
- }
332
+ const messageId = await sendText(bot, chatId, text, undefined, {
333
+ inline_keyboard: keyboard,
334
+ });
335
+ return { ok: true, message_id: messageId };
330
336
  },
331
337
 
332
338
  schedule_message: (body, chatId, { bot, scheduledMessages }) => {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Shared helpers for Telegram action handlers: reply-parameter extraction and
3
- * the HTML-with-plain-fallback text sender.
3
+ * native Rich Markdown delivery with legacy fallbacks.
4
4
  */
5
5
 
6
6
  import type { Bot } from "grammy";
@@ -27,31 +27,97 @@ export function toPositiveId(v: unknown): number | undefined {
27
27
  return Number.isInteger(n) && n > 0 ? n : undefined;
28
28
  }
29
29
 
30
+ /**
31
+ * Rich Messages arrived in Bot API 10.2. A self-hosted Bot API server (or a
32
+ * deployment pinned to an older release) rejects `sendRichMessage` outright,
33
+ * and retrying it per message would cost a doomed round-trip plus a warning
34
+ * line on every single send. Probe once and latch, mirroring the
35
+ * `draftsSupported` capability probe in `handlers/delivery.ts`.
36
+ */
37
+ let richMessagesSupported = true;
38
+
39
+ /** Telegram's shape for "this build doesn't have that method". */
40
+ const METHOD_UNAVAILABLE_RE =
41
+ /method not found|not supported|unknown method|unsupported method/i;
42
+
43
+ /** True while native Rich Markdown delivery is still worth attempting. */
44
+ export function richMessagesAvailable(): boolean {
45
+ return richMessagesSupported;
46
+ }
47
+
48
+ /**
49
+ * Log a failed Rich Message call and latch the capability off when the failure
50
+ * says the method itself is missing. Payload-level rejections (a markdown
51
+ * string Telegram won't parse) stay one-off — the next message may be fine.
52
+ */
53
+ export function noteRichMessageFailure(err: unknown, context: string): void {
54
+ const msg = err instanceof Error ? err.message : String(err);
55
+ if (METHOD_UNAVAILABLE_RE.test(msg)) {
56
+ richMessagesSupported = false;
57
+ logWarn(
58
+ "bot",
59
+ `Rich Messages unavailable on this Bot API server — using legacy HTML from now on (${context}): ${msg}`,
60
+ );
61
+ return;
62
+ }
63
+ logWarn(
64
+ "bot",
65
+ `Rich Markdown failed; falling back to HTML (${context}): ${msg}`,
66
+ );
67
+ }
68
+
69
+ /** Test seam: re-arm the capability probe between cases. */
70
+ export function resetRichMessageSupport(): void {
71
+ richMessagesSupported = true;
72
+ }
73
+
30
74
  export async function sendText(
31
75
  bot: Bot,
32
76
  chatId: number,
33
77
  text: string,
34
78
  replyTo?: number,
79
+ replyMarkup?: NonNullable<
80
+ Parameters<Bot["api"]["sendRichMessage"]>[2]
81
+ >["reply_markup"],
35
82
  ): Promise<number> {
36
83
  if (text.length > TELEGRAM_MAX_TEXT) {
37
84
  throw new Error(
38
85
  `Message too long (${text.length} chars, max ${TELEGRAM_MAX_TEXT}).`,
39
86
  );
40
87
  }
88
+
89
+ if (richMessagesSupported) {
90
+ try {
91
+ const sent = await bot.api.sendRichMessage(
92
+ chatId,
93
+ { markdown: text },
94
+ {
95
+ reply_parameters: replyTo ? { message_id: replyTo } : undefined,
96
+ reply_markup: replyMarkup,
97
+ },
98
+ );
99
+ return sent.message_id;
100
+ } catch (err) {
101
+ noteRichMessageFailure(err, `send chat=${chatId}`);
102
+ }
103
+ }
104
+
41
105
  const html = markdownToTelegramHtml(text);
42
106
  try {
43
107
  const sent = await bot.api.sendMessage(chatId, html, {
44
108
  parse_mode: "HTML",
45
109
  reply_parameters: replyTo ? { message_id: replyTo } : undefined,
110
+ reply_markup: replyMarkup,
46
111
  });
47
112
  return sent.message_id;
48
113
  } catch (err) {
49
114
  logWarn(
50
115
  "bot",
51
- `sendText with parse_mode=HTML failed; retrying without parse_mode (chat=${chatId}): ${err instanceof Error ? err.message : err}`,
116
+ `Legacy HTML send failed; retrying as plain text (chat=${chatId}): ${err instanceof Error ? err.message : err}`,
52
117
  );
53
118
  const sent = await bot.api.sendMessage(chatId, text, {
54
119
  reply_parameters: replyTo ? { message_id: replyTo } : undefined,
120
+ reply_markup: replyMarkup,
55
121
  });
56
122
  return sent.message_id;
57
123
  }
@@ -5,12 +5,12 @@
5
5
 
6
6
  import type { Bot } from "grammy";
7
7
  import type { TalonConfig } from "../../../util/config.js";
8
- import { markdownToTelegramHtml } from "../formatting.js";
9
8
  import { execute } from "../../../core/engine/dispatcher.js";
10
9
  import { toolInputToRecord } from "../../../core/agent-runtime/events.js";
11
10
  import { appendDailyLogResponse } from "../../../storage/daily-log.js";
12
11
  import { stripMcpPrefix } from "../../../core/tools/index.js";
13
12
  import { logWarn } from "../../../util/log.js";
13
+ import { sendText } from "../actions/shared.js";
14
14
  import { trackDmUser } from "./access.js";
15
15
 
16
16
  export async function sendHtml(
@@ -112,7 +112,7 @@ function createStreamCallbacks(
112
112
  };
113
113
 
114
114
  const onTextBlock = async (text: string) => {
115
- await sendHtml(bot, chatId, markdownToTelegramHtml(text), _replyToId);
115
+ await sendText(bot, chatId, text, _replyToId);
116
116
  appendDailyLogResponse("Talon", text, { chatTitle });
117
117
  state.lastSentLength = 0;
118
118
  state.sentTextBlock = true;