mirror of
https://github.com/cft0808/edict.git
synced 2026-09-03 07:17:16 +08:00
fix: add Windows OpenCode startup scripts
This commit is contained in:
@@ -314,6 +314,9 @@ chmod +x install.sh && ./install.sh
|
||||
# OpenCode 模式:一键生成 opencode.json 并启动 OpenCode + 看板
|
||||
bash edict.sh opencode
|
||||
|
||||
# Windows PowerShell
|
||||
powershell -ExecutionPolicy Bypass -File .\edict.ps1 opencode
|
||||
|
||||
# OpenClaw 模式:一键启动(推荐)
|
||||
chmod +x start.sh && ./start.sh
|
||||
|
||||
@@ -472,6 +475,7 @@ edict/
|
||||
│ └── groups/ # Agent 分组(sansheng / liubu)
|
||||
├── scripts/
|
||||
│ ├── run_loop.sh # 数据刷新循环(每 15 秒)
|
||||
│ ├── run_loop_opencode.ps1 # OpenCode 数据刷新循环(Windows PowerShell)
|
||||
│ ├── kanban_update.py # 看板 CLI(含旨意数据清洗 + 标题校验 + 状态机)
|
||||
│ ├── skill_manager.py # Skill 管理工具(远程/本地 Skills 添加、更新、移除)
|
||||
│ ├── agentrec_advisor.py # Agent 模型推荐(功过簿 + 成本优化)
|
||||
@@ -497,6 +501,7 @@ edict/
|
||||
├── start.sh # 一键启动(Dashboard + 数据刷新)
|
||||
├── edict.service # systemd 服务配置(生产部署)
|
||||
├── edict.sh # 服务管理脚本(start/stop/restart/status)
|
||||
├── edict.ps1 # Windows 服务管理脚本(opencode/stop/status/logs)
|
||||
├── CONTRIBUTING.md # 贡献指南
|
||||
└── LICENSE # MIT License
|
||||
```
|
||||
|
||||
@@ -51,6 +51,14 @@ cd C:\Users\<YOUR_USER>\.openclaw\workspace\skills\edict
|
||||
powershell -ExecutionPolicy Bypass -File .\install.ps1
|
||||
```
|
||||
|
||||
如果你使用 OpenCode 模式,可以不依赖 Git Bash,直接启动:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\edict.ps1 opencode
|
||||
```
|
||||
|
||||
这个命令会自动生成 `opencode.json`、同步 `.opencode\prompts\*`,并启动 OpenCode server、看板服务器和后台刷新循环。
|
||||
|
||||
---
|
||||
|
||||
## 4. 安装后检查两件事
|
||||
@@ -72,7 +80,19 @@ openclaw config set tools.sessions.visibility all
|
||||
---
|
||||
|
||||
## 5. 启动后台刷新循环
|
||||
在 Git Bash / MINGW64 里运行:
|
||||
如果使用 OpenCode 模式,推荐直接运行:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\edict.ps1 opencode
|
||||
```
|
||||
|
||||
如果使用 OpenClaw 模式,在 PowerShell 里运行:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\scripts\run_loop.ps1
|
||||
```
|
||||
|
||||
也可以在 Git Bash / MINGW64 里运行旧版脚本:
|
||||
|
||||
```bash
|
||||
cd ~/.openclaw/workspace/skills/edict/scripts
|
||||
@@ -84,7 +104,7 @@ bash run_loop.sh
|
||||
---
|
||||
|
||||
## 6. 启动 dashboard
|
||||
在 PowerShell 里运行:
|
||||
OpenCode 模式下 `edict.ps1 opencode` 会自动启动 dashboard。OpenClaw 手动模式下,在 PowerShell 里运行:
|
||||
|
||||
```powershell
|
||||
cd C:\Users\<YOUR_USER>\.openclaw\workspace\skills\edict
|
||||
@@ -106,7 +126,7 @@ http://127.0.0.1:7891
|
||||
- 面板可以打开
|
||||
- `官员总览` 能显示三省六部官员信息
|
||||
- `模型配置` 能显示 agent 列表
|
||||
- 右上角 Gateway 状态正常
|
||||
- `省部调度` 里显示 OpenCode 或 OpenClaw Gateway 状态正常
|
||||
- 倒计时会持续刷新页面数据
|
||||
|
||||
---
|
||||
@@ -199,7 +219,7 @@ openclaw config set tools.sessions.visibility all
|
||||
|
||||
---
|
||||
|
||||
## 5. 为什么还要跑 `run_loop.sh`
|
||||
## 5. 为什么还要跑刷新循环
|
||||
|
||||
dashboard 右上角虽然有一个 5 秒倒计时,但它只是:
|
||||
|
||||
@@ -209,8 +229,8 @@ dashboard 右上角虽然有一个 5 秒倒计时,但它只是:
|
||||
|
||||
真正负责后台数据刷新的是:
|
||||
|
||||
```bash
|
||||
bash run_loop.sh
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\scripts\run_loop.ps1
|
||||
```
|
||||
|
||||
它会持续执行同步脚本,更新:
|
||||
@@ -222,9 +242,9 @@ bash run_loop.sh
|
||||
所以:
|
||||
|
||||
- dashboard 倒计时 = **读数据**
|
||||
- `run_loop.sh` = **产数据 / 刷数据**
|
||||
- `scripts\run_loop.ps1` = **产数据 / 刷数据**
|
||||
|
||||
Windows 下也建议正常运行 `run_loop.sh`。
|
||||
Windows 下不建议再强依赖 `bash run_loop.sh`;优先使用 `scripts\run_loop.ps1` 或 `edict.ps1 opencode`。
|
||||
|
||||
---
|
||||
|
||||
@@ -290,8 +310,8 @@ agents.json
|
||||
## 第四步
|
||||
启动后台刷新循环:
|
||||
|
||||
```bash
|
||||
bash run_loop.sh
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\scripts\run_loop.ps1
|
||||
```
|
||||
|
||||
## 第五步
|
||||
@@ -301,10 +321,17 @@ bash run_loop.sh
|
||||
python dashboard\server.py
|
||||
```
|
||||
|
||||
如果使用 OpenCode,则第四步和第五步可以合并为:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\edict.ps1 opencode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 五、一句话总结
|
||||
|
||||
## Windows 用户最稳的做法就是:
|
||||
先清旧链接,再运行安装脚本;安装后检查 agent 配置和 `tools.sessions.visibility = all`;如有需要可参考 `agents.json` 脱敏模板并替换 `<YOUR_USER>`;最后启动 `run_loop.sh` 和 `dashboard/server.py`。
|
||||
OpenCode 模式:直接运行 `powershell -ExecutionPolicy Bypass -File .\edict.ps1 opencode`。
|
||||
|
||||
OpenClaw 模式:先清旧链接,再运行安装脚本;安装后检查 agent 配置和 `tools.sessions.visibility = all`;如有需要可参考 `agents.json` 脱敏模板并替换 `<YOUR_USER>`;最后启动 `scripts\run_loop.ps1` 和 `dashboard\server.py`。
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
三省六部现在支持 OpenCode 和 [OpenClaw](https://openclaw.ai) 两种运行时。新项目本地体验推荐 OpenCode;已有 OpenClaw 工作区可继续沿用。
|
||||
|
||||
```bash
|
||||
# OpenCode(推荐)
|
||||
# OpenCode(macOS / Linux)
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
# OpenCode(Node.js 方式)
|
||||
# OpenCode(Windows PowerShell 推荐先装 Node.js 18+,再用 npm)
|
||||
npm install -g opencode-ai
|
||||
|
||||
# OpenClaw(macOS)
|
||||
@@ -67,6 +67,9 @@ openclaw channels add --type feishu --agent taizi
|
||||
# OpenCode 模式:一键启动 OpenCode server、看板和数据循环
|
||||
bash edict.sh opencode
|
||||
|
||||
# Windows PowerShell
|
||||
powershell -ExecutionPolicy Bypass -File .\edict.ps1 opencode
|
||||
|
||||
# OpenClaw 模式:终端 1,数据刷新循环(每 15 秒同步)
|
||||
bash scripts/run_loop.sh
|
||||
|
||||
|
||||
133
edict.ps1
Normal file
133
edict.ps1
Normal file
@@ -0,0 +1,133 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
三省六部 Windows 服务管理脚本
|
||||
.DESCRIPTION
|
||||
PowerShell 入口,对应 edict.sh。
|
||||
用法: powershell -ExecutionPolicy Bypass -File .\edict.ps1 {opencode|stop|status|logs}
|
||||
#>
|
||||
#Requires -Version 5.1
|
||||
param(
|
||||
[ValidateSet("opencode", "stop", "status", "logs")]
|
||||
[string]$Command,
|
||||
[ValidateSet("server", "loop", "opencode", "all")]
|
||||
[string]$Target = "all"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
$RepoDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$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"
|
||||
$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 }
|
||||
|
||||
function Is-Running([string]$PidFile) {
|
||||
if (-not (Test-Path $PidFile)) { return $false }
|
||||
$pidText = Get-Content $PidFile -ErrorAction SilentlyContinue
|
||||
if (-not ($pidText -match '^\d+$')) {
|
||||
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
|
||||
return $false
|
||||
}
|
||||
$p = Get-Process -Id ([int]$pidText) -ErrorAction SilentlyContinue
|
||||
if ($p) { return $true }
|
||||
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-PidText([string]$PidFile) {
|
||||
if (Test-Path $PidFile) { return (Get-Content $PidFile -ErrorAction SilentlyContinue) }
|
||||
return ""
|
||||
}
|
||||
|
||||
function Stop-PidFile([string]$PidFile) {
|
||||
if (-not (Test-Path $PidFile)) { return $false }
|
||||
$pidText = Get-Content $PidFile -ErrorAction SilentlyContinue
|
||||
if ($pidText -and ($pidText -match '^\d+$')) {
|
||||
try { Stop-Process -Id ([int]$pidText) -Force -ErrorAction SilentlyContinue } catch {}
|
||||
}
|
||||
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
|
||||
return $true
|
||||
}
|
||||
|
||||
function Show-Status {
|
||||
Write-Host "三省六部 · Windows 服务状态" -ForegroundColor Blue
|
||||
Write-Host ""
|
||||
foreach ($item in @(
|
||||
@{ Name = "看板服务器"; File = $ServerPidFile },
|
||||
@{ Name = "数据刷新循环"; File = $LoopPidFile },
|
||||
@{ Name = "OpenCode server"; File = $OpenCodePidFile }
|
||||
)) {
|
||||
if (Is-Running $item.File) {
|
||||
Write-Host " ● $($item.Name) PID=$(Get-PidText $item.File) 运行中" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ○ $($item.Name) 未运行" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
try {
|
||||
$health = Invoke-RestMethod -Uri "http://${DashboardHost}:${DashboardPort}/healthz" -TimeoutSec 3
|
||||
if ($health.status -eq "ok") {
|
||||
Write-Host " 健康检查: 正常" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " 健康检查: degraded" -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host " 看板地址: http://${DashboardHost}:${DashboardPort}" -ForegroundColor Blue
|
||||
} catch {
|
||||
Write-Host " 健康检查: 无法连接" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-All {
|
||||
Write-Host "正在关闭服务..." -ForegroundColor Yellow
|
||||
$stopped = 0
|
||||
foreach ($item in @(
|
||||
@{ Name = "看板服务器"; File = $ServerPidFile },
|
||||
@{ Name = "数据刷新循环"; File = $LoopPidFile },
|
||||
@{ Name = "OpenCode server"; File = $OpenCodePidFile }
|
||||
)) {
|
||||
if (Stop-PidFile $item.File) {
|
||||
Write-Host " 已停止 $($item.Name)" -ForegroundColor Green
|
||||
$stopped += 1
|
||||
}
|
||||
}
|
||||
Remove-Item (Join-Path $env:TEMP "sansheng_liubu_opencode_refresh.pid") -Force -ErrorAction SilentlyContinue
|
||||
if ($stopped -eq 0) {
|
||||
Write-Host " 没有发现由本脚本管理的服务" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
function Show-Logs([string]$Which) {
|
||||
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
|
||||
$files = switch ($Which) {
|
||||
"server" { @("server.log", "server.err.log") }
|
||||
"loop" { @("loop.log", "loop.err.log") }
|
||||
"opencode" { @("opencode.log", "opencode.err.log") }
|
||||
default { @("server.log", "server.err.log", "loop.log", "loop.err.log", "opencode.log", "opencode.err.log") }
|
||||
}
|
||||
$paths = $files | ForEach-Object { Join-Path $LogDir $_ } | Where-Object { Test-Path $_ }
|
||||
if (-not $paths) {
|
||||
Write-Host "暂无日志文件: $LogDir" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
Get-Content -Path $paths -Wait -Tail 80
|
||||
}
|
||||
|
||||
switch ($Command) {
|
||||
"opencode" {
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $RepoDir "scripts\start_opencode.ps1")
|
||||
}
|
||||
"stop" { Stop-All }
|
||||
"status" { Show-Status }
|
||||
"logs" { Show-Logs $Target }
|
||||
default {
|
||||
Write-Host "用法: powershell -ExecutionPolicy Bypass -File .\edict.ps1 {opencode|stop|status|logs}" -ForegroundColor Yellow
|
||||
Write-Host " opencode 启动 OpenCode 模式"
|
||||
Write-Host " stop 停止脚本管理的服务"
|
||||
Write-Host " status 查看状态"
|
||||
Write-Host " logs 查看日志,可选 server|loop|opencode|all"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
@@ -303,9 +303,12 @@ Write-Host "下一步:"
|
||||
Write-Host " 1. 配置 API Key(如尚未配置):"
|
||||
Write-Host " openclaw agents add taizi # 按提示输入 Anthropic API Key"
|
||||
Write-Host " .\install.ps1 # 重新运行以同步到所有 Agent"
|
||||
Write-Host " 2. 启动数据刷新循环: bash scripts/run_loop.sh"
|
||||
Write-Host " 2. 启动数据刷新循环: powershell -ExecutionPolicy Bypass -File .\scripts\run_loop.ps1"
|
||||
Write-Host " 3. 启动看板服务器: python dashboard/server.py"
|
||||
Write-Host " 4. 打开看板: http://127.0.0.1:7891"
|
||||
Write-Host ""
|
||||
Write-Host " OpenCode 模式可直接运行:"
|
||||
Write-Host " powershell -ExecutionPolicy Bypass -File .\edict.ps1 opencode"
|
||||
Write-Host ""
|
||||
Warn "首次安装必须配置 API Key,否则 Agent 会报错"
|
||||
Info "文档: docs/getting-started.md"
|
||||
|
||||
128
scripts/run_loop_opencode.ps1
Normal file
128
scripts/run_loop_opencode.ps1
Normal file
@@ -0,0 +1,128 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
三省六部 OpenCode 数据刷新循环 (Windows PowerShell 版本)
|
||||
.DESCRIPTION
|
||||
run_loop_opencode.sh 的 Windows 等效脚本。
|
||||
用法: .\scripts\run_loop_opencode.ps1 [-Interval 15] [-ScanInterval 120]
|
||||
#>
|
||||
#Requires -Version 5.1
|
||||
param(
|
||||
[int]$Interval = 15,
|
||||
[int]$ScanInterval = 120
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$RepoDir = Split-Path -Parent $ScriptDir
|
||||
$env:EDICT_HOME = if ($env:EDICT_HOME) { $env:EDICT_HOME } else { $RepoDir }
|
||||
$env:EDICT_RUNTIME = "opencode"
|
||||
$env:EDICT_AGENT_RUNTIME = "opencode"
|
||||
|
||||
$Log = Join-Path $env:TEMP "sansheng_liubu_opencode_refresh.log"
|
||||
$PidFile = Join-Path $env:TEMP "sansheng_liubu_opencode_refresh.pid"
|
||||
$MaxLogSize = 10MB
|
||||
$ScriptTimeout = 30
|
||||
$DashboardPort = if ($env:EDICT_DASHBOARD_PORT) { $env:EDICT_DASHBOARD_PORT } else { "7891" }
|
||||
|
||||
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) { return @{ File = $cmd.Source; Prefix = @() } }
|
||||
}
|
||||
$py = Get-Command py -ErrorAction SilentlyContinue
|
||||
if ($py) { return @{ File = $py.Source; Prefix = @("-3") } }
|
||||
throw "未找到 python / python3 / py。请先安装 Python 3.10+。"
|
||||
}
|
||||
|
||||
$Python = Resolve-Python
|
||||
|
||||
if (Test-Path $PidFile) {
|
||||
$OldPid = Get-Content $PidFile -ErrorAction SilentlyContinue
|
||||
if ($OldPid -and (Get-Process -Id $OldPid -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "已有 OpenCode 刷新循环运行中 (PID=$OldPid),退出"
|
||||
exit 1
|
||||
}
|
||||
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
$PID | Out-File $PidFile -Encoding ASCII
|
||||
|
||||
$cleanup = {
|
||||
"$(Get-Date -Format HH:mm:ss) [opencode-loop] Shutting down..." | Out-File $Log -Append -Encoding UTF8
|
||||
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Register-EngineEvent PowerShell.Exiting -Action $cleanup | Out-Null
|
||||
|
||||
function Rotate-Log {
|
||||
if (Test-Path $Log) {
|
||||
$size = (Get-Item $Log).Length
|
||||
if ($size -gt $MaxLogSize) {
|
||||
Move-Item $Log "$Log.1" -Force
|
||||
"$(Get-Date -Format HH:mm:ss) [opencode-loop] Log rotated" | Out-File $Log -Encoding UTF8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Safe-Run([string]$Script) {
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = $Python.File
|
||||
$args = @($Python.Prefix) + @($Script)
|
||||
$psi.Arguments = ($args | ForEach-Object { '"' + ($_ -replace '"', '\"') + '"' }) -join " "
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $true
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.WorkingDirectory = $RepoDir
|
||||
|
||||
$process = New-Object System.Diagnostics.Process
|
||||
$process.StartInfo = $psi
|
||||
$process.Start() | Out-Null
|
||||
|
||||
if (-not $process.WaitForExit($ScriptTimeout * 1000)) {
|
||||
try {
|
||||
$process.Kill()
|
||||
"$(Get-Date -Format HH:mm:ss) [opencode-loop] Script timeout (${ScriptTimeout}s): $Script" | Out-File $Log -Append -Encoding UTF8
|
||||
} catch {}
|
||||
}
|
||||
|
||||
$stdout = $process.StandardOutput.ReadToEnd()
|
||||
$stderr = $process.StandardError.ReadToEnd()
|
||||
if ($stdout) { $stdout | Out-File $Log -Append -Encoding UTF8 }
|
||||
if ($stderr) { $stderr | Out-File $Log -Append -Encoding UTF8 }
|
||||
}
|
||||
|
||||
function Invoke-SchedulerScan {
|
||||
try {
|
||||
Invoke-RestMethod -Uri "http://127.0.0.1:$DashboardPort/api/scheduler-scan" `
|
||||
-Method POST `
|
||||
-ContentType "application/json" `
|
||||
-Body '{"thresholdSec":180}' `
|
||||
-TimeoutSec 5 | Out-Null
|
||||
} catch {
|
||||
$_ | Out-File $Log -Append -Encoding UTF8
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "三省六部 OpenCode 数据刷新循环启动 (PID=$PID)"
|
||||
Write-Host " Script dir: $ScriptDir"
|
||||
Write-Host " Interval: ${Interval}s Scan: ${ScanInterval}s Timeout: ${ScriptTimeout}s"
|
||||
Write-Host " Log: $Log"
|
||||
Write-Host " Ctrl+C to stop"
|
||||
|
||||
$ScanCounter = 0
|
||||
while ($true) {
|
||||
Rotate-Log
|
||||
Safe-Run (Join-Path $ScriptDir "sync_opencode_agents.py")
|
||||
Safe-Run (Join-Path $ScriptDir "refresh_live_data.py")
|
||||
|
||||
$ScanCounter += $Interval
|
||||
if ($ScanCounter -ge $ScanInterval) {
|
||||
$ScanCounter = 0
|
||||
Invoke-SchedulerScan
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds $Interval
|
||||
}
|
||||
261
scripts/start_opencode.ps1
Normal file
261
scripts/start_opencode.ps1
Normal file
@@ -0,0 +1,261 @@
|
||||
<#
|
||||
.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 {}
|
||||
Reference in New Issue
Block a user