Deliver final Harness answers to QQ as msg_type=2 markdown so bold,
headings, code blocks, lists, and tables render natively in QQ.
- add markdown-reply.mjs: chunk long answers at code-fence/table
boundaries (4500-char limit) and send each chunk with a unique
msg_seq to avoid passive-reply dedup (40054005)
- fall back to plain text per chunk when the platform rejects
markdown, so bots without markdown capability keep working
- route the non-stream final answer path in qq-bridge through the
markdown sender; streaming replies already use markdown content
type, and command/notice texts stay plain
Add setMyCommands and setChatMenuButton to the Telegram Bot API client
and register a 12-command menu with a commands-type menu button when a
bot runtime connects. Menu setup failures are logged as warnings and do
not block startup.
Co-authored-by: xmanrui <841206367@qq.com>
Each session row is a column_set with a fixed 90px watch-toggle column
(⭐关注/⭐取关 for already-watched sessions) and a weighted session button
carrying the number label; number replies bind the session. Replaces the
stacked button pair; also honors the per-bot archived-session policy.
The session list (and the numeric /watch index) honors a per-bot persisted
policy: /archived off hides archived sessions from cards and index
resolution, /archived on restores them. Defaults to on.
- /watch resolves the target READ-ONLY: sessions are validated against
registered workspace listings (current or any other) without binding the
conversation or switching workspaces. /unwatch and /watchlist round out
the command set; the watchlist card supports button and number-reply
unwatching.
- Watches persist in the state store (sessionId + title + chatId + lastSeq)
and resume at runtime start: the bridge starts the global event-mux
watcher in its constructor when the harness supports it.
- Completion pushes fire on turn/end, deduped by sessionId + turn id, with
the seq watermark persisted per watch. After a mux reconnect the bridge
replays each watched session's recent history (session.history) through
the normal handler, so missed turn/end events are compensated without
duplicates.
- Bound-session push targets are persisted (chatTargets) instead of living
only in memory, and refresh on every accepted message.
- Watch failures map to safe user-facing messages.
- /m (or /menu) opens a card menu; /sessionlist and /workspacelist render
cards with bind/switch buttons; number replies stay usable as a fallback
when the app does not subscribe card.action.trigger.
- card.action.trigger callbacks validate the operator's open_id against the
allowed senders: group members outside the allowlist can never drive
binding, workspace switches or other card actions.
- Session-list pagination uses page numbers everywhere (buttons carry
sessions:<page>), fixing the previous double page-size scaling that
skipped pages past 20 sessions.
- Bind/workspace failures map to safe user-facing messages instead of raw
error details.
- Apps registered after this change subscribe card.action.trigger during
the scan flow.