polygram 0.12.0-rc.10 → 0.12.0-rc.11

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.
@@ -118,6 +118,16 @@ const STREAMING_HINT_RE = /esc to interrupt/i;
118
118
  // — false positives surface as no-op telemetry, false negatives surface
119
119
  // as the idle-ceiling timeout (~10min).
120
120
  const UNKNOWN_PROMPT_HEURISTIC_RE = /(\?\s*$|\(y\/N\)|Yes\/No|❯\s|^\s*[12345]\.\s)/im;
121
+ // Dead-bridge signal. Claude Code prints "<source> no MCP server configured
122
+ // with that name" when a channel source references an MCP server that isn't
123
+ // registered. Music topic incident (2026-06-01): seen mid-turn after an
124
+ // auto-/compact of a large resumed session redrew the TUI and dropped the
125
+ // `server:polygram-bridge` binding — the turn could no longer deliver its
126
+ // reply and hung. The bridge name precedes the error on the same line, so
127
+ // anchor on it: matching the bare phrase risks a false hit on prose that
128
+ // merely quotes the error, while the healthy "polygram-bridge: <polygram-info>
129
+ // …" connection line never contains "no MCP server configured".
130
+ const BRIDGE_DEAD_RE = /polygram-bridge[^\n]*no MCP server configured/i;
121
131
  // Per-pattern rate limit so a dialog that lingers across multiple polls
122
132
  // doesn't spam sendControl/event emissions. Aligned with the 5s poll cadence.
123
133
  const MID_TURN_DEDUP_WINDOW_MS = 30_000;
@@ -1848,11 +1858,11 @@ class CliProcess extends Process {
1848
1858
  * landing just before the disconnect would otherwise leave a stray
1849
1859
  * timer on the dead instance).
1850
1860
  */
1851
- _handleBridgeDisconnected() {
1861
+ _handleBridgeDisconnected(reason = 'socket-close') {
1852
1862
  this.bridgeReady = false;
1853
1863
  this.mcpReady = false;
1854
1864
  if (this.closed) return;
1855
- this.logger.warn?.(`[${this.label}] channels: bridge disconnected unexpectedly`);
1865
+ this.logger.warn?.(`[${this.label}] channels: bridge disconnected unexpectedly (${reason})`);
1856
1866
  // L6: clear the interrupt grace timer alongside the rest of the lifecycle.
1857
1867
  if (this._interruptGraceTimer) {
1858
1868
  clearTimeout(this._interruptGraceTimer);
@@ -1875,7 +1885,7 @@ class CliProcess extends Process {
1875
1885
  this.pendingQueue.length = 0;
1876
1886
  this.inFlight = false;
1877
1887
  this.emit('bridge-disconnected');
1878
- this._logEvent('bridge-disconnected', { reason: 'socket-close' });
1888
+ this._logEvent('bridge-disconnected', { reason });
1879
1889
  }
1880
1890
 
1881
1891
  async _doKill(reason) {
@@ -2328,6 +2338,36 @@ class CliProcess extends Process {
2328
2338
  }
2329
2339
  if (!pane) return;
2330
2340
 
2341
+ // Wedge recovery (Music topic incident, 2026-06-01). Claude Code's
2342
+ // channel source can lose its MCP-server registration mid-turn — most
2343
+ // often after an auto-/compact of a large resumed session redraws the
2344
+ // TUI and the `server:polygram-bridge` binding fails to re-resolve
2345
+ // ("polygram-bridge no MCP server configured with that name"). The
2346
+ // bridge SOCKET stays up, so the socket-close path
2347
+ // (bridgeServer 'bridge-disconnected' → _handleBridgeDisconnected) never
2348
+ // fires — but claude can no longer deliver the reply, so the turn
2349
+ // orphans and would hang until the wall-clock cap while this watchdog
2350
+ // logged cli-mid-turn-unknown-prompt every 30s and recovered nothing.
2351
+ // Detect it from the pane and route through the SAME recovery as a real
2352
+ // socket disconnect: reject pending turns (user gets the 🔌 resend
2353
+ // message via BRIDGE_DISCONNECTED) and emit 'bridge-disconnected' so
2354
+ // process-manager kills + lazy-respawns the dead instance (next msg
2355
+ // recovers the conversation via `claude --resume`). Gated on
2356
+ // pendingTurns.size>0 by the early return above, so it can't fire on an
2357
+ // idle session that merely has the string in scrollback.
2358
+ if (BRIDGE_DEAD_RE.test(pane)) {
2359
+ this.logger.warn?.(
2360
+ `[${this.label}] cli: channels MCP registration lost mid-turn ` +
2361
+ `(pane-detected) — recovering ${this.pendingTurns.size} orphaned turn(s)`,
2362
+ );
2363
+ this._logEvent('cli-bridge-detached-midturn', {
2364
+ pending_count: this.pendingTurns.size,
2365
+ session_id: this.claudeSessionId,
2366
+ });
2367
+ this._handleBridgeDisconnected('mcp-registration-lost');
2368
+ return;
2369
+ }
2370
+
2331
2371
  const now = Date.now();
2332
2372
 
2333
2373
  // 0.12 Phase 3.2: liveness heartbeat. The TUI prints "esc to interrupt"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.12.0-rc.10",
3
+ "version": "0.12.0-rc.11",
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": {