polygram 0.8.0-rc.35 → 0.8.0-rc.37

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.35",
4
+ "version": "0.8.0-rc.37",
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",
@@ -91,10 +91,18 @@ function createAutosteerBuffer() {
91
91
  * non-empty output, with kind='autosteer-hook-drained'.
92
92
  * @param {string|null} [opts.chatId] — for the logEvent payload only.
93
93
  * @param {object} [opts.logger] — for error logging (must have .error).
94
+ * @param {(sessionKey: string, drainedCount: number) => void} [opts.onDrained]
95
+ * — fired AFTER the hook successfully injects additionalContext.
96
+ * rc.37 wires this to clearAutosteeredReactions so the ✍ reaction
97
+ * fades the moment the agent absorbs the follow-up — not at SDK
98
+ * turn-end, which under autosteer can stretch tens of minutes
99
+ * (one SDK turn keeps absorbing follow-ups via additionalContext
100
+ * and never emits result, so the old turn-end-only cleanup left
101
+ * ✍ stuck across many user messages).
94
102
  *
95
103
  * @returns {async () => Promise<HookJSONOutput>}
96
104
  */
97
- function makePostToolBatchHook({ buffer, sessionKey, logEvent = null, chatId = null, logger = console } = {}) {
105
+ function makePostToolBatchHook({ buffer, sessionKey, logEvent = null, chatId = null, logger = console, onDrained = null } = {}) {
98
106
  if (!buffer) throw new TypeError('buffer required');
99
107
  if (!sessionKey) throw new TypeError('sessionKey required');
100
108
  return async () => {
@@ -111,6 +119,10 @@ function makePostToolBatchHook({ buffer, sessionKey, logEvent = null, chatId = n
111
119
  });
112
120
  } catch { /* logger errors must not break the hook */ }
113
121
  }
122
+ if (typeof onDrained === 'function') {
123
+ try { onDrained(sessionKey, drained.length); }
124
+ catch (err) { logger?.error?.(`[${sessionKey}] onDrained: ${err?.message || err}`); }
125
+ }
114
126
  return {
115
127
  continue: true,
116
128
  hookSpecificOutput: {
@@ -39,7 +39,13 @@ const STATES = {
39
39
  // All three emoji on Telegram's curated standard reaction list.
40
40
  THINKING: { label: 'thinking', chain: ['🤔'] },
41
41
  THINKING_DEEPER: { label: 'thinking-deeper', chain: ['🤨', '🤔'] },
42
- THINKING_DEEPEST: { label: 'thinking-deepest', chain: ['🧐', '🤓', '🤔'] },
42
+ // rc.37: 🧐 (face with monocle) is REACTION_INVALID for bots — only
43
+ // Telegram Premium users can pick arbitrary emoji; bots are limited
44
+ // to ~70 standard reactions and 🧐 isn't on the list. Production
45
+ // log: `reaction apply failed (THINKING_DEEPEST → 🧐): 400: Bad
46
+ // Request: REACTION_INVALID`. 🤓 (nerd face, intellectual focus) is
47
+ // on the list and reads as "deeper than skeptical-eyebrow".
48
+ THINKING_DEEPEST: { label: 'thinking-deepest', chain: ['🤓', '🤔'] },
43
49
  CODING: { label: 'coding', chain: ['👨‍💻', '✍', '🤔'] },
44
50
  WEB: { label: 'web', chain: ['⚡', '🔥', '🤔'] },
45
51
  TOOL: { label: 'tool', chain: ['🔥', '🤔'] },
@@ -81,12 +87,17 @@ const DEFAULT_STALL_MS = 45_000;
81
87
  const DEFAULT_FREEZE_MS = 180_000;
82
88
  // rc.32: thinking-deepening cascade thresholds. Calibrated to Ivan DM
83
89
  // 14-day production data (445 turns) — 8% finish <5s, 33% in 5-15s,
84
- // 25% in 15-30s. 8s catches the meaty band, 20s catches the
85
- // mid-bracket. STALL still fires at 45s for the genuinely-long 17%.
90
+ // 25% in 15-30s. rc.35 bump: 8s→12s, 20s→30s. User feedback after
91
+ // rc.32 deploy was that the cascade kicked in too eagerly — most
92
+ // 5-15s turns showed 🤨 briefly before resolving, and the resulting
93
+ // emoji churn felt noisy rather than informative. Bumping deeper to
94
+ // 12s lets the entire 5-15s bracket (33%) resolve on plain 🤔, and
95
+ // deepest to 30s lets the 15-30s bracket (25%) resolve on 🤨 without
96
+ // cascading. STALL still fires at 45s for the genuinely-long 17%.
86
97
  // CODING / TOOL / WEB / WRITING reset the cascade (any state-change
87
98
  // in setState clears the deepening timers).
88
- const DEFAULT_THINKING_DEEPER_MS = 8_000;
89
- const DEFAULT_THINKING_DEEPEST_MS = 20_000;
99
+ const DEFAULT_THINKING_DEEPER_MS = 12_000;
100
+ const DEFAULT_THINKING_DEEPEST_MS = 30_000;
90
101
 
91
102
  // Tool name → state classifier. Case-insensitive substring match so we
92
103
  // don't have to enumerate every existing or future tool. Order matters:
@@ -219,8 +230,8 @@ function createReactionManager({
219
230
  };
220
231
 
221
232
  // rc.32: thinking-deepening cascade. When state is THINKING, schedule
222
- // auto-promotion at 8s (→ THINKING_DEEPER, 🤨) and 20s (→ THINKING_DEEPEST,
223
- // 🧐). Any other setState (CODING / TOOL / WEB / WRITING / terminal) clears
233
+ // auto-promotion at 12s (→ THINKING_DEEPER, 🤨) and 30s (→ THINKING_DEEPEST,
234
+ // 🤓). Any other setState (CODING / TOOL / WEB / WRITING / terminal) clears
224
235
  // these. heartbeat() does NOT re-arm them — heartbeat is for keeping
225
236
  // STALL/TIMEOUT at bay during silent activity, not for resetting
226
237
  // visible deepening progression.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.8.0-rc.35",
3
+ "version": "0.8.0-rc.37",
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
@@ -931,6 +931,18 @@ function buildSdkOptions(sessionKey, ctx) {
931
931
  chatId: ctx?.chatId ?? null,
932
932
  logEvent,
933
933
  logger: console,
934
+ // rc.37: clear ✍ reactions when the hook ABSORBS follow-ups, not
935
+ // at SDK turn-end. Under autosteer one SDK "turn" can stretch
936
+ // tens of minutes — every drain feeds more user text via
937
+ // additionalContext, the agent keeps reasoning, no `result` event
938
+ // fires, inFlight stays true, ✍ stays stuck on every drained
939
+ // follow-up. Clearing at drain time matches user perception
940
+ // ("the bot got my message → ✍ goes away").
941
+ onDrained: (key) => {
942
+ clearAutosteeredReactions(key).catch((err) => {
943
+ console.error(`[${BOT_NAME}] autosteer-hook clearReactions: ${err.message}`);
944
+ });
945
+ },
934
946
  });
935
947
 
936
948
  // 0.8.0-rc.21: SessionStart hook preloads recent polygram-DB