Simplify agent mode working directory display

Change from: 📁 Working directory: "/Users/dhanji/src/g3"
To: -> ~/src/g3

Replaces home directory with ~ for cleaner output.
This commit is contained in:
Dhanji R. Prasanna
2026-01-11 17:20:26 +05:30
parent f83ae7fd39
commit 4962f439f3
2 changed files with 68 additions and 202 deletions

View File

@@ -540,7 +540,14 @@ async fn run_agent_mode(
}; };
output.print(&format!("🤖 Running as agent: {}", agent_name)); output.print(&format!("🤖 Running as agent: {}", agent_name));
output.print(&format!("📁 Working directory: {:?}", workspace_dir)); // Format workspace path, replacing home dir with ~
let workspace_display = {
let path_str = workspace_dir.display().to_string();
dirs::home_dir()
.and_then(|home| path_str.strip_prefix(&home.display().to_string()).map(|s| format!("~{}", s)))
.unwrap_or(path_str)
};
output.print(&format!("-> {}", workspace_display));
// Load config // Load config
let mut config = g3_config::Config::load(config_path)?; let mut config = g3_config::Config::load(config_path)?;

View File

@@ -962,6 +962,8 @@ impl<W: UiWriter> Agent<W> {
/// Manually trigger context compaction regardless of context window size /// Manually trigger context compaction regardless of context window size
/// Returns Ok(true) if compaction was successful, Ok(false) if it failed /// Returns Ok(true) if compaction was successful, Ok(false) if it failed
pub async fn force_compact(&mut self) -> Result<bool> { pub async fn force_compact(&mut self) -> Result<bool> {
use crate::compaction::{CompactionConfig, perform_compaction};
debug!("Manual compaction triggered"); debug!("Manual compaction triggered");
self.ui_writer.print_context_status(&format!( self.ui_writer.print_context_status(&format!(
@@ -973,87 +975,6 @@ impl<W: UiWriter> Agent<W> {
let provider_name = provider.name().to_string(); let provider_name = provider.name().to_string();
let _ = provider; // Release borrow early let _ = provider; // Release borrow early
// Apply fallback sequence: thinnify -> skinnify -> hard-coded 5000
let mut summary_max_tokens = self.apply_summary_fallback_sequence(&provider_name);
// Apply provider-specific caps
// For Anthropic with thinking enabled, we need max_tokens > thinking.budget_tokens
// So we set a higher cap when thinking is configured
let anthropic_cap = match self.get_thinking_budget_tokens(&provider_name) {
Some(budget) => (budget + 2000).max(10_000), // At least budget + 2000 for response
None => 10_000,
};
summary_max_tokens = match provider_name.as_str() {
"anthropic" => summary_max_tokens.min(anthropic_cap),
"databricks" => summary_max_tokens.min(10_000),
"embedded" => summary_max_tokens.min(3000),
_ => summary_max_tokens.min(5000),
};
// Ensure minimum floor as defense-in-depth (primary protection is in calculate_summary_max_tokens)
summary_max_tokens = summary_max_tokens.max(Self::SUMMARY_MIN_TOKENS);
debug!(
"Requesting summary with max_tokens: {} (current usage: {} tokens)",
summary_max_tokens, self.context_window.used_tokens
);
// Create summary request with FULL history
let summary_prompt = self.context_window.create_summary_prompt();
// Get the full conversation history
let conversation_text = self
.context_window
.conversation_history
.iter()
.map(|m| format!("{:?}: {}", m.role, m.content))
.collect::<Vec<_>>()
.join("\n\n");
let summary_messages = vec![
Message::new(
MessageRole::System,
"You are a helpful assistant that creates concise summaries.".to_string(),
),
Message::new(
MessageRole::User,
format!(
"Based on this conversation history, {}\n\nConversation:\n{}",
summary_prompt, conversation_text
),
),
];
let provider = self.providers.get(None)?;
// Determine if we need to disable thinking mode for this request
// Anthropic requires: max_tokens > thinking.budget_tokens + 1024
let disable_thinking = self.get_thinking_budget_tokens(provider.name()).map_or(false, |budget| {
let minimum_for_thinking = budget + 1024;
let should_disable = summary_max_tokens <= minimum_for_thinking;
if should_disable {
tracing::warn!("Disabling thinking mode for summary: max_tokens ({}) <= minimum_for_thinking ({})", summary_max_tokens, minimum_for_thinking);
}
should_disable
});
tracing::debug!("Creating summary request: max_tokens={}, disable_thinking={}", summary_max_tokens, disable_thinking);
let summary_request = CompletionRequest {
messages: summary_messages,
max_tokens: Some(summary_max_tokens),
temperature: Some(self.resolve_temperature(provider.name())),
stream: false,
tools: None,
disable_thinking,
};
// Get the summary
match provider.complete(summary_request).await {
Ok(summary_response) => {
self.ui_writer
.print_context_status("✅ Context compacted successfully.\n");
// Get the latest user message to preserve it // Get the latest user message to preserve it
let latest_user_msg = self let latest_user_msg = self
.context_window .context_window
@@ -1063,24 +984,31 @@ impl<W: UiWriter> Agent<W> {
.find(|m| matches!(m.role, MessageRole::User)) .find(|m| matches!(m.role, MessageRole::User))
.map(|m| m.content.clone()); .map(|m| m.content.clone());
// Reset context with summary let compaction_config = CompactionConfig {
let chars_saved = self provider_name: &provider_name,
.context_window latest_user_msg,
.reset_with_summary(summary_response.content, latest_user_msg); };
self.compaction_events.push(chars_saved);
let result = perform_compaction(
&self.providers,
&mut self.context_window,
&self.config,
compaction_config,
&self.ui_writer,
&mut self.thinning_events,
).await?;
if result.success {
self.ui_writer.print_context_status("✅ Context compacted successfully.\n");
self.compaction_events.push(result.chars_saved);
Ok(true) Ok(true)
} } else {
Err(e) => {
error!("Failed to create summary: {}", e);
self.ui_writer.print_context_status( self.ui_writer.print_context_status(
"⚠️ Unable to create summary. Please try again or start a new session.\n", "⚠️ Unable to create summary. Please try again or start a new session.\n",
); );
Ok(false) Ok(false)
} }
} }
}
/// Manually trigger context thinning regardless of thresholds /// Manually trigger context thinning regardless of thresholds
pub fn force_thin(&mut self) -> String { pub fn force_thin(&mut self) -> String {
debug!("Manual context thinning triggered"); debug!("Manual context thinning triggered");
@@ -1773,6 +1701,8 @@ impl<W: UiWriter> Agent<W> {
// Only proceed with compaction if still needed after thinning // Only proceed with compaction if still needed after thinning
if self.context_window.should_compact() { if self.context_window.should_compact() {
use crate::compaction::{CompactionConfig, perform_compaction};
// Notify user about compaction // Notify user about compaction
self.ui_writer.print_context_status(&format!( self.ui_writer.print_context_status(&format!(
"\n🗜️ Context window reaching capacity ({}%). Compacting...", "\n🗜️ Context window reaching capacity ({}%). Compacting...",
@@ -1783,89 +1713,7 @@ impl<W: UiWriter> Agent<W> {
let provider_name = provider.name().to_string(); let provider_name = provider.name().to_string();
let _ = provider; // Release borrow early let _ = provider; // Release borrow early
// Apply fallback sequence: thinnify -> skinnify -> hard-coded 5000 // Extract the latest user message from the request (not context_window)
let mut summary_max_tokens = self.apply_summary_fallback_sequence(&provider_name);
// Apply provider-specific caps
// For Anthropic with thinking enabled, we need max_tokens > thinking.budget_tokens
// So we set a higher cap when thinking is configured
let anthropic_cap = match self.get_thinking_budget_tokens(&provider_name) {
Some(budget) => (budget + 2000).max(10_000), // At least budget + 2000 for response
None => 10_000,
};
summary_max_tokens = match provider_name.as_str() {
"anthropic" => summary_max_tokens.min(anthropic_cap),
"databricks" => summary_max_tokens.min(10_000),
"embedded" => summary_max_tokens.min(3000),
_ => summary_max_tokens.min(5000),
};
// Ensure minimum floor as defense-in-depth (primary protection is in calculate_summary_max_tokens)
summary_max_tokens = summary_max_tokens.max(Self::SUMMARY_MIN_TOKENS);
debug!(
"Requesting summary with max_tokens: {} (current usage: {} tokens)",
summary_max_tokens, self.context_window.used_tokens
);
// Create summary request with FULL history
let summary_prompt = self.context_window.create_summary_prompt();
// Get the full conversation history
let conversation_text = self
.context_window
.conversation_history
.iter()
.map(|m| format!("{:?}: {}", m.role, m.content))
.collect::<Vec<_>>()
.join("\n\n");
let summary_messages = vec![
Message::new(
MessageRole::System,
"You are a helpful assistant that creates concise summaries.".to_string(),
),
Message::new(
MessageRole::User,
format!(
"Based on this conversation history, {}\n\nConversation:\n{}",
summary_prompt, conversation_text
),
),
];
let provider = self.providers.get(None)?;
// Determine if we need to disable thinking mode for this request
// Anthropic requires: max_tokens > thinking.budget_tokens + 1024
let disable_thinking = self.get_thinking_budget_tokens(provider.name()).map_or(false, |budget| {
let minimum_for_thinking = budget + 1024;
let should_disable = summary_max_tokens <= minimum_for_thinking;
if should_disable {
tracing::warn!("Disabling thinking mode for summary: max_tokens ({}) <= minimum_for_thinking ({})", summary_max_tokens, minimum_for_thinking);
}
should_disable
});
tracing::debug!("Creating auto-summary request: max_tokens={}, disable_thinking={}", summary_max_tokens, disable_thinking);
let summary_request = CompletionRequest {
messages: summary_messages,
max_tokens: Some(summary_max_tokens),
temperature: Some(self.resolve_temperature(provider.name())),
stream: false,
tools: None,
disable_thinking,
};
// Get the summary
match provider.complete(summary_request).await {
Ok(summary_response) => {
self.ui_writer.print_context_status(
"✅ Context compacted successfully. Continuing...\n",
);
// Extract the latest user message from the request
let latest_user_msg = request let latest_user_msg = request
.messages .messages
.iter() .iter()
@@ -1873,17 +1721,29 @@ impl<W: UiWriter> Agent<W> {
.find(|m| matches!(m.role, MessageRole::User)) .find(|m| matches!(m.role, MessageRole::User))
.map(|m| m.content.clone()); .map(|m| m.content.clone());
// Reset context with summary let compaction_config = CompactionConfig {
let chars_saved = self provider_name: &provider_name,
.context_window latest_user_msg,
.reset_with_summary(summary_response.content, latest_user_msg); };
self.compaction_events.push(chars_saved);
let result = perform_compaction(
&self.providers,
&mut self.context_window,
&self.config,
compaction_config,
&self.ui_writer,
&mut self.thinning_events,
).await?;
if result.success {
self.ui_writer.print_context_status(
"✅ Context compacted successfully. Continuing...\n",
);
self.compaction_events.push(result.chars_saved);
// Update the request with new context // Update the request with new context
request.messages = self.context_window.conversation_history.clone(); request.messages = self.context_window.conversation_history.clone();
} } else {
Err(e) => {
error!("Failed to create summary: {}", e);
self.ui_writer.print_context_status("⚠️ Unable to compact context. Consider starting a new session if you continue to see errors.\n"); self.ui_writer.print_context_status("⚠️ Unable to compact context. Consider starting a new session if you continue to see errors.\n");
// Don't continue with the original request if compaction failed // Don't continue with the original request if compaction failed
// as we're likely at token limit // as we're likely at token limit
@@ -1891,7 +1751,6 @@ impl<W: UiWriter> Agent<W> {
} }
} }
} }
}
loop { loop {
iteration_count += 1; iteration_count += 1;