auto-sync: 2026-05-17 00:34:18
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
"""黑板数据库连接管理(per-project SQLite)"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def init_db(db_path: Path) -> None:
|
||||
"""初始化数据库,创建全部表"""
|
||||
db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = _connect(db_path)
|
||||
try:
|
||||
conn.executescript(SCHEMA_SQL)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_connection(db_path: Path) -> sqlite3.Connection:
|
||||
"""获取数据库连接(WAL + busy_timeout + foreign_keys)"""
|
||||
return _connect(db_path)
|
||||
|
||||
|
||||
def _connect(db_path: Path) -> sqlite3.Connection:
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute("PRAGMA foreign_keys=ON")
|
||||
conn.execute("PRAGMA busy_timeout=5000")
|
||||
return conn
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 状态机
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
VALID_STATUSES = frozenset({
|
||||
"pending", "claimed", "working", "review",
|
||||
"done", "failed", "blocked", "cancelled",
|
||||
})
|
||||
|
||||
TERMINAL_STATUSES = frozenset({"done", "cancelled"})
|
||||
|
||||
VALID_TRANSITIONS = {
|
||||
"pending": {"claimed", "cancelled"},
|
||||
"claimed": {"working", "pending", "cancelled"},
|
||||
"working": {"review", "blocked", "failed", "cancelled"},
|
||||
"review": {"done", "pending", "failed", "cancelled"},
|
||||
"blocked": {"pending", "cancelled"},
|
||||
"done": set(),
|
||||
"failed": {"pending"},
|
||||
"cancelled": set(),
|
||||
}
|
||||
|
||||
COMMENT_TYPES = frozenset({
|
||||
"general", "handoff", "observation", "rebuttal",
|
||||
"rebuttal_response", "debate_argument", "debate_rebuttal", "debate_judgment",
|
||||
})
|
||||
|
||||
SEVERITY_LEVELS = frozenset({"blocking", "warning", "info", "audit"})
|
||||
|
||||
EVENT_TYPES = frozenset({
|
||||
"task_created", "task_claimed", "task_started", "task_completed",
|
||||
"task_failed", "task_blocked", "task_unblocked", "task_reviewed",
|
||||
"task_cancelled", "task_retried",
|
||||
"comment_added", "output_written", "observation_added", "decision_recorded",
|
||||
"agent_spawned", "agent_completed", "agent_zombie_detected",
|
||||
"session_spawned", "session_archived", "session_cleanup",
|
||||
"daemon_tick", "daemon_manual_tick",
|
||||
})
|
||||
|
||||
OUTPUT_TYPES = frozenset({"code", "document", "data", "config", "other"})
|
||||
|
||||
REVIEW_TYPES = frozenset({"plan_review", "output_review", "guardrail", "final_review"})
|
||||
VERDICT_TYPES = frozenset({"approved", "rejected", "needs_revision"})
|
||||
|
||||
EXPERIENCE_SOURCES = frozenset({
|
||||
"task_completion", "error_correction", "review_finding", "manual",
|
||||
})
|
||||
EXPERIENCE_CATEGORIES = frozenset({
|
||||
"pitfall", "best_practice", "pattern", "anti_pattern",
|
||||
})
|
||||
EXPERIENCE_STATUSES = frozenset({"draft", "active", "deprecated"})
|
||||
|
||||
ATTEMPT_OUTCOMES = frozenset({
|
||||
"completed", "blocked", "crashed", "timed_out", "spawn_failed", "reclaimed",
|
||||
})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Schema SQL
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SCHEMA_SQL = """
|
||||
-- ===== 任务表 =====
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
CHECK (status IN ('pending','claimed','working','review','done','failed','blocked','cancelled')),
|
||||
|
||||
assignee TEXT,
|
||||
assigned_by TEXT,
|
||||
|
||||
depends_on TEXT,
|
||||
parent_task TEXT,
|
||||
|
||||
priority INTEGER NOT NULL DEFAULT 5,
|
||||
task_type TEXT,
|
||||
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
claimed_at TEXT,
|
||||
started_at TEXT,
|
||||
completed_at TEXT,
|
||||
deadline TEXT,
|
||||
|
||||
retry_count INTEGER NOT NULL DEFAULT 0,
|
||||
max_retries INTEGER NOT NULL DEFAULT 2,
|
||||
|
||||
must_haves TEXT,
|
||||
risk_level TEXT DEFAULT 'standard',
|
||||
estimated_duration_minutes INTEGER,
|
||||
|
||||
escalated INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task);
|
||||
|
||||
-- ===== 评论线程表 =====
|
||||
CREATE TABLE IF NOT EXISTS comments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
task_id TEXT NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
comment_type TEXT NOT NULL DEFAULT 'general',
|
||||
body TEXT NOT NULL,
|
||||
mentions TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (task_id) REFERENCES tasks(id),
|
||||
CHECK (comment_type IN ('general','handoff','observation','rebuttal','rebuttal_response','debate_argument','debate_rebuttal','debate_judgment'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_task ON comments(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_type ON comments(task_id, comment_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_author ON comments(author);
|
||||
|
||||
-- ===== 产出表 =====
|
||||
CREATE TABLE IF NOT EXISTS outputs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
task_id TEXT NOT NULL,
|
||||
agent TEXT NOT NULL,
|
||||
output_type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
content_path TEXT,
|
||||
summary TEXT,
|
||||
metadata TEXT,
|
||||
attempt_number INTEGER DEFAULT 1,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (task_id) REFERENCES tasks(id),
|
||||
CHECK (output_type IN ('code','document','data','config','other'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_outputs_task ON outputs(task_id);
|
||||
|
||||
-- ===== 决策记录表 =====
|
||||
CREATE TABLE IF NOT EXISTS decisions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
task_id TEXT NOT NULL,
|
||||
decider TEXT NOT NULL,
|
||||
decision TEXT NOT NULL,
|
||||
rationale TEXT NOT NULL,
|
||||
alternatives TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (task_id) REFERENCES tasks(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_decisions_task ON decisions(task_id);
|
||||
|
||||
-- ===== 观察表 =====
|
||||
CREATE TABLE IF NOT EXISTS observations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
task_id TEXT NOT NULL,
|
||||
observer TEXT NOT NULL,
|
||||
severity TEXT NOT NULL DEFAULT 'info',
|
||||
CHECK (severity IN ('blocking','warning','info','audit')),
|
||||
body TEXT NOT NULL,
|
||||
resolved_by TEXT,
|
||||
resolved_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (task_id) REFERENCES tasks(id)
|
||||
);
|
||||
|
||||
-- ===== 事件日志 =====
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
task_id TEXT,
|
||||
agent TEXT,
|
||||
event_type TEXT NOT NULL,
|
||||
detail TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_events_task ON events(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_events_time ON events(created_at);
|
||||
|
||||
-- ===== Agent 注册表 =====
|
||||
CREATE TABLE IF NOT EXISTS agents (
|
||||
agent_id TEXT PRIMARY KEY,
|
||||
role TEXT,
|
||||
current_status TEXT DEFAULT 'idle',
|
||||
current_task TEXT,
|
||||
last_active TEXT,
|
||||
capabilities TEXT
|
||||
);
|
||||
|
||||
-- ===== 任务尝试记录 =====
|
||||
CREATE TABLE IF NOT EXISTS task_attempts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
task_id TEXT NOT NULL,
|
||||
attempt_number INTEGER NOT NULL,
|
||||
agent TEXT NOT NULL,
|
||||
outcome TEXT NOT NULL,
|
||||
CHECK (outcome IN ('completed','blocked','crashed','timed_out','spawn_failed','reclaimed')),
|
||||
exit_code INTEGER,
|
||||
log_path TEXT,
|
||||
summary TEXT,
|
||||
metadata TEXT,
|
||||
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
completed_at TEXT,
|
||||
FOREIGN KEY (task_id) REFERENCES tasks(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_attempts_task ON task_attempts(task_id);
|
||||
|
||||
-- ===== 评审表 =====
|
||||
CREATE TABLE IF NOT EXISTS reviews (
|
||||
id TEXT PRIMARY KEY,
|
||||
task_id TEXT NOT NULL,
|
||||
output_id TEXT,
|
||||
reviewer TEXT NOT NULL,
|
||||
review_type TEXT NOT NULL,
|
||||
CHECK (review_type IN ('plan_review','output_review','guardrail','final_review')),
|
||||
verdict TEXT NOT NULL,
|
||||
CHECK (verdict IN ('approved','rejected','needs_revision')),
|
||||
confidence REAL,
|
||||
round INTEGER NOT NULL DEFAULT 1,
|
||||
max_rounds INTEGER NOT NULL DEFAULT 3,
|
||||
consensus_reached INTEGER DEFAULT 0,
|
||||
summary TEXT NOT NULL,
|
||||
detail_path TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (task_id) REFERENCES tasks(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_reviews_task ON reviews(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_reviews_output ON reviews(output_id);
|
||||
|
||||
-- ===== 经验表 =====
|
||||
CREATE TABLE IF NOT EXISTS experiences (
|
||||
experience_id TEXT PRIMARY KEY,
|
||||
source TEXT NOT NULL,
|
||||
CHECK (source IN ('task_completion','error_correction','review_finding','manual')),
|
||||
task_id TEXT,
|
||||
summary TEXT NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
CHECK (category IN ('pitfall','best_practice','pattern','anti_pattern')),
|
||||
confidence REAL DEFAULT 0.8,
|
||||
status TEXT DEFAULT 'active',
|
||||
CHECK (status IN ('draft','active','deprecated')),
|
||||
skill_id TEXT,
|
||||
usage_count INTEGER DEFAULT 0,
|
||||
last_used_at TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
created_by TEXT NOT NULL,
|
||||
updated_at TEXT,
|
||||
deprecated_reason TEXT
|
||||
);
|
||||
|
||||
-- ===== 经验标签关联表 =====
|
||||
CREATE TABLE IF NOT EXISTS experience_tags (
|
||||
experience_id TEXT NOT NULL REFERENCES experiences(experience_id),
|
||||
tag TEXT NOT NULL,
|
||||
PRIMARY KEY (experience_id, tag)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_exptags_tag ON experience_tags(tag);
|
||||
"""
|
||||
Reference in New Issue
Block a user