Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148// transcript.mjs — one history per session, whatever engine wrote it.//// Claude, Codex and the AC engine each keep their own logs in their own// shapes, in their own places, some of them not at all. A harness that can// switch engines mid-session (`/backend`) needs a history that survives the// switch and reads the same afterwards, so this writes one: a directory per// session with `events.jsonl` (what happened, in order) and `meta.json` (what// the session is). Both 0600 in a 0700 directory — it is the user's work.//// Events are batched. A streaming engine can emit hundreds of deltas a second// and one appendFileSync per token is a syscall per token; instead every event// in a tick lands in one write on the next setImmediate. `flush()` forces it,// `close()` flushes, and nothing is ever rewritten — only appended — so a// crash mid-turn leaves a readable prefix rather than a torn file.import { appendFileSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync,} from "node:fs";import { transcriptsDir } from "./paths.mjs";import { join } from "node:path";
export const SUMMARY_MAX = 500;export const KINDS = [ "user", "inbox", "assistant", "tool_call", "tool_result", "approval", "notice", "turn", "engine", // The media file the session took up: a path, a mime and a kind. "media",];
export const defaultRoot = () => transcriptsDir();
const now = () => new Date().toISOString();
// Tool inputs and results are the bulk of a transcript and rarely what anyone// re-reads; keep the first 500 characters so the shape is visible.function clip(value, max = SUMMARY_MAX) { if (value === undefined || value === null) return ""; const text = typeof value === "string" ? value : JSON.stringify(value); return text.length > max ? `${text.slice(0, max - 1)}…` : text;}
function readJson(path) { try { return JSON.parse(readFileSync(path, "utf8")); } catch { return null; }}
export class Transcript { constructor({ sessionId, root = defaultRoot(), private: isPrivate = false }) { this.sessionId = sessionId; this.private = Boolean(isPrivate); this.dir = join(root, sessionId); this.eventsPath = join(this.dir, "events.jsonl"); this.metaPath = join(this.dir, "meta.json"); this.pending = []; this.scheduled = null; mkdirSync(this.dir, { recursive: true, mode: 0o700 }); this.record = readJson(this.metaPath) || { session_id: sessionId, started: now(), pro: false, }; this.meta({}); }
meta(patch = {}) { Object.assign(this.record, patch, { session_id: this.sessionId, updated: now(), private: this.private, }); // A private session's subject never reaches disk as prose, so nothing that // lists transcripts can read the work back off the client's directory. if (this.private) this.record.subject = "private"; const temporary = `${this.metaPath}.${process.pid}.tmp`; try { writeFileSync(temporary, `${JSON.stringify(this.record, null, 2)}\n`, { mode: 0o600 }); renameSync(temporary, this.metaPath); } catch { rmSync(temporary, { force: true }); } return { ...this.record }; }
event(kind, data = {}) { const entry = { ts: Date.now(), kind, ...data }; if (kind === "tool_call") entry.input = clip(data.input); if (kind === "tool_result") entry.summary = clip(data.summary); this.pending.push(JSON.stringify(entry)); if (!this.scheduled) this.scheduled = setImmediate(() => this.flush()); return entry; }
flush() { if (this.scheduled) clearImmediate(this.scheduled); this.scheduled = null; if (!this.pending.length) return 0; const lines = this.pending; this.pending = []; appendFileSync(this.eventsPath, `${lines.join("\n")}\n`, { mode: 0o600 }); return lines.length; }
close() { this.flush(); }
static list(root = defaultRoot()) { let names = []; try { names = readdirSync(root); } catch { return []; } return names .map((name) => readJson(join(root, name, "meta.json"))) .filter((meta) => meta && meta.session_id) .sort((a, b) => String(b.updated || "").localeCompare(String(a.updated || ""))); }
static read(sessionId, root = defaultRoot()) { let text; try { text = readFileSync(join(root, sessionId, "events.jsonl"), "utf8"); } catch { return []; } const events = []; for (const line of text.split("\n")) { if (!line.trim()) continue; try { events.push(JSON.parse(line)); } catch { // A torn last line after a crash is expected; everything before it is good. } } return events; }}