2026-07-20 09:04:57 +07:00
|
|
|
//! # Zesdex Infrastructure Layer
|
|
|
|
|
//!
|
|
|
|
|
//! ALL concrete implementations of domain repository traits, application port
|
|
|
|
|
//! traits, and platform services. This is the outermost ring of the Clean
|
|
|
|
|
//! Architecture onion — it depends on `zesdex-domain` and `zesdex-application`
|
|
|
|
|
//! but NEVER on interface/presentation crates.
|
|
|
|
|
//!
|
|
|
|
|
//! ## Architecture
|
|
|
|
|
//!
|
|
|
|
|
//! ```text
|
|
|
|
|
//! src/
|
|
|
|
|
//! ├── lib.rs — Foundational types + re-exports
|
|
|
|
|
//! ├── utils.rs — CastOr, write_json_atomic, slugify
|
|
|
|
|
//! ├── persistence/ — Repository implementations (IAM, CMS, SQLite)
|
|
|
|
|
//! ├── auth/ — JWT, Argon2, OAuth loopback
|
|
|
|
|
//! ├── llm/ — LLM provider HTTP client
|
|
|
|
|
//! ├── ipc/ — Unix-socket IPC protocol
|
|
|
|
|
//! ├── lsp/ — Native LSP client + provisioner
|
|
|
|
|
//! ├── mcp/ — Model Context Protocol bridge
|
|
|
|
|
//! ├── bgbash/ — Background bash job management
|
|
|
|
|
//! ├── tools/ — All 37 agent-invocable tools
|
|
|
|
|
//! ├── subagent/ — Subagent spawning & execution engine
|
|
|
|
|
//! ├── workflow/ — Hive-mind orchestration engine
|
|
|
|
|
//! ├── review/ — Post-edit auto-review subagent
|
|
|
|
|
//! ├── guard/ — Tool-gate access control
|
|
|
|
|
//! └── middleware/ — Axum HTTP middleware (auth, cors, rate-limit)
|
|
|
|
|
//! ```
|
|
|
|
|
|
2026-07-20 16:29:32 +07:00
|
|
|
pub mod agent;
|
2026-07-20 09:04:57 +07:00
|
|
|
pub mod auth;
|
|
|
|
|
pub mod bgbash;
|
|
|
|
|
pub mod guard;
|
|
|
|
|
pub mod ipc;
|
|
|
|
|
pub mod llm;
|
|
|
|
|
pub mod lsp;
|
|
|
|
|
pub mod mcp;
|
|
|
|
|
pub mod middleware;
|
|
|
|
|
pub mod persistence;
|
|
|
|
|
pub mod review;
|
|
|
|
|
pub mod subagent;
|
|
|
|
|
pub mod tools;
|
|
|
|
|
pub mod utils;
|
|
|
|
|
pub mod workflow;
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Re-exports from domain
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
pub use zesdex_domain::*;
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Foundation types — these replace `crate::app::state::*` references
|
|
|
|
|
// from the legacy backend code.
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
/// 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)]
|
|
|
|
|
pub enum Origin {
|
|
|
|
|
/// The main agent turn loop.
|
|
|
|
|
Main,
|
|
|
|
|
/// A spawned subagent (test-gen, arch-review, security-review, etc.).
|
|
|
|
|
SubAgent,
|
|
|
|
|
/// The auto-inline review step after an edit.
|
|
|
|
|
Reviewer,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Origin {
|
|
|
|
|
/// Short string tag for this origin, used in filenames and logs.
|
|
|
|
|
pub fn tag(self) -> String {
|
|
|
|
|
match self {
|
|
|
|
|
Origin::Main => "main",
|
|
|
|
|
Origin::SubAgent => "subagent",
|
|
|
|
|
Origin::Reviewer => "reviewer",
|
|
|
|
|
}
|
|
|
|
|
.to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Severity/category of a toast notification, used to pick its color.
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
pub enum ToastKind {
|
|
|
|
|
Info,
|
|
|
|
|
Success,
|
|
|
|
|
Warning,
|
|
|
|
|
Error,
|
|
|
|
|
Lesson,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A transient status message shown in the TUI, auto-dismissed after `lifetime_ms`.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct Toast {
|
|
|
|
|
pub kind: ToastKind,
|
|
|
|
|
pub message: String,
|
|
|
|
|
pub created_at: i64,
|
|
|
|
|
pub lifetime_ms: u64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Toast {
|
|
|
|
|
/// Create a toast with a default 5-second lifetime, stamped with now.
|
|
|
|
|
pub fn new(kind: ToastKind, message: String) -> Self {
|
|
|
|
|
Toast {
|
|
|
|
|
kind,
|
|
|
|
|
message,
|
|
|
|
|
created_at: chrono::Utc::now().timestamp_millis(),
|
|
|
|
|
lifetime_ms: 5000,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Whether this toast's lifetime has elapsed as of `now_ms`.
|
|
|
|
|
pub fn expired(&self, now_ms: i64) -> bool {
|
|
|
|
|
let lifetime = self.lifetime_ms as i64;
|
|
|
|
|
now_ms - self.created_at > lifetime
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A shared, async-writable cache of directory entries, used to avoid
|
|
|
|
|
/// re-reading a directory every render frame.
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct DirCache {
|
|
|
|
|
entries: Arc<tokio::sync::RwLock<Vec<PathBuf>>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DirCache {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
DirCache {
|
|
|
|
|
entries: Arc::new(tokio::sync::RwLock::new(Vec::new())),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn set(&self, paths: Vec<PathBuf>) {
|
|
|
|
|
let mut w = self.entries.write().await;
|
|
|
|
|
*w = paths;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for DirCache {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A shared, whole-workspace file-path index used for `@file` mention
|
|
|
|
|
/// autocomplete.
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct MentionIndex {
|
|
|
|
|
entries: Arc<std::sync::RwLock<Vec<String>>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MentionIndex {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
MentionIndex {
|
|
|
|
|
entries: Arc::new(std::sync::RwLock::new(Vec::new())),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set(&self, paths: Vec<String>) {
|
|
|
|
|
if let Ok(mut w) = self.entries.write() {
|
|
|
|
|
*w = paths;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn push(&self, path: String) {
|
|
|
|
|
if let Ok(mut w) = self.entries.write() {
|
|
|
|
|
w.push(path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn snapshot(&self) -> Vec<String> {
|
|
|
|
|
self.entries.read().map(|r| r.clone()).unwrap_or_default()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for MentionIndex {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// TurnEvent & runtime types
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
/// Events emitted onto the turn-event queue while an agent turn runs,
|
|
|
|
|
/// consumed by the event loop to update state and drive re-renders.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum TurnEvent {
|
|
|
|
|
AssistantMessage(ChatMessage),
|
|
|
|
|
ToolResult {
|
|
|
|
|
tool_call_id: String,
|
|
|
|
|
tool_name: String,
|
|
|
|
|
output: String,
|
|
|
|
|
is_error: bool,
|
|
|
|
|
path: Option<String>,
|
|
|
|
|
},
|
|
|
|
|
SystemNote {
|
|
|
|
|
kind: String,
|
|
|
|
|
message: String,
|
|
|
|
|
},
|
|
|
|
|
StreamStart,
|
|
|
|
|
StreamToken(String),
|
2026-07-20 14:56:03 +07:00
|
|
|
StreamReasoning(String),
|
2026-07-20 09:04:57 +07:00
|
|
|
StreamDone(ChatMessage),
|
|
|
|
|
Usage {
|
|
|
|
|
tokens_in: u64,
|
|
|
|
|
tokens_out: u64,
|
|
|
|
|
},
|
|
|
|
|
ReviewUsage {
|
|
|
|
|
tokens_in: u64,
|
|
|
|
|
tokens_out: u64,
|
|
|
|
|
},
|
|
|
|
|
Compacted(Vec<ChatMessage>),
|
|
|
|
|
Error(String),
|
|
|
|
|
Done,
|
|
|
|
|
WorkflowAgentUpdate {
|
|
|
|
|
agent_id: String,
|
|
|
|
|
agent_name: String,
|
|
|
|
|
status: crate::AgentStatus,
|
|
|
|
|
},
|
2026-07-20 14:47:13 +07:00
|
|
|
TodoUpdate(String),
|
|
|
|
|
PlanUpdate(String),
|
2026-07-20 09:04:57 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A tool call awaiting execution, along with which execution model
|
|
|
|
|
/// (inline, deferred, async) it should run under.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct PendingTool {
|
|
|
|
|
pub tool_name: String,
|
|
|
|
|
pub args: serde_json::Value,
|
|
|
|
|
pub execution_model: ExecutionModel,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// How a pending tool call should be executed when the turn resumes.
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
pub enum ExecutionModel {
|
|
|
|
|
Inline,
|
|
|
|
|
Deferred,
|
|
|
|
|
AsyncTokio,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Reference to a background bash job tracked in session state.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct BashJobRef {
|
|
|
|
|
pub id: String,
|
|
|
|
|
pub command: String,
|
|
|
|
|
pub started_at: i64,
|
|
|
|
|
pub running: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Per-session runtime state: message history, pending tool queue,
|
|
|
|
|
/// background bash jobs, lesson/review counters.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct SessionRuntime {
|
|
|
|
|
pub messages: Vec<ChatMessage>,
|
|
|
|
|
pub tool_call_results: Vec<ToolCallResult>,
|
|
|
|
|
pub pending_tool_queue: Vec<PendingTool>,
|
|
|
|
|
pub bash_jobs: Vec<BashJobRef>,
|
|
|
|
|
pub subagent_queue: usize,
|
|
|
|
|
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,
|
|
|
|
|
pub review_count: u32,
|
|
|
|
|
pub session_dir: PathBuf,
|
|
|
|
|
pub usage: UsageStats,
|
|
|
|
|
pub hive_mind_converged: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SessionRuntime {
|
|
|
|
|
pub fn new(session_dir: PathBuf) -> Self {
|
|
|
|
|
SessionRuntime {
|
|
|
|
|
messages: Vec::new(),
|
|
|
|
|
tool_call_results: Vec::new(),
|
|
|
|
|
pending_tool_queue: Vec::new(),
|
|
|
|
|
bash_jobs: Vec::new(),
|
|
|
|
|
subagent_queue: 0,
|
|
|
|
|
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,
|
|
|
|
|
review_count: 0,
|
|
|
|
|
session_dir,
|
|
|
|
|
usage: UsageStats::default(),
|
|
|
|
|
hive_mind_converged: false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn push_message(&mut self, msg: ChatMessage) {
|
|
|
|
|
self.messages.push(msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Simple ASCII progress display for a long-running operation.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct ProgressState {
|
|
|
|
|
pub current: u64,
|
|
|
|
|
pub total: u64,
|
|
|
|
|
pub message: String,
|
|
|
|
|
pub start_time: i64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Agent status for workflow engine progress tracking.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
|
|
|
pub enum AgentStatus {
|
|
|
|
|
Pending,
|
|
|
|
|
Running,
|
|
|
|
|
Completed,
|
|
|
|
|
Failed(String),
|
|
|
|
|
Cancelled,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for AgentStatus {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
AgentStatus::Pending => write!(f, "pending"),
|
|
|
|
|
AgentStatus::Running => write!(f, "running"),
|
|
|
|
|
AgentStatus::Completed => write!(f, "completed"),
|
|
|
|
|
AgentStatus::Failed(msg) => write!(f, "failed: {msg}"),
|
|
|
|
|
AgentStatus::Cancelled => write!(f, "cancelled"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Tool types — needed by all tool modules
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
pub use tools::{GraduatedCheck, Tool, ToolCtx, ToolCtxBuilder};
|
|
|
|
|
|
|
|
|
|
// Re-export commonly needed types at the crate root
|
|
|
|
|
pub use zesdex_domain::core::{ChatMessage, Role, Store, UsageStats, ToolCallResult};
|