Skip to content

feat: streaming command UI improvements#48

Open
chinkan wants to merge 53 commits into
mainfrom
feat/streaming-cmd-ui
Open

feat: streaming command UI improvements#48
chinkan wants to merge 53 commits into
mainfrom
feat/streaming-cmd-ui

Conversation

@chinkan

@chinkan chinkan commented Jul 14, 2026

Copy link
Copy Markdown
Owner

This PR adds extensive improvements to the streaming command UI:

Key Features:

  • Add rich_sender module with sendRichMessage API integration
  • Implement preprocess_markdown and postprocess_entities for proper markdown formatting
  • Upgrade streaming to use sendRichMessage with entity fallback for better markdown support
  • Add UTF-16 text encoding awareness for proper message handling
  • Add loop detection with recovery nudges to prevent infinite loops
  • Add inline keyboard callback handlers for loop control
  • Improve command cancellation and error handling
  • Add scheduled task history and rerun capabilities
  • Enhance markdown entity formatting (blockquotes, spoilers, underlines)
  • Add context-forked side query for /btw command

Technical Improvements:

  • Better markdown preprocessing for Telegram's , , and tags
  • Rich message support with fallback to entity-based formatting
  • Proper UTF-16 boundary handling for long messages
  • Improved streaming final flush logic with retroactive entity formatting
  • Enhanced error handling for callback queries
  • Better command cancellation patterns

chinkan added 30 commits July 7, 2026 11:46
…ting

- Show command text in verbose tool notification (remove 'command' from
  sensitive keys, add to SAFE_KEYS)
- Add truncate_tail utility for command output display
- Add RunningCommand registry and interactive command execution with
  cancel button via inline keyboard callback
- Wire cancel_cmd:* callback handler in Telegram
- Retroactively format split streaming messages with entities
- Convert all command responses to entity-based send_markdown_message
- Add blockquote and table support to markdown_to_entities
…command output

The select! loop in execute_command_interactive races child.wait() against
the spawned pipe reader tasks. When the child exits quickly, child.wait()
resolves before the readers have flushed pipe data through the mpsc
channel, producing a result of just "Exit code: 0" with no output.

Fix: capture JoinHandle from each spawned reader, then after the select
loop exits, await both handles and drain remaining channel data with
try_recv() before building the result. This guarantees the output buffer
is complete, with no borrow conflicts or cancel-safety issues.

Also: fix pre-existing clippy issue in markdown_entities.rs where
in_table_cell flag had identical handling to the else branch.
…cate body formatting

Changes per code-quality-reviewer PASS_WITH_CONCERNS:
- Remove 250ms timeout around post-loop tokio::join!(handles) — any
  timeout reintroduces a race window where try_recv could miss late
  chunks. Readers finish within microseconds after pipe EOF, so no
  timeout is needed.
- Remove pre-drain edit_message_text calls in select branches — only
  update display once after drain with complete output (avoids flicker
  and duplicate API calls).
- Extract format_body() helper to deduplicate the output-formatting
  pattern that appeared 4 times.
- Replace unreachable else with unreachable!().
…ation

Root causes:
- Double answer_callback_query: first answer (no text, unconditional)
  swallowed the '⛔ Command cancelled' toast. Telegram only honors the
  first answer per callback_id. If the first answer failed (used ?),
  the cancel handler never ran.
- child.kill() only killed sh, not descendants of sh -c.
- edit_message_text failures were silently swallowed by .ok().

Fixes:
1. telegram.rs: Remove unconditional answer at fn entry. Each branch
   answers exactly once with appropriate text. Cancel branch now shows
   the toast. Non-cancel branches use silent answers.
2. agent.rs: Add .process_group(0) to spawn so sh -c gets its own
   process group. On cancel, call nix::sys::signal::killpg() to send
   SIGKILL to the entire process group, killing both sh and all
   descendant processes.
3. agent.rs: Replace .ok() with if-let Err + warn! on all three
   edit_message_text calls so API failures are logged.
4. tests/command_cancel.rs: Add integration tests for process-group
   killpg and oneshot-cancel-in-select-loop patterns.
Three independent features:

(A) Markdown entities upgrade (src/utils/markdown_entities.rs)
- Blockquote entity via Bot API 7.0+ MessageEntityKind::Blockquote
- Spoiler (||text||) and Underline (<u>text</u>) via PUA sentinel pre-processing
- List formatting: unordered gets \u{2022} bullet, ordered gets 1./2. numbering

(B) Lightweight /btw (src/agent.rs, src/platform/telegram.rs)
- Replaces heavy ask_parallel with ask_parallel_lightweight
- Single LLM call, no tools, no agentic loop, zero lock contention

(C) Steer messages (src/agent.rs, src/platform/telegram.rs)
- MidRunMode enum (Steer/Queue) with memory persistence
- /mode command to toggle between modes
- Injection point moved to pre-LLM-call with mode-aware formatting
- Steer: ephemeral injection; Queue: persisted injection
- /clear also resets mid-run mode
chinkan and others added 23 commits July 13, 2026 09:05
Three fixes for the /btw command:

1. **Model ID bug**: Switched from  (which passed the provider
   name 'openrouter' as the model string) to
    with the agent's
   (e.g. 'openrouter/qwen/qwen3-235b-a22b').

2. **True parallelism**: Added  to teloxide's
   Dispatcher that returns  for commands (bypass per-chat
   serialization) and  for regular messages (preserve
   ordering). This fixes the root cause — teloxide's default groups
   ALL updates by chat, processing them sequentially.

3. **DB ops in spawned task**: Moved  and
    into the  block so the
   handler returns immediately after sending the acknowledgment.
…ionManager, AgenticLoop from Agent god module

M1: CancelRegistry, PlatformSender trait, ToolHandler+T哪怕是ToolRegistry, TelegramAdapter,
    BuiltinTools, MemoryTools, SchedulingTools, SkillTools, CommandTool handlers
M2: ConversationManager with compaction, RAG injection, steer application
M3: AgenticLoop loop runner with MessageContainer for dual main/subagent paths
M4: Supervisor ADR, seed::write_lock helper

Bug fixes:
- conversation.rs: use get_or_create_conversation instead of fake ID format
- llm.rs: use default_qualified_model instead of default_provider_name

Critical fixes from code review:
- AgenticLoop::run now receives real user_id/chat_id (not empty strings)
- process_message passes MessageContainer::Conversation to keep compaction live
- Compaction, loop detection, interactive callback re-enabled in main loop
- RAG context injected via inject_rag_context
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.

2 participants