@link-assistant/hive-mind 1.69.15 → 1.69.16

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.69.16
4
+
5
+ ### Patch Changes
6
+
7
+ - ca0d938: Fix Telegram work-session completion failing with "Bad Request: can't parse entities" when the discovered Pull request URL contained Markdown-significant characters (`_`, `*`, `` ` ``, `[`). `appendPullRequestLine` (issue #1688) inserted the raw URL into a Markdown message even though the surrounding `Issue:` line was already escaped by `buildTelegramInfoBlock`, so a repo slug like `save_visiogetbb/pull/8` opened an italic entity at byte offset 318 that never closed. The appended `Pull request:` line is now passed through `escapeMarkdown`, and `safeReply`/`safeEditMessageText`/`installTelegramFormattingFallback` now log the offending byte-offset window and the plain-text fallback under `--verbose` so future parse errors point straight to the unescaped character. Resolves #1801.
8
+
3
9
  ## 1.69.15
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.69.15",
3
+ "version": "1.69.16",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -49,11 +49,30 @@ export function buildTelegramFormattingFallbackText(text, options = {}) {
49
49
  return `${getFormattingFallbackWarning(locale)}\n\n${stripTelegramMarkdown(text)}`;
50
50
  }
51
51
 
52
- function logFormattingFailure(scope, error, text, verbose = false) {
52
+ function logFormattingFailure(scope, error, text, verbose = false, fallbackText = null) {
53
53
  const message = error?.description || error?.message || String(error || '');
54
54
  console.error(`[telegram-bot] ${scope}: formatted Telegram message failed: ${message}`);
55
55
  if (verbose) {
56
- console.error(`[telegram-bot] ${scope}: Failing message (${Buffer.byteLength(String(text ?? ''), 'utf-8')} bytes): ${text}`);
56
+ const originalBytes = Buffer.byteLength(String(text ?? ''), 'utf-8');
57
+ console.error(`[telegram-bot] ${scope}: Failing message (${originalBytes} bytes): ${text}`);
58
+ // Issue #1801: when the parser rejects an entity, surface the byte offset
59
+ // from the error along with a small window of the message around it to
60
+ // make pinpointing the offending character trivial on the next iteration.
61
+ const offsetMatch = /byte offset (\d+)/i.exec(message);
62
+ if (offsetMatch) {
63
+ const offset = Number(offsetMatch[1]);
64
+ const buf = Buffer.from(String(text ?? ''), 'utf-8');
65
+ const start = Math.max(0, offset - 32);
66
+ const end = Math.min(buf.length, offset + 32);
67
+ // Decode the window; replacement character is used for bytes that fall
68
+ // mid-codepoint so we still print *something* useful.
69
+ const window = buf.slice(start, end).toString('utf-8');
70
+ console.error(`[telegram-bot] ${scope}: Byte offset ${offset} context [${start}..${end}]: ${JSON.stringify(window)}`);
71
+ }
72
+ if (fallbackText !== null) {
73
+ const fallbackBytes = Buffer.byteLength(String(fallbackText ?? ''), 'utf-8');
74
+ console.error(`[telegram-bot] ${scope}: Fallback message (${fallbackBytes} bytes): ${fallbackText}`);
75
+ }
57
76
  }
58
77
  }
59
78
 
@@ -65,8 +84,8 @@ export async function safeReply(ctx, text, options = {}) {
65
84
  return await ctx.reply(text, firstOptions);
66
85
  } catch (error) {
67
86
  if (!isTelegramFormattingError(error)) throw error;
68
- logFormattingFailure('safeReply', error, text, verbose);
69
87
  const fallbackText = buildTelegramFormattingFallbackText(text, { fallbackLocale });
88
+ logFormattingFailure('safeReply', error, text, verbose, fallbackText);
70
89
  return await ctx.reply(fallbackText, { ...telegramOptions, parse_mode: undefined });
71
90
  }
72
91
  }
@@ -78,8 +97,8 @@ export async function safeEditMessageText(telegram, chatId, messageId, inlineMes
78
97
  return await telegram.editMessageText(chatId, messageId, inlineMessageId, text, firstOptions);
79
98
  } catch (error) {
80
99
  if (!isTelegramFormattingError(error)) throw error;
81
- logFormattingFailure('safeEditMessageText', error, text, verbose);
82
100
  const fallbackText = buildTelegramFormattingFallbackText(text, { fallbackLocale });
101
+ logFormattingFailure('safeEditMessageText', error, text, verbose, fallbackText);
83
102
  return await telegram.editMessageText(chatId, messageId, inlineMessageId, fallbackText, { ...telegramOptions, parse_mode: undefined });
84
103
  }
85
104
  }
@@ -98,9 +117,10 @@ function wrapTelegramMethod(telegram, methodName, textIndex, optionsIndex, defau
98
117
  return await original.apply(this, args);
99
118
  } catch (error) {
100
119
  if (!isTelegramFormattingError(error) || typeof text !== 'string') throw error;
101
- logFormattingFailure(methodName, error, text, verbose || defaults.verbose);
120
+ const fallbackText = buildTelegramFormattingFallbackText(text, { fallbackLocale: fallbackLocale || defaults.fallbackLocale });
121
+ logFormattingFailure(methodName, error, text, verbose || defaults.verbose, fallbackText);
102
122
  const retryArgs = [...args];
103
- retryArgs[textIndex] = buildTelegramFormattingFallbackText(text, { fallbackLocale: fallbackLocale || defaults.fallbackLocale });
123
+ retryArgs[textIndex] = fallbackText;
104
124
  retryArgs[optionsIndex] = { ...telegramOptions, parse_mode: undefined };
105
125
  return await original.apply(this, retryArgs);
106
126
  }
@@ -1,4 +1,5 @@
1
1
  import { t } from './i18n.lib.mjs';
2
+ import { escapeMarkdown } from './telegram-markdown.lib.mjs';
2
3
 
3
4
  const FAILURE_STATUSES = new Set(['failed', 'cancelled', 'canceled', 'error']);
4
5
 
@@ -74,7 +75,7 @@ export function formatExecutingWorkSessionMessage({ sessionName = 'unknown', iso
74
75
  */
75
76
  export function appendPullRequestLine(infoBlock, pullRequestUrl, { locale = null } = {}) {
76
77
  if (!pullRequestUrl || !infoBlock) return infoBlock || '';
77
- if (infoBlock.includes(pullRequestUrl)) return infoBlock;
78
+ if (infoBlock.includes(pullRequestUrl) || infoBlock.includes(escapeMarkdown(pullRequestUrl))) return infoBlock;
78
79
 
79
80
  const lines = infoBlock.split('\n');
80
81
  let lastUrlLineIdx = -1;
@@ -89,7 +90,10 @@ export function appendPullRequestLine(infoBlock, pullRequestUrl, { locale = null
89
90
  lastUrlLineIdx = i;
90
91
  }
91
92
  }
92
- const prLine = `${text(locale, 'telegram.info_pull_request_label', 'Pull request')}: ${pullRequestUrl}`;
93
+ // Issue #1801: escape underscores/asterisks so Markdown parser doesn't open
94
+ // an entity on URLs like .../save_visiogetbb/pull/8 that the Issue: line
95
+ // above already had escaped at buildTelegramInfoBlock time.
96
+ const prLine = `${text(locale, 'telegram.info_pull_request_label', 'Pull request')}: ${escapeMarkdown(pullRequestUrl)}`;
93
97
  if (lastUrlLineIdx === -1) {
94
98
  return `${infoBlock}\n${prLine}`;
95
99
  }