cmux NEXT agent pane: a prompt a dropped connection lost goes again, no "Not sent" (lost-prompt replay on)

Merged
TE
4d ago
+157-22

Leo, about a chat that said "Not sent: The agent connection was interrupted. Read its saved state before retrying." while the agent was alive:

why does it say it's not responding? also you previously weren't running???

Leo-ask: ask-7877c9aa95

What was wrong

When acpmux's connection drops while a prompt is still on its way (an acpmux restart or a daemon update mid-send, a reset socket), the pane's pending session/prompt is rejected with "The agent connection was interrupted". The agent itself was fine. Then:

  • A prompt its composer still held (a new chat, the Quick Composer, a View Only chat) showed "Not sent: The agent connection was interrupted. Read its saved state before retrying." at once and stayed in the composer.
  • A started chat's prompt left the transcript and silently came back to the composer, so it had to be sent again by hand.

#23027 built the fix's parts: the host's prompt journal sends such a prompt again on the next connection, under its promptId and marked resend so acpmux runs it at most once, and the page has hooks to wait for it. But the replay stayed behind a Debug toggle that was off, and the page never waited. The other line in Leo's screenshot, "Permission groups are disconnected. Refresh before answering.", is already gone on base. #23027 made the permission panel wait quietly for the reconnect attach, and that string has no callers left.

What changes

  • AgentPaneTunables.lostPromptReplay is on by default (schemas/defaults/cmux-next.json follows). The host journal sends a prompt the daemon dropped with its connection again on the next one. The Debug toggle stays, so it can be turned off.
  • direct.ts: a prompt lost with its connection before acpmux took it, on a connection to this Mac's acpmux and small enough for the journal (8 MiB), carries lostPrompt {sessionId, promptId}. A held sender's immediate "Not sent" notice is skipped for it.
  • lostPrompt.ts: resentAfterLoss waits on the pane's next connected client for the prompt to show there. It reuses the existing waitForResend and awaitResent, waiting 30 s for a connection and 10 s for the prompt to show.
  • App.tsx: the established-chat send and the held send both wait for that before giving the prompt back. If it shows, the send counts as delivered (promptLanded), and the held composer lets go of it. Only a prompt that never shows comes back to the composer, with the "Not sent" line, the same way as before.

No new Swift logic: the Swift change is a default flip and two comments.

Risk

This changes how an agent-pane send recovers from a lost connection. When the wait gives up (no reconnect within 30 s) and the journal later sends the prompt after all, a person who edits the returned prompt and sends it gets a new promptId. That is the same edge #23027's reviews found. It is an edge of the new behaviour, filed as a follow-up. A remote-origin connection, which has no journal, and a prompt over 8 MiB keep today's behaviour.

Tests

  • lostPrompt.test.ts adds a prompt the connection lost before acpmux took it waits for the host's resend, never 'Not sent' at once. It uses a mock acpmux whose socket drops as the prompt reaches it. With base direct.ts swapped in, it fails (lostPrompt undefined; the notice is drawn). With this change, it passes.
  • resentAfterLoss is covered for each outcome: the prompt shows, a connection comes back but the prompt never shows, no connection comes back, and a refusal that is not a lost prompt.
  • Fleet fast checks at 71a596db: webview lint 0/0, typecheck clean, fmt clean. lostPrompt.test.ts, agent-pane-connect-disconnect.test.ts and src/agent-session/acpmux/conversation give 761 pass and 1 fail. The failure is BackgroundMark.test.tsx "only Open in terminal…", which this PR does not touch.
  • cmux-ci compile-check --app at 1d5a4d1c passed. The Swift diff is the same at the head. The existing AgentPaneUnsentPromptTests replay test runs both arms of the toggle.

Agent session: agent session

šŸ¤– Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes a lost agent-pane prompt showing "Not sent: The agent connection was interrupted" while the agent was fine. When acpmux's connection drops mid-send (a restart, an update, a reset socket), the host's prompt journal now sends the prompt again on the next connection and the pane waits for it to show there instead of saying "Not sent" at once. The lostPromptReplay debug toggle is now on by default.

  • A held send (a new chat, the Quick Composer, a View Only chat) waits 30 s for a reconnect and 10 s for the prompt to show; only one that never shows says "Not sent" and comes back to the composer.
  • A started chat's lost prompt is treated the same and no longer silently returns to the composer.
  • Remote-origin connections and prompts over 8 MiB are not journaled and keep today's "Not sent" behavior.
  • Known edge: if the wait gives up but the journal sends the prompt later, editing the returned prompt and re-sending it creates a new promptId (filed as a follow-up).