In-process test double for ClaudeWrapper.DuplexSession.Adapter.
Instead of a claude subprocess, the transport is a lightweight
controller process the test drives directly. The controller is created
per session and passed in via :controller in the session's
:adapter_opts; because nothing is shared between sessions, concurrent
(async: true) tests cannot collide -- no global registry, no extra
dependency.
Drive it through ClaudeWrapper.Test rather than calling this module
directly.
Protocol
The controller accepts:
{:emit, lines}-- send each line to the session as a{handle, {:data, line}}message (NDJSON the session parses){:set_reply, lines}-- queue a one-shot canned reply emitted on the next outboundcommand/2(a faked turn response){:exit_status, code}-- send the session{handle, {:exit_status, code}}(simulate the subprocess exiting):close-- stop
and, on open/1, is told its :owner (the session pid) and linked to
it so a session crash tears the controller down and vice versa.