Skip to content

feat(pulse/telegram): v2 — per-thread sessions, live tool reactions, empty-response recovery#1281

Open
janrenz wants to merge 1 commit into
danielmiessler:mainfrom
janrenz:feat/telegram-v2-threading-reactions-empty-handling
Open

feat(pulse/telegram): v2 — per-thread sessions, live tool reactions, empty-response recovery#1281
janrenz wants to merge 1 commit into
danielmiessler:mainfrom
janrenz:feat/telegram-v2-threading-reactions-empty-handling

Conversation

@janrenz
Copy link
Copy Markdown

@janrenz janrenz commented May 17, 2026

What

Three coupled improvements that together replace the v1 single-session Telegram bot with a thread-aware, observable, self-recovering one. Touches modules/telegram.ts (rewrite of the SDK runner + handler) and adds one new file lib/threadStore.ts (thread persistence layer).

Releases/v5.0.0/.claude/PAI/PULSE/lib/threadStore.ts    | 205 ++++ (new)
Releases/v5.0.0/.claude/PAI/PULSE/modules/telegram.ts   | 513 / -143
2 files changed, 575 insertions(+), 143 deletions(-)

The three improvements

1. Threading

The v1 bot kept one SDK session per chat — every new question contaminated the previous topic's context. v2 keys sessions to threads:

  • Each top-level user message → fresh thread.
  • A reply to one of the bot's messages → resumes THAT thread's SDK session.
  • botMessageId → threadId reverse map so reply-to-continue works even after several turns.
  • Per-thread sessionId + 10-turn history persisted via atomic tmp+rename writes.
  • Auto-prune + size caps to bound the on-disk store.

New commands:

  • /new — clear all threads, start fresh
  • /threads — list active threads with topic, turn count, age
  • /status — uptime, thread count, msg in/out, processing flag, current max-turns / timeout
  • /help — usage cheatsheet

2. Live tool reactions

The bot now reacts in real time to what Claude is doing inside the SDK loop. Same Telegram message, edited in place:

  • sendChatAction('typing') while processing
  • Tool-start indicators edited into the bot message:
    • 📬 Reading email…
    • 📅 Checking calendar…
    • 🔍 Searching…
    • ⚡ Running command…
    • ✏️ Editing…
    • 🤖 Delegating…
    • 🌐 Fetching…
  • Text deltas streamed via editMessageText with a cursor, throttled at edit_interval_ms to stay under Telegram rate limits

Note on the word reactions: this is reactions to Claude's progress on the bot's own message, not the Telegram message-reactions API (setMessageReaction). That's deliberately scoped out.

3. Empty-response recovery

The v1 bot would silently return a generic 'sorry' when the SDK produced no output. v2 distinguishes failure modes:

  • Empty response → retry once with a fresh session (no resume) and a prefix hint asking for a more direct approach.
  • Second failure → specific diagnosis returned to the user:
    • ⏱️ Timeout nach 120s. Bitte den Task kleiner schneiden oder mit /new neu starten.
    • 🔁 Hat zu viele Schritte gebraucht (Max-Turns erreicht). Bitte konkretere Frage stellen.
    • ⚠️ Konnte keine Antwort erzeugen (subtype=...).
  • Diagnostics are never written into thread history — they don't pollute resume context. The botMessage → thread map IS updated so the user can still reply-to-continue.

Why these go together

Threading without recovery means a stuck tool-loop kills a topic permanently (the diagnostic ends up in history, then resume re-triggers the loop). Recovery without threading means recovery contaminates the next topic. Reactions are the observability that makes both threading and recovery legible to the user — without them the bot is a black box that 'either works or doesn't'. One coherent v2.

Build & verify

No build step — TypeScript loaded by Pulse via Bun at module init.

Verified locally on 2026-05-18 against real allowed_users:

  • New thread on top-level msg ✓
  • Reply-to-resume confirmed across multiple bot replies ✓
  • /new, /threads, /status, /help all functioning ✓
  • Empty-retry observed recovering a stuck tool-loop session ✓
  • No contamination across threads ✓

Out of scope

  • imessage.ts — already covered by #1277.
  • Telegram message-reactions API (setMessageReaction).
  • Per-thread effort/tier control — currently every thread runs at the SDK's max-turns default; future PR could let /effort e2 etc. constrain a thread.

Security / auth (unchanged)

  • allowed_users allow-list enforced as middleware before any command or message reaches the SDK.
  • sanitize() + analyzeForInjection() pipeline preserved on every inbound message.
  • delete process.env.ANTHROPIC_API_KEY preserved (forces subscription billing — root cause of the April 2026 API-billing leak the comment documents).

…empty-response recovery

Three coupled improvements that together replace the v1 single-session
Telegram bot with a thread-aware, observable, self-recovering one.

1) Threading (new lib/threadStore.ts + telegram.ts changes)
   - Each top-level user message starts a fresh thread.
   - A reply to a bot message resumes THAT thread's SDK session.
   - Per-thread sessionId + 10-turn history persisted to disk via atomic
     tmp+rename writes. botMessageId → threadId reverse map for reply-resume.
   - New commands: /new (clear all threads), /threads (list active),
     /status (uptime + counts + processing flag), /help.
   - Auto-pruning + size caps to bound the on-disk store.

2) Live status reactions (telegram.ts streaming)
   - Bot reacts in real time to what Claude is doing:
       * 'typing' chat action while processing
       * Tool-start indicators ('📬 Reading email…', '📅 Checking calendar…',
         '🔍 Searching…', '⚡ Running command…', etc.) edited into the same
         bot message while the SDK runs
       * Text deltas streamed via editMessageText with cursor (' ▌')
         throttled at edit_interval_ms to stay under Telegram rate limits
   - Friendly tool-name mapping centralised in friendlyToolStatus().

3) Empty-response recovery (telegram.ts)
   - Empty SDK responses retry ONCE with a fresh session (no resume) and
     a 'try a more direct approach' prompt prefix.
   - Second failure returns a SPECIFIC diagnosis ('⏱️ Timeout', '🔁 Max-turns',
     '⚠️ subtype=...') instead of a generic 'sorry'.
   - Diagnostics are NEVER written into thread history — they don't pollute
     resume context. The bot-message → thread map IS updated so the user
     can still reply-to-continue.

Out of scope (intentionally not in this PR):
- Telegram message-reactions API (setMessageReaction) — 'reactions' here
  refers to live status updates on the bot's own message, not the Telegram
  Reactions feature.
- imessage.ts changes — already in PR danielmiessler#1277.

Verified locally: bot running under Pulse against real allowed_users with
the v2 threading model on 2026-05-18; new thread on top-level msg,
reply-resume confirmed, /new + /threads + /status + /help all working,
empty-retry observed recovering a stuck tool-loop session.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant