Skip to content

fix(coding-agent): send the parked message queue with Enter after an interrupt - #1519

Open
snimu wants to merge 5 commits into
mainfrom
fix/resume-parked-queue-on-empty-submit
Open

fix(coding-agent): send the parked message queue with Enter after an interrupt#1519
snimu wants to merge 5 commits into
mainfrom
fix/resume-parked-queue-on-empty-submit

Conversation

@snimu

@snimu snimu commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

What this does

Fixes the parked-queue trap from discussion #1476: a message queued mid-turn survives Escape/Ctrl+C, but the interrupt suspends the input pump and the idle TUI offered no way to send it. Enter on an empty editor was a no-op, so the queued message looked lost and the session looked frozen; the only recovery was typing a new message or opening the queue editor.

How

Durable-queue direction (option C from the discussion) — the queue stays server-side and the idle UI gains the missing affordance:

  • AgentConnection.resumeQueuedWork(): in-process delegates to AgentSession.resumeQueuedWork(); the daemon connection uses the existing resume_queue protocol command (already served by the daemon and used by update-restart recovery), so no protocol change.
  • Enter on an empty editor while idle with queued messages resumes the parked queue (resumeParkedQueueIfIdle()); no-op while streaming or with an empty queue, errors surface via showError.
  • The queued-messages footer reads ╰─ enter to send · <key> to browse and edit queued messages while idle, and is refreshed on the streaming → idle transition (agent_end).

Tests

  • test/suite/agent-session-parked-queue.test.ts: end-to-end repro — steer mid-turn, abort, queue parked while idle, resumeQueuedWork() drains it.
  • test/interactive-resume-parked-queue.test.ts: the empty-Enter branch (idle+queued resumes; streaming/empty-queue no-ops; errors surfaced).
  • interactive-mode-status.test.ts: footer shows the hint when idle and omits it while streaming.

All focused suites green (333 tests), daemon-mode 198/198, typecheck clean.


Note

Medium Risk
Changes TUI submit behavior (empty Enter now drains a parked queue) and wires a daemon resume_queue command; queue/session input is important but the protocol already existed.

Overview
After an interrupt, queued steering/follow-up messages stay parked because the input pump is suspended. Enter on an empty editor while idle now resumes that queue instead of being a no-op.

Exposes existing session resumeQueuedWork() through AgentConnection (in-process + daemon resume_queue). Empty-queue daemon failures are treated as a no-op; real errors still surface.

The queued-messages footer shows enter to send only while idle, and the hint refreshes on streaming start/end so it stays in sync.

Reviewed by Cursor Bugbot for commit 04fe9b9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Resume parked message queue with Enter after an interrupt in InteractiveMode

  • Adds resumeQueuedWork(): Promise<boolean> to the AgentConnection interface, implemented in daemon-agent-connection.ts (sends a resume_queue daemon command) and in-process-agent-connection.ts.
  • Pressing Enter on an empty editor in interactive-mode.ts now resumes a parked queue via resumeParkedQueueIfIdle() instead of submitting an empty prompt.
  • The queued-messages footer hint gains an "enter to send" prefix while idle, and updatePendingMessagesDisplay() is called on agent_start and run-end transitions to keep the hint in sync.
  • Behavioral Change: Enter on an empty editor no longer does nothing; it resumes queued work. Callers that relied on Enter-on-empty being a no-op should check resumeParkedQueueIfIdle in interactive-mode.ts.
📊 Macroscope summarized 04fe9b9. 41 files reviewed, 7 issues evaluated, 7 issues filtered, 0 comments posted

🗂️ Filtered Issues

.github/workflows/changelog-fragment.yml — 0 comments posted, 1 evaluated, 1 filtered
  • line 54: The additions > 0 check treats a fragment containing only whitespace/newlines as valid. A PR that changes src/ can therefore pass this required check with such a file, but the release path filters it out with !fragment.content.trim() and emits no changelog entry, leaving the source change undocumented (and the empty fragment behind). Validate trimmed file content rather than only its diff line count. [ Out of scope (post-validation triage) ]
packages/coding-agent/docs/mcp-integrations.md — 0 comments posted, 1 evaluated, 1 filtered
  • line 176: Removing the Authoring your own integration, Authentication, and McpIntegration sections leaves their entries in the unchanged table of contents (lines 23–27). Those links now point to nonexistent anchors, so readers following the navigation cannot reach any section. [ Out of scope ]
