- ConsoleInputPump gains an optional ILogger so the reader-fault and
command-dispatch catch blocks log warnings/errors again, matching the
behavior the old ConsoleInputHandler had before the pump refactor.
- Remove ConsoleInputPump.Stop() — dead code, nothing calls it; the reader
thread already terminates on EOF/Core.Closing/finally cleanup.
- GetInputCommand no longer clears the help-description cache on every
lookup; RegisterCommand/UnregisterInputCommand still invalidate it
correctly on the paths that actually change the command set.
ConsoleInputHandler.Initialize() now checks Core.Headless and skips
starting the input thread entirely when stdin isn't a TTY, logging
instead. When not headless, it delegates the read loop to
ConsoleInputPump (Task 2) rather than running its own async void
loop with a two-AutoResetEvent handshake.
ReadLine() throws HeadlessConsoleInputException when headless, falls
back to a direct Console.ReadLine() during early startup or after the
pump loop has ended, and otherwise blocks on the pump.
Wrap the Run() loop in try/finally so cleanup (_running = false;
ReleasePendingPrompt(null)) always executes, even if the injected
lookup throws. Previously a throwing lookup escaped the loop and left
any pending ReadLine() caller blocked forever. Also fold the lookup
call into the same try/catch as the handler invocation so a bad
command is swallowed without tearing down the reader loop.
Document via XML comments that ReadLine() supports a single sequential
caller (not concurrent callers) and that command handlers dispatched
by Run() execute on the reader thread and must not call ReadLine().
Add a regression test proving a throwing lookup neither hangs a
pending prompt nor kills the reader loop. Also drop an unused
System.Text using from the test file.