workflows/default/hooks/scripts/steering.ps1
|
#!/usr/bin/env pwsh <# .SYNOPSIS Operator helper script for the steering channel. .DESCRIPTION Send whispers to running DOTBOT sessions and monitor their status. .EXAMPLE .\steering.ps1 whisper -SessionId "proc-a1b2c3" -Message "Focus on tests" -Priority normal .EXAMPLE .\steering.ps1 status .EXAMPLE .\steering.ps1 watch .EXAMPLE .\steering.ps1 abort -SessionId "proc-a1b2c3" #> param( [Parameter(Position = 0)] [ValidateSet('whisper', 'status', 'watch', 'list', 'abort', 'history')] [string]$Command = 'status', [Parameter()] [string]$SessionId, [Parameter()] [Alias('m')] [string]$Message, [Parameter()] [ValidateSet('normal', 'urgent', 'abort')] [string]$Priority = 'normal' ) # Import theme for consistent output $themePath = Join-Path $PSScriptRoot "..\..\systems\runtime\modules\DotBotTheme.psm1" if (Test-Path $themePath) { Import-Module $themePath -Force $t = Get-DotBotTheme } else { # Fallback if theme not available $t = @{ Primary = ''; Success = ''; Error = ''; Warning = ''; Muted = ''; Reset = '' } } $controlDir = Join-Path $PSScriptRoot "..\..\..\.control" $controlDir = [System.IO.Path]::GetFullPath($controlDir) $processesDir = Join-Path $controlDir "processes" $statusFile = Join-Path $controlDir "steering-status.json" function Get-RunningProcesses { $procs = @() if (Test-Path $processesDir) { $procFiles = Get-ChildItem -Path $processesDir -Filter "*.json" -File -ErrorAction SilentlyContinue foreach ($pf in $procFiles) { try { $proc = Get-Content $pf.FullName -Raw | ConvertFrom-Json if ($proc.status -eq 'running') { $procs += $proc } } catch { Write-Verbose "Failed to parse process data: $_" } } } return $procs } function Send-Whisper { param( [Parameter(Mandatory)] [string]$SessionId, [Parameter(Mandatory)] [string]$Message, [ValidateSet('normal', 'urgent', 'abort')] [string]$Priority = 'normal' ) if (-not (Test-Path $processesDir)) { New-Item -ItemType Directory -Path $processesDir -Force | Out-Null } $whisperFile = Join-Path $processesDir "$SessionId.whisper.jsonl" $whisper = @{ instruction = $Message priority = $Priority timestamp = (Get-Date).ToUniversalTime().ToString("o") } | ConvertTo-Json -Compress Add-Content -Path $whisperFile -Value $whisper -Encoding utf8NoBOM Write-Host "$($t.Success)$($t.Reset) Whisper sent to $($t.Primary)$SessionId$($t.Reset)" Write-Host " $($t.Muted)Priority:$($t.Reset) $Priority" Write-Host " $($t.Muted)Message:$($t.Reset) $Message" } function Get-SteeringStatus { if (-not (Test-Path $statusFile)) { Write-Host "$($t.Warning)$($t.Reset) No steering status file found" Write-Host " $($t.Muted)Either no session is running or it hasn't posted status yet.$($t.Reset)" return } try { $status = Get-Content $statusFile -Raw | ConvertFrom-Json $updatedAt = [DateTime]::Parse($status.updated_at) $age = (Get-Date).ToUniversalTime() - $updatedAt $ageStr = if ($age.TotalMinutes -lt 1) { "$([int]$age.TotalSeconds)s ago" } elseif ($age.TotalHours -lt 1) { "$([int]$age.TotalMinutes)m ago" } else { "$([int]$age.TotalHours)h ago" } Write-Host "" Write-Host "$($t.Primary)--- Steering Status ---$($t.Reset)" Write-Host "$($t.Primary)|$($t.Reset) $($t.Muted)Session:$($t.Reset) $($status.session_id)" Write-Host "$($t.Primary)|$($t.Reset) $($t.Muted)Status:$($t.Reset) $($status.status)" if ($status.next_action) { Write-Host "$($t.Primary)|$($t.Reset) $($t.Muted)Next:$($t.Reset) $($status.next_action)" } Write-Host "$($t.Primary)|$($t.Reset) $($t.Muted)Updated:$($t.Reset) $ageStr" Write-Host "$($t.Primary)|$($t.Reset) $($t.Muted)Whisper Idx:$($t.Reset) $($status.last_whisper_index)" Write-Host "" } catch { Write-Host "$($t.Error)x$($t.Reset) Failed to read status: $_" } } function Watch-SteeringStatus { Write-Host "$($t.Primary)Watching steering status...$($t.Reset) (Ctrl+C to stop)" Write-Host "" $lastContent = "" while ($true) { if (Test-Path $statusFile) { $content = Get-Content $statusFile -Raw if ($content -ne $lastContent) { Clear-Host Write-Host "$($t.Muted)$(Get-Date -Format 'HH:mm:ss')$($t.Reset) Steering status updated" Get-SteeringStatus $lastContent = $content } } Start-Sleep -Milliseconds 500 } } function Get-RunningSessions { Write-Host "" Write-Host "$($t.Primary)Running Processes$($t.Reset)" Write-Host "$($t.Muted)----------------------------------------$($t.Reset)" $procs = Get-RunningProcesses if ($procs.Count -gt 0) { foreach ($proc in $procs) { Write-Host "$($t.Success)o$($t.Reset) $($proc.id) [$($proc.type)]" Write-Host " $($t.Muted)Model:$($t.Reset) $($proc.model)" Write-Host " $($t.Muted)Started:$($t.Reset) $($proc.started_at)" if ($proc.heartbeat_status) { Write-Host " $($t.Muted)Status:$($t.Reset) $($proc.heartbeat_status)" } } } else { Write-Host "$($t.Muted)No running processes detected$($t.Reset)" } Write-Host "" } function Send-Abort { param( [Parameter(Mandatory)] [string]$SessionId ) Send-Whisper -SessionId $SessionId -Message "ABORT: Commit any work in progress and exit gracefully." -Priority "abort" Write-Host "" Write-Host "$($t.Warning)!$($t.Reset) Abort signal sent. Session should commit WIP and exit." } function Get-WhisperHistory { Write-Host "" Write-Host "$($t.Primary)Whisper History$($t.Reset)" Write-Host "$($t.Muted)----------------------------------------$($t.Reset)" if (-not (Test-Path $processesDir)) { Write-Host "$($t.Muted)No whispers recorded yet.$($t.Reset)" return } $whisperFiles = Get-ChildItem -Path $processesDir -Filter "*.whisper.jsonl" -File -ErrorAction SilentlyContinue if ($whisperFiles.Count -eq 0) { Write-Host "$($t.Muted)No whispers recorded yet.$($t.Reset)" return } foreach ($wf in $whisperFiles) { $procId = $wf.BaseName -replace '\.whisper$', '' Write-Host "$($t.Primary)Process: $procId$($t.Reset)" $lines = Get-Content $wf.FullName -Encoding utf8 $index = 0 foreach ($line in $lines) { if ($line.Trim()) { $index++ try { $whisper = $line | ConvertFrom-Json $ts = [DateTime]::Parse($whisper.timestamp).ToLocalTime().ToString("HH:mm:ss") $priorityColor = switch ($whisper.priority) { 'urgent' { $t.Warning } 'abort' { $t.Error } default { $t.Muted } } Write-Host "$($t.Muted)[$index]$($t.Reset) $ts $priorityColor[$($whisper.priority)]$($t.Reset)" Write-Host " $($whisper.instruction)" } catch { Write-Host "$($t.Muted)[$index]$($t.Reset) $($t.Error)(malformed)$($t.Reset)" } } } Write-Host "" } } # Main command dispatch switch ($Command) { 'whisper' { if (-not $SessionId) { # Try to find a running process to target $procs = Get-RunningProcesses if ($procs.Count -eq 1) { $SessionId = $procs[0].id Write-Host "$($t.Muted)Using running process: $SessionId$($t.Reset)" } elseif ($procs.Count -gt 1) { Write-Host "$($t.Error)x$($t.Reset) Multiple processes running. Specify -SessionId:" foreach ($p in $procs) { Write-Host " $($p.id) [$($p.type)]" } exit 1 } else { Write-Host "$($t.Error)x$($t.Reset) -SessionId required (no running processes detected)" exit 1 } } if (-not $Message) { Write-Host "$($t.Error)x$($t.Reset) -Message required" exit 1 } Send-Whisper -SessionId $SessionId -Message $Message -Priority $Priority } 'status' { Get-SteeringStatus } 'watch' { Watch-SteeringStatus } 'list' { Get-RunningSessions } 'abort' { if (-not $SessionId) { # Try to find a running process to target $procs = Get-RunningProcesses if ($procs.Count -eq 1) { $SessionId = $procs[0].id Write-Host "$($t.Muted)Using running process: $SessionId$($t.Reset)" } elseif ($procs.Count -gt 1) { Write-Host "$($t.Error)x$($t.Reset) Multiple processes running. Specify -SessionId:" foreach ($p in $procs) { Write-Host " $($p.id) [$($p.type)]" } exit 1 } else { Write-Host "$($t.Error)x$($t.Reset) -SessionId required (no running processes detected)" exit 1 } } Send-Abort -SessionId $SessionId } 'history' { Get-WhisperHistory } } |