diff options
Diffstat (limited to 'api/debug')
| -rw-r--r-- | api/debug/agent-replies.php | 57 | ||||
| -rw-r--r-- | api/debug/report.php | 9 |
2 files changed, 66 insertions, 0 deletions
diff --git a/api/debug/agent-replies.php b/api/debug/agent-replies.php new file mode 100644 index 0000000..070c0e6 --- /dev/null +++ b/api/debug/agent-replies.php @@ -0,0 +1,57 @@ +<?php +// api/debug/agent-replies.php — outbound half of the maintenance thread. +// Hermes (or the operator) writes replies here; the in-page agent reads them with +// `feedback-inbox`. nginx gates this path behind the WebX11 admin session, so it is +// owner-only: never expose it in the public debug/course location block. +require_once __DIR__ . '/config.php'; + +define('REPLIES_DIR', __DIR__ . '/../../data/maintenance'); +define('REPLIES_FILE', REPLIES_DIR . '/replies.json'); + +if (!is_dir(REPLIES_DIR)) { + @mkdir(REPLIES_DIR, 0775, true); + @chgrp(REPLIES_DIR, 'www-data'); +} + +$method = $_SERVER['REQUEST_METHOD']; + +if ($method === 'GET') { + $since = $_GET['since'] ?? null; + $all = load_json(REPLIES_FILE); + if (!is_array($all)) $all = []; + if ($since) { + $all = array_values(array_filter($all, fn($r) => ($r['created_at'] ?? '') > $since)); + } + json_response(['success' => true, 'replies' => array_slice($all, -100)]); +} + +if ($method !== 'POST') { + error_response('Method not allowed', 405); +} + +$raw = file_get_contents('php://input'); +if (!$raw) error_response('Empty request body'); +if (strlen($raw) > 16384) error_response('Payload too large (max 16KB)', 413); + +$p = json_decode($raw, true); +if (!is_array($p)) error_response('Invalid JSON'); + +$body = trim((string)($p['body'] ?? '')); +if ($body === '') error_response('body is required'); +if (mb_strlen($body) > 2000) $body = mb_substr($body, 0, 2000); + +$list = load_json(REPLIES_FILE); +if (!is_array($list)) $list = []; + +$author = (($p['author'] ?? 'hermes') === 'newos4') ? 'newos4' : 'hermes'; +$entry = [ + 'id' => 'REP-' . gmdate('Ymd-His') . '-' . substr(bin2hex(random_bytes(3)), 0, 6), + 'created_at' => gmdate('c'), + 'author' => $author, + 'body' => $body, +]; + +$list[] = $entry; +save_json(REPLIES_FILE, array_slice($list, -200)); + +json_response(['success' => true, 'id' => $entry['id']], 201); diff --git a/api/debug/report.php b/api/debug/report.php index cefcae6..5d4fc95 100644 --- a/api/debug/report.php +++ b/api/debug/report.php @@ -29,6 +29,13 @@ $body = (string)($payload['body'] ?? ''); if (mb_strlen($body) > 4000) $body = mb_substr($body, 0, 4000); $diag = (isset($payload['diagnostics']) && is_array($payload['diagnostics'])) ? $payload['diagnostics'] : []; +// Maintenance thread: lets the in-app agent and the Hermes maintenance job hold a conversation. +// `thread` is a client-generated id (thr-xxxxxxxx); `kind` distinguishes agent feedback from +// operator bug reports so triage can weight them differently. +$thread = preg_replace('/[^a-zA-Z0-9_-]/', '', (string)($payload['thread'] ?? '')); +if (mb_strlen($thread) > 40) $thread = substr($thread, 0, 40); +$kind = (($payload['kind'] ?? '') === 'maintenance') ? 'maintenance' : 'user'; + // Storage guard: data/ is not writable by php-fpm, so never pretend we wrote a report. if (!is_dir(REPORTS_DIR)) @mkdir(REPORTS_DIR, 0775, true); if (!is_dir(REPORTS_DIR) || !is_writable(REPORTS_DIR)) { @@ -85,6 +92,8 @@ $record = [ 'title' => $title, 'body' => $body, 'diagnostics' => $diag, + 'thread' => $thread, + 'kind' => $kind, 'meta' => [ 'ip' => $ip, 'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '', |