Cloud chat: a send to a paused machine wakes it and goes once

Merged
TE
4d ago
+290-23

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 onSend stops at if (!window.cmuxAcpmuxActions?.["chat.send"]) return false and the prompt just stays in the composer. If the client was still around, direct.ts request() rejects at once on a closed socket (native.not_connected), and journalsPrompts() 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.ts cloudSendWakes: 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 runs cloudRecovery.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.attach now 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: hold has no waking mode, and there is no wakingAny or takeWaking.
  • 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
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.