The stop button called aiCancelStream -> notify_one(), but the four
stream_*_with_tools SSE loops only `break`ed and returned Ok(token_usage)
when cancelled. run_agent_loop treated that as a normal completed turn and
kept going (often into contract-repair or the next turn), so isGenerating
stayed true and a single click did not stop the agent.
Return Err(AGENT_CANCELLED_ERROR) from the cancel branch so the existing
Err-cancel match arm sets loop_exit = Cancelled and stops the loop cleanly,
preserving whatever was streamed so far.
Additional:
- Promote AGENT_CANCELLED_ERROR to a shared `pub const` in ai.rs; agent_loop
now references ai::AGENT_CANCELLED_ERROR.
- Check cancellation before executing requested tools, so a stop landing
between stream-end and tool execution is honored.
- Wire cancellation into the text-only fallback (previously `_cancelled`,
ignored) via tokio::select! racing ai::complete against cancelled.notified().
Known limitation left: in-flight tool execution (e.g. execute_query) is
still not interruptible mid-call.