feat(task): 添加任务修订功能和超时处理机制

添加了 `revise_task` 路由动作类型,允许用户修改、纠正或重新执行最新活动任务结果。
实现了工具失败指导原则,防止相同类别工具重复失败。
为任务规划器添加了超时处理机制,避免长时间等待。

BREAKING CHANGE: 任务路由逻辑已更新,新增 `revise_task` 动作类型。

fix(api): 修复任务详情API返回完整流程投影

修复了任务详情API端点,现在会包含过滤后的流程运行、事件和工件信息,
并确保时间戳字段正确序列化。

refactor(engine): 优化任务技能解析器摘要节点处理

改进了任务技能解析器对摘要节点的处理逻辑,对于仅依赖文本生成功能的摘要节
点不再分配具体技能,直接使用依赖项输出进行汇总。

test: 增加任务修订和超时处理测试用例

添加了测试用例验证任务修订输入记录反馈、超时回退到单模式以及
摘要节点技能解析等新功能。
This commit is contained in:
2026-05-21 16:40:44 +08:00
parent 0caca8db8a
commit a27560102b
22 changed files with 855 additions and 93 deletions

View File

@ -6,6 +6,7 @@ import { Bot, CheckCircle2, ChevronRight, Loader2, Paperclip, RefreshCcw, Thumbs
import type { ChatMessage, ProcessArtifact, ProcessEvent, ProcessRun } from '@/types';
import { getAccessToken, getFileUrl } from '@/lib/api';
import { getTaskCardMessageIndexes } from '@/lib/chat-messages';
import { AgentTeamBlock } from '@/components/chat-workbench/AgentTeamBlock';
import { MarkdownContent } from '@/components/chat-workbench/MarkdownContent';
import { ScrollArea } from '@/components/ui/scroll-area';
@ -40,17 +41,21 @@ function AuthImage({ src, alt, className }: { src: string; alt: string; classNam
function MessageBubble({
message,
showTaskCard,
canSendFeedback,
onFeedback,
onRequestRevision,
}: {
message: ChatMessage;
showTaskCard: boolean;
canSendFeedback: boolean;
onFeedback: (runId: string, feedbackType: 'satisfied' | 'revise' | 'abandon', comment?: string) => void;
onRequestRevision: (runId: string) => void;
}) {
const { locale } = useAppI18n();
const isUser = message.role === 'user';
const textContent = typeof message.content === 'string' ? message.content : String(message.content || '');
const [feedbackMode, setFeedbackMode] = React.useState<'satisfied' | 'revise' | null>(null);
const [feedbackMode, setFeedbackMode] = React.useState<'satisfied' | null>(null);
const [feedbackComment, setFeedbackComment] = React.useState('');
const validationFailed = message.validation_status === 'failed';
const validationDetails =
@ -118,7 +123,7 @@ function MessageBubble({
) : (
<MarkdownContent content={textContent} />
)}
{!isUser && message.task_id && (
{!isUser && showTaskCard && message.task_id && (
<div className="mt-3 rounded-md border border-border bg-muted/35 p-3">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="min-w-0">
@ -145,7 +150,7 @@ function MessageBubble({
<p className="mt-2 text-xs leading-5 text-muted-foreground">{validationDetails}</p>
</details>
)}
{!isUser && canSendFeedback && message.run_id && (
{!isUser && (canSendFeedback || message.feedback_state) && message.run_id && (
<div className="mt-3 space-y-2 border-t border-border/70 pt-3">
{message.feedback_state ? (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
@ -171,7 +176,7 @@ function MessageBubble({
</button>
<button
type="button"
onClick={() => setFeedbackMode('revise')}
onClick={() => onRequestRevision(message.run_id!)}
className="inline-flex h-8 items-center gap-1 rounded-md border border-border px-3 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
>
<RefreshCcw className="h-3.5 w-3.5" />
@ -191,11 +196,7 @@ function MessageBubble({
<textarea
value={feedbackComment}
onChange={(event) => setFeedbackComment(event.target.value)}
placeholder={
feedbackMode === 'revise'
? pickAppText(locale, '写下需要修改的地方...', 'Describe what needs to change...')
: pickAppText(locale, '可选:补充说明...', 'Optional note...')
}
placeholder={pickAppText(locale, '可选:补充说明...', 'Optional note...')}
className="min-h-20 w-full resize-none rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring"
/>
<div className="flex justify-end gap-2">
@ -330,6 +331,7 @@ export function MessageList({
selectedRunId,
onSelectRun,
onFeedback,
onRequestRevision,
}: {
messages: ChatMessage[];
isThinking: boolean;
@ -341,6 +343,7 @@ export function MessageList({
selectedRunId: string | null;
onSelectRun: (runId: string) => void;
onFeedback: (runId: string, feedbackType: 'satisfied' | 'revise' | 'abandon', comment?: string) => void;
onRequestRevision: (runId: string) => void;
}) {
const { locale } = useAppI18n();
const visibleMessages = React.useMemo(
@ -361,6 +364,7 @@ export function MessageList({
sortTime: parseTimelineTime(message.timestamp) ?? Number.MAX_SAFE_INTEGER / 2 + index,
order: index,
message,
messageIndex: index,
}));
const teamItems = teamGroups.map((group, index) => ({
kind: 'team' as const,
@ -377,9 +381,18 @@ export function MessageList({
return a.order - b.order;
});
}, [teamGroups, visibleMessages]);
const latestAssistantRunId = [...visibleMessages]
const taskCardMessageIndexes = React.useMemo(
() => getTaskCardMessageIndexes(visibleMessages),
[visibleMessages]
);
const latestFeedbackRunId = [...visibleMessages]
.reverse()
.find((message) => message.role === 'assistant' && message.run_id && message.task_id)?.run_id;
.find((message) =>
message.role === 'assistant'
&& message.run_id
&& message.task_id
&& message.task_status === 'awaiting_feedback'
)?.run_id;
return (
<ScrollArea className="h-full px-8" viewportRef={viewportRef}>
@ -397,8 +410,10 @@ export function MessageList({
<MessageBubble
key={item.key}
message={item.message}
canSendFeedback={Boolean(latestAssistantRunId && item.message.run_id === latestAssistantRunId)}
showTaskCard={taskCardMessageIndexes.has(item.messageIndex)}
canSendFeedback={Boolean(latestFeedbackRunId && item.message.run_id === latestFeedbackRunId)}
onFeedback={onFeedback}
onRequestRevision={onRequestRevision}
/>
) : (
<AgentTeamBlock