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.
- package/.claude-plugin/plugin.json +1 -1
- package/lib/prompt.js +21 -5
- package/package.json +2 -2
- package/polygram.js +2 -1
- package/skills/polygram-send/SKILL.md +30 -9
|
@@ -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.
|
|
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
|
|
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.
|
|
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": "
|
|
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:
|
|
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
|
|
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
|
-
##
|
|
12
|
+
## Choose exactly one delivery lane
|
|
13
13
|
|
|
14
|
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
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.
|