Files
edict/scripts/start_opencode.ps1
2026-05-27 18:58:20 +08:00

262 lines
10 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
三省六部 OpenCode 一键启动 (Windows PowerShell 版本)
.DESCRIPTION
生成 OpenCode agent 配置,启动 OpenCode server、dashboard/server.py 和数据刷新循环。
用法: powershell -ExecutionPolicy Bypass -File .\scripts\start_opencode.ps1
#>
#Requires -Version 5.1
$ErrorActionPreference = "Stop"
$RepoDir = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
$DashboardHost = if ($env:EDICT_DASHBOARD_HOST) { $env:EDICT_DASHBOARD_HOST } else { "127.0.0.1" }
$DashboardPort = if ($env:EDICT_DASHBOARD_PORT) { [int]$env:EDICT_DASHBOARD_PORT } else { 7891 }
$OpenCodeHost = if ($env:OPENCODE_HOST) { $env:OPENCODE_HOST } else { "127.0.0.1" }
$OpenCodePort = if ($env:OPENCODE_PORT) { [int]$env:OPENCODE_PORT } else { 4096 }
$OpenCodeServerUrl = if ($env:OPENCODE_SERVER_URL) { $env:OPENCODE_SERVER_URL.TrimEnd("/") } else { "http://${OpenCodeHost}:${OpenCodePort}" }
$PidDir = Join-Path $RepoDir ".pids"
$LogDir = Join-Path $RepoDir "logs"
$ServerPidFile = Join-Path $PidDir "server.pid"
$LoopPidFile = Join-Path $PidDir "loop.pid"
$OpenCodePidFile = Join-Path $PidDir "opencode.pid"
$ServerLog = Join-Path $LogDir "server.log"
$ServerErrLog = Join-Path $LogDir "server.err.log"
$LoopLog = Join-Path $LogDir "loop.log"
$LoopErrLog = Join-Path $LogDir "loop.err.log"
$OpenCodeLog = Join-Path $LogDir "opencode.log"
$OpenCodeErrLog = Join-Path $LogDir "opencode.err.log"
function Log($msg) { Write-Host "$msg" -ForegroundColor Green }
function Info($msg) { Write-Host "$msg" -ForegroundColor Green }
function Warn($msg) { Write-Host "⚠️ $msg" -ForegroundColor Yellow }
function Fail($msg) { Write-Host "$msg" -ForegroundColor Red; exit 1 }
function Resolve-Python {
if ($env:EDICT_PYTHON) {
$cmd = Get-Command $env:EDICT_PYTHON -ErrorAction SilentlyContinue
if ($cmd) { return @{ File = $cmd.Source; Prefix = @() } }
}
foreach ($candidate in @("python", "python3")) {
$cmd = Get-Command $candidate -ErrorAction SilentlyContinue
if ($cmd) {
$version = & $cmd.Source -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" 2>$null
if ($LASTEXITCODE -eq 0) {
$parts = $version.Split(".")
if ([int]$parts[0] -gt 3 -or ([int]$parts[0] -eq 3 -and [int]$parts[1] -ge 10)) {
return @{ File = $cmd.Source; Prefix = @() }
}
}
}
}
$py = Get-Command py -ErrorAction SilentlyContinue
if ($py) {
$version = & $py.Source -3 -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" 2>$null
if ($LASTEXITCODE -eq 0) { return @{ File = $py.Source; Prefix = @("-3") } }
}
throw "未找到 Python 3.10+。"
}
function Resolve-OpenCode {
if ($env:OPENCODE_BIN) {
$cmd = Get-Command $env:OPENCODE_BIN -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
if (Test-Path $env:OPENCODE_BIN) { return $env:OPENCODE_BIN }
}
$cmd = Get-Command opencode -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
foreach ($p in @(
(Join-Path $env:USERPROFILE ".opencode\bin\opencode.exe"),
(Join-Path $env:USERPROFILE ".opencode\bin\opencode.cmd"),
(Join-Path $env:USERPROFILE ".opencode\bin\opencode")
)) {
if (Test-Path $p) { return $p }
}
throw "未找到 OpenCode CLI。请先安装 opencode或设置 OPENCODE_BIN。"
}
function Join-Args([string[]]$Args) {
return ($Args | ForEach-Object { '"' + ($_ -replace '"', '\"') + '"' }) -join " "
}
function Invoke-Python([string[]]$Args) {
& $script:Python.File @($script:Python.Prefix + $Args)
}
function Test-HttpOk([string]$Url) {
try {
$r = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 3
return ($r.StatusCode -ge 200 -and $r.StatusCode -lt 500)
} catch {
return $false
}
}
function Test-OpenCodeAgents {
try {
$dir = [System.Uri]::EscapeDataString($RepoDir)
$agents = Invoke-RestMethod -Uri "$OpenCodeServerUrl/agent?directory=$dir" -TimeoutSec 5
$names = @($agents | ForEach-Object { $_.name })
return ($names -contains "taizi" -and $names -contains "zhongshu" -and $names -contains "shangshu")
} catch {
return $false
}
}
function Get-PortPids([int]$Port) {
try {
return @(Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique)
} catch {
return @()
}
}
function Stop-PidFile([string]$PidFile) {
if (-not (Test-Path $PidFile)) { return }
$pidText = Get-Content $PidFile -ErrorAction SilentlyContinue
if ($pidText -and ($pidText -match '^\d+$')) {
$p = Get-Process -Id ([int]$pidText) -ErrorAction SilentlyContinue
if ($p) {
try { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } catch {}
}
}
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
}
function Stop-OpenCodeOnPort {
foreach ($pid in Get-PortPids $OpenCodePort) {
$cmd = (Get-CimInstance Win32_Process -Filter "ProcessId=$pid" -ErrorAction SilentlyContinue).CommandLine
if ($cmd -and $cmd.ToLowerInvariant().Contains("opencode")) {
Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue
}
}
}
function Stop-RepoDashboardOnPort {
foreach ($pid in Get-PortPids $DashboardPort) {
$cmd = (Get-CimInstance Win32_Process -Filter "ProcessId=$pid" -ErrorAction SilentlyContinue).CommandLine
if ($cmd -and $cmd.Contains("dashboard") -and $cmd.Contains("server.py")) {
Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue
}
}
}
function Ensure-DataFiles {
New-Item -ItemType Directory -Path $PidDir, $LogDir, (Join-Path $RepoDir "data") -Force | Out-Null
foreach ($f in @("live_status.json","agent_config.json","model_change_log.json","sync_status.json","officials_stats.json")) {
$fp = Join-Path $RepoDir "data\$f"
if (-not (Test-Path $fp)) { "{}" | Out-File $fp -Encoding UTF8 }
}
foreach ($f in @("pending_model_changes.json","tasks_source.json","tasks.json","officials.json")) {
$fp = Join-Path $RepoDir "data\$f"
if (-not (Test-Path $fp)) { "[]" | Out-File $fp -Encoding UTF8 }
}
}
function Start-OpenCodeServer {
$args = @("serve", "--hostname", $OpenCodeHost, "--port", [string]$OpenCodePort)
$p = Start-Process -FilePath $OpenCodeBin -ArgumentList (Join-Args $args) `
-WorkingDirectory $RepoDir `
-RedirectStandardOutput $OpenCodeLog `
-RedirectStandardError $OpenCodeErrLog `
-WindowStyle Hidden `
-PassThru
$p.Id | Out-File $OpenCodePidFile -Encoding ASCII
}
function Start-DashboardServer {
$args = @($Python.Prefix) + @((Join-Path $RepoDir "dashboard\server.py"), "--host", $DashboardHost, "--port", [string]$DashboardPort)
$p = Start-Process -FilePath $Python.File -ArgumentList (Join-Args $args) `
-WorkingDirectory $RepoDir `
-RedirectStandardOutput $ServerLog `
-RedirectStandardError $ServerErrLog `
-WindowStyle Hidden `
-PassThru
$p.Id | Out-File $ServerPidFile -Encoding ASCII
}
function Start-RefreshLoop {
$script = Join-Path $RepoDir "scripts\run_loop_opencode.ps1"
$args = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $script)
$p = Start-Process -FilePath "powershell" -ArgumentList (Join-Args $args) `
-WorkingDirectory $RepoDir `
-RedirectStandardOutput $LoopLog `
-RedirectStandardError $LoopErrLog `
-WindowStyle Hidden `
-PassThru
$p.Id | Out-File $LoopPidFile -Encoding ASCII
}
Write-Host ""
Write-Host "╔══════════════════════════════════════════╗" -ForegroundColor Blue
Write-Host "║ 三省六部 · OpenCode Windows 启动中 ║" -ForegroundColor Blue
Write-Host "╚══════════════════════════════════════════╝" -ForegroundColor Blue
Write-Host ""
try { $script:Python = Resolve-Python } catch { Fail $_ }
try { $script:OpenCodeBin = Resolve-OpenCode } catch { Fail $_ }
$env:EDICT_RUNTIME = "opencode"
$env:EDICT_AGENT_RUNTIME = "opencode"
$env:EDICT_PYTHON = $Python.File
$env:OPENCODE_BIN = $OpenCodeBin
$env:OPENCODE_SERVER_URL = $OpenCodeServerUrl
Ensure-DataFiles
Write-Host "Python: $($Python.File)" -ForegroundColor Green
Write-Host "OpenCode: $OpenCodeBin" -ForegroundColor Green
Info "同步 OpenCode agent 配置..."
Invoke-Python @((Join-Path $RepoDir "scripts\sync_opencode_agents.py"))
if ((Test-HttpOk "$OpenCodeServerUrl/doc") -and (Test-OpenCodeAgents)) {
Log "复用已运行的 OpenCode server: $OpenCodeServerUrl"
} else {
if (Test-HttpOk "$OpenCodeServerUrl/doc") {
Warn "当前 $OpenCodeServerUrl 不是本项目的 OpenCode server正在切换..."
Stop-OpenCodeOnPort
Stop-PidFile $OpenCodePidFile
Start-Sleep -Milliseconds 500
}
Info "启动 OpenCode server..."
Stop-PidFile $OpenCodePidFile
Start-OpenCodeServer
for ($i = 0; $i -lt 20; $i++) {
if (Test-HttpOk "$OpenCodeServerUrl/doc") { break }
Start-Sleep -Milliseconds 500
}
if (-not ((Test-HttpOk "$OpenCodeServerUrl/doc") -and (Test-OpenCodeAgents))) {
Fail "OpenCode server 启动失败,请查看日志: $OpenCodeLog / $OpenCodeErrLog"
}
}
Info "切换看板到 OpenCode 运行时..."
Stop-PidFile $ServerPidFile
Stop-PidFile $LoopPidFile
Stop-RepoDashboardOnPort
Remove-Item (Join-Path $env:TEMP "sansheng_liubu_opencode_refresh.pid") -Force -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 500
Start-DashboardServer
Start-RefreshLoop
for ($i = 0; $i -lt 20; $i++) {
if (Test-HttpOk "http://${DashboardHost}:${DashboardPort}/healthz") { break }
Start-Sleep -Milliseconds 500
}
if (-not (Test-HttpOk "http://${DashboardHost}:${DashboardPort}/healthz")) {
Fail "看板启动失败,请查看日志: $ServerLog / $ServerErrLog"
}
Write-Host ""
Log "OpenCode 模式已启动"
Write-Host " 看板: http://${DashboardHost}:${DashboardPort}" -ForegroundColor Blue
Write-Host " OpenCode: $OpenCodeServerUrl" -ForegroundColor Blue
Write-Host " 日志: $LogDir" -ForegroundColor Blue
try {
Start-Process "http://${DashboardHost}:${DashboardPort}" | Out-Null
} catch {}