# Live end-to-end test of clawdcursor 0.9.2

**Date:** 2026-05-16
**Tester:** Claude (Opus 4.7, 1M context), driving clawdcursor over MCP stdio from Claude Code.
**Environment:** Windows 11 Pro, Node 20, clawdcursor@0.9.2 installed via `npm link` from a fresh clone.
**Method:** Compact MCP surface (6 compound tools), plus a parallel HTTP MCP daemon (`clawdcursor agent --no-llm`) on `127.0.0.1:3847`.

This is exactly the consumer clawdcursor was built for: an LLM in an editor host (Claude Code, Cursor, Windsurf, Zed) calling the tools, reasoning about the results, and chaining the next call. It's the "is this tool actually usable as advertised?" test, run by an actual agent.

The short answer: **the engine is in good shape**. Every tool I called returned correct structured data. The full a11y → type → verify loop works on Win11 Notepad without escalating to vision. The safety layer caught a destructive keystroke. The HTTP MCP transport, bearer auth, and graceful shutdown all behaved.

Below: what I tested, what worked, what I found.

## Tool surface coverage

I exercised the following sub-actions across the compact compounds:

| Compound | Sub-actions tested | Result |
|---|---|---|
| `system` | `system_time`, `clipboard_write`, `clipboard_read`, `classify_task`, `detect_app`, `shortcuts_list`, `ocr`, `app_guide` | ✓ all returned structured data |
| `window` | `list`, `active`, `open_app`, `focus`, `screen_size`, `list_displays` | ✓ all worked |
| `accessibility` | `read_tree`, `focused`, `invoke`, `get_value` | ✓ mostly worked (see `get_value` finding below) |
| `computer` | `screenshot`, `type`, `key`, `middle_click`, `wait` | ✓ all worked (one safety block as designed — see Ctrl+W finding) |
| `browser` (CDP) | `connect` | ✓ attached to a live Edge instance |
| `task` (compact) | `task({instruction:…})` | ✗ correctly errored without daemon, redirected user to start one |
| **HTTP MCP daemon** | `tools/list`, `tools/call` for `get_system_time`, `submit_task`, `agent_status`, `/stop` | ✓ transport works, bearer auth enforced, graceful stop |

## End-to-end loop that worked

The canonical "drive a GUI" loop in <30 seconds:

1. `window.open_app("notepad")` → focused the active Notepad.
2. `accessibility.read_tree(pid)` → 51 well-formed elements: tabs, menu items, format toolbar, document body, status bar, window chrome. Depth-first, indented, easy for an LLM to consume.
3. `accessibility.invoke({name:"Add New Tab"})` → new tab created without me supplying coords or controlType.
4. `computer.type(...)` → 225 chars typed into the new tab; tool acknowledged the target window name.
5. `computer.key({combo:"Return"})` → newline registered.
6. `computer.type(...)` → 38 more chars appended.
7. `accessibility.read_tree(pid)` → confirmed 264-character total, line 4 column 39, Windows CRLF, UTF-8. **Numbers matched expectations exactly** (225 + 1 newline + 38 = 264).

Total tool calls: 7. Total time: a few seconds. No vision tier needed — the a11y tree was rich enough for the entire write+verify loop.

## Findings (graded)

### CRITICAL — already fixed in 0.9.3 (this commit)

1. **`navigate_browser` PowerShell shell injection** — `src/tools/orchestration.ts:246` interpolated a URL into `Start-Process -ArgumentList @(…, "${url}")`. A URL with `")` or `$()` or backticks could escape the quoting. **Fixed**: direct `execFile()` against `msedge.exe` from standard install locations; no shell shim.
2. **`screenshot_full` MIME mismatch** — declared `image/png`, returned JPEG bytes. Any client decoding the bytes by the advertised type would corrupt. **Fixed**: `mimeType` now follows the actual `frame.format` field.
3. **`learn_app` silent no-op** — returned `{saved: true}` even when neither save branch executed (e.g., when only `processName` was passed). **Fixed**: tracks `wroteLesson`/`wroteGuide` flags; returns `{saved: false, reason: ...}` with `isError: true` when nothing was persisted.
4. **Windows window-title UTF-8 corruption** — non-ASCII characters in window titles arrived as `�` (UTF-8 decode replacement) or `?`. Confirmed live: every `window.list` and `window.active` call I made returned mangled Edge/WindowsTerminal titles. Root cause: `scripts/ps-bridge.ps1` (and `ocr-recognize.ps1`) don't set `[Console]::OutputEncoding = UTF8`, so PowerShell wrote in the system code page while Node decoded as UTF-8. **Fixed**: encoding headers added to both scripts. Will be visible after the MCP child restarts.

### IMPORTANT — fixed or partially fixed in 0.9.3

5. **`computer.scroll_horizontal` enum was wrong on the compact surface** — `mouse_scroll`'s `direction: ['up','down']` was declared first and won the field-merge race; `mouse_scroll_horizontal`'s `['left','right']` was silently dropped, so an LLM calling `computer({action:'scroll_horizontal', direction:'left'})` violated the published schema. **Fixed**: `buildCompoundSchema` in `compact.ts` now unions enum values across delegates instead of first-wins.
6. **`task` and `delegate_to_agent` description omits the daemon requirement** — both error with `ECONNREFUSED` (or "no agent") when called from a stdio MCP host (which has no daemon). The description didn't say so. **Fixed**: both descriptions now lead with `**Requires the \`clawdcursor agent\` daemon to be running**`.
7. **`submit_task` no-LLM error message is misleading** — when called against the daemon HTTP MCP without an LLM configured, it returns: *"no autonomous agent is attached to this MCP context. … Stdio MCP clients cannot use this — use the granular tools."* The mention of stdio is wrong here (the caller IS on the daemon); the real fix is to configure an LLM. **Not yet fixed** — flagged for 0.9.4. Conflates two failure modes.

### NICE-TO-HAVE — found during live test, not yet fixed

8. **`accessibility.get_value({name:"Text editor"})` returned empty string**, but `accessibility.focused()` against the same focused Document returned the full text. The UIA Value pattern IS exposed, just not via `get_value` by name. Either `get_value` should consult focused-element fall-through, or the docs should steer agents to `focused()` for read-back on Document/RichEdit controls.
9. **`type_text` lost em-dashes** — I typed `—` (U+2014) and got `-` in the readback. Suggests the typing path falls back to per-key on non-keyboard chars. Either preserve via paste-as-type always, or document that non-ASCII may be approximated.
10. **`system.ocr` returns ~58 KB on a 1280×720 screenshot.** 336 word-level elements with bounds is technically rich, but it's a lot of context for the consuming LLM. Worth a `compact: true` mode that returns line-level text only, or a default `maxElements` cap.
11. **`system.app_guide({app:"notepad"})` surfaced stale auto-learned entries** from v0.7.5-era sessions (`type_clawdcursor_v075_shipped_with_adapt`, `save_the_file_as_adaptivetesttxt_on_the_`). The learned-lesson store has no age-out or cap. Over time, the prompt fragment will bloat with experiments. Suggest LRU + a hard cap (~10 lessons per app).
12. **`system.detect_app({activeWindowProcessName:"claude", activeWindowTitle:"Claude"})` returned `{appKey: null}`** — Claude Desktop isn't in the TITLE_FALLBACKS table. Easy gap fix.
13. **`shortcuts_list({category:"editing"})` returned 5 entries with empty `key` on Windows** (copy-style, paste-style, show-fonts, spelling, definition — all macOS-specific). Either filter platform-irrelevant entries by default, or expose a `platform: 'all' | 'current'` arg.
14. **Notepad close requires hovering to reveal the close button**, and the safety layer correctly blocks `Ctrl+W`. There's no smooth way for an agent to close a single tab in the new Win11 Notepad without either bypassing safety or middle-clicking the tab (which is what I ended up doing, after consulting the post-hover tree). Worth a `window({action:'close_tab', processId, tabName})` convenience that wraps the middle-click pattern.

### What was already great

- **Safety layer worked exactly as advertised.** Ctrl+W was blocked with a clear message naming the tier ("Confirm-tier safety"). I had to find a legitimate path (middle-click → invoke "Don't save" button) — exactly the pattern a careful agent should follow.
- **Bearer auth on `/mcp` is correct.** Anonymous POST returned 401 cleanly. The token file at `~/.clawdcursor/token` is mode 0600, written once at daemon start.
- **`/stop` worked first try.** Bearer-authed, localhost-only, returned `{"stopped":true}`, and the next `GET /health` correctly returned connection-refused.
- **`window.screen_size` and `list_displays` carry DPI metadata** (`screenshotScaleFactor`, `mouseScaleFactor`, `dpiRatio`) so an agent that uses raw pixel coordinates from the screenshot doesn't have to compute scale itself.
- **`accessibility.invoke({name:"Add New Tab"})` worked without me supplying `processId` or `controlType`** — it correctly resolved against the foreground window.
- **`browser.connect()` succeeded silently** — Edge was already running with a debug port. The tool didn't try to relaunch and clobber the user's session. Correct behavior.
- **`app_guide({app:"notepad"})` is genuinely useful.** It returns shortcuts table, workflows, layout description, tips, AND a `promptFragment` ready to inject as agent context. This is the kind of thing that makes the agent's first interaction with an app non-magical-but-also-not-trial-and-error.

## What the daemon test couldn't cover

The full `task({instruction: ...})` pipeline requires an LLM configured via `clawdcursor doctor`. No LLM key was available in the test environment, so I only exercised the HTTP MCP transport (`/mcp`, `/health`, `/stop`, bearer auth, `tools/list`, `tools/call`) and the `submit_task` failure path. The pipeline's actual decomposition + routing + ladder behavior was not exercised end-to-end against a live LLM. Worth a follow-up test once a key is available (Ollama would be the cleanest fit since it stays local).

## Net assessment

clawdcursor 0.9.2 is **functionally solid**. The bugs found are mostly polish-tier (description omissions, message clarity, missing app-detector entry) plus three real correctness fixes (the three CRITICAL items above) that land in 0.9.3 along with this report.

The product I tested matches the README's claims: any tool-calling LLM connects via MCP, picks up the compact 6-tool surface, drives any GUI through a11y-first ladder, falls back to OCR/screenshot when needed, with a safety layer that enforces tiered consent. Where the docs say "fallback only", the runtime backs that with a verifier and ladder that actually picks the cheap path first.

The credibility gap isn't engineering — it's presentation. The README doesn't have a demo, the security audit issue from February is unanswered, and the v0.9.0 release week shipped enough churn (REST removed, directory restructured, flag flip-flopped) that observers reasonably worried. The engine itself is in good shape and getting better.
