param( [Parameter(Mandatory = $true)] [string]$Version, [int]$McpLoops = 0, [int]$McpServers = 20, [int]$MarkdownLoops = 1, [int]$StartupSeconds = 20, [int]$PtySeconds = 120, [switch]$OnlyMarkdown ) $ErrorActionPreference = "Stop" function Write-Section([string]$Name) { Write-Host "" Write-Host "==== $Name ====" } function Read-TextFile([string]$Path) { if (!(Test-Path $Path)) { return "" } return [System.IO.File]::ReadAllText($Path) } function Test-CrashText([string]$Text) { if ($Text -match "Segmentation fault") { return $true } if ($Text -match "Bun has crashed") { return $true } if ($Text -match "ACCESS_VIOLATION") { return $true } if ($Text -match "0xC0000005") { return $true } if ($Text -match "322122") { return $true } if ($Text -match "panic\(main thread\)") { return $true } return $false } function Invoke-Opencode { param( [Parameter(Mandatory = $true)] [string]$Label, [Parameter(Mandatory = $true)] [string]$Exe, [string[]]$Args = @(), [Parameter(Mandatory = $true)] [string]$WorkingDirectory, [int]$TimeoutSeconds = 60, [switch]$AllowNonZero, [switch]$AllowTimeout ) Write-Section $Label Write-Host "cwd=$WorkingDirectory" Write-Host "cmd=$Exe $($Args -join ' ')" $safe = ($Label -replace "[^A-Za-z0-9_.-]", "_") $stdout = Join-Path $env:RUNNER_TEMP "$safe.out.log" $stderr = Join-Path $env:RUNNER_TEMP "$safe.err.log" Remove-Item -ErrorAction SilentlyContinue $stdout, $stderr $process = Start-Process -FilePath $Exe -ArgumentList $Args -WorkingDirectory $WorkingDirectory -NoNewWindow -PassThru -RedirectStandardOutput $stdout -RedirectStandardError $stderr $exited = $process.WaitForExit($TimeoutSeconds * 1000) if (!$exited) { Write-Host "timeout after ${TimeoutSeconds}s; terminating process id $($process.Id)" Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 } $outText = Read-TextFile $stdout $errText = Read-TextFile $stderr if ($outText.Length -gt 0) { Write-Host "--- stdout ---" Write-Host $outText } if ($errText.Length -gt 0) { Write-Host "--- stderr ---" Write-Host $errText } $combined = $outText + "`n" + $errText if (Test-CrashText $combined) { throw "native crash signature detected in $Label" } if (!$exited) { if ($AllowTimeout) { Write-Host "timeout accepted for long-running TUI startup check" return } throw "timeout in $Label" } $exitCode = [int64]$process.ExitCode Write-Host "exitCode=$exitCode" if (@(3, -1073741819, -1073740791, -1073741571) -contains $exitCode) { throw "native crash exit code $exitCode in $Label" } if (!$AllowNonZero -and $process.ExitCode -ne 0) { throw "unexpected exit code $($process.ExitCode) in $Label" } } function Invoke-PtyScenario { param( [Parameter(Mandatory = $true)] [string]$Scenario, [Parameter(Mandatory = $true)] [string]$Exe, [Parameter(Mandatory = $true)] [string]$Project, [Parameter(Mandatory = $true)] [string]$Version, [Parameter(Mandatory = $true)] [int]$Seconds ) node (Join-Path $PSScriptRoot "repro-windows-opentui-pty-session.mjs") -- --exe $Exe --project $Project --version $Version --seconds $Seconds --scenario $Scenario if ($LASTEXITCODE -ne 0) { $script:PtyFailures += "PTY scenario '$Scenario' failed with exit code $LASTEXITCODE" Write-Host "::error::$($script:PtyFailures[-1])" } } function Invoke-MarkdownScenarios { param( [Parameter(Mandatory = $true)] [string]$Exe, [Parameter(Mandatory = $true)] [string]$Project, [Parameter(Mandatory = $true)] [string]$Version, [Parameter(Mandatory = $true)] [int]$Seconds, [Parameter(Mandatory = $true)] [int]$Loops ) for ($i = 1; $i -le $Loops; $i++) { $suffix = if ($Loops -gt 1) { "-$($i.ToString('00'))" } else { "" } if ($Version -eq "1.17.10") { Invoke-PtyScenario -Scenario "markdown-no-native-render$suffix" -Exe $Exe -Project $Project -Version $Version -Seconds $Seconds } Invoke-PtyScenario -Scenario "markdown$suffix" -Exe $Exe -Project $Project -Version $Version -Seconds $Seconds } } Write-Section "Environment" node --version npm --version Write-Host "RUNNER_OS=$env:RUNNER_OS RUNNER_ARCH=$env:RUNNER_ARCH" Write-Host "PROCESSOR_ARCHITECTURE=$env:PROCESSOR_ARCHITECTURE" Write-Host "MarkdownLoops=$MarkdownLoops OnlyMarkdown=$OnlyMarkdown" if ($MarkdownLoops -lt 1) { throw "MarkdownLoops must be >= 1" } Write-Section "Install opencode-ai@$Version" npm uninstall -g opencode-ai opencode-windows-x64 2>$null | Out-Host npm install -g "opencode-ai@$Version" if ($LASTEXITCODE -ne 0) { throw "npm install opencode-ai@$Version failed with exit code $LASTEXITCODE" } $npmRoot = (npm root -g).Trim() $exe = Join-Path $npmRoot "opencode-ai\bin\opencode.exe" if (!(Test-Path $exe)) { $cmd = Get-Command opencode -ErrorAction Stop $exe = $cmd.Source } Write-Host "opencode executable=$exe" if (Test-Path $exe) { $info = (Get-Item $exe).VersionInfo Write-Host "FileDescription=$($info.FileDescription) FileVersion=$($info.FileVersion) ProductVersion=$($info.ProductVersion) CompanyName=$($info.CompanyName)" } $root = Join-Path $env:RUNNER_TEMP "opencode-windows-opentui-$Version" Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $root New-Item -ItemType Directory -Force $root | Out-Null $env:XDG_CONFIG_HOME = Join-Path $root "xdg-config" $env:XDG_DATA_HOME = Join-Path $root "xdg-data" $env:XDG_CACHE_HOME = Join-Path $root "xdg-cache" $env:XDG_STATE_HOME = Join-Path $root "xdg-state" $env:APPDATA = Join-Path $root "appdata" $env:LOCALAPPDATA = Join-Path $root "localappdata" $env:OPENCODE_DISABLE_AUTOUPDATE = "1" $env:OPENCODE_DISABLE_EXTERNAL_SKILLS = "1" $env:OPENCODE_DISABLE_LSP_DOWNLOAD = "1" $env:OPENCODE_PURE = "1" New-Item -ItemType Directory -Force $env:XDG_CONFIG_HOME, $env:XDG_DATA_HOME, $env:XDG_CACHE_HOME, $env:XDG_STATE_HOME, $env:APPDATA, $env:LOCALAPPDATA | Out-Null $emptyProject = Join-Path $root "empty-project" $mcpProject = Join-Path $root "mcp-project" $sessionProject = Join-Path $root "session-project" New-Item -ItemType Directory -Force $emptyProject, $mcpProject, $sessionProject | Out-Null Invoke-Opencode -Label "version" -Exe $exe -Args @("--version") -WorkingDirectory $emptyProject -TimeoutSeconds 30 Invoke-Opencode -Label "help" -Exe $exe -Args @("--help") -WorkingDirectory $emptyProject -TimeoutSeconds 30 Write-Section "Write MCP spawn-storm config" $servers = [ordered]@{} for ($i = 1; $i -le $McpServers; $i++) { $name = "server$($i.ToString('00'))" $servers[$name] = [ordered]@{ type = "local" command = @("node", "-e", "setTimeout(() => {}, 30000)") timeout = 350 } } $config = [ordered]@{ autoupdate = $false mcp = $servers } ($config | ConvertTo-Json -Depth 10) | Set-Content -Encoding UTF8 (Join-Path $mcpProject "opencode.json") Get-Content (Join-Path $mcpProject "opencode.json") | Write-Host for ($i = 1; $i -le $McpLoops; $i++) { Invoke-Opencode -Label "mcp-list-$i" -Exe $exe -Args @("mcp", "list") -WorkingDirectory $mcpProject -TimeoutSeconds 90 -AllowNonZero } Invoke-Opencode -Label "empty-startup-tui" -Exe $exe -Args @() -WorkingDirectory $emptyProject -TimeoutSeconds $StartupSeconds -AllowTimeout -AllowNonZero Write-Section "Install PTY dependency" $ptyRoot = Join-Path $root "pty-harness" New-Item -ItemType Directory -Force $ptyRoot | Out-Null Push-Location $ptyRoot try { npm init -y | Out-Host if ($LASTEXITCODE -ne 0) { throw "npm init for PTY harness failed with exit code $LASTEXITCODE" } npm install "@lydell/node-pty@1.2.0-beta.12" | Out-Host if ($LASTEXITCODE -ne 0) { throw "npm install @lydell/node-pty failed with exit code $LASTEXITCODE" } $script:PtyFailures = @() if ($OnlyMarkdown) { Invoke-MarkdownScenarios -Exe $exe -Project $sessionProject -Version $Version -Seconds $PtySeconds -Loops $MarkdownLoops } else { Invoke-PtyScenario -Scenario "text" -Exe $exe -Project $sessionProject -Version $Version -Seconds $PtySeconds Invoke-MarkdownScenarios -Exe $exe -Project $sessionProject -Version $Version -Seconds $PtySeconds -Loops $MarkdownLoops Invoke-PtyScenario -Scenario "bash-permission" -Exe $exe -Project $sessionProject -Version $Version -Seconds $PtySeconds Invoke-PtyScenario -Scenario "task-permission" -Exe $exe -Project $sessionProject -Version $Version -Seconds $PtySeconds Invoke-PtyScenario -Scenario "mcp-npx" -Exe $exe -Project $sessionProject -Version $Version -Seconds $PtySeconds if ($Version -eq "1.17.10") { Invoke-PtyScenario -Scenario "mcp-npx-no-native-render" -Exe $exe -Project $sessionProject -Version $Version -Seconds $PtySeconds } } if ($script:PtyFailures.Count -gt 0) { Write-Section "PTY Failures" $script:PtyFailures | ForEach-Object { Write-Host $_ } throw "$($script:PtyFailures.Count) PTY scenario(s) failed" } } finally { Pop-Location } Write-Section "Result" Write-Host "No native crash signature detected for opencode-ai@$Version"