Skip to content

Commit d494701

Browse files
committed
feat: Improve terminal detection and configuration path resolution across operating systems, update LLM provider integrations, and refine agent slash commands and welcome flow.
1 parent 1c66819 commit d494701

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+492
-344
lines changed

.vtcode/tool-policy.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"analyze_agent": "allow",
3232
"apply_patch": "prompt",
3333
"close_pty_session": "allow",
34-
"create_file": "prompt",
34+
"create_file": "allow",
3535
"create_pty_session": "allow",
3636
"debug_agent": "allow",
3737
"delete_file": "prompt",
@@ -52,12 +52,13 @@
5252
"search_replace": "prompt",
5353
"skill": "allow",
5454
"code_intelligence": "prompt",
55+
"mcp_get_current_time": "prompt",
56+
"mcp_time_get_current_time": "prompt",
5557
"mcp_convert_time": "prompt",
5658
"mcp_time_convert_time": "prompt",
5759
"mcp_fetch": "prompt",
5860
"mcp_fetch_fetch": "prompt",
59-
"mcp_get_current_time": "prompt",
60-
"mcp_time_get_current_time": "prompt"
61+
"run_pty": "allow"
6162
},
6263
"constraints": {},
6364
"mcp": {

src/agent/runloop/context_usage.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,14 @@ impl ContextUsageInfo {
8686
(self.current_tokens as f64 / self.max_tokens as f64) * 100.0
8787
}
8888

89-
9089
/// Add MCP tool
9190
pub fn add_mcp_tool(&mut self, name: impl Into<String>, tokens: usize) {
9291
self.mcp_tools.push(ContextItem::new(name, tokens));
9392
self.mcp_tools.sort_by(|a, b| b.tokens.cmp(&a.tokens));
9493
self.mcp_tools_tokens = self.mcp_tools.iter().map(|t| t.tokens).sum();
9594
}
96-
9795
}
9896

