c0f1f69e16
The h2 data loop parses every frame in a chunk synchronously and starts each handleServerMessage with `void`, so the socket keeps draining while a handler runs. Nothing tracked those promises: after h2Completion the provider went straight to `done`. When an exec request, turnEnded and the stream close arrive in ONE chunk - routine, since the server has no reason to split them - the transport completes while the exec handler (and any onToolResult transformer) is still resolving. The Agent drains its Cursor result buffer on the terminal event, so the result is reserved after the snapshot and never persisted, leaving the synthesized (already resolved) toolCall block unpaired and stripped on replay. Dispatches are tracked in a Set and awaited after h2Completion. Each already swallows its own rejection, so this only waits. The test drives a real h2 server whose final chunk carries readArgs + turnEnded, and releases the handler only once the server flushed its response and the handler is known to be running - no wall-clock delay. Verified it fails 5/5 without the barrier and passes 5/5 with it. A microtask drain in place of the yield does NOT discriminate: the client end handler is IO, not a microtask.