polygram 0.11.0-rc.3 → 0.11.0-rc.5

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.11.0-rc.3",
4
+ "version": "0.11.0-rc.5",
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",
@@ -431,10 +431,24 @@ class ChannelsProcess extends Process {
431
431
  const cwd = topicConfig?.cwd || opts.chatConfig?.cwd || opts.cwd;
432
432
  if (cwd) claudeArgs.unshift('--add-dir', cwd);
433
433
 
434
+ // rc.5 (2026-05-25 shumorobot diagnosis): the launch cwd MUST be the
435
+ // resolved topic/chat cwd, not just opts.cwd. claude's TUI indexes
436
+ // session storage by current working directory — its session files
437
+ // live under ~/.claude/projects/<cwd-with-dashes>/<session-id>.jsonl.
438
+ // When polygram launched claude with cwd=process.cwd() (the daemon's
439
+ // own working dir, e.g. ~/.polygram), `--resume <id>` looked in the
440
+ // wrong projects dir and printed "No conversation found with session
441
+ // ID: <id>" then exited clean — surfacing as "Process exited (code 0)"
442
+ // immediately after the bridge briefly connected.
443
+ //
444
+ // Mirrors lib/process/tmux-process.js:488 + :659 — same resolution,
445
+ // same fallback chain. The `--add-dir` flag is independent: it
446
+ // declares an additional trusted-roots entry, NOT the launch dir.
447
+ //
434
448
  // Real tmuxRunner.spawn signature: {name, cwd, command, args, envExtras, paneWidth}
435
449
  await this.runner.spawn({
436
450
  name: tmuxName,
437
- cwd: opts.cwd || process.cwd(),
451
+ cwd: cwd || opts.cwd || process.cwd(),
438
452
  command: this.claudeBin,
439
453
  args: claudeArgs,
440
454
  });
@@ -72,16 +72,42 @@ async function runStartupGate({
72
72
  const deadline = startedAt + deadlineMs;
73
73
  const seen = new Set();
74
74
  const matchedTriggers = [];
75
+ // rc.4: remember the most recent successful pane snapshot. If the gate
76
+ // fails (deadline OR session-gone), include the snapshot in the error
77
+ // so we can see what the TUI last printed before claude exited. Without
78
+ // this, "claude exits code 0 after dev-channels Enter" surfaces as a
79
+ // 30-second `can't find pane` spam with no diagnostic about WHY.
80
+ let lastPane = null;
75
81
 
76
82
  while (Date.now() < deadline) {
77
83
  let pane;
78
84
  try {
79
85
  pane = await runner.captureWide(tmuxName);
80
86
  } catch (err) {
81
- logger.warn?.(`[${label}] captureWide failed: ${err.message}`);
87
+ // rc.4: detect "can't find pane" / "no server" — tmux session died
88
+ // (claude exited, killed the bash that hosted it, tmux tore down the
89
+ // pane). Fast-fail with a distinct code instead of spinning for the
90
+ // full deadline. Pattern matches the actual tmux capture-pane errors:
91
+ // - "can't find pane: <name>" (session/pane gone after spawn)
92
+ // - "no server running" (entire tmux server gone)
93
+ const msg = err?.message || '';
94
+ if (/can't find (pane|session)|no server running|session not found/i.test(msg)) {
95
+ const goneErr = new Error(
96
+ `[${label}] tmux session disappeared for ${tmuxName} after ${Date.now() - startedAt}ms ` +
97
+ `(matched: ${matchedTriggers.length ? matchedTriggers.join(', ') : 'none'}). ` +
98
+ `claude likely exited; last pane content:\n` +
99
+ _formatPaneTail(lastPane),
100
+ );
101
+ goneErr.code = 'TMUX_SESSION_GONE';
102
+ goneErr.lastPane = lastPane;
103
+ goneErr.matchedTriggers = matchedTriggers;
104
+ throw goneErr;
105
+ }
106
+ logger.warn?.(`[${label}] captureWide failed: ${msg}`);
82
107
  await new Promise(r => setTimeout(r, settleMs));
83
108
  continue;
84
109
  }
110
+ lastPane = pane;
85
111
 
86
112
  // Walk triggers in declaration order — first match (and not yet seen) wins
87
113
  let matched = false;
@@ -111,12 +137,33 @@ async function runStartupGate({
111
137
 
112
138
  const err = new Error(
113
139
  `[${label}] startup gate did not resolve within ${deadlineMs}ms for ${tmuxName} ` +
114
- `(matched: ${matchedTriggers.length ? matchedTriggers.join(', ') : 'none'})`,
140
+ `(matched: ${matchedTriggers.length ? matchedTriggers.join(', ') : 'none'}). ` +
141
+ `Last pane content:\n` +
142
+ _formatPaneTail(lastPane),
115
143
  );
116
144
  err.code = timeoutCode;
145
+ err.lastPane = lastPane;
146
+ err.matchedTriggers = matchedTriggers;
117
147
  throw err;
118
148
  }
119
149
 
150
+ /**
151
+ * Render the last ~800 chars of pane content for inclusion in error messages.
152
+ * Truncate at line boundaries when possible so the diagnostic isn't visually
153
+ * mangled. Returns "(no pane content ever captured)" for null/undefined.
154
+ */
155
+ function _formatPaneTail(pane) {
156
+ if (!pane) return ' (no pane content ever captured — claude exited before first captureWide)';
157
+ const MAX = 800;
158
+ const text = String(pane);
159
+ if (text.length <= MAX) return text.split('\n').map(l => ' ' + l).join('\n');
160
+ // Take last MAX chars, then trim to a line boundary if one exists nearby
161
+ let tail = text.slice(-MAX);
162
+ const nl = tail.indexOf('\n');
163
+ if (nl > 0 && nl < 80) tail = tail.slice(nl + 1);
164
+ return ' …(truncated)…\n' + tail.split('\n').map(l => ' ' + l).join('\n');
165
+ }
166
+
120
167
  module.exports = {
121
168
  runStartupGate,
122
169
  DEFAULT_DEADLINE_MS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.11.0-rc.3",
3
+ "version": "0.11.0-rc.5",
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": {