"""雅文内容创造工作台 - 数据模型(SQLModel / SQLite)""" from datetime import datetime from typing import Optional from sqlalchemy import UniqueConstraint from sqlmodel import SQLModel, Field, JSON, Column def now() -> str: return datetime.now().strftime("%Y-%m-%d %H:%M:%S") # ---------- 组织 ---------- class Organization(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True, unique=True) slug: str = Field(index=True, unique=True) enabled: bool = True created_at: str = Field(default_factory=now) # ---------- 内容标签 ---------- class ContentTag(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True, unique=True) # 标签名:心法/技法… category: str = "内容" # 大类:结构/内容/技法/产品/表现/辅助 definition: str = "" # 一句话定义 usage: str = "" # 典型用法 is_core: bool = False # ★核心 enabled: bool = True # 停用后不再出现在选项里 organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "global" # private|shared|global # ---------- 模型 Provider ---------- class ModelProvider(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str base_url: str api_key: str = "" # 本地 SQLite 明文存储(本机单用户) models: list = Field(default_factory=list, sa_column=Column(JSON)) default_model: str = "" builtin: bool = False note: str = "" enabled: bool = True # Provider 默认按组织共享;保留 0 代表系统级 Provider。 organization_id: int = Field(default=1, index=True) class ModelCatalog(SQLModel, table=True): """面向用户的模型目录;Provider 凭据和目录能力完全分离。""" id: Optional[int] = Field(default=None, primary_key=True) provider_id: int = Field(index=True) model_name: str = Field(index=True) display_name: str = "" capabilities: list = Field(default_factory=list, sa_column=Column(JSON)) capability_status: str = "untested" # untested|supported|partial|blocked rate_version: str = "v1" input_rate: float = 0.0 output_rate: float = 0.0 enabled: bool = True builtin: bool = False created_at: str = Field(default_factory=now) updated_at: str = Field(default_factory=now) # ---------- 系统设置 ---------- class AppSetting(SQLModel, table=True): key: str = Field(primary_key=True) value: str = "" class AuditLog(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) actor_user_id: int = Field(default=0, index=True) actor_display_name: str = "" action: str = Field(index=True) resource: str = Field(index=True) result: str = "success" created_at: str = Field(default_factory=now, index=True) class SystemLog(SQLModel, table=True): """应用运行日志,与管理员操作审计严格分离。""" id: Optional[int] = Field(default=None, primary_key=True) level: str = Field(default="INFO", index=True) source: str = Field(default="app", index=True) message: str = "" trace_id: str = Field(default="", index=True) status: str = Field(default="ok", index=True) created_at: str = Field(default_factory=now, index=True) # ---------- 知识库 ---------- class KnowledgeBase(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True, unique=True) desc: str = "" source_dir: str = "" # KB_DIR/_/ index_status: str = "empty" # empty|indexing|ready|needs_reindex|error file_count: int = 0 chunk_count: int = 0 chunk_target: int = 0 # 分段目标长度:0=自动(500);2200=长文聚合;自定义任意 120~8000 chunk_mode: str = "auto" # auto=按长度聚合 | heading=按标题切分 organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "global" # private|shared|global created_at: str = Field(default_factory=now) class KnowledgeBaseGrant(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) kb_id: int = Field(index=True) user_id: int = Field(index=True) permission: str = "use" # read|use|manage|export enabled: bool = True created_at: str = Field(default_factory=now) class KnowledgeCategory(SQLModel, table=True): """知识库内的无限层级分类树。""" id: Optional[int] = Field(default=None, primary_key=True) kb_id: int = Field(index=True) parent_id: Optional[int] = Field(default=None, index=True) name: str desc: str = "" sort_order: int = 0 created_at: str = Field(default_factory=now) class KnowledgeFile(SQLModel, table=True): """源文件与分类的关系;物理文件仍保存在知识库目录内。""" id: Optional[int] = Field(default=None, primary_key=True) kb_id: int = Field(index=True) file_name: str = Field(index=True) category_id: Optional[int] = Field(default=None, index=True) ai_category_id: Optional[int] = Field(default=None, index=True) classification_status: str = "unclassified" # unclassified|ai_suggested|human_confirmed ai_confidence: float = 0.0 ai_reason: str = "" content_hash: str = Field(default="", index=True) # 源文件内容指纹,重建索引后保持可追溯 revision: int = 1 # 同名文件内容变化时递增 created_at: str = Field(default_factory=now) # ---------- 知识片段消费记录 ---------- class KnowledgeUsage(SQLModel, table=True): """同一个知识片段仅对消费它的 Agent 锁定,其他 Agent 不受影响。""" __table_args__ = ( UniqueConstraint("agent_id", "kb_id", "chunk_id", name="uq_knowledge_usage_agent_chunk"), ) id: Optional[int] = Field(default=None, primary_key=True) agent_id: int = Field(index=True) kb_id: int = Field(index=True) chunk_id: str = Field(index=True) file_name: str = "" heading: str = "" excerpt: str = "" campaign_id: int = 0 segment_id: int = 0 status: str = "consumed" # reserved|consumed|available banned: bool = False # 人工审查不通过且选择"不再使用"→ 检索永久排除 used_at: str = Field(default_factory=now) released_at: str = "" # ---------- 技能库 ---------- class AgentSkill(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True, unique=True) desc: str = "" instructions: str = "" enabled: bool = True builtin: bool = False version: int = 1 organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "global" created_at: str = Field(default_factory=now) class AgentSkillBinding(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) agent_id: int = Field(index=True) skill_id: int = Field(index=True) class AgentSkillGrant(SQLModel, table=True): """技能的成员授权;技能挂接到 Agent 后仍遵循技能自身的访问边界。""" id: Optional[int] = Field(default=None, primary_key=True) skill_id: int = Field(index=True) user_id: int = Field(index=True) permission: str = "use" # read|use|manage|export enabled: bool = True created_at: str = Field(default_factory=now) # ---------- 提示模板 ---------- class AgentPromptTemplate(SQLModel, table=True): """可复用、可版本化的提示模板;任务创建时会冻结完整快照。""" id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True, unique=True) desc: str = "" role_prompt: str = "" task_template: str = "" constraints: list = Field(default_factory=list, sa_column=Column(JSON)) examples: list = Field(default_factory=list, sa_column=Column(JSON)) output_schema: dict = Field(default_factory=dict, sa_column=Column(JSON)) style: str = "" version: int = 1 enabled: bool = True builtin: bool = False organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "global" created_at: str = Field(default_factory=now) updated_at: str = Field(default_factory=now) # ---------- Agent ---------- class Agent(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str role: str = "production" # production|training_map|qc|service|coordinator|composer desc: str = "" content_tags: list = Field(default_factory=list, sa_column=Column(JSON)) # 生产 Agent 擅长标签 kb_ids: list = Field(default_factory=list, sa_column=Column(JSON)) # 知识库挂接 kb_category_ids: dict = Field(default_factory=dict, sa_column=Column(JSON)) fusion_agent_ids: list = Field(default_factory=list, sa_column=Column(JSON)) # 融合写法的生产 Agent model_provider_id: Optional[int] = None # None=跟随全局默认 model_name: str = "" model_catalog_id: Optional[int] = Field(default=None, index=True) prompt_template_id: Optional[int] = Field(default=None, index=True) # 允许该 Agent 使用的 Runtime 工具名称;空列表按系统内置工具兼容处理。 allowed_tools: list = Field(default_factory=list, sa_column=Column(JSON)) rules: str = "" # rules.md 内容 rules_version: int = 1 persona_json: dict = Field(default_factory=dict, sa_column=Column(JSON)) # Agent 独立人格档案(优先级低于绑定的数字人) digital_human_id: Optional[int] = Field(default=None, index=True) # 绑定数字人:绑定时以数字人身份生成 runtime_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON)) # 发布冻结的运行配置(借鉴 Coze 草稿/发布分离) status: str = "active" # active|disabled builtin: bool = False organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "global" # private|shared|global created_at: str = Field(default_factory=now) class AgentGrant(SQLModel, table=True): """Agent 本身的成员授权;与 Agent 内部技能授权独立。""" id: Optional[int] = Field(default=None, primary_key=True) agent_id: int = Field(index=True) user_id: int = Field(index=True) permission: str = "use" # read|use|manage|export enabled: bool = True created_at: str = Field(default_factory=now) # ---------- 数字人(蒸馏人格档案,可绑定到 Agent) ---------- class DigitalHuman(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True) desc: str = "" persona: dict = Field(default_factory=dict, sa_column=Column(JSON)) # 十一维人格档案 # 蒸馏质量元数据(覆盖范围、证据来源、不确定性、情境规则);不混入 persona 字段,兼容旧档案。 persona_meta: dict = Field(default_factory=dict, sa_column=Column(JSON)) status: str = "distilling" # distilling 蒸馏中 | testing 待人工测试 | active 已生效 version: int = 1 # 每次重新蒸馏 +1 source_files: list = Field(default_factory=list, sa_column=Column(JSON)) # 蒸馏来源文件名 last_feedback: str = "" # 最近一次人工测试意见 organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "private" # private=仅本人 | shared=授权成员 | global=全员可用 created_at: str = Field(default_factory=now) class DigitalHumanGrant(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) dh_id: int = Field(index=True) user_id: int = Field(index=True) permission: str = "use" # read|use enabled: bool = True created_at: str = Field(default_factory=now) class WeChatBotAccount(SQLModel, table=True): """微信 Bot 账号连接;凭证只由独立适配器持有,数据库不保存明文。""" id: Optional[int] = Field(default=None, primary_key=True) owner_user_id: int = Field(default=0, index=True) organization_id: int = Field(default=1, index=True) account_name: str = "" provider: str = "ilink" # ilink 官方通道 | wechaty 开发通道 status: str = "disconnected" # pending_qr|connected|disconnected|error qr_code: str = "" # 短期二维码 URL/内容,不保存长期凭证 external_account_id: str = "" # 适配器返回的脱敏账号标识 agent_id: Optional[int] = Field(default=None, index=True) credential_ref: str = "" # 适配器凭证引用(不含密钥) bot_secret_hash: str = "" # 每个账号独立的 Bot 密钥哈希 bot_secret_hint: str = "" # 仅显示末四位 last_error: str = "" created_at: str = Field(default_factory=now) updated_at: str = Field(default_factory=now) # ---------- 画布(M2 完整实现,M1 存简版) ---------- class Canvas(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str nodes: list = Field(default_factory=list, sa_column=Column(JSON)) # [{node_id,agent_id,enabled}] edges: list = Field(default_factory=list, sa_column=Column(JSON)) # [{from_node,to_node}] supervisor_agent_id: Optional[int] = None # 全局总监 Agent:负责各段不跑偏,None=不启用 supervisor_mode: str = "on_drift" # always|on_drift|off —— 每段必审/疑似跑偏才审/关闭 organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "global" created_at: str = Field(default_factory=now) class CanvasGrant(SQLModel, table=True): """画布授权是 Agent、关联知识库和记忆的统一使用边界。""" id: Optional[int] = Field(default=None, primary_key=True) canvas_id: int = Field(index=True) user_id: int = Field(index=True) permission: str = "use" # read|use|manage|export enabled: bool = True created_at: str = Field(default_factory=now) # ---------- DSH Planner:工作流模板与计划版本 ---------- class WorkflowTemplate(SQLModel, table=True): """工作台管理的工作流模板;DSH Planner 只能在模板约束内生成 DAG。""" id: Optional[int] = Field(default=None, primary_key=True) key: str = Field(index=True, unique=True) name: str description: str = "" # {mode, required_tags, optional_tags, max_segments, allow_auto_replan, risk_rules, default_kb_categories} config: dict = Field(default_factory=dict, sa_column=Column(JSON)) enabled: bool = True builtin: bool = False organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "global" # private|shared|global created_at: str = Field(default_factory=now) updated_at: str = Field(default_factory=now) class CampaignPlanVersion(SQLModel, table=True): """DSH Planner 产出的不可变计划版本;批准后才允许 Worker 执行。""" __table_args__ = ( UniqueConstraint("campaign_id", "version", name="uq_campaign_plan_version"), ) id: Optional[int] = Field(default=None, primary_key=True) campaign_id: int = Field(index=True) version: int = 1 source: str = "dsh" # dsh|manual template_id: int = Field(default=0, index=True) template_key: str = "default-live-course" status: str = Field(default="draft", index=True) # draft|waiting_review|approved|rejected|superseded|failed plan: dict = Field(default_factory=dict, sa_column=Column(JSON)) validation: dict = Field(default_factory=dict, sa_column=Column(JSON)) summary: str = "" risks: list = Field(default_factory=list, sa_column=Column(JSON)) advice: str = "" created_by: int = 0 reviewed_by: int = 0 review_note: str = "" created_at: str = Field(default_factory=now, index=True) reviewed_at: str = "" class ResourceRevision(SQLModel, table=True): """用户对全局资源的个人修改提案。 全局资源本体保持不变;普通用户编辑/删除时写入 pending 修订,管理员 审核后才会把内容合并到全局资源。这样同一资源可被多人使用而互不覆盖。 """ id: Optional[int] = Field(default=None, primary_key=True) resource_type: str = Field(index=True) # kb|agent|skill|canvas|memory|digital_human resource_id: int = Field(index=True) fork_resource_id: int = Field(default=0, index=True) owner_user_id: int = Field(index=True) organization_id: int = Field(default=1, index=True) operation: str = "update" # update|delete base_version: int = 1 base_fingerprint: str = "" version: int = 1 payload: dict = Field(default_factory=dict, sa_column=Column(JSON)) status: str = Field(default="pending", index=True) # pending|approved|rejected reviewer_user_id: int = 0 review_note: str = "" created_at: str = Field(default_factory=now) reviewed_at: str = "" class ResourceFork(SQLModel, table=True): """个人副本与全局源资源的关系,用于版本提交和冲突检测。""" id: Optional[int] = Field(default=None, primary_key=True) resource_type: str = Field(index=True) source_id: int = Field(index=True) copy_id: int = Field(index=True) owner_user_id: int = Field(index=True) organization_id: int = Field(default=1, index=True) base_version: int = 1 base_fingerprint: str = "" created_at: str = Field(default_factory=now) # ---------- 生产任务 ---------- class Campaign(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str # 如 2026-08-26-day5 mindmap_text: str = "" # 用户输入的导图/框架原文 objective: str = "" # 本次任务唯一业务目标 success_criteria: str = "" # 人工验收标准 canvas_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON)) # {nodes:[{node_id,agent_id,enabled}], edges:[{from,to}]} canvas_name: str = "" # 画布名(快照来源) duration_minutes: int = 0 # 直播总时长(分钟):0=未指定;拆解按此分配各段时间 qc_mode: str = "full" # 质检模式:full=出稿同步质检 | lazy=先出稿后台质检 | off=不质检 previous_campaign_id: Optional[int] = None # 关联上一天任务(跨稿对账) task_spec: list = Field(default_factory=list, sa_column=Column(JSON)) # 内容段列表 pending_confirms: list = Field(default_factory=list, sa_column=Column(JSON)) # 待确认清单 notes: list = Field(default_factory=list, sa_column=Column(JSON)) # 拆解说明/压缩决策 approved_plan_version_id: Optional[int] = None # 已人工批准的 DSH Planner 计划版本;空=旧流程兼容 status: str = "draft" # draft|planning|plan_review|plan_failed|parsed|running|qc|human_review|composing|done|failed has_failure: bool = False # ⚠️ 有待处理失败 organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "private" created_at: str = Field(default_factory=now) finished_at: str = "" # ---------- 内容段 ---------- class Segment(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) campaign_id: int = Field(index=True) node_id: str = "" # 画布节点 id(M1 用 seg-N) agent_id: int = 0 agent_assignment_mode: str = "auto" # manual 后标签修改不再覆盖用户选择 generation_history: list = Field(default_factory=list, sa_column=Column(JSON)) fusion_agent_ids: list = Field(default_factory=list, sa_column=Column(JSON)) # 创建任务时冻结的融合 Agent 快照 runtime_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON)) # 任务拆解时冻结的规则/技能/模型配置 content_tag: str = "" # 内容标签 title: str = "" time_range: str = "" # 00:00-00:08 mindmap_node: str = "" # 导图节点原文 product_ref: str = "" # 产品节点(如有) status: str = "pending" # pending|running|qc|human_review|approved|rework|failed|skipped round: int = 1 # 第几轮 artifact_path: str = "" # 产物文件(md) anchor_card: dict = Field(default_factory=dict, sa_column=Column(JSON)) qc_reports: list = Field(default_factory=list, sa_column=Column(JSON)) failure: dict = Field(default_factory=dict, sa_column=Column(JSON)) # 失败详情 references: list = Field(default_factory=list, sa_column=Column(JSON)) # RAG 引用来源(溯源核对) kb_category_ids: list = Field(default_factory=list, sa_column=Column(JSON)) # 段级知识分类限定:空=用整个知识库 kb_category_recommendation: dict = Field(default_factory=dict, sa_column=Column(JSON)) # AI 推荐的真实分类及理由,需人工采用 updated_at: str = Field(default_factory=now) # ---------- 质检报告 ---------- class QCReport(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) segment_id: int = Field(index=True) gate: str = "" # structure|content|style|cross checks: list = Field(default_factory=list, sa_column=Column(JSON)) verdict: str = "PASS" # PASS|FAIL run_id: int = Field(default=0, index=True) trace_id: str = Field(default="", index=True) runtime_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON)) created_at: str = Field(default_factory=now) # ---------- 调用日志 ---------- class LLMCall(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) run_id: int = Field(default=0, index=True) campaign_id: int = 0 segment_id: int = 0 agent_id: int = 0 agent_name: str = "" provider: str = "" model: str = "" status: str = "ok" # ok|error|rejected|timeout error: str = "" trace_id: str = Field(default="", index=True) span_id: str = "" attempt: int = 1 prompt_hash: str = "" output_hash: str = "" prompt_tokens: int = 0 completion_tokens: int = 0 duration_ms: int = 0 user_id: int = Field(default=0, index=True) organization_id: int = Field(default=1, index=True) usage_event_id: Optional[int] = Field(default=None, index=True) billing_status: str = "unbilled" # unbilled|measured|estimated|settled|void created_at: str = Field(default_factory=now) # ---------- 不可变用量事件与配额 ---------- class UsageEvent(SQLModel, table=True): """每一次模型、Embedding、OCR 或导出行为的追加式计量记录。""" id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(default=0, index=True) organization_id: int = Field(default=1, index=True) campaign_id: int = Field(default=0, index=True) run_id: int = Field(default=0, index=True) segment_id: int = Field(default=0, index=True) trace_id: str = Field(default="", index=True) llm_call_id: Optional[int] = Field(default=None, index=True) agent_id: int = Field(default=0, index=True) provider_id: int = Field(default=0, index=True) provider: str = "" model: str = "" service_type: str = "llm" # llm|embedding|ocr|storage|export|rerank unit: str = "token" # token|image|byte|gb_month|request quantity: float = 0.0 input_quantity: int = 0 output_quantity: int = 0 input_rate: float = 0.0 output_rate: float = 0.0 amount_minor: int = 0 # CNY 分,避免浮点金额作为账本依据 currency: str = "CNY" pricing_version: str = "v1" status: str = "measured" # measured|estimated|settled|void idempotency_key: str = Field(index=True, unique=True) usage_metadata: dict = Field(default_factory=dict, sa_column=Column("metadata", JSON)) created_at: str = Field(default_factory=now, index=True) class PointAccount(SQLModel, table=True): """用户点数余额;10 点数折算 1 Token。""" id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(index=True) organization_id: int = Field(default=1, index=True) balance_points: int = 0 updated_at: str = Field(default_factory=now) class PointTransaction(SQLModel, table=True): """点数不可变流水:管理员充值为 credit,模型消耗为 debit。""" id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(index=True) organization_id: int = Field(default=1, index=True) delta_points: int kind: str = "credit" # credit|debit|adjustment reason: str = "" source: str = "admin" operator_user_id: int = 0 idempotency_key: str = Field(index=True, unique=True) transaction_metadata: dict = Field(default_factory=dict, sa_column=Column("metadata", JSON)) created_at: str = Field(default_factory=now, index=True) class UsageQuota(SQLModel, table=True): """用户级配额;0 表示该项不设上限,策略由管理员配置。""" id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(index=True) organization_id: int = Field(default=1, index=True) period: str = "monthly" # daily|monthly token_limit: int = 0 amount_limit_minor: int = 0 storage_limit_bytes: int = 0 concurrent_limit: int = 0 warning_ratio: float = 0.8 overage_policy: str = "warn" # warn|soft_limit|hard_limit enabled: bool = True updated_at: str = Field(default_factory=now) # ---------- 通知 ---------- class Notification(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) type: str = "failure" # failure|pending_review|memory_pending|tag_pending|info ref_id: int = 0 message: str = "" status: str = "unread" created_at: str = Field(default_factory=now) class ProgressEvent(SQLModel, table=True): """Cross-process progress events shared by API workbench and DSH worker.""" id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(default=0, index=True) event: str = Field(default="info", index=True) payload: dict = Field(default_factory=dict, sa_column=Column(JSON)) created_at: str = Field(default_factory=now, index=True) # ---------- 记忆待确认池 ---------- class PendingMemory(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) target_agent_id: int type: str = "L3" # L1规则|L2纠错|L3偏好|L4成功 content: str = "" source_ref: str = "" # 来源:哪次打回/哪场 status: str = "pending" # pending|approved|rejected|blocked organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "private" created_at: str = Field(default_factory=now) # ---------- 已确认记忆 ---------- class Memory(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) agent_id: int = Field(index=True) layer: str = "L3" title: str = "" description: str = "" content: str = "" source_ref: str = "" status: str = "active" # active|expired privacy: str = "internal" # internal|public tags: list = Field(default_factory=list, sa_column=Column(JSON)) timestamp: str = Field(default_factory=now) # 记忆沉淀日期 index_status: str = "indexed" # indexed|needs_reindex usage_count: int = 0 last_used_at: str = "" supersedes_id: int = 0 # 人工确认替代的旧记忆;正文保留为历史版本 organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "private" created_at: str = Field(default_factory=now) class MemoryGrant(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) memory_id: int = Field(index=True) user_id: int = Field(index=True) permission: str = "use" # read|use|manage|export enabled: bool = True created_at: str = Field(default_factory=now) # ---------- 组合产物 ---------- class Artifact(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) campaign_id: int = Field(index=True) docx_path: str = "" compose_report: dict = Field(default_factory=dict, sa_column=Column(JSON)) final_status: str = "composed" run_id: int = Field(default=0, index=True) trace_id: str = Field(default="", index=True) runtime_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON)) organization_id: int = Field(default=1, index=True) owner_user_id: int = Field(default=0, index=True) visibility: str = "private" created_at: str = Field(default_factory=now) # ---------- 本地账号与会话 ---------- class AppUser(SQLModel, table=True): """本地工作台账号;密码只保存 PBKDF2 派生摘要。""" id: Optional[int] = Field(default=None, primary_key=True) username: str = Field(index=True, unique=True) display_name: str = "" password_hash: str = "" role: str = "viewer" # admin|editor|reviewer|viewer organization_id: int = Field(default=1, index=True) enabled: bool = True created_at: str = Field(default_factory=now) updated_at: str = Field(default_factory=now) class RoleDefinition(SQLModel, table=True): """管理员可维护的角色权限集合;AppUser.role 保存角色标识。""" id: Optional[int] = Field(default=None, primary_key=True) role: str = Field(index=True, unique=True) label: str = "" description: str = "" permissions: list = Field(default_factory=list, sa_column=Column(JSON)) builtin: bool = True organization_id: int = Field(default=1, index=True) updated_at: str = Field(default_factory=now) class AuthSession(SQLModel, table=True): """不透明令牌的可撤销会话,数据库只保存令牌哈希。""" id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(index=True) token_hash: str = Field(index=True, unique=True) expires_at: str = "" revoked_at: str = "" created_at: str = Field(default_factory=now) last_seen_at: str = Field(default_factory=now) # ---------- 可恢复执行记录 ---------- class CampaignRun(SQLModel, table=True): """一次任务运行的持久化状态;服务重启后可从检查点恢复。""" id: Optional[int] = Field(default=None, primary_key=True) campaign_id: int = Field(index=True) user_id: int = Field(default=0, index=True) organization_id: int = Field(default=1, index=True) status: str = "queued" # queued|running|paused|awaiting_review|completed|failed|cancelled cancel_requested: bool = False pause_requested: bool = False checkpoint: dict = Field(default_factory=dict, sa_column=Column(JSON)) config_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON)) trace_id: str = Field(default="", index=True) runtime_version: str = "v1" error: str = "" created_at: str = Field(default_factory=now) started_at: str = "" finished_at: str = "" class RunEvent(SQLModel, table=True): """运行审计轨迹,供前端展示生成进度与诊断失败。""" id: Optional[int] = Field(default=None, primary_key=True) run_id: int = Field(index=True) campaign_id: int = Field(index=True) segment_id: int = 0 node_id: str = "" trace_id: str = Field(default="", index=True) span_id: str = "" parent_span_id: str = "" actor_agent_id: int = 0 actor_name: str = "" tool_name: str = "" event_type: str = "info" status: str = "" payload: dict = Field(default_factory=dict, sa_column=Column(JSON)) created_at: str = Field(default_factory=now) # ---------- Agent Runtime 会话 ---------- class AgentSession(SQLModel, table=True): """单个 Agent 的可恢复会话;messages 保存 Agent loop 的上下文和工具结果。""" id: Optional[int] = Field(default=None, primary_key=True) run_id: int = Field(default=0, index=True) campaign_id: int = Field(default=0, index=True) segment_id: int = Field(default=0, index=True) agent_id: int = Field(default=0, index=True) agent_name: str = "" parent_session_id: Optional[int] = Field(default=None, index=True) phase: str = "production" # coordinator|production|qc|composer|handoff trace_id: str = Field(default="", index=True) status: str = "running" # running|waiting_approval|interrupted|completed|failed|cancelled turn: int = 0 max_turns: int = 6 messages: list = Field(default_factory=list, sa_column=Column(JSON)) tool_state: dict = Field(default_factory=dict, sa_column=Column(JSON)) result_text: str = "" result_meta: dict = Field(default_factory=dict, sa_column=Column(JSON)) error: str = "" created_at: str = Field(default_factory=now) updated_at: str = Field(default_factory=now) finished_at: str = "" # ---------- 持久任务队列(P0-3)---------- class TaskQueueItem(SQLModel, table=True): """可靠任务队列:租约 + 重试退避 + 死信。task_key 幂等防重复入队。""" id: Optional[int] = Field(default=None, primary_key=True) task_key: str = Field(index=True) # 幂等键(如 campaign-run:12) payload: dict = Field(default_factory=dict, sa_column=Column(JSON)) status: str = Field(default="pending", index=True) # pending|running|done|dead|cancelled attempt: int = 0 max_attempts: int = 3 worker_id: str = "" lease_until: str = "" # 租约到期时间(ISO) next_run_at: str = Field(default_factory=now) # 退避后可领取时间 error: str = "" created_at: str = Field(default_factory=now) updated_at: str = Field(default_factory=now)