feat: centralize default constants and refactor overlay enter handling in TUI
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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."))
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user