diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f0f7f..efc74e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ form with the wrong option. Anode now reads the control back: if it still shows its old value 1.5 seconds later, the action fails and says to expand the list and select the option. A control that reformats the value succeeds and returns what it now reads. +- **`seat_observe` lists each control once.** While a Chrome drop-down list was open, `seat_observe` + returned the window's tree nested inside itself, over and over, until its element budget ran out. + An app can list one control in two places, or even inside itself. The walk now skips a control with + the same UI Automation runtime ID as one it has already listed, without using the budget, and + `skippedRepeats` says how many it skipped. - **Packaged apps' Anode folders.** A daemon started by a desktop app that redirects AppData, such as the Claude desktop app, keeps its log and state in that app's `%LOCALAPPDATA%\Packages\\LocalCache\Local\Anode`, which the privacy, install and troubleshooting diff --git a/docs/DESKTOP-TOOLS.md b/docs/DESKTOP-TOOLS.md index 8b83862..f7d9645 100644 --- a/docs/DESKTOP-TOOLS.md +++ b/docs/DESKTOP-TOOLS.md @@ -83,7 +83,10 @@ action may already have happened: inspect again instead of replaying it. Observations default to 200 controls, depth 8 and 6,000 text characters. MCP accepts `maxElements` (1–500), `maxDepth` (0–20), `maxTextChars` (0–20,000), and `includeOffscreen`. CLI uses `--max-elements`, `--max-depth`, `--max-text` and -`--offscreen`. The tree reports truncation and provider warnings. Accessibility +`--offscreen`. The tree reports truncation and provider warnings. A control the app +lists more than once (the same UI Automation runtime ID), even inside itself as +Chrome appeared to while a drop-down list was open, appears once; `skippedRepeats` +says how many copies were skipped. Accessibility work runs in a disposable child-session process with a ten-second deadline, so an unresponsive app provider does not hold the seat indefinitely. diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 7aaef36..c4c281e 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -176,7 +176,7 @@ is still restoring when the operation's deadline passes, it fails with `errorCod | `op` | Arguments | Result | | --- | --- | --- | | `desktop.windows` | `query`, `pid` | `{windows: [{windowId, pid, process, title, bounds, foreground, minimized, maximized}], summary}` | -| `desktop.observe` | `windowId`, `maxElements`, `maxDepth`, `maxTextChars`, `includeOffscreen`, `includeScreenshot`, `maxWidth` | `{windowId, snapshotId, expiresInSeconds, window, elements, observedAt, truncated, warnings, screenshot?, screenshotError?, summary}` | +| `desktop.observe` | `windowId`, `maxElements`, `maxDepth`, `maxTextChars`, `includeOffscreen`, `includeScreenshot`, `maxWidth` | `{windowId, snapshotId, expiresInSeconds, window, elements, observedAt, truncated, warnings, skippedRepeats?, screenshot?, screenshotError?, summary}` | | `desktop.window` | `windowId`, `action`, optional move geometry `x`, `y`, `width`, `height` | `{requested, note, summary}` | | `desktop.element` | `snapshotId`, `elementId`, `action`, optional `value`, `number`, `direction`, `amount` | `{performed, note, value?, summary}`; `value` is what the control reads after a `set_value` it reformatted | | `desktop.capabilities` | `probeCapture` (default true) | `{version, session, workingDirectory, screen, capture, input, supported, limitations, summary}` | diff --git a/src/Anode/Cli/DesktopChecks.cs b/src/Anode/Cli/DesktopChecks.cs index b71482b..d89ef78 100644 --- a/src/Anode/Cli/DesktopChecks.cs +++ b/src/Anode/Cli/DesktopChecks.cs @@ -293,4 +293,86 @@ JsonObject Set(string mode, JsonObject? control = null) return "set_value reads the control back: an ignored value (as Chrome's open) or under a second parent, is observed once, without using the budget; waits and actions still work"; + }); + } } diff --git a/src/Anode/Cli/SelfTest.cs b/src/Anode/Cli/SelfTest.cs index 7ca4ba8..05435d4 100644 --- a/src/Anode/Cli/SelfTest.cs +++ b/src/Anode/Cli/SelfTest.cs @@ -132,6 +132,7 @@ public static int Run(string[] args) Check(results, "desktop report escaping", DesktopChecks.Presentation); Check(results, "desktop unknown control types", DesktopChecks.UnknownControlTypes); Check(results, "desktop ignored set_value", DesktopChecks.IgnoredValues); + Check(results, "desktop repeated elements", DesktopChecks.RepeatedElements); Check(results, "execution output", DevelopmentChecks.Output); Check(results, "execution jobs", () => DevelopmentChecks.Jobs().GetAwaiter().GetResult()); Check(results, "cancelled execution", () => DevelopmentChecks.CancelledStart().GetAwaiter().GetResult()); diff --git a/src/Anode/Core/Desktop/AccessibilityReader.cs b/src/Anode/Core/Desktop/AccessibilityReader.cs index 256c3df..835ecf7 100644 --- a/src/Anode/Core/Desktop/AccessibilityReader.cs +++ b/src/Anode/Core/Desktop/AccessibilityReader.cs @@ -28,17 +28,35 @@ public static JsonObject Observe(WindowTarget target, JsonObject request) var elements = new JsonArray(); var warnings = new JsonArray(); var watch = Stopwatch.StartNew(); - var queue = new Queue<(AutomationElement Element, int[] Path, string? Parent)>(); - queue.Enqueue((root, Array.Empty(), null)); + // An app can list the same control twice in its tree, even below itself (Chrome appeared to while a