mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
5.9 KiB
5.9 KiB
Plan: openhuman tui — feature-gated terminal chat UI
Historical v1 plan. The shipped CLI now extends this foundation into a Logs-first four-tab UI (Logs, Chat, Config, Settings). Bare
openhumanauto-launches only with terminal stdin/stdout on a non-container host;--no-tuisuppresses that default and explicitopenhuman tuistill forces the UI. Config uses curated safe getters/updaters, and Settings uses registered auth controllers.
Goal
Running openhuman-core tui (alias chat) opens a ratatui-based terminal UI that is an
interface into the general chat (the same web_chat surface the desktop app uses),
gated behind a Cargo feature tui.
Architecture decisions (grounded in current code)
- Cargo feature:
tui = ["dep:ratatui", "dep:crossterm"], added todefault(repo convention: gates default-ON). It must NOT ship in the desktop app — add'tui': 'Terminal UI subcommand; the desktop app ships its own Tauri UI.'toINTENTIONALLY_NOT_FORWARDEDinscripts/ci/check-feature-forwarding.mjs. - Module: crate-level
src/tui/, gated at its declaration insrc/lib.rs:mod.rsalways compiled; real submodules#[cfg(feature = "tui")];#[cfg(not(feature = "tui"))] mod stub;exposing the samerun_from_cli.stub.rsrun_from_clibails with"tui feature disabled at compile time … rebuild with --features tui"(mirrorsrc/openhuman/mcp_server/stub.rs:42).- No controllers, no agent tools, no
all.rschanges (leaf client, likeflows' philosophy: absence, not degraded registration — but here the only outside touch-point is the CLI arm, which uses the stub for a build-fact error).
- CLI arm: in
src/core/cli.rsmatch (~line 63), add"tui" | "chat" => run_tui_from_cli(&args[1..]). Arm stays un-cfg'd (mcp precedent). Add"tui" | "chat"to the banner-suppressionmatches!at lines 48–50 (a TUI must own the terminal). - In-process core, no HTTP: build a multi-thread tokio runtime with
AGENT_WORKER_STACK_BYTESstack (copyrun_server_commandshape, cli.rs:219–311), thenCoreBuilder::new(HostKind::Cli).domains(DomainSet::full()).services(ServiceSet::none()).build().channel.web_chatneedsDomainGroup::Channels, soharness()is not enough. - Chat flow:
client_id = "tui-<random hex>".- Threads via
runtime.invoke("threads.list"| "threads.create_new", …); CLI flags:--thread <id>,--new(default: create new thread). - Send turn:
runtime.invoke("channel.web_chat", {client_id, thread_id, message, …})(schema:src/openhuman/web_chat/schemas.rs:45; ops entryops.rs:1199/start_chatat 391). - Stream: drain
web_chat::subscribe_web_channel_events()(broadcast bus,src/openhuman/web_chat/event_bus.rs:14), filter by ourclient_id. Rendertext_delta/thinking_delta(delta,delta_kindfields onWebChannelEvent,src/core/socketio.rs:98), showtool_call/tool_resultas status lines, finish onchat_done(usefull_responseas authoritative final text) orchat_error(showmessage). - Cancel in-flight turn:
channel.web_cancelon Esc.
- UI v1 scope (keep it tight):
- Alternate screen + raw mode; transcript viewport with scrollback (PgUp/PgDn/mouse optional), single-line input box, status bar (thread id, model/turn state, key hints), spinner while streaming.
- Keys: Enter=send, Esc=cancel turn, Ctrl+N=new thread, Ctrl+C/Ctrl+D=quit.
- Distinguish user / assistant / thinking (dim) / tool activity / errors. Ocean-ish accent per design tokens is fine but keep it terminal-native.
- Terminal hygiene (critical):
- Panic hook + Drop guard that restores the terminal (leave raw mode, LeaveAlternateScreen) before the panic message prints.
- Logging must not hit stdout/stderr while the TUI owns the terminal —
inspect how
run_from_cli/run_server_commandinit logging (src/core/logging.rs) and route core logs to file only (or suppress console) for the tui arm. Core boot logs corrupting the UI is a bug.
- Separation for testability: pure state module (
transcript.rsorstate.rs) holding a reducerapply_event(&mut TranscriptState, &WebChannelEvent)with no terminal deps — unit-testable. Rendering (render.rs) and event loop (app.rs) stay thin.
Tests / verification (definition of done)
- Unit tests for the reducer: text_delta accumulation, thinking vs text separation, chat_done replaces with full_response, chat_error, ignores other client_ids, tool_call/result lines.
src/core/cli_tests.rs:tui_subcommand_reports_disabled_build_when_gate_off(+chatalias) under#[cfg(not(feature = "tui"))], mirroring the mcp tests (assert error contains "tui feature disabled" and NOT "unknown namespace").- Builds (Apple Silicon: prefix
GGML_NATIVE=OFF):cargo check --manifest-path Cargo.tomlcargo check --no-default-features --features tokenjuice-treesitter(disabled build)cargo test --lib core::cliand the tui module tests, both feature directions:cargo test --lib --no-default-features --features tokenjuice-treesitter core::
node scripts/ci/check-feature-forwarding.mjspasses with the allowlist entry.cargo fmtclean.
Docs
- AGENTS.md: add
tuirow to the feature table + a short gate section (leaf-ish gate, shedsratatui+crossterm, intentionally not forwarded to desktop). src/openhuman/about_app/: add user-facing feature entry for the terminal chat UI.
Non-goals (v1)
- No thread-picker UI beyond
--thread/--newflags, no markdown rendering, no image/artifact display, no approval-request interaction (surface as a status line telling the user to handle it elsewhere), no remote-core (HTTP) mode.
Workflow
Small focused commits on feat/tui-chat in this worktree; don't push.
Debug logging with [tui] prefix on all state transitions.