Files
note-zero/app/db.py
T

1117 lines
42 KiB
Python

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
]