feat(pulse/telegram): v2 — per-thread sessions, live tool reactions, empty-response recovery#1281
Open
janrenz wants to merge 1 commit into
Conversation
…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.
This was referenced May 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 filelib/threadStore.ts(thread persistence layer).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:
botMessageId → threadIdreverse map so reply-to-continue works even after several turns.sessionId+ 10-turn history persisted via atomic tmp+rename writes.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 cheatsheet2. 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📬 Reading email…📅 Checking calendar…🔍 Searching…⚡ Running command…✏️ Editing…🤖 Delegating…🌐 Fetching…editMessageTextwith a▌cursor, throttled atedit_interval_msto stay under Telegram rate limits3. Empty-response recovery
The v1 bot would silently return a generic 'sorry' when the SDK produced no output. v2 distinguishes failure modes:
⏱️ 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=...).botMessage → threadmap 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,/threads,/status,/helpall functioning ✓Out of scope
imessage.ts— already covered by #1277.setMessageReaction)./effort e2etc. constrain a thread.Security / auth (unchanged)
allowed_usersallow-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_KEYpreserved (forces subscription billing — root cause of the April 2026 API-billing leak the comment documents).