Merge branch 'master-nj' into 'master'
fix: 8BF 无 taskId 开全新会话,公共链条发布联动席位智能体 See merge request knowledge-base-bj/deerflow-code!35
This commit is contained in:
commit
1764d0d5f2
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}
|
lockedChainId={mapping?.chainId ?? null}
|
||||||
lockBusinessChain
|
lockBusinessChain
|
||||||
storageTaskId={storageTaskId}
|
storageTaskId={storageTaskId}
|
||||||
|
// 无 taskId 进入时每次都开全新会话(不回显 anonymous 键下的最新草稿);带 taskId 保持回显现状。
|
||||||
|
skipInitialDraftRestore={!taskId}
|
||||||
taskContext={taskContext as Record<string, unknown> | null}
|
taskContext={taskContext as Record<string, unknown> | null}
|
||||||
entryRailTaskId={taskId || null}
|
entryRailTaskId={taskId || null}
|
||||||
entryRailTaskCardSlot={taskId ? (
|
entryRailTaskCardSlot={taskId ? (
|
||||||
|
|||||||
@@ -310,6 +310,11 @@ export default function RoundtablePlanningPage({
|
|||||||
lockBusinessChain = false,
|
lockBusinessChain = false,
|
||||||
/** 独立业务页的草稿/后台作业存储键。 */
|
/** 独立业务页的草稿/后台作业存储键。 */
|
||||||
storageTaskId,
|
storageTaskId,
|
||||||
|
/**
|
||||||
|
* 独立业务页要求「每次进入都是全新会话」(8BF 无 taskId 模式):挂载时跳过按存储键
|
||||||
|
* 回显最新草稿,直接展示空白新会话;历史会话仍留在历史下拉/分析记录里,可手动打开。
|
||||||
|
*/
|
||||||
|
skipInitialDraftRestore = false,
|
||||||
/** 供独立业务页在原页头部插入自己的操作入口。 */
|
/** 供独立业务页在原页头部插入自己的操作入口。 */
|
||||||
toolbarSlot,
|
toolbarSlot,
|
||||||
/** 独立业务页在第一步左上角替换默认业务链选择区的内容。 */
|
/** 独立业务页在第一步左上角替换默认业务链选择区的内容。 */
|
||||||
@@ -351,6 +356,7 @@ export default function RoundtablePlanningPage({
|
|||||||
lockedChainId?: string | null;
|
lockedChainId?: string | null;
|
||||||
lockBusinessChain?: boolean;
|
lockBusinessChain?: boolean;
|
||||||
storageTaskId?: string | null;
|
storageTaskId?: string | null;
|
||||||
|
skipInitialDraftRestore?: boolean;
|
||||||
toolbarSlot?: ReactNode;
|
toolbarSlot?: ReactNode;
|
||||||
entryRailBusinessChainSlot?: ReactNode;
|
entryRailBusinessChainSlot?: ReactNode;
|
||||||
entryRailTaskCardSlot?: ReactNode;
|
entryRailTaskCardSlot?: ReactNode;
|
||||||
@@ -1368,6 +1374,12 @@ export default function RoundtablePlanningPage({
|
|||||||
// 回显聊天记录按存储键:8BF 用独立 `storageTaskId`;xdfx-A 按行动id;其它深链按真实 taskId。
|
// 回显聊天记录按存储键:8BF 用独立 `storageTaskId`;xdfx-A 按行动id;其它深链按真实 taskId。
|
||||||
const tid = draftStorageTaskId;
|
const tid = draftStorageTaskId;
|
||||||
if (!tid) return;
|
if (!tid) return;
|
||||||
|
// 8BF 无 taskId 模式:每次进入开全新会话,跳过回显(历史下拉仍可手动打开旧会话)。
|
||||||
|
// 仍要解除 taskRestoreResolved,别把依赖它的 autoStart 闸门永久挂起。
|
||||||
|
if (skipInitialDraftRestore) {
|
||||||
|
setTaskRestoreResolved(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request
|
|||||||
from langchain_core.messages import HumanMessage, SystemMessage
|
from langchain_core.messages import HumanMessage, SystemMessage
|
||||||
from pydantic import BaseModel, Field
|
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.config.app_config import AppConfig
|
||||||
from deerflow.models import create_chat_model
|
from deerflow.models import create_chat_model
|
||||||
from deerflow.runtime.user_context import get_effective_user_id
|
from deerflow.runtime.user_context import get_effective_user_id
|
||||||
@@ -202,6 +202,65 @@ def _get_store(request: Request):
|
|||||||
return store
|
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:
|
def _validate_seats(seats: list[ChainSeatSchema]) -> None:
|
||||||
"""Enforce 2 ≤ N ≤ 20 and reject duplicate agent ids within a chain."""
|
"""Enforce 2 ≤ N ≤ 20 and reject duplicate agent ids within a chain."""
|
||||||
if len(seats) < MIN_SEATS or len(seats) > MAX_SEATS:
|
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
|
MAX_STAGE_GOAL_CHARS = 1000
|
||||||
|
|
||||||
|
|
||||||
def _validate_stage_goals(
|
def _validate_stage_goals(stages: list[list[str]] | None, stage_goals: list[str | None] | None) -> None:
|
||||||
stages: list[list[str]] | None, stage_goals: list[str | None] | None
|
|
||||||
) -> None:
|
|
||||||
"""Validate the optional per-stage goals (None = no goals, always valid).
|
"""Validate the optional per-stage goals (None = no goals, always valid).
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
@@ -379,11 +436,16 @@ async def update_chain(request: Request, chain_id: str, body: ChainUpdateRequest
|
|||||||
_validate_seat_mode(body.seat_mode)
|
_validate_seat_mode(body.seat_mode)
|
||||||
store = _get_store(request)
|
store = _get_store(request)
|
||||||
user_id = _current_user_id(request)
|
user_id = _current_user_id(request)
|
||||||
|
is_admin = _is_admin(request)
|
||||||
# Distinguish "omitted" from "explicitly sent"; only forward provided fields.
|
# Distinguish "omitted" from "explicitly sent"; only forward provided fields.
|
||||||
fields = body.model_dump(exclude_unset=True)
|
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:
|
if row is None:
|
||||||
raise HTTPException(status_code=404, detail="Chain not found")
|
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])
|
await _enrich_owner_names([row])
|
||||||
return ChainResponse(**row)
|
return ChainResponse(**row)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user