param( [string]$HelperExe = "runs/win-helper/ISphereWinHelper.exe", [switch]$SkipBuild ) $ErrorActionPreference = "Stop" $repo = Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..") $helperPath = Join-Path $repo $HelperExe function Invoke-HelperJson([hashtable]$Request) { $json = $Request | ConvertTo-Json -Depth 8 -Compress $output = $json | & $helperPath --json if ($LASTEXITCODE -ne 0) { throw "helper exited with code $LASTEXITCODE" } try { return $output | ConvertFrom-Json } catch { throw "helper output was not JSON: $output" } } if (-not $SkipBuild) { & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $repo "scripts\build-win-helper.ps1") | Out-Host if ($LASTEXITCODE -ne 0) { throw "build-win-helper.ps1 failed with exit code $LASTEXITCODE" } } if (-not (Test-Path -LiteralPath $helperPath)) { throw "helper not found: $helperPath" } $version = Invoke-HelperJson @{ protocol = "isphere.helper.v1" request_id = "verify-version" op = "version" timeout_ms = 5000 args = @{} } if (-not $version.ok -or $version.data.helper_name -ne "ISphereWinHelper") { throw "version check failed: $($version | ConvertTo-Json -Depth 8 -Compress)" } $selfCheck = Invoke-HelperJson @{ protocol = "isphere.helper.v1" request_id = "verify-self-check" op = "self_check" timeout_ms = 5000 args = @{} } if (-not $selfCheck.ok) { throw "self_check failed: $($selfCheck | ConvertTo-Json -Depth 8 -Compress)" } $scan = Invoke-HelperJson @{ protocol = "isphere.helper.v1" request_id = "verify-scan" op = "scan_windows" timeout_ms = 5000 args = @{ include_all_visible = $true } } if (-not $scan.ok -or $null -eq $scan.data.windows) { throw "scan_windows failed: $($scan | ConvertTo-Json -Depth 8 -Compress)" } $dumpMissing = Invoke-HelperJson @{ protocol = "isphere.helper.v1" request_id = "verify-dump-missing" op = "dump_uia" timeout_ms = 5000 args = @{ hwnd = "0x0"; max_depth = 1; include_text = $true; max_children = 20 } } if ($dumpMissing.ok -or $dumpMissing.error.code -notin @("WINDOW_NOT_FOUND", "UIA_DUMP_FAILED")) { throw "dump_uia missing-window check failed: $($dumpMissing | ConvertTo-Json -Depth 8 -Compress)" } $title = "Codex WinHelper Verify " + [guid]::NewGuid().ToString("N").Substring(0, 8) $formScript = "Add-Type -AssemblyName System.Windows.Forms; `$form = New-Object System.Windows.Forms.Form; `$form.Text = '$title'; `$form.Width = 420; `$form.Height = 180; `$button = New-Object System.Windows.Forms.Button; `$button.Text = 'ProbeButton'; `$button.Dock = 'Fill'; `$form.Controls.Add(`$button); [void]`$form.ShowDialog()" $process = Start-Process powershell -ArgumentList @("-NoProfile", "-Command", $formScript) -PassThru try { $candidate = $null for ($i = 0; $i -lt 20 -and -not $candidate; $i++) { Start-Sleep -Milliseconds 500 $scanForForm = Invoke-HelperJson @{ protocol = "isphere.helper.v1" request_id = "verify-form-scan" op = "scan_windows" timeout_ms = 5000 args = @{ include_all_visible = $true } } $candidate = $scanForForm.data.windows | Where-Object { $_.title -eq $title } | Select-Object -First 1 } if (-not $candidate) { throw "verification form window not found" } $dump = Invoke-HelperJson @{ protocol = "isphere.helper.v1" request_id = "verify-form-dump" op = "dump_uia" timeout_ms = 5000 args = @{ hwnd = $candidate.hwnd; max_depth = 4; include_text = $true; max_children = 80 } } if (-not $dump.ok -or $dump.data.root.control_type -ne "Window") { throw "dump_uia form check failed: $($dump | ConvertTo-Json -Depth 12 -Compress)" } } finally { if ($process -and -not $process.HasExited) { Stop-Process -Id $process.Id -Force } } [ordered]@{ ok = $true helper = (Resolve-Path -LiteralPath $helperPath).Path version = $version.data.helper_version scan_window_count = $scan.data.windows.Count uia_available = $selfCheck.data.uia_available } | ConvertTo-Json -Depth 4 -Compress