mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-11 06:30:25 +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."
|
"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:
|
else:
|
||||||
# The prompt must say up front whether WE can install this (pinned build for
|
# 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
|
# 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()
|
name = str(args.get("name", "")).strip()
|
||||||
info = toolchain.describe(name)
|
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(
|
item = manager.inbox.add_tool_request(
|
||||||
session_id,
|
session_id,
|
||||||
f"Install {name}?" if name else "Install a tool?",
|
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_tool() -> object:
|
||||||
def request_tool(name: str, reason: str) -> dict:
|
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
|
"""Ask the user to install one of the PINNED catalog tools you need but can't find
|
||||||
machine (e.g. `gitleaks`, `osv-scanner`, `semgrep`).
|
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
|
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)
|
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):
|
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 —
|
"""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
|
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
|
from coworker import toolchain
|
||||||
|
|
||||||
monkeypatch.setattr(toolchain, "_platform_key", lambda: "darwin_arm64")
|
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["summary"]
|
||||||
assert data["source"] == "github.com/gitleaks"
|
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
|
@pytest.mark.asyncio
|
||||||
assert data["installable"] is False
|
async def test_non_catalog_tool_gets_no_card_and_a_shell_steer(tmp_path, monkeypatch):
|
||||||
assert data["version"] == "" and data["summary"] == ""
|
"""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
|
@pytest.mark.asyncio
|
||||||
|
|||||||
Reference in New Issue
Block a user