feat: 新增 agentfx 任务深链与研判报告入库流程

支持 goPath=agentfx 的 iframe 嵌入问答、内置研判助手种子与报告导入协助卡,并完善深度研究流式报告能力。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
董宏凯andCursor committed 2026-08-27 16:32:22 +08:00
1 parent 6e9e89a6cb
commit 52b9cbf707
34 files changed
+4488 -392

No files matched your search

@@ -0,0 +1,116 @@
/**
* 「agentfx」任务深链:复制自 [[task-deeplink]] 的 3qfx 单智能体模式,供新系统 iframe 嵌入
* (``${loginBase}?password=xxx&taskId=xxx&goPath=agentfx&embed=1&hideJump=1&theme=dark-blue``)。
*
* 与 3qfx 的差异(也是单独复制一份的原因,后续逻辑改动互不影响):
* 1. 任务详情不走外部 consumer(``VITE_AIPI_CONSUMER_URL``),改调本系统 Gateway 的
* ``GET {backend}/taskAnalyseSearch/cop-task-detail?taskId=``(带 DeerFlow 登录态,任务数据
* 来自 TaskCOP 任务表,即任务列表 / 报告导入用的同一份)。
* 2. **固定使用内置智能体** ``agentfx-analyst``(后端 seeder ``_agentfx_seed.py`` 启动时补建:
* 搜集信息 → 生成 report.md + task-reports.json(14 类)→ 总结 → 前端「入库」协助卡 /
* task-report-import 技能入库),不走「配置管理」业务映射。
* 3. 路由挂在 ``plan-task-agentfx`` 前缀下,页面代码见 [[AgentFxWorkspace]]。
*
* 任务详情整包仍暂存到与 3qfx 相同的 sessionStorage 键(``login.copTaskDetail``,见
* [[persistCopTaskDetail]]):该键按标签页隔离(嵌入态无跨标签污染),工作区「当前任务」
* 卡片与 AgentChatPage 的 taskBind 自恢复逻辑都读它,保持行为一致。
*/
import { apiFetch } from "@/core/api/fetch-client";
import { getBackendBaseURL } from "@/core/config";
import { clearTaskContext, persistTaskContext } from "@/core/auth/task-purpose";
import {
type CopTaskDetail,
type TaskDeeplinkTarget,
} from "@/core/auth/task-deeplink";
/** 深链 goPath 基础码(大小写不敏感匹配;不支持 ``-A`` 会商后缀)。 */
export const AGENTFX_GOPATH = "agentfx";
/**
* 固定使用的内置智能体 id(后端 ``_agentfx_seed.py`` 启动时补建并 upsert 进 agents 表,
* ``user_id IS NULL`` 对所有用户可见)。前端深链不查业务映射,直接进该智能体的问答。
*/
export const AGENTFX_BUILTIN_AGENT_ID = "agentfx-analyst";
/** 工作区路由前缀(与 StrategyRoutes 的 plan-task-agentfx 子树一致)。 */
export const AGENTFX_TASK_BASE = "/page/strategy/task-management/plan-task-agentfx";
/** 判断 goPath 是否为 agentfx(大小写不敏感)。 */
export function isAgentfxGoPath(goPath: string): boolean {
return goPath.trim().toLowerCase() === AGENTFX_GOPATH;
}
/**
* 拉取任务详情:本系统 Gateway 的 cop-task-detail(返回与旧 consumer 同构的
* ``{state, msg, data}`` 信封)。任务不存在 / 网络失败时返回 null,调用方(LoginPage)
* 用仅含任务 id 的 stub 兜底继续跳转。
*/
export async function fetchAgentfxTaskDetail(taskId: string): Promise<CopTaskDetail | null> {
if (!taskId) return null;
try {
// apiFetch 已统一注入 DeerFlow 登录态(Authorization / cookie),登录刚完成即可用。
const query = new URLSearchParams({ taskId });
const res = await apiFetch(
`${getBackendBaseURL()}/taskAnalyseSearch/cop-task-detail?${query.toString()}`,
{ method: "GET" },
);
if (!res.ok) {
console.warn("[agentfx-deeplink] cop-task-detail failed", { taskId, status: res.status });
return null;
}
const body = (await res.json().catch(() => null)) as { data?: unknown } | null;
const data = body && typeof body === "object" ? body.data : null;
if (!data || typeof data !== "object") return null;
const d = data as Record<string, unknown>;
return {
id: (d.id as string | number | null) ?? null,
overview: typeof d.overview === "string" ? d.overview : "",
taskDirection: typeof d.taskDirection === "string" ? d.taskDirection : "",
taskName: typeof d.taskName === "string" ? d.taskName : "",
taskContent: typeof d.taskContent === "string" ? d.taskContent : "",
};
} catch (error) {
console.warn("[agentfx-deeplink] cop-task-detail failed", { taskId, error });
return null;
}
}
/**
* 用 taskName / taskContent 拼开场信息文本;末尾附任务 id(task_id)供后端识别任务。
* 复制自 task-deeplink 的 buildOpeningText 基础三行(无 xdfx 行动追加),便于后续按新系统
* 要求单独调整格式。
*/
export function buildAgentfxOpeningText(detail: CopTaskDetail, taskId: string): string {
return [
detail.taskName ? `任务名称:${detail.taskName}` : "",
detail.taskContent ? `任务内容:${detail.taskContent}` : "",
taskId ? `任务id(task_id)为${taskId}` : "",
]
.filter(Boolean)
.join("\n");
}
/**
* 解析 agentfx 跳转目标:内置「任务研判报告智能体」的问答工作区(任务↔对话 1:N,按
* taskId + agentId 隔离)。清残留任务上下文 → 暂存任务上下文(taskId + agentId + 开场
* 信息)→ 返回 chats 索引路径,由工作区决定开最新历史对话 / 新建(首次无历史自动发
* 开场信息,见 AgentChatPage 的 taskBind)。
*/
export async function resolveAgentfxDeeplinkTarget(
detail: CopTaskDetail,
taskId: string,
): Promise<TaskDeeplinkTarget | null> {
// 清掉上一次深链可能残留的任务上下文(下面用本次任务重新写入)。
clearTaskContext();
const agentId = AGENTFX_BUILTIN_AGENT_ID;
persistTaskContext({
taskId,
agentId,
openingText: buildAgentfxOpeningText(detail, taskId),
actionId: taskId,
partitionByAction: false,
});
return { path: `${AGENTFX_TASK_BASE}/agent/${agentId}/chats` };
}