From 48a861aa1a17ef286acb1294ea0800b5a9d80e5f Mon Sep 17 00:00:00 2001 From: alex-clawd Date: Wed, 13 May 2026 02:06:51 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20resolve=20all=20CI=20failures=20?= =?UTF-8?q?=E2=80=94=20format,=20lint,=20mypy,=20and=20review=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Format: auto-reformat agent_tui.py, benchmark.py, coworker_tools.py via ruff - Lint: 0 remaining errors after format pass - Mypy: fix _NullPrinter to subclass Printer for type compatibility in executor.py, planning.py, and skill_builder.py; add isinstance(r, Message) guards in spawn_tools.py; annotate return types and fix dict type params and MCPToolResolver logger type in new_agent.py; add missing printer args to get_llm_response calls - cli.py: fix _read_config to use sentinel so falsy values (0, false) are returned correctly instead of being treated as missing keys - create_agent.py: replace regex-based JSONC comment stripper with a token-aware parser that preserves // inside quoted strings (e.g. URLs) Co-Authored-By: Claude Sonnet 4.6 --- lib/cli/src/crewai_cli/agent_tui.py | 10 +++++-- lib/cli/src/crewai_cli/benchmark.py | 1 - lib/cli/src/crewai_cli/cli.py | 6 +++-- lib/cli/src/crewai_cli/create_agent.py | 26 ++++++++++++++++--- .../src/crewai/new_agent/coworker_tools.py | 4 ++- lib/crewai/src/crewai/new_agent/executor.py | 7 ++--- lib/crewai/src/crewai/new_agent/new_agent.py | 24 +++++++++++------ .../src/crewai/new_agent/spawn_tools.py | 7 +++-- 8 files changed, 63 insertions(+), 22 deletions(-) diff --git a/lib/cli/src/crewai_cli/agent_tui.py b/lib/cli/src/crewai_cli/agent_tui.py index c9564a49b..2749ed298 100644 --- a/lib/cli/src/crewai_cli/agent_tui.py +++ b/lib/cli/src/crewai_cli/agent_tui.py @@ -533,8 +533,13 @@ class AgentTUI(App[None]): yield Static("AGENTS", classes="sidebar-label") yield OptionList(id="agent-list") with TabPane("Memory", id="tab-memory"): - yield Static("Click below to open the memory browser.", id="memory-scope-label") - yield Button("Open Memory Browser", id="btn-memory", variant="default") + yield Static( + "Click below to open the memory browser.", + id="memory-scope-label", + ) + yield Button( + "Open Memory Browser", id="btn-memory", variant="default" + ) with Vertical(id="chat-area"): yield VerticalScroll(id="chat-scroll") with Horizontal(id="input-row"): @@ -1617,6 +1622,7 @@ class AgentTUI(App[None]): callback=self._on_room_created, ) return + # ── Room creation ── def _on_room_created(self, result: dict[str, Any] | None) -> None: diff --git a/lib/cli/src/crewai_cli/benchmark.py b/lib/cli/src/crewai_cli/benchmark.py index 0f1f7493a..89229ace7 100644 --- a/lib/cli/src/crewai_cli/benchmark.py +++ b/lib/cli/src/crewai_cli/benchmark.py @@ -111,7 +111,6 @@ def load_benchmark_cases(path: str | Path) -> LoadedCases: return LoadedCases(cases, threshold) - def _check_expected(expected: str, actual: str) -> tuple[bool, float]: """Check if expected output is found in actual (case-insensitive substring match). diff --git a/lib/cli/src/crewai_cli/cli.py b/lib/cli/src/crewai_cli/cli.py index 10cf002f3..183aa1bf8 100644 --- a/lib/cli/src/crewai_cli/cli.py +++ b/lib/cli/src/crewai_cli/cli.py @@ -604,10 +604,12 @@ def _read_config(*keys: str) -> Any: """Read a nested value from config.json (JSONC-safe). Example: _read_config("test", "threshold") reads config["test"]["threshold"]. + Returns None only when the key is missing, not when the value is falsy. """ import json from pathlib import Path + _MISSING = object() config_path = Path("config.json") if not config_path.exists(): return None @@ -618,8 +620,8 @@ def _read_config(*keys: str) -> Any: for k in keys: if not isinstance(data, dict): return None - data = data.get(k) - if data is None: + data = data.get(k, _MISSING) + if data is _MISSING: return None return data except Exception: diff --git a/lib/cli/src/crewai_cli/create_agent.py b/lib/cli/src/crewai_cli/create_agent.py index 65e4e929c..6e31a30a9 100644 --- a/lib/cli/src/crewai_cli/create_agent.py +++ b/lib/cli/src/crewai_cli/create_agent.py @@ -812,10 +812,30 @@ def _prompt_agent_name() -> str: ) +_JSONC_TOKEN_RE = re.compile( + r'"(?:[^"\\]|\\.)*"' # double-quoted string + r"|'(?:[^'\\]|\\.)*'" # single-quoted string (not standard JSON, but safe) + r"|/\*.*?\*/" # /* block comment */ + r"|//[^\n]*" # // line comment + r"|.", # any other character + re.DOTALL, +) + + def _strip_jsonc(text: str) -> str: - """Strip // and /* */ comments from JSONC text, then fix trailing commas.""" - result = re.sub(r"(? str: + token = match.group(0) + if token.startswith(("//", "/*")): + return "" + return token + + result = _JSONC_TOKEN_RE.sub(_replacer, text) result = re.sub(r",\s*([}\]])", r"\1", result) return result diff --git a/lib/crewai/src/crewai/new_agent/coworker_tools.py b/lib/crewai/src/crewai/new_agent/coworker_tools.py index c9dcbde8a..8bf55e5d3 100644 --- a/lib/crewai/src/crewai/new_agent/coworker_tools.py +++ b/lib/crewai/src/crewai/new_agent/coworker_tools.py @@ -244,7 +244,9 @@ class DelegateToCoworkerTool(BaseTool): ) raise - async def _arun(self, message: str, fire_and_forget: bool = False, **kwargs: Any) -> str: + async def _arun( + self, message: str, fire_and_forget: bool = False, **kwargs: Any + ) -> str: """Async delegation — avoids blocking the event loop.""" from crewai.new_agent.events import ( NewAgentDelegationCompletedEvent, diff --git a/lib/crewai/src/crewai/new_agent/executor.py b/lib/crewai/src/crewai/new_agent/executor.py index d13cdeab3..61dcde247 100644 --- a/lib/crewai/src/crewai/new_agent/executor.py +++ b/lib/crewai/src/crewai/new_agent/executor.py @@ -11,6 +11,7 @@ import re import time from typing import TYPE_CHECKING, Any +from crewai_core.printer import Printer from pydantic import BaseModel, Field, PrivateAttr from crewai.new_agent.models import ( @@ -2419,8 +2420,8 @@ class ConversationalAgentExecutor(BaseModel): pass -class _NullPrinter: - """Minimal printer that swallows output.""" +class _NullPrinter(Printer): + """Minimal printer that subclasses Printer but swallows all output.""" - def print(self, *args: Any, **kwargs: Any) -> None: + def print(self, *args: Any, **kwargs: Any) -> None: # type: ignore[override] pass diff --git a/lib/crewai/src/crewai/new_agent/new_agent.py b/lib/crewai/src/crewai/new_agent/new_agent.py index e7ba53160..1612c9add 100644 --- a/lib/crewai/src/crewai/new_agent/new_agent.py +++ b/lib/crewai/src/crewai/new_agent/new_agent.py @@ -36,11 +36,11 @@ def _get_init_chain() -> set[str]: """Return the thread-local set of agent IDs currently being initialized.""" if not hasattr(_init_chain, "agent_ids"): _init_chain.agent_ids = set() - return _init_chain.agent_ids + return _init_chain.agent_ids # type: ignore[no-any-return] # ── GAP-63: Process-level AMP definition cache ────────────────── -_amp_cache: dict[str, dict] = {} +_amp_cache: dict[str, dict[str, Any]] = {} def clear_amp_cache() -> None: @@ -286,8 +286,10 @@ class NewAgent(BaseModel): if self.mcps: try: from crewai.mcp.tool_resolver import MCPToolResolver + from crewai.utilities.logger import Logger as _CrewLogger - resolver = MCPToolResolver(agent=self, logger=self._logger) + _mcp_logger = _CrewLogger(verbose=self.verbose) + resolver = MCPToolResolver(agent=self, logger=_mcp_logger) mcp_tools = resolver.resolve(self.mcps) resolved.extend(mcp_tools) except Exception as e: @@ -631,7 +633,7 @@ class NewAgent(BaseModel): if self.on_message: self.on_message(user_msg) - response = executor.invoke(user_msg) + response: Message = executor.invoke(user_msg) if self.on_complete: self.on_complete(response) @@ -656,7 +658,7 @@ class NewAgent(BaseModel): if self.on_message: self.on_message(user_msg) - response = await executor.ainvoke(user_msg) + response: Message = await executor.ainvoke(user_msg) if self.on_complete: self.on_complete(response) @@ -793,10 +795,13 @@ class NewAgent(BaseModel): messages: list[LLMMessage] = [ format_message_for_llm(prompt, role="user") ] + from crewai.new_agent.executor import _NullPrinter + reasoning_text = get_llm_response( llm=self._llm_instance, messages=messages, callbacks=[], + printer=_NullPrinter(), ) if reasoning_text: reasoning_str = str(reasoning_text).strip() @@ -855,7 +860,7 @@ class NewAgent(BaseModel): executor = self._executors.get(conversation_id) if executor is None: return [] - return executor.conversation_history + return list(executor.conversation_history) @property def conversation_history(self) -> list[Message]: @@ -863,14 +868,15 @@ class NewAgent(BaseModel): executor = self._executors.get(self._default_conversation_id) if executor is None: return [] - return executor.conversation_history + return list(executor.conversation_history) @property def last_prompt_stack(self) -> PromptStack | None: executor = self._executors.get(self._default_conversation_id) if executor is None: return None - return executor.prompt_stack + result: PromptStack | None = executor.prompt_stack + return result @property def usage_metrics(self) -> dict[str, int]: @@ -962,6 +968,7 @@ class NewAgent(BaseModel): ) try: + from crewai.new_agent.executor import _NullPrinter from crewai.utilities.agent_utils import ( format_message_for_llm, get_llm_response, @@ -973,6 +980,7 @@ class NewAgent(BaseModel): llm=llm, messages=messages, callbacks=[], + printer=_NullPrinter(), ) resolved = str(result).strip() return resolved if resolved else text diff --git a/lib/crewai/src/crewai/new_agent/spawn_tools.py b/lib/crewai/src/crewai/new_agent/spawn_tools.py index 3eac5a996..98809ae7d 100644 --- a/lib/crewai/src/crewai/new_agent/spawn_tools.py +++ b/lib/crewai/src/crewai/new_agent/spawn_tools.py @@ -14,6 +14,7 @@ from uuid import uuid4 from pydantic import BaseModel, Field +from crewai.new_agent.models import Message from crewai.tools.base_tool import BaseTool @@ -248,7 +249,8 @@ class SpawnSubtaskTool(BaseTool): except Exception: pass else: - output.append(f"[Subtask {i + 1}] {r.content}") + content = r.content if isinstance(r, Message) else str(r) + output.append(f"[Subtask {i + 1}] {content}") # GAP-57: Emit spawn completed event try: from crewai.new_agent.events import NewAgentSpawnCompletedEvent @@ -427,7 +429,8 @@ class SpawnSubtaskTool(BaseTool): except Exception: pass else: - results.append(f"[Subtask {i + 1}] {r.content}") + content = r.content if isinstance(r, Message) else str(r) + results.append(f"[Subtask {i + 1}] {content}") try: from crewai.new_agent.events import NewAgentSpawnCompletedEvent