polygram 0.8.0-rc.32 β 0.8.0-rc.34
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/status-reactions.js +81 -7
- package/package.json +1 -1
- package/polygram.js +27 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "polygram",
|
|
4
|
-
"version": "0.8.0-rc.
|
|
4
|
+
"version": "0.8.0-rc.34",
|
|
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 and a history skill.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"telegram",
|
package/lib/status-reactions.js
CHANGED
|
@@ -29,12 +29,21 @@
|
|
|
29
29
|
// are progressively safer. All endings in this list are in Telegram's
|
|
30
30
|
// default available reactions as of 2026-04.
|
|
31
31
|
const STATES = {
|
|
32
|
-
QUEUED:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
QUEUED: { label: 'queued', chain: ['π', 'π€'] },
|
|
33
|
+
// rc.32: progressive-deepening cascade. setState('THINKING') auto-
|
|
34
|
+
// promotes through THINKING_DEEPER β THINKING_DEEPEST as time passes
|
|
35
|
+
// without a state change. Thresholds calibrated to Ivan DM 14-day
|
|
36
|
+
// production: 8% turns finish <5s, 33% in 5-15s (so 8s catches the
|
|
37
|
+
// meaty band), 25% in 15-30s (20s threshold catches them mid-flow),
|
|
38
|
+
// 17% over 60s (existing 45s STALL still fires for the truly long).
|
|
39
|
+
// All three emoji on Telegram's curated standard reaction list.
|
|
40
|
+
THINKING: { label: 'thinking', chain: ['π€'] },
|
|
41
|
+
THINKING_DEEPER: { label: 'thinking-deeper', chain: ['π€¨', 'π€'] },
|
|
42
|
+
THINKING_DEEPEST: { label: 'thinking-deepest', chain: ['π§', 'π€', 'π€'] },
|
|
43
|
+
CODING: { label: 'coding', chain: ['π¨βπ»', 'β', 'π€'] },
|
|
44
|
+
WEB: { label: 'web', chain: ['β‘', 'π₯', 'π€'] },
|
|
45
|
+
TOOL: { label: 'tool', chain: ['π₯', 'π€'] },
|
|
46
|
+
WRITING: { label: 'writing', chain: ['β', 'π€'] },
|
|
38
47
|
// 0.8.0-rc.11: terminal "your follow-up was incorporated into the
|
|
39
48
|
// in-flight turn" state. Used by polygram's autosteer block when a
|
|
40
49
|
// mid-turn user message is buffered for the next PostToolBatch
|
|
@@ -70,6 +79,14 @@ const DEFAULT_STALL_MS = 45_000;
|
|
|
70
79
|
// which 180s captures while letting routine work breathe. Pm has its
|
|
71
80
|
// own 5-minute hard idle timeout that actually rejects stuck turns.
|
|
72
81
|
const DEFAULT_FREEZE_MS = 180_000;
|
|
82
|
+
// rc.32: thinking-deepening cascade thresholds. Calibrated to Ivan DM
|
|
83
|
+
// 14-day production data (445 turns) β 8% finish <5s, 33% in 5-15s,
|
|
84
|
+
// 25% in 15-30s. 8s catches the meaty band, 20s catches the
|
|
85
|
+
// mid-bracket. STALL still fires at 45s for the genuinely-long 17%.
|
|
86
|
+
// CODING / TOOL / WEB / WRITING reset the cascade (any state-change
|
|
87
|
+
// in setState clears the deepening timers).
|
|
88
|
+
const DEFAULT_THINKING_DEEPER_MS = 8_000;
|
|
89
|
+
const DEFAULT_THINKING_DEEPEST_MS = 20_000;
|
|
73
90
|
|
|
74
91
|
// Tool name β state classifier. Case-insensitive substring match so we
|
|
75
92
|
// don't have to enumerate every existing or future tool. Order matters:
|
|
@@ -139,6 +156,8 @@ function createReactionManager({
|
|
|
139
156
|
throttleMs = DEFAULT_THROTTLE_MS,
|
|
140
157
|
stallMs = DEFAULT_STALL_MS,
|
|
141
158
|
freezeMs = DEFAULT_FREEZE_MS,
|
|
159
|
+
thinkingDeeperMs = DEFAULT_THINKING_DEEPER_MS,
|
|
160
|
+
thinkingDeepestMs = DEFAULT_THINKING_DEEPEST_MS,
|
|
142
161
|
logError = () => {},
|
|
143
162
|
} = {}) {
|
|
144
163
|
if (typeof apply !== 'function') throw new Error('apply function required');
|
|
@@ -149,6 +168,8 @@ function createReactionManager({
|
|
|
149
168
|
let pendingTimer = null;
|
|
150
169
|
let stallTimer = null;
|
|
151
170
|
let freezeTimer = null;
|
|
171
|
+
let deeperTimer = null; // rc.32: THINKING β THINKING_DEEPER (8s)
|
|
172
|
+
let deepestTimer = null; // rc.32: THINKING β THINKING_DEEPEST (20s)
|
|
152
173
|
let stopped = false;
|
|
153
174
|
// 0.8.0-rc.11: serialize Telegram setMessageReaction calls. Without
|
|
154
175
|
// this, multiple flush()es race at the network layer because each
|
|
@@ -162,7 +183,13 @@ function createReactionManager({
|
|
|
162
183
|
// States the auto-stall path may transition to. Once we've already
|
|
163
184
|
// shown STALL or TIMEOUT we don't downgrade or rearm β only an
|
|
164
185
|
// explicit setState() call (Claude resumed) can move us forward.
|
|
165
|
-
|
|
186
|
+
// rc.32: THINKING_DEEPER and THINKING_DEEPEST are stall-promotable
|
|
187
|
+
// too β if the model is stuck in a deep-thinking phase past 45s,
|
|
188
|
+
// STALL still fires.
|
|
189
|
+
const STALL_PROMOTABLE = new Set([
|
|
190
|
+
'THINKING', 'THINKING_DEEPER', 'THINKING_DEEPEST',
|
|
191
|
+
'CODING', 'WEB', 'TOOL', 'WRITING',
|
|
192
|
+
]);
|
|
166
193
|
|
|
167
194
|
const flush = async (stateName) => {
|
|
168
195
|
if (stopped && !TERMINAL_STATES.has(stateName)) return;
|
|
@@ -191,6 +218,42 @@ function createReactionManager({
|
|
|
191
218
|
if (freezeTimer) { clearTimeout(freezeTimer); freezeTimer = null; }
|
|
192
219
|
};
|
|
193
220
|
|
|
221
|
+
// rc.32: thinking-deepening cascade. When state is THINKING, schedule
|
|
222
|
+
// auto-promotion at 8s (β THINKING_DEEPER, π€¨) and 20s (β THINKING_DEEPEST,
|
|
223
|
+
// π§). Any other setState (CODING / TOOL / WEB / WRITING / terminal) clears
|
|
224
|
+
// these. heartbeat() does NOT re-arm them β heartbeat is for keeping
|
|
225
|
+
// STALL/TIMEOUT at bay during silent activity, not for resetting
|
|
226
|
+
// visible deepening progression.
|
|
227
|
+
const clearDeepeningTimers = () => {
|
|
228
|
+
if (deeperTimer) { clearTimeout(deeperTimer); deeperTimer = null; }
|
|
229
|
+
if (deepestTimer) { clearTimeout(deepestTimer); deepestTimer = null; }
|
|
230
|
+
};
|
|
231
|
+
const armDeepeningTimers = () => {
|
|
232
|
+
clearDeepeningTimers();
|
|
233
|
+
if (stopped) return;
|
|
234
|
+
if (currentState !== 'THINKING') return;
|
|
235
|
+
deeperTimer = setTimeout(() => {
|
|
236
|
+
deeperTimer = null;
|
|
237
|
+
if (stopped) return;
|
|
238
|
+
if (currentState !== 'THINKING') return;
|
|
239
|
+
// Promote without going through setState β we want the visual
|
|
240
|
+
// change but NOT to reset the deepest timer (which keeps
|
|
241
|
+
// counting from the original THINKING start).
|
|
242
|
+
currentState = 'THINKING_DEEPER';
|
|
243
|
+
flush('THINKING_DEEPER');
|
|
244
|
+
}, thinkingDeeperMs);
|
|
245
|
+
deeperTimer.unref?.();
|
|
246
|
+
deepestTimer = setTimeout(() => {
|
|
247
|
+
deepestTimer = null;
|
|
248
|
+
if (stopped) return;
|
|
249
|
+
// Promote from THINKING or THINKING_DEEPER (NOT from CODING etc).
|
|
250
|
+
if (currentState !== 'THINKING' && currentState !== 'THINKING_DEEPER') return;
|
|
251
|
+
currentState = 'THINKING_DEEPEST';
|
|
252
|
+
flush('THINKING_DEEPEST');
|
|
253
|
+
}, thinkingDeepestMs);
|
|
254
|
+
deepestTimer.unref?.();
|
|
255
|
+
};
|
|
256
|
+
|
|
194
257
|
const armStallTimers = () => {
|
|
195
258
|
clearStallTimers();
|
|
196
259
|
if (stopped) return;
|
|
@@ -223,6 +286,7 @@ function createReactionManager({
|
|
|
223
286
|
if (TERMINAL_STATES.has(stateName)) {
|
|
224
287
|
if (pendingTimer) { clearTimeout(pendingTimer); pendingTimer = null; }
|
|
225
288
|
clearStallTimers();
|
|
289
|
+
clearDeepeningTimers();
|
|
226
290
|
return flush(stateName);
|
|
227
291
|
}
|
|
228
292
|
|
|
@@ -230,6 +294,12 @@ function createReactionManager({
|
|
|
230
294
|
// doing *something*. Re-arm only if the new state is promotable
|
|
231
295
|
// (no point arming over QUEUED/STALL/TIMEOUT itself).
|
|
232
296
|
armStallTimers();
|
|
297
|
+
// rc.32: arm/clear deepening cascade. Only THINKING starts the
|
|
298
|
+
// 8s/20s progression; any other state (CODING / TOOL / etc)
|
|
299
|
+
// clears it (the visible state has moved on, deepening would be
|
|
300
|
+
// a regression).
|
|
301
|
+
if (stateName === 'THINKING') armDeepeningTimers();
|
|
302
|
+
else clearDeepeningTimers();
|
|
233
303
|
|
|
234
304
|
// 0.8.0-rc.24: drop the 800ms throttle. Pre-rc.24, when a tool-
|
|
235
305
|
// using turn fired QUEUED β THINKING β TOOL within a few ms,
|
|
@@ -251,6 +321,7 @@ function createReactionManager({
|
|
|
251
321
|
const clear = async () => {
|
|
252
322
|
if (pendingTimer) { clearTimeout(pendingTimer); pendingTimer = null; }
|
|
253
323
|
clearStallTimers();
|
|
324
|
+
clearDeepeningTimers();
|
|
254
325
|
if (currentEmoji == null) return;
|
|
255
326
|
currentEmoji = null;
|
|
256
327
|
// Same applyChain serialization as flush β clear() is a state
|
|
@@ -269,6 +340,7 @@ function createReactionManager({
|
|
|
269
340
|
stopped = true;
|
|
270
341
|
if (pendingTimer) { clearTimeout(pendingTimer); pendingTimer = null; }
|
|
271
342
|
clearStallTimers();
|
|
343
|
+
clearDeepeningTimers();
|
|
272
344
|
};
|
|
273
345
|
|
|
274
346
|
// 0.8.0-rc.16: heartbeat β re-arm stall/freeze timers without
|
|
@@ -306,5 +378,7 @@ module.exports = {
|
|
|
306
378
|
DEFAULT_THROTTLE_MS,
|
|
307
379
|
DEFAULT_STALL_MS,
|
|
308
380
|
DEFAULT_FREEZE_MS,
|
|
381
|
+
DEFAULT_THINKING_DEEPER_MS,
|
|
382
|
+
DEFAULT_THINKING_DEEPEST_MS,
|
|
309
383
|
GENERIC_FALLBACKS,
|
|
310
384
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.8.0-rc.
|
|
3
|
+
"version": "0.8.0-rc.34",
|
|
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
|
@@ -2429,7 +2429,20 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
2429
2429
|
// 0.7.4 (item G) voice-ack guard preserved: if π is up from
|
|
2430
2430
|
// voice transcription, don't overwrite it. Let onFirstStream
|
|
2431
2431
|
// promote to π€ when Claude actually starts work.
|
|
2432
|
-
|
|
2432
|
+
//
|
|
2433
|
+
// rc.32 second guard: skip THINKING if we're going to autosteer
|
|
2434
|
+
// this message. Pre-fix, autosteer messages briefly showed
|
|
2435
|
+
// π€ β β (THINKING set here, then AUTOSTEERED set inside the
|
|
2436
|
+
// autosteer block ~50 lines below). The flash was visible to
|
|
2437
|
+
// users. Detect the autosteer pre-condition (SDK pm active AND
|
|
2438
|
+
// session in-flight AND chat hasn't opted out) and skip.
|
|
2439
|
+
const willAutosteer = pm.has(sessionKey)
|
|
2440
|
+
&& pm.get(sessionKey)?.inFlight
|
|
2441
|
+
&& pm.isSdkFor(sessionKey)
|
|
2442
|
+
&& (chatConfig.autosteer != null
|
|
2443
|
+
? chatConfig.autosteer !== false
|
|
2444
|
+
: config.bot?.autosteer !== false);
|
|
2445
|
+
if (!voiceAck.ackEmitted && !willAutosteer) {
|
|
2433
2446
|
reactor.setState('THINKING');
|
|
2434
2447
|
}
|
|
2435
2448
|
|
|
@@ -2524,7 +2537,19 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
2524
2537
|
// (queue-head transition) flipped to THINKING the moment we wrote
|
|
2525
2538
|
// stdin, even though Claude could spend hundreds of ms loading.
|
|
2526
2539
|
// Result: long flat π€ with nothing happening; users assumed stall.
|
|
2527
|
-
|
|
2540
|
+
//
|
|
2541
|
+
// rc.33: also stop typing here. Telegram's "typingβ¦" indicator
|
|
2542
|
+
// shows for ~5s after the LAST sendChatAction tick. If we stop
|
|
2543
|
+
// typing at turn-end (~5-10s post final reply), the indicator
|
|
2544
|
+
// persists 5s past when the user sees the reply β confusing
|
|
2545
|
+
// ("bot replied AND is still typing?"). Stopping at the first
|
|
2546
|
+
// chunk means typing fades naturally as the streamer emits the
|
|
2547
|
+
// bubble. Reactions (THINKING/CODING/etc) take over as the
|
|
2548
|
+
// "still working" signal for long agent turns.
|
|
2549
|
+
onFirstStream: () => {
|
|
2550
|
+
reactor.setState('THINKING');
|
|
2551
|
+
stopTyping();
|
|
2552
|
+
},
|
|
2528
2553
|
});
|
|
2529
2554
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
2530
2555
|
|