polygram 0.12.0-rc.24 → 0.12.0-rc.26

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.
@@ -24,6 +24,13 @@
24
24
 
25
25
  const CONCURRENT_WARN_THRESHOLD_DEFAULT = 20;
26
26
 
27
+ // Startup auto-retry (option a, 2026-06-04): a short breath before silently
28
+ // re-dispatching a message whose first attempt died in the dev-channels startup
29
+ // gate (TMUX_SESSION_GONE). Long enough that a host under momentary load isn't
30
+ // hammered with a back-to-back respawn, short enough that a transient flake
31
+ // still recovers fast enough to feel instant to the user.
32
+ const STARTUP_RETRY_DELAY_MS = 1500;
33
+
27
34
  function createDispatcher({
28
35
  config,
29
36
  db,
@@ -48,6 +55,9 @@ function createDispatcher({
48
55
  // the historic 4096 for back-compat in synthetic test runs that pass
49
56
  // pre-formatted text.
50
57
  chunkBudget = 4096,
58
+ // Delay before a silent startup auto-retry re-dispatches (TMUX_SESSION_GONE).
59
+ // Injected so tests can drive it to 0; production uses STARTUP_RETRY_DELAY_MS.
60
+ startupRetryDelayMs = STARTUP_RETRY_DELAY_MS,
51
61
  // State accessors (need late binding because polygram.js mutates):
52
62
  getIsShuttingDown, // () → boolean
53
63
  logger = console,
@@ -217,6 +227,35 @@ function createDispatcher({
217
227
  // - shutting down ("Process killed" isn't a real error),
218
228
  // - user just /stop'd (already saw their abort ack).
219
229
  if (!wasAborted && !isReplay && !isShuttingDown) {
230
+ // Startup auto-retry (option a, 2026-06-04). TMUX_SESSION_GONE = claude
231
+ // exited INSIDE the startup gate, before the dev-channels channel went
232
+ // live — so the user's message was NEVER delivered to claude. That makes
233
+ // a re-send idempotent BY CONSTRUCTION (unlike a mid-turn drop, where
234
+ // claude might still be slowly processing). The session_id was just
235
+ // poison-cleared above, so re-dispatching the SAME message spawns a FRESH
236
+ // session and delivers it. Silent: a transient startup flake (recurs
237
+ // ~once/9h on the channels backend) never reaches the user — instead of
238
+ // the "🔄 reset it, resend" papercut, polygram just retries. One-shot
239
+ // (_startupRetried) so a host that genuinely can't start claude surfaces
240
+ // the friendly reset reply (below) after EXACTLY one retry, never a loop.
241
+ // Scoped to TMUX_SESSION_GONE only: CHANNELS_DIALOG_TIMEOUT is a real
242
+ // blocking dialog (usage-limit / permission) a retry would just re-hit,
243
+ // so it keeps its "please resend" copy.
244
+ if (err.code === 'TMUX_SESSION_GONE' && !msg._startupRetried) {
245
+ logEvent('startup-auto-retry', {
246
+ chat_id: chatId, session_key: sessionKey, msg_id: msg?.message_id,
247
+ });
248
+ // Re-dispatch a COPY carrying the one-shot marker — never mutate the
249
+ // caller's msg (the boot-replay path shares/re-reads it). unref the
250
+ // best-effort timer so a pending retry can't pin the daemon alive
251
+ // (the Telegram long-poll already keeps the loop running).
252
+ const retryMsg = { ...msg, _startupRetried: true };
253
+ setTimeout(
254
+ () => dispatchHandleMessage(sessionKey, chatId, retryMsg, bot),
255
+ startupRetryDelayMs,
256
+ ).unref?.();
257
+ return;
258
+ }
220
259
  // rc.54: auto-resume on 300s no-activity timeout. The
221
260
  // resume turn itself runs through sendToProcess directly
222
261
  // (not handleMessage), so its errors don't re-enter this
@@ -309,4 +348,5 @@ function createDispatcher({
309
348
  module.exports = {
310
349
  createDispatcher,
311
350
  CONCURRENT_WARN_THRESHOLD_DEFAULT,
351
+ STARTUP_RETRY_DELAY_MS,
312
352
  };
@@ -115,13 +115,20 @@ const STREAMING_HINT_RE = /esc to interrupt/i;
115
115
 
116
116
  // 0.12.0 background-work lifecycle: claude's TUI mode line shows a live
117
117
  // background-shell COUNT while a `run_in_background:true` Bash outlives its turn,
118
- // e.g. `⏵⏵ auto mode on · 1 shell · ← for agents · ↓ to manage`. Confirmed on
119
- // claude 2.1.158 (P0 spike — docs/0.12.0-background-work-lifecycle-plan.md): the
120
- // count is always-present in the viewport mode line while shells run and clears
121
- // IN-PLACE within ~3s when they exit (no stale scrollback). Anchored to the
122
- // `auto mode on` line and matched only against the captured TAIL so a scrolled-off
123
- // history line never trips it. R1: re-validate on each pinned-claude bump.
124
- const BACKGROUND_SHELL_RE = /auto mode on[^\n]*·\s*(\d+)\s+shells?\b/i;
118
+ // e.g. `⏵⏵ bypass permissions on · 1 shell · ← for agents · ↓ to manage`.
119
+ // Confirmed on claude 2.1.158 (P0 spike — docs/0.12.0-background-work-lifecycle-
120
+ // plan.md): the count is always-present in the viewport mode line while shells run
121
+ // and clears IN-PLACE within ~3s when they exit (no stale scrollback).
122
+ //
123
+ // MODE-INDEPENDENT (prod regression fix, 2026-06-04): the original regex anchored
124
+ // on "auto mode on", but EVERY shumorobot session runs "⏵⏵ bypass permissions on"
125
+ // — the spike happened to be captured in auto mode. So the detector never matched
126
+ // in prod and bg-work-status fired zero times. Anchor instead on the `⏵⏵` mode-
127
+ // line glyph (present in auto / bypass / accept-edits modes alike); only the mode
128
+ // label between it and `· N shell` varies. Still matched only against the captured
129
+ // TAIL so a scrolled-off history line never trips it. R1: re-validate on each
130
+ // pinned-claude bump (glyph + `N shell` wording).
131
+ const BACKGROUND_SHELL_RE = /⏵⏵[^\n]*·\s*(\d+)\s+shells?\b/i;
125
132
  // How long a detached background shell may run AFTER its turn resolved (claude
126
133
  // idle) before the stall-watchdog fires one read-only self-check. Override via
127
134
  // the constructor (tests use a small value).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.12.0-rc.24",
3
+ "version": "0.12.0-rc.26",
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": {