Enhance tool documentation and add new features
- Added module-level documentation for memory tools (`remember`, `recall`, `forget`) to clarify their purpose. - Improved documentation in `recall.rs` and `remember.rs` to describe the functionality and flow of memory entry operations. - Updated `mod.rs` to include descriptions for the tool trait and execution context. - Enhanced `plan.rs` with detailed comments on plan-mode signaling tools. - Documented text search tools in `search.rs` to explain their functionality. - Improved sequential-thinking tool documentation in `seqthink.rs`. - Added safety filter documentation in `shell_filter` for credential and git operations. - Enhanced utility tools documentation, including `cd`, `dir_cache_update`, and `todowrite`. - Improved rendering documentation in view modules (`chat`, `markdown`, `status`, `workflow`) to clarify rendering flows and purposes.
This commit is contained in:
+102
@@ -1,3 +1,10 @@
|
||||
//! Zesdex binary entry point.
|
||||
//!
|
||||
//! Parses `--daemon` / `--attach <id>` flags to select one of three
|
||||
//! process modes (single-process TUI+agent, background daemon, or
|
||||
//! attach-only TUI client), sets up file logging, and runs the
|
||||
//! corresponding event loop.
|
||||
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::sync::Mutex;
|
||||
@@ -17,6 +24,16 @@ mod tool;
|
||||
mod resources;
|
||||
mod view;
|
||||
|
||||
/// Process entry point: parse CLI flags, initialize logging, then dispatch
|
||||
/// to single-process, daemon, or attach mode.
|
||||
///
|
||||
/// Flow: parse `--daemon`/`--attach <id>` from argv → create/open the log
|
||||
/// file under the platform data dir (falling back to `/dev/null` if that
|
||||
/// fails, so a broken log path can't crash the TUI) → init tracing →
|
||||
/// reject `--daemon` + `--attach` together → dispatch.
|
||||
///
|
||||
/// Why: logging is routed to a file (never stderr/stdout) because writing
|
||||
/// to the terminal while ratatui owns the alternate screen corrupts the UI.
|
||||
fn main() -> Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let is_daemon = args.iter().any(|a| a == "--daemon");
|
||||
@@ -61,6 +78,17 @@ fn main() -> Result<()> {
|
||||
run_single_process()
|
||||
}
|
||||
|
||||
/// Run zesdex as a self-contained TUI + agent loop in one process.
|
||||
///
|
||||
/// Flow: create the store, a fresh session dir, and take an exclusive
|
||||
/// session lock → build `AppStateRest` → enter raw mode / alternate
|
||||
/// screen → run the event loop → always restore the terminal (even on
|
||||
/// error) → save settings and release the session lock.
|
||||
///
|
||||
/// Why: the session lock prevents two zesdex processes from concurrently
|
||||
/// writing the same session directory. Terminal restoration happens
|
||||
/// outside `run_loop`'s `Result` so a panicking/erroring loop still
|
||||
/// leaves the user's terminal usable.
|
||||
fn run_single_process() -> Result<()> {
|
||||
let store = model::store::Store::new();
|
||||
store.ensure_dirs()?;
|
||||
@@ -110,6 +138,11 @@ fn run_single_process() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Map a `crossterm` key code to the wire-serializable `KeyAction`, for
|
||||
/// sending key input from an attached client to the daemon.
|
||||
///
|
||||
/// Return: `None` for key codes with no `KeyAction` equivalent (e.g.
|
||||
/// media keys), which are silently dropped.
|
||||
fn key_code_to_action(code: crossterm::event::KeyCode) -> Option<ipc::protocol::KeyAction> {
|
||||
use crossterm::event::KeyCode;
|
||||
match code {
|
||||
@@ -132,6 +165,9 @@ fn key_code_to_action(code: crossterm::event::KeyCode) -> Option<ipc::protocol::
|
||||
}
|
||||
}
|
||||
|
||||
/// Inverse of `key_code_to_action`: reconstruct a `crossterm::KeyCode`
|
||||
/// from a `KeyAction` received over IPC, for replaying it into the
|
||||
/// daemon's normal key-handling path.
|
||||
fn key_action_to_code(action: &ipc::protocol::KeyAction) -> crossterm::event::KeyCode {
|
||||
use crossterm::event::KeyCode;
|
||||
match action {
|
||||
@@ -153,6 +189,15 @@ fn key_action_to_code(action: &ipc::protocol::KeyAction) -> crossterm::event::Ke
|
||||
}
|
||||
}
|
||||
|
||||
/// Flatten the daemon's `AppStateRest` into a `StatePayload` and send it
|
||||
/// to the attached client as a `DaemonFrame::StateUpdate`.
|
||||
///
|
||||
/// Flow: map transcript messages/toasts to their wire DTOs → derive the
|
||||
/// active overlay name (or `None` if no overlay is active) → build and
|
||||
/// send one `DaemonFrame`.
|
||||
///
|
||||
/// Why: the client never shares memory with the daemon, so every action
|
||||
/// on the daemon side is followed by a full state push rather than a diff.
|
||||
fn send_daemon_update(conn: &mut ipc::conn::Connection, state: &app::state::rest::AppStateRest) -> Result<()> {
|
||||
use ipc::protocol::{DaemonFrame, MessageEntry, ToastEntry, StatePayload};
|
||||
|
||||
@@ -194,6 +239,17 @@ fn send_daemon_update(conn: &mut ipc::conn::Connection, state: &app::state::rest
|
||||
conn.send(&frame)
|
||||
}
|
||||
|
||||
/// Apply a `StatePayload` received from the daemon onto the client's
|
||||
/// local `AppStateRest`, so the attach-mode TUI can render it.
|
||||
///
|
||||
/// Flow: copy scalar fields directly → rebuild the transcript cache from
|
||||
/// `MessageEntry`s (mapping role strings back to the `Role` enum) →
|
||||
/// resolve the overlay name string to an `Overlay` variant → rebuild
|
||||
/// toasts from `ToastEntry`s.
|
||||
///
|
||||
/// Why: unrecognized role/overlay/toast-kind strings fall back to a safe
|
||||
/// default (`Role::User`, `Overlay::None`, `ToastKind::Info`) rather than
|
||||
/// panicking, so a protocol/version mismatch degrades gracefully.
|
||||
fn apply_client_update(
|
||||
state: &mut app::state::rest::AppStateRest,
|
||||
payload: ipc::protocol::StatePayload,
|
||||
@@ -260,6 +316,20 @@ fn apply_client_update(
|
||||
state.input.cursor = payload.input_cursor;
|
||||
}
|
||||
|
||||
/// Run zesdex as a background daemon: owns the agent state, listens on a
|
||||
/// per-session Unix socket, and drives one attached client.
|
||||
///
|
||||
/// Flow: create session + lock it → bind a Unix socket under
|
||||
/// `<store>/run/<session_id>.sock` → block for a single client to
|
||||
/// `accept()` → loop reading `ClientRequest`s, translating each into
|
||||
/// `Action`(s) via the same `controller::input`/`apply_action` path the
|
||||
/// single-process mode uses, then pushing a full state update back →
|
||||
/// on `Close` or client disconnect, clean up the socket file, save
|
||||
/// settings, and release the lock.
|
||||
///
|
||||
/// Why: reuses `controller::input::handle_key` by synthesizing a
|
||||
/// `crossterm::KeyEvent` from the IPC `KeyAction`, so daemon and
|
||||
/// single-process modes share identical key-handling logic.
|
||||
fn run_daemon() -> Result<()> {
|
||||
use app::runtime::actions::{Action, apply_action};
|
||||
use ipc::protocol::ClientRequest;
|
||||
@@ -362,6 +432,19 @@ fn run_daemon() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run zesdex as a TUI-only client attached to an existing daemon session.
|
||||
///
|
||||
/// Flow: connect to the daemon's Unix socket → enter raw mode/alternate
|
||||
/// screen → build a local `AppStateRest` mirror (only used for rendering
|
||||
/// and toast/overlay bookkeeping, not agent logic) → loop: poll for a
|
||||
/// terminal event (key/resize) and forward it as a `ClientRequest`, or
|
||||
/// send a `Tick` if idle → read the daemon's `DaemonFrame` reply and
|
||||
/// apply it via `apply_client_update` → redraw → exit when the daemon
|
||||
/// closes or the user quits (sending `ClientRequest::Close` first).
|
||||
///
|
||||
/// Why: Ctrl+C is intercepted locally to quit the client without going
|
||||
/// through the daemon, since the daemon has no notion of "this client
|
||||
/// wants to leave" beyond the explicit `Close` request.
|
||||
fn run_attach(session_id: &str) -> Result<()> {
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||
use ipc::protocol::ClientRequest;
|
||||
@@ -467,6 +550,14 @@ fn run_attach(session_id: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run the single-process event loop, guaranteeing terminal restoration
|
||||
/// on error.
|
||||
///
|
||||
/// Flow: delegate to `run_loop_inner` → if it errors, clear the screen
|
||||
/// and tear down raw mode / alternate screen before propagating the error.
|
||||
///
|
||||
/// Why: without this wrapper, an error inside the loop would leave the
|
||||
/// user's terminal in raw/alternate-screen mode after the process exits.
|
||||
fn run_loop(
|
||||
state: &mut app::state::rest::AppStateRest,
|
||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||
@@ -481,6 +572,17 @@ fn run_loop(
|
||||
result
|
||||
}
|
||||
|
||||
/// The core single-process render/input loop.
|
||||
///
|
||||
/// Flow: until `state.quit` → drain expired toasts → draw the frame →
|
||||
/// poll for a terminal event with a 50ms timeout (keys go through
|
||||
/// `handle_key` → `apply_action`; resize and scroll map to `Action`
|
||||
/// variants directly) → always fire `Action::Tick` each iteration
|
||||
/// (drives streaming/background progress) → on exit, clear the terminal.
|
||||
///
|
||||
/// Why: the 50ms poll timeout bounds input latency while still yielding
|
||||
/// regularly for the `Tick` action, which drives async work like LLM
|
||||
/// streaming without a separate polling thread.
|
||||
fn run_loop_inner(
|
||||
state: &mut app::state::rest::AppStateRest,
|
||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||
|
||||
Reference in New Issue
Block a user