diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index bbd14f518..2b487071b 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -1907,6 +1907,37 @@ class AgentExecutor(Flow[AgentExecutorState], CrewAgentExecutorMixin): "original_tool": original_tool, } + def _extract_tool_name(self, tool_call: Any) -> str: + """Extract tool name from various tool call formats.""" + if hasattr(tool_call, "function"): + return sanitize_tool_name(tool_call.function.name) + if hasattr(tool_call, "function_call") and tool_call.function_call: + return sanitize_tool_name(tool_call.function_call.name) + if hasattr(tool_call, "name"): + return sanitize_tool_name(tool_call.name) + if isinstance(tool_call, dict): + func_info = tool_call.get("function", {}) + return sanitize_tool_name( + func_info.get("name", "") or tool_call.get("name", "unknown") + ) + return "unknown" + + @router(execute_native_tool) + def check_native_todo_completion( + self, + ) -> Literal["todo_satisfied", "todo_not_satisfied"]: + """Check if the native tool execution satisfied the active todo. + + Similar to check_todo_completion but for native tool execution path. + """ + current_todo = self.state.todos.current_todo + + if not current_todo: + return "todo_not_satisfied" + + # For native tools, any tool execution satisfies the todo + return "todo_satisfied" + @listen("initialized") def continue_iteration(self) -> Literal["check_iteration"]: """Bridge listener that connects iteration loop back to iteration check.""" diff --git a/lib/crewai/tests/agents/test_agent_executor.py b/lib/crewai/tests/agents/test_agent_executor.py index 1ec1a1788..91fa12f27 100644 --- a/lib/crewai/tests/agents/test_agent_executor.py +++ b/lib/crewai/tests/agents/test_agent_executor.py @@ -927,6 +927,30 @@ class TestNativeToolExecution: assert len(tool_messages) == 1 assert tool_messages[0]["tool_call_id"] == "call_1" + def test_check_native_todo_completion_requires_current_todo( + self, mock_dependencies + ): + from crewai.utilities.planning_types import TodoList + + executor = _build_executor(**mock_dependencies) + + # No current todo → not satisfied + executor.state.todos = TodoList(items=[]) + assert executor.check_native_todo_completion() == "todo_not_satisfied" + + # With a current todo that has tool_to_use → satisfied + running = TodoItem( + step_number=1, + description="Use the expected tool", + tool_to_use="expected_tool", + status="running", + ) + executor.state.todos = TodoList(items=[running]) + assert executor.check_native_todo_completion() == "todo_satisfied" + + # With a current todo without tool_to_use → still satisfied + running.tool_to_use = None + assert executor.check_native_todo_completion() == "todo_satisfied" class TestPlannerObserver: