fix: preserve investigation execution history by round

This commit is contained in:
2026-10-30 17:07:53 +08:00
parent 1ee21d1c7f
commit 185bf31446
16 changed files with 830 additions and 32 deletions
@@ -1,5 +1,5 @@
import { useEffect, useState, type ReactNode } from "react";
import { MessageSquare } from "lucide-react";
import { History, LoaderCircle, MessageSquare } from "lucide-react";
import { Button } from "@/shared/ui/button";
import { Disclosure } from "@/shared/ui/disclosure";
import { StatusBadge } from "@/shared/ui/status";
@@ -11,7 +11,8 @@ import type {
OperationalTaskAction,
OperationalTaskEvidence,
OperationalTaskReport,
OperationalTaskReportPage
OperationalTaskReportPage,
TaskExecutionHistoryTarget
} from "../api/operational-task-client";
import {
investigationDiagram,
@@ -24,11 +25,13 @@ import { useReportUpdateMotion } from "../hooks/use-report-update-motion";
import { ExpandableReading } from "@/shared/ui/reading-workspace";
import { InvestigationReportBlocks } from "./investigation-report-blocks";
import { InvestigationReportNavigation, type InvestigationReportSection } from "./investigation-report-navigation";
import { showMapNotice } from "@/features/map/core";
type ReportSelection = { snapshot: OperationalTaskReport; diagram: InvestigationDiagram | null };
type Props = {
task: OperationalTask;
onContinue: (discussion?: InvestigationDiscussion) => Promise<boolean | void> | boolean | void;
onViewExecutionHistory?: (target: TaskExecutionHistoryTarget, knownRunId?: string) => Promise<void> | void;
renderAction: (action: OperationalTaskAction) => ReactNode;
renderEvidence: (evidence: OperationalTaskEvidence) => ReactNode;
onLoadReports?: (offset: number) => Promise<OperationalTaskReportPage>;
@@ -126,6 +129,7 @@ function investigationTimeline(
export function InvestigationTaskReport({
task,
onContinue,
onViewExecutionHistory,
renderAction,
renderEvidence,
onLoadReports,
@@ -139,8 +143,24 @@ export function InvestigationTaskReport({
const [historyOffset, setHistoryOffset] = useState<number | null>(0);
const [historyLoading, setHistoryLoading] = useState(false);
const [historyError, setHistoryError] = useState<string | null>(null);
const [openingExecutionHistory, setOpeningExecutionHistory] = useState(false);
const live = selection === null;
const snapshot = selection?.snapshot ?? task.latest_report;
const displayedGeneration = live ? task.execution_generation : snapshot?.execution_generation;
const fallbackExecutionRun = task.latest_run ?? task.active_run;
const executionHistoryTarget: TaskExecutionHistoryTarget | null = snapshot ? {
executionGeneration: snapshot.execution_generation ?? null,
sessionId: snapshot.source_session_id
} : live && fallbackExecutionRun?.agent_session_id ? {
executionGeneration: fallbackExecutionRun.generation,
sessionId: fallbackExecutionRun.agent_session_id
} : null;
const knownExecutionRun = executionHistoryTarget
? [task.active_run, task.latest_run].find((run) => run
&& (executionHistoryTarget.executionGeneration == null
|| run.generation === executionHistoryTarget.executionGeneration)
&& run.agent_session_id === executionHistoryTarget.sessionId) ?? null
: null;
const report = snapshot?.report;
const progress = task.latest_progress?.execution_generation === task.execution_generation ? task.latest_progress : null;
// A current final report owns its archived graph. During investigation, a
@@ -221,6 +241,25 @@ export function InvestigationTaskReport({
setHistoryLoading(false);
}
};
const openExecutionHistory = async () => {
if (!onViewExecutionHistory || !executionHistoryTarget || openingExecutionHistory) return;
setOpeningExecutionHistory(true);
try {
if (knownExecutionRun) {
await onViewExecutionHistory(executionHistoryTarget, knownExecutionRun.run_id);
} else {
await onViewExecutionHistory(executionHistoryTarget);
}
} catch (error) {
showMapNotice({
tone: "error",
title: "调查执行记录打开失败",
message: error instanceof Error ? error.message : "请稍后重试。"
});
} finally {
setOpeningExecutionHistory(false);
}
};
return (
<ExpandableReading title={snapshot?.report.title ?? task.title}
@@ -229,7 +268,7 @@ export function InvestigationTaskReport({
<header className="investigation-report-header">
<div className="min-w-0">
<p className="text-xs text-[var(--ink-muted)]">
调查任务 · 第 {task.execution_generation} 轮
{displayedGeneration == null ? "调查任务 · 历史轮次" : `调查任务 · 第 ${displayedGeneration} 轮`}
</p>
<div className="mt-1 flex flex-wrap items-center gap-2">
<h2 className="text-base font-semibold leading-6 text-[var(--ink)]">{task.title}</h2>
@@ -247,17 +286,31 @@ export function InvestigationTaskReport({
</div>
<p className="mt-1 text-xs leading-5 text-[var(--ink-muted)]">{task.summary}</p>
</div>
<Button
variant="outline"
size="sm"
title="打开调查协作对话,引用此任务,不会直接发送。"
onClick={() =>
onContinue({ context: snapshot ? { report_revision: snapshot.revision } : {}, reportGeneration: snapshot?.execution_generation })
}
>
<MessageSquare size={14} aria-hidden />
Agent 协作
</Button>
<div className="flex flex-wrap items-center justify-end gap-2">
<Button
variant="outline"
size="sm"
disabled={!onViewExecutionHistory || !executionHistoryTarget || openingExecutionHistory}
title={onViewExecutionHistory && executionHistoryTarget ? "在左侧 Agent 中打开所选轮次自动同步的调查执行记录,并可创建调试会话。" : "所选轮次还没有可查看的执行记录。"}
onClick={() => void openExecutionHistory()}
>
{openingExecutionHistory
? <LoaderCircle size={14} className="animate-spin" aria-hidden />
: <History size={14} aria-hidden />}
执行记录
</Button>
<Button
variant="outline"
size="sm"
title="打开调查协作对话,引用此任务,不会直接发送。"
onClick={() =>
onContinue({ context: snapshot ? { report_revision: snapshot.revision } : {}, reportGeneration: snapshot?.execution_generation })
}
>
<MessageSquare size={14} aria-hidden />
Agent 协作
</Button>
</div>
</header>
<InvestigationReportNavigation reportRef={reportRef} sections={sections} />