feat(telegram): queue messages sent mid-run instead of rejecting them - #238
feat(telegram): queue messages sent mid-run instead of rejecting them#238tamircher wants to merge 2 commits into
Conversation
Each Telegram topic already gets its own session and the runner keeps independent per-thread serial queues (threadQueues), but the handler gated every incoming message on the global isMainBusy() — any in-flight run anywhere blocked all topics. Add per-thread busy tracking in the runner (isThreadBusy/isGlobalBusy) and check only the target topic's queue (or the global session for non-topic chats), so each topic is a fully independent session running in parallel. A second message to the same topic still gets the polite busy reply.
A message arriving while the same topic (or the global session) is mid-run was rejected with a busy notice, forcing the user to resend. The runner already serializes runs per thread via enqueue(), so the message can simply be submitted and execute next, in order, with full session context via --resume — like Claude Code's message queue. While queued, the bot reacts 👀 to the incoming message as a lightweight received-and-waiting ack. The typing indicator already runs while queued, and the stream preview message is only created on first output chunk, so nothing else changes visibly until the queued run starts.
TerrysPOV
left a comment
There was a problem hiding this comment.
Code review
Nice swap from reject to queue — session context is correctly preserved through enqueue → execClaude → --resume (the 0f8a351 rationale for the rejection was the auto-fork path, not queueing, so this isn't a regression).
Two correctness concerns worth flagging before merge, plus one note:
-
Stale clock prefix on queued messages.
runUserMessagebuilds the[YYYY-MM-DD HH:MM:SS UTC±X]prefix synchronously at call time, then enqueues the closure. A message that sits behind a 5-minute run is handed to Claude with a 5-minute-stale "now". Time-aware prompts ("what time is it?", "schedule in 30 min", "is it past my bedtime?") will silently get wrong answers. Pre-PR the reject-and-retry pattern always rebuilt a fresh prefix on the user's resend.Lines 1697 to 1700 in 37c95e0
Fix: move
prefixUserMessageWithClock(prompt)inside the enqueued closure so it stamps at execution time instead of arrival time. -
/killno longer means "stop everything".killActive()only SIGTERMs entries inmainActiveProcs, which are the currently-running subprocesses. Queued messages aren't in there yet — they're closures chained onthreadQueues. So/killafter spamming a topic kills the in-flight run, then the queue happily fires each queued message one-by-one, spinning up a new subprocess for each. User intent is silently broken. This is distinct from the cross-topic kill issue on #237.Lines 283 to 295 in 37c95e0
Fix: when
/killfires, also drain or replace the matchingthreadQueuesentry withPromise.resolve()so pending closures are short-circuited.
Note (not blocking): the per-thread queue has no length cap. For a personal bot gated by isAllowed this is fine; worth a follow-up if you ever broaden access.
Also: the runner.ts comment block introduced in #237 still says "while still rejecting a second message to the SAME thread" — stale after this PR:
Lines 299 to 308 in 37c95e0
Once #237 lands and you rebase, please run bun run bump:plugin-version + bun run bump:marketplace-version (will need the next-after-237's bump).
|
Closing in favour of #250, which implements the same fix — removing the busy-rejection so mid-run Telegram messages queue instead of being dropped — as a simpler standalone change that doesn't depend on the #237 stack. Consolidating the two competing PRs onto one. Thanks for this, @tamircher — the approach was right, and #250 carries it forward. The per-thread parallel-topics work in #237 is a separate feature and stays open on its own track. The same open items from the earlier review still apply and have been raised on #250 (queue backpressure, |
Stacked on #237 — review the last commit only; I'll rebase once #237 lands.
A message arriving while the same topic (or the global session) is mid-run is rejected with a busy notice, forcing the user to resend. Since
run()already serializes per thread viaenqueue(), the message can simply be submitted — it executes next, in order, with full session context via--resume, like Claude Code's message queue.While queued, the bot reacts 👀 to the incoming message as a lightweight received-and-waiting ack. The typing indicator already runs while queued, and the stream preview message is only created on the first output chunk, so nothing else changes visibly until the queued run starts.
Changes
src/commands/telegram.ts: drop the busy rejection; submit to the per-thread queue and 👀-react while waiting🤖 Generated with Claude Code