// Every connector action, timestamped: kept in memory for the panel and // appended to /actions.log as JSON lines, rotated at 1 MB (one old // file kept as actions.log.1). const fs = require("node:fs"); const path = require("node:path"); const KEEP = 500; const ROTATE_BYTES = 1024 * 1024; class ActionLog { constructor(dir, { onEntry } = {}) { this.file = path.join(dir, "actions.log"); this.onEntry = typeof onEntry === "function" ? onEntry : null; this.entries = []; this.seq = 0; try { const lines = fs.readFileSync(this.file, "utf8").trim().split("\n").slice(-KEEP); for (const l of lines) { try { this.entries.push(JSON.parse(l)); } catch {} } this.seq = this.entries.reduce((m, e) => Math.max(m, Number(e.seq) || 0), 0); } catch {} } add(fields) { const entry = { seq: ++this.seq, ts: new Date().toISOString(), ...fields }; this.entries.push(entry); if (this.entries.length > KEEP) this.entries.splice(0, this.entries.length - KEEP); try { try { if (fs.statSync(this.file).size > ROTATE_BYTES) fs.renameSync(this.file, this.file + ".1"); } catch {} fs.appendFileSync(this.file, JSON.stringify(entry) + "\n"); } catch {} if (this.onEntry) { try { this.onEntry(entry); } catch {} } return entry; } recent(n = 200) { return this.entries.slice(-Math.max(1, Math.min(KEEP, n))); } clear() { this.entries = []; try { fs.rmSync(this.file, { force: true }); fs.rmSync(this.file + ".1", { force: true }); } catch {} } } // A short, safe description of a tool's arguments for the log. Typed and // pasted content is logged by length and its first characters only. function summarizeArgs(args) { const a = args && typeof args === "object" ? args : {}; const out = []; const clip = (s, n) => { s = String(s); return s.length > n ? s.slice(0, n) + "…" : s; }; for (const [k, v] of Object.entries(a)) { if (v == null || k === "tab_id") continue; if (k === "text" || k === "html" || k === "expression") { const s = String(v); out.push(`${k}: ${s.length} chars "${clip(s.replace(/\s+/g, " "), 60)}"`); } else if (typeof v === "object") out.push(`${k}: ${clip(JSON.stringify(v), 80)}`); else out.push(`${k}: ${clip(v, 120)}`); } return out.join(", "); } module.exports = { ActionLog, summarizeArgs };