From 22f30adee80f1ff9221a04fe8b1ad28b945ce499 Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Tue, 22 Jul 2025 13:33:35 +1000 Subject: [PATCH 01/10] added retryConfig toSchema --- crates/goose/src/agents/types.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/goose/src/agents/types.rs b/crates/goose/src/agents/types.rs index ccfb5debf926..a3b83d3ccbaa 100644 --- a/crates/goose/src/agents/types.rs +++ b/crates/goose/src/agents/types.rs @@ -2,6 +2,7 @@ use crate::session; use mcp_core::{Tool, ToolResult}; use rmcp::model::Content; use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; @@ -16,7 +17,7 @@ pub const DEFAULT_RETRY_TIMEOUT_SECONDS: u64 = 300; pub const DEFAULT_ON_FAILURE_TIMEOUT_SECONDS: u64 = 600; /// Configuration for retry logic in recipe execution -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct RetryConfig { /// Maximum number of retry attempts before giving up pub max_retries: u32, From af5e81511c49791b28efaf63c1dde2885170c15d Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Tue, 22 Jul 2025 18:04:15 +1000 Subject: [PATCH 02/10] enabled sub recipe in desktop --- crates/goose-server/src/routes/agent.rs | 28 ++++++++++++++++++- .../subagent_execution_tool/executor/mod.rs | 7 ++++- ui/desktop/src/components/RecipesView.tsx | 1 + ui/desktop/src/main.ts | 2 +- ui/desktop/src/utils/providerUtils.ts | 17 ++++++++++- 5 files changed, 51 insertions(+), 4 deletions(-) diff --git a/crates/goose-server/src/routes/agent.rs b/crates/goose-server/src/routes/agent.rs index 7167dcae5737..00feacc7f642 100644 --- a/crates/goose-server/src/routes/agent.rs +++ b/crates/goose-server/src/routes/agent.rs @@ -6,7 +6,7 @@ use axum::{ routing::{get, post}, Json, Router, }; -use goose::config::Config; +use goose::{config::Config, recipe::SubRecipe}; use goose::config::PermissionManager; use goose::model::ModelConfig; use goose::providers::create; @@ -35,6 +35,16 @@ struct ExtendPromptResponse { success: bool, } +#[derive(Deserialize)] +struct AddSubRecipesRequest { + sub_recipes: Vec, +} + +#[derive(Serialize)] +struct AddSubRecipesResponse { + success: bool, +} + #[derive(Deserialize)] struct ProviderFile { name: String, @@ -88,6 +98,21 @@ async fn get_versions() -> Json { }) } +async fn add_sub_recipes( + State(state): State>, + headers: HeaderMap, + Json(payload): Json, +) -> Result, StatusCode> { + verify_secret_key(&headers, &state)?; + + let agent = state + .get_agent() + .await + .map_err(|_| StatusCode::PRECONDITION_FAILED)?; + agent.add_sub_recipes(payload.sub_recipes.clone()).await; + Ok(Json(AddSubRecipesResponse { success: true })) +} + async fn extend_prompt( State(state): State>, headers: HeaderMap, @@ -318,5 +343,6 @@ pub fn routes(state: Arc) -> Router { post(update_router_tool_selector), ) .route("/agent/session_config", post(update_session_config)) + .route("/agent/add_sub_recipes", post(add_sub_recipes)) .with_state(state) } diff --git a/crates/goose/src/agents/subagent_execution_tool/executor/mod.rs b/crates/goose/src/agents/subagent_execution_tool/executor/mod.rs index 9a71ad4ad7f9..414300150980 100644 --- a/crates/goose/src/agents/subagent_execution_tool/executor/mod.rs +++ b/crates/goose/src/agents/subagent_execution_tool/executor/mod.rs @@ -179,7 +179,12 @@ async fn collect_results( if let Some(data) = result.data.as_mut() { if let Some(data_str) = data.as_str() { if data_str.len() > 650 { - *data = serde_json::Value::String(format!("{}...", &data_str[..650])); + let mut end = 650; + while end > 0 && !data_str.is_char_boundary(end) { + end -= 1; + } + + *data = serde_json::Value::String(format!("{}...", &data_str[..end])); } } } diff --git a/ui/desktop/src/components/RecipesView.tsx b/ui/desktop/src/components/RecipesView.tsx index 452dd0a292fa..f7d927289a03 100644 --- a/ui/desktop/src/components/RecipesView.tsx +++ b/ui/desktop/src/components/RecipesView.tsx @@ -161,6 +161,7 @@ export default function RecipesView({ _onLoadRecipe }: RecipesViewProps = {}) { }; const handlePreviewRecipe = (savedRecipe: SavedRecipe) => { + console.log('======handlePreviewRecipe', savedRecipe); setSelectedRecipe(savedRecipe); setShowPreview(true); }; diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 1934ac4eded7..59b2ca32b61f 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -196,7 +196,6 @@ if (process.platform === 'win32') { const protocolUrl = commandLine.find((arg) => arg.startsWith('goose://')); if (protocolUrl) { const parsedUrl = new URL(protocolUrl); - // If it's a bot/recipe URL, handle it directly by creating a new window if (parsedUrl.hostname === 'bot' || parsedUrl.hostname === 'recipe') { app.whenReady().then(async () => { @@ -259,6 +258,7 @@ async function handleProtocolUrl(url: string) { const openDir = recentDirs.length > 0 ? recentDirs[0] : null; if (parsedUrl.hostname === 'bot' || parsedUrl.hostname === 'recipe') { + console.log('======parsedUrl', parsedUrl); // For bot/recipe URLs, get existing window or create new one const existingWindows = BrowserWindow.getAllWindows(); const targetWindow = diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index eeb29e7c2c65..ae25ccb89f32 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -10,7 +10,7 @@ import { extractExtensionConfig } from '../components/settings/extensions/utils' import type { ExtensionConfig, FixedExtensionEntry } from '../components/ConfigContext'; // TODO: remove when removing migration logic import { toastService } from '../toasts'; -import { ExtensionQuery, addExtension as apiAddExtension } from '../api'; +import { ExtensionQuery, SubRecipe, addExtension as apiAddExtension } from '../api'; export interface Provider { id: string; // Lowercase key (e.g., "openai") @@ -192,6 +192,7 @@ export const initializeSystem = async ( const recipeConfig = window.appConfig?.get?.('recipe'); const botPrompt = (recipeConfig as { instructions?: string })?.instructions; const responseConfig = (recipeConfig as { response?: { json_schema?: unknown } })?.response; + const subRecipes = (recipeConfig as { sub_recipes?: SubRecipe[] })?.sub_recipes; // Extend the system prompt with desktop-specific information const response = await fetch(getApiUrl('/agent/prompt'), { @@ -206,6 +207,20 @@ export const initializeSystem = async ( : desktopPrompt, }), }); + console.log('======subRecipes', subRecipes); + if (subRecipes && subRecipes.length > 0) { + const add_sub_recipe_response = await fetch(getApiUrl('/agent/add_sub_recipes'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Secret-Key': getSecretKey(), + }, + body: JSON.stringify({ + sub_recipes: subRecipes, + }), + }); + console.log('======add_sub_recipe_response', add_sub_recipe_response); + } if (!response.ok) { console.warn(`Failed to extend system prompt: ${response.statusText}`); From 9e749b754eb474439521a07312a9df71a808146c Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 24 Jul 2025 10:30:43 +1000 Subject: [PATCH 03/10] added set subrecipes --- ui/desktop/src/recipe/update_agent.ts | 20 ++++++++++ ui/desktop/src/utils/providerUtils.ts | 55 +++++++++++++-------------- 2 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 ui/desktop/src/recipe/update_agent.ts diff --git a/ui/desktop/src/recipe/update_agent.ts b/ui/desktop/src/recipe/update_agent.ts new file mode 100644 index 000000000000..257084d049ec --- /dev/null +++ b/ui/desktop/src/recipe/update_agent.ts @@ -0,0 +1,20 @@ +import { SubRecipe } from "../api"; +import { getApiUrl, getSecretKey } from "../config"; + +export async function addSubRecipes(subRecipes: SubRecipe[]) { + const add_sub_recipe_response = await fetch(getApiUrl('/agent/add_sub_recipes'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Secret-Key': getSecretKey(), + }, + body: JSON.stringify({ + sub_recipes: subRecipes, + }), + }); + if (!add_sub_recipe_response.ok) { + console.warn(`Failed to add sub recipes: ${add_sub_recipe_response.statusText}`); + } else { + console.log('Added sub recipes'); + } +} \ No newline at end of file diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index ae25ccb89f32..e207660fd72a 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -10,7 +10,8 @@ import { extractExtensionConfig } from '../components/settings/extensions/utils' import type { ExtensionConfig, FixedExtensionEntry } from '../components/ConfigContext'; // TODO: remove when removing migration logic import { toastService } from '../toasts'; -import { ExtensionQuery, SubRecipe, addExtension as apiAddExtension } from '../api'; +import { ExtensionQuery, RecipeParameter, SubRecipe, addExtension as apiAddExtension } from '../api'; +import { addSubRecipes } from '../recipe/update_agent'; export interface Provider { id: string; // Lowercase key (e.g., "openai") @@ -72,14 +73,13 @@ const substituteParameters = (text: string, params: Record): str export const updateSystemPromptWithParameters = async ( recipeParameters: Record ): Promise => { - try { - const recipeConfig = window.appConfig?.get?.('recipe'); + const recipeConfig = window.appConfig?.get?.('recipe'); + const subRecipes = (recipeConfig as { sub_recipes?: SubRecipe[] })?.sub_recipes; const originalInstructions = (recipeConfig as { instructions?: string })?.instructions; - + try { if (!originalInstructions) { return; } - // Substitute parameters in the instructions const substitutedInstructions = substituteParameters(originalInstructions, recipeParameters); @@ -101,6 +101,17 @@ export const updateSystemPromptWithParameters = async ( } catch (error) { console.error('Error updating system prompt with parameters:', error); } + if (subRecipes && subRecipes?.length > 0) { + for (const subRecipe of subRecipes) { + if (subRecipe.values) { + // Iterate over each key in subRecipe.values and substitute parameters + for (const key in subRecipe.values) { + subRecipe.values[key] = substituteParameters(subRecipe.values[key], recipeParameters); + } + } + } + await addSubRecipes(subRecipes); + } }; /** @@ -190,9 +201,15 @@ export const initializeSystem = async ( // Get recipeConfig directly here const recipeConfig = window.appConfig?.get?.('recipe'); - const botPrompt = (recipeConfig as { instructions?: string })?.instructions; + const recipe_instructions = (recipeConfig as { instructions?: string })?.instructions; const responseConfig = (recipeConfig as { response?: { json_schema?: unknown } })?.response; const subRecipes = (recipeConfig as { sub_recipes?: SubRecipe[] })?.sub_recipes; + const parameters = (recipeConfig as { parameters?: RecipeParameter[] })?.parameters; + const hasParameters = parameters && parameters?.length > 0; + const hasSubRecipes = subRecipes && subRecipes?.length > 0; + let prompt = desktopPrompt; + if (!hasParameters && recipe_instructions) { + prompt = `${desktopPromptBot}\nIMPORTANT instructions for you to operate as agent:\n${recipe_instructions}`; // Extend the system prompt with desktop-specific information const response = await fetch(getApiUrl('/agent/prompt'), { @@ -202,35 +219,17 @@ export const initializeSystem = async ( 'X-Secret-Key': getSecretKey(), }, body: JSON.stringify({ - extension: botPrompt - ? `${desktopPromptBot}\nIMPORTANT instructions for you to operate as agent:\n${botPrompt}` - : desktopPrompt, + extension: prompt, }), }); - console.log('======subRecipes', subRecipes); - if (subRecipes && subRecipes.length > 0) { - const add_sub_recipe_response = await fetch(getApiUrl('/agent/add_sub_recipes'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': getSecretKey(), - }, - body: JSON.stringify({ - sub_recipes: subRecipes, - }), - }); - console.log('======add_sub_recipe_response', add_sub_recipe_response); - } - if (!response.ok) { console.warn(`Failed to extend system prompt: ${response.statusText}`); } else { console.log('Extended system prompt with desktop-specific information'); - if (botPrompt) { - console.log('Added custom bot prompt to system prompt'); - } } - + if (!hasParameters && hasSubRecipes) { + addSubRecipes(subRecipes); + } // Configure session with response config if present if (responseConfig?.json_schema) { const sessionConfigResponse = await fetch(getApiUrl('/agent/session_config'), { From 0732d11004a9bba50b652076448897e2f062cca3 Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 24 Jul 2025 10:45:39 +1000 Subject: [PATCH 04/10] fixed the js --- crates/goose/src/agents/types.rs | 1 - ui/desktop/src/utils/providerUtils.ts | 21 +++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/crates/goose/src/agents/types.rs b/crates/goose/src/agents/types.rs index 6bbcb869fe9f..96c01336f488 100644 --- a/crates/goose/src/agents/types.rs +++ b/crates/goose/src/agents/types.rs @@ -6,7 +6,6 @@ use utoipa::ToSchema; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; -use utoipa::ToSchema; /// Type alias for the tool result channel receiver pub type ToolResultReceiver = Arc>)>>>; diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index 81c891591dde..8c6dffc49bbd 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -10,7 +10,12 @@ import { extractExtensionConfig } from '../components/settings/extensions/utils' import type { ExtensionConfig, FixedExtensionEntry } from '../components/ConfigContext'; // TODO: remove when removing migration logic import { toastService } from '../toasts'; -import { ExtensionQuery, RecipeParameter, SubRecipe, addExtension as apiAddExtension } from '../api'; +import { + ExtensionQuery, + RecipeParameter, + SubRecipe, + addExtension as apiAddExtension, +} from '../api'; import { addSubRecipes } from '../recipe/update_agent'; export interface Provider { @@ -72,9 +77,13 @@ const substituteParameters = (text: string, params: Record): str */ export const updateSystemPromptWithParameters = async ( recipeParameters: Record, - recipeConfig?: { instructions?: string | null } + recipeConfig?: { + instructions?: string | null; + sub_recipes?: SubRecipe[] | null; + parameters?: RecipeParameter[] | null; + } ): Promise => { - const subRecipes = (recipeConfig as { sub_recipes?: SubRecipe[] })?.sub_recipes; + const subRecipes = recipeConfig?.sub_recipes; try { const originalInstructions = recipeConfig?.instructions; @@ -107,8 +116,8 @@ export const updateSystemPromptWithParameters = async ( if (subRecipes && subRecipes?.length > 0) { for (const subRecipe of subRecipes) { if (subRecipe.values) { - // Iterate over each key in subRecipe.values and substitute parameters - for (const key in subRecipe.values) { + // Iterate over each key in subRecipe.values and substitute parameters + for (const key in subRecipe.values) { subRecipe.values[key] = substituteParameters(subRecipe.values[key], recipeParameters); } } @@ -213,7 +222,7 @@ export const initializeSystem = async ( let prompt = desktopPrompt; if (!hasParameters && recipe_instructions) { prompt = `${desktopPromptBot}\nIMPORTANT instructions for you to operate as agent:\n${recipe_instructions}`; - + } // Extend the system prompt with desktop-specific information const response = await fetch(getApiUrl('/agent/prompt'), { method: 'POST', From 1ac6c8f50f6013c5cd89b9ac0ade524b6a1a6233 Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 24 Jul 2025 11:03:33 +1000 Subject: [PATCH 05/10] moved strip ansi code to be called in task result. this is only for running subrecipe cli --- .../src/session/task_execution_display/mod.rs | 38 +------------ .../session/task_execution_display/tests.rs | 17 ------ crates/goose-server/src/routes/agent.rs | 2 +- .../subagent_execution_tool/executor/mod.rs | 15 +---- .../agents/subagent_execution_tool/tasks.rs | 57 ++++++++++++++++++- crates/goose/src/agents/types.rs | 2 +- 6 files changed, 61 insertions(+), 70 deletions(-) diff --git a/crates/goose-cli/src/session/task_execution_display/mod.rs b/crates/goose-cli/src/session/task_execution_display/mod.rs index b0b208ed546e..f10465c230a1 100644 --- a/crates/goose-cli/src/session/task_execution_display/mod.rs +++ b/crates/goose-cli/src/session/task_execution_display/mod.rs @@ -18,7 +18,7 @@ static INITIAL_SHOWN: AtomicBool = AtomicBool::new(false); fn format_result_data_for_display(result_data: &Value) -> String { match result_data { - Value::String(s) => strip_ansi_codes(s), + Value::String(s) => s.to_string(), Value::Object(obj) => { if let Some(partial_output) = obj.get("partial_output").and_then(|v| v.as_str()) { format!("Partial output: {}", partial_output) @@ -45,8 +45,7 @@ fn process_output_for_display(output: &str) -> String { }; let clean_output = recent_lines.join(" ... "); - let stripped = strip_ansi_codes(&clean_output); - truncate_with_ellipsis(&stripped, OUTPUT_PREVIEW_LENGTH) + truncate_with_ellipsis(&clean_output, OUTPUT_PREVIEW_LENGTH) } fn truncate_with_ellipsis(text: &str, max_len: usize) -> String { @@ -61,39 +60,6 @@ fn truncate_with_ellipsis(text: &str, max_len: usize) -> String { } } -fn strip_ansi_codes(text: &str) -> String { - let mut result = String::new(); - let mut chars = text.chars(); - - while let Some(ch) = chars.next() { - if ch == '\x1b' { - if let Some(next_ch) = chars.next() { - if next_ch == '[' { - // This is an ANSI escape sequence, consume until alphabetic character - loop { - match chars.next() { - Some(c) if c.is_ascii_alphabetic() => break, - Some(_) => continue, - None => break, - } - } - } else { - // Not an ANSI sequence, keep both characters - result.push(ch); - result.push(next_ch); - } - } else { - // End of string after \x1b - result.push(ch); - } - } else { - result.push(ch); - } - } - - result -} - pub fn format_task_execution_notification( data: &Value, ) -> Option<(String, Option, Option)> { diff --git a/crates/goose-cli/src/session/task_execution_display/tests.rs b/crates/goose-cli/src/session/task_execution_display/tests.rs index 725d161dff5b..8dc04d4391e0 100644 --- a/crates/goose-cli/src/session/task_execution_display/tests.rs +++ b/crates/goose-cli/src/session/task_execution_display/tests.rs @@ -4,23 +4,6 @@ use goose::agents::subagent_execution_tool::notification_events::{ }; use serde_json::json; -#[test] -fn test_strip_ansi_codes() { - assert_eq!(strip_ansi_codes("hello world"), "hello world"); - assert_eq!(strip_ansi_codes("\x1b[31mred text\x1b[0m"), "red text"); - assert_eq!( - strip_ansi_codes("\x1b[1;32mbold green\x1b[0m"), - "bold green" - ); - assert_eq!( - strip_ansi_codes("normal\x1b[33myellow\x1b[0mnormal"), - "normalyellownormal" - ); - assert_eq!(strip_ansi_codes("\x1bhello"), "\x1bhello"); - assert_eq!(strip_ansi_codes("hello\x1b"), "hello\x1b"); - assert_eq!(strip_ansi_codes(""), ""); -} - #[test] fn test_truncate_with_ellipsis() { assert_eq!(truncate_with_ellipsis("hello", 10), "hello"); diff --git a/crates/goose-server/src/routes/agent.rs b/crates/goose-server/src/routes/agent.rs index 00feacc7f642..990c754724ca 100644 --- a/crates/goose-server/src/routes/agent.rs +++ b/crates/goose-server/src/routes/agent.rs @@ -6,7 +6,6 @@ use axum::{ routing::{get, post}, Json, Router, }; -use goose::{config::Config, recipe::SubRecipe}; use goose::config::PermissionManager; use goose::model::ModelConfig; use goose::providers::create; @@ -15,6 +14,7 @@ use goose::{ agents::{extension::ToolInfo, extension_manager::get_parameter_names}, config::permission::PermissionLevel, }; +use goose::{config::Config, recipe::SubRecipe}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; diff --git a/crates/goose/src/agents/subagent_execution_tool/executor/mod.rs b/crates/goose/src/agents/subagent_execution_tool/executor/mod.rs index bebf965a5ea2..7aaa14c79f64 100644 --- a/crates/goose/src/agents/subagent_execution_tool/executor/mod.rs +++ b/crates/goose/src/agents/subagent_execution_tool/executor/mod.rs @@ -193,20 +193,7 @@ async fn collect_results( expected_count: usize, ) -> Vec { let mut results = Vec::new(); - while let Some(mut result) = result_rx.recv().await { - // Truncate data to 650 chars if needed - if let Some(data) = result.data.as_mut() { - if let Some(data_str) = data.as_str() { - if data_str.len() > 650 { - let mut end = 650; - while end > 0 && !data_str.is_char_boundary(end) { - end -= 1; - } - - *data = serde_json::Value::String(format!("{}...", &data_str[..end])); - } - } - } + while let Some(result) = result_rx.recv().await { task_execution_tracker .complete_task(&result.task_id, result.clone()) .await; diff --git a/crates/goose/src/agents/subagent_execution_tool/tasks.rs b/crates/goose/src/agents/subagent_execution_tool/tasks.rs index 4ecd5b628ffa..3cacbfc20961 100644 --- a/crates/goose/src/agents/subagent_execution_tool/tasks.rs +++ b/crates/goose/src/agents/subagent_execution_tool/tasks.rs @@ -70,7 +70,7 @@ async fn get_task_result( if success { process_output(stdout_output) } else { - Err(format!("Command failed:\n{}", stderr_output)) + Err(format!("Command failed:\n{}", &stderr_output)) } } } @@ -224,6 +224,7 @@ fn spawn_output_reader( let mut buffer = String::new(); let mut lines = BufReader::new(reader).lines(); while let Ok(Some(line)) = lines.next_line().await { + let line = strip_ansi_codes(&line); buffer.push_str(&line); buffer.push('\n'); @@ -268,3 +269,57 @@ fn process_output(stdout_output: String) -> Result { Ok(Value::String(stdout_output)) } } + +fn strip_ansi_codes(text: &str) -> String { + let mut result = String::new(); + let mut chars = text.chars(); + + while let Some(ch) = chars.next() { + if ch == '\x1b' { + if let Some(next_ch) = chars.next() { + if next_ch == '[' { + // This is an ANSI escape sequence, consume until alphabetic character + loop { + match chars.next() { + Some(c) if c.is_ascii_alphabetic() => break, + Some(_) => continue, + None => break, + } + } + } else { + // Not an ANSI sequence, keep both characters + result.push(ch); + result.push(next_ch); + } + } else { + // End of string after \x1b + result.push(ch); + } + } else { + result.push(ch); + } + } + + result +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_strip_ansi_codes() { + assert_eq!(strip_ansi_codes("hello world"), "hello world"); + assert_eq!(strip_ansi_codes("\x1b[31mred text\x1b[0m"), "red text"); + assert_eq!( + strip_ansi_codes("\x1b[1;32mbold green\x1b[0m"), + "bold green" + ); + assert_eq!( + strip_ansi_codes("normal\x1b[33myellow\x1b[0mnormal"), + "normalyellownormal" + ); + assert_eq!(strip_ansi_codes("\x1bhello"), "\x1bhello"); + assert_eq!(strip_ansi_codes("hello\x1b"), "hello\x1b"); + assert_eq!(strip_ansi_codes(""), ""); + } +} diff --git a/crates/goose/src/agents/types.rs b/crates/goose/src/agents/types.rs index 96c01336f488..10054d167233 100644 --- a/crates/goose/src/agents/types.rs +++ b/crates/goose/src/agents/types.rs @@ -2,10 +2,10 @@ use crate::session; use mcp_core::{Tool, ToolResult}; use rmcp::model::Content; use serde::{Deserialize, Serialize}; -use utoipa::ToSchema; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; +use utoipa::ToSchema; /// Type alias for the tool result channel receiver pub type ToolResultReceiver = Arc>)>>>; From 35dca2182d2953139e21f5f4a97e20116f4d193a Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 24 Jul 2025 14:49:34 +1000 Subject: [PATCH 06/10] fixed tests --- .../src/session/task_execution_display/tests.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/goose-cli/src/session/task_execution_display/tests.rs b/crates/goose-cli/src/session/task_execution_display/tests.rs index 8dc04d4391e0..a2c730fd262a 100644 --- a/crates/goose-cli/src/session/task_execution_display/tests.rs +++ b/crates/goose-cli/src/session/task_execution_display/tests.rs @@ -32,20 +32,15 @@ fn test_process_output_for_display() { assert!(result.len() <= 100); assert!(result.ends_with("...")); - let ansi_output = "\x1b[31mred line 1\x1b[0m\n\x1b[32mgreen line 2\x1b[0m"; - let result = process_output_for_display(ansi_output); - assert_eq!(result, "red line 1 ... green line 2"); - assert_eq!(process_output_for_display(""), ""); } #[test] fn test_format_result_data_for_display() { - let string_val = json!("hello world"); - assert_eq!(format_result_data_for_display(&string_val), "hello world"); - - let ansi_string = json!("\x1b[31mred text\x1b[0m"); - assert_eq!(format_result_data_for_display(&ansi_string), "red text"); + assert_eq!( + format_result_data_for_display(&json!("red text")), + "red text" + ); assert_eq!(format_result_data_for_display(&json!(true)), "true"); assert_eq!(format_result_data_for_display(&json!(false)), "false"); From f3647a8b9a9c12144efa30faa27a76bb800e393c Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 24 Jul 2025 15:42:34 +1000 Subject: [PATCH 07/10] added missing await --- ui/desktop/src/main.ts | 1 - ui/desktop/src/utils/providerUtils.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index cbc92bde7429..e1f9a7cba28a 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -259,7 +259,6 @@ async function handleProtocolUrl(url: string) { const openDir = recentDirs.length > 0 ? recentDirs[0] : null; if (parsedUrl.hostname === 'bot' || parsedUrl.hostname === 'recipe') { - console.log('======parsedUrl', parsedUrl); // For bot/recipe URLs, get existing window or create new one const existingWindows = BrowserWindow.getAllWindows(); const targetWindow = diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index 8c6dffc49bbd..dcb25e646747 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -240,7 +240,7 @@ export const initializeSystem = async ( console.log('Extended system prompt with desktop-specific information'); } if (!hasParameters && hasSubRecipes) { - addSubRecipes(subRecipes); + await addSubRecipes(subRecipes); } // Configure session with response config if present if (responseConfig?.json_schema) { From 1a5f0c52d94e655c5738b935deda26fa60e41cf6 Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 24 Jul 2025 16:08:50 +1000 Subject: [PATCH 08/10] renamed the file --- .../src/recipe/add_sub_recipe_on_agent.ts | 20 +++++++++++++++++++ ui/desktop/src/recipe/update_agent.ts | 20 ------------------- ui/desktop/src/utils/providerUtils.ts | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 ui/desktop/src/recipe/add_sub_recipe_on_agent.ts delete mode 100644 ui/desktop/src/recipe/update_agent.ts diff --git a/ui/desktop/src/recipe/add_sub_recipe_on_agent.ts b/ui/desktop/src/recipe/add_sub_recipe_on_agent.ts new file mode 100644 index 000000000000..4408511fa11f --- /dev/null +++ b/ui/desktop/src/recipe/add_sub_recipe_on_agent.ts @@ -0,0 +1,20 @@ +import { SubRecipe } from '../api'; +import { getApiUrl, getSecretKey } from '../config'; + +export async function addSubRecipes(subRecipes: SubRecipe[]) { + const add_sub_recipe_response = await fetch(getApiUrl('/agent/add_sub_recipes'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Secret-Key': getSecretKey(), + }, + body: JSON.stringify({ + sub_recipes: subRecipes, + }), + }); + if (!add_sub_recipe_response.ok) { + console.warn(`Failed to add sub recipes: ${add_sub_recipe_response.statusText}`); + } else { + console.log('Added sub recipes'); + } +} diff --git a/ui/desktop/src/recipe/update_agent.ts b/ui/desktop/src/recipe/update_agent.ts deleted file mode 100644 index 257084d049ec..000000000000 --- a/ui/desktop/src/recipe/update_agent.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { SubRecipe } from "../api"; -import { getApiUrl, getSecretKey } from "../config"; - -export async function addSubRecipes(subRecipes: SubRecipe[]) { - const add_sub_recipe_response = await fetch(getApiUrl('/agent/add_sub_recipes'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': getSecretKey(), - }, - body: JSON.stringify({ - sub_recipes: subRecipes, - }), - }); - if (!add_sub_recipe_response.ok) { - console.warn(`Failed to add sub recipes: ${add_sub_recipe_response.statusText}`); - } else { - console.log('Added sub recipes'); - } -} \ No newline at end of file diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index dcb25e646747..7a328d289193 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -16,7 +16,7 @@ import { SubRecipe, addExtension as apiAddExtension, } from '../api'; -import { addSubRecipes } from '../recipe/update_agent'; +import { addSubRecipes } from '../recipe/add_sub_recipe_on_agent'; export interface Provider { id: string; // Lowercase key (e.g., "openai") From 671914086cdc9e7020fa1c3499da1304ba993f04 Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 24 Jul 2025 22:41:48 +1000 Subject: [PATCH 09/10] addresses the pr comments --- .../src/session/task_execution_display/mod.rs | 7 +-- crates/goose-server/src/openapi.rs | 3 + crates/goose-server/src/routes/agent.rs | 17 ++++-- .../agents/subagent_execution_tool/tasks.rs | 55 +----------------- .../subagent_execution_tool/utils/mod.rs | 33 +++++++++++ .../subagent_execution_tool/utils/tests.rs | 25 +++++++- ui/desktop/openapi.json | 58 +++++++++++++++++++ ui/desktop/src/api/sdk.gen.ts | 13 ++++- ui/desktop/src/api/types.gen.ts | 31 ++++++++++ .../src/recipe/add_sub_recipe_on_agent.ts | 20 ++----- ui/desktop/src/utils/providerUtils.ts | 7 +-- 11 files changed, 185 insertions(+), 84 deletions(-) diff --git a/crates/goose-cli/src/session/task_execution_display/mod.rs b/crates/goose-cli/src/session/task_execution_display/mod.rs index f10465c230a1..e03b22f8a00a 100644 --- a/crates/goose-cli/src/session/task_execution_display/mod.rs +++ b/crates/goose-cli/src/session/task_execution_display/mod.rs @@ -2,6 +2,7 @@ use goose::agents::subagent_execution_tool::lib::TaskStatus; use goose::agents::subagent_execution_tool::notification_events::{ TaskExecutionNotificationEvent, TaskInfo, }; +use goose::utils::safe_truncate; use serde_json::Value; use std::sync::atomic::{AtomicBool, Ordering}; @@ -50,11 +51,7 @@ fn process_output_for_display(output: &str) -> String { fn truncate_with_ellipsis(text: &str, max_len: usize) -> String { if text.len() > max_len { - let mut end = max_len.saturating_sub(3); - while end > 0 && !text.is_char_boundary(end) { - end -= 1; - } - format!("{}...", &text[..end]) + format!("{}...", safe_truncate(text, max_len)) } else { text.to_string() } diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index befc27f0d920..7d6819f8dfda 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -305,6 +305,7 @@ derive_utoipa!(ResourceContents as ResourceContentsSchema); super::routes::config_management::providers, super::routes::config_management::upsert_permissions, super::routes::agent::get_tools, + super::routes::agent::add_sub_recipes, super::routes::reply::confirm_permission, super::routes::context::manage_context, super::routes::session::list_sessions, @@ -394,6 +395,8 @@ derive_utoipa!(ResourceContents as ResourceContentsSchema); goose::recipe::SubRecipe, goose::agents::types::RetryConfig, goose::agents::types::SuccessCheck, + super::routes::agent::AddSubRecipesRequest, + super::routes::agent::AddSubRecipesResponse, )) )] pub struct ApiDoc; diff --git a/crates/goose-server/src/routes/agent.rs b/crates/goose-server/src/routes/agent.rs index 990c754724ca..a896cdb2e6d7 100644 --- a/crates/goose-server/src/routes/agent.rs +++ b/crates/goose-server/src/routes/agent.rs @@ -35,13 +35,13 @@ struct ExtendPromptResponse { success: bool, } -#[derive(Deserialize)] -struct AddSubRecipesRequest { +#[derive(Deserialize, utoipa::ToSchema)] +pub struct AddSubRecipesRequest { sub_recipes: Vec, } -#[derive(Serialize)] -struct AddSubRecipesResponse { +#[derive(Serialize, utoipa::ToSchema)] +pub struct AddSubRecipesResponse { success: bool, } @@ -98,6 +98,15 @@ async fn get_versions() -> Json { }) } +#[utoipa::path( + post, + path = "/agent/add_sub_recipes", + request_body = AddSubRecipesRequest, + responses( + (status = 200, description = "added sub recipes to agent successfully", body = AddSubRecipesResponse), + (status = 401, description = "Unauthorized - invalid secret key"), + ), +)] async fn add_sub_recipes( State(state): State>, headers: HeaderMap, diff --git a/crates/goose/src/agents/subagent_execution_tool/tasks.rs b/crates/goose/src/agents/subagent_execution_tool/tasks.rs index 3cacbfc20961..64ee4ff2461d 100644 --- a/crates/goose/src/agents/subagent_execution_tool/tasks.rs +++ b/crates/goose/src/agents/subagent_execution_tool/tasks.rs @@ -8,6 +8,7 @@ use tokio_util::sync::CancellationToken; use crate::agents::subagent_execution_tool::task_execution_tracker::TaskExecutionTracker; use crate::agents::subagent_execution_tool::task_types::{Task, TaskResult, TaskStatus}; +use crate::agents::subagent_execution_tool::utils::strip_ansi_codes; use crate::agents::subagent_handler::run_complete_subagent_task; use crate::agents::subagent_task_config::TaskConfig; @@ -269,57 +270,3 @@ fn process_output(stdout_output: String) -> Result { Ok(Value::String(stdout_output)) } } - -fn strip_ansi_codes(text: &str) -> String { - let mut result = String::new(); - let mut chars = text.chars(); - - while let Some(ch) = chars.next() { - if ch == '\x1b' { - if let Some(next_ch) = chars.next() { - if next_ch == '[' { - // This is an ANSI escape sequence, consume until alphabetic character - loop { - match chars.next() { - Some(c) if c.is_ascii_alphabetic() => break, - Some(_) => continue, - None => break, - } - } - } else { - // Not an ANSI sequence, keep both characters - result.push(ch); - result.push(next_ch); - } - } else { - // End of string after \x1b - result.push(ch); - } - } else { - result.push(ch); - } - } - - result -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_strip_ansi_codes() { - assert_eq!(strip_ansi_codes("hello world"), "hello world"); - assert_eq!(strip_ansi_codes("\x1b[31mred text\x1b[0m"), "red text"); - assert_eq!( - strip_ansi_codes("\x1b[1;32mbold green\x1b[0m"), - "bold green" - ); - assert_eq!( - strip_ansi_codes("normal\x1b[33myellow\x1b[0mnormal"), - "normalyellownormal" - ); - assert_eq!(strip_ansi_codes("\x1bhello"), "\x1bhello"); - assert_eq!(strip_ansi_codes("hello\x1b"), "hello\x1b"); - assert_eq!(strip_ansi_codes(""), ""); - } -} diff --git a/crates/goose/src/agents/subagent_execution_tool/utils/mod.rs b/crates/goose/src/agents/subagent_execution_tool/utils/mod.rs index 5d75675283d3..e1e48835c7c3 100644 --- a/crates/goose/src/agents/subagent_execution_tool/utils/mod.rs +++ b/crates/goose/src/agents/subagent_execution_tool/utils/mod.rs @@ -23,5 +23,38 @@ pub fn count_by_status(tasks: &HashMap) -> (usize, usize, usiz (total, pending, running, completed, failed) } +pub fn strip_ansi_codes(text: &str) -> String { + let mut result = String::new(); + let mut chars = text.chars(); + + while let Some(ch) = chars.next() { + if ch == '\x1b' { + if let Some(next_ch) = chars.next() { + if next_ch == '[' { + // This is an ANSI escape sequence, consume until alphabetic character + loop { + match chars.next() { + Some(c) if c.is_ascii_alphabetic() => break, + Some(_) => continue, + None => break, + } + } + } else { + // Not an ANSI sequence, keep both characters + result.push(ch); + result.push(next_ch); + } + } else { + // End of string after \x1b + result.push(ch); + } + } else { + result.push(ch); + } + } + + result +} + #[cfg(test)] mod tests; diff --git a/crates/goose/src/agents/subagent_execution_tool/utils/tests.rs b/crates/goose/src/agents/subagent_execution_tool/utils/tests.rs index b4e7f757b420..253d28ebff00 100644 --- a/crates/goose/src/agents/subagent_execution_tool/utils/tests.rs +++ b/crates/goose/src/agents/subagent_execution_tool/utils/tests.rs @@ -1,5 +1,7 @@ use crate::agents::subagent_execution_tool::task_types::{Task, TaskInfo, TaskStatus}; -use crate::agents::subagent_execution_tool::utils::{count_by_status, get_task_name}; +use crate::agents::subagent_execution_tool::utils::{ + count_by_status, get_task_name, strip_ansi_codes, +}; use serde_json::json; use std::collections::HashMap; @@ -152,3 +154,24 @@ mod count_by_status { ); } } + +mod strip_ansi_codes { + use super::*; + + #[test] + fn test_strip_ansi_codes() { + assert_eq!(strip_ansi_codes("hello world"), "hello world"); + assert_eq!(strip_ansi_codes("\x1b[31mred text\x1b[0m"), "red text"); + assert_eq!( + strip_ansi_codes("\x1b[1;32mbold green\x1b[0m"), + "bold green" + ); + assert_eq!( + strip_ansi_codes("normal\x1b[33myellow\x1b[0mnormal"), + "normalyellownormal" + ); + assert_eq!(strip_ansi_codes("\x1bhello"), "\x1bhello"); + assert_eq!(strip_ansi_codes("hello\x1b"), "hello\x1b"); + assert_eq!(strip_ansi_codes(""), ""); + } +} diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 3fa2f9e0b322..a567e05ea012 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -13,6 +13,39 @@ "version": "1.1.0" }, "paths": { + "/agent/add_sub_recipes": { + "post": { + "tags": [ + "super::routes::agent" + ], + "operationId": "add_sub_recipes", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSubRecipesRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "added sub recipes to agent successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddSubRecipesResponse" + } + } + } + }, + "401": { + "description": "Unauthorized - invalid secret key" + } + } + } + }, "/agent/tools": { "get": { "tags": [ @@ -1033,6 +1066,31 @@ }, "components": { "schemas": { + "AddSubRecipesRequest": { + "type": "object", + "required": [ + "sub_recipes" + ], + "properties": { + "sub_recipes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SubRecipe" + } + } + } + }, + "AddSubRecipesResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean" + } + } + }, "Annotations": { "type": "object", "properties": { diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts index c772a1b0048b..d3b39f08533a 100644 --- a/ui/desktop/src/api/sdk.gen.ts +++ b/ui/desktop/src/api/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { GetToolsData, GetToolsResponse, ReadAllConfigData, ReadAllConfigResponse, BackupConfigData, BackupConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, InitConfigData, InitConfigResponse, UpsertPermissionsData, UpsertPermissionsResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RecoverConfigData, RecoverConfigResponse, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse, ValidateConfigData, ValidateConfigResponse, ConfirmPermissionData, ManageContextData, ManageContextResponse, CreateRecipeData, CreateRecipeResponse2, DecodeRecipeData, DecodeRecipeResponse2, EncodeRecipeData, EncodeRecipeResponse2, CreateScheduleData, CreateScheduleResponse, DeleteScheduleData, DeleteScheduleResponse, ListSchedulesData, ListSchedulesResponse2, UpdateScheduleData, UpdateScheduleResponse, InspectRunningJobData, InspectRunningJobResponse, KillRunningJobData, PauseScheduleData, PauseScheduleResponse, RunNowHandlerData, RunNowHandlerResponse, SessionsHandlerData, SessionsHandlerResponse, UnpauseScheduleData, UnpauseScheduleResponse, ListSessionsData, ListSessionsResponse, GetSessionHistoryData, GetSessionHistoryResponse } from './types.gen'; +import type { AddSubRecipesData, AddSubRecipesResponse2, GetToolsData, GetToolsResponse, ReadAllConfigData, ReadAllConfigResponse, BackupConfigData, BackupConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, InitConfigData, InitConfigResponse, UpsertPermissionsData, UpsertPermissionsResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RecoverConfigData, RecoverConfigResponse, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse, ValidateConfigData, ValidateConfigResponse, ConfirmPermissionData, ManageContextData, ManageContextResponse, CreateRecipeData, CreateRecipeResponse2, DecodeRecipeData, DecodeRecipeResponse2, EncodeRecipeData, EncodeRecipeResponse2, CreateScheduleData, CreateScheduleResponse, DeleteScheduleData, DeleteScheduleResponse, ListSchedulesData, ListSchedulesResponse2, UpdateScheduleData, UpdateScheduleResponse, InspectRunningJobData, InspectRunningJobResponse, KillRunningJobData, PauseScheduleData, PauseScheduleResponse, RunNowHandlerData, RunNowHandlerResponse, SessionsHandlerData, SessionsHandlerResponse, UnpauseScheduleData, UnpauseScheduleResponse, ListSessionsData, ListSessionsResponse, GetSessionHistoryData, GetSessionHistoryResponse } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -18,6 +18,17 @@ export type Options; }; +export const addSubRecipes = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/agent/add_sub_recipes', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + export const getTools = (options?: Options) => { return (options?.client ?? _heyApiClient).get({ url: '/agent/tools', diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 655688b7ef71..924b74a5717b 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -1,5 +1,13 @@ // This file is auto-generated by @hey-api/openapi-ts +export type AddSubRecipesRequest = { + sub_recipes: Array; +}; + +export type AddSubRecipesResponse = { + success: boolean; +}; + export type Annotations = { audience?: Array; priority?: number; @@ -777,6 +785,29 @@ export type UpsertPermissionsQuery = { tool_permissions: Array; }; +export type AddSubRecipesData = { + body: AddSubRecipesRequest; + path?: never; + query?: never; + url: '/agent/add_sub_recipes'; +}; + +export type AddSubRecipesErrors = { + /** + * Unauthorized - invalid secret key + */ + 401: unknown; +}; + +export type AddSubRecipesResponses = { + /** + * added sub recipes to agent successfully + */ + 200: AddSubRecipesResponse; +}; + +export type AddSubRecipesResponse2 = AddSubRecipesResponses[keyof AddSubRecipesResponses]; + export type GetToolsData = { body?: never; path?: never; diff --git a/ui/desktop/src/recipe/add_sub_recipe_on_agent.ts b/ui/desktop/src/recipe/add_sub_recipe_on_agent.ts index 4408511fa11f..448e4d5f0068 100644 --- a/ui/desktop/src/recipe/add_sub_recipe_on_agent.ts +++ b/ui/desktop/src/recipe/add_sub_recipe_on_agent.ts @@ -1,19 +1,9 @@ -import { SubRecipe } from '../api'; -import { getApiUrl, getSecretKey } from '../config'; +import { addSubRecipes, SubRecipe } from '../api'; -export async function addSubRecipes(subRecipes: SubRecipe[]) { - const add_sub_recipe_response = await fetch(getApiUrl('/agent/add_sub_recipes'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': getSecretKey(), - }, - body: JSON.stringify({ - sub_recipes: subRecipes, - }), - }); - if (!add_sub_recipe_response.ok) { - console.warn(`Failed to add sub recipes: ${add_sub_recipe_response.statusText}`); +export async function addSubRecipesToAgent(subRecipes: SubRecipe[]) { + const add_sub_recipe_response = await addSubRecipes({ body: { sub_recipes: subRecipes } }); + if (add_sub_recipe_response.error) { + console.warn(`Failed to add sub recipes: ${add_sub_recipe_response.error}`); } else { console.log('Added sub recipes'); } diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index 7a328d289193..a6c089a0118d 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -16,7 +16,7 @@ import { SubRecipe, addExtension as apiAddExtension, } from '../api'; -import { addSubRecipes } from '../recipe/add_sub_recipe_on_agent'; +import { addSubRecipesToAgent } from '../recipe/add_sub_recipe_on_agent'; export interface Provider { id: string; // Lowercase key (e.g., "openai") @@ -116,13 +116,12 @@ export const updateSystemPromptWithParameters = async ( if (subRecipes && subRecipes?.length > 0) { for (const subRecipe of subRecipes) { if (subRecipe.values) { - // Iterate over each key in subRecipe.values and substitute parameters for (const key in subRecipe.values) { subRecipe.values[key] = substituteParameters(subRecipe.values[key], recipeParameters); } } } - await addSubRecipes(subRecipes); + await addSubRecipesToAgent(subRecipes); } }; @@ -240,7 +239,7 @@ export const initializeSystem = async ( console.log('Extended system prompt with desktop-specific information'); } if (!hasParameters && hasSubRecipes) { - await addSubRecipes(subRecipes); + await addSubRecipesToAgent(subRecipes); } // Configure session with response config if present if (responseConfig?.json_schema) { From 8eaf6431849556fd63ba56887d2784d2fd29b04d Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 24 Jul 2025 23:11:04 +1000 Subject: [PATCH 10/10] removed unnecessary function --- .../src/session/task_execution_display/mod.rs | 12 ++---------- .../src/session/task_execution_display/tests.rs | 11 ----------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/crates/goose-cli/src/session/task_execution_display/mod.rs b/crates/goose-cli/src/session/task_execution_display/mod.rs index e03b22f8a00a..434c777271a2 100644 --- a/crates/goose-cli/src/session/task_execution_display/mod.rs +++ b/crates/goose-cli/src/session/task_execution_display/mod.rs @@ -46,15 +46,7 @@ fn process_output_for_display(output: &str) -> String { }; let clean_output = recent_lines.join(" ... "); - truncate_with_ellipsis(&clean_output, OUTPUT_PREVIEW_LENGTH) -} - -fn truncate_with_ellipsis(text: &str, max_len: usize) -> String { - if text.len() > max_len { - format!("{}...", safe_truncate(text, max_len)) - } else { - text.to_string() - } + safe_truncate(&clean_output, OUTPUT_PREVIEW_LENGTH) } pub fn format_task_execution_notification( @@ -196,7 +188,7 @@ fn format_task_display(task: &TaskInfo) -> String { if matches!(task.status, TaskStatus::Failed) { if let Some(error) = &task.error { - let error_preview = truncate_with_ellipsis(error, 80); + let error_preview = safe_truncate(error, 80); task_display.push_str(&format!( " ⚠️ {}{}\n", error_preview.replace('\n', " "), diff --git a/crates/goose-cli/src/session/task_execution_display/tests.rs b/crates/goose-cli/src/session/task_execution_display/tests.rs index a2c730fd262a..7968af7da724 100644 --- a/crates/goose-cli/src/session/task_execution_display/tests.rs +++ b/crates/goose-cli/src/session/task_execution_display/tests.rs @@ -4,17 +4,6 @@ use goose::agents::subagent_execution_tool::notification_events::{ }; use serde_json::json; -#[test] -fn test_truncate_with_ellipsis() { - assert_eq!(truncate_with_ellipsis("hello", 10), "hello"); - assert_eq!(truncate_with_ellipsis("hello", 5), "hello"); - assert_eq!(truncate_with_ellipsis("hello world", 8), "hello..."); - assert_eq!(truncate_with_ellipsis("hello", 3), "..."); - assert_eq!(truncate_with_ellipsis("hello", 2), "..."); - assert_eq!(truncate_with_ellipsis("hello", 1), "..."); - assert_eq!(truncate_with_ellipsis("", 5), ""); -} - #[test] fn test_process_output_for_display() { assert_eq!(process_output_for_display("hello world"), "hello world");