polygram 0.8.0-rc.43 → 0.8.0-rc.45
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/process-manager-sdk.js +57 -8
- package/lib/stream-reply.js +49 -10
- package/package.json +1 -1
- package/polygram.js +26 -5
|
@@ -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.45",
|
|
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",
|
|
@@ -458,22 +458,51 @@ class ProcessManagerSdk {
|
|
|
458
458
|
}
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
461
|
+
// rc.45: multi-segment same-bubble streaming.
|
|
462
|
+
//
|
|
463
|
+
// Pre-rc.45: every message-id transition fired
|
|
464
|
+
// onAssistantMessageStart (= forceNewMessage on the streamer),
|
|
465
|
+
// producing a fresh bubble per SDK assistant message even
|
|
466
|
+
// though the user only sent one input. Tool-heavy turns
|
|
467
|
+
// showed 2-6 bubbles per logical user-input cycle.
|
|
468
|
+
//
|
|
469
|
+
// rc.45: only fire onAssistantMessageStart when the user
|
|
470
|
+
// STEERED (injectUserMessage set pendingSteerCausesNewBubble).
|
|
471
|
+
// Otherwise, accumulate the prior segment's text into
|
|
472
|
+
// priorMessagesText and append the new segment to it — same
|
|
473
|
+
// bubble grows naturally. Same-message-id events (cumulative
|
|
474
|
+
// streaming within a single SDKAssistantMessage) still
|
|
475
|
+
// REPLACE the segment text; the carry-over only kicks in on
|
|
476
|
+
// message-id TRANSITIONS.
|
|
465
477
|
if (added) {
|
|
466
478
|
const isNewMessage = head.lastAssistantMessageId != null
|
|
467
479
|
&& messageId != null
|
|
468
480
|
&& head.lastAssistantMessageId !== messageId
|
|
469
481
|
&& head.streamText
|
|
470
482
|
&& head.streamText.length > 0;
|
|
471
|
-
if (isNewMessage
|
|
472
|
-
|
|
473
|
-
|
|
483
|
+
if (isNewMessage) {
|
|
484
|
+
if (head.pendingSteerCausesNewBubble) {
|
|
485
|
+
// Steered: fire onAssistantMessageStart so the streamer
|
|
486
|
+
// forceNewMessage's. Reset the prior carry-over so the
|
|
487
|
+
// new bubble starts clean.
|
|
488
|
+
if (this.onAssistantMessageStart) {
|
|
489
|
+
try { await this.onAssistantMessageStart(entry.sessionKey, entry); }
|
|
490
|
+
catch (err) { this.logger.error?.(`[${entry.label}] onAssistantMessageStart: ${err.message}`); }
|
|
491
|
+
}
|
|
492
|
+
head.priorMessagesText = '';
|
|
493
|
+
head.pendingSteerCausesNewBubble = false;
|
|
494
|
+
} else {
|
|
495
|
+
// No steer: roll the just-finished segment's full text
|
|
496
|
+
// into priorMessagesText so the new segment appends to it.
|
|
497
|
+
head.priorMessagesText = head.streamText;
|
|
498
|
+
}
|
|
474
499
|
}
|
|
475
500
|
if (messageId != null) head.lastAssistantMessageId = messageId;
|
|
476
|
-
|
|
501
|
+
// Compose visible bubble text: carry-over (prior segments in
|
|
502
|
+
// this bubble) + the current segment's cumulative text.
|
|
503
|
+
head.streamText = head.priorMessagesText
|
|
504
|
+
? head.priorMessagesText + '\n\n' + added
|
|
505
|
+
: added;
|
|
477
506
|
if (this.onStreamChunk) {
|
|
478
507
|
try { this.onStreamChunk(entry.sessionKey, head.streamText, entry); }
|
|
479
508
|
catch (err) { this.logger.error?.(`[${entry.label}] onStreamChunk: ${err.message}`); }
|
|
@@ -615,6 +644,17 @@ class ProcessManagerSdk {
|
|
|
615
644
|
transientRetries: 0,
|
|
616
645
|
firstAssistantSeen: false,
|
|
617
646
|
thinkingFired: false, // rc.29: extended-thinking → reactor THINKING
|
|
647
|
+
// rc.45: multi-segment same-bubble streaming. priorMessagesText
|
|
648
|
+
// accumulates the full text of completed assistant-message
|
|
649
|
+
// segments in the SAME bubble. On message-id transition WITHOUT
|
|
650
|
+
// a steer, the just-finished segment rolls into priorMessagesText
|
|
651
|
+
// and the new segment's text appends to it (one bubble grows).
|
|
652
|
+
// On message-id transition WITH a steer, priorMessagesText
|
|
653
|
+
// resets and a new bubble starts. pendingSteerCausesNewBubble is
|
|
654
|
+
// set by injectUserMessage; consumed + cleared on the next
|
|
655
|
+
// message-id transition.
|
|
656
|
+
priorMessagesText: '',
|
|
657
|
+
pendingSteerCausesNewBubble: false,
|
|
618
658
|
};
|
|
619
659
|
|
|
620
660
|
pending.fireFirstStream = () => {
|
|
@@ -881,6 +921,15 @@ class ProcessManagerSdk {
|
|
|
881
921
|
if (priority !== undefined) msg.priority = priority;
|
|
882
922
|
if (shouldQuery !== undefined) msg.shouldQuery = shouldQuery;
|
|
883
923
|
entry.inputController.push(msg);
|
|
924
|
+
// rc.45: signal the streamer to start a new bubble at the next
|
|
925
|
+
// assistant-message-id transition. Without this flag,
|
|
926
|
+
// _handleEvent would APPEND the post-steer assistant text into
|
|
927
|
+
// the same bubble as the pre-steer text, hiding the user's
|
|
928
|
+
// intervention. Only set when there's a head pending — if the
|
|
929
|
+
// session is idle, the next pm.send will start a fresh bubble
|
|
930
|
+
// anyway.
|
|
931
|
+
const head = entry.pendingQueue?.[0];
|
|
932
|
+
if (head) head.pendingSteerCausesNewBubble = true;
|
|
884
933
|
this._logEvent('inject-user-message', {
|
|
885
934
|
session_key: sessionKey,
|
|
886
935
|
chat_id: entry.chatId,
|
package/lib/stream-reply.js
CHANGED
|
@@ -55,6 +55,21 @@ function createStreamer({
|
|
|
55
55
|
schedule = setTimeout,
|
|
56
56
|
cancel = clearTimeout,
|
|
57
57
|
logger = console,
|
|
58
|
+
// rc.44: by default, KEEP intermediate text bubbles when
|
|
59
|
+
// forceNewMessage transitions to a fresh bubble for a new
|
|
60
|
+
// top-level assistant message. These are NOT "thinking" tokens
|
|
61
|
+
// (those are filtered out by extractAssistantText —
|
|
62
|
+
// b.type === 'text' only). They're regular text segments the
|
|
63
|
+
// model emitted as part of the reply (e.g. "Let me check that..."
|
|
64
|
+
// → tool runs → "Found it. Here's the answer..."). Pre-0.7.2
|
|
65
|
+
// these were preserved (the original 0.7.0 multi-bubble design);
|
|
66
|
+
// 0.7.2 added archive-and-delete-at-turn-end as OpenClaw-parity
|
|
67
|
+
// cleanup. rc.44 reverts to the 0.7.0 preserve-all default
|
|
68
|
+
// because the intermediate text is substantive reply content,
|
|
69
|
+
// not noise. Set to false to restore the 0.7.2 deletion behaviour
|
|
70
|
+
// (only final bubble visible) for partner-facing chats that
|
|
71
|
+
// prefer terse output.
|
|
72
|
+
preserveIntermediateBubbles = true,
|
|
58
73
|
} = {}) {
|
|
59
74
|
throttleMs = Math.max(THROTTLE_FLOOR_MS, throttleMs);
|
|
60
75
|
let state = 'idle'; // 'idle' | 'live' | 'finalized'
|
|
@@ -67,9 +82,22 @@ function createStreamer({
|
|
|
67
82
|
// 0.7.2: msg_ids of bubbles that have been superseded by
|
|
68
83
|
// forceNewMessage(). The caller (polygram.js handleMessage at
|
|
69
84
|
// end-of-turn) reads getArchived() and issues deleteMessage on
|
|
70
|
-
// each
|
|
71
|
-
//
|
|
72
|
-
//
|
|
85
|
+
// each.
|
|
86
|
+
//
|
|
87
|
+
// History note (rc.44 correction): the 0.7.2 commit claimed this
|
|
88
|
+
// was "OpenClaw-parity / archivedAnswerPreviews cleanup" — that
|
|
89
|
+
// was wrong. The OFFICIAL OpenClaw + pi-telegram model is
|
|
90
|
+
// single-bubble-per-turn edited in place via sendMessageDraft (or
|
|
91
|
+
// sendMessage + editMessageText fallback); intermediate text
|
|
92
|
+
// segments don't exist there because the streamer concatenates
|
|
93
|
+
// everything into the same bubble. Polygram's multi-bubble shape
|
|
94
|
+
// is a 0.7.0 polygram-specific decision (one bubble per top-level
|
|
95
|
+
// assistant-message id, motivated by the SDK's segmentation), and
|
|
96
|
+
// the 0.7.2 archive-and-delete was a polygram-specific terseness
|
|
97
|
+
// cleanup, not OpenClaw porting. rc.44 made preserve-all the
|
|
98
|
+
// default again — archived[] only fills when
|
|
99
|
+
// preserveIntermediateBubbles=false (opt-out for partner-facing
|
|
100
|
+
// chats that prefer only-final-answer-visible output).
|
|
73
101
|
const archived = [];
|
|
74
102
|
|
|
75
103
|
// LIVE-EDIT truncation only — used during streaming when latestText
|
|
@@ -158,15 +186,26 @@ function createStreamer({
|
|
|
158
186
|
// emits a new top-level assistant message mid-turn (post tool-result):
|
|
159
187
|
// we want it in its own bubble below the previous one, not appended
|
|
160
188
|
// via editMessageText to the original.
|
|
189
|
+
//
|
|
190
|
+
// rc.44: by default, the previous bubble is PRESERVED (not archived
|
|
191
|
+
// for end-of-turn deletion). Intermediate text segments are
|
|
192
|
+
// substantive reply content the user typed up — not "thinking"
|
|
193
|
+
// tokens (those are filtered upstream). Pre-0.7.2 polygram kept
|
|
194
|
+
// them all; 0.7.2 added deletion for OpenClaw-parity terseness.
|
|
195
|
+
// rc.44 reverts to the 0.7.0 preserve-all default. Opt back into
|
|
196
|
+
// the 0.7.2 behaviour with `preserveIntermediateBubbles: false`.
|
|
197
|
+
//
|
|
198
|
+
// When preserving, we still cancel the pending throttled edit (it
|
|
199
|
+
// wouldn't fire after we transition to a new bubble anyway) but
|
|
200
|
+
// there may be a recently-flushed edit in flight whose result we
|
|
201
|
+
// don't await — the bubble will display whatever its last
|
|
202
|
+
// successful edit landed, which is typically very close to the
|
|
203
|
+
// segment's final text (throttle is 250ms; segments take seconds).
|
|
161
204
|
function forceNewMessage() {
|
|
162
205
|
if (pendingEdit) { cancel(pendingEdit); pendingEdit = null; }
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
// Without this, every intermediate "thinking out loud" assistant
|
|
167
|
-
// message in a tool-heavy turn leaves a permanent bubble in the
|
|
168
|
-
// chat — the user wants only the final answer's bubble visible.
|
|
169
|
-
if (msgId != null) archived.push(msgId);
|
|
206
|
+
if (msgId != null && !preserveIntermediateBubbles) {
|
|
207
|
+
archived.push(msgId);
|
|
208
|
+
}
|
|
170
209
|
msgId = null;
|
|
171
210
|
currentText = '';
|
|
172
211
|
latestText = '';
|
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.45",
|
|
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
|
@@ -2303,16 +2303,37 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
2303
2303
|
},
|
|
2304
2304
|
minChars: botCfg.streamMinChars,
|
|
2305
2305
|
throttleMs: botCfg.streamThrottleMs,
|
|
2306
|
+
// rc.44: preserve intermediate bubbles by default. These are
|
|
2307
|
+
// regular text segments the model emits across an agentic
|
|
2308
|
+
// multi-step turn ("Let me check..." → tool runs → "Found it.
|
|
2309
|
+
// Here's the answer..."). Pre-0.7.2 polygram preserved them;
|
|
2310
|
+
// 0.7.2 added archive-and-delete-at-turn-end for terseness.
|
|
2311
|
+
// rc.44 reverts to preserve-all (the 0.7.0 default). Per-chat /
|
|
2312
|
+
// per-bot opt-out via `preserveIntermediateBubbles: false` for
|
|
2313
|
+
// chats where the partner-facing UX wants only the final answer
|
|
2314
|
+
// (e.g. UMI Group).
|
|
2315
|
+
preserveIntermediateBubbles: chatConfig.preserveIntermediateBubbles != null
|
|
2316
|
+
? chatConfig.preserveIntermediateBubbles
|
|
2317
|
+
: (botCfg.preserveIntermediateBubbles != null
|
|
2318
|
+
? botCfg.preserveIntermediateBubbles
|
|
2319
|
+
: true),
|
|
2306
2320
|
logger: { error: (m) => console.error(`[${label}] ${m}`) },
|
|
2307
2321
|
});
|
|
2308
2322
|
// streamer is registered with this turn via pm.send's context (below)
|
|
2309
2323
|
|
|
2310
2324
|
// 0.7.2: clean up bubbles superseded by forceNewMessage() — the
|
|
2311
|
-
// intermediate
|
|
2312
|
-
//
|
|
2313
|
-
//
|
|
2314
|
-
//
|
|
2315
|
-
//
|
|
2325
|
+
// intermediate text segments that fired across a tool-heavy turn.
|
|
2326
|
+
// Pre-0.7.2 (since 0.7.0 multi-bubble landed) those bubbles were
|
|
2327
|
+
// kept; 0.7.2 added cleanup motivated by a post-0.7.1 deploy
|
|
2328
|
+
// screenshot of six bubbles per logical turn — terseness goal,
|
|
2329
|
+
// NOT OpenClaw porting. (Earlier comments mis-cited OpenClaw
|
|
2330
|
+
// parity; the official OpenClaw + pi-telegram model is
|
|
2331
|
+
// single-bubble-per-turn edited in place. Polygram's
|
|
2332
|
+
// multi-bubble shape is its own decision.)
|
|
2333
|
+
// rc.44 made preservation the default again — getArchived()
|
|
2334
|
+
// returns [] unless the chat opted out via
|
|
2335
|
+
// `preserveIntermediateBubbles: false`. This function still runs
|
|
2336
|
+
// unconditionally because the opt-out path needs it to fire.
|
|
2316
2337
|
// Call AFTER finalize/discard decisions so we never delete the
|
|
2317
2338
|
// bubble that's the final reply.
|
|
2318
2339
|
async function cleanupArchivedBubbles() {
|