from __future__ import annotations import hashlib import json import sqlite3 import uuid from contextlib import contextmanager from datetime import UTC, datetime, timedelta from pathlib import Path from typing import Any, Iterable, Iterator from .config import UserSeed def utc_now() -> str: return datetime.now(UTC).isoformat(timespec="milliseconds") def _json(value: Any) -> str: return json.dumps(value, ensure_ascii=False, separators=(",", ":")) def _loads(value: str | None, fallback: Any) -> Any: if not value: return fallback try: return json.loads(value) except json.JSONDecodeError: return fallback class Database: def __init__(self, path: Path): self.path = path @contextmanager def connect(self) -> Iterator[sqlite3.Connection]: connection = sqlite3.connect(self.path, timeout=10) connection.row_factory = sqlite3.Row connection.execute("PRAGMA foreign_keys = ON") connection.execute("PRAGMA busy_timeout = 10000") try: yield connection connection.commit() except Exception: connection.rollback() raise finally: connection.close() def initialize(self, user_seeds: Iterable[UserSeed] = ()) -> None: seeds = list(user_seeds) with self.connect() as connection: connection.executescript( """ PRAGMA journal_mode = WAL; CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, label TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('admin', 'member')), access_key_hash TEXT NOT NULL, debug_sharing INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS fragments ( id TEXT PRIMARY KEY, user_id TEXT, content TEXT NOT NULL, created_at TEXT NOT NULL, analysis_status TEXT NOT NULL DEFAULT 'pending', analysis_error TEXT ); CREATE TABLE IF NOT EXISTS ideas ( id TEXT PRIMARY KEY, user_id TEXT, title TEXT NOT NULL, summary TEXT NOT NULL DEFAULT '', maturity_ai REAL NOT NULL DEFAULT 12, maturity_override REAL, confidence REAL NOT NULL DEFAULT 0.5, motion TEXT NOT NULL DEFAULT '浮现', position TEXT NOT NULL DEFAULT '', tension TEXT NOT NULL DEFAULT '', trajectory TEXT NOT NULL DEFAULT '', possible_moves TEXT NOT NULL DEFAULT '[]', created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS idea_fragments ( idea_id TEXT NOT NULL REFERENCES ideas(id) ON DELETE CASCADE, fragment_id TEXT NOT NULL REFERENCES fragments(id) ON DELETE CASCADE, relevance REAL NOT NULL DEFAULT 1, created_at TEXT NOT NULL, PRIMARY KEY (idea_id, fragment_id) ); CREATE TABLE IF NOT EXISTS idea_snapshots ( id TEXT PRIMARY KEY, idea_id TEXT NOT NULL REFERENCES ideas(id) ON DELETE CASCADE, source_fragment_id TEXT REFERENCES fragments(id) ON DELETE SET NULL, title TEXT, summary TEXT, maturity_ai REAL NOT NULL, maturity_override REAL, confidence REAL, motion TEXT NOT NULL, position TEXT NOT NULL, tension TEXT NOT NULL, trajectory TEXT NOT NULL, possible_moves TEXT NOT NULL, change_kind TEXT NOT NULL DEFAULT 'legacy_partial', fragment_ids TEXT NOT NULL DEFAULT '[]', created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS sessions ( token_hash TEXT PRIMARY KEY, user_id TEXT, created_at TEXT NOT NULL, expires_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS agent_runs ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, fragment_id TEXT NOT NULL, status TEXT NOT NULL, prompt_version TEXT NOT NULL, model TEXT NOT NULL, thinking_effort TEXT NOT NULL, started_at TEXT NOT NULL, finished_at TEXT, duration_ms INTEGER, attempt_count INTEGER NOT NULL DEFAULT 0, model_rounds INTEGER NOT NULL DEFAULT 0, tool_calls INTEGER NOT NULL DEFAULT 0, search_calls INTEGER NOT NULL DEFAULT 0, inspection_calls INTEGER NOT NULL DEFAULT 0, input_tokens INTEGER NOT NULL DEFAULT 0, output_tokens INTEGER NOT NULL DEFAULT 0, reasoning_tokens INTEGER NOT NULL DEFAULT 0, cached_tokens INTEGER NOT NULL DEFAULT 0, error_type TEXT, error_message TEXT ); CREATE TABLE IF NOT EXISTS agent_events ( id TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES agent_runs(id) ON DELETE CASCADE, user_id TEXT NOT NULL, event_type TEXT NOT NULL, event_at TEXT NOT NULL, duration_ms INTEGER, payload TEXT NOT NULL DEFAULT '{}' ); CREATE TABLE IF NOT EXISTS audit_events ( id TEXT PRIMARY KEY, user_id TEXT, event_type TEXT NOT NULL, created_at TEXT NOT NULL, payload TEXT NOT NULL DEFAULT '{}' ); CREATE INDEX IF NOT EXISTS idx_fragments_created ON fragments(created_at DESC); CREATE INDEX IF NOT EXISTS idx_fragments_status ON fragments(analysis_status); CREATE INDEX IF NOT EXISTS idx_ideas_updated ON ideas(updated_at DESC); CREATE INDEX IF NOT EXISTS idx_links_fragment ON idea_fragments(fragment_id); CREATE INDEX IF NOT EXISTS idx_snapshots_idea ON idea_snapshots(idea_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_runs_started ON agent_runs(started_at DESC); CREATE INDEX IF NOT EXISTS idx_runs_status ON agent_runs(status, started_at DESC); CREATE INDEX IF NOT EXISTS idx_events_run ON agent_events(run_id, event_at ASC); CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_events(created_at DESC); """ ) self._ensure_column(connection, "fragments", "user_id", "TEXT") self._ensure_column(connection, "ideas", "user_id", "TEXT") self._ensure_column(connection, "sessions", "user_id", "TEXT") self._ensure_column(connection, "idea_snapshots", "title", "TEXT") self._ensure_column(connection, "idea_snapshots", "summary", "TEXT") self._ensure_column( connection, "idea_snapshots", "maturity_override", "REAL" ) self._ensure_column( connection, "idea_snapshots", "confidence", "REAL" ) self._ensure_column( connection, "idea_snapshots", "change_kind", "TEXT NOT NULL DEFAULT 'legacy_partial'", ) self._ensure_column( connection, "idea_snapshots", "fragment_ids", "TEXT NOT NULL DEFAULT '[]'", ) now = utc_now() for seed in seeds: connection.execute( """ INSERT INTO users ( id, label, role, access_key_hash, debug_sharing, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET label = excluded.label, role = excluded.role, access_key_hash = excluded.access_key_hash, updated_at = excluded.updated_at """, ( seed.id, seed.label, seed.role, seed.access_key_hash, int(seed.debug_sharing), now, now, ), ) if seeds: primary = next( (seed for seed in seeds if seed.role == "admin"), seeds[0] ) for table in ("fragments", "ideas", "sessions"): connection.execute( f""" UPDATE {table} SET user_id = ? WHERE user_id IS NULL OR user_id = '' """, (primary.id,), ) connection.executescript( """ CREATE INDEX IF NOT EXISTS idx_fragments_user_created ON fragments(user_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_ideas_user_updated ON ideas(user_id, updated_at DESC); CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id, expires_at DESC); CREATE INDEX IF NOT EXISTS idx_runs_user_started ON agent_runs(user_id, started_at DESC); """ ) @staticmethod def _ensure_column( connection: sqlite3.Connection, table: str, column: str, definition: str ) -> None: columns = { row["name"] for row in connection.execute(f"PRAGMA table_info({table})").fetchall() } if column not in columns: connection.execute( f"ALTER TABLE {table} ADD COLUMN {column} {definition}" ) # Users and sessions ------------------------------------------------- def list_auth_users(self) -> list[dict[str, Any]]: with self.connect() as connection: rows = connection.execute( """ SELECT id, label, role, access_key_hash, debug_sharing FROM users ORDER BY created_at ASC """ ).fetchall() return [dict(row) for row in rows] def get_user(self, user_id: str) -> dict[str, Any] | None: with self.connect() as connection: row = connection.execute( """ SELECT id, label, role, debug_sharing, created_at FROM users WHERE id = ? """, (user_id,), ).fetchone() if not row: return None user = dict(row) user["debug_sharing"] = bool(user["debug_sharing"]) return user def list_users(self) -> list[dict[str, Any]]: with self.connect() as connection: rows = connection.execute( """ SELECT u.id, u.label, u.role, u.debug_sharing, u.created_at, COUNT(DISTINCT f.id) AS fragment_count, COUNT(DISTINCT i.id) AS idea_count FROM users u LEFT JOIN fragments f ON f.user_id = u.id LEFT JOIN ideas i ON i.user_id = u.id GROUP BY u.id ORDER BY u.created_at ASC """ ).fetchall() result = [dict(row) for row in rows] for user in result: user["debug_sharing"] = bool(user["debug_sharing"]) return result def update_debug_sharing( self, user_id: str, enabled: bool ) -> dict[str, Any] | None: with self.connect() as connection: result = connection.execute( """ UPDATE users SET debug_sharing = ?, updated_at = ? WHERE id = ? """, (int(enabled), utc_now(), user_id), ) if result.rowcount == 0: return None return self.get_user(user_id) # Raw fragments ------------------------------------------------------ def create_fragment(self, user_id: str, content: str) -> dict[str, Any]: fragment = { "id": str(uuid.uuid4()), "user_id": user_id, "content": content, "created_at": utc_now(), "analysis_status": "pending", } with self.connect() as connection: connection.execute( """ INSERT INTO fragments ( id, user_id, content, created_at, analysis_status ) VALUES (:id, :user_id, :content, :created_at, :analysis_status) """, fragment, ) return fragment def list_fragments( self, user_id: str, limit: int = 300 ) -> list[dict[str, Any]]: with self.connect() as connection: rows = connection.execute( """ SELECT id, content, created_at FROM fragments WHERE user_id = ? ORDER BY created_at DESC, rowid DESC LIMIT ? """, (user_id, limit), ).fetchall() return [dict(row) for row in reversed(rows)] def get_fragment( self, fragment_id: str, user_id: str | None = None ) -> dict[str, Any] | None: query = "SELECT * FROM fragments WHERE id = ?" params: list[Any] = [fragment_id] if user_id is not None: query += " AND user_id = ?" params.append(user_id) with self.connect() as connection: row = connection.execute(query, params).fetchone() return dict(row) if row else None def pending_fragment_ids(self, limit: int = 100) -> list[str]: with self.connect() as connection: rows = connection.execute( """ SELECT id FROM fragments WHERE analysis_status IN ('pending', 'processing') AND user_id IS NOT NULL ORDER BY created_at ASC, rowid ASC LIMIT ? """, (limit,), ).fetchall() connection.execute( """ UPDATE fragments SET analysis_status = 'pending' WHERE analysis_status = 'processing' """ ) return [row["id"] for row in rows] def set_fragment_status( self, fragment_id: str, status: str, error: str | None = None ) -> None: with self.connect() as connection: connection.execute( """ UPDATE fragments SET analysis_status = ?, analysis_error = ? WHERE id = ? """, (status, error, fragment_id), ) def processing_count(self, user_id: str | None = None) -> int: query = """ SELECT COUNT(*) AS count FROM fragments WHERE analysis_status IN ('pending', 'processing') """ params: list[Any] = [] if user_id: query += " AND user_id = ?" params.append(user_id) with self.connect() as connection: row = connection.execute(query, params).fetchone() return int(row["count"]) # Ideas -------------------------------------------------------------- def list_ideas(self, user_id: str) -> list[dict[str, Any]]: with self.connect() as connection: rows = connection.execute( """ SELECT i.*, COUNT(l.fragment_id) AS fragment_count, MAX(f.created_at) AS latest_fragment_at FROM ideas i LEFT JOIN idea_fragments l ON l.idea_id = i.id LEFT JOIN fragments f ON f.id = l.fragment_id AND f.user_id = i.user_id WHERE i.user_id = ? GROUP BY i.id ORDER BY i.updated_at DESC """, (user_id,), ).fetchall() return [self._idea_from_row(row) for row in rows] def get_idea(self, user_id: str, idea_id: str) -> dict[str, Any] | None: with self.connect() as connection: row = connection.execute( """ SELECT i.*, COUNT(l.fragment_id) AS fragment_count, MAX(f.created_at) AS latest_fragment_at FROM ideas i LEFT JOIN idea_fragments l ON l.idea_id = i.id LEFT JOIN fragments f ON f.id = l.fragment_id AND f.user_id = i.user_id WHERE i.id = ? AND i.user_id = ? GROUP BY i.id """, (idea_id, user_id), ).fetchone() if not row: return None idea = self._idea_from_row(row) fragments = connection.execute( """ SELECT f.id, f.content, f.created_at FROM fragments f JOIN idea_fragments l ON l.fragment_id = f.id WHERE l.idea_id = ? AND f.user_id = ? ORDER BY f.created_at ASC, f.rowid ASC """, (idea_id, user_id), ).fetchall() snapshots = connection.execute( """ SELECT s.title, s.summary, s.maturity_ai, s.maturity_override, s.confidence, s.motion, s.position, s.tension, s.trajectory, s.possible_moves, s.change_kind, s.fragment_ids, s.created_at FROM idea_snapshots s JOIN ideas i ON i.id = s.idea_id WHERE s.idea_id = ? AND i.user_id = ? ORDER BY s.created_at ASC, s.rowid ASC """, (idea_id, user_id), ).fetchall() idea["fragments"] = [dict(row) for row in fragments] idea["snapshots"] = [ { **dict(row), "possible_moves": _loads(row["possible_moves"], []), "fragment_ids": _loads(row["fragment_ids"], []), } for row in snapshots ] return idea def curator_context( self, user_id: str ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: ideas = self.list_ideas(user_id) fragments = self.list_fragments(user_id, limit=80) for idea in ideas: detailed = self.get_idea(user_id, idea["id"]) idea["recent_fragments"] = (detailed or {}).get("fragments", [])[-8:] idea["recent_snapshots"] = (detailed or {}).get("snapshots", [])[-5:] return ideas, fragments def apply_assessments( self, user_id: str, fragment_id: str, assessments: list[dict[str, Any]], ) -> list[str]: now = utc_now() affected: list[str] = [] with self.connect() as connection: fragment_exists = connection.execute( """ SELECT 1 FROM fragments WHERE id = ? AND user_id = ? """, (fragment_id, user_id), ).fetchone() if not fragment_exists: raise ValueError("fragment not found for user") seen: set[str] = set() for assessment in assessments[:2]: idea_id = assessment.get("idea_id") if idea_id: exists = connection.execute( """ SELECT 1 FROM ideas WHERE id = ? AND user_id = ? """, (idea_id, user_id), ).fetchone() if not exists: idea_id = None if not idea_id: idea_id = str(uuid.uuid4()) connection.execute( """ INSERT INTO ideas ( id, user_id, title, summary, maturity_ai, confidence, motion, position, tension, trajectory, possible_moves, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( idea_id, user_id, assessment["title"], assessment["summary"], assessment["maturity"], assessment["confidence"], assessment["motion"], assessment["position"], assessment["tension"], assessment["trajectory"], _json(assessment["possible_moves"]), now, now, ), ) elif idea_id not in seen: connection.execute( """ UPDATE ideas SET title = ?, summary = ?, maturity_ai = ?, confidence = ?, motion = ?, position = ?, tension = ?, trajectory = ?, possible_moves = ?, updated_at = ? WHERE id = ? AND user_id = ? """, ( assessment["title"], assessment["summary"], assessment["maturity"], assessment["confidence"], assessment["motion"], assessment["position"], assessment["tension"], assessment["trajectory"], _json(assessment["possible_moves"]), now, idea_id, user_id, ), ) if idea_id in seen: continue seen.add(idea_id) affected.append(idea_id) connection.execute( """ INSERT OR IGNORE INTO idea_fragments ( idea_id, fragment_id, relevance, created_at ) VALUES (?, ?, ?, ?) """, ( idea_id, fragment_id, assessment.get("relevance", 1), now, ), ) version_state = connection.execute( """ SELECT maturity_override FROM ideas WHERE id = ? AND user_id = ? """, (idea_id, user_id), ).fetchone() fragment_ids = [ row["fragment_id"] for row in connection.execute( """ SELECT l.fragment_id FROM idea_fragments l JOIN fragments f ON f.id = l.fragment_id WHERE l.idea_id = ? AND f.user_id = ? ORDER BY f.created_at ASC, f.rowid ASC """, (idea_id, user_id), ).fetchall() ] connection.execute( """ INSERT INTO idea_snapshots ( id, idea_id, source_fragment_id, title, summary, maturity_ai, maturity_override, confidence, motion, position, tension, trajectory, possible_moves, change_kind, fragment_ids, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( str(uuid.uuid4()), idea_id, fragment_id, assessment["title"], assessment["summary"], assessment["maturity"], ( version_state["maturity_override"] if version_state else None ), assessment["confidence"], assessment["motion"], assessment["position"], assessment["tension"], assessment["trajectory"], _json(assessment["possible_moves"]), "analysis", _json(fragment_ids), now, ), ) connection.execute( """ UPDATE fragments SET analysis_status = 'done', analysis_error = NULL WHERE id = ? AND user_id = ? """, (fragment_id, user_id), ) return affected def update_idea_override( self, user_id: str, idea_id: str, maturity_override: float | None ) -> dict[str, Any] | None: now = utc_now() with self.connect() as connection: result = connection.execute( """ UPDATE ideas SET maturity_override = ?, updated_at = ? WHERE id = ? AND user_id = ? """, (maturity_override, now, idea_id, user_id), ) if result.rowcount == 0: return None idea = connection.execute( """ SELECT title, summary, maturity_ai, maturity_override, confidence, motion, position, tension, trajectory, possible_moves FROM ideas WHERE id = ? AND user_id = ? """, (idea_id, user_id), ).fetchone() fragment_ids = [ row["fragment_id"] for row in connection.execute( """ SELECT l.fragment_id FROM idea_fragments l JOIN fragments f ON f.id = l.fragment_id WHERE l.idea_id = ? AND f.user_id = ? ORDER BY f.created_at ASC, f.rowid ASC """, (idea_id, user_id), ).fetchall() ] connection.execute( """ INSERT INTO idea_snapshots ( id, idea_id, source_fragment_id, title, summary, maturity_ai, maturity_override, confidence, motion, position, tension, trajectory, possible_moves, change_kind, fragment_ids, created_at ) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( str(uuid.uuid4()), idea_id, idea["title"], idea["summary"], idea["maturity_ai"], idea["maturity_override"], idea["confidence"], idea["motion"], idea["position"], idea["tension"], idea["trajectory"], idea["possible_moves"], "manual_calibration", _json(fragment_ids), now, ), ) return self.get_idea(user_id, idea_id) @staticmethod def _idea_from_row(row: sqlite3.Row) -> dict[str, Any]: idea = dict(row) idea["possible_moves"] = _loads(idea["possible_moves"], []) idea["maturity"] = ( idea["maturity_override"] if idea["maturity_override"] is not None else idea["maturity_ai"] ) idea["is_overridden"] = idea["maturity_override"] is not None idea.pop("user_id", None) return idea # Observability ------------------------------------------------------ def start_agent_run( self, user_id: str, fragment_id: str, model: str, prompt_version: str, thinking_effort: str, ) -> str: run_id = str(uuid.uuid4()) now = utc_now() with self.connect() as connection: connection.execute( """ INSERT INTO agent_runs ( id, user_id, fragment_id, status, prompt_version, model, thinking_effort, started_at ) VALUES (?, ?, ?, 'running', ?, ?, ?, ?) """, ( run_id, user_id, fragment_id, prompt_version, model, thinking_effort, now, ), ) self.add_agent_event( run_id, user_id, "run_started", { "model": model, "thinking_effort": thinking_effort, "prompt_version": prompt_version, }, ) return run_id def add_agent_event( self, run_id: str, user_id: str, event_type: str, payload: dict[str, Any] | None = None, duration_ms: int | None = None, ) -> None: with self.connect() as connection: connection.execute( """ INSERT INTO agent_events ( id, run_id, user_id, event_type, event_at, duration_ms, payload ) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( str(uuid.uuid4()), run_id, user_id, event_type, utc_now(), duration_ms, _json(payload or {}), ), ) def finish_agent_run( self, run_id: str, *, status: str, duration_ms: int, attempt_count: int, model_rounds: int, tool_calls: int, search_calls: int, inspection_calls: int, input_tokens: int, output_tokens: int, reasoning_tokens: int, cached_tokens: int, error: Exception | None = None, ) -> None: with self.connect() as connection: connection.execute( """ UPDATE agent_runs SET status = ?, finished_at = ?, duration_ms = ?, attempt_count = ?, model_rounds = ?, tool_calls = ?, search_calls = ?, inspection_calls = ?, input_tokens = ?, output_tokens = ?, reasoning_tokens = ?, cached_tokens = ?, error_type = ?, error_message = ? WHERE id = ? """, ( status, utc_now(), duration_ms, attempt_count, model_rounds, tool_calls, search_calls, inspection_calls, input_tokens, output_tokens, reasoning_tokens, cached_tokens, type(error).__name__ if error else None, str(error)[:500] if error else None, run_id, ), ) def add_audit_event( self, event_type: str, user_id: str | None = None, payload: dict[str, Any] | None = None, ) -> None: with self.connect() as connection: connection.execute( """ INSERT INTO audit_events ( id, user_id, event_type, created_at, payload ) VALUES (?, ?, ?, ?, ?) """, ( str(uuid.uuid4()), user_id, event_type, utc_now(), _json(payload or {}), ), ) def admin_overview(self) -> dict[str, Any]: since = (datetime.now(UTC) - timedelta(hours=24)).isoformat() seven_days = (datetime.now(UTC) - timedelta(days=6)).date().isoformat() with self.connect() as connection: totals = { "users": connection.execute( "SELECT COUNT(*) FROM users" ).fetchone()[0], "fragments": connection.execute( "SELECT COUNT(*) FROM fragments" ).fetchone()[0], "ideas": connection.execute( "SELECT COUNT(*) FROM ideas" ).fetchone()[0], "pending": connection.execute( """ SELECT COUNT(*) FROM fragments WHERE analysis_status IN ('pending', 'processing') """ ).fetchone()[0], "audit_events": connection.execute( "SELECT COUNT(*) FROM audit_events" ).fetchone()[0], } recent = dict( connection.execute( """ SELECT COUNT(*) AS runs, SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) AS successes, SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) AS errors, COALESCE(AVG(duration_ms), 0) AS avg_duration_ms, COALESCE(SUM(input_tokens), 0) AS input_tokens, COALESCE(SUM(output_tokens), 0) AS output_tokens, COALESCE(SUM(reasoning_tokens), 0) AS reasoning_tokens, COALESCE(SUM(cached_tokens), 0) AS cached_tokens, COALESCE(SUM(model_rounds), 0) AS model_rounds, COALESCE(SUM(tool_calls), 0) AS tool_calls FROM agent_runs WHERE started_at >= ? """, (since,), ).fetchone() ) daily = connection.execute( """ SELECT substr(started_at, 1, 10) AS day, COUNT(*) AS runs, SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) AS successes, SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) AS errors, COALESCE(SUM(reasoning_tokens), 0) AS reasoning_tokens FROM agent_runs WHERE substr(started_at, 1, 10) >= ? GROUP BY day ORDER BY day ASC """, (seven_days,), ).fetchall() recent_errors = connection.execute( """ SELECT r.id, r.started_at, r.error_type, r.error_message, u.label AS user_label FROM agent_runs r JOIN users u ON u.id = r.user_id WHERE r.status = 'error' ORDER BY r.started_at DESC LIMIT 5 """ ).fetchall() audit_24h = dict( connection.execute( """ SELECT COUNT(*) AS events, SUM(CASE WHEN event_type = 'login_failed' THEN 1 ELSE 0 END) AS failed_logins FROM audit_events WHERE created_at >= ? """, (since,), ).fetchone() ) recent["successes"] = int(recent["successes"] or 0) recent["errors"] = int(recent["errors"] or 0) audit_24h["events"] = int(audit_24h["events"] or 0) audit_24h["failed_logins"] = int( audit_24h["failed_logins"] or 0 ) recent["success_rate"] = ( round(recent["successes"] / recent["runs"] * 100, 1) if recent["runs"] else 100.0 ) return { "totals": totals, "last_24h": recent, "daily": [dict(row) for row in daily], "recent_errors": [dict(row) for row in recent_errors], "audit_24h": audit_24h, } def list_agent_runs(self, limit: int = 80) -> list[dict[str, Any]]: with self.connect() as connection: rows = connection.execute( """ SELECT r.*, u.label AS user_label, u.role AS user_role, u.debug_sharing, length(f.content) AS content_length FROM agent_runs r JOIN users u ON u.id = r.user_id JOIN fragments f ON f.id = r.fragment_id ORDER BY r.started_at DESC LIMIT ? """, (limit,), ).fetchall() result = [dict(row) for row in rows] for run in result: run["debug_sharing"] = bool(run["debug_sharing"]) return result def get_agent_run(self, run_id: str) -> dict[str, Any] | None: with self.connect() as connection: row = connection.execute( """ SELECT r.*, u.label AS user_label, u.role AS user_role, u.debug_sharing, f.content, length(f.content) AS content_length FROM agent_runs r JOIN users u ON u.id = r.user_id JOIN fragments f ON f.id = r.fragment_id WHERE r.id = ? """, (run_id,), ).fetchone() if not row: return None run = dict(row) run["debug_sharing"] = bool(run["debug_sharing"]) run["content_sha256"] = hashlib.sha256( run["content"].encode("utf-8") ).hexdigest()[:16] return run def prepare_agent_retry(self, run_id: str) -> dict[str, str] | None: with self.connect() as connection: row = connection.execute( """ SELECT r.user_id, r.fragment_id, r.status AS run_status, f.analysis_status FROM agent_runs r JOIN fragments f ON f.id = r.fragment_id WHERE r.id = ? AND f.user_id = r.user_id """, (run_id,), ).fetchone() if ( not row or row["run_status"] != "error" or row["analysis_status"] != "error" ): return None newer = connection.execute( """ SELECT 1 FROM agent_runs WHERE fragment_id = ? AND user_id = ? AND id != ? AND status IN ('running', 'success') LIMIT 1 """, (row["fragment_id"], row["user_id"], run_id), ).fetchone() if newer: return None connection.execute( """ UPDATE fragments SET analysis_status = 'pending', analysis_error = NULL WHERE id = ? AND user_id = ? """, (row["fragment_id"], row["user_id"]), ) return { "user_id": row["user_id"], "fragment_id": row["fragment_id"], } def list_agent_events(self, run_id: str) -> list[dict[str, Any]]: with self.connect() as connection: rows = connection.execute( """ SELECT id, event_type, event_at, duration_ms, payload FROM agent_events WHERE run_id = ? ORDER BY event_at ASC, rowid ASC """, (run_id,), ).fetchall() return [ {**dict(row), "payload": _loads(row["payload"], {})} for row in rows ] def list_audit_events(self, limit: int = 80) -> list[dict[str, Any]]: with self.connect() as connection: rows = connection.execute( """ SELECT a.id, a.event_type, a.created_at, a.payload, u.label AS user_label FROM audit_events a LEFT JOIN users u ON u.id = a.user_id ORDER BY a.created_at DESC, a.rowid DESC LIMIT ? """, (limit,), ).fetchall() return [ {**dict(row), "payload": _loads(row["payload"], {})} for row in rows ]