diff --git a/coworker/engine.py b/coworker/engine.py index 1e393199..9b36e43d 100644 --- a/coworker/engine.py +++ b/coworker/engine.py @@ -976,6 +976,7 @@ class TurnEngine: "installable": info is not None, "version": (info or {}).get("version", ""), "summary": (info or {}).get("summary", ""), + "source": (info or {}).get("source", ""), }, ) self._audit(tool_call, stage="tool_requested", reason=reason) diff --git a/coworker/server/app.py b/coworker/server/app.py index 460a0d2c..375037a7 100644 --- a/coworker/server/app.py +++ b/coworker/server/app.py @@ -1717,6 +1717,7 @@ def create_app(manager: SessionManager) -> FastAPI: "version": (info or {}).get("version", ""), "summary": (info or {}).get("summary", ""), "url": (info or {}).get("url", ""), + "source": (info or {}).get("source", ""), }, tool_call_id=tool_call_id, ) diff --git a/coworker/tools/toolreq.py b/coworker/tools/toolreq.py index e611b838..a0ad4040 100644 --- a/coworker/tools/toolreq.py +++ b/coworker/tools/toolreq.py @@ -18,8 +18,11 @@ 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`). Say in `reason` what check it - unlocks, so the user can judge whether it's worth installing. + machine (e.g. `gitleaks`, `osv-scanner`, `semgrep`). + + 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) + and what happens if they decline — don't restate any of that in `reason`. Use this INSTEAD of quietly skipping a check. If the user declines, carry on with a fallback (e.g. reading git history yourself instead of running gitleaks) and state diff --git a/surfaces/gui/e2e/fixtures.ts b/surfaces/gui/e2e/fixtures.ts index f1da713e..0fd085d7 100644 --- a/surfaces/gui/e2e/fixtures.ts +++ b/surfaces/gui/e2e/fixtures.ts @@ -628,6 +628,7 @@ export async function mockApi(page: import("@playwright/test").Page) { installable: true, version: "8.30.1", summary: "scans git history and the working tree for committed secrets", + source: "github.com/gitleaks", }); return; // suspended on the tool request } diff --git a/surfaces/gui/e2e/toolreq.spec.ts b/surfaces/gui/e2e/toolreq.spec.ts index b3a879cf..5e420e5f 100644 --- a/surfaces/gui/e2e/toolreq.spec.ts +++ b/surfaces/gui/e2e/toolreq.spec.ts @@ -17,10 +17,14 @@ test("request_tool surfaces a card naming the tool, the reason and the pinned ve const card = page.locator(".dirreq-card"); await expect(card).toContainText("gitleaks"); await expect(card).toContainText("scan the git history for committed secrets"); - await expect(card).toContainText("8.30.1"); - await expect(card).toContainText(/checksum-verified/i); - // Declining must read as a normal choice, not a failure. - await expect(card.getByTestId("toolreq-skip")).toBeVisible(); + // The fact strip is the product's voice: version, publisher, checksum — kept apart from + // the coworker's quoted reason (mixing them is what made the card confusing, 2026-08-14). + const facts = card.locator(".toolreq-facts"); + await expect(facts).toContainText("8.30.1"); + await expect(facts).toContainText(/checksum-verified/i); + await expect(facts).toContainText("from github.com/gitleaks"); + // Declining must read as a normal choice that continues the run, not a failure. + await expect(card.getByTestId("toolreq-skip")).toHaveText("Continue without it"); }); test("an event without install metadata fails CLOSED — Install disabled, skip offered", async ({ diff --git a/surfaces/gui/src/App.tsx b/surfaces/gui/src/App.tsx index e9ae5662..dd70e707 100644 --- a/surfaces/gui/src/App.tsx +++ b/surfaces/gui/src/App.tsx @@ -741,6 +741,7 @@ export function App() { installable: d.installable === true, version: d.version || "", summary: d.summary || "", + source: d.source || "", }, ]); break; diff --git a/surfaces/gui/src/components/ToolRequestCard.tsx b/surfaces/gui/src/components/ToolRequestCard.tsx index 7a8e04d8..9847e59c 100644 --- a/surfaces/gui/src/components/ToolRequestCard.tsx +++ b/surfaces/gui/src/components/ToolRequestCard.tsx @@ -22,23 +22,30 @@ export function ToolRequestCard({ {item.reason &&
“{item.reason}”
} + {/* The fact strip is the PRODUCT speaking (registry metadata), styled apart from the + coworker's quoted ask above — mixing the two voices is what made the card confusing. */} {item.installable ? ( -
- {item.summary ? `${item.summary}. ` : ""} - Installs {item.tool} - {item.version ? ` ${item.version}` : ""} — a pinned build, checksum-verified before - it runs. +
+ + {item.tool} + {item.version ? ` ${item.version}` : ""} + + {item.summary && {item.summary}} + pinned & checksum-verified + {item.source && from {item.source}}
) : ( -
- No verified build is available for this machine — install it yourself if you want - this check, or skip and the coworker will note the gap. +
+ + No verified build is available for this machine — install it yourself if you want + this check, or continue and the coworker will note the gap. +
)}