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:
Rohit C Prasad
2026-08-20 22:14:02 -07:00
parent 3fc782edd7
commit 1e564d56eb
4 changed files with 62 additions and 7 deletions
+17
View File
@@ -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
+14
View File
@@ -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?",
+7 -2
View File
@@ -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)
+24 -5
View File
@@ -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