polygram 0.9.0-rc.4 → 0.9.0-rc.6
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/db.js +14 -0
- package/lib/handlers/dispatcher.js +8 -2
- package/lib/handlers/edit-correction.js +77 -0
- package/package.json +1 -1
- package/polygram.js +29 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "polygram",
|
|
4
|
-
"version": "0.9.0-rc.
|
|
4
|
+
"version": "0.9.0-rc.6",
|
|
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/db.js
CHANGED
|
@@ -521,6 +521,20 @@ function wrap(db) {
|
|
|
521
521
|
`).run(status, chat_id, msg_id);
|
|
522
522
|
},
|
|
523
523
|
|
|
524
|
+
// 0.9.0: True when a specific inbound msg is still being processed
|
|
525
|
+
// by the SDK turn loop (handler_status in dispatched/processing).
|
|
526
|
+
// Used by the edit-correction injector — only inject a typo-fix
|
|
527
|
+
// note when the SDK actually still has the turn in flight.
|
|
528
|
+
isInboundLive({ chat_id, msg_id }) {
|
|
529
|
+
const row = db.prepare(`
|
|
530
|
+
SELECT 1 FROM messages
|
|
531
|
+
WHERE chat_id = ? AND msg_id = ? AND direction = 'in'
|
|
532
|
+
AND handler_status IN ('dispatched', 'processing')
|
|
533
|
+
LIMIT 1
|
|
534
|
+
`).get(chat_id, msg_id);
|
|
535
|
+
return !!row;
|
|
536
|
+
},
|
|
537
|
+
|
|
524
538
|
// Find inbound messages that were being processed when polygram stopped.
|
|
525
539
|
// Scoped by bot_name via the chat_id → config mapping, so each bot only
|
|
526
540
|
// replays its own turns on boot. Scoped by olderThanMs (default 3 min)
|
|
@@ -41,7 +41,13 @@ function createDispatcher({
|
|
|
41
41
|
autoResumeTracker, // lib/db/auto-resume.js instance
|
|
42
42
|
chunkMarkdownText, // lib/telegram/chunk.js
|
|
43
43
|
deliverReplies, // lib/telegram/deliver.js
|
|
44
|
-
|
|
44
|
+
// Raw-markdown size budget for chunkMarkdownText. Set BELOW Telegram's
|
|
45
|
+
// 4096 hard limit to leave headroom for HTML inflation (toTelegramHtml
|
|
46
|
+
// adds <b>/<i>/<code> tags + entity escapes; ~10-15% in practice).
|
|
47
|
+
// Polygram passes TG_CHUNK_BUDGET (default 3500). Test default keeps
|
|
48
|
+
// the historic 4096 for back-compat in synthetic test runs that pass
|
|
49
|
+
// pre-formatted text.
|
|
50
|
+
chunkBudget = 4096,
|
|
45
51
|
// State accessors (need late binding because polygram.js mutates):
|
|
46
52
|
getIsShuttingDown, // () → boolean
|
|
47
53
|
logger = console,
|
|
@@ -119,7 +125,7 @@ function createDispatcher({
|
|
|
119
125
|
|
|
120
126
|
// 4. Send the continuation reply as regular Telegram messages,
|
|
121
127
|
// threaded under the original user message.
|
|
122
|
-
const chunks = chunkMarkdownText(result.text,
|
|
128
|
+
const chunks = chunkMarkdownText(result.text, chunkBudget);
|
|
123
129
|
await deliverReplies({
|
|
124
130
|
bot,
|
|
125
131
|
send: (b, method, params, m) => tg(b, method, params, m),
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Edit-correction injector.
|
|
5
|
+
*
|
|
6
|
+
* When the user edits a Telegram message that's still being processed
|
|
7
|
+
* by the SDK turn loop, inject a correction note into the active turn
|
|
8
|
+
* via the same hook channel autosteer uses (pm.injectUserMessage,
|
|
9
|
+
* priority: 'next'). Lets users fix typos mid-turn without /stop +
|
|
10
|
+
* resend.
|
|
11
|
+
*
|
|
12
|
+
* Skipped when the turn already finished — at that point the
|
|
13
|
+
* conversation has moved on and re-opening it would confuse Claude
|
|
14
|
+
* more than help. Also skipped when the SDK session has been
|
|
15
|
+
* LRU-evicted (no live target to inject into).
|
|
16
|
+
*
|
|
17
|
+
* Architectural note: there's no polygram-side "buffer" to replace.
|
|
18
|
+
* pm.send and pm.injectUserMessage push the message text directly into
|
|
19
|
+
* the SDK's inputController AsyncIterable; once pushed, polygram has
|
|
20
|
+
* no way to retract it. So edit handling is always "inject correction
|
|
21
|
+
* note as additional context" — Claude reconciles.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
function createEditCorrectionInjector({
|
|
25
|
+
pm,
|
|
26
|
+
db,
|
|
27
|
+
getSessionKey,
|
|
28
|
+
config,
|
|
29
|
+
logEvent,
|
|
30
|
+
logger = console,
|
|
31
|
+
} = {}) {
|
|
32
|
+
|
|
33
|
+
return function maybeInjectEditCorrection(editedMsg) {
|
|
34
|
+
if (!editedMsg?.chat) return false;
|
|
35
|
+
const chatId = editedMsg.chat.id.toString();
|
|
36
|
+
const chatConfig = config.chats[chatId];
|
|
37
|
+
if (!chatConfig) return false;
|
|
38
|
+
|
|
39
|
+
// Per-chat / bot-level opt-out. Default on.
|
|
40
|
+
const optOut = chatConfig.editCorrection != null
|
|
41
|
+
? chatConfig.editCorrection === false
|
|
42
|
+
: config.bot?.editCorrection === false;
|
|
43
|
+
if (optOut) return false;
|
|
44
|
+
|
|
45
|
+
const threadIdStr = editedMsg.message_thread_id?.toString() || null;
|
|
46
|
+
const sessionKey = getSessionKey(chatId, threadIdStr, chatConfig);
|
|
47
|
+
|
|
48
|
+
// Three skip gates — all must be true for an injection:
|
|
49
|
+
// 1. SDK session exists (not LRU-evicted)
|
|
50
|
+
// 2. session has a turn in flight
|
|
51
|
+
// 3. the edited msg is still in the dispatched/processing pipeline
|
|
52
|
+
if (!pm.has(sessionKey)) return false;
|
|
53
|
+
if (!pm.get(sessionKey)?.inFlight) return false;
|
|
54
|
+
if (!db.isInboundLive({ chat_id: chatId, msg_id: editedMsg.message_id })) return false;
|
|
55
|
+
|
|
56
|
+
const newText = editedMsg.text || editedMsg.caption || '';
|
|
57
|
+
if (!newText) return false;
|
|
58
|
+
|
|
59
|
+
const ok = pm.injectUserMessage(sessionKey, {
|
|
60
|
+
content: `[edit] I corrected my previous message — it now reads: ${newText}`,
|
|
61
|
+
priority: 'next',
|
|
62
|
+
});
|
|
63
|
+
if (!ok) {
|
|
64
|
+
logger.error?.(`[${chatConfig.name || chatId}] edit-correction inject failed`);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
logEvent('message-edit-injected', {
|
|
68
|
+
chat_id: chatId,
|
|
69
|
+
msg_id: editedMsg.message_id,
|
|
70
|
+
session_key: sessionKey,
|
|
71
|
+
text_len: newText.length,
|
|
72
|
+
});
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { createEditCorrectionInjector };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.9.0-rc.
|
|
3
|
+
"version": "0.9.0-rc.6",
|
|
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
|
@@ -44,6 +44,7 @@ const { createDownloadAttachments } = require('./lib/handlers/download');
|
|
|
44
44
|
const { createHandleConfigCallback } = require('./lib/handlers/config-callback');
|
|
45
45
|
const { createHandleAbort } = require('./lib/handlers/abort');
|
|
46
46
|
const { createAutosteerHandlers } = require('./lib/handlers/autosteer');
|
|
47
|
+
const { createEditCorrectionInjector } = require('./lib/handlers/edit-correction');
|
|
47
48
|
const { createSlashCommands } = require('./lib/handlers/slash-commands');
|
|
48
49
|
const { createApprovals } = require('./lib/handlers/approvals');
|
|
49
50
|
const { canonicalizeToolInput } = require('./lib/canonical-json');
|
|
@@ -108,6 +109,15 @@ const CLAUDE_BIN = process.env.POLYGRAM_CLAUDE_BIN
|
|
|
108
109
|
|| path.join(process.env.HOME || '', '.npm-global/bin/claude');
|
|
109
110
|
const CHILD_HOME = process.env.POLYGRAM_CHILD_HOME || process.env.HOME || '';
|
|
110
111
|
const TG_MAX_LEN = 4096;
|
|
112
|
+
// 0.9.0-rc.6: chunker budget is intentionally lower than TG_MAX_LEN to
|
|
113
|
+
// leave HTML headroom. toTelegramHtml converts markdown to HTML for
|
|
114
|
+
// parse_mode=HTML — that conversion adds <b>/<i>/<code>/<a> tags and
|
|
115
|
+
// entity-escapes &/</> chars, inflating length by ~10-15% for realistic
|
|
116
|
+
// markdown. 2026-05-11 incident proved a 4044-char chunk inflated to
|
|
117
|
+
// 4506 HTML chars and Telegram rejected. 3500 raw → max ~4030 HTML on
|
|
118
|
+
// observed inputs, with headroom for adversarial code-heavy text.
|
|
119
|
+
// Override via POLYGRAM_CHUNK_BUDGET if your traffic profile differs.
|
|
120
|
+
const TG_CHUNK_BUDGET = Number.parseInt(process.env.POLYGRAM_CHUNK_BUDGET, 10) || 3500;
|
|
111
121
|
const DEFAULT_MAX_WARM_PROCS = 10;
|
|
112
122
|
|
|
113
123
|
let stickerMap = {}; // name → file_id
|
|
@@ -525,6 +535,7 @@ let handleConfigCallback = null;
|
|
|
525
535
|
let handleAbortIfRequested = null;
|
|
526
536
|
let autosteer = null;
|
|
527
537
|
let dispatchSlashCommand = null;
|
|
538
|
+
let maybeInjectEditCorrection = null;
|
|
528
539
|
|
|
529
540
|
// rc.20: approvalCardText + safeParse moved to lib/approvals/ui.js.
|
|
530
541
|
// 0.9.0 commit 29: makeCanUseTool / handleApprovalCallback /
|
|
@@ -1251,7 +1262,7 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1251
1262
|
// send the body as proper chunks.
|
|
1252
1263
|
try { await streamer.discard(); }
|
|
1253
1264
|
catch (err) { console.error(`[${label}] discard failed: ${err.message}`); }
|
|
1254
|
-
const chunks = chunkMarkdownText(parsed.text,
|
|
1265
|
+
const chunks = chunkMarkdownText(parsed.text, TG_CHUNK_BUDGET);
|
|
1255
1266
|
const r = await deliverReplies({
|
|
1256
1267
|
bot,
|
|
1257
1268
|
send: (b, method, params, m) => tg(b, method, params, m),
|
|
@@ -1317,7 +1328,7 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1317
1328
|
// 0.7.0: use markdown-aware chunker + deliverReplies primitive.
|
|
1318
1329
|
// The old chunkText was newline/byte-only; chunkMarkdownText also
|
|
1319
1330
|
// respects code-fence boundaries (closes + reopens across chunks).
|
|
1320
|
-
const chunks = chunkMarkdownText(parsed.text,
|
|
1331
|
+
const chunks = chunkMarkdownText(parsed.text, TG_CHUNK_BUDGET);
|
|
1321
1332
|
await deliverReplies({
|
|
1322
1333
|
bot,
|
|
1323
1334
|
send: (b, method, params, m) => tg(b, method, params, m),
|
|
@@ -1702,6 +1713,18 @@ function createBot(token) {
|
|
|
1702
1713
|
user_id: ctx.editedMessage.from?.id || null,
|
|
1703
1714
|
});
|
|
1704
1715
|
console.log(`[${BOT_NAME}] edited ${chatId}/${ctx.editedMessage.message_id}`);
|
|
1716
|
+
|
|
1717
|
+
// 0.9.0: typo-correction injection. If the SDK still has this turn
|
|
1718
|
+
// in flight (handler_status in dispatched/processing AND
|
|
1719
|
+
// pm.get(sk).inFlight), inject a `[edit] corrected: <NEW>` note
|
|
1720
|
+
// via the same channel autosteer uses. Lets users fix typos
|
|
1721
|
+
// mid-turn without /stop + resend. No-op when the turn already
|
|
1722
|
+
// completed.
|
|
1723
|
+
try {
|
|
1724
|
+
maybeInjectEditCorrection?.(ctx.editedMessage);
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
console.error(`[${BOT_NAME}] edit-correction injector error: ${err.message}`);
|
|
1727
|
+
}
|
|
1705
1728
|
});
|
|
1706
1729
|
|
|
1707
1730
|
bot.on('message:migrate_to_chat_id', async (ctx) => {
|
|
@@ -1955,6 +1978,9 @@ async function main() {
|
|
|
1955
1978
|
autosteer = createAutosteerHandlers({
|
|
1956
1979
|
config, pm, autosteeredRefs, logEvent,
|
|
1957
1980
|
});
|
|
1981
|
+
maybeInjectEditCorrection = createEditCorrectionInjector({
|
|
1982
|
+
pm, db, getSessionKey, config, logEvent, logger: console,
|
|
1983
|
+
});
|
|
1958
1984
|
recordInbound = createRecordInbound({
|
|
1959
1985
|
db, dbWrite, config, botName: BOT_NAME, extractAttachments,
|
|
1960
1986
|
});
|
|
@@ -1973,7 +1999,7 @@ async function main() {
|
|
|
1973
1999
|
classifyError, isAutoResumable,
|
|
1974
2000
|
abortGrace, autoResumeTracker,
|
|
1975
2001
|
chunkMarkdownText, deliverReplies,
|
|
1976
|
-
|
|
2002
|
+
chunkBudget: TG_CHUNK_BUDGET,
|
|
1977
2003
|
getIsShuttingDown: () => isShuttingDown,
|
|
1978
2004
|
logger: console,
|
|
1979
2005
|
}));
|