Skip to content

Commit f77c19f

Browse files
mabry1985claude
andauthored
fix: the manifest understated the "full" tool surface by ten (v0.1.3) (#1)
`full` binds 38 tools, not ~28. v0.1.1 verified the real count against driver 0.8.3 and corrected driver.py; the manifest kept the original estimate in two places and nothing caught it: - protoagent.plugin.yaml:29 a yaml comment - protoagent.plugin.yaml:70 the tool_surface SETTINGS DESCRIPTION — the text an operator reads in the console *before* flipping to "full", i.e. the one surface where the number is load-bearing The ten unlisted tools are the dangerous end of the surface, so the description now names what "full" adds rather than only counting it: kill_app, bring_to_front, set_config, check_for_update (driver self-update, which ADR 0084 keeps operator-driven). Why it drifted: test_stated_tool_count_matches_the_code already pinned the "17 tools" claim in that same sentence — its docstring even says "numbers in prose drift silently — pin it to the list" — but the "full" half of the sentence was unpinned, so only the guarded half survived. - driver.py: VERIFIED_DRIVER_VERSION / VERIFIED_PUBLISHED_TOOLS promote the count from a comment to something a test can hold. - test_stated_full_surface_count_matches_the_code pins the rendered description, the way the core count is pinned. - test_manifest_states_no_approximate_tool_counts holds the RAW file: yaml comments never reach the parsed manifest, so the :29 copy was invisible to any fixture-based test. It bans `~<digits>` — the un-pinnable form is the actual tell. Both new guards verified RED against the pre-fix manifest before being kept; an earlier version of the second one passed against the bug (it required the word "tools" after the number, and the stale text read "~28)" / "~28 and") and was rewritten. No behavior change — tool_filter and CORE_TOOLS are untouched. Claude-Session: https://claude.ai/code/session_01Qjyu1YKjvmuga7sFtaxzUw Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 7d59498 commit f77c19f

4 files changed

Lines changed: 46 additions & 8 deletions

File tree

driver.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,17 @@
3131
#: sidecar, which does not inherit your shell's environment.
3232
_FALLBACK_DIRS = ("~/.local/bin", "/opt/homebrew/bin", "/usr/local/bin")
3333

34-
#: The documented snapshot->act loop (ADR 0084 D3). Driver 0.8.3 publishes **38**
35-
#: tools; binding all of them costs context on every turn for surface the agent
36-
#: rarely needs (ADR 0005). Deliberately excluded from the default, available via
34+
#: The driver build the tool-surface claims were verified against. The published
35+
#: count is version-dependent, and operator-facing copy quotes it, so it lives
36+
#: here as a constant the manifest tests can hold rather than as a number in prose
37+
#: that drifts — which it did: v0.1.1 corrected this file to 38 and left the
38+
#: manifest saying "~28", where it stayed until v0.1.3.
39+
VERIFIED_DRIVER_VERSION = "0.8.3"
40+
VERIFIED_PUBLISHED_TOOLS = 38
41+
42+
#: The documented snapshot->act loop (ADR 0084 D3). Binding all of the driver's
43+
#: published tools costs context on every turn for surface the agent rarely needs
44+
#: (ADR 0005). Deliberately excluded from the default, available via
3745
#: ``extra_tools``:
3846
#: bring_to_front — steals focus, which is the whole thing the driver avoids
3947
#: kill_app — destructive (kill -9)

protoagent.plugin.yaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
id: cua
22
name: Computer use (cua-driver)
3-
version: 0.1.2
3+
version: 0.1.3
44
description: >-
55
Let the agent drive native GUI apps on THIS machine — snapshot a window's
66
accessibility tree, then click/type/scroll by element, without bringing the app
@@ -26,7 +26,8 @@ config:
2626
# runs with a scrubbed PATH — notably the frozen desktop sidecar (ADR 0058).
2727
binary_path: ""
2828
# Tool surface. "core" binds the documented snapshot->act loop only; "full"
29-
# binds every tool the driver exposes (~28) and floods context (ADR 0005).
29+
# binds every tool the driver exposes (38 on driver 0.8.3) and floods context
30+
# (ADR 0005) — including the ones "core" excludes on purpose.
3031
tool_surface: core
3132
# Extra tool names to bind on top of "core" — e.g. ["kill_app", "bring_to_front"].
3233
# Ignored when tool_surface is "full". Verify names with the Test button.
@@ -67,8 +68,11 @@ settings:
6768
options: [core, full]
6869
group: "Computer use"
6970
description: >-
70-
“core” binds the documented loop (17 tools). “full” binds all ~28 and
71-
costs context on every turn.
71+
“core” binds the documented loop (17 tools). “full” binds every tool the
72+
installed driver publishes — 38 on driver 0.8.3 — costing context on every
73+
turn and adding the surface “core” leaves out on purpose: kill_app,
74+
bring_to_front, set_config and check_for_update (driver self-update).
75+
“Test connection” reports the real count for your installed driver.
7276
- key: extra_tools
7377
label: "Extra tools"
7478
type: string_list

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "cua-plugin"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
description = "protoAgent plugin: computer use — drive native GUI apps via the third-party cua-driver binary, wrapped as a managed MCP server."
55
requires-python = ">=3.11"
66

tests/test_manifest.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,32 @@ def test_stated_tool_count_matches_the_code(m):
109109
assert int(claimed.group(1)) == len(driver.CORE_TOOLS)
110110

111111

112+
def test_stated_full_surface_count_matches_the_code(m):
113+
"""The `full` count is the blast-radius number an operator reads *before*
114+
flipping the switch, and nothing pinned it — so it sat at "~28" while the code
115+
said 38, from v0.1.1 to v0.1.3. The test above guarded the `core` number in the
116+
same sentence. Guard both, or the unguarded half drifts again."""
117+
import re
118+
119+
field = next(s for s in m["settings"] if s["key"] == "tool_surface")
120+
claimed = re.search(r"(\d+) on driver", field["description"])
121+
assert claimed, "the tool_surface description should state how many tools 'full' binds"
122+
assert int(claimed.group(1)) == driver.VERIFIED_PUBLISHED_TOOLS
123+
assert driver.VERIFIED_DRIVER_VERSION in field["description"]
124+
125+
126+
def test_manifest_states_no_approximate_tool_counts():
127+
"""Comments aren't parsed into `m`, so the yaml-level copy drifted unseen too —
128+
and the approximate form is the tell: "~28" is not a checkable claim, so nothing
129+
checked it, so it outlived the code by two releases. The real counts are pinned
130+
by the two tests above; ban the un-pinnable form outright. (`~/` paths are fine —
131+
this matches `~` followed by a digit.)"""
132+
import re
133+
134+
approx = re.findall(r"~\d+", MANIFEST.read_text())
135+
assert not approx, f"approximate count(s) in the manifest — state the real number: {approx}"
136+
137+
112138
def test_declares_no_secrets(m):
113139
"""Nothing to leak — the driver is a local binary, not an API."""
114140
assert not m.get("secrets")

0 commit comments

Comments
 (0)