/** * capture.js — client-side session-context capture for the Feedback feature. * * Exposes window.FeedbackCapture with pure capture primitives (no network, no * app-specific closure state). The console wires these into its submit/flush * flow and pushes on-screen events into the ring buffer at its render points. * * FeedbackCapture.sessionId — stable per-tab session id * FeedbackCapture.pushLog(entry) — record one on-screen event * FeedbackCapture.getLogJSON() — serialize the buffer to a JSON string * FeedbackCapture.clearLog() — reset the buffer * FeedbackCapture.buildSnapshot() — Promise single self-contained page * * Interaction log scope (FR-014): callers push ONLY content that is visible on * screen. This module never reads tokens, keys, or storage — it only holds what * it is handed. */ (function () { 'use strict'; var SESSION_KEY = 'feedback_session_id'; var MAX_ENTRIES = 500; // bound memory for long / continuous sessions function _uuid() { try { return crypto.randomUUID(); } catch (_) { return 'sess-' + Date.now() + '-' + Math.random().toString(16).slice(2); } } var sessionId; try { sessionId = sessionStorage.getItem(SESSION_KEY) || _uuid(); sessionStorage.setItem(SESSION_KEY, sessionId); } catch (_) { sessionId = _uuid(); } var _buffer = []; /** * Record one on-screen event. * @param {{role?:string, kind?:string, text?:string}} entry * role: 'user' | 'agent' | 'system' * kind: 'input' | 'output' | 'progress' | 'step' | 'error' */ function pushLog(entry) { entry = entry || {}; var text = entry.text == null ? '' : String(entry.text); if (text.length > 4000) text = text.slice(0, 4000) + '…'; _buffer.push({ ts: new Date().toISOString(), role: entry.role || 'system', kind: entry.kind || 'output', text: text }); if (_buffer.length > MAX_ENTRIES) _buffer.splice(0, _buffer.length - MAX_ENTRIES); } function getLogJSON() { return JSON.stringify(_buffer); } function getLogCount() { return _buffer.length; } function clearLog() { _buffer = []; } function _blobToDataURL(blob) { return new Promise(function (resolve, reject) { var fr = new FileReader(); fr.onload = function () { resolve(fr.result); }; fr.onerror = reject; fr.readAsDataURL(blob); }); } /** * Serialize the current page into ONE self-contained HTML file: clone the DOM, * drop scripts (so the snapshot never re-runs or re-fetches), and inline * external images as data: URIs. Inline