polygram 0.20.1 → 0.20.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "polygram",
4
- "version": "0.20.1",
4
+ "version": "0.20.2",
5
5
  "description": "Telegram integration for Claude Code that preserves the OpenClaw per-chat session model. Migration target for OpenClaw users. Multi-bot, multi-chat, per-topic isolation; SQLite transcripts; inline-keyboard approvals. Bundles /polygram:status|logs|pair-code|approvals admin commands plus history (transcript queries) and polygram-send (out-of-turn IPC sends with file-upload validation) skills.",
6
6
  "keywords": [
7
7
  "telegram",
package/lib/prompt.js CHANGED
@@ -5,23 +5,37 @@
5
5
  * Telegram payload → polygram DB → unresolvable marker.
6
6
  */
7
7
 
8
+ const { pickBackend } = require('@shumkov/orchestra');
9
+
10
+ function resolvePromptBackend({ config, chatId, threadId = null }) {
11
+ return pickBackend({
12
+ config,
13
+ chatId: String(chatId),
14
+ threadId: threadId == null || threadId === '' ? null : String(threadId),
15
+ pmDefault: 'sdk',
16
+ });
17
+ }
18
+
8
19
  /**
9
20
  * The <polygram-info> preamble. Sticker guidance is included ONLY when this bot
10
21
  * has a sticker set loaded (`stickerEmojis` non-empty) — a bot with no stickers.json
11
22
  * must not be told to emit stickers it doesn't have. The emoji list is the bot's OWN
12
23
  * pack (its emojiToSticker keys), not a hard-coded set, so it always matches reality.
13
24
  *
14
- * @param {{ stickerEmojis?: string[] }} [opts]
25
+ * @param {{ stickerEmojis?: string[], backend?: 'sdk'|'cli' }} [opts]
15
26
  */
16
- function polygramInfo({ stickerEmojis = [] } = {}) {
27
+ function polygramInfo({ stickerEmojis = [], backend = 'sdk' } = {}) {
17
28
  const hasStickers = stickerEmojis.length > 0;
29
+ const deliveryContract = backend === 'cli'
30
+ ? `You are connected via a Telegram daemon (polygram) in channels mode. Inline final text is invisible to the Telegram user. For every user-visible response, you MUST call \`mcp__polygram-bridge__reply\` within this turn. Only a successful reply-tool receipt proves delivery. Do not claim something was already sent or posted above based only on the transcript; transcript content is not a delivery receipt. If delivery is uncertain, send it now with the reply tool.`
31
+ : `You are connected via a Telegram daemon (polygram). Just reply with text — polygram delivers your response automatically. Do NOT use Telegram MCP tools.`;
18
32
  const emojiLine = hasStickers
19
33
  ? `Single emoji reply = auto-converted: ${stickerEmojis.join('')} become your stickers, any other emoji (🔥👍💪❤️) becomes a reaction on the user's message.`
20
34
  : `Single emoji reply = auto-converted to a reaction on the user's message (any Telegram emoji: 🔥👍💪❤️ …).`;
21
35
  const stickerTag = hasStickers
22
36
  ? `\n- \`[sticker:NAME]\` anywhere in your reply sends that sticker after the text. NAME must match polygram's sticker map.`
23
37
  : '';
24
- return `You are connected via a Telegram daemon (polygram). Just reply with text — polygram delivers your response automatically. Do NOT use Telegram MCP tools.
38
+ return `${deliveryContract}
25
39
  ${emojiLine}
26
40
  Inline tags (rc.63):${stickerTag}
27
41
  - \`[react:EMOJI]\` anywhere in your reply adds that emoji as a reaction on the user's message. Use any Telegram-supported emoji (👍 🔥 ❤️ 🎉 😢 …). Only the FIRST [react:] tag in a reply is applied; additional ones are dropped.
@@ -182,8 +196,9 @@ function buildVoiceTags(attachments) {
182
196
  * @param {string} params.sessionCtx - session context file contents (optional)
183
197
  * @param {Array} params.attachments - downloaded attachments
184
198
  * @param {Object} params.replyTo - input for buildReplyToBlock (optional)
199
+ * @param {'sdk'|'cli'} params.backend - delivery contract for the selected process backend
185
200
  */
186
- function buildPrompt({ msg, topicName = '', sessionCtx = '', attachments = [], replyTo = null, polygramHistory = '', stickerEmojis = [] }) {
201
+ function buildPrompt({ msg, topicName = '', sessionCtx = '', attachments = [], replyTo = null, polygramHistory = '', stickerEmojis = [], backend = 'sdk' }) {
187
202
  const chatId = msg.chat.id.toString();
188
203
  const msgId = msg.message_id.toString();
189
204
  const user = msg.from?.first_name || msg.from?.username || 'Unknown';
@@ -207,7 +222,7 @@ function buildPrompt({ msg, topicName = '', sessionCtx = '', attachments = [], r
207
222
  if (polygramHistory) {
208
223
  prompt += polygramHistory + '\n\n';
209
224
  }
210
- prompt += `<polygram-info>${polygramInfo({ stickerEmojis })}</polygram-info>\n\n`;
225
+ prompt += `<polygram-info>${polygramInfo({ stickerEmojis, backend })}</polygram-info>\n\n`;
211
226
 
212
227
  const replyBlock = buildReplyToBlock(replyTo);
213
228
  const attachmentTags = buildAttachmentTags(attachments);
@@ -225,6 +240,7 @@ function buildPrompt({ msg, topicName = '', sessionCtx = '', attachments = [], r
225
240
  }
226
241
 
227
242
  module.exports = {
243
+ resolvePromptBackend,
228
244
  xmlEscape,
229
245
  truncateReplyText,
230
246
  buildReplyToBlock,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.20.1",
3
+ "version": "0.20.2",
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": {
@@ -63,7 +63,7 @@
63
63
  "dependencies": {
64
64
  "@anthropic-ai/claude-agent-sdk": "0.2.123",
65
65
  "@modelcontextprotocol/sdk": "^1.29.0",
66
- "@shumkov/orchestra": "^0.4.2",
66
+ "@shumkov/orchestra": "0.4.3",
67
67
  "better-sqlite3": "^12.9.0",
68
68
  "grammy": "^1.42.0",
69
69
  "markdown-it": "^14.1.1",
package/polygram.js CHANGED
@@ -27,7 +27,7 @@ const dbClient = require('./lib/db');
27
27
  const {
28
28
  migrateJsonToDb, getClaudeSessionId, resolveSessionForSpawn,
29
29
  } = require('./lib/db/sessions');
30
- const { buildPrompt } = require('./lib/prompt');
30
+ const { buildPrompt, resolvePromptBackend } = require('./lib/prompt');
31
31
  const { filterAttachments, resolveFileCaps, resolveMaxFileOverride, MAX_TOTAL_BYTES } = require('./lib/attachments');
32
32
  // 0.9.0: SDK ProcessManager is the only pm. CLI pm
33
33
  // (lib/process-manager.js) deleted in commit 6.
@@ -430,6 +430,7 @@ function formatPrompt(msg, sessionCtx, attachments = [], { sessionKey = null } =
430
430
  attachments,
431
431
  replyTo: resolveReplyTo(msg),
432
432
  polygramHistory,
433
+ backend: resolvePromptBackend({ config, chatId, threadId }),
433
434
  // Only advertise stickers this bot actually has — empty for a bot with no
434
435
  // sticker set, so its prompt never mentions stickers.
435
436
  stickerEmojis: Object.keys(emojiToSticker),
@@ -1,25 +1,45 @@
1
1
  ---
2
2
  name: polygram-send
3
- description: Send messages, photos, documents, or stickers from a script (or from agent Bash) into the same Telegram chat the bot is in. Use when an agent or cron job needs to post a file, photo, or out-of-band text to a polygram chat — typically Xero invoices (PDF/.docx), generated reports, screenshots, or status pings tied to long-running work. Do NOT use for normal turn replies (those go through the agent's text reply, polygram delivers automatically). Do NOT use Telegram MCP — polygram-send is the supported path.
3
+ description: Choose the correct polygram delivery path for SDK turns, CLI/channels turns, and detached scripts. Use IPC for cron jobs or genuinely out-of-turn sends; use the reply MCP for interactive CLI text and files; return inline text for interactive SDK replies.
4
4
  ---
5
5
 
6
6
  # polygram-send
7
7
 
8
8
  Polygram exposes a per-bot Unix socket at `/tmp/polygram-<bot>.sock`. Authorized callers (same UID, with the per-bot secret) can send Telegram API methods through it. Polygram routes them to the live bot connection, logs them in the `messages` table, and applies markdown / chunking / dedup.
9
9
 
10
- This is the **supported path** for any agent or script that needs to deliver a file or out-of-band message into a polygram-managed chat. Telegram MCP is **not** wired up; agents that try to call Telegram directly will hit auth or routing issues.
10
+ This is the **supported IPC path** for a detached script that needs to deliver a file or out-of-band message into a polygram-managed chat. Interactive turns have backend-specific delivery paths described below. Generic Telegram MCP is not wired up; CLI/channels turns use polygram's own reply MCP instead.
11
11
 
12
- ## When to use
12
+ ## Choose exactly one delivery lane
13
13
 
14
- - Agent generated a file (PDF, .docx, image, etc.) and the user asked for it in chat
14
+ ### SDK interactive turn
15
+
16
+ Return the user-visible reply as inline assistant text. Polygram delivers it through the streamed SDK reply path. Do not also call the reply MCP or IPC, because that duplicates the message.
17
+
18
+ Conversation transcript text does not prove an earlier reply reached Telegram. Do not claim a report was "already sent" or "posted above" based only on transcript history. If delivery is uncertain, include the content in the current inline reply.
19
+
20
+ ### CLI/channels interactive turn
21
+
22
+ Inline final text is not delivered to Telegram. Send every user-visible text reply through `mcp__polygram-bridge__reply` within the turn. Attach generated files through that tool's `files` array. Do not use IPC for an interactive text or file reply.
23
+
24
+ Only a successful reply-tool receipt proves delivery. Transcript text and an attempted call without a successful receipt do not. If delivery is uncertain, call the reply tool now instead of claiming the message was sent earlier.
25
+
26
+ ### Cron or detached script
27
+
28
+ Use the `polygram-send` IPC client described below. This lane is for code running outside an interactive turn, including cron jobs, detached background scripts that generated files, and genuinely out-of-turn status updates. Check the IPC response and treat only a successful response as proof of delivery.
29
+
30
+ ## When to use IPC
31
+
32
+ - A detached script generated a file (PDF, .docx, image, etc.) after the interactive turn ended
15
33
  - Cron job needs to post a status update to the relevant chat
16
34
  - Long-running work needs an out-of-turn nudge (e.g. "build finished, here's the artifact")
17
35
 
18
- ## When NOT to use
36
+ ## When NOT to use IPC
19
37
 
20
- - Your normal text reply to the user — return text from the turn, polygram delivers it through the streamed reply path. Don't double-send.
38
+ - A normal SDK text reply — return inline text.
39
+ - A normal CLI/channels text reply — use `mcp__polygram-bridge__reply`.
40
+ - A CLI/channels file reply during an interactive turn — pass absolute paths in the reply tool's `files` array.
21
41
  - Anything that needs the message attributed to "user" — IPC always sends as the bot.
22
- - The Telegram MCP — explicitly NOT supported. Polygram blocks outbound message routing through anything other than its own bridge.
42
+ - Generic Telegram MCP — it is not a polygram delivery path.
23
43
 
24
44
  ## Allowed methods
25
45
 
@@ -139,8 +159,9 @@ The IPC socket validates a per-bot secret (`/tmp/polygram-<bot>.secret`, mode 06
139
159
 
140
160
  ## Don't
141
161
 
142
- - Don't call this for normal text replies. Just emit text from the turn — polygram delivers it.
143
- - Don't use Telegram MCP.
162
+ - Don't use IPC for a normal interactive text reply; follow the SDK or CLI lane above.
163
+ - Don't use generic Telegram MCP.
164
+ - Don't infer successful delivery from transcript text.
144
165
  - Don't pass localhost URLs to `document`/`photo`.
145
166
  - Don't hardcode `message_thread_id` from previous sessions.
146
167
  - Don't omit `source` from agent-triggered sends.