polygram 0.5.9 → 0.5.11

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/lib/prompt.js CHANGED
@@ -121,9 +121,17 @@ function buildChannelAttrs({ chatId, msgId, user, userId, ts, threadId, topicNam
121
121
 
122
122
  function buildAttachmentTags(attachments) {
123
123
  if (!attachments?.length) return '';
124
- return attachments.map((a) =>
125
- `<attachment kind="${xmlEscape(a.kind)}" name="${xmlEscape(a.name)}" mime="${xmlEscape(a.mime_type)}" size="${a.size || 0}" path="${xmlEscape(a.path || '')}" />`
126
- ).join('\n');
124
+ // Failed downloads (no `path`, has `error`) get a separate tag so claude
125
+ // can mention them to the user instead of pretending nothing was sent.
126
+ // The actual failure reason is included so claude can offer a useful
127
+ // recovery hint ("looks like the file is too large", "Telegram CDN had
128
+ // a 410 — could you resend?").
129
+ return attachments.map((a) => {
130
+ if (a.error || !a.path) {
131
+ return `<attachment-failed kind="${xmlEscape(a.kind)}" name="${xmlEscape(a.name)}" mime="${xmlEscape(a.mime_type)}" reason="${xmlEscape(a.error || 'no local path')}" />`;
132
+ }
133
+ return `<attachment kind="${xmlEscape(a.kind)}" name="${xmlEscape(a.name)}" mime="${xmlEscape(a.mime_type)}" size="${a.size || 0}" path="${xmlEscape(a.path)}" />`;
134
+ }).join('\n');
127
135
  }
128
136
 
129
137
  function buildVoiceTags(attachments) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
4
4
  "description": "Telegram daemon for Claude Code that preserves the OpenClaw per-chat session model. Migration path for OpenClaw users moving to Claude Code.",
5
5
  "main": "lib/ipc-client.js",
6
6
  "bin": {
package/polygram.js CHANGED
@@ -415,7 +415,15 @@ async function downloadAttachments(bot, token, chatId, msg, attachments) {
415
415
  results.push({ ...att, path: localPath, size: att.size || buf.length });
416
416
  console.log(`[attach] ${chatId} ← ${att.kind} ${safeName} (${buf.length} bytes) → ${localPath}`);
417
417
  } catch (err) {
418
- console.error(`[attach] download failed for ${att.name}: ${err.message}`);
418
+ // Don't drop the attachment silently — push it through with the
419
+ // failure noted. buildAttachmentTags renders this as
420
+ // <attachment-failed reason="..." /> so claude tells the user
421
+ // "I couldn't see your <kind>" instead of pretending it received
422
+ // text only. Pre-0.5.11 these were logged to console and dropped,
423
+ // so claude got the prompt as if no attachment was sent.
424
+ const reason = (err.message || 'unknown').slice(0, 200);
425
+ console.error(`[attach] download failed for ${att.name}: ${reason}`);
426
+ results.push({ ...att, path: null, error: reason });
419
427
  }
420
428
  }
421
429
  return results;
@@ -1626,9 +1634,17 @@ function shouldHandle(msg, chatConfig, botUsername) {
1626
1634
  const text = msg.text || msg.caption || '';
1627
1635
  const isReplyToBot = msg.reply_to_message?.from?.username === botUsername;
1628
1636
  const hasMention = text.includes(`@${botUsername}`);
1629
- // Paired users bypass requireMention they've been explicitly trusted
1630
- // in this chat by an operator, no need for a mention every time.
1631
- const paired = pairings && msg.from?.id
1637
+ // A reply targeting some other user (not the bot) is a strong signal
1638
+ // "this message is for that person, not me". Paired users normally
1639
+ // bypass requireMention, but not in this case — without the guard a
1640
+ // paired user saying "Gotcha!" to a teammate gets processed by the
1641
+ // bot just because the user is paired, which is what bit us in
1642
+ // UMI Group on 0.5.9 (bot leaked reasoning as a reply to "Gotcha!").
1643
+ const repliesToOtherUser = !!msg.reply_to_message
1644
+ && msg.reply_to_message.from?.username !== botUsername;
1645
+ // Paired users bypass requireMention — operator-trusted, no @ needed
1646
+ // every time. Skipped when they're replying to a non-bot user (above).
1647
+ const paired = !repliesToOtherUser && pairings && msg.from?.id
1632
1648
  ? pairings.hasLivePairing({ bot_name: BOT_NAME, user_id: msg.from.id, chat_id: chatId })
1633
1649
  : false;
1634
1650
  if (!isReplyToBot && !hasMention && !paired) return false;