feat(cli): add --debug flag to goose session / run (#1564)

This commit is contained in:
Kalvin C
2025-03-06 18:58:25 -08:00
committed by GitHub
parent 798d657e7e
commit 7b37ab0b52
5 changed files with 91 additions and 40 deletions

View File

@@ -83,8 +83,14 @@ async fn run_eval(
let requirements = evaluation.required_extensions();
// Create session with error capture
let base_session =
build_session(None, false, requirements.external, requirements.builtin).await;
let base_session = build_session(
None,
false,
requirements.external,
requirements.builtin,
false,
)
.await;
let bench_session = Arc::new(Mutex::new(BenchSession::new(base_session)));
let bench_session_clone = bench_session.clone();

View File

@@ -90,6 +90,14 @@ enum Command {
)]
resume: bool,
/// Enable debug output mode
#[arg(
long,
help = "Enable debug output mode with full content and no truncation",
long_help = "When enabled, shows complete tool responses without truncation and full paths."
)]
debug: bool,
/// Add stdio extensions with environment variables and commands
#[arg(
long = "with-extension",
@@ -157,6 +165,14 @@ enum Command {
)]
resume: bool,
/// Enable debug output mode
#[arg(
long,
help = "Enable debug output mode with full content and no truncation",
long_help = "When enabled, shows complete tool responses without truncation and full paths."
)]
debug: bool,
/// Add stdio extensions with environment variables and commands
#[arg(
long = "with-extension",
@@ -285,6 +301,7 @@ async fn main() -> Result<()> {
Some(Command::Session {
identifier,
resume,
debug,
extension,
builtin,
}) => {
@@ -293,6 +310,7 @@ async fn main() -> Result<()> {
resume,
extension,
builtin,
debug,
)
.await;
@@ -306,6 +324,7 @@ async fn main() -> Result<()> {
interactive,
identifier,
resume,
debug,
extension,
builtin,
}) => {
@@ -332,6 +351,7 @@ async fn main() -> Result<()> {
resume,
extension,
builtin,
debug,
)
.await;
setup_logging(session.session_file().file_stem().and_then(|s| s.to_str()))?;
@@ -410,7 +430,7 @@ async fn main() -> Result<()> {
return Ok(());
} else {
// Run session command by default
let mut session = build_session(None, false, vec![], vec![]).await;
let mut session = build_session(None, false, vec![], vec![], false).await;
setup_logging(session.session_file().file_stem().and_then(|s| s.to_str()))?;
let _ = session.interactive(None).await;
return Ok(());

View File

@@ -15,6 +15,7 @@ pub async fn build_session(
resume: bool,
extensions: Vec<String>,
builtins: Vec<String>,
debug: bool,
) -> Session {
// Load config and get provider/model
let config = Config::global();
@@ -92,7 +93,7 @@ pub async fn build_session(
};
// Create new session
let mut session = Session::new(agent, session_file.clone());
let mut session = Session::new(agent, session_file.clone(), debug);
// Add extensions if provided
for extension_str in extensions {

View File

@@ -36,6 +36,7 @@ pub struct Session {
session_file: PathBuf,
// Cache for completion data - using std::sync for thread safety without async
completion_cache: Arc<std::sync::RwLock<CompletionCache>>,
debug: bool, // New field for debug mode
}
// Cache structure for completion data
@@ -56,7 +57,7 @@ impl CompletionCache {
}
impl Session {
pub fn new(agent: Box<dyn Agent>, session_file: PathBuf) -> Self {
pub fn new(agent: Box<dyn Agent>, session_file: PathBuf, debug: bool) -> Self {
let messages = match session::read_messages(&session_file) {
Ok(msgs) => msgs,
Err(e) => {
@@ -70,6 +71,7 @@ impl Session {
messages,
session_file,
completion_cache: Arc::new(std::sync::RwLock::new(CompletionCache::new())),
debug,
}
}
@@ -393,7 +395,7 @@ impl Session {
}
if msg.role == mcp_core::Role::User {
output::render_message(&msg);
output::render_message(&msg, self.debug);
}
self.messages.push(msg);
}
@@ -461,7 +463,7 @@ impl Session {
session::persist_messages(&self.session_file, &self.messages, None).await?;
if interactive {output::hide_thinking()};
output::render_message(&message);
output::render_message(&message, self.debug);
if interactive {output::show_thinking()};
}
}
@@ -547,7 +549,7 @@ impl Session {
// No need for description update here
session::persist_messages(&self.session_file, &self.messages, None).await?;
output::render_message(&Message::assistant().with_text(&prompt));
output::render_message(&Message::assistant().with_text(&prompt), self.debug);
} else {
// An interruption occurred outside of a tool request-response.
if let Some(last_msg) = self.messages.last() {
@@ -562,13 +564,19 @@ impl Session {
session::persist_messages(&self.session_file, &self.messages, None)
.await?;
output::render_message(&Message::assistant().with_text(prompt));
output::render_message(
&Message::assistant().with_text(prompt),
self.debug,
);
}
Some(_) => {
// A real users message
self.messages.pop();
let prompt = "Interrupted before the model replied and removed the last message.";
output::render_message(&Message::assistant().with_text(prompt));
output::render_message(
&Message::assistant().with_text(prompt),
self.debug,
);
}
None => panic!("No content in last message"),
}

View File

@@ -96,14 +96,14 @@ pub fn hide_thinking() {
THINKING.with(|t| t.borrow_mut().hide());
}
pub fn render_message(message: &Message) {
pub fn render_message(message: &Message, debug: bool) {
let theme = get_theme();
for content in &message.content {
match content {
MessageContent::Text(text) => print_markdown(&text.text, theme),
MessageContent::ToolRequest(req) => render_tool_request(req, theme),
MessageContent::ToolResponse(resp) => render_tool_response(resp, theme),
MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug),
MessageContent::ToolResponse(resp) => render_tool_response(resp, theme, debug),
MessageContent::Image(image) => {
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
}
@@ -126,18 +126,18 @@ pub fn render_message(message: &Message) {
println!();
}
fn render_tool_request(req: &ToolRequest, theme: Theme) {
fn render_tool_request(req: &ToolRequest, theme: Theme, debug: bool) {
match &req.tool_call {
Ok(call) => match call.name.as_str() {
"developer__text_editor" => render_text_editor_request(call),
"developer__shell" => render_shell_request(call),
_ => render_default_request(call),
"developer__text_editor" => render_text_editor_request(call, debug),
"developer__shell" => render_shell_request(call, debug),
_ => render_default_request(call, debug),
},
Err(e) => print_markdown(&e.to_string(), theme),
}
}
fn render_tool_response(resp: &ToolResponse, theme: Theme) {
fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) {
let config = Config::global();
match &resp.tool_result {
@@ -157,12 +157,14 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme) {
if content
.priority()
.is_some_and(|priority| priority < min_priority)
|| content.priority().is_none()
|| (content.priority().is_none() && !debug)
{
continue;
}
if let mcp_core::content::Content::Text(text) = content {
if debug {
println!("{:#?}", content);
} else if let mcp_core::content::Content::Text(text) = content {
print_markdown(&text.text, theme);
}
}
@@ -266,7 +268,7 @@ pub fn render_builtin_error(names: &str, error: &str) {
println!();
}
fn render_text_editor_request(call: &ToolCall) {
fn render_text_editor_request(call: &ToolCall, debug: bool) {
print_tool_header(call);
// Print path first with special formatting
@@ -274,7 +276,7 @@ fn render_text_editor_request(call: &ToolCall) {
println!(
"{}: {}",
style("path").dim(),
style(shorten_path(path)).green()
style(shorten_path(path, debug)).green()
);
}
@@ -286,26 +288,26 @@ fn render_text_editor_request(call: &ToolCall) {
other_args.insert(k.clone(), v.clone());
}
}
print_params(&Value::Object(other_args), 0);
print_params(&Value::Object(other_args), 0, debug);
}
println!();
}
fn render_shell_request(call: &ToolCall) {
fn render_shell_request(call: &ToolCall, debug: bool) {
print_tool_header(call);
match call.arguments.get("command") {
Some(Value::String(s)) => {
println!("{}: {}", style("command").dim(), style(s).green());
}
_ => print_params(&call.arguments, 0),
_ => print_params(&call.arguments, 0, debug),
}
println!();
}
fn render_default_request(call: &ToolCall) {
fn render_default_request(call: &ToolCall, debug: bool) {
print_tool_header(call);
print_params(&call.arguments, 0);
print_params(&call.arguments, 0, debug);
println!();
}
@@ -342,7 +344,7 @@ fn print_markdown(content: &str, theme: Theme) {
const MAX_STRING_LENGTH: usize = 40;
const INDENT: &str = " ";
fn print_params(value: &Value, depth: usize) {
fn print_params(value: &Value, depth: usize, debug: bool) {
let indent = INDENT.repeat(depth);
match value {
@@ -351,17 +353,17 @@ fn print_params(value: &Value, depth: usize) {
match val {
Value::Object(_) => {
println!("{}{}:", indent, style(key).dim());
print_params(val, depth + 1);
print_params(val, depth + 1, debug);
}
Value::Array(arr) => {
println!("{}{}:", indent, style(key).dim());
for item in arr.iter() {
println!("{}{}- ", indent, INDENT);
print_params(item, depth + 2);
print_params(item, depth + 2, debug);
}
}
Value::String(s) => {
if s.len() > MAX_STRING_LENGTH {
if !debug && s.len() > MAX_STRING_LENGTH {
println!("{}{}: {}", indent, style(key).dim(), style("...").dim());
} else {
println!("{}{}: {}", indent, style(key).dim(), style(s).green());
@@ -382,11 +384,11 @@ fn print_params(value: &Value, depth: usize) {
Value::Array(arr) => {
for (i, item) in arr.iter().enumerate() {
println!("{}{}.", indent, i + 1);
print_params(item, depth + 1);
print_params(item, depth + 1, debug);
}
}
Value::String(s) => {
if s.len() > MAX_STRING_LENGTH {
if !debug && s.len() > MAX_STRING_LENGTH {
println!(
"{}{}",
indent,
@@ -408,7 +410,12 @@ fn print_params(value: &Value, depth: usize) {
}
}
fn shorten_path(path: &str) -> String {
fn shorten_path(path: &str, debug: bool) -> String {
// In debug mode, return the full path
if debug {
return path.to_string();
}
let path = Path::new(path);
// First try to convert to ~ if it's in home directory
@@ -485,9 +492,17 @@ mod tests {
#[test]
fn test_short_paths_unchanged() {
assert_eq!(shorten_path("/usr/bin"), "/usr/bin");
assert_eq!(shorten_path("/a/b/c"), "/a/b/c");
assert_eq!(shorten_path("file.txt"), "file.txt");
assert_eq!(shorten_path("/usr/bin", false), "/usr/bin");
assert_eq!(shorten_path("/a/b/c", false), "/a/b/c");
assert_eq!(shorten_path("file.txt", false), "file.txt");
}
#[test]
fn test_debug_mode_returns_full_path() {
assert_eq!(
shorten_path("/very/long/path/that/would/normally/be/shortened", true),
"/very/long/path/that/would/normally/be/shortened"
);
}
#[test]
@@ -499,13 +514,13 @@ mod tests {
env::set_var("HOME", "/Users/testuser");
assert_eq!(
shorten_path("/Users/testuser/documents/file.txt"),
shorten_path("/Users/testuser/documents/file.txt", false),
"~/documents/file.txt"
);
// A path that starts similarly to home but isn't in home
assert_eq!(
shorten_path("/Users/testuser2/documents/file.txt"),
shorten_path("/Users/testuser2/documents/file.txt", false),
"/Users/testuser2/documents/file.txt"
);
@@ -521,7 +536,8 @@ mod tests {
fn test_long_path_shortening() {
assert_eq!(
shorten_path(
"/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv/long/path/with/many/components/file.txt"
"/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv/long/path/with/many/components/file.txt",
false
),
"/v/l/p/w/m/components/file.txt"
);