Cloud: relay-first chat in cloud start-chat
Summary
The app and CLI half of the relay-first Cloud chat (cmuxterm-hq#7086; Leo: "it should take <30 seconds from starting a thing to having a response on cloud"). With #35785, a Cloud machine holds a socket to the platform from boot. A prompt now goes to the machine over HTTPS and down that socket, so the first reply never waits for this Mac's link to the machine.
-
cmux cloud relay-chat --target machine:ID --agent A --prompt P [--cwd C] [--prompt-id ID](new actioncloudRelayChat) posts the chat to the platform and reads the turn's NDJSON stream (CmuxNextCloud/API/CloudRelayChat.swift).- It answers when the turn ends:
session_id,reply,stop_reason,first_text_ms,total_ms. - If the turn is still running at 35 s, it answers what has come so far with
ended: false, and the stream stays open until the turn ends (closing it would cancel the turn on the machine).
- It answers when the turn ends:
-
delivered: falsemeans the prompt certainly never reached the machine. That covers three cases:- the platform refused it;
- no machine connected within the 30 s the request asks for (
start_within_ms); - the run was refused before sending (an agent's run, a bad target or argument).
The platform alone decides whether a chat was sent, so a resend never races a send.
-
cmux cloud start-chat --waittries the relay first and falls back tostart-sessionand the link only whendeliveredis false or the app has norelay-chat. Any other failure, including a broken stream or a late answer, is reported with how to see the machine's sessions. It is never sent again.- A turn that is still running is waited on in its session with the existing poll loop, and the relay time counts against
--timeout. - Each call carries its own
--prompt-id.
- A turn that is still running is waited on in its session with the existing poll loop, and the relay time counts against
-
An agent's run is refused by
relay-chatand goes thestart-sessionway, which asks the person. -
The App lines this adds come with
plans/cmux-next/arch-exceptions/cloud-relay-chat-7086.json. The binding needs App-only types; the logic is in CmuxNextCloud.
Depends on, at runtime: manaflow-ai/cmux#35785 (the machine side) and the platform relay route. Until both are deployed, start-chat falls back to today's path.
Changelog
none
Testing
Observed / Not run
- Observed: Swift suite
CloudRelayChatTests(stream parsing, delivery, the whole reply indone) and the cmux-tuicloud_start_chatunit tests are added. Both will run through the fleet after this push, and the results will be posted here. - Independent review subagent (correctness first): SHIP at 093c46fe123ce5d51c7670e226588bd0b458c99e. The delta since is the arch exception JSON and one message string.
- Review (Codex, independent model family): SHIP at f82f5996 (the broken-stream fix).
- Review (independent review subagent, delta c9ad820..279baa0: translations, then a "safe:" send-path cap allowlist entry for the 64 KiB refusal-body read): SHIP at 279baa03a52f6583a006f6c1b6b0a0b130d958ee
- Guard group compared against base cf49e02: the one new red (send-path caps) is fixed in 279baa0; the other 3 are red on the base too.
- Not run: an end-to-end chat against prod. That needs the platform route deployed and a machine image with #35785; it is tracked in cmuxterm-hq#7086.
Agent session: agent session
๐ค Generated with Claude Code
![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
Implements the relay-first Cloud chat: a prompt now goes over HTTPS to the platform and down the socket the machine holds from boot, so the first reply no longer waits for a link to the machine.
- New
cmux cloud relay-chataction posts the chat and streams the turn's NDJSON back, answering withsession_id,reply,stop_reason,first_text_ms, andtotal_ms. cmux cloud start-chat --waittries the relay first and falls back tostart-sessionand the link only whendeliveredis false or the app has norelay-chat; a prompt over 96 KiB goes the link path, which uploads it.--timeoutcounts from the start of the relay attempt, and with less of it left than the relay's answer window the link path runs to keep to it.delivered: falsemeans the prompt certainly never reached the machine (the platform refused it, no machine took it within 30s, or the run was refused). A stream that breaks once the platform accepted the chat counts as delivered โ it may be on the machine โ so it and any other failure are reported with how to check the machine's sessions, never resent.- A turn still running after 35s answers with
ended: falseand keeps its stream open โ closing it would cancel the turn on the machine. - An agent's run is refused by
relay-chatand goes thestart-sessionway, which asks the person. - A person's palette run waits for the whole turn and shows the reply in a sheet; a refusal reaches the person directly, while the CLI and MCP keep the result fields.
- Each call carries its own
--prompt-idso a resend runs only once. - The relay request carries the bearer and the prompt, so it uses the redirect-refusing owner transport, not the machines client's session.
- Until the machine-side socket and the platform relay route are deployed,
start-chatfalls back to today's path.