diff options
Diffstat (limited to 'mcp/senia/tasks.py')
| -rw-r--r-- | mcp/senia/tasks.py | 93 |
1 files changed, 93 insertions, 0 deletions
diff --git a/mcp/senia/tasks.py b/mcp/senia/tasks.py new file mode 100644 index 0000000..1bf901f --- /dev/null +++ b/mcp/senia/tasks.py @@ -0,0 +1,93 @@ +#!/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()) |
