12 tests using testEffect + TestLLM pattern (no HTTP server):
- loop exits on stop finish, calls LLM for new messages, continues on tool-calls
- loop sets status busy→idle
- cancel interrupts cleanly, returns last assistant, records MessageAbortedError
- cancel with queued callers resolves all cleanly
- concurrent callers get same result via Deferred queue
- concurrent callers receive same error on failure
- assertNotBusy throws BusyError when running, succeeds when idle
- shell rejects with BusyError when loop running