diff --git a/src/agentacct/tool_activity.py b/src/agentacct/tool_activity.py index 38b2662..d2f2592 100644 --- a/src/agentacct/tool_activity.py +++ b/src/agentacct/tool_activity.py @@ -94,6 +94,25 @@ "update_plan": "plan", # Generic shell aliases other agents use (e.g. Hermes' ``terminal``). "terminal": "execute", + # opencode: the names it uses that are not already shared with the + # Claude/Codex tools above. Without these they collapsed to ``other``. + "list": "search", + "patch": "edit", + "todoread": "plan", + # Anthropic's official text-editor tool — one tool whose NAME varies by API + # version (str_replace_based_edit_tool / str_replace_editor / text_editor). + "str_replace_based_edit_tool": "edit", + "str_replace_editor": "edit", + "text_editor": "edit", + # Cross-agent snake_case names used by Cursor/Windsurf and various MCP + # servers; each of these otherwise fell through to ``other``. + "read_file": "read", + "write_file": "edit", + "edit_file": "edit", + "list_dir": "search", + "codebase_search": "search", + "run_terminal_cmd": "execute", + "web_search": "network", } diff --git a/tests/test_codex_actions.py b/tests/test_codex_actions.py index 7efff7e..d4fb1c7 100644 --- a/tests/test_codex_actions.py +++ b/tests/test_codex_actions.py @@ -146,7 +146,7 @@ def test_tool_activity_from_calls_aggregates_all_signals(): ("exec_command", json.dumps({"cmd": "pytest -q"})), ("apply_patch", "*** Begin Patch\n*** Add File: src/a.py\n+x\n*** End Patch\n"), ("spawn_agent", "{}"), - ("read_file", "{}"), # unknown -> category "other", name kept + ("some_unknown_tool", "{}"), # unknown -> category "other", name kept ] activity = _tool_activity_from_calls(calls, cwd="/work/project") assert activity["tool_category_counts"] == { @@ -161,7 +161,7 @@ def test_tool_activity_from_calls_aggregates_all_signals(): "exec_command": 1, "apply_patch": 1, "spawn_agent": 1, - "read_file": 1, + "some_unknown_tool": 1, } assert activity["commands"] == ["git status", "pytest -q"] assert activity["touched_files"] == ["src/a.py"] diff --git a/tests/test_tool_activity.py b/tests/test_tool_activity.py index 2ed4b37..cf6e458 100644 --- a/tests/test_tool_activity.py +++ b/tests/test_tool_activity.py @@ -83,6 +83,24 @@ def test_tool_category_maps_names_only_and_collapses_mcp() -> None: assert tool_category("WebFetch") == "network" assert tool_category("Task") == "agent" assert tool_category("TodoWrite") == "plan" + # Names are matched case-insensitively. + assert tool_category("READ_FILE") == "read" + # opencode names not shared with the Claude/Codex tools. + assert tool_category("list") == "search" + assert tool_category("patch") == "edit" + assert tool_category("todoread") == "plan" + # Anthropic's text-editor tool, whatever the API version names it. + assert tool_category("str_replace_based_edit_tool") == "edit" + assert tool_category("str_replace_editor") == "edit" + assert tool_category("text_editor") == "edit" + # Cross-agent snake_case names (Cursor/Windsurf/MCP) that used to be `other`. + assert tool_category("read_file") == "read" + assert tool_category("write_file") == "edit" + assert tool_category("edit_file") == "edit" + assert tool_category("list_dir") == "search" + assert tool_category("codebase_search") == "search" + assert tool_category("run_terminal_cmd") == "execute" + assert tool_category("web_search") == "network" # Any MCP tool collapses to a single bucket by PREFIX, so a specific MCP # tool name (which could reveal a connector) is never recorded. assert tool_category("mcp__github__create_issue") == "mcp"