packages/coding-agent/src/core/mcp/mcp-command.ts — 0 comments posted, 1 evaluated, 1 filtered
  • line 177: validateName accepts constructor, but MCP servers are stored in ordinary object records and looked up with bracket access. When no own constructor server exists, getGlobalMcpServers()?.[name] resolves inherited Object.prototype.constructor, so mcp get constructor reports constructor: undefined, mcp remove constructor reports success without removing a server, and a normal add is incorrectly treated as an existing entry unless --force is supplied. [ Posting failed ]
packages/coding-agent/src/core/mcp/mcp-manager.ts — 0 comments posted, 1 evaluated, 1 filtered
  • line 167: mcp.config returns a user server's config without checking isAuthed() or config.enabled. A model can call mcp.list_tools("disabled-server") directly even though getEnabledGenericServers() omits an { enabled: false } entry; the kernel accepts the returned config and starts the HTTP/stdio server. Thus the documented force-disable flag only hides the server from the prompt and does not actually disable it. [ Failed validation ]
packages/coding-agent/src/core/settings-manager.ts — 0 comments posted, 1 evaluated, 1 filtered
  • line 1229: removeGlobalMcpServer cannot persist a deletion. It marks mcpServers as a nested-field change, but persistScopedSettings copies inMemoryNested[name] into the on-disk object; after delete servers[name] that value is undefined, and JSON.stringify omits it rather than deleting the existing key. Thus removing a server reports success but it reappears after reload/restart. [ Failed validation ]
prime-agent-runtime/src/rlm/mcp.py — 0 comments posted, 2 evaluated, 2 filtered
  • line 323: bind_owner permanently retains the first running event loop even after that loop has been closed. A normal caller using asyncio.run(list_tools(...)) binds the registry to that short-lived loop; every later public call runs on a new loop, reaches _dispatch's unavailable-owner check, and fails with RuntimeError instead of being able to use MCP again. [ Out of scope (triage) ]
  • line 414: Cancelling reload() while _close_name() is awaiting generation.close() leaves the generation in _generations: close() has already set its close event, but cancellation skips the generation.closed check/pop. A subsequent call can acquire the released lock before the shielded lifecycle finishes, see closed == False, and reuse a session that is actively being torn down, causing the call to fail instead of opening a new generation. Ensure cancellation waits for/removes the closing generation before releasing the per-server lock. [ Posting failed ]

Linear: ENG-5304

Escape or Ctrl+C during a turn preserves queued steering/follow-up
messages server-side but suspends the input pump. Nothing in the idle
TUI resumed it: Enter on an empty editor was a no-op and the only
recovery was typing a new message or editing the queue, so the queued
message looked lost and the session looked stuck (discussion #1476).

- AgentConnection gains resumeQueuedWork(); in-process calls the session
  primitive, daemon uses the existing resume_queue command.
- Enter on an empty editor while idle with queued messages resumes the
  parked queue instead of doing nothing.
- The queued-messages footer shows "enter to send" while idle, and
  refreshes on the streaming -> idle transition.
Comment thread packages/coding-agent/src/modes/interactive/interactive-mode.ts
Comment thread packages/coding-agent/src/modes/agent-connection/types.ts
snimu added 3 commits August 18, 2026 13:32
…sume errors, fix doc comment

- agent_start now refreshes the queued-messages footer so the
  enter-to-send hint disappears when a resumed turn starts streaming.
- DaemonAgentConnection.resumeQueuedWork maps only the "No queued work
  to resume" failure to false; other daemon errors propagate so the TUI
  shows them.
- The interface doc now describes the actual return value: whether
  queued input was there to drain.
…ue-on-empty-submit

# Conflicts:
#	packages/coding-agent/CHANGELOG.md
#	packages/coding-agent/src/modes/agent-connection/daemon-agent-connection.ts
#	packages/coding-agent/src/modes/agent-connection/in-process-agent-connection.ts
#	packages/coding-agent/src/modes/agent-connection/types.ts
#	packages/coding-agent/test/agent-connection-daemon.test.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fc23228. Configure here.

Comment thread packages/coding-agent/src/modes/interactive/interactive-mode.ts
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.

1 participant