src / core / journal.ts
import { createId } from "./id";
import type { InternalStorage } from "./internalStorage";
const journalWriteQueues = new Map<string, Promise<void>>();
export interface JournalEvent {
id: string;
timestamp: string;
category: "workspace" | "edit" | "command" | "agent" | "security" | "approval";
action: string;
summary: string;
runId?: string;
transactionId?: string;
details?: Record<string, unknown>;
}
export class Journal {
private readonly relativePath: string;
private readonly queueKey: string;
public constructor(private readonly storage: InternalStorage) {
this.relativePath = storage.relative("journal.ndjson");
this.queueKey = `${storage.boundary.realRoot}\0${this.relativePath}`;
}
public async initialize(): Promise<void> {
await this.storage.initialize();
await this.storage.resolveWrite(this.relativePath);
}
public async record(
event: Omit<JournalEvent, "id" | "timestamp">,
): Promise<JournalEvent> {
const complete: JournalEvent = {
id: createId("evt"),
timestamp: new Date().toISOString(),
...event,
};
const previous = journalWriteQueues.get(this.queueKey) ?? Promise.resolve();
const write = previous.then(async () => {
await this.storage.appendJsonLine(this.relativePath, complete);
});
const tracked = write.catch(() => undefined);
journalWriteQueues.set(this.queueKey, tracked);
try {
await write;
return complete;
} finally {
if (journalWriteQueues.get(this.queueKey) === tracked) {
journalWriteQueues.delete(this.queueKey);
}
}
}
}
src / core / journal.ts
import { createId } from "./id";
import type { InternalStorage } from "./internalStorage";
const journalWriteQueues = new Map<string, Promise<void>>();
export interface JournalEvent {
id: string;
timestamp: string;
category: "workspace" | "edit" | "command" | "agent" | "security" | "approval";
action: string;
summary: string;
runId?: string;
transactionId?: string;
details?: Record<string, unknown>;
}
export class Journal {
private readonly relativePath: string;
private readonly queueKey: string;
public constructor(private readonly storage: InternalStorage) {
this.relativePath = storage.relative("journal.ndjson");
this.queueKey = `${storage.boundary.realRoot}\0${this.relativePath}`;
}
public async initialize(): Promise<void> {
await this.storage.initialize();
await this.storage.resolveWrite(this.relativePath);
}
public async record(
event: Omit<JournalEvent, "id" | "timestamp">,
): Promise<JournalEvent> {
const complete: JournalEvent = {
id: createId("evt"),
timestamp: new Date().toISOString(),
...event,
};
const previous = journalWriteQueues.get(this.queueKey) ?? Promise.resolve();
const write = previous.then(async () => {
await this.storage.appendJsonLine(this.relativePath, complete);
});
const tracked = write.catch(() => undefined);
journalWriteQueues.set(this.queueKey, tracked);
try {
await write;
return complete;
} finally {
if (journalWriteQueues.get(this.queueKey) === tracked) {
journalWriteQueues.delete(this.queueKey);
}
}
}
}