feat(engine): 添加运行时上下文支持并重构工具迭代限制

添加 RuntimeContext 类用于捕获模型运行时的日期时间信息,
包括UTC时间、本地时间和时区信息,并在系统提示中显示这些信息。

同时增加最大上下文消息数和工具迭代次数的配置选项,
将验证服务从引擎加载器中移除,并更新相关的数据结构和接口。

BREAKING CHANGE: 移除了验证服务,相关字段被替换为证据状态和接受状态。

- 添加 RuntimeContext 类和相关渲染方法
- 增加 max_context_messages 和 max_tool_iterations 配置
- 移除 ValidationService 相关代码
- 更新消息记录中的验证状态字段
- 添加原始工具调用检测和回退处理
This commit is contained in:
2026-05-26 11:18:35 +08:00
parent 16347caf5e
commit 6e9e74d1ee
57 changed files with 5710 additions and 1582 deletions

View File

@ -3,7 +3,7 @@
import Link from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import React, { useMemo, useState } from 'react';
import { AlertCircle, ArrowLeft, Bot, CheckCircle2, Download, FileText, HelpCircle, Loader2, MessageSquare, RefreshCw, ThumbsUp, Trash2, User, XCircle } from 'lucide-react';
import { AlertCircle, ArrowLeft, Bot, CheckCircle2, Download, FileText, Loader2, MessageSquare, RefreshCw, ThumbsUp, Trash2, User, XCircle } from 'lucide-react';
import { TaskRuntimeStatusBadge, formatTaskRuntimeDuration, formatTaskRuntimeTime, progressPercent } from '@/components/task-runtime/TaskRuntimeShared';
import { Badge } from '@/components/ui/badge';
@ -17,8 +17,9 @@ import { buildTaskRuntimeView, type TaskRuntimeNodeView } from '@/lib/task-runti
import { useChatStore } from '@/lib/store';
import type { BackendTask, BackendTaskRun, ProcessArtifact, ProcessEvent, ProcessRun } from '@/types';
type TaskFeedbackType = 'satisfied' | 'revise' | 'abandon';
type TaskFeedbackType = 'accept' | 'revise' | 'abandon';
type TaskFeedbackItem = {
acceptance_type?: unknown;
feedback_type?: unknown;
comment?: unknown;
created_at?: unknown;
@ -151,12 +152,6 @@ export default function TaskDetailPage() {
const backendFeedbackRunId = backendTask ? pickFeedbackRunId(backendTask) : null;
if (!task && backendTask) {
const validation = backendTask.validation_result;
const accepted = Boolean(validation?.accepted);
const validationIssues = [
...arrayOfStrings(validation?.issues),
...arrayOfStrings(validation?.missing_requirements),
];
const feedbackItems = backendTask.feedback || [];
return (
<div className="mx-auto max-w-5xl space-y-6 p-6">
@ -232,57 +227,6 @@ export default function TaskDetailPage() {
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">{pickAppText(locale, '验证和反馈', 'Validation and feedback')}</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<div className="rounded-lg border border-border bg-muted/25 p-4">
<div className="flex items-center gap-2">
{validation ? (
accepted ? <CheckCircle2 className="h-5 w-5 text-[#657162]" /> : <XCircle className="h-5 w-5 text-destructive" />
) : (
<HelpCircle className="h-5 w-5 text-muted-foreground" />
)}
<div className="font-medium">
{validation
? accepted
? pickAppText(locale, '验证通过', 'Validation passed')
: pickAppText(locale, '需要继续修改', 'Needs revision')
: pickAppText(locale, '尚未验证', 'Not validated yet')}
</div>
</div>
{validation ? (
<div className="mt-2 text-muted-foreground">
{pickAppText(locale, '评分', 'Score')}: {String(validation.score ?? '-')} · {pickAppText(locale, '验证器', 'Validator')}: {String(validation.validator ?? '-')}
</div>
) : null}
{validationIssues.length > 0 && (
<ul className="mt-3 list-disc space-y-1 pl-5 text-muted-foreground">
{validationIssues.map((item, index) => <li key={`${item}:${index}`}>{item}</li>)}
</ul>
)}
{typeof validation?.recommended_revision_prompt === 'string' && validation.recommended_revision_prompt && (
<p className="mt-3 rounded-md bg-background p-3 text-muted-foreground">{validation.recommended_revision_prompt}</p>
)}
</div>
<div className="space-y-2">
<div className="font-medium">{pickAppText(locale, '用户反馈', 'User feedback')}</div>
{feedbackItems.length === 0 ? (
<p className="text-muted-foreground">{pickAppText(locale, '还没有用户反馈。', 'No user feedback yet.')}</p>
) : (
feedbackItems.map((item, index) => (
<div key={index} className="rounded-md border border-border p-3">
<div className="font-medium">{humanFeedback(String(item.feedback_type || ''), locale)}</div>
{item.comment ? <p className="mt-1 text-muted-foreground">{String(item.comment)}</p> : null}
{item.created_at ? <p className="mt-1 text-xs text-muted-foreground">{formatTaskRuntimeTime(String(item.created_at), locale)}</p> : null}
</div>
))
)}
</div>
</CardContent>
</Card>
</div>
);
}
@ -476,6 +420,7 @@ export default function TaskDetailPage() {
comment,
});
setRuntimeFeedback({
acceptance_type: feedbackType,
feedback_type: feedbackType,
comment: comment || '',
created_at: new Date().toISOString(),
@ -660,14 +605,14 @@ function TaskFeedbackPanel({
return (
<Card>
<CardHeader>
<CardTitle className="text-base">{pickAppText(locale, '任务反馈', 'Task feedback')}</CardTitle>
<CardTitle className="text-base">{pickAppText(locale, '任务验收', 'Task acceptance')}</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{recordedFeedback ? (
<div className="rounded-md border border-border bg-muted/25 p-3 text-sm">
<div className="flex items-center gap-2 font-medium">
<CheckCircle2 className="h-4 w-4 text-[#657162]" />
{pickAppText(locale, '已提交反馈', 'Feedback submitted')}: {humanFeedback(String(recordedFeedback.feedback_type || ''), locale)}
{pickAppText(locale, '已提交验收', 'Acceptance submitted')}: {humanFeedback(String(recordedFeedback.acceptance_type || recordedFeedback.feedback_type || ''), locale)}
</div>
{recordedFeedback.comment ? (
<p className="mt-2 text-muted-foreground">{String(recordedFeedback.comment)}</p>
@ -678,22 +623,22 @@ function TaskFeedbackPanel({
</div>
) : isFinalized ? (
<div className="rounded-md border border-border bg-muted/25 p-3 text-sm text-muted-foreground">
{pickAppText(locale, '任务已结束,不能再提交新的反馈。', 'This task is finalized and cannot accept new feedback.')}
{pickAppText(locale, '任务已结束,不能再提交新的验收。', 'This task is finalized and cannot accept new acceptance.')}
</div>
) : !runId ? (
<div className="rounded-md border border-border bg-muted/25 p-3 text-sm text-muted-foreground">
{pickAppText(locale, '暂无可反馈的运行记录。', 'No run is available for feedback yet.')}
{pickAppText(locale, '暂无可验收的运行记录。', 'No run is available for acceptance yet.')}
</div>
) : null}
<div className="grid gap-2 sm:grid-cols-3">
<FeedbackButton
type="satisfied"
type="accept"
icon={<ThumbsUp className="mr-2 h-4 w-4" />}
label={pickAppText(locale, '满意', 'Satisfied')}
label={pickAppText(locale, '接受', 'Accept')}
actionBusy={actionBusy}
disabled={!canSubmit}
onClick={() => submit('satisfied', comment.trim() || undefined)}
onClick={() => submit('accept', comment.trim() || undefined)}
/>
<FeedbackButton
type="revise"
@ -717,10 +662,10 @@ function TaskFeedbackPanel({
value={comment}
onChange={(event) => setComment(event.target.value)}
disabled={Boolean(recordedFeedback) || isFinalized || Boolean(actionBusy)}
placeholder={pickAppText(locale, '需要修改时写下具体要求;满意或放弃可选填说明。', 'Describe requested changes; notes are optional for satisfied or abandon.')}
placeholder={pickAppText(locale, '需要修改时写下具体要求;接受或放弃可选填说明。', 'Describe requested changes; notes are optional for accept or abandon.')}
/>
<div className="text-xs text-muted-foreground">
{pickAppText(locale, '反馈将记录到当前任务运行:', 'Feedback will be recorded on run: ')}
{pickAppText(locale, '验收将记录到当前任务运行:', 'Acceptance will be recorded on run: ')}
<span className="font-mono">{runId || '-'}</span>
<span className="mx-1">·</span>
{pickAppText(locale, '会话:', 'Session: ')}
@ -807,8 +752,7 @@ function humanTaskStatus(status: string, locale: 'zh-CN' | 'en-US') {
const map: Record<string, [string, string]> = {
open: ['已创建', 'Open'],
running: ['执行中', 'Running'],
validating: ['验证中', 'Validating'],
awaiting_feedback: ['等待反馈', 'Awaiting feedback'],
awaiting_acceptance: ['等待验收', 'Awaiting acceptance'],
needs_revision: ['需要修改', 'Needs revision'],
closed: ['已完成', 'Closed'],
abandoned: ['已放弃', 'Abandoned'],
@ -818,10 +762,10 @@ function humanTaskStatus(status: string, locale: 'zh-CN' | 'en-US') {
}
function humanFeedback(type: string, locale: 'zh-CN' | 'en-US') {
if (type === 'satisfied') return pickAppText(locale, '满意', 'Satisfied');
if (type === 'accept' || type === 'satisfied') return pickAppText(locale, '接受', 'Accepted');
if (type === 'revise') return pickAppText(locale, '请求修改', 'Revision requested');
if (type === 'abandon') return pickAppText(locale, '放弃任务', 'Abandoned');
return type || pickAppText(locale, '反馈', 'Feedback');
return type || pickAppText(locale, '验收', 'Acceptance');
}
function humanFinishReason(reason: string, locale: 'zh-CN' | 'en-US') {
@ -848,7 +792,3 @@ function feedbackForRun(items: TaskFeedbackItem[], runId: string | null): TaskFe
function latestFeedback(items: TaskFeedbackItem[]): TaskFeedbackItem | null {
return [...items].reverse()[0] ?? null;
}
function arrayOfStrings(value: unknown): string[] {
return Array.isArray(value) ? value.map((item) => String(item)).filter(Boolean) : [];
}

View File

@ -142,7 +142,7 @@ function OrdinaryTasks() {
</div>
</TableCell>
<TableCell>
<Badge variant={task.status === 'awaiting_feedback' || task.status === 'closed' ? 'default' : 'secondary'}>
<Badge variant={task.status === 'awaiting_acceptance' || task.status === 'closed' ? 'default' : 'secondary'}>
{taskStatusLabel(task.status, locale)}
</Badge>
</TableCell>
@ -185,8 +185,7 @@ function taskStatusLabel(status: string, locale: 'zh-CN' | 'en-US') {
const labels: Record<string, [string, string]> = {
open: ['已创建', 'Open'],
running: ['执行中', 'Running'],
validating: ['验证中', 'Validating'],
awaiting_feedback: ['等待反馈', 'Awaiting feedback'],
awaiting_acceptance: ['等待验收', 'Awaiting acceptance'],
needs_revision: ['需要修改', 'Needs revision'],
closed: ['已完成', 'Closed'],
abandoned: ['已放弃', 'Abandoned'],