taskId 深链会商存储独立化: - 新增独立表 roundtable_task_drafts + 路由 /api/roundtable-task-drafts,只按 task_id 归属、完全不按 user 分权(created_by 仅审计),与个人 roundtable_drafts 物理分离; 个人草稿表恢复严格按用户隔离(去掉原 task 放权)。 - 后台研讨作业按 taskId 共享:roundtable_jobs 加 task_id,get/get_by_draft/stream/ resume/cancel 支持 ?task_id= 跨用户读取与控制,作业报告写回 task-draft 存储。 - 前端:新增 api/task-drafts.ts 客户端,useDraftPersistence / useRoundtableDrafts / roundtable-jobs / DispatchChainJobModal / RoundtablePlanningPage 按 taskId 路由读写。 - 迁移 20260618_01 + column_additions_mysql.sql(jobs.task_id) + 数据搬迁脚本 scripts/migrate_roundtable_task_drafts.py(把历史 task_id 行迁出个人表)。 第三步聊天记录像第二步一样存全: - 总结报告 / 经典看板 / 大屏 三条流的生成过程对话(中间文字 + 步骤条 + stub)落库, 加载草稿整段回显,而非只剩最终 md / 流程图 / HTML。DraftStep3Snapshot 扩展 dialogues/seatStubMessages 槽位;存稿走页面 render 期 ref,恢复走各 hook hydrateSaved (旧草稿无字段则回退单条「历史记录」气泡)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
5.2 KiB
Python
82 lines
5.2 KiB
Python
"""ORM model for roundtable background-orchestration jobs.
|
||
|
||
A "job" = one **backend** run of the roundtable orchestration pipeline kicked off
|
||
from Step 2「后台挂起」: it drives the leader→dispatch→sub-agents loop to consensus
|
||
and then Step 3 report drawing, entirely server-side, surviving page refresh/close.
|
||
|
||
Bound 1:1 to a ``roundtable_drafts`` row (``draft_id``) so the history dropdown can
|
||
show "this task is running" and the dispatch-chain modal can render live progress.
|
||
|
||
Big JSON columns (``dispatch_chain`` / ``thread_ids`` / ``chain``) are stored as
|
||
serialized strings in ``PortableLongText`` (MySQL ``LONGTEXT``) — same approach as
|
||
``ai_writing_sessions`` / ``roundtable_drafts`` — to avoid MySQL's 64 KB ``TEXT`` cap.
|
||
|
||
See ``frontend-web/docs/multi-agent-background-run-dev.md`` §2.5 for the field
|
||
contract and §Phase 1 for the build steps.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import UTC, datetime
|
||
|
||
from sqlalchemy import Integer, String, Text
|
||
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
||
from deerflow.persistence.base import Base
|
||
from deerflow.persistence.types import BeijingDateTime, PortableLongText
|
||
|
||
|
||
class RoundtableJobRow(Base):
|
||
__tablename__ = "roundtable_jobs"
|
||
|
||
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||
# The draft/session this job runs for. Indexed for "active job of draft" lookups.
|
||
draft_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||
user_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||
# 外部任务 id(taskId 深链会商)。非空 = 这是一条 task 作业:读取按 task_id 共享、
|
||
# **不按 user 分权**(任何人打开同一 task 都能看在跑的研讨);其 draft_id 指向独立的
|
||
# ``roundtable_task_drafts`` 表。为空 = 普通个人作业(按 user 分权)。
|
||
task_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
||
# Job status machine (§2.1): queued / running / awaiting_input / done / error / cancelled.
|
||
status: Mapped[str] = mapped_column(String(32), nullable=False, default="queued", index=True)
|
||
# Fine-grained phase (§2.2): initializing / leader_thinking / dispatching /
|
||
# consensus / report_drawing / report_done.
|
||
phase: Mapped[str] = mapped_column(String(32), nullable=False, default="initializing")
|
||
# Current orchestration cycle (0..MAX_CYCLES).
|
||
cycle: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||
# Dispatch chain nodes (§2.3): JSON array of
|
||
# { agentId, name, avatarType, order, state, lastSpokeCycle? }.
|
||
dispatch_chain: Mapped[str | None] = mapped_column(PortableLongText, nullable=True)
|
||
# Currently-speaking seat agent_id (null = coordinator / none).
|
||
active_agent_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
||
consensus_percentage: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||
# When status=awaiting_input: the coordinator's clarification question text.
|
||
pending_clarification: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||
# ThreadIdMap: JSON object { "<agent_id>": "<thread_id>", ... }.
|
||
thread_ids: Mapped[str | None] = mapped_column(PortableLongText, nullable=True)
|
||
coordinator_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
||
# "recommend"(总控自由派活)| "chain"(按业务链条顺序派活)| "dag"(分层 DAG:stage 间串行、stage 内并行).
|
||
orchestration_mode: Mapped[str] = mapped_column(String(16), nullable=False, default="recommend")
|
||
# chain 模式来源链条快照:JSON object { id, title }(recommend 模式为 null).
|
||
chain: Mapped[str | None] = mapped_column(PortableLongText, nullable=True)
|
||
# dag 模式分层编排计划:JSON object { mode, stages:[{id,agentIds,goal?}], finalSynthesis,
|
||
# coordinatorPrompt? }(对齐前端 OrchestrationPlan)。仅 dag 模式有值;驱动后端 _run_dag
|
||
# (stage 串行 / stage 内并行)+ 前端大弹窗按 stage 分层画流程图 + resume 时重建计划。
|
||
orchestration_plan: Mapped[str | None] = mapped_column(PortableLongText, nullable=True)
|
||
# 研讨对话记录:JSON array of { id, role('leader'|'seat'), agentId, name, content, cycle }。
|
||
# 让用户跑完 / 进行中都能看到真实研讨内容(leader 说明 + 各席位交付),而非只有流程图。
|
||
dialogues: Mapped[str | None] = mapped_column(PortableLongText, nullable=True)
|
||
# Step3 报告快照:JSON object { html, generatedAt, model, summary }。done 时写入,
|
||
# 使前端从单个 job 即可拿到完整结果(对话 + 报告)。
|
||
step3: Mapped[str | None] = mapped_column(PortableLongText, nullable=True)
|
||
# Terminal error detail when status=error.
|
||
error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||
# 时间列必须用 BeijingDateTime(项目规范,与 ai_writing_sessions / roundtable_drafts 对齐)。
|
||
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),
|
||
)
|