Ctrl K
ComunidadGuías…

deploy/windows/run.ps1

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# WindsurfAPI 监督循环内核。
# 阻塞式跑 `node src/index.js`,按退出码路由:75/0 重启,其它退避后停。
# 由 start.ps1(前台)、run.bat(前台)、run.vbs(隐藏后台)共用。
#
# ⚠️ 退出码语义与 KiroStudio 相反(见 README):
#   75 = 自更新/面板 Update 请求重启(EX_TEMPFAIL, api.js:2055)
#   0  = 优雅停机(index.js exit(0),已抽干 SSE;K7 起关机只 drain 不回写池,
#        盘上池态由 30s dirty-flush 保持最新)
#   其它非零 = 崩溃/启动错/端口占用重试耗尽(index.js/server.js exit(1))
#
# 关键红线:阻塞等子进程完全退出再重启,杜绝两个 node 抢写 accounts.json。
# 绝不用 taskkill /F 来「重启」(那是外部写者红线;监督者靠等子退出即零写重叠)。

$ErrorActionPreference = 'Stop'
# 控制台按 UTF-8 输出,否则中文文案在默认 GBK/936 码页的窗口里显示为乱码。
try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch { }

# 项目根 = 本脚本上两级(deploy/windows/ → 根)。config.js 按模块路径解析
# .env/数据文件(ROOT=resolve(__dirname,'..')),cwd 必须是根以保 git/自更新正确。
$Root = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
Set-Location $Root

$LogDir = Join-Path $Root 'logs'
if (-not (Test-Path $LogDir)) { New-Item -ItemType Directory -Path $LogDir -Force | Out-Null }
$PidFile = Join-Path $LogDir 'windsurfapi.pid'
$Date = Get-Date -Format 'yyyy-MM-dd'
$SupLog = Join-Path $LogDir "supervisor-$Date.log"

# DEVIN_CONNECT 必需(language_server 仅 Linux/macOS)。仅在未设时补默认,
# 不覆盖用户显式值(config.js:27 环境变量优先于 .env)。
if (-not $env:DEVIN_CONNECT -and -not $env:DEVIN_ONLY) { $env:DEVIN_CONNECT = '1' }
if (-not $env:LOG_LEVEL) { $env:LOG_LEVEL = 'info' }

function Write-Sup([string]$msg) {
  $line = "[{0}] {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg
  Write-Host $line
  Add-Content -Path $SupLog -Value $line -Encoding UTF8
}

$MaxCrashLoop = 5
$crashStreak = 0

Write-Sup "supervisor start (root=$Root)"

while ($true) {
  # 原始 stdout/stderr 必须落盘:app 的 JSONL 日志不含启动 banner(index.js:78)
  # 与 pre-logger 致命 console.error(index.js:214)。给子进程单独的 node 日志,
  # 与监督者自己的 Write-Sup 记录分离(否则同一文件被 stdout 重定向截断,监督
  # 记录会被 app 输出覆盖 —— 老版 Start-Process 方案的隐患)。
  $nodeOut = Join-Path $LogDir "node-$Date.log"

  # 关键:用 [Diagnostics.Process] 而非 Start-Process -PassThru。后者在
  # -RedirectStandardOutput 到文件时,$proc.ExitCode 会返回 $null(Windows
  # PowerShell 已知缺陷),导致 75/0 全部落入 else 崩溃分支 —— 热更新与优雅
  # 重启彻底失效。.NET Process 的 ExitCode 可靠,且能拿到 PID 写 pidfile
  # (stop.ps1/status.ps1 依赖)。异步抽干 stdout/stderr 到 node 日志,避免
  # 管道缓冲写满导致子进程阻塞(经典死锁),同时满足"等子退出再重启"红线。
  $psi = New-Object System.Diagnostics.ProcessStartInfo
  $psi.FileName = 'node'
  $psi.Arguments = 'src/index.js'
  $psi.WorkingDirectory = $Root
  $psi.UseShellExecute = $false
  $psi.RedirectStandardOutput = $true
  $psi.RedirectStandardError = $true
  $proc = New-Object System.Diagnostics.Process
  $proc.StartInfo = $psi
  $sink = { if ($EventArgs.Data -ne $null) { Add-Content -Path $Event.MessageData -Value $EventArgs.Data -Encoding UTF8 } }
  $so = Register-ObjectEvent -InputObject $proc -EventName OutputDataReceived -Action $sink -MessageData $nodeOut
  $se = Register-ObjectEvent -InputObject $proc -EventName ErrorDataReceived  -Action $sink -MessageData $nodeOut
  [void]$proc.Start()
  $proc.BeginOutputReadLine()
  $proc.BeginErrorReadLine()

  Set-Content -Path $PidFile -Value $proc.Id -Encoding ASCII
  Write-Sup "node started pid=$($proc.Id)"

  $proc.WaitForExit()
  $code = $proc.ExitCode
  # 注销事件订阅,防止跨循环泄漏。
  Unregister-Event -SourceIdentifier $so.Name -ErrorAction SilentlyContinue
  Unregister-Event -SourceIdentifier $se.Name -ErrorAction SilentlyContinue
  $proc.Dispose()
  Write-Sup "node exited code=$code"

  if ($code -eq 75) {
    # 自更新/面板 Update:重启,重置崩溃计数。
    $crashStreak = 0
    Write-Sup 'exit 75 (self-update/restart requested) -> restarting'
    Start-Sleep -Milliseconds 1000
    continue
  }
  elseif ($code -eq 0) {
    # 优雅停机。真实 Ctrl-C/关窗会同时终结本监督者,走不到重拉;这段可打断的
    # 等待只是兜底(例如程序内部主动 exit(0))。
    $crashStreak = 0
    Write-Sup 'exit 0 (graceful) -> restarting after 2s (Ctrl-C to stop)'
    Start-Sleep -Seconds 2
    continue
  }
  else {
    # 崩溃/启动错/端口占用重试耗尽。退避,连续 MaxCrashLoop 次后停机。
    $crashStreak++
    if ($crashStreak -ge $MaxCrashLoop) {
      Write-Sup "CRASH LOOP: $crashStreak consecutive non-zero exits (last=$code). Stopping."
      Write-Host ''
      Write-Host '============================================================' -ForegroundColor Red
      Write-Host " WindsurfAPI 连续 $crashStreak 次异常退出,监督循环已停止。" -ForegroundColor Red
      Write-Host " 最近日志:$SupLog" -ForegroundColor Red
      Write-Host '============================================================' -ForegroundColor Red
      if (Test-Path $outLog) { Write-Host '--- 日志尾 ---'; Get-Content $outLog -Tail 30 }
      break
    }
    $backoff = [Math]::Min(2 * $crashStreak, 10)
    Write-Sup "non-zero exit (code=$code), crashStreak=$crashStreak -> backoff ${backoff}s"
    Start-Sleep -Seconds $backoff
    continue
  }
}

# 干净退出:删 pidfile。
if (Test-Path $PidFile) { Remove-Item $PidFile -Force -ErrorAction SilentlyContinue }
Write-Sup 'supervisor stopped'