#!/usr/bin/env python3 """Live task tracking with owner-based handling. Completed/cancelled tasks are removed from this table; their history lives in the other memory layers. """ import sys from datetime import datetime, timezone from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from db import get_conn def _now() -> str: return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds") def _row(r): return dict(r) if r else None _VALID_OWNERS = ("agent", "user") def _validate_owner(owner: str | None) -> dict | None: """Return an error dict if owner is set to an invalid value, else None.""" if owner is not None and owner not in _VALID_OWNERS: return {"error": f"invalid owner '{owner}'. Must be one of: {', '.join(_VALID_OWNERS)}"} return None def add_task(owner: str, content: str, due_at: str | None = None, notes: str | None = None) -> dict: if err := _validate_owner(owner): return err with get_conn() as conn: cur = conn.execute( "INSERT INTO tasks (owner, content, due_at, notes) VALUES (?, ?, ?, ?)", (owner, content, due_at, notes), ) return _row(conn.execute("SELECT * FROM tasks WHERE id=?", (cur.lastrowid,)).fetchone()) def list_tasks(owner: str | None = None, due_before: str | None = None) -> list[dict]: sql = "SELECT * FROM tasks WHERE 1=1" params = [] if owner: sql += " AND owner=?"; params.append(owner) if due_before: sql += " AND due_at IS NOT NULL AND due_at < ?"; params.append(due_before) sql += " ORDER BY due_at IS NULL, due_at" with get_conn() as conn: return [dict(r) for r in conn.execute(sql, params).fetchall()] def update_task(task_id: int, content: str | None = None, due_at: str | None = None, owner: str | None = None, notes: str | None = None) -> dict: if err := _validate_owner(owner): return err sets, params = [], [] for col, val in (("content", content), ("due_at", due_at), ("owner", owner), ("notes", notes)): if val is not None: sets.append(f"{col}=?"); params.append(val) if not sets: return {"error": "no fields to update"} sets.append("updated_at=?"); params.append(_now()) params.append(task_id) with get_conn() as conn: if not conn.execute("SELECT id FROM tasks WHERE id=?", (task_id,)).fetchone(): return {"error": f"no task with id {task_id}"} conn.execute(f"UPDATE tasks SET {', '.join(sets)} WHERE id=?", params) return _row(conn.execute("SELECT * FROM tasks WHERE id=?", (task_id,)).fetchone()) def remove_task(task_id: int) -> dict: with get_conn() as conn: row = conn.execute("SELECT * FROM tasks WHERE id=?", (task_id,)).fetchone() if not row: return {"error": f"no task with id {task_id}"} conn.execute("DELETE FROM tasks WHERE id=?", (task_id,)) return {"deleted": _row(row)} def record_nudge(task_id: int) -> dict: with get_conn() as conn: if not conn.execute("SELECT id FROM tasks WHERE id=?", (task_id,)).fetchone(): return {"error": f"no task with id {task_id}"} conn.execute("UPDATE tasks SET last_nudged_at=? WHERE id=?", (_now(), task_id)) return _row(conn.execute("SELECT * FROM tasks WHERE id=?", (task_id,)).fetchone())