feat(coding-agent): edit queued messages in place and preserve the queue on interrupt - #838
Conversation
Fixes OpenAI Responses 400 error 'reasoning without following item' by skipping errored/aborted assistant messages entirely rather than filtering at the provider level. This covers openai-responses, openai-codex-responses, and future providers. Removes strictResponsesPairing compat option (no longer needed). Closes PrimeIntellect-ai#838
|
Both fixed in bb8ffe2:
|
|
Fixed in b082b76. The race is real: in-process connections emit Queue events always assign a fresh Same commit also trims review-fix bloat from the previous round: the session-switch coverage now extends the existing render-state reset tests instead of a duplicated 40-line harness, and the defensive |
…eue on interrupt Browse queued steering/follow-up messages with Alt+Up/Alt+Down, edit the selected item in the editor, and apply with Enter (steering) or Alt+Enter (follow-up), flipping lanes when needed. Submitting an empty edit deletes the item; Ctrl+Alt+Up/Down reorders it within its lane. Ctrl+C/Escape now abort the active turn without clearing the queue: queued messages stay server-owned and visible, and draining resumes on the next successful edit or fresh submit (the input pump is already suspended by requestAbort and resumed by _prompt on main). Server: AgentSession.mutateQueuedMessage addresses items by (lane, index, expectedText) against the same projection the snapshot publishes, so no ids, revisions, or snapshot changes are needed. Deletes settle agentMessageId outcomes; replaces rebuild content/records, honor an image tri-state (preserve/clear/replace), reject non-editable turns, and resume queued work. Daemon wire adds one mutate_queued_message request behind the queue_message_mutation capability (schema 13 -> 14); older daemons report "unsupported" and the UI shows a status message. app.message.dequeue is migrated to app.message.navigateOlder; the restore-queue-to-editor path is removed.
- Serialize queue mutations through a single chain so rapid Enter/reorder presses cannot race each other or send stale indices; surface rejected reorders as a status message. - Guard editor writes after a mutation with an editor-text ownership check so a stale completion never clobbers newer typing, and restore the edit when the mutation throws (Enter clears the editor before onSubmit runs). - Restore the stashed draft when the browsed queue item is consumed or removed externally, so Enter cannot resubmit stale queued text. - Deduplicate repeated [image #N] markers in replace mutations. - Use the configured app.message.followUp key in the browse header instead of a hard-coded alt+enter. - Drop the dead clearEditor() wrapper and tolerate partial test harnesses in the onSubmit/handleFollowUp queue interception (fixes the 4509 CI shard).
… event arrives Over the daemon, the mutation response can land before the corresponding session_action_update, so a queued follow-up move would still address the pre-move index and be rejected. On a successful move, swap the local queue mirror optimistically and resync the selection; the later event settles to the same state.
…edits optimistically resetCurrentSessionRenderState now clears the queue selection and its stashed draft, so Enter in a freshly switched session can never issue a mutation addressed at the previous session's queue. Applied replace and delete mutations update the local queue mirror immediately (like moves already did), so an immediate re-browse sees the new text instead of a stale expectedText that the daemon would reject.
…eady landed In-process connections deliver the session_action_update before the mutateQueuedMessage await resumes; sync can retarget the selection onto a same-text item, so the text guard alone let the optimistic patch apply the delete or lane move a second time. Queue events always assign a fresh connectionQueue object, so patch only when the mirror is still the exact object captured before the request. Also folds the session-switch regression coverage into the existing render-state reset tests instead of a duplicated harness, and drops the defensive optional chaining that hid missing harness fields.
…sion 15 Main took revision 14 for the telemetry policy surface (a18809e), so the queued-message-mutation gate and the advertised schema identity move to a fresh revision 15 with the recomputed wire-shape digest.
…ppear in the other queue
0afb3d7 to
3513024
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3bdb038. Configure here.
Patch release. Bug fixes, small UX additions behind existing surfaces, and a dependency consolidation; no breaking changes, per the no-major-releases policy. Contents since v0.7.1: - #838 in-place queue editing (Alt+Up/Alt+Down browse, Enter/Alt+Enter apply) and queue preservation on interrupt - #850/#851/#852 worker lifecycle truthfulness, timed-out stop finalization, and stale-registration self-heal (the "Session worker is not connected" family) - #1226 Down Arrow stays in a nonempty prompt until the cursor reaches the end - #767 independent expand/collapse for tool calls, a2a messages, and thinking - #1135 agents view keeps expansion state when leaving and returning - #647 login URL copy action - #521 privacy-safe agent analytics with disclosure and opt-out - #846 Homebrew ownership preserved on self-update - #772 sent a2a messages show only message text when expanded - #632 consolidated dependency updates (undici 7.29, biome 2.5.5, marked 18, typescript 7 dev-only, typebox 1.3, aws-sdk bedrock, vitest 4.1.10, et al.) - #1132 stale Gemini test model update (test-only) Missing changelog entries for #838/#850/#851/#852 are added under 0.7.2. Lockstep bump across the root package and the four published packages; example and private workspaces untouched. Lockfile updated surgically (version fields and inter-package ranges only).
…eue on interrupt (PrimeIntellect-ai#838) * feat(coding-agent): edit queued messages in place and preserve the queue on interrupt Browse queued steering/follow-up messages with Alt+Up/Alt+Down, edit the selected item in the editor, and apply with Enter (steering) or Alt+Enter (follow-up), flipping lanes when needed. Submitting an empty edit deletes the item; Ctrl+Alt+Up/Down reorders it within its lane. Ctrl+C/Escape now abort the active turn without clearing the queue: queued messages stay server-owned and visible, and draining resumes on the next successful edit or fresh submit (the input pump is already suspended by requestAbort and resumed by _prompt on main). Server: AgentSession.mutateQueuedMessage addresses items by (lane, index, expectedText) against the same projection the snapshot publishes, so no ids, revisions, or snapshot changes are needed. Deletes settle agentMessageId outcomes; replaces rebuild content/records, honor an image tri-state (preserve/clear/replace), reject non-editable turns, and resume queued work. Daemon wire adds one mutate_queued_message request behind the queue_message_mutation capability (schema 13 -> 14); older daemons report "unsupported" and the UI shows a status message. app.message.dequeue is migrated to app.message.navigateOlder; the restore-queue-to-editor path is removed. * fix(coding-agent): address queue-edit review findings - Serialize queue mutations through a single chain so rapid Enter/reorder presses cannot race each other or send stale indices; surface rejected reorders as a status message. - Guard editor writes after a mutation with an editor-text ownership check so a stale completion never clobbers newer typing, and restore the edit when the mutation throws (Enter clears the editor before onSubmit runs). - Restore the stashed draft when the browsed queue item is consumed or removed externally, so Enter cannot resubmit stale queued text. - Deduplicate repeated [image #N] markers in replace mutations. - Use the configured app.message.followUp key in the browse header instead of a hard-coded alt+enter. - Drop the dead clearEditor() wrapper and tolerate partial test harnesses in the onSubmit/handleFollowUp queue interception (fixes the 4509 CI shard). * fix(coding-agent): keep chained reorders addressable before the queue event arrives Over the daemon, the mutation response can land before the corresponding session_action_update, so a queued follow-up move would still address the pre-move index and be rejected. On a successful move, swap the local queue mirror optimistically and resync the selection; the later event settles to the same state. * fix(coding-agent): reset queue browsing on session switch and mirror edits optimistically resetCurrentSessionRenderState now clears the queue selection and its stashed draft, so Enter in a freshly switched session can never issue a mutation addressed at the previous session's queue. Applied replace and delete mutations update the local queue mirror immediately (like moves already did), so an immediate re-browse sees the new text instead of a stale expectedText that the daemon would reject. * fix(coding-agent): skip the optimistic queue patch when the event already landed In-process connections deliver the session_action_update before the mutateQueuedMessage await resumes; sync can retarget the selection onto a same-text item, so the text guard alone let the optimistic patch apply the delete or lane move a second time. Queue events always assign a fresh connectionQueue object, so patch only when the mirror is still the exact object captured before the request. Also folds the session-switch regression coverage into the existing render-state reset tests instead of a duplicated harness, and drops the defensive optional chaining that hid missing harness fields. * fix(coding-agent): move queued message mutation to daemon schema revision 15 Main took revision 14 for the telemetry policy surface (a18809e), so the queued-message-mutation gate and the advertised schema identity move to a fresh revision 15 with the recomputed wire-shape digest. * docs(coding-agent): record revision 15 in the schema history comment * when switching sessions with a queued message, make sure it doesn't appear in the other queue * fix(coding-agent): harden queued edit lifecycle * fix(coding-agent): preserve failed queue edit drafts * fix(coding-agent): close queue edit state races * fix: stashed draft lost after edit --------- Co-authored-by: Alex Zhang <alex.lx.zhang@gmail.com>
Patch release. Bug fixes, small UX additions behind existing surfaces, and a dependency consolidation; no breaking changes, per the no-major-releases policy. Contents since v0.7.1: - PrimeIntellect-ai#838 in-place queue editing (Alt+Up/Alt+Down browse, Enter/Alt+Enter apply) and queue preservation on interrupt - PrimeIntellect-ai#850/PrimeIntellect-ai#851/PrimeIntellect-ai#852 worker lifecycle truthfulness, timed-out stop finalization, and stale-registration self-heal (the "Session worker is not connected" family) - PrimeIntellect-ai#1226 Down Arrow stays in a nonempty prompt until the cursor reaches the end - PrimeIntellect-ai#767 independent expand/collapse for tool calls, a2a messages, and thinking - PrimeIntellect-ai#1135 agents view keeps expansion state when leaving and returning - PrimeIntellect-ai#647 login URL copy action - PrimeIntellect-ai#521 privacy-safe agent analytics with disclosure and opt-out - PrimeIntellect-ai#846 Homebrew ownership preserved on self-update - PrimeIntellect-ai#772 sent a2a messages show only message text when expanded - PrimeIntellect-ai#632 consolidated dependency updates (undici 7.29, biome 2.5.5, marked 18, typescript 7 dev-only, typebox 1.3, aws-sdk bedrock, vitest 4.1.10, et al.) - PrimeIntellect-ai#1132 stale Gemini test model update (test-only) Missing changelog entries for PrimeIntellect-ai#838/PrimeIntellect-ai#850/PrimeIntellect-ai#851/PrimeIntellect-ai#852 are added under 0.7.2. Lockstep bump across the root package and the four published packages; example and private workspaces untouched. Lockfile updated surgically (version fields and inter-package ranges only).
Upstream PrimeIntellect-ai/prime-agent main, 14 commits since merge base a18809e. Seven files conflicted; resolutions: daemon-protocol.ts - both sides independently shipped DAEMON_SCHEMA_REVISION 15 for different features (this fork: attach ownership / wasAttached; upstream: mutate_queued_message, then 16 for the "stopping" workerState). Kept all three features and moved this fork's change to revision 17. DAEMON_SCHEMA_ID is a digest of the wire-type slices, recomputed rather than hand-written; it matches upstream's because wasAttached lives outside those slices. daemon-supervisor.ts - both sides independently implemented pid-reuse identity verification. Took upstream's (this.processIdentity / isWorkerProcessAlive with throttled recheck) because upstream's new daemon-supervisor-monitor tests encode it, and dropped this fork's parallel helpers, folding workerMightBeAlive into isWorkerProcessAlive, which has the same "unknown identity counts as alive" semantics. interactive-mode.ts - upstream's server-side queue preservation on interrupt (PrimeIntellect-ai#838) supersedes this fork's restoreQueuedMessagesToEditor; kept this fork's refine-command abort condition, which upstream has no equivalent for. Kept syncWorkingLoader; dropped the duplicate updatePendingMessagesDisplay that replaceConnectionQueue already performs. models.generated.ts - regenerated from the merged generator rather than hand-merged, since upstream's change was to the curated PRIME_INFERENCE_FEATURED_MODELS input. CHANGELOGs - this fork's entries stay under [Unreleased]; upstream's ship under [0.7.2]. Constraint: packages/ai keeps this fork's decoupled build (no generate-models); upstream still regenerates during build Confidence: medium Scope-risk: broad Not-tested: 11 failures remain under investigation against a develop baseline

Replaces #633 with a ground-up redesign at roughly a third of the diff (+1,033/−89 vs +2,397/−696) and less than half the net production code.
What it does
Design
The key observation is that
mainalready preserves the queue across aborts:requestAbort()suspends the input pump without clearing queued actions, and_prompt()resumes it on the next submit. The only reason Ctrl+C dumped the queue into the editor was one explicitrestoreQueuedMessagesToEditor({ abort: true })call. Interrupt is now a plainabort().Mutations are addressed by (lane, index, expectedText), verified server-side against the same projection the session-action snapshot publishes. Same-text queue items are semantically interchangeable, so this gives compare-and-swap-equivalent safety with no ids, no revisions, and no snapshot schema changes — queue state on the wire stays plain strings.
AgentSession.mutateQueuedMessage(lane, index, expectedText, mutation)→applied | rejected | invalidwithdelete,move, andreplacemutations. Deletes settleagentMessageIddelivery/completion outcomes (a deletedpromptAndWaitturn rejects instead of hanging). Replaces rebuild content and primary records, honor an image tri-state (undefinedpreserves server images,[]clears, list replaces), reject non-editable turns (accepted agent messages, injected custom-message turns), update the wake policy on lane flips, and resume queued work.QueueSelection(85 lines) tracks the browse cursor and stashes the draft; on queue changes it keeps, retargets by text, or drops the selection while preserving the stashed draft.mutate_queued_messagerequest behind thequeue_message_mutationcapability (schema 14 → 15). Older daemons reportunsupportedand the UI shows "Queue editing requires a newer daemon". The legacyabort_and_clear_queuewire command is untouched for compatibility.app.message.dequeue(restore-queue-to-editor) is superseded and migrated toapp.message.navigateOlder; existing custom bindings carry over via the keybindings migration.Not in this PR (dropped from the #633 approach)
Stable action ids, queue revisions/CAS plumbing, snapshot
items[]/revisionfields, client pause/resume state machinery (pause flags, generations, single-flight resume, interrupt-and-recall, ownership sentinels, checkpoint/reconcile), and image tri-state UI plumbing — all unnecessary under the queue-preserving interrupt semantics above.Tests
agent-session-queue-mutation.test.ts: projection addressing, stale-address rejection, delete settlement, boundary moves, lane flips (both directions, wake policy), image tri-state incl. content/record rebuild, session-command validation, injected-turn rejection, and abort → edit → resume.queue-selection.test.ts: browse order, boundary noops, sync keep/retarget/drop, draft stash survival across external drops.interactive-queue-edit.test.ts: enter/alt+enter lane targets, empty-submit delete, edit restoration when a mutation is rejected after Enter cleared the editor, unsupported-daemon status, interrupt preserving the queue.interactive-mode-ctrl-csuite to the new interrupt contract; daemon protocol/round-trip coverage; keybinding migration; tui key-encoding tests.npm run checkgreen; 660 coding-agent tests across 17 suites green; 740 tui tests green.Note
Medium Risk
Touches session queue delivery, abort/interrupt UX, and daemon protocol revision 15; mistakes could drop or mis-order prompts or strand suspended work, though extensive tests cover mutations and race cases.
Overview
Queued messages can be browsed and edited in place instead of dumping the whole queue into the editor. Alt+Up / Alt+Down walk steering and follow-up items (newest-first) while stashing the draft; Enter / Alt+Enter apply edits as steering or follow-up, and an empty submit deletes the selected item. Ctrl+Alt+Up / Down reorder within a lane (with TUI support for legacy Option-as-Meta arrow encodings).
Server-side
mutateQueuedMessage(lane, index,expectedText, delete/move/replace) backs this viamutate_queued_messageon the daemon (schema 15,queue_message_mutationcapability). The interactive UI usesQueueSelectionand serialized optimistic updates.Ctrl+C during streaming now calls
abort()only—the queue stays server-owned and visible; draining resumes on the next submit or queue edit.app.message.dequeueis migrated toapp.message.navigateOlder.Reviewed by Cursor Bugbot for commit eb16eac. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add in-place queue message editing and preserve the queue on interrupt
QueueSelection(queue-selection.ts) to track cursor position, stash/restore the editor draft, and reconcile selection when the server queue changes.AgentSession.mutateQueuedMessage(agent-session.ts) supporting delete, move (in-lane swap), replace (text/images), and lane flip (steering↔follow-up) with optimistic concurrency checks.mutateQueuedMessagethrough the daemon protocol (schema revision 15, newqueue_message_mutationcapability) and bothDaemonAgentConnectionandInProcessAgentConnection.app.message.dequeuekeybinding with four commands:navigateOlder,navigateNewer,moveEarlier,moveLater; existing user configs are migrated automatically.Macroscope summarized eb16eac.