mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-30 09:22:24 +00:00
86 lines
5.3 KiB
JavaScript
86 lines
5.3 KiB
JavaScript
// One JSON line per wake attempt, in the fleet state dir (not the checkout).
|
|
// The line records what the pane looked like and what the nudge did. It does
|
|
// not record mail bodies. The file is capped so it cannot grow without bound.
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { writeAtomic } from '../lib.mjs';
|
|
|
|
export const WAKE_LOG_MAX = 400;
|
|
|
|
export function wakeLogPath(c) {
|
|
return path.join(c.stateDir, 'wake-log.jsonl');
|
|
}
|
|
|
|
export function appendWakeLog(c, entry) {
|
|
const file = wakeLogPath(c);
|
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
let lines = [];
|
|
try { lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean); } catch { /* new log */ }
|
|
lines.push(JSON.stringify(entry));
|
|
if (lines.length > WAKE_LOG_MAX) lines = lines.slice(lines.length - WAKE_LOG_MAX);
|
|
writeAtomic(file, `${lines.join('\n')}\n`);
|
|
}
|
|
|
|
export function priorWakes(c, area, file) {
|
|
return readRaw(c).filter((e) => e && e.kind !== 'stale' && e.area === area && e.file === file);
|
|
}
|
|
|
|
function readRaw(c) {
|
|
try {
|
|
return fs.readFileSync(wakeLogPath(c), 'utf8').split('\n').filter(Boolean).map((line) => {
|
|
try { return JSON.parse(line); } catch { return null; }
|
|
}).filter(Boolean);
|
|
} catch { return []; }
|
|
}
|
|
|
|
/** Plain words for the failure a later reader should tell the user. */
|
|
export function wakeFlag(entry) {
|
|
if (!entry) return 'unknown';
|
|
if (entry.kind === 'stale') {
|
|
const last = Array.isArray(entry.prior) ? entry.prior[entry.prior.length - 1] : null;
|
|
const age = entry.ageSec ?? '?';
|
|
if (!last) return `still unread after ${age}s; no earlier wake`;
|
|
if (last.outcome === 'deferred-enter' || (last.action === 'later-enter' && last.outcome !== 'submitted')) return `woke at ${last.t} (${last.trigger}), wake line still in the composer, still unread after ${age}s`;
|
|
if (last.outcome === 'swallowed') return `woke at ${last.t} (${last.trigger}), wake line still in the composer, still unread after ${age}s`;
|
|
if (last.outcome === 'timeout') return `woke at ${last.t} (${last.trigger}), pane stayed busy until the waiter stopped, still unread after ${age}s`;
|
|
if (last.outcome === 'deferred' || last.action === 'deferred') return `woke at ${last.t} (${last.trigger}), handed to a waiter because the pane was busy, still unread after ${age}s`;
|
|
if (last.action === 'skipped-busy') return `woke at ${last.t} (${last.trigger}), skipped because an interrupt hint was showing, still unread after ${age}s`;
|
|
if (last.action === 'no-window' || last.outcome === 'no-window') return `woke at ${last.t} (${last.trigger}), pane not found, still unread after ${age}s`;
|
|
if (last.outcome === 'submitted') return `woke at ${last.t} (${last.trigger}), line left the composer, still unread after ${age}s`;
|
|
return `woke at ${last.t} (${last.trigger}), outcome ${last.outcome}, still unread after ${age}s`;
|
|
}
|
|
if (entry.action === 'no-window' || entry.outcome === 'no-window') {
|
|
const names = Array.isArray(entry.windows) && entry.windows.length ? entry.windows.join(', ') : 'none';
|
|
const looked = entry.lookup || entry.window || 'unknown window';
|
|
const sock = entry.socket ? `socket ${entry.socket}${entry.socketExists === false ? ' missing' : ''}` : 'no recorded socket';
|
|
return `pane not found (looked up ${looked}; ${sock}; session windows: ${names})`;
|
|
}
|
|
if (entry.action === 'later-enter' && entry.outcome === 'submitted') {
|
|
return entry.enterAttempts ? 'a later Enter left the composer' : 'the wake line left the composer before the later Enter';
|
|
}
|
|
if (entry.action === 'later-enter' && entry.outcome === 'swallowed') return `a later Enter still left the wake line in the composer (${entry.enterAttempts} more)`;
|
|
if (entry.outcome === 'deferred-enter') return `wake line still in the composer after ${entry.enterAttempts} Enter; a later Enter is waiting`;
|
|
if (entry.outcome === 'waiter-running') return 'a waiter is already polling this pane';
|
|
if (entry.outcome === 'already-read') return 'mail was read before the pane went idle, so nothing was typed';
|
|
if (entry.outcome === 'timeout') return 'pane stayed busy until the waiter stopped';
|
|
if (entry.outcome === 'deferred') return 'handed to a waiter because an interrupt hint was showing';
|
|
if (entry.action === 'skipped-busy') return 'skipped: interrupt hint was showing, so the pane looked mid-turn';
|
|
if (entry.action === 'send-failed') return 'send-keys failed before Enter';
|
|
if (entry.trigger === 'deferred' && entry.outcome === 'swallowed') return `waiter typed it; wake line still on the cursor row after ${entry.enterAttempts} Enter`;
|
|
if (entry.outcome === 'swallowed') return `wake line still on the cursor row after ${entry.enterAttempts} Enter`;
|
|
if (entry.trigger === 'deferred' && entry.outcome === 'submitted') return 'waiter typed the line and it left the composer';
|
|
if (entry.outcome === 'submitted') {
|
|
if (entry.before && entry.before.idleSec != null && entry.before.idleSec < 3 && !entry.before.interrupt) {
|
|
return 'typed and the line left the composer while the pane was recently active';
|
|
}
|
|
return 'typed and the line left the composer';
|
|
}
|
|
return entry.outcome || entry.action || 'unknown';
|
|
}
|
|
|
|
export function readWakeLog(c, { limit = 40 } = {}) {
|
|
const all = readRaw(c);
|
|
const entries = all.slice(-Math.max(1, Number(limit) || 40)).map((entry) => ({ ...entry, flag: wakeFlag(entry) }));
|
|
return { path: wakeLogPath(c), entries };
|
|
}
|