- Per-fold click-to-expand: assistant messages render as a sequence of
text/fold-header/fold-body widgets; FoldHeader is a clickable Static
that toggles m._toggled_folds and remounts just that message
- Ctrl+O is authoritative: clears all per-fold overrides so the state
is uniformly all-collapsed or all-expanded
- Streaming spinner: dedicated tiny Static (`m._spinner_widget`) after
segment widgets, driven by an independent 100ms timer. Bypasses
Markdown completely so unclosed code fences / paragraph whitespace
trimming can't eat it
- Hide `**LLM Running (Turn N) ...**` markers at render time only;
fold_turns still uses them for splitting
- Ctrl+B sidebar toggle: invalidate width-keyed cache and reuse the
resize debounce path so the chat reflows reliably after toggling
- #input min-width: 10 — guards Textual TextArea.render_lines against
`range() arg 3 must not be zero` when sidebar is shown on a very
narrow window and #main collapses to ≤ 0 cols
- Pasting non-image files (.docx/.pdf/.py …) was folded as `[Image #N]`
because clipboard helper returned any file path. Split into image vs
file by extension; non-images now fold as `[File #N]` and expand to
the path on submit, same flow as images.
- Backspace/Delete used to nibble placeholders one char at a time.
Override `action_delete_left/right`: when the caret sits flush against
a `[Pasted text #N +X lines]` / `[Image #N]` / `[File #N]` placeholder
on the matching side, remove the whole span and drop its dict entry.
- Ctrl+C inside the input was clearing the whole field. Root cause:
`screen.get_selected_text()` doesn't see TextArea's internal selection,
so the handler fell through to the input-reset branch. Fixed by
preferring the focused InputArea's `selected_text` before screen-level
drag selection.
- Input text/history/pastes were globally shared across agent sessions
(single InputArea widget). Moved the four state slots
(text/history/pastes/paste_counter) onto `AgentSession`, with a
`_swap_input_for_session` snapshot/restore on session switch.
- Fix 1: _patched_log function signature mismatch
- Location: plugins-langfuse_tracing.py
- Problem: monkey-patch function only accepts 2 parameters, but the original function has 3 parameters (including the default parameter log_path)
- Fix: add log_path=None parameter and pass it to the original function
- Fix 2: Agentmain module import failure
- Location: frontends-chatapp_common.py
- Problem: frontends directory cannot be imported into the agentmain of the upper directory
- Fix: Add the parent directory to sys.path at the beginning of the file
* fix(tui): debounce v2 layout reflow on resize
Same-size resize events are deduped, layout reflow is debounced to 80ms,
and message remount is deferred to the next frame so #messages.content_region
reports the post-resize width instead of the pre-resize one.
* feat(tui): rebind v2 keys (Ctrl+C dual, Ctrl+↑↓ sessions, Ctrl+O fold) + help overlay
- Ctrl+C is the single stop/quit key: stops a running task; otherwise
clears non-empty input; otherwise arms quit (press again within 2s to
exit). Removes Ctrl+S, Ctrl+Q, and InputArea's prior Ctrl+C
copy-or-clear binding. Any non-Ctrl+C key disarms the pending quit.
- Ctrl+O toggles turn fold (was Ctrl+F, which collides with terminal
app search shortcuts on macOS). Topbar gains a small "▾ fold"
indicator when fold mode is on.
- Ctrl+Up / Ctrl+Down switches sessions (was Ctrl+Left / Ctrl+Right)
to match the vertical session-list mental model.
- Esc is the single one-press cancel for ChoiceList — drops the prior
two-step "Left to leave, Left to cancel" flow. Esc also continues to
close the command palette and now closes the help overlay.
- Adds a Ctrl+? help overlay listing every keybinding. Bound on both
ctrl+? and ctrl+question_mark to survive terminal reporting quirks.
- Newline keeps Ctrl+J / Ctrl+Enter / Shift+Enter as cross-terminal
fallbacks (Ctrl+J is the portable one for macOS Terminal.app); drops
Alt+Enter.
- Bottom bar simplifies to four essentials + Ctrl+? and / hints, and
shows a transient "再按 Ctrl+C 退出" while quit is armed.
* fix(tui): switch help shortcut from Ctrl+? to Ctrl+/
Ctrl+? (= Ctrl+Shift+/) is unreliable across terminals — many emulators
either swallow it or report it as something Textual can't parse. Ctrl+/
is the conventional VS Code / command-line help shortcut and ships with
the legacy Ctrl+_ fallback (ASCII 0x1F is what older terminals send for
Ctrl+/). All three forms are bound (ctrl+slash, ctrl+/, ctrl+underscore)
so the binding survives terminal-reporting differences.
Bottom hint bar collapses to a single left-aligned line carrying the
command-palette and help hints alongside the action keys, since the
prior right column repeated information the left column already had room
for. The help overlay header reads "快捷键帮助" instead of "快捷键".
* fix(tui): ctrl+c dual-press with non-empty input, centered help modal
- Ctrl+C with non-empty input now clears AND arms quit; a second Ctrl+C
within 2s exits. Previously typing in the input let one Ctrl+C exit
silently after the clear, which was easy to trigger by accident.
- Help is now a ModalScreen pushed over the main screen so opening it
no longer shifts the input box. Centered with a lighter background
(#21262d on #30363d border) to read as a lifted panel.
- Esc-cancelling a ChoiceList removes the prompt cleanly with no
"已取消" output — the canceled prompt simply disappears from the
chat, matching expectations for in-flight tool prompts.
* fix(tui): keep Ctrl+C copy semantics and update choice hints
When there is selected text, Ctrl+C now copies the selection and returns
without triggering stop, input clear, or quit-arming. This preserves the
TUI selection/copy path after Ctrl+C became the global stop/quit key.
Choice prompts now advertise the current Esc-cancel behavior instead of
the removed Left-arrow flow, and stale comments/docs mentioning the old
left-arrow return path were cleaned up.
PR #360 disabled Textual mouse support on darwin (run(mouse=False)) to
keep terminal-native selection working. Side effect: ?1006 SGR mouse was
never enabled, while macOS Terminal.app / iTerm2 default ?1007 (alternate
scroll mode) translated wheel events into Up/Down keys — which then
triggered the newly added input-history navigation.
Re-enable mouse globally and explicitly disable ?1007 on mount so wheel
events flow through the SGR channel without spawning duplicate arrow
keys. Keyboard Up/Down history navigation is unaffected. The ?1007
disable is a no-op on Linux / Windows Terminal where it defaults off.
When an assistant message already has a mounted body widget, streaming updates only refreshed the widget content. Scroll the messages container after those in-place updates so both streaming chunks and final responses remain visible at the bottom.
Handle paste events directly for both short and multi-line input instead of mixing TextArea's default paste path with the custom placeholder path. This keeps single-line paste from being inserted twice while preserving the existing multi-line placeholder behavior.
Keep Telegram streaming overflow messages tied to the primary live message, reuse them across edits, and delete stale overflow replies when streamed content shrinks.
Mirrors Claude Code's /btw: ask the agent a quick question without
interrupting the in-flight main task.
- frontends/btw_cmd.py: snapshot backend.history under lock + deepcopy →
background-thread backend.raw_ask single-turn answer; never writes
backend.history; never touches task_queue. Reuses backend.raw_ask +
make_messages, no new LLM instance.
- frontends/chatapp_common.py: register /btw in HELP_COMMANDS; handle
/btw in AgentChatMixin (sync via asyncio.to_thread); install() at
module bottom so the slash hook is wired for any frontend that
imports chatapp_common.
- frontends/stapp.py: persist display_queue + partial_response in
st.session_state so the streaming bubble survives Streamlit reruns;
new render_main_stream() helper covers both new tasks and resume;
/btw branch uses plain st.rerun() (preserves dq) so the main task's
remaining output renders in a follow-up bubble; non-/btw prompts
explicitly call agent.abort() to keep the original "submit cancels"
UX. Strip <summary> meta tags from text segments and drop dangling
"LLM Running (Turn N) ..." markers in resumed partials to clean up
the rendered chat.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous implementation called send_typing once with an empty
typing_ticket, which had no effect. The iLink API requires a valid
typing_ticket obtained from getconfig first.
Changes:
- Add get_typing_ticket() method to fetch ticket via getconfig API
- Replace single send_typing call with continuous typing loop
- Typing refreshes every 2 seconds until agent finishes processing
- Stops cleanly via threading.Event when message handling completes
Co-authored-by: succ985 <succ985@users.noreply.github.com>
Replace fixed 5s sleep with the same backoff pattern already used in
dcapp.py: 5 -> 10 -> 20 -> ... -> 300s, resetting to 5s when a session
survived >=60s. Uses time.monotonic() to avoid clock-jump skew.
Switch to inc_out=True mode so queue items are incremental deltas
instead of cumulative text. The old cumulative approach broke when
agentmain's post-processing (</summary>\n\n insertion) shifted offsets,
causing the bridge's delta calculation to produce overlapping or
truncated content.
Now "next" items are forwarded directly, and "done" is only used as
content when no streaming occurred (error fallback).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>