Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions docs/user-guide/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,18 @@ Ask: Search for TODO|FIXME across the repo with 2 lines of context in .rs files

- `list_files(path, max_items?, include_hidden?)`
- `read_file(path, max_bytes?)`
- `write_file(path, content, overwrite?)`
- `edit_file(path, old_str, new_str)`
- `write_file(path, content, mode?)` — mode: `overwrite`, `append`, or `skip_if_exists`
- `edit_file(path, old_str, new_str)` — tolerant to whitespace differences and detects rename conflicts

## stats (session metrics)

Display current configuration, available tools, and live performance metrics for the running
session. Use `--format` to choose `text`, `json`, or `html` output and `--detailed` to list each
tool.

## Tips

- The agent respects `.vtagentgitignore` to exclude files from search and I/O.
- Prefer `rp_search` for fast, focused searches with glob filters and context.
- Ask for “N lines of context” when searching to understand usage in-place.
- Shell commands are filtered by allow/deny lists and can be extended via `VTAGENT_<AGENT>_COMMANDS_*` environment variables.
7 changes: 5 additions & 2 deletions prompts/system.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ Your capabilities:
Within this context, VTAgent refers to the open-source agentic coding interface created by vinhnx, not any other coding tools or models.

## AVAILABLE TOOLS
- **File Operations**: list_files, read_file, write_file, edit_file
- **File Operations**: list_files, read_file, write_file, edit_file (rename conflict detection and safe writes)
- **Search & Analysis**: grep_search (modes: exact, fuzzy, multi, similarity) and ast_grep_search
- **Terminal Access**: run_terminal_cmd (modes: terminal, pty, streaming)
- **Terminal Access**: run_terminal_cmd (modes: terminal, pty, streaming) with per-agent allow/deny policies

### Advanced Code Analysis
VTAgent provides intelligent code analysis tools that understand code structure:
Expand Down Expand Up @@ -462,12 +462,15 @@ Before yielding to user:
- **Verify file existence** before operations using list_files or search tools
- **Read files first** to understand current state before making changes
- **Test changes** after making modifications to ensure correctness
- **`write_file` supports modes**: overwrite, append, and skip_if_exists
- **`edit_file` matches text** exactly but tolerates whitespace differences

### Search Operations
- **Choose appropriate search tools** based on query type and scope
- **Use ripgrep (rp_search)** for fast, broad text searches
- **Use AST grep** for syntax-aware code pattern matching
- **Combine search tools** when comprehensive analysis is needed
- **Retrieve latest rp_search results** via tool registry when needed

