diff --git a/src/blackboard/operations.py b/src/blackboard/operations.py index 93f1350..7c1b8c0 100644 --- a/src/blackboard/operations.py +++ b/src/blackboard/operations.py @@ -682,3 +682,134 @@ class Blackboard: finally: conn.close() + + # ── Checkpoint CRUD(M3) ── + + def create_checkpoint( + self, + task_id: str, + cp_type: str, + title: str, + payload: dict, + description: str | None = None, + checkpoint_id: str | None = None, + ) -> dict: + """创建 Checkpoint""" + import uuid + cp_id = checkpoint_id or f"cp-{uuid.uuid4().hex[:8]}" + conn = self._conn() + try: + conn.execute("BEGIN IMMEDIATE") + conn.execute( + "INSERT INTO checkpoints (id, task_id, type, title, description, payload) " + "VALUES (?,?,?,?,?,?)", + (cp_id, task_id, cp_type, title, description, json.dumps(payload)), + ) + conn.execute( + "INSERT INTO events (task_id, agent, event_type, detail) VALUES (?,?,?,?)", + (task_id, "daemon", "checkpoint_created", + json.dumps({"checkpoint_id": cp_id, "type": cp_type, "title": title})), + ) + conn.commit() + return {"id": cp_id, "task_id": task_id, "type": cp_type, + "title": title, "status": "pending"} + finally: + conn.close() + + def list_checkpoints(self, task_id: str) -> list[dict]: + """列出 task 的所有 checkpoint""" + conn = self._conn() + try: + rows = conn.execute( + "SELECT * FROM checkpoints WHERE task_id=? ORDER BY created_at", + (task_id,), + ).fetchall() + return [dict(r) for r in rows] + finally: + conn.close() + + def get_checkpoint(self, checkpoint_id: str) -> dict | None: + """获取单个 checkpoint""" + conn = self._conn() + try: + row = conn.execute( + "SELECT * FROM checkpoints WHERE id=?", (checkpoint_id,) + ).fetchone() + return dict(row) if row else None + finally: + conn.close() + + def resolve_checkpoint( + self, + checkpoint_id: str, + action: str, + resolved_by: str = "user", + note: str | None = None, + ) -> dict | None: + """通过/驳回 checkpoint,并自动推进 task 状态 + + approve: verify → done, decision/action → working + reject: 一律 → working + """ + conn = self._conn() + try: + conn.execute("BEGIN IMMEDIATE") + row = conn.execute( + "SELECT * FROM checkpoints WHERE id=?", (checkpoint_id,) + ).fetchone() + if not row: + return None + cp = dict(row) + if cp["status"] != "pending": + return {"error": f"Checkpoint already {cp['status']}"} + + # 更新 checkpoint 状态 + new_status = "approved" if action == "approve" else "rejected" + conn.execute( + "UPDATE checkpoints SET status=?, resolved_at=datetime('now'), " + "resolved_by=?, resolve_note=? WHERE id=?", + (new_status, resolved_by, note, checkpoint_id), + ) + + # 推进 task 状态 + task_id = cp["task_id"] + cp_type = cp["type"] + task_row = conn.execute( + "SELECT status FROM tasks WHERE id=?", (task_id,) + ).fetchone() + if not task_row: + conn.commit() + return {"error": "Task not found"} + + task_status = task_row["status"] + if action == "approve": + if cp_type == "verify": + new_task_status = "done" + else: + new_task_status = "working" + else: + new_task_status = "working" + + conn.execute( + "UPDATE tasks SET status=?, updated_at=datetime('now') WHERE id=?", + (new_task_status, task_id), + ) + conn.execute( + "INSERT INTO events (task_id, agent, event_type, detail) VALUES (?,?,?,?)", + (task_id, resolved_by, f"checkpoint_{new_status}", + json.dumps({ + "checkpoint_id": checkpoint_id, + "action": action, + "task_status": new_task_status, + "note": note, + })), + ) + conn.commit() + return { + "checkpoint_id": checkpoint_id, + "checkpoint_status": new_status, + "task_id": task_id, + "task_status": new_task_status, + } + finally: + conn.close()