import type { AgentIdentity, JsonRpcId, JsonRpcMessage } from "./protocol";
export type SessionInfo = {
/** `legacy`: a 2025-era Mcp-Session-Id session; `listen`: one 2026-era subscriptions/listen stream. */
kind: "legacy" | "listen";
userId: string;
tokenId: string;
agent: AgentIdentity;
protocolVersion: string;
/** JSON text of the client capabilities declared at initialize. */
clientCapabilities: string;
createdAt: number;
subscriptionId: JsonRpcId | null;
resourceUris: string[];
repos: string[];
/** subscribe_events filters by repo id; resources/subscribe is tracked separately in resourceUris. */
toolEvents: Record<string, string[]>;
};
const MAX_LOG = 500;
const MAX_EVENT_BYTES = 16 * 1024;
/**
* The replay log behind `Last-Event-ID`: every server-to-client message for the session's standalone
* stream is persisted before it is written, so a reconnect (or a wake from hibernation) resumes exactly
* where the client left off.
*/
export class SessionLog {
constructor(private readonly sql: SqlStorage) {
// v2 records which repository each event is about, so losing access to one only drops that repository's backlog.
sql.exec("DROP TABLE IF EXISTS session_log");
sql.exec(
"CREATE TABLE IF NOT EXISTS session_log_v2 (seq INTEGER PRIMARY KEY AUTOINCREMENT, repo_id TEXT, data TEXT NOT NULL)",
);
}
append(message: JsonRpcMessage, repoId: string | null): { id: string; data: string } | null {
const data = JSON.stringify(message);
if (data.length > MAX_EVENT_BYTES) return null;
const seq = this.sql