#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: " } 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" "")) Write-Host ("Model: {0}" -f (Get-EnvValue "GOD_LLM_MODEL" "")) } 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 } }