polygram 0.8.0-rc.2 → 0.8.0-rc.21
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/agent-loader.js +219 -64
- package/lib/approval-ui.js +135 -0
- package/lib/autosteer-buffer.js +131 -0
- package/lib/canonical-json.js +44 -0
- package/lib/error-classify.js +38 -9
- package/lib/history-preload.js +160 -0
- package/lib/pm-interface.js +95 -0
- package/lib/pm-router.js +159 -0
- package/lib/process-manager-sdk.js +32 -1
- package/lib/process-manager.js +13 -0
- package/lib/status-reactions.js +70 -19
- package/package.json +1 -1
- package/polygram.js +412 -204
package/lib/status-reactions.js
CHANGED
|
@@ -29,19 +29,30 @@
|
|
|
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
|
-
THINKING:
|
|
34
|
-
CODING:
|
|
35
|
-
WEB:
|
|
36
|
-
TOOL:
|
|
37
|
-
WRITING:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
QUEUED: { label: 'queued', chain: ['👀', '🤔'] },
|
|
33
|
+
THINKING: { label: 'thinking', chain: ['🤔'] },
|
|
34
|
+
CODING: { label: 'coding', chain: ['👨💻', '✍', '🤔'] },
|
|
35
|
+
WEB: { label: 'web', chain: ['⚡', '🔥', '🤔'] },
|
|
36
|
+
TOOL: { label: 'tool', chain: ['🔥', '🤔'] },
|
|
37
|
+
WRITING: { label: 'writing', chain: ['✍', '🤔'] },
|
|
38
|
+
// 0.8.0-rc.11: terminal "your follow-up was incorporated into the
|
|
39
|
+
// in-flight turn" state. Used by polygram's autosteer block when a
|
|
40
|
+
// mid-turn user message is buffered for the next PostToolBatch
|
|
41
|
+
// injection.
|
|
42
|
+
AUTOSTEERED: { label: 'autosteered', chain: ['✍', '👀'] },
|
|
43
|
+
DONE: { label: 'done', chain: ['👍'] },
|
|
44
|
+
ERROR: { label: 'error', chain: ['🤯', '🤔'] },
|
|
45
|
+
STALL: { label: 'stall', chain: ['🥱', '🤔'] },
|
|
46
|
+
TIMEOUT: { label: 'timeout', chain: ['😨', '🤯'] },
|
|
42
47
|
};
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
// Terminal states bypass throttle, disarm stall promotion, and the
|
|
50
|
+
// reactor stays at this emoji until explicitly cleared. AUTOSTEERED
|
|
51
|
+
// is included so setState('AUTOSTEERED') flushes immediately
|
|
52
|
+
// (matters because the autosteer code path returns from
|
|
53
|
+
// handleMessage right after — we don't want the apply to be
|
|
54
|
+
// scheduled-and-cancelled by reactor.stop in the outer finally).
|
|
55
|
+
const TERMINAL_STATES = new Set(['DONE', 'ERROR', 'TIMEOUT', 'AUTOSTEERED']);
|
|
45
56
|
const DEFAULT_THROTTLE_MS = 800;
|
|
46
57
|
// 0.7.4 (item A): after this long with no setState() call (Claude is
|
|
47
58
|
// silently chugging on a long tool / model latency), auto-flip to STALL
|
|
@@ -132,24 +143,40 @@ function createReactionManager({
|
|
|
132
143
|
let stallTimer = null;
|
|
133
144
|
let freezeTimer = null;
|
|
134
145
|
let stopped = false;
|
|
146
|
+
// 0.8.0-rc.11: serialize Telegram setMessageReaction calls. Without
|
|
147
|
+
// this, multiple flush()es race at the network layer because each
|
|
148
|
+
// calls `await apply(emoji)` from a separate stack — Telegram
|
|
149
|
+
// processes them in arbitrary order and the FINAL visible state is
|
|
150
|
+
// whichever apply landed last. Symptom: 👀 stuck on autosteered
|
|
151
|
+
// messages when the QUEUED apply landed AFTER our explicit ✍ apply.
|
|
152
|
+
// Chaining all applies through `applyChain` guarantees they're sent
|
|
153
|
+
// to Telegram in setState() invocation order.
|
|
154
|
+
let applyChain = Promise.resolve();
|
|
135
155
|
// States the auto-stall path may transition to. Once we've already
|
|
136
156
|
// shown STALL or TIMEOUT we don't downgrade or rearm — only an
|
|
137
157
|
// explicit setState() call (Claude resumed) can move us forward.
|
|
138
158
|
const STALL_PROMOTABLE = new Set(['THINKING', 'CODING', 'WEB', 'TOOL', 'WRITING']);
|
|
139
159
|
|
|
140
160
|
const flush = async (stateName) => {
|
|
141
|
-
if (stopped) return;
|
|
161
|
+
if (stopped && !TERMINAL_STATES.has(stateName)) return;
|
|
142
162
|
const spec = STATES[stateName];
|
|
143
163
|
if (!spec) return;
|
|
144
164
|
const emoji = resolveEmoji(spec.chain, availableEmojis);
|
|
145
165
|
if (emoji === currentEmoji) return;
|
|
146
166
|
currentEmoji = emoji;
|
|
147
167
|
lastFlushTs = Date.now();
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
// Chain through applyChain so concurrent flushes are sent to
|
|
169
|
+
// Telegram serially in invocation order. Returning the chain
|
|
170
|
+
// promise lets callers await this specific flush completing.
|
|
171
|
+
const myApply = applyChain.then(async () => {
|
|
172
|
+
try {
|
|
173
|
+
await apply(emoji);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
logError(`reaction apply failed (${stateName} → ${emoji}): ${err?.message || err}`);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
applyChain = myApply;
|
|
179
|
+
return myApply;
|
|
153
180
|
};
|
|
154
181
|
|
|
155
182
|
const clearStallTimers = () => {
|
|
@@ -217,8 +244,16 @@ function createReactionManager({
|
|
|
217
244
|
clearStallTimers();
|
|
218
245
|
if (currentEmoji == null) return;
|
|
219
246
|
currentEmoji = null;
|
|
220
|
-
|
|
221
|
-
|
|
247
|
+
// Same applyChain serialization as flush — clear() is a state
|
|
248
|
+
// transition, just to "no emoji". Without chaining, a clear()
|
|
249
|
+
// racing with a pending apply (e.g. THINKING flush in flight)
|
|
250
|
+
// could land BEFORE that apply, leaving the emoji visible.
|
|
251
|
+
const myApply = applyChain.then(async () => {
|
|
252
|
+
try { await apply(null); }
|
|
253
|
+
catch (err) { logError(`reaction clear failed: ${err?.message || err}`); }
|
|
254
|
+
});
|
|
255
|
+
applyChain = myApply;
|
|
256
|
+
return myApply;
|
|
222
257
|
};
|
|
223
258
|
|
|
224
259
|
const stop = () => {
|
|
@@ -227,10 +262,26 @@ function createReactionManager({
|
|
|
227
262
|
clearStallTimers();
|
|
228
263
|
};
|
|
229
264
|
|
|
265
|
+
// 0.8.0-rc.16: heartbeat — re-arm stall/freeze timers without
|
|
266
|
+
// changing the visible emoji. Used by SDK pm's onStreamChunk
|
|
267
|
+
// callback so long text generation doesn't unfairly trip STALL
|
|
268
|
+
// (🥱) / TIMEOUT (😨) promotions after 10/30s of no explicit
|
|
269
|
+
// setState calls. Each text chunk is "I'm still here" evidence.
|
|
270
|
+
// Pre-rc.16, prolonged streaming with no tool use was reliably
|
|
271
|
+
// promoted to 🥱 within 10s even though the bot was producing
|
|
272
|
+
// output the whole time.
|
|
273
|
+
const heartbeat = () => {
|
|
274
|
+
if (stopped) return;
|
|
275
|
+
if (!STALL_PROMOTABLE.has(currentState)) return;
|
|
276
|
+
lastSetStateTs = Date.now();
|
|
277
|
+
armStallTimers();
|
|
278
|
+
};
|
|
279
|
+
|
|
230
280
|
return {
|
|
231
281
|
setState,
|
|
232
282
|
clear,
|
|
233
283
|
stop,
|
|
284
|
+
heartbeat,
|
|
234
285
|
// Introspection for tests:
|
|
235
286
|
get currentState() { return currentState; },
|
|
236
287
|
get currentEmoji() { return currentEmoji; },
|
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.21",
|
|
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": {
|