### Terminal Operations
- **Select execution mode** based on command requirements (terminal, pty, streaming)
Expand Down
13 changes: 8 additions & 5 deletions scripts/test_tools_e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ test_tool_directly() {

echo -e "\n${YELLOW}Testing $tool_name tool directly${NC}"

# Try to build and test the tool
# Build and execute the tool through the binary
if cargo build --bin vtagent >/dev/null 2>&1; then
# If binary builds, we could test it directly
# For now, just check if it compiles
echo -e "${GREEN}Tool compiles successfully${NC}"
TESTS_PASSED=$((TESTS_PASSED + 1))
output=$(cargo run --quiet --bin vtagent -- "$tool_name" "$test_input" 2>/dev/null)
if echo "$output" | grep -q "$expected_contains"; then
echo -e "${GREEN}Tool output verified${NC}"
TESTS_PASSED=$((TESTS_PASSED + 1))
else
echo -e "${RED}❌ Unexpected tool output${NC}"
fi
else
echo -e "${RED}❌ Tool compilation failed${NC}"
fi
Expand Down
25 changes: 17 additions & 8 deletions src/cli/init.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use anyhow::Result;
use crate::cli::handle_chat_command;
use anyhow::{Context, Result};
use console::style;
use std::path::Path;
use vtagent_core::config::loader::VTAgentConfig;
use vtagent_core::config::types::AgentConfig as CoreAgentConfig;

/// Handle the init command
pub async fn handle_init_command(workspace: &Path, force: bool, run: bool) -> Result<()> {
Expand All @@ -12,15 +15,21 @@ pub async fn handle_init_command(workspace: &Path, force: bool, run: bool) -> Re
println!("Force overwrite: {}", force);
println!("Run after init: {}", run);

// Configuration initialization implementation
// This would create the vtagent.toml and .vtagentgitignore files
println!("Configuration files created successfully!");
// Bootstrap configuration files in the workspace
VTAgentConfig::bootstrap_project(workspace, force)
.with_context(|| "failed to initialize configuration files")?;

if run {
println!("Running vtagent after initialization...");
// This would actually run the agent
// For now, we'll just print a message
println!("vtagent is now running!");
// After successful initialization, launch a chat session using default config
let config = CoreAgentConfig {
model: String::new(),
api_key: String::new(),
workspace: workspace.to_path_buf(),
verbose: false,
};
handle_chat_command(&config, false)
.await
.with_context(|| "failed to start chat session")?;
}

Ok(())
Expand Down
76 changes: 64 additions & 12 deletions src/cli/init_project.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Init-project command implementation

use anyhow::Result;
use anyhow::{Context, Result};
use console::style;
use std::fs;
use std::path::Path;
use vtagent_core::{ProjectData, SimpleProjectManager};
use walkdir::WalkDir;

/// Handle the init-project command
pub async fn handle_init_project_command(
Expand Down Expand Up @@ -148,18 +150,68 @@ async fn migrate_existing_files(
println!(" - {} ({})", name, path.display());
}

// In a real implementation, we would:
// 1. Prompt user for confirmation
// 2. Backup original files
// 3. Copy/move files to appropriate project directories
// 4. Update any relative paths in config files
for (name, path) in files_to_migrate {
let destination = if name.contains("cache") {
let dir = _project_manager.cache_dir(_project_name).join(name);
dir
} else if name.contains("vtagent.toml") {
_project_manager
.config_dir(_project_name)
.join("vtagent.toml")
} else {
_project_manager.project_data_dir(_project_name).join(name)
};

if let Some(parent) = destination.parent() {
fs::create_dir_all(parent).with_context(|| {
format!(
"failed to create destination directory {}",
parent.display()
)
})?;
}

if destination.exists() {
let backup = destination.with_extension("bak");
fs::rename(&destination, &backup)
.with_context(|| format!("failed to backup {}", destination.display()))?;
}

println!("\nMigration functionality would be implemented here in a full version.");
println!("In a complete implementation, this would:");
println!(" • Prompt for user confirmation before migration");
println!(" • Backup original files before migration");
println!(" • Copy/move files to appropriate project directories");
println!(" • Update any relative paths in config files");
if path.is_dir() {
for entry in WalkDir::new(&path).into_iter().filter_map(|e| e.ok()) {
let file_path = entry.path();
let relative = file_path.strip_prefix(&path).unwrap_or(file_path);
let dest_path = destination.join(relative);
if entry.file_type().is_dir() {
fs::create_dir_all(&dest_path).with_context(|| {
format!("failed to create directory {}", dest_path.display())
})?;
} else {
if let Some(parent) = dest_path.parent() {
fs::create_dir_all(parent).with_context(|| {
format!("failed to create directory {}", parent.display())
})?;
}
fs::copy(file_path, &dest_path).with_context(|| {
format!(
"failed to copy {} to {}",
file_path.display(),
dest_path.display()
)
})?;
}
}
} else {
fs::copy(&path, &destination).with_context(|| {
format!(
"failed to copy {} to {}",
path.display(),
destination.display()
)
})?;
}
}

println!("Migration completed successfully.");
Ok(())
}
12 changes: 6 additions & 6 deletions src/cli/trajectory.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::{Context, Result};
use chrono::{DateTime, NaiveDateTime, Utc};
use console::style;
use serde::Deserialize;
use serde_json::Value;
Expand Down Expand Up @@ -177,12 +178,11 @@ pub async fn handle_trajectory_command(
}

fn format_timestamp(ts: i64) -> String {
if let Some(_dt) = SystemTime::UNIX_EPOCH.checked_add(std::time::Duration::from_secs(ts as u64))
{
// For simplicity, just return a basic format. In a real implementation,
// you might want to use chrono for better date formatting.
format!("{}", ts)
if let Some(dt) = NaiveDateTime::from_timestamp_opt(ts, 0) {
DateTime::<Utc>::from_utc(dt, Utc)
.format("%Y-%m-%d %H:%M:%S UTC")
.to_string()
} else {
format!("{}", ts)
ts.to_string()
}
}
44 changes: 20 additions & 24 deletions src/main_modular.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use console::style;
use std::path::PathBuf;
use vtagent_core::constants::defaults;
use vtagent_core::api_keys::{get_api_key, load_dotenv, ApiKeySources};
use vtagent_core::config::loader::ConfigManager;
use vtagent_core::api_keys::{ApiKeySources, get_api_key, load_dotenv};
use vtagent_core::constants::defaults;
use vtagent_core::{
config::ConfigManager,
models::{Provider, ModelId},
models::{ModelId, Provider},
safety::SafetyValidator,
types::AgentConfig as CoreAgentConfig,
};
Expand All @@ -22,11 +22,7 @@ use cli::*;

/// Main CLI structure for VT Code
#[derive(Parser, Debug)]
#[command(
name = "vtcode",
version,
about = "minimal coding agent",
)]
#[command(name = "vtcode", version, about = "minimal coding agent")]
pub struct Cli {
/// Gemini model ID (e.g., gemini-2.5-flash-lite)
#[arg(long, global = true, default_value = defaults::DEFAULT_CLI_MODEL)]
Expand Down Expand Up @@ -75,7 +71,7 @@ pub enum Commands {
/// Generate configuration file
Config {
#[arg(long)]
output: Option<PathBuf>
output: Option<PathBuf>,
},
/// Show performance metrics
Performance,
Expand All @@ -89,11 +85,13 @@ async fn main() -> Result<()> {
let args = Cli::parse();

// Determine workspace
let workspace = args.workspace.unwrap_or_else(|| std::env::current_dir().unwrap());
let workspace = args
.workspace
.unwrap_or_else(|| std::env::current_dir().unwrap());

// Load configuration
let config_manager = ConfigManager::load_from_workspace(&workspace)
.context("Failed to load configuration")?;
let config_manager =
ConfigManager::load_from_workspace(&workspace).context("Failed to load configuration")?;
let vtcode_config = config_manager.config();

// Create API key sources configuration
Expand Down Expand Up @@ -133,7 +131,7 @@ async fn main() -> Result<()> {
let validated_model = SafetyValidator::validate_model_usage(
&config.model,
Some("Interactive coding session"),
args.skip_confirmations
args.skip_confirmations,
)?;

config.model = validated_model;
Expand All @@ -156,12 +154,14 @@ async fn main() -> Result<()> {
// Create a simple LLM client and get a response
let client = vtagent_core::llm::make_client(
config.api_key.clone(),
config.model.parse().unwrap_or_default()
config.model.parse().unwrap_or_default(),
);

// For a minimal implementation, we'll just print a placeholder response
// In a full implementation, this would actually call the LLM
println!("Answer: This is a placeholder response. In a full implementation, this would call the LLM with your question.");
println!(
"Answer: This is a placeholder response. In a full implementation, this would call the LLM with your question."
);
}
Commands::Analyze => {
handle_analyze_command(&config).await?;
Expand All @@ -176,15 +176,11 @@ async fn main() -> Result<()> {
handle_config_command(output.as_deref()).await?;
}
Commands::Performance => {
println!("{}", style("Performance metrics mode selected").blue().bold());

// Performance metrics implementation
// In a real implementation, this would collect and display actual performance metrics
println!("Performance Metrics:");
println!(" Response time: N/A (not implemented)");
println!(" Memory usage: N/A (not implemented)");
println!(" Token usage: N/A (not implemented)");
println!(" Cache hit rate: N/A (not implemented)");
println!(
"{}",
style("Performance metrics mode selected").blue().bold()
);
handle_performance_command().await?;
}
}

Expand Down
26 changes: 26 additions & 0 deletions tests/compaction_engine_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use vtagent_core::core::agent::types::MessageType;
use vtagent_core::core::agent::{CompactionConfig, CompactionEngine};
use vtagent_core::gemini::Content;

#[tokio::test]
async fn compaction_engine_compacts_messages() {
let mut config = CompactionConfig::default();
config.max_uncompressed_messages = 1;
let engine = CompactionEngine::with_config(config);

let msg = Content::user_text("hello world");
engine
.add_message(&msg, MessageType::UserMessage)
.await
.unwrap();
engine
.add_message(&msg, MessageType::UserMessage)
.await
.unwrap();

assert!(engine.should_compact().await.unwrap());
let result = engine.compact_messages_intelligently().await.unwrap();
assert_eq!(result.messages_compacted, 1);
let stats = engine.get_statistics().await.unwrap();
assert_eq!(stats.total_messages, 1);
}
Loading