Files
wehub-resource-sync aa1a5d5763
CI / Release hygiene (push) Waiting to run
CI / Python targeted tests (push) Waiting to run
CI / Frontend build (push) Waiting to run
Pages / Build and deploy public pages (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:18:17 +08:00

1587 lines
54 KiB
PowerShell

#requires -Version 5.1
param(
[Parameter(Position = 0)]
[string]$Action = "menu"
)
Set-StrictMode -Version 2.0
$ErrorActionPreference = "Stop"
if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) {
throw "scripts/god.ps1 is for native Windows PowerShell. Use ./scripts/god.sh on macOS/Linux."
}
function Join-PathMany {
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$Parts)
if ($Parts.Count -eq 0) {
return ""
}
$path = $Parts[0]
for ($i = 1; $i -lt $Parts.Count; $i++) {
$path = Join-Path $path $Parts[$i]
}
return $path
}
function Write-GodLog {
param([string]$Message)
Write-Host "[GOD] $Message"
}
function Write-GodDiagnostic {
param([AllowNull()][string]$Message)
[Console]::Error.WriteLine($Message)
}
function Stop-God {
param([string]$Message)
throw "[GOD] error: $Message"
}
function Test-CommandExists {
param([string]$Name)
return [bool](Get-Command $Name -ErrorAction SilentlyContinue)
}
function Get-ToolPath {
param([string[]]$Names)
foreach ($name in $Names) {
$cmd = Get-Command $name -ErrorAction SilentlyContinue
if ($cmd) {
return $cmd.Source
}
}
return $Names[0]
}
function Quote-PSLiteral {
param([AllowNull()][string]$Value)
if ($null -eq $Value) {
return "''"
}
return "'" + $Value.Replace("'", "''") + "'"
}
function Write-TextUtf8NoBom {
param(
[string]$Path,
[string]$Content
)
$parent = Split-Path -Parent $Path
if ($parent) {
[System.IO.Directory]::CreateDirectory($parent) | Out-Null
}
$encoding = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($Path, $Content, $encoding)
}
$script:RootDir = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
$script:EnvFile = if ($env:GOD_ENV_FILE) { $env:GOD_ENV_FILE } else { Join-Path $script:RootDir ".env" }
$script:InitialEnv = @{
GOD_EXPERIMENT = $env:GOD_EXPERIMENT
GOD_EXPERIMENT_RUN = $env:GOD_EXPERIMENT_RUN
GOD_HYPOTHESIS_ID = $env:GOD_HYPOTHESIS_ID
GOD_EXPERIMENT_ID = $env:GOD_EXPERIMENT_ID
LIVE_WORKSPACE_PATH = $env:LIVE_WORKSPACE_PATH
GOD_MAP_ID = $env:GOD_MAP_ID
}
$script:StateDir = Join-Path $script:RootDir ".god"
$script:LogDir = Join-Path $script:StateDir "logs"
$script:PidDir = Join-Path $script:StateDir "pids"
$script:RunDir = Join-Path $script:StateDir "run"
New-Item -ItemType Directory -Force -Path $script:LogDir, $script:PidDir, $script:RunDir | Out-Null
$script:BackendPidFile = Join-Path $script:PidDir "backend.pid"
$script:FrontendPidFile = Join-Path $script:PidDir "frontend.pid"
$script:RuntimePidFile = Join-Path $script:PidDir "runtime.pid"
$script:CurrentExperimentFile = Join-Path $script:StateDir "current_experiment.json"
$script:StartRequestFile = Join-Path $script:RunDir "start-request.json"
$script:BackendLog = Join-Path $script:LogDir "backend.log"
$script:FrontendLog = Join-Path $script:LogDir "frontend.log"
$script:RuntimeLog = Join-Path $script:LogDir "runtime.log"
$script:RuntimeInitLog = Join-Path $script:LogDir "runtime-init.log"
function Get-EnvValue {
param(
[string]$Name,
[string]$Default = ""
)
$value = [System.Environment]::GetEnvironmentVariable($Name, "Process")
if ([string]::IsNullOrEmpty($value)) {
return $Default
}
return $value
}
function Set-ProcessEnv {
param(
[string]$Name,
[AllowNull()][string]$Value
)
[System.Environment]::SetEnvironmentVariable($Name, $Value, "Process")
}
function Remove-ProcessEnv {
param([string]$Name)
[System.Environment]::SetEnvironmentVariable($Name, $null, "Process")
}
function Refresh-ConfigFromEnv {
$script:BackendRoot = Get-EnvValue "BACKEND_ROOT" (Join-Path $script:RootDir "agentsociety")
$script:RuntimeRoot = Get-EnvValue "RUNTIME_ROOT" (Join-Path $script:RootDir "jiuwenclaw")
$script:LiveWorkspacePath = Get-EnvValue "LIVE_WORKSPACE_PATH" (Join-Path $script:BackendRoot "quick_experiments")
$script:RuntimeInstance = Get-EnvValue "RUNTIME_INSTANCE" "god-town"
$script:RuntimeMode = Get-EnvValue "RUNTIME_MODE" "dev"
$script:RuntimeLanguage = Get-EnvValue "RUNTIME_LANGUAGE" "zh"
$script:RuntimeLegacyInstances = (Get-EnvValue "RUNTIME_LEGACY_INSTANCES" "jiuwenclaw-town jiuwenclaw-town-native-skill").Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)
$script:RuntimeAgentPort = [int](Get-EnvValue "RUNTIME_AGENT_PORT" "19092")
$script:RuntimeWebPort = [int](Get-EnvValue "RUNTIME_WEB_PORT" "20000")
$script:RuntimeGatewayPort = [int](Get-EnvValue "RUNTIME_GATEWAY_PORT" "20001")
$script:RuntimeUiPort = [int](Get-EnvValue "RUNTIME_UI_PORT" "6173")
$script:GodExtraStopPorts = (Get-EnvValue "GOD_EXTRA_STOP_PORTS" "20092 21000 21001 7173").Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries) | ForEach-Object { [int]$_ }
$script:GodExperiment = Get-EnvValue "GOD_EXPERIMENT" (Get-EnvValue "GOD_HYPOTHESIS_ID" "god_town")
$script:GodExperimentRun = Get-EnvValue "GOD_EXPERIMENT_RUN" (Get-EnvValue "GOD_EXPERIMENT_ID" "1")
$script:GodBackendHost = Get-EnvValue "GOD_BACKEND_HOST" (Get-EnvValue "BACKEND_HOST" "127.0.0.1")
$script:GodBackendPort = [int](Get-EnvValue "GOD_BACKEND_PORT" (Get-EnvValue "BACKEND_PORT" "8001"))
$script:GodFrontendPort = [int](Get-EnvValue "GOD_FRONTEND_PORT" (Get-EnvValue "AGENTSOCIETY_FRONTEND_PORT" "5174"))
$script:GodLiveStepTimeout = Get-EnvValue "GOD_LIVE_STEP_TIMEOUT" (Get-EnvValue "AGENTSOCIETY_LIVE_STEP_TIMEOUT" "900")
$script:BackendUrl = "http://$($script:GodBackendHost):$($script:GodBackendPort)"
$script:FrontendUrl = "http://127.0.0.1:$($script:GodFrontendPort)"
$script:RuntimeUiUrl = "http://localhost:$($script:RuntimeUiPort)"
}
Refresh-ConfigFromEnv
function Show-Usage {
@"
Usage: .\scripts\god.ps1 [menu|setup|configure|start|restart|new-run|stop|status|tail|open]
menu Interactive menu.
setup Install or check Python and Node dependencies only.
configure Open the experiment setup wizard and wait for a new experiment request.
start Start GOD (idempotent; reuses running services) and open frontend pages.
restart Stop everything cleanly, then start.
new-run Stop, wipe the current run, then start a fresh session.
stop Stop GOD and release its ports.
status Print URLs, ports, and model status.
tail Follow GOD service logs.
open Open the GOD frontend pages in the default browser.
"@ | Write-Host
}
function Update-ProcessPath {
$currentPath = $env:Path
$machinePath = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
$userPath = [System.Environment]::GetEnvironmentVariable("Path", "User")
$localBin = Join-Path $HOME ".local\bin"
$parts = @($currentPath, $machinePath, $userPath, $localBin) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
$env:Path = ($parts -join [System.IO.Path]::PathSeparator)
}
function Require-Winget {
if (-not (Test-CommandExists "winget")) {
Stop-God "winget is required to auto-install Git and Node.js on Windows. Install App Installer from Microsoft Store, then rerun this command."
}
}
function Install-WingetPackage {
param(
[string]$Id,
[string]$Name
)
Require-Winget
Write-GodLog "Installing $Name with winget"
& winget install --id $Id -e --accept-package-agreements --accept-source-agreements
if ($LASTEXITCODE -ne 0) {
Stop-God "winget failed to install $Name ($Id)"
}
Update-ProcessPath
}
function Install-Uv {
Write-GodLog "Installing uv"
Invoke-RestMethod https://astral.sh/uv/install.ps1 | Invoke-Expression
Update-ProcessPath
if (-not (Test-CommandExists "uv")) {
Stop-God "uv was installed but is not available on PATH. Open a new PowerShell window and rerun this command."
}
}
function Test-NpmAvailable {
return (Test-CommandExists "npm.cmd") -or (Test-CommandExists "npm")
}
function Ensure-Prerequisites {
if (-not (Test-CommandExists "uv")) {
Install-Uv
}
if (-not (Test-CommandExists "git")) {
Install-WingetPackage "Git.Git" "Git"
}
if (-not (Test-NpmAvailable)) {
Install-WingetPackage "OpenJS.NodeJS.LTS" "Node.js LTS"
}
Update-ProcessPath
foreach ($tool in @("uv", "git")) {
if (-not (Test-CommandExists $tool)) {
Stop-God "$tool is not available after installation. Open a new PowerShell window and rerun this command."
}
}
if (-not (Test-NpmAvailable)) {
Stop-God "npm is not available after Node.js installation. Open a new PowerShell window and rerun this command."
}
}
function Read-DotEnv {
if (-not (Test-Path $script:EnvFile)) {
return
}
foreach ($line in [System.IO.File]::ReadAllLines($script:EnvFile)) {
if ($line -match '^\s*#' -or $line -notmatch '^\s*([A-Za-z_][A-Za-z0-9_]*)=(.*)$') {
continue
}
$key = $Matches[1]
$value = $Matches[2].Trim()
if (($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'"))) {
if ($value.Length -ge 2) {
$value = $value.Substring(1, $value.Length - 2)
}
}
Set-ProcessEnv $key $value
}
}
function Set-EnvValue {
param(
[string]$Key,
[string]$Value
)
$lines = @()
if (Test-Path $script:EnvFile) {
$lines = [System.IO.File]::ReadAllLines($script:EnvFile)
}
$out = New-Object System.Collections.Generic.List[string]
$seen = $false
foreach ($line in $lines) {
if ($line -like "$Key=*") {
$out.Add("$Key=$Value")
$seen = $true
} else {
$out.Add($line)
}
}
if (-not $seen) {
if ($out.Count -gt 0 -and $out[$out.Count - 1] -ne "") {
$out.Add("")
}
$out.Add("$Key=$Value")
}
Write-TextUtf8NoBom $script:EnvFile (($out -join "`n").TrimEnd() + "`n")
Set-ProcessEnv $Key $Value
Refresh-ConfigFromEnv
}
function Set-EnvValuesInFile {
param(
[string]$Path,
[hashtable]$Values
)
$lines = @()
if (Test-Path $Path) {
$lines = [System.IO.File]::ReadAllLines($Path)
}
$out = New-Object System.Collections.Generic.List[string]
$seen = @{}
foreach ($line in $lines) {
$replaced = $false
foreach ($key in $Values.Keys) {
if ($line -like "$key=*") {
$out.Add("$key=$($Values[$key])")
$seen[$key] = $true
$replaced = $true
break
}
}
if (-not $replaced) {
$out.Add($line)
}
}
foreach ($key in $Values.Keys) {
if (-not $seen.ContainsKey($key)) {
if ($out.Count -gt 0 -and $out[$out.Count - 1] -ne "") {
$out.Add("")
}
$out.Add("$key=$($Values[$key])")
}
}
Write-TextUtf8NoBom $Path (($out -join "`n").TrimEnd() + "`n")
}
function Set-ObjectProperty {
param(
[object]$Object,
[string]$Name,
[object]$Value
)
if ($Object.PSObject.Properties[$Name]) {
$Object.$Name = $Value
} else {
$Object | Add-Member -NotePropertyName $Name -NotePropertyValue $Value
}
}
function Get-ObjectPropertyValue {
param(
[AllowNull()][object]$Object,
[string]$Name,
[AllowNull()][object]$Default = $null
)
if ($null -ne $Object -and $Object.PSObject.Properties[$Name]) {
return $Object.$Name
}
return $Default
}
function Export-InternalEnv {
$godLlmKey = Get-EnvValue "GOD_LLM_API_KEY" (Get-EnvValue "AGENTSOCIETY_LLM_API_KEY" (Get-EnvValue "JIUWENCLAW_API_KEY" ""))
$godLlmBase = Get-EnvValue "GOD_LLM_API_BASE" (Get-EnvValue "AGENTSOCIETY_LLM_API_BASE" (Get-EnvValue "JIUWENCLAW_API_BASE" ""))
$godLlmModel = Get-EnvValue "GOD_LLM_MODEL" (Get-EnvValue "AGENTSOCIETY_LLM_MODEL" (Get-EnvValue "JIUWENCLAW_MODEL" ""))
$godEmbeddingKey = Get-EnvValue "GOD_EMBEDDING_API_KEY" (Get-EnvValue "AGENTSOCIETY_EMBEDDING_API_KEY" (Get-EnvValue "JIUWENCLAW_EMBED_API_KEY" ""))
$godEmbeddingBase = Get-EnvValue "GOD_EMBEDDING_API_BASE" (Get-EnvValue "AGENTSOCIETY_EMBEDDING_API_BASE" (Get-EnvValue "JIUWENCLAW_EMBED_API_BASE" ""))
$godEmbeddingModel = Get-EnvValue "GOD_EMBEDDING_MODEL" (Get-EnvValue "AGENTSOCIETY_EMBEDDING_MODEL" (Get-EnvValue "JIUWENCLAW_EMBED_MODEL" ""))
if ([string]::IsNullOrEmpty($godLlmBase)) { $godLlmBase = "https://api.openai.com/v1" }
if ([string]::IsNullOrEmpty($godLlmModel)) { $godLlmModel = "gpt-5.4" }
$nanoModel = Get-EnvValue "GOD_LLM_NANO_MODEL" ""
if ([string]::IsNullOrEmpty($nanoModel)) {
if ($godLlmModel -eq "gpt-5.4") {
$nanoModel = "gpt-5.4-nano"
} else {
$nanoModel = ($godLlmModel -replace '\.[^.]*$', '') + ".*-nano"
}
}
if ([string]::IsNullOrEmpty($godEmbeddingKey)) { $godEmbeddingKey = $godLlmKey }
if ([string]::IsNullOrEmpty($godEmbeddingBase)) { $godEmbeddingBase = $godLlmBase }
if ([string]::IsNullOrEmpty($godEmbeddingModel)) { $godEmbeddingModel = "text-embedding-3-large" }
Set-ProcessEnv "GOD_LLM_API_KEY" $godLlmKey
Set-ProcessEnv "GOD_LLM_API_BASE" $godLlmBase
Set-ProcessEnv "GOD_LLM_MODEL" $godLlmModel
Set-ProcessEnv "GOD_EMBEDDING_API_KEY" $godEmbeddingKey
Set-ProcessEnv "GOD_EMBEDDING_API_BASE" $godEmbeddingBase
Set-ProcessEnv "GOD_EMBEDDING_MODEL" $godEmbeddingModel
Set-ProcessEnv "AGENTSOCIETY_LLM_API_KEY" $godLlmKey
Set-ProcessEnv "AGENTSOCIETY_LLM_API_BASE" $godLlmBase
Set-ProcessEnv "AGENTSOCIETY_LLM_MODEL" $godLlmModel
Set-ProcessEnv "AGENTSOCIETY_NANO_LLM_MODEL" $nanoModel
Set-ProcessEnv "AGENTSOCIETY_EMBEDDING_API_KEY" $godEmbeddingKey
Set-ProcessEnv "AGENTSOCIETY_EMBEDDING_API_BASE" $godEmbeddingBase
Set-ProcessEnv "AGENTSOCIETY_EMBEDDING_MODEL" $godEmbeddingModel
Set-ProcessEnv "JIUWENCLAW_API_KEY" $godLlmKey
Set-ProcessEnv "JIUWENCLAW_API_BASE" $godLlmBase
Set-ProcessEnv "JIUWENCLAW_MODEL" $godLlmModel
Set-ProcessEnv "JIUWENCLAW_MODEL_PROVIDER" (Get-EnvValue "JIUWENCLAW_MODEL_PROVIDER" "OpenAI")
Set-ProcessEnv "JIUWENCLAW_EMBED_API_KEY" $godEmbeddingKey
Set-ProcessEnv "JIUWENCLAW_EMBED_API_BASE" $godEmbeddingBase
Set-ProcessEnv "JIUWENCLAW_EMBED_MODEL" $godEmbeddingModel
Set-ProcessEnv "BACKEND_HOST" $script:GodBackendHost
Set-ProcessEnv "BACKEND_PORT" ([string]$script:GodBackendPort)
Set-ProcessEnv "AGENTSOCIETY_FRONTEND_PORT" ([string]$script:GodFrontendPort)
Set-ProcessEnv "GOD_HYPOTHESIS_ID" $script:GodExperiment
Set-ProcessEnv "GOD_EXPERIMENT_ID" $script:GodExperimentRun
}
function Load-CurrentExperiment {
if (-not (Test-Path $script:CurrentExperimentFile)) {
return
}
try {
$data = Get-Content $script:CurrentExperimentFile -Raw | ConvertFrom-Json
} catch {
return
}
$hypothesisId = Get-ObjectPropertyValue $data "hypothesis_id" ""
$experimentId = Get-ObjectPropertyValue $data "experiment_id" ""
$workspacePath = Get-ObjectPropertyValue $data "workspace_path" ""
if (($script:InitialEnv.GOD_EXPERIMENT -eq $null) -and ($script:InitialEnv.GOD_HYPOTHESIS_ID -eq $null) -and $hypothesisId) {
Set-ProcessEnv "GOD_EXPERIMENT" ([string]$hypothesisId)
}
if (($script:InitialEnv.GOD_EXPERIMENT_RUN -eq $null) -and ($script:InitialEnv.GOD_EXPERIMENT_ID -eq $null) -and $experimentId) {
Set-ProcessEnv "GOD_EXPERIMENT_RUN" ([string]$experimentId)
}
if (($script:InitialEnv.LIVE_WORKSPACE_PATH -eq $null) -and $workspacePath) {
Set-ProcessEnv "LIVE_WORKSPACE_PATH" ([string]$workspacePath)
}
}
function Load-Env {
Read-DotEnv
if ($script:InitialEnv.GOD_EXPERIMENT -ne $null) {
Set-ProcessEnv "GOD_EXPERIMENT" $script:InitialEnv.GOD_EXPERIMENT
} elseif ($script:InitialEnv.GOD_HYPOTHESIS_ID -ne $null) {
Set-ProcessEnv "GOD_EXPERIMENT" $script:InitialEnv.GOD_HYPOTHESIS_ID
} else {
Remove-ProcessEnv "GOD_EXPERIMENT"
Remove-ProcessEnv "GOD_HYPOTHESIS_ID"
}
if ($script:InitialEnv.GOD_EXPERIMENT_RUN -ne $null) {
Set-ProcessEnv "GOD_EXPERIMENT_RUN" $script:InitialEnv.GOD_EXPERIMENT_RUN
} elseif ($script:InitialEnv.GOD_EXPERIMENT_ID -ne $null) {
Set-ProcessEnv "GOD_EXPERIMENT_RUN" $script:InitialEnv.GOD_EXPERIMENT_ID
} else {
Remove-ProcessEnv "GOD_EXPERIMENT_RUN"
Remove-ProcessEnv "GOD_EXPERIMENT_ID"
}
if ($script:InitialEnv.LIVE_WORKSPACE_PATH -ne $null) {
Set-ProcessEnv "LIVE_WORKSPACE_PATH" $script:InitialEnv.LIVE_WORKSPACE_PATH
}
if ($script:InitialEnv.GOD_MAP_ID -ne $null) {
Set-ProcessEnv "GOD_MAP_ID" $script:InitialEnv.GOD_MAP_ID
} else {
Remove-ProcessEnv "GOD_MAP_ID"
}
Refresh-ConfigFromEnv
Export-InternalEnv
Load-CurrentExperiment
Refresh-ConfigFromEnv
Export-InternalEnv
Refresh-ConfigFromEnv
}
function Get-ConfiguredState {
param([string]$Value)
if ([string]::IsNullOrEmpty($Value)) {
return "missing"
}
return "configured"
}
function Get-RuntimeWorkspace {
return Join-PathMany $HOME ".jiuwenclaw-instances" $script:RuntimeInstance
}
function Ensure-EnvFile {
$envWasCreated = $false
if (-not (Test-Path $script:EnvFile)) {
Copy-Item (Join-Path $script:RootDir ".env.example") $script:EnvFile
$envWasCreated = $true
Write-GodLog "Created .env from .env.example"
}
Load-Env
if ((Get-EnvValue "GOD_SETUP_MODE" "0") -eq "1") {
Set-EnvValue "GOD_LLM_API_BASE" (Get-EnvValue "GOD_LLM_API_BASE" "https://api.openai.com/v1")
Set-EnvValue "GOD_LLM_MODEL" (Get-EnvValue "GOD_LLM_MODEL" "gpt-5.4")
Set-EnvValue "GOD_EMBEDDING_MODEL" (Get-EnvValue "GOD_EMBEDDING_MODEL" "text-embedding-3-large")
Load-Env
return
}
if ([string]::IsNullOrEmpty((Get-EnvValue "GOD_LLM_API_KEY" ""))) {
if ([Environment]::UserInteractive) {
$secureKey = Read-Host "[GOD] LLM API key is required. Paste it now" -AsSecureString
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey)
try {
$apiKey = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
} finally {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
}
if ([string]::IsNullOrEmpty($apiKey)) {
Stop-God "LLM API key is empty"
}
Set-EnvValue "GOD_LLM_API_KEY" $apiKey
} else {
Stop-God "GOD_LLM_API_KEY is empty. Fill $($script:EnvFile) first."
}
}
if ($envWasCreated -and [Environment]::UserInteractive) {
$defaultApiBase = Get-EnvValue "GOD_LLM_API_BASE" "https://api.openai.com/v1"
$defaultModel = Get-EnvValue "GOD_LLM_MODEL" "gpt-5.4"
$apiBaseInput = Read-Host "[GOD] LLM API base URL [$defaultApiBase]"
$modelInput = Read-Host "[GOD] LLM model [$defaultModel]"
if ([string]::IsNullOrEmpty($apiBaseInput)) { $apiBaseInput = $defaultApiBase }
if ([string]::IsNullOrEmpty($modelInput)) { $modelInput = $defaultModel }
Set-EnvValue "GOD_LLM_API_BASE" $apiBaseInput
Set-EnvValue "GOD_LLM_MODEL" $modelInput
}
Set-EnvValue "GOD_LLM_API_BASE" (Get-EnvValue "GOD_LLM_API_BASE" "https://api.openai.com/v1")
Set-EnvValue "GOD_LLM_MODEL" (Get-EnvValue "GOD_LLM_MODEL" "gpt-5.4")
Set-EnvValue "GOD_EMBEDDING_MODEL" (Get-EnvValue "GOD_EMBEDDING_MODEL" "text-embedding-3-large")
Load-Env
}
function Test-PortOpen {
param([int]$Port)
foreach ($connectHost in @("127.0.0.1", "::1", "localhost")) {
$client = New-Object System.Net.Sockets.TcpClient
try {
$async = $client.BeginConnect($connectHost, $Port, $null, $null)
if ($async.AsyncWaitHandle.WaitOne(350)) {
$client.EndConnect($async)
return $true
}
} catch {
} finally {
$client.Close()
}
}
return $false
}
function Wait-ForPort {
param(
[int]$Port,
[string]$Label,
[int]$TimeoutSeconds = 90
)
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
while ((Get-Date) -lt $deadline) {
if (Test-PortOpen $Port) {
Write-GodLog "$Label ready on port $Port"
return
}
Start-Sleep -Seconds 1
}
Write-WaitTimeoutContext $Label $Port
Stop-God "Timed out waiting for $Label on port $Port"
}
function Write-WaitTimeoutContext {
param(
[string]$Label,
[int]$Port
)
Write-GodDiagnostic "[GOD] timeout diagnostics for $Label on port $Port"
try {
Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue |
Format-Table -AutoSize |
Out-String |
ForEach-Object { Write-GodDiagnostic $_ }
} catch {
}
if ($Label -like "Agent runtime*" -and (Test-Path $script:RuntimeLog)) {
Write-GodDiagnostic "[GOD] recent runtime log:"
Get-Content $script:RuntimeLog -Tail 80 | ForEach-Object { Write-GodDiagnostic $_ }
}
if ($Label -eq "Backend" -and (Test-Path $script:BackendLog)) {
Write-GodDiagnostic "[GOD] recent backend log:"
Get-Content $script:BackendLog -Tail 80 | ForEach-Object { Write-GodDiagnostic $_ }
}
}
function ConvertTo-UrlEncoded {
param([string]$Value)
return [System.Uri]::EscapeDataString($Value)
}
function Get-ReplayUrl {
return "$($script:FrontendUrl)/pixel-replay/$($script:GodExperiment)/$($script:GodExperimentRun)"
}
function Get-SessionUrl {
$workspace = ConvertTo-UrlEncoded $script:LiveWorkspacePath
return "$($script:BackendUrl)/api/v1/live-experiments/$($script:GodExperiment)/$($script:GodExperimentRun)/sessions?workspace_path=$workspace"
}
function Get-RunStepUrl {
$workspace = ConvertTo-UrlEncoded $script:LiveWorkspacePath
return "$($script:BackendUrl)/api/v1/live-experiments/$($script:GodExperiment)/$($script:GodExperimentRun)/run-step?workspace_path=$workspace"
}
function Get-StopLiveUrl {
$workspace = ConvertTo-UrlEncoded $script:LiveWorkspacePath
return "$($script:BackendUrl)/api/v1/live-experiments/$($script:GodExperiment)/$($script:GodExperimentRun)/stop?workspace_path=$workspace"
}
function Get-SetupUrl {
return "$($script:FrontendUrl)/setup"
}
function Show-FrontendLinks {
Write-Host ("Control room: {0}" -f (Get-ReplayUrl))
Write-Host ("Agent runtime UI: {0}" -f $script:RuntimeUiUrl)
}
function Open-BrowserWindow {
param([string]$Url)
if ((Get-EnvValue "GOD_OPEN_BROWSER" "1") -ne "1") {
return
}
Start-Process $Url | Out-Null
}
function Open-FrontendPages {
Write-GodLog "Opening frontend pages"
Open-BrowserWindow (Get-ReplayUrl)
Open-BrowserWindow $script:RuntimeUiUrl
}
function Open-SetupPage {
Write-GodLog "Opening setup wizard"
Open-BrowserWindow (Get-SetupUrl)
Write-Host ("Setup wizard: {0}" -f (Get-SetupUrl))
}
function Get-CurrentExperimentConfigPath {
if (-not (Test-Path $script:CurrentExperimentFile)) {
return $null
}
try {
$data = Get-Content $script:CurrentExperimentFile -Raw | ConvertFrom-Json
} catch {
return $null
}
$hypothesisId = [string](Get-ObjectPropertyValue $data "hypothesis_id" "")
$rawExperimentId = Get-ObjectPropertyValue $data "experiment_id" ""
$experimentId = if ($rawExperimentId) { [string]$rawExperimentId } else { "1" }
if ([string]::IsNullOrWhiteSpace($hypothesisId) -or [string]::IsNullOrWhiteSpace($experimentId)) {
return $null
}
$rawWorkspace = Get-ObjectPropertyValue $data "workspace_path" ""
$workspace = if ($rawWorkspace) { [string]$rawWorkspace } else { $script:LiveWorkspacePath }
return Join-PathMany $workspace "hypothesis_$hypothesisId" "experiment_$experimentId" "init" "init_config.json"
}
function Test-CurrentExperimentConfig {
$configPath = Get-CurrentExperimentConfigPath
return (-not [string]::IsNullOrEmpty($configPath)) -and (Test-Path $configPath)
}
function Test-PendingStartRequest {
if (-not (Test-Path $script:StartRequestFile)) {
return $false
}
if ((Test-Path $script:CurrentExperimentFile) -and ((Get-Item $script:StartRequestFile).LastWriteTime -lt (Get-Item $script:CurrentExperimentFile).LastWriteTime)) {
$stalePath = "$($script:StartRequestFile).stale.$((Get-Date).ToString('yyyyMMdd_HHmmss'))"
Write-GodLog "Ignoring stale setup startup request: $($script:StartRequestFile)"
try {
Move-Item $script:StartRequestFile $stalePath -Force
} catch {
Remove-Item $script:StartRequestFile -Force -ErrorAction SilentlyContinue
}
return $false
}
return $true
}
function Test-ReadyStartConfig {
Load-Env
return (-not [string]::IsNullOrEmpty((Get-EnvValue "GOD_LLM_API_KEY" ""))) -and (Test-CurrentExperimentConfig)
}
function Require-CurrentExperimentConfig {
if (-not (Test-CurrentExperimentConfig)) {
Stop-God "No current experiment is configured. Run .\scripts\god.ps1 start and choose an experiment in setup."
}
}
function Stop-ProcessTree {
param([int]$ProcessId)
if ($ProcessId -le 0) {
return
}
try {
$children = Get-CimInstance Win32_Process -Filter "ParentProcessId = $ProcessId" -ErrorAction SilentlyContinue
foreach ($child in $children) {
Stop-ProcessTree -ProcessId ([int]$child.ProcessId)
}
} catch {
}
try {
$proc = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue
if ($proc) {
Stop-Process -Id $ProcessId -Force -ErrorAction SilentlyContinue
}
} catch {
}
}
function Stop-PidFile {
param(
[string]$Path,
[string]$Label
)
if (-not (Test-Path $Path)) {
return
}
$processIdText = (Get-Content $Path -ErrorAction SilentlyContinue | Select-Object -First 1)
$processId = 0
[int]::TryParse($processIdText, [ref]$processId) | Out-Null
if ($processId -gt 0 -and (Get-Process -Id $processId -ErrorAction SilentlyContinue)) {
Write-GodLog "Stopping $Label pid=$processId"
Stop-ProcessTree -ProcessId $processId
}
Remove-Item $Path -Force -ErrorAction SilentlyContinue
}
function Stop-ListenersOnPort {
param([int]$Port)
$processIds = @()
try {
$processIds = @(Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty OwningProcess -Unique)
} catch {
$processIds = @()
}
if ($processIds.Count -eq 0) {
try {
$processIds = @(netstat -ano -p tcp |
Select-String "LISTENING" |
Where-Object { $_.Line -match "[:.]$Port\s" } |
ForEach-Object { ($_ -split '\s+')[-1] } |
Select-Object -Unique)
} catch {
$processIds = @()
}
}
$processIds = @($processIds | Where-Object { $_ -and ([int]$_) -gt 0 } | Select-Object -Unique)
if ($processIds.Count -eq 0) {
return
}
Write-GodLog "Clearing port $Port"
foreach ($processId in $processIds) {
Stop-ProcessTree -ProcessId ([int]$processId)
}
}
function Stop-ControlServices {
Stop-PidFile $script:FrontendPidFile "control room"
Stop-PidFile $script:BackendPidFile "backend"
Stop-ListenersOnPort $script:GodFrontendPort
Stop-ListenersOnPort $script:GodBackendPort
}
function Stop-LivePorts {
$ports = @(
$script:GodBackendPort,
$script:GodFrontendPort,
$script:RuntimeAgentPort,
$script:RuntimeWebPort,
$script:RuntimeGatewayPort,
$script:RuntimeUiPort
) + $script:GodExtraStopPorts
foreach ($port in $ports) {
Stop-ListenersOnPort ([int]$port)
}
}
function Start-DetachedService {
param(
[string]$SessionName,
[string]$PidFile,
[string]$Command,
[string]$LogPath = ""
)
$psExe = (Get-Process -Id $PID).Path
if ([string]::IsNullOrEmpty($psExe)) {
$psExe = "powershell.exe"
}
if (-not [string]::IsNullOrEmpty($LogPath)) {
$Command = @(
"`$ErrorActionPreference = 'Stop'",
"try {",
$Command,
"} catch {",
" '[GOD] service startup error' | Out-File -FilePath $(Quote-PSLiteral $LogPath) -Append -Encoding utf8",
" `$_.Exception.Message | Out-File -FilePath $(Quote-PSLiteral $LogPath) -Append -Encoding utf8",
" `$_.ScriptStackTrace | Out-File -FilePath $(Quote-PSLiteral $LogPath) -Append -Encoding utf8",
" exit 1",
"}"
) -join "`n"
}
$encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($Command))
$process = Start-Process -FilePath $psExe `
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-EncodedCommand", $encoded) `
-PassThru `
-WindowStyle Hidden
Write-TextUtf8NoBom $PidFile ("$($process.Id)`n")
Start-Sleep -Seconds 2
if (-not (Get-Process -Id $process.Id -ErrorAction SilentlyContinue)) {
if (-not [string]::IsNullOrEmpty($LogPath) -and (Test-Path $LogPath)) {
Write-GodDiagnostic "[GOD] recent $SessionName log ($LogPath):"
Get-Content $LogPath -Tail 80 -ErrorAction SilentlyContinue | ForEach-Object { Write-GodDiagnostic $_ }
}
Stop-God "$SessionName exited during startup. See $LogPath"
}
}
function Stop-RuntimeInstance {
param([string]$Name)
if (-not (Test-Path $script:RuntimeRoot)) {
return
}
if (-not (Test-CommandExists "uv")) {
return
}
Push-Location $script:RuntimeRoot
try {
Invoke-NativeCommand -FilePath (Get-ToolPath @("uv")) -ArgumentList @("run", "jiuwenclaw-start", "--stop", $Name) *> $null
} catch {
} finally {
Pop-Location
}
}
function Stop-All {
Write-GodLog "Stopping GOD services"
if (Test-PortOpen $script:GodBackendPort) {
try {
Invoke-RestMethod -Method Post -Uri (Get-StopLiveUrl) | Out-Null
} catch {
}
}
Stop-PidFile $script:FrontendPidFile "control room"
Stop-PidFile $script:BackendPidFile "backend"
Stop-PidFile $script:RuntimePidFile "agent runtime"
Stop-RuntimeInstance $script:RuntimeInstance
foreach ($name in $script:RuntimeLegacyInstances) {
Stop-RuntimeInstance $name
}
Stop-LivePorts
}
function Remove-PathSafe {
param(
[string]$Path,
[string]$Label
)
if (Test-Path $Path) {
Write-GodLog "Removing $Label"
Remove-Item $Path -Recurse -Force -ErrorAction SilentlyContinue
}
}
function Remove-ExperimentRuns {
if (-not (Test-Path $script:LiveWorkspacePath)) {
return
}
Write-GodLog "Removing experiment run directories"
Get-ChildItem -Path $script:LiveWorkspacePath -Directory -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.Name -eq "run" -or $_.Name -like "run_*" } |
ForEach-Object { Remove-Item $_.FullName -Recurse -Force -ErrorAction SilentlyContinue }
}
function Remove-RuntimeRegistryEntries {
if (-not (Test-Path $script:RuntimeRoot) -or -not (Test-CommandExists "uv")) {
return
}
$names = @($script:RuntimeInstance) + $script:RuntimeLegacyInstances
$nameArgs = ($names | ForEach-Object { Quote-PSLiteral $_ }) -join ", "
$code = @"
from pathlib import Path
from ruamel.yaml import YAML
names = {$nameArgs}
path = Path.home() / ".jiuwenclaw" / "instances.yaml"
if not path.exists():
raise SystemExit(0)
yaml = YAML()
data = yaml.load(path.read_text(encoding="utf-8")) or {}
instances = data.get("instances")
if not isinstance(instances, dict):
raise SystemExit(0)
for name in names:
instances.pop(name, None)
if instances:
with path.open("w", encoding="utf-8") as f:
yaml.dump(data, f)
else:
path.unlink()
"@
Push-Location $script:RuntimeRoot
try {
$code | & (Get-ToolPath @("uv")) run python -
} catch {
Write-GodLog "Could not update runtime registry; continuing reset"
} finally {
Pop-Location
}
}
function Reset-Factory {
Ensure-Prerequisites
Write-GodLog "Factory reset"
Stop-All
Remove-ExperimentRuns
Remove-RuntimeRegistryEntries
foreach ($name in @($script:RuntimeInstance) + $script:RuntimeLegacyInstances) {
Remove-PathSafe (Join-PathMany $HOME ".jiuwenclaw-instances" $name) "runtime instance state"
}
Remove-PathSafe $script:EnvFile ".env"
Remove-PathSafe $script:StateDir "state and logs"
Remove-PathSafe (Join-Path $script:BackendRoot ".venv") "backend virtualenv"
Remove-PathSafe (Join-Path $script:RuntimeRoot ".venv") "runtime virtualenv"
Remove-PathSafe (Join-PathMany $script:BackendRoot "frontend" "node_modules") "frontend dependencies"
Remove-PathSafe (Join-PathMany $script:RuntimeRoot "jiuwenclaw" "channels" "web" "frontend" "node_modules") "runtime UI dependencies"
Write-GodLog "Factory reset complete."
}
function Invoke-NativeCommand {
param(
[Parameter(ValueFromPipeline = $true)]
[AllowNull()][object]$InputObject,
[string]$FilePath,
[object[]]$ArgumentList,
[string]$LogPath = ""
)
begin {
$previousEap = $ErrorActionPreference
$ErrorActionPreference = "Continue"
$pipelineInput = New-Object System.Collections.Generic.List[object]
}
process {
if ($null -ne $InputObject) {
$pipelineInput.Add($InputObject)
}
}
end {
try {
$hasInput = $pipelineInput.Count -gt 0
$inputText = if ($hasInput) { ($pipelineInput | ForEach-Object { [string]$_ }) -join [Environment]::NewLine } else { $null }
if ($LogPath) {
if ($hasInput) {
$inputText | & $FilePath @ArgumentList 2>&1 | Out-File -FilePath $LogPath -Append -Encoding utf8
} else {
& $FilePath @ArgumentList 2>&1 | Out-File -FilePath $LogPath -Append -Encoding utf8
}
} else {
if ($hasInput) {
$inputText | & $FilePath @ArgumentList
} else {
& $FilePath @ArgumentList
}
}
} finally {
$ErrorActionPreference = $previousEap
}
}
}
function Get-DetachedNativeExecLine {
param(
[string]$FilePath,
[string]$ArgumentExpression,
[string]$LogPath = ""
)
$redirect = if ($LogPath) {
" 2>&1 | Out-File -FilePath $(Quote-PSLiteral $LogPath) -Append -Encoding utf8"
} else {
""
}
return "`$ErrorActionPreference = 'Continue'; & $(Quote-PSLiteral $FilePath) $ArgumentExpression$redirect; if (`$LASTEXITCODE -ne 0) { exit `$LASTEXITCODE }"
}
function Invoke-CheckedCommand {
param(
[string]$FilePath,
[string[]]$Arguments,
[string]$WorkingDirectory
)
Push-Location $WorkingDirectory
try {
Invoke-NativeCommand -FilePath $FilePath -ArgumentList $Arguments
if ($LASTEXITCODE -ne 0) {
Stop-God "Command failed: $FilePath $($Arguments -join ' ')"
}
} finally {
Pop-Location
}
}
function Ensure-BackendDependencies {
if ((Get-EnvValue "GOD_SKIP_SETUP" "0") -eq "1") {
return
}
$uv = Get-ToolPath @("uv")
Write-GodLog "Ensuring backend Python dependencies (first run may take several minutes)"
Invoke-CheckedCommand -FilePath $uv -Arguments @("sync", "--managed-python") -WorkingDirectory $script:BackendRoot
}
function Setup-Dependencies {
Ensure-Prerequisites
if ((Get-EnvValue "GOD_SKIP_SETUP" "0") -eq "1") {
Write-GodLog "Skipping dependency setup (GOD_SKIP_SETUP=1)"
return
}
$uv = Get-ToolPath @("uv")
$npm = Get-ToolPath @("npm.cmd", "npm")
Write-GodLog "Syncing backend Python dependencies"
Invoke-CheckedCommand -FilePath $uv -Arguments @("sync", "--managed-python") -WorkingDirectory $script:BackendRoot
Write-GodLog "Syncing agent runtime Python dependencies"
Invoke-CheckedCommand -FilePath $uv -Arguments @("sync", "--managed-python") -WorkingDirectory $script:RuntimeRoot
$backendFrontend = Join-Path $script:BackendRoot "frontend"
if ((-not (Test-Path (Join-Path $backendFrontend "node_modules"))) -or (Get-EnvValue "GOD_FORCE_SETUP" "0") -eq "1") {
Write-GodLog "Installing control-room dependencies"
Invoke-CheckedCommand -FilePath $npm -Arguments @("install", "--no-audit", "--no-fund", "--loglevel=error") -WorkingDirectory $backendFrontend
}
$runtimeFrontend = Join-PathMany $script:RuntimeRoot "jiuwenclaw" "channels" "web" "frontend"
if ((-not (Test-Path (Join-Path $runtimeFrontend "node_modules"))) -or (Get-EnvValue "GOD_FORCE_SETUP" "0") -eq "1") {
Write-GodLog "Installing runtime UI dependencies"
Invoke-CheckedCommand -FilePath $npm -Arguments @("install", "--no-audit", "--no-fund", "--loglevel=error") -WorkingDirectory $runtimeFrontend
}
}
function Sync-RuntimeModelEnv {
$workspace = Get-RuntimeWorkspace
if (-not (Test-Path $workspace)) {
return
}
$apiKey = Get-EnvValue "GOD_LLM_API_KEY" ""
$apiBase = Get-EnvValue "GOD_LLM_API_BASE" ""
$model = Get-EnvValue "GOD_LLM_MODEL" ""
if ([string]::IsNullOrEmpty($apiKey) -or [string]::IsNullOrEmpty($apiBase) -or [string]::IsNullOrEmpty($model)) {
return
}
Write-GodLog "Syncing runtime model config"
$configEnv = Join-PathMany $workspace "config" ".env"
Set-EnvValuesInFile $configEnv @{
API_KEY = $apiKey
API_BASE = $apiBase
MODEL_NAME = $model
MODEL_PROVIDER = Get-EnvValue "JIUWENCLAW_MODEL_PROVIDER" "OpenAI"
EMBED_API_KEY = Get-EnvValue "GOD_EMBEDDING_API_KEY" $apiKey
EMBED_API_BASE = Get-EnvValue "GOD_EMBEDDING_API_BASE" $apiBase
EMBED_MODEL = Get-EnvValue "GOD_EMBEDDING_MODEL" ""
}
}
function Ensure-RuntimeInstance {
Setup-Dependencies
$uv = Get-ToolPath @("uv")
$existing = $false
Push-Location $script:RuntimeRoot
try {
Invoke-NativeCommand -FilePath $uv -ArgumentList @("run", "jiuwenclaw-start", "--status", $script:RuntimeInstance) *> $null
$existing = ($LASTEXITCODE -eq 0)
} finally {
Pop-Location
}
if (-not $existing) {
Write-GodLog "Initializing agent runtime instance (default language: $($script:RuntimeLanguage))"
Write-TextUtf8NoBom $script:RuntimeInitLog ""
$workspace = Get-RuntimeWorkspace
$initInput = if (Test-Path $workspace) { "yes`n$($script:RuntimeLanguage)`n" } else { "$($script:RuntimeLanguage)`n" }
Push-Location $script:RuntimeRoot
try {
$initInput | Invoke-NativeCommand -FilePath $uv -ArgumentList @("run", "jiuwenclaw-init", "--name", $script:RuntimeInstance) -LogPath $script:RuntimeInitLog
if ($LASTEXITCODE -ne 0) {
Get-Content $script:RuntimeInitLog -Tail 80 -ErrorAction SilentlyContinue | ForEach-Object { Write-GodDiagnostic $_ }
Stop-God "Failed to initialize agent runtime instance"
}
} finally {
Pop-Location
}
}
$code = @'
import os
from pathlib import Path
from ruamel.yaml import YAML
name = os.environ["RUNTIME_INSTANCE"]
path = Path.home() / ".jiuwenclaw" / "instances.yaml"
path.parent.mkdir(parents=True, exist_ok=True)
yaml = YAML()
data = yaml.load(path.read_text(encoding="utf-8")) if path.exists() else None
if not isinstance(data, dict):
data = {"instances": {}}
instances = data.setdefault("instances", {})
entry = instances.setdefault(name, {})
workspace = entry.get("workspace") or str(Path.home() / ".jiuwenclaw-instances" / name)
entry["workspace"] = workspace
entry["ports"] = {
"agent_server": int(os.environ["RUNTIME_AGENT_PORT"]),
"web": int(os.environ["RUNTIME_WEB_PORT"]),
"gateway": int(os.environ["RUNTIME_GATEWAY_PORT"]),
"frontend": int(os.environ["RUNTIME_UI_PORT"]),
}
with path.open("w", encoding="utf-8") as f:
yaml.dump(data, f)
workspace_path = Path(workspace).expanduser()
workspace_path.mkdir(parents=True, exist_ok=True)
bootstrap_env = workspace_path / ".env"
bootstrap_env.write_text(
"\n".join(
[
f"# Bootstrap .env for runtime instance: {name}",
f"JIUWENCLAW_DATA_DIR={workspace_path}",
f"JIUWENCLAW_INSTANCE={name}",
f"AGENT_SERVER_PORT={os.environ['RUNTIME_AGENT_PORT']}",
f"WEB_PORT={os.environ['RUNTIME_WEB_PORT']}",
f"GATEWAY_PORT={os.environ['RUNTIME_GATEWAY_PORT']}",
f"FRONTEND_PORT={os.environ['RUNTIME_UI_PORT']}",
"",
]
),
encoding="utf-8",
)
'@
Set-ProcessEnv "RUNTIME_INSTANCE" $script:RuntimeInstance
Set-ProcessEnv "RUNTIME_AGENT_PORT" ([string]$script:RuntimeAgentPort)
Set-ProcessEnv "RUNTIME_WEB_PORT" ([string]$script:RuntimeWebPort)
Set-ProcessEnv "RUNTIME_GATEWAY_PORT" ([string]$script:RuntimeGatewayPort)
Set-ProcessEnv "RUNTIME_UI_PORT" ([string]$script:RuntimeUiPort)
Push-Location $script:RuntimeRoot
try {
$code | Invoke-NativeCommand -FilePath $uv -ArgumentList @("run", "python", "-")
if ($LASTEXITCODE -ne 0) {
Stop-God "Failed to update runtime instance registry"
}
} finally {
Pop-Location
}
Sync-RuntimeModelEnv
}
function Prepare-Experiment {
$experimentPath = Join-PathMany $script:LiveWorkspacePath "hypothesis_$($script:GodExperiment)" "experiment_$($script:GodExperimentRun)"
$configPath = Join-PathMany $experimentPath "init" "init_config.json"
if (-not (Test-Path $configPath)) {
Stop-God "Experiment config not found: $configPath"
}
$sessionPrefix = Get-EnvValue "GOD_SESSION_PREFIX" "$($script:GodExperiment)_run_$($script:GodExperimentRun)"
Write-GodLog "Preparing experiment: $($script:GodExperiment) (run $($script:GodExperimentRun))"
$config = Get-Content $configPath -Raw | ConvertFrom-Json
$agentRoot = (Resolve-Path $script:BackendRoot).Path
$wsUrl = "ws://127.0.0.1:$($script:RuntimeAgentPort)"
$requestedMapId = if ($script:InitialEnv.GOD_MAP_ID -ne $null) { Get-EnvValue "GOD_MAP_ID" "" } else { "" }
foreach ($module in @(Get-ObjectPropertyValue -Object $config -Name "env_modules" -Default @())) {
if ($null -eq $module) {
continue
}
if ((Get-ObjectPropertyValue $module "module_type" "") -ne "PixelTownSocialEnv") {
continue
}
if (-not $module.PSObject.Properties["kwargs"] -or $null -eq $module.kwargs) {
Set-ObjectProperty $module "kwargs" ([pscustomobject]@{})
}
$kwargs = $module.kwargs
if (-not [string]::IsNullOrWhiteSpace($requestedMapId)) {
Set-ObjectProperty $kwargs "map_id" $requestedMapId
Set-ObjectProperty $kwargs "map_manifest_path" "custom/maps/$requestedMapId/map.yaml"
} else {
$mapId = if ($kwargs.PSObject.Properties["map_id"]) { [string]$kwargs.map_id } else { "" }
$manifestPath = if ($kwargs.PSObject.Properties["map_manifest_path"]) { [string]$kwargs.map_manifest_path } else { "" }
if ([string]::IsNullOrWhiteSpace($manifestPath)) {
if ([string]::IsNullOrWhiteSpace($mapId)) {
$mapId = "the_ville"
Set-ObjectProperty $kwargs "map_id" $mapId
}
Set-ObjectProperty $kwargs "map_manifest_path" "custom/maps/$mapId/map.yaml"
}
}
}
foreach ($agent in @(Get-ObjectPropertyValue -Object $config -Name "agents" -Default @())) {
if ($null -eq $agent) {
continue
}
if (-not $agent.PSObject.Properties["kwargs"] -or $null -eq $agent.kwargs) {
Set-ObjectProperty $agent "kwargs" ([pscustomobject]@{})
}
$kwargs = $agent.kwargs
$agentId = 0
$rawAgentId = Get-ObjectPropertyValue $agent "agent_id" $null
$rawKwargsId = Get-ObjectPropertyValue $kwargs "id" $null
if ($rawAgentId) {
$agentId = [int]$rawAgentId
} elseif ($rawKwargsId) {
$agentId = [int]$rawKwargsId
}
Set-ObjectProperty $kwargs "jiuwenclaw_ws_url" $wsUrl
Set-ObjectProperty $kwargs "session_id" "$sessionPrefix`_agent_$agentId"
Set-ObjectProperty $kwargs "trusted_dirs" @($agentRoot)
}
Write-TextUtf8NoBom $configPath (($config | ConvertTo-Json -Depth 100) + "`n")
}
function Start-Runtime {
Ensure-RuntimeInstance
if ((Test-PortOpen $script:RuntimeAgentPort) -and (Test-PortOpen $script:RuntimeWebPort) -and (Test-PortOpen $script:RuntimeGatewayPort) -and (Test-PortOpen $script:RuntimeUiPort)) {
Write-GodLog "Agent runtime already up"
return
}
Write-GodLog "Starting agent runtime"
Write-TextUtf8NoBom $script:RuntimeLog ""
$uv = Get-ToolPath @("uv")
$runtimeDataDir = Get-RuntimeWorkspace
$runtimeCmd = @(
"`$ErrorActionPreference = 'Stop'",
"Set-Location $(Quote-PSLiteral $script:RuntimeRoot)",
"`$env:JIUWENCLAW_DATA_DIR = $(Quote-PSLiteral $runtimeDataDir)",
"`$env:JIUWENCLAW_ROOT = $(Quote-PSLiteral $script:RuntimeRoot)",
"`$env:JIUWENCLAW_PROJECT_ROOT = $(Quote-PSLiteral $script:RuntimeRoot)",
"`$env:AGENT_SERVER_PORT = $(Quote-PSLiteral ([string]$script:RuntimeAgentPort))",
"`$env:WEB_PORT = $(Quote-PSLiteral ([string]$script:RuntimeWebPort))",
"`$env:GATEWAY_PORT = $(Quote-PSLiteral ([string]$script:RuntimeGatewayPort))",
"`$env:FRONTEND_PORT = $(Quote-PSLiteral ([string]$script:RuntimeUiPort))",
(Get-DetachedNativeExecLine -FilePath $uv -ArgumentExpression "run jiuwenclaw-start $(Quote-PSLiteral $script:RuntimeMode) --name $(Quote-PSLiteral $script:RuntimeInstance)" -LogPath $script:RuntimeLog)
) -join "; "
Start-DetachedService -SessionName $script:RuntimeInstance -PidFile $script:RuntimePidFile -Command $runtimeCmd -LogPath $script:RuntimeLog
Wait-ForPort $script:RuntimeAgentPort "Agent runtime" 180
Wait-ForPort $script:RuntimeWebPort "Agent runtime web" 120
Wait-ForPort $script:RuntimeGatewayPort "Agent runtime gateway" 120
Wait-ForPort $script:RuntimeUiPort "Agent runtime UI" 120
}
function Test-BackendHealthy {
if (-not (Test-PortOpen $script:GodBackendPort)) {
return $false
}
try {
Invoke-RestMethod -Uri "$($script:BackendUrl)/health" -TimeoutSec 5 | Out-Null
return $true
} catch {
return $false
}
}
function Start-Backend {
Ensure-EnvFile
if (Test-BackendHealthy) {
Write-GodLog "Backend already up"
return
}
Ensure-BackendDependencies
Write-GodLog "Starting backend"
Write-TextUtf8NoBom $script:BackendLog ""
$uv = Get-ToolPath @("uv")
$backendLogLevel = Get-EnvValue "BACKEND_LOG_LEVEL" "info"
$backendCmd = @(
"`$ErrorActionPreference = 'Stop'",
"Set-Location $(Quote-PSLiteral $script:BackendRoot)",
"`$env:GOD_ROOT = $(Quote-PSLiteral $script:RootDir)",
"`$env:GOD_ENV_FILE = $(Quote-PSLiteral $script:EnvFile)",
"`$env:LIVE_WORKSPACE_PATH = $(Quote-PSLiteral $script:LiveWorkspacePath)",
"`$env:AGENTSOCIETY_LLM_API_KEY = $(Quote-PSLiteral (Get-EnvValue 'AGENTSOCIETY_LLM_API_KEY' ''))",
"`$env:AGENTSOCIETY_LLM_API_BASE = $(Quote-PSLiteral (Get-EnvValue 'AGENTSOCIETY_LLM_API_BASE' 'https://api.openai.com/v1'))",
"`$env:AGENTSOCIETY_LLM_MODEL = $(Quote-PSLiteral (Get-EnvValue 'AGENTSOCIETY_LLM_MODEL' 'gpt-5.4'))",
"`$env:AGENTSOCIETY_NANO_LLM_MODEL = $(Quote-PSLiteral (Get-EnvValue 'AGENTSOCIETY_NANO_LLM_MODEL' 'gpt-5.4-nano'))",
"`$env:AGENTSOCIETY_EMBEDDING_API_KEY = $(Quote-PSLiteral (Get-EnvValue 'AGENTSOCIETY_EMBEDDING_API_KEY' (Get-EnvValue 'AGENTSOCIETY_LLM_API_KEY' '')))",
"`$env:AGENTSOCIETY_EMBEDDING_API_BASE = $(Quote-PSLiteral (Get-EnvValue 'AGENTSOCIETY_EMBEDDING_API_BASE' (Get-EnvValue 'AGENTSOCIETY_LLM_API_BASE' 'https://api.openai.com/v1')))",
"`$env:AGENTSOCIETY_EMBEDDING_MODEL = $(Quote-PSLiteral (Get-EnvValue 'AGENTSOCIETY_EMBEDDING_MODEL' 'text-embedding-3-large'))",
"`$env:BACKEND_HOST = $(Quote-PSLiteral $script:GodBackendHost)",
"`$env:BACKEND_PORT = $(Quote-PSLiteral ([string]$script:GodBackendPort))",
"`$env:AGENTSOCIETY_LIVE_STEP_TIMEOUT = $(Quote-PSLiteral $script:GodLiveStepTimeout)",
"`$env:BACKEND_LOG_LEVEL = $(Quote-PSLiteral $backendLogLevel)",
(Get-DetachedNativeExecLine -FilePath $uv -ArgumentExpression "run python -m agentsociety2.backend.run --log-level $(Quote-PSLiteral $backendLogLevel)" -LogPath $script:BackendLog)
) -join "; "
Start-DetachedService -SessionName "god-backend" -PidFile $script:BackendPidFile -Command $backendCmd -LogPath $script:BackendLog
$backendStartTimeout = [int](Get-EnvValue "GOD_BACKEND_START_TIMEOUT" "180")
Wait-ForPort $script:GodBackendPort "Backend" $backendStartTimeout
$deadline = (Get-Date).AddSeconds(30)
while ((Get-Date) -lt $deadline) {
if (Test-BackendHealthy) {
return
}
Start-Sleep -Seconds 1
}
Stop-God "Backend port is open but /health did not respond"
}
function Start-Frontend {
if (Test-PortOpen $script:GodFrontendPort) {
Write-GodLog "Control room already up"
return
}
Write-GodLog "Starting control room"
Write-TextUtf8NoBom $script:FrontendLog ""
$npm = Get-ToolPath @("npm.cmd", "npm")
$frontendRoot = Join-Path $script:BackendRoot "frontend"
$frontendCmd = @(
"`$ErrorActionPreference = 'Stop'",
"Set-Location $(Quote-PSLiteral $frontendRoot)",
"`$env:VITE_REPLAY_WORKSPACE_PATH = $(Quote-PSLiteral $script:LiveWorkspacePath)",
"`$env:VITE_DEFAULT_REPLAY_HYPOTHESIS_ID = $(Quote-PSLiteral $script:GodExperiment)",
"`$env:VITE_DEFAULT_REPLAY_EXPERIMENT_ID = $(Quote-PSLiteral $script:GodExperimentRun)",
(Get-DetachedNativeExecLine -FilePath $npm -ArgumentExpression "run dev -- --host 127.0.0.1 --port $(Quote-PSLiteral ([string]$script:GodFrontendPort))" -LogPath $script:FrontendLog)
) -join "; "
Start-DetachedService -SessionName "god-frontend" -PidFile $script:FrontendPidFile -Command $frontendCmd -LogPath $script:FrontendLog
Wait-ForPort $script:GodFrontendPort "Control room" 120
}
function Start-SetupServices {
Setup-Dependencies
Set-ProcessEnv "GOD_SETUP_MODE" "1"
Ensure-EnvFile
Remove-Item $script:StartRequestFile -Force -ErrorAction SilentlyContinue
Stop-All
Start-Backend
Start-Frontend
Open-SetupPage
}
function Receive-StartRequest {
try {
$data = Get-Content $script:StartRequestFile -Raw | ConvertFrom-Json
} catch {
Stop-God "Could not read setup start request"
}
$hypothesisId = [string](Get-ObjectPropertyValue $data "hypothesis_id" "")
$rawExperimentId = Get-ObjectPropertyValue $data "experiment_id" ""
$experimentId = if ($rawExperimentId) { [string]$rawExperimentId } else { "1" }
$rawWorkspacePath = Get-ObjectPropertyValue $data "workspace_path" ""
$workspacePath = if ($rawWorkspacePath) { [string]$rawWorkspacePath } else { "" }
if ([string]::IsNullOrWhiteSpace($hypothesisId)) {
Stop-God "Setup start request is missing hypothesis_id"
}
try {
Move-Item $script:StartRequestFile "$($script:StartRequestFile).consumed" -Force
} catch {
Remove-Item $script:StartRequestFile -Force -ErrorAction SilentlyContinue
}
Set-ProcessEnv "GOD_EXPERIMENT" $hypothesisId
Set-ProcessEnv "GOD_EXPERIMENT_RUN" $experimentId
if (-not [string]::IsNullOrWhiteSpace($workspacePath)) {
Set-ProcessEnv "LIVE_WORKSPACE_PATH" $workspacePath
}
Load-Env
Write-GodLog "Received start request: $($script:GodExperiment) / experiment_$($script:GodExperimentRun)"
}
function Wait-ForStartRequest {
Write-GodLog "Waiting for setup wizard to save and request startup"
$nextNotice = (Get-Date).AddSeconds(30)
while (-not (Test-Path $script:StartRequestFile)) {
Start-Sleep -Seconds 2
if ((Get-Date) -ge $nextNotice) {
Write-GodLog "Still waiting on $(Get-SetupUrl)"
$nextNotice = (Get-Date).AddSeconds(30)
}
}
Receive-StartRequest
}
function Start-WithSetupIfNeeded {
Ensure-Prerequisites
Load-Env
if (Test-PendingStartRequest) {
Write-GodLog "Applying pending setup startup request"
Receive-StartRequest
Remove-ProcessEnv "GOD_SETUP_MODE"
Stop-All
Start-All
return
}
if (Test-ReadyStartConfig) {
Start-All
return
}
Write-GodLog "No complete experiment configuration found; starting setup wizard first"
Start-SetupServices
Wait-ForStartRequest
Remove-ProcessEnv "GOD_SETUP_MODE"
Stop-ControlServices
Start-All
}
function Configure-Experiment {
Ensure-Prerequisites
Write-GodLog "Starting new experiment setup wizard"
Start-SetupServices
Wait-ForStartRequest
Remove-ProcessEnv "GOD_SETUP_MODE"
Stop-ControlServices
Start-All
}
function New-LiveSession {
if (-not (Test-BackendHealthy)) {
Start-Backend
}
Write-GodLog "Creating live session"
try {
$statusJson = Invoke-RestMethod -Method Post -Uri (Get-SessionUrl) -ContentType "application/json" -Body "{}"
} catch {
Stop-God "Failed to create live session"
}
$stepCount = if ($statusJson.PSObject.Properties["step_count"]) { [int]$statusJson.step_count } else { 0 }
if ((Get-EnvValue "GOD_PRIME_FIRST_STEP" "1") -ne "0" -and $stepCount -eq 0) {
Write-GodLog "Priming live session (step 1)"
try {
Invoke-RestMethod -Method Post -Uri (Get-RunStepUrl) -ContentType "application/json" -Body "{}" | Out-Null
} catch {
Stop-God "Failed to run the first live step"
}
}
}
function Start-All {
Load-Env
Require-CurrentExperimentConfig
Setup-Dependencies
Ensure-EnvFile
Prepare-Experiment
Start-Runtime
Start-Backend
Start-Frontend
New-LiveSession
Show-Status
}
function Restart-All {
Ensure-Prerequisites
Stop-All
Start-WithSetupIfNeeded
}
function Start-NewRun {
Ensure-Prerequisites
Load-Env
Require-CurrentExperimentConfig
Set-ProcessEnv "GOD_SESSION_PREFIX" "$($script:GodExperiment)_fresh_$((Get-Date).ToString('yyyyMMdd_HHmmss'))"
Stop-All
$runDir = Join-PathMany $script:LiveWorkspacePath "hypothesis_$($script:GodExperiment)" "experiment_$($script:GodExperimentRun)" "run"
Write-GodLog "New run will clear: $runDir"
Remove-Item $runDir -Recurse -Force -ErrorAction SilentlyContinue
Write-GodLog "Cleared previous run"
Start-All
}
function Show-PortStatus {
param(
[string]$Label,
[int]$Port
)
if (Test-PortOpen $Port) {
"{0,-24} {1} up" -f $Label, $Port | Write-Host
} else {
"{0,-24} {1} down" -f $Label, $Port | Write-Host
}
}
function Show-Status {
Load-Env
Write-Host ""
Write-Host "GOD status"
Write-Host "----------"
Show-PortStatus "Backend" $script:GodBackendPort
Show-PortStatus "Control room" $script:GodFrontendPort
Show-PortStatus "Agent runtime" $script:RuntimeAgentPort
Show-PortStatus "Agent runtime web" $script:RuntimeWebPort
Show-PortStatus "Agent runtime gateway" $script:RuntimeGatewayPort
Show-PortStatus "Agent runtime UI" $script:RuntimeUiPort
Write-Host ""
Write-Host "URLs"
Show-FrontendLinks
Write-Host ("Backend: {0}/health" -f $script:BackendUrl)
Write-Host ""
Write-Host "Current experiment"
if (Test-CurrentExperimentConfig) {
Write-Host ("Experiment: {0} / experiment_{1}" -f $script:GodExperiment, $script:GodExperimentRun)
Write-Host ("Workspace: {0}" -f $script:LiveWorkspacePath)
} else {
Write-Host "Experiment: <not configured>"
}
Write-Host ""
Write-Host "Model"
Write-Host ("API key: {0}" -f (Get-ConfiguredState (Get-EnvValue "GOD_LLM_API_KEY" "")))
Write-Host ("API base: {0}" -f (Get-EnvValue "GOD_LLM_API_BASE" "<unset>"))
Write-Host ("Model: {0}" -f (Get-EnvValue "GOD_LLM_MODEL" "<unset>"))
}
function Watch-Logs {
foreach ($logPath in @($script:RuntimeLog, $script:BackendLog, $script:FrontendLog)) {
if (-not (Test-Path $logPath)) {
Write-TextUtf8NoBom $logPath ""
}
}
Get-Content $script:RuntimeLog, $script:BackendLog, $script:FrontendLog -Wait
}
function Open-Replay {
Load-Env
if (-not (Test-CurrentExperimentConfig)) {
Open-SetupPage
return
}
Open-FrontendPages
}
function Show-InteractiveMenu {
@"
GOD - Govern, Observe, Direct
1. Start
2. Restart
3. New run (reset replay and start fresh)
4. Configure new experiment
5. Status
6. Stop
7. Tail logs
8. Setup dependencies only
"@ | Write-Host
$choice = Read-Host "Choose"
switch ($choice) {
"1" { Start-WithSetupIfNeeded }
"" { Start-WithSetupIfNeeded }
"2" { Restart-All }
"3" { Start-NewRun }
"4" { Configure-Experiment; Open-FrontendPages }
"5" { Show-Status }
"6" { Stop-All }
"7" { Watch-Logs }
"8" { Setup-Dependencies; Set-ProcessEnv "GOD_SETUP_MODE" "1"; Ensure-EnvFile }
default { Show-Usage; exit 2 }
}
}
Load-Env
switch ($Action) {
"menu" { Show-InteractiveMenu }
"setup" { Setup-Dependencies; Set-ProcessEnv "GOD_SETUP_MODE" "1"; Ensure-EnvFile }
"configure" { Configure-Experiment; Open-FrontendPages }
"start" { Start-WithSetupIfNeeded; Open-FrontendPages }
"restart" { Restart-All; Open-FrontendPages }
"new-run" { Start-NewRun; Open-FrontendPages }
"factory-reset" { Reset-Factory }
"reset" { Reset-Factory }
"session" { Ensure-Prerequisites; Prepare-Experiment; New-LiveSession }
"stop" { Stop-All }
"status" { Show-Status }
"tail" { Watch-Logs }
"open" { Open-Replay }
"-h" { Show-Usage }
"--help" { Show-Usage }
"help" { Show-Usage }
default { Show-Usage; exit 2 }
}