Cloud chat: a send to a paused machine wakes it and goes once
Goal: in cmux NEXT, sending a message to a chat whose Cloud machine is paused resumes the machine transparently and then delivers the message once. The chat shows a brief "Waking…" state, never an error and never a manual Resume button.
Before
A send to a paused Cloud chat never woke the machine:
- With the link down (the machine paused while the chat was open), the composer's
onSendstops atif (!window.cmuxAcpmuxActions?.["chat.send"]) return falseand the prompt just stays in the composer. If the client was still around,direct.tsrequest()rejects at once on a closed socket (native.not_connected), andjournalsPrompts()is false for a non-local origin, so the prompt comes back with "Not sent". - Only the unavailable card's Resume machine button (
useCloudChatRecovery.resume→cloudMachine.attach, #27031) woke it.
After
cloudWake.tscloudSendWakes: an existing Cloud chat whose link is down, whose machine reads paused, or whose chat is not listed wakes its machine on send.- The prompt leaves the composer into the pane's existing hold (
trustHeld.ts,hold(..., waking)), shown in the composer queue under "Waking the machine…" (composer.heldForWake). It runscloudRecovery.resume()(the same native attach as the card's button, which resumes a paused machine or reconnects a live one). The recovery loop also runs over a transcript that is still showing, and asks the pane to connect again as soon as the machine answers (retryHost), instead of waiting out the reconnect backoff. - When the chat is back (link up, client ready, not paused, listed), the hold sends each prompt exactly once, in order. A wake that fails or runs out (the recovery's existing one-minute bound), or that finds the machine deleted, puts the prompts back in the composer, the same as any failed send.
- The paused card shows only where it did before (an empty or unlisted chat). It no longer covers a transcript that is still showing while the machine wakes.
- Gestures: the wake's
cloudMachine.attachnow uses the gesture's scope-add credit (AgentPaneCloudMachineRequest). That leaves the Enter's grant for the prompt when it goes. The held prompt reserves no ticket, because tickets end with the connection the wake replaces. The card's button still takes one click per attach (existing test unchanged).
Risk: contained to Cloud chats that can't currently be sent to. Local, SSH and live Cloud chats take the old path (cloudSendWakes returns undefined).
Tests
webviews/test/cloud-chat-send-wakes.test.tsx(new): which sends wake. A waking prompt reserves no ticket, waits while held, and goes exactly once when released. A failed wake gives back only the waking prompts and leaves the folder-trust ones. This is red on base:holdhas no waking mode, and there is nowakingAnyortakeWaking.AgentPaneCloudMachineRequestTests.aWakeLeavesTheSendsGrant(new): the attach leaves the send's grant.- Existing:
trust-held-prompts,cloud-chat-resume,cloud-chat-recovery.
Follow-up (not in this PR): when a machine pauses itself under an open chat, the header could say "Sleeping" instead of reconnecting.
Agent session: agent session
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Sending to a paused Cloud chat now wakes the machine and delivers the message once, instead of failing or leaving the prompt stuck in the composer.
- The prompt waits under "Waking the machine…" while the machine resumes, then goes exactly once when the chat is back; a failed or timed-out wake, a deleted machine, or switching to another chat puts it back in the composer.
- The wake's attach takes the Enter gesture's scope-add credit, leaving its grant for the prompt; held waking prompts reserve no ticket.
- Waking prompts stay visible in the queue under the chat's recorded id while it is not listed yet, and the paused card no longer covers a still-showing transcript during the wake.
- Only wakes when the link is genuinely down or the machine is paused (or unlisted); a prompt's own failure on a healthy socket does not. Local, SSH, and live Cloud chats send as before.