From 616432b026b07854b11995d2528c1a76121ed4fc Mon Sep 17 00:00:00 2001 From: DeepSeekEyes Builder Date: Wed, 19 Aug 2026 13:44:42 +0800 Subject: [PATCH 1/3] fix: restore Windows UI Automation elements --- .github/workflows/ci.yml | 18 ++++++++++++++++++ CHANGELOG.md | 5 +++++ lib/client.js | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/desktop/helpers/windows.ps1 | 5 ++++- tests/desktop-driver.test.js | 2 ++ 7 files changed, 33 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c10b23e..6995469 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,24 @@ jobs: exit 1 } Write-Host 'WINDOWS_HELPER_PARSE_OK' + - name: Exercise Windows UIA list materialization + if: runner.os == 'Windows' + shell: powershell + run: | + $output = New-Object System.Collections.Generic.List[object] + $output.Add([pscustomobject]@{ role = 'window'; name = 'fixture' }) | Out-Null + $elements = @($output.ToArray()) + if ($elements.Count -ne 1 -or $elements[0].name -ne 'fixture') { + throw 'Windows PowerShell failed to materialize the UIA Generic.List[object]' + } + $helper = Get-Content 'src/desktop/helpers/windows.ps1' -Raw + if ($helper -notmatch 'return\s+\$output\.ToArray\(\)') { + throw 'Windows helper does not use the verified list materialization path' + } + if ($helper -match 'return\s+@\(\$output\)') { + throw 'Windows helper still contains the PowerShell 5.1 incompatible return path' + } + Write-Host 'WINDOWS_UIA_LIST_MATERIALIZATION_OK' - name: Compile macOS native helper if: runner.os == 'macOS' run: /usr/bin/osacompile -l JavaScript -o "$RUNNER_TEMP/deepseekeyes-macos-helper.scpt" src/desktop/helpers/macos.jxa diff --git a/CHANGELOG.md b/CHANGELOG.md index c01b77e..9a125c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.5.9 - 2026-08-19 + +- Fix Windows PowerShell 5.1 UI Automation observations returning an empty semantic tree by materializing `Generic.List[object]` with `ToArray()` before returning it through the PowerShell pipeline. +- Add source-level regression coverage and a Windows PowerShell CI smoke test for the exact list-materialization failure reported in Issue #1. + ## 0.5.8 - 2026-08-17 - Make desktop `type` target-bound: require an `elementRef` or complete screenshot coordinates, while retaining an explicit `allowFocusedTarget` compatibility escape hatch. diff --git a/lib/client.js b/lib/client.js index a7b56f8..ca37441 100644 --- a/lib/client.js +++ b/lib/client.js @@ -244,7 +244,7 @@ function providerVisionMutation(namespaces, target, enabled) { var import_jsx_runtime = require("react/jsx-runtime"); var NS = "deepseekeyes.settings"; var PROVIDER_ID = "deepseekeyes"; -var PLUGIN_VERSION = "0.5.8"; +var PLUGIN_VERSION = "0.5.9"; var zh = { title: "DeepSeekEyes", version: "\u7248\u672C", diff --git a/package-lock.json b/package-lock.json index f033805..d954aa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dttxorg/deepseekeyes", - "version": "0.5.8", + "version": "0.5.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dttxorg/deepseekeyes", - "version": "0.5.8", + "version": "0.5.9", "license": "MIT", "dependencies": { "@deepseek-ai/schemastery": "3.18.1", diff --git a/package.json b/package.json index 51c76f4..009ac6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dttxorg/deepseekeyes", - "version": "0.5.8", + "version": "0.5.9", "description": "Auditable vision and cross-platform Computer Use runtime for DeepSeek Harness with source-preserving evidence.", "type": "module", "main": "./dsh/index.js", diff --git a/src/desktop/helpers/windows.ps1 b/src/desktop/helpers/windows.ps1 index 2874619..dcbdbaa 100644 --- a/src/desktop/helpers/windows.ps1 +++ b/src/desktop/helpers/windows.ps1 @@ -325,7 +325,10 @@ function Get-AutomationElements($Window, [int]$Maximum) { if ($null -eq $root) { return @() } $output = New-Object System.Collections.Generic.List[object] Add-AutomationChildren $root $Window @() 0 $Maximum $output ([System.Windows.Automation.TreeWalker]::RawViewWalker) - return @($output) + # Windows PowerShell 5.1 cannot array-wrap Generic.List[object] + # directly (ArgumentException: "Argument types do not match"). + # Materialize the list first so callers receive the collected UIA tree. + return $output.ToArray() } catch { return @() } } diff --git a/tests/desktop-driver.test.js b/tests/desktop-driver.test.js index 496fea3..f564176 100644 --- a/tests/desktop-driver.test.js +++ b/tests/desktop-driver.test.js @@ -116,6 +116,8 @@ test('packaged helpers retain both native platforms and avoid the macOS CFReleas assert.match(windows, /SendUnicode/) assert.match(windows, /System\.Windows\.Automation/) assert.match(windows, /Get-AutomationElements/) + assert.match(windows, /return \$output\.ToArray\(\)/) + assert.doesNotMatch(windows, /return @\(\$output\)/) assert.match(windows, /\$elementCount = @\(\$elements\)\.Count/) assert.match(windows, /public static void Scroll/) assert.match(windows, /\[Console\]::InputEncoding = \$script:DeepSeekEyesUtf8/) From e2af2fba8a3021b77d4c3c7e764c941ead250e44 Mon Sep 17 00:00:00 2001 From: DeepSeekEyes Builder Date: Wed, 19 Aug 2026 13:48:53 +0800 Subject: [PATCH 2/3] fix: absorb late native helper pipe errors --- CHANGELOG.md | 1 + src/desktop/native-runner.js | 11 +++++++++++ tests/desktop-driver.test.js | 27 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a125c9..59e7fe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix Windows PowerShell 5.1 UI Automation observations returning an empty semantic tree by materializing `Generic.List[object]` with `ToArray()` before returning it through the PowerShell pipeline. - Add source-level regression coverage and a Windows PowerShell CI smoke test for the exact list-materialization failure reported in Issue #1. +- Prevent a helper's late asynchronous stdin `EPIPE` from escaping after its authoritative close/result event, removing the cross-platform native-runner CI race exposed while validating this patch. ## 0.5.8 - 2026-08-17 diff --git a/src/desktop/native-runner.js b/src/desktop/native-runner.js index 6971e21..1ca938f 100644 --- a/src/desktop/native-runner.js +++ b/src/desktop/native-runner.js @@ -56,6 +56,17 @@ export function runNativeJson(command, args, input, options = {}) { `native desktop helper failed to start: ${errorMessage(error)}`, 'DESKTOP_HELPER_START_FAILED', ))) + child.stdin.on('error', error => { + // A helper can exit after producing its result while Node is still + // completing the stdin write. Broken-pipe errors are then followed by + // the authoritative child close event and must not escape asynchronously. + if (settled || error?.code === 'EPIPE' || error?.code === 'ERR_STREAM_DESTROYED') return + terminate() + finish(new DeepSeekEyesError( + `native desktop helper input failed: ${errorMessage(error)}`, + 'DESKTOP_HELPER_INPUT_FAILED', + )) + }) child.stdout.on('data', chunk => { stdoutBytes += chunk.length if (stdoutBytes > MAX_OUTPUT_BYTES) { diff --git a/tests/desktop-driver.test.js b/tests/desktop-driver.test.js index f564176..23a10a9 100644 --- a/tests/desktop-driver.test.js +++ b/tests/desktop-driver.test.js @@ -1,6 +1,8 @@ import assert from 'node:assert/strict' +import { EventEmitter } from 'node:events' import { readFile, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' +import { PassThrough } from 'node:stream' import test from 'node:test' import { resolveConfig } from '../src/config.js' import { NativeDesktopDriver, runNativeJson } from '../src/desktop/index.js' @@ -70,6 +72,31 @@ test('native JSON runner keeps input on stdin and validates helper output', asyn ) }) +test('native JSON runner absorbs a late broken pipe after the helper closes', async () => { + const child = new EventEmitter() + child.stdin = new PassThrough() + child.stdout = new PassThrough() + child.stderr = new PassThrough() + child.kill = () => {} + const end = child.stdin.end.bind(child.stdin) + child.stdin.end = (...args) => { + end(...args) + queueMicrotask(() => { + child.stdout.end(JSON.stringify({ ok: true, source: 'fixture' })) + child.emit('close', 0, null) + const error = new Error('write EPIPE') + error.code = 'EPIPE' + child.stdin.emit('error', error) + }) + } + + const result = await runNativeJson('fixture', [], { action: 'observe' }, { + spawnImpl: () => child, + }) + assert.equal(result.source, 'fixture') + await new Promise(resolve => setImmediate(resolve)) +}) + test('packaged helpers retain both native platforms and avoid the macOS CFRelease crash', async () => { const mac = await readFile(new URL('../src/desktop/helpers/macos.jxa', import.meta.url), 'utf8') const windows = await readFile(new URL('../src/desktop/helpers/windows.ps1', import.meta.url), 'utf8') From 99870003ff27292eb80fdde1957f01e6cbd92263 Mon Sep 17 00:00:00 2001 From: DeepSeekEyes Builder Date: Wed, 19 Aug 2026 13:52:27 +0800 Subject: [PATCH 3/3] fix: normalize non-finite Windows UIA bounds --- CHANGELOG.md | 2 ++ acceptance/desktop-native.mjs | 4 ++++ src/desktop/helpers/windows.ps1 | 33 +++++++++++++++++++++++++++------ tests/desktop-driver.test.js | 3 +++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59e7fe0..57859ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,9 @@ ## 0.5.9 - 2026-08-19 - Fix Windows PowerShell 5.1 UI Automation observations returning an empty semantic tree by materializing `Generic.List[object]` with `ToArray()` before returning it through the PowerShell pipeline. +- Preserve UIA elements whose virtual/offscreen bounds are non-finite while omitting only those unusable bounds, preventing PowerShell's bare `Infinity` token from corrupting native JSON. - Add source-level regression coverage and a Windows PowerShell CI smoke test for the exact list-materialization failure reported in Issue #1. +- Require the Windows native window-observation acceptance test to return a real non-empty UI Automation tree. - Prevent a helper's late asynchronous stdin `EPIPE` from escaping after its authoritative close/result event, removing the cross-platform native-runner CI race exposed while validating this patch. ## 0.5.8 - 2026-08-17 diff --git a/acceptance/desktop-native.mjs b/acceptance/desktop-native.mjs index ace9888..184b252 100644 --- a/acceptance/desktop-native.mjs +++ b/acceptance/desktop-native.mjs @@ -111,6 +111,10 @@ if (target !== undefined) { assert.ok(windowResult.screen.width > 0) assert.ok(windowResult.screen.height > 0) assert.ok(Array.isArray(windowResult.elements)) + if (process.platform === 'win32') { + assert.ok(windowResult.elementTotal > 0, 'Windows UI Automation returned no window elements') + assert.ok(windowResult.elements.length > 0, 'Windows UI Automation element tree was not delivered') + } assert.equal(windowResult.stateDelta.fromStateId, windowSource.stateId) assert.equal(renderDesktopResult(windowResult).filter(block => block.type === 'image').length, windowResult.screenshot.tileCount) } diff --git a/src/desktop/helpers/windows.ps1 b/src/desktop/helpers/windows.ps1 index dcbdbaa..c1e7138 100644 --- a/src/desktop/helpers/windows.ps1 +++ b/src/desktop/helpers/windows.ps1 @@ -264,6 +264,13 @@ function Get-AutomationActions($Element) { return @($actions) } +function Test-FiniteDouble($Value) { + try { + $number = [double]$Value + return -not [double]::IsNaN($number) -and -not [double]::IsInfinity($number) + } catch { return $false } +} + function Convert-AutomationElement($Element, $Window, [int[]]$Path) { try { $current = $Element.Current @@ -277,7 +284,7 @@ function Convert-AutomationElement($Element, $Window, [int[]]$Path) { $value = if ($null -eq $valuePattern -or $current.IsPassword) { $null } else { [string]$valuePattern.Current.Value } $checked = if ($null -eq $togglePattern) { $null } else { [string]$togglePattern.Current.ToggleState -eq 'On' } $selected = if ($null -eq $selectionPattern) { $null } else { [bool]$selectionPattern.Current.IsSelected } - return [pscustomobject]@{ + $record = [ordered]@{ nativeId = "$($Window.NativeId):$runtime" windowNativeId = $Window.NativeId pid = [int]$current.ProcessId @@ -295,12 +302,22 @@ function Convert-AutomationElement($Element, $Window, [int[]]$Path) { editable = $null -ne $valuePattern -and -not [bool]$valuePattern.Current.IsReadOnly selected = $selected checked = $checked - x = [double]$rectangle.X - y = [double]$rectangle.Y - width = [double]$rectangle.Width - height = [double]$rectangle.Height actions = @(Get-AutomationActions $Element) } + # UIA uses infinite rectangles for some virtual/offscreen controls. + # PowerShell emits Infinity as a bare token, which is not valid JSON. + # Keep the semantic element and omit only its unusable pixel bounds. + $finiteBounds = (Test-FiniteDouble $rectangle.X) ` + -and (Test-FiniteDouble $rectangle.Y) ` + -and (Test-FiniteDouble $rectangle.Width) ` + -and (Test-FiniteDouble $rectangle.Height) + if ($finiteBounds) { + $record.Add('x', [double]$rectangle.X) + $record.Add('y', [double]$rectangle.Y) + $record.Add('width', [double]$rectangle.Width) + $record.Add('height', [double]$rectangle.Height) + } + return [pscustomobject]$record } catch { return $null } } @@ -358,7 +375,11 @@ function Resolve-AutomationElement($InputObject) { function Get-AutomationCenter($Element) { $rectangle = $Element.Current.BoundingRectangle - if ($rectangle.Width -le 0 -or $rectangle.Height -le 0) { throw 'accessibility element has no clickable bounds' } + $finiteBounds = (Test-FiniteDouble $rectangle.X) ` + -and (Test-FiniteDouble $rectangle.Y) ` + -and (Test-FiniteDouble $rectangle.Width) ` + -and (Test-FiniteDouble $rectangle.Height) + if (-not $finiteBounds -or $rectangle.Width -le 0 -or $rectangle.Height -le 0) { throw 'accessibility element has no clickable bounds' } $centerX = [int][Math]::Round([double]$rectangle.X + ([double]$rectangle.Width / 2)) $centerY = [int][Math]::Round([double]$rectangle.Y + ([double]$rectangle.Height / 2)) return [int[]]@($centerX, $centerY) diff --git a/tests/desktop-driver.test.js b/tests/desktop-driver.test.js index 23a10a9..bb80095 100644 --- a/tests/desktop-driver.test.js +++ b/tests/desktop-driver.test.js @@ -145,6 +145,9 @@ test('packaged helpers retain both native platforms and avoid the macOS CFReleas assert.match(windows, /Get-AutomationElements/) assert.match(windows, /return \$output\.ToArray\(\)/) assert.doesNotMatch(windows, /return @\(\$output\)/) + assert.match(windows, /function Test-FiniteDouble\(\$Value\)/) + assert.match(windows, /\$record\.Add\('x', \[double\]\$rectangle\.X\)/) + assert.doesNotMatch(windows, /x = \[double\]\$rectangle\.X/) assert.match(windows, /\$elementCount = @\(\$elements\)\.Count/) assert.match(windows, /public static void Scroll/) assert.match(windows, /\[Console\]::InputEncoding = \$script:DeepSeekEyesUtf8/)