diff --git a/docs/plans/2026-09-07-headless-console.md b/docs/plans/2026-09-07-headless-console.md new file mode 100644 index 00000000..04aece66 --- /dev/null +++ b/docs/plans/2026-09-07-headless-console.md @@ -0,0 +1,68 @@ +# Headless console — an interactive CLI for the bot host + +Date: 2026-09-07 +Status: ACTIVE (owner direction 2026-09-07: "the headless client should have +a CLI as well. Like we have the chat loaded in headless so we can see what it +does and we can talk via it if we want and control plugins like /moss bla or +/say hello") +Belongs with the "one plugin, two hosts" model recorded in +`docs/plans/2026-09-06-campaign-vt-vtank-oracle.md`. Independent of Campaign +VT's slices (host side only) — runs in parallel. + +## What it is +When the headless host runs with a terminal attached (or `--console` / +`ACDREAM_HEADLESS_CONSOLE=1`, documented in `docs/launch-options.md`), it +becomes a chat-shaped REPL for the session: + +- **Output.** The ordered event stream rendered one line per event, chat + first-class: `[Tell] Bob: hi`, `[Fellowship] …`, `[Local] …`, system text, + plugin output (everything a plugin writes through the host log/chat sink), + and lifecycle/command outcomes (`connected`, `entered world`, `portal → + Holtburg`, `command rejected: …`). Same channel names and ordering the + graphical SpewBox shows; no colors required (ANSI channel colors only when + the terminal supports them, off when piped). +- **Input.** Each typed line goes through the SAME command pipeline the + graphical chat box uses: a `/`-prefixed line is dispatched to the plugin + command registry first (`/vt …`, any plugin verb) and otherwise to the + client/server slash commands (`/say`, `/tell`, `/t`, `/f`, `/a`, emotes, + `/help`), a plain line is `/say`. No second parser, no bot-only shortcuts: + it must reuse the exact seam the retained chat box calls (find it — the + CH-campaign command registry and the chat-send route), through the + Runtime command surface Slice K already exposes ("chat and slash/server + commands"). +- **Control.** `/quit` (graceful logout, exit 0), `/status` (session + generation, position, macro state if the plugin reports one). Ctrl+C keeps + today's graceful behavior. +- **Multi-session.** Out of scope for the first cut: the console attaches to + the single-session host (K1's production mode). A multi-session prefix + (`@name …`) is a later addition; leave the seam shaped for it (input routed + through a session-scoped adapter, not a static). + +## Rules +- Headless stays Runtime-only: no App references, no rendering types. If the + chat-send or command-dispatch seam currently lives in App, lift the + presentation-free part into Runtime/Core with a dependency guard, and leave + the graphical client calling the same lifted code (behavior-preserving). +- Reads of stdin never block the scheduler: a reader thread feeding a + thread-safe queue drained on the session tick (Slice K's monotonic + scheduler contract). +- Every line of output goes through one renderer over the typed event stream + — the same events the K2 bot API emits — so tests assert on the rendered + text without a terminal. +- Portable: Windows and Linux; no ANSI when `Console.IsOutputRedirected`. + +## Tests (Headless test project, no live server) +- Scripted stdin → rendered stdout: chat lines, a plugin command reaching + the plugin's registered verb, `/say` producing the same outbound message + the graphical route produces (assert on the command the fake Runtime + received), plain text → say, `/quit` → graceful teardown, unknown verb → + the same error text the chat box prints. +- Reader-thread/scheduler test: input queued while the tick is busy is + drained in order on the next tick, never on the reader thread. +- Launch-options doc row + the documentation test. +One connected proof at closeout (real ACE, `testaccount`, type `/say hello` +and see it echo; type `/vt start` with MossTank loaded) — the owner may run +it or the lead may, it is not a visual gate. + +## Ledger +- 2026-09-07 planned; implementer dispatched.