feat(tui): Alt+C copy hotkey and harden copy-as-markdown behavior#16966
feat(tui): Alt+C copy hotkey and harden copy-as-markdown behavior#16966fcoury-oai wants to merge 28 commits intomainfrom
Conversation
|
Nice work on the clipboard hardening. Just wanted to share my quickly made fix, where Alt+C keybinding itself can be done in 12 lines, with reusing the existing The approach is a single It works on all platforms since it goes through the existing Obviously this doesn't include your big fixes. This is just keybinding for quick fix and minimal patch, if the team wants to land on quick shortcut while the larger rewrite is tested thoroughly for the roadmap vision. I'd like to have more features in the copy to clipboard and paste from clipboard in the future, what the big rewrite enables. For example image pasting is currently broken. |
1b807bc to
9517b15
Compare
Promote non-empty TurnItem::Plan text to the active copy source so Alt+C and /copy copy the final plan output instead of prior commentary in the same turn. Adds regression coverage for replayed user message + commentary + completed plan item flow.
Rename the per-turn copy-source flag to reflect non-AgentMessage sources, clarify ordinal docs for copy-history entries, and make the backtrack continuation-group test assert real merged output.
Record rendered review output on ExitedReviewMode so Alt+C and /copy use the latest review content, including explanation-only and findings-based review results. Adds regression tests for both paths.
Remove the newer /copy cache and clipboard_text module so the rebased branch consistently uses the markdown-history implementation. Update slash command tests and snapshots to assert the markdown history state, including rollback truncation and item-completion fallback.
Keep the native Linux `arboard::Clipboard` in a TUI-held lease after `/copy` succeeds so X11 and Wayland clipboard contents remain available while Codex is running. Thread the optional lease through copy handling, preserve the previous lease on failed copies, and cover the behavior in focused copy tests.
Remove the empty rollback callback now that copy-state rollback is owned by app_backtrack, and document the copy-state invariants.
Restore the WSL PowerShell clipboard fallback after native copy fails so WSL users are not dependent on terminal OSC 52 support. Keep SSH on OSC 52 only, preserve native clipboard as the first local path, and cover the fallback ordering with injected backend tests.
Add required argument comments to new copy-as-markdown callsites. This keeps the Bazel argument-comment lint aligned with CI.
Explain that the ChatWidget lease keeps copied clipboard text available while the platform requires the lease to be held.
Wrap OSC 52 clipboard copies in tmux passthrough sequences when the TUI is running under `TMUX`. This keeps SSH and terminal fallback clipboard copy working for nested tmux sessions.
Advance the copy turn count when locally rendered prompts are added to history, so resumed threads do not overwrite prior assistant markdown when a new response arrives before rollback. Use the recorded item-level assistant markdown as the completion notification fallback when `TurnComplete` omits final text, while avoiding stale notification previews across later empty completions.
Construct new slash command test turn-completion events through serde so they compile against both the current branch protocol shape and the newer PR merge-base shape with completion timing fields.
Add the required parameter comments to the new turn-completion test helper calls so the Bazel argument-comment lint accepts the explicit `None` fallback cases.
9517b15 to
36b6896
Compare
Add inline comments to record_agent_markdown and on_task_complete explaining the turn ordinal computation and saw_copy_source_this_turn guard, plus a doc comment on the copy_last_agent_markdown_with testing seam. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Annotate the three AgentMessage match arms that now feed record_agent_markdown (previously no-ops), the ThreadRolledBack handler pointing to app_backtrack for cleanup, and the completed_turn_count bump that drives ordinal assignment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TL;DR
Alt+Cshortcut on top of the existing/copycommand, allowing users to copy a plan without having to cancel the plan and type/copyTested on macOS, Linux/X11, Windows WSL2, Windows cmd.exe, Windows PowerShell, Windows VSCode PowerShell, Windows VSCode WSL2, SSH (macOS -> macOS).
Problem
The TUI's
/copycommand was fragile. It relied on a singlelast_copyable_outputfield that was bluntly cleared on every rollback and thread reconfiguration, making copied content unavailable after common operations like backtracking. It also had no keyboard shortcut, requiring users to type/copyeach time. The previous clipboard backend mixed platform selection policy with low-level I/O in a way that was hard to test, and it did not keep the Linux clipboard owner alive — meaning pasted content could vanish once the process that wrote it dropped itsarboard::Clipboard.This addresses the text-copy failure modes reported in #12836, #15452, and #15663: native Linux clipboard access failing in remote or unreachable-display environments, copy state going blank even after visible assistant output, and local Linux X11 reporting success while leaving the clipboard empty.
Mental model
Agent responses are now tracked as a bounded, ordinal-indexed history (
agent_turn_markdowns: Vec<AgentTurnMarkdown>) rather than a single nullable string. Each completed agent turn appends an entry keyed by its ordinal (the number of user turns seen so far). Rollbacks pop entries whose ordinal exceeds the remaining turn count, then use the visible transcript cells as a best-effort fallback if the ordinal history no longer has a surviving entry. This means/copyandAlt+Creflect the most recent surviving agent response after a backtrack, instead of going blank.The clipboard backend was rewritten as
clipboard_copy.rswith a strategy-injection design:copy_to_clipboard_withaccepts closures for the OSC 52, arboard, and WSL PowerShell paths, making the selection logic fully unit-testable without touching real clipboards. On Linux, theClipboardhandle is returned as aClipboardLeasestored onChatWidget, keeping X11/Wayland clipboard ownership alive for the lifetime of the TUI. When native copy fails under WSL, the backend now tries the Windows clipboard through PowerShell before falling back to OSC 52.Non-goals
Tradeoffs
MAX_AGENT_COPY_HISTORYcaps memory. For sessions with thousands of turns this silently drops the oldest entries. The cap is generous enough for realistic sessions.saw_copy_source_this_turnflag: Prevents double-recording when bothAgentMessageandTurnComplete.last_agent_messagefire for the same turn. The flag is reset on turn start and on turn complete, creating a narrow window where a race between the two events could theoretically skip recording. In practice the protocol delivers them sequentially.last_agent_markdown_from_transcriptwalks the visible transcript cells to reconstruct plain text when the ordinal history has been fully truncated. This path usesAgentMessageCell::plain_text()which joins rendered spans, so it reconstructs display text rather than the original raw markdown. It keeps visible text copyable after rollback, but responses with markdown-specific syntax can diverge from the original source.Architecture
Observability
tracing::warn!on native clipboard failure before OSC 52 fallback.tracing::debug!on/dev/ttyopen/write failure before stdout fallback.Tests
clipboard_copy.rs: Unit tests cover OSC 52 encoding roundtrip, payload size rejection, writer output, SSH-only OSC52 routing, non-WSL native-to-OSC52 fallback, WSL native-to-PowerShell fallback, WSL PowerShell-to-OSC52 fallback, and all-error reporting via strategy injection.chatwidget/tests/slash_commands.rs: Updated existing/copytests to uselast_agent_markdown_text()accessor. Added coverage for the Linux clipboard lease lifecycle, missingTurnComplete.last_agent_messagefallback through completed assistant items, replayed legacy agent messages, and stale-output prevention after rollback.app_backtrack.rs: Addedagent_group_count_ignores_context_compacted_markerverifying that info-event cells don't inflate the agent group count.