polygram 0.8.0-rc.46 → 0.8.0-rc.47

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.8.0-rc.46",
4
+ "version": "0.8.0-rc.47",
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",
@@ -193,6 +193,17 @@ class ProcessManagerSdk {
193
193
  onStreamChunk = null,
194
194
  onToolUse = null,
195
195
  onAssistantMessageStart = null,
196
+ // rc.47: fires when an SDK assistant message arrives with NO head
197
+ // pending in entry.pendingQueue — i.e. an autonomous turn (typical
198
+ // ScheduleWakeup case where the agent self-fires without a
199
+ // corresponding pm.send). Polygram wires this to a Telegram-send
200
+ // function that derives chat_id (always) and thread_id (when
201
+ // isolateTopics) from the sessionKey via getChatIdFromKey /
202
+ // getThreadIdFromKey, then forwards the text to the right chat/
203
+ // topic. Pre-rc.47 these messages were silently dropped at the
204
+ // `&& head` gate in _handleEvent. Subagent messages
205
+ // (parent_tool_use_id != null) are still filtered upstream.
206
+ onAutonomousAssistantMessage = null,
196
207
  onCompactBoundary = null,
197
208
  onQueueDrop = null,
198
209
  onThinking = null,
@@ -211,6 +222,7 @@ class ProcessManagerSdk {
211
222
  this.onStreamChunk = onStreamChunk;
212
223
  this.onToolUse = onToolUse;
213
224
  this.onAssistantMessageStart = onAssistantMessageStart;
225
+ this.onAutonomousAssistantMessage = onAutonomousAssistantMessage;
214
226
  this.onCompactBoundary = onCompactBoundary;
215
227
  this.onQueueDrop = onQueueDrop;
216
228
  this.onThinking = onThinking;
@@ -425,6 +437,23 @@ class ProcessManagerSdk {
425
437
  return;
426
438
  }
427
439
 
440
+ if (msg.type === 'assistant' && !head) {
441
+ // rc.47: autonomous assistant message — no user-initiated
442
+ // pm.send is in flight. Typical cause: ScheduleWakeup fired,
443
+ // the agent emitted a self-driven response. Pre-rc.47 these
444
+ // were silently dropped by the `&& head` gate. Now we route
445
+ // them via onAutonomousAssistantMessage so polygram can
446
+ // forward the text to the right Telegram chat/topic.
447
+ if (msg.parent_tool_use_id != null) return;
448
+ const text = extractAssistantText(msg);
449
+ if (!text) return;
450
+ if (this.onAutonomousAssistantMessage) {
451
+ try { this.onAutonomousAssistantMessage(entry.sessionKey, msg, entry); }
452
+ catch (err) { this.logger.error?.(`[${entry.label}] onAutonomousAssistantMessage: ${err.message}`); }
453
+ }
454
+ return;
455
+ }
456
+
428
457
  if (msg.type === 'assistant' && head) {
429
458
  // Subagent filter (Phase 1 step 7): top-level only.
430
459
  if (msg.parent_tool_use_id != null) return;
@@ -28,4 +28,20 @@ function getChatIdFromKey(sessionKey) {
28
28
  return sessionKey.split(':')[0];
29
29
  }
30
30
 
31
- module.exports = { getSessionKey, getChatIdFromKey };
31
+ /**
32
+ * Inverse of `getChatIdFromKey`: returns the thread_id portion of an
33
+ * isolated-topic sessionKey, or null when there's no thread suffix.
34
+ * Used by rc.47 autonomous-wakeup routing — when ScheduleWakeup
35
+ * fires inside a polygram-spawned Query without a corresponding
36
+ * pm.send, we derive (chat_id, thread_id) from sessionKey to route
37
+ * the autonomous output back to the right Telegram chat/topic.
38
+ */
39
+ function getThreadIdFromKey(sessionKey) {
40
+ if (typeof sessionKey !== 'string' || !sessionKey) return null;
41
+ const idx = sessionKey.indexOf(':');
42
+ if (idx < 0) return null;
43
+ const thread = sessionKey.slice(idx + 1);
44
+ return thread || null;
45
+ }
46
+
47
+ module.exports = { getSessionKey, getChatIdFromKey, getThreadIdFromKey };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.8.0-rc.46",
3
+ "version": "0.8.0-rc.47",
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
@@ -30,7 +30,7 @@ const { ProcessManager } = require('./lib/process-manager');
30
30
  // callbacks), so the rest of polygram.js doesn't branch beyond the
31
31
  // pick-at-startup. Phase 4 deletes the CLI version after Phase 5
32
32
  // soak proves SDK stable. See docs/0.8.0-architecture-decisions.md.
33
- const { ProcessManagerSdk } = require('./lib/process-manager-sdk');
33
+ const { ProcessManagerSdk, extractAssistantText } = require('./lib/process-manager-sdk');
34
34
  // rc.42: autosteer-buffer module deleted. Native SDK priority push
35
35
  // (pm.injectUserMessage) replaces the buffer + PostToolBatch detour.
36
36
  const { createAutosteeredRefs } = require('./lib/autosteered-refs');
@@ -195,7 +195,7 @@ function isWellFormedMessage(msg) {
195
195
  }
196
196
 
197
197
  // ─── Session key — moved to lib/session-key.js so tests can import it. ─
198
- const { getSessionKey, getChatIdFromKey } = require('./lib/session-key');
198
+ const { getSessionKey, getChatIdFromKey, getThreadIdFromKey } = require('./lib/session-key');
199
199
 
200
200
  function getTopicName(chatConfig, threadId) {
201
201
  if (!threadId) return null;
@@ -3665,6 +3665,50 @@ async function main() {
3665
3665
  const r = head?.context?.reactor;
3666
3666
  if (r && typeof r.heartbeat === 'function') r.heartbeat();
3667
3667
  },
3668
+ // rc.47: autonomous wakeup forwarding. Fires when an SDK
3669
+ // assistant message arrives with no head pending — typical
3670
+ // ScheduleWakeup case where the agent self-fires without an
3671
+ // inbound user message. We derive chat_id (always) and thread_id
3672
+ // (when isolateTopics) from the sessionKey, then send the text
3673
+ // to that chat/topic. Subagent messages were already filtered
3674
+ // upstream (parent_tool_use_id != null check in pm-sdk).
3675
+ //
3676
+ // Best-effort send: failures are logged but don't propagate —
3677
+ // an autonomous turn that can't be delivered shouldn't crash
3678
+ // the daemon. Telemetry emitted as `autonomous-wakeup-message`
3679
+ // so we can audit how often these fire and whether any get lost.
3680
+ onAutonomousAssistantMessage: (sessionKey, msg /* , entry */) => {
3681
+ try {
3682
+ const text = extractAssistantText(msg);
3683
+ if (!text) return;
3684
+ const chatId = getChatIdFromKey(sessionKey);
3685
+ const threadIdRaw = getThreadIdFromKey(sessionKey);
3686
+ const threadId = threadIdRaw ? parseInt(threadIdRaw, 10) : null;
3687
+ if (!bot) {
3688
+ console.error(`[${BOT_NAME}] autonomous wakeup: bot not ready, dropping ${text.length} chars`);
3689
+ return;
3690
+ }
3691
+ const params = {
3692
+ chat_id: chatId,
3693
+ text,
3694
+ ...(Number.isInteger(threadId) && { message_thread_id: threadId }),
3695
+ };
3696
+ // Don't `await` — keep the pm-sdk event loop unblocked. The
3697
+ // tg() wrapper handles its own retries / chunking.
3698
+ tg(bot, 'sendMessage', params,
3699
+ { source: 'autonomous-wakeup', botName: BOT_NAME }).catch((err) => {
3700
+ console.error(`[${BOT_NAME}] autonomous wakeup send failed: ${err.message}`);
3701
+ });
3702
+ logEvent('autonomous-wakeup-message', {
3703
+ chat_id: chatId,
3704
+ session_key: sessionKey,
3705
+ thread_id: threadIdRaw,
3706
+ text_len: text.length,
3707
+ });
3708
+ } catch (err) {
3709
+ console.error(`[${BOT_NAME}] autonomous wakeup handler: ${err.message}`);
3710
+ }
3711
+ },
3668
3712
  // rc.29 onThinking removed — replaced by simpler timer-based
3669
3713
  // approach in handleMessage (post-QUEUED setState). The
3670
3714
  // SDK-thinking-block detection added complexity (partial-message