mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-13 07:40:18 +00:00
request_tool: non-catalog names never raise the install card
Fast-fail with a shell steer before any prompt; docstring names the closed catalog and stops citing semgrep (pip/brew, deliberately unmanaged) as an example.
This commit is contained in:
@@ -1132,6 +1132,23 @@ class TurnEngine:
|
||||
"your report which checks were degraded."
|
||||
),
|
||||
}
|
||||
elif _toolchain.describe(name) is None:
|
||||
# Not in the pinned catalog: no card at all (owner-hit 2026-08-20 — agents
|
||||
# routed ordinary brew/pip installs through the install card, which could
|
||||
# only fail after approval). The agent has a shell with its own approval
|
||||
# flow; steer it there instead of at the user.
|
||||
catalog = ", ".join(sorted(_toolchain.MANAGED))
|
||||
result = {
|
||||
"installed": False,
|
||||
"error": (
|
||||
f"'{name}' is not in the pinned tool catalog ({catalog})."
|
||||
),
|
||||
"guidance": (
|
||||
"Install it yourself with the shell (brew/pip/…, subject to the "
|
||||
"normal command approval), or continue without it and say in your "
|
||||
"report which checks were degraded."
|
||||
),
|
||||
}
|
||||
else:
|
||||
# The prompt must say up front whether WE can install this (pinned build for
|
||||
# this platform) — a card that offers Install for a tool we can't fetch turns
|
||||
|
||||
@@ -2073,6 +2073,20 @@ def create_app(manager: SessionManager) -> FastAPI:
|
||||
"""
|
||||
name = str(args.get("name", "")).strip()
|
||||
info = toolchain.describe(name)
|
||||
if not info:
|
||||
# Not in the pinned catalog: never show an install card that can only
|
||||
# end in "no pinned build" AFTER approval (owner-hit 2026-08-20 — agents
|
||||
# routed ordinary brew/pip installs through the card). Steer to the
|
||||
# shell, which has its own approval flow.
|
||||
return {
|
||||
"installed": False,
|
||||
"error": (
|
||||
f"'{name}' is not in the pinned tool catalog "
|
||||
f"({', '.join(sorted(toolchain.MANAGED))}). Install it yourself "
|
||||
"with the shell (brew/pip/…, subject to the normal command "
|
||||
"approval), or proceed without it and note the gap."
|
||||
),
|
||||
}
|
||||
item = manager.inbox.add_tool_request(
|
||||
session_id,
|
||||
f"Install {name}?" if name else "Install a tool?",
|
||||
|
||||
@@ -17,8 +17,13 @@ from aisuite.agents import ToolMetadata, tool
|
||||
|
||||
def request_tool_tool() -> object:
|
||||
def request_tool(name: str, reason: str) -> dict:
|
||||
"""Ask the user to install a command-line tool you need but can't find on this
|
||||
machine (e.g. `gitleaks`, `osv-scanner`, `semgrep`).
|
||||
"""Ask the user to install one of the PINNED catalog tools you need but can't find
|
||||
on this machine. The catalog is a small closed set — currently `gitleaks`,
|
||||
`trivy`, `osv-scanner` — installed at a pinned, checksum-verified version.
|
||||
|
||||
For ANY other missing CLI (semgrep, jq, kubectl, …) do NOT use this tool: install
|
||||
it yourself with the shell (brew/pip/…), which goes through the normal command
|
||||
approval, or proceed without it.
|
||||
|
||||
Keep `reason` to ONE sentence: which check needs the tool. The prompt the user
|
||||
sees already explains what the install is (pinned version, publisher, checksum)
|
||||
|
||||
@@ -118,7 +118,7 @@ async def test_decline_recheck_finds_a_copy_the_user_installed_themselves(tmp_pa
|
||||
async def test_event_tells_the_truth_about_installability(tmp_path, monkeypatch):
|
||||
"""Owner-hit 2026-08-14: the card offered Install for a tool with no pinned build —
|
||||
the surface guessed because the event said nothing. The event must carry the
|
||||
registry's verdict, and no metadata means NO."""
|
||||
registry's verdict for catalog tools."""
|
||||
from coworker import toolchain
|
||||
|
||||
monkeypatch.setattr(toolchain, "_platform_key", lambda: "darwin_arm64")
|
||||
@@ -133,10 +133,29 @@ async def test_event_tells_the_truth_about_installability(tmp_path, monkeypatch)
|
||||
assert data["summary"]
|
||||
assert data["source"] == "github.com/gitleaks"
|
||||
|
||||
events = await _run(_engine(tmp_path, requester, tool="not-a-managed-tool"))
|
||||
data = [e for e in events if e.type is EventType.TOOL_REQUESTED][0].data
|
||||
assert data["installable"] is False
|
||||
assert data["version"] == "" and data["summary"] == ""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_non_catalog_tool_gets_no_card_and_a_shell_steer(tmp_path, monkeypatch):
|
||||
"""Owner-hit 2026-08-20: agents routed ordinary brew/pip installs through the
|
||||
install card, which could only fail AFTER the user approved. A non-catalog name
|
||||
must produce NO prompt at all — just a result steering the agent to the shell."""
|
||||
from coworker import toolchain
|
||||
|
||||
monkeypatch.setattr(toolchain, "_platform_key", lambda: "darwin_arm64")
|
||||
called = []
|
||||
|
||||
async def requester(args, tool_call_id=None):
|
||||
called.append(args)
|
||||
return {"installed": False, "reason": "declined"}
|
||||
|
||||
engine = _engine(tmp_path, requester, tool="semgrep")
|
||||
events = await _run(engine)
|
||||
assert not [e for e in events if e.type is EventType.TOOL_REQUESTED]
|
||||
assert called == [] # the user was never asked
|
||||
tool_msg = [m for m in engine.messages if m.get("role") == "tool"][-1]
|
||||
body = str(tool_msg["content"])
|
||||
assert "not in the pinned tool catalog" in body
|
||||
assert "shell" in body and "gitleaks" in body # the catalog is named
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user