polygram 0.17.5 → 0.17.7

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.
@@ -69,6 +69,22 @@ function createSessionFeedback({
69
69
  });
70
70
  }
71
71
 
72
+ // Stop the cycle's TYPING the moment it delivers its answer, decoupled from
73
+ // endCycle. endCycle fires on the Process 'idle' edge (= session idle), which a
74
+ // later turn delays — so without this the "typing…" indicator spun minutes past the
75
+ // delivered answer (field: Ivan DM 2026-06-26). The entry is LEFT in place so
76
+ // endCycle still clears the anchor 🤔 and tears the entry down. docs/typing-tracks-activity-spec.md
77
+ function stopCycleTyping(sessionKey) {
78
+ const entry = active.get(sessionKey);
79
+ if (!entry || entry.typingStopped) return;
80
+ entry.typingStopped = true;
81
+ try { entry.stop(); } catch { /* best-effort */ }
82
+ logEvent('autonomous-cycle-visuals', {
83
+ chat_id: entry.anchor?.chatId ?? getChatIdFromKey(sessionKey),
84
+ session_key: sessionKey, state: 'typing-stopped',
85
+ });
86
+ }
87
+
72
88
  function endCycle(sessionKey) {
73
89
  const entry = active.get(sessionKey);
74
90
  if (!entry) return;
@@ -85,7 +101,7 @@ function createSessionFeedback({
85
101
  });
86
102
  }
87
103
 
88
- return { startAutonomousCycle, endCycle };
104
+ return { startAutonomousCycle, stopCycleTyping, endCycle };
89
105
  }
90
106
 
91
107
  module.exports = { createSessionFeedback };
@@ -133,8 +133,24 @@ function createDispatcher({
133
133
  throw new Error('auto-resume turn produced no text');
134
134
  }
135
135
 
136
- // 4. Send the continuation reply as regular Telegram messages,
137
- // threaded under the original user message.
136
+ // 4. Deliver the continuation reply UNLESS the resumed turn already
137
+ // delivered it itself. On the channels/cli backend Claude responds via the
138
+ // reply tool DURING the turn, so result.alreadyDelivered is set and the main
139
+ // dispatch path short-circuits its own deliver (cli-process.js ~2116). The
140
+ // resume path must honor it too, or the reply-tool send + this re-send
141
+ // double-post the SAME answer (field: shumabit@umi WhatsApp topic 2026-06-27,
142
+ // a bridge-disconnect resume sent "Fixed. ✅…" twice). SDK / genuine no-reply
143
+ // turns leave it falsy → deliver as before.
144
+ if (result.alreadyDelivered) {
145
+ logEvent('auto-resume-already-delivered', {
146
+ chat_id: chatId, session_key: sessionKey, msg_id: originalMsg.message_id,
147
+ text_len: result.text.length,
148
+ });
149
+ return result.text;
150
+ }
151
+
152
+ // Send the continuation reply as regular Telegram messages, threaded under
153
+ // the original user message.
138
154
  const chunks = chunkMarkdownText(result.text, chunkBudget);
139
155
  await deliverReplies({
140
156
  bot,
@@ -216,6 +216,10 @@ function createSdkCallbacks({
216
216
  const text = (msg && typeof msg.text === 'string' && msg.text)
217
217
  || extractAssistantText(msg);
218
218
  if (!text) return;
219
+ // 0.13 D3 fix: the cycle delivered its answer → stop its "typing…" NOW. endCycle
220
+ // (Process idle = SESSION idle) is delayed by any later turn, so typing otherwise
221
+ // spins minutes past the delivered answer. docs/typing-tracks-activity-spec.md
222
+ sessionFeedback?.stopCycleTyping?.(sessionKey);
219
223
  const chatId = getChatIdFromKey(sessionKey);
220
224
  const threadIdRaw = getThreadIdFromKey(sessionKey);
221
225
  const threadId = threadIdRaw ? parseInt(threadIdRaw, 10) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.17.5",
3
+ "version": "0.17.7",
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": {