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:
asepharyana
2026-07-12 11:28:39 +07:00
parent 7158d362fd
commit 2efd40ca88
124 changed files with 2379 additions and 19 deletions
+102
View File
@@ -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>>,