99-
10097
/// Format token count for display (e.g., "3.2k" or "150")
10198
fn format_tokens(tokens: usize) -> String {
10299
if tokens >= 1000 {
@@ -107,10 +104,7 @@ fn format_tokens(tokens: usize) -> String {
107104
}
108105

109106
/// Render the context usage visualization
110-
pub fn render_context_usage(
111-
renderer: &mut AnsiRenderer,
112-
info: &ContextUsageInfo,
113-
) -> Result<()> {
107+
pub fn render_context_usage(renderer: &mut AnsiRenderer, info: &ContextUsageInfo) -> Result<()> {
114108
renderer.line(MessageStyle::Info, "")?;
115109
renderer.line(MessageStyle::Info, "Context Usage")?;
116110

@@ -205,7 +199,9 @@ fn render_token_bar(renderer: &mut AnsiRenderer, info: &ContextUsageInfo) -> Res
205199
// Calculate cells for each type
206200
let used_cells = ((usage_percent / 100.0) * total_cells as f64).round() as usize;
207201
let buffer_cells = ((buffer_percent / 100.0) * total_cells as f64).round() as usize;
208-
let free_cells = total_cells.saturating_sub(used_cells).saturating_sub(buffer_cells);
202+
let free_cells = total_cells
203+
.saturating_sub(used_cells)
204+
.saturating_sub(buffer_cells);
209205

210206
// Build the bar symbols
211207
let mut symbols: Vec<char> = Vec::with_capacity(total_cells);
@@ -267,7 +263,8 @@ fn render_token_bar(renderer: &mut AnsiRenderer, info: &ContextUsageInfo) -> Res
267263

268264
// Render free space and buffer info
269265
let free_tokens = info.free_tokens();
270-
let buffer_tokens = ((info.autocompact_buffer_percent / 100.0) * info.max_tokens as f64) as usize;
266+
let buffer_tokens =
267+
((info.autocompact_buffer_percent / 100.0) * info.max_tokens as f64) as usize;
271268

272269
let row8: String = symbols[80..90].iter().collect();
273270
renderer.line(

src/agent/runloop/unified/status_line.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use vtcode_core::models::ModelId;
1515
use vtcode_core::ui::tui::InlineHandle;
1616
use vtcode_core::utils::ansi_parser::strip_ansi;
1717

18-
use vtcode_core::terminal_setup::detector::TerminalType;
1918
use crate::agent::runloop::git::{GitStatusSummary, git_status_summary};
19+
use vtcode_core::terminal_setup::detector::TerminalType;
2020

2121
#[derive(Default, Clone)]
2222
pub(crate) struct InputStatusState {

src/agent/runloop/unified/turn/session/slash_commands/handlers.rs

Lines changed: 85 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ use std::sync::Arc;
33
use anyhow::{Context, Result};
44

55
use vtcode_core::commands::init::{GenerateAgentsFileStatus, generate_agents_file};
6+
use vtcode_core::config::constants::tools as tools_consts;
67
use vtcode_core::core::decision_tracker::DecisionTracker;
8+
use vtcode_core::llm::provider as uni;
79
use vtcode_core::ui::theme;
810
use vtcode_core::ui::tui::theme_from_styles;
911
use vtcode_core::utils::ansi::MessageStyle;
1012
use vtcode_core::utils::session_archive;
1113
use vtcode_core::utils::transcript;
12-
use vtcode_core::llm::provider as uni;
13-
use vtcode_core::config::constants::tools as tools_consts;
1414

15-
use crate::agent::runloop::unified::turn::workspace::{bootstrap_config_files, build_workspace_index};
1615
use crate::agent::runloop::model_picker::{ModelPickerStart, ModelPickerState};
1716
use crate::agent::runloop::slash_commands::McpCommandAction;
1817
use crate::agent::runloop::unified::diagnostics::run_doctor_diagnostics;
@@ -28,13 +27,16 @@ use crate::agent::runloop::unified::palettes::{
2827
};
2928
use crate::agent::runloop::unified::state::SessionStats;
3029
use crate::agent::runloop::unified::tool_routing::{ToolPermissionFlow, ensure_tool_permission};
30+
use crate::agent::runloop::unified::turn::workspace::{
31+
bootstrap_config_files, build_workspace_index,
32+
};
3133
use crate::agent::runloop::unified::ui_interaction::{display_session_status, display_token_cost};
3234
use crate::agent::runloop::unified::workspace_links::handle_workspace_directory_command;
3335
use crate::hooks::lifecycle::SessionEndReason;
3436
use webbrowser;
3537

36-
use crate::agent::runloop::unified::turn::config_modal::load_config_modal_content;
3738
use super::{SlashCommandContext, SlashCommandControl};
39+
use crate::agent::runloop::unified::turn::config_modal::load_config_modal_content;
3840

3941
pub async fn handle_debug_agent(ctx: SlashCommandContext<'_>) -> Result<SlashCommandControl> {
4042
// Prefer tool-driven diagnostics when available
@@ -195,15 +197,21 @@ pub async fn handle_analyze_agent(ctx: SlashCommandContext<'_>) -> Result<SlashC
195197
Ok(SlashCommandControl::Continue)
196198
}
197199

198-
pub async fn handle_theme_changed(ctx: SlashCommandContext<'_>, theme_id: String) -> Result<SlashCommandControl> {
200+
pub async fn handle_theme_changed(
201+
ctx: SlashCommandContext<'_>,
202+
theme_id: String,
203+
) -> Result<SlashCommandControl> {
199204
persist_theme_preference(ctx.renderer, &theme_id).await?;
200205
let styles = theme::active_styles();
201206
ctx.handle.set_theme(theme_from_styles(&styles));
202207
apply_prompt_style(ctx.handle);
203208
Ok(SlashCommandControl::Continue)
204209
}
205210

206-
pub async fn handle_start_theme_palette(ctx: SlashCommandContext<'_>, mode: crate::agent::runloop::slash_commands::ThemePaletteMode) -> Result<SlashCommandControl> {
211+
pub async fn handle_start_theme_palette(
212+
ctx: SlashCommandContext<'_>,
213+
mode: crate::agent::runloop::slash_commands::ThemePaletteMode,
214+
) -> Result<SlashCommandControl> {
207215
if ctx.model_picker_state.is_some() {
208216
ctx.renderer.line(
209217
MessageStyle::Error,
@@ -224,7 +232,10 @@ pub async fn handle_start_theme_palette(ctx: SlashCommandContext<'_>, mode: crat
224232
Ok(SlashCommandControl::Continue)
225233
}
226234

227-
pub async fn handle_start_sessions_palette(ctx: SlashCommandContext<'_>, limit: usize) -> Result<SlashCommandControl> {
235+
pub async fn handle_start_sessions_palette(
236+
ctx: SlashCommandContext<'_>,
237+
limit: usize,
238+
) -> Result<SlashCommandControl> {
228239
if ctx.model_picker_state.is_some() {
229240
ctx.renderer.line(
230241
MessageStyle::Error,
@@ -255,9 +266,12 @@ pub async fn handle_start_sessions_palette(ctx: SlashCommandContext<'_>, limit:
255266
Ok(SlashCommandControl::Continue)
256267
}
257268

258-
pub async fn handle_start_file_browser(ctx: SlashCommandContext<'_>, initial_filter: Option<String>) -> Result<SlashCommandControl> {
269+
pub async fn handle_start_file_browser(
270+
ctx: SlashCommandContext<'_>,
271+
initial_filter: Option<String>,
272+
) -> Result<SlashCommandControl> {
259273
if ctx.model_picker_state.is_some() {
260-
ctx.renderer.line(
274+
ctx.renderer.line(
261275
MessageStyle::Error,
262276
"Close the active model picker before opening file browser.",
263277
)?;
@@ -283,7 +297,9 @@ pub async fn handle_start_file_browser(ctx: SlashCommandContext<'_>, initial_fil
283297
Ok(SlashCommandControl::Continue)
284298
}
285299

286-
pub async fn handle_start_model_selection(ctx: SlashCommandContext<'_>) -> Result<SlashCommandControl> {
300+
pub async fn handle_start_model_selection(
301+
ctx: SlashCommandContext<'_>,
302+
) -> Result<SlashCommandControl> {
287303
if ctx.model_picker_state.is_some() {
288304
ctx.renderer.line(
289305
MessageStyle::Error,
@@ -331,7 +347,10 @@ pub async fn handle_start_model_selection(ctx: SlashCommandContext<'_>) -> Resul
331347
Ok(SlashCommandControl::Continue)
332348
}
333349

334-
pub async fn handle_initialize_workspace(ctx: SlashCommandContext<'_>, force: bool) -> Result<SlashCommandControl> {
350+
pub async fn handle_initialize_workspace(
351+
ctx: SlashCommandContext<'_>,
352+
force: bool,
353+
) -> Result<SlashCommandControl> {
335354
let workspace_path = ctx.config.workspace.clone();
336355
let workspace_label = workspace_path.display().to_string();
337356
ctx.renderer.line(
@@ -391,14 +410,16 @@ pub async fn handle_initialize_workspace(ctx: SlashCommandContext<'_>, force: bo
391410
Ok(SlashCommandControl::Continue)
392411
}
393412

394-
pub async fn handle_generate_agent_file(ctx: SlashCommandContext<'_>, overwrite: bool) -> Result<SlashCommandControl> {
413+
pub async fn handle_generate_agent_file(
414+
ctx: SlashCommandContext<'_>,
415+
overwrite: bool,
416+
) -> Result<SlashCommandControl> {
395417
let workspace_path = ctx.config.workspace.clone();
396418
ctx.renderer.line(
397419
MessageStyle::Info,
398420
"Generating AGENTS.md guidance. This may take a moment...",
399421
)?;
400-
match generate_agents_file(ctx.tool_registry, workspace_path.as_path(), overwrite).await
401-
{
422+
match generate_agents_file(ctx.tool_registry, workspace_path.as_path(), overwrite).await {
402423
Ok(report) => match report.status {
403424
GenerateAgentsFileStatus::Created => {
404425
ctx.renderer.line(
@@ -457,7 +478,11 @@ pub async fn handle_show_config(ctx: SlashCommandContext<'_>) -> Result<SlashCom
457478
Ok(SlashCommandControl::Continue)
458479
}
459480

460-
pub async fn handle_execute_tool(ctx: SlashCommandContext<'_>, name: String, args: serde_json::Value) -> Result<SlashCommandControl> {
481+
pub async fn handle_execute_tool(
482+
ctx: SlashCommandContext<'_>,
483+
name: String,
484+
args: serde_json::Value,
485+
) -> Result<SlashCommandControl> {
461486
match ensure_tool_permission(
462487
ctx.tool_registry,
463488
&name,
@@ -496,7 +521,9 @@ pub async fn handle_execute_tool(ctx: SlashCommandContext<'_>, name: String, arg
496521
}
497522
}
498523

499-
pub async fn handle_clear_conversation(ctx: SlashCommandContext<'_>) -> Result<SlashCommandControl> {
524+
pub async fn handle_clear_conversation(
525+
ctx: SlashCommandContext<'_>,
526+
) -> Result<SlashCommandControl> {
500527
ctx.conversation_history.clear();
501528
*ctx.session_stats = SessionStats::default();
502529
{
@@ -552,10 +579,7 @@ pub async fn handle_show_context(ctx: SlashCommandContext<'_>) -> Result<SlashCo
552579
use crate::agent::runloop::context_usage::{ContextUsageInfo, render_context_usage};
553580

554581
// Build context usage info from current session state
555-
let mut info = ContextUsageInfo::new(
556-
&ctx.config.model,
557-
ctx.trim_config.max_tokens,
558-
);
582+
let mut info = ContextUsageInfo::new(&ctx.config.model, ctx.trim_config.max_tokens);
559583

560584
// Get token budget stats
561585
let token_budget = ctx.context_manager.token_budget();
@@ -570,7 +594,9 @@ pub async fn handle_show_context(ctx: SlashCommandContext<'_>) -> Result<SlashCo
570594
info.system_tools_tokens = tool_count * 50; // ~50 tokens per tool definition
571595

572596
// Count messages tokens from conversation history
573-
info.messages_tokens = ctx.conversation_history.iter()
597+
info.messages_tokens = ctx
598+
.conversation_history
599+
.iter()
574600
.map(|msg| msg.content.as_text().len() / 4)
575601
.sum();
576602

@@ -581,9 +607,15 @@ pub async fn handle_show_context(ctx: SlashCommandContext<'_>) -> Result<SlashCo
581607
// Determine scope based on path
582608
let path_str = skill.path.to_string_lossy();
583609
if path_str.contains("/.vtcode/skills") || path_str.contains("/.claude/skills") {
584-
info.user_skills.push(crate::agent::runloop::context_usage::ContextItem::new(name, tokens));
610+
info.user_skills
611+
.push(crate::agent::runloop::context_usage::ContextItem::new(
612+
name, tokens,
613+
));
585614
} else {
586-
info.project_skills.push(crate::agent::runloop::context_usage::ContextItem::new(name, tokens));
615+
info.project_skills
616+
.push(crate::agent::runloop::context_usage::ContextItem::new(
617+
name, tokens,
618+
));
587619
}
588620
}
589621

@@ -606,7 +638,10 @@ pub async fn handle_show_context(ctx: SlashCommandContext<'_>) -> Result<SlashCo
606638
Ok(SlashCommandControl::Continue)
607639
}
608640

609-
pub async fn handle_manage_mcp(ctx: SlashCommandContext<'_>, action: McpCommandAction) -> Result<SlashCommandControl> {
641+
pub async fn handle_manage_mcp(
642+
ctx: SlashCommandContext<'_>,
643+
action: McpCommandAction,
644+
) -> Result<SlashCommandControl> {
610645
let manager = ctx.async_mcp_manager.map(|m| m.as_ref());
611646
match action {
612647
McpCommandAction::Overview => {
@@ -638,8 +673,7 @@ pub async fn handle_manage_mcp(ctx: SlashCommandContext<'_>, action: McpCommandA
638673
.await?;
639674
}
640675
McpCommandAction::EditConfig => {
641-
render_mcp_config_edit_guidance(ctx.renderer, ctx.config.workspace.as_path())
642-
.await?;
676+
render_mcp_config_edit_guidance(ctx.renderer, ctx.config.workspace.as_path()).await?;
643677
}
644678
McpCommandAction::Repair => {
645679
repair_mcp_runtime(
@@ -687,15 +721,22 @@ pub async fn handle_run_doctor(ctx: SlashCommandContext<'_>) -> Result<SlashComm
687721
Ok(SlashCommandControl::Continue)
688722
}
689723

690-
pub async fn handle_start_terminal_setup(ctx: SlashCommandContext<'_>) -> Result<SlashCommandControl> {
691-
let vt_cfg = ctx.vt_cfg.as_ref().context("VT Code configuration not available")?;
692-
vtcode_core::terminal_setup::run_terminal_setup_wizard(ctx.renderer, vt_cfg)
693-
.await?;
724+
pub async fn handle_start_terminal_setup(
725+
ctx: SlashCommandContext<'_>,
726+
) -> Result<SlashCommandControl> {
727+
let vt_cfg = ctx
728+
.vt_cfg
729+
.as_ref()
730+
.context("VT Code configuration not available")?;
731+
vtcode_core::terminal_setup::run_terminal_setup_wizard(ctx.renderer, vt_cfg).await?;
694732
ctx.renderer.line_if_not_empty(MessageStyle::Output)?;
695733
Ok(SlashCommandControl::Continue)
696734
}
697735

698-
pub async fn handle_manage_workspace_directories(ctx: SlashCommandContext<'_>, command: crate::agent::runloop::slash_commands::WorkspaceDirectoryCommand) -> Result<SlashCommandControl> {
736+
pub async fn handle_manage_workspace_directories(
737+
ctx: SlashCommandContext<'_>,
738+
command: crate::agent::runloop::slash_commands::WorkspaceDirectoryCommand,
739+
) -> Result<SlashCommandControl> {
699740
handle_workspace_directory_command(
700741
ctx.renderer,
701742
&ctx.config.workspace,
@@ -737,7 +778,9 @@ pub async fn handle_open_docs(ctx: SlashCommandContext<'_>) -> Result<SlashComma
737778
Ok(SlashCommandControl::Continue)
738779
}
739780

740-
pub async fn handle_show_pruning_report(ctx: SlashCommandContext<'_>) -> Result<SlashCommandControl> {
781+
pub async fn handle_show_pruning_report(
782+
ctx: SlashCommandContext<'_>,
783+
) -> Result<SlashCommandControl> {
741784
ctx.renderer.line(MessageStyle::Info, "Pruning Report:")?;
742785
let ledger = ctx.pruning_ledger.read().await;
743786
let report = ledger.generate_report();
@@ -786,7 +829,10 @@ pub async fn handle_show_pruning_report(ctx: SlashCommandContext<'_>) -> Result<
786829
Ok(SlashCommandControl::Continue)
787830
}
788831

789-
pub async fn handle_launch_editor(ctx: SlashCommandContext<'_>, file: Option<String>) -> Result<SlashCommandControl> {
832+
pub async fn handle_launch_editor(
833+
ctx: SlashCommandContext<'_>,
834+
file: Option<String>,
835+
) -> Result<SlashCommandControl> {
790836
use std::path::PathBuf;
791837
use vtcode_core::tools::terminal_app::TerminalAppLauncher;
792838

@@ -882,7 +928,10 @@ pub async fn handle_launch_git(ctx: SlashCommandContext<'_>) -> Result<SlashComm
882928
Ok(SlashCommandControl::Continue)
883929
}
884930

885-
pub async fn handle_manage_skills(ctx: SlashCommandContext<'_>, action: crate::agent::runloop::SkillCommandAction) -> Result<SlashCommandControl> {
931+
pub async fn handle_manage_skills(
932+
ctx: SlashCommandContext<'_>,
933+
action: crate::agent::runloop::SkillCommandAction,
934+
) -> Result<SlashCommandControl> {
886935
use crate::agent::runloop::handle_skill_command;
887936
use vtcode_core::config::types::CapabilityLevel;
888937
use vtcode_core::skills::executor::SkillToolAdapter;
@@ -905,11 +954,8 @@ pub async fn handle_manage_skills(ctx: SlashCommandContext<'_>, action: crate::a
905954

906955
let name_static: &'static str = Box::leak(Box::new(skill_name.clone()));
907956

908-
let registration = ToolRegistration::from_tool(
909-
name_static,
910-
CapabilityLevel::Bash,
911-
adapter_arc,
912-
);
957+
let registration =
958+
ToolRegistration::from_tool(name_static, CapabilityLevel::Bash, adapter_arc);
913959

914960
if let Err(e) = ctx.tool_registry.register_tool(registration) {
915961
ctx.renderer.line(

0 commit comments

Comments
 (0)