polygram 0.8.0-rc.6 → 0.8.0-rc.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.
@@ -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.6",
4
+ "version": "0.8.0-rc.7",
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",
@@ -470,6 +470,7 @@ class ProcessManagerSdk {
470
470
  entry.inputController.push({
471
471
  type: 'user',
472
472
  message: { role: 'user', content: head.prompt },
473
+ parent_tool_use_id: null,
473
474
  });
474
475
  } catch (err) {
475
476
  entry.pendingQueue.shift();
@@ -655,6 +656,7 @@ class ProcessManagerSdk {
655
656
  entry.inputController.push({
656
657
  type: 'user',
657
658
  message: { role: 'user', content: prompt },
659
+ parent_tool_use_id: null,
658
660
  });
659
661
  } catch (err) {
660
662
  const idx = entry.pendingQueue.indexOf(pending);
@@ -754,13 +756,30 @@ class ProcessManagerSdk {
754
756
  * Returns true if push succeeded; false if session not found or
755
757
  * input controller closed.
756
758
  */
757
- steer(sessionKey, text, { shouldQuery = true } = {}) {
759
+ steer(sessionKey, text, { shouldQuery = false } = {}) {
758
760
  const entry = this.procs.get(sessionKey);
759
761
  if (!entry || entry.closed) return false;
760
762
  try {
763
+ // 0.8.0-rc.7 (per v4 plan §0 row 9 + Phase 2 step 1's original
764
+ // shape): push with `shouldQuery: false` so the SDK appends to
765
+ // the transcript without trying to terminate the in-flight turn.
766
+ // The previous default `shouldQuery: true` triggered the CLI
767
+ // binary's `m87` gate (transcript well-formedness check) which
768
+ // emitted `result.subtype = error_during_execution` whenever a
769
+ // plain-text user message arrived while the assistant was mid-
770
+ // tool-use. With shouldQuery=false the message merges into the
771
+ // next natural user turn — the in-flight tools complete first,
772
+ // then the assistant sees the steered context.
773
+ //
774
+ // parent_tool_use_id is required by SDKUserMessage type
775
+ // (sdk.d.ts:3479-3498). The SDK runtime checks `!== null` in
776
+ // multiple places; omitting it falls through to wrong handling
777
+ // branches. The SDK's own `mz.send()` and `pz` replay set it
778
+ // to null explicitly.
761
779
  entry.inputController.push({
762
780
  type: 'user',
763
781
  message: { role: 'user', content: text },
782
+ parent_tool_use_id: null,
764
783
  priority: 'now',
765
784
  shouldQuery,
766
785
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.8.0-rc.6",
3
+ "version": "0.8.0-rc.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": {
package/polygram.js CHANGED
@@ -2565,6 +2565,26 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
2565
2565
  // those still markReplied silently.
2566
2566
  if (result.text === 'NO_REPLY') { markReplied(); return; }
2567
2567
  if (!result.text) {
2568
+ // 0.8.0-rc.7: tool-only completion is NOT an error. Under SDK
2569
+ // pm, a turn that ends after running tools (no closing text
2570
+ // block) leaves result.text empty even though the bot DID
2571
+ // respond — via tool side effects the user already saw. Don't
2572
+ // post a "No response generated" apology in that case; it's
2573
+ // confusing and it spams the chat. Just clear the reactor
2574
+ // (otherwise 👀 stays stuck — reactor.stop() doesn't remove
2575
+ // the emoji visually) and silently mark replied.
2576
+ const toolOnlyTurn = (result.metrics?.numToolUses ?? 0) > 0
2577
+ && (result.metrics?.numAssistantMessages ?? 0) > 0;
2578
+ if (toolOnlyTurn) {
2579
+ await reactor.clear().catch(() => {});
2580
+ logEvent('tool-only-completion', {
2581
+ chat_id: chatId, msg_id: msg.message_id, bot: BOT_NAME,
2582
+ num_tool_uses: result.metrics?.numToolUses,
2583
+ num_assistant_messages: result.metrics?.numAssistantMessages,
2584
+ });
2585
+ markReplied();
2586
+ return;
2587
+ }
2568
2588
  // 0.7.1: if the fallback send itself fails, throw rather than
2569
2589
  // silently markReplied — the user gets nothing AND the inbound
2570
2590
  // is marked replied so boot replay won't redispatch. Same
@@ -2590,6 +2610,12 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
2590
2610
  logEvent('telegram-empty-response-fallback', {
2591
2611
  chat_id: chatId, msg_id: msg.message_id, bot: BOT_NAME,
2592
2612
  });
2613
+ // 0.8.0-rc.7: clear the THINKING/QUEUED emoji on the user's
2614
+ // message so 👀 doesn't stay stuck after the apology lands.
2615
+ // reactor.stop() (in the finally block) only kills timers; it
2616
+ // does NOT remove the visible emoji. Without this clear, the
2617
+ // user sees 👀 next to their message indefinitely.
2618
+ await reactor.clear().catch(() => {});
2593
2619
  markReplied();
2594
2620
  return;
2595
2621
  }