"""ORM model for roundtable-planning business chains. A "business chain" (业务链条) is a named, reusable, *ordered* list of agents the user assembles in the chain editor. In Step 2 the coordinator dispatches to the seats following this order (see ``multi-agent-business-chain-dev.md``). Persisted per user (like ``roundtable_drafts``) so it follows the account across devices/sessions. ``seats`` is stored as a JSON array serialized into ``PortableLongText`` (MySQL ``LONGTEXT``) rather than ``PortableJSON`` for headroom, mirroring the sibling roundtable persistence modules. """ from __future__ import annotations from datetime import UTC, datetime from sqlalchemy import Boolean, String from sqlalchemy.orm import Mapped, mapped_column from deerflow.persistence.base import Base from deerflow.persistence.types import BeijingDateTime, PortableLongText class RoundtableChainRow(Base): __tablename__ = "roundtable_chains" id: Mapped[str] = mapped_column(String(64), primary_key=True) user_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True) # Human-readable chain title shown on the picker card. title: Mapped[str] = mapped_column(String(512), nullable=False, default="") # Optional free-text description. description: Mapped[str | None] = mapped_column(String(2048), nullable=True) # JSON: ordered [{"agent_id": str, "name": str, "description": str}, ...]. # seats[0] is the first seat to speak. Stored as LONGTEXT-backed JSON string. seats: Mapped[str | None] = mapped_column(PortableLongText, nullable=True) # Optional JSON: layered orchestration grouping over ``seats`` — a list of # stages, each a list of agent_ids run in parallel; stages run serially in # order. e.g. [["a","b"],["c"]] = (a‖b) → c. NULL/absent = linear chain # (each seat its own stage). Drives Step 2 dag-mode orchestration; see # frontend ``lib/dag-types.ts``. Stored as LONGTEXT-backed JSON string. stages: Mapped[str | None] = mapped_column(PortableLongText, nullable=True) # Optional JSON: per-stage goals aligned by index to ``stages`` — a list where # ``stage_goals[i]`` is the「本阶段目标」for ``stages[i]`` (what that serial stage # should produce / how it depends on the previous stage's output). Injected to the # coordinator dispatch prompt (and seat tasks as fallback) in dag-mode only. NULL = # no goals (and always NULL when ``stages`` is NULL/linear). Stored as JSON string. stage_goals: Mapped[str | None] = mapped_column(PortableLongText, nullable=True) # 可选「总控编排提示」:用户为本链条写给**总控(协调)智能体**的一段指引,告诉总控该 # 怎么派活、做什么(整体编排策略 / 风格 / 约束)。**仅分层 DAG 模式生效**:注入到总控 # 每阶段派活提示(``buildDagStageLeaderTask``)。NULL = 不注入。纯文本,存为 LONGTEXT。 coordinator_prompt: Mapped[str | None] = mapped_column(PortableLongText, nullable=True) # 可选「默认席位执行模式」:flash/thinking/pro/ultra(见前端 lib/seat-mode.ts)。选入研讨时 # 弹窗默认用它(仍可临时改)。NULL = 未设,调用方回退默认(pro)。短串,VARCHAR(16)。 seat_mode: Mapped[str | None] = mapped_column(String(16), nullable=True) # 可选「研讨前并行取数」开关:True = 选入研讨时,研讨前先跑一轮全席位并行『取数』(各席位只调 # 技能取数、不研讨),产出作为共享资料注入后续研讨轮,把最慢的技能延迟前置并行、给串行研讨段提速。 # 默认 False(不变更现有行为)。逐条 opt-in。BOOLEAN,server_default "0"。 gather_first: Mapped[bool] = mapped_column( Boolean, nullable=False, default=False, server_default="0" ) # 发布开关:True = 公共(所有账号可在「公共」筛选里查看 / 选用);False = 仅本人可见。 # 编辑 / 删除始终只限 owner;发布只是放开「查看 + 选入研讨」。索引以支持「公共」列表筛选。 is_public: Mapped[bool] = mapped_column( Boolean, nullable=False, default=False, server_default="0", index=True ) # 启停开关:True = 启用(可在选链弹窗 / 公共入口里选用);False = 停用(管理列表仍可见, # 但不可选入研讨)。默认启用,存量链条补列后也为 True。``server_default`` 必须显式声明, # 否则既有表上启动期 ``_ensure_orm_columns_sync`` 会跳过「NOT NULL 且无 server_default」的列。 enabled: Mapped[bool] = mapped_column( Boolean, nullable=False, default=True, server_default="1" ) # 「待审核」标记:True = owner 已在编辑器提交审核(发布前需管理员在「待审核」筛选里确认 # 发布);管理员发布时置回 False 并同时置 ``is_public=True``。缺省 False,存量链条不受影响。 # 索引以支持管理端「待审核」列表筛选。 pending_review: Mapped[bool] = mapped_column( Boolean, nullable=False, default=False, server_default="0", index=True ) created_at: Mapped[datetime] = mapped_column( BeijingDateTime(), nullable=False, default=lambda: datetime.now(UTC) ) updated_at: Mapped[datetime] = mapped_column( BeijingDateTime(), nullable=False, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC), )