支持 goPath=agentfx 的 iframe 嵌入问答、内置研判助手种子与报告导入协助卡,并完善深度研究流式报告能力。 Co-authored-by: Cursor <cursoragent@cursor.com>
102 lines
3.8 KiB
Python
102 lines
3.8 KiB
Python
"""Seeder for the built-in agentfx analyst agent and its import skill.
|
||
|
||
agentfx 任务深链(新系统 iframe 嵌入的智能体问答)固定使用内置智能体
|
||
``agentfx-analyst``:根据任务详情与用户输入搜集信息,生成 md 研判报告 +
|
||
结构化 ``task-reports.json``(14 类),总结后经前端「入库」协助卡或
|
||
``task-report-import`` 技能写入任务详情。
|
||
|
||
种子内容与 ``forced-research-responder`` 同一套模式:
|
||
- ``_agentfx_seed_assets/agentfx-analyst/{config.yaml, SOUL.md}`` → 复制到
|
||
``{DEER_FLOW_HOME}/agents/agentfx-analyst/``(目录存在则不动,尊重现场修改),
|
||
由启动流程的 ``_sync_legacy_agents`` upsert 进 ``agents`` 表(user_id IS NULL)。
|
||
- ``_agentfx_seed_assets/skills/task-report-import/`` → 复制到
|
||
``skills/public/task-report-import/``(``skills/`` 被 .gitignore 排除,同样
|
||
需要补建;public 类技能在 extensions_config 缺省即启用)。
|
||
|
||
必须在 ``_sync_legacy_agents`` **之前**调用。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import shutil
|
||
from pathlib import Path
|
||
|
||
from deerflow.config.paths import get_paths
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
AGENTFX_AGENT_ID = "agentfx-analyst"
|
||
TASK_REPORT_IMPORT_SKILL_NAME = "task-report-import"
|
||
|
||
_ASSETS_DIR = Path(__file__).parent / "_agentfx_seed_assets"
|
||
_SEED_FILES: tuple[str, ...] = ("config.yaml", "SOUL.md")
|
||
|
||
|
||
def _ensure_agentfx_agent_dir() -> bool:
|
||
"""Create the built-in agent directory when missing; never overwrite it."""
|
||
try:
|
||
agent_dir = get_paths().agent_dir(AGENTFX_AGENT_ID)
|
||
if agent_dir.exists():
|
||
return False
|
||
|
||
src_dir = _ASSETS_DIR / AGENTFX_AGENT_ID
|
||
if not src_dir.is_dir():
|
||
logger.error(
|
||
"Missing seed assets for agent '%s' (expected at %s)",
|
||
AGENTFX_AGENT_ID,
|
||
src_dir,
|
||
)
|
||
return False
|
||
|
||
agent_dir.mkdir(parents=True, exist_ok=True)
|
||
for filename in _SEED_FILES:
|
||
src = src_dir / filename
|
||
if src.exists():
|
||
(agent_dir / filename).write_bytes(src.read_bytes())
|
||
logger.info("Seeded built-in agent '%s'", AGENTFX_AGENT_ID)
|
||
return True
|
||
except Exception:
|
||
logger.exception("Failed to seed built-in agent '%s'", AGENTFX_AGENT_ID)
|
||
return False
|
||
|
||
|
||
def _ensure_task_report_import_skill() -> bool:
|
||
"""Seed the task-report-import skill into ``skills/public/`` when missing."""
|
||
try:
|
||
src_dir = _ASSETS_DIR / "skills" / TASK_REPORT_IMPORT_SKILL_NAME
|
||
if not (src_dir / "SKILL.md").exists():
|
||
logger.error(
|
||
"Missing seed assets for skill '%s' (expected at %s)",
|
||
TASK_REPORT_IMPORT_SKILL_NAME,
|
||
src_dir,
|
||
)
|
||
return False
|
||
|
||
from deerflow.skills.storage import get_or_new_skill_storage
|
||
|
||
public_dir = Path(get_or_new_skill_storage().get_skills_root_path()) / "public"
|
||
skill_dir = public_dir / TASK_REPORT_IMPORT_SKILL_NAME
|
||
if skill_dir.exists():
|
||
return False
|
||
|
||
# 技能带 scripts/ 子目录,整目录复制(字节级,保留原样)。
|
||
shutil.copytree(src_dir, skill_dir)
|
||
logger.info("Seeded built-in skill '%s'", TASK_REPORT_IMPORT_SKILL_NAME)
|
||
return True
|
||
except Exception:
|
||
logger.exception("Failed to seed built-in skill '%s'", TASK_REPORT_IMPORT_SKILL_NAME)
|
||
return False
|
||
|
||
|
||
def ensure_agentfx_agent() -> bool:
|
||
"""Seed the agentfx analyst agent + its import skill (idempotent, non-fatal).
|
||
|
||
Returns True if the agent directory was just created.
|
||
"""
|
||
_ensure_task_report_import_skill()
|
||
return _ensure_agentfx_agent_dir()
|
||
|
||
|
||
__all__ = ["AGENTFX_AGENT_ID", "TASK_REPORT_IMPORT_SKILL_NAME", "ensure_agentfx_agent"]
|