mirror of
https://github.com/block/goose.git
synced 2026-04-26 10:40:45 +00:00
nit: show dir in title, and less... jank (#7138)
Some checks failed
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check OpenAPI Schema is Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Deploy Documentation / deploy (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Some checks failed
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check OpenAPI Schema is Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Deploy Documentation / deploy (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
This commit is contained in:
parent
860c7d7b97
commit
85348d2745
8 changed files with 183 additions and 216 deletions
|
|
@ -410,10 +410,7 @@ impl Hinter for GooseCompleter {
|
|||
}
|
||||
HintStatus::Default => {
|
||||
let newline_key = super::input::get_newline_key().to_ascii_uppercase();
|
||||
Some(format!(
|
||||
"Press Enter to send, Ctrl-{} for new line",
|
||||
newline_key
|
||||
))
|
||||
Some(format!("Enter to send · Ctrl+{} newline", newline_key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -398,18 +398,12 @@ fn parse_plan_command(input: String) -> Option<InputResult> {
|
|||
Some(InputResult::Plan(options))
|
||||
}
|
||||
|
||||
/// Generates the input prompt string for the CLI interface.
|
||||
/// Returns a styled prompt with the goose face "( O)>" followed by a space.
|
||||
/// On Windows, returns plain text without ANSI styling for better compatibility.
|
||||
/// On other platforms, applies styling using ANSI escape codes.
|
||||
fn get_input_prompt_string() -> String {
|
||||
let goose = "( O)>";
|
||||
let goose = "🪿";
|
||||
if cfg!(target_os = "windows") {
|
||||
// Use plain text on Windows to avoid ANSI compatibility issues
|
||||
format!("{goose} ")
|
||||
} else {
|
||||
// On other platforms, use styled prompt with ANSI colors
|
||||
format!("{} ", console::style(goose).cyan().bold())
|
||||
format!("{} ", console::style(goose))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -702,38 +696,14 @@ mod tests {
|
|||
let prompt = get_input_prompt_string();
|
||||
|
||||
// Prompt should always end with a space
|
||||
assert!(prompt.ends_with(" "));
|
||||
assert!(prompt.ends_with(' '));
|
||||
|
||||
// Prompt should contain the goose face
|
||||
assert!(prompt.contains("( O)>"));
|
||||
// Prompt should contain the goose emoji
|
||||
assert!(prompt.contains("🪿"));
|
||||
|
||||
// On Windows, prompt should be plain text without ANSI codes
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
assert_eq!(prompt, "( O)> ");
|
||||
// Ensure no ANSI escape sequences
|
||||
assert!(!prompt.contains("\x1b["));
|
||||
}
|
||||
|
||||
// On non-Windows, prompt behavior depends on terminal capabilities
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
// In CI environments, console crate may strip ANSI codes
|
||||
let is_ci = std::env::var("CI").is_ok();
|
||||
|
||||
if is_ci {
|
||||
// In CI, just verify basic structure - console crate handles ANSI detection
|
||||
assert!(prompt.len() >= "( O)> ".len());
|
||||
} else {
|
||||
// In interactive terminals, expect styling to be applied
|
||||
// Note: This may still vary based on terminal capabilities
|
||||
assert!(prompt.len() >= "( O)> ".len());
|
||||
|
||||
// If ANSI codes are present, they should be valid
|
||||
if prompt.contains("\x1b[") {
|
||||
assert!(prompt.contains("36") || prompt.contains("1"));
|
||||
}
|
||||
}
|
||||
assert_eq!(prompt, "🪿 ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ pub struct CliSession {
|
|||
completion_cache: Arc<std::sync::RwLock<CompletionCache>>,
|
||||
debug: bool,
|
||||
run_mode: RunMode,
|
||||
scheduled_job_id: Option<String>, // ID of the scheduled job that triggered this session
|
||||
scheduled_job_id: Option<String>,
|
||||
max_turns: Option<u32>,
|
||||
edit_mode: Option<EditMode>,
|
||||
retry_config: Option<RetryConfig>,
|
||||
|
|
@ -479,7 +479,6 @@ impl CliSession {
|
|||
loop {
|
||||
self.display_context_usage().await?;
|
||||
|
||||
// Convert conversation messages to strings for editor mode
|
||||
let conversation_strings: Vec<String> = self
|
||||
.messages
|
||||
.iter()
|
||||
|
|
@ -502,8 +501,9 @@ impl CliSession {
|
|||
}
|
||||
|
||||
println!(
|
||||
"Closing session. Session ID: {}",
|
||||
console::style(&self.session_id).cyan()
|
||||
"\n {} {}",
|
||||
console::style("●").red(),
|
||||
console::style(format!("session closed · {}", &self.session_id)).dim()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -636,10 +636,7 @@ impl CliSession {
|
|||
|
||||
let elapsed = start_time.elapsed();
|
||||
let elapsed_str = format_elapsed_time(elapsed);
|
||||
println!(
|
||||
"\n{}",
|
||||
console::style(format!("⏱️ Elapsed time: {}", elapsed_str)).dim()
|
||||
);
|
||||
println!("{}", console::style(format!(" ⏱ {}", elapsed_str)).dim());
|
||||
}
|
||||
RunMode::Plan => {
|
||||
let mut plan_messages = self.messages.clone();
|
||||
|
|
@ -1152,20 +1149,10 @@ impl CliSession {
|
|||
.collect()
|
||||
});
|
||||
|
||||
let interrupt_prompt = "Yes — what would you like me to do?";
|
||||
|
||||
if !tool_requests.is_empty() {
|
||||
// Interrupted during a tool request
|
||||
// Create tool responses for all interrupted tool requests
|
||||
// TODO(Douwe): if we need this, it should happen in agent reply
|
||||
let mut response_message = Message::user();
|
||||
let last_tool_name = tool_requests
|
||||
.last()
|
||||
.and_then(|(_, tool_call)| {
|
||||
tool_call
|
||||
.as_ref()
|
||||
.ok()
|
||||
.map(|tool| tool.name.to_string().clone())
|
||||
})
|
||||
.unwrap_or_else(|| "tool".to_string());
|
||||
|
||||
let notification = if interrupt {
|
||||
"Interrupted by the user to make a correction".to_string()
|
||||
|
|
@ -1183,36 +1170,29 @@ impl CliSession {
|
|||
));
|
||||
}
|
||||
self.push_message(response_message);
|
||||
let prompt = format!(
|
||||
"The existing call to {} was interrupted. How would you like to proceed?",
|
||||
last_tool_name
|
||||
self.push_message(Message::assistant().with_text(interrupt_prompt));
|
||||
output::render_message(
|
||||
&Message::assistant().with_text(interrupt_prompt),
|
||||
self.debug,
|
||||
);
|
||||
self.push_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() {
|
||||
if last_msg.role == rmcp::model::Role::User {
|
||||
match last_msg.content.first() {
|
||||
Some(MessageContent::ToolResponse(_)) => {
|
||||
// Interruption occurred after a tool had completed but not assistant reply
|
||||
let prompt = "The tool calling loop was interrupted. How would you like to proceed?";
|
||||
self.push_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),
|
||||
self.debug,
|
||||
);
|
||||
}
|
||||
None => panic!("No content in last message"),
|
||||
} else if let Some(last_msg) = self.messages.last() {
|
||||
if last_msg.role == rmcp::model::Role::User {
|
||||
match last_msg.content.first() {
|
||||
Some(MessageContent::ToolResponse(_)) => {
|
||||
self.push_message(Message::assistant().with_text(interrupt_prompt));
|
||||
output::render_message(
|
||||
&Message::assistant().with_text(interrupt_prompt),
|
||||
self.debug,
|
||||
);
|
||||
}
|
||||
Some(_) => {
|
||||
self.messages.pop();
|
||||
let assistant_msg = Message::assistant().with_text(interrupt_prompt);
|
||||
self.push_message(assistant_msg.clone());
|
||||
output::render_message(&assistant_msg, self.debug);
|
||||
}
|
||||
None => {
|
||||
// Empty message content — nothing to do, just continue gracefully
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1271,11 +1251,10 @@ impl CliSession {
|
|||
return;
|
||||
}
|
||||
|
||||
// Print session restored message
|
||||
println!(
|
||||
"\n{} {} messages loaded into context.",
|
||||
console::style("Session restored:").green().bold(),
|
||||
console::style(self.messages.len()).green()
|
||||
"\n {} {}",
|
||||
console::style("↻").cyan(),
|
||||
console::style(format!("{} messages restored", self.messages.len())).dim()
|
||||
);
|
||||
|
||||
// Render each message
|
||||
|
|
@ -1283,11 +1262,7 @@ impl CliSession {
|
|||
output::render_message(message, self.debug);
|
||||
}
|
||||
|
||||
// Add a visual separator after restored messages
|
||||
println!(
|
||||
"\n{}\n",
|
||||
console::style("──────── New Messages ────────").dim()
|
||||
);
|
||||
println!();
|
||||
}
|
||||
|
||||
pub async fn get_session(&self) -> Result<goose::session::Session> {
|
||||
|
|
|
|||
|
|
@ -120,16 +120,18 @@ pub struct ThinkingIndicator {
|
|||
impl ThinkingIndicator {
|
||||
pub fn show(&mut self) {
|
||||
let spinner = cliclack::spinner();
|
||||
let hint = style("(Ctrl+C to interrupt)").dim();
|
||||
if Config::global()
|
||||
.get_param("RANDOM_THINKING_MESSAGES")
|
||||
.unwrap_or(true)
|
||||
{
|
||||
spinner.start(format!(
|
||||
"{}...",
|
||||
super::thinking::get_random_thinking_message()
|
||||
"{}... {}",
|
||||
super::thinking::get_random_thinking_message(),
|
||||
hint,
|
||||
));
|
||||
} else {
|
||||
spinner.start("Thinking...");
|
||||
spinner.start(format!("Thinking... {}", hint));
|
||||
}
|
||||
self.spinner = Some(spinner);
|
||||
}
|
||||
|
|
@ -467,17 +469,15 @@ pub fn render_builtin_error(names: &str, error: &str) {
|
|||
fn render_text_editor_request(call: &CallToolRequestParams, debug: bool) {
|
||||
print_tool_header(call);
|
||||
|
||||
// Print path first with special formatting
|
||||
if let Some(args) = &call.arguments {
|
||||
if let Some(Value::String(path)) = args.get("path") {
|
||||
println!(
|
||||
"{}: {}",
|
||||
" {} {}",
|
||||
style("path").dim(),
|
||||
style(shorten_path(path, debug)).green()
|
||||
style(shorten_path(path, debug)).dim()
|
||||
);
|
||||
}
|
||||
|
||||
// Print other arguments normally, excluding path
|
||||
if let Some(args) = &call.arguments {
|
||||
let mut other_args = serde_json::Map::new();
|
||||
for (k, v) in args {
|
||||
|
|
@ -486,7 +486,7 @@ fn render_text_editor_request(call: &CallToolRequestParams, debug: bool) {
|
|||
}
|
||||
}
|
||||
if !other_args.is_empty() {
|
||||
print_params(&Some(other_args), 0, debug);
|
||||
print_params(&Some(other_args), 1, debug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -495,7 +495,7 @@ fn render_text_editor_request(call: &CallToolRequestParams, debug: bool) {
|
|||
|
||||
fn render_shell_request(call: &CallToolRequestParams, debug: bool) {
|
||||
print_tool_header(call);
|
||||
print_params(&call.arguments, 0, debug);
|
||||
print_params(&call.arguments, 1, debug);
|
||||
println!();
|
||||
}
|
||||
|
||||
|
|
@ -515,10 +515,11 @@ fn render_execute_code_request(call: &CallToolRequestParams, debug: bool) {
|
|||
let plural = if count == 1 { "" } else { "s" };
|
||||
println!();
|
||||
println!(
|
||||
"─── {} tool call{} | {} ──────────────────────────",
|
||||
style(count).cyan(),
|
||||
" {} {} {} tool call{}",
|
||||
style("▸").dim(),
|
||||
style("execute").dim(),
|
||||
style(count).dim(),
|
||||
plural,
|
||||
style("execute").magenta().dim()
|
||||
);
|
||||
|
||||
for (i, node) in tool_graph.iter().filter_map(Value::as_object).enumerate() {
|
||||
|
|
@ -544,10 +545,10 @@ fn render_execute_code_request(call: &CallToolRequestParams, debug: bool) {
|
|||
format!(" (uses {})", deps.join(", "))
|
||||
};
|
||||
println!(
|
||||
" {}. {}: {}{}",
|
||||
" {}. {} {}{}",
|
||||
style(i + 1).dim(),
|
||||
style(tool).cyan(),
|
||||
style(desc).green(),
|
||||
style(tool).dim(),
|
||||
style(desc).dim(),
|
||||
style(deps_str).dim()
|
||||
);
|
||||
}
|
||||
|
|
@ -570,7 +571,7 @@ fn render_delegate_request(call: &CallToolRequestParams, debug: bool) {
|
|||
|
||||
if let Some(args) = &call.arguments {
|
||||
if let Some(Value::String(source)) = args.get("source") {
|
||||
println!("{}: {}", style("source").dim(), style(source).cyan());
|
||||
println!(" {} {}", style("source").dim(), style(source).dim());
|
||||
}
|
||||
|
||||
if let Some(Value::String(instructions)) = args.get("instructions") {
|
||||
|
|
@ -580,15 +581,15 @@ fn render_delegate_request(call: &CallToolRequestParams, debug: bool) {
|
|||
instructions.clone()
|
||||
};
|
||||
println!(
|
||||
"{}: {}",
|
||||
" {} {}",
|
||||
style("instructions").dim(),
|
||||
style(display).green()
|
||||
style(display).dim()
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(Value::Object(params)) = args.get("parameters") {
|
||||
println!("{}:", style("parameters").dim());
|
||||
print_params(&Some(params.clone()), 1, debug);
|
||||
println!(" {}:", style("parameters").dim());
|
||||
print_params(&Some(params.clone()), 2, debug);
|
||||
}
|
||||
|
||||
let skip_keys = ["source", "instructions", "parameters"];
|
||||
|
|
@ -599,7 +600,7 @@ fn render_delegate_request(call: &CallToolRequestParams, debug: bool) {
|
|||
}
|
||||
}
|
||||
if !other_args.is_empty() {
|
||||
print_params(&Some(other_args), 0, debug);
|
||||
print_params(&Some(other_args), 1, debug);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -611,7 +612,7 @@ fn render_todo_request(call: &CallToolRequestParams, _debug: bool) {
|
|||
|
||||
if let Some(args) = &call.arguments {
|
||||
if let Some(Value::String(content)) = args.get("content") {
|
||||
println!("{}: {}", style("content").dim(), style(content).green());
|
||||
println!(" {} {}", style("content").dim(), style(content).dim());
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
|
@ -619,7 +620,7 @@ fn render_todo_request(call: &CallToolRequestParams, _debug: bool) {
|
|||
|
||||
fn render_default_request(call: &CallToolRequestParams, debug: bool) {
|
||||
print_tool_header(call);
|
||||
print_params(&call.arguments, 0, debug);
|
||||
print_params(&call.arguments, 1, debug);
|
||||
println!();
|
||||
}
|
||||
|
||||
|
|
@ -660,14 +661,13 @@ pub fn render_subagent_tool_call(
|
|||
}
|
||||
}
|
||||
let tool_header = format!(
|
||||
"─── {} ──────────────────────────",
|
||||
style(format_subagent_tool_call_message(subagent_id, tool_name))
|
||||
.magenta()
|
||||
.dim()
|
||||
" {} {}",
|
||||
style("▸").dim(),
|
||||
style(format_subagent_tool_call_message(subagent_id, tool_name)).dim(),
|
||||
);
|
||||
println!();
|
||||
println!("{}", tool_header);
|
||||
print_params(&arguments.cloned(), 0, debug);
|
||||
print_params(&arguments.cloned(), 1, debug);
|
||||
println!();
|
||||
}
|
||||
|
||||
|
|
@ -677,11 +677,12 @@ fn render_subagent_tool_graph(subagent_id: &str, tool_graph: &[Value]) {
|
|||
let plural = if count == 1 { "" } else { "s" };
|
||||
println!();
|
||||
println!(
|
||||
"─── {} {} tool call{} | {} ──────────────────────────",
|
||||
style(format!("[subagent:{}]", short_id)).cyan(),
|
||||
style(count).cyan(),
|
||||
" {} {} {} {} tool call{}",
|
||||
style("▸").dim(),
|
||||
style(format!("[subagent:{}]", short_id)).dim(),
|
||||
style("execute_code").dim(),
|
||||
style(count).dim(),
|
||||
plural,
|
||||
style("execute_code").magenta().dim()
|
||||
);
|
||||
|
||||
for (i, node) in tool_graph.iter().filter_map(Value::as_object).enumerate() {
|
||||
|
|
@ -707,10 +708,10 @@ fn render_subagent_tool_graph(subagent_id: &str, tool_graph: &[Value]) {
|
|||
format!(" (uses {})", deps.join(", "))
|
||||
};
|
||||
println!(
|
||||
" {}. {}: {}{}",
|
||||
" {}. {} {}{}",
|
||||
style(i + 1).dim(),
|
||||
style(tool).cyan(),
|
||||
style(desc).green(),
|
||||
style(tool).dim(),
|
||||
style(desc).dim(),
|
||||
style(deps_str).dim()
|
||||
);
|
||||
}
|
||||
|
|
@ -721,11 +722,16 @@ fn render_subagent_tool_graph(subagent_id: &str, tool_graph: &[Value]) {
|
|||
|
||||
fn print_tool_header(call: &CallToolRequestParams) {
|
||||
let (tool, extension) = split_tool_name(&call.name);
|
||||
let tool_header = format!(
|
||||
"─── {} | {} ──────────────────────────",
|
||||
style(tool),
|
||||
style(extension).magenta().dim(),
|
||||
);
|
||||
let tool_header = if extension.is_empty() {
|
||||
format!(" {} {}", style("▸").dim(), style(&tool).dim())
|
||||
} else {
|
||||
format!(
|
||||
" {} {} {}",
|
||||
style("▸").dim(),
|
||||
style(&tool).dim(),
|
||||
style(extension).magenta().dim(),
|
||||
)
|
||||
};
|
||||
println!();
|
||||
println!("{}", tool_header);
|
||||
}
|
||||
|
|
@ -889,7 +895,6 @@ fn shorten_path(path: &str, debug: bool) -> String {
|
|||
shortened.join("/")
|
||||
}
|
||||
|
||||
// Session display functions
|
||||
pub fn display_session_info(
|
||||
resume: bool,
|
||||
provider: &str,
|
||||
|
|
@ -897,107 +902,126 @@ pub fn display_session_info(
|
|||
session_id: &Option<String>,
|
||||
provider_instance: Option<&Arc<dyn goose::providers::base::Provider>>,
|
||||
) {
|
||||
let start_session_msg = if resume {
|
||||
"resuming session |"
|
||||
let status = if resume {
|
||||
"resuming"
|
||||
} else if session_id.is_none() {
|
||||
"running without session |"
|
||||
"ephemeral"
|
||||
} else {
|
||||
"starting session |"
|
||||
"new session"
|
||||
};
|
||||
|
||||
// Check if we have lead/worker mode
|
||||
if let Some(provider_inst) = provider_instance {
|
||||
let model_display = if let Some(provider_inst) = provider_instance {
|
||||
if let Some(lead_worker) = provider_inst.as_lead_worker() {
|
||||
let (lead_model, worker_model) = lead_worker.get_model_info();
|
||||
println!(
|
||||
"{} {} {} {} {} {} {}",
|
||||
style(start_session_msg).dim(),
|
||||
style("provider:").dim(),
|
||||
style(provider).cyan().dim(),
|
||||
style("lead model:").dim(),
|
||||
style(&lead_model).cyan().dim(),
|
||||
style("worker model:").dim(),
|
||||
style(&worker_model).cyan().dim(),
|
||||
);
|
||||
format!("{} → {}", lead_model, worker_model)
|
||||
} else {
|
||||
println!(
|
||||
"{} {} {} {} {}",
|
||||
style(start_session_msg).dim(),
|
||||
style("provider:").dim(),
|
||||
style(provider).cyan().dim(),
|
||||
style("model:").dim(),
|
||||
style(model).cyan().dim(),
|
||||
);
|
||||
model.to_string()
|
||||
}
|
||||
} else {
|
||||
// Fallback to original behavior if no provider instance
|
||||
println!(
|
||||
"{} {} {} {} {}",
|
||||
style(start_session_msg).dim(),
|
||||
style("provider:").dim(),
|
||||
style(provider).cyan().dim(),
|
||||
style("model:").dim(),
|
||||
style(model).cyan().dim(),
|
||||
);
|
||||
}
|
||||
model.to_string()
|
||||
};
|
||||
|
||||
println!(
|
||||
"\n {} {} {} {} {}",
|
||||
style("●").green(),
|
||||
style(status).dim(),
|
||||
style("·").dim(),
|
||||
style(provider).dim(),
|
||||
style(&model_display).cyan(),
|
||||
);
|
||||
|
||||
let cwd_display = std::env::current_dir()
|
||||
.ok()
|
||||
.map(|p| p.display().to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
|
||||
if let Some(id) = session_id {
|
||||
println!(
|
||||
" {} {}",
|
||||
style("session id:").dim(),
|
||||
style(id).cyan().dim()
|
||||
" {} {} {}",
|
||||
style(" ").dim(),
|
||||
style(id).dim(),
|
||||
style(format!("· {}", cwd_display)).dim(),
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
" {} {}",
|
||||
style(" ").dim(),
|
||||
style(format!(" {}", cwd_display)).dim(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!(
|
||||
" {} {}",
|
||||
style("working directory:").dim(),
|
||||
style(std::env::current_dir().unwrap().display())
|
||||
.cyan()
|
||||
.dim()
|
||||
);
|
||||
pub fn set_terminal_title() {
|
||||
if !std::io::stdout().is_terminal() {
|
||||
return;
|
||||
}
|
||||
let dir_name = std::env::current_dir()
|
||||
.ok()
|
||||
.and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
|
||||
.unwrap_or_default();
|
||||
// Sanitize: strip control characters (ESC, BEL, etc.) to prevent terminal escape injection
|
||||
let sanitized: String = dir_name.chars().filter(|c| !c.is_control()).collect();
|
||||
// OSC 0 sets the terminal window/tab title
|
||||
print!("\x1b]0;🪿 {}\x07", sanitized);
|
||||
let _ = std::io::stdout().flush();
|
||||
}
|
||||
|
||||
pub fn display_greeting() {
|
||||
println!("\ngoose is running! Enter your instructions, or try asking what goose can do.\n");
|
||||
set_terminal_title();
|
||||
println!(
|
||||
"\n{} {}\n",
|
||||
style("🪿 goose").bold(),
|
||||
style("ready — type a message to get started").dim()
|
||||
);
|
||||
}
|
||||
|
||||
/// Display context window usage with both current and session totals
|
||||
pub fn display_context_usage(total_tokens: usize, context_limit: usize) {
|
||||
use console::style;
|
||||
|
||||
if context_limit == 0 {
|
||||
println!("Context: Error - context limit is zero");
|
||||
println!(
|
||||
" {}",
|
||||
style("context usage unavailable (context limit is 0)").dim()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate percentage used with bounds checking
|
||||
let percentage =
|
||||
(((total_tokens as f64 / context_limit as f64) * 100.0).round() as usize).min(100);
|
||||
|
||||
// Create dot visualization with safety bounds
|
||||
let dot_count = 10;
|
||||
let filled_dots =
|
||||
(((percentage as f64 / 100.0) * dot_count as f64).round() as usize).min(dot_count);
|
||||
let empty_dots = dot_count - filled_dots;
|
||||
let bar_width = 20;
|
||||
let filled = ((percentage as f64 / 100.0) * bar_width as f64).round() as usize;
|
||||
let empty = bar_width - filled.min(bar_width);
|
||||
|
||||
let filled = "●".repeat(filled_dots);
|
||||
let empty = "○".repeat(empty_dots);
|
||||
|
||||
// Combine dots and apply color
|
||||
let dots = format!("{}{}", filled, empty);
|
||||
let colored_dots = if percentage < 50 {
|
||||
style(dots).green()
|
||||
let bar = format!("{}{}", "━".repeat(filled), "╌".repeat(empty));
|
||||
let colored_bar = if percentage < 50 {
|
||||
style(bar).green().dim()
|
||||
} else if percentage < 85 {
|
||||
style(dots).yellow()
|
||||
style(bar).yellow()
|
||||
} else {
|
||||
style(dots).red()
|
||||
style(bar).red()
|
||||
};
|
||||
|
||||
// Print the status line
|
||||
fn format_tokens(n: usize) -> String {
|
||||
if n >= 1_000_000 {
|
||||
format!("{:.1}M", n as f64 / 1_000_000.0)
|
||||
} else if n >= 1_000 {
|
||||
format!("{:.0}k", n as f64 / 1_000.0)
|
||||
} else {
|
||||
n.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
println!(
|
||||
"Context: {} {}% ({}/{} tokens)",
|
||||
colored_dots, percentage, total_tokens, context_limit
|
||||
" {} {} {}",
|
||||
colored_bar,
|
||||
style(format!("{}%", percentage)).dim(),
|
||||
style(format!(
|
||||
"{}/{}",
|
||||
format_tokens(total_tokens),
|
||||
format_tokens(context_limit)
|
||||
))
|
||||
.dim(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ TMPFILE=$(mktemp)
|
|||
(cd "$TESTDIR" && GOOSE_PROVIDER="$TEST_PROVIDER" GOOSE_MODEL="$TEST_MODEL" \
|
||||
"$GOOSE_BIN" run --recipe recipe.yaml 2>&1) | tee "$TMPFILE"
|
||||
|
||||
if grep -q "add | test_mcp" "$TMPFILE" && grep -q "100" "$TMPFILE"; then
|
||||
if grep -qE "(add \| test_mcp)|(▸.*add.*test_mcp)" "$TMPFILE" && grep -q "100" "$TMPFILE"; then
|
||||
echo "✓ FastMCP stderr test passed"
|
||||
RESULTS+=("✓ FastMCP stderr")
|
||||
else
|
||||
|
|
@ -73,20 +73,20 @@ TESTDIR=$(mktemp -d)
|
|||
TMPFILE=$(mktemp)
|
||||
|
||||
(cd "$TESTDIR" && GOOSE_PROVIDER="$TEST_PROVIDER" GOOSE_MODEL="$TEST_MODEL" \
|
||||
"$GOOSE_BIN" run --text "Use the sampleLLM tool to ask for a quote from The Great Gatsby" \
|
||||
"$GOOSE_BIN" run --text "Use the sampleLLM tool to ask for an original short poem about the ocean" \
|
||||
--with-extension "npx -y @modelcontextprotocol/server-everything@2026.1.14" 2>&1) | tee "$TMPFILE"
|
||||
|
||||
if grep -q "$MCP_SAMPLING_TOOL | " "$TMPFILE"; then
|
||||
if grep -qE "($MCP_SAMPLING_TOOL \| )|(▸.*$MCP_SAMPLING_TOOL)" "$TMPFILE"; then
|
||||
JUDGE_PROMPT=$(cat <<EOF
|
||||
You are a validator. You will be given a transcript of a CLI run that used an MCP tool to initiate MCP sampling.
|
||||
The MCP server requests a quote from The Great Gatsby from the model via sampling.
|
||||
The MCP server requests an original short poem about the ocean from the model via sampling.
|
||||
|
||||
Task: Determine whether the transcript shows that the sampling request reached the model and that the output included either:
|
||||
• A recognizable quote, paraphrase, or reference from The Great Gatsby, or
|
||||
• A clear attempt or explanation from the model about why the quote could not be returned.
|
||||
• A poem, verse, or creative text about the ocean or sea, or
|
||||
• A clear attempt or explanation from the model about the poem request.
|
||||
|
||||
If either of these conditions is true, respond PASS.
|
||||
If there is no evidence that the model attempted or returned a Gatsby-related response, respond FAIL.
|
||||
If there is no evidence that the model attempted or returned a poem-related response, respond FAIL.
|
||||
If uncertain, lean toward PASS.
|
||||
|
||||
Output format: Respond with exactly one word on a single line:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ run_test() {
|
|||
echo "failure|test content not found by model" > "$result_file"
|
||||
fi
|
||||
else
|
||||
if ! grep -q "text_editor | developer" "$output_file"; then
|
||||
if ! grep -qE "(text_editor \| developer)|(▸.*text_editor.*developer)" "$output_file"; then
|
||||
echo "failure|model did not use text_editor tool" > "$result_file"
|
||||
elif ! grep -q "TEST-CONTENT-ABC123" "$output_file"; then
|
||||
echo "failure|model did not return uppercased file content" > "$result_file"
|
||||
|
|
|
|||
|
|
@ -30,8 +30,9 @@ run_test() {
|
|||
|
||||
# Verify: code_execution tool must be called
|
||||
# Matches: "execute | code_execution", "get_function_details | code_execution",
|
||||
# "tool call | execute", "tool calls | execute"
|
||||
if grep -qE "(execute \| code_execution)|(get_function_details \| code_execution)|(tool calls? \| execute)" "$output_file"; then
|
||||
# "tool call | execute", "tool calls | execute" (old format)
|
||||
# "▸ execute N tool call" (new format with tool_graph)
|
||||
if grep -qE "(execute \| code_execution)|(get_function_details \| code_execution)|(tool calls? \| execute)|(▸.*execute.*tool call)" "$output_file"; then
|
||||
echo "success|code_execution tool called" > "$result_file"
|
||||
else
|
||||
echo "failure|no code_execution tool calls found" > "$result_file"
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ check_recipe_output() {
|
|||
local tmpfile=$1
|
||||
local mode=$2
|
||||
|
||||
# Check for delegate tool invocation (new format: "─── delegate |")
|
||||
if grep -q "─── delegate" "$tmpfile"; then
|
||||
# Check for delegate tool invocation (old: "─── delegate |", new: "▸ delegate")
|
||||
if grep -qE "(─── delegate)|(▸.*delegate)" "$tmpfile"; then
|
||||
echo "✓ SUCCESS: Delegate tool invoked"
|
||||
RESULTS+=("✓ Delegate tool invocation ($mode)")
|
||||
else
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue