When the relay drops mid-request, the prompt_async body frames can be
lost. The tunnel host forwarded the request to loopback as an
empty/truncated chunked body, which the server rejects with a bare 400
(empty response body) — the mobile app's 'Failed to send message (400)'.
Host now buffers request bodies (<512KB) and forwards the complete body
only once StreamEnd arrives; larger bodies still stream live. A new
hasBody flag on the request head lets the host detect a body that
delivered zero frames and abort it as an ambiguous transport failure
(which the client already retries) instead of forwarding an empty body.
A 15s body-delivery deadline converts stalled tunnels into clean aborts.