(int)($e['count'] ?? 1), $errors)); $lastIngest = $errors ? max(array_column($errors, 'ingested_at')) : null; $totalSessions = count($sessions); $byType = []; $bySeverity = []; $byCategory = []; $byModule = []; $byDay = []; $topErrors = []; foreach ($errors as $e) { $t = $e['type'] ?? 'unknown'; $byType[$t] = ($byType[$t] ?? 0) + ($e['count'] ?? 1); $s = $e['severity'] ?? 'error'; $bySeverity[$s] = ($bySeverity[$s] ?? 0) + ($e['count'] ?? 1); $m = $e['module'] ?? 'unknown'; $byModule[$m] = ($byModule[$m] ?? 0) + ($e['count'] ?? 1); $c = $e['category'] ?? 'runtime'; $byCategory[$c] = ($byCategory[$c] ?? 0) + ($e['count'] ?? 1); $day = substr($e['ingested_at'] ?? $e['last_seen'], 0, 10); $byDay[$day] = ($byDay[$day] ?? 0) + ($e['count'] ?? 1); $hash = $e['hash'] ?? ''; if ($hash) { if (!isset($topErrors[$hash])) { $topErrors[$hash] = ['hash' => $hash, 'message' => $e['message'] ?? '', 'count' => 0, 'type' => $t, 'frame' => $e['frame'] ?? null]; } $topErrors[$hash]['count'] += $e['count'] ?? 1; } } usort($topErrors, fn($a, $b) => $b['count'] - $a['count']); $topErrors = array_slice($topErrors, 0, 10); ksort($byDay); $recentSessions = array_slice($sessions, -10, 10, true); // --- Maintenance layer ------------------------------------------------------- // Both files are written by the Hermes maintenance job. Read them defensively: a partially // written JSON must render as "unavailable", never as zero (a zero here would read as // "nothing is wrong", which is the exact failure this dashboard exists to catch). $DOCROOT = dirname(__DIR__, 4); $triageFile = $DOCROOT . '/admin/docs/newos4-triage-latest.json'; $repliesFile = __DIR__ . '/../../data/maintenance/replies.json'; $triage = null; $triageState = 'missing'; if (is_file($triageFile)) { $raw = @file_get_contents($triageFile); $parsed = $raw === false ? null : json_decode($raw, true); if (is_array($parsed)) { $triage = $parsed; $triageState = 'ok'; } else { $triageState = 'unreadable'; } } $replies = []; $repliesState = 'missing'; if (is_file($repliesFile)) { $raw = @file_get_contents($repliesFile); $parsed = $raw === false ? null : json_decode($raw, true); if (is_array($parsed)) { $replies = array_slice($parsed, -5); $repliesState = 'ok'; } else { $repliesState = 'unreadable'; } } // In-app feedback (kind=maintenance) — the other half of the thread. $feedback = []; foreach (glob(__DIR__ . '/../../data/reports/RPT-*.json') ?: [] as $rf) { $r = json_decode((string)@file_get_contents($rf), true); if (is_array($r) && ($r['kind'] ?? '') === 'maintenance') { $feedback[] = $r; } } usort($feedback, fn($a, $b) => strcmp($b['created_at'] ?? '', $a['created_at'] ?? '')); $feedback = array_slice($feedback, 0, 5); ?> newOS4 Debug Dashboard

🔍 newOS4 Debug Dashboard

Server-side error aggregation · · JSON Stats · Clusters · Raw Errors · Health
Last ingest: UTC · signatures sorted by occurrence count

Total Occurrences

unique signatures
last ingest:

Sessions

Error Types

Modules Affected

Maintenance triage

Triage digest — run python3 scripts/newos4-triage.py or check the newos4-maintenance cron.
SignaturesOccurrencesRealNoiseSelf-refNew reportsGenerated
Z
Top real signatures
×
$f['created_at'] ?? '', 'who' => 'app', 'body' => ($f['title'] ?? '') . (($f['body'] ?? '') !== ($f['title'] ?? '') ? "\n" . ($f['body'] ?? '') : '')]; } foreach ($replies as $r) { $thread[] = ['at' => $r['created_at'] ?? '', 'who' => ($r['author'] ?? 'hermes'), 'body' => $r['body'] ?? '']; } usort($thread, fn($a, $b) => strcmp($a['at'], $b['at'])); $thread = array_slice($thread, -6); ?>

Maintenance thread

No messages yet. In the app: feedback <message>; agent replies appear here.
·

By Severity

'error', 'error' => 'error', 'warning' => 'warn'] as $sev => $cls): ?>

By Category

0): ?>

By Module

$count): if ($i++ > 5) break; ?>

Top Errors

#MessageTypeCount
No errors recorded

Daily Trend (last 14 days)

$count): $pct = round($count / $maxDay * 100, 0); ?>
DayErrors

Recent Sessions

$s): ?>
SessionVersionErrorsLast Seen
Auto-refresh every 30s (paused while the tab is hidden)