From 8d04c61facb3d7d9c2842cdac20aac02d91a866d Mon Sep 17 00:00:00 2001 From: skulitom Date: Fri, 9 Oct 2026 09:29:29 +0100 Subject: [PATCH 1/4] [compass] Anode 0.11.2 release candidate - Version 0.11.2 in Anode.csproj, server.json, the MCPB manifest and the Claude Code plugin; CHANGELOG's Unreleased section becomes 0.11.2. - .github/RELEASE_NOTES.md: what's new in 0.11.2, known issues, upgrade. - docs/RELEASE-READINESS.md: a 0.11.2 section with what was checked without a seat, the owner's live check as exact commands (about 25 minutes), what stays covered only by stand-ins, and the known open bugs. - scripts/test-chrome-form.ps1: an opt-in live check for #28 and #29 on a local Chrome page (text field set_value read-back, `), `set_value` + reported success while the list kept its old choice. Anode now reads the control back and fails, + saying to expand the list and pick the option, if the control still shows its old value. +- **Observation copes with more windows.** `seat_observe` failed on File Explorer windows ("Object + reference not set to an instance of an object"), and repeated a Chrome window's tree while one of + its drop-down lists was open. Both are fixed; a control listed twice now appears once. +- **Queued requests keep their deadline.** A request that waited behind a long operation, such as + typing, could lose its reply and leave the seat `detached`. It now answers, or fails, in time. +- **Browser sign-in is clearer.** `anode browser --sign-in` explains its window in its first tab + and opens several addresses. When the seat's profile is open on the other desktop, `seat_browser` + and `--sign-in` refuse and name the process holding it, instead of reporting a window. +- **Faster startup.** CLI and MCP skip their first connection wait when no daemon is running, and + waiting callers wake as soon as the seat is ready ([measurements](https://github.com/skulitom/Anode/blob/v0.11.2/docs/STARTUP-PERFORMANCE.md)). +- **Clearer records of what Anode keeps.** A daemon started by a desktop app that redirects AppData, + such as the Claude desktop app, keeps its files in that app's package folder, which the privacy, + install and troubleshooting pages now say; `anode rendering --restore` finds a backup saved there. + The uninstaller ends with everything it leaves on the machine and how to remove each item. -**Validation.** All 83 quick checks passed. Live on Windows 11 Pro build 26200, native desktop -controls, actual mouse/keyboard delivery to Chrome, portrait and scaled displays, audio playback -and recording, Android boot/capture/stop, hidden-viewer pointer isolation and a seat-only Xbox -controller passed. Killing an MCP client cleared its lease in 406 ms and restored the display -before the next client acquired it. Both legacy and modern MCP audio formats passed against the -running seat. Package, installation and distribution checks are also part of the release workflow. -See the [validation record](https://github.com/skulitom/Anode/blob/v0.11.1/docs/RELEASE-READINESS.md) -for evidence and remaining coverage limits, including the untested live display reconnect fallback. +**Known issues.** A Chrome window's first observation can come back without the page, and the +page's controls sit deeper than the default `maxDepth` of 8: observe again with `maxDepth` 16. On +Windows' "Open File - Security Warning" dialog, `invoke` reports success without running anything: +focus the dialog with `seat_window`, then click. `seat_window` `raise` can leave a UWP app behind +the foreground window: use `focus`. `anode type` has no option for a slower pace. Fixes are planned +for a later release. + +**Validation.** All 97 quick checks and the documentation check passed, and Windows CI repeated +the build, packaging, installation and distribution checks. The live checks in a Windows 11 seat +and their limits are in the +[validation record](https://github.com/skulitom/Anode/blob/v0.11.2/docs/RELEASE-READINESS.md). +See the [changelog](https://github.com/skulitom/Anode/blob/v0.11.2/CHANGELOG.md#0112--2026-10-09) for every +change. ## Install or upgrade @@ -51,7 +65,7 @@ anode capabilities | Out-Host agent afterward. Other MCP clients can launch `anode.exe` with the argument `mcp`. **Upgrading:** save seat work, run `anode quit` (which closes every program in the seat), and close -agent sessions using Anode. Install 0.11.1, start Anode again and restart those agent sessions. +agent sessions using Anode. Install 0.11.2, start Anode again and restart those agent sessions. Run `anode configure` to refresh the installed skill. Requires 64-bit Windows 10/11 Pro, Enterprise or Education, or Windows Server with a Remote Desktop diff --git a/CHANGELOG.md b/CHANGELOG.md index efc74e6..ce5d8f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.11.2 — 2026-10-09 - **`set_value` checks that the value took.** On a Chrome drop-down list (`` fails instead of reporting success; the open list's observation lists each control once | it prints `"passed": true`; a warning that the open list's options weren't listed is a finding for a later release, not a failure | + | `test-development.ps1` | typing's new pace, with mouse, key and text delivery checked in a headed Chrome page | it ends without an error | + | File Explorer | `inspect` on an Explorer window, which failed in 0.11.1 | it lists the window's controls | + +4. **Release.** If every check passed, tag the candidate and push the tag from the candidate + checkout, whose `HEAD` is the candidate ([release process](../CONTRIBUTING.md#release-process), + step 6): + + ```powershell + git tag v0.11.2 HEAD + git push origin v0.11.2 + ``` + + The Release workflow builds, tests and publishes the release. After it: the package manifests + (step 7), and the Claude Desktop test of the workflow's MCPB bundle + ([release gate](../packaging/mcpb/README.md#claude-desktop-test-release-gate)), which lets the + registry list 0.11.2 as installable. The validation results go into this record then. + +### Covered without a live run + +The seat profile held on the other desktop (Restart Manager), requests queued behind a long +operation, packaged apps' folders and the uninstaller's list are covered by quick checks with +stand-ins and by CI's disposable installs, not by a live seat. Startup timings are in +[startup performance](STARTUP-PERFORMANCE.md). + +### Known and open, for a later release + +- A Chrome window's first observation can come back without the page, and page controls sit below + the default `maxDepth` of 8. Observe again with `maxDepth` 16. +- On Windows' "Open File - Security Warning" dialog, `invoke` on Run reports success, but nothing + runs. Focus the dialog with `seat_window`, then click. +- `seat_window` `raise` can leave a UWP window behind the foreground window. Use `focus`. +- `anode type` has no option for a slower pace (MCP's `seat_type` has `perCharMs`). +- An action cut short by its deadline answers "A task was canceled." without saying it timed out. +- In rare timing, `seat_job` can answer `finished: true` before it has returned the last of the + output. +- `anode.log` is never rotated ([privacy](PRIVACY.md)). + +## 0.11.1 release validation — 2 October 2026 **Scope: 0.11.1 release validation.** All open PRs were reviewed, corrected where necessary and merged after their Windows CI passed. The patch includes legacy MCP audio compatibility, shared @@ -6,7 +126,7 @@ adb server/forward protections, bounded emulator startup, confirmed display memo of leases owned by killed MCP processes. Publication remains gated by Windows CI and the Release workflow. -## Live validation — 2 October 2026 +### Live validation — 2 October 2026 The self-contained 0.11.1 candidate daemon and seat host ran together on Windows 11 Pro build 26200 in the separate `dev` channel, child session 3, initially 1280x720 at 100%. The user made @@ -31,7 +151,7 @@ Local logs, JSON results, screenshots, audio and the MCP integration harness are `artifacts/validation-0.11.1` (ignored by Git). The separate PR review record is under `artifacts/pr-review-2026-10-02`. -## Automated validation +### Automated validation - The candidate passed all **83 quick self-tests** and documentation checks. The environment self-test (`selftest --no-gamepad`) passed **86 checks**, adding screen capture, geometry and @@ -48,7 +168,7 @@ Local logs, JSON results, screenshots, audio and the MCP integration harness are - The .NET 10 runtime pin remains **10.0.12**, verified against Microsoft's release metadata on 2 October 2026. The package includes that runtime. -## Remaining coverage limits +### Remaining coverage limits - The live display reconnect fallback was not needed: every requested display applied live. Credential-dialog seats refuse reconnects that would need the password again. Failed, clamped, @@ -69,7 +189,7 @@ Local logs, JSON results, screenshots, audio and the MCP integration harness are Authenticode signature. No clean-machine Windows compatibility certification, long-running soak test or comprehensive security audit is claimed. -## Live validation — 28-29 September 2026 +### Live validation — 28-29 September 2026 The earlier 0.11.0 evidence, including Android Studio, seat-browser profile checks and diagnosis of the damaged Pixel 3a image, is preserved in the diff --git a/packaging/mcpb/manifest.json b/packaging/mcpb/manifest.json index b306b42..4b19a0c 100644 --- a/packaging/mcpb/manifest.json +++ b/packaging/mcpb/manifest.json @@ -2,7 +2,7 @@ "manifest_version": "0.3", "name": "anode", "display_name": "Anode background Windows desktop", - "version": "0.11.1", + "version": "0.11.2", "description": "Background Windows desktop for AI agents: native GUI automation, screenshots and app testing.", "long_description": "Runs apps in a Windows child session with its own screen, pointer and keyboard focus, so an agent can automate native UI, take screenshots and run headed app/browser tests while you keep working. Requires 64-bit Windows 10/11 Pro, Enterprise or Education (or Windows Server with an RDP host) and a one-time `anode setup` as administrator. Shares your files, account and network; it is not a security sandbox.", "author": { diff --git a/scripts/test-chrome-form.ps1 b/scripts/test-chrome-form.ps1 new file mode 100644 index 0000000..4211d06 --- /dev/null +++ b/scripts/test-chrome-form.ps1 @@ -0,0 +1,175 @@ +#requires -Version 5.1 +<# +.SYNOPSIS +Tests set_value and observation on a Chrome form in an already running Anode seat. +Uses a disposable local page and a temporary Chrome profile, never the seat's signed-in profile. +Never starts a seat or activates the parent viewer; it renews the lease you already hold. +.DESCRIPTION +Checks that set_value on a text field still succeeds and reads back within its 1.5-second wait, that +set_value on a drop-down list (

+

+

+ + +"@ + +function Call-Anode([string[]]$Arguments) { + if ($Arguments[0] -in @('windows','inspect','window','element','wait') -and $Arguments -notcontains '--json') { $Arguments += '--json' } + $resultText = (& $Anode @Arguments | Out-String) + if ($LASTEXITCODE -ne 0) { throw "Anode command failed: $($Arguments[0])" } + return $resultText | ConvertFrom-Json +} +# For a command that may fail on purpose: its exit code and everything it wrote. +function Try-Anode([string[]]$Arguments) { + $savedPreference = $ErrorActionPreference + try { + $ErrorActionPreference = 'Continue' + $text = (& $Anode @Arguments 2>&1 | Out-String) + return [pscustomobject]@{ ExitCode = $LASTEXITCODE; Text = $text } + } finally { $ErrorActionPreference = $savedPreference } +} +function Assert-That($Condition, [string]$Message) { if (-not $Condition) { throw $Message } } +function Find-Control($Observation, [string]$AutomationId) { @($Observation.elements | Where-Object { $_.automationId -eq $AutomationId }) } + +$status = Call-Anode @('status', '--json') +Assert-That ($status.state -eq 'ready' -and $status.session -ne $status.parentSession) 'An existing verified seat is required.' +# The caller supplies an existing lease; never acquire/start a seat from this opt-in test. +$null = Call-Anode @('lease', 'renew', '--ttl', '600') +$url = ([Uri]$page).AbsoluteUri +$launched = Call-Anode @('run', $Chrome, "--user-data-dir=$profileDirectory", '--no-first-run', '--no-default-browser-check', + '--force-renderer-accessibility', "--app=$url") +Assert-That ($launched.session -eq $status.session) 'Chrome launched in the wrong session.' +$windowId = $null +$chromePid = 0 +$chromeStarted = $null +$warnings = @() +try { + $deadline = (Get-Date).AddSeconds(20) + do { + $windows = @((Call-Anode @('windows', '--query', $title)).windows) + if ($windows.Count -eq 0) { Start-Sleep -Milliseconds 250 } + } while ($windows.Count -eq 0 -and (Get-Date) -lt $deadline) + Assert-That ($windows.Count -eq 1) "Chrome's window '$title' did not appear exactly once." + $windowId = $windows[0].windowId + $chromePid = [int]$windows[0].pid + $chrome = Get-Process -Id $chromePid + Assert-That ($chrome.SessionId -eq $status.session) 'Chrome window is outside the seat.' + $chromeStarted = $chrome.StartTime + # Page controls sit deep in Chrome's tree, and Chrome builds the tree only once something asks. + function Observe-Page { Call-Anode @('inspect', $windowId, '--max-depth', '16', '--max-elements', '500') } + $deadline = (Get-Date).AddSeconds(15) + do { + $observation = Observe-Page + $ready = (Find-Control $observation 'name').Count -gt 0 -and (Find-Control $observation 'plan').Count -gt 0 + if (-not $ready) { Start-Sleep -Milliseconds 500 } + } while (-not $ready -and (Get-Date) -lt $deadline) + Assert-That $ready "The page's fields never appeared in Chrome's accessibility tree." + + # A text field: set_value succeeds only once the field reads the new value back. + $field = (Find-Control $observation 'name')[0] + Assert-That ($field.actions -contains 'set_value') 'The text field offers no set_value.' + $watch = [Diagnostics.Stopwatch]::StartNew() + $null = Call-Anode @('element', $observation.snapshotId, $field.id, 'set_value', '--value', 'Sam Example') + $watch.Stop() + $fieldMilliseconds = $watch.ElapsedMilliseconds + $observation = Observe-Page + $field = (Find-Control $observation 'name')[0] + Assert-That ($field.text -eq 'Sam Example') "The text field reads '$($field.text)' after set_value." + + # A drop-down list: Chrome may ignore set_value, which must then fail instead of reporting success. + $plan = (Find-Control $observation 'plan')[0] + Assert-That ($plan.text -eq 'Free') "The drop-down list starts at '$($plan.text)', not Free." + $attempt = Try-Anode @('element', $observation.snapshotId, $plan.id, 'set_value', '--value', 'Team') + $observation = Observe-Page + $plan = (Find-Control $observation 'plan')[0] + $result = (Find-Control $observation 'result')[0] + if ($attempt.ExitCode -eq 0) { + Assert-That ($plan.text -eq 'Team' -and $result.text -eq 'Plan: Team') 'set_value on the drop-down list reported success, but the choice did not change.' + $dropDown = 'applied' + } else { + Assert-That ($attempt.Text -match 'still showed its old value') "set_value on the drop-down list failed for another reason: $($attempt.Text.Trim())" + Assert-That ($plan.text -eq 'Free' -and $result.text -eq 'Plan: Free') 'set_value on the drop-down list failed, but the choice changed.' + $dropDown = 'refused' + } + + # The open list: each control once, and the options if Chrome lists them. + Assert-That ($plan.actions -contains 'expand') 'The drop-down list offers no expand.' + $null = Call-Anode @('element', $observation.snapshotId, $plan.id, 'expand') + Start-Sleep -Milliseconds 500 + $open = Observe-Page + $copies = (Find-Control $open 'name').Count + Assert-That ($copies -eq 1) "With the list open, the observation listed the text field $copies times." + $skippedRepeats = 0 + if ($open.PSObject.Properties['skippedRepeats']) { $skippedRepeats = [int]$open.skippedRepeats } + $target = if ($dropDown -eq 'applied') { 'Enterprise' } else { 'Team' } + $option = @($open.elements | Where-Object { $_.role -eq 'ListItem' -and $_.name -eq $target }) | Select-Object -First 1 + $optionChosen = $false + if (-not $option) { + $warnings += "The open list's options were not in the observation; choose an option with seat_screenshot and seat_click." + } else { + $action = @('select', 'invoke') | Where-Object { $option.actions -contains $_ } | Select-Object -First 1 + if (-not $action) { + $warnings += "The option '$target' was listed without select or invoke." + } else { + $null = Call-Anode @('element', $open.snapshotId, $option.id, $action) + $chosen = Try-Anode @('wait', $windowId, '--automation-id', 'result', '--text', "Plan: $target", '--wait', '5000', '--json') + $optionChosen = $chosen.ExitCode -eq 0 + if (-not $optionChosen) { $warnings += "'$action' on the option '$target' did not change the choice within 5 seconds." } + } + } + $finalStatus = Call-Anode @('status', '--json') + Assert-That ($finalStatus.session -eq $status.session -and $finalStatus.viewerVisible -eq $status.viewerVisible) 'Seat or parent viewer state changed.' + foreach ($warning in $warnings) { Write-Warning $warning } + [pscustomobject]@{ + passed = $true; session = $status.session; chrome = $Chrome; chromeVersion = (Get-Item -LiteralPath $Chrome).VersionInfo.ProductVersion + textFieldSetValueMs = $fieldMilliseconds; dropDownSetValue = $dropDown; openListSkippedRepeats = $skippedRepeats + optionsListed = [bool]$option; optionChosen = $optionChosen; warnings = $warnings + checks = @('page in the accessibility tree', 'text field set_value read back', 'drop-down set_value changes the choice or fails', + 'open list observed without repeats', 'parent viewer unchanged') + } | ConvertTo-Json -Depth 5 | Tee-Object -FilePath (Join-Path $OutputDirectory 'result.json') +} finally { + if ($windowId) { & $Anode window $windowId close | Out-Host } + $remaining = if ($chromePid) { Get-Process -Id $chromePid -ErrorAction SilentlyContinue } + if ($remaining -and $remaining.SessionId -eq $status.session -and $remaining.StartTime -eq $chromeStarted) { + if (-not $remaining.WaitForExit(5000)) { & $Anode ps kill $chromePid | Out-Host } + } + # Chrome's helper processes can hold the profile for a moment after the browser exits. + for ($try = 0; $try -lt 10 -and (Test-Path -LiteralPath $profileDirectory); $try++) { + try { Remove-Item -LiteralPath $profileDirectory -Recurse -Force -ErrorAction Stop } catch { Start-Sleep -Milliseconds 500 } + } + if (Test-Path -LiteralPath $profileDirectory) { Write-Warning "The temporary Chrome profile is still at ${profileDirectory}; delete it once Chrome has exited." } +} diff --git a/server.json b/server.json index a0f8836..09859e9 100644 --- a/server.json +++ b/server.json @@ -3,7 +3,7 @@ "name": "io.github.skulitom/anode", "title": "Anode background Windows desktop", "description": "Background Windows desktop for AI agents: native GUI automation, screenshots and app testing.", - "version": "0.11.1", + "version": "0.11.2", "websiteUrl": "https://github.com/skulitom/Anode/blob/main/docs/INSTALL.md", "icons": [ { diff --git a/src/Anode/Anode.csproj b/src/Anode/Anode.csproj index 2372be4..c4de0e2 100644 --- a/src/Anode/Anode.csproj +++ b/src/Anode/Anode.csproj @@ -12,7 +12,7 @@ true anode Anode - 0.11.1 + 0.11.2 Anode Anode Anode background Windows desktop From e89e79d7c210ba3da5fc69232cf211c874cf1c7d Mon Sep 17 00:00:00 2001 From: skulitom Date: Fri, 9 Oct 2026 09:44:14 +0100 Subject: [PATCH 2/4] [compass] 0.11.2 candidate: review fixes - test-chrome-form.ps1: count Find-Control's results through @(...), since Windows PowerShell 5.1 gives a single PSCustomObject no Count; start Chrome inside the try, and clean up every Chrome process it started. - RELEASE_NOTES: link the validation record on main, where the results are added after the release. - RELEASE-READINESS: say which PRs are in scope; stop if no File Explorer window appeared. - README: the 0.11.1 known issues now say 0.11.2 fixes them, which stays true inside the 0.11.2 package. Co-Authored-By: Claude Opus 5.5 --- .github/RELEASE_NOTES.md | 6 ++--- README.md | 4 ++-- docs/RELEASE-READINESS.md | 4 +++- scripts/test-chrome-form.ps1 | 44 +++++++++++++++++++++--------------- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/.github/RELEASE_NOTES.md b/.github/RELEASE_NOTES.md index 479cada..c9dacf6 100644 --- a/.github/RELEASE_NOTES.md +++ b/.github/RELEASE_NOTES.md @@ -33,9 +33,9 @@ the foreground window: use `focus`. `anode type` has no option for a slower pace for a later release. **Validation.** All 97 quick checks and the documentation check passed, and Windows CI repeated -the build, packaging, installation and distribution checks. The live checks in a Windows 11 seat -and their limits are in the -[validation record](https://github.com/skulitom/Anode/blob/v0.11.2/docs/RELEASE-READINESS.md). +the build, packaging, installation and distribution checks. The live checks in a Windows 11 seat, +their results and their limits are in the +[validation record](https://github.com/skulitom/Anode/blob/main/docs/RELEASE-READINESS.md). See the [changelog](https://github.com/skulitom/Anode/blob/v0.11.2/CHANGELOG.md#0112--2026-10-09) for every change. diff --git a/README.md b/README.md index 651c640..41caeba 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,10 @@ See [Install](#install) for custom locations and offline installs, or **Ctrl+Alt+Shift+K** stops it (if another program holds that shortcut, use **Stop seat** in the viewer or `anode kill`). Unsigned builds may trigger SmartScreen. No telemetry. - + ### Known issues in 0.11.1 -Two bugs in 0.11.1 have fixes waiting for the next release. Until then: +Two bugs in 0.11.1 are fixed in 0.11.2. On 0.11.1: - **`seat_observe` fails on Windows 11 File Explorer windows** with "Object reference not set to an instance of an object", and so does `seat_wait`. Use `seat_screenshot` and `seat_click` on those diff --git a/docs/RELEASE-READINESS.md b/docs/RELEASE-READINESS.md index 5ee31e0..24390a1 100644 --- a/docs/RELEASE-READINESS.md +++ b/docs/RELEASE-READINESS.md @@ -2,7 +2,8 @@ ## 0.11.2 candidate — 9 October 2026 -**Scope:** the bug fixes merged since 0.11.1 (#10 to #29) and the startup changes in +**Scope:** the fixes merged since 0.11.1 (pull requests #10 to #29, except those still open, such +as #19, the Business edition's terms) and the startup changes in [startup performance](STARTUP-PERFORMANCE.md). No new tools or commands; the tool count stays 41. The candidate is the commit that sets the version to 0.11.2. Fixes merged after it are not in it. The changelog dates 0.11.2 to 9 October, when its contents were fixed. @@ -72,6 +73,7 @@ closes every program in it), and the .NET 10 SDK, Node.js and Chrome installed. anode run explorer.exe C:\Windows | Out-Host Start-Sleep -Seconds 2 $explorer = @((anode windows --query Windows --json | ConvertFrom-Json).windows | Where-Object process -eq 'explorer')[0] + if (-not $explorer) { throw 'No File Explorer window in the seat yet: wait a moment, then set $explorer again.' } anode inspect $explorer.windowId | Out-Host anode window $explorer.windowId close | Out-Host anode lease release | Out-Host diff --git a/scripts/test-chrome-form.ps1 b/scripts/test-chrome-form.ps1 index 4211d06..4bd4495 100644 --- a/scripts/test-chrome-form.ps1 +++ b/scripts/test-chrome-form.ps1 @@ -70,14 +70,21 @@ Assert-That ($status.state -eq 'ready' -and $status.session -ne $status.parentSe # The caller supplies an existing lease; never acquire/start a seat from this opt-in test. $null = Call-Anode @('lease', 'renew', '--ttl', '600') $url = ([Uri]$page).AbsoluteUri -$launched = Call-Anode @('run', $Chrome, "--user-data-dir=$profileDirectory", '--no-first-run', '--no-default-browser-check', - '--force-renderer-accessibility', "--app=$url") -Assert-That ($launched.session -eq $status.session) 'Chrome launched in the wrong session.' $windowId = $null -$chromePid = 0 -$chromeStarted = $null +# The Chrome processes this test started, by id and start time, so cleanup never touches another one. +$owned = @() +function Add-Owned([int]$ProcessId) { + $process = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue + if ($process -and $process.SessionId -eq $status.session -and -not ($script:owned | Where-Object { $_.Id -eq $ProcessId })) { + $script:owned += [pscustomobject]@{ Id = $ProcessId; Started = $process.StartTime } + } +} $warnings = @() try { + $launched = Call-Anode @('run', $Chrome, "--user-data-dir=$profileDirectory", '--no-first-run', '--no-default-browser-check', + '--force-renderer-accessibility', "--app=$url") + Assert-That ($launched.session -eq $status.session) 'Chrome launched in the wrong session.' + Add-Owned ([int]$launched.pid) $deadline = (Get-Date).AddSeconds(20) do { $windows = @((Call-Anode @('windows', '--query', $title)).windows) @@ -85,38 +92,37 @@ try { } while ($windows.Count -eq 0 -and (Get-Date) -lt $deadline) Assert-That ($windows.Count -eq 1) "Chrome's window '$title' did not appear exactly once." $windowId = $windows[0].windowId - $chromePid = [int]$windows[0].pid - $chrome = Get-Process -Id $chromePid + $chrome = Get-Process -Id ([int]$windows[0].pid) Assert-That ($chrome.SessionId -eq $status.session) 'Chrome window is outside the seat.' - $chromeStarted = $chrome.StartTime + Add-Owned $chrome.Id # Page controls sit deep in Chrome's tree, and Chrome builds the tree only once something asks. function Observe-Page { Call-Anode @('inspect', $windowId, '--max-depth', '16', '--max-elements', '500') } $deadline = (Get-Date).AddSeconds(15) do { $observation = Observe-Page - $ready = (Find-Control $observation 'name').Count -gt 0 -and (Find-Control $observation 'plan').Count -gt 0 + $ready = @(Find-Control $observation 'name').Count -gt 0 -and @(Find-Control $observation 'plan').Count -gt 0 if (-not $ready) { Start-Sleep -Milliseconds 500 } } while (-not $ready -and (Get-Date) -lt $deadline) Assert-That $ready "The page's fields never appeared in Chrome's accessibility tree." # A text field: set_value succeeds only once the field reads the new value back. - $field = (Find-Control $observation 'name')[0] + $field = @(Find-Control $observation 'name')[0] Assert-That ($field.actions -contains 'set_value') 'The text field offers no set_value.' $watch = [Diagnostics.Stopwatch]::StartNew() $null = Call-Anode @('element', $observation.snapshotId, $field.id, 'set_value', '--value', 'Sam Example') $watch.Stop() $fieldMilliseconds = $watch.ElapsedMilliseconds $observation = Observe-Page - $field = (Find-Control $observation 'name')[0] + $field = @(Find-Control $observation 'name')[0] Assert-That ($field.text -eq 'Sam Example') "The text field reads '$($field.text)' after set_value." # A drop-down list: Chrome may ignore set_value, which must then fail instead of reporting success. - $plan = (Find-Control $observation 'plan')[0] + $plan = @(Find-Control $observation 'plan')[0] Assert-That ($plan.text -eq 'Free') "The drop-down list starts at '$($plan.text)', not Free." $attempt = Try-Anode @('element', $observation.snapshotId, $plan.id, 'set_value', '--value', 'Team') $observation = Observe-Page - $plan = (Find-Control $observation 'plan')[0] - $result = (Find-Control $observation 'result')[0] + $plan = @(Find-Control $observation 'plan')[0] + $result = @(Find-Control $observation 'result')[0] if ($attempt.ExitCode -eq 0) { Assert-That ($plan.text -eq 'Team' -and $result.text -eq 'Plan: Team') 'set_value on the drop-down list reported success, but the choice did not change.' $dropDown = 'applied' @@ -131,7 +137,7 @@ try { $null = Call-Anode @('element', $observation.snapshotId, $plan.id, 'expand') Start-Sleep -Milliseconds 500 $open = Observe-Page - $copies = (Find-Control $open 'name').Count + $copies = @(Find-Control $open 'name').Count Assert-That ($copies -eq 1) "With the list open, the observation listed the text field $copies times." $skippedRepeats = 0 if ($open.PSObject.Properties['skippedRepeats']) { $skippedRepeats = [int]$open.skippedRepeats } @@ -163,9 +169,11 @@ try { } | ConvertTo-Json -Depth 5 | Tee-Object -FilePath (Join-Path $OutputDirectory 'result.json') } finally { if ($windowId) { & $Anode window $windowId close | Out-Host } - $remaining = if ($chromePid) { Get-Process -Id $chromePid -ErrorAction SilentlyContinue } - if ($remaining -and $remaining.SessionId -eq $status.session -and $remaining.StartTime -eq $chromeStarted) { - if (-not $remaining.WaitForExit(5000)) { & $Anode ps kill $chromePid | Out-Host } + foreach ($item in $owned) { + $remaining = Get-Process -Id $item.Id -ErrorAction SilentlyContinue + if ($remaining -and $remaining.SessionId -eq $status.session -and $remaining.StartTime -eq $item.Started) { + if (-not $remaining.WaitForExit(5000)) { & $Anode ps kill $item.Id | Out-Host } + } } # Chrome's helper processes can hold the profile for a moment after the browser exits. for ($try = 0; $try -lt 10 -and (Test-Path -LiteralPath $profileDirectory); $try++) { From 52068c647e6bd32ca0c8f940445dba35f16b5ffe Mon Sep 17 00:00:00 2001 From: skulitom Date: Sat, 10 Oct 2026 00:40:56 +0100 Subject: [PATCH 3/4] docs: align pinned installer example with 0.11.2 --- docs/INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 2da413f..60e12b4 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -37,7 +37,7 @@ Useful options: ```powershell # Choose a version and register installed Codex/Claude Code CLIs as well. -powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 -Version 0.11.1 -Client Auto +powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 -Version 0.11.2 -Client Auto # A dedicated custom folder; leave PATH and the Start menu alone. powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 -InstallDirectory C:\Tools\Anode -NoPath -NoShortcut ``` From a71fe1c8a32387898b69b4fefffd0b7c05773cdb Mon Sep 17 00:00:00 2001 From: skulitom Date: Sat, 10 Oct 2026 09:18:54 +0100 Subject: [PATCH 4/4] [compass] 0.11.2 candidate: fix test-chrome-form's Chrome variable; add #31 and #32 - scripts/test-chrome-form.ps1: the window's process went into $chrome, which in PowerShell is the same variable as the -Chrome parameter, so the session check always failed and the final report named a process instead of chrome.exe. It is now $chromeProcess. - #31 (B-025) and #32 (B-023) merged before this candidate, so they ship in 0.11.2: the release notes list both, RELEASE-READINESS drops them from "Known and open", and the check count is 99. - The changelog, readiness record and release-notes anchor date 0.11.2 to 10 October, when its contents were fixed again. Co-Authored-By: Claude Opus 5.5 --- .github/RELEASE_NOTES.md | 10 ++++++++-- CHANGELOG.md | 2 +- docs/RELEASE-READINESS.md | 17 +++++++---------- scripts/test-chrome-form.ps1 | 6 +++--- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/.github/RELEASE_NOTES.md b/.github/RELEASE_NOTES.md index c9dacf6..883dca6 100644 --- a/.github/RELEASE_NOTES.md +++ b/.github/RELEASE_NOTES.md @@ -15,6 +15,12 @@ This patch fixes bugs found after 0.11.1, most of them by AI agents using Anode its drop-down lists was open. Both are fixed; a control listed twice now appears once. - **Queued requests keep their deadline.** A request that waited behind a long operation, such as typing, could lose its reply and leave the seat `detached`. It now answers, or fails, in time. +- **Running out of time is reported as such.** A desktop action stopped at its deadline answered + with a bare "A task was canceled." It now fails with `errorCode: "timed_out"` and says whether + anything ran (`started`). +- **A finished job's reply holds all its output.** `seat_job` and `anode job` could answer + `finished: true` with the end of the command's output missing. `finished: true` now comes only + with the output to its end. - **Browser sign-in is clearer.** `anode browser --sign-in` explains its window in its first tab and opens several addresses. When the seat's profile is open on the other desktop, `seat_browser` and `--sign-in` refuse and name the process holding it, instead of reporting a window. @@ -32,11 +38,11 @@ focus the dialog with `seat_window`, then click. `seat_window` `raise` can leave the foreground window: use `focus`. `anode type` has no option for a slower pace. Fixes are planned for a later release. -**Validation.** All 97 quick checks and the documentation check passed, and Windows CI repeated +**Validation.** All 99 quick checks and the documentation check passed, and Windows CI repeated the build, packaging, installation and distribution checks. The live checks in a Windows 11 seat, their results and their limits are in the [validation record](https://github.com/skulitom/Anode/blob/main/docs/RELEASE-READINESS.md). -See the [changelog](https://github.com/skulitom/Anode/blob/v0.11.2/CHANGELOG.md#0112--2026-10-09) for every +See the [changelog](https://github.com/skulitom/Anode/blob/v0.11.2/CHANGELOG.md#0112--2026-10-10) for every change. ## Install or upgrade diff --git a/CHANGELOG.md b/CHANGELOG.md index edbaecb..ab121bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.11.2 — 2026-10-09 +## 0.11.2 — 2026-10-10 - **A finished job's reply holds all its output.** `seat_job` and `anode job` could answer `finished: true` with the end of the command's output missing, when the command ended while the reply was being put diff --git a/docs/RELEASE-READINESS.md b/docs/RELEASE-READINESS.md index 24390a1..1e3a2ff 100644 --- a/docs/RELEASE-READINESS.md +++ b/docs/RELEASE-READINESS.md @@ -1,25 +1,25 @@ # Release readiness -## 0.11.2 candidate — 9 October 2026 +## 0.11.2 candidate — 10 October 2026 -**Scope:** the fixes merged since 0.11.1 (pull requests #10 to #29, except those still open, such +**Scope:** the fixes merged since 0.11.1 (pull requests #10 to #32, except those still open, such as #19, the Business edition's terms) and the startup changes in [startup performance](STARTUP-PERFORMANCE.md). No new tools or commands; the tool count stays 41. The candidate is the commit that sets the version to 0.11.2. Fixes merged after it are not in it. -The changelog dates 0.11.2 to 9 October, when its contents were fixed. +The changelog dates 0.11.2 to 10 October, when its contents were fixed. ### Already checked, without a seat -- The candidate's tree (`main` at `a504b9a` plus the version change): all **97 quick checks** and - the documentation check passed locally on 9 October, and so did the release-mode distribution +- The candidate's tree (`main` at `98173de` plus the version change): all **99 quick checks** and + the documentation check passed locally on 10 October, and so did the release-mode distribution check (`test-distribution.ps1 -Release`) on its package, MCPB bundle and registry entry. Windows - CI passed on `a504b9a`: Release build, packaging and the MCPB bundle, registry-entry validation, + CI passed on `98173de`: Release build, packaging and the MCPB bundle, registry-entry validation, quick self-tests, disposable install, update, rollback and uninstall, distribution and documentation checks. CI repeats them on the candidate. - Code fixes came with regression checks, in the quick suite or in CI's install tests, and each pull request had a second, independent review before it merged. - The .NET runtime pin is still the current 10.0 patch, **10.0.12** (Microsoft's release metadata, - 9 October 2026). If a newer 10.0 patch is out when you tag, update `RuntimeFrameworkVersion` first. + 10 October 2026). If a newer 10.0 patch is out when you tag, update `RuntimeFrameworkVersion` first. - Publishing: merging the version change doesn't publish anything. Anode is already listed in the MCP Registry (0.11.0, metadata only), so `publish-registry.yml` skips pushes to `main`; 0.11.2's installable entry follows the release and the Claude Desktop bundle test @@ -115,9 +115,6 @@ stand-ins and by CI's disposable installs, not by a live seat. Startup timings a runs. Focus the dialog with `seat_window`, then click. - `seat_window` `raise` can leave a UWP window behind the foreground window. Use `focus`. - `anode type` has no option for a slower pace (MCP's `seat_type` has `perCharMs`). -- An action cut short by its deadline answers "A task was canceled." without saying it timed out. -- In rare timing, `seat_job` can answer `finished: true` before it has returned the last of the - output. - `anode.log` is never rotated ([privacy](PRIVACY.md)). ## 0.11.1 release validation — 2 October 2026 diff --git a/scripts/test-chrome-form.ps1 b/scripts/test-chrome-form.ps1 index 4bd4495..1375030 100644 --- a/scripts/test-chrome-form.ps1 +++ b/scripts/test-chrome-form.ps1 @@ -92,9 +92,9 @@ try { } while ($windows.Count -eq 0 -and (Get-Date) -lt $deadline) Assert-That ($windows.Count -eq 1) "Chrome's window '$title' did not appear exactly once." $windowId = $windows[0].windowId - $chrome = Get-Process -Id ([int]$windows[0].pid) - Assert-That ($chrome.SessionId -eq $status.session) 'Chrome window is outside the seat.' - Add-Owned $chrome.Id + $chromeProcess = Get-Process -Id ([int]$windows[0].pid) + Assert-That ($chromeProcess.SessionId -eq $status.session) 'Chrome window is outside the seat.' + Add-Owned $chromeProcess.Id # Page controls sit deep in Chrome's tree, and Chrome builds the tree only once something asks. function Observe-Page { Call-Anode @('inspect', $windowId, '--max-depth', '16', '--max-elements', '500') } $deadline = (Get-Date).AddSeconds(15)