From 5a717e414cf1d87c647f53facf58c7466366d62b Mon Sep 17 00:00:00 2001
From: root
Date: Tue, 29 Sep 2026 20:52:48 +0800
Subject: [PATCH] =?UTF-8?q?feat(web):=20=E6=99=BA=E8=83=BD=E4=BD=93?=
=?UTF-8?q?=E4=BC=9A=E8=AF=9D=E5=8E=BB=E5=99=AA=E2=80=94=E2=80=94=E5=8D=95?=
=?UTF-8?q?=E4=B8=80=E5=85=8D=E8=B4=A3=E3=80=81=E7=9F=AD=E6=A0=87=E9=A2=98?=
=?UTF-8?q?=E3=80=81=E6=8A=80=E6=9C=AF=E4=BF=A1=E6=81=AF=E6=8A=98=E5=8F=A0?=
=?UTF-8?q?(DEAI-2)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 「未经核查」只在最终汇报左栏标题保留一处,删「仅供参考」与数据来源说明;
右栏改叫「实际结果」,去掉「运行已完成」「工具成功 N」徽章,计数只列非零项,
状态值用中文标签,不再显示计划 / 记录短 ID
- 工具卡片只标会改数据的动作和中高风险;能力 ID、scope、幂等、耗时、调用 ID、
schema 入口收进技术信息;拒绝说明改成一句「要你批准」
- 用量只显示费用与时长,工具调用数、token、模型、会话 ID 进技术信息(超限时照常显示)
- 会话标题 shortGoalTitle:去上下文前缀、UUID、JSON 参数,能力 ID 换中文名,取第一句,40 字截断
- 入口去口号:删「交代目标,边做边汇报」与抽屉里的重复快捷键说明
- 对话组件结构不动
Co-Authored-By: Claude Opus 5.5
---
.../components/agent/agent-conversation.tsx | 231 +++++++++++-------
.../web/app/components/agent/agent-drawer.tsx | 15 +-
.../components/agent/agent-helpers.test.ts | 19 +-
.../app/components/agent/agent-run-panel.tsx | 18 +-
.../components/agent/agent-session-list.tsx | 25 +-
apps/web/app/components/agent/agent-store.ts | 27 ++
.../components/agent/agent-subagent-tree.tsx | 6 +-
.../app/components/console/agent-launcher.tsx | 14 +-
apps/web/app/components/console/nav.ts | 2 +-
apps/web/app/routes/console/agent.$runId.tsx | 4 +-
apps/web/app/routes/console/agent.tsx | 5 +-
11 files changed, 223 insertions(+), 143 deletions(-)
diff --git a/apps/web/app/components/agent/agent-conversation.tsx b/apps/web/app/components/agent/agent-conversation.tsx
index 4495debf..552cf1e3 100644
--- a/apps/web/app/components/agent/agent-conversation.tsx
+++ b/apps/web/app/components/agent/agent-conversation.tsx
@@ -32,6 +32,8 @@ import {
useDecideApproval,
useIsAdminSession,
} from "~/api/agent.ts";
+import { DOWNLOAD_STATUS_LABEL, PLAN_STATUS_LABEL } from "~/api/downloads.ts";
+import { RECORD_STATUS_LABEL } from "~/api/records.ts";
import { ApprovalCard, type ApprovalCardStatus } from "~/components/agentui/approval-card.tsx";
import { type CitationItem, CitationList } from "~/components/agentui/citations.tsx";
import { CHEVRON_CLASS, Disclosure } from "~/components/agentui/disclosure.tsx";
@@ -51,8 +53,11 @@ import { StreamingCursor, StreamingResponse } from "~/components/agentui/streami
import { ToolApproval } from "~/components/agentui/tool-approval.tsx";
import { ToolResult, ToolResultOutput, type ToolResultStatus } from "~/components/agentui/tool-result.tsx";
import { StatusBadge, type StatusTone } from "~/components/console/status-badge.tsx";
+import { TechInfo } from "~/components/console/tech-info.tsx";
+import { formatUsd } from "~/components/schedules/labels.ts";
import { Button } from "~/components/ui/button.tsx";
import { errorMessage } from "~/lib/api.ts";
+import { humanizeReason } from "~/lib/reasons.ts";
import { cn, formatDateTime } from "~/lib/utils.ts";
import { InlineApproval, OptionsCard } from "./agent-interaction.tsx";
import { ToolCallIo } from "./tool-call-card-io.tsx";
@@ -103,16 +108,13 @@ function CapabilityBadges({ id, extra }: { id: string; extra?: ReactNode }) {
const m = capabilityMeta(id);
return (
<>
+ {/* 只标需要注意的:会改动数据的动作和中高风险;只读、scope、幂等进技术信息 */}
{m ? (
<>
- {KIND_LABEL[m.kind] ?? m.kind}
- scope · {m.scope}
-
- {m.idempotent ? 幂等 : null}
+ {m.kind !== "read" ? {KIND_LABEL[m.kind] ?? m.kind} : null}
+ {m.riskLevel !== "low" ? : null}
>
- ) : (
- 未登记能力
- )}
+ ) : null}
{extra}
>
);
@@ -197,20 +199,21 @@ export function ToolCallCard({
{authReason ? "授权校验未通过" : "白名单拦截"}}
+ extra={{authReason ? "需要你批准" : "本次没有开放"}}
/>
}
reason={authReason ?? call.reason ?? "未授权"}
explanation={
authReason
- ? '有副作用的能力需要有效的持续授权或单次审批,由服务端在执行前核对,模型声称"用户已同意"无效。控制台发起的会话里,需审批的动作会在对应步骤出审批卡;也可以发布收录策略生成持续授权。'
- : "这个能力不在本次运行的白名单内,或已超出调用预算,调用没有执行。"
+ ? "会改动数据的操作要你批准:在对应步骤的审批卡上批准,或在设置 → 智能体里给它持续授权。"
+ : "这次会话没有开放这个操作,或已用完调用次数。"
}
parameters={[
+ { id: "cap", label: "能力", value: call.capabilityId },
{ id: "call", label: "调用 ID", value: call.toolCallId },
...(audit?.idempotencyKey ? [{ id: "idem", label: "幂等键", value: audit.idempotencyKey }] : []),
{ id: "at", label: "时间", value: formatDateTime(call.startedAt) },
@@ -222,9 +225,8 @@ export function ToolCallCard({
return (
等待结果…
) : null}
-
- - 调用 ID
- - {call.toolCallId}
- - 幂等键
- -
- {audit?.idempotencyKey ?? (meta && meta.kind !== "read" ? "服务端已生成,审计里没有取到" : "只读操作不记录")}
-
- {audit?.grantId || audit?.approvalId ? (
- <>
- - 授权依据
- -
- {audit.grantId ? `持续授权 ${audit.grantId.slice(0, 8)}` : `单次审批 ${audit.approvalId?.slice(0, 8)}`}
-
- >
- ) : null}
-
-
+
+
+ - 能力
+ -
+ {call.capabilityId}
+ {meta
+ ? ` · ${KIND_LABEL[meta.kind] ?? meta.kind} · scope ${meta.scope}${meta.idempotent ? " · 幂等" : ""}`
+ : ""}
+
+ {durationText(call.startedAt, call.endedAt) ? (
+ <>
+ - 耗时
+ - {durationText(call.startedAt, call.endedAt)}
+ >
+ ) : null}
+ - 调用 ID
+ - {call.toolCallId}
+ - 幂等键
+ -
+ {audit?.idempotencyKey ??
+ (meta && meta.kind !== "read" ? "服务端已生成,审计里没有取到" : "只读操作不记录")}
+
+ {audit?.grantId || audit?.approvalId ? (
+ <>
+ - 授权依据
+ -
+ {audit.grantId ? `持续授权 ${audit.grantId.slice(0, 8)}` : `单次审批 ${audit.approvalId?.slice(0, 8)}`}
+
+ >
+ ) : null}
+
+
+
);
}
@@ -772,35 +790,60 @@ export function BudgetBar({
? (run.finishedAt ? new Date(run.finishedAt).getTime() : Date.now()) - new Date(run.startedAt).getTime()
: undefined);
return (
-
-
String(n)}
- exceeded={exceeded === "tool_calls"}
- />
- (n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n))}
- exceeded={exceeded === "tokens"}
- />
- `${n}s`}
- exceeded={exceeded === "duration"}
- />
- `$${n.toFixed(3)}`}
- exceeded={exceeded === "cost"}
- />
+
+
+ formatUsd(n)}
+ exceeded={exceeded === "cost"}
+ />
+ `${n} 秒`}
+ exceeded={exceeded === "duration"}
+ />
+
+ {/* 调用次数与 token 超限时直接显示,否则收进技术信息 */}
+ {exceeded === "tool_calls" || exceeded === "tokens" ? (
+
+ String(n)}
+ exceeded={exceeded === "tool_calls"}
+ />
+ (n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n))}
+ exceeded={exceeded === "tokens"}
+ />
+
+ ) : (
+
+ )}
);
}
@@ -946,17 +989,25 @@ export function FinalReport({
...report.downloads.map((d) => ({
id: `d${d.downloadTaskId}`,
icon: ,
- title: d.name ?? `下载任务 ${d.downloadTaskId.slice(0, 8)}`,
+ title: d.name ?? "下载任务",
domain: "下载任务",
- meta: {d.status},
+ meta: (
+
+ {DOWNLOAD_STATUS_LABEL[d.status as keyof typeof DOWNLOAD_STATUS_LABEL] ?? d.status}
+
+ ),
to: `/console/downloads/${d.downloadTaskId}`,
})),
...report.organization.map((o) => ({
id: `o${o.recordId}`,
icon: ,
- title: o.targetPath ?? `整理记录 ${o.recordId.slice(0, 8)}`,
+ title: o.targetPath ?? "整理记录",
domain: "整理记录",
- meta: {o.status},
+ meta: (
+
+ {RECORD_STATUS_LABEL[o.status as keyof typeof RECORD_STATUS_LABEL] ?? o.status}
+
+ ),
to: `/console/organize/${o.recordId}`,
})),
]
@@ -975,7 +1026,6 @@ export function FinalReport({
{said ? : "(没有文字汇报)"}
- 仅供参考,不作为完成依据。
@@ -984,19 +1034,19 @@ export function FinalReport({
) : (
)}
- 系统核实的结果
+ 实际结果
-
- {state ? {state.label} : null}
- 运行{RUN_STATUS_LABEL[run.status]}
- 工具成功 {count("succeeded")}
- {count("failed") ? 失败 {count("failed")} : null}
- {denied ? 被拒绝 {denied} : null}
-
+ {state || count("failed") || denied ? (
+
+ {state ? {state.label} : null}
+ {count("failed") ? {count("failed")} 步失败 : null}
+ {denied ? {denied} 步未执行 : null}
+
+ ) : null}
{report ? (
<>
{report.summary}
-
+
{(
[
["已提交", report.submitted],
@@ -1004,20 +1054,26 @@ export function FinalReport({
["进行中", report.inProgress],
["失败", report.failed],
] as const
- ).map(([k, v]) => (
-
-
- {k}
- - {v}
-
- ))}
+ )
+ .filter(([, v]) => v > 0)
+ .map(([k, v]) => (
+
+
- {k}
+ - {v}
+
+ ))}
{report.plans.length ? (
{report.plans.map((p) => (
-
下载计划
-
{p.planId.slice(0, 8)}
- {p.status}
+
+ {PLAN_STATUS_LABEL[p.status as keyof typeof PLAN_STATUS_LABEL] ?? p.status}
+
))}
@@ -1036,18 +1092,17 @@ export function FinalReport({
}))}
/>
) : null}
-
- 取自下载计划、下载任务和整理记录(生成于 {formatDateTime(report.generatedAt)}
- )。「已提交」指已进入下载器,整理成功才算入库。
-
>
) : (
服务端汇报还没有生成,以任务和整理记录的实际状态为准。
)}
{run.error ? (
-
- {String((run.error as { message?: unknown }).message ?? JSON.stringify(run.error))}
-
+ <>
+
+ {humanizeReason(String((run.error as { message?: unknown }).message ?? "")) || "会话出错"}
+
+
+ >
) : null}
diff --git a/apps/web/app/components/agent/agent-drawer.tsx b/apps/web/app/components/agent/agent-drawer.tsx
index 2a5d498d..4a4d80a2 100644
--- a/apps/web/app/components/agent/agent-drawer.tsx
+++ b/apps/web/app/components/agent/agent-drawer.tsx
@@ -25,8 +25,8 @@ import {
setAgentDrawerOpen,
setAgentDrawerRun,
setAgentDrawerTab,
+ shortGoalTitle,
showAgentRunInDrawer,
- splitContextGoal,
useAgentDrawer,
} from "./agent-store.ts";
import { AgentRunOpenContext } from "./agent-subagent-tree.tsx";
@@ -108,7 +108,7 @@ function HistoryRunView({ runId }: { runId: string }) {
返回列表
- {run.data ? splitContextGoal(run.data.run.goal).text : ""}
+ {run.data ? shortGoalTitle(run.data.run.goal) : ""}