Repository navigation
Conversation
|
I'm genuinely excited about this contribution. This is a considerably large implementation, and I fully understand it won't be accepted without thorough review. I'm very open to feedback and suggestions from maintainers and the community. I really hope this change can be incorporated, as I believe it's a truly useful feature that addresses a common pain point. I apologize for the lengthy PR description, but I've put significant thought into it with the goal of being as detailed as possible, explaining the decisions I made. I went through a considerable research phase (studying how Codex and other tools handle this) before implementing, so I wanted to document that reasoning. Thank you for your time and understanding. |
|
Rebased onto dev and added TypeScript suppressions for pre-existing Hono type instantiation errors. These compiler limitations were already present in the codebase. |
Add the ability to edit and discard messages that are queued while the agent is working. This allows users to fix typos or completely remove queued messages before they are processed. Changes: - Add queue_edit (<leader>i) and queue_discard (<leader>d) keybinds - Add REST endpoints: GET/DELETE /session/:sessionID/queue/:messageID - Show QUEUED badge with contextual hints on queued messages - Show EDITING badge when a queued message is being edited - Preserve file attachments when loading queued message for editing - Clear editing state with toast when message is processed mid-edit - Fix: prevent disabled commands from triggering via keybinds
|
Rebased onto dev and resolved conflicts. Kept queued message UI hints (QUEUED/EDITING), restored session queue endpoints under /session/*, preserved MCP + new queue keybind tests, and adopted the upstream Hono type-instantiation suppression in server.ts. |
New SessionV2.pending/withdraw/revise methods let a caller remove or edit a message that's been durably admitted but not yet promoted into a visible turn -- plus three new HTTP routes and a TUI /queue dialog. Ported from upstream anomalyco#4821 (126 reactions): PR anomalyco#6415 "Easy Message Unqueue with /unqueue" (closed by a 60-day-stale bot, never reviewed) and PR anomalyco#6271 "add edit and discard actions for queued messages" (more thorough, self-closed after apparent lack of maintainer response). Both PRs remove a queued message from an in-memory callback list and delete the V1 message -- a backend model with no equivalent in this repo's V2 session core, where a queued message is a durable row that stays pending until a safe promotion point. So the backend here is a fresh, V2-native design, not a port: a durable event (session.next.prompt.withdrawn / .revised) is published and projected the same way SessionInput.Prompted already works, guarded to only affect rows that are neither promoted nor withdrawn, with a withdraw winning any race against an in-flight promotion. What was reused: the remove/edit concepts themselves, /unqueue kept as a command alias (anomalyco#6415), and the general shape of a queued-message picker UI (anomalyco#6271) -- reworked as a /queue list dialog rather than anomalyco#6271's inline "EDITING" mode in the main prompt box. New requireInput flag threads through the turn loop in packages/core/src/session/runner/llm.ts so a turn whose only justification was a withdrawn input is skipped rather than run with nothing to answer -- merged by hand against this fork's own model-fallback feature, which had already added a different parameter (tried) to the exact same functions. KNOWN, SIGNIFICANT GAP: the TUI still sends every prompt through the legacy V1 SessionPrompt path, which never creates the V2 session_input rows this feature acts on. /queue will show an empty list for anything typed in the actual running TUI today. Switching the TUI to send V2 prompts is a materially bigger decision than this feature's own scope and was deliberately left for a separate follow-up rather than silently expanded into. The backend is fully real, tested, and reachable via the HTTP API directly today. New tests: packages/core/test/session-prompt.test.ts (+10 cases), session-runner.test.ts (+4), packages/tui/test/cli/tui/data.test.tsx (+2), three new httpapi-exercise scenarios, event-manifest count updated (88 -> 90 for the two new durable events). Client and legacy JS SDKs regenerated. Verified against this fork's current dev, including the manual merge against model-fallback: bun typecheck clean across all 30 packages; packages/core 1178 pass (only the pre-existing pty flake); packages/opencode 3613 pass, only the 4 pre-existing cf-ai-gateway failures; packages/tui 230 pass; httpapi-exercise coverage mode 214 pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
Add the ability to edit and discard queued messages while the agent is working.
This allows users to fix typos, rephrase, or completely remove messages from
the queue before they are processed.
New keybinds:
<leader>i- Edit the last queued message<leader>d- Discard the last queued messageMotivation
When iterating quickly with the AI assistant, users often queue multiple messages.
Currently, there's no way to modify or remove these queued messages once sent.
This leads to:
This feature addresses a common workflow need requested by multiple users.
Related Issues & PRs
Closes
Supersedes
Related
Screenshots
Queued message with hints
Editing a queued message
Command palette with queue commands
Demo
https://streamable.com/urjeww
The video demonstrates the complete flow:
<leader>i<leader>dImplementation
Why this PR exists (context on #5415)
This implementation started from PR #5415 which was stale and had several issues:
Problems with the original PR #5415
history_previouskeybind - Hijacked the UP arrow when prompt was empty, conflicting with normal history navigationWhat we fixed/improved
<leader>i/<leader>dArchitecture Decisions
Why dedicated keybinds instead of UP arrow?
The original PR #5415 proposed reusing
history_previous(UP arrow) when the promptis empty. We chose dedicated keybinds (
<leader>iand<leader>d) because:<leader>hfor tips,<leader>upfor parent session)Why separate Edit and Discard?
<leader>i): Loads the message into the prompt for modification<leader>d): Removes the message entirely without loading itThis separation allows:
Why only edit the last queued message?
Both OpenAI Codex and our implementation only allow editing the most recent queued message.
Rationale:
State Management
We track
editingQueuedMessageIDin the prompt store to:EDITINGbadge on the correct messageFile/Image Preservation
When loading a queued message for editing, we preserve non-text parts by reconstructing them with virtual text representations (e.g.,
[File: filename]or[Image 1]) and creating extmarks for proper rendering. This ensures images and files aren't silently lost when editing.Comparison with Other Tools
We studied how other AI coding assistants handle queued message editing:
OpenAI Codex (Rust TUI)
Source:
codex-rs/tui/src/chatwidget.rsCodex stores queued messages in a
VecDeque<UserMessage>with text and image paths. It usesAlt+Upto pop the most recent queued message back into the composer, preserving image paths separately. A static hint "Alt+Up edit" is shown on queued messages.What we learned from Codex:
How we differ from Codex:
Alt+Up<leader>i<leader>dEDITINGbadgeClaude Code
Claude Code allows editing queued messages via UP arrow when prompt is empty.
This is similar to the original #5415 approach, which we improved upon with
dedicated keybinds to avoid conflicts with history navigation.
Flow Diagram
API Endpoints Added
GET/session/:sessionID/queueGET/session/:sessionID/queue/:messageIDDELETE/session/:sessionID/queue/:messageIDChanges
Backend (
packages/opencode/src/)session/prompt.tsqueued(),getQueued(),cancelQueued()functions; trackmessageIDin callbacksserver/server.tsconfig/config.tsqueue_editandqueue_discardkeybind defaultsFrontend (
packages/opencode/src/cli/cmd/tui/)component/prompt/index.tsxeditingQueuedMessageIDstate; preserve file parts with extmarks; handle edge cases (message processed mid-edit, cancel with ctrl+c, clear with ctrl+u)component/dialog-command.tsxroutes/session/index.tsxSDK (auto-generated)
packages/sdk/js/src/v2/gen/*packages/sdk/openapi.jsonBug fix: disabled commands triggering via keybinds
Found and fixed a bug in
dialog-command.tsxwhere disabled commands could still be triggered via their keybinds:This affected all commands with a
disabledproperty, not just the queue commands.Edge Cases
[File: filename]with extmarksctrl+c)ctrl+u)How to Test
QUEUEDbadge)ctrl+x ito edit the queued messageEnterto submit, orctrl+cto cancelctrl+x dto discard without editingTesting
test/session/queue.test.ts(new file)SessionPrompt.queued()- returns empty array for non-existent sessionSessionPrompt.getQueued()- returns undefined for non-existent session/messageSessionPrompt.cancelQueued()- returns undefined for non-existent session/messagetest/config/config.test.ts(additions)queue_editdefaults to<leader>iqueue_discarddefaults to<leader>dBreaking Changes
None. This is an additive feature with new keybinds that don't conflict with existing ones.