From 25833fb43a0ed3e9c7522dfb6a78f29dfd947720 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. --- scripts/newos4-reply.py | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100755 scripts/newos4-reply.py (limited to 'scripts/newos4-reply.py') diff --git a/scripts/newos4-reply.py b/scripts/newos4-reply.py new file mode 100755 index 0000000..8144285 --- /dev/null +++ b/scripts/newos4-reply.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Append one reply to the newOS4 maintenance thread. + +Hermes writes here instead of POSTing to agent-replies.php, so an expired admin cookie can +never make a reply look delivered when it was not. Same JSON schema and file the PHP endpoint +serves to the in-app `feedback-inbox` command. + +Usage: newos4-reply.py "Triage 2026-10-23: ..." [--author hermes] +""" +import argparse, fcntl, json, os, time + +ROOT = '/var/www/webx11.duckdns.org/spaces/newos4' +DIR = os.path.join(ROOT, 'data', 'maintenance') +FILE = os.path.join(DIR, 'replies.json') +MAX = 200 + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('body') + ap.add_argument('--author', default='hermes', choices=['hermes', 'newos4']) + a = ap.parse_args() + + body = a.body.strip() + if not body: + raise SystemExit('refusing to write an empty reply') + body = body[:2000] + + os.makedirs(DIR, exist_ok=True) + lock = open(FILE + '.lock', 'a+') + fcntl.flock(lock, fcntl.LOCK_EX) + try: + try: + with open(FILE, encoding='utf-8') as f: + data = json.load(f) + except Exception: + data = [] + if not isinstance(data, list): + data = [] + entry = { + 'id': 'REP-' + time.strftime('%Y%m%d-%H%M%S') + '-' + os.urandom(3).hex(), + 'created_at': time.strftime('%Y-%m-%dT%H:%M:%S+00:00', time.gmtime()), + 'author': a.author, + 'body': body, + } + data.append(entry) + tmp = FILE + '.tmp' + with open(tmp, 'w', encoding='utf-8') as f: + json.dump(data[-MAX:], f, indent=2) + f.flush() + os.fsync(f.fileno()) + os.replace(tmp, FILE) + os.chmod(FILE, 0o664) + try: + os.chmod(FILE, 0o664) + except Exception: + pass + print(entry['id']) + finally: + fcntl.flock(lock, fcntl.LOCK_UN) + lock.close() + + +if __name__ == '__main__': + main() -- cgit v1.2.3