polygram 0.10.0-rc.46 → 0.10.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.10.0-rc.46",
4
+ "version": "0.10.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 plus history (transcript queries) and polygram-send (out-of-turn IPC sends with file-upload validation) skills.",
6
6
  "keywords": [
7
7
  "telegram",
@@ -74,6 +74,18 @@ const PATTERNS = {
74
74
  // image-failure verb co-located with "image" or "photo".
75
75
  imageProcess: /(could not process|cannot process|failed to (process|load|decode)|unsupported|invalid|corrupt(?:ed)?)[^\n]{0,80}\b(image|photo)\b|\b(image|photo)\b[^\n]{0,80}(could not process|failed to (process|load|decode)|is (invalid|corrupted|unsupported))/i,
76
76
 
77
+ // rc.47: tmux backend's TMUX_TURN_TIMEOUT after H3 idle-ceiling fired.
78
+ // Production wedge 2026-05-24 msg 1020: a Bash tool's `PreToolUse`
79
+ // fired but `PostToolUse` never came — claude waited forever for the
80
+ // tool result, polygram waited 30 min then killed the turn, and the
81
+ // user got the generic `Hit a snag: turn did not complete in time`
82
+ // (the fall-through "unknown" path). This kind matches the enriched
83
+ // error message that `_runTurn` now throws (with the wedged-tool
84
+ // name + outstanding count in parens) AND the bare pre-rc.47 form
85
+ // for backwards compatibility. Placed BEFORE the generic `timeout`
86
+ // pattern so the more-specific TmuxProcess message wins.
87
+ tmuxToolWedge: /TmuxProcess: turn did not complete in time/i,
88
+
77
89
  // Idle/wall-clock timeout from polygram's pm timers, OR
78
90
  // model-side timeout. Mapped to a single class; user message is
79
91
  // identical either way.
@@ -102,6 +114,7 @@ const USER_MESSAGES = {
102
114
  roleOrdering: '⚠️ Conversation got into a tangled state. Try /new.',
103
115
  missingToolInput: '⚠️ Session history looks corrupted. Try /new.',
104
116
  imageProcess: '🖼 One of the images in this conversation can\'t be re-processed by Claude — likely an older one in the history. Starting a fresh session for this chat.',
117
+ tmuxToolWedge: '🔧 A tool didn\'t return in time — I cut it off. Most often this is a Bash command waiting on something external (a server, a file lock, an interactive prompt). Try resending; if it happens again, break the task into smaller steps.',
105
118
  timeout: '⏳ I went quiet too long without finishing. Try resending or simplifying.',
106
119
  format: '⚠️ Invalid request format. Try rephrasing or /new.',
107
120
  // Used both for in-flight retry attempts AND for the post-retry-failed
@@ -927,6 +927,18 @@ class TmuxProcess extends Process {
927
927
  `[${this.label}] turn timeout (${outcome.reason || 'unknown'}`
928
928
  + `${outcome.idleMs != null ? `, idle ${Math.round(outcome.idleMs)} ms` : ''})`,
929
929
  );
930
+ // rc.47: surface the wedged-tool diagnostic on BOTH the event
931
+ // and the thrown error message. Production wedge 2026-05-24
932
+ // msg 1020: a Bash tool's `PreToolUse` fired but no
933
+ // `PostToolUse` ever came; the user got the generic
934
+ // "Hit a snag: turn did not complete in time" with zero hint
935
+ // about WHAT got stuck. The hook stream + JSONL stream already
936
+ // told us "Bash" — surface it. lib/error/classify.js's
937
+ // `tmuxToolWedge` pattern matches this enriched message and
938
+ // produces a friendlier reply naming the wedged tool class.
939
+ const lastToolName = turn.lastToolName || null;
940
+ const outstandingToolsCount = turn.outstandingTools?.size ?? 0;
941
+ const outstandingSubagentsCount = turn.outstandingSubagents?.size ?? 0;
930
942
  this.emit('turn-timeout', {
931
943
  turnId: turn.turnId,
932
944
  reason: outcome.reason || null,
@@ -935,14 +947,23 @@ class TmuxProcess extends Process {
935
947
  hardBackstopMs: this.hardBackstopMs,
936
948
  sessionId: this.claudeSessionId,
937
949
  backend: 'tmux',
950
+ lastToolName,
951
+ outstandingToolsCount,
952
+ outstandingSubagentsCount,
938
953
  });
954
+ const wedgeContext = lastToolName
955
+ ? ` (last tool: ${lastToolName}, outstanding: ${outstandingToolsCount + outstandingSubagentsCount})`
956
+ : '';
939
957
  throw Object.assign(
940
- new Error('TmuxProcess: turn did not complete in time'),
958
+ new Error(`TmuxProcess: turn did not complete in time${wedgeContext}`),
941
959
  {
942
960
  code: 'TMUX_TURN_TIMEOUT',
943
961
  tmuxName: this.tmuxName,
944
962
  reason: outcome.reason || null,
945
963
  idleMs: outcome.idleMs ?? null,
964
+ lastToolName,
965
+ outstandingToolsCount,
966
+ outstandingSubagentsCount,
946
967
  },
947
968
  );
948
969
  }
@@ -1428,6 +1449,15 @@ class TmuxProcess extends Process {
1428
1449
  // tracked (B10); the predicate uses both sets to keep `quiet`
1429
1450
  // unreachable while any tool is genuinely outstanding.
1430
1451
  outstandingTools: new Set(),
1452
+ // rc.47 (production wedge 2026-05-24, msg 1020): the most recent
1453
+ // tool_use NAME observed from the JSONL stream — used by the
1454
+ // turn-timeout throw site so the user-facing error tells them
1455
+ // WHICH kind of tool wedged (almost always Bash) rather than the
1456
+ // generic "turn did not complete in time". Set in
1457
+ // `_handleSessionEvent` on every `tool-use`; not cleared by
1458
+ // `tool-result` (the diagnostic is "what was last running," and
1459
+ // the timeout path only reads this AFTER all racers gave up).
1460
+ lastToolName: null,
1431
1461
  };
1432
1462
  }
1433
1463
 
@@ -1619,6 +1649,12 @@ class TmuxProcess extends Process {
1619
1649
  if (typeof ev.id === 'string' && ev.name !== 'Agent') {
1620
1650
  t.outstandingTools.add(ev.id);
1621
1651
  }
1652
+ // rc.47: track the most recent tool name so a turn-timeout's
1653
+ // user-facing message can name the wedged tool class. Agent
1654
+ // tool-use intentionally INCLUDED here — a wedged subagent is
1655
+ // legitimate diagnostic context even though it doesn't go into
1656
+ // `outstandingTools` (B10 tracks Agents separately).
1657
+ if (typeof ev.name === 'string') t.lastToolName = ev.name;
1622
1658
  // Phase choice: subagent > tool > streaming. A Bash tool-use
1623
1659
  // arriving while an Agent is outstanding must NOT demote
1624
1660
  // SUBAGENT_RUNNING — both are "in flight," but the predicate's
@@ -68,6 +68,23 @@ const POLYGRAM_DISPLAY_HINT = [
68
68
  '- Any other shell-prompt-style filler that acknowledges silence',
69
69
  '',
70
70
  'If a user message is short, ambiguous, or feels like a no-op acknowledgement (e.g. `okay`, `ok`, `yes`, `got it`, `thanks`), reply with a brief substantive line — acknowledge what you understood and what (if anything) you will do next. If you genuinely have nothing useful to say, ask ONE specific clarifying question. NEVER emit a placeholder or a shell-style canned string — the chat surface has no silent-no-op state. Every reply must be intentional content.',
71
+ '',
72
+ '### Long-running work — periodic status updates — HARD RULE',
73
+ '',
74
+ 'If a turn is going to take more than ~5 minutes (long subagent work, batch processing, slow downloads, large refactors, scraping with backoff), you MUST emit a brief status line at least once every 5 minutes — even when you\'re mid-tool. The user cannot tell if you\'re working or stuck; silence reads as "frozen."',
75
+ '',
76
+ 'A status line is a one-liner about what you\'re doing right now. Format:',
77
+ '',
78
+ '`Still working — currently <what>, <progress signal>, <ETA if known>.`',
79
+ '',
80
+ 'Examples:',
81
+ '- `Still working — processing track 5 of 12, ~2 min left.`',
82
+ '- `Still working — waiting on SoundCloud rate-limit backoff (~90s), then resuming the batch.`',
83
+ '- `Still working — rebuilding the index, no ETA but it\'s active.`',
84
+ '',
85
+ 'If a single tool call is the slow thing (e.g. a build, a long script), emit the status line BEFORE invoking it: `About to run the build; this usually takes 3-4 min.` That way the user knows the silence is expected.',
86
+ '',
87
+ 'Why: polygram has a 30-minute idle ceiling that kills wedged turns. Periodic status lines (a) keep the user informed, (b) make a real wedge obvious (no status arriving for >5 min = something is genuinely stuck, not "the model is thinking quietly"). This is a UX contract, not a performance overhead — a one-liner every 5 min is cheap.',
71
88
  ].join('\n');
72
89
 
73
90
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.10.0-rc.46",
3
+ "version": "0.10.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": {