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:
@@ -1,3 +1,21 @@
|
||||
//! The `Action` enum and its single dispatcher, `apply_action` — the
|
||||
//! chokepoint through which every key input, streaming event, and async
|
||||
//! background-thread result mutates `AppStateRest`.
|
||||
//!
|
||||
//! Flow: controllers/subagent threads construct `Action` values → the event
|
||||
//! loop calls `apply_action(&mut state, action)` → for turn-producing
|
||||
//! actions (`SubmitInput`), `spawn_turn` is kicked off on a background OS
|
||||
//! thread which drives `run_agent_turn` (stream to the LLM, gate and
|
||||
//! execute tool calls via `Harness`, archive messages to SQLite, log edits)
|
||||
//! and pushes `TurnEvent`s onto a shared queue → on the next `Tick`, queued
|
||||
//! `TurnEvent`s are drained back into `AppStateRest` (transcript, toasts,
|
||||
//! usage counters).
|
||||
//!
|
||||
//! Why: keeping all state mutation behind one function means callers only
|
||||
//! need to know how to *produce* actions, not how to update state safely;
|
||||
//! running turns on plain OS threads (rather than blocking the main loop)
|
||||
//! keeps the TUI responsive while the LLM streams.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::app::harness::Verdict;
|
||||
@@ -9,11 +27,14 @@ use crate::app::state::runtime::TurnEvent;
|
||||
use crate::app::state::types::{Origin, Overlay, Toast, ToastKind};
|
||||
use crate::dto::chat::message::{ChatMessage, Role};
|
||||
|
||||
// Step bounds intentionally left unbounded (usize::MAX) so the agent can
|
||||
// continue across as many turns as needed. Each iteration still honours
|
||||
// `tc.abort_flag` and the per-call LLM timeout, so a runaway loop is
|
||||
// observable and cancellable from the UI.
|
||||
|
||||
/// A single, well-typed event in the app — produced by key input, the
|
||||
/// streaming pipeline, or subagent threads — that mutates `AppStateRest`
|
||||
/// when applied via `apply_action`.
|
||||
///
|
||||
/// Step bounds intentionally left unbounded (usize::MAX) so the agent can
|
||||
/// continue across as many turns as needed. Each iteration still honours
|
||||
/// `tc.abort_flag` and the per-call LLM timeout, so a runaway loop is
|
||||
/// observable and cancellable from the UI.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Action {
|
||||
ForceQuit,
|
||||
@@ -63,6 +84,18 @@ pub enum Action {
|
||||
AbortTurn,
|
||||
}
|
||||
|
||||
/// Apply an `Action` to the application state.
|
||||
///
|
||||
/// Flow: pattern-match the variant → mutate `state` (input buffer, scroll
|
||||
/// position, overlay, transcript, runtime, toasts, dirty flag, etc.) →
|
||||
/// for `Tick`, also drain queued `TurnEvent`s and run periodic side jobs
|
||||
/// (staleness sweep, pending-lesson commit).
|
||||
///
|
||||
/// Why: the single chokepoint that turns every typed key and async event
|
||||
/// into a state change, so callers (controllers, subagent threads) only
|
||||
/// need to know how to *produce* actions.
|
||||
///
|
||||
/// Return: nothing; `state` is mutated in place.
|
||||
pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
match action {
|
||||
Action::ForceQuit => {
|
||||
@@ -449,6 +482,19 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a background thread that runs one full LLM turn.
|
||||
///
|
||||
/// Flow: check that no turn is currently in-flight → bail if so →
|
||||
/// collect messages and config from state → determine API key (from
|
||||
/// settings, env var, or default) → resolve generation params from
|
||||
/// the current effort level → collect all tools (built-in + MCP) →
|
||||
/// build `TurnCtx` → spawn a thread running `run_agent_turn` →
|
||||
/// on any error, push a `TurnEvent::Error` → clear the in-flight flag
|
||||
/// when the thread exits.
|
||||
///
|
||||
/// Why: runs on a plain OS thread so the async event loop stays responsive.
|
||||
///
|
||||
/// Return: nothing; results flow through `state.turn_events`.
|
||||
fn spawn_turn(state: &AppStateRest) {
|
||||
let in_flight = if let Ok(guard) = state.turn_in_flight.lock() {
|
||||
*guard
|
||||
@@ -532,6 +578,7 @@ fn spawn_turn(state: &AppStateRest) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Context bundle passed to `run_agent_turn` on its background thread.
|
||||
struct TurnCtx {
|
||||
client: crate::service::provider::LlmClient,
|
||||
tdefs: Vec<crate::dto::provider::request::ToolDef>,
|
||||
@@ -547,6 +594,14 @@ struct TurnCtx {
|
||||
abort_flag: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||
}
|
||||
|
||||
/// Build an ASCII tree of the workspace directory structure for the
|
||||
/// system prompt, so the LLM can see the file layout.
|
||||
///
|
||||
/// Flow: for each root, walk using `ignore::WalkBuilder` (respecting
|
||||
/// `.gitignore` and hidden files) → prefix `[DIR]` for directories →
|
||||
/// truncate after 1000 entries.
|
||||
///
|
||||
/// Return: a formatted string with one entry per line.
|
||||
fn generate_workspace_tree(roots: &[std::path::PathBuf]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("Current Workspace Directory Structure:\n");
|
||||
@@ -575,6 +630,11 @@ fn generate_workspace_tree(roots: &[std::path::PathBuf]) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
/// Persist a `ChatMessage` to the SQLite message log, if a database
|
||||
/// connection is available.
|
||||
///
|
||||
/// Flow: if `db` is `Some`, lock the mutex and call `insert_message`.
|
||||
/// Errors are silently ignored.
|
||||
fn archive_message(db: &Option<std::sync::Arc<std::sync::Mutex<rusqlite::Connection>>>, session_id: &str, msg: &ChatMessage) {
|
||||
if let Some(ref arc) = db {
|
||||
if let Ok(conn) = arc.lock() {
|
||||
@@ -583,6 +643,28 @@ fn archive_message(db: &Option<std::sync::Arc<std::sync::Mutex<rusqlite::Connect
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute one full agent turn: stream the conversation to the LLM,
|
||||
/// handle tool calls, and loop until the LLM produces a non-tool response
|
||||
/// or runs out of unfinished todo items.
|
||||
///
|
||||
/// Flow: build system prompt with workspace tree → optionally shape
|
||||
/// (compact) messages via `shortsend` → call `chat_with_tools_streaming`
|
||||
/// with a callback that pushes `StreamStart`, `StreamToken`, `Reasoning`,
|
||||
/// and `Usage` events → on streaming success, handle tool calls (gated
|
||||
/// through `Harness::gate_tool_call`) or unwrap the final assistant
|
||||
/// message → check for unfinished todo.md tasks (auto-retry with a
|
||||
/// system message if any remain) → finalise with `Done` and an `edits`
|
||||
/// SystemNote.
|
||||
///
|
||||
/// On streaming failure: retry once with a non-streaming call → if that
|
||||
/// also fails and there are unfinished tasks, sleep 5s and loop back;
|
||||
/// otherwise return the error.
|
||||
///
|
||||
/// Why: non-streaming fallback handles flaky connections without aborting
|
||||
/// the turn; todo.md polling lets the agent self-direct toward completeness.
|
||||
///
|
||||
/// Return: `Ok(())` on successful completion, or an error from the LLM
|
||||
/// API after retries are exhausted.
|
||||
fn run_agent_turn(
|
||||
tc: TurnCtx,
|
||||
messages: &[ChatMessage],
|
||||
@@ -836,6 +918,20 @@ fn run_agent_turn(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute a single tool call: find the tool by name, snapshot the file
|
||||
/// (if write/edit) for rewind, run the tool, log an `EditLogEntry` for
|
||||
/// write/edit, and return the output.
|
||||
///
|
||||
/// Flow: iterate tools → match by name → for write/edit, snapshot the
|
||||
/// pre-existing file content into the blob store → call `tool.run()` →
|
||||
/// for write/edit, compute SHA-256 of the new content and append an
|
||||
/// `EditLogEntry` → return the tool output string.
|
||||
///
|
||||
/// Why: snapshots enable the rewind feature to restore previous content
|
||||
/// after a write/edit.
|
||||
///
|
||||
/// Return: the tool's stdout string, or an error if no matching tool was
|
||||
/// found or the tool run itself failed.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn execute_one_tool(
|
||||
tools: &[Box<dyn crate::tool::Tool>],
|
||||
@@ -910,6 +1006,15 @@ fn execute_one_tool(
|
||||
anyhow::bail!("tool not found: {}", name)
|
||||
}
|
||||
|
||||
/// Optionally push a review-available toast at the end of a turn that
|
||||
/// performed edits.
|
||||
///
|
||||
/// Flow: skip if review is disabled → skip if `edit_count` is zero →
|
||||
/// push an info toast listing the number of modified files.
|
||||
///
|
||||
/// Why: does not launch the review itself (that happens inside
|
||||
/// `should_trigger_review` on `Tick`), only informs the user that
|
||||
/// a review has material to examine.
|
||||
fn maybe_trigger_review(state: &mut AppStateRest) {
|
||||
if !state.settings.review_enabled {
|
||||
return;
|
||||
@@ -928,6 +1033,13 @@ fn maybe_trigger_review(state: &mut AppStateRest) {
|
||||
));
|
||||
}
|
||||
|
||||
/// Persist the current session metadata and conversation to disk.
|
||||
///
|
||||
/// Flow: build a `Session` object → save its metadata → write
|
||||
/// `rt.messages` as JSON to the conversation file → errors are silently
|
||||
/// ignored.
|
||||
///
|
||||
/// Why: called on `ForceQuit` so the session can be resumed later.
|
||||
fn save_current_session(state: &AppStateRest) {
|
||||
let base = state.store_base_dir();
|
||||
let session = crate::model::session::Session::new(
|
||||
@@ -943,6 +1055,20 @@ fn save_current_session(state: &AppStateRest) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a browser-based OAuth PKCE flow for the given provider.
|
||||
///
|
||||
/// Flow: look up config by provider name ("zen"/"opencode", "openai",
|
||||
/// or a custom provider via env vars) → bind a loopback server → generate
|
||||
/// a PKCE code verifier and challenge → build the authorisation URL →
|
||||
/// wait for the redirect code on the loopback server (with a 120s timeout)
|
||||
/// → exchange the code for a token → save the token to
|
||||
/// `~/.config/zesdex/oauth_{provider}.json`.
|
||||
///
|
||||
/// Why: the `webbrowser::open` call is currently commented out; the user
|
||||
/// must open the auth URL manually until that line is reinstated.
|
||||
///
|
||||
/// Return: a success message on completion, or an error if the flow fails
|
||||
/// at any step.
|
||||
fn run_oauth_flow(provider: &str) -> anyhow::Result<String> {
|
||||
use crate::service::oauth::manager::{OAuthConfig, OAuthManager};
|
||||
use crate::service::oauth::loopback::LoopbackServer;
|
||||
@@ -1013,6 +1139,11 @@ fn run_oauth_flow(provider: &str) -> anyhow::Result<String> {
|
||||
Ok(format!("Successfully authenticated with {}.", provider))
|
||||
}
|
||||
|
||||
/// Generate `n` pseudo-random bytes from the current sub-second timestamp.
|
||||
///
|
||||
/// Why: avoids pulling in a full RNG crate for the OAuth state token;
|
||||
/// sufficient for a nonce that only needs to be unpredictable over the
|
||||
/// lifetime of a single OAuth flow.
|
||||
fn rand_bytes(n: usize) -> Vec<u8> {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().subsec_nanos();
|
||||
|
||||
Reference in New Issue
Block a user