aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-debug-supervisor.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/core/module-debug-supervisor.js')
-rw-r--r--modules/core/module-debug-supervisor.js67
1 files changed, 65 insertions, 2 deletions
diff --git a/modules/core/module-debug-supervisor.js b/modules/core/module-debug-supervisor.js
index 959d162..485f4e2 100644
--- a/modules/core/module-debug-supervisor.js
+++ b/modules/core/module-debug-supervisor.js
@@ -41,6 +41,8 @@ class DebugSupervisorModule {
'debug-health': 'debug-health — Check supervisor module health status',
'debug-sync': 'debug-sync [url] — Sync errors to backend API (or set sync URL)',
'debug-sync-url': 'debug-sync-url [url] — View or set the backend sync endpoint',
+ 'feedback': 'feedback <message> — Send feedback to the WebX11 maintenance agent',
+ 'feedback-inbox': 'feedback-inbox — Read replies from the maintenance agent (admin session)',
};
}
@@ -53,6 +55,8 @@ class DebugSupervisorModule {
this.commands['debug-health'] = () => this._cmdHealth();
this.commands['debug-sync'] = (args) => this._cmdSync(args);
this.commands['debug-sync-url'] = (args) => this._cmdSyncUrl(args);
+ this.commands['feedback'] = (args) => this._cmdFeedback(args);
+ this.commands['feedback-inbox'] = () => this._cmdFeedbackInbox();
}
seed() {
@@ -61,6 +65,9 @@ class DebugSupervisorModule {
init() {
this._loadDB();
+ try {
+ this._syncedCounts = JSON.parse(localStorage.getItem('webx11_debug_synced') || '{}');
+ } catch (_e) { this._syncedCounts = {}; }
this._installInterceptors();
// Start periodic sync (every 5 minutes)
this._syncInterval = setInterval(() => this._syncToBackend(), 300000);
@@ -329,6 +336,12 @@ class DebugSupervisorModule {
_maybeAutoFileInner(e) {
try { if (localStorage.getItem('webx11_autoreport') === 'off') return; } catch (_e) {}
+ // Aborts/timeouts are the client's own cancellation, not application bugs, and the debug
+ // pipeline's own requests must never be filed as reports (self-referential loop).
+ // Measured 2026-10-21..23: 2 of 5 auto-filed reports were "user aborted a request".
+ if (/(user aborted|aborted without reason|signal timed out|AbortError|TimeoutError)/i.test(String(e.message || ''))) return;
+ if (/\/api\/debug\//.test(String(e.source || '')) || /\/api\/debug\//.test(String(e.message || ''))) return;
+
const today = new Date().toISOString().slice(0, 10);
let state = { day: today, count: 0, hashes: [] };
try {
@@ -764,7 +777,12 @@ class DebugSupervisorModule {
async _syncToBackend() {
if (!this._healthy) return;
const db = this._errorDB.errors;
- const keys = Object.keys(db);
+ const allKeys = Object.keys(db);
+ if (allKeys.length === 0) return;
+ // Only send hashes whose cumulative count changed since the last successful sync.
+ // The server counts deltas per (session, hash); re-sending unchanged rows is what
+ // multiplied one error into 46,133 occurrences before 2026-10-23.
+ const keys = allKeys.filter(k => (this._syncedCounts[k] || 0) !== db[k].count);
if (keys.length === 0) return;
const payload = {
@@ -799,12 +817,14 @@ class DebugSupervisorModule {
const result = await resp.json();
if (result.success) {
const sent = keys.length;
- const accepted = result.ingested || 0;
+ const accepted = result.accepted ?? result.ingested ?? 0;
if (accepted < sent) {
console.warn('[DebugSupervisor] Sync truncated: sent ' + sent + ' errors, server accepted ' + accepted + ' (max ' + (result.total_errors || '?') + ' total)');
} else {
console.log('[DebugSupervisor] Synced ' + accepted + ' errors to backend');
}
+ keys.forEach(k => { this._syncedCounts[k] = db[k].count; });
+ try { localStorage.setItem('webx11_debug_synced', JSON.stringify(this._syncedCounts)); } catch (_e) {}
this._lastSyncOk = Date.now();
return true;
}
@@ -817,6 +837,49 @@ class DebugSupervisorModule {
}
}
+ _feedbackThread() {
+ let t = null;
+ try { t = localStorage.getItem('webx11_feedback_thread'); } catch (_e) {}
+ if (!t) {
+ t = 'thr-' + Math.random().toString(36).slice(2, 10);
+ try { localStorage.setItem('webx11_feedback_thread', t); } catch (_e) {}
+ }
+ return t;
+ }
+
+ _cmdFeedback(args) {
+ const msg = (Array.isArray(args) ? args.join(' ') : String(args || '')).trim();
+ if (!msg) { this.terminal.print('usage: feedback <message>', 'error'); return; }
+ const payload = {
+ title: msg.slice(0, 120),
+ body: msg.slice(0, 4000),
+ thread: this._feedbackThread(),
+ kind: 'maintenance',
+ diagnostics: { version: window.NEWOS4_VERSION || '?', url: location.href, auto: false },
+ };
+ this.terminal.print('📨 Sending feedback…', '');
+ fetch('/spaces/newos4/api/debug/report.php', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload),
+ }).then(r => r.json()).then(j => {
+ if (j && j.success) this.terminal.print('✅ Sent: ' + (j.id || 'ok') + (j.duplicate ? ' (merged)' : ''), 'success');
+ else this.terminal.print('❌ ' + ((j && j.error) || 'send failed'), 'error');
+ }).catch(e => this.terminal.print('❌ ' + e.message, 'error'));
+ }
+
+ _cmdFeedbackInbox() {
+ this.terminal.print('📬 Fetching replies…', '');
+ fetch('/spaces/newos4/api/debug/agent-replies.php', { credentials: 'same-origin' })
+ .then(r => r.ok ? r.json() : Promise.reject(new Error('HTTP ' + r.status + ' (admin session required)')))
+ .then(j => {
+ const list = (j && j.replies) || [];
+ if (!list.length) { this.terminal.print('(no replies yet)', ''); return; }
+ list.slice(-10).forEach(r => this.terminal.print('🤖 ' + (r.created_at || '') + ' ' + r.body, ''));
+ })
+ .catch(e => this.terminal.print('❌ ' + e.message, 'error'));
+ }
+
_cmdSync(args) {
const url = (args && args[0]) ? args[0] : null;
if (url) {