feat: 新增 agentfx 任务深链与研判报告入库流程
支持 goPath=agentfx 的 iframe 嵌入问答、内置研判助手种子与报告导入协助卡,并完善深度研究流式报告能力。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
6e9e89a6cb
commit
52b9cbf707
34 files changed
+4488
-392
No files matched your search
@@ -42,6 +42,8 @@ import { useModels } from "@/core/models/hooks";
|
||||
import { useNotification } from "@/core/notification/hooks";
|
||||
import { saveLastAgentId, useThreadSettings } from "@/core/settings";
|
||||
import { stopActiveThreadBeforeClarificationReply, stopThreadRun, useThreadStream } from "@/core/threads/hooks";
|
||||
import { useResearchWritingFeed } from "@/core/threads/use-research-writing-feed";
|
||||
import { clearResearchReportStream } from "@/core/threads/use-research-report-stream";
|
||||
import { textOfMessage } from "@/core/threads/utils";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
import {
|
||||
@@ -75,7 +77,14 @@ import {
|
||||
readActionDetail,
|
||||
readCopTaskDetail,
|
||||
} from "@/core/auth/task-deeplink";
|
||||
import { hasTaskReportImportApproval } from "@/core/messages/utils";
|
||||
import type { RwfxSqGateStartContext } from "@/strategy-components/components/task-components/rwfx-sq-gate";
|
||||
import { TaskReportImportCard } from "@/strategy-components/components/task-components/TaskReportImportCard";
|
||||
import {
|
||||
AGENTFX_REPORT_JSON_OUTPUT_PATH,
|
||||
notifyTaskReportSaved,
|
||||
} from "@/strategy-components/lib/agentfx-report-schema";
|
||||
import type { Message } from "@langchain/langgraph-sdk";
|
||||
|
||||
/** 内置「模板数据转换器」agent 的稳定 id。仅此 agent 的对话页显示「页面模板 + 渲染预览」调试入口。 */
|
||||
const TEMPLATE_BUILDER_AGENT_ID = "template-json-builder";
|
||||
@@ -150,6 +159,36 @@ function rwfxSqReportRunContext(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务工作区(taskBind:3qfx/xdfx/agentfx)每次运行的隐藏任务上下文:把任务名称/
|
||||
* 内容/id(含开场白原文)经 ``task_external_context`` 随 run context 发给后端,
|
||||
* 由 TaskExternalContextMiddleware 在每次模型调用前注入为隐藏消息(不进聊天记录)。
|
||||
* 解决「新建对话后消息历史里没有开场信息、模型不知道任务id」的问题——任何新对话、
|
||||
* 旧对话续聊、上下文自恢复后模型都能拿到任务id,不需要向用户询问。
|
||||
*/
|
||||
function taskBindRunContext(
|
||||
taskContext: TaskContext | null,
|
||||
): Record<string, unknown> | undefined {
|
||||
if (!taskContext?.taskId) return undefined;
|
||||
const detail = readCopTaskDetail();
|
||||
return {
|
||||
task_external_context: {
|
||||
kind: "task_bind_opening",
|
||||
title: "任务开场信息",
|
||||
usage:
|
||||
"这是任务工作区随每次运行提供的隐藏任务上下文,不是用户的新输入。当前任务的" +
|
||||
"任务id(task_id)以此为准,直接使用,不要向用户询问、确认或让用户补充任务id;" +
|
||||
"回答时结合该任务背景。不要向用户说明页面注入了隐藏上下文。",
|
||||
data: {
|
||||
任务名称: detail?.taskName ?? "",
|
||||
任务内容: (detail?.taskContent ?? "") || (detail?.overview ?? ""),
|
||||
任务id: taskContext.taskId,
|
||||
开场信息原文: taskContext.openingText,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function AgentChatPage({
|
||||
embeddedAgentId,
|
||||
chatBasePath,
|
||||
@@ -372,6 +411,13 @@ export default function AgentChatPage({
|
||||
}),
|
||||
[knowledgeSelectionReady, selectedKnowledgeBaseIds, taskContext, taskId],
|
||||
);
|
||||
// 任务工作区(taskBind):每次运行都带隐藏任务上下文(任务id/名称/内容),
|
||||
// 新建对话的消息历史里没有开场信息时模型也能拿到任务id(见 taskBindRunContext)。
|
||||
// useMemo 缓存:流式期间 render 频繁,避免每次都读 sessionStorage + JSON.parse。
|
||||
const taskBindRunContextValue = useMemo(
|
||||
() => (taskBind && !taskGate ? taskBindRunContext(taskContext) : undefined),
|
||||
[taskBind, taskGate, taskContext],
|
||||
);
|
||||
const [thread, sendMessage] = useThreadStream({
|
||||
threadId: isNewThread ? undefined : threadId,
|
||||
assistantId: agent_id,
|
||||
@@ -381,6 +427,7 @@ export default function AgentChatPage({
|
||||
...(knowledgeSelectionReady
|
||||
? { llmwiki_knowledge_base_ids: selectedKnowledgeBaseIds }
|
||||
: {}),
|
||||
...(taskBindRunContextValue ?? {}),
|
||||
},
|
||||
// 任务闸门:新建 thread 打 metadata.taskId 标签,供任务对话列表(1:N)按任务过滤;
|
||||
// xdfx 额外打 metadata.actionId 标签,按「行动id」划分聊天记录(见 taskThreadMetadata)。
|
||||
@@ -439,6 +486,143 @@ export default function AgentChatPage({
|
||||
},
|
||||
});
|
||||
|
||||
// 写作模式深度研究进度:注入 displayThread 尾部(进度步骤条 + report.md
|
||||
// 流式 stub),成功且真实产物落地后自动撤出。
|
||||
const { displayThread } = useResearchWritingFeed(threadId, thread);
|
||||
|
||||
// ── agentfx「入库」协助卡 ──────────────────────────────────────────────────
|
||||
// 任务工作区(taskBind)里,内置研判智能体写出的 task-reports.json 产物落地且
|
||||
// 本轮流式结束后,向消息流尾部注入「是否入库」卡片。本地沙箱下 artifacts 可能
|
||||
// 记录宿主真实路径,按文件名匹配(虚拟/宿主路径都命中)。任务已有详情**不抑制**
|
||||
// 卡片(入库是整体替换,由卡片文案说明覆盖语义);已导入按 thread 记忆在
|
||||
// sessionStorage(刷新后不再重复询问)、「暂不入库」为本次会话内记忆。
|
||||
const reportImportSessionKey = threadId
|
||||
? `agentfx.reportImport.done.${threadId}`
|
||||
: "";
|
||||
const [reportImportHidden] = useState(() => {
|
||||
if (!reportImportSessionKey || typeof window === "undefined") return false;
|
||||
try {
|
||||
return sessionStorage.getItem(reportImportSessionKey) === "1";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const [reportImportDismissed, setReportImportDismissed] = useState(false);
|
||||
const reportJsonArtifactReady = (thread.values.artifacts ?? []).some((path) =>
|
||||
path.replace(/\\/g, "/").endsWith("/task-reports.json"),
|
||||
);
|
||||
const showReportImportCard =
|
||||
taskBind &&
|
||||
!taskGate &&
|
||||
!!taskId &&
|
||||
reportJsonArtifactReady &&
|
||||
!thread.isLoading &&
|
||||
!reportImportHidden &&
|
||||
!reportImportDismissed;
|
||||
const markReportImported = useCallback(() => {
|
||||
// 只写会话标记不改本地 state:导入成功的卡片保持挂载展示「已入库」,
|
||||
// 刷新/重进后因标记存在不再重复出现。
|
||||
if (!reportImportSessionKey) return;
|
||||
try {
|
||||
sessionStorage.setItem(reportImportSessionKey, "1");
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
}, [reportImportSessionKey]);
|
||||
|
||||
// ── 技能入库成功 → 通知外部宿主(iframe 嵌入场景)──────────────────────────
|
||||
// 技能路径的入库在后端 bash 里执行(卡片路径由 TaskReportImportCard 直接通知)。
|
||||
// 这里从消息流检测 task_report_import.py 的成功 stdout(含 "success": true 与
|
||||
// task_id),代为发送 window.top.postMessage。**只在回合结束后检测**(流式过程中
|
||||
// thread.messages 每个增量都会变,全量扫描会拖垮页面);只看线程里**最后一次**
|
||||
// 成功的技能入库:新一次入库会取代它触发新通知;刷新/重进旧对话时它已被记忆
|
||||
// (sessionStorage 按 thread 存最近已通知的消息 id),不会重复通知。
|
||||
const skillSavedNotifyKey = `agentfx.reportSavedNotified.${threadId}`;
|
||||
const [notifiedSkillMsgId, setNotifiedSkillMsgId] = useState<string | null>(() => {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
return sessionStorage.getItem(skillSavedNotifyKey);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!taskBind || thread.isLoading) return;
|
||||
const importCallIds = new Set<string>();
|
||||
for (const message of thread.messages) {
|
||||
if (message.type !== "ai" || !message.tool_calls) continue;
|
||||
for (const call of message.tool_calls) {
|
||||
const command = (call.args as { command?: unknown } | undefined)?.command;
|
||||
if (
|
||||
typeof command === "string" &&
|
||||
command.includes("task_report_import.py") &&
|
||||
call.id
|
||||
) {
|
||||
importCallIds.add(call.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (importCallIds.size === 0) return;
|
||||
let latest: { key: string; taskId: string } | null = null;
|
||||
for (const message of thread.messages) {
|
||||
if (message.type !== "tool" || !message.tool_call_id) continue;
|
||||
if (!importCallIds.has(message.tool_call_id)) continue;
|
||||
const content = typeof message.content === "string" ? message.content : "";
|
||||
if (!/"success"\s*:\s*true/.test(content)) continue;
|
||||
const taskIdMatch = content.match(/"task_id"\s*:\s*"([^"]+)"/);
|
||||
latest = {
|
||||
key: String(message.id ?? message.tool_call_id),
|
||||
taskId: taskIdMatch?.[1] ?? taskId ?? "",
|
||||
};
|
||||
}
|
||||
if (!latest || latest.key === notifiedSkillMsgId) return;
|
||||
notifyTaskReportSaved(latest.taskId);
|
||||
setNotifiedSkillMsgId(latest.key);
|
||||
try {
|
||||
sessionStorage.setItem(skillSavedNotifyKey, latest.key);
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
}, [
|
||||
taskBind,
|
||||
thread.isLoading,
|
||||
thread.messages,
|
||||
taskId,
|
||||
notifiedSkillMsgId,
|
||||
skillSavedNotifyKey,
|
||||
]);
|
||||
const displayThreadWithImportCard = useMemo(() => {
|
||||
if (!showReportImportCard) return displayThread;
|
||||
const cardMessage = {
|
||||
id: `agentfx-report-import-${threadId}`,
|
||||
type: "ai",
|
||||
content: "",
|
||||
additional_kwargs: {
|
||||
task_report_import_approval: {
|
||||
taskId,
|
||||
jsonPath: AGENTFX_REPORT_JSON_OUTPUT_PATH,
|
||||
},
|
||||
},
|
||||
} as unknown as Message;
|
||||
const messages = [...displayThread.messages, cardMessage];
|
||||
return {
|
||||
...displayThread,
|
||||
values: { ...displayThread.values, messages },
|
||||
messages,
|
||||
} as typeof displayThread;
|
||||
}, [displayThread, showReportImportCard, taskId, threadId]);
|
||||
|
||||
// 写作模式续写轮:已有 md 产物(report.md 已生成)时回传 writing_artifact_path,
|
||||
// 后端据此走「直接编辑画布」分支而不是再次挂载深度研究写报告管线(与
|
||||
// ChatPage 的回传逻辑对齐)。
|
||||
const writingArtifactPath = useMemo(() => {
|
||||
const isMarkdown = (path: string | null | undefined) =>
|
||||
!!path && /\.(md|markdown)(?:$|[?#])/i.test(path);
|
||||
return [...(thread.values.artifacts ?? [])]
|
||||
.reverse()
|
||||
.find((path) => isMarkdown(path));
|
||||
}, [thread.values.artifacts]);
|
||||
|
||||
// ── 「模板数据转换器」对话页专属调试入口(仅此 agent)──────────────────────────
|
||||
// 选一个页面模板,问答跑完后点「渲染预览」:取最近一条 AI 回复里的 JSON 填进模板,
|
||||
// 弹出 HTML 预览 + 结构校验信息,用于调试该智能体的转换效果。
|
||||
@@ -483,9 +667,19 @@ export default function AgentChatPage({
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("workspace:sandbox-resume-autoscroll"),
|
||||
);
|
||||
void sendMessage(threadId, message, { ...(extraContext ?? {}), agent_id });
|
||||
// 新一轮提问:清掉上一轮深度研究报告的注入状态(中断态步骤条等)。
|
||||
if (threadId) clearResearchReportStream(threadId);
|
||||
const writingExtraContext =
|
||||
settings.context.writing_mode && writingArtifactPath
|
||||
? { writing_artifact_path: writingArtifactPath }
|
||||
: {};
|
||||
void sendMessage(threadId, message, {
|
||||
...writingExtraContext,
|
||||
...(extraContext ?? {}),
|
||||
agent_id,
|
||||
});
|
||||
},
|
||||
[sendMessage, threadId, agent_id],
|
||||
[sendMessage, threadId, agent_id, settings.context.writing_mode, writingArtifactPath],
|
||||
);
|
||||
|
||||
// 任务流程:切换 / 新建会话时,停掉「上一会话服务端在跑」的 run(老对话要停止)。
|
||||
@@ -585,7 +779,7 @@ export default function AgentChatPage({
|
||||
: "ready";
|
||||
|
||||
return (
|
||||
<ThreadContext.Provider value={{ thread }}>
|
||||
<ThreadContext.Provider value={{ thread: displayThread }}>
|
||||
{/* Root: fixed-height horizontal split. ChatBox (chat + artifact
|
||||
preview) lives in the left flex cell; the agent detail panel is a
|
||||
separate flex sibling OUTSIDE ChatBox, so the artifact preview
|
||||
@@ -618,11 +812,11 @@ export default function AgentChatPage({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-1 items-center text-sm font-medium">
|
||||
<div className="flex min-w-0 flex-1 items-center overflow-hidden text-sm font-medium">
|
||||
<ThreadTitle threadId={threadId} thread={thread} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="flex shrink-0 items-center">
|
||||
{/* 智能体使用引导(chat 阶段)手动重放入口。 */}
|
||||
{agentGuideEligible && (
|
||||
<Tooltip content="重新播放智能体使用引导">
|
||||
@@ -719,7 +913,7 @@ export default function AgentChatPage({
|
||||
)}
|
||||
<TokenUsageIndicator
|
||||
enabled={tokenUsageEnabled}
|
||||
messages={thread.messages}
|
||||
messages={displayThread.messages}
|
||||
/>
|
||||
<ExportTrigger threadId={threadId} />
|
||||
<ArtifactTrigger />
|
||||
@@ -772,18 +966,22 @@ export default function AgentChatPage({
|
||||
onContextChange={(context) =>
|
||||
setSettings("context", context)
|
||||
}
|
||||
// 任务深链工作区:受控场景,隐藏「写作/记忆/参考文献」与「知识空间」。
|
||||
hideContextTools={taskCtxOn}
|
||||
knowledgeSelector={
|
||||
<LlmWikiKnowledgeSelector
|
||||
source="agent"
|
||||
agentId={agent_id}
|
||||
value={selectedKnowledgeBaseIds}
|
||||
onChange={changeKnowledgeSelection}
|
||||
autoSelectAll={
|
||||
isNewThread && pendingKnowledgeSelection.current === null
|
||||
}
|
||||
disabled={isDemoMode || knowledgeSelectionLoading}
|
||||
label="知识空间"
|
||||
/>
|
||||
taskCtxOn ? undefined : (
|
||||
<LlmWikiKnowledgeSelector
|
||||
source="agent"
|
||||
agentId={agent_id}
|
||||
value={selectedKnowledgeBaseIds}
|
||||
onChange={changeKnowledgeSelection}
|
||||
autoSelectAll={
|
||||
isNewThread && pendingKnowledgeSelection.current === null
|
||||
}
|
||||
disabled={isDemoMode || knowledgeSelectionLoading}
|
||||
label="知识空间"
|
||||
/>
|
||||
)
|
||||
}
|
||||
onFollowupsVisibilityChange={setShowFollowups}
|
||||
onSubmit={handleSubmit}
|
||||
@@ -804,19 +1002,31 @@ export default function AgentChatPage({
|
||||
{/* Question index pinned to this conversation column; the
|
||||
bottom inset keeps it clear of the docked input box. */}
|
||||
<QuestionNavigator
|
||||
messages={thread.messages}
|
||||
messages={displayThread.messages}
|
||||
className="top-14 right-0 bottom-40 left-0"
|
||||
/>
|
||||
<MessageList
|
||||
className="min-h-0 flex-1"
|
||||
threadId={threadId}
|
||||
thread={thread}
|
||||
thread={displayThreadWithImportCard}
|
||||
paddingBottom={showFollowups ? 96 : 24}
|
||||
tokenUsageEnabled={tokenUsageEnabled}
|
||||
// 输入框在流内(MessageList 下方),按钮贴对话区底部即可。
|
||||
scrollButtonClassName="bottom-2"
|
||||
onClarificationSubmit={handleClarificationSubmit}
|
||||
onEditResubmit={(text) => handleSubmit({ text, files: [] })}
|
||||
// agentfx「入库」协助卡:消息流尾部注入的虚拟消息经此插槽渲染。
|
||||
taskReportImportSlot={(message) =>
|
||||
hasTaskReportImportApproval(message) && showReportImportCard && taskId ? (
|
||||
<TaskReportImportCard
|
||||
taskId={taskId}
|
||||
threadId={threadId}
|
||||
jsonPath={AGENTFX_REPORT_JSON_OUTPUT_PATH}
|
||||
onImported={markReportImported}
|
||||
onDismiss={() => setReportImportDismissed(true)}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="from-background pointer-events-none shrink-0 bg-gradient-to-t to-transparent px-4 pb-4 pt-2">
|
||||
@@ -843,16 +1053,20 @@ export default function AgentChatPage({
|
||||
onContextChange={(context) =>
|
||||
setSettings("context", context)
|
||||
}
|
||||
// 任务深链工作区:受控场景,隐藏「写作/记忆/参考文献」与「知识空间」。
|
||||
hideContextTools={taskCtxOn}
|
||||
knowledgeSelector={
|
||||
<LlmWikiKnowledgeSelector
|
||||
source="agent"
|
||||
agentId={agent_id}
|
||||
value={selectedKnowledgeBaseIds}
|
||||
onChange={changeKnowledgeSelection}
|
||||
disabled={isDemoMode || knowledgeSelectionLoading}
|
||||
readOnly
|
||||
label="知识空间"
|
||||
/>
|
||||
taskCtxOn ? undefined : (
|
||||
<LlmWikiKnowledgeSelector
|
||||
source="agent"
|
||||
agentId={agent_id}
|
||||
value={selectedKnowledgeBaseIds}
|
||||
onChange={changeKnowledgeSelection}
|
||||
disabled={isDemoMode || knowledgeSelectionLoading}
|
||||
readOnly
|
||||
label="知识空间"
|
||||
/>
|
||||
)
|
||||
}
|
||||
onFollowupsVisibilityChange={setShowFollowups}
|
||||
onSubmit={handleSubmit}
|
||||
|
||||
@@ -61,6 +61,8 @@ import {
|
||||
useThreadSettings,
|
||||
} from "@/core/settings";
|
||||
import { useThreadStream, stopActiveThreadBeforeClarificationReply } from "@/core/threads/hooks";
|
||||
import { useResearchWritingFeed } from "@/core/threads/use-research-writing-feed";
|
||||
import { clearResearchReportStream } from "@/core/threads/use-research-report-stream";
|
||||
import { queueLlmWikiConversationDeposit } from "@/core/llmwiki/conversation-deposit";
|
||||
import { useSingleRoundRagMessages } from "@/core/threads/use-single-round-rag";
|
||||
import { useMultiRoundRagMessages } from "@/core/threads/use-multi-round-rag";
|
||||
@@ -319,33 +321,37 @@ export default function ChatPage() {
|
||||
const singleRoundRagSelected = isSingleRoundRagProfile(settings.context);
|
||||
const multiRoundRagSelected = isMultiRoundRagProfile(settings.context);
|
||||
const frontendRagSelected = isFrontendRagProfile(settings.context);
|
||||
// 写作模式深度研究进度:注入 displayThread 尾部(进度步骤条 + report.md
|
||||
// 流式 stub),成功且真实产物落地后自动撤出。
|
||||
const { displayThread: researchDisplayThread } = useResearchWritingFeed(threadId, thread);
|
||||
const displayThread = useMemo(() => {
|
||||
const base = researchDisplayThread;
|
||||
if (
|
||||
singleRoundRag.messages.length === 0 &&
|
||||
multiRoundRag.messages.length === 0 &&
|
||||
!singleRoundRag.isLoading &&
|
||||
!multiRoundRag.isLoading
|
||||
) {
|
||||
return thread;
|
||||
return base;
|
||||
}
|
||||
const messages = [
|
||||
...thread.messages,
|
||||
...base.messages,
|
||||
...singleRoundRag.messages,
|
||||
...multiRoundRag.messages,
|
||||
];
|
||||
return {
|
||||
...thread,
|
||||
values: { ...thread.values, messages },
|
||||
...base,
|
||||
values: { ...base.values, messages },
|
||||
messages,
|
||||
isLoading:
|
||||
thread.isLoading || singleRoundRag.isLoading || multiRoundRag.isLoading,
|
||||
} as typeof thread;
|
||||
base.isLoading || singleRoundRag.isLoading || multiRoundRag.isLoading,
|
||||
} as typeof base;
|
||||
}, [
|
||||
multiRoundRag.isLoading,
|
||||
multiRoundRag.messages,
|
||||
singleRoundRag.isLoading,
|
||||
singleRoundRag.messages,
|
||||
thread,
|
||||
researchDisplayThread,
|
||||
]);
|
||||
|
||||
const writingArtifactPath = useMemo(() => {
|
||||
@@ -491,6 +497,8 @@ export default function ChatPage() {
|
||||
if (settings.context.writing_mode && writingArtifactPath) {
|
||||
extraContext.writing_artifact_path = writingArtifactPath;
|
||||
}
|
||||
// 新一轮提问:清掉上一轮深度研究报告的注入状态(中断态步骤条等)。
|
||||
if (threadId) clearResearchReportStream(threadId);
|
||||
void sendMessage(
|
||||
threadId,
|
||||
message,
|
||||
@@ -697,10 +705,10 @@ export default function ChatPage() {
|
||||
: "bg-card/90 border-border/70 shadow-sm backdrop-blur-md",
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full items-center text-sm font-medium">
|
||||
<div className="flex min-w-0 flex-1 items-center overflow-hidden text-sm font-medium">
|
||||
<ThreadTitle threadId={threadId} thread={displayThread} />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{/* 仅在收起态显示「展开历史」钮;展开后由侧栏内(新对话右侧)的收起钮负责收起。 */}
|
||||
{compactChrome && !historySidebarOpen && !compactActionsHidden && (
|
||||
<Button
|
||||
|
||||
@@ -25,7 +25,13 @@ import {
|
||||
resolveDeeplinkTaskId,
|
||||
resolveTaskDeeplinkTarget,
|
||||
stubCopTaskDetail,
|
||||
type TaskDeeplinkTarget,
|
||||
} from '@/core/auth/task-deeplink';
|
||||
import {
|
||||
fetchAgentfxTaskDetail,
|
||||
isAgentfxGoPath,
|
||||
resolveAgentfxDeeplinkTarget,
|
||||
} from '@/core/auth/agentfx-deeplink';
|
||||
import { resolveUsernameByOnlineTicket } from '@/core/auth/wujie-ticket-login';
|
||||
import { isWujieEnv, useWujieProps } from '@/core/embed/wujie';
|
||||
import { isEmbeddedHost } from '@/core/embed/frame-context';
|
||||
@@ -465,22 +471,40 @@ export default function LoginPage() {
|
||||
// 真正无法解析(goPath 无法识别 / 单智能体未配置业务映射)才回退默认首页。
|
||||
if (taskId && goPath) {
|
||||
try {
|
||||
// xdfx 深链 ?taskId= 传入的是「行动id」,先换成真正的「任务id」;解析失败回退原始 id。
|
||||
const resolvedTaskId = await resolveDeeplinkTaskId(goPath, taskId);
|
||||
const realTaskId = resolvedTaskId || taskId;
|
||||
if (cancelled) return;
|
||||
let detail = await fetchCopTaskDetail(realTaskId);
|
||||
if (cancelled) return;
|
||||
if (!detail) {
|
||||
console.warn('[login] cop-task-detail missing, continue with stub', {
|
||||
goPath,
|
||||
taskId,
|
||||
realTaskId,
|
||||
});
|
||||
detail = stubCopTaskDetail(realTaskId);
|
||||
let target: TaskDeeplinkTarget | null = null;
|
||||
let realTaskId = taskId;
|
||||
if (isAgentfxGoPath(goPath)) {
|
||||
// agentfx 深链(新系统 iframe 嵌入):任务详情走本系统 Gateway 的
|
||||
// cop-task-detail(带刚存好的登录态);拉取失败不阻断,用仅含任务 id 的 stub 继续。
|
||||
let detail = await fetchAgentfxTaskDetail(taskId);
|
||||
if (cancelled) return;
|
||||
if (!detail) {
|
||||
console.warn('[login] agentfx cop-task-detail missing, continue with stub', {
|
||||
goPath,
|
||||
taskId,
|
||||
});
|
||||
detail = stubCopTaskDetail(taskId);
|
||||
}
|
||||
persistCopTaskDetail(detail);
|
||||
target = await resolveAgentfxDeeplinkTarget(detail, taskId);
|
||||
} else {
|
||||
// xdfx 深链 ?taskId= 传入的是「行动id」,先换成真正的「任务id」;解析失败回退原始 id。
|
||||
const resolvedTaskId = await resolveDeeplinkTaskId(goPath, taskId);
|
||||
realTaskId = resolvedTaskId || taskId;
|
||||
if (cancelled) return;
|
||||
let detail = await fetchCopTaskDetail(realTaskId);
|
||||
if (cancelled) return;
|
||||
if (!detail) {
|
||||
console.warn('[login] cop-task-detail missing, continue with stub', {
|
||||
goPath,
|
||||
taskId,
|
||||
realTaskId,
|
||||
});
|
||||
detail = stubCopTaskDetail(realTaskId);
|
||||
}
|
||||
persistCopTaskDetail(detail);
|
||||
target = await resolveTaskDeeplinkTarget(goPath, detail, realTaskId, taskId);
|
||||
}
|
||||
persistCopTaskDetail(detail);
|
||||
const target = await resolveTaskDeeplinkTarget(goPath, detail, realTaskId, taskId);
|
||||
if (cancelled) return;
|
||||
if (target) {
|
||||
console.info('[login] deeplink →', target.path, { goPath, realTaskId });
|
||||
|
||||
@@ -18,6 +18,11 @@ import {
|
||||
TaskFxIndexResolver,
|
||||
TaskFxWorkspace,
|
||||
} from "@/strategy-components/components/task-components/TaskFxWorkspace";
|
||||
import {
|
||||
AgentFxChat,
|
||||
AgentFxIndexResolver,
|
||||
AgentFxWorkspace,
|
||||
} from "@/strategy-components/components/task-components/AgentFxWorkspace";
|
||||
import { ActionAnalyzes } from "@/strategy-components/components/task-components/ActionAnalyzes";
|
||||
import KnowledgeBaseResourceManagement from "@/strategy-components/components/resource-management/KnowledgeBaseResourceManagement";
|
||||
import NetworkActorResourceManagement from "@/strategy-components/components/resource-management/NetworkActorResourceManagement";
|
||||
@@ -288,6 +293,27 @@ export default function StrategyRoutes() {
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
{/* 任务深链(agentfx):新系统 iframe 嵌入的智能体问答工作区(独立复制的 3qfx lite 版,
|
||||
任务详情来自本系统 cop-task-detail;业务映射用 AFX 行,见 agentfx-deeplink)。 */}
|
||||
<Route
|
||||
path="plan-task-agentfx/agent/:agent_id/chats"
|
||||
element={
|
||||
<AgentFxWorkspace taskBase="/page/strategy/task-management/plan-task-agentfx" />
|
||||
}
|
||||
>
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<AgentFxIndexResolver taskBase="/page/strategy/task-management/plan-task-agentfx" />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path=":thread_id"
|
||||
element={
|
||||
<AgentFxChat taskBase="/page/strategy/task-management/plan-task-agentfx" />
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="mnty-task-mechanism" element={<TaskTyFrom defaultPage="iframeXdty" title="XDTY任务" />} />
|
||||
<Route path="mnty-task-plan" element={<TaskTyFrom defaultPage="iframeFaty" title="方案TY任务" />} />
|
||||
<Route path="mnty-task-zsty" element={<TaskTyFrom defaultPage="iframeSysystem" title="XXTY任务" />} />
|
||||
|
||||
Reference in new issue
Block a user