polygram 0.8.0-rc.44 → 0.8.0-rc.46
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 +36 -16
- package/package.json +1 -1
- package/polygram.js +53 -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.8.0-rc.
|
|
4
|
+
"version": "0.8.0-rc.46",
|
|
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,13 +55,20 @@ function createStreamer({
|
|
|
55
55
|
schedule = setTimeout,
|
|
56
56
|
cancel = clearTimeout,
|
|
57
57
|
logger = console,
|
|
58
|
-
// rc.44: by default, KEEP intermediate
|
|
59
|
-
//
|
|
60
|
-
// top-level assistant message.
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
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.
|
|
65
72
|
preserveIntermediateBubbles = true,
|
|
66
73
|
} = {}) {
|
|
67
74
|
throttleMs = Math.max(THROTTLE_FLOOR_MS, throttleMs);
|
|
@@ -75,9 +82,22 @@ function createStreamer({
|
|
|
75
82
|
// 0.7.2: msg_ids of bubbles that have been superseded by
|
|
76
83
|
// forceNewMessage(). The caller (polygram.js handleMessage at
|
|
77
84
|
// end-of-turn) reads getArchived() and issues deleteMessage on
|
|
78
|
-
// each
|
|
79
|
-
//
|
|
80
|
-
//
|
|
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).
|
|
81
101
|
const archived = [];
|
|
82
102
|
|
|
83
103
|
// LIVE-EDIT truncation only — used during streaming when latestText
|
|
@@ -168,12 +188,12 @@ function createStreamer({
|
|
|
168
188
|
// via editMessageText to the original.
|
|
169
189
|
//
|
|
170
190
|
// rc.44: by default, the previous bubble is PRESERVED (not archived
|
|
171
|
-
// for end-of-turn deletion).
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
// 0.7.2 deletion
|
|
175
|
-
//
|
|
176
|
-
// the
|
|
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`.
|
|
177
197
|
//
|
|
178
198
|
// When preserving, we still cancel the pending throttled edit (it
|
|
179
199
|
// wouldn't fire after we transition to a new bubble anyway) but
|
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.46",
|
|
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
|
@@ -1947,6 +1947,38 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1947
1947
|
}
|
|
1948
1948
|
return;
|
|
1949
1949
|
}
|
|
1950
|
+
// 0.8.0-rc.46: /reload — close + respawn the SDK Query while
|
|
1951
|
+
// PRESERVING the persisted claude_session_id. The next user
|
|
1952
|
+
// message resumes the conversation (model has prior context via
|
|
1953
|
+
// SDK resume) but the spawn re-reads agent / skill / plugin files
|
|
1954
|
+
// from disk, so any local edits to ~/.claude/agents/<name>.md or
|
|
1955
|
+
// skill files take effect.
|
|
1956
|
+
//
|
|
1957
|
+
// Difference vs /new:
|
|
1958
|
+
// /new → resetSession clears session_id → fresh conversation
|
|
1959
|
+
// /reload → kill closes Query, session_id preserved → same
|
|
1960
|
+
// conversation continues with fresh agent/skill code
|
|
1961
|
+
//
|
|
1962
|
+
// Mechanism: pm.kill drains the pending queue (with code 'KILLED')
|
|
1963
|
+
// and closes the SDK Query. Crucially it does NOT call
|
|
1964
|
+
// db.clearSessionId — the contract test
|
|
1965
|
+
// 'pm.kill does NOT call db.clearSessionId' pins this invariant.
|
|
1966
|
+
// The next user message hits getOrSpawn → no warm Query → spawn
|
|
1967
|
+
// fresh with `resume: <session_id>` from sessions table → SDK
|
|
1968
|
+
// restores conversation history → fresh files loaded.
|
|
1969
|
+
if (botAllowsCommands && text === '/reload') {
|
|
1970
|
+
if (pm.has(sessionKey)) {
|
|
1971
|
+
try { await pm.kill(sessionKey); }
|
|
1972
|
+
catch (err) { console.error(`[${label}] kill on /reload: ${err.message}`); }
|
|
1973
|
+
}
|
|
1974
|
+
logEvent('session-reload-command', {
|
|
1975
|
+
chat_id: chatId, command: text,
|
|
1976
|
+
user: cmdUser, user_id: cmdUserId,
|
|
1977
|
+
});
|
|
1978
|
+
await sendReply('🔄 Reloaded. Next message picks up the conversation with fresh skills/agents.');
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1950
1982
|
if (botAllowsCommands && (text === '/new' || text === '/reset')) {
|
|
1951
1983
|
let drained = 0;
|
|
1952
1984
|
const target = pm.pickFor(sessionKey);
|
|
@@ -2303,10 +2335,15 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
2303
2335
|
},
|
|
2304
2336
|
minChars: botCfg.streamMinChars,
|
|
2305
2337
|
throttleMs: botCfg.streamThrottleMs,
|
|
2306
|
-
// rc.44: preserve intermediate
|
|
2307
|
-
//
|
|
2308
|
-
//
|
|
2309
|
-
//
|
|
2338
|
+
// rc.44: preserve intermediate bubbles by default. These are
|
|
2339
|
+
// regular text segments the model emits across an agentic
|
|
2340
|
+
// multi-step turn ("Let me check..." → tool runs → "Found it.
|
|
2341
|
+
// Here's the answer..."). Pre-0.7.2 polygram preserved them;
|
|
2342
|
+
// 0.7.2 added archive-and-delete-at-turn-end for terseness.
|
|
2343
|
+
// rc.44 reverts to preserve-all (the 0.7.0 default). Per-chat /
|
|
2344
|
+
// per-bot opt-out via `preserveIntermediateBubbles: false` for
|
|
2345
|
+
// chats where the partner-facing UX wants only the final answer
|
|
2346
|
+
// (e.g. UMI Group).
|
|
2310
2347
|
preserveIntermediateBubbles: chatConfig.preserveIntermediateBubbles != null
|
|
2311
2348
|
? chatConfig.preserveIntermediateBubbles
|
|
2312
2349
|
: (botCfg.preserveIntermediateBubbles != null
|
|
@@ -2317,11 +2354,18 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
2317
2354
|
// streamer is registered with this turn via pm.send's context (below)
|
|
2318
2355
|
|
|
2319
2356
|
// 0.7.2: clean up bubbles superseded by forceNewMessage() — the
|
|
2320
|
-
// intermediate
|
|
2321
|
-
//
|
|
2322
|
-
//
|
|
2323
|
-
//
|
|
2324
|
-
//
|
|
2357
|
+
// intermediate text segments that fired across a tool-heavy turn.
|
|
2358
|
+
// Pre-0.7.2 (since 0.7.0 multi-bubble landed) those bubbles were
|
|
2359
|
+
// kept; 0.7.2 added cleanup motivated by a post-0.7.1 deploy
|
|
2360
|
+
// screenshot of six bubbles per logical turn — terseness goal,
|
|
2361
|
+
// NOT OpenClaw porting. (Earlier comments mis-cited OpenClaw
|
|
2362
|
+
// parity; the official OpenClaw + pi-telegram model is
|
|
2363
|
+
// single-bubble-per-turn edited in place. Polygram's
|
|
2364
|
+
// multi-bubble shape is its own decision.)
|
|
2365
|
+
// rc.44 made preservation the default again — getArchived()
|
|
2366
|
+
// returns [] unless the chat opted out via
|
|
2367
|
+
// `preserveIntermediateBubbles: false`. This function still runs
|
|
2368
|
+
// unconditionally because the opt-out path needs it to fire.
|
|
2325
2369
|
// Call AFTER finalize/discard decisions so we never delete the
|
|
2326
2370
|
// bubble that's the final reply.
|
|
2327
2371
|
async function cleanupArchivedBubbles() {
|