polygram 0.8.0-rc.59 → 0.8.0-rc.60

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.59",
4
+ "version": "0.8.0-rc.60",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.8.0-rc.59",
3
+ "version": "0.8.0-rc.60",
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
@@ -2085,10 +2085,37 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
2085
2085
  const cmdUser = msg.from?.first_name || msg.from?.username || null;
2086
2086
  const cmdUserId = msg.from?.id || null;
2087
2087
 
2088
+ // Mark the inbound row terminal so boot replay doesn't pick it up
2089
+ // again. Must fire down EVERY non-throwing exit path (early returns
2090
+ // for error / NO_REPLY, streamed-reply preview-becomes-final, the
2091
+ // discard+redeliver branch, regular reply at end). Earlier versions
2092
+ // only marked at the bottom of try, so streamed-reply early returns
2093
+ // left handler_status stuck at 'dispatched' forever and the next
2094
+ // boot replayed every long turn.
2095
+ //
2096
+ // rc.59: hoisted ABOVE sendReply (was originally below all slash
2097
+ // commands) so sendReply can call it. Slash commands like /compact
2098
+ // /new /reset /model /effort all reply via sendReply but never
2099
+ // dispatched a turn, so they were leaving handler_status='dispatched'
2100
+ // forever. Boot-replay (now with rc.57's auto-derived 72-min window
2101
+ // for chats with maxTurn=3600) would then re-fire the same /compact
2102
+ // command — which post-compaction lands in a stale-session state and
2103
+ // emits "🗜️ No active session — /compact only works once a turn has
2104
+ // started." Visible duplicate-reply UX bug.
2105
+ const markReplied = () => dbWrite(() => db.setInboundHandlerStatus({
2106
+ chat_id: chatId, msg_id: msg.message_id, status: 'replied',
2107
+ }), 'set handler_status=replied');
2108
+
2088
2109
  // sendReply accepts (text, meta?) with optional extra Telegram params
2089
2110
  // pulled out via meta.params (kept separate so meta stays for DB tags).
2111
+ // rc.59: also calls markReplied() so slash-command paths don't leave
2112
+ // handler_status='dispatched' for boot-replay to re-fire later. All
2113
+ // 29 sendReply call sites in handleMessage are slash-command exit
2114
+ // paths — the contract "we sent a response, the inbound is handled"
2115
+ // holds for every one of them.
2090
2116
  const sendReply = (replyText, meta = {}) => {
2091
2117
  const { params: extraParams = {}, ...metaTags } = meta;
2118
+ markReplied();
2092
2119
  return tg(bot, 'sendMessage', {
2093
2120
  chat_id: chatId, text: replyText, ...replyOpts(threadId), ...extraParams,
2094
2121
  }, { source: 'command-reply', botName: BOT_NAME, model: chatConfig.model, effort: chatConfig.effort, ...metaTags });
@@ -2697,16 +2724,11 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
2697
2724
  reactor.setState('THINKING');
2698
2725
  }
2699
2726
 
2700
- // Mark the inbound row terminal so boot replay doesn't pick it up
2701
- // again. Must fire down EVERY non-throwing exit path (early returns
2702
- // for error / NO_REPLY, streamed-reply preview-becomes-final, the
2703
- // discard+redeliver branch, regular reply at end). Earlier versions
2704
- // only marked at the bottom of try, so streamed-reply early returns
2705
- // left handler_status stuck at 'dispatched' forever and the next
2706
- // boot replayed every long turn.
2707
- const markReplied = () => dbWrite(() => db.setInboundHandlerStatus({
2708
- chat_id: chatId, msg_id: msg.message_id, status: 'replied',
2709
- }), 'set handler_status=replied');
2727
+ // markReplied hoisted to top of handleMessage in rc.59 (see
2728
+ // definition near sendReply for context). Slash commands path
2729
+ // through sendReply which now calls it; all the OTHER non-throwing
2730
+ // exit paths below (NO_REPLY, streamed-reply preview-becomes-final,
2731
+ // discard+redeliver, regular reply at end) call it directly.
2710
2732
 
2711
2733
  // 0.8.0 Phase 2 step 1 — AUTOSTEER. If SDK pm is active AND there's
2712
2734
  // already an in-flight turn for this session AND autosteer isn't