# Changelog All notable changes to Clawd Cursor will be documented in this file. ## [1.5.19] - 2026-10-19 — typing never pastes the user's own clipboard ### Security - **Typing never pastes the user's own clipboard.** `type` / `type_text` / `smart_type` type by writing the text to the clipboard and pressing Ctrl+V. On Windows the clipboard write is a fresh PowerShell with a 2 s budget whose failures were ignored, so on a busy machine Ctrl+V pasted whatever the user had copied — seen live: typing a URL + Enter into Edge's address bar submitted the user's clipboard as a web search, twice. Every paste path now reads the clipboard back first and pastes only when it holds the text; otherwise it types the text key by key and says so. ## [1.5.18] - 2026-10-19 — "Something else" on the pause prompt; copy_all_text without Ctrl+A; tool annotations ### Added - **"Something else" on the pause prompt.** When the agent pauses for you (blue glow), the prompt now has a third button between Resume and End task: type an answer instead of doing the step — "use the test account instead", a code, a change of plan — and press Enter. The agent continues from what you wrote. The prompt takes the keyboard only after your click on "Something else"; Esc goes back. Windows, macOS and Linux. Over MCP, `pause_for_user` returns `{status:"replied", reply}`; `POST /resume` accepts `{reply}`. - **MCP tool annotations** (title, read-only, destructive, idempotent, open-world hints) on every tool — the 7 compound tools explicitly, the granular ones derived from their safety tier. ### Changed - **`window` and `system` describe every action**: when to use them rather than the other tools, which parameters go with which action, what changes the system (`close` can lose unsaved work and needs confirmation, `relaunch_with_cdp` restarts the app, `clipboard_write` overwrites the clipboard, `report` sends nothing) and what comes back. `accessibility` documents its aliases and the shape of what `read_tree` / `find` return. - **Catalog size, measured:** the 7 compound tools are about 7,000 tokens of `tools/list` (schemas included) and the granular surface about 20,000. The README, site and skill said "~1,500 tokens, ~12× smaller". ### Fixed - **`copy_all_text` no longer highlights the page (Windows).** It read pages with Ctrl+A / Ctrl+C, so every page an agent read stayed selected. It now reads the document's text through UI Automation (TextPattern, what a screen reader reads): exact text, no keystrokes, nothing selected, the clipboard untouched. Select-all remains only as the fallback where no document can be read (and still never in an editor or a list, #249). ## [1.5.17] - 2026-10-18 — non-ASCII typing and pasting on macOS; copy_all_text never selects an editor ### Fixed - **macOS: text outside ASCII is typed as itself (#242).** System Events' `keystroke` types key code 0 ("a") for any character the keyboard layout has no key for, so "café 日本語" came out as "cafa aaa" — and every tool reported success. Typing that does not paste (the `type` fallback, `set_field_value`'s keyboard fallback, find-and-replace, the Spotlight launch, `key_press "é"`) now sends such text through the accessibility helper as Unicode key events. Without the helper it is refused with an explanation instead of typed as "a"s; a non-ASCII key with modifiers (`cmd+é`, which pressed ⌘A) is refused. ASCII typing is unchanged. - **macOS: pasted text outside ASCII no longer comes out garbled.** `pbcopy` / `pbpaste` encode by the locale, and clawdcursor started by an app (the Claude desktop app) has none, so they used MacRoman: `type`, `type_text` and `smart_type` pasted "café 日本語" as "caf√© Êó•Êú¨Ë™û" while reporting success, and restoring the user's clipboard turned CJK into "?". Both now run with a UTF-8 locale. - **`copy_all_text` no longer selects an editor's content or a list (#249).** It pressed Ctrl+A on whatever had focus and left it selected: in an editor (Notepad, Word, VS Code, a form field) the agent's next keystroke replaced the whole document; in a mail list or file view every item was selected. It now presses select-all only on page content: with focus in an editable field, an editor document, a list/tree/table, or an Electron app that won't say what has focus, it returns the window's accessibility text instead. After a select-all copy the result says the content is selected. Verified live in Edge (caret in a text box: untouched; page: copied exactly). ## [1.5.16] - 2026-10-16 — pause for the user at a roadblock; blind mode on every OS ### Changed - **OCR is warm, on every OS.** It spent ~95% of its time starting a process, not recognizing text. Measured on a 3840×2400 screen: - Windows: the recognizer now stays loaded in the PowerShell bridge (loaded ahead of the first read). Full screen 1.2–1.8 s → ~0.54 s; one window ~0.8 s → 55–85 ms. Same results as before, word for word; the one-shot script remains the fallback if the bridge is down. - macOS: the Vision script is compiled once into a cached binary (`~/.clawdcursor/bin`) instead of being re-interpreted on every read; the interpreted script is the fallback while it compiles or if `swiftc` is missing. - Linux: unchanged (tesseract is already a process per read); use `scope:"window"` below to read less. - **`getActiveWindow` is ~10× faster on Windows** (200–400 ms → ~20 ms): the bridge reports the focused window's own bounds instead of the adapter enumerating every window to look it up. It is also now the window that has focus, not the first window of the same process, and unnamed windows get real bounds. It runs before nearly every click and key press. ### Added - **Pause for the user at a roadblock, then continue.** A sign-in, a 2FA code, a CAPTCHA, or a step the agent may not take no longer ends the task with the agent confused. The run pauses, keeping its history: the glow turns blue and the pill says what to do, with **Resume / End task**. After Resume the agent re-reads the screen and carries on from where it is. - Built-in agent: new `ask_user` tool. `give_up` and the repeat guard now pause instead of failing. Paused time doesn't count toward the 12-minute task limit. No answer in 15 min (`CLAWD_PAUSE_TIMEOUT_MIN`) ends the task. Confirm-tier actions (Send, Delete…) are still refused as before; if that blocks the task, the agent pauses so the user can do that step. - Outside agents: `system pause_for_user` (bounded wait; re-call to keep waiting). A delegated `task` reports `{status:"paused", waitingFor}`, and `task {action:"resume"}` resumes it. - Also answerable with `clawdcursor resume [--end]`, `POST /resume`, or the dashboard, whose pause banner now has working Resume / End task buttons. - `CLAWD_NO_PAUSE=1` restores the old fail-fast behaviour (CI, unattended runs). Nothing reads the screen while paused. - Only the person at the machine confirms a pause. An AI calling `task {action:"resume"}` resumes it as unverified, and the agent is told the user did not confirm it on screen. The pill shows the message as "The agent says: …", so injected page text can't pass for clawdcursor's own instructions. - **`clawdcursor versions`, and the same list in `clawdcursor doctor`:** every copy of clawdcursor on the machine with its version: the global install, Claude Desktop extensions (including the Microsoft Store build's folder), the Claude Code plugin's pinned version and npx's cache. Old copies are flagged with the fix for each. The website installer only updates the global copy, so an old Desktop extension used to go unnoticed. - **The installers say what they did.** "Updated 1.5.12 → 1.5.15" (or "installed", or "already the latest"), a reminder to restart editor and Claude sessions, and the list of other copies. On Windows they warn when open sessions are using the global copy (the cause of `EBUSY` failures) and explain that error if it happens. - **The on-screen overlay (glow + pill) now works on macOS and Linux**, not only Windows, and the glow covers every monitor, not just the primary one. - macOS: `scripts/mac/overlay.swift`, compiled once into `~/.clawdcursor/bin` (needs the Xcode Command Line Tools, like the OCR helper). - Linux: `scripts/linux/overlay.py` (GTK 3 via python3-gi, already needed for the accessibility bridge). X11, or Wayland through XWayland. With a compositor the glow fades in from the edges; without one it is a thin solid border. Clicks pass through the glow on every OS. - Where the overlay can't run, nothing is drawn and pauses fall back to the old fail-fast behaviour. - **`system ocr scope:"window"`** reads only the focused window: ~80% fewer words (39 vs 486 on the test screen), faster, and it does not read other apps' text. The default stays the whole screen — the focused window can be the agent's own host app. - **Thin accessibility trees are no longer mistaken for empty ones.** Chromium / Electron apps build their page tree only after a client asks for it, and it arrives ~1.5–2.5 s later: a plain Electron window read 6 elements and none of the page at first, then 14 with all of it. `read_tree` and `compile_ui` now wake the app (macOS: sets the per-app `AXManualAccessibility` switch) and keep reading for up to 3 s until the tree fills in, noting when they did. One wait per app per 5 minutes (also when the app cannot be identified), so a genuinely sparse app (canvas, game) is not slowed down again. `CLAWDCURSOR_WAKE_BUDGET_MS=0` turns the wait off. - The thin-tree guidance now says that Electron/Chromium apps launched with `--force-renderer-accessibility` expose the whole page — required on Linux, where nothing enabled after launch (including AT-SPI's own switch) exposes a Chromium page. ### Fixed Blind mode (accessibility first, no screenshots), from live tests on a Windows 11 VM and an Ubuntu 24.04 VM: - **Windows: classic controls were reported as blank "Pane"s.** Windows' UIA client loads the Edit/Button/ComboBox type providers once, on first need, and that load failed when the bridge's first UIA call came from script code — almost every start. Text boxes, buttons and combo boxes then had no type, no value and no Invoke, so `set_value`, `invoke` and `smart_type` failed on them. The bridge now registers the providers at startup. - **Windows: the open dialog is what gets read.** `compile_ui` / `read_tree` read the app's main window even while a Save As or error box was in front; they now read the foreground dialog of that app. `window list` includes owned dialogs, and `window focus` finds them. - **Windows: no more 10 s stall** when invoking a button that opens a modal box (Save → "replace?"): the press runs on a worker and the call returns after ~1.2 s saying a dialog probably opened (Save-As flow: 10.5 s → 2.2 s). - **Windows: the right field by name.** "File name" no longer matches a column header; value reads and writes prefer the edit over its label; the file-name box is filled by typing so the dialog actually takes the new name; control type filters accept any spelling (`MenuItem`, `menu item`…). - **`smart_click "Save"` clicks the Save button,** not the "Save as" title: a standalone word beats the same word inside a longer phrase. - **Content below the fold is readable blind.** `read_tree` lists scrolled-out elements after the visible ones (marked offscreen) and pages with `offset`/`limit`; `compile_ui` shows them in an OFFSCREEN section; `list_children` works when the parent has no on-screen bounds. Settings → About now reads "Windows 11 Pro, 25H2" without guessing names. - **`copy_all_text` in Settings-style (UWP/XAML) windows:** 13.4 s and nothing → 1.1 s, falling back to the window's accessibility text (and saying so). - **No false success from OCR clicks on running text.** When the only match is a word inside a sentence and nothing changes after the click, `smart_click` reports "not confirmed — that is text, not a control". - **`state` reports checked / unchecked** for checkboxes and toggles. - **`set_value` types into fields that have no settable value, on every OS** (Chrome text fields on Linux, Win11 XAML composites), and only after the field was focused or located. It never types into whatever has focus. - **`find_field` with a multi-word purpose** ("file name") prefers the whole phrase ("File name:") over a field matching one word of it (the file list's "Name" column). Trailing colons on labels no longer cost the exact match. - **`smart_click` says why OCR didn't help** (engine unavailable, failed, read nothing, or read N words with no match), and a click on running text that changed nothing is reported as performed but unconfirmed, not as an error, so an agent doesn't click a toggle twice. - **Windows: an earlier still-running click that failed later** is reported on the next invoke instead of being lost. - **Windows: focusing a window no longer un-maximizes it.** Every focus (window focus, raising the target before a click) ran `ShowWindow(SW_RESTORE)`, which shrinks a maximized window to its normal size. A maximized window is now shown with `SW_SHOWMAXIMIZED`; normal and minimized windows behave as before. The Alt-key fallback for Windows' foreground lock also loads the library it needs, so it no longer fails silently. - **`get_value` reads the field, not a same-named label** ("City" = "City"). - **OCR clicks never hit another app on a partial match.** `smart_click "1"` in Calculator clicked the "14" of the menu-bar clock on a Mac and opened Notification Center. Short and numeric targets now need a whole-token match, and with a `processId` (or the agent's working window) a match outside that window is refused, not clicked with a warning. The window check also compares in the same coordinate space on Retina screens. - **Multi-word OCR targets survive one misread word** ("Make … Text"), and a clear miss fails fast instead of a 24 s `deadline_exceeded`. - **`smart_type` replaces a field's text by default** (`mode: "append"` to add); in the macOS Save panel it typed after the preselected base name ("blind-note.txt.txt"). - **`compile_ui` accepts `processId`** and reads that app's window; OCR coordinates from it, and from the agent loop's `smart_click` / `read_text`, are screen coordinates (points on macOS), not physical pixels. - **The overlay shows for what an action does.** `accessibility invoke` / `set_value` (a tier-0 compound) never lit it, while reads on `system` / `task` / `window` (tier-1 compounds) did, e.g. polling `task status`. - **HTTP `/mcp` works with standard MCP SDK clients.** Every request after the first failed with "Already connected to a transport"; each SDK session now gets its own server, and one-shot posts (dashboard, curl, CLI) still work. - **`clawdcursor task "…"` and the dashboard work against a `--compact` daemon** (they called granular tool names it doesn't serve). - **The session log records refused and consent-blocked calls,** and counts `batch` steps individually. - **`open_app Settings`: 10–17 s → ~5 s** (launched directly instead of after a failing launcher and a Start-menu search). - **No false "FOCUS NOT CONFIRMED" warning** for clicks on clawdcursor's own never-focused overlay. - **Linux: `invoke` by name picks what you can see.** It took the first match in tree order — a hidden 0×0 toolbar "Save" instead of the dialog's — and reported success. Visible matches in the active window now win; a match that only exists hidden is refused. - **Linux: text fields and the Calculator display read empty.** PyGObject's Text interface clashed with an old method name; text is now read through the interface explicitly. - **Linux: `focused` returned another app's element,** and a window without a process id made `read_tree` read a different app. Both are scoped to the foreground app / window now. - **Linux: `smart_read` no longer claims accessibility is unavailable,** and the tree lists visible elements first (closed menus and nameless rows last; GTK4 positions corrected). - **`open_app` says what really happened.** "Opened" with the window, or "Started … no window yet, do not launch it again" while a slow app loads, or an error when nothing started — never "Launched" with nothing on screen. Linux resolves app names through installed `.desktop` entries (calculator → gnome-calculator). - **`batch` accepts `args` and `arguments`** for each step, on both the MCP and the agent-loop batch. - **`accessibility find_field` works on the compact surface** (every OS). Its free-text `purpose` had inherited `compile_ui`'s `purpose` enum, so every call failed validation. A test now checks every compound action's params. - **Linux: window positions under openbox / framed WMs** come from the real client geometry (wmctrl counted the frame twice), so element refs in dialogs are no longer rejected as "off the active window". - **Linux: two windows with the same title** (e.g. xcalc and GNOME Calculator, "Calculator") can't be confused: a window without a pid is never scoped to another window's app. - **Linux: the agent's own Chrome is accessible** (`--force-renderer- accessibility` on its launch; the user's browser is untouched) and shows as `chrome`, not its rewritten command line. - **`open_app` recognises a new window without a pid** as the launched app (xcalc: 8 s and "no window" → 0.3 s, opened). - **OCR click targets: rows no longer merge** (tesseract's sparse mode put every word on "line 1", so a click could land between two rows) and a 2-letter fragment no longer matches a longer target. - **macOS Retina: clicks landed 2× off again (#154), in `agent` and `mcp` modes alike.** The NSScreen probe behind `getScreenSize()` used `size of frame`, which throws on macOS 26 (an NSRect bridges into AppleScript as a nested list, not a record). The error was swallowed, the logical width silently fell back to the physical width, `dpiRatio` became 1 and the mouse scale was physical/1280 (3.5 on a 4480×2520 panel) instead of logical/1280 (1.75). Measured on a 2× MacBook display: `image (681,43) → screen (2384,151)`, past the 2240-pt edge. The same bug made `list_displays` report `dpiRatio: 1`, and `NativeDesktop` had a twin on its normal (screenshot-helper) path: `connect()` returned before its DPI probe, so `getDpiRatio()` stayed 1 — `system ocr` / `ocr_read_screen` returned physical pixels labelled as screen coordinates (so `space:"screen"` clicks from OCR missed), `smart_click`'s OCR fallback clicked physical pixels, and `scope:"window"` OCR cropped half the window. One shared probe (`src/platform/mac-screen.ts`) now reads the frame by list index plus `backingScaleFactor`, and is used by the adapter, the helper path and the nut-js fallback alike. The v1.5.0 fix had only ever been exercised against pinned fixtures; `mac-screen.test.ts` runs the real script on a Mac and `scripts/verify-macos.mjs` checks the resulting geometry. - **macOS `window list_displays` never enumerated monitors.** Its AppleScript did not compile (`(s as any) = primaryScreen`, -2741), so it always fell back to a single display. `NSScreen.screens` index 0 is the primary; each screen now reports its own `backingScaleFactor`. - **The accessibility wake never fired on macOS.** `enable-accessibility.jxa` passed `$.kCFBooleanTrue`, which the JXA bridge exposes as an opaque `Ref`, so every `AXManualAccessibility` set returned `kAXErrorFailure` (-25200) — on Claude Desktop and on a cold Discord alike. Passing an `NSNumber` works: a cold Discord goes from 191 to 1,082 AX nodes within a second. Known limitation, tracked separately: the JXA / System Events walk behind `read_tree` still reads only a sliver of Chromium / Catalyst apps (Claude Desktop 15 of 1,119 AX nodes in 11 s; Discord 13 of 1,082; Freeform 0 of 549) and hits its 12 s timeout, so a woken tree is mostly invisible to it. - **The wake budget is wall-clock.** It counted only the pauses between re-reads, so on macOS, where that walk takes ~10 s per read, "up to 3 s" became 41 s. Slow reads now count against the budget (one re-read is always attempted). - **`scripts/verify-macos.mjs` reported false results.** The compiled-vs- interpreted OCR check compared raw JSON — Swift dictionaries serialize in a random key order per process, while the words and boxes were identical (the Swift script now emits sorted keys as well); the Screen Recording probe called `$.CGPreflightScreenCaptureAccess`, which JXA does not bridge, so it threw and reported "not granted" on a Mac that has the permission, skipping the live-OCR check; and the Electron-wake check passed on a set that had failed. It now asks the native `permission-check` helper, counts AX nodes through the AX C API before and after the wake (`scripts/mac/ax-node-count.swift`) so the wake is judged independently of the tree walk, and checks the Retina geometry and `listDisplays()`. macOS reads that failed and said nothing, from a blind test on a real Mac (macOS 26.5, M1 iMac, no screenshots): - **`window list` said "Windows (0)" with 15 windows open, and `read_tree` said "empty a11y tree — app may be custom-canvas" for TextEdit, Calculator and Safari.** Both were timeouts turned into empty answers: the osascript behind the window list takes 9–11 s against an 8 s limit, a tree walk 12–25 s against 12 s, and the adapter swallowed the error. Reads now go through a small compiled Swift helper on the accessibility API (`scripts/mac/ax-helper.swift`, built once into `~/.clawdcursor/bin` in about 3 s, only where the Command Line Tools exist; the first call waits for the build, and a Mac without them is never shown the "install developer tools" dialog). Same calls, same Mac, minutes apart: | call | before | after | |---|---|---| | `window list` | 9.0–11.2 s, "Windows (0)" | 0.37 s, 22 windows | | `read_tree` TextEdit | 24.9 s, "empty tree" | 0.07 s, 11 elements | | `read_tree` a Safari page | 24.8 s, "empty tree" | 0.15 s, 403 elements | | `find` buttons in TextEdit | 9.2 s, nothing | 0.1 s | | `window active` | 1.0 s | 0.05 s | | `read_tree` Calculator | 24.8 s, "empty tree" | 0.15 s, 35 named elements | A timeout, a hung app or a locked screen is now an error that says so, never an empty answer; an app that stalls shows as "(not responding)" and costs only its own 1.5 s. WebKit and Electron helper processes (`Safari Web Content`, …) are not apps and are not listed. Without the helper (no `swiftc`, or `CLAWD_NO_MAC_AX=1`) the old osascript scripts still work, now with limits that fit their real speed (window list 11.8 s → 17 windows, not zero). - **`window active` and `smart_click` used the wrong window.** The active window was the biggest window of the frontmost app ("Untitled 4") while you typed in the small main one ("Untitled 7"), and `smart_click`'s ctx.a11y path took the first window of the frontmost *process* — hence the false "matched outside focused window" warning on menu-bar items. It is now the app's focused (else main) window, in the adapter, in the legacy `AccessibilityBridge` and in the osascript fallback. - **SwiftUI controls with only an `AXIdentifier` could not be found or invoked.** The identifier is now read as `automationId` (and names a control that has no label), so `invoke {automationId:"One"}` and `find {automationId}` work on the macOS 26 Calculator: `find` 73 ms, `invoke` 246 ms (they used to miss). `find_element` passes `automationId` to the adapter instead of filtering a capped result afterwards. `invoke` with nothing to aim at (no name, controlType or automationId) is refused; `invoke-element.jxa` pressed the best-scoring button in the window. - **`open_app` said "Could not confirm … no window appeared" for apps that had launched** (3 of 3 in the blind test, ~26 s each), then typed the app's name into Spotlight. The launch is now confirmed from the process (NSWorkspace) and its windows instead of a diff of the slow window list: a cold Calculator is `Opened "Calculator" (pid=…, window="Calculator")` in 0.84 s; an app that is up but has no window yet is reported as still loading. Spotlight is only used when the app never appeared. - **A system dialog or the lock screen owned the keyboard while every tool said OK.** System Events keeps naming the app behind a permission prompt, an auth sheet or the lock screen as frontmost; NSWorkspace knows better. `type` and `key` now refuse while one of those owns the keyboard (a stray Return could press "Allow"), naming the dialog and how to answer it (read its buttons with `processId`), and `window active` shows it. Spotlight, the Dock, Control Center and clawdcursor's own overlay are not treated as dialogs. `CLAWD_NO_MAC_AX=1` switches the guard off together with the helper. - `scripts/verify-macos.mjs` now compiles and times the helper, compares its window list with the old script's (nothing missing, same geometry) and reads a live tree; `mac-ax-live.test.ts` drives a throwaway AppKit app (identifier-only buttons, a secure field, two windows, a frozen process) with `CLAWD_LIVE_MAC_TESTS=1`. - Hardening from an independent review of the helper, each reproduced on the Mac first: every accessibility call is bounded by one timeout (the per-app setting never reached a window's or a control's own calls); a button whose action opens a modal panel is reported as *pending*, not as a failed click, so callers stop clicking it again; a value that starts with `--` is a value (`set-value "--- notes"` wrote an empty string and said success); `find` / `invoke` match the label the tree shows (text that is a child of the control) and refuse to act on a partial match like "Save As…" for "Save" when part of the window did not answer; a subtree that did not answer is reported as PARTIAL instead of vanishing, an unreadable window is an error, and a hung app is a timeout, not "no elements found". One transient failure of the helper no longer pushes the whole session onto the slow osascript path (retried after a minute); a launcher's panel (Raycast, Alfred) is typed into, not mistaken for a system dialog. On the osascript path, secure-field values are no longer returned by `find` / `invoke get-value` / the focused element, the focused window (not the main one) is preferred like the helper does, and a locked screen is reported instead of System Events' empty answers. `read_screen` prints "(window list failed: …)" instead of "(no windows found)" when the list failed. - **Focusing a window on macOS never changes its size, zoom or full-screen state** (Windows' focus un-maximized windows; this was checked, pinned by a test, and confirmed on a real zoomed window), **and now brings a minimized window back** — clearing "minimized" returns it at the size it had. With no title given, focus prefers a window that is on screen over one you put away. - **A Mac without the Xcode Command Line Tools** (no `swiftc`, so no fast helper) keeps working through the osascript path, and says how to get the fast one: its timeouts and errors end with "run `xcode-select --install`", an identifier-only `find` reports that it cannot search by identifier there instead of "no elements found", and `clawdcursor doctor` shows the helper's state (built / builds on first use / needs the Command Line Tools / switched off). - **Upgrade walls are a roadblock (#239).** A click on `Upgrade`, `Subscribe`, `Start (a / your) (free) trial`, `Billing`, a payment method or "Add credit card" now needs confirmation, like Pay / Checkout. Page text such as "Subscriber count", "Unsubscribe", "Upgrade guide" or Trello's "Add a card" stays ungated, and typing the words is never gated. The agent prompt, the MCP instructions, `pause_for_user` and the skill say it: at a plan limit or upgrade wall, stop, quote the blocking text and ask the user; never click Upgrade / Subscribe yourself. - **No "Sign in to Chrome?" bubble in the agent's browser (#236).** A fresh agent profile could open with a focused sign-in bubble that swallowed keystrokes, one click away from signing the automation profile in to your Google account. Before launching its own browser, clawdcursor now writes `signin.allowed` and `signin.allowed_on_next_startup` = false into `~/.clawdcursor/cdp-profile/Default/Preferences` (other prefs are kept; the second key is the one Chrome reads at startup). Checked on Chrome 155 and Edge: both report sign-in disallowed after launch. Your own browser is never touched. - **macOS: the page of clawdcursor's own Chrome is visible to accessibility (#234).** `read_tree` / `find` saw only the window's toolbar and tab strip: Chromium on macOS builds the page tree only when asked, and the wake never fired because a browser's own UI (~59 elements) is not a "thin" tree. The agent browser now starts with `--force-renderer-accessibility` on macOS as on Linux (Windows stays on demand: Chromium turns UIA on when a client asks). The wake also treats a browser whose tree has no web area, or an empty one, as asleep, so other Chromium browsers wake too. Browser detection now recognizes macOS app names ("Google Chrome", "Microsoft Edge", "Brave Browser"). - **The blind hint points at the browser tools for clawdcursor's browser (#235).** An empty `accessibility find` / `read_tree` on the browser clawdcursor is attached to over CDP used to suggest OCR and "restart it with --force-renderer-accessibility". It now leads with `browser page_context` / `read_text` / `click`, and for clawdcursor's own browser says not to restart it. The CDP DOM fallback of `find` honours `processId`: scoped to the CDP browser's process it answers even when another window is in front, and it never answers for a different process. Unscoped queries keep the focused-window title check, and now also work for macOS browser names. - **The pause prompt makes a sound, and its state is readable (#237).** A user not looking at the screen never knew it was their turn. When the paused pill appears it now plays a short system sound: Windows' Asterisk sound, macOS's "Glass" (a beep if it's missing), and on Linux `canberra-gtk-play -i dialog-information` or the display's beep. `CLAWD_PAUSE_SOUND=0` turns it off. `task {action:"status"}` / `agent_status` documents its `pause` field and adds `overlay`: `{mode:"hidden"|"running"}` or `{mode:"paused", message, since}`, so a watcher no longer has to tell the two pill processes apart. The paused pill also carries `--paused` in its command line. - Fixes from a second independent review of the helper, each reproduced on the Mac first (and the tests failing against the old helper): - **A slow Mac no longer makes healthy apps look hung.** When the window list's 4 s budget ran out, the apps it had not reached were reported as bounds-0 "(not responding)" windows and their real windows vanished with no sign the list was cut short. They are now named as not read: `window list` and `read_screen` say the list is PARTIAL and which apps are missing, and a list that read nothing is a timeout error. A real accessibility timeout still shows "(not responding)". - **A click on an app that froze while the click ran is a timeout**, not "pending" ("… stopped responding while the click was running — it may or may not have been performed", and to look at the screen before retrying). "Pending" is kept for a button that opens a modal panel, which keeps answering. - **Typing into a launcher or password-manager popover works again** (Raycast, Alfred, 1Password Quick Access are modal dialogs of an accessory app). Only permission, Gatekeeper and authentication prompts — a known system bundle, or the system-dialog role — still refuse `type` / `key`; a modal dialog of any other app is typed into with a warning in the log. Escape is always sent: it cancels, it never answers "Allow". `open_app`'s Spotlight fallback still leaves a system dialog alone, Escape included. - **`find {automationId}` is exact.** "savebutton" used to find "saveButton" and then print nothing, because `find_element` filters the reply with `===`. - **`invoke` by controlType alone no longer presses the first Button.** Click, toggle, select, expand and collapse need a name or automationId; `set-value` / `focus` / `get-value` by controlType alone act only when exactly one element has that role, and otherwise list the candidates. - clawdcursor's own overlay no longer appears in window lists. - Typed text, names and identifiers go to the helper on stdin instead of argv, where any process could read them with `ps` while the helper ran. - `verify-macos.mjs`: on a Mac where the old osascript window list fails or takes over 90 s, the helper's check no longer fails for lack of anything to compare with; speed alone decides. - A review of those fixes found more, again reproduced first: - **`open_app`'s Spotlight fallback typed into a modal dialog of another app.** Once that case became a warning instead of a refusal, the fallback's own keystrokes (Escape, Cmd+Space, the app name, Return — which presses the dialog's default button) went into it. The launch fallback now refuses on any dialog that has the keyboard and says whose it is; only an agent that asks to type there does. - **A refusal is no longer routed around.** The osascript fallback now refuses an aim with no name too (it cannot tell that a role is unique, so `set-value` wrote into the first field); the helper marks its refusals (`refused`), and `set_field_value` — both the MCP tool and the agent's — stops there instead of clicking the widest field and typing into it. - **The reason reaches the agent.** The agent loop shows `data.error`; the Mac adapter returned it top-level only, so "matches 2 elements: …", "secure text field" and the rest all read "element not found or not actionable". - **"The action may already have happened" is said only when it may have.** A timeout while still searching, or checking that a role is unique, pressed nothing. - `focus_window` by process name works for an app the window list did not reach (the old "(not responding)" placeholder made it work by accident). - A lone surrogate in typed text no longer makes the helper reject the whole request; a name or identifier of only spaces is no name; identifiers are compared byte for byte (NFC and NFD forms of an accented id are different ids, as in the caller's own filter); the older `smart_*` path can set a field to "". - **Typing on macOS: one keyboard guard for every path, and the typed text off `ps`.** - `smart_type` and the older agent pipeline typed through a second macOS keyboard (`NativeDesktop`) that never asked who owned the keyboard, so with a permission prompt up a Return could still press "Allow" while the tool reported success. Both keyboards now ask the same guard (`src/platform/mac-keyboard.ts`); checked through the built MCP server against a dialog with the system-dialog role: `type`, `key Tab` and `smart_type` are refused, a bare `Escape` goes through. - The text being typed no longer appears in `osascript`'s arguments, where any process on the Mac could read it with `ps` for as long as the call ran: the AppleScript program is sent on stdin (typed text, single characters, held keys). - When a modal dialog of another app has the keyboard (a launcher, a password manager, an app's own "Delete everything?" box) the keys are still sent, as before, but the tool result now says whose dialog got them instead of only logging it. - A refused paste (`type`, `type_text`, `smart_type`) puts the user's clipboard back; the text that was to be typed — a password, say — used to stay on it. ## [1.5.15] - 2026-10-08 — multiple monitors in any layout; clicks hit the window you see ### Added - **Multiple monitors, any layout.** clawdcursor no longer assumes one screen. It reads the real layout from the OS — every monitor's position (left of / above the primary means negative coordinates), size and its own scaling — and adapts: - Windows: the pointer reaches every monitor. nut-js can only address the primary display, so clicks, moves, drags and scrolls are placed through the PowerShell bridge under per-monitor DPI awareness, in physical desktop pixels (the same space accessibility reports). - `computer screenshot` shows the monitor clawdcursor last worked on (a click, a focused or launched window — however it was named), else the primary; `display:N` picks one. On a multi-monitor desktop the reply names the other monitors. Coordinates read off a screenshot of any monitor map back onto that monitor. - `window list_displays` reports each monitor's physical bounds and scale. - Accessibility positions are exact on every monitor, including one whose scaling differs from the primary's (a 225% laptop beside a 100% monitor reported an 820×620 window as 364×276, so `space:"screen"` clicks missed). - OCR reads the monitor clawdcursor is working on (it always read the primary). `ocr_read_screen` returns screen coordinates on every OS (logical points on macOS) and says to click them with `space:"screen"` instead of dividing by a scale factor. - Linux: `display:0` crops to that monitor when it is not the primary; macOS: `display:N` for a non-main display says it is not supported yet instead of labelling the main display's image as display N. - `focus` treats a window as on-screen when it overlaps any monitor (it used to minimize / snap windows on a monitor left of or above the primary). Linux X11 already spans all monitors (one root window) and gains `display:N`; on macOS the pointer already spans displays, screenshots stay on the main display for now. - **Session log: see what an agent actually did.** Each MCP session writes a small local log (`~/.clawdcursor/sessions/`, last 50 kept): tool, action, whether it was a blind read (accessibility / text), a screenshot, an action by name or by coordinates, the outcome (`empty` / `timeout` / `partial` / `ok`), timing and the host's name. Never typed text, clipboard, element names, window titles, URLs or coordinates. Off with `CLAWDCURSOR_SESSION_LOG=0`. - **One-step reports.** `clawdcursor report` now reports on the latest MCP session (it only knew agent task logs, which MCP users never have), and an agent can call `system report` with a note. The report server is unreachable, so both save the full report locally and open (or print) a prefilled GitHub issue — the user reviews it and submits; nothing is sent automatically. - **Blind-first nudge.** After 3 screenshots in a row with no text or accessibility read in between (and every 5th after), the screenshot result adds one line naming the exact, cheaper path (`compile_ui` / `smart_read`, `copy_all_text`, act by name). ### Fixed - **Windows: clicks could raise the window BEHIND the one you see** — on any setup, one monitor included. Before a click clawdcursor brings the window under the pointer forward, asking `WindowFromPoint`. It was declared with two ints instead of a `POINT`, which on 64-bit Windows drops the y coordinate, so every hit-test looked at the TOP ROW of the screen: with a maximized window behind the target (a browser, Settings), that window was raised and took the click. Declared correctly now; when the hit is a DWM-cloaked window it falls back to the top visible window at the point. - **Windows clipboard reads mangled non-ASCII text** (`café — ✓ 日本` came back as `caf? - ? ??`): PowerShell's stdout codepage. `clipboard_read` and `copy_all_text` now read UTF-8 exactly. - **`copy_all_text` refused GUI windows owned by PowerShell / cmd** (e.g. a WinForms tool): the terminal guard now checks the window class on Windows — a real console window is refused, a GUI window is not. - **Tests no longer write to the user's real `~/.clawdcursor/logs`** (`CLAWDCURSOR_LOG_DIR`; the suite points it and the session log at a temp folder), and never start the real PowerShell bridge (which moves the real pointer and captures the real screen) unless a test mocks it. ### Security - OCR temporary screenshots are created owner-only and exclusively (CodeQL `js/insecure-temporary-file`). - Dependencies: `qs` 6.16.0, `body-parser` 2.3.0 and `@hono/node-server` 1.19.17 (bundled into the CLI); dev tooling `vitest` 4.1.11 (GHSA-82fw-gwwq-j7x9), `eslint` 10.12, `typescript-eslint` 8.71, `tsx` 4.23; the release workflow uses `actions/setup-node` v7. ## [1.5.14] - 2026-10-07 — scrolling that moves; consistent window units; exact page text ### Added - **`system copy_all_text` — read a page's exact text.** Select-all + copy, return the text, put the user's clipboard back. OCR misreads identifiers (`l`→`1`, `o`→`0`); this returns the real characters. Refuses in terminals, where Ctrl+C would interrupt the running program. ### Fixed - **Scrolling barely moved on Windows and macOS.** The mouse library passes its amount straight to the OS, and the units differ: on Windows 120 is one wheel notch, so `scroll amount:3` was 3/120 of a notch (Notepad: 0 lines); on macOS it was 3 pixels; the compact path sent 3 units per tick (9 notches on X11). One tick is now one notch everywhere (Notepad: ~9 lines). - **Window resize used different units from everything else.** On Windows the window list reports physical pixels but resize took logical ones, so 1200×900 became 2700×2025 on a 225% display. Resize now takes the same screen units as `window list` and accessibility coordinates, and `space:"image"` accepts screenshot coordinates. Resizing a maximized window now un-maximizes it first (the window manager ignored the new width on Linux; Windows kept the maximized flag). - **A resize aimed at a window that doesn't exist moved the front window.** A title or process that matched nothing fell back to whatever window was in front (Windows). It now fails, like minimize / maximize / close already did. - **`smart_read` stalled 20 s on huge web apps.** The accessibility tree walk outran the bridge timeout and returned nothing. It now stops at an 8 s budget and returns a partial tree, marked PARTIAL. ## [1.5.13] - 2026-10-07 — choose dropdown values; faster cold start ### Added - **`accessibility select` takes a `value`: choose an option inside a dropdown / list.** `select name:"Plan" value:"Pro"` expands the control, requires an option whose name matches `value` exactly (bridge name matching is fuzzy — "Pro" must never press "Profile"), selects it, falls back to pressing it, and reads the control back after each attempt. The reply says "verified", reports what the control shows instead, or says it couldn't be verified. Built from existing primitives, so it works the same on Windows, macOS and Linux. `get-value` now reads a combo box / list's selected item (UIA SelectionPattern; AT-SPI Selection on the combo or its popup menu). ### Changed - **Faster cold start: the CLI ships as one bundled file.** A start used to load ~840 files (zod, ajv, semver, the MCP SDK, express…); on a cold Windows disk with on-access scanning that took 15–25 s and hosts dropped the server before it answered. `dist/surface/cli.js` is now an esbuild bundle; only native or self-locating packages (nut-js, sharp, playwright, clipboardy) load from `node_modules` — ~390 files at startup. ## [1.5.12] - 2026-10-06 — Claude Desktop extension; clicks land on target on any screen and host ### Added - **Claude Desktop extension (`.mcpb`) — install with no terminal.** Every release now attaches `clawdcursor-win32.mcpb`, `clawdcursor-darwin.mcpb` and `clawdcursor-linux.mcpb` (stable links under `releases/latest/download/`). Open one and Claude Desktop installs and runs clawdcursor with its own Node. The install screen asks "Allow clawdcursor to control this computer", which is the one-time desktop-control consent (passed as `CLAWDCURSOR_CONSENT`; only `true`/`1` records it). Each bundle is built and smoke-tested natively on its OS in CI (`npm run build:mcpb`, `scripts/smoke-mcpb.mjs`). - **Consent is asked in the app, not in a terminal.** On the first tool call without consent, clawdcursor asks the user through the host's own UI (MCP elicitation): "Allow clawdcursor to control this computer". Only the person can answer it, not the model. Accepting records consent and the call goes ahead; declining, or a host without elicitation, gets the usual `clawdcursor consent --accept` instructions. Asked at most once per server. ### Changed - **Dependencies:** `@modelcontextprotocol/sdk` ^1.32.1 and `sharp` ^0.35.5 (patched releases for two high advisories: an OAuth-client issue in the SDK — clawdcursor is a server and never runs that client — and a librsvg CVE in sharp's bundled libvips); lockfile `npm audit fix` for `proxy-addr`. - **Smaller npm package.** Source maps and type declarations no longer ship (no library entry point uses them): 893 kB → 622 kB packed. Removed dead files: `perf/` (patch notes for a deleted module), unused `src/core/classify` and `src/core/decompose`, two broken legacy test scripts, a stale `guides/README.md` and two old release drafts. - **No more `punycode` deprecation warning on every command.** It came from a transitive dependency (nut-js → jimp → node-fetch@2 → whatwg-url@5 → tr46); only that one warning code (DEP0040) is silenced. - **MCP servers no longer refuse to start when another is running.** The single-instance lock made every copy after the first exit with "already running … Kill it first". Claude Desktop runs several copies of one extension (protocol probe, main connection, Cowork/Code pool), and other editors may run clawdcursor at the same time, so the extension could never connect. The lock only existed to stop orphans piling up; each MCP server already exits on its own when its host goes away (stdin EOF + parent-PID watchdog). The `agent` daemon keeps its lock. ### Fixed - **Windows clicks landed at a fraction of the target in DPI-aware hosts.** On a 225% display every click went to 1/2.25 of its target when the server ran inside Claude Desktop (an Electron utility process is DPI-aware, so nut-js already drives physical pixels). The mouse ratio was measured by a separate, DPI-unaware PowerShell and divided in anyway. It is now measured in-process; the PowerShell bridge keeps the logical ratio. This also removes a synchronous PowerShell call that stalled the MCP handshake on a cold start. - **Screenshots and clicks work on any screen shape.** The screenshot scale came from the width alone, so portrait (1080×1920 → a 2 MP image), 4:3 and 5:4 screens sent images over the model's limit; the provider shrank them again and every coordinate read back drifted (up to ~37% short). One helper (`llmSize`) now caps the long edge at 1280 px and the area at 1.15 MP for every capture path, the reported size, and the mouse mapping. Also: - scale factors are read live from the last capture instead of frozen at startup (rotation / resolution changes / a late first Wayland capture); - Linux X11 with two monitors no longer halves every click (the root window was compared to the primary output's width); - macOS: `agent` (daemon) mode maps to logical points like MCP mode (#154), and a "larger text" display (logical narrower than the image) scales down instead of clamping to 1; - Windows relative moves no longer overshoot by the DPI ratio; - OCR's conversion hint names the right factor. - **Field fixes from a live session** driving Edge on Windows: - `type` text ending in a newline pastes the rest, then presses Return (single-line fields such as the address bar dropped a pasted `\n`). - `key ctrl+minus` (and other spelled-out keys) work on Windows. - `browser connect` never falls back to `chrome-extension://` pages, and says so when no regular web page is open instead of calling it the user's own session. - `triple_click` only adds select-all when the focused field is the one clicked. - `accessibility find` with a `processId` searches every window of the process, not just the first. - `computer zoom` is accepted as an alias of `screenshot_region`. - **The CLI misread its arguments inside Electron hosts.** Claude Desktop runs extensions in an Electron utility process; commander's Electron detection took the script path for the command and the server died with `error: unknown command '…\cli.js'` before logging anything. The CLI now finds its own script in `argv` and parses what follows. - **`expect` is checked before the action runs, on every path.** A malformed `expect` used to be rejected only AFTER the click/key had been sent, so a retrying agent acted twice. The projected MCP handlers, the agent loop and both batch executors now refuse with "expect rejected (nothing executed)" and touch nothing. `expect` and `verify`/`done` `assertions` also accept the JSON-encoded array string the compound schema already advertises for array params (all OSes). - **`accessibility invoke` by name no longer misses an element `find` can see.** When the element is found but exposes no invoke/toggle/select pattern, the Windows and macOS bridges now surface its bounds and the by-name path falls back to clicking the bounds centre — only when the rect is sane and on the virtual screen (never INT_MIN, never offscreen) — reporting "via a11y bounds (coordinate fallback)". Windows `invokeElement` also normalizes `ControlType.Button` → `Button` like `findElements` does, instead of silently dropping the role filter. - **Windows: unscoped `find` retries the window clawdcursor last focused.** An unscoped search follows the live foreground window; when that is the MCP host and the search is empty, it retries once against the window a prior `window focus` targeted, and "(no elements found)" now says which window(s) were searched. - **Windows: a PowerShell bridge command timeout restarts the bridge.** The bridge protocol has no request ids; previously the next command was sent while the bridge was still busy and the late reply resolved the WRONG call. - **macOS: `scripts/mac/find-element.jxa` is now UTF-8/LF** (it was committed as UTF-16LE with CRLF). A test now asserts every bridge script is UTF-8 without a UTF-16 BOM and uses LF. - **The compact surface could not drive a web page.** `window navigate` launched its own browser on the user debug port and never told the CDP driver, so the next `browser connect` attached to a *different* instance (the dedicated agent browser at `about:blank`), `page_context` saw an empty page, `type`/`select_option` failed, and `list_tabs` — which probed the user port directly — reported no browser at all. `navigate_browser` now goes through the one driver with the same attach-or-launch policy as `cdp_connect` (including `CLAWD_AGENT_CDP_OFF=1` attach-only), and `cdp_list_tabs` lists the connected browser's tabs. Attaching to a browser the user already has on the debug port keeps its tab discipline: the page opens in the agent's own tab and the result says so. - **`browser` compound gains a `navigate` action** (`window navigate` still works). Every hint now names tools and actions that exist on the surface the caller is using: compact results say `browser {action:"connect"}` instead of `cdp_connect`, and projected agent-loop tools say `navigate_browser` / `cdp_page_context` instead of `browser_navigate` / `browser_read` (this also corrects `read_screen`'s hint to `ocr_read_screen`). - **Screenshots had red and blue swapped on every OS that captures through nut-js (Windows, Linux X11, macOS fallback).** nut-js returns BGR pixels and every capture path handed them to the PNG encoder as RGB. A single shared helper now reorders the channels, so screenshots, region captures, monitor captures and the frames fed to OCR show true colours. - **Linux X11 screenshots were black, which also blinded OCR.** The 4th byte of an X11 grab is 0, which the encoder read as "fully transparent". Screen captures are now always opaque. - **The MCP server no longer crashes at startup on Wayland.** Without XWayland it segfaulted ("Could not open main display"); with XWayland (GNOME/KDE) it exited with an X11 BadMatch. Wayland sessions never touch the X11 screen APIs any more: screenshots and OCR use `grim` when it is installed (sway, Hyprland and other wlroots compositors) and otherwise return a clear "needs grim" error, and screen size comes from `swaymsg` / `wlr-randr` / `xrandr`. - **Wayland input on Ubuntu 24.04 / Debian (ydotool 0.1.8).** clawdcursor spoke the ydotool 1.x syntax, which 0.1.8 either ignores while exiting 0 (mouse moves "succeeded" without moving) or misreads (key presses typed digits, every click was a left click). The installed generation is now detected once and 0.1.8 gets its own syntax; what 0.1.8 cannot do at all (scroll, drag, holding keys) reports an honest error instead of typing stray characters. On ydotool 1.x, scrolling now uses the real wheel (`mousemove -w`) instead of a non-existent `scroll` command. ### Fixed (Linux) Found driving the MCP server on Ubuntu 24.04 (Xvfb + openbox, GTK3/GTK4 apps, at-spi2, tesseract 5) and scored against the apps' own logs. - **OCR failed on current distros.** tesseract 5 prints float confidences (`81.879456`); the Python parser used `int()` and the whole OCR call died with `ValueError`. Worse, the engine latched "OCR is not available" after that first failure, so every later call returned nothing in 1ms. The parser accepts floats, and the engine now only marks itself unavailable when the OCR binary / language pack is actually missing, not on a transient error. - **`type` took 6 s and `clipboard_write` 3 s.** xclip / wl-copy fork a child that keeps serving the selection; with stdout/stderr piped it held the pipes open, so the adapter waited for a `close` that never came until the 3 s timeout (twice per `type`: paste + restore). The write now resolves on process exit and inherits no pipes. The forked server is never killed. - **`window restore` left minimized windows minimized.** Clearing the `hidden` hint doesn't un-iconify an X window; the adapter now activates it (`wmctrl -i -a`) after clearing the states, the same path focus-by-title already took. - **`list_windows` reported an empty process name for every window**, so `window focus processName:"gnome-calculator"` and `app_running` assertions never matched. Names come from `/proc//cmdline` (argv[0] basename — not the 15-char-truncated `comm`), falling back to `comm`. - **Unknown key names reported success.** `key combo:"notarealkey"` typed the word and answered "Pressed notarealkey"; `"super"` was typed as text. The Linux adapter now throws `Unknown key: "..."` like Windows and macOS, and bare modifiers (`super`, `ctrl`, `alt`, …) press the modifier key. - **`GDK_SCALE` faked the DPI on X11.** With `GDK_SCALE=2` on a real 1920×1080 X server, `list_displays` claimed dpiRatio 2 / 3840×2160 and every coordinate click landed at half the target. X11 input (xdotool, nut-js) is physical pixels — the env scale is only consulted on Wayland now, in both the platform adapter and the legacy desktop layer. - **`accessibility find` without a process id searched the wrong app** (the bridge guessed by a focused-descendant heuristic, else took the first app on the bus); `wait_for` timed out for the same reason. Find / invoke now scope to the foreground window's pid by default, like Windows and macOS. - **AT-SPI actions were a stub**, so `accessibility invoke / set_value / toggle / select / focus / get_value`, `smart_click`, `smart_type`, `find_button`→invoke and the `a11y_*` depth tools all failed on Linux even though `find` worked. `atspi-bridge.py --cmd invoke` drives the AT-SPI Action / EditableText / Value / Selection / Component interfaces and always returns the element's bounds so callers can fall back to a coordinate click; the legacy accessibility bridge routes its Linux branches to the adapter instead of answering "not implemented". - **AT-SPI roles and bounds.** `compile_ui` showed role `unknown` for almost every element because raw AT-SPI role names (`push button`, `check box`, `combo box`, `text`, `menu item`, …) weren't mapped to the shared vocabulary, so `find_button` found no candidate. Roles are normalized (raw name kept as `subrole`, password fields flagged `secure`), a `controlType` filter now matches the normalized role, elements scrolled out of view (INT_MIN extents) are reported offscreen instead of at `-2147483648,-2147483648`, an empty or partial state set no longer marks a widget disabled (GTK4), and window-relative extents (GTK4 on X11 answers zeros for screen coordinates) are offset by the window origin when the bridge reports them. (libatspi's client cache stays on: disabling it made a single `find` on a real GTK3 app run past 30 s.) - **OCR found no text on a Linux desktop.** Tesseract's default page mode is built for prose and read nothing from a 1080p screen of small UI labels. Linux OCR now runs sparse-text mode on a 2x-upscaled capture and maps the boxes back to screen pixels — every button label in the test app was read. ### Fixed (all platforms) - **Hint rewriting corrupted JSON results.** Rewriting tool names in results to the caller's surface inserted raw double quotes into JSON, so every `system ocr` response failed to parse; it would also have edited tool names appearing in page text, files or the clipboard. JSON results and `` blocks are now never rewritten. - **`smart_click` could click the wrong item on a repeated OCR word.** Token coverage counted repeats, so "Row Row" matched "Row 50" and the wrong list row was clicked. Coverage now counts distinct target words. - **The invoke-by-name coordinate fallback raises the target window first** when the lookup was scoped to a process, so the click can't land on a window covering the element. ## [1.5.11] - 2026-10-01 — honest compound surface; works with any model, host and OS (security) Every bug here was hit driving clawdcursor for real to configure npm trusted publishing during the v1.5.10 release — none are hypothetical. ### Security - **macOS: unknown key and modifier names are refused.** `keyPress` builds an AppleScript program from the key combo, and a key name outside the known table (or an unknown modifier) was inserted without escaping. They now throw `Unknown key` / `Unknown modifier` before osascript runs, matching the Windows adapter, and the numbers interpolated into window scripts (process id, position, size) are coerced to integers. Valid combos emit exactly the same AppleScript as before. - **The compound surface silently dropped arguments it advertised.** `buildCompoundSchema` unions every route delegate's parameters into one flat schema, so a parameter only ONE action implements is advertised on the WHOLE tool — and an action that never declared it ignored it in silence. No error, no warning, and the constraint the caller asked for was void. Same defect class as GHSA-35pc-g74h-p476, dangerous for the same reason: the publishing is what makes it bite, because the caller has positive evidence the control exists. `dispatchCompound` now surfaces ignored arguments and names which actions do accept them. A warning rather than an error, so a caller passing a surplus argument keeps working instead of failing mid-task. - **`space` was dropped by every System A coordinate action.** `space: 'screen'` means "already physical, do not scale", but it was implemented only on click/drag/scroll. The other pointer tools advertised it and always image-scaled, so accessibility coordinates were scaled a SECOND time and the pointer landed on a different control than the agent had resolved. Now honored by `desktop_screenshot_region`, `mouse_click`, `mouse_double_click`, `mouse_right_click`, `mouse_hover`, `mouse_middle_click`, `mouse_triple_click`, `mouse_scroll_horizontal` and `mouse_drag_stepped`. - **`setWindowState` fell through to the foreground window.** A selector that matched nothing fell through to `GetForegroundWindow()`, so closing a window that was not open posted `WM_CLOSE` to whatever happened to be in front — an unsaved document, or the agent's own host. Windows was the only adapter with this: Linux already failed closed and macOS fails closed through AppleScript. Omitting the selector still targets the foreground window, which is an explicit request; only a selector that resolved to nothing now fails. - **`controlType` was dropped, then the search fuzzy-matched names.** The bridge keys its table on bare names, but everything the agent reads is normalized the other way and the schema's own example told callers to send the prefixed form — so the documented input was the one form that could not match, and the search ran unfiltered. Asking for a CheckBox named "Allow npm publish" could return a Text element named "npm". The same path backs invoke/toggle/select/ set_value, so a dropped role filter could ACT on the wrong control. Both forms are now accepted, and an unhonorable type returns no results instead of degrading to a fuzzy name search. - **Tool calls with malformed arguments were executed with empty ones.** When a provider sent arguments that were not valid JSON, they became `{}` and the tool ran anyway — a different action from the one requested. For `minimize_window`, which the safety gate does not stop, an empty selector targets the foreground window, so a garbled call minimized whatever was in front. Such calls are now refused and reported back to the model. - **The dashboard served the control token off-loopback.** With remote binding enabled, the dashboard — which embeds the bearer token — was still served, so anyone who could reach the port and load `/` got full desktop control. It is now served on loopback only. ### Compatibility — any model, any host, any OS - **The autonomous agent was blind on every non-Anthropic provider.** Anthropic lets a tool result carry images; the OpenAI wire format does not, and the translation dropped them. On OpenAI, Gemini, Mistral, xAI, Groq, Ollama and every OpenAI-compatible endpoint, a screenshot reached the model as the words "Screenshot captured", and the agent clicked at coordinates the model guessed. Images now travel in the message after the tool replies, which is where that format allows them. - **Tool schemas now load on every model provider.** Hosts forward the server's schemas to whatever model the user picked, and three features on the wire are documented to make a provider reject the entire server: `anyOf` with a sibling description and a root `$schema` (Gemini), and `items: {}` with no type (OpenAI strict mode). All three are gone; inputs are validated exactly as before. - **Modern OpenAI and OpenRouter keys were detected as Kimi** and failed with a 401. Prefixed keys are now matched first. **OpenRouter is a supported provider** — one key, hundreds of models. - **OCR clicks were converted twice on Windows/Linux HiDPI** (landing at ~1/5 of the target on a 2.25x display). They are now converted exactly once on every OS. The obvious fix would have broken every Retina Mac; the per-OS rule is now pinned by tests on all three platforms. - **macOS:** switching browser tabs opened the OS app switcher (Cmd+Tab), and set-field's select-all prepended text instead of replacing it. - **stdio hosts:** `console.debug`, `info`, `dir` and `table` still wrote to stdout, which is the protocol channel, and could corrupt it. - **Install docs:** the one-click Cursor and VS Code badges did nothing on GitHub — it strips custom URL schemes — so they now use HTTPS redirects. The Zed snippet used an outdated format. Added verified configs for VS Code (which uses `servers`, not `mcpServers`), opencode, Gemini CLI and Cline, and a Windows `cmd /c` note for hosts that start servers without a shell. - **Mixed-provider pipelines sent vision requests in the wrong format.** With a different provider for vision than for text (say, OpenAI text and Anthropic vision), vision calls used the main provider's wire format and key. They now use the vision layer's own. - **OpenAI reasoning models (o1, o3, GPT-5) were sent parameters they reject** on the plain text and vision paths, which skipped the model-specific fixups every other path applied. - **Streamed responses lost text** whenever a line was split across network reads, and failed outright when a stream arrived in small pieces. ### Added - Regression coverage on the compact surface (`dispatchCompound`), which is exactly where the gap was — `max_cost` had been tested only on the agent-loop path, never through the compound. Includes an invariant test that every `computer` action taking x/y also declares `space`, so a new pointer tool cannot reintroduce the scaling bug. ### Known follow-ups - `ps-bridge.ps1` has the same control-type drop in `Cmd-InvokeElement` for callers that bypass the adapter. - `smart_click`/`smart_type` do not honor `expect`, so they can report hollow success on consequential actions. - `accessibility.find` output carries no provenance, so a hit from the wrong application is invisible to the agent. ## [1.5.10] - 2026-09-27 — keyboard gate hardening (security) > **First npm release since 1.5.7.** v1.5.8 and v1.5.9 were tagged and released > on GitHub but never published to npm, so `npm i -g clawdcursor` and > `npx -y clawdcursor` both still resolved to 1.5.7. Installing this version > also delivers everything from those two releases — the HiDPI agent-path click > fix, the self/host foreground guard, the retired-vision-model-id heal, > deterministic MCP orphan reaping, `cursor_position`, and the > GHSA-3v3f-5rwv-whc9 `batch allowConfirm` fix. ### Security - **Close two independent bypasses of the SafetyLayer keyboard gate (GHSA-35pc-g74h-p476, medium).** `evaluate()` decided whether a combo was blocked only after matching the call against four tool *names*. The autonomous agent loop registers its keyboard tool as `key` and gates every call with `safetyEvaluate({ tool: call.name })`, so `key` matched none of them, is absent from `TOOL_TIER`, and fell through to the default allow: measured, 9 of 9 block- and confirm-tier combos resolved to `allow` on the agent path, `win+l` and `cmd+opt+esc` among them. Separately, `normalizeCombo` collapses whitespace, but a space-separated sequence is a documented input, so `esc win+l` became `escwin+l`, matched no entry, and the executor then split that same string and pressed `win+l`. Neither backstop caught the result: both carried their own three-entry copy (`alt+f4`, `ctrl+alt+delete`, `ctrl+alt+del`) that omits every machine-locking combo, so on the agent-loop path a lock or force-quit passed both layers and reached the OS. Keyboard calls are now detected by argument shape rather than tool name, which also survives the next rename; matching is sequence-aware and reports which element offended; and both backstops defer to `keys-blocklist` at hard tier only, so confirm-tier combos keep their `allowConfirm` path. Reachable by an operator running `clawdcursor agent` against a task built from untrusted content. All versions through 1.5.9 are affected. Reported by @kasparovabi. - **Clear every HIGH dependency advisory.** Four production advisories were shipping, the most serious being `fast-uri` (SSRF and host confusion, six advisories) reached through the MCP SDK's `ajv`; also `express-rate-limit`, `ip-address`, and `sharp`. None were real version constraints — all were stale lockfile resolutions the declared ranges already permitted. Both `overrides` pins were removed rather than bumped, because each had rotted into the thing it was added to prevent: `ip-address: 10.1.1` sits inside its own `<=10.3.0` advisory range and was forcing a vulnerable version over `express-rate-limit`'s safe `^10.2.0`. HIGH/CRITICAL count is now zero in both production and dev. ### Fixed - **CI is unstuck.** `cross-platform.yml` gates on `npm audit --audit-level=high --omit=dev`. With four HIGH production advisories outstanding, no single dependency PR could clear them all, so every one of the 16 open dependency PRs failed all six jobs and none could merge — a deadlock rather than 16 independent failures. ### Added - Regression coverage for the keyboard gate under the tool name the agent loop actually uses. `safety-layer.test.ts` only ever passed `key_press`, which is why neither bypass was caught; the new cases pin `key`, the arg-shape path for a renamed tool, sequence handling, strongest-tier selection, and no-over-block controls. ## [1.5.9] - 2026-07-03 — batch confirm-tier hardening (security) ### Security - **Close the `batch` `allowConfirm` bypass (GHSA-3v3f-5rwv-whc9, high).** The `batch` MCP tool accepted a caller-controlled `allowConfirm:true` that skipped the confirm-tier `SafetyLayer` halt, letting any bearer-token holder or a compromised agent run confirm-gated actions (e.g. `open_uri file:` launching an arbitrary OS handler) that a direct `tools/call` would stop. `allowConfirm` is now honored only when the operator started the daemon with `CLAWD_BATCH_ALLOW_CONFIRM=1`; a caller-supplied flag alone can no longer satisfy a confirm step. Block-tier remains unbypassable, and the agent-loop batch was never affected (it already halts on any non-allow verdict). v1.5.8 and earlier are affected; there is no npm release of 1.5.8 (it went straight to 1.5.9). ### Changed - **clawdcursor.com**: removed every em/en dash from the copy and tightened the page a further ~16% on top of the v1.5.8 cut. ## [1.5.8] - 2026-07-02 — the agent path clicks straight The autonomous agent path (`clawdcursor agent` + granular `click`/`smart_click`) now behaves on HiDPI displays, WebView2 apps expose their real accessibility tree, and a set of first-run failures that read like credential problems are fixed at the root. ### Fixed - **HiDPI clicks on the agent path land on target.** `WindowsAdapter` mouse functions now convert physical→logical (`÷dpiRatio`) exactly like `NativeDesktop` has since v1.5.6 — the autonomous agent was still clicking ~2.25× off on scaled displays and force-foregrounding the wrong window. The conversion feeds both the cursor move and the `activate-at-point` foreground check so they can never disagree. (#173) - **The controller can no longer steal the stage.** The foreground bridge refuses to raise AI-host windows (Claude, Cursor, Code, Windsurf — extend via `CLAWD_FOREGROUND_DENYLIST`) or clawdcursor's own consoles at a click point, and reports `TARGET OCCLUDED` guidance instead. The pre-click focus guard gained an `activeApp` fallback for anchor-less delegated tasks, with the same denylist on the fallback so it can't raise the host on macOS/Linux either. (#173) - **WebView2/Electron apps (new Outlook, Teams, VS Code…) expose their real a11y tree.** Unnamed wrapper Panes/Groups no longer consume tree-walk depth or bloat the snapshot — measured on new Outlook: 7 named elements before → 113 after at the same depth budget, including To/Subject/Message body/Send. Form tasks that escalated to OCR + vision now run on the cheap a11y path. (#173) - **Retired Anthropic vision model id healed everywhere.** The dead `claude-sonnet-4-20250514` default 404'd mid-task the moment vision was needed (it read like a billing failure). Fixed in the provider defaults AND auto-healed when read from persisted configs, which override code defaults and would otherwise keep every existing install broken after upgrade. (#173) - **No more orphaned processes.** `doctor` auto-exits after 5 idle minutes on both of its prompt surfaces instead of hanging forever with its PowerShell bridge child; stale MCP locks from quit/reopen editor hosts are reaped deterministically via a parent-PID watchdog. (#173, #175) - **Banner/bridge spawns can't flash-steal foreground** (`windowsHide` on the pill/glow/ps-bridge PowerShell spawns). (#173) ### Added - **`cursor_position` tool** (granular + compact `computer` group) — Anthropic computer-use parity pointer read; round-trips exactly with `mouse_hover` on every OS. (#176) ### Changed - **Onboarding fixes for first-time agents**: README's HTTP example sends the required MCP `Accept` header (it 406'd before), the consent error offers the `npx` variant for plugin users with no global install, `llms.txt` no longer documents a nonexistent `--granular` flag, tool counts are normalized to the real 98 granular / 7 compound everywhere, and Zed gets a correct `context_servers` config block. (#176) - **clawdcursor.com**: mobile layout fixed (clipped tools table, overlapping copy button, stats grid, cursor animation on touch) and visible copy cut ~40%; Quick Start now sits directly under the hero. (#176) ## [1.5.7] - 2026-06-26 — accuracy + repo polish Documentation-and-hygiene release; no runtime behavior change. ### Changed - **Reframed the perception story around the UI State Compiler.** README, website (`docs/index.html` + `llms.txt`), and `SKILL.md` now say what clawdcursor actually does: it **compiles the screen into one fused UI map** (accessibility tree + OCR, elements with stable `el_NN` ids) and acts on elements **by id** — dropping to the **screenshot/vision tier** (live pixel coords off the current frame) only as a last resort, for canvas-only or custom-drawn UIs. Replaces the old "reads the a11y tree, screenshots as fallback" framing. - **Fixed a cost-tier inaccuracy in `SKILL.md`:** "screenshot" and "vision" were listed as two tiers (they're one — the only tier that puts pixels in the model), and `smart_read`/`smart_click`/`smart_type` were mis-filed under vision when they're OCR-backed. Now a correct 3-tier ladder (structured → OCR → screenshot). ### Repo hygiene - ESLint is now **0 warnings** (was 16): removed dead imports + stale `eslint-disable` directives, fixed useless escapes, and documented the intentional control-char sanitisers at source. - Added `CODE_OF_CONDUCT.md` (Contributor Covenant v2.1), `.editorconfig`, and `.gitattributes` (`eol=lf` — line-ending hygiene for a cross-OS project). - CI (`cross-platform.yml`): added a `concurrency` group (cancel in-progress PR runs) and a 30-minute job timeout; dropped the dead `master` push trigger. ## [1.5.6] - 2026-06-25 — straight-line cross-OS onboarding + HiDPI clicks The theme is **an agent can install clawdcursor and actually take control** — the two things that were blocking that, plus a frictionless install path. ### Fixed - **HiDPI clicks land on target (Windows/Linux-X11).** On a scaled display every coordinate click/drag landed ~`dpiRatio`× off and stole foreground to the wrong window. `NativeDesktop` mouse ops now convert physical→logical (`÷dpiRatio`), so clicks hit where intended. macOS is explicitly exempt (callers already pass logical there — avoids re-introducing the #154 Retina double-convert). (#170) - **No more opaque "MCP server failed."** A missing one-time consent used to `process.exit(1)` the `mcp` server, and the reason went only to stderr (which editor hosts hide). Consent is now non-fatal and **visible**: every tool call returns a clear `clawdcursor consent --accept` prompt until consent is given, re-checked per call (takes effect with no restart). (#169) ### Added - **One-paste install into any MCP host.** A Claude Code plugin marketplace (`.claude-plugin/marketplace.json`), `server.json` registry launch args (`mcp --compact` via npx), and per-host README snippets — install the way every other MCP server is distributed. The plugin's skill is single-sourced from the root `SKILL.md` (auto-copied + drift-guarded). (#168) ### Security / maintenance - **hono → 4.12.26** — fixes a HIGH `serve-static` path-traversal advisory on Windows (`%5C`). (#167) - Dependency bumps: `@types/node` 26, `eslint` 10.5 + `@typescript-eslint` 8.62, `@vitest/coverage-v8` 4.1.9, `sharp` 0.35, `playwright` 1.61, `form-data` 4.0.6. ## [1.5.5] - 2026-06-16 — the skill follows the install (cross-framework) ### Fixed - **MCP-direct installs got the tools but not the skill.** The cross-framework skill registration (Claude Code, OpenClaw, Codex, Cursor) lived *only* inside `clawdcursor doctor` — which the MCP-first onboarding explicitly tells people to skip. So an agent connected over MCP saw bare tools with none of the "how to use me" knowledge (fallback positioning, the el_NN UI map, sustainable/autonomous execution via the daemon + `task`), and clawdcursor stopped appearing as a skill. Registration is now extracted into a shared module and runs on **`consent`** (the always-required step) and via a new **`clawdcursor register-skill`** command, so the skill installs into every detected agent framework regardless of install path. ### Changed - **Richer MCP server `instructions`.** Even an agent with no skill file (a host that doesn't support skills) now learns the essentials on connect: drive UI symbolically (`compile_ui` / `find_button` / `find_field` → `{element_id, snapshot_id}`, survives layout shifts), verify with `expect`, the fallback-only positioning, and where to find the full guide (the registered skill or `clawdcursor.com/llms.txt`). ## [1.5.4] - 2026-06-15 — install & distribution hardening ### Changed - **Installer is now `npm i -g`, not a git-clone-and-build.** The `curl … | bash` / `irm … | iex` one-liners previously cloned the repo and ran `npm install` + `npm run build` on the user's machine — requiring git and a full build toolchain, and diverging from the `npm i -g clawdcursor` the README advertises. They now install the published package globally. macOS still gets a working native helper because the package's `postinstall` builds and ad-hoc-signs it (ad-hoc is `build.sh`'s default). `VERSION=vX.Y.Z` still pins, now via `clawdcursor@X.Y.Z`. - **New Claude Code plugin** (`.claude-plugin/plugin.json`) registers the MCP server in compact mode — launched via `npx -y clawdcursor` so there's **no global install to do first** (npx fetches on demand, or uses a global install if present), while still resolving the package `bin` so it survives entry-path refactors — and bundles the root `SKILL.md`. A one-step, config-free install for Claude Code. Manifest version auto-syncs via `scripts/sync-version.ts` (and is guarded by the version-drift test). ### Fixed - **Back-compat entry point at `dist/index.js`.** v0.x shipped the CLI there; v1.0 moved it to `dist/surface/cli.js`. Hosts that had hard-pinned `node /dist/index.js …` (e.g. a hand-written MCP entry in Claude Code's `.claude.json`) silently broke on a routine `npm i -g clawdcursor` upgrade — the MCP server just failed to start with no clear cause. A thin re-export shim (`src/index.ts` → `dist/index.js`) now forwards to the real CLI, so those pinned paths keep working across the move. New configs should still launch the `clawdcursor` bin directly or use the Claude Code plugin, neither of which pins a deep dist path. - **`uninstall` no longer dead-ends.** It removes the global `clawdcursor` command, so `clawdcursor install` can't follow it — and the old success message only said how to delete *more*. Uninstall now prints the reinstall one-liner (`npm i -g clawdcursor`, plus the OS turnkey installer), so there's always an obvious way back. ## [1.5.3] - 2026-06-14 — edge-glow indicator + security hardening ### Added - **Screen-edge "task in progress" glow.** A full-screen, click-through amber glow pulses (dim ↔ bright) on all four screen edges whenever an agent is driving the desktop — ambient, at-a-glance awareness that automation is live. It rides the same lifecycle as the control-banner pill (shown together, hidden together) and never steals focus or intercepts input: a per-pixel-alpha layered window with `WS_EX_NOACTIVATE | WS_EX_TRANSPARENT`. Opt out of just the glow with `CLAWD_NO_GLOW=1` — the pill (and its double-click-to-stop) stays. Windows-only today, like the banner; the API is platform-neutral so macOS/Linux overlays can land later. (`scripts/edge-glow.ps1`) ### Security / hardening - **Insecure temp files (CWE-377).** The `agent console` terminal scripts were written to a predictable `tmpdir/clawdcursor-task-