2026-04-24 20:11:18 +01:00
function Get-VoidBotDashboardProperty {
param (
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
$InputObject ,
[ Parameter ( Mandatory = $true ) ]
[ string ] $PropertyName
)
if ( $null -eq $InputObject ) {
return $null
}
$property = $InputObject . PSObject . Properties [ $PropertyName ]
if ( $null -eq $property ) {
return $null
}
return $property . Value
}
function Read-VoidBotDashboardJson {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Path
)
if ( -not ( Test-Path -LiteralPath $Path ) ) {
return [ PSCustomObject ] @ {
_path = $Path
_missing = $true
}
}
try {
$content = Get-Content -LiteralPath $Path -Raw
if ( [ string ] :: IsNullOrWhiteSpace ( $content ) ) {
return [ PSCustomObject ] @ {
_path = $Path
_missing = $true
}
}
$parsed = $content | ConvertFrom-Json
Add-Member -InputObject $parsed -NotePropertyName " _path " -NotePropertyValue $Path -Force
return $parsed
} catch {
return [ PSCustomObject ] @ {
_path = $Path
_error = $_ . Exception . Message
}
}
}
function ConvertTo-VoidBotDashboardText {
param (
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
$Value
)
if ( $null -eq $Value ) {
return " missing "
}
if ( $Value -is [ bool ] ) {
return $ ( if ( $Value ) { " yes " } else { " no " } )
}
if ( $Value -is [ DateTime ] ) {
return $Value . ToString ( " yyyy-MM-dd HH:mm:ss " )
}
if ( $Value -is [ DateTimeOffset ] ) {
return $Value . ToString ( " yyyy-MM-dd HH:mm:ss zzz " )
}
if ( $Value -is [ System.Collections.IEnumerable ] -and -not ( $Value -is [ string ] ) ) {
$items = @ ( $Value )
if ( $items . Count -eq 0 ) {
return " none "
}
if ( $items | Where-Object { $_ -is [ string ] -or $_ -is [ ValueType ] } ) {
return ( $items | ForEach-Object { [ string ] $_ } ) -join " , "
}
return ( $Value | ConvertTo-Json -Depth 6 -Compress )
}
return [ string ] $Value
}
function Escape-VoidBotDashboardHtml {
param (
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
$Value
)
return [ System.Net.WebUtility ] :: HtmlEncode ( ( ConvertTo-VoidBotDashboardText -Value $Value ) )
}
function ConvertTo-VoidBotDashboardFileUri {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Path
)
return ( [ System.Uri ] :: new ( $Path ) ) . AbsoluteUri
}
2026-05-13 19:46:29 +01:00
function Write-VoidBotDashboardUtf8File {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Path ,
[ Parameter ( Mandatory = $true ) ]
[ AllowEmptyString ( ) ]
[ string ] $Content
)
$directory = Split-Path -Parent $Path
if ( -not [ string ] :: IsNullOrWhiteSpace ( $directory ) ) {
New-Item -ItemType Directory -Force -Path $directory | Out-Null
}
$encoding = [ System.Text.UTF8Encoding ] :: new ( $false )
[ System.IO.File ] :: WriteAllText ( $Path , $Content , $encoding )
}
2026-04-24 20:11:18 +01:00
function New-VoidBotDashboardFileLink {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Path ,
[ Parameter ( Mandatory = $false ) ]
[ string ] $Label
)
$text = if ( [ string ] :: IsNullOrWhiteSpace ( $Label ) ) { $Path } else { $Label }
return " <a class='file-link' href=' " + ( Escape-VoidBotDashboardHtml ( ConvertTo-VoidBotDashboardFileUri -Path $Path ) ) + " '> " + ( Escape-VoidBotDashboardHtml $text ) + " </a> "
}
function Get-VoidBotDashboardTone {
param (
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
[ string ] $State
)
if ( [ string ] :: IsNullOrWhiteSpace ( $State ) ) {
return " neutral "
}
switch ( $State . ToLowerInvariant ( ) ) {
" healthy " { return " good " }
" passed " { return " good " }
" completed " { return " good " }
" ready " { return " good " }
" enabled " { return " good " }
" ok " { return " good " }
" warning " { return " warn " }
" running " { return " info " }
" starting " { return " info " }
" compressing " { return " info " }
" uploading " { return " info " }
" pruning " { return " info " }
" bootstrapping " { return " info " }
" build " { return " info " }
" bot_start " { return " info " }
" worker_start " { return " info " }
" already_running " { return " warn " }
" failed " { return " bad " }
" error " { return " bad " }
" disabled " { return " bad " }
" stalled " { return " bad " }
" missing " { return " bad " }
default { return " neutral " }
}
}
function New-VoidBotDashboardBadge {
param (
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
[ string ] $State ,
[ Parameter ( Mandatory = $false ) ]
[ string ] $Label
)
$text = if ( [ string ] :: IsNullOrWhiteSpace ( $Label ) ) {
if ( [ string ] :: IsNullOrWhiteSpace ( $State ) ) { " unknown " } else { $State }
} else {
$Label
}
$tone = Get-VoidBotDashboardTone -State $State
return " <span class='badge badge- " + $tone + " '> " + ( Escape-VoidBotDashboardHtml $text ) + " </span> "
}
function Get-VoidBotDashboardRelativeTime {
param (
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
[ string ] $Timestamp
)
if ( [ string ] :: IsNullOrWhiteSpace ( $Timestamp ) ) {
return " unknown "
}
try {
$target = [ DateTimeOffset ] :: Parse ( $Timestamp )
$delta = [ DateTimeOffset ] :: Now - $target
if ( $delta . TotalSeconds -lt 60 ) {
return " {0}s ago " -f [ Math ] :: Max ( [ int][Math ] :: Round ( $delta . TotalSeconds ) , 0 )
}
if ( $delta . TotalMinutes -lt 60 ) {
return " {0}m ago " -f [ int][Math ] :: Round ( $delta . TotalMinutes )
}
if ( $delta . TotalHours -lt 48 ) {
return " {0}h ago " -f [ int][Math ] :: Round ( $delta . TotalHours )
}
return " {0}d ago " -f [ int][Math ] :: Round ( $delta . TotalDays )
} catch {
return " unknown "
}
}
function Get-VoidBotDashboardElapsedTime {
param (
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
[ string ] $StartedAt
)
if ( [ string ] :: IsNullOrWhiteSpace ( $StartedAt ) ) {
return " unknown "
}
try {
$delta = [ DateTimeOffset ] :: Now - [ DateTimeOffset ] :: Parse ( $StartedAt )
if ( $delta . TotalMinutes -lt 1 ) {
return " {0}s " -f [ Math ] :: Max ( [ int][Math ] :: Round ( $delta . TotalSeconds ) , 0 )
}
if ( $delta . TotalHours -lt 1 ) {
return " {0}m " -f [ int][Math ] :: Round ( $delta . TotalMinutes )
}
return " {0}h {1}m " -f [ int][Math ] :: Floor ( $delta . TotalHours ) , $delta . Minutes
} catch {
return " unknown "
}
}
function New-VoidBotDashboardFactRow {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Label ,
[ Parameter ( Mandatory = $true ) ]
[ AllowEmptyString ( ) ]
[ string ] $ValueHtml
)
return " <tr><th> " + ( Escape-VoidBotDashboardHtml $Label ) + " </th><td> " + $ValueHtml + " </td></tr> "
}
function New-VoidBotDashboardFactSection {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Title ,
[ Parameter ( Mandatory = $true ) ]
[ string[] ] $Rows
)
$body = if ( $Rows . Count -gt 0 ) {
( $Rows -join [ Environment ] :: NewLine )
} else {
" <tr><td colspan='2'><span class='muted'>Nothing here yet.</span></td></tr> "
}
return @"
< section class = 'panel' >
< h2 > $ ( Escape-VoidBotDashboardHtml $Title ) < / h2 >
< table class = 'facts' >
< tbody >
$body
< / tbody >
< / table >
< / section >
" @
}
function New-VoidBotDashboardCard {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Title ,
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
[ string ] $State ,
[ Parameter ( Mandatory = $true ) ]
[ string ] $Subtitle ,
[ Parameter ( Mandatory = $false ) ]
[ string ] $Meta = " "
)
$metaHtml = if ( [ string ] :: IsNullOrWhiteSpace ( $Meta ) ) {
" "
} else {
" <div class='card-meta'> " + ( Escape-VoidBotDashboardHtml $Meta ) + " </div> "
}
return @"
< article class = 'card' >
< div class = 'card-head' >
< h2 > $ ( Escape-VoidBotDashboardHtml $Title ) < / h2 >
$ ( New-VoidBotDashboardBadge -State $State )
< / div >
< p class = 'card-subtitle' > $ ( Escape-VoidBotDashboardHtml $Subtitle ) < / p >
$metaHtml
< / article >
" @
}
2026-04-25 01:58:53 +01:00
function Read-VoidBotDashboardDotEnv {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Path
)
$values = @ { }
if ( -not ( Test-Path -LiteralPath $Path ) ) {
return $values
}
foreach ( $line in Get-Content -LiteralPath $Path ) {
if ( [ string ] :: IsNullOrWhiteSpace ( $line ) ) {
continue
}
if ( $line . TrimStart ( ) . StartsWith ( " # " ) ) {
continue
}
$separatorIndex = $line . IndexOf ( " = " )
if ( $separatorIndex -lt 1 ) {
continue
}
$key = $line . Substring ( 0 , $separatorIndex ) . Trim ( )
$value = $line . Substring ( $separatorIndex + 1 )
$values [ $key ] = $value
}
return $values
}
function Resolve-VoidBotDashboardConfigPath {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $RepoRoot ,
[ Parameter ( Mandatory = $false ) ]
[ AllowEmptyString ( ) ]
[ string ] $Value ,
[ Parameter ( Mandatory = $true ) ]
[ string ] $Fallback
)
$raw = if ( [ string ] :: IsNullOrWhiteSpace ( $Value ) ) { $Fallback } else { $Value }
return [ System.IO.Path ] :: GetFullPath ( ( Join-Path $RepoRoot $raw ) )
}
function Format-VoidBotDashboardBytes {
param (
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
[ long ] $Bytes
)
if ( $null -eq $Bytes -or $Bytes -lt 0 ) {
return " missing "
}
$units = @ ( " B " , " KB " , " MB " , " GB " , " TB " )
$value = [ double ] $Bytes
$unitIndex = 0
while ( $value -ge 1024 -and $unitIndex -lt ( $units . Count - 1 ) ) {
$value / = 1024
$unitIndex + = 1
}
$format = if ( $unitIndex -eq 0 ) { " {0:0} {1} " } else { " {0:0.0} {1} " }
return $format -f $value , $units [ $unitIndex ]
}
function Get-VoidBotDashboardFileSummary {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Path
)
if ( -not ( Test-Path -LiteralPath $Path ) ) {
return [ PSCustomObject ] @ {
exists = $false
path = $Path
size = $null
sizeText = " missing "
updatedAt = $null
updatedRelative = " missing "
}
}
$item = Get-Item -LiteralPath $Path
$updatedAt = $item . LastWriteTime . ToString ( " o " )
return [ PSCustomObject ] @ {
exists = $true
path = $Path
size = $item . Length
sizeText = Format-VoidBotDashboardBytes -Bytes $item . Length
updatedAt = $updatedAt
updatedRelative = Get-VoidBotDashboardRelativeTime -Timestamp $updatedAt
}
}
function New-VoidBotDashboardListHtml {
param (
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
[ string[] ] $Items ,
[ Parameter ( Mandatory = $false ) ]
[ string ] $EmptyText = " Nothing here yet. "
)
if ( $null -eq $Items -or $Items . Count -eq 0 ) {
return " <span class='muted'> $( [ System.Net.WebUtility ] :: HtmlEncode ( $EmptyText ) ) </span> "
}
return " <div class='stack-list'> " + ( ( $Items | ForEach-Object { " <div class='stack-item'> $_ </div> " } ) -join " " ) + " </div> "
}
function Invoke-VoidBotDashboardJsonRequest {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $Url ,
[ Parameter ( Mandatory = $false ) ]
[ ValidateSet ( " Get " , " Post " ) ]
[ string ] $Method = " Get " ,
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
$Body
)
if ( $PSBoundParameters . ContainsKey ( " Body " ) ) {
$payload = $Body | ConvertTo-Json -Depth 8 -Compress
return Invoke-RestMethod -Method $Method -Uri $Url -ContentType " application/json " -Body $payload
}
return Invoke-RestMethod -Method $Method -Uri $Url
}
function Get-VoidBotDashboardPostgresDiagnostics {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $DatabaseDsn ,
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
$Runtime
)
$result = [ ordered ] @ {
state = " missing "
detail = " Postgres has not checked in yet. "
jobsTotal = $null
jobsByState = [ ordered ] @ { }
auditEvents = $null
interactionMemoryEvents = $null
providerRuns = $null
toolInvocations = $null
latestJobAt = $null
latestAuditAt = $null
latestInteractionAt = $null
latestProviderRunAt = $null
latestToolInvocationAt = $null
}
$runtimePostgres = if ( $null -ne $Runtime ) { Get-VoidBotDashboardProperty -InputObject $Runtime -PropertyName " postgres " } else { $null }
if ( $null -eq $runtimePostgres ) {
return [ PSCustomObject ] $result
}
if ( -not ( Get-Command docker -ErrorAction SilentlyContinue ) ) {
$result . state = " warning "
$result . detail = " Docker is unavailable, so live Postgres counts are hidden. "
return [ PSCustomObject ] $result
}
$sql = @ '
select json_build_object (
'jobsTotal' , ( select count ( * ) from jobs ) ,
'jobsByState' , coalesce ( ( select json_object_agg ( state , job_count ) from ( select state , count ( * ) as job_count from jobs group by state ) s ) , '{}' :: json ) ,
'auditEvents' , ( select count ( * ) from audit_events ) ,
'interactionMemoryEvents' , ( select count ( * ) from interaction_memory_events ) ,
'providerRuns' , ( select count ( * ) from provider_runs ) ,
'toolInvocations' , ( select count ( * ) from tool_invocations ) ,
'latestJobAt' , ( select max ( updated_at ) :: text from jobs ) ,
'latestAuditAt' , ( select max ( event_timestamp ) :: text from audit_events ) ,
'latestInteractionAt' , ( select max ( event_timestamp ) :: text from interaction_memory_events ) ,
'latestProviderRunAt' , ( select max ( created_at ) :: text from provider_runs ) ,
'latestToolInvocationAt' , ( select max ( created_at ) :: text from tool_invocations )
) ;
' @
try {
$json = & docker exec voidbot-postgres psql -U voidbot -d voidbot -t -A -c $sql 2 > $null
if ( $LASTEXITCODE -ne 0 -or [ string ] :: IsNullOrWhiteSpace ( $json ) ) {
throw " psql returned no data. "
}
$parsed = $json . Trim ( ) | ConvertFrom-Json
$result . state = " healthy "
$result . detail = " Live Postgres counts loaded. "
$result . jobsTotal = $parsed . jobsTotal
$result . jobsByState = [ ordered ] @ { }
if ( $null -ne $parsed . jobsByState ) {
foreach ( $property in $parsed . jobsByState . PSObject . Properties ) {
$result . jobsByState [ $property . Name ] = $property . Value
}
}
$result . auditEvents = $parsed . auditEvents
$result . interactionMemoryEvents = $parsed . interactionMemoryEvents
$result . providerRuns = $parsed . providerRuns
$result . toolInvocations = $parsed . toolInvocations
$result . latestJobAt = $parsed . latestJobAt
$result . latestAuditAt = $parsed . latestAuditAt
$result . latestInteractionAt = $parsed . latestInteractionAt
$result . latestProviderRunAt = $parsed . latestProviderRunAt
$result . latestToolInvocationAt = $parsed . latestToolInvocationAt
} catch {
$result . state = " warning "
$result . detail = " Live Postgres query failed: $( $_ . Exception . Message ) "
}
return [ PSCustomObject ] $result
}
function Get-VoidBotDashboardQdrantDiagnostics {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $QdrantUrl ,
[ Parameter ( Mandatory = $true ) ]
[ string ] $HistoryCollection ,
[ Parameter ( Mandatory = $true ) ]
[ string ] $SourceCollection
)
$baseUrl = $QdrantUrl . TrimEnd ( " / " )
$result = [ ordered ] @ {
state = " missing "
detail = " Qdrant has not checked in yet. "
history = $null
source = $null
topRepos = @ ( )
topLanguages = @ ( )
}
try {
$historyInfo = Invoke-VoidBotDashboardJsonRequest -Url " $baseUrl /collections/ $HistoryCollection "
$sourceInfo = Invoke-VoidBotDashboardJsonRequest -Url " $baseUrl /collections/ $SourceCollection "
$repoFacet = Invoke-VoidBotDashboardJsonRequest -Method Post -Url " $baseUrl /collections/ $SourceCollection /facet " -Body @ {
key = " repoName "
limit = 8
}
$languageFacet = Invoke-VoidBotDashboardJsonRequest -Method Post -Url " $baseUrl /collections/ $SourceCollection /facet " -Body @ {
key = " language "
limit = 6
}
$result . state = " healthy "
$result . detail = " Live Qdrant collection stats loaded. "
$result . history = [ PSCustomObject ] @ {
name = $HistoryCollection
pointsCount = Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $historyInfo -PropertyName " result " ) -PropertyName " points_count "
indexedVectorsCount = Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $historyInfo -PropertyName " result " ) -PropertyName " indexed_vectors_count "
status = Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $historyInfo -PropertyName " result " ) -PropertyName " status "
segmentsCount = Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $historyInfo -PropertyName " result " ) -PropertyName " segments_count "
}
$result . source = [ PSCustomObject ] @ {
name = $SourceCollection
pointsCount = Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $sourceInfo -PropertyName " result " ) -PropertyName " points_count "
indexedVectorsCount = Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $sourceInfo -PropertyName " result " ) -PropertyName " indexed_vectors_count "
status = Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $sourceInfo -PropertyName " result " ) -PropertyName " status "
segmentsCount = Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $sourceInfo -PropertyName " result " ) -PropertyName " segments_count "
}
$result . topRepos = @ ( ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $repoFacet -PropertyName " result " ) -PropertyName " hits " ) )
$result . topLanguages = @ ( ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $languageFacet -PropertyName " result " ) -PropertyName " hits " ) )
} catch {
$result . state = " warning "
$result . detail = " Live Qdrant query failed: $( $_ . Exception . Message ) "
}
return [ PSCustomObject ] $result
}
function Get-VoidBotDashboardSourceHookDiagnostics {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $SourceHookStatusDir
)
$result = [ ordered ] @ {
state = " missing "
detail = " No source hook statuses yet. "
total = 0
completed = 0
running = 0
failed = 0
recentHooks = @ ( )
latestActivityAt = $null
}
if ( -not ( Test-Path -LiteralPath $SourceHookStatusDir ) ) {
return [ PSCustomObject ] $result
}
$hookFiles = @ ( Get-ChildItem -LiteralPath $SourceHookStatusDir -File -Filter " *.json " )
if ( $hookFiles . Count -eq 0 ) {
return [ PSCustomObject ] $result
}
$hooks = foreach ( $file in $hookFiles ) {
$status = Read-VoidBotDashboardJson -Path $file . FullName
$state = [ string ] ( Get-VoidBotDashboardProperty -InputObject $status -PropertyName " state " )
$activityAt = @ (
[ string ] ( Get-VoidBotDashboardProperty -InputObject $status -PropertyName " completedAt " )
[ string ] ( Get-VoidBotDashboardProperty -InputObject $status -PropertyName " lastSkippedAt " )
[ string ] ( Get-VoidBotDashboardProperty -InputObject $status -PropertyName " startedAt " )
$file . LastWriteTime . ToString ( " o " )
) | Where-Object { -not [ string ] :: IsNullOrWhiteSpace ( $_ ) } | Select-Object -First 1
[ PSCustomObject ] @ {
repoName = [ string ] ( Get-VoidBotDashboardProperty -InputObject $status -PropertyName " repoName " )
state = if ( [ string ] :: IsNullOrWhiteSpace ( $state ) ) { " unknown " } else { $state }
activityAt = $activityAt
logPath = [ string ] ( Get-VoidBotDashboardProperty -InputObject $status -PropertyName " logPath " )
note = @ (
[ string ] ( Get-VoidBotDashboardProperty -InputObject $status -PropertyName " lastSkipReason " )
[ string ] ( Get-VoidBotDashboardProperty -InputObject $status -PropertyName " repoPath " )
) | Where-Object { -not [ string ] :: IsNullOrWhiteSpace ( $_ ) } | Select-Object -First 1
}
}
$orderedHooks = $hooks | Sort-Object {
try {
[ DateTimeOffset ] :: Parse ( $_ . activityAt )
} catch {
[ DateTimeOffset ] :: MinValue
}
} -Descending
$result . state = if ( @ ( $orderedHooks | Where-Object { $_ . state -eq " failed " } ) . Count -gt 0 ) {
" warning "
} elseif ( @ ( $orderedHooks | Where-Object { $_ . state -eq " running " } ) . Count -gt 0 ) {
" running "
} else {
" healthy "
}
$result . detail = " Source hook statuses loaded. "
$result . total = $orderedHooks . Count
$result . completed = @ ( $orderedHooks | Where-Object { $_ . state -eq " completed " } ) . Count
$result . running = @ ( $orderedHooks | Where-Object { $_ . state -eq " running " } ) . Count
$result . failed = @ ( $orderedHooks | Where-Object { $_ . state -eq " failed " } ) . Count
$result . recentHooks = @ ( $orderedHooks | Select-Object -First 6 )
$result . latestActivityAt = $orderedHooks [ 0 ] . activityAt
return [ PSCustomObject ] $result
}
function Get-VoidBotDashboardMcpUsageDiagnostics {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $ArtifactsRoot
)
$result = [ ordered ] @ {
state = " missing "
detail = " No Codex trace artifacts yet. "
traceCount = 0
scannedTraceCount = 0
totalMcpCalls = 0
averageDurationMs = $null
latestTraceAt = $null
topTools = @ ( )
recentTraces = @ ( )
}
if ( -not ( Test-Path -LiteralPath $ArtifactsRoot ) ) {
return [ PSCustomObject ] $result
}
$traceFiles = @ ( Get-ChildItem -LiteralPath $ArtifactsRoot -Recurse -File -Filter " codex-turn-*-trace.json " | Sort-Object LastWriteTime -Descending )
if ( $traceFiles . Count -eq 0 ) {
return [ PSCustomObject ] $result
}
$recentFiles = @ ( $traceFiles | Select-Object -First 12 )
$toolCounts = @ { }
$recentTraces = @ ( )
$durations = @ ( )
$totalMcpCalls = 0
foreach ( $file in $recentFiles ) {
$trace = Read-VoidBotDashboardJson -Path $file . FullName
$events = @ ( ( Get-VoidBotDashboardProperty -InputObject $trace -PropertyName " events " ) )
$mcpEvents = @ ( $events | Where-Object { [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " kind " ) -eq " mcp_tool_completed " } )
foreach ( $event in $mcpEvents ) {
$toolName = [ string ] ( Get-VoidBotDashboardProperty -InputObject $event -PropertyName " tool " )
if ( [ string ] :: IsNullOrWhiteSpace ( $toolName ) ) {
continue
}
if ( -not $toolCounts . ContainsKey ( $toolName ) ) {
$toolCounts [ $toolName ] = 0
}
$toolCounts [ $toolName ] + = 1
}
$durationMs = Get-VoidBotDashboardProperty -InputObject $trace -PropertyName " durationMs "
if ( $durationMs -is [ ValueType ] ) {
$durations + = [ double ] $durationMs
}
$tools = @ ( $mcpEvents | ForEach-Object { [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " tool " ) } | Where-Object { -not [ string ] :: IsNullOrWhiteSpace ( $_ ) } | Select-Object -Unique )
$totalMcpCalls + = $mcpEvents . Count
$recentTraces + = [ PSCustomObject ] @ {
traceFile = $file . FullName
jobId = $file . Directory . Name
startedAt = [ string ] ( Get-VoidBotDashboardProperty -InputObject $trace -PropertyName " startedAt " )
durationMs = $durationMs
mcpCalls = $mcpEvents . Count
tools = $tools
}
}
$topTools = $toolCounts . GetEnumerator ( ) |
Sort-Object Value -Descending |
Select-Object -First 6 |
ForEach-Object {
[ PSCustomObject ] @ {
name = $_ . Key
count = $_ . Value
}
}
$result . state = if ( $totalMcpCalls -gt 0 ) { " healthy " } else { " neutral " }
$result . detail = " Recent Codex trace usage loaded. "
$result . traceCount = $traceFiles . Count
$result . scannedTraceCount = $recentFiles . Count
$result . totalMcpCalls = $totalMcpCalls
$result . averageDurationMs = if ( $durations . Count -gt 0 ) { [ Math ] :: Round ( ( ( $durations | Measure-Object -Average ) . Average ) , 0 ) } else { $null }
$result . latestTraceAt = $recentTraces [ 0 ] . startedAt
$result . topTools = @ ( $topTools )
$result . recentTraces = @ ( $recentTraces )
return [ PSCustomObject ] $result
}
2026-04-25 02:04:04 +01:00
function Get-VoidBotDashboardJobDiagnostics {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $DatabaseDsn ,
[ Parameter ( Mandatory = $false ) ]
[ AllowNull ( ) ]
$Runtime ,
[ Parameter ( Mandatory = $true ) ]
[ string ] $ArtifactsRoot
)
$result = [ ordered ] @ {
state = " missing "
detail = " No job diagnostics yet. "
current = @ ( )
recent = @ ( )
counts = [ ordered ] @ { }
}
$runtimePostgres = if ( $null -ne $Runtime ) { Get-VoidBotDashboardProperty -InputObject $Runtime -PropertyName " postgres " } else { $null }
if ( $null -eq $runtimePostgres ) {
return [ PSCustomObject ] $result
}
if ( -not ( Get-Command docker -ErrorAction SilentlyContinue ) ) {
$result . state = " warning "
$result . detail = " Docker is unavailable, so live job details are hidden. "
return [ PSCustomObject ] $result
}
$sql = @ '
select json_build_object (
'current' , coalesce ( ( select json_agg ( row_to_json ( t ) ) from (
select
id :: text ,
state ,
provider_name ,
command_name ,
requester_discord_id ,
created_at :: text ,
updated_at :: text ,
request_message_id ,
coalesce ( job_json - > > 'summary' , '' ) as summary ,
coalesce ( job_json - > > 'error' , '' ) as error
from jobs
where state in ( 'queued' , 'approved' , 'running' , 'awaiting_approval' , 'awaiting_post_approval' )
order by updated_at desc
limit 10
) t ) , '[]' :: json ) ,
'recent' , coalesce ( ( select json_agg ( row_to_json ( t ) ) from (
select
id :: text ,
state ,
provider_name ,
command_name ,
requester_discord_id ,
created_at :: text ,
updated_at :: text ,
request_message_id ,
coalesce ( job_json - > > 'summary' , '' ) as summary ,
coalesce ( job_json - > > 'error' , '' ) as error
from jobs
order by updated_at desc
limit 12
) t ) , '[]' :: json ) ,
'counts' , coalesce ( ( select json_object_agg ( state , job_count ) from (
select state , count ( * ) as job_count
from jobs
group by state
) s ) , '{}' :: json )
) ;
' @
try {
$json = & docker exec voidbot-postgres psql -U voidbot -d voidbot -t -A -c $sql 2 > $null
if ( $LASTEXITCODE -ne 0 -or [ string ] :: IsNullOrWhiteSpace ( $json ) ) {
throw " psql returned no data. "
}
$parsed = $json . Trim ( ) | ConvertFrom-Json
function Expand-JobRow {
param (
[ Parameter ( Mandatory = $true ) ]
$Job
)
$jobId = [ string ] ( Get-VoidBotDashboardProperty -InputObject $Job -PropertyName " id " )
$artifactDir = Join-Path $ArtifactsRoot $jobId
$traceFile = $null
$stdoutFile = $null
$stderrFile = $null
$debugFile = $null
$handoffFile = $null
$requestFile = $null
$ragTranscriptFile = $null
if ( Test-Path -LiteralPath $artifactDir ) {
$traceFile = @ ( Get-ChildItem -LiteralPath $artifactDir -File -Filter " codex-turn-*-trace.json " | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | ForEach-Object { $_ . FullName } ) | Select-Object -First 1
$stdoutFile = @ ( Get-ChildItem -LiteralPath $artifactDir -File -Filter " codex-turn-*-stdout.txt " | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | ForEach-Object { $_ . FullName } ) | Select-Object -First 1
$stderrFile = @ ( Get-ChildItem -LiteralPath $artifactDir -File -Filter " codex-turn-*-stderr.txt " | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | ForEach-Object { $_ . FullName } ) | Select-Object -First 1
$debugFile = @ ( Get-ChildItem -LiteralPath $artifactDir -File -Filter " codex-turn-*-debug.md " | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | ForEach-Object { $_ . FullName } ) | Select-Object -First 1
$handoffFile = if ( Test-Path -LiteralPath ( Join-Path $artifactDir " handoff.md " ) ) { Join-Path $artifactDir " handoff.md " } else { $null }
$requestFile = if ( Test-Path -LiteralPath ( Join-Path $artifactDir " request.json " ) ) { Join-Path $artifactDir " request.json " } else { $null }
$ragTranscriptFile = if ( Test-Path -LiteralPath ( Join-Path $artifactDir " rag-tool-transcript.md " ) ) { Join-Path $artifactDir " rag-tool-transcript.md " } else { $null }
}
return [ PSCustomObject ] @ {
id = $jobId
state = [ string ] ( Get-VoidBotDashboardProperty -InputObject $Job -PropertyName " state " )
providerName = [ string ] ( Get-VoidBotDashboardProperty -InputObject $Job -PropertyName " provider_name " )
commandName = [ string ] ( Get-VoidBotDashboardProperty -InputObject $Job -PropertyName " command_name " )
requesterDiscordId = [ string ] ( Get-VoidBotDashboardProperty -InputObject $Job -PropertyName " requester_discord_id " )
createdAt = [ string ] ( Get-VoidBotDashboardProperty -InputObject $Job -PropertyName " created_at " )
updatedAt = [ string ] ( Get-VoidBotDashboardProperty -InputObject $Job -PropertyName " updated_at " )
requestMessageId = [ string ] ( Get-VoidBotDashboardProperty -InputObject $Job -PropertyName " request_message_id " )
summary = [ string ] ( Get-VoidBotDashboardProperty -InputObject $Job -PropertyName " summary " )
error = [ string ] ( Get-VoidBotDashboardProperty -InputObject $Job -PropertyName " error " )
artifactDir = $ ( if ( Test-Path -LiteralPath $artifactDir ) { $artifactDir } else { $null } )
traceFile = $traceFile
stdoutFile = $stdoutFile
stderrFile = $stderrFile
debugFile = $debugFile
handoffFile = $handoffFile
requestFile = $requestFile
ragTranscriptFile = $ragTranscriptFile
}
}
$result . state = " healthy "
$result . detail = " Live job history loaded. "
$result . current = @ ( ( Get-VoidBotDashboardProperty -InputObject $parsed -PropertyName " current " ) | ForEach-Object { Expand-JobRow -Job $_ } )
$result . recent = @ ( ( Get-VoidBotDashboardProperty -InputObject $parsed -PropertyName " recent " ) | ForEach-Object { Expand-JobRow -Job $_ } )
$result . counts = [ ordered ] @ { }
if ( $null -ne $parsed . counts ) {
foreach ( $property in $parsed . counts . PSObject . Properties ) {
$result . counts [ $property . Name ] = $property . Value
}
}
} catch {
$result . state = " warning "
$result . detail = " Live job query failed: $( $_ . Exception . Message ) "
}
return [ PSCustomObject ] $result
}
2026-04-24 20:11:18 +01:00
function Update-VoidBotOperationsDashboard {
param (
[ Parameter ( Mandatory = $true ) ]
[ string ] $RepoRoot ,
[ Parameter ( Mandatory = $true ) ]
[ string ] $StorageRoot
)
$statusDir = Join-Path $StorageRoot " status "
$logsDir = Join-Path $StorageRoot " logs "
2026-04-25 01:58:53 +01:00
$artifactsRoot = Join-Path $StorageRoot " artifacts "
2026-04-24 20:11:18 +01:00
$dashboardPath = Join-Path $statusDir " operations-dashboard.html "
$snapshotPath = Join-Path $statusDir " operations-dashboard.json "
2026-04-25 01:58:53 +01:00
$sourceHookStatusDir = Join-Path $statusDir " source-hooks "
2026-04-24 20:11:18 +01:00
New-Item -ItemType Directory -Force -Path $statusDir | Out-Null
2026-04-25 01:58:53 +01:00
$envPath = Join-Path $RepoRoot " .env "
$config = Read-VoidBotDashboardDotEnv -Path $envPath
$databaseDsn = if ( $config . ContainsKey ( " DATABASE_DSN " ) ) { $config [ " DATABASE_DSN " ] } else { " postgres://voidbot:voidbot@localhost:5432/voidbot " }
$vectorStoreKind = if ( $config . ContainsKey ( " VECTOR_STORE_KIND " ) ) { $config [ " VECTOR_STORE_KIND " ] } else { " qdrant " }
$stateStorageBackend = if ( $config . ContainsKey ( " STATE_STORAGE_BACKEND " ) ) { $config [ " STATE_STORAGE_BACKEND " ] } else { " postgres " }
$qdrantUrl = if ( $config . ContainsKey ( " QDRANT_URL " ) ) { $config [ " QDRANT_URL " ] } else { " http://127.0.0.1:6333 " }
$qdrantHistoryCollection = if ( $config . ContainsKey ( " QDRANT_HISTORY_COLLECTION " ) ) { $config [ " QDRANT_HISTORY_COLLECTION " ] } else { " voidbot_discord_history_chunks " }
$qdrantSourceCollection = if ( $config . ContainsKey ( " QDRANT_SOURCE_COLLECTION " ) ) { $config [ " QDRANT_SOURCE_COLLECTION " ] } else { " voidbot_repository_source_chunks " }
$historyArchivePath = Resolve-VoidBotDashboardConfigPath -RepoRoot $RepoRoot -Value $config [ " RAG_ARCHIVE_PATH " ] -Fallback " .voidbot/rag/messages.json "
$sourceArchivePath = Resolve-VoidBotDashboardConfigPath -RepoRoot $RepoRoot -Value $config [ " RAG_SOURCE_ARCHIVE_PATH " ] -Fallback " .voidbot/rag/source-documents.json "
$importStatePath = Resolve-VoidBotDashboardConfigPath -RepoRoot $RepoRoot -Value $config [ " RAG_IMPORT_STATE_PATH " ] -Fallback " .voidbot/rag/import-state.json "
2026-04-24 20:11:18 +01:00
$runtimePath = Join-Path $statusDir " runtime-stack.json "
$operationsPath = Join-Path $statusDir " operations-health.json "
$watchdogPath = Join-Path $statusDir " operations-watchdog.json "
$offsitePath = Join-Path $statusDir " offsite-backup.json "
$runtime = Read-VoidBotDashboardJson -Path $runtimePath
$operations = Read-VoidBotDashboardJson -Path $operationsPath
$watchdog = Read-VoidBotDashboardJson -Path $watchdogPath
$offsite = Read-VoidBotDashboardJson -Path $offsitePath
2026-04-25 01:58:53 +01:00
$historyArchive = Get-VoidBotDashboardFileSummary -Path $historyArchivePath
$sourceArchive = Get-VoidBotDashboardFileSummary -Path $sourceArchivePath
$importState = Get-VoidBotDashboardFileSummary -Path $importStatePath
2026-04-24 20:11:18 +01:00
$operationsState = [ string ] ( Get-VoidBotDashboardProperty -InputObject $operations -PropertyName " status " )
if ( [ string ] :: IsNullOrWhiteSpace ( $operationsState ) ) {
$operationsState = if ( Get-VoidBotDashboardProperty -InputObject $operations -PropertyName " _missing " ) { " missing " } else { " unknown " }
}
$runtimeState = if ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " _missing " ) {
" missing "
} elseif ( [ bool ] ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " ready " ) ) {
" ready "
} else {
$stageValue = [ string ] ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " stage " )
if ( [ string ] :: IsNullOrWhiteSpace ( $stageValue ) ) { " starting " } else { $stageValue }
}
$watchdogRunStatus = [ string ] ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " runStatus " )
$watchdogStartedAt = [ string ] ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " startedAt " )
$watchdogStep = [ string ] ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " currentStep " )
$watchdogLimitMinutes = Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " executionTimeLimitMinutes "
$watchdogReportStatus = [ string ] ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " reportStatus " )
$watchdogState = if ( [ string ] :: IsNullOrWhiteSpace ( $watchdogRunStatus ) ) {
if ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " _missing " ) { " missing " } else { $operationsState }
} elseif ( $watchdogRunStatus -eq " running " -and -not [ string ] :: IsNullOrWhiteSpace ( $watchdogStartedAt ) ) {
try {
$elapsedMinutes = ( [ DateTimeOffset ] :: Now - [ DateTimeOffset ] :: Parse ( $watchdogStartedAt ) ) . TotalMinutes
$limitValue = if ( $watchdogLimitMinutes -is [ ValueType ] ) { [ double ] $watchdogLimitMinutes } else { 15.0 }
if ( $elapsedMinutes -gt $limitValue ) { " stalled " } else { " running " }
} catch {
" running "
}
} elseif ( $watchdogRunStatus -eq " completed " -and -not [ string ] :: IsNullOrWhiteSpace ( $watchdogReportStatus ) ) {
$watchdogReportStatus
} else {
$watchdogRunStatus
}
$offsiteState = [ string ] ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " status " )
if ( [ string ] :: IsNullOrWhiteSpace ( $offsiteState ) ) {
$offsiteState = if ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " _missing " ) { " missing " } else { " unknown " }
}
2026-04-25 01:58:53 +01:00
$postgresDiagnostics = if ( $stateStorageBackend -eq " postgres " ) {
Get-VoidBotDashboardPostgresDiagnostics -DatabaseDsn $databaseDsn -Runtime $runtime
} else {
[ PSCustomObject ] @ {
state = " neutral "
detail = " State storage backend is $stateStorageBackend . "
jobsTotal = $null
jobsByState = [ ordered ] @ { }
auditEvents = $null
interactionMemoryEvents = $null
providerRuns = $null
toolInvocations = $null
latestJobAt = $null
latestAuditAt = $null
latestInteractionAt = $null
latestProviderRunAt = $null
latestToolInvocationAt = $null
}
}
$qdrantDiagnostics = if ( $vectorStoreKind -eq " qdrant " ) {
Get-VoidBotDashboardQdrantDiagnostics -QdrantUrl $qdrantUrl -HistoryCollection $qdrantHistoryCollection -SourceCollection $qdrantSourceCollection
} else {
[ PSCustomObject ] @ {
state = " neutral "
detail = " Vector backend is $vectorStoreKind . "
history = $null
source = $null
topRepos = @ ( )
topLanguages = @ ( )
}
}
$sourceHookDiagnostics = Get-VoidBotDashboardSourceHookDiagnostics -SourceHookStatusDir $sourceHookStatusDir
$mcpDiagnostics = Get-VoidBotDashboardMcpUsageDiagnostics -ArtifactsRoot $artifactsRoot
2026-04-25 02:04:04 +01:00
$jobDiagnostics = if ( $stateStorageBackend -eq " postgres " ) {
Get-VoidBotDashboardJobDiagnostics -DatabaseDsn $databaseDsn -Runtime $runtime -ArtifactsRoot $artifactsRoot
} else {
[ PSCustomObject ] @ {
state = " neutral "
detail = " State storage backend is $stateStorageBackend . "
current = @ ( )
recent = @ ( )
counts = [ ordered ] @ { }
}
}
2026-04-25 01:58:53 +01:00
$ingestState = if ( $sourceHookDiagnostics . failed -gt 0 ) {
" warning "
} elseif ( $sourceHookDiagnostics . running -gt 0 ) {
" running "
} elseif ( $historyArchive . exists -or $sourceArchive . exists ) {
" healthy "
} else {
" missing "
}
2026-04-24 20:11:18 +01:00
$operationsSummary = Get-VoidBotDashboardProperty -InputObject $operations -PropertyName " summary "
$operationsSubtitle = if ( $null -ne $operationsSummary ) {
" {0} failed, {1} warning, {2} passed " -f `
( Get-VoidBotDashboardProperty -InputObject $operationsSummary -PropertyName " failed " ) , `
( Get-VoidBotDashboardProperty -InputObject $operationsSummary -PropertyName " warning " ) , `
( Get-VoidBotDashboardProperty -InputObject $operationsSummary -PropertyName " passed " )
} else {
" No health report yet. "
}
$runtimeSubtitle = if ( $runtimeState -eq " ready " ) {
" Bot and worker reported ready. "
} elseif ( $runtimeState -eq " missing " ) {
" Runtime stack has not written status yet. "
} else {
" Current stage: {0} " -f $runtimeState
}
2026-04-25 01:58:53 +01:00
$stateStorageSubtitle = if ( $postgresDiagnostics . jobsTotal -is [ ValueType ] ) {
" {0} jobs, {1} audit events, {2} memory events " -f `
$postgresDiagnostics . jobsTotal , `
( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " auditEvents " ) , `
( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " interactionMemoryEvents " )
} else {
[ string ] ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " detail " )
}
$vectorSubtitle = if ( $null -ne ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " history " ) -and $null -ne ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " source " ) ) {
" {0} history points, {1} source points " -f `
( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " history " ) -PropertyName " pointsCount " ) , `
( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " source " ) -PropertyName " pointsCount " )
} else {
[ string ] ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " detail " )
}
$latestHook = @ ( ( Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " recentHooks " ) ) | Select-Object -First 1
$ingestSubtitle = if ( $latestHook ) {
" History archive {0}; latest source hook {1} {2} " -f `
$historyArchive . updatedRelative , `
( [ string ] ( Get-VoidBotDashboardProperty -InputObject $latestHook -PropertyName " repoName " ) ) , `
( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $latestHook -PropertyName " activityAt " ) ) )
} elseif ( $historyArchive . exists -or $sourceArchive . exists ) {
" Archives are present. Source hooks have not reported in yet. "
} else {
" No archive or hook signal yet. "
}
$mcpSubtitle = if ( ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " traceCount " ) -gt 0 ) {
" {0} MCP calls across {1} recent traces " -f `
( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " totalMcpCalls " ) , `
( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " scannedTraceCount " )
} else {
[ string ] ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " detail " )
}
2026-04-25 02:04:04 +01:00
$jobCounts = Get-VoidBotDashboardProperty -InputObject $jobDiagnostics -PropertyName " counts "
$currentJobs = @ ( ( Get-VoidBotDashboardProperty -InputObject $jobDiagnostics -PropertyName " current " ) )
$recentJobs = @ ( ( Get-VoidBotDashboardProperty -InputObject $jobDiagnostics -PropertyName " recent " ) )
$jobSubtitle = if ( $recentJobs . Count -gt 0 ) {
" {0} active, {1} completed, {2} failed " -f `
$currentJobs . Count , `
$ ( if ( $jobCounts . Contains ( " completed " ) ) { $jobCounts [ " completed " ] } else { 0 } ) , `
$ ( if ( $jobCounts . Contains ( " failed " ) ) { $jobCounts [ " failed " ] } else { 0 } )
} else {
[ string ] ( Get-VoidBotDashboardProperty -InputObject $jobDiagnostics -PropertyName " detail " )
}
2026-04-24 20:11:18 +01:00
$offsiteSubtitle = if ( $offsiteState -eq " completed " ) {
" Latest offsite sync finished. "
} elseif ( $offsiteState -eq " missing " ) {
" No offsite sync status yet. "
} else {
" Current phase: {0} " -f $offsiteState
}
2026-04-25 01:58:53 +01:00
$jobStateItems = @ ( )
foreach ( $entry in ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " jobsByState " ) . GetEnumerator ( ) | Sort-Object Name ) {
$jobStateItems + = " <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( $entry . Key ) ) </span> <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] $entry . Value ) ) </span> "
}
$topRepoItems = @ ( ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " topRepos " ) | ForEach-Object {
" <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'value' ) ) ) </span> <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'count' ) ) ) </span> "
} )
$topLanguageItems = @ ( ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " topLanguages " ) | ForEach-Object {
" <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'value' ) ) ) </span> <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'count' ) ) ) </span> "
} )
$recentHookItems = @ ( ( Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " recentHooks " ) | ForEach-Object {
$repoName = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " repoName " )
$logPath = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " logPath " )
$repoHtml = if ( [ string ] :: IsNullOrWhiteSpace ( $logPath ) ) {
" <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( $repoName ) ) </span> "
} else {
New-VoidBotDashboardFileLink -Path $logPath -Label $repoName
}
$note = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " note " )
$noteHtml = if ( [ string ] :: IsNullOrWhiteSpace ( $note ) ) {
" "
} else {
" <div class='muted-inline'> $( [ System.Net.WebUtility ] :: HtmlEncode ( $note ) ) </div> "
}
$repoHtml + " " + ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " state " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " state " ) ) ) + " <span class='muted-inline'> $( [ System.Net.WebUtility ] :: HtmlEncode ( ( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'activityAt' ) ) ) ) ) </span> " + $noteHtml
} )
$topToolItems = @ ( ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " topTools " ) | ForEach-Object {
" <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'name' ) ) ) </span> <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'count' ) ) ) </span> "
} )
$recentTraceItems = @ ( ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " recentTraces " ) | ForEach-Object {
$traceFile = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " traceFile " )
$jobId = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " jobId " )
$traceLink = if ( [ string ] :: IsNullOrWhiteSpace ( $traceFile ) ) {
" <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( $jobId ) ) </span> "
} else {
New-VoidBotDashboardFileLink -Path $traceFile -Label $jobId
}
$durationMs = Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " durationMs "
$durationText = if ( $durationMs -is [ ValueType ] ) {
" {0:N1}s " -f ( [ double ] $durationMs / 1000 )
} else {
" unknown "
}
$toolSummary = @ ( ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " tools " ) ) -join " , "
$toolHtml = if ( [ string ] :: IsNullOrWhiteSpace ( $toolSummary ) ) {
" <span class='muted-inline'>No MCP calls.</span> "
} else {
" <div class='muted-inline'> $( [ System.Net.WebUtility ] :: HtmlEncode ( $toolSummary ) ) </div> "
}
$traceLink + " <span class='muted-inline'> $( [ System.Net.WebUtility ] :: HtmlEncode ( ( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'startedAt' ) ) ) ) ) / $( [ System.Net.WebUtility ] :: HtmlEncode ( $durationText ) ) / $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'mcpCalls' ) ) ) calls</span> " + $toolHtml
} )
2026-04-25 02:04:04 +01:00
$currentJobItems = @ ( $currentJobs | ForEach-Object {
$jobId = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " id " )
$artifactDir = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " artifactDir " )
$jobLink = if ( [ string ] :: IsNullOrWhiteSpace ( $artifactDir ) ) {
" <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( $jobId ) ) </span> "
} else {
New-VoidBotDashboardFileLink -Path $artifactDir -Label $jobId
}
$links = @ ( )
foreach ( $entry in @ (
@ { name = " request " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " requestFile " ) }
@ { name = " trace " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " traceFile " ) }
@ { name = " stdout " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " stdoutFile " ) }
@ { name = " stderr " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " stderrFile " ) }
@ { name = " debug " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " debugFile " ) }
@ { name = " handoff " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " handoffFile " ) }
) ) {
if ( -not [ string ] :: IsNullOrWhiteSpace ( $entry . path ) ) {
$links + = New-VoidBotDashboardFileLink -Path $entry . path -Label $entry . name
}
}
$linksHtml = if ( $links . Count -gt 0 ) {
" <div class='muted-inline'> " + ( $links -join " · " ) + " </div> "
} else {
" "
}
$summary = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " summary " )
$error = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " error " )
$note = if ( -not [ string ] :: IsNullOrWhiteSpace ( $error ) ) { $error } elseif ( -not [ string ] :: IsNullOrWhiteSpace ( $summary ) ) { $summary } else { " " }
$noteHtml = if ( [ string ] :: IsNullOrWhiteSpace ( $note ) ) { " " } else { " <div class='muted-inline'> $( [ System.Net.WebUtility ] :: HtmlEncode ( $note ) ) </div> " }
$jobLink + " " + ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " state " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " state " ) ) ) + " <span class='muted-inline'> $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'providerName' ) ) ) / $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'commandName' ) ) ) / $( [ System.Net.WebUtility ] :: HtmlEncode ( ( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'updatedAt' ) ) ) ) ) </span> " + $linksHtml + $noteHtml
} )
$recentJobItems = @ ( $recentJobs | ForEach-Object {
$jobId = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " id " )
$artifactDir = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " artifactDir " )
$jobLink = if ( [ string ] :: IsNullOrWhiteSpace ( $artifactDir ) ) {
" <span class='mono'> $( [ System.Net.WebUtility ] :: HtmlEncode ( $jobId ) ) </span> "
} else {
New-VoidBotDashboardFileLink -Path $artifactDir -Label $jobId
}
$links = @ ( )
foreach ( $entry in @ (
@ { name = " trace " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " traceFile " ) }
@ { name = " stdout " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " stdoutFile " ) }
@ { name = " stderr " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " stderrFile " ) }
@ { name = " debug " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " debugFile " ) }
@ { name = " handoff " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " handoffFile " ) }
@ { name = " request " ; path = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " requestFile " ) }
) ) {
if ( -not [ string ] :: IsNullOrWhiteSpace ( $entry . path ) ) {
$links + = New-VoidBotDashboardFileLink -Path $entry . path -Label $entry . name
}
}
$linksHtml = if ( $links . Count -gt 0 ) {
" <div class='muted-inline'> " + ( $links -join " · " ) + " </div> "
} else {
" "
}
$summary = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " summary " )
$error = [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " error " )
$note = if ( -not [ string ] :: IsNullOrWhiteSpace ( $error ) ) { $error } elseif ( -not [ string ] :: IsNullOrWhiteSpace ( $summary ) ) { $summary } else { " " }
$noteHtml = if ( [ string ] :: IsNullOrWhiteSpace ( $note ) ) { " " } else { " <div class='muted-inline'> $( [ System.Net.WebUtility ] :: HtmlEncode ( $note ) ) </div> " }
$jobLink + " " + ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " state " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName " state " ) ) ) + " <span class='muted-inline'> $( [ System.Net.WebUtility ] :: HtmlEncode ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'providerName' ) ) ) / $( [ System.Net.WebUtility ] :: HtmlEncode ( ( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $_ -PropertyName 'updatedAt' ) ) ) ) ) </span> " + $linksHtml + $noteHtml
} )
2026-04-25 01:58:53 +01:00
2026-04-24 20:11:18 +01:00
$cardsHtml = @ (
New-VoidBotDashboardCard -Title " Operations Health " -State $operationsState -Subtitle $operationsSubtitle -Meta ( " Checked " + ( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $operations -PropertyName " checkedAt " ) ) ) )
New-VoidBotDashboardCard -Title " Runtime Stack " -State $runtimeState -Subtitle $runtimeSubtitle -Meta ( " Updated " + ( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " completedAt " ) ) ) )
2026-04-25 01:58:53 +01:00
New-VoidBotDashboardCard -Title " State Storage " -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " state " ) ) -Subtitle $stateStorageSubtitle -Meta ( " Latest job " + ( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " latestJobAt " ) ) ) )
New-VoidBotDashboardCard -Title " Vector Store " -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " state " ) ) -Subtitle $vectorSubtitle -Meta $qdrantUrl
New-VoidBotDashboardCard -Title " Ingest & Sync " -State $ingestState -Subtitle $ingestSubtitle -Meta ( " Source hooks: " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " total " ) ) )
New-VoidBotDashboardCard -Title " MCP Usage " -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " state " ) ) -Subtitle $mcpSubtitle -Meta ( " Latest trace " + ( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " latestTraceAt " ) ) ) )
2026-04-25 02:04:04 +01:00
New-VoidBotDashboardCard -Title " Jobs " -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $jobDiagnostics -PropertyName " state " ) ) -Subtitle $jobSubtitle -Meta ( " Latest job " + ( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " latestJobAt " ) ) ) )
2026-04-24 20:11:18 +01:00
New-VoidBotDashboardCard -Title " Offsite Backup " -State $offsiteState -Subtitle $offsiteSubtitle -Meta ( " Updated " + ( Get-VoidBotDashboardRelativeTime -Timestamp ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " completedAt " ) ) ) )
) -join [ Environment ] :: NewLine
$runtimeRows = @ (
New-VoidBotDashboardFactRow -Label " Status file " -ValueHtml ( New-VoidBotDashboardFileLink -Path $runtimePath )
New-VoidBotDashboardFactRow -Label " Ready " -ValueHtml ( New-VoidBotDashboardBadge -State $runtimeState -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " ready " ) ) )
New-VoidBotDashboardFactRow -Label " Stage " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " stage " ) ) + " </span> " )
2026-04-25 01:58:53 +01:00
New-VoidBotDashboardFactRow -Label " Started " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " startedAt " ) )
2026-04-24 20:11:18 +01:00
New-VoidBotDashboardFactRow -Label " Bot PID " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " bot " ) -PropertyName " pid " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Bot log " -ValueHtml $ ( if ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " bot " ) -PropertyName " log " ) { New-VoidBotDashboardFileLink -Path ( [ string ] ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " bot " ) -PropertyName " log " ) ) } else { " <span class='muted'>missing</span> " } )
New-VoidBotDashboardFactRow -Label " Worker PID " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " worker " ) -PropertyName " pid " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Worker log " -ValueHtml $ ( if ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " worker " ) -PropertyName " log " ) { New-VoidBotDashboardFileLink -Path ( [ string ] ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " worker " ) -PropertyName " log " ) ) } else { " <span class='muted'>missing</span> " } )
New-VoidBotDashboardFactRow -Label " Completed " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $runtime -PropertyName " completedAt " ) )
)
2026-04-25 01:58:53 +01:00
$stateStorageRows = @ (
New-VoidBotDashboardFactRow -Label " Backend " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml $stateStorageBackend ) + " </span> " )
New-VoidBotDashboardFactRow -Label " State " -ValueHtml ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " state " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " state " ) ) )
New-VoidBotDashboardFactRow -Label " Detail " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " detail " ) )
New-VoidBotDashboardFactRow -Label " Jobs total " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " jobsTotal " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Jobs by state " -ValueHtml ( New-VoidBotDashboardListHtml -Items $jobStateItems -EmptyText " No jobs recorded. " )
New-VoidBotDashboardFactRow -Label " Audit events " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " auditEvents " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Memory events " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " interactionMemoryEvents " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Provider runs " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " providerRuns " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Tool invocations " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " toolInvocations " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Latest job " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " latestJobAt " ) )
)
$vectorRows = @ (
New-VoidBotDashboardFactRow -Label " Backend " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml $vectorStoreKind ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Qdrant URL " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml $qdrantUrl ) + " </span> " )
New-VoidBotDashboardFactRow -Label " State " -ValueHtml ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " state " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " state " ) ) )
New-VoidBotDashboardFactRow -Label " History collection " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml $qdrantHistoryCollection ) + " </span> " )
New-VoidBotDashboardFactRow -Label " History points " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " history " ) -PropertyName " pointsCount " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " History indexed " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " history " ) -PropertyName " indexedVectorsCount " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Source collection " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml $qdrantSourceCollection ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Source points " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " source " ) -PropertyName " pointsCount " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Source indexed " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " source " ) -PropertyName " indexedVectorsCount " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Top repos " -ValueHtml ( New-VoidBotDashboardListHtml -Items $topRepoItems -EmptyText " No repo breakdown yet. " )
New-VoidBotDashboardFactRow -Label " Top languages " -ValueHtml ( New-VoidBotDashboardListHtml -Items $topLanguageItems -EmptyText " No language breakdown yet. " )
)
$ingestRows = @ (
New-VoidBotDashboardFactRow -Label " History archive " -ValueHtml $ ( if ( $historyArchive . exists ) { ( New-VoidBotDashboardFileLink -Path $historyArchive . path -Label " messages.json " ) + " <div class='muted-inline'> " + ( Escape-VoidBotDashboardHtml ( $historyArchive . sizeText + " , updated " + $historyArchive . updatedRelative ) ) + " </div> " } else { " <span class='muted'>missing</span> " } )
New-VoidBotDashboardFactRow -Label " Source archive " -ValueHtml $ ( if ( $sourceArchive . exists ) { ( New-VoidBotDashboardFileLink -Path $sourceArchive . path -Label " source-documents.json " ) + " <div class='muted-inline'> " + ( Escape-VoidBotDashboardHtml ( $sourceArchive . sizeText + " , updated " + $sourceArchive . updatedRelative ) ) + " </div> " } else { " <span class='muted'>missing</span> " } )
New-VoidBotDashboardFactRow -Label " Import state " -ValueHtml $ ( if ( $importState . exists ) { ( New-VoidBotDashboardFileLink -Path $importState . path -Label " import-state.json " ) + " <div class='muted-inline'> " + ( Escape-VoidBotDashboardHtml ( " updated " + $importState . updatedRelative ) ) + " </div> " } else { " <span class='muted'>missing</span> " } )
New-VoidBotDashboardFactRow -Label " Hook state " -ValueHtml ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " state " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " state " ) ) )
New-VoidBotDashboardFactRow -Label " Hook totals " -ValueHtml ( Escape-VoidBotDashboardHtml ( " total={0}, completed={1}, running={2}, failed={3} " -f ( Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " total " ) , ( Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " completed " ) , ( Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " running " ) , ( Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " failed " ) ) )
New-VoidBotDashboardFactRow -Label " Latest activity " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " latestActivityAt " ) )
New-VoidBotDashboardFactRow -Label " Recent hooks " -ValueHtml ( New-VoidBotDashboardListHtml -Items $recentHookItems -EmptyText " No source hook activity yet. " )
)
$mcpRows = @ (
New-VoidBotDashboardFactRow -Label " State " -ValueHtml ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " state " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " state " ) ) )
New-VoidBotDashboardFactRow -Label " Trace files " -ValueHtml ( Escape-VoidBotDashboardHtml ( " {0} total, {1} scanned " -f ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " traceCount " ) , ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " scannedTraceCount " ) ) )
New-VoidBotDashboardFactRow -Label " MCP calls " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " totalMcpCalls " ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Avg duration " -ValueHtml ( Escape-VoidBotDashboardHtml $ ( if ( ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " averageDurationMs " ) -is [ ValueType ] ) { " {0:N1}s " -f ( [ double ] ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " averageDurationMs " ) / 1000 ) } else { " missing " } ) )
New-VoidBotDashboardFactRow -Label " Latest trace " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " latestTraceAt " ) )
New-VoidBotDashboardFactRow -Label " Top tools " -ValueHtml ( New-VoidBotDashboardListHtml -Items $topToolItems -EmptyText " No MCP tool calls yet. " )
New-VoidBotDashboardFactRow -Label " Recent traces " -ValueHtml ( New-VoidBotDashboardListHtml -Items $recentTraceItems -EmptyText " No recent trace files yet. " )
)
2026-04-25 02:04:04 +01:00
$jobRows = @ (
New-VoidBotDashboardFactRow -Label " State " -ValueHtml ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $jobDiagnostics -PropertyName " state " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $jobDiagnostics -PropertyName " state " ) ) )
New-VoidBotDashboardFactRow -Label " Active jobs " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml ( [ string ] $currentJobs . Count ) ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Current jobs " -ValueHtml ( New-VoidBotDashboardListHtml -Items $currentJobItems -EmptyText " No queued or running jobs. " )
New-VoidBotDashboardFactRow -Label " Recent history " -ValueHtml ( New-VoidBotDashboardListHtml -Items $recentJobItems -EmptyText " No recent jobs recorded. " )
)
2026-04-24 20:11:18 +01:00
$watchdogRows = @ (
New-VoidBotDashboardFactRow -Label " Status file " -ValueHtml ( New-VoidBotDashboardFileLink -Path $watchdogPath )
New-VoidBotDashboardFactRow -Label " Run status " -ValueHtml ( New-VoidBotDashboardBadge -State $watchdogState -Label $watchdogState )
New-VoidBotDashboardFactRow -Label " Current step " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml $watchdogStep ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Started " -ValueHtml ( Escape-VoidBotDashboardHtml $watchdogStartedAt )
New-VoidBotDashboardFactRow -Label " Elapsed " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardElapsedTime -StartedAt $watchdogStartedAt ) )
New-VoidBotDashboardFactRow -Label " Completed " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " completedAt " ) )
2026-04-25 01:58:53 +01:00
New-VoidBotDashboardFactRow -Label " Report status " -ValueHtml ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " reportStatus " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " reportStatus " ) ) )
2026-04-24 20:11:18 +01:00
New-VoidBotDashboardFactRow -Label " Notification " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " lastNotificationReason " ) )
New-VoidBotDashboardFactRow -Label " Last run error " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " lastRunError " ) )
New-VoidBotDashboardFactRow -Label " Watchdog log " -ValueHtml $ ( if ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " logPath " ) { New-VoidBotDashboardFileLink -Path ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $watchdog -PropertyName " logPath " ) ) } else { " <span class='muted'>missing</span> " } )
)
$offsiteRows = @ (
New-VoidBotDashboardFactRow -Label " Status file " -ValueHtml ( New-VoidBotDashboardFileLink -Path $offsitePath )
New-VoidBotDashboardFactRow -Label " Status " -ValueHtml ( New-VoidBotDashboardBadge -State $offsiteState -Label $offsiteState )
2026-04-25 01:58:53 +01:00
New-VoidBotDashboardFactRow -Label " Verification " -ValueHtml ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " backupVerification " ) -PropertyName " status " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " backupVerification " ) -PropertyName " status " ) ) )
2026-04-24 20:11:18 +01:00
New-VoidBotDashboardFactRow -Label " Started " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " startedAt " ) )
New-VoidBotDashboardFactRow -Label " Completed " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " completedAt " ) )
2026-04-25 01:58:53 +01:00
New-VoidBotDashboardFactRow -Label " Backup directory " -ValueHtml $ ( if ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " backupDirectory " ) { New-VoidBotDashboardFileLink -Path ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " backupDirectory " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " backupLabel " ) ) } else { " <span class='muted'>missing</span> " } )
New-VoidBotDashboardFactRow -Label " Archive path " -ValueHtml $ ( if ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " archivePath " ) { New-VoidBotDashboardFileLink -Path ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " archivePath " ) ) -Label ( [ System.IO.Path ] :: GetFileName ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " archivePath " ) ) ) } else { " <span class='muted'>missing</span> " } )
2026-04-24 20:11:18 +01:00
New-VoidBotDashboardFactRow -Label " Remote file " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " remoteFile " ) -PropertyName " path " ) )
2026-04-25 01:58:53 +01:00
New-VoidBotDashboardFactRow -Label " Archive size " -ValueHtml ( Escape-VoidBotDashboardHtml ( Format-VoidBotDashboardBytes -Bytes ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " archiveSize " ) ) )
2026-04-24 20:11:18 +01:00
New-VoidBotDashboardFactRow -Label " Error " -ValueHtml ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " error " ) )
New-VoidBotDashboardFactRow -Label " Offsite log " -ValueHtml $ ( if ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " logPath " ) { New-VoidBotDashboardFileLink -Path ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $offsite -PropertyName " logPath " ) ) } else { New-VoidBotDashboardFileLink -Path ( Join-Path $logsDir " offsite-backup.log " ) } )
)
$fileRows = @ (
New-VoidBotDashboardFactRow -Label " Dashboard HTML " -ValueHtml ( New-VoidBotDashboardFileLink -Path $dashboardPath )
New-VoidBotDashboardFactRow -Label " Dashboard snapshot " -ValueHtml ( New-VoidBotDashboardFileLink -Path $snapshotPath )
New-VoidBotDashboardFactRow -Label " Operations report " -ValueHtml ( New-VoidBotDashboardFileLink -Path $operationsPath )
New-VoidBotDashboardFactRow -Label " Operations log " -ValueHtml ( New-VoidBotDashboardFileLink -Path ( Join-Path $logsDir " operations-watchdog.log " ) )
2026-04-25 01:58:53 +01:00
New-VoidBotDashboardFactRow -Label " History archive " -ValueHtml $ ( if ( $historyArchive . exists ) { New-VoidBotDashboardFileLink -Path $historyArchive . path -Label " messages.json " } else { " <span class='muted'>missing</span> " } )
New-VoidBotDashboardFactRow -Label " Source archive " -ValueHtml $ ( if ( $sourceArchive . exists ) { New-VoidBotDashboardFileLink -Path $sourceArchive . path -Label " source-documents.json " } else { " <span class='muted'>missing</span> " } )
New-VoidBotDashboardFactRow -Label " Import state " -ValueHtml $ ( if ( $importState . exists ) { New-VoidBotDashboardFileLink -Path $importState . path -Label " import-state.json " } else { " <span class='muted'>missing</span> " } )
2026-04-24 20:11:18 +01:00
New-VoidBotDashboardFactRow -Label " Repo root " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml $RepoRoot ) + " </span> " )
New-VoidBotDashboardFactRow -Label " Storage root " -ValueHtml ( " <span class='mono'> " + ( Escape-VoidBotDashboardHtml $StorageRoot ) + " </span> " )
)
$checkRows = @ ( )
foreach ( $check in @ ( ( Get-VoidBotDashboardProperty -InputObject $operations -PropertyName " checks " ) ) ) {
$checkRows + = " <tr><td> " + ( New-VoidBotDashboardBadge -State ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $check -PropertyName " status " ) ) -Label ( [ string ] ( Get-VoidBotDashboardProperty -InputObject $check -PropertyName " status " ) ) ) + " </td><td class='mono'> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $check -PropertyName " name " ) ) + " </td><td> " + ( Escape-VoidBotDashboardHtml ( Get-VoidBotDashboardProperty -InputObject $check -PropertyName " detail " ) ) + " </td></tr> "
}
if ( $checkRows . Count -eq 0 ) {
$checkRows = @ ( " <tr><td colspan='3'><span class='muted'>No watchdog checks have been written yet.</span></td></tr> " )
}
$checksSection = @"
< section class = 'panel' >
< h2 > Watchdog Checks < / h2 >
< table class = 'checks' >
< thead >
< tr > < th > Status < / th > < th > Check < / th > < th > Detail < / th > < / tr >
< / thead >
< tbody >
$ ( $checkRows -join [ Environment ] :: NewLine )
< / tbody >
< / table >
< / section >
" @
$snapshot = [ ordered ] @ {
generatedAt = ( Get-Date ) . ToString ( " o " )
dashboardPath = $dashboardPath
repoRoot = $RepoRoot
storageRoot = $StorageRoot
states = [ ordered ] @ {
operations = $operationsState
runtime = $runtimeState
2026-04-25 01:58:53 +01:00
postgres = [ string ] ( Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " state " )
qdrant = [ string ] ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " state " )
ingest = $ingestState
mcp = [ string ] ( Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " state " )
2026-04-25 02:04:04 +01:00
jobs = [ string ] ( Get-VoidBotDashboardProperty -InputObject $jobDiagnostics -PropertyName " state " )
2026-04-24 20:11:18 +01:00
watchdog = $watchdogState
offsite = $offsiteState
}
2026-04-25 01:58:53 +01:00
diagnostics = [ ordered ] @ {
postgres = [ ordered ] @ {
jobsTotal = Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " jobsTotal "
auditEvents = Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " auditEvents "
interactionMemoryEvents = Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " interactionMemoryEvents "
providerRuns = Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " providerRuns "
toolInvocations = Get-VoidBotDashboardProperty -InputObject $postgresDiagnostics -PropertyName " toolInvocations "
}
qdrant = [ ordered ] @ {
historyPoints = Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " history " ) -PropertyName " pointsCount "
sourcePoints = Get-VoidBotDashboardProperty -InputObject ( Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " source " ) -PropertyName " pointsCount "
topRepos = Get-VoidBotDashboardProperty -InputObject $qdrantDiagnostics -PropertyName " topRepos "
}
ingest = [ ordered ] @ {
historyArchiveUpdatedAt = $historyArchive . updatedAt
sourceArchiveUpdatedAt = $sourceArchive . updatedAt
sourceHookCount = Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " total "
latestSourceHookAt = Get-VoidBotDashboardProperty -InputObject $sourceHookDiagnostics -PropertyName " latestActivityAt "
}
mcp = [ ordered ] @ {
traceCount = Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " traceCount "
scannedTraceCount = Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " scannedTraceCount "
totalMcpCalls = Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " totalMcpCalls "
topTools = Get-VoidBotDashboardProperty -InputObject $mcpDiagnostics -PropertyName " topTools "
}
2026-04-25 02:04:04 +01:00
jobs = [ ordered ] @ {
currentCount = $currentJobs . Count
recentCount = $recentJobs . Count
counts = Get-VoidBotDashboardProperty -InputObject $jobDiagnostics -PropertyName " counts "
}
2026-04-25 01:58:53 +01:00
}
2026-04-24 20:11:18 +01:00
files = [ ordered ] @ {
runtime = $runtimePath
operations = $operationsPath
watchdog = $watchdogPath
offsite = $offsitePath
operationsLog = Join-Path $logsDir " operations-watchdog.log "
offsiteLog = Join-Path $logsDir " offsite-backup.log "
2026-04-25 01:58:53 +01:00
historyArchive = $historyArchivePath
sourceArchive = $sourceArchivePath
importState = $importStatePath
2026-04-24 20:11:18 +01:00
}
}
2026-05-13 19:46:29 +01:00
$snapshotJson = $snapshot | ConvertTo-Json -Depth 10
Write-VoidBotDashboardUtf8File -Path $snapshotPath -Content $snapshotJson
2026-04-24 20:11:18 +01:00
$html = @"
< ! doctype html >
< html lang = 'en' >
< head >
< meta charset = 'utf-8' >
< meta http-equiv = 'refresh' content = '10' >
< title > VoidBot Operations Dashboard < / title >
2026-04-24 21:00:57 +01:00
< link rel = 'preconnect' href = 'https://fonts.googleapis.com' >
< link rel = 'preconnect' href = 'https://fonts.gstatic.com' crossorigin >
< link href = 'https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;600&family=Ubuntu:wght@300;400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap' rel = 'stylesheet' >
2026-04-24 20:11:18 +01:00
< style >
: root {
2026-04-24 21:00:57 +01:00
color-scheme : dark ;
- -light: #07111a;
- -lightgray: #16212c;
- -gray: #63758a;
- -darkgray: #b7c7d9;
- -dark: #eef5ff;
- -secondary: #ff8a2a;
- -tertiary: #59b7ff;
- -highlight: rgba ( 89 , 183 , 255 , 0.14 ) ;
- -textHighlight: #ff8a2a55;
- -panel: linear-gradient ( 180deg , rgba ( 13 , 20 , 31 , 0.96 ) , rgba ( 8 , 13 , 21 , 0.92 ) ) ;
- -panel -strong: linear-gradient ( 135deg , rgba ( 7 , 12 , 19 , 0.98 ) , rgba ( 12 , 20 , 31 , 0.95 ) ) ;
- -panel -soft: linear-gradient ( 135deg , rgba ( 10 , 16 , 24 , 0.9 ) , rgba ( 17 , 24 , 35 , 0.82 ) ) ;
- -surface -line: rgba ( 89 , 183 , 255 , 0.12 ) ;
- -surface -line -strong: rgba ( 89 , 183 , 255 , 0.18 ) ;
- -surface -shadow:
0 20px 44px rgba ( 0 , 0 , 0 , 0.24 ) ,
inset 0 1px 0 rgba ( 255 , 255 , 255 , 0.03 ) ;
- -card -shadow:
0 18px 36px rgba ( 0 , 0 , 0 , 0.2 ) ,
inset 0 1px 0 rgba ( 255 , 255 , 255 , 0.02 ) ;
- -good: #4dd39a;
- -warn: #ffb347;
- -bad: #ff6b6b;
- -info: var ( - -tertiary ) ;
- -neutral: var ( - -gray ) ;
- -titleFont: " Montserrat " , " Segoe UI Variable " , sans-serif ;
- -headerFont: " Montserrat " , " Segoe UI Variable " , sans-serif ;
- -bodyFont: " Ubuntu " , " Segoe UI " , sans-serif ;
- -codeFont: " IBM Plex Mono " , " Cascadia Mono " , monospace ;
2026-04-24 20:11:18 +01:00
}
* { box-sizing : border-box ; }
body {
margin : 0 ;
2026-04-24 21:00:57 +01:00
padding : 36px ;
2026-04-24 20:11:18 +01:00
background :
2026-04-24 21:00:57 +01:00
radial-gradient ( circle at 14 % 12 % , rgba ( 255 , 138 , 42 , 0.18 ) , transparent 24 % ) ,
radial-gradient ( circle at 84 % 10 % , rgba ( 89 , 183 , 255 , 0.18 ) , transparent 22 % ) ,
linear-gradient ( 180deg , rgba ( 6 , 10 , 16 , 1 ) 0 % , rgba ( 7 , 12 , 19 , 1 ) 38 % , rgba ( 8 , 13 , 21 , 1 ) 100 % ) ;
color : var ( - -dark ) ;
font : 15px / 1.6 var ( - -bodyFont ) ;
2026-04-24 20:11:18 +01:00
}
h1 , h2 , h3 , p { margin : 0 ; }
a { color : inherit ; }
. shell {
2026-04-24 21:00:57 +01:00
max-width : 1460px ;
2026-04-24 20:11:18 +01:00
margin : 0 auto ;
display : grid ;
2026-04-24 21:00:57 +01:00
gap : 22px ;
2026-04-24 20:11:18 +01:00
}
. hero , . panel , . card {
2026-04-24 21:04:56 +01:00
min-width : 0 ;
2026-04-24 21:00:57 +01:00
border : 1px solid var ( - -surface -line ) ;
2026-04-24 20:11:18 +01:00
border-radius : 20px ;
2026-04-24 21:00:57 +01:00
box-shadow : var ( - -card -shadow ) ;
2026-04-24 21:04:56 +01:00
overflow : hidden ;
2026-04-24 20:11:18 +01:00
}
. hero {
2026-04-24 21:00:57 +01:00
padding : 1 . 35rem 1 . 45rem 1 . 5rem ;
2026-04-24 20:11:18 +01:00
display : grid ;
2026-04-24 21:00:57 +01:00
gap : 0 . 85rem ;
background :
radial-gradient ( circle at 82 % 28 % , rgba ( 255 , 138 , 42 , 0.14 ) , transparent 20 % ) ,
radial-gradient ( circle at 14 % 10 % , rgba ( 89 , 183 , 255 , 0.1 ) , transparent 18 % ) ,
var ( - -panel -strong ) ;
overflow : hidden ;
2026-04-24 20:11:18 +01:00
}
. eyebrow {
2026-04-24 21:00:57 +01:00
color : var ( - -secondary ) ;
font-family : var ( - -titleFont ) ;
font-size : 0 . 78rem ;
font-weight : 300 ;
2026-04-24 20:11:18 +01:00
letter-spacing : 0 . 16em ;
text-transform : uppercase ;
2026-04-24 21:00:57 +01:00
}
. hero h1 ,
. card-head h2 ,
. panel h2 {
color : var ( - -dark ) ;
font-family : var ( - -headerFont ) ;
font-weight : 300 ;
letter-spacing : 0 . 04em ;
}
. hero h1 {
font-size : clamp ( 2rem , 3 . 4vw , 3 . 15rem ) ;
line-height : 1.02 ;
text-transform : uppercase ;
2026-04-24 20:11:18 +01:00
}
. hero p {
2026-04-24 21:00:57 +01:00
color : rgba ( 183 , 199 , 217 , 0.88 ) ;
2026-04-24 20:11:18 +01:00
max-width : 80ch ;
2026-04-24 21:00:57 +01:00
font-size : 0 . 98rem ;
2026-04-24 20:11:18 +01:00
}
. cards {
display : grid ;
grid-template -columns: repeat ( auto-fit , minmax ( 240px , 1fr ) ) ;
2026-04-24 21:00:57 +01:00
gap : 1rem ;
2026-04-24 20:11:18 +01:00
}
. card {
2026-04-24 21:00:57 +01:00
padding : 1rem 1 . 05rem ;
2026-04-24 20:11:18 +01:00
display : grid ;
2026-04-24 21:00:57 +01:00
gap : 0 . 75rem ;
background :
radial-gradient ( circle at 84 % 18 % , rgba ( 255 , 138 , 42 , 0.09 ) , transparent 24 % ) ,
var ( - -panel ) ;
2026-04-24 20:11:18 +01:00
}
. card-head {
display : flex ;
justify-content : space-between ;
gap : 12px ;
align-items : start ;
}
. card-head h2 {
2026-04-24 21:00:57 +01:00
font-size : 0 . 98rem ;
text-transform : uppercase ;
2026-04-24 20:11:18 +01:00
}
. card-subtitle , . card-meta , . muted {
2026-04-24 21:00:57 +01:00
color : rgba ( 183 , 199 , 217 , 0.78 ) ;
2026-04-24 20:11:18 +01:00
}
. grid {
display : grid ;
grid-template -columns: repeat ( auto-fit , minmax ( 320px , 1fr ) ) ;
2026-04-24 21:00:57 +01:00
gap : 1rem ;
2026-04-24 20:11:18 +01:00
}
. panel {
2026-04-24 21:00:57 +01:00
padding : 1 . 1rem 1 . 15rem ;
2026-04-24 20:11:18 +01:00
display : grid ;
2026-04-24 21:00:57 +01:00
gap : 0 . 9rem ;
background : var ( - -panel -soft ) ;
border-color : rgba ( 89 , 183 , 255 , 0.1 ) ;
2026-04-24 20:11:18 +01:00
}
. panel h2 {
2026-04-24 21:00:57 +01:00
font-size : 1 . 02rem ;
text-transform : uppercase ;
2026-04-24 20:11:18 +01:00
}
. facts , . checks {
width : 100 % ;
border-collapse : collapse ;
2026-04-24 21:04:56 +01:00
table-layout : fixed ;
2026-04-24 20:11:18 +01:00
}
. facts th ,
. facts td ,
. checks th ,
. checks td {
padding : 10px 12px ;
2026-04-24 21:00:57 +01:00
border-top : 1px solid rgba ( 89 , 183 , 255 , 0.1 ) ;
2026-04-24 20:11:18 +01:00
text-align : left ;
vertical-align : top ;
}
. facts th ,
. checks th {
2026-04-24 21:00:57 +01:00
color : var ( - -darkgray ) ;
font-family : var ( - -headerFont ) ;
font-size : 0 . 74rem ;
font-weight : 400 ;
letter-spacing : 0 . 08em ;
text-transform : uppercase ;
2026-04-24 21:04:56 +01:00
width : 140px ;
2026-04-24 20:11:18 +01:00
}
. checks thead th {
border-top : none ;
}
2026-04-24 21:04:56 +01:00
. facts td ,
. checks td {
min-width : 0 ;
overflow-wrap : anywhere ;
word-break : break-word ;
}
2026-04-24 20:11:18 +01:00
. mono {
2026-04-24 21:00:57 +01:00
font-family : var ( - -codeFont ) ;
2026-04-24 20:11:18 +01:00
font-size : 13px ;
word-break : break-word ;
}
. badge {
display : inline-flex ;
align-items : center ;
justify-content : center ;
padding : 4px 10px ;
border-radius : 999px ;
font-size : 12px ;
font-weight : 700 ;
letter-spacing : 0 . 04em ;
text-transform : uppercase ;
2026-04-24 21:00:57 +01:00
color : #04101b;
2026-04-24 20:11:18 +01:00
white-space : nowrap ;
2026-04-24 21:00:57 +01:00
box-shadow : inset 0 1px 0 rgba ( 255 , 255 , 255 , 0.24 ) ;
2026-04-24 20:11:18 +01:00
}
. badge-good { background : var ( - -good ) ; }
. badge-warn { background : var ( - -warn ) ; }
. badge-bad { background : var ( - -bad ) ; }
. badge-info { background : var ( - -info ) ; }
2026-04-24 21:00:57 +01:00
. badge-neutral { background : var ( - -darkgray ) ; }
2026-04-24 20:11:18 +01:00
. file-link {
2026-04-24 21:04:56 +01:00
display : inline-block ;
max-width : 100 % ;
2026-04-24 20:11:18 +01:00
text-decoration : none ;
2026-04-24 21:00:57 +01:00
color : var ( - -tertiary ) ;
border-bottom : 1px dashed rgba ( 89 , 183 , 255 , 0.45 ) ;
2026-04-24 21:04:56 +01:00
overflow-wrap : anywhere ;
word-break : break-word ;
vertical-align : top ;
2026-04-24 21:00:57 +01:00
transition : color 120ms ease , border-color 120ms ease ;
}
. file-link : hover {
color : var ( - -secondary ) ;
border-color : rgba ( 255 , 138 , 42 , 0.45 ) ;
}
. checks tbody tr : hover td ,
. facts tbody tr : hover td ,
. facts tbody tr : hover th {
background : rgba ( 89 , 183 , 255 , 0.05 ) ;
}
. checks td : last-child ,
. facts td : last-child {
color : rgba ( 238 , 245 , 255 , 0.9 ) ;
2026-04-24 20:11:18 +01:00
}
2026-04-25 01:58:53 +01:00
. stack-list {
display : grid ;
gap : 0 . 45rem ;
}
. stack-item {
padding-top : 0 . 2rem ;
border-top : 1px dashed rgba ( 89 , 183 , 255 , 0.1 ) ;
}
. stack-item : first-child {
padding-top : 0 ;
border-top : none ;
}
. muted-inline {
color : rgba ( 183 , 199 , 217 , 0.72 ) ;
font-size : 0 . 92em ;
}
2026-04-24 20:11:18 +01:00
@media ( max-width : 720px ) {
body { padding : 16px ; }
. hero , . panel , . card { border-radius : 16px ; }
. facts th , . checks th { width : 120px ; }
}
< / style >
< / head >
< body >
< main class = 'shell' >
< section class = 'hero' >
< div class = 'eyebrow' > VoidBot Operations Dashboard < / div >
< h1 > Status files , but with fewer lies . < / h1 >
< p > This page is regenerated from the live JSON status files under < span class = 'mono' > $ ( Escape-VoidBotDashboardHtml $statusDir ) < / span > and refreshes itself every 10 seconds . If a hidden task hangs , it should stay frozen here with the last step it managed to confess . < / p >
< div class = 'muted' > Generated $ ( Escape-VoidBotDashboardHtml ( Get-Date -Format " yyyy-MM-dd HH:mm:ss zzz " ) ) < / div >
< / section >
< section class = 'cards' >
$cardsHtml
< / section >
< section class = 'grid' >
$ ( New-VoidBotDashboardFactSection -Title " Runtime Stack " -Rows $runtimeRows )
2026-04-25 01:58:53 +01:00
$ ( New-VoidBotDashboardFactSection -Title " State Storage " -Rows $stateStorageRows )
$ ( New-VoidBotDashboardFactSection -Title " Vector Store " -Rows $vectorRows )
$ ( New-VoidBotDashboardFactSection -Title " Ingest & Sync " -Rows $ingestRows )
$ ( New-VoidBotDashboardFactSection -Title " MCP Usage " -Rows $mcpRows )
2026-04-25 02:04:04 +01:00
$ ( New-VoidBotDashboardFactSection -Title " Jobs " -Rows $jobRows )
2026-04-24 20:11:18 +01:00
$ ( New-VoidBotDashboardFactSection -Title " Watchdog Run " -Rows $watchdogRows )
$ ( New-VoidBotDashboardFactSection -Title " Offsite Backup " -Rows $offsiteRows )
$ ( New-VoidBotDashboardFactSection -Title " Useful Files " -Rows $fileRows )
< / section >
$checksSection
< / main >
< / body >
< / html >
" @
2026-05-13 19:46:29 +01:00
Write-VoidBotDashboardUtf8File -Path $dashboardPath -Content $html
2026-04-24 20:11:18 +01:00
return [ PSCustomObject ] @ {
DashboardPath = $dashboardPath
SnapshotPath = $snapshotPath
}
}