import {
  getDatabase
} from "./chunk-FBGWG4N6.js";
import {
  buildContextMessagesFromEventHistory,
  buildMessagesFromStoredEvents,
  buildSnapshot,
  foldContextState,
  foldSessionState,
  spreadOptionalMessageFields,
  stripPromptContextMessages
} from "./chunk-6WZI6F6G.js";
import {
  getRuntimeConfig
} from "./chunk-CGZHLM3K.js";
import {
  logger
} from "./chunk-K44MW7JJ.js";

// src/server/events/store.ts
function createEventIterator(state, subscriber) {
  return {
    [Symbol.asyncIterator]() {
      return this;
    },
    async next() {
      if (state.closed) {
        return { value: void 0, done: true };
      }
      const queued = state.queue.shift();
      if (queued) {
        return { value: queued, done: false };
      }
      return new Promise((resolve) => {
        state.resolveNext = resolve;
      });
    },
    async return() {
      state.closed = true;
      subscriber.closed = true;
      return { value: void 0, done: true };
    }
  };
}
function createIteratorState() {
  const state = {
    closed: false,
    queue: [],
    resolveNext: null,
    closeIterator: () => {
      state.closed = true;
      if (state.resolveNext) {
        state.resolveNext({ value: void 0, done: true });
        state.resolveNext = null;
      }
    }
  };
  return state;
}
function createSubscriber(state, id) {
  return {
    ...id,
    callback: (event) => {
      if (state.closed) return;
      if (state.resolveNext) {
        state.resolveNext({ value: event, done: false });
        state.resolveNext = null;
      } else {
        state.queue.push(event);
      }
    },
    close: state.closeIterator,
    closed: false
  };
}
var EventStore = class {
  db;
  subscribers = /* @__PURE__ */ new Map();
  globalSubscribers = /* @__PURE__ */ new Map();
  globalSubscriberIdCounter = 0;
  constructor(db) {
    this.db = db;
    this.initSchema();
  }
  // --------------------------------------------------------------------------
  // Schema
  // --------------------------------------------------------------------------
  initSchema() {
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS events (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        session_id TEXT NOT NULL,
        seq INTEGER NOT NULL,
        timestamp INTEGER NOT NULL,
        event_type TEXT NOT NULL,
        payload TEXT NOT NULL,
        UNIQUE(session_id, seq)
      )
    `);
    this.db.exec(`
      CREATE INDEX IF NOT EXISTS idx_events_session_seq 
      ON events(session_id, seq)
    `);
    this.db.exec(`
      CREATE INDEX IF NOT EXISTS idx_events_session_type 
      ON events(session_id, event_type)
    `);
  }
  // --------------------------------------------------------------------------
  // Append
  // --------------------------------------------------------------------------
  /**
   * Append a single event to a session
   */
  append(sessionId, event) {
    if (!sessionId || typeof sessionId !== "string") {
      throw new Error("Invalid sessionId: must be a non-empty string");
    }
    if (!event || typeof event.type !== "string" || !event.type) {
      throw new Error("Invalid event: must have a type property");
    }
    if (!event.data || typeof event.data !== "object") {
      throw new Error("Invalid event: must have data object");
    }
    const timestamp = Date.now();
    const seq = this.getNextSeq(sessionId);
    const payload = JSON.stringify(event.data);
    this.db.prepare(
      `INSERT INTO events (session_id, seq, timestamp, event_type, payload)
         VALUES (?, ?, ?, ?, ?)`
    ).run(sessionId, seq, timestamp, event.type, payload);
    const stored = {
      seq,
      timestamp,
      sessionId,
      type: event.type,
      data: event.data
    };
    this.notifySubscribers(sessionId, stored);
    return stored;
  }
  /**
   * Append multiple events atomically
   */
  appendBatch(sessionId, events) {
    if (events.length === 0) return [];
    const timestamp = Date.now();
    let seq = this.getNextSeq(sessionId);
    const results = [];
    const insert = this.db.prepare(
      `INSERT INTO events (session_id, seq, timestamp, event_type, payload)
       VALUES (?, ?, ?, ?, ?)`
    );
    const transaction = this.db.transaction(() => {
      for (const event of events) {
        const payload = JSON.stringify(event.data);
        insert.run(sessionId, seq, timestamp, event.type, payload);
        const stored = {
          seq,
          timestamp,
          sessionId,
          type: event.type,
          data: event.data
        };
        results.push(stored);
        seq++;
      }
    });
    transaction();
    for (const stored of results) {
      this.notifySubscribers(sessionId, stored);
    }
    return results;
  }
  getNextSeq(sessionId) {
    const row = this.db.prepare(`SELECT MAX(seq) as max_seq FROM events WHERE session_id = ?`).get(sessionId);
    return (row?.max_seq ?? 0) + 1;
  }
  // --------------------------------------------------------------------------
  // Retrieval
  // --------------------------------------------------------------------------
  /**
   * Get all events for a session, optionally starting from a specific seq
   */
  getEvents(sessionId, fromSeq) {
    const query = fromSeq !== void 0 ? `SELECT * FROM events WHERE session_id = ? AND seq >= ? ORDER BY seq` : `SELECT * FROM events WHERE session_id = ? ORDER BY seq`;
    const rows = fromSeq !== void 0 ? this.db.prepare(query).all(sessionId, fromSeq) : this.db.prepare(query).all(sessionId);
    return rows.map((row) => this.rowToStoredEvent(row));
  }
  /**
   * Get the latest sequence number for a session
   */
  getLatestSeq(sessionId) {
    const row = this.db.prepare(`SELECT MAX(seq) as max_seq FROM events WHERE session_id = ?`).get(sessionId);
    return row?.max_seq ?? void 0;
  }
  /**
   * Get the latest snapshot event for a session
   */
  getLatestSnapshot(sessionId) {
    const row = this.db.prepare(
      `SELECT * FROM events 
         WHERE session_id = ? AND event_type = 'turn.snapshot' 
         ORDER BY seq DESC LIMIT 1`
    ).get(sessionId);
    if (!row) return void 0;
    return this.rowToStoredEvent(row);
  }
  /**
   * Get the latest snapshot and all events since it
   * This is the primary method for loading a session efficiently
   */
  getEventsSinceSnapshot(sessionId) {
    const snapshotEvent = this.getLatestSnapshot(sessionId);
    if (!snapshotEvent) {
      return {
        snapshot: void 0,
        events: this.getEvents(sessionId)
      };
    }
    const events = this.getEvents(sessionId, snapshotEvent.seq + 1);
    return {
      snapshot: snapshotEvent.data,
      events
    };
  }
  rowToStoredEvent(row) {
    return {
      seq: row.seq,
      timestamp: row.timestamp,
      sessionId: row.session_id,
      type: row.event_type,
      data: JSON.parse(row.payload)
    };
  }
  // --------------------------------------------------------------------------
  // Subscriptions
  // --------------------------------------------------------------------------
  /**
   * Subscribe to events for a session
   * Optionally replay events from a specific seq
   *
   * Returns an async iterator that yields events and an unsubscribe function
   */
  subscribe(sessionId, fromSeq) {
    const state = createIteratorState();
    const subscriber = createSubscriber(state, { sessionId });
    let sessionSubs = this.subscribers.get(sessionId);
    if (!sessionSubs) {
      sessionSubs = /* @__PURE__ */ new Set();
      this.subscribers.set(sessionId, sessionSubs);
    }
    sessionSubs.add(subscriber);
    if (fromSeq !== void 0) {
      const replayEvents = this.getEvents(sessionId, fromSeq);
      state.queue.push(...replayEvents);
    }
    const unsubscribe = () => {
      subscriber.closed = true;
      state.closeIterator();
      sessionSubs?.delete(subscriber);
    };
    return { iterator: createEventIterator(state, subscriber), unsubscribe };
  }
  notifySubscribers(sessionId, event) {
    const sessionSubs = this.subscribers.get(sessionId);
    if (sessionSubs) {
      const subscribersCopy = Array.from(sessionSubs);
      for (const subscriber of subscribersCopy) {
        if (!subscriber.closed) {
          subscriber.callback(event);
        }
      }
    }
    const globalSubscribersCopy = Array.from(this.globalSubscribers.values());
    for (const subscriber of globalSubscribersCopy) {
      if (!subscriber.closed) {
        subscriber.callback(event);
      }
    }
  }
  /**
   * Subscribe to ALL events across ALL sessions.
   * Unlike subscribe() which is session-specific, this receives every event.
   * Used by WebSocket clients to receive real-time updates for all sessions.
   *
   * Returns an async iterator that yields events and an unsubscribe function
   */
  subscribeAll() {
    const state = createIteratorState();
    const wsId = ++this.globalSubscriberIdCounter;
    const subscriber = createSubscriber(state, { wsId });
    this.globalSubscribers.set(wsId, subscriber);
    const unsubscribe = () => {
      subscriber.closed = true;
      state.closeIterator();
      this.globalSubscribers.delete(wsId);
    };
    return { iterator: createEventIterator(state, subscriber), unsubscribe };
  }
  // --------------------------------------------------------------------------
  // Cleanup
  // --------------------------------------------------------------------------
  /**
   * Delete all events for a session
   */
  deleteSession(sessionId) {
    this.db.prepare(`DELETE FROM events WHERE session_id = ?`).run(sessionId);
    const sessionSubs = this.subscribers.get(sessionId);
    if (sessionSubs) {
      for (const subscriber of sessionSubs) {
        subscriber.closed = true;
        subscriber.close();
      }
      this.subscribers.delete(sessionId);
    }
  }
  /**
   * Delete all events up to (and including) a given sequence number.
   * This is used to clean up events that are now contained in a snapshot.
   *
   * @param sessionId - The session ID
   * @param upToSeq - The sequence number to delete up to (inclusive)
   * @returns The number of events deleted
   */
  deleteEventsUpToSeq(sessionId, upToSeq) {
    const result = this.db.prepare(`DELETE FROM events WHERE session_id = ? AND seq <= ?`).run(sessionId, upToSeq);
    return result.changes;
  }
  /**
   * Delete all events after a given sequence number (exclusive).
   * Retains session.initialized (seq 1) and any events at/below fromSeq.
   * Used when truncating session history.
   *
   * @param sessionId - The session ID
   * @param fromSeq - Events with seq > fromSeq will be deleted
   * @returns The number of events deleted
   */
  deleteEventsAfterSeq(sessionId, fromSeq) {
    const result = this.db.prepare(`DELETE FROM events WHERE session_id = ? AND seq > ?`).run(sessionId, fromSeq);
    return result.changes;
  }
  /**
   * Clean up old events, keeping only:
   * - session.initialized event (seq 1)
   * - All snapshot events
   * - State-changing events (criteria.set, criterion.updated, mode.changed, phase.changed, context.state, etc.)
   * - Events after the latest snapshot (current window)
   *
   * This is the recommended cleanup method that preserves all snapshots and state.
   *
   * @param sessionId - The session ID
   * @returns The number of events deleted
   */
  cleanupOldEvents(sessionId) {
    const latestSnapshotSeq = this.getLatestSnapshotSeq(sessionId);
    if (latestSnapshotSeq === 0) {
      return 0;
    }
    const result = this.db.prepare(
      `
        DELETE FROM events
        WHERE session_id = ? AND seq > 1 AND seq < ?
        AND event_type NOT IN (
          'criteria.set',
          'criterion.updated',
          'mode.changed',
          'phase.changed',
          'todo.updated',
          'context.state'
        )
      `
    ).run(sessionId, latestSnapshotSeq);
    return result.changes;
  }
  /**
   * One-time storage optimization: delete old snapshots and strip
   * promptContext.messages from remaining snapshots across all sessions.
   * Safe to run multiple times (idempotent).
   */
  optimizeStorage() {
    let deletedSnapshots = 0;
    let strippedSnapshots = 0;
    const deleteResult = this.db.prepare(
      `
        DELETE FROM events
        WHERE event_type = 'turn.snapshot'
        AND id NOT IN (
          SELECT e1.id FROM events e1
          WHERE e1.event_type = 'turn.snapshot'
          AND e1.seq = (
            SELECT MAX(e2.seq) FROM events e2
            WHERE e2.session_id = e1.session_id
            AND e2.event_type = 'turn.snapshot'
          )
        )
      `
    ).run();
    deletedSnapshots = deleteResult.changes;
    const snapshots = this.db.prepare(`SELECT id, payload FROM events WHERE event_type = 'turn.snapshot'`).all();
    const updateStmt = this.db.prepare(`UPDATE events SET payload = ? WHERE id = ?`);
    for (const row of snapshots) {
      const data = JSON.parse(row.payload);
      const messages = data.messages;
      if (!messages) continue;
      const changed = stripPromptContextMessages(messages);
      if (changed) {
        updateStmt.run(JSON.stringify(data), row.id);
        strippedSnapshots++;
      }
    }
    return { deletedSnapshots, strippedSnapshots };
  }
  /**
   * Get the latest snapshot sequence number for a session
   * @returns The sequence number of the latest snapshot, or 0 if none
   */
  getLatestSnapshotSeq(sessionId) {
    const row = this.db.prepare(
      `
        SELECT seq FROM events 
        WHERE session_id = ? AND event_type = 'turn.snapshot' 
        ORDER BY seq DESC LIMIT 1
      `
    ).get(sessionId);
    return row?.seq ?? 0;
  }
  /**
   * Consolidate orphaned events into a new snapshot and delete raw events.
   * Uses transaction to ensure atomicity.
   * @returns Object with snapshotSeq and deletedCount, or null if no events to consolidate
   */
  consolidateSession(sessionId) {
    const transaction = this.db.transaction(() => {
      const events = this.getEvents(sessionId);
      if (events.length === 0) return null;
      const latestSnapshot = [...events].reverse().find((e) => e.type === "turn.snapshot");
      const eventsAfterSnapshot = events.filter((e) => e.seq > (latestSnapshot?.seq ?? 0));
      if (eventsAfterSnapshot.length === 0) return null;
      const initEvent = events.find((e) => e.type === "session.initialized");
      const initialWindowId = initEvent && typeof initEvent.data === "object" && "contextWindowId" in initEvent.data ? initEvent.data.contextWindowId : "legacy-window-1";
      const latestSeq = events[events.length - 1].seq;
      const snapshotMessages = latestSnapshot?.data && typeof latestSnapshot.data === "object" && "messages" in latestSnapshot.data ? latestSnapshot.data.messages : [];
      const foldedState = foldSessionState(events, initialWindowId, 2e5, snapshotMessages);
      const newSnapshot = buildSnapshot(foldedState, latestSeq);
      const deleteResult = this.db.prepare(
        `
        DELETE FROM events 
        WHERE session_id = ? AND seq <= ?
        AND event_type != 'session.initialized'
      `
      ).run(sessionId, latestSeq);
      const snapshotEvent = this.append(sessionId, {
        type: "turn.snapshot",
        data: newSnapshot
      });
      return {
        snapshotSeq: snapshotEvent.seq,
        deletedCount: deleteResult.changes
      };
    });
    try {
      return transaction();
    } catch (error) {
      const err = error instanceof Error ? error : new Error(String(error));
      const errnoError = error;
      const isFkError = errnoError.code === "SQLITE_CONSTRAINT_FOREIGNKEY";
      if (isFkError || err.message.includes("FOREIGN KEY constraint failed")) {
        logger.debug("Session no longer exists during consolidation", { sessionId });
        return null;
      }
      logger.error("Failed to consolidate session", { sessionId, error: err.message, stack: err.stack });
      return null;
    }
  }
  /**
   * Find session IDs that have orphaned events (events after latest snapshot)
   * and are not currently running.
   */
  findOrphanedSessions() {
    const cutoff = new Date(Date.now() - 5 * 60 * 1e3).toISOString();
    logger.debug("Looking for orphaned sessions", { cutoff });
    const sessions = this.db.prepare(
      `
      SELECT id, is_running 
      FROM sessions 
      WHERE is_running = 0 AND updated_at < ?
    `
    ).all(cutoff);
    logger.debug("Idle sessions found", { count: sessions.length });
    const orphaned = [];
    for (const session of sessions) {
      const hasSnapshot = this.db.prepare(
        `
        SELECT 1 FROM events WHERE session_id = ? AND event_type = 'turn.snapshot' LIMIT 1
      `
      ).get(session.id);
      if (hasSnapshot) {
        const latestSnapshotSeq = this.getLatestSnapshotSeq(session.id);
        const eventsAfter = this.db.prepare(
          `
          SELECT 1 FROM events WHERE session_id = ? AND seq > ? LIMIT 1
        `
        ).get(session.id, latestSnapshotSeq);
        if (eventsAfter) {
          orphaned.push(session.id);
        }
      }
    }
    logger.info("Orphaned sessions found", { count: orphaned.length, ids: orphaned });
    return orphaned;
  }
};
var eventStoreInstance = null;
function initEventStore(db) {
  eventStoreInstance = new EventStore(db);
  try {
    db.prepare(`UPDATE sessions SET is_running = 0`).run();
  } catch {
  }
  resetStaleRunningSessions(eventStoreInstance, db);
  const result = eventStoreInstance.optimizeStorage();
  if (result.deletedSnapshots > 0 || result.strippedSnapshots > 0) {
    logger.info("Storage optimized", result);
  }
  setImmediate(() => {
    try {
      logger.info("Starting orphaned session consolidation");
      const orphanedSessions = eventStoreInstance.findOrphanedSessions();
      if (orphanedSessions.length > 0) {
        logger.info("Found orphaned sessions to consolidate", { count: orphanedSessions.length });
        let consolidated = 0;
        for (const sessionId of orphanedSessions) {
          const result2 = eventStoreInstance.consolidateSession(sessionId);
          if (result2) {
            consolidated++;
            logger.debug("Consolidated session", { sessionId, deletedCount: result2.deletedCount });
          }
        }
        logger.info("Sessions consolidated", { consolidated, total: orphanedSessions.length });
      } else {
        logger.info("No orphaned sessions to consolidate");
      }
    } catch {
    }
  });
  return eventStoreInstance;
}
function resetStaleRunningSessions(eventStore, db) {
  const sessions = db.prepare(`SELECT id FROM sessions`).all();
  let resetCount = 0;
  for (const { id: sessionId } of sessions) {
    const lastRunningEvent = db.prepare(
      `
      SELECT payload FROM events 
      WHERE session_id = ? AND event_type = 'running.changed'
      ORDER BY seq DESC LIMIT 1
    `
    ).get(sessionId);
    if (lastRunningEvent) {
      const data = JSON.parse(lastRunningEvent.payload);
      if (data.isRunning === true) {
        eventStore.append(sessionId, {
          type: "running.changed",
          data: { isRunning: false }
        });
        resetCount++;
      }
    }
  }
  if (resetCount > 0) {
    logger.info("EventStore reset stale running sessions", { count: resetCount });
  }
}
function getEventStore() {
  if (!eventStoreInstance) {
    throw new Error("EventStore not initialized. Call initEventStore first.");
  }
  return eventStoreInstance;
}

// src/server/events/types.ts
function isTurnEvent(event) {
  return typeof event === "object" && event !== null && "type" in event && "data" in event && typeof event.type === "string";
}
function isStoredEvent(event) {
  return typeof event === "object" && event !== null && "seq" in event && "timestamp" in event && "sessionId" in event && "type" in event && "data" in event;
}
function createEvent(type, data) {
  return { type, data };
}

// src/server/db/sessions.ts
function getProjectDangerLevel(projectId) {
  try {
    const db = getDatabase();
    const row = db.prepare("SELECT danger_level FROM projects WHERE id = ?").get(projectId);
    return row?.danger_level ?? "normal";
  } catch {
    return "normal";
  }
}
function createSession(projectId, workdir, title, providerId, providerModel) {
  const db = getDatabase();
  const now = (/* @__PURE__ */ new Date()).toISOString();
  const id = crypto.randomUUID();
  const dangerLevel = getProjectDangerLevel(projectId);
  db.prepare(
    `
    INSERT INTO sessions (id, project_id, workdir, phase, mode, workflow_phase, is_running, created_at, updated_at, title, provider_id, provider_model, danger_level)
    VALUES (?, ?, ?, 'idle', 'planner', 'plan', 0, ?, ?, ?, ?, ?, ?)
  `
  ).run(id, projectId, workdir, now, now, title ?? null, providerId ?? null, providerModel ?? null, dangerLevel);
  return {
    id,
    projectId,
    workdir,
    mode: "planner",
    phase: "plan",
    isRunning: false,
    providerId: providerId ?? null,
    providerModel: providerModel ?? null,
    createdAt: now,
    updatedAt: now,
    messages: [],
    criteria: [],
    contextWindows: [],
    executionState: null,
    metadata: {
      ...title ? { title } : {},
      totalTokensUsed: 0,
      totalToolCalls: 0,
      iterationCount: 0
    },
    dangerLevel
  };
}
function getSession(id) {
  const db = getDatabase();
  const row = db.prepare(
    `
    SELECT * FROM sessions WHERE id = ?
  `
  ).get(id);
  if (!row) {
    return null;
  }
  return {
    ...mapSessionBase(row),
    messages: [],
    criteria: [],
    contextWindows: [],
    executionState: null,
    metadata: {
      ...row.title ? { title: row.title } : {},
      totalTokensUsed: row.total_tokens_used,
      totalToolCalls: row.total_tool_calls,
      iterationCount: row.iteration_count
    },
    dangerLevel: row.danger_level ?? "normal"
  };
}
function updateSessionProvider(id, providerId, providerModel) {
  const db = getDatabase();
  const now = (/* @__PURE__ */ new Date()).toISOString();
  db.prepare(
    `
    UPDATE sessions SET provider_id = ?, provider_model = ?, updated_at = ? WHERE id = ?
  `
  ).run(providerId, providerModel, now, id);
}
function updateSessionDangerLevel(id, dangerLevel) {
  const db = getDatabase();
  const now = (/* @__PURE__ */ new Date()).toISOString();
  db.prepare(
    `
    UPDATE sessions SET danger_level = ?, updated_at = ? WHERE id = ?
  `
  ).run(dangerLevel, now, id);
}
function updateSessionRunning(id, isRunning) {
  const db = getDatabase();
  const now = (/* @__PURE__ */ new Date()).toISOString();
  db.prepare(
    `
    UPDATE sessions SET is_running = ?, updated_at = ? WHERE id = ?
  `
  ).run(isRunning ? 1 : 0, now, id);
}
function updateSessionMetadata(id, metadata) {
  const db = getDatabase();
  const now = (/* @__PURE__ */ new Date()).toISOString();
  const updates = ["updated_at = ?"];
  const values = [now];
  if (metadata.title !== void 0) {
    updates.push("title = ?");
    values.push(metadata.title ?? "");
  }
  if (metadata.totalTokensUsed !== void 0) {
    updates.push("total_tokens_used = ?");
    values.push(metadata.totalTokensUsed);
  }
  if (metadata.totalToolCalls !== void 0) {
    updates.push("total_tool_calls = ?");
    values.push(metadata.totalToolCalls);
  }
  if (metadata.iterationCount !== void 0) {
    updates.push("iteration_count = ?");
    values.push(metadata.iterationCount);
  }
  values.push(id);
  db.prepare(
    `
    UPDATE sessions SET ${updates.join(", ")} WHERE id = ?
  `
  ).run(...values);
}
function updateSessionMessageCount(id, delta) {
  try {
    const db = getDatabase();
    const now = (/* @__PURE__ */ new Date()).toISOString();
    db.prepare(
      `
      UPDATE sessions SET message_count = message_count + ?, updated_at = ? WHERE id = ?
    `
    ).run(delta, now, id);
  } catch {
  }
}
function listSessions() {
  const db = getDatabase();
  const rows = db.prepare(
    `
    SELECT
      s.id,
      s.project_id,
      s.workdir,
      s.mode,
      s.workflow_phase,
      s.is_running,
      s.created_at,
      s.updated_at,
      s.title,
      s.provider_id,
      s.provider_model,
      s.message_count
    FROM sessions s
    ORDER BY s.updated_at DESC
  `
  ).all();
  return rows.map(mapSessionSummaryRow);
}
function listSessionsByProject(projectId, limit = 20, offset = 0) {
  const db = getDatabase();
  const rows = db.prepare(
    `
    SELECT
      s.id,
      s.project_id,
      s.workdir,
      s.mode,
      s.workflow_phase,
      s.is_running,
      s.created_at,
      s.updated_at,
      s.title,
      s.provider_id,
      s.provider_model,
      s.message_count
    FROM sessions s
    WHERE s.project_id = ?
    ORDER BY s.updated_at DESC
    LIMIT ? OFFSET ?
  `
  ).all(projectId, limit + 1, offset);
  const hasMore = rows.length > limit;
  const sessions = rows.slice(0, limit).map(mapSessionSummaryRow);
  return { sessions, hasMore };
}
function deleteSession(id) {
  const db = getDatabase();
  db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
}
function mapSessionBase(row) {
  return {
    id: row.id,
    projectId: row.project_id,
    workdir: row.workdir,
    mode: row.mode ?? "planner",
    phase: row.workflow_phase ?? "plan",
    isRunning: Boolean(row.is_running),
    providerId: row.provider_id ?? null,
    providerModel: row.provider_model ?? null,
    createdAt: row.created_at,
    updatedAt: row.updated_at
  };
}
function mapSessionSummaryRow(row) {
  return {
    ...mapSessionBase(row),
    ...row.title ? { title: row.title } : {},
    criteriaCount: 0,
    criteriaCompleted: 0,
    messageCount: row.message_count
  };
}

// src/server/events/session.ts
function toSnapshotMessage(message) {
  return {
    id: message.id,
    role: message.role,
    content: message.content,
    timestamp: new Date(message.timestamp).getTime(),
    ...spreadOptionalMessageFields(message)
  };
}
function getSessionState(sessionId, maxTokens) {
  const eventStore = getEventStore();
  const latestSnapshotEvent = eventStore.getLatestSnapshot(sessionId);
  const events = eventStore.getEvents(sessionId);
  if (events.length === 0) {
    return void 0;
  }
  let initialWindowId;
  for (const event of events) {
    if (event.type === "session.initialized") {
      const data = event.data;
      initialWindowId = data.contextWindowId;
      break;
    }
  }
  if (!initialWindowId) {
    for (const event of events) {
      if (event.type === "turn.snapshot") {
        const snapshotData = event.data;
        if (snapshotData.sessionInit?.contextWindowId) {
          initialWindowId = snapshotData.sessionInit.contextWindowId;
          break;
        }
      }
    }
  }
  if (!initialWindowId) {
    for (const event of events) {
      if (event.type === "turn.snapshot") {
        const snapshotData = event.data;
        if (snapshotData.currentContextWindowId) {
          initialWindowId = snapshotData.currentContextWindowId;
          break;
        }
      }
    }
  }
  if (!initialWindowId) {
    return void 0;
  }
  const config = getRuntimeConfig();
  const effectiveMaxTokens = maxTokens ?? config.context.maxTokens;
  if (latestSnapshotEvent) {
    const state = foldSessionState(events, initialWindowId, effectiveMaxTokens);
    return {
      ...state,
      messages: buildMessagesFromStoredEvents(events).map(toSnapshotMessage)
    };
  }
  return foldSessionState(events, initialWindowId, effectiveMaxTokens);
}
function getCurrentWindowMessages(sessionId) {
  const currentWindowId = getCurrentContextWindowId(sessionId);
  if (!currentWindowId) return [];
  const state = getSessionState(sessionId);
  if (!state) return [];
  return state.messages.filter((m) => m.contextWindowId === currentWindowId);
}
function getContextMessages(sessionId) {
  const eventStore = getEventStore();
  const currentWindowId = getCurrentContextWindowId(sessionId);
  if (!currentWindowId) return [];
  const events = eventStore.getEvents(sessionId);
  if (events.length === 0) return [];
  return buildContextMessagesFromEventHistory(events, currentWindowId, { includeVerifier: false });
}
function getCurrentContextWindowId(sessionId) {
  const eventStore = getEventStore();
  const events = eventStore.getEvents(sessionId);
  const contextResult = foldContextState(events, "");
  return contextResult.currentContextWindowId || void 0;
}
function getReadFilesCache(sessionId) {
  const state = getSessionState(sessionId);
  return state?.readFiles ?? [];
}
function isFileInCache(sessionId, path) {
  const cache = getReadFilesCache(sessionId);
  return cache.some((f) => f.path === path);
}
function emitSessionInitialized(sessionId, projectId, workdir, contextWindowId, title) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "session.initialized",
    data: {
      projectId,
      workdir,
      contextWindowId,
      ...title !== void 0 && { title }
    }
  });
}
function emitUserMessage(sessionId, content, options) {
  const eventStore = getEventStore();
  const messageId = crypto.randomUUID();
  eventStore.append(sessionId, {
    type: "message.start",
    data: {
      messageId,
      role: "user",
      content,
      ...options?.contextWindowId !== void 0 && { contextWindowId: options.contextWindowId },
      ...options?.isSystemGenerated !== void 0 && { isSystemGenerated: options.isSystemGenerated },
      ...options?.messageKind !== void 0 && { messageKind: options.messageKind },
      ...options?.isCompactionSummary !== void 0 && { isCompactionSummary: options.isCompactionSummary },
      ...options?.tokenCount !== void 0 && { tokenCount: options.tokenCount },
      ...options?.attachments !== void 0 && { attachments: options.attachments },
      ...options?.subAgentId !== void 0 && { subAgentId: options.subAgentId },
      ...options?.subAgentType !== void 0 && { subAgentType: options.subAgentType },
      ...options?.metadata !== void 0 && { metadata: options.metadata }
    }
  });
  eventStore.append(sessionId, {
    type: "message.done",
    data: { messageId }
  });
  updateSessionMessageCount(sessionId, 1);
  return messageId;
}
function emitAssistantMessageStart(sessionId, options) {
  const eventStore = getEventStore();
  const messageId = crypto.randomUUID();
  eventStore.append(sessionId, {
    type: "message.start",
    data: {
      messageId,
      role: "assistant",
      ...options?.contextWindowId !== void 0 && { contextWindowId: options.contextWindowId },
      ...options?.subAgentId !== void 0 && { subAgentId: options.subAgentId },
      ...options?.subAgentType !== void 0 && { subAgentType: options.subAgentType }
    }
  });
  updateSessionMessageCount(sessionId, 1);
  return messageId;
}
function emitMessageDelta(sessionId, messageId, content) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "message.delta",
    data: { messageId, content }
  });
}
function emitMessageThinking(sessionId, messageId, content) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "message.thinking",
    data: { messageId, content }
  });
}
function emitMessageDone(sessionId, messageId, options) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "message.done",
    data: {
      messageId,
      ...options?.stats !== void 0 && { stats: options.stats },
      ...options?.segments !== void 0 && { segments: options.segments },
      ...options?.partial !== void 0 && { partial: options.partial },
      ...options?.promptContext !== void 0 && { promptContext: options.promptContext },
      ...options?.tokenCount !== void 0 && { tokenCount: options.tokenCount }
    }
  });
}
function emitToolPreparing(sessionId, messageId, index, name) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "tool.preparing",
    data: { messageId, index, name }
  });
}
function emitToolCall(sessionId, messageId, toolCall) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "tool.call",
    data: { messageId, toolCall }
  });
}
function emitToolOutput(sessionId, messageId, toolCallId, stream, content) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "tool.output",
    data: { messageId, toolCallId, stream, content }
  });
}
function emitToolResult(sessionId, messageId, toolCallId, result) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "tool.result",
    data: { messageId, toolCallId, result }
  });
}
function emitModeChanged(sessionId, mode, auto, reason) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "mode.changed",
    data: {
      mode,
      auto,
      ...reason !== void 0 && { reason }
    }
  });
}
function emitPhaseChanged(sessionId, phase) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "phase.changed",
    data: { phase }
  });
}
function emitRunningChanged(sessionId, isRunning) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "running.changed",
    data: { isRunning }
  });
}
function emitCriteriaSet(sessionId, criteria) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "criteria.set",
    data: { criteria }
  });
}
function emitCriterionUpdated(sessionId, criterionId, status) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "criterion.updated",
    data: { criterionId, status }
  });
}
function emitTodosUpdated(sessionId, todos) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "todo.updated",
    data: { todos }
  });
}
function emitFileRead(sessionId, path, tokenCount, contextWindowId) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "file.read",
    data: { path, tokenCount, contextWindowId }
  });
}
function emitContextCompacted(sessionId, closedWindowId, newWindowId, beforeTokens, afterTokens, summary) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "context.compacted",
    data: {
      closedWindowId,
      newWindowId,
      beforeTokens,
      afterTokens,
      summary
    }
  });
}
function emitContextState(sessionId, currentTokens, maxTokens, compactionCount, dangerZone, canCompact, subAgentId, dynamicContextChanged) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "context.state",
    data: {
      currentTokens,
      maxTokens,
      compactionCount,
      dangerZone,
      canCompact,
      dynamicContextChanged: dynamicContextChanged ?? false,
      ...subAgentId !== void 0 && { subAgentId }
    }
  });
}
function emitChatDone(sessionId, messageId, reason, stats) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "chat.done",
    data: {
      messageId,
      reason,
      ...stats !== void 0 && { stats }
    }
  });
}
function emitChatError(sessionId, error, recoverable) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "chat.error",
    data: { error, recoverable }
  });
}
function emitFormatRetry(sessionId, attempt, maxAttempts) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "format.retry",
    data: { attempt, maxAttempts }
  });
}
function emitTurnSnapshot(sessionId, snapshot) {
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "turn.snapshot",
    data: snapshot
  });
}
function truncateSessionMessages(sessionId, messageIndex) {
  const eventStore = getEventStore();
  const snapshotEvent = eventStore.getLatestSnapshot(sessionId);
  if (!snapshotEvent) return;
  const snapshot = snapshotEvent.data;
  const messages = snapshot.messages;
  const lastKept = messageIndex + 1;
  if (lastKept < 0 || lastKept >= messages.length) return;
  snapshot.messages = messages.slice(0, lastKept);
  eventStore.deleteEventsAfterSeq(sessionId, snapshotEvent.seq);
  eventStore.append(sessionId, {
    type: "turn.snapshot",
    data: snapshot
  });
  const removed = messages.length - lastKept;
  updateSessionMessageCount(sessionId, -removed);
}
function compactContext(sessionId, summary, beforeTokens) {
  const state = getSessionState(sessionId);
  if (!state) {
    throw new Error("Session not found");
  }
  const closedWindowId = state.currentContextWindowId;
  const newWindowId = crypto.randomUUID();
  const summaryMessageId = crypto.randomUUID();
  const eventStore = getEventStore();
  eventStore.append(sessionId, {
    type: "message.start",
    data: {
      messageId: summaryMessageId,
      role: "assistant",
      content: summary,
      contextWindowId: closedWindowId,
      isCompactionSummary: true
    }
  });
  eventStore.append(sessionId, {
    type: "message.done",
    data: { messageId: summaryMessageId }
  });
  emitContextCompacted(sessionId, closedWindowId, newWindowId, beforeTokens, 0, summary);
  return { newWindowId, summaryMessageId };
}
function getRecentUserPromptsForSession(sessionId, limit = 10) {
  try {
    const eventStore = getEventStore();
    const db = eventStore.db;
    if (!db) {
      return [];
    }
    const isRealUserMessage = (msg) => msg.role === "user" && !msg.isSystemGenerated && !msg.messageKind && !msg.subAgentType;
    const promptMap = /* @__PURE__ */ new Map();
    const snapshotRow = db.prepare(
      `
        SELECT payload, timestamp
        FROM events
        WHERE session_id = ? AND event_type = 'turn.snapshot'
        ORDER BY timestamp DESC
        LIMIT 1
      `
    ).get(sessionId);
    if (snapshotRow) {
      const snapshot = JSON.parse(snapshotRow.payload);
      for (const msg of snapshot.messages) {
        if (isRealUserMessage(msg)) {
          promptMap.set(msg.id, {
            id: msg.id,
            content: msg.content,
            timestamp: new Date(msg.timestamp).toISOString()
          });
        }
      }
    }
    const rows = db.prepare(
      `
        SELECT payload, timestamp
        FROM events
        WHERE session_id = ? AND event_type = 'message.start'
          AND json_extract(payload, '$.role') = 'user'
          AND json_extract(payload, '$.isSystemGenerated') IS NULL
          AND json_extract(payload, '$.messageKind') IS NULL
          AND json_extract(payload, '$.subAgentType') IS NULL
        ORDER BY timestamp DESC
        LIMIT ?
      `
    ).all(sessionId, limit);
    for (const row of rows) {
      const message = JSON.parse(row.payload);
      promptMap.set(message.messageId, {
        id: message.messageId,
        content: message.content,
        timestamp: new Date(row.timestamp).toISOString()
      });
    }
    return [...promptMap.values()].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).slice(0, limit);
  } catch {
    return [];
  }
}

export {
  EventStore,
  initEventStore,
  getEventStore,
  isTurnEvent,
  isStoredEvent,
  createEvent,
  createSession,
  getSession,
  updateSessionProvider,
  updateSessionDangerLevel,
  updateSessionRunning,
  updateSessionMetadata,
  listSessions,
  listSessionsByProject,
  deleteSession,
  getSessionState,
  getCurrentWindowMessages,
  getContextMessages,
  getCurrentContextWindowId,
  getReadFilesCache,
  isFileInCache,
  emitSessionInitialized,
  emitUserMessage,
  emitAssistantMessageStart,
  emitMessageDelta,
  emitMessageThinking,
  emitMessageDone,
  emitToolPreparing,
  emitToolCall,
  emitToolOutput,
  emitToolResult,
  emitModeChanged,
  emitPhaseChanged,
  emitRunningChanged,
  emitCriteriaSet,
  emitCriterionUpdated,
  emitTodosUpdated,
  emitFileRead,
  emitContextCompacted,
  emitContextState,
  emitChatDone,
  emitChatError,
  emitFormatRetry,
  emitTurnSnapshot,
  truncateSessionMessages,
  compactContext,
  getRecentUserPromptsForSession
};
//# sourceMappingURL=chunk-J335KZ6U.js.map