polygram 0.10.0-rc.43 → 0.10.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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "polygram",
|
|
4
|
-
"version": "0.10.0-rc.
|
|
4
|
+
"version": "0.10.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 plus history (transcript queries) and polygram-send (out-of-turn IPC sends with file-upload validation) skills.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"telegram",
|
|
@@ -1569,9 +1569,26 @@ class TmuxProcess extends Process {
|
|
|
1569
1569
|
_heartbeat(turn, source) {
|
|
1570
1570
|
if (!turn) return;
|
|
1571
1571
|
turn.lastActivityAt = this._now();
|
|
1572
|
-
//
|
|
1573
|
-
//
|
|
1574
|
-
//
|
|
1572
|
+
// rc.45 (shumorobot main topic 2026-05-23): the reactor's
|
|
1573
|
+
// cascade timers (THINKING_DEEPER at 12 s, THINKING_DEEPEST at
|
|
1574
|
+
// 30 s, STALL at 45 s) were firing during long pure-thinking
|
|
1575
|
+
// turns because no signal reset the clock — hooks don't fire
|
|
1576
|
+
// during thinking, JSONL `assistant-chunk` doesn't fire either.
|
|
1577
|
+
// But capture-pane DOES see claude's continuous "esc to
|
|
1578
|
+
// interrupt" indicator throughout the thinking phase, and the
|
|
1579
|
+
// existing `_heartbeat(turn, 'capture:streaming')` call from
|
|
1580
|
+
// line 2620 already runs on every poll while claude is busy.
|
|
1581
|
+
// Route that signal to the reactor too — the cascade clock is
|
|
1582
|
+
// reset and the user sees a stable 🤔 instead of escalating to
|
|
1583
|
+
// 🥱 (STALL) on a healthy long-thinking turn.
|
|
1584
|
+
//
|
|
1585
|
+
// The SDK backend gets this for free via typed `thinking`
|
|
1586
|
+
// events that flow through onStreamChunk → reactor.heartbeat.
|
|
1587
|
+
// tmux had no equivalent until this hookup.
|
|
1588
|
+
const reactor = turn.context?.reactor;
|
|
1589
|
+
if (reactor && typeof reactor.heartbeat === 'function') {
|
|
1590
|
+
try { reactor.heartbeat(); } catch { /* swallow — heartbeat is best-effort */ }
|
|
1591
|
+
}
|
|
1575
1592
|
void source;
|
|
1576
1593
|
}
|
|
1577
1594
|
|
|
@@ -2559,7 +2576,7 @@ class TmuxProcess extends Process {
|
|
|
2559
2576
|
}
|
|
2560
2577
|
|
|
2561
2578
|
async _waitForReady() {
|
|
2562
|
-
|
|
2579
|
+
let deadline = this._now() + this.readyTimeoutMs;
|
|
2563
2580
|
let lastBuf = '';
|
|
2564
2581
|
// B6 (shumorobot 2026-05-18, Music topic, twice): a slow
|
|
2565
2582
|
// custom-agent spawn (`music-curation:music-curator` loading
|
|
@@ -2655,6 +2672,15 @@ class TmuxProcess extends Process {
|
|
|
2655
2672
|
);
|
|
2656
2673
|
}
|
|
2657
2674
|
sessionAgePromptDismissed = true;
|
|
2675
|
+
// rc.44 (shumorobot 2026-05-22 23:30+23:36): "Resume from
|
|
2676
|
+
// summary" actually triggers claude's `/compact` command,
|
|
2677
|
+
// which observably takes ~2 min on an 8h/117k-token Music
|
|
2678
|
+
// session. The original `readyTimeoutMs` (120 s) was
|
|
2679
|
+
// running out BEFORE compact finished — rc.43 dismissed
|
|
2680
|
+
// the menu but the post-dismiss wait still timed out.
|
|
2681
|
+
// Reset the deadline so compact has a full
|
|
2682
|
+
// `readyTimeoutMs` budget from dismissal time.
|
|
2683
|
+
deadline = this._now() + this.readyTimeoutMs;
|
|
2658
2684
|
// Reset readiness clock + prev-pane so the menu's content
|
|
2659
2685
|
// doesn't satisfy the byte-stability check while claude is
|
|
2660
2686
|
// reloading from the summary.
|
|
@@ -53,24 +53,43 @@ const SANITIZED_REPLACEMENT =
|
|
|
53
53
|
'_(the model returned no actual reply — try rephrasing or asking again)_';
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Inspect an outbound assistant text.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* substitution.
|
|
56
|
+
* Inspect an outbound assistant text. Replaces any occurrence of a
|
|
57
|
+
* known CLI-context canned string (as a SUBSTRING) with the honest
|
|
58
|
+
* fallback, and returns a `replaced` flag so the caller can log the
|
|
59
|
+
* substitution.
|
|
60
|
+
*
|
|
61
|
+
* Why substring-replace, not exact-match (rc.45 production discovery):
|
|
62
|
+
* claude can emit MULTIPLE assistant-message blocks within one turn,
|
|
63
|
+
* and polygram concatenates them into the final `parsed.text`. If
|
|
64
|
+
* claude's first block was a substantive reply and the second was
|
|
65
|
+
* the canned `No response requested.`, the combined `parsed.text`
|
|
66
|
+
* matches neither canned string exactly — so the previous exact-
|
|
67
|
+
* match sanitizer didn't fire and the canned string still reached
|
|
68
|
+
* Telegram as a separate bubble (shumorobot Music topic and main
|
|
69
|
+
* topic, 2026-05-23, msg 999).
|
|
70
|
+
*
|
|
71
|
+
* Substring replacement risk: a legitimate reply that DISCUSSES the
|
|
72
|
+
* canned phrase ("the model leaks 'No response requested.' here")
|
|
73
|
+
* will have the inner phrase replaced. Cost is cosmetic (slightly
|
|
74
|
+
* ugly italicised replacement embedded in prose), not catastrophic.
|
|
75
|
+
* The CANNED_STRINGS allowlist stays narrow to keep this rare.
|
|
60
76
|
*
|
|
61
77
|
* @param {string} text — the assistant text about to be sent.
|
|
62
78
|
* @returns {{ text: string, replaced: boolean, original?: string }}
|
|
63
79
|
*/
|
|
64
80
|
function sanitizeAssistantReply(text) {
|
|
65
81
|
if (typeof text !== 'string') return { text, replaced: false };
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
82
|
+
if (!text) return { text, replaced: false };
|
|
83
|
+
let mutated = text;
|
|
84
|
+
let firstHit = null;
|
|
85
|
+
for (const canned of CANNED_STRINGS) {
|
|
86
|
+
if (mutated.includes(canned)) {
|
|
87
|
+
if (firstHit == null) firstHit = canned;
|
|
88
|
+
mutated = mutated.split(canned).join(SANITIZED_REPLACEMENT);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (firstHit != null) {
|
|
92
|
+
return { text: mutated, replaced: true, original: firstHit };
|
|
74
93
|
}
|
|
75
94
|
return { text, replaced: false };
|
|
76
95
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.10.0-rc.
|
|
3
|
+
"version": "0.10.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": {
|