fix(web): 智能体汇报展示层兜底与进度口径(FIX-7)

- 汇报正文里的 UUID、能力 ID、原因码展示时换成名称和人话,原文收进「正文原文」,历史不改
- 步骤进度统一 stepProgress:分子只数完成,跳过单独计(「4/6 · 跳过 2」)
- 进度行不再把失败的调用算进动作计数(3 次被拒的派发曾写成「派出了 5 个子会话」);
  实际结果行按会话树显示派出的子会话数
- 会话标题里的 ISO 时间与 download_failed 等码转人话

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 00:00:03 +08:00
1 parent 1b22bb5131
commit 417af9f253
9 files changed
+393 -27

No files matched your search

@@ -60,6 +60,7 @@ import {
runningText,
toolSummary,
} from "./conversation-model.ts";
import { collectNames, humanizeReport } from "./report-humanize.ts";
import { ToolCallIo } from "./tool-call-card-io.tsx";
import type { AgentStreamState, StreamItem, ToolCallView } from "./use-agent-stream.ts";
@@ -439,6 +440,7 @@ export function AgentConversation({
userMessages = [],
audit,
after,
knownNames,
}: {
run: AgentRunView;
live: AgentStreamState;
@@ -446,6 +448,8 @@ export function AgentConversation({
audit?: InvocationAudit[];
/** 流末尾追加的内容(其它审批卡、实际结果行) */
after?: ReactNode;
/** 工具结果之外已知的「ID → 名字」(子会话),汇报兜底换名用 */
knownNames?: [string, string][];
}) {
const active = isRunActive(run.status) && !live.finished;
const waiting = run.status === "waiting" || run.status === "running" || run.status === "queued";
@@ -460,6 +464,7 @@ export function AgentConversation({
const lastKey = live.items.at(-1)?.key;
const tail = live.items.at(-1);
const blocks = buildConversation(live.items, userMessages);
const names = collectNames(live.items, knownNames);
const anim = "animate-in fade-in-0 slide-in-from-bottom-1 duration-base ease-pop motion-reduce:animate-none";
const lastBlock = blocks.at(-1);
@@ -501,18 +506,21 @@ export function AgentConversation({
) : null}
{p.body.map((t) => {
const streaming = streamingText && t.key === lastKey;
// 旧会话 / 不守汇报格式的模型:UUID、能力 ID、原因码换成名称和人话,原文进技术信息
const human = streaming ? null : humanizeReport(t.text, names);
return (
<div key={t.key} className={cn("w-full min-w-0", freshKeys.has(t.key) && anim)}>
<StreamingResponse
className="max-w-[72ch]"
status={streaming ? "streaming" : "complete"}
copyText={t.text}
copyText={human?.text ?? t.text}
announce={false}
showActions={false}
>
{/* 流式中按原文显示(半截 Markdown 会闪),结束后按安全子集渲染 */}
{streaming ? t.text : <Markdown text={t.text} />}
{human ? <Markdown text={human.text} /> : t.text}
</StreamingResponse>
{human?.replaced.length ? <ReplacedIds replaced={human.replaced} /> : null}
</div>
);
})}
@@ -556,6 +564,32 @@ export function AgentConversation({
);
}
/** 汇报里被换掉的原文(ID、能力、原因码),收在技术信息里 */
function ReplacedIds({ replaced }: { replaced: { from: string; to: string }[] }) {
const uniq = [...new Map(replaced.map((r) => [r.from, r] as const)).values()];
return (
<TechInfo
title="正文原文"
className="mt-1"
items={[
{
label: "换掉的标识",
value: (
<ul className="space-y-0.5">
{uniq.map((r) => (
<li key={r.from}>
{r.from} → {r.to || "(省略)"}
</li>
))}
</ul>
),
wide: true,
},
]}
/>
);
}
// ── 实际结果 ──
/** 实际结果的一句话(服务端从下载计划 / 下载任务 / 整理记录汇总,不采信模型文本) */
@@ -592,10 +626,13 @@ export function ResultLine({
run,
report,
live,
subagents = 0,
}: {
run: AgentRunView;
report: GoalReport | null;
live: AgentStreamState;
/** 实际派出的子会话数(会话树里的直接子会话,不含被拒绝的派发) */
subagents?: number;
}) {
const u = { ...run.usage, ...(live.usage ?? {}) };
const durationMs =
@@ -623,6 +660,7 @@ export function ResultLine({
<p className="flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-0.5 text-meta text-muted-foreground">
{report ? null : <LoaderCircle aria-hidden className="size-3.5 animate-spin motion-reduce:animate-none" />}
<span className={cn("text-foreground/85", bad && "text-warning")}>实际结果:{resultText(report)}</span>
{subagents ? <span className="tabular-nums">· 派出 {subagents} 个子会话</span> : null}
{durationMs ? <span className="tabular-nums">· {durationLabel(durationMs)}</span> : null}
{u.costUsd ? <span className="tabular-nums">· 约 {formatUsd(u.costUsd)}</span> : null}
</p>
@@ -643,7 +681,7 @@ export function ResultLine({
items={[
{
label: "说明",
value: "上面的正文是智能体的说法,未经核查;实际结果取自下载任务和整理记录。",
value: "上面的正文是智能体的说法,未经核查;实际结果取自下载任务、整理记录和会话树。",
wide: true,
mono: false,
},