feat(tui): introduce comprehensive state management for TUI interface

- Add AppStateRest as the central state struct for managing TUI state.
- Implement InputState for handling user input, autocomplete, and history.
- Create MiscState to manage overlays, notifications, and editor state.
- Introduce ScrollState for viewport scrolling functionality.
- Develop TranscriptCache for efficient message rendering in the chat pane.
- Implement SimpleAgent and SimpleWorkflowEngine for agent lifecycle management.
- Add helper functions for managing effort levels and token counting.
- Organize state-related modules for better maintainability and clarity.
This commit is contained in:
asepharyana
2026-07-21 06:42:53 +07:00
parent 802346f909
commit 8c58faf292
25 changed files with 2594 additions and 2158 deletions
+39 -22
View File
@@ -1,8 +1,14 @@
//! Domain types for agent lifecycle: turn events, session runtime, progress
//! reporting, prompts, and the agent-turn parameter bundle.
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::core::{ChatMessage, ToolCallResult, UsageStats};
pub mod prompt;
pub mod progress;
/// Which kind of caller (main agent vs. subagent vs. reviewer) is
/// invoking a tool, used to scope permissions and tag log/output paths.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
@@ -124,6 +130,9 @@ pub enum TurnEvent {
},
TodoUpdate(String),
PlanUpdate(String),
/// Structured progress report from a subagent or workflow node,
/// carrying the current tool name and optional step counters.
AgentProgress(crate::agent::progress::AgentProgress),
}
/// How a pending tool call should be executed when the turn resumes.
@@ -152,6 +161,33 @@ pub struct BashJobRef {
pub running: bool,
}
/// Tracks counts of learned patterns by outcome and lifecycle stage.
#[derive(Debug, Clone, Default)]
pub struct LessonStats {
/// Total number of lessons tracked.
pub total: u32,
/// User-initiated lessons.
pub user: u32,
/// Feedback-driven lessons.
pub feedback: u32,
/// Project-scoped lessons.
pub project: u32,
/// Reference-scoped lessons.
pub reference: u32,
/// Currently active lessons.
pub active: u32,
/// Stale (outdated) lessons.
pub stale: u32,
/// Contradicted lessons.
pub contradicted: u32,
/// Human-authored lessons.
pub human: u32,
/// Verified lessons.
pub verified: u32,
/// Unverified lessons.
pub unverified: u32,
}
/// Per-session runtime state: message history, pending tool queue,
/// background bash jobs, lesson/review counters.
#[derive(Debug, Clone)]
@@ -164,17 +200,8 @@ pub struct SessionRuntime {
pub edit_count: u32,
pub consecutive_empty_reviews: u32,
pub session_start: i64,
pub lesson_count: u32,
pub lessons_user: u32,
pub lessons_feedback: u32,
pub lessons_project: u32,
pub lessons_reference: u32,
pub lessons_active: u32,
pub lessons_stale: u32,
pub lessons_contradicted: u32,
pub lessons_human: u32,
pub lessons_verified: u32,
pub lessons_unverified: u32,
/// Aggregated lesson statistics.
pub lessons: LessonStats,
pub review_count: u32,
pub session_dir: PathBuf,
pub usage: UsageStats,
@@ -192,17 +219,7 @@ impl SessionRuntime {
edit_count: 0,
consecutive_empty_reviews: 0,
session_start: chrono::Utc::now().timestamp_millis(),
lesson_count: 0,
lessons_user: 0,
lessons_feedback: 0,
lessons_project: 0,
lessons_reference: 0,
lessons_active: 0,
lessons_stale: 0,
lessons_contradicted: 0,
lessons_human: 0,
lessons_verified: 0,
lessons_unverified: 0,
lessons: LessonStats::default(),
review_count: 0,
session_dir,
usage: UsageStats::default(),
+81
View File
@@ -0,0 +1,81 @@
//! Progress reporting types for long-running agent and subagent operations.
//!
//! These types are emitted onto the turn-event queue to drive the TUI's
//! spinner, progress bar, and agent-status sidebar. They are pure domain
//! types with no I/O or framework dependency.
use crate::agent::AgentStatus;
/// Describes progress within a single subagent or workflow-node execution.
///
/// Emitted as a `TurnEvent::AgentProgress` so the UI can show which tool
/// the subagent is currently invoking, or which step it has reached.
#[derive(Debug, Clone)]
pub struct AgentProgress {
/// Unique identifier for this agent (e.g. `"Node-0-1"`, `"auto-review"`).
pub agent_id: String,
/// Human-readable display name shown in the TUI sidebar.
pub agent_name: String,
/// Current lifecycle status.
pub status: AgentStatus,
/// Optional description of the current tool or step being executed.
/// Set to `None` when the agent is not actively executing a tool.
pub current_tool: Option<String>,
/// Optional progress range: (completed_steps, total_steps).
/// When `None`, the agent shows an indeterminate spinner.
pub steps: Option<(u32, u32)>,
}
impl AgentProgress {
/// Mark this agent as running with an optional tool name.
pub fn running(
agent_id: impl Into<String>,
agent_name: impl Into<String>,
current_tool: Option<String>,
) -> Self {
AgentProgress {
agent_id: agent_id.into(),
agent_name: agent_name.into(),
status: AgentStatus::Running,
current_tool,
steps: None,
}
}
/// Mark this agent as pending (queued but not yet started).
pub fn pending(agent_id: impl Into<String>, agent_name: impl Into<String>) -> Self {
AgentProgress {
agent_id: agent_id.into(),
agent_name: agent_name.into(),
status: AgentStatus::Pending,
current_tool: None,
steps: None,
}
}
/// Mark this agent as completed successfully.
pub fn completed(agent_id: impl Into<String>, agent_name: impl Into<String>) -> Self {
AgentProgress {
agent_id: agent_id.into(),
agent_name: agent_name.into(),
status: AgentStatus::Completed,
current_tool: None,
steps: None,
}
}
/// Mark this agent as failed with an error message.
pub fn failed(
agent_id: impl Into<String>,
agent_name: impl Into<String>,
error: String,
) -> Self {
AgentProgress {
agent_id: agent_id.into(),
agent_name: agent_name.into(),
status: AgentStatus::Failed(error),
current_tool: None,
steps: None,
}
}
}
+92
View File
@@ -0,0 +1,92 @@
//! System prompts and directive templates for agent and subagent turns.
//!
//! Centralising all prompt text here keeps the core turn logic free of
//! hardcoded prose, making prompts easier to maintain, review, and localise.
//!
//! # Flow
//! The application layer's `AgentTurnServiceImpl` calls `main_agent_prompt()`
//! to construct the system message at the start of each turn. Subagent and
//! review prompts are provided by their respective modules.
/// Build the main-agent system prompt.
///
/// The prompt establishes the agent's identity as Zesdex, an AI coding
/// assistant, and defines the priority hierarchy that governs tool selection:
///
/// 1. **Workflow first** — `workflow_run` / `hive_mind` for complex tasks
/// 2. **Planning & TODOs** — `plan_enter` / `todowrite` for structural work
/// 3. **Reasoning** — `seq_think` for deep analysis
/// 4. **Tool execution** — direct tools for simple actions
pub fn main_agent_prompt() -> String {
"\
You are Zesdex, an AI coding assistant. You have access to various tools \
via native function calling to help the user.
CRITICAL DIRECTIVES & PRIORITY HIERARCHY:
1. WORKFLOW FIRST: For any multi-step, complex, or non-trivial task, \
you MUST prioritise using `workflow_run` (to construct and execute a \
multi-phase YAML workflow) or `hive_mind` (to orchestrate parallel \
autonomous agents). Workflows are your primary strategy.
2. PLANNING & TODOS: Use `plan_enter` to establish high-level \
architectural plans and `todowrite` to maintain granular task checklists.
3. REASONING: Use `seq_think` for deep step-by-step analysis.
4. TOOL EXECUTION: Execute individual tools (file edits, terminal commands) \
within or guided by your workflows. If an error occurs, analyse and fix it.
Respond conversationally, concisely, and helpfully."
.to_string()
}
/// Build a subagent directive prompt.
///
/// The directive is embedded in a system message that also communicates the
/// current working directory and workspace root so the subagent can resolve
/// paths correctly.
pub fn subagent_directive(directive: &str, cwd: &str, ws_root: &str) -> String {
format!(
"\
You are a focused subagent.
Current directory (PWD): {cwd}
Workspace root: {ws_root}
Your directive:
{directive}
Complete the directive autonomously using the tools available to you. \
Return your final answer when done."
)
}
/// Build a conversation-compaction prompt.
///
/// The LLM is asked to produce a concise bulleted summary of the key
/// requests, decisions, tools executed, and files modified.
pub fn compaction_prompt() -> String {
"\
You are a helpful assistant summarising conversation history. \
Provide a concise summary of the key user requests, decisions, tools \
executed, and modified files. Format as a clear bulleted list."
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn main_prompt_is_non_empty() {
let prompt = main_agent_prompt();
assert!(!prompt.is_empty());
assert!(prompt.contains("Zesdex"));
assert!(prompt.contains("WORKFLOW FIRST"));
}
#[test]
fn subagent_directive_includes_directive_text() {
let prompt = subagent_directive("test directive", "/home", "/home/project");
assert!(prompt.contains("test directive"));
assert!(prompt.contains("/home"));
assert!(prompt.contains("/home/project"));
}
}
+5
View File
@@ -53,6 +53,11 @@ pub use core::{
ToolCallResult, ToolDef, ToolFunction, ToolFunctionDef, UsageStats,
};
pub use error::DomainError;
// Agent module top-level items (TurnEvent, SessionRuntime, etc.)
pub use agent::*;
// Sub-module items need explicit re-exports
pub use agent::progress::AgentProgress;
pub use agent::prompt::{compaction_prompt, main_agent_prompt, subagent_directive};
pub use workflow::*;
pub use subagent::*;