Skip to content

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

Open
SergiioB wants to merge 1 commit into
AtomicBot-ai:mainfrom
SergiioB:feat/telegram-live-progress-indicator
Open

feat(telegram): live progress indicator for the remote-control channel#28
SergiioB wants to merge 1 commit into
AtomicBot-ai:mainfrom
SergiioB:feat/telegram-live-progress-indicator

Conversation

@SergiioB

Copy link
Copy Markdown

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.

Post an editable "Thinking…" bubble the moment an owner message lands and
update it live with the turn's activity (step count, tool name, step
summary) until the reply is ready, then remove it so the answer stands
alone.

The existing chatAction "typing" dots are kept but are too subtle to read
as an acknowledgement and vanish the instant a fast reply lands, leaving
the operator with no visible feedback that their message was received.

The indicator is pure channel infrastructure: every post/edit/delete is
best-effort and serialised through an internal promise chain, throttled to
~1.4 edits/s with identical-text suppression, and a fast turn that removes
the bubble before the initial send resolves still cleans up correctly.
Adds an optional deleteMessage to the TelegramApi surface.
@sosidudku1

Copy link
Copy Markdown
Collaborator

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.

@Ooooze

Ooooze commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

+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.

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.

3 participants