Skip to content

feat: cherry-pick upstream PRs for slash commands, model switching, and rich formatting#1

Merged
jhbarnett merged 10 commits intomainfrom
feat/upstream-cherry-picks
Mar 19, 2026
Merged

feat: cherry-pick upstream PRs for slash commands, model switching, and rich formatting#1
jhbarnett merged 10 commits intomainfrom
feat/upstream-cherry-picks

Conversation

@jhbarnett
Copy link
Owner

Summary

Cherry-picks 4 high-value open PRs from upstream (RichardAtCT/claude-code-telegram):

Details

Rich HTML Formatting (RichardAtCT#145)

14-step markdown→Telegram HTML conversion pipeline: aligned monospace tables with box-drawing characters, <blockquote> support, bullet/numbered lists, horizontal rules, and a stack-based _repair_html_nesting() fixer for strict Telegram tag validation.

Slash Command Passthrough (RichardAtCT#131)

Forwards unrecognized /commands to Claude as natural language via a fallback CommandHandler. Dynamically builds a _known_commands set from registered handlers to avoid double-firing. Handles @bot suffixed commands correctly.

Model Switching (RichardAtCT#139)

/model shows current model (source: user override, server config, or Claude Code default). /model <name> sets a per-user override. /model default resets. Threaded through orchestrator→facade→SDK. Includes input validation, audit logging, and 14 tests.

Streaming Drafts (RichardAtCT#146)

Rewrites DraftStreamer to use sendMessageDraft in private chats with editMessageText fallback for groups. Anti-regressive updates, smart error classification, self-disabling after 3 errors. New config: ENABLE_STREAM_DRAFTS, STREAM_DRAFT_INTERVAL.

Test plan

  • Verify rich HTML formatting renders correctly in Telegram (tables, code blocks, blockquotes)
  • Test unknown slash commands (e.g. /workflow, /skill) are forwarded to Claude
  • Test known commands (/start, /help) still work normally
  • Test /model, /model sonnet, /model default lifecycle
  • Test streaming drafts in private chat and group chat
  • Run existing test suite to check for regressions

🤖 Generated with Claude Code

Fr4nzz and others added 10 commits March 19, 2026 00:34
- Convert markdown tables to aligned monospace <pre> blocks
- Add blockquote support (> text → <blockquote>)
- Add bullet list conversion (- item → • item)
- Add numbered list formatting
- Add horizontal rule conversion (--- → ──────────)
- Fix HTML tag nesting issues that cause Telegram rejection
- Add _repair_html_nesting() to fix overlapping bold/italic tags
In agentic mode, unknown slash commands (e.g. /workflow, /skill, /add-memory)
were silently dropped because the TEXT handler excluded all COMMAND messages.

Remove ~filters.COMMAND from the agentic text handler so unrecognised
slash commands are forwarded to Claude as natural language. Registered
commands (/start, /new, /status, etc.) still take priority via higher-priority
CommandHandlers and are unaffected.

Classic (non-agentic) mode retains the original filter — unknown commands
are not forwarded there.

Closes RichardAtCT#129
Previous approach (removing ~filters.COMMAND) caused registered commands
like /start and /new to fire twice — once via CommandHandler (group 0)
and again via the agentic text handler (group 10).

Fix: revert TEXT filter and add a separate filters.COMMAND MessageHandler
in group 10 that dispatches to _handle_unknown_command(). That method
checks the command name against KNOWN_COMMANDS and returns early for
registered ones, forwarding only truly unknown slash commands to Claude.

Dispatch flow:
- /start, /new, /status ... -> CommandHandler (group 0) only
- /workflow, /skill, /add-memory ... -> forwarded to Claude via agentic_text
- Regular text -> TEXT & ~filters.COMMAND handler as before

Also updates test to expect 5 message handlers (was 4).
…ation, routing tests

- Add ClassVar[frozenset[str]] type annotation on _known_commands (mypy strict)
- Derive _known_commands dynamically from registered handlers list to prevent
  drift when new commands are added in future
- Remove hardcoded KNOWN_COMMANDS class variable
- Add 3 routing tests:
  - test_known_command_not_forwarded_to_claude
  - test_unknown_command_forwarded_to_claude
  - test_bot_suffixed_command_not_forwarded
Allow users to switch Claude models at runtime without restarting
the bot. Supports aliases (sonnet, opus, haiku), full model names,
and /model default to reset. Override is per-user.
- Add audit logging to /model set and reset paths
- Update last_model in _handle_agentic_media_message
- Add input validation (max length) for model name
- Add tests for audit logging and long model name rejection
Log model set as command="model" and reset as command="model_reset"
with empty args for cleaner audit log queries.
- Rewrite DraftStreamer with group chat fallback (editMessageText)
- Add cursor ▌ during streaming for visual feedback
- Min initial chars gate (20 chars before first send)
- Anti-regressive updates (skip if text got shorter)
- Smart error classification (draft unavailable → edit fallback)
- Support verbose level 3 (full tool output)
@jhbarnett jhbarnett merged commit 89e36c2 into main Mar 19, 2026
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.

4 participants