feat: centralize default constants and refactor overlay enter handling in TUI

This commit is contained in:
asepharyana
2026-07-21 07:00:15 +07:00
parent 8c58faf292
commit d615090dcd
34 changed files with 216 additions and 359 deletions
-1
View File
@@ -35,7 +35,6 @@ pub mod lsp;
pub mod mcp;
pub mod middleware;
pub mod persistence;
pub mod review;
pub mod subagent;
pub mod tools;
pub mod utils;
+2 -2
View File
@@ -11,8 +11,8 @@ use zesdex_domain::core::{
};
use zesdex_application::ports::ProviderService;
const DEFAULT_BASE_URL: &str = "https://opencode.ai/zen/v1";
const DEFAULT_MODEL: &str = "deepseek-v4-flash-free";
use zesdex_domain::agent::defaults::{DEFAULT_API_BASE, DEFAULT_MODEL};
const DEFAULT_BASE_URL: &str = DEFAULT_API_BASE;
pub const DEFAULT_API_KEY: &str = "";
const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
const REQUEST_TIMEOUT: Duration = Duration::from_secs(600);
-8
View File
@@ -1,8 +0,0 @@
//! Post-edit auto-review subagent — validates file edits and suggests
//! improvements.
pub mod pending;
pub mod probe;
pub mod prompt;
pub mod staleness;
pub mod types;
-43
View File
@@ -1,43 +0,0 @@
//! Pending review queue — tracks files modified by tools that have not
//! yet been reviewed.
use std::collections::VecDeque;
/// A file mutation awaiting review.
#[derive(Debug, Clone)]
pub struct PendingReview {
pub path: String,
pub tool: String,
pub reason: String,
pub content_sha256: String,
}
/// Queue of files modified but not yet reviewed.
#[derive(Debug, Clone, Default)]
pub struct PendingReviewQueue {
entries: VecDeque<PendingReview>,
}
impl PendingReviewQueue {
pub fn new() -> Self {
PendingReviewQueue {
entries: VecDeque::new(),
}
}
pub fn push(&mut self, entry: PendingReview) {
self.entries.push_back(entry);
}
pub fn pop(&mut self) -> Option<PendingReview> {
self.entries.pop_front()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn len(&self) -> usize {
self.entries.len()
}
}
-18
View File
@@ -1,18 +0,0 @@
//! Review probe — diff analysis and file inspection for review purposes.
use similar::{ChangeTag, TextDiff};
/// Compute a simple unified diff between old and new text.
pub fn compute_diff(old: &str, new: &str) -> String {
let diff = TextDiff::from_lines(old, new);
let mut result = String::new();
for change in diff.iter_all_changes() {
let sign = match change.tag() {
ChangeTag::Delete => "-",
ChangeTag::Insert => "+",
ChangeTag::Equal => " ",
};
result.push_str(&format!("{}{}", sign, change.value()));
}
result
}
-25
View File
@@ -1,25 +0,0 @@
//! Review prompt construction — builds the system prompt for the
//! auto-review subagent.
/// Build the review system prompt for the given diff and context.
pub fn build_review_prompt(diff: &str, file_path: &str) -> String {
format!(
"You are a code reviewer. Review the following diff for file '{}':\n\
\n\
Focus on:\n\
1. Correctness — does the change introduce bugs?\n\
2. Security — does the change introduce vulnerabilities?\n\
3. Style — does the change follow best practices?\n\
4. Edge cases — are there unhandled edge cases?\n\
\n\
Diff:\n\
```diff\n\
{}\n\
```\n\
\n\
Provide your review as a JSON array of findings with \
'severity' (Info/Warning/Error), 'file', 'line' (optional), \
'message', and 'suggestion' (optional).",
file_path, diff
)
}
@@ -1,15 +0,0 @@
//! Staleness detection for lesson cache entries.
use std::time::{SystemTime, UNIX_EPOCH};
/// How long (in seconds) before a lesson is considered stale.
const STALE_THRESHOLD_SECS: u64 = 86400 * 7; // 7 days
/// Check whether a lesson timestamp is stale.
pub fn is_stale(updated_at: i64) -> bool {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
now.saturating_sub(updated_at) > STALE_THRESHOLD_SECS as i64
}
-29
View File
@@ -1,29 +0,0 @@
//! Review types — findings, severity, and configuration.
use serde::{Deserialize, Serialize};
/// Severity of a review finding.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReviewSeverity {
Info,
Warning,
Error,
}
/// A single review finding from the auto-review subagent.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReviewFinding {
pub severity: ReviewSeverity,
pub file: String,
pub line: Option<usize>,
pub message: String,
pub suggestion: Option<String>,
}
/// Configuration for the auto-review subagent.
#[derive(Debug, Clone)]
pub struct ReviewConfig {
pub max_lessons_per_run: usize,
pub adaptive_max_skip: u32,
pub enabled: bool,
}
@@ -109,7 +109,7 @@ pub fn spawn_background_review(
// 3. Resolve LLM credentials
let base_url = api_base.unwrap_or_else(|| {
std::env::var("OPENAI_API_BASE")
.unwrap_or_else(|_| "https://opencode.ai/zen/v1".to_string())
.unwrap_or_else(|_| zesdex_domain::agent::defaults::DEFAULT_API_BASE.to_string())
});
let client = LlmClient::new(api_key, model, Some(base_url));
@@ -1,3 +0,0 @@
//! Subagent event types — events emitted during subagent execution.
pub use zesdex_domain::subagent::SubagentEvent;
@@ -1,23 +0,0 @@
//! Subagent gating — decide whether to run review/test/arch agents based
//! on the current context.
use tracing::instrument;
/// Determine whether an auto-review should be triggered after an edit.
///
/// Gating logic:
/// - Returns `false` if there are no edits (`edit_count == 0`).
/// - Returns `false` if `consecutive_empty_reviews >= max_skip` (too many
/// consecutive reviews produced no findings, so skip further reviews).
/// - Otherwise returns `true`.
#[instrument]
pub fn should_review(edit_count: u32, consecutive_empty_reviews: u32, max_skip: u32) -> bool {
if edit_count == 0 {
return false;
}
// Skip review if we've had several consecutive empty reviews
if consecutive_empty_reviews >= max_skip {
return false;
}
true
}
-4
View File
@@ -5,9 +5,5 @@ pub mod auto;
pub mod context;
pub mod division;
pub mod engine;
pub mod event;
pub mod gating;
pub mod provider;
pub mod spawn;
pub mod tools;
pub mod workspace;
+2 -2
View File
@@ -67,7 +67,7 @@ impl SubagentProvider {
///
/// Flow: reads `settings.provider` and `settings.model` → if model is empty,
/// falls back to the provider config's `default_model` → if that is also
/// empty, uses `"deepseek-v4-flash-free"` as the ultimate default.
/// empty, uses the domain default model constant.
#[instrument]
pub fn resolve_subagent_provider(
settings: &zesdex_domain::cms::Settings,
@@ -82,7 +82,7 @@ pub fn resolve_subagent_provider(
.providers
.get(&provider)
.and_then(|p| p.default_model.clone())
.unwrap_or_else(|| "deepseek-v4-flash-free".to_string())
.unwrap_or_else(|| zesdex_domain::agent::defaults::DEFAULT_MODEL.to_string())
} else {
model
};
-19
View File
@@ -1,19 +0,0 @@
//! Subagent tool helpers — wrap tool execution for subagent use.
use anyhow::Result;
use tracing::instrument;
use crate::tools::{Tool, ToolCtx};
/// Execute a single tool call within a subagent context.
///
/// Delegates directly to the tool's `run` method with the given context and
/// JSON arguments.
#[instrument(skip(tool, ctx, args))]
pub fn execute_tool_call(
tool: &dyn Tool,
ctx: &ToolCtx,
args: &serde_json::Value,
) -> Result<String> {
tool.run(ctx, args)
}
@@ -1,16 +0,0 @@
//! Subagent workspace management — create isolated workspaces for subagents.
use std::path::{Path, PathBuf};
use tracing::instrument;
/// Create an isolated workspace directory for a subagent.
///
/// Creates `{base_dir}/subagent-workspaces/{agent_id}` and all parent
/// directories if they do not already exist.
#[instrument]
pub fn create_subagent_workspace(base_dir: &Path, agent_id: &str) -> anyhow::Result<PathBuf> {
let ws = base_dir.join("subagent-workspaces").join(agent_id);
std::fs::create_dir_all(&ws)?;
Ok(ws)
}
@@ -100,7 +100,7 @@ impl Tool for ParallelDelegate {
.providers
.get(&provider)
.map(|p| p.api_base.clone())
.unwrap_or_else(|| "https://opencode.ai/zen/v1".to_string());
.unwrap_or_else(|| zesdex_domain::agent::defaults::DEFAULT_API_BASE.to_string());
let api_key = crate::llm::provider::resolve_api_key(&settings, &app_config);
+6 -4
View File
@@ -45,8 +45,9 @@ impl Tool for PlanEnter {
let plan_path = ctx.session_dir.join("PLAN.md");
let _ = std::fs::write(&plan_path, &plan_text);
if let Some(events) = &ctx.turn_events {
let mut q = events.lock().unwrap();
q.push_back(crate::TurnEvent::PlanUpdate(plan_text.clone()));
if let Ok(mut q) = events.lock() {
q.push_back(crate::TurnEvent::PlanUpdate(plan_text.clone()));
}
}
Ok(format!(
@@ -103,8 +104,9 @@ impl Tool for PlanReady {
let plan_path = ctx.session_dir.join("PLAN.md");
let _ = std::fs::write(&plan_path, &plan_content);
if let Some(events) = &ctx.turn_events {
let mut q = events.lock().unwrap();
q.push_back(crate::TurnEvent::PlanUpdate(plan_content.clone()));
if let Ok(mut q) = events.lock() {
q.push_back(crate::TurnEvent::PlanUpdate(plan_content.clone()));
}
}
Ok(format!("Plan saved to {filename}. Starting execution."))
+2 -2
View File
@@ -79,7 +79,7 @@ impl Tool for SpawnAgents {
.providers
.get(&provider)
.map(|p| p.api_base.clone())
.unwrap_or_else(|| "https://opencode.ai/zen/v1".to_string());
.unwrap_or_else(|| zesdex_domain::agent::defaults::DEFAULT_API_BASE.to_string());
let api_key = crate::llm::provider::resolve_api_key(&settings, &app_config);
@@ -196,7 +196,7 @@ impl Tool for SpawnPipeline {
.providers
.get(&provider)
.map(|p| p.api_base.clone())
.unwrap_or_else(|| "https://opencode.ai/zen/v1".to_string());
.unwrap_or_else(|| zesdex_domain::agent::defaults::DEFAULT_API_BASE.to_string());
let api_key = crate::llm::provider::resolve_api_key(&settings, &app_config);
@@ -68,8 +68,9 @@ impl Tool for Todofinish {
content = new_lines.join("\n") + "\n";
let _ = std::fs::write(&todo_path, &content);
if let Some(events) = &ctx.turn_events {
let mut q = events.lock().unwrap();
q.push_back(crate::TurnEvent::TodoUpdate(content));
if let Ok(mut q) = events.lock() {
q.push_back(crate::TurnEvent::TodoUpdate(content));
}
}
info!(item, "TODO item completed and written back");
} else {
@@ -62,8 +62,9 @@ impl Tool for Todowrite {
let _ = std::fs::write(&todo_path, &content);
if let Some(events) = &ctx.turn_events {
let mut q = events.lock().unwrap();
q.push_back(crate::TurnEvent::TodoUpdate(content));
if let Ok(mut q) = events.lock() {
q.push_back(crate::TurnEvent::TodoUpdate(content));
}
}
info!(item, priority, "TODO item added");
+1 -1
View File
@@ -62,7 +62,7 @@ impl Tool for WorkflowRun {
let llm_client = LlmClient::new(
crate::llm::provider::DEFAULT_API_KEY.to_string(),
"deepseek-v4-flash-free".to_string(),
zesdex_domain::agent::defaults::DEFAULT_MODEL.to_string(),
None,
);
let rt = tokio::runtime::Runtime::new()?;
@@ -41,7 +41,7 @@ pub async fn execute_primitive(directive: &str, tool_ctx: &ToolCtx) -> Result<St
.providers
.get(&provider)
.map(|p| p.api_base.clone())
.unwrap_or_else(|| "https://opencode.ai/zen/v1".to_string());
.unwrap_or_else(|| zesdex_domain::agent::defaults::DEFAULT_API_BASE.to_string());
let api_key = crate::llm::provider::resolve_api_key(&settings, &app_config);
@@ -50,7 +50,7 @@ pub async fn execute_cycle(
.providers
.get(&provider)
.map(|p| p.api_base.clone())
.unwrap_or_else(|| "https://opencode.ai/zen/v1".to_string());
.unwrap_or_else(|| zesdex_domain::agent::defaults::DEFAULT_API_BASE.to_string());
let api_key = crate::llm::provider::resolve_api_key(&settings, &app_config);