fix: 8BF 无 taskId 开全新会话,公共链条发布联动席位智能体
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
32e509b2b3
commit
84370e2072
3 files changed
+81
-5
No files matched your search
@@ -257,6 +257,8 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa
|
||||
lockedChainId={mapping?.chainId ?? null}
|
||||
lockBusinessChain
|
||||
storageTaskId={storageTaskId}
|
||||
// 无 taskId 进入时每次都开全新会话(不回显 anonymous 键下的最新草稿);带 taskId 保持回显现状。
|
||||
skipInitialDraftRestore={!taskId}
|
||||
taskContext={taskContext as Record<string, unknown> | null}
|
||||
entryRailTaskId={taskId || null}
|
||||
entryRailTaskCardSlot={taskId ? (
|
||||
|
||||
@@ -310,6 +310,11 @@ export default function RoundtablePlanningPage({
|
||||
lockBusinessChain = false,
|
||||
/** 独立业务页的草稿/后台作业存储键。 */
|
||||
storageTaskId,
|
||||
/**
|
||||
* 独立业务页要求「每次进入都是全新会话」(8BF 无 taskId 模式):挂载时跳过按存储键
|
||||
* 回显最新草稿,直接展示空白新会话;历史会话仍留在历史下拉/分析记录里,可手动打开。
|
||||
*/
|
||||
skipInitialDraftRestore = false,
|
||||
/** 供独立业务页在原页头部插入自己的操作入口。 */
|
||||
toolbarSlot,
|
||||
/** 独立业务页在第一步左上角替换默认业务链选择区的内容。 */
|
||||
@@ -351,6 +356,7 @@ export default function RoundtablePlanningPage({
|
||||
lockedChainId?: string | null;
|
||||
lockBusinessChain?: boolean;
|
||||
storageTaskId?: string | null;
|
||||
skipInitialDraftRestore?: boolean;
|
||||
toolbarSlot?: ReactNode;
|
||||
entryRailBusinessChainSlot?: ReactNode;
|
||||
entryRailTaskCardSlot?: ReactNode;
|
||||
@@ -1368,6 +1374,12 @@ export default function RoundtablePlanningPage({
|
||||
// 回显聊天记录按存储键:8BF 用独立 `storageTaskId`;xdfx-A 按行动id;其它深链按真实 taskId。
|
||||
const tid = draftStorageTaskId;
|
||||
if (!tid) return;
|
||||
// 8BF 无 taskId 模式:每次进入开全新会话,跳过回显(历史下拉仍可手动打开旧会话)。
|
||||
// 仍要解除 taskRestoreResolved,别把依赖它的 autoStart 闸门永久挂起。
|
||||
if (skipInitialDraftRestore) {
|
||||
setTaskRestoreResolved(true);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
try {
|
||||
|
||||
@@ -28,7 +28,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.gateway.deps import get_config, get_local_provider
|
||||
from app.gateway.deps import get_agent_store, get_config, get_local_provider
|
||||
from deerflow.config.app_config import AppConfig
|
||||
from deerflow.models import create_chat_model
|
||||
from deerflow.runtime.user_context import get_effective_user_id
|
||||
@@ -202,6 +202,65 @@ def _get_store(request: Request):
|
||||
return store
|
||||
|
||||
|
||||
async def _publish_seat_agents(request: Request, row: dict, user_id: str, *, is_admin: bool) -> list[str]:
|
||||
"""把链条席位里尚未公开的自定义智能体一并发布(best-effort)。
|
||||
|
||||
公共链条对其他账号可用的前提是链上每个席位智能体本身已发布(见
|
||||
business_mapping 的 ``_validate_public_chain_mapping`` 联动校验——只发布
|
||||
链条、不发布其中的自定义智能体,别的账号会解析不到席位)。故链条处于
|
||||
公共态时自动补发:
|
||||
- 内置智能体(``user_id IS NULL``)全局可见,跳过;
|
||||
- 已公开(``published`` 且非 ``is_private``)跳过;
|
||||
- 其余置 ``published=True`` 并清 ``is_private=False``(两者互斥,私密
|
||||
广场智能体要变公共必须同时清私密标记)。
|
||||
非管理员只能发布本人的智能体(store.update 的 owner 限定);他人的智能体
|
||||
正常流程不会出现在自己链条里,跳过并记日志。单个失败不阻断链条发布。
|
||||
返回本次实际发布成功的智能体名列表(仅供日志/排查)。
|
||||
"""
|
||||
agent_ids: list[str] = []
|
||||
for seat in row.get("seats") or []:
|
||||
if isinstance(seat, dict) and seat.get("agent_id"):
|
||||
agent_ids.append(str(seat["agent_id"]))
|
||||
if not agent_ids:
|
||||
return []
|
||||
try:
|
||||
agent_store = get_agent_store(request)
|
||||
except HTTPException:
|
||||
return []
|
||||
published: list[str] = []
|
||||
for agent_id in dict.fromkeys(agent_ids):
|
||||
try:
|
||||
agent = await agent_store.get_any(agent_id)
|
||||
except Exception:
|
||||
logger.warning("chain seat agent lookup failed: %s", agent_id, exc_info=True)
|
||||
continue
|
||||
if agent is None or agent.get("user_id") is None:
|
||||
continue # 已删除 / 内置智能体:内置全局可见,无需发布
|
||||
if agent.get("published") and not agent.get("is_private"):
|
||||
continue # 已公开
|
||||
updates = {"published": True, "is_private": False}
|
||||
try:
|
||||
updated = await agent_store.update_any(agent_id, updates) if is_admin else await agent_store.update(agent_id, user_id, updates)
|
||||
except Exception:
|
||||
logger.warning("chain seat agent publish failed: %s", agent_id, exc_info=True)
|
||||
continue
|
||||
if updated is None:
|
||||
logger.info(
|
||||
"chain seat agent %s not publishable by user %s (not owner); skipped",
|
||||
agent_id,
|
||||
user_id,
|
||||
)
|
||||
continue
|
||||
published.append(agent.get("name") or agent_id)
|
||||
logger.info(
|
||||
"auto-published chain seat agent %s (%s) with chain %s",
|
||||
agent_id,
|
||||
agent.get("name"),
|
||||
row.get("id"),
|
||||
)
|
||||
return published
|
||||
|
||||
|
||||
def _validate_seats(seats: list[ChainSeatSchema]) -> None:
|
||||
"""Enforce 2 ≤ N ≤ 20 and reject duplicate agent ids within a chain."""
|
||||
if len(seats) < MIN_SEATS or len(seats) > MAX_SEATS:
|
||||
@@ -259,9 +318,7 @@ def _validate_stages(seats: list[ChainSeatSchema], stages: list[list[str]] | Non
|
||||
MAX_STAGE_GOAL_CHARS = 1000
|
||||
|
||||
|
||||
def _validate_stage_goals(
|
||||
stages: list[list[str]] | None, stage_goals: list[str | None] | None
|
||||
) -> None:
|
||||
def _validate_stage_goals(stages: list[list[str]] | None, stage_goals: list[str | None] | None) -> None:
|
||||
"""Validate the optional per-stage goals (None = no goals, always valid).
|
||||
|
||||
Rules:
|
||||
@@ -379,11 +436,16 @@ async def update_chain(request: Request, chain_id: str, body: ChainUpdateRequest
|
||||
_validate_seat_mode(body.seat_mode)
|
||||
store = _get_store(request)
|
||||
user_id = _current_user_id(request)
|
||||
is_admin = _is_admin(request)
|
||||
# Distinguish "omitted" from "explicitly sent"; only forward provided fields.
|
||||
fields = body.model_dump(exclude_unset=True)
|
||||
row = await store.update_chain(chain_id, user_id, is_admin=_is_admin(request), **fields)
|
||||
row = await store.update_chain(chain_id, user_id, is_admin=is_admin, **fields)
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="Chain not found")
|
||||
# 发布联动:链条更新后处于公共态时(点发布,或已发布链再改席位),把链上
|
||||
# 尚未发布的自定义智能体一并发布,保证其他账号解析公共链条时席位可用。
|
||||
if row.get("is_public"):
|
||||
await _publish_seat_agents(request, row, user_id, is_admin=is_admin)
|
||||
await _enrich_owner_names([row])
|
||||
return ChainResponse(**row)
|
||||
|
||||
|
||||
Reference in new issue
Block a user