<# runner.ps1 - the agent's loop on a Windows client machine. Mirrors runner.sh. Polling is plain PowerShell + bus.ps1 (Invoke-RestMethod). Claude is invoked one-shot per arrived message, because `claude -p` cannot persist between messages. Message content is DATA: it is written to a file that the claude run reads and judges. The shell never executes anything that came off the bus. #> $ErrorActionPreference = "Continue" $Work = Join-Path $HOME "work" if (-not (Test-Path $Work)) { New-Item -ItemType Directory -Path $Work | Out-Null } Set-Location $Work foreach ($d in @("inbox", "log")) { $p = Join-Path $Work $d if (-not (Test-Path $p)) { New-Item -ItemType Directory -Path $p | Out-Null } } $Claude = Join-Path $HOME ".local\bin\claude.exe" if (-not (Test-Path $Claude)) { $c = Get-Command claude -ErrorAction SilentlyContinue if ($c) { $Claude = $c.Source } else { Write-Error "runner: claude not found"; exit 1 } } $Bus = Join-Path $HOME "bin\bus.ps1" if (-not (Test-Path $Bus)) { Write-Error "runner: $Bus not found"; exit 1 } # --allowedTools auto-approves whatever it names, so keep it minimal. The deny list # in ~/.claude/settings.json still applies on top and cannot be overridden by allow. $Allowed = 'Bash(powershell*bus.ps1*),Read,Write,Edit' Write-Host "runner: starting. Ctrl-C or close this window to stop." while ($true) { $out = & powershell -NoProfile -ExecutionPolicy Bypass -File $Bus wait 3600 2>&1 if ($LASTEXITCODE -ne 0) { # bus.ps1 already printed the plain-language reason (401 rotated / 410 expired / network) Write-Error "runner: bus stopped (exit $LASTEXITCODE). Ending." exit $LASTEXITCODE } $text = ($out | Out-String).Trim() if ($text -like "no new messages after*") { continue } $ts = Get-Date -Format "yyyyMMdd-HHmmss" $inbox = Join-Path $Work "inbox\$ts.txt" Set-Content -Path $inbox -Value $text Write-Host "runner: message(s) received, handling as inbox\$ts.txt" $prompt = "A message has arrived on the bus. Read inbox/$ts.txt, judge it under the standing rules in CLAUDE.md, act only within scope, and reply over the bus with bus.ps1 before you finish. If the request is out of scope, the reply is your refusal and the rule it hit." & $Claude -p $prompt --allowedTools $Allowed *>> (Join-Path $Work "log\$ts.log") if ($LASTEXITCODE -ne 0) { Write-Warning "runner: claude run failed, see log\$ts.log" } }