From 6f6e3ffbc969c4e986c6edcb861e35fbf71518c8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 07:54:43 +0000 Subject: [PATCH 1/3] feat: implement idle tool for agent system - Add Rust implementation in idle.rs by copying from done.rs - Add TypeScript implementation in idle-tool.ts by copying from done-tool.ts - Update all agent modes (review, analysis, follow_up_initialization, planning) to include idle tool - Update analyst-agent.ts to include idleTool - Add idle tool to terminating_tools lists - Export idle module and tool from respective index files Addresses BUS-1468: idle tool indicates agent finished current work but available for future tasks Co-Authored-By: Dallin Bentley --- .../libs/agents/src/agents/modes/analysis.rs | 12 +- .../agents/modes/follow_up_initialization.rs | 11 +- .../libs/agents/src/agents/modes/planning.rs | 13 +- .../libs/agents/src/agents/modes/review.rs | 13 +- .../tools/categories/response_tools/idle.rs | 133 ++++++++++++++++++ .../tools/categories/response_tools/mod.rs | 4 +- .../src/agents/analyst-agent/analyst-agent.ts | 2 + .../tools/communication-tools/idle-tool.ts | 99 +++++++++++++ packages/ai/src/tools/index.ts | 1 + 9 files changed, 280 insertions(+), 8 deletions(-) create mode 100644 apps/api/libs/agents/src/tools/categories/response_tools/idle.rs create mode 100644 packages/ai/src/tools/communication-tools/idle-tool.ts diff --git a/apps/api/libs/agents/src/agents/modes/analysis.rs b/apps/api/libs/agents/src/agents/modes/analysis.rs index 25ad2a98a..46d9dee89 100644 --- a/apps/api/libs/agents/src/agents/modes/analysis.rs +++ b/apps/api/libs/agents/src/agents/modes/analysis.rs @@ -18,7 +18,7 @@ use crate::tools::{ CreateDashboardFilesTool, CreateMetricFilesTool, ModifyDashboardFilesTool, ModifyMetricFilesTool, SearchDataCatalogTool, }, - response_tools::Done, + response_tools::{Done, Idle}, }, IntoToolCallExecutor, }; @@ -67,6 +67,7 @@ pub fn get_configuration(agent_data: &ModeAgentData, data_source_syntax: Option< let create_dashboard_files_tool = CreateDashboardFilesTool::new(agent_clone.clone()); let modify_dashboard_files_tool = ModifyDashboardFilesTool::new(agent_clone.clone()); let done_tool = Done::new(agent_clone.clone()); + let idle_tool = Idle::new(agent_clone.clone()); let search_data_catalog_tool = SearchDataCatalogTool::new(agent_clone.clone()); // --- Define Conditions based on Agent State (as per original load_tools) --- @@ -144,6 +145,13 @@ pub fn get_configuration(agent_data: &ModeAgentData, data_source_syntax: Option< .add_tool( done_tool.get_name(), done_tool.into_tool_call_executor(), + done_condition.clone(), + ) + .await; + agent_clone + .add_tool( + idle_tool.get_name(), + idle_tool.into_tool_call_executor(), done_condition, ) .await; @@ -160,7 +168,7 @@ pub fn get_configuration(agent_data: &ModeAgentData, data_source_syntax: Option< }); // 4. Define terminating tools for this mode - let terminating_tools = vec![Done::get_name()]; + let terminating_tools = vec![Done::get_name(), Idle::get_name()]; // 5. Construct and return the ModeConfiguration ModeConfiguration { diff --git a/apps/api/libs/agents/src/agents/modes/follow_up_initialization.rs b/apps/api/libs/agents/src/agents/modes/follow_up_initialization.rs index 73f4fae55..c7d0cdcae 100644 --- a/apps/api/libs/agents/src/agents/modes/follow_up_initialization.rs +++ b/apps/api/libs/agents/src/agents/modes/follow_up_initialization.rs @@ -18,7 +18,7 @@ use crate::tools::{ ModifyMetricFilesTool, SearchDataCatalogTool, }, planning_tools::{CreatePlanInvestigative, CreatePlanStraightforward}, - response_tools::{Done, MessageUserClarifyingQuestion}, + response_tools::{Done, Idle, MessageUserClarifyingQuestion}, utility_tools::no_search_needed::NoSearchNeededTool, }, planning_tools::ReviewPlan, @@ -65,6 +65,7 @@ pub fn get_configuration(agent_data: &ModeAgentData) -> ModeConfiguration { let modify_dashboard_files_tool = ModifyDashboardFilesTool::new(agent_clone.clone()); let message_user_clarifying_question_tool = MessageUserClarifyingQuestion::new(); let done_tool = Done::new(agent_clone.clone()); + let idle_tool = Idle::new(agent_clone.clone()); let review_tool = ReviewPlan::new(agent_clone.clone()); // --- Define Conditions based on Agent State (as per original load_tools) --- @@ -172,6 +173,13 @@ pub fn get_configuration(agent_data: &ModeAgentData) -> ModeConfiguration { .add_tool( done_tool.get_name(), done_tool.into_tool_call_executor(), + always_available.clone(), + ) + .await; + agent_clone + .add_tool( + idle_tool.get_name(), + idle_tool.into_tool_call_executor(), always_available, ) .await; @@ -186,6 +194,7 @@ pub fn get_configuration(agent_data: &ModeAgentData) -> ModeConfiguration { // Use hardcoded names if static access isn't available "message_user_clarifying_question".to_string(), // Assuming this is the name "finish_and_respond".to_string(), // Assuming this is the name for Done tool + Idle::get_name(), // Add idle tool ]; // 5. Construct and return the ModeConfiguration diff --git a/apps/api/libs/agents/src/agents/modes/planning.rs b/apps/api/libs/agents/src/agents/modes/planning.rs index a894accfb..a3216a4ae 100644 --- a/apps/api/libs/agents/src/agents/modes/planning.rs +++ b/apps/api/libs/agents/src/agents/modes/planning.rs @@ -15,7 +15,7 @@ use super::{ModeAgentData, ModeConfiguration}; use crate::tools::{ categories::{ planning_tools::{CreatePlanInvestigative, CreatePlanStraightforward}, - response_tools::{Done, MessageUserClarifyingQuestion}, + response_tools::{Done, Idle, MessageUserClarifyingQuestion}, }, IntoToolCallExecutor, }; @@ -56,6 +56,7 @@ pub fn get_configuration( CreatePlanStraightforward::new(agent_clone.clone()); let create_plan_investigative_tool = CreatePlanInvestigative::new(agent_clone.clone()); let done_tool = Done::new(agent_clone.clone()); + let idle_tool = Idle::new(agent_clone.clone()); let clarify_tool = MessageUserClarifyingQuestion::new(); // Condition (always true for this mode's tools) @@ -86,6 +87,14 @@ pub fn get_configuration( ) .await; + agent_clone + .add_tool( + idle_tool.get_name(), + idle_tool.into_tool_call_executor(), + condition.clone(), + ) + .await; + agent_clone .add_tool( clarify_tool.get_name(), @@ -102,7 +111,7 @@ pub fn get_configuration( prompt, model, tool_loader, - terminating_tools: vec![Done::get_name(), MessageUserClarifyingQuestion::get_name()], + terminating_tools: vec![Done::get_name(), Idle::get_name(), MessageUserClarifyingQuestion::get_name()], } } diff --git a/apps/api/libs/agents/src/agents/modes/review.rs b/apps/api/libs/agents/src/agents/modes/review.rs index e18cce767..926eb6905 100644 --- a/apps/api/libs/agents/src/agents/modes/review.rs +++ b/apps/api/libs/agents/src/agents/modes/review.rs @@ -13,7 +13,7 @@ use super::{ModeAgentData, ModeConfiguration}; // Import necessary tools for this mode use crate::tools::{ - categories::response_tools::{Done, MessageUserClarifyingQuestion}, + categories::response_tools::{Done, Idle, MessageUserClarifyingQuestion}, planning_tools::ReviewPlan, IntoToolCallExecutor, }; @@ -46,6 +46,7 @@ pub fn get_configuration( // Instantiate tools for this mode let review_tool = ReviewPlan::new(agent_clone.clone()); let done_tool = Done::new(agent_clone.clone()); + let idle_tool = Idle::new(agent_clone.clone()); // Condition (always true for this mode's tools) let condition = Some(|_state: &HashMap| -> bool { true }); @@ -67,12 +68,20 @@ pub fn get_configuration( ) .await; + agent_clone + .add_tool( + idle_tool.get_name(), + idle_tool.into_tool_call_executor(), + condition.clone(), + ) + .await; + Ok(()) }) }); // 4. Define terminating tools for this mode (From original load_tools) - let terminating_tools = vec![Done::get_name()]; + let terminating_tools = vec![Done::get_name(), Idle::get_name()]; // 5. Construct and return the ModeConfiguration ModeConfiguration { diff --git a/apps/api/libs/agents/src/tools/categories/response_tools/idle.rs b/apps/api/libs/agents/src/tools/categories/response_tools/idle.rs new file mode 100644 index 000000000..0af19d653 --- /dev/null +++ b/apps/api/libs/agents/src/tools/categories/response_tools/idle.rs @@ -0,0 +1,133 @@ +use anyhow::Result; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::sync::Arc; + +use crate::{agent::Agent, tools::ToolExecutor}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct IdleInput { + final_response: String, +} + +// Define the new standard output struct +#[derive(Debug, Serialize, Deserialize)] +pub struct IdleOutput { + pub success: bool, + pub todos: String, +} + +pub struct Idle { + agent: Arc, +} + +impl Idle { + pub fn new(agent: Arc) -> Self { + Self { agent } + } + + pub fn get_name() -> String { + "idle".to_string() + } +} + +#[async_trait] +impl ToolExecutor for Idle { + type Output = IdleOutput; + type Params = IdleInput; + + fn get_name(&self) -> String { + "idle".to_string() + } + + async fn execute(&self, params: Self::Params, _tool_call_id: String) -> Result { + // Get the current todos from state + let mut todos = match self.agent.get_state_value("todos").await { + Some(Value::Array(arr)) => arr, + _ => { + // If no todos exist, just return success without a list + return Ok(IdleOutput { + success: true, + todos: "No to-do list found.".to_string(), + }); + } + }; + + let mut marked_by_idle = vec![]; // Track items marked by this tool + + // Mark all remaining unfinished todos as complete + for (idx, todo_val) in todos.iter_mut().enumerate() { + if let Value::Object(map) = todo_val { + let is_completed = map + .get("completed") + .and_then(Value::as_bool) + .unwrap_or(false); + if !is_completed { + map.insert("completed".to_string(), Value::Bool(true)); + marked_by_idle.push(idx); // Track 0-based index + } + } else { + // Handle invalid item format if necessary, maybe log a warning? + eprintln!("Warning: Invalid todo item format at index {}", idx); + } + } + + // Save the updated todos back to state + self.agent + .set_state_value("todos".to_string(), Value::Array(todos.clone())) + .await; // Clone needed for iteration below + + // Format the output string, potentially noting items marked by 'idle' + let todos_string = todos + .iter() + .enumerate() + .map(|(idx, todo_val)| { + if let Value::Object(map) = todo_val { + let completed = map + .get("completed") + .and_then(Value::as_bool) + .unwrap_or(false); // Should always be true now + let todo_text = map + .get("todo") + .and_then(Value::as_str) + .unwrap_or("Invalid todo text"); + let annotation = if marked_by_idle.contains(&idx) { + " *Marked complete by calling the idle tool" + } else { + "" + }; + format!("[x] {}{}", todo_text, annotation) + } else { + "Invalid todo item format".to_string() + } + }) + .collect::>() + .join("\n"); + + Ok(IdleOutput { + success: true, + todos: todos_string, + }) // Include todos in output + } + + async fn get_schema(&self) -> Value { + serde_json::json!({ + "name": self.get_name(), + "description": "Marks all remaining unfinished tasks as complete, sends a final response to the user, and enters an idle state. Use this when current work is finished but the agent should remain available for future tasks. This must be in markdown format and not use the '•' bullet character.", + "parameters": { + "type": "object", + "required": [ + "final_response" + ], + "properties": { + "final_response": { + "type": "string", + "description": "The final response message to the user. **MUST** be formatted in Markdown. Use bullet points or other appropriate Markdown formatting. Do not include headers. Do not use the '•' bullet character. Do not include markdown tables." + } + }, + "additionalProperties": false + } + }) + } +} diff --git a/apps/api/libs/agents/src/tools/categories/response_tools/mod.rs b/apps/api/libs/agents/src/tools/categories/response_tools/mod.rs index 36341f5ce..6a14f5079 100644 --- a/apps/api/libs/agents/src/tools/categories/response_tools/mod.rs +++ b/apps/api/libs/agents/src/tools/categories/response_tools/mod.rs @@ -1,5 +1,7 @@ pub mod message_user_clarifying_question; pub mod done; +pub mod idle; pub use message_user_clarifying_question::*; -pub use done::*; \ No newline at end of file +pub use done::*; +pub use idle::*; \ No newline at end of file diff --git a/packages/ai/src/agents/analyst-agent/analyst-agent.ts b/packages/ai/src/agents/analyst-agent/analyst-agent.ts index 80d2f21ae..f1b4bfbc4 100644 --- a/packages/ai/src/agents/analyst-agent/analyst-agent.ts +++ b/packages/ai/src/agents/analyst-agent/analyst-agent.ts @@ -4,6 +4,7 @@ import { createDashboards, createMetrics, doneTool, + idleTool, modifyDashboards, modifyMetrics, } from '../../tools'; @@ -31,6 +32,7 @@ export const analystAgent = new Agent({ createDashboards, modifyDashboards, doneTool, + idleTool, }, defaultGenerateOptions: DEFAULT_OPTIONS, defaultStreamOptions: DEFAULT_OPTIONS, diff --git a/packages/ai/src/tools/communication-tools/idle-tool.ts b/packages/ai/src/tools/communication-tools/idle-tool.ts new file mode 100644 index 000000000..af082ce4c --- /dev/null +++ b/packages/ai/src/tools/communication-tools/idle-tool.ts @@ -0,0 +1,99 @@ +import { createTool } from '@mastra/core/tools'; +import { wrapTraced } from 'braintrust'; +import { z } from 'zod'; + +// Input/Output schemas +const idleInputSchema = z.object({ + final_response: z + .string() + .min(1, 'Final response is required') + .describe( + "The final response message to the user. **MUST** be formatted in Markdown. Use bullet points or other appropriate Markdown formatting. Do not include headers. Do not use the '•' bullet character. Do not include markdown tables." + ), +}); + +export type IdleToolExecuteInput = z.infer; + +/** + * Optimistic parsing function for streaming idle tool arguments + * Extracts the final_response field as it's being built incrementally + */ +export function parseStreamingArgs( + accumulatedText: string +): Partial> | null { + // Validate input type + if (typeof accumulatedText !== 'string') { + throw new Error(`parseStreamingArgs expects string input, got ${typeof accumulatedText}`); + } + + try { + // First try to parse as complete JSON + const parsed = JSON.parse(accumulatedText); + return { + final_response: parsed.final_response || undefined, + }; + } catch (error) { + // Only catch JSON parse errors - let other errors bubble up + if (error instanceof SyntaxError) { + // JSON parsing failed - try regex extraction for partial content + // Handle both complete and incomplete strings, accounting for escaped quotes + const match = accumulatedText.match(/"final_response"\s*:\s*"((?:[^"\\]|\\.)*)"/); + if (match && match[1] !== undefined) { + // Unescape the string + const unescaped = match[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\'); + return { + final_response: unescaped, + }; + } + + // Try to extract partial string that's still being built (incomplete quote) + const partialMatch = accumulatedText.match(/"final_response"\s*:\s*"((?:[^"\\]|\\.*)*)/); + if (partialMatch && partialMatch[1] !== undefined) { + // Unescape the partial string + const unescaped = partialMatch[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\'); + return { + final_response: unescaped, + }; + } + + return null; + } + // Unexpected error - re-throw with context + throw new Error( + `Unexpected error in parseStreamingArgs: ${error instanceof Error ? error.message : 'Unknown error'}` + ); + } +} + +const idleOutputSchema = z.object({ + success: z.boolean().describe('Whether the operation was successful'), +}); + +type IdleOutput = z.infer; + +async function processIdle(_input: IdleToolExecuteInput): Promise { + return { + success: true, + }; +} + +const executeIdle = wrapTraced( + async (input: IdleToolExecuteInput): Promise> => { + return await processIdle(input); + }, + { name: 'idle-tool' } +); + +// Export the tool +export const idleTool = createTool({ + id: 'idle', + description: + "Marks all remaining unfinished tasks as complete, sends a final response to the user, and enters an idle state. Use this when current work is finished but the agent should remain available for future tasks. This must be in markdown format and not use the '•' bullet character.", + inputSchema: idleInputSchema, + outputSchema: idleOutputSchema, + execute: async ({ context }) => { + return await executeIdle(context as IdleToolExecuteInput); + }, +}); + +export default idleTool; diff --git a/packages/ai/src/tools/index.ts b/packages/ai/src/tools/index.ts index 0bd8f0a60..f83892557 100644 --- a/packages/ai/src/tools/index.ts +++ b/packages/ai/src/tools/index.ts @@ -1,4 +1,5 @@ export { doneTool } from './communication-tools/done-tool'; +export { idleTool } from './communication-tools/idle-tool'; export { respondWithoutAnalysis } from './communication-tools/respond-without-analysis'; export { submitThoughts } from './communication-tools/submit-thoughts-tool'; export { messageUserClarifyingQuestion } from './communication-tools/message-user-clarifying-question'; From 5ca93b6de52be8f94c55c4d7546ec1055d091b6d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:09:19 +0000 Subject: [PATCH 2/3] revert: remove Rust implementation, keep TypeScript-only idle tool - Remove idle.rs file completely - Revert all agent mode files to remove Idle tool integration - Revert mod.rs to remove idle module - Keep TypeScript implementation in packages/ai: - idle-tool.ts - index.ts export - analyst-agent.ts integration Addresses user feedback to implement idle tool only in TypeScript packages/ai Co-Authored-By: Dallin Bentley --- .../libs/agents/src/agents/modes/analysis.rs | 12 +- .../agents/modes/follow_up_initialization.rs | 11 +- .../libs/agents/src/agents/modes/planning.rs | 13 +- .../libs/agents/src/agents/modes/review.rs | 13 +- .../tools/categories/response_tools/idle.rs | 133 ------------------ .../tools/categories/response_tools/mod.rs | 4 +- 6 files changed, 8 insertions(+), 178 deletions(-) delete mode 100644 apps/api/libs/agents/src/tools/categories/response_tools/idle.rs diff --git a/apps/api/libs/agents/src/agents/modes/analysis.rs b/apps/api/libs/agents/src/agents/modes/analysis.rs index 46d9dee89..25ad2a98a 100644 --- a/apps/api/libs/agents/src/agents/modes/analysis.rs +++ b/apps/api/libs/agents/src/agents/modes/analysis.rs @@ -18,7 +18,7 @@ use crate::tools::{ CreateDashboardFilesTool, CreateMetricFilesTool, ModifyDashboardFilesTool, ModifyMetricFilesTool, SearchDataCatalogTool, }, - response_tools::{Done, Idle}, + response_tools::Done, }, IntoToolCallExecutor, }; @@ -67,7 +67,6 @@ pub fn get_configuration(agent_data: &ModeAgentData, data_source_syntax: Option< let create_dashboard_files_tool = CreateDashboardFilesTool::new(agent_clone.clone()); let modify_dashboard_files_tool = ModifyDashboardFilesTool::new(agent_clone.clone()); let done_tool = Done::new(agent_clone.clone()); - let idle_tool = Idle::new(agent_clone.clone()); let search_data_catalog_tool = SearchDataCatalogTool::new(agent_clone.clone()); // --- Define Conditions based on Agent State (as per original load_tools) --- @@ -145,13 +144,6 @@ pub fn get_configuration(agent_data: &ModeAgentData, data_source_syntax: Option< .add_tool( done_tool.get_name(), done_tool.into_tool_call_executor(), - done_condition.clone(), - ) - .await; - agent_clone - .add_tool( - idle_tool.get_name(), - idle_tool.into_tool_call_executor(), done_condition, ) .await; @@ -168,7 +160,7 @@ pub fn get_configuration(agent_data: &ModeAgentData, data_source_syntax: Option< }); // 4. Define terminating tools for this mode - let terminating_tools = vec![Done::get_name(), Idle::get_name()]; + let terminating_tools = vec![Done::get_name()]; // 5. Construct and return the ModeConfiguration ModeConfiguration { diff --git a/apps/api/libs/agents/src/agents/modes/follow_up_initialization.rs b/apps/api/libs/agents/src/agents/modes/follow_up_initialization.rs index c7d0cdcae..73f4fae55 100644 --- a/apps/api/libs/agents/src/agents/modes/follow_up_initialization.rs +++ b/apps/api/libs/agents/src/agents/modes/follow_up_initialization.rs @@ -18,7 +18,7 @@ use crate::tools::{ ModifyMetricFilesTool, SearchDataCatalogTool, }, planning_tools::{CreatePlanInvestigative, CreatePlanStraightforward}, - response_tools::{Done, Idle, MessageUserClarifyingQuestion}, + response_tools::{Done, MessageUserClarifyingQuestion}, utility_tools::no_search_needed::NoSearchNeededTool, }, planning_tools::ReviewPlan, @@ -65,7 +65,6 @@ pub fn get_configuration(agent_data: &ModeAgentData) -> ModeConfiguration { let modify_dashboard_files_tool = ModifyDashboardFilesTool::new(agent_clone.clone()); let message_user_clarifying_question_tool = MessageUserClarifyingQuestion::new(); let done_tool = Done::new(agent_clone.clone()); - let idle_tool = Idle::new(agent_clone.clone()); let review_tool = ReviewPlan::new(agent_clone.clone()); // --- Define Conditions based on Agent State (as per original load_tools) --- @@ -173,13 +172,6 @@ pub fn get_configuration(agent_data: &ModeAgentData) -> ModeConfiguration { .add_tool( done_tool.get_name(), done_tool.into_tool_call_executor(), - always_available.clone(), - ) - .await; - agent_clone - .add_tool( - idle_tool.get_name(), - idle_tool.into_tool_call_executor(), always_available, ) .await; @@ -194,7 +186,6 @@ pub fn get_configuration(agent_data: &ModeAgentData) -> ModeConfiguration { // Use hardcoded names if static access isn't available "message_user_clarifying_question".to_string(), // Assuming this is the name "finish_and_respond".to_string(), // Assuming this is the name for Done tool - Idle::get_name(), // Add idle tool ]; // 5. Construct and return the ModeConfiguration diff --git a/apps/api/libs/agents/src/agents/modes/planning.rs b/apps/api/libs/agents/src/agents/modes/planning.rs index a3216a4ae..a894accfb 100644 --- a/apps/api/libs/agents/src/agents/modes/planning.rs +++ b/apps/api/libs/agents/src/agents/modes/planning.rs @@ -15,7 +15,7 @@ use super::{ModeAgentData, ModeConfiguration}; use crate::tools::{ categories::{ planning_tools::{CreatePlanInvestigative, CreatePlanStraightforward}, - response_tools::{Done, Idle, MessageUserClarifyingQuestion}, + response_tools::{Done, MessageUserClarifyingQuestion}, }, IntoToolCallExecutor, }; @@ -56,7 +56,6 @@ pub fn get_configuration( CreatePlanStraightforward::new(agent_clone.clone()); let create_plan_investigative_tool = CreatePlanInvestigative::new(agent_clone.clone()); let done_tool = Done::new(agent_clone.clone()); - let idle_tool = Idle::new(agent_clone.clone()); let clarify_tool = MessageUserClarifyingQuestion::new(); // Condition (always true for this mode's tools) @@ -87,14 +86,6 @@ pub fn get_configuration( ) .await; - agent_clone - .add_tool( - idle_tool.get_name(), - idle_tool.into_tool_call_executor(), - condition.clone(), - ) - .await; - agent_clone .add_tool( clarify_tool.get_name(), @@ -111,7 +102,7 @@ pub fn get_configuration( prompt, model, tool_loader, - terminating_tools: vec![Done::get_name(), Idle::get_name(), MessageUserClarifyingQuestion::get_name()], + terminating_tools: vec![Done::get_name(), MessageUserClarifyingQuestion::get_name()], } } diff --git a/apps/api/libs/agents/src/agents/modes/review.rs b/apps/api/libs/agents/src/agents/modes/review.rs index 926eb6905..e18cce767 100644 --- a/apps/api/libs/agents/src/agents/modes/review.rs +++ b/apps/api/libs/agents/src/agents/modes/review.rs @@ -13,7 +13,7 @@ use super::{ModeAgentData, ModeConfiguration}; // Import necessary tools for this mode use crate::tools::{ - categories::response_tools::{Done, Idle, MessageUserClarifyingQuestion}, + categories::response_tools::{Done, MessageUserClarifyingQuestion}, planning_tools::ReviewPlan, IntoToolCallExecutor, }; @@ -46,7 +46,6 @@ pub fn get_configuration( // Instantiate tools for this mode let review_tool = ReviewPlan::new(agent_clone.clone()); let done_tool = Done::new(agent_clone.clone()); - let idle_tool = Idle::new(agent_clone.clone()); // Condition (always true for this mode's tools) let condition = Some(|_state: &HashMap| -> bool { true }); @@ -68,20 +67,12 @@ pub fn get_configuration( ) .await; - agent_clone - .add_tool( - idle_tool.get_name(), - idle_tool.into_tool_call_executor(), - condition.clone(), - ) - .await; - Ok(()) }) }); // 4. Define terminating tools for this mode (From original load_tools) - let terminating_tools = vec![Done::get_name(), Idle::get_name()]; + let terminating_tools = vec![Done::get_name()]; // 5. Construct and return the ModeConfiguration ModeConfiguration { diff --git a/apps/api/libs/agents/src/tools/categories/response_tools/idle.rs b/apps/api/libs/agents/src/tools/categories/response_tools/idle.rs deleted file mode 100644 index 0af19d653..000000000 --- a/apps/api/libs/agents/src/tools/categories/response_tools/idle.rs +++ /dev/null @@ -1,133 +0,0 @@ -use anyhow::Result; -use async_trait::async_trait; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::sync::Arc; - -use crate::{agent::Agent, tools::ToolExecutor}; - -#[derive(Debug, Deserialize, Serialize)] -pub struct IdleInput { - final_response: String, -} - -// Define the new standard output struct -#[derive(Debug, Serialize, Deserialize)] -pub struct IdleOutput { - pub success: bool, - pub todos: String, -} - -pub struct Idle { - agent: Arc, -} - -impl Idle { - pub fn new(agent: Arc) -> Self { - Self { agent } - } - - pub fn get_name() -> String { - "idle".to_string() - } -} - -#[async_trait] -impl ToolExecutor for Idle { - type Output = IdleOutput; - type Params = IdleInput; - - fn get_name(&self) -> String { - "idle".to_string() - } - - async fn execute(&self, params: Self::Params, _tool_call_id: String) -> Result { - // Get the current todos from state - let mut todos = match self.agent.get_state_value("todos").await { - Some(Value::Array(arr)) => arr, - _ => { - // If no todos exist, just return success without a list - return Ok(IdleOutput { - success: true, - todos: "No to-do list found.".to_string(), - }); - } - }; - - let mut marked_by_idle = vec![]; // Track items marked by this tool - - // Mark all remaining unfinished todos as complete - for (idx, todo_val) in todos.iter_mut().enumerate() { - if let Value::Object(map) = todo_val { - let is_completed = map - .get("completed") - .and_then(Value::as_bool) - .unwrap_or(false); - if !is_completed { - map.insert("completed".to_string(), Value::Bool(true)); - marked_by_idle.push(idx); // Track 0-based index - } - } else { - // Handle invalid item format if necessary, maybe log a warning? - eprintln!("Warning: Invalid todo item format at index {}", idx); - } - } - - // Save the updated todos back to state - self.agent - .set_state_value("todos".to_string(), Value::Array(todos.clone())) - .await; // Clone needed for iteration below - - // Format the output string, potentially noting items marked by 'idle' - let todos_string = todos - .iter() - .enumerate() - .map(|(idx, todo_val)| { - if let Value::Object(map) = todo_val { - let completed = map - .get("completed") - .and_then(Value::as_bool) - .unwrap_or(false); // Should always be true now - let todo_text = map - .get("todo") - .and_then(Value::as_str) - .unwrap_or("Invalid todo text"); - let annotation = if marked_by_idle.contains(&idx) { - " *Marked complete by calling the idle tool" - } else { - "" - }; - format!("[x] {}{}", todo_text, annotation) - } else { - "Invalid todo item format".to_string() - } - }) - .collect::>() - .join("\n"); - - Ok(IdleOutput { - success: true, - todos: todos_string, - }) // Include todos in output - } - - async fn get_schema(&self) -> Value { - serde_json::json!({ - "name": self.get_name(), - "description": "Marks all remaining unfinished tasks as complete, sends a final response to the user, and enters an idle state. Use this when current work is finished but the agent should remain available for future tasks. This must be in markdown format and not use the '•' bullet character.", - "parameters": { - "type": "object", - "required": [ - "final_response" - ], - "properties": { - "final_response": { - "type": "string", - "description": "The final response message to the user. **MUST** be formatted in Markdown. Use bullet points or other appropriate Markdown formatting. Do not include headers. Do not use the '•' bullet character. Do not include markdown tables." - } - }, - "additionalProperties": false - } - }) - } -} diff --git a/apps/api/libs/agents/src/tools/categories/response_tools/mod.rs b/apps/api/libs/agents/src/tools/categories/response_tools/mod.rs index 6a14f5079..2a69a3418 100644 --- a/apps/api/libs/agents/src/tools/categories/response_tools/mod.rs +++ b/apps/api/libs/agents/src/tools/categories/response_tools/mod.rs @@ -1,7 +1,5 @@ pub mod message_user_clarifying_question; pub mod done; -pub mod idle; pub use message_user_clarifying_question::*; -pub use done::*; -pub use idle::*; \ No newline at end of file +pub use done::*; \ No newline at end of file From 8383257b58265a93af7e4184a0fcd42e1eb68dac Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:00:18 +0000 Subject: [PATCH 3/3] remove idle tool from analyst-agent integration - Remove idleTool import and usage from analyst-agent.ts - Keep only idle-tool.ts in communication-tools and its export - No agent integration per user feedback - Minimal TypeScript-only implementation Co-Authored-By: Dallin Bentley --- packages/ai/src/agents/analyst-agent/analyst-agent.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/ai/src/agents/analyst-agent/analyst-agent.ts b/packages/ai/src/agents/analyst-agent/analyst-agent.ts index f1b4bfbc4..80d2f21ae 100644 --- a/packages/ai/src/agents/analyst-agent/analyst-agent.ts +++ b/packages/ai/src/agents/analyst-agent/analyst-agent.ts @@ -4,7 +4,6 @@ import { createDashboards, createMetrics, doneTool, - idleTool, modifyDashboards, modifyMetrics, } from '../../tools'; @@ -32,7 +31,6 @@ export const analystAgent = new Agent({ createDashboards, modifyDashboards, doneTool, - idleTool, }, defaultGenerateOptions: DEFAULT_OPTIONS, defaultStreamOptions: DEFAULT_OPTIONS,