polygram 0.10.0-rc.2 → 0.10.0-rc.4

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.2",
4
+ "version": "0.10.0-rc.4",
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",
@@ -78,7 +78,14 @@ const TOOL_INVOCATION_RE = /⏺\s+([A-Za-z_]\w*)\s*\((.*?)\)\s*$/m;
78
78
 
79
79
  // ─── Defaults — overridable per construction for tests ───────────────
80
80
 
81
- const DEFAULT_READY_TIMEOUT_MS = 30_000;
81
+ // Cold-spawn budget for claude TUI to reach "? for shortcuts" / "accept
82
+ // edits on" state. 30s was enough in dev (interactive shell, warm
83
+ // keychain) but consistently timed out under launchd in production:
84
+ // MCP server starts each have a 30s connection timeout, and the
85
+ // keychain/Aqua context warm-up is slower outside an attached terminal.
86
+ // 120s is generous; the only cost is waiting longer when something is
87
+ // genuinely stuck (we kill + retry in that case anyway).
88
+ const DEFAULT_READY_TIMEOUT_MS = 120_000;
82
89
  const DEFAULT_TURN_TIMEOUT_MS = 5 * 60_000;
83
90
  const DEFAULT_POLL_MS = 250;
84
91
  const DEFAULT_QUIESCE_MS = 500; // require READY for this long before declaring done
@@ -624,22 +631,32 @@ class TmuxProcess extends Process {
624
631
 
625
632
  async _waitForReady() {
626
633
  const deadline = this._now() + this.readyTimeoutMs;
634
+ let lastBuf = '';
627
635
  if (this.pollScheduler) this.pollScheduler.acquire();
628
636
  try {
629
637
  while (this._now() < deadline) {
630
638
  // OPTIMIZATION: ready hint lives in the bottom ~5 lines of the
631
639
  // pane. Polling 1000 lines each tick is wasteful — cap at 80
632
640
  // for a ~12× cheaper tmux subprocess.
633
- const buf = await this.runner.captureWide(this.tmuxName, { lines: 80 });
634
- if (READY_HINTS_RE.test(buf)) return;
641
+ lastBuf = await this.runner.captureWide(this.tmuxName, { lines: 80 });
642
+ if (READY_HINTS_RE.test(lastBuf)) return;
635
643
  await this._waitForNextTick();
636
644
  }
637
645
  } finally {
638
646
  if (this.pollScheduler) this.pollScheduler.release();
639
647
  }
648
+ // On timeout, surface what the TUI was actually showing so the
649
+ // operator can diagnose whether it was hung, on a setup prompt,
650
+ // or just slow to render the ready hint. capture the last ~40
651
+ // lines for the error event payload + log.
652
+ const tail = lastBuf.split('\n').slice(-40).join('\n');
653
+ this.logger.warn?.(
654
+ `[${this.label}] TUI did not signal ready in ${this.readyTimeoutMs}ms; last pane tail:\n${tail}`,
655
+ );
640
656
  throw Object.assign(new Error('TmuxProcess: TUI did not signal ready'), {
641
657
  code: 'TMUX_READY_TIMEOUT',
642
658
  tmuxName: this.tmuxName,
659
+ paneTail: tail,
643
660
  });
644
661
  }
645
662
 
@@ -173,13 +173,16 @@ function createTmuxRunner({ logger = console, runFn = run } = {}) {
173
173
  stderr: err.stderr,
174
174
  });
175
175
  }
176
- // Set a wide pane to reduce capture-pane wrap artifacts.
177
- // Best-effort if the set-option fails, capture-pane -J fallback
178
- // in captureWide() handles the wrap case.
176
+ // Try to widen the detached pane so claude TUI has room to render
177
+ // long lines. `resize-window` is the supported way; older
178
+ // attempts used a non-existent `pane-width` option that always
179
+ // errored (tmux 3.x: pane-width is a format variable, not a
180
+ // settable option). capture-pane -J in captureWide() handles
181
+ // any remaining wrap artifacts.
179
182
  try {
180
- await runFn('tmux', ['set-option', '-t', name, '-w', 'pane-width', String(paneWidth)]);
183
+ await runFn('tmux', ['resize-window', '-t', name, '-x', String(paneWidth)]);
181
184
  } catch (err) {
182
- logger.warn?.(`[tmux-runner] set-option pane-width failed for ${name}: ${err.message}`);
185
+ logger.debug?.(`[tmux-runner] resize-window failed for ${name}: ${err.message} (capture-pane -J handles wrap)`);
183
186
  }
184
187
  return name;
185
188
  }
@@ -200,10 +203,19 @@ function createTmuxRunner({ logger = console, runFn = run } = {}) {
200
203
  * 2. \n → MULTILINE_SEPARATOR (F-spike-3)
201
204
  * 3. set-buffer + paste-buffer (atomic; bracketed-paste-aware
202
205
  * in modern claude TUI versions)
206
+ * 4. brief drain delay so a subsequent send-keys (e.g. Enter) is
207
+ * processed as a key event by the TUI, NOT consumed as part of
208
+ * the paste's bracketed-paste content.
203
209
  *
204
- * NO Enter is sent. Caller follows up with `sendControl(name, 'Enter')`
205
- * when they want to submit. (Splitting paste + Enter lets callers
206
- * verify the text landed via capture-pane before submitting.)
210
+ * INCIDENT (0.10.0-rc.2): without the drain delay, send-keys Enter
211
+ * fired immediately after paste-buffer was being swallowed by
212
+ * claude TUI's bracketed-paste handler — the paste sat in the input
213
+ * area unsubmitted. Manual `tmux send-keys ... Enter` unstuck it.
214
+ * 80ms is enough on macOS tmux 3.6a for the close-bracket ESC[201~
215
+ * to land before any subsequent key arrives.
216
+ *
217
+ * NO Enter is sent here. Caller follows up with
218
+ * `sendControl(name, 'Enter')` when they want to submit.
207
219
  */
208
220
  async function pasteText(name, text) {
209
221
  const sanitized = sanitize(text);
@@ -218,6 +230,8 @@ function createTmuxRunner({ logger = console, runFn = run } = {}) {
218
230
  await runFn('tmux', ['delete-buffer', '-b', bufName]).catch(() => {});
219
231
  throw err;
220
232
  }
233
+ // Drain delay — see incident note above.
234
+ await new Promise((r) => setTimeout(r, 80));
221
235
  return { sanitized, oneLine, stripped: text.length - sanitized.length };
222
236
  }
223
237
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.10.0-rc.2",
3
+ "version": "0.10.0-rc.4",
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": {