From cfe5b7a842890c232358ae191652c5e5e81591e1 Mon Sep 17 00:00:00 2001 From: Agando Date: Fri, 23 Oct 2026 14:39:38 +0200 Subject: feat(newos4): maintenance agent channel — triage engine, two-way replies, cron MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/newos4-triage.py: deterministic collector (records vs occurrences, real vs noise vs self-referential, new reports since last run, checker WARN/FAIL). Untrusted strings are sanitised and prefixed DATA| before they can reach an agent prompt; state + output written under flock with mode 644. - scripts/newos4-reply.py: appends one reply to data/maintenance/replies.json under flock. Hermes writes here instead of POSTing with an admin cookie, so an expired session can never look like a delivered reply. - api/debug/agent-replies.php: GET/POST (admin-gated by nginx) half of the thread. - api/debug/report.php: accepts thread + kind=maintenance so in-app feedback is distinguishable from operator bug reports. - terminal: `feedback ` and `feedback-inbox` commands; help.html rows added (help coverage 357/357, modules-index regenerated). Live proof: `feedback "…"` filed RPT-20261023-123825-781fba86 with thread thr-glmfwae2 and kind=maintenance; agent-replies GET returned the posted reply, POST returned 201; triage first run reported 9 new reports, second run 0. Cron: newos4-maintenance (7e3b326daa4c) daily 07:15, newos4-skills-weekly (cb6bc2a16216) Mondays 06:30. --- api/debug/agent-replies.php | 57 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 api/debug/agent-replies.php (limited to 'api/debug/agent-replies.php') 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 @@ + ($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); -- cgit v1.2.3