chore: lint
This commit is contained in:
@@ -129,10 +129,7 @@ _TOOL_CATEGORIES: dict[str, list[str]] = {
|
||||
# Research — paper search, Wikipedia, ad-hoc web scrape. Pair with
|
||||
# browser_basic for richer site-by-site research; this category is the
|
||||
# lightweight always-available fallback.
|
||||
"research": [
|
||||
"web_scrape",
|
||||
"pdf_read"
|
||||
],
|
||||
"research": ["web_scrape", "pdf_read"],
|
||||
# Security — defensive scanning and reconnaissance. Engineering-only
|
||||
# surface; the rest of the queens shouldn't see port scanners.
|
||||
"security": [
|
||||
|
||||
@@ -52,11 +52,11 @@ _DEFAULT_LOCAL_SERVERS: dict[str, dict[str, Any]] = {
|
||||
"args": ["run", "python", "files_server.py", "--stdio"],
|
||||
},
|
||||
"terminal-tools": {
|
||||
"description": "Terminal capabilities: process exec, background jobs, PTY sessions, fs search. Bash-only on POSIX.",
|
||||
"description": "Terminal capabilities",
|
||||
"args": ["run", "python", "terminal_tools_server.py", "--stdio"],
|
||||
},
|
||||
"chart-tools": {
|
||||
"description": "BI/financial chart + diagram rendering: ECharts, Mermaid. Returns spec + downloadable PNG; chat embeds live.",
|
||||
"description": "BI/financial chart + diagram rendering: ECharts, Mermaid",
|
||||
"args": ["run", "python", "chart_tools_server.py", "--stdio"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -22,13 +22,11 @@ Usage:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import difflib
|
||||
import fnmatch
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading as _threading
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
@@ -924,8 +922,7 @@ def _apply_hunk(content: str, hunk: _Hunk) -> tuple[str, str | None]:
|
||||
count = content.count(hunk.context_hint)
|
||||
if count > 1:
|
||||
return content, (
|
||||
f"addition-only hunk: context hint "
|
||||
f"'{hunk.context_hint}' is ambiguous ({count} occurrences)"
|
||||
f"addition-only hunk: context hint '{hunk.context_hint}' is ambiguous ({count} occurrences)"
|
||||
)
|
||||
if count == 1:
|
||||
idx = content.find(hunk.context_hint)
|
||||
@@ -1045,9 +1042,7 @@ def _apply_v4a(
|
||||
for hunk_idx, hunk in enumerate(op.hunks):
|
||||
new_content, herr = _apply_hunk(content, hunk)
|
||||
if herr:
|
||||
errors.append(
|
||||
f"Op #{op_idx + 1} update {op.path} hunk #{hunk_idx + 1}: {herr}"
|
||||
)
|
||||
errors.append(f"Op #{op_idx + 1} update {op.path} hunk #{hunk_idx + 1}: {herr}")
|
||||
break
|
||||
content = new_content
|
||||
fs_state[resolved] = content
|
||||
@@ -1063,9 +1058,7 @@ def _apply_v4a(
|
||||
errors.append(f"Op #{op_idx + 1} move {op.path}: {err}")
|
||||
continue
|
||||
if os.path.exists(dst_resolved) and fs_exists.get(dst_resolved, True):
|
||||
errors.append(
|
||||
f"Op #{op_idx + 1} move {op.path}: destination already exists"
|
||||
)
|
||||
errors.append(f"Op #{op_idx + 1} move {op.path}: destination already exists")
|
||||
continue
|
||||
fs_state[dst_resolved] = fs_state[resolved]
|
||||
fs_exists[dst_resolved] = True
|
||||
@@ -1121,8 +1114,7 @@ def _apply_v4a(
|
||||
|
||||
if apply_errors:
|
||||
return None, (
|
||||
"Apply phase failed (state may be inconsistent — run `git diff` to assess):\n "
|
||||
+ "\n ".join(apply_errors)
|
||||
"Apply phase failed (state may be inconsistent — run `git diff` to assess):\n " + "\n ".join(apply_errors)
|
||||
)
|
||||
|
||||
summary_parts: list[str] = []
|
||||
@@ -1177,10 +1169,7 @@ def _patch_replace(
|
||||
f"harness can track its state before you edit it."
|
||||
)
|
||||
if _fresh.status is Freshness.STALE:
|
||||
return (
|
||||
f"Refusing to edit '{path}': {_fresh.detail}. Re-read the file with "
|
||||
f"read_file before editing."
|
||||
)
|
||||
return f"Refusing to edit '{path}': {_fresh.detail}. Re-read the file with read_file before editing."
|
||||
|
||||
try:
|
||||
with open(resolved, encoding="utf-8") as f:
|
||||
@@ -1217,9 +1206,7 @@ def _patch_replace(
|
||||
break
|
||||
|
||||
if matched is None:
|
||||
close = difflib.get_close_matches(
|
||||
old_string[:200], content.split("\n"), n=3, cutoff=0.4
|
||||
)
|
||||
close = difflib.get_close_matches(old_string[:200], content.split("\n"), n=3, cutoff=0.4)
|
||||
msg = (
|
||||
f"Error: Could not find a unique match for old_string in {path}. "
|
||||
f"Use read_file to verify the current content, or search_files "
|
||||
@@ -1352,14 +1339,8 @@ EDIT_FILE_PARAMS = {
|
||||
"tabs vs spaces, smart quotes vs ASCII, and literal \\n/\\t/\\r "
|
||||
"vs real control chars."
|
||||
),
|
||||
"new_string": (
|
||||
"Replace mode only. Replacement text. Pass an empty string to "
|
||||
"delete the matched text."
|
||||
),
|
||||
"replace_all": (
|
||||
"Replace mode only. Replace every occurrence instead of requiring "
|
||||
"a unique match. Default False."
|
||||
),
|
||||
"new_string": ("Replace mode only. Replacement text. Pass an empty string to delete the matched text."),
|
||||
"replace_all": ("Replace mode only. Replace every occurrence instead of requiring a unique match. Default False."),
|
||||
"patch_text": (
|
||||
"Patch mode only. Structured patch body. File paths are embedded "
|
||||
"inside the body via '*** Update File: <path>' / "
|
||||
@@ -1396,18 +1377,14 @@ SEARCH_FILES_DOC = (
|
||||
)
|
||||
SEARCH_FILES_PARAMS = {
|
||||
"pattern": (
|
||||
"Regex (content mode) or glob (files mode, e.g. '*.py'). For an "
|
||||
"'ls'-style listing pass '*' or '*.<ext>'."
|
||||
"Regex (content mode) or glob (files mode, e.g. '*.py'). For an 'ls'-style listing pass '*' or '*.<ext>'."
|
||||
),
|
||||
"target": (
|
||||
"'content' to grep inside files, 'files' to list/find files. "
|
||||
"Legacy aliases: 'grep' -> 'content', 'find'/'ls' -> 'files'. "
|
||||
"Default 'content'."
|
||||
),
|
||||
"path": (
|
||||
"Directory (or, in content mode, a single file) to search. "
|
||||
"Default '.'."
|
||||
),
|
||||
"path": ("Directory (or, in content mode, a single file) to search. Default '.'."),
|
||||
"file_glob": (
|
||||
"Restrict content search to filenames matching this glob. "
|
||||
"Ignored in files mode (use the 'pattern' argument instead)."
|
||||
@@ -1419,14 +1396,8 @@ SEARCH_FILES_PARAMS = {
|
||||
"default), 'files_only' (paths only), 'count' (per-file match "
|
||||
"counts)."
|
||||
),
|
||||
"context": (
|
||||
"Lines of context before and after each match (content mode "
|
||||
"only). Default 0."
|
||||
),
|
||||
"hashline": (
|
||||
"Content mode: include N:hhhh hash anchors in matched lines. "
|
||||
"Default False."
|
||||
),
|
||||
"context": ("Lines of context before and after each match (content mode only). Default 0."),
|
||||
"hashline": ("Content mode: include N:hhhh hash anchors in matched lines. Default False."),
|
||||
"task_id": (
|
||||
"Optional anti-loop scope key. Defaults to a shared bucket; pass "
|
||||
"a per-task id when multiple agents share a process."
|
||||
@@ -1719,4 +1690,3 @@ def register_file_tools(
|
||||
"Results have not changed — use what you have instead of re-searching.]"
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@@ -137,10 +137,7 @@ def register_tools(mcp: FastMCP) -> None:
|
||||
"error": f"Blocked by robots.txt: {url}",
|
||||
"url": url,
|
||||
"skipped": True,
|
||||
"hint": (
|
||||
"Pass respect_robots_txt=False if you have "
|
||||
"authorization to scrape this site."
|
||||
),
|
||||
"hint": ("Pass respect_robots_txt=False if you have authorization to scrape this site."),
|
||||
}
|
||||
except Exception:
|
||||
pass # If robots.txt can't be fetched, proceed anyway
|
||||
@@ -343,8 +340,19 @@ def register_tools(mcp: FastMCP) -> None:
|
||||
for br in content_elem.find_all("br"):
|
||||
br.replace_with(NavigableString("\n"))
|
||||
block_tags = (
|
||||
"p", "h1", "h2", "h3", "h4", "h5", "h6",
|
||||
"li", "tr", "div", "section", "article", "blockquote",
|
||||
"p",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"li",
|
||||
"tr",
|
||||
"div",
|
||||
"section",
|
||||
"article",
|
||||
"blockquote",
|
||||
)
|
||||
for block in content_elem.find_all(block_tags):
|
||||
block.insert_before(NavigableString("\n"))
|
||||
|
||||
@@ -29,11 +29,7 @@ def register_chart_tools(mcp: FastMCP) -> list[str]:
|
||||
|
||||
register_tools(mcp)
|
||||
|
||||
return [
|
||||
name
|
||||
for name in mcp._tool_manager._tools.keys()
|
||||
if name.startswith("chart_")
|
||||
]
|
||||
return [name for name in mcp._tool_manager._tools.keys() if name.startswith("chart_")]
|
||||
|
||||
|
||||
__all__ = ["register_chart_tools"]
|
||||
|
||||
@@ -247,9 +247,7 @@ async def _render_in_page(
|
||||
# expected to have already coerced JSON-string specs into dicts
|
||||
# in chart_tools/tools.py — this is a defense-in-depth check.
|
||||
if isinstance(spec, str):
|
||||
raise RendererError(
|
||||
"spec arrived as a string; it should have been parsed to a dict in chart_render"
|
||||
)
|
||||
raise RendererError("spec arrived as a string; it should have been parsed to a dict in chart_render")
|
||||
try:
|
||||
json.dumps(spec)
|
||||
except (TypeError, ValueError) as exc:
|
||||
|
||||
@@ -174,9 +174,7 @@ def register_tools(mcp: FastMCP) -> None:
|
||||
# browser-side flakes. We retry once for the latter; if
|
||||
# the second attempt fails too, surface the error so the
|
||||
# agent can fix it.
|
||||
logger.warning(
|
||||
"chart_render attempt %d/%d failed: %s", attempt + 1, 2, exc
|
||||
)
|
||||
logger.warning("chart_render attempt %d/%d failed: %s", attempt + 1, 2, exc)
|
||||
if attempt == 0:
|
||||
await asyncio.sleep(0.15)
|
||||
continue
|
||||
|
||||
@@ -71,17 +71,10 @@ def build_exec_envelope(
|
||||
# the foundational skill documents). For simplicity we always
|
||||
# store both when either overflows so the agent can fetch the
|
||||
# other stream in full too if it wants.
|
||||
combined = (
|
||||
b"--- stdout ---\n"
|
||||
+ stdout_bytes
|
||||
+ b"\n--- stderr ---\n"
|
||||
+ stderr_bytes
|
||||
)
|
||||
combined = b"--- stdout ---\n" + stdout_bytes + b"\n--- stderr ---\n" + stderr_bytes
|
||||
output_handle = store.put(combined)
|
||||
|
||||
semantic_status, semantic_message = classify(
|
||||
command, exit_code, timed_out=timed_out, signaled=signaled
|
||||
)
|
||||
semantic_status, semantic_message = classify(command, exit_code, timed_out=timed_out, signaled=signaled)
|
||||
|
||||
warning = get_warning(command)
|
||||
|
||||
|
||||
@@ -53,9 +53,7 @@ if TYPE_CHECKING:
|
||||
# directly — the alternative is spawning the first program with the rest
|
||||
# of the line as junk argv, which either errors or returns fake success
|
||||
# (e.g. `echo "..." && ps ...` → echo prints the literal command).
|
||||
_SHELL_METACHARS: frozenset[str] = frozenset(
|
||||
{"|", "&&", "||", ";", ">", "<", ">>", "<<", "&", "2>", "2>&1", "|&"}
|
||||
)
|
||||
_SHELL_METACHARS: frozenset[str] = frozenset({"|", "&&", "||", ";", ">", "<", ">>", "<<", "&", "2>", "2>&1", "|&"})
|
||||
|
||||
|
||||
def register_exec_tools(mcp: FastMCP) -> None:
|
||||
@@ -126,7 +124,8 @@ def register_exec_tools(mcp: FastMCP) -> None:
|
||||
return _err_envelope(command, "command was empty")
|
||||
if any(t in _SHELL_METACHARS for t in tokens) or any(
|
||||
# globs that shlex left unexpanded (`*`, `?`, `[`)
|
||||
any(c in t for c in "*?[") and t != "[" for t in tokens
|
||||
any(c in t for c in "*?[") and t != "["
|
||||
for t in tokens
|
||||
):
|
||||
auto_shell = True
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ from gcu.browser.bridge import BeelineBridge
|
||||
from gcu.browser.tools.advanced import register_advanced_tools
|
||||
from gcu.browser.tools.inspection import register_inspection_tools
|
||||
from gcu.browser.tools.interactions import register_interaction_tools
|
||||
from gcu.browser.tools.lifecycle import register_lifecycle_tools
|
||||
from gcu.browser.tools.navigation import register_navigation_tools
|
||||
from gcu.browser.tools.tabs import register_tab_tools
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@ def test_register_chart_tools_lands_all(mcp):
|
||||
from chart_tools import register_chart_tools
|
||||
|
||||
names = register_chart_tools(mcp)
|
||||
assert set(names) == EXPECTED_TOOLS, (
|
||||
f"missing: {EXPECTED_TOOLS - set(names)}, extra: {set(names) - EXPECTED_TOOLS}"
|
||||
)
|
||||
assert set(names) == EXPECTED_TOOLS, f"missing: {EXPECTED_TOOLS - set(names)}, extra: {set(names) - EXPECTED_TOOLS}"
|
||||
|
||||
|
||||
def test_all_tools_have_chart_prefix(mcp):
|
||||
|
||||
@@ -63,9 +63,7 @@ def test_merge_stderr(job_tools):
|
||||
merge_stderr=True,
|
||||
)
|
||||
job_id = started["job_id"]
|
||||
result = job_tools["logs"](
|
||||
job_id=job_id, stream="merged", wait_until_exit=True, wait_timeout_sec=5
|
||||
)
|
||||
result = job_tools["logs"](job_id=job_id, stream="merged", wait_until_exit=True, wait_timeout_sec=5)
|
||||
assert "stdout1" in result["data"]
|
||||
assert "stderr1" in result["data"]
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@ def test_register_terminal_tools_lands_all_ten(mcp):
|
||||
from terminal_tools import register_terminal_tools
|
||||
|
||||
names = register_terminal_tools(mcp)
|
||||
assert set(names) == EXPECTED_TOOLS, (
|
||||
f"missing: {EXPECTED_TOOLS - set(names)}, extra: {set(names) - EXPECTED_TOOLS}"
|
||||
)
|
||||
assert set(names) == EXPECTED_TOOLS, f"missing: {EXPECTED_TOOLS - set(names)}, extra: {set(names) - EXPECTED_TOOLS}"
|
||||
|
||||
|
||||
def test_all_tools_have_terminal_prefix(mcp):
|
||||
|
||||
@@ -280,7 +280,7 @@ class TestPatchToolReplaceMode:
|
||||
result = edit_fn(
|
||||
mode="replace",
|
||||
path="b.py",
|
||||
old_string='print(“hi”)',
|
||||
old_string="print(“hi”)",
|
||||
new_string='print("HELLO")',
|
||||
)
|
||||
assert "Error" not in result
|
||||
@@ -331,14 +331,7 @@ class TestPatchToolPatchMode:
|
||||
"""A V4A Update hunk replaces matched lines and writes."""
|
||||
target = tmp_path / "u.py"
|
||||
target.write_text("def f():\n return 1\n", encoding="utf-8")
|
||||
body = (
|
||||
"*** Begin Patch\n"
|
||||
"*** Update File: u.py\n"
|
||||
" def f():\n"
|
||||
"- return 1\n"
|
||||
"+ return 42\n"
|
||||
"*** End Patch\n"
|
||||
)
|
||||
body = "*** Begin Patch\n*** Update File: u.py\n def f():\n- return 1\n+ return 42\n*** End Patch\n"
|
||||
edit_fn = _get_tool_fn(file_ops_mcp, "edit_file")
|
||||
result = edit_fn(mode="patch", patch_text=body)
|
||||
assert "Error" not in result
|
||||
@@ -347,13 +340,7 @@ class TestPatchToolPatchMode:
|
||||
|
||||
def test_patch_add_file(self, file_ops_mcp, tmp_path):
|
||||
"""Add File: creates a new file from + lines."""
|
||||
body = (
|
||||
"*** Begin Patch\n"
|
||||
"*** Add File: new.py\n"
|
||||
"+# new\n"
|
||||
"+x = 1\n"
|
||||
"*** End Patch\n"
|
||||
)
|
||||
body = "*** Begin Patch\n*** Add File: new.py\n+# new\n+x = 1\n*** End Patch\n"
|
||||
edit_fn = _get_tool_fn(file_ops_mcp, "edit_file")
|
||||
result = edit_fn(mode="patch", patch_text=body)
|
||||
assert "Error" not in result
|
||||
|
||||
@@ -466,9 +466,7 @@ class TestWebScrapeToolAIFriendlyOutput:
|
||||
|
||||
result = await web_scrape_fn(url="https://example.com")
|
||||
assert "structured_data" in result
|
||||
assert result["structured_data"]["json_ld"] == [
|
||||
{"@type": "Article", "headline": "Hello"}
|
||||
]
|
||||
assert result["structured_data"]["json_ld"] == [{"@type": "Article", "headline": "Hello"}]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch(_STEALTH_PATH)
|
||||
|
||||
Reference in New Issue
Block a user