Skip to content

feat(messaging): mirror local-surface turns (Desktop/TUI/dashboard) back to the platform chat - #111631

Open
Maziyang2 wants to merge 4 commits into
NousResearch:mainfrom
Maziyang2:feat/telegram-mirror-local-turns
Open

Maziyang2 wants to merge 4 commits into
NousResearch:mainfrom
Maziyang2:feat/telegram-mirror-local-turns

Conversation

@Maziyang2

Copy link
Copy Markdown

Why this matters

Keep the record complete when you continue a messaging conversation from the Hermes Desktop app (or TUI / dashboard).

Hermes sessions are cross-surface: you can reopen your Telegram DM in the Desktop app and keep chatting. But turns served by a local surface exist only on that surface — the platform's own chat never sees them, so the history on your phone has a hole exactly where you kept talking (and the bot cannot replay your side — it can only post as itself).

This PR adds an opt-in mirror: every completed turn served locally for a gateway-owned session is forwarded to that session's own platform chat — your message as a quote block, then the assistant's reply:

📲

> the message you sent from the Desktop app
> (quoted line by line)

the assistant's reply
  • Off by default — display.platforms.<platform>.mirror_local_turns: true enables it per platform (Telegram shown; any platform whose plugin exposes a standalone sender works).
  • Exact target — delivered to the chat_id / thread_id stored on the session row: the same chat the gateway itself delivers to.
  • No gateway connection needed — uses the same standalone sender chain as hermes send / cron delivery; failures are logged and never affect the turn.
  • Quiet by default — sent with disable_notification (configurable), so a phone that already followed along on the desktop isn't pinged twice.
  • Never noisy — synthetic turns (compaction notices, auto-continue, background-process notes) and silent ([SILENT]) responses are skipped.

Companion PR — a two-part messaging upgrade

This is the second half of a coherent messaging/Telegram upgrade; both PRs are designed to land and be used together:

PR What it adds
1 #110611 single-message streaming for long replies + Bot API 10.3 extras (stop button, message effects, ephemeral notices) — each with an opt-in switch
2 this PR cross-surface continuity — local-surface turns are mirrored to the platform chat, so the record stays complete

They are split into two PRs only for compatibility / independent reviewability — neither depends on the other's code, and they can merge in any order. The long-text handling here deliberately follows the same parameter semantics introduced in #110611 (streaming_single_message_4096_split):

  • mirror_local_turns_4096_split: false (default) — keep one message where possible; past the platform cap the remainder paginates (page markers kept)
  • mirror_local_turns_4096_split: true — split into standalone ≤4096 messages (markers stripped)

so both features behave consistently when enabled together.

What changed

  • tui_gateway/turn_mirror.py (new) — decision point, message composition, detached delivery (daemon thread; never blocks or breaks the turn it mirrors).
  • tui_gateway/prompt_turn.py — dispatch after message.complete (no-op unless enabled). One hook covers Desktop + TUI + dashboard, which share this backend.
  • gateway/display_config.py — mirror_local_turns, mirror_local_turns_silent, mirror_local_turns_4096_split, mirror_local_turns_label (+ normalisers).
  • tools/send_message_senders.py — silent + chunk_indicators kwargs on the standalone Telegram sender; _registry_standalone_send forwards them only to senders that declare them (other platforms keep their existing call shape).
  • plugins/platforms/telegram/adapter.py — _standalone_send pass-through.
  • Docs (website/docs/user-guide/messaging/index.md), cli-config.yaml.example, tests.

How to test

bash scripts/run_tests.sh tests/tui_gateway/test_turn_mirror.py tests/gateway/test_display_config.py \
  tests/tools/test_send_message_tool.py tests/tui_gateway/test_auto_continue.py
  • New suite tests/tui_gateway/test_turn_mirror.py — 24 passed (composition, decision table, target resolution, delivery options, contained failures).
  • Scoped suites across display config / senders / tui_gateway server+contracts / caption / proxy / relay — 990 passed, 0 failed, 3 skipped.
  • Live check: set display.platforms.telegram.mirror_local_turns: true, continue a Telegram session from the Desktop app — each completed turn arrives in the Telegram chat as quote + reply.

Tested on macOS (Darwin 27), Python 3.11.

Type of Change

  • ✨ New feature (non-breaking; opt-in, default off)

Checklist

Code

Documentation & Housekeeping

  • Docs updated (messaging guide + cli-config.yaml.example)
  • Cross-platform impact considered — generic by design; Telegram path exercised; other platforms degrade gracefully (silent/chunk_indicators forwarded only when the sender declares them)
  • Additive, default off — no behavior change without config

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/plugins Plugin system and bundled plugins platform/telegram Telegram bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Sep 15, 2026
@Maziyang2
Maziyang2 force-pushed the feat/telegram-mirror-local-turns branch from b632378 to 737c3e7 Compare September 19, 2026 17:28
@Maziyang2

Copy link
Copy Markdown
Author

Follow-up commit 80d0c24b48 adds reliable delivery for the opt-in local-turn mirror path.

Why this matters

A Desktop/TUI/dashboard turn should not disappear from the platform chat because the standalone send raced a process exit or gateway restart. This keeps the mirror asynchronous while making the delivery obligation durable and recoverable.

What changed

  • Records a pending obligation before spawning the mirror sender.
  • Uses the turn request id for atomic deduplication; an already-recorded turn is not sent again.
  • Persists the transport-owning profile and binds both ledger access and standalone credentials to that profile.
  • Preserves adapter send metadata across recovery and records the final platform message id.
  • Reuses the existing bounded retry/recovery sweeps; pending/attempting/failed rows recover after restart with the existing duplicate-warning markers.
  • Leaves the existing opt-in flag and the feat(telegram): single-message streaming + Bot API 10.3 extras (stop button, effects, ephemerals), all opt-in (#110564) #110611 Telegram streaming work unchanged.

Verification

  • 118 focused gateway/ledger/mirror tests passed, plus the real two-profile state-store scope test.
  • ruff, compileall, git diff --check, and compat-pointer checks passed.
  • No live Telegram send/edit/stop test was run.

This is intentionally a focused follow-up commit on #111631 rather than adding more scope to #110611.

@Maziyang2
Maziyang2 force-pushed the feat/telegram-mirror-local-turns branch 2 times, most recently from fd41b18 to 74fae9b Compare September 23, 2026 19:35
…heir chat

When a gateway-owned session (state.db source = a messaging platform) is continued from a local surface (Desktop chat, TUI, dashboard), only that surface sees the exchange. With display.platforms.<platform>.mirror_local_turns enabled, every completed turn is forwarded to the session's own chat - the user's message as a quote block, then the reply - through the same standalone sender chain hermes send / cron delivery use (no live gateway needed; silent by default).

- tui_gateway/turn_mirror.py (new): decision + composition + detached delivery; fail-closed target lookup from the session row (chat_id/thread_id); synthetic turns and silent replies never mirrored
- tui_gateway/prompt_turn.py: dispatch after message.complete (no-op unless enabled)
- gateway/display_config.py: mirror_local_turns, _silent, _4096_split, _label
- tools/send_message_senders.py + telegram plugin: silent/chunk_indicators kwargs on the standalone sender
- docs, cli-config example, tests
@Maziyang2
Maziyang2 force-pushed the feat/telegram-mirror-local-turns branch from 74fae9b to 2338ea9 Compare October 14, 2026 06:57

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have platform/telegram Telegram bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants