mirror of
https://github.com/cft0808/edict.git
synced 2026-09-06 00:49:30 +08:00
262 lines
10 KiB
PowerShell
262 lines
10 KiB
PowerShell
<#
|
||
.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 {}
|