Menu

#28 feat(telegram): live progress indicator for the remote-control channel

closed
nobody
None
2026-08-04
2026-07-23
Anonymous
No

Originally created by: SergiioB

What

The Telegram remote-control channel now posts an editable "Thinking…" bubble the moment an owner message lands, updates it live with the turn's activity, and removes it when the reply is ready.

Why

Today the only feedback is the native chatAction: "typing" dots. They are easy to miss and disappear the instant a fast reply lands, so a short answer gives no visible sign the message was received. This adds a clear, persistent acknowledgement that also surfaces what the agent is doing on longer turns.

Behavior

  • On message receipt: posts 🤔 Thinking….
  • During the turn: edits the bubble with live activity — ⚙️ Working… (step N), 🔧 <tool> (from tool_call_parsed), ✅ <step summary> (from step_finished), 🔁 Repeating <tool> (×N) (from loop_detected).
  • On settle: deletes the bubble and posts the reply (or the existing cancel/failure infra message) so the answer stands alone.
  • The existing typing-dot keepalive is unchanged and still runs.

Implementation notes

  • New TelegramProgressIndicator in inbound-handler.ts, wired into dispatchToRuntime through the existing eventHook.
  • Adds an optional deleteMessage to the TelegramApi surface (grammy's bot.api already provides it).
  • Pure channel infrastructure: every post/edit/delete is best-effort and serialised through one promise chain. Edits are throttled (~700 ms) with identical-text suppression to stay well under Telegram's per-chat edit rate limits. A fast turn that calls remove() before the initial sendMessage resolves still cleans up the just-posted message.
  • No config change; the indicator is always on, matching the always-on typing keepalive. Happy to gate it behind a telegram.* flag if you prefer.

Testing

  • All 129 existing Telegram channel tests pass (vitest run src/channels/telegram/), including the 23 inbound-handler tests.
  • Running in production on a headless Linux (ARM64) serve deployment against a remote llama-server.

Happy to add a dedicated unit test for the indicator lifecycle and/or put it behind a config flag if that helps merge.

Related

Tickets: #56

Discussion

  • Anonymous

    Anonymous - 2026-07-28

    Originally posted by: sosidudku1

    Thanks @SergiioB, and sorry it took four days to get to you. The chain-serialised start/update/remove is genuinely careful work, I traced the fast-turn race you describe in the comments and it holds up.

    One thing I do not think you had in view. progressLabel echoes step_finished.summary into the bubble, and for a single-tool step that summary is not a label, it is the tail of the tool's raw output. compressToolResult builds it from the last non-blank lines of whatever the tool printed, so an os.fs.read on a config file, a grep hit, or a stack trace ends up in the Telegram message. Your 80 char clip bounds the length but not the content, and 80 chars is plenty for a file path or the start of a key. Telegram history is forwardable and outlives the session, so I would rather the bubble stayed on things you construct yourself, 🔧 <tool> and the step counter, and dropped the summary echo.

    Second, lighter: the TelegramApi fake in inbound-handler.test.ts only implements sendMessage, so every editMessageText?.() and deleteMessage?.() in your class silently no-ops under test. The suite stays green whether the indicator works or not. If you add the lifecycle test you offered, adding those two to the fake would make it meaningful.

     
  • Anonymous

    Anonymous - 2026-07-28

    Originally posted by: Ooooze

    +1 on the summary-echo concern @sosidudku1 raised — traced it myself: progressLabel's step_finished branch forwards event.summary straight from compressToolResult's tail-of-raw-output, and the 80-char clip bounds length, not content. Telegram history is forwardable and outlives the session, so agreed this should drop to just 🔧 <tool> + the step counter, no summary text.

    Two more before this can land:

    1. File size. inbound-handler.ts was already at 347 lines pre-PR (over the repo's 300-line guideline) and this pushes it to 510. Could you pull TelegramProgressIndicator + progressLabel + truncateForProgress into their own file, e.g. telegram-progress-indicator.ts?
    2. Tests are silently passing regardless of correctness. The TelegramApi fake in inbound-handler.test.ts only implements sendMessageeditMessageText/deleteMessage are optional and no-op when absent, so every call the indicator makes to them is untested. Since you offered to add the lifecycle test, adding both methods to the fake would make it actually exercise start/update/remove (including the fast-turn race you handled carefully in the code).

    Happy to take another look once those land — the throttling/chain-serialization design itself is solid.

     
  • Anonymous

    Anonymous - 2026-08-03

    Originally posted by: sosidudku1

    Following up with three additions from a deeper pass, on top of the summary-echo drop, the file split and the real fake-API tests already requested above:

    1. Send the bubble with disable_notification: true. Right now every owner message produces two push notifications, one for the Thinking bubble and one for the reply. On fast turns the bubble also flashes and disappears before it can be read.
    2. Raise the edit throttle to at least 1000 ms and respect retry_after when Telegram returns 429. The practical per-chat limit is about one message per second and 700 ms sits above it on chatty turns. The risk is not the bubble itself: a flood-wait earned by edits applies to the whole chat and can delay or drop the final reply, and edits currently swallow the 429 and keep going.
    3. Yes to the config flag you offered: telegram.progressIndicator, default true, plumbed the same way parseMode was. Always-on needs a kill switch in production.

    Non-blocking: the throttle drops the newest label instead of deferring it, so the last meaningful state (for example the tool name) can be lost until the next event. Fine as a follow-up.

    The chain serialization and best-effort isolation still hold up on the deeper pass. Once these plus the earlier items land, this is good to go from my side.

     
  • Anonymous

    Anonymous - 2026-08-04

    Ticket changed by: Ooooze

    • status: open --> closed
     

Log in to post a comment.