diff --git a/dream-server/installers/windows/dream.ps1 b/dream-server/installers/windows/dream.ps1 index e84d3906..3702ac6a 100644 --- a/dream-server/installers/windows/dream.ps1 +++ b/dream-server/installers/windows/dream.ps1 @@ -442,6 +442,19 @@ function Invoke-Status { } } + # Host agent status + try { + $resp = Invoke-WebRequest -Uri $script:DREAM_AGENT_HEALTH_URL ` + -TimeoutSec 3 -UseBasicParsing -ErrorAction SilentlyContinue + if ($resp.StatusCode -eq 200) { + Write-AISuccess "Host Agent: running (port $($script:DREAM_AGENT_PORT))" + } else { + Write-AIWarn "Host Agent: responded with $($resp.StatusCode)" + } + } catch { + Write-AIWarn "Host Agent: not responding (port $($script:DREAM_AGENT_PORT))" + } + # Docker services Write-Host "" & docker compose @flags ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" 2>$null @@ -514,6 +527,11 @@ function Invoke-Start { Start-NativeInferenceServer } + # Start host agent (if not already running) + if (-not $Service) { + Invoke-Agent -Action "start" + } + $flags = Get-ComposeFlags if ($Service) { Write-AI "Starting $Service..." @@ -574,6 +592,9 @@ function Invoke-Stop { Stop-NativeInferenceServer } + # Stop host agent + Invoke-Agent -Action "stop" + Write-AISuccess "All services stopped" } } finally { @@ -739,6 +760,118 @@ function Invoke-Report { } } +function Invoke-Agent { + param([string]$Action = "status") + + $agentScript = Join-Path (Join-Path $InstallDir "bin") "dream-host-agent.py" + $pidFile = $script:DREAM_AGENT_PID_FILE + $logFile = $script:DREAM_AGENT_LOG_FILE + $port = $script:DREAM_AGENT_PORT + $healthUrl = $script:DREAM_AGENT_HEALTH_URL + + switch ($Action.ToLower()) { + "status" { + try { + $resp = Invoke-WebRequest -Uri $healthUrl -TimeoutSec 3 ` + -UseBasicParsing -ErrorAction SilentlyContinue + if ($resp.StatusCode -eq 200) { + Write-AISuccess "Host agent: running (port $port)" + } else { + Write-AIWarn "Host agent: responded with status $($resp.StatusCode)" + } + } catch { + Write-AIWarn "Host agent: not responding (port $port)" + } + } + "start" { + # Check if already running + try { + $resp = Invoke-WebRequest -Uri $healthUrl -TimeoutSec 2 ` + -UseBasicParsing -ErrorAction SilentlyContinue + if ($resp.StatusCode -eq 200) { + Write-AISuccess "Host agent already running (port $port)" + return + } + } catch { } + + # Find Python + $_python3 = Get-Command python3 -ErrorAction SilentlyContinue + if (-not $_python3) { $_python3 = Get-Command python -ErrorAction SilentlyContinue } + if (-not $_python3) { + Write-AIError "Python not found in PATH -- install Python 3 and try again" + return + } + if (-not (Test-Path $agentScript)) { + Write-AIError "Agent script not found: $agentScript" + return + } + + # Clean stale PID + if (Test-Path $pidFile) { + try { + $_oldPid = [int](Get-Content $pidFile -Raw).Trim() + Stop-Process -Id $_oldPid -Force -ErrorAction SilentlyContinue + } catch { } + Remove-Item $pidFile -Force -ErrorAction SilentlyContinue + } + + $pidDir = Split-Path $pidFile + New-Item -ItemType Directory -Path $pidDir -Force -ErrorAction SilentlyContinue | Out-Null + + # Prepend Docker to PATH so the agent can find docker.exe + # (Docker Desktop may not be in the system PATH yet after fresh install) + $_dockerBin = "C:\Program Files\Docker\Docker\resources\bin" + $_agentArgs = "set `"PATH=$_dockerBin;%PATH%`" && `"$($_python3.Source)`" `"$agentScript`" --port $port --pid-file `"$pidFile`" --install-dir `"$InstallDir`" 2>> `"$logFile`"" + Start-Process -FilePath "cmd.exe" -ArgumentList "/c", $_agentArgs ` + -WindowStyle Hidden -WorkingDirectory $InstallDir + + Start-Sleep -Seconds 3 + try { + $resp = Invoke-WebRequest -Uri $healthUrl -TimeoutSec 3 ` + -UseBasicParsing -ErrorAction SilentlyContinue + if ($resp.StatusCode -eq 200) { + Write-AISuccess "Host agent started (port $port)" + } else { + Write-AIWarn "Host agent started but health check returned $($resp.StatusCode)" + } + } catch { + Write-AIWarn "Host agent started but not yet responding -- check: .\dream.ps1 agent status" + } + } + "stop" { + if (Test-Path $pidFile) { + try { + $_pid = [int](Get-Content $pidFile -Raw).Trim() + Stop-Process -Id $_pid -Force -ErrorAction SilentlyContinue + Write-AISuccess "Host agent stopped (PID $_pid)" + } catch { + Write-AIWarn "Could not stop agent PID: $_" + } + Remove-Item $pidFile -Force -ErrorAction SilentlyContinue + } else { + Write-AI "Host agent not running (no PID file)" + } + } + "restart" { + Invoke-Agent -Action "stop" + Start-Sleep -Seconds 1 + Invoke-Agent -Action "start" + } + "logs" { + if (Test-Path $logFile) { + Get-Content $logFile -Tail 100 -Wait + } else { + Write-AIWarn "No log file at $logFile" + } + } + default { + Write-Host "" + Write-Host " Usage: .\dream.ps1 agent [status|start|stop|restart|logs]" -ForegroundColor DarkGray + Write-Host "" + } + } +} + function Show-Help { Write-Host "" Write-Host " Dream Server CLI (Windows)" -ForegroundColor Green @@ -766,6 +899,8 @@ function Show-Help { Write-Host "Quick chat via API" -ForegroundColor DarkGray Write-Host " update " -ForegroundColor Cyan -NoNewline Write-Host "Pull latest images and restart" -ForegroundColor DarkGray + Write-Host " agent [action] " -ForegroundColor Cyan -NoNewline + Write-Host "Host agent: status|start|stop|restart|logs" -ForegroundColor DarkGray Write-Host " report " -ForegroundColor Cyan -NoNewline Write-Host "Generate Windows diagnostics bundle" -ForegroundColor DarkGray Write-Host " version " -ForegroundColor Cyan -NoNewline @@ -807,6 +942,11 @@ switch ($Command.ToLower()) { "chat" { Invoke-Chat -Message ($Arguments -join " ") } "update" { Invoke-Update } "report" { Invoke-Report } + "agent" { + $action = ($Arguments | Select-Object -First 1) + if (-not $action) { $action = "status" } + Invoke-Agent -Action $action + } "version" { Write-Host "Dream Server v$($script:DS_VERSION) (Windows)" -ForegroundColor Green } "help" { Show-Help } default { diff --git a/dream-server/installers/windows/lib/constants.ps1 b/dream-server/installers/windows/lib/constants.ps1 index 67bce2b7..1422c4d1 100644 --- a/dream-server/installers/windows/lib/constants.ps1 +++ b/dream-server/installers/windows/lib/constants.ps1 @@ -59,6 +59,13 @@ $script:OPENCODE_EXE = Join-Path (Join-Path $env:USERPROFILE ".opencode") "bin\o $script:OPENCODE_CONFIG_DIR = Join-Path (Join-Path $env:USERPROFILE ".config") "opencode" $script:OPENCODE_PORT = 3003 +# Dream Host Agent (host-level extension lifecycle manager) +$script:DREAM_AGENT_PORT = 7710 +$script:DREAM_AGENT_PID_FILE = Join-Path (Join-Path $script:DS_INSTALL_DIR "data") "dream-host-agent.pid" +$script:DREAM_AGENT_LOG_FILE = Join-Path (Join-Path $script:DS_INSTALL_DIR "data") "dream-host-agent.log" +$script:DREAM_AGENT_HEALTH_URL = "http://127.0.0.1:7710/health" +$script:DREAM_AGENT_TASK_NAME = "DreamServerHostAgent" + # Timing $script:INSTALL_START = Get-Date diff --git a/dream-server/installers/windows/phases/07-devtools.ps1 b/dream-server/installers/windows/phases/07-devtools.ps1 index f1615b06..3eacc7f1 100644 --- a/dream-server/installers/windows/phases/07-devtools.ps1 +++ b/dream-server/installers/windows/phases/07-devtools.ps1 @@ -31,6 +31,8 @@ if ($dryRun) { if (-not $cloudMode) { Write-AI "[DRY RUN] Would check for Node.js and install Claude Code + Codex CLI via npm" } + Write-AI "[DRY RUN] Would start Dream Host Agent on port $($script:DREAM_AGENT_PORT)" + Write-AI "[DRY RUN] Would register $($script:DREAM_AGENT_TASK_NAME) scheduled task for login persistence" return } @@ -227,4 +229,77 @@ if ($_npmCmd) { } } +# ── Dream Host Agent (extension lifecycle management) ──────────────────────── +$_agentScript = Join-Path (Join-Path $installDir "bin") "dream-host-agent.py" +if (Test-Path $_agentScript) { + $_python3 = Get-Command python3 -ErrorAction SilentlyContinue + if (-not $_python3) { $_python3 = Get-Command python -ErrorAction SilentlyContinue } + + if ($_python3) { + # Kill existing agent on reinstall (matches Linux force-restart pattern) + if (Test-Path $script:DREAM_AGENT_PID_FILE) { + $_oldPid = $null + try { + $_oldPid = [int](Get-Content $script:DREAM_AGENT_PID_FILE -Raw).Trim() + Stop-Process -Id $_oldPid -Force -ErrorAction SilentlyContinue + } catch { } + Remove-Item $script:DREAM_AGENT_PID_FILE -Force -ErrorAction SilentlyContinue + } + + # Ensure data directory exists for PID and log files + $pidDir = Split-Path $script:DREAM_AGENT_PID_FILE + New-Item -ItemType Directory -Path $pidDir -Force -ErrorAction SilentlyContinue | Out-Null + + # Start agent via cmd.exe wrapper for stderr→log redirect. + # Prepend Docker to PATH so the agent can find docker.exe + # (Docker Desktop may not be in the system PATH yet after fresh install). + $_dockerBin = "C:\Program Files\Docker\Docker\resources\bin" + $_agentArgs = "set `"PATH=$_dockerBin;%PATH%`" && `"$($_python3.Source)`" `"$_agentScript`" --port $($script:DREAM_AGENT_PORT) --pid-file `"$($script:DREAM_AGENT_PID_FILE)`" --install-dir `"$installDir`" 2>> `"$($script:DREAM_AGENT_LOG_FILE)`"" + Start-Process -FilePath "cmd.exe" -ArgumentList "/c", $_agentArgs ` + -WindowStyle Hidden -WorkingDirectory $installDir + + # Brief health check + Start-Sleep -Seconds 3 + try { + $resp = Invoke-WebRequest -Uri $script:DREAM_AGENT_HEALTH_URL ` + -TimeoutSec 3 -UseBasicParsing -ErrorAction SilentlyContinue + if ($resp.StatusCode -eq 200) { + Write-AISuccess "Dream host agent started (port $($script:DREAM_AGENT_PORT))" + } else { + Write-AIWarn "Dream host agent started but health check returned $($resp.StatusCode)" + } + } catch { + Write-AIWarn "Dream host agent started but not yet responding -- check: .\dream.ps1 agent status" + } + + # Register Windows Scheduled Task for login persistence + Unregister-ScheduledTask -TaskName $script:DREAM_AGENT_TASK_NAME ` + -Confirm:$false -ErrorAction SilentlyContinue + + $taskAction = New-ScheduledTaskAction -Execute "cmd.exe" ` + -Argument "/c set `"PATH=$_dockerBin;%PATH%`" && `"$($_python3.Source)`" `"$_agentScript`" --port $($script:DREAM_AGENT_PORT) --pid-file `"$($script:DREAM_AGENT_PID_FILE)`" --install-dir `"$installDir`" 2>> `"$($script:DREAM_AGENT_LOG_FILE)`"" ` + -WorkingDirectory $installDir + $taskTrigger = New-ScheduledTaskTrigger -AtLogOn + $taskSettings = New-ScheduledTaskSettingsSet ` + -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` + -StartWhenAvailable -ExecutionTimeLimit ([TimeSpan]::Zero) + + Register-ScheduledTask -TaskName $script:DREAM_AGENT_TASK_NAME ` + -Action $taskAction -Trigger $taskTrigger -Settings $taskSettings ` + -Description "DreamServer Host Agent -- manages extensions and bridges dashboard to host" ` + -ErrorAction SilentlyContinue | Out-Null + + if ($?) { + Write-AISuccess "Host agent registered to start at login (Task: $($script:DREAM_AGENT_TASK_NAME))" + } else { + Write-AIWarn "Could not register login task -- start manually: .\dream.ps1 agent start" + } + } else { + Write-AIWarn "Python not found -- Dream host agent not started" + Write-AI " Install Python 3 and re-run the installer, or start manually: .\dream.ps1 agent start" + } +} else { + Write-AI "Dream host agent script not found -- skipping" +} + Write-AISuccess "Developer tools setup complete"