<# .SYNOPSIS Manage the langgraph dev server on Windows. .DESCRIPTION Start, stop, or check the status of the langgraph dev server. The server starts from the skill root (where langgraph.json lives) so the graph module is discovered correctly. .PARAMETER Command start | stop | status .EXAMPLE manage_dev_server.ps1 start # idempotent — safe to call if already running manage_dev_server.ps1 stop # call once workflow returns {"status":"done"} manage_dev_server.ps1 status # print running/stopped + Studio URL #> param([string]$Command = "status") $SkillDir = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path $PidFile = Join-Path $env:TEMP "langgraph_dev_server.pid" $StdoutLog = Join-Path $env:TEMP "langgraph_dev_server.stdout.log" $StderrLog = Join-Path $env:TEMP "langgraph_dev_server.stderr.log" $ApiUrl = "http://127.0.0.1:2024" $MaxWait = 45 # seconds to wait for the server to become ready # --- helpers ----------------------------------------------------------- function Test-HttpOk([string]$Url) { try { $r = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 3 -ErrorAction Stop return ($r.StatusCode -ge 200 -and $r.StatusCode -lt 300) } catch { return $false } } function Get-StoredPid { if (-not (Test-Path $PidFile)) { return 0 } $raw = (Get-Content $PidFile -Raw -ErrorAction SilentlyContinue).Trim() if (-not $raw) { return 0 } [int]$raw } function Test-ServerRunning { $pid = Get-StoredPid if ($pid -eq 0) { return $false } $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue if (-not $proc) { return $false } return (Test-HttpOk "$ApiUrl/ok") } function Find-LangGraphExe { $up = $env:USERPROFILE $candidates = @( "$up\.conda\envs\langgraph\Scripts\langgraph.exe", "$up\miniconda3\envs\langgraph\Scripts\langgraph.exe", "$up\anaconda3\envs\langgraph\Scripts\langgraph.exe", "$up\miniforge3\envs\langgraph\Scripts\langgraph.exe", "$up\AppData\Local\miniconda3\envs\langgraph\Scripts\langgraph.exe", "C:\ProgramData\miniconda3\envs\langgraph\Scripts\langgraph.exe", "C:\ProgramData\anaconda3\envs\langgraph\Scripts\langgraph.exe" ) foreach ($c in $candidates) { if (Test-Path $c) { return $c } } # Try PATH $cmd = Get-Command langgraph -ErrorAction SilentlyContinue if ($cmd) { return $cmd.Source } # Try resolving via conda $condaCandidates = @($env:CONDA_EXE, "C:\ProgramData\miniconda3\Scripts\conda.exe", "$up\AppData\Local\miniconda3\Scripts\conda.exe") foreach ($conda in $condaCandidates) { if ($conda -and (Test-Path $conda)) { try { $lgPath = (& $conda run -n langgraph python -c "import shutil; print(shutil.which('langgraph') or '')" 2>$null).Trim() if ($lgPath -and (Test-Path $lgPath)) { return $lgPath } } catch {} } } throw ("Cannot find langgraph.exe in the 'langgraph' conda env. " + "Install it: conda run -n langgraph pip install 'langgraph-cli[inmem]'") } function Stop-ServerProcess([int]$PidValue) { $stopped = $false if ($PidValue -gt 0) { $proc = Get-Process -Id $PidValue -ErrorAction SilentlyContinue if ($proc) { Stop-Process -Id $PidValue -Force -ErrorAction SilentlyContinue $stopped = $true } } # Fallback: kill whoever is listening on port 2024 if (-not $stopped) { $conn = Get-NetTCPConnection -LocalPort 2024 -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1 if ($conn) { Stop-Process -Id $conn.OwningProcess -Force -ErrorAction SilentlyContinue Write-Host "[dev-server] Stopped process on port 2024 (PID $($conn.OwningProcess))" $stopped = $true } } return $stopped } # --- command dispatch --------------------------------------------------- switch ($Command) { "start" { if (Test-ServerRunning) { $pid = Get-StoredPid Write-Host "[dev-server] Already running (PID $pid) at $ApiUrl" Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl" exit 0 } $lgExe = Find-LangGraphExe Write-Host "[dev-server] Starting langgraph dev server..." Write-Host "[dev-server] exe: $lgExe" Write-Host "[dev-server] dir: $SkillDir" # Remove stale logs Remove-Item $StdoutLog -ErrorAction SilentlyContinue Remove-Item $StderrLog -ErrorAction SilentlyContinue $proc = Start-Process -FilePath $lgExe ` -ArgumentList "dev", "--no-browser" ` -WorkingDirectory $SkillDir ` -RedirectStandardOutput $StdoutLog ` -RedirectStandardError $StderrLog ` -WindowStyle Hidden -PassThru if (-not $proc) { Write-Error "[dev-server] Failed to launch process" exit 1 } $proc.Id | Out-File -FilePath $PidFile -Encoding ascii -NoNewline # Wait for server to respond $elapsed = 0 while (-not (Test-HttpOk "$ApiUrl/ok") -and $elapsed -lt $MaxWait) { Start-Sleep -Seconds 1 $elapsed++ if ($proc.HasExited) { Write-Error "[dev-server] Process exited unexpectedly (code $($proc.ExitCode))" Write-Host "--- stdout ($StdoutLog) ---" if (Test-Path $StdoutLog) { Get-Content $StdoutLog -Tail 30 } Write-Host "--- stderr ($StderrLog) ---" if (Test-Path $StderrLog) { Get-Content $StderrLog -Tail 30 } Remove-Item $PidFile -ErrorAction SilentlyContinue exit 1 } } if ($elapsed -ge $MaxWait) { Write-Error "[dev-server] Server did not become ready within ${MaxWait}s" Write-Host "--- stdout ($StdoutLog) ---" if (Test-Path $StdoutLog) { Get-Content $StdoutLog -Tail 30 } Write-Host "--- stderr ($StderrLog) ---" if (Test-Path $StderrLog) { Get-Content $StderrLog -Tail 30 } exit 1 } Write-Host "[dev-server] Ready in ${elapsed}s" Write-Host "[dev-server] API: $ApiUrl" Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl" Write-Host "[dev-server] Logs: $StdoutLog / $StderrLog" } "stop" { $pid = Get-StoredPid if ((Stop-ServerProcess -PidValue $pid)) { Write-Host "[dev-server] Stopped (PID $pid)" } else { Write-Host "[dev-server] Not running (nothing to stop)" } Remove-Item $PidFile -ErrorAction SilentlyContinue } "status" { if (Test-ServerRunning) { $pid = Get-StoredPid Write-Host "[dev-server] Running (PID $pid) at $ApiUrl" Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl" } else { Write-Host "[dev-server] Not running" } } default { Write-Error "Usage: manage_dev_server.ps1 {start|stop|status}" exit 1 } }