feat: show server version in UI + resolve codex CLI before launch #13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Windows smoke test | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| jobs: | |
| smoke: | |
| name: start.bat end-to-end on Windows | |
| runs-on: windows-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| - name: Install dependencies | |
| shell: pwsh | |
| run: npm ci | |
| - name: Lint Windows launchers (.bat / .ps1) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $targets = @( | |
| "start.bat", "stop.bat", | |
| "scripts/dev/start-local-agent.ps1", | |
| "scripts/dev/stop-local-agent.ps1", | |
| "scripts/windows/capture-screen.ps1" | |
| ) | |
| $failed = $false | |
| foreach ($target in $targets) { | |
| if (-not (Test-Path $target)) { | |
| Write-Host "skip missing: $target" | |
| continue | |
| } | |
| $bytes = [System.IO.File]::ReadAllBytes($target) | |
| $text = [System.Text.Encoding]::ASCII.GetString($bytes) | |
| # 1. Pure ASCII. cmd.exe on zh-CN runs code page 936 and will | |
| # misdecode UTF-8 multi-byte sequences in .bat files, | |
| # producing '文件名、目录名或卷标语法不正确'. Applying the | |
| # same rule to .ps1 keeps us consistent and protects | |
| # Windows PowerShell 5.1 hosts that can't read BOM-less | |
| # UTF-8 reliably. | |
| foreach ($b in $bytes) { | |
| if ($b -gt 0x7f) { | |
| Write-Error "$target contains non-ASCII byte 0x$("{0:X2}" -f $b) - use ASCII-only for Windows compatibility" | |
| $failed = $true | |
| break | |
| } | |
| } | |
| # 2. CRLF line endings. LF-only .bat files confuse some cmd.exe | |
| # versions, and Windows PowerShell 5.1 parses CRLF more | |
| # reliably than LF. | |
| if ($text -notmatch "`r`n") { | |
| Write-Error "$target has no CRLF line endings" | |
| $failed = $true | |
| } | |
| # 3. No Unix-style /dev/null redirection. cmd.exe would create | |
| # an actual file named 'null' under a 'dev' directory. | |
| if ($text -match "/dev/null") { | |
| Write-Error "$target contains Unix-style '/dev/null' - use 'NUL' instead" | |
| $failed = $true | |
| } | |
| } | |
| if ($failed) { exit 1 } | |
| Write-Host "All Windows launchers passed ASCII / CRLF / NUL lint." | |
| - name: Run start.bat end-to-end | |
| shell: cmd | |
| run: | | |
| start.bat | |
| - name: Capture PID before stop.bat | |
| shell: pwsh | |
| run: | | |
| $pidFile = "runtime/agent/agent.pid" | |
| if (-not (Test-Path $pidFile)) { | |
| Write-Error "agent.pid missing after start.bat — agent never wrote it" | |
| exit 1 | |
| } | |
| $capturedPid = (Get-Content $pidFile -Raw).Trim() | |
| Write-Host "agent PID before stop: $capturedPid" | |
| "AGENT_PID_BEFORE_STOP=$capturedPid" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| - name: Run stop.bat end-to-end | |
| shell: cmd | |
| run: | | |
| stop.bat | |
| - name: Verify stop.bat killed the full process tree | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| # 1. The tracked node.exe must be gone. | |
| $tracked = [int]$env:AGENT_PID_BEFORE_STOP | |
| try { | |
| $null = Get-Process -Id $tracked -ErrorAction Stop | |
| Write-Error "stop.bat left node.exe PID $tracked running" | |
| exit 1 | |
| } catch { | |
| Write-Host "tracked node.exe PID $tracked is gone" | |
| } | |
| # 2. The pid / port files must be cleaned up. | |
| if (Test-Path "runtime/agent/agent.pid") { | |
| Write-Error "stop.bat left runtime/agent/agent.pid behind" | |
| exit 1 | |
| } | |
| if (Test-Path "runtime/agent/agent.port") { | |
| Write-Error "stop.bat left runtime/agent/agent.port behind" | |
| exit 1 | |
| } | |
| # 3. The runtime/agent folder must be deletable — i.e. no | |
| # wrapper powershell.exe is still holding agent.log open. | |
| try { | |
| Copy-Item -Recurse -Force "runtime/agent" "runtime/agent.stop-test-copy" | |
| Remove-Item -Recurse -Force "runtime/agent" | |
| Write-Host "runtime/agent deleted cleanly — no leaked handles" | |
| Move-Item "runtime/agent.stop-test-copy" "runtime/agent" | |
| } catch { | |
| Write-Error "runtime/agent could not be deleted after stop.bat: $_" | |
| exit 1 | |
| } | |
| - name: Start agent via start-local-agent.ps1 (foreground) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Continue" | |
| Write-Host "Running start-local-agent.ps1 in foreground to surface errors..." | |
| & "$env:GITHUB_WORKSPACE\scripts\dev\start-local-agent.ps1" | |
| Write-Host "start-local-agent.ps1 exit code: $LASTEXITCODE" | |
| - name: Wait for agent to be ready | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $portFile = "runtime/agent/agent.port" | |
| $port = $null | |
| for ($i = 0; $i -lt 90; $i++) { | |
| if (Test-Path $portFile) { | |
| $port = (Get-Content $portFile -Raw).Trim() | |
| if ($port) { break } | |
| } | |
| Start-Sleep -Seconds 2 | |
| } | |
| if (-not $port) { | |
| Write-Error "agent.port file never appeared — start.bat failed to start the agent" | |
| exit 1 | |
| } | |
| Write-Host "Agent port: $port" | |
| "AGENT_PORT=$port" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| $url = "http://127.0.0.1:$port/api/config" | |
| for ($i = 0; $i -lt 60; $i++) { | |
| try { | |
| $resp = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 3 | |
| if ($resp.StatusCode -eq 200) { | |
| Write-Host "GET /api/config -> 200 OK" | |
| Write-Host $resp.Content | |
| exit 0 | |
| } | |
| } catch { | |
| Start-Sleep -Seconds 2 | |
| } | |
| } | |
| Write-Error "Agent never responded on /api/config within 2 minutes" | |
| exit 1 | |
| - name: Smoke test /desktop route | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $url = "http://127.0.0.1:$env:AGENT_PORT/desktop" | |
| $resp = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 10 | |
| if ($resp.StatusCode -ne 200) { | |
| Write-Error "/desktop returned $($resp.StatusCode)" | |
| exit 1 | |
| } | |
| Write-Host "/desktop -> 200 OK ($($resp.Content.Length) bytes)" | |
| - name: Smoke test login (POST /api/pair) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $tokenFile = "runtime/agent/app_data/pairing-token.txt" | |
| if (-not (Test-Path $tokenFile)) { | |
| Write-Error "pairing-token.txt missing — agent never initialized session store" | |
| exit 1 | |
| } | |
| $token = (Get-Content $tokenFile -Raw).Trim() | |
| Write-Host "Pairing token: $token" | |
| $url = "http://127.0.0.1:$env:AGENT_PORT/api/pair" | |
| # 1. Wrong token -> 401 | |
| try { | |
| $bad = Invoke-WebRequest -UseBasicParsing -Uri $url -Method POST ` | |
| -ContentType "application/json" ` | |
| -Body '{"token":"wrong-token"}' -TimeoutSec 5 | |
| Write-Error "Wrong token should have failed, got $($bad.StatusCode)" | |
| exit 1 | |
| } catch { | |
| if ($_.Exception.Response.StatusCode.value__ -ne 401) { | |
| Write-Error "Wrong token expected 401, got $($_.Exception.Response.StatusCode.value__)" | |
| exit 1 | |
| } | |
| Write-Host "Wrong token -> 401 (as expected)" | |
| } | |
| # 2. Correct token -> 200 { ok: true } | |
| $body = @{ token = $token } | ConvertTo-Json -Compress | |
| $ok = Invoke-WebRequest -UseBasicParsing -Uri $url -Method POST ` | |
| -ContentType "application/json" ` | |
| -Body $body -TimeoutSec 5 | |
| if ($ok.StatusCode -ne 200) { | |
| Write-Error "/api/pair returned $($ok.StatusCode)" | |
| exit 1 | |
| } | |
| Write-Host "/api/pair -> 200 OK: $($ok.Content)" | |
| - name: Run unit tests | |
| shell: pwsh | |
| run: npm test | |
| - name: Stop agent | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| $pidFile = "runtime/agent/agent.pid" | |
| if (Test-Path $pidFile) { | |
| $agentPid = (Get-Content $pidFile -Raw).Trim() | |
| try { | |
| Stop-Process -Id $agentPid -Force -ErrorAction Stop | |
| Write-Host "Stopped agent PID $agentPid" | |
| } catch { | |
| Write-Host "Agent PID $agentPid already gone" | |
| } | |
| } | |
| - name: Upload agent log | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: agent-log | |
| path: | | |
| runtime/agent/agent.log | |
| runtime/agent/agent.port | |
| runtime/agent/agent.pid | |
| if-no-files-found: warn |