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)" } $runtimeProbe = Invoke-HelperJson @{ protocol = "isphere.helper.v1" request_id = "verify-runtime-probe" op = "probe_client_runtime" timeout_ms = 5000 args = @{ process_names = @("IMPlatformClient", "IMPP.ISphere", "iSphere", "importal") include_modules = $false max_modules = 0 } } if (-not $runtimeProbe.ok -or $runtimeProbe.data.probe_mode -ne "read_only_process_module_inventory" -or $null -eq $runtimeProbe.data.targets) { throw "probe_client_runtime failed: $($runtimeProbe | ConvertTo-Json -Depth 8 -Compress)" } $entrypointProbe = Invoke-HelperJson @{ protocol = "isphere.helper.v1" request_id = "verify-send-entrypoints" op = "probe_send_entrypoints" timeout_ms = 5000 args = @{} } if (-not $entrypointProbe.ok -or $entrypointProbe.data.probe_mode -ne "read_only_send_entrypoint_metadata" -or $null -eq $entrypointProbe.data.entrypoints) { throw "probe_send_entrypoints failed: $($entrypointProbe | ConvertTo-Json -Depth 8 -Compress)" } if ($entrypointProbe.data.safety.sent_message -or $entrypointProbe.data.safety.uploaded_file -or $entrypointProbe.data.safety.clicked_ui) { throw "probe_send_entrypoints safety flags failed: $($entrypointProbe | 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) $formScriptPath = Join-Path $env:TEMP ("codex-winhelper-form-" + [guid]::NewGuid().ToString("N") + ".ps1") $formScript = @" Add-Type -AssemblyName System.Windows.Forms `$form = New-Object System.Windows.Forms.Form `$form.Text = '$title' `$form.Width = 620 `$form.Height = 420 `$layout = New-Object System.Windows.Forms.TableLayoutPanel `$layout.Dock = 'Fill' `$layout.RowCount = 6 `$layout.ColumnCount = 1 `$form.Controls.Add(`$layout) `$search = New-Object System.Windows.Forms.TextBox `$search.Name = 'skinAlphaTxt' `$search.Text = 'search placeholder' `$search.Dock = 'Fill' `$layout.Controls.Add(`$search) `$recv = New-Object System.Windows.Forms.RichTextBox `$recv.Name = 'rtbRecvMessage' `$recv.Text = '' `$recv.Dock = 'Fill' `$layout.Controls.Add(`$recv) `$send = New-Object System.Windows.Forms.RichTextBox `$send.Name = 'rtbSendMessage' `$send.Text = '' `$send.Dock = 'Fill' `$layout.Controls.Add(`$send) `$button = New-Object System.Windows.Forms.Button `$button.Name = 'btnSend' `$button.Text = 'Send' `$button.Dock = 'Fill' `$layout.Controls.Add(`$button) `$file = New-Object System.Windows.Forms.Button `$file.Name = 'btnFile' `$file.Text = 'File' `$file.Dock = 'Fill' `$layout.Controls.Add(`$file) `$offline = New-Object System.Windows.Forms.Label `$offline.Name = 'offlineSendBlocker' `$offline.Text = 'Offline send blocker' `$offline.Dock = 'Fill' `$layout.Controls.Add(`$offline) [void]`$form.ShowDialog() "@ Set-Content -LiteralPath $formScriptPath -Value $formScript -Encoding UTF8 $process = Start-Process powershell -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $formScriptPath) -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)" } $sendUiaProbe = Invoke-HelperJson @{ protocol = "isphere.helper.v1" request_id = "verify-send-uia-controls" op = "probe_send_uia_controls" timeout_ms = 5000 args = @{ hwnd = $candidate.hwnd; max_depth = 6; max_children = 120 } } if (-not $sendUiaProbe.ok -or $sendUiaProbe.data.probe_mode -ne "read_only_uia_send_control_classifier") { throw "probe_send_uia_controls failed: $($sendUiaProbe | ConvertTo-Json -Depth 12 -Compress)" } if (-not $sendUiaProbe.data.flags.has_search_edit -or -not $sendUiaProbe.data.flags.has_send_editor -or -not $sendUiaProbe.data.flags.has_send_button -or -not $sendUiaProbe.data.flags.has_file_menu -or -not $sendUiaProbe.data.flags.offline_blocker_visible) { throw "probe_send_uia_controls missing expected synthetic controls: $($sendUiaProbe | ConvertTo-Json -Depth 12 -Compress)" } if ($sendUiaProbe.data.safety.sent_message -or $sendUiaProbe.data.safety.uploaded_file -or $sendUiaProbe.data.safety.clicked_ui -or $sendUiaProbe.data.safety.typed_text) { throw "probe_send_uia_controls safety flags failed: $($sendUiaProbe | ConvertTo-Json -Depth 12 -Compress)" } } finally { if ($process -and -not $process.HasExited) { Stop-Process -Id $process.Id -Force } if (Test-Path -LiteralPath $formScriptPath) { Remove-Item -LiteralPath $formScriptPath -Force } } [ordered]@{ ok = $true helper = (Resolve-Path -LiteralPath $helperPath).Path version = $version.data.helper_version scan_window_count = $scan.data.windows.Count runtime_probe_target_count = $runtimeProbe.data.target_count send_entrypoint_probe_mode = $entrypointProbe.data.probe_mode synthetic_send_uia_route_hint = $sendUiaProbe.data.flags.route_hint uia_available = $selfCheck.data.uia_available } | ConvertTo-Json -Depth 4 -Compress