forked from Rockachopa/Timmy-time-dashboard
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import sqlite3
|
||||
from contextlib import closing
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
@@ -39,13 +40,9 @@ def _query_database(db_path: str) -> dict:
|
||||
"""Open a database read-only and return all tables with their rows."""
|
||||
result = {"tables": {}, "error": None}
|
||||
try:
|
||||
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
||||
with closing(sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
except Exception as exc:
|
||||
result["error"] = str(exc)
|
||||
return result
|
||||
|
||||
try:
|
||||
tables = conn.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
||||
).fetchall()
|
||||
@@ -87,8 +84,6 @@ def _query_database(db_path: str) -> dict:
|
||||
}
|
||||
except Exception as exc:
|
||||
result["error"] = str(exc)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -6,8 +6,11 @@ for the Mission Control dashboard.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import sqlite3
|
||||
import time
|
||||
from contextlib import closing
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
@@ -134,13 +137,9 @@ def _check_lightning() -> DependencyStatus:
|
||||
def _check_sqlite() -> DependencyStatus:
|
||||
"""Check SQLite database status."""
|
||||
try:
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
db_path = Path(settings.repo_root) / "data" / "timmy.db"
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
with closing(sqlite3.connect(str(db_path))) as conn:
|
||||
conn.execute("SELECT 1")
|
||||
conn.close()
|
||||
|
||||
return DependencyStatus(
|
||||
name="SQLite Database",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import logging
|
||||
import sqlite3
|
||||
import uuid
|
||||
from contextlib import closing
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
@@ -101,8 +102,7 @@ class _TaskView:
|
||||
@router.get("/tasks", response_class=HTMLResponse)
|
||||
async def tasks_page(request: Request):
|
||||
"""Render the main task queue page with 3-column layout."""
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
pending = [
|
||||
_TaskView(_row_to_dict(r))
|
||||
for r in db.execute(
|
||||
@@ -121,8 +121,6 @@ async def tasks_page(request: Request):
|
||||
"SELECT * FROM tasks WHERE status IN ('completed','vetoed','failed') ORDER BY completed_at DESC LIMIT 50"
|
||||
).fetchall()
|
||||
]
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
@@ -145,13 +143,10 @@ async def tasks_page(request: Request):
|
||||
|
||||
@router.get("/tasks/pending", response_class=HTMLResponse)
|
||||
async def tasks_pending(request: Request):
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
rows = db.execute(
|
||||
"SELECT * FROM tasks WHERE status='pending_approval' ORDER BY created_at DESC"
|
||||
).fetchall()
|
||||
finally:
|
||||
db.close()
|
||||
tasks = [_TaskView(_row_to_dict(r)) for r in rows]
|
||||
parts = []
|
||||
for task in tasks:
|
||||
@@ -167,13 +162,10 @@ async def tasks_pending(request: Request):
|
||||
|
||||
@router.get("/tasks/active", response_class=HTMLResponse)
|
||||
async def tasks_active(request: Request):
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
rows = db.execute(
|
||||
"SELECT * FROM tasks WHERE status IN ('approved','running','paused') ORDER BY created_at DESC"
|
||||
).fetchall()
|
||||
finally:
|
||||
db.close()
|
||||
tasks = [_TaskView(_row_to_dict(r)) for r in rows]
|
||||
parts = []
|
||||
for task in tasks:
|
||||
@@ -189,13 +181,10 @@ async def tasks_active(request: Request):
|
||||
|
||||
@router.get("/tasks/completed", response_class=HTMLResponse)
|
||||
async def tasks_completed(request: Request):
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
rows = db.execute(
|
||||
"SELECT * FROM tasks WHERE status IN ('completed','vetoed','failed') ORDER BY completed_at DESC LIMIT 50"
|
||||
).fetchall()
|
||||
finally:
|
||||
db.close()
|
||||
tasks = [_TaskView(_row_to_dict(r)) for r in rows]
|
||||
parts = []
|
||||
for task in tasks:
|
||||
@@ -231,16 +220,13 @@ async def create_task_form(
|
||||
now = datetime.utcnow().isoformat()
|
||||
priority = priority if priority in VALID_PRIORITIES else "normal"
|
||||
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
db.execute(
|
||||
"INSERT INTO tasks (id, title, description, priority, assigned_to, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(task_id, title, description, priority, assigned_to, now),
|
||||
)
|
||||
db.commit()
|
||||
row = db.execute("SELECT * FROM tasks WHERE id=?", (task_id,)).fetchone()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
task = _TaskView(_row_to_dict(row))
|
||||
return templates.TemplateResponse(request, "partials/task_card.html", {"task": task})
|
||||
@@ -283,16 +269,13 @@ async def modify_task(
|
||||
title: str = Form(...),
|
||||
description: str = Form(""),
|
||||
):
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
db.execute(
|
||||
"UPDATE tasks SET title=?, description=? WHERE id=?",
|
||||
(title, description, task_id),
|
||||
)
|
||||
db.commit()
|
||||
row = db.execute("SELECT * FROM tasks WHERE id=?", (task_id,)).fetchone()
|
||||
finally:
|
||||
db.close()
|
||||
if not row:
|
||||
raise HTTPException(404, "Task not found")
|
||||
task = _TaskView(_row_to_dict(row))
|
||||
@@ -304,16 +287,13 @@ async def _set_status(request: Request, task_id: str, new_status: str):
|
||||
completed_at = (
|
||||
datetime.utcnow().isoformat() if new_status in ("completed", "vetoed", "failed") else None
|
||||
)
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
db.execute(
|
||||
"UPDATE tasks SET status=?, completed_at=COALESCE(?, completed_at) WHERE id=?",
|
||||
(new_status, completed_at, task_id),
|
||||
)
|
||||
db.commit()
|
||||
row = db.execute("SELECT * FROM tasks WHERE id=?", (task_id,)).fetchone()
|
||||
finally:
|
||||
db.close()
|
||||
if not row:
|
||||
raise HTTPException(404, "Task not found")
|
||||
task = _TaskView(_row_to_dict(row))
|
||||
@@ -339,8 +319,7 @@ async def api_create_task(request: Request):
|
||||
if priority not in VALID_PRIORITIES:
|
||||
priority = "normal"
|
||||
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
db.execute(
|
||||
"INSERT INTO tasks (id, title, description, priority, assigned_to, created_by, created_at) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
@@ -356,8 +335,6 @@ async def api_create_task(request: Request):
|
||||
)
|
||||
db.commit()
|
||||
row = db.execute("SELECT * FROM tasks WHERE id=?", (task_id,)).fetchone()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
return JSONResponse(_row_to_dict(row), status_code=201)
|
||||
|
||||
@@ -365,11 +342,8 @@ async def api_create_task(request: Request):
|
||||
@router.get("/api/tasks", response_class=JSONResponse)
|
||||
async def api_list_tasks():
|
||||
"""List all tasks as JSON."""
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
rows = db.execute("SELECT * FROM tasks ORDER BY created_at DESC").fetchall()
|
||||
finally:
|
||||
db.close()
|
||||
return JSONResponse([_row_to_dict(r) for r in rows])
|
||||
|
||||
|
||||
@@ -384,16 +358,13 @@ async def api_update_status(task_id: str, request: Request):
|
||||
completed_at = (
|
||||
datetime.utcnow().isoformat() if new_status in ("completed", "vetoed", "failed") else None
|
||||
)
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
db.execute(
|
||||
"UPDATE tasks SET status=?, completed_at=COALESCE(?, completed_at) WHERE id=?",
|
||||
(new_status, completed_at, task_id),
|
||||
)
|
||||
db.commit()
|
||||
row = db.execute("SELECT * FROM tasks WHERE id=?", (task_id,)).fetchone()
|
||||
finally:
|
||||
db.close()
|
||||
if not row:
|
||||
raise HTTPException(404, "Task not found")
|
||||
return JSONResponse(_row_to_dict(row))
|
||||
@@ -402,12 +373,9 @@ async def api_update_status(task_id: str, request: Request):
|
||||
@router.delete("/api/tasks/{task_id}", response_class=JSONResponse)
|
||||
async def api_delete_task(task_id: str):
|
||||
"""Delete a task."""
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
cursor = db.execute("DELETE FROM tasks WHERE id=?", (task_id,))
|
||||
db.commit()
|
||||
finally:
|
||||
db.close()
|
||||
if cursor.rowcount == 0:
|
||||
raise HTTPException(404, "Task not found")
|
||||
return JSONResponse({"success": True, "id": task_id})
|
||||
@@ -421,8 +389,7 @@ async def api_delete_task(task_id: str):
|
||||
@router.get("/api/queue/status", response_class=JSONResponse)
|
||||
async def queue_status(assigned_to: str = "default"):
|
||||
"""Return queue status for the chat panel's agent status indicator."""
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
running = db.execute(
|
||||
"SELECT * FROM tasks WHERE status='running' AND assigned_to=? LIMIT 1",
|
||||
(assigned_to,),
|
||||
@@ -431,8 +398,6 @@ async def queue_status(assigned_to: str = "default"):
|
||||
"SELECT COUNT(*) as cnt FROM tasks WHERE status IN ('pending_approval','approved') AND assigned_to=?",
|
||||
(assigned_to,),
|
||||
).fetchone()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if running:
|
||||
return JSONResponse(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import logging
|
||||
import sqlite3
|
||||
import uuid
|
||||
from contextlib import closing
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
@@ -104,14 +105,11 @@ def _query_wos(db, statuses):
|
||||
|
||||
@router.get("/work-orders/queue", response_class=HTMLResponse)
|
||||
async def work_orders_page(request: Request):
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
pending = _query_wos(db, ["submitted", "triaged"])
|
||||
active = _query_wos(db, ["approved", "in_progress"])
|
||||
completed = _query_wos(db, ["completed"])
|
||||
rejected = _query_wos(db, ["rejected"])
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
@@ -148,8 +146,7 @@ async def submit_work_order(
|
||||
priority = priority if priority in PRIORITIES else "medium"
|
||||
category = category if category in CATEGORIES else "suggestion"
|
||||
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
db.execute(
|
||||
"INSERT INTO work_orders (id, title, description, priority, category, submitter, related_files, created_at) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
@@ -157,8 +154,6 @@ async def submit_work_order(
|
||||
)
|
||||
db.commit()
|
||||
row = db.execute("SELECT * FROM work_orders WHERE id=?", (wo_id,)).fetchone()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
wo = _WOView(_row_to_dict(row))
|
||||
return templates.TemplateResponse(request, "partials/work_order_card.html", {"wo": wo})
|
||||
@@ -171,11 +166,8 @@ async def submit_work_order(
|
||||
|
||||
@router.get("/work-orders/queue/pending", response_class=HTMLResponse)
|
||||
async def pending_partial(request: Request):
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
wos = _query_wos(db, ["submitted", "triaged"])
|
||||
finally:
|
||||
db.close()
|
||||
if not wos:
|
||||
return HTMLResponse(
|
||||
'<div style="color: var(--text-muted); font-size: 0.8rem; padding: 12px 0;">'
|
||||
@@ -193,11 +185,8 @@ async def pending_partial(request: Request):
|
||||
|
||||
@router.get("/work-orders/queue/active", response_class=HTMLResponse)
|
||||
async def active_partial(request: Request):
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
wos = _query_wos(db, ["approved", "in_progress"])
|
||||
finally:
|
||||
db.close()
|
||||
if not wos:
|
||||
return HTMLResponse(
|
||||
'<div style="color: var(--text-muted); font-size: 0.8rem; padding: 12px 0;">'
|
||||
@@ -222,8 +211,7 @@ async def _update_status(request: Request, wo_id: str, new_status: str, **extra)
|
||||
completed_at = (
|
||||
datetime.utcnow().isoformat() if new_status in ("completed", "rejected") else None
|
||||
)
|
||||
db = _get_db()
|
||||
try:
|
||||
with closing(_get_db()) as db:
|
||||
sets = ["status=?", "completed_at=COALESCE(?, completed_at)"]
|
||||
vals = [new_status, completed_at]
|
||||
for col, val in extra.items():
|
||||
@@ -233,8 +221,6 @@ async def _update_status(request: Request, wo_id: str, new_status: str, **extra)
|
||||
db.execute(f"UPDATE work_orders SET {', '.join(sets)} WHERE id=?", vals)
|
||||
db.commit()
|
||||
row = db.execute("SELECT * FROM work_orders WHERE id=?", (wo_id,)).fetchone()
|
||||
finally:
|
||||
db.close()
|
||||
if not row:
|
||||
raise HTTPException(404, "Work order not found")
|
||||
wo = _WOView(_row_to_dict(row))
|
||||
|
||||
@@ -10,6 +10,7 @@ import json
|
||||
import logging
|
||||
import sqlite3
|
||||
from collections.abc import Callable, Coroutine
|
||||
from contextlib import closing
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
@@ -99,14 +100,11 @@ class EventBus:
|
||||
if self._persistence_db_path is None:
|
||||
return
|
||||
self._persistence_db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(self._persistence_db_path))
|
||||
try:
|
||||
with closing(sqlite3.connect(str(self._persistence_db_path))) as conn:
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute("PRAGMA busy_timeout=5000")
|
||||
conn.executescript(_EVENTS_SCHEMA)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def _get_persistence_conn(self) -> sqlite3.Connection | None:
|
||||
"""Get a connection to the persistence database."""
|
||||
@@ -123,6 +121,7 @@ class EventBus:
|
||||
if conn is None:
|
||||
return
|
||||
try:
|
||||
with closing(conn):
|
||||
task_id = event.data.get("task_id", "")
|
||||
agent_id = event.data.get("agent_id", "")
|
||||
conn.execute(
|
||||
@@ -142,8 +141,6 @@ class EventBus:
|
||||
conn.commit()
|
||||
except Exception as exc:
|
||||
logger.debug("Failed to persist event: %s", exc)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# ── Replay ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -170,6 +167,7 @@ class EventBus:
|
||||
return []
|
||||
|
||||
try:
|
||||
with closing(conn):
|
||||
conditions = []
|
||||
params: list = []
|
||||
|
||||
@@ -202,8 +200,6 @@ class EventBus:
|
||||
except Exception as exc:
|
||||
logger.debug("Failed to replay events: %s", exc)
|
||||
return []
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# ── Subscribe / Publish ──────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ model roles (student, teacher, judge/PRM) run on dedicated resources.
|
||||
import logging
|
||||
import sqlite3
|
||||
import threading
|
||||
from contextlib import closing
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from enum import StrEnum
|
||||
@@ -105,7 +106,7 @@ class ModelRegistry:
|
||||
def _load_from_db(self) -> None:
|
||||
"""Bootstrap cache from SQLite."""
|
||||
try:
|
||||
conn = _get_conn()
|
||||
with closing(_get_conn()) as conn:
|
||||
for row in conn.execute("SELECT * FROM custom_models WHERE active = 1").fetchall():
|
||||
self._models[row["name"]] = CustomModel(
|
||||
name=row["name"],
|
||||
@@ -121,7 +122,6 @@ class ModelRegistry:
|
||||
)
|
||||
for row in conn.execute("SELECT * FROM agent_model_assignments").fetchall():
|
||||
self._agent_assignments[row["agent_id"]] = row["model_name"]
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to load model registry from DB: %s", exc)
|
||||
|
||||
@@ -130,7 +130,7 @@ class ModelRegistry:
|
||||
def register(self, model: CustomModel) -> CustomModel:
|
||||
"""Register a new custom model."""
|
||||
with self._lock:
|
||||
conn = _get_conn()
|
||||
with closing(_get_conn()) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT OR REPLACE INTO custom_models
|
||||
@@ -152,7 +152,6 @@ class ModelRegistry:
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
self._models[model.name] = model
|
||||
logger.info("Registered model: %s (%s)", model.name, model.format.value)
|
||||
return model
|
||||
@@ -162,11 +161,10 @@ class ModelRegistry:
|
||||
with self._lock:
|
||||
if name not in self._models:
|
||||
return False
|
||||
conn = _get_conn()
|
||||
with closing(_get_conn()) as conn:
|
||||
conn.execute("DELETE FROM custom_models WHERE name = ?", (name,))
|
||||
conn.execute("DELETE FROM agent_model_assignments WHERE model_name = ?", (name,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
del self._models[name]
|
||||
# Remove any agent assignments using this model
|
||||
self._agent_assignments = {
|
||||
@@ -193,13 +191,12 @@ class ModelRegistry:
|
||||
return False
|
||||
with self._lock:
|
||||
model.active = active
|
||||
conn = _get_conn()
|
||||
with closing(_get_conn()) as conn:
|
||||
conn.execute(
|
||||
"UPDATE custom_models SET active = ? WHERE name = ?",
|
||||
(int(active), name),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
# ── Agent-model assignments ────────────────────────────────────────────
|
||||
@@ -210,7 +207,7 @@ class ModelRegistry:
|
||||
return False
|
||||
with self._lock:
|
||||
now = datetime.now(UTC).isoformat()
|
||||
conn = _get_conn()
|
||||
with closing(_get_conn()) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT OR REPLACE INTO agent_model_assignments
|
||||
@@ -220,7 +217,6 @@ class ModelRegistry:
|
||||
(agent_id, model_name, now),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
self._agent_assignments[agent_id] = model_name
|
||||
logger.info("Assigned model %s to agent %s", model_name, agent_id)
|
||||
return True
|
||||
@@ -230,13 +226,12 @@ class ModelRegistry:
|
||||
with self._lock:
|
||||
if agent_id not in self._agent_assignments:
|
||||
return False
|
||||
conn = _get_conn()
|
||||
with closing(_get_conn()) as conn:
|
||||
conn.execute(
|
||||
"DELETE FROM agent_model_assignments WHERE agent_id = ?",
|
||||
(agent_id,),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
del self._agent_assignments[agent_id]
|
||||
return True
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ Default is always True. The owner changes this intentionally.
|
||||
|
||||
import sqlite3
|
||||
import uuid
|
||||
from contextlib import closing
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
@@ -96,7 +97,7 @@ def create_item(
|
||||
created_at=datetime.now(UTC),
|
||||
status="pending",
|
||||
)
|
||||
conn = _get_conn(db_path)
|
||||
with closing(_get_conn(db_path)) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO approval_items
|
||||
@@ -114,62 +115,55 @@ def create_item(
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return item
|
||||
|
||||
|
||||
def list_pending(db_path: Path = _DEFAULT_DB) -> list[ApprovalItem]:
|
||||
"""Return all pending approval items, newest first."""
|
||||
conn = _get_conn(db_path)
|
||||
with closing(_get_conn(db_path)) as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM approval_items WHERE status = 'pending' ORDER BY created_at DESC"
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [_row_to_item(r) for r in rows]
|
||||
|
||||
|
||||
def list_all(db_path: Path = _DEFAULT_DB) -> list[ApprovalItem]:
|
||||
"""Return all approval items regardless of status, newest first."""
|
||||
conn = _get_conn(db_path)
|
||||
with closing(_get_conn(db_path)) as conn:
|
||||
rows = conn.execute("SELECT * FROM approval_items ORDER BY created_at DESC").fetchall()
|
||||
conn.close()
|
||||
return [_row_to_item(r) for r in rows]
|
||||
|
||||
|
||||
def get_item(item_id: str, db_path: Path = _DEFAULT_DB) -> ApprovalItem | None:
|
||||
conn = _get_conn(db_path)
|
||||
with closing(_get_conn(db_path)) as conn:
|
||||
row = conn.execute("SELECT * FROM approval_items WHERE id = ?", (item_id,)).fetchone()
|
||||
conn.close()
|
||||
return _row_to_item(row) if row else None
|
||||
|
||||
|
||||
def approve(item_id: str, db_path: Path = _DEFAULT_DB) -> ApprovalItem | None:
|
||||
"""Mark an approval item as approved."""
|
||||
conn = _get_conn(db_path)
|
||||
with closing(_get_conn(db_path)) as conn:
|
||||
conn.execute("UPDATE approval_items SET status = 'approved' WHERE id = ?", (item_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return get_item(item_id, db_path)
|
||||
|
||||
|
||||
def reject(item_id: str, db_path: Path = _DEFAULT_DB) -> ApprovalItem | None:
|
||||
"""Mark an approval item as rejected."""
|
||||
conn = _get_conn(db_path)
|
||||
with closing(_get_conn(db_path)) as conn:
|
||||
conn.execute("UPDATE approval_items SET status = 'rejected' WHERE id = ?", (item_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return get_item(item_id, db_path)
|
||||
|
||||
|
||||
def expire_old(db_path: Path = _DEFAULT_DB) -> int:
|
||||
"""Auto-expire pending items older than EXPIRY_DAYS. Returns count removed."""
|
||||
cutoff = (datetime.now(UTC) - timedelta(days=_EXPIRY_DAYS)).isoformat()
|
||||
conn = _get_conn(db_path)
|
||||
with closing(_get_conn(db_path)) as conn:
|
||||
cursor = conn.execute(
|
||||
"DELETE FROM approval_items WHERE status = 'pending' AND created_at < ?",
|
||||
(cutoff,),
|
||||
)
|
||||
conn.commit()
|
||||
count = cursor.rowcount
|
||||
conn.close()
|
||||
return count
|
||||
|
||||
@@ -10,6 +10,7 @@ regenerates the briefing every 6 hours.
|
||||
|
||||
import logging
|
||||
import sqlite3
|
||||
from contextlib import closing
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
@@ -74,7 +75,7 @@ def _get_cache_conn(db_path: Path = _DEFAULT_DB) -> sqlite3.Connection:
|
||||
|
||||
|
||||
def _save_briefing(briefing: Briefing, db_path: Path = _DEFAULT_DB) -> None:
|
||||
conn = _get_cache_conn(db_path)
|
||||
with closing(_get_cache_conn(db_path)) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO briefings (generated_at, period_start, period_end, summary)
|
||||
@@ -88,14 +89,12 @@ def _save_briefing(briefing: Briefing, db_path: Path = _DEFAULT_DB) -> None:
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def _load_latest(db_path: Path = _DEFAULT_DB) -> Briefing | None:
|
||||
"""Load the most-recently cached briefing, or None if there is none."""
|
||||
conn = _get_cache_conn(db_path)
|
||||
with closing(_get_cache_conn(db_path)) as conn:
|
||||
row = conn.execute("SELECT * FROM briefings ORDER BY generated_at DESC LIMIT 1").fetchone()
|
||||
conn.close()
|
||||
if row is None:
|
||||
return None
|
||||
return Briefing(
|
||||
@@ -129,7 +128,7 @@ def _gather_swarm_summary(since: datetime) -> str:
|
||||
return "No swarm activity recorded yet."
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(str(swarm_db))
|
||||
with closing(sqlite3.connect(str(swarm_db))) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
since_iso = since.isoformat()
|
||||
@@ -149,8 +148,6 @@ def _gather_swarm_summary(since: datetime) -> str:
|
||||
(since_iso,),
|
||||
).fetchone()["c"]
|
||||
|
||||
conn.close()
|
||||
|
||||
parts = []
|
||||
if completed:
|
||||
parts.append(f"{completed} task(s) completed")
|
||||
|
||||
@@ -25,6 +25,7 @@ import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
import uuid
|
||||
from contextlib import closing
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
@@ -163,7 +164,7 @@ def _bridge_to_work_order(title: str, body: str, category: str) -> None:
|
||||
try:
|
||||
db_path = Path(settings.repo_root) / "data" / "work_orders.db"
|
||||
db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
with closing(sqlite3.connect(str(db_path))) as conn:
|
||||
conn.execute(
|
||||
"""CREATE TABLE IF NOT EXISTS work_orders (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -193,7 +194,6 @@ def _bridge_to_work_order(title: str, body: str, category: str) -> None:
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
logger.debug("Work order bridge failed: %s", exc)
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import hashlib
|
||||
import json
|
||||
import logging
|
||||
import sqlite3
|
||||
from contextlib import closing
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
@@ -113,7 +114,7 @@ class SemanticMemory:
|
||||
def _init_db(self) -> None:
|
||||
"""Initialize SQLite with vector storage."""
|
||||
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(self.db_path))
|
||||
with closing(sqlite3.connect(str(self.db_path))) as conn:
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS chunks (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -126,7 +127,6 @@ class SemanticMemory:
|
||||
""")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_chunks_source ON chunks(source)")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def index_file(self, filepath: Path) -> int:
|
||||
"""Index a single file into semantic memory."""
|
||||
@@ -136,14 +136,13 @@ class SemanticMemory:
|
||||
content = filepath.read_text()
|
||||
file_hash = hashlib.md5(content.encode()).hexdigest()
|
||||
|
||||
with closing(sqlite3.connect(str(self.db_path))) as conn:
|
||||
# Check if already indexed with same hash
|
||||
conn = sqlite3.connect(str(self.db_path))
|
||||
cursor = conn.execute(
|
||||
"SELECT source_hash FROM chunks WHERE source = ? LIMIT 1", (str(filepath),)
|
||||
)
|
||||
existing = cursor.fetchone()
|
||||
if existing and existing[0] == file_hash:
|
||||
conn.close()
|
||||
return 0 # Already indexed
|
||||
|
||||
# Delete old chunks for this file
|
||||
@@ -168,7 +167,6 @@ class SemanticMemory:
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.info("SemanticMemory: Indexed %s (%d chunks)", filepath.name, len(chunks))
|
||||
return len(chunks)
|
||||
@@ -222,14 +220,12 @@ class SemanticMemory:
|
||||
"""Search for relevant memory chunks."""
|
||||
query_embedding = embed_text(query)
|
||||
|
||||
conn = sqlite3.connect(str(self.db_path))
|
||||
with closing(sqlite3.connect(str(self.db_path))) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
# Get all chunks (in production, use vector index)
|
||||
rows = conn.execute("SELECT source, content, embedding FROM chunks").fetchall()
|
||||
|
||||
conn.close()
|
||||
|
||||
# Calculate similarities
|
||||
scored = []
|
||||
for row in rows:
|
||||
@@ -268,10 +264,9 @@ class SemanticMemory:
|
||||
|
||||
def stats(self) -> dict:
|
||||
"""Get indexing statistics."""
|
||||
conn = sqlite3.connect(str(self.db_path))
|
||||
with closing(sqlite3.connect(str(self.db_path))) as conn:
|
||||
cursor = conn.execute("SELECT COUNT(*), COUNT(DISTINCT source) FROM chunks")
|
||||
total_chunks, total_files = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
"total_chunks": total_chunks,
|
||||
|
||||
@@ -21,6 +21,7 @@ import logging
|
||||
import random
|
||||
import sqlite3
|
||||
import uuid
|
||||
from contextlib import closing
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from difflib import SequenceMatcher
|
||||
@@ -289,19 +290,17 @@ class ThinkingEngine:
|
||||
|
||||
def get_recent_thoughts(self, limit: int = 20) -> list[Thought]:
|
||||
"""Retrieve the most recent thoughts."""
|
||||
conn = _get_conn(self._db_path)
|
||||
with closing(_get_conn(self._db_path)) as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM thoughts ORDER BY created_at DESC LIMIT ?",
|
||||
(limit,),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [_row_to_thought(r) for r in rows]
|
||||
|
||||
def get_thought(self, thought_id: str) -> Thought | None:
|
||||
"""Retrieve a single thought by ID."""
|
||||
conn = _get_conn(self._db_path)
|
||||
with closing(_get_conn(self._db_path)) as conn:
|
||||
row = conn.execute("SELECT * FROM thoughts WHERE id = ?", (thought_id,)).fetchone()
|
||||
conn.close()
|
||||
return _row_to_thought(row) if row else None
|
||||
|
||||
def get_thought_chain(self, thought_id: str, max_depth: int = 20) -> list[Thought]:
|
||||
@@ -311,8 +310,8 @@ class ThinkingEngine:
|
||||
"""
|
||||
chain = []
|
||||
current_id: str | None = thought_id
|
||||
conn = _get_conn(self._db_path)
|
||||
|
||||
with closing(_get_conn(self._db_path)) as conn:
|
||||
for _ in range(max_depth):
|
||||
if not current_id:
|
||||
break
|
||||
@@ -322,15 +321,13 @@ class ThinkingEngine:
|
||||
chain.append(_row_to_thought(row))
|
||||
current_id = row["parent_id"]
|
||||
|
||||
conn.close()
|
||||
chain.reverse() # Chronological order
|
||||
return chain
|
||||
|
||||
def count_thoughts(self) -> int:
|
||||
"""Return total number of stored thoughts."""
|
||||
conn = _get_conn(self._db_path)
|
||||
with closing(_get_conn(self._db_path)) as conn:
|
||||
count = conn.execute("SELECT COUNT(*) as c FROM thoughts").fetchone()["c"]
|
||||
conn.close()
|
||||
return count
|
||||
|
||||
def prune_old_thoughts(self, keep_days: int = 90, keep_min: int = 200) -> int:
|
||||
@@ -338,7 +335,7 @@ class ThinkingEngine:
|
||||
|
||||
Returns the number of deleted rows.
|
||||
"""
|
||||
conn = _get_conn(self._db_path)
|
||||
with closing(_get_conn(self._db_path)) as conn:
|
||||
try:
|
||||
total = conn.execute("SELECT COUNT(*) as c FROM thoughts").fetchone()["c"]
|
||||
if total <= keep_min:
|
||||
@@ -355,8 +352,6 @@ class ThinkingEngine:
|
||||
except Exception as exc:
|
||||
logger.warning("Thought pruning failed: %s", exc)
|
||||
return 0
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# ── Private helpers ──────────────────────────────────────────────────
|
||||
|
||||
@@ -576,12 +571,11 @@ class ThinkingEngine:
|
||||
# Thought count today (cheap DB query)
|
||||
try:
|
||||
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
conn = _get_conn(self._db_path)
|
||||
with closing(_get_conn(self._db_path)) as conn:
|
||||
count = conn.execute(
|
||||
"SELECT COUNT(*) as c FROM thoughts WHERE created_at >= ?",
|
||||
(today_start.isoformat(),),
|
||||
).fetchone()["c"]
|
||||
conn.close()
|
||||
parts.append(f"Thoughts today: {count}")
|
||||
except Exception as exc:
|
||||
logger.debug("Thought count query failed: %s", exc)
|
||||
@@ -934,16 +928,21 @@ class ThinkingEngine:
|
||||
created_at=datetime.now(UTC).isoformat(),
|
||||
)
|
||||
|
||||
conn = _get_conn(self._db_path)
|
||||
with closing(_get_conn(self._db_path)) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO thoughts (id, content, seed_type, parent_id, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(thought.id, thought.content, thought.seed_type, thought.parent_id, thought.created_at),
|
||||
(
|
||||
thought.id,
|
||||
thought.content,
|
||||
thought.seed_type,
|
||||
thought.parent_id,
|
||||
thought.created_at,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return thought
|
||||
|
||||
def _log_event(self, thought: Thought) -> None:
|
||||
|
||||
@@ -6,7 +6,9 @@ being told about it in the system prompt.
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import sqlite3
|
||||
import sys
|
||||
from contextlib import closing
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -174,11 +176,9 @@ def get_memory_status() -> dict[str, Any]:
|
||||
# Tier 3: Semantic memory row count
|
||||
tier3_info: dict[str, Any] = {"available": False}
|
||||
try:
|
||||
import sqlite3
|
||||
|
||||
sem_db = repo_root / "data" / "memory.db"
|
||||
if sem_db.exists():
|
||||
conn = sqlite3.connect(str(sem_db))
|
||||
with closing(sqlite3.connect(str(sem_db))) as conn:
|
||||
row = conn.execute(
|
||||
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='chunks'"
|
||||
).fetchone()
|
||||
@@ -186,7 +186,6 @@ def get_memory_status() -> dict[str, Any]:
|
||||
count = conn.execute("SELECT COUNT(*) FROM chunks").fetchone()
|
||||
tier3_info["available"] = True
|
||||
tier3_info["vector_count"] = count[0] if count else 0
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
logger.debug("Memory status query failed: %s", exc)
|
||||
pass
|
||||
@@ -194,12 +193,10 @@ def get_memory_status() -> dict[str, Any]:
|
||||
# Self-coding journal stats
|
||||
journal_info: dict[str, Any] = {"available": False}
|
||||
try:
|
||||
import sqlite3 as _sqlite3
|
||||
|
||||
journal_db = repo_root / "data" / "self_coding.db"
|
||||
if journal_db.exists():
|
||||
conn = _sqlite3.connect(str(journal_db))
|
||||
conn.row_factory = _sqlite3.Row
|
||||
with closing(sqlite3.connect(str(journal_db))) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute(
|
||||
"SELECT outcome, COUNT(*) as cnt FROM modification_journal GROUP BY outcome"
|
||||
).fetchall()
|
||||
@@ -213,7 +210,6 @@ def get_memory_status() -> dict[str, Any]:
|
||||
"failures": counts.get("failure", 0),
|
||||
"success_rate": round(counts.get("success", 0) / total, 2) if total else 0,
|
||||
}
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
logger.debug("Journal stats query failed: %s", exc)
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user