Skip to content

feat(telegram): bidirectional reply threading - #200

Open
bkuri wants to merge 10 commits into
owainlewis:mainfrom
bkuri:feat/telegram-reply-threading
Open

bkuri wants to merge 10 commits into
owainlewis:mainfrom
bkuri:feat/telegram-reply-threading

Conversation

@bkuri

@bkuri bkuri commented Sep 17, 2026

Copy link
Copy Markdown

What

Bidirectional reply threading for Telegram.

Outbound: bot replies anchor to the triggering message via reply_parameters: {message_id, allow_sending_without_reply: true}, so replies still deliver if the trigger message was deleted. Multi-chunk replies all anchor. Scheduled/proactive sends are never anchored. Other channels unaffected. A backend reply may also start with a > quoted portion line — the gateway lifts it into reply_parameters.quote so the reply header highlights exactly the part being answered (one quote per message: API limit). The convention ships in the managed push skill (version bumped to 4 so push init refreshes it).

Inbound: when the user replies to a specific message, the backend prompt gets a flattened one-line excerpt of it (200-char cap) as a blockquote — preferring the user's text selection (quote) over the full message when they selected a portion. Approval matching and /stop still see the raw user text, so replying /stop to an old message still stops the run.

Resume-after-restart deliveries send unanchored rather than wrongly anchored.

Test plan

  • cargo test: 486 passed, 0 failed.
  • Manual: private chat + topics-enabled group; anchoring; selected-portion replies; backend-chosen quotes; /stop on reply.

homelab and others added 6 commits September 17, 2026 14:56
…eduled sends

The reply anchor rode in a shared Arc<Mutex<Option<i64>>> slot on Ctx:
concurrent worker turns could overwrite each other's anchor mid-delivery,
and scheduled/proactive sends inherited whatever anchor the last
interactive job left behind.

Thread the anchor as a parameter from the job through the delivery chain
instead. Scheduled sends pass None and are never anchored; approval
confirmations anchor to the message they answer; schedule questions,
primary delivery, and other system replies are unanchored.

Adds a payload test asserting reply_parameters is present only when
anchored, with allow_sending_without_reply set.
Telegram updates carry reply_to_message when the user replies to a
specific message; the gateway dropped it at parse time, so the backend
saw only the bare reply text with no linkage.

Capture the replied-to message as a flattened one-line excerpt (200 char
cap) on RawMessage.reply_context and prefix it to the backend prompt as
a blockquote. The quote is context only: approval answer matching and
the /stop check still see the raw user text, so replying "/stop" to an
old message still stops the run.

iMessage and Slack set reply_context: None.
When the user selects a specific portion of a message and replies,
Telegram sends the selection in message.quote. Use it for the backend
excerpt when present; fall back to the full replied-to message text.
@bkuri bkuri changed the title feat(telegram): anchor replies to the trigger message feat(telegram): bidirectional reply threading Sep 18, 2026
Bernardo Kuri added 2 commits September 17, 2026 19:08
A backend reply may start with a '> quoted portion' line; the gateway
lifts it into reply_parameters.quote (1024-char cap, API limit), strips
it from the displayed text, and it renders in the Telegram reply header
when the message is anchored. Quote-only replies are left untouched so
they cannot become empty messages. System and scheduled sends never
quote.
…skill

The '> quoted portion' convention ships in the managed skill so every
assistant learns it when push init refreshes the skill (managed version
bumped to 4 to trigger the refresh on upgrade).
@bkuri
bkuri marked this pull request as ready for review September 18, 2026 04:11
@greptile-apps

greptile-apps Bot commented Sep 18, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the previous reply-quote findings are resolved and no new actionable failures were found.

Summary

This PR adds bidirectional Telegram reply threading while preserving channel-neutral delivery and recovery behavior.

  • Anchors ordinary Telegram responses to their triggering messages across every outbound chunk.
  • Adds replied-message excerpts to inbound assistant context without changing raw /stop or approval matching.
  • Converts backend-selected leading quote lines into Telegram reply-header quotes while preserving those lines on unsupported channels.
  • Keeps scheduled, proactive, and restart-recovered deliveries unanchored.
  • Updates the managed Push skill with the reply-quote convention and increments its version.
Diagram
sequenceDiagram
    participant U as Telegram user
    participant T as Telegram adapter
    participant G as Gateway
    participant A as Agent backend
    U->>T: Message or reply
    T->>G: Raw text, reply excerpt, message ID
    G->>A: Prompt with optional quoted context
    A-->>G: Reply with optional leading quote line
    G->>G: Lift quote and persist displayed text
    G->>T: Chunks with reply anchor and quote
    T-->>U: Anchored Telegram replies
Loading

Reviews (3) · Last reviewed commit: "fix(gateway): limit reply-quote lifting ..."

Comment thread src/gateway/worker.rs Outdated
Comment thread src/telegram.rs
…ss rich fallback

Greptile review follow-ups:

- The backend run completion path recorded out.reply and delivered with
  no lifted quote, so the '> quoted portion' convention never fired for
  the one reply type that carries it. Lift before recording and thread
  the quote through deliver_stored.
- The rich-to-plain fallback in send_rich_reply dropped the reply anchor
  and quote; pass them through to send_plain_reply.
- Drop the now-unused send_rich/send_plain wrappers.
- FakeRunner gains an optional reply override; new gateway test asserts
  a leading quote line is stripped from the delivered reply.
Comment thread src/gateway/worker.rs Outdated
Non-Telegram channels never render the extracted quote, so lifting a
leading '> quoted' line from a backend reply there silently dropped it
from the user's message. Gate the lift on the channel; Slack and
iMessage keep the blockquote line as ordinary formatting.
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