Commit 36b0296 changed expert speech generation from gateway.chat()
(non-streaming) to gateway.chat_stream() (streaming) for progressive
UI output. However, DashScope/DeepSeek via LiteLLM occasionally accept
the streaming request but never emit the first SSE chunk — the async
generator hangs indefinitely with no error and no timeout, freezing
the entire board.
Root cause: `async for chunk in gateway.chat_stream(...)` blocks
forever when the provider silently stalls. The moderator path
(gateway.chat) still works because it's synchronous — that's why
the moderator opening always succeeds but experts hang.
Fix: pull the first chunk via `__anext__()` wrapped in
`asyncio.wait_for(timeout=30s)`. If no chunk arrives within 30s,
close the stream and fall back to non-streaming gateway.chat() +
_replay_stream() (which splits the response into small chunks for
progressive UI rendering). This preserves the "逐个输出" UX while
guaranteeing the board never hangs on a stalled streaming provider.
Add 4 unit tests covering:
- Normal streaming works (existing path)
- First-chunk timeout → fallback to chat()
- Empty stream → fallback to chat()
- chat_stream raising → fallback to chat()
E2E verification: 5-expert board with DashScope completes in ~277s
(status=completed), all experts produce content, no hang.
Same branch as the earlier _handle_llm_gateway fix (commit d0fe661).