supercode-harness 0.4.12

The optional native Supercode agent and tool harness
Documentation
//! P5-4 (COMPOSABLE-HARNESS-DESIGN.md §2 module 30 `tui`; §1.9 recorded
//! deviation; §2.1 `tools.question`/`permissions.approvals`(ask-UI) →
//! `tui`|`server`): the full-screen interactive TUI, AND — because §2.1
//! names it as the interactive surface three EARLIER phases explicitly
//! deferred here — the home for the three handlers that close those
//! deferred chains:
//!
//! 1. **P5-1's approval ask-UI** — [`handlers::TuiApprovalHandler`]
//!    implements `crate::permissions::PermissionsApprovalHandler`.
//! 2. **P5-2's elicitation prompt** (+ OAuth device-code display) —
//!    [`handlers::TuiElicitationHandler`] implements
//!    `crate::mcp::McpElicitationHandler`;
//!    [`bridge::PendingOAuthDisplay`] carries the device-code modal push.
//! 3. **P5-3's parent-answerable child approvals** —
//!    [`handlers::TuiChildApprovalHandler`], installed via
//!    `crate::agent::Agent::set_child_approval_handler_factory` (P5-4's own
//!    new seam — see that method's doc comment for why this can only ever
//!    grant what the rule engine already routed to a prompt, never
//!    escalate past a `Deny`).
//!
//! ## Two layers (why this crate only has ONE of them)
//!
//! A real terminal can't be driven headlessly in a unit test — so this
//! module is split in two, and only the TESTABLE half lives here:
//!
//! - **The view-model core** ([`state`], [`key`], [`keymap`], [`theme`],
//!   [`history`], [`bridge`], [`handlers`]) — a pure `handle_key(KeyEvent)
//!   -> Vec<Action>` / `apply(Action)` state machine plus the three
//!   interactive handlers above. Zero terminal-library dependency (no
//!   `ratatui`/`crossterm` in `crates/harness`'s `Cargo.toml` at all — see
//!   [`key::KeyEvent`]'s doc comment) — every interaction in this crate is
//!   unit-testable by constructing a [`key::KeyEvent`] by hand.
//! - **The render + event-loop layer** lives in `crates/cli/src/tui/` (a
//!   binary-crate concern: `ratatui` + `crossterm`, an alternate-screen
//!   terminal, real keyboard/paste input). It translates real
//!   `crossterm::event::KeyEvent`s into [`key::KeyEvent`], drives
//!   [`state::TuiState`], and renders the result — see that module's own
//!   doc comment for the render loop and its `TestBackend` smoke test.
//!
//! ## Activation
//!
//! `Config::tui_enabled` (`capabilities.tui.enabled`, default `false`) is
//! the master gate — `crates/cli`'s `chat()` runs the pre-P5-4 rustyline
//! REPL loop byte-for-byte when it's off, or when stdin/stdout/stderr
//! aren't all a real tty (`--print`, a piped/non-interactive invocation,
//! or any headless test harness) — see [`should_activate`].
//!
//! ## Shippable-complete vs honestly-staged
//!
//! - **Shippable-complete:** the view-model core (composer editing,
//!   scrollback, streaming accumulation, the three interactive-handler
//!   modals, theme toggle, configurable global keybindings, cross-session
//!   Ctrl+R prompt-history search, BASIC vim emulation), the render/
//!   event-loop layer, image-paste passthrough — a placeholder token +
//!   [`state::TuiState::pending_images`], drained by `crates/cli`'s
//!   render loop via [`state::TuiState::take_pending_images`] and routed
//!   into the turn as real multimodal content (`Agent::send_with_images`)
//!   — external-`$EDITOR` invocation.
//! - **Honestly staged, not half-built:**
//!   - FULL vim emulation (registers, visual mode, `.`-repeat, counts,
//!     `dd`/`yy`/`p` as real two-keystroke commands rather than the
//!     single-keystroke approximation
//!     [`state::TuiState::handle_key`]'s vim-normal-mode match
//!     implements) — see [`state::VimMode`]'s doc comment.
//!   - OAuth device-code display (`bridge::TuiBridge::oauth_sender` /
//!     [`bridge::PendingOAuthDisplay`] / `Action::ShowOAuthModal`): the
//!     plumbing is complete and tested end-to-end (push → modal → render
//!     → dismiss), but `crates/cli` has no call site that ever SENDS into
//!     it — `attach_mcp`'s OAuth handling (`resolve_oauth_header`) only
//!     ever consults an ALREADY-stored, possibly-refreshed token; a fresh
//!     device-code flow (`mcp_oauth::run_device_flow`) only ever runs via
//!     the separate `supercode mcp login <name>` subcommand, which never
//!     activates the TUI. So a TUI session currently never has a moment
//!     where a device code needs displaying at all — wiring one in would
//!     mean teaching `attach_mcp` to trigger a live, session-startup-
//!     blocking device-code flow when no cached token exists, a real
//!     interactive-flow design decision (timeout? cancel-and-continue
//!     without the server? non-interactive callers?) out of scope for
//!     this module's own render-loop-and-handlers job. Kept (not removed)
//!     as the ready seam for whichever future unit makes that call —
//!     zero cost while unused, the same "installing a handler alone
//!     changes nothing" posture every other optional seam in this crate
//!     has. Cited as a follow-up for this module, not a broken partial
//!     implementation — same posture as the vim item above.

pub mod bridge;
pub mod handlers;
pub mod history;
pub mod key;
pub mod keymap;
pub mod state;
pub mod theme;

pub use bridge::{
    PendingApprovalRequest, PendingChildApproval, PendingElicitation, PendingOAuthDisplay,
};
pub use handlers::{TuiApprovalHandler, TuiBridge, TuiChildApprovalHandler, TuiElicitationHandler};
pub use history::PromptHistory;
pub use key::{Key, KeyEvent};
pub use keymap::{Keymap, KeymapAction};
pub use state::{
    Action, HistorySearchState, InputFocus, Modal, Role, StatusLine, TranscriptEntry, TuiState,
    VimMode,
};
pub use theme::Theme;

/// Whether the TUI should activate for this process: `config.tui_enabled`
/// AND stdin/stdout/stderr are all a real terminal. `crates/cli`'s `chat()`
/// is the sole call site that matters for the "default-off / non-tty /
/// `--print` = byte-identical REPL" contract (P5-4's build brief) — a
/// piped/non-interactive invocation (virtually every CI/test run,
/// regardless of whether a parity preset sets `capabilities.tui.enabled =
/// true`) always falls through to the pre-P5-4 REPL because THIS check
/// fails, not because the config bit is off.
///
/// Takes the three tty booleans as explicit parameters (rather than
/// calling `std::io::IsTerminal` itself) so this stays testable without a
/// real terminal — `crates/cli` passes
/// `io::stdin().is_terminal()`/`io::stdout().is_terminal()`/
/// `io::stderr().is_terminal()` at the real call site.
pub fn should_activate(
    tui_enabled: bool,
    stdin_tty: bool,
    stdout_tty: bool,
    stderr_tty: bool,
) -> bool {
    tui_enabled && stdin_tty && stdout_tty && stderr_tty
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn should_activate_requires_the_config_bit_and_every_tty_check() {
        assert!(should_activate(true, true, true, true));
        assert!(!should_activate(false, true, true, true));
        assert!(!should_activate(true, false, true, true));
        assert!(!should_activate(true, true, false, true));
        assert!(!should_activate(true, true, true, false));
    }
}