polygram 0.10.0-rc.42 → 0.10.0-rc.43

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.42",
4
+ "version": "0.10.0-rc.43",
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",
@@ -225,6 +225,32 @@ const DEFAULT_STOP_GRACE_MS = 2_000; // 2 s
225
225
  // block, but `_waitForReady` runs only at startup before any turn).
226
226
  const DEFAULT_READY_DEBUG_QUIET_MS = 1000;
227
227
 
228
+ // rc.43 (shumorobot Music topic, 2026-05-22 21:11): claude TUI on
229
+ // `--resume` of a session that has crossed an age/token threshold
230
+ // (~8h / ~120k tokens observed) renders an INTERACTIVE menu instead
231
+ // of the chat input:
232
+ //
233
+ // This session is 8h 38m old and 117.6k tokens.
234
+ // Resuming the full session will consume a substantial portion of
235
+ // your usage limits. We recommend resuming from a summary.
236
+ // ❯ 1. Resume from summary (recommended)
237
+ // 2. Resume full session as-is
238
+ // 3. Don't ask me again
239
+ // Enter to confirm · Esc to cancel
240
+ //
241
+ // `_waitForReady` doesn't recognise this menu — there's no
242
+ // `? for shortcuts` / `accept edits on` / `bypass permissions on`
243
+ // hint visible. It times out at `readyTimeoutMs` (120 s) and polygram
244
+ // fails the user's message with a generic "TUI did not signal ready"
245
+ // error. Detection in the pane and pressing Enter dismisses the menu
246
+ // (selects option 1 = resume from summary, the safe default).
247
+ //
248
+ // The detection regex matches a distinctive substring from the menu
249
+ // header that doesn't appear during normal turns or other startup
250
+ // banners — looking for the literal "Resume from summary" option
251
+ // combined with the "Resuming the full session" rationale.
252
+ const SESSION_AGE_PROMPT_RE = /Resuming the full session.*Resume from summary/s;
253
+
228
254
  // R7: sentinel returned by _awaitTurnComplete when its poll loop is
229
255
  // stopped by the caller's absolute-deadline abort (rather than by a
230
256
  // real READY quiescence or its own internal timeout). _runTurn maps
@@ -2591,6 +2617,10 @@ class TmuxProcess extends Process {
2591
2617
  // next polls, which is what resets the clock.
2592
2618
  let prevDebugSize = null;
2593
2619
  let lastGrowthAt = null;
2620
+ // rc.43: track whether we've already dismissed the session-age
2621
+ // prompt this wait, so we don't fire Enter every poll if claude
2622
+ // is slow to re-render after dismissing it.
2623
+ let sessionAgePromptDismissed = false;
2594
2624
  if (this.pollScheduler) this.pollScheduler.acquire();
2595
2625
  try {
2596
2626
  while (this._now() < deadline) {
@@ -2598,6 +2628,41 @@ class TmuxProcess extends Process {
2598
2628
  // pane. Polling 1000 lines each tick is wasteful — cap at 80
2599
2629
  // for a ~12× cheaper tmux subprocess.
2600
2630
  lastBuf = await this.runner.captureWide(this.tmuxName, { lines: 80 });
2631
+ // rc.43: if claude rendered the session-age "resume from
2632
+ // summary" prompt (only happens on `--resume` of an aged
2633
+ // session, see SESSION_AGE_PROMPT_RE comment), press Enter
2634
+ // once to confirm the default selection (option 1 — resume
2635
+ // from summary) and emit a `session-age-prompt-dismissed`
2636
+ // event for forensics. The TUI then proceeds to load the
2637
+ // summary and the ready hint appears normally; subsequent
2638
+ // polls take the standard B6/B8 path.
2639
+ if (!sessionAgePromptDismissed
2640
+ && SESSION_AGE_PROMPT_RE.test(lastBuf)) {
2641
+ this.logger.warn?.(
2642
+ `[${this.label}] claude TUI showed session-age resume prompt; `
2643
+ + `auto-dismissing with Enter (select option 1 — resume from summary)`,
2644
+ );
2645
+ this.emit('session-age-prompt-dismissed', {
2646
+ sessionId: this.claudeSessionId,
2647
+ backend: 'tmux',
2648
+ });
2649
+ try {
2650
+ await this.runner.sendControl(this.tmuxName, 'Enter');
2651
+ } catch (err) {
2652
+ this.logger.warn?.(
2653
+ `[${this.label}] sendControl(Enter) for session-age dismissal failed: `
2654
+ + `${err.message}`,
2655
+ );
2656
+ }
2657
+ sessionAgePromptDismissed = true;
2658
+ // Reset readiness clock + prev-pane so the menu's content
2659
+ // doesn't satisfy the byte-stability check while claude is
2660
+ // reloading from the summary.
2661
+ readySinceAt = null;
2662
+ prevBuf = null;
2663
+ await this._waitForNextTick();
2664
+ continue;
2665
+ }
2601
2666
  // Ready ⇔ the hint is on the pane AND the pane is identical to
2602
2667
  // the previous poll (the MCP-loading repaint storm has
2603
2668
  // stopped). The first poll has no previous buffer to compare,
@@ -3225,4 +3290,6 @@ class TmuxProcess extends Process {
3225
3290
  module.exports = {
3226
3291
  TmuxProcess,
3227
3292
  CLAUDE_CLI_PINNED_VERSION,
3293
+ // rc.43 — exported for unit-test coverage of the menu pattern.
3294
+ SESSION_AGE_PROMPT_RE,
3228
3295
  };
@@ -119,6 +119,10 @@ const CALLBACK_TO_EVENT = {
119
119
  // the event lets the soak count how often H4 actually fires its
120
120
  // rescue contract.
121
121
  onStopHookResolved: 'stop-hook-resolved',
122
+ // 0.10.0 rc.43: claude TUI's "This session is N old…" interactive
123
+ // menu auto-dismissed by `_waitForReady`. Surfacing the event so
124
+ // soak can count how often aged-session resumes hit this path.
125
+ onSessionAgePromptDismissed: 'session-age-prompt-dismissed',
122
126
  };
123
127
 
124
128
  class ProcessManager {
@@ -520,6 +520,23 @@ function createSdkCallbacks({
520
520
  }
521
521
  },
522
522
 
523
+ // 0.10.0 rc.43: claude TUI session-age resume prompt was
524
+ // auto-dismissed by `_waitForReady`. Counting these helps decide
525
+ // whether to push the "Don't ask me again" option globally vs
526
+ // keep the auto-dismiss as a safety net.
527
+ onSessionAgePromptDismissed: (sessionKey, payload /* , entry */) => {
528
+ try {
529
+ logEvent('session-age-prompt-dismissed', {
530
+ chat_id: getChatIdFromKey(sessionKey),
531
+ session_key: sessionKey,
532
+ backend: 'tmux',
533
+ claude_session_id: payload?.sessionId ?? null,
534
+ });
535
+ } catch (err) {
536
+ logger.error?.(`[${botName}] session-age-prompt-dismissed handler: ${err.message}`);
537
+ }
538
+ },
539
+
523
540
  onInjectFail: (sessionKey, payload /* , entry */) => {
524
541
  try {
525
542
  const msgId = payload?.msgId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.10.0-rc.42",
3
+ "version": "0.10.0-rc.43",
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": {