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:
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
Reference in New Issue
Block a user