fix(rectification): keep a live analyzing step and drop the range-stop exits
A completed tool left only checkmarks under 正在分析, so the run looked stuck. The composer and scoring-card 先这样 buttons were unused early exits. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7951,3 +7951,51 @@
|
||||
- 相关记录:`TASK-upstream-sync-fix-20260903.md`
|
||||
- 复发自:`45d13258` / `f2241463` 上游同步批次
|
||||
- 修复版本:待发布
|
||||
|
||||
## BUG-516 | 校正工具完成后「正在分析」没有实时步骤,看起来卡住
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-04
|
||||
- 最近更新:2026-09-04
|
||||
- 影响面:生时校正对话时间线、`rectificationTimelineRows`、采集输入栏
|
||||
- 用户现象:用户回答采集题后,分析面板只显示已完成的「读取校正记录」,标题仍是「正在分析」,没有转圈或闪动的实时步骤,会误以为卡住。采集输入栏下方另有「先这样,先看当前范围」出口。
|
||||
- 触发条件:校正公开流完成一个工具、尚未开始下一工具或写出回答;同一回合仍处于 thinking。
|
||||
- 根因:公开流故意丢掉 `thinking.delta`。工具完成后界面把当前活动改成完成文案,时间线因此没有 `live` 行。标题虽带微弱闪动,步骤列表全是对勾。
|
||||
- 修复:工具完成后把当前活动改成「正在分析…」,并在未结束且没有实时步骤时补一条思考行。共享时间线在 `live` 且全是完成步骤时同样补这条行。去掉采集输入栏的 `rectification-collect-stop` 按钮。卡片上的同文案按钮见 BUG-518。
|
||||
- 验证:`rectification-timeline-adapter`、`chat-stream-settle-contract`、`rectification-spoken-collect`、`rectification-adopt-flow-20260902`。
|
||||
- 防复发:未结束的校正时间线在已有完成步骤时必须仍有一条 `live` 行和 spinner。不得把已完成工具名留作当前活动。不得把 token 级 thinking 重新放到公开流。
|
||||
- 相关记录:BUG-473
|
||||
- 复发自:无
|
||||
- 修复版本:待发布
|
||||
|
||||
## BUG-517 | 选择题选中态右边的对勾「已选择」是多余确认文案
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-04
|
||||
- 最近更新:2026-09-04
|
||||
- 影响面:`rectification-choice-card`
|
||||
- 用户现象:点了 A–D 后,选中项右侧再出现对勾和「已选择」。底色和边框已经标明选中,这组标记没有新信息。
|
||||
- 触发条件:点校正选择题任一选项,或点卡片上的停止项。
|
||||
- 根因:BUG-508 为了补确认感,在 `data-selected` 底色之外又加了 `Check` + 「已选择」。
|
||||
- 修复:去掉选中徽标和对应 CSS。选中仍靠 `data-selected="true"` 的底色与边框;pending 时卡顶「正在记录…」保留。
|
||||
- 验证:`rectification-surface-contract`。
|
||||
- 防复发:选择题选中态不得再渲染对勾或「已选择」。确认感只来自选项底色和卡顶记录行。
|
||||
- 相关记录:BUG-508
|
||||
- 复发自:无
|
||||
- 修复版本:待发布
|
||||
|
||||
## BUG-518 | 选择题卡片仍画出「先这样,先看当前范围」
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-04
|
||||
- 最近更新:2026-09-04
|
||||
- 影响面:`rectification-choice-card`
|
||||
- 用户现象:A–D 下面还有一颗「先这样,先看当前范围」。输入栏那颗同文案按钮已经去掉,卡片上这颗还在。
|
||||
- 触发条件:校正判断题(非 reverse_verify)画出选择题卡片。
|
||||
- 根因:卡片把 `stop_label` 一律画成第五个选项。采集输入栏的 `rectification-collect-stop` 去掉后,卡片出口还在。
|
||||
- 修复:`stop_label` 为「先这样,先看当前范围」时不画这颗按钮。盘外核对的「这题跳过」仍保留。服务端 stop 语义和文案常量不改。
|
||||
- 验证:`rectification-surface-contract`、`rectification-adopt-flow-20260902`。
|
||||
- 防复发:校正判断卡不得再渲染「先这样,先看当前范围」。不得把盘外核对的「这题跳过」一并删掉。
|
||||
- 相关记录:BUG-516
|
||||
- 复发自:无
|
||||
- 修复版本:待发布
|
||||
|
||||
+1
-1
@@ -224,7 +224,7 @@ The birth-time rectification session is the consultation transcript plus a house
|
||||
| `question-live` | the asking message carries the embedded card or the spoken stem | enabled, placeholder points at the card |
|
||||
| `question-gap`, retries left | one timeline live row “正在准备下一个问题…”, refetching on a 2s timer up to two retries | enabled |
|
||||
| `question-gap`, retries spent | “没有拿到下一个问题。” and a 44px “重新加载” | enabled |
|
||||
| `choice-pending` | the answered card (`Check` + “已选择”, a top row “正在记录…”) and the same live row from “正在记录本次选择…” through the follow-up turn | disabled, stop visible |
|
||||
| `choice-pending` | the answered card (`data-selected` fill, a top row “正在记录…”) and the same live row from “正在记录本次选择…” through the follow-up turn | disabled, stop visible |
|
||||
| `candidates` | the offer cards under the message that offered them | enabled |
|
||||
| `adopting` | “正在采用 HH:MM…” through the follow-up turn | disabled during the chain |
|
||||
| `confirmed` | “已确认校正时间:HH:MM” | enabled |
|
||||
|
||||
@@ -2980,17 +2980,6 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
font-size: var(--type-body-sm);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.rectification-choice-card__selected {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-inline-start: var(--space-2);
|
||||
color: var(--color-action);
|
||||
font-size: var(--type-caption);
|
||||
font-weight: 600;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.rectification-choice-card__selected svg { width: 14px; height: 14px; }
|
||||
.rectification-board__clock.is-declared { color: var(--color-ink-tertiary); }
|
||||
.rectification-board__empty { display: grid; gap: var(--space-1); }
|
||||
.rectification-board__empty p { margin: 0; }
|
||||
@@ -3200,20 +3189,6 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-caption);
|
||||
}
|
||||
.rectification-collect-stop {
|
||||
margin: var(--space-2) 0 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--color-ink-secondary);
|
||||
font: inherit;
|
||||
font-size: var(--type-caption);
|
||||
cursor: pointer;
|
||||
}
|
||||
.rectification-collect-stop:disabled {
|
||||
cursor: default;
|
||||
opacity: .48;
|
||||
}
|
||||
.rectification-house-table {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
|
||||
@@ -27,6 +27,24 @@ export const QUEUED_TIMELINE_ROW: ConsultationTimelineRow = {
|
||||
label: "正在处理…",
|
||||
};
|
||||
|
||||
/** Shown while the request is still in flight after completed steps, before the next live step. */
|
||||
export const ANALYZING_TIMELINE_ROW: ConsultationTimelineRow = {
|
||||
id: "analyzing",
|
||||
kind: "think",
|
||||
status: "live",
|
||||
label: "正在分析…",
|
||||
};
|
||||
|
||||
export function visibleTimelineRows(
|
||||
rows: readonly ConsultationTimelineRow[],
|
||||
live: boolean,
|
||||
): ConsultationTimelineRow[] {
|
||||
if (!live) return [...rows];
|
||||
if (rows.length === 0) return [QUEUED_TIMELINE_ROW];
|
||||
if (rows.some((row) => row.status === "live")) return [...rows];
|
||||
return [...rows, ANALYZING_TIMELINE_ROW];
|
||||
}
|
||||
|
||||
export function timelineSummaryLabel(rows: readonly ConsultationTimelineRow[], live: boolean): string {
|
||||
return live ? "正在分析" : `已完成 ${rows.length} 步`;
|
||||
}
|
||||
@@ -45,7 +63,7 @@ export function ConsultationRunTimeline({
|
||||
}>) {
|
||||
const bodyId = useId();
|
||||
const [userOpen, setUserOpen] = useState<boolean | null>(null);
|
||||
const visibleRows = rows.length === 0 && live ? [QUEUED_TIMELINE_ROW] : rows;
|
||||
const visibleRows = visibleTimelineRows(rows, live);
|
||||
if (visibleRows.length === 0) return null;
|
||||
const open = userOpen ?? live;
|
||||
const summary = timelineSummaryLabel(rows, live);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "@/lib/agent-activity-trace";
|
||||
import {
|
||||
RECTIFICATION_ACTIVITY_PROGRESS_LABELS,
|
||||
RECTIFICATION_ANALYZING_LIVE_LABEL,
|
||||
RECTIFICATION_TOOL_DONE_LABELS,
|
||||
RECTIFICATION_TOOL_PROGRESS_LABELS,
|
||||
activityTraceFromReceipt,
|
||||
@@ -938,8 +939,8 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
});
|
||||
completedReceipt = receiptFromRectificationActivityState(activityReceiptState);
|
||||
currentActivity = nextActivityView(currentActivity, {
|
||||
phase: rectificationToolActivityPhase(tool),
|
||||
label: rememberLiveActivity(RECTIFICATION_TOOL_DONE_LABELS[tool], tool),
|
||||
phase: "evidence-validation",
|
||||
label: rememberLiveActivity(RECTIFICATION_ANALYZING_LIVE_LABEL, null),
|
||||
completedTrail: rectificationCompletedTrail(activityReceiptState.completedSteps),
|
||||
});
|
||||
frames.touch();
|
||||
@@ -1371,15 +1372,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const collectSpokenPrompt = currentQuestion?.kind === "collect_spoken"
|
||||
? currentQuestion.prompt
|
||||
: null;
|
||||
const showCollectStop = Boolean(
|
||||
latestLiveQuestion
|
||||
&& latestLiveQuestion.kind === "collect_spoken"
|
||||
&& currentQuestion?.focus_id === latestLiveQuestion.focus_id
|
||||
&& !questionIsAnswered(latestLiveQuestion)
|
||||
&& !busy
|
||||
&& !readonly
|
||||
&& regeneratingMessageKey === null,
|
||||
);
|
||||
const adoptedRangeLabel = candidateResult?.credibleRange
|
||||
? `${candidateResult.credibleRange[0]}–${candidateResult.credibleRange[1]}`
|
||||
: null;
|
||||
@@ -1459,16 +1451,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
void submitStructuredChoice(STOP_ACTION, "stop");
|
||||
}
|
||||
|
||||
function submitCollectStop() {
|
||||
if (!latestLiveQuestion || latestLiveQuestion.kind !== "collect_spoken") return;
|
||||
void submitStructuredChoice(STOP_ACTION, "stop", {
|
||||
focusId: latestLiveQuestion.focus_id,
|
||||
questionId: latestLiveQuestion.question_id,
|
||||
probeId: latestLiveQuestion.probe_id,
|
||||
caseRevision: choiceCard?.case_revision ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
const boardPeek = compactBoard && !boardOpen ? (
|
||||
<RectificationBoardPeek
|
||||
result={candidateResult}
|
||||
@@ -1707,16 +1689,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
}}
|
||||
onStop={stopRun}
|
||||
/>
|
||||
{showCollectStop && (
|
||||
<button
|
||||
type="button"
|
||||
className="rectification-collect-stop"
|
||||
disabled={!canSend}
|
||||
onClick={submitCollectStop}
|
||||
>
|
||||
{CHOICE_STOP_LABEL}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="composer-footer">
|
||||
<ModelSelector
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { Check } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { InlineSpinner } from "@/components/inline-spinner";
|
||||
import type {
|
||||
ChoiceKey,
|
||||
RectificationChoiceCard as ChoiceCard,
|
||||
import {
|
||||
CHOICE_STOP_LABEL,
|
||||
type ChoiceKey,
|
||||
type RectificationChoiceCard as ChoiceCard,
|
||||
} from "@/lib/rectification-agentic/v9/choice-card";
|
||||
|
||||
type RectificationChoiceCardProps = Readonly<{
|
||||
@@ -71,28 +71,18 @@ export function RectificationChoiceCard(props: RectificationChoiceCardProps) {
|
||||
onClick={() => select(option.key)}
|
||||
>
|
||||
<strong>{option.key}.</strong> {option.label}
|
||||
{selectedKey === option.key ? (
|
||||
<span className="rectification-choice-card__selected">
|
||||
<Check aria-hidden="true" />
|
||||
已选择
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="birth-time-choice-option is-primary"
|
||||
data-selected={selectedKey === "stop" ? "true" : "false"}
|
||||
onClick={stop}
|
||||
>
|
||||
{props.card.stop_label}
|
||||
{selectedKey === "stop" ? (
|
||||
<span className="rectification-choice-card__selected">
|
||||
<Check aria-hidden="true" />
|
||||
已选择
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
{props.card.stop_label !== CHOICE_STOP_LABEL ? (
|
||||
<button
|
||||
type="button"
|
||||
className="birth-time-choice-option is-primary"
|
||||
data-selected={selectedKey === "stop" ? "true" : "false"}
|
||||
onClick={stop}
|
||||
>
|
||||
{props.card.stop_label}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</fieldset>
|
||||
</section>
|
||||
|
||||
@@ -33,6 +33,9 @@ export const RECTIFICATION_ACTIVITY_PROGRESS_LABELS: Readonly<Record<PublicRecti
|
||||
preparing_result: "正在准备结果…",
|
||||
};
|
||||
|
||||
/** Live label between a completed tool and the next tool or spoken answer. */
|
||||
export const RECTIFICATION_ANALYZING_LIVE_LABEL = "正在分析…";
|
||||
|
||||
export const RECTIFICATION_SLOW_STEP_MS = 45_000;
|
||||
export const RECTIFICATION_AGENT_ATTEMPT_TIMEOUT_MS = 210_000;
|
||||
export const RECTIFICATION_TIMEOUT_WARN_BEFORE_MS = 20_000;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { AgentActivityTraceItem } from "./agent-activity-trace.ts";
|
||||
import type { AgentActivityView } from "./chat-message-view.ts";
|
||||
import type { ConsultationTimelineRow } from "./consultation-run-timeline.ts";
|
||||
import type { ConsultationTimelineKind, ConsultationTimelineRow } from "./consultation-run-timeline.ts";
|
||||
import { RECTIFICATION_ANALYZING_LIVE_LABEL } from "./rectification-activity-labels.ts";
|
||||
import type { CompletedActivityReceiptView } from "./rectification-activity-receipt.ts";
|
||||
import { PUBLIC_RECTIFICATION_METHOD_LABELS } from "./rectification-varga-sentence.ts";
|
||||
|
||||
@@ -34,6 +35,12 @@ function isProgressLabel(label: string): boolean {
|
||||
return /^正在/.test(label.trim());
|
||||
}
|
||||
|
||||
function liveRowKind(phase: AgentActivityView["phase"] | undefined, label: string): ConsultationTimelineKind {
|
||||
if (phase === "answer-composition") return "write";
|
||||
if (label === RECTIFICATION_ANALYZING_LIVE_LABEL) return "think";
|
||||
return "calculate";
|
||||
}
|
||||
|
||||
/**
|
||||
* Project the rectification agent's tool trace and receipt onto the same
|
||||
* timeline rows the consultation surface renders, so both sessions share one
|
||||
@@ -76,10 +83,17 @@ export function rectificationTimelineRows(input: RectificationTimelineInput): Co
|
||||
if (!input.settled && !hasLiveRow && isProgressLabel(label)) {
|
||||
rows.push({
|
||||
id: RECTIFICATION_TIMELINE_LIVE_ID,
|
||||
kind: input.activity?.phase === "answer-composition" ? "write" : "calculate",
|
||||
kind: liveRowKind(input.activity?.phase, label),
|
||||
status: "live",
|
||||
label,
|
||||
});
|
||||
} else if (!input.settled && !hasLiveRow && rows.length > 0) {
|
||||
rows.push({
|
||||
id: RECTIFICATION_TIMELINE_LIVE_ID,
|
||||
kind: "think",
|
||||
status: "live",
|
||||
label: RECTIFICATION_ANALYZING_LIVE_LABEL,
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import test from "node:test";
|
||||
import { createElement } from "react";
|
||||
import { renderToString } from "react-dom/server";
|
||||
|
||||
import { ConsultationRunTimeline, timelineSummaryLabel } from "../src/components/consultation-run-timeline.tsx";
|
||||
import { ConsultationRunTimeline, timelineSummaryLabel, visibleTimelineRows } from "../src/components/consultation-run-timeline.tsx";
|
||||
import { latestAssistantView, type ChatMessage } from "../src/lib/chat-message-view.ts";
|
||||
|
||||
const read = (path: string) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
|
||||
@@ -69,6 +69,17 @@ test("the timeline collapses in place with a 180ms height transition and honours
|
||||
assert.match(live, /aria-expanded="true"/);
|
||||
assert.match(live, /正在处理…/);
|
||||
assert.match(live, /inline-spinner/);
|
||||
const liveAfterTool = renderToString(createElement(ConsultationRunTimeline, {
|
||||
rows: [{ id: "read", kind: "calculate", status: "done", label: "读取校正记录" }],
|
||||
live: true,
|
||||
}));
|
||||
assert.match(liveAfterTool, /读取校正记录/);
|
||||
assert.match(liveAfterTool, /正在分析…/);
|
||||
assert.match(liveAfterTool, /inline-spinner/);
|
||||
assert.deepEqual(
|
||||
visibleTimelineRows([{ id: "read", kind: "calculate", status: "done", label: "读取校正记录" }], true).map((row) => row.status),
|
||||
["done", "live"],
|
||||
);
|
||||
const settled = renderToString(createElement(ConsultationRunTimeline, {
|
||||
rows: [{ id: "method", kind: "method", status: "done", label: "已加载方法" }],
|
||||
live: false,
|
||||
|
||||
@@ -404,6 +404,11 @@ test("chat owns adopt cards on the offering message and drops the list-end hando
|
||||
assert.doesNotMatch(chat, /用这个时间看盘/);
|
||||
assert.match(chat, /改选/);
|
||||
assert.match(chat, /CHOICE_STOP_LABEL/);
|
||||
assert.doesNotMatch(chat, /rectification-collect-stop/);
|
||||
assert.match(
|
||||
readFileSync(new URL("../src/components/rectification-choice-card.tsx", import.meta.url), "utf8"),
|
||||
/stop_label !== CHOICE_STOP_LABEL/,
|
||||
);
|
||||
assert.match(chat, /已跳过(已采用/);
|
||||
assert.match(chat, /rectificationLiveProgressLabel/);
|
||||
assert.doesNotMatch(chat, /rectification-consult-handoff/);
|
||||
|
||||
@@ -101,6 +101,7 @@ test("cases current_question remains the submit contract, not a visual slot", ()
|
||||
assert.doesNotMatch(chat, /finalizeRectificationSpokenAndThinking/);
|
||||
assert.doesNotMatch(chat, /rectification-agentic\/v9\/spoken-answer/);
|
||||
assert.doesNotMatch(chat, /message\.text\.(?:includes|match|search)\(/);
|
||||
assert.doesNotMatch(chat, /rectification-collect-stop|showCollectStop|submitCollectStop/);
|
||||
assert.equal(parseRectificationChoiceCard(COLLECT_GET_QUESTION), null);
|
||||
});
|
||||
|
||||
|
||||
@@ -119,13 +119,17 @@ test("stopping keeps what streamed and says so; a 402 explains itself before lea
|
||||
});
|
||||
|
||||
test("the choice card confirms the tap and the board's first state shows the declared minute", () => {
|
||||
// BUG-508
|
||||
assert.match(choiceCard, /<Check aria-hidden="true" \/>/);
|
||||
assert.match(choiceCard, /已选择/);
|
||||
// BUG-508 / BUG-517: selected fill stays on the option; the Check + 已选择 badge is gone.
|
||||
assert.doesNotMatch(choiceCard, /<Check\b/);
|
||||
assert.doesNotMatch(choiceCard, /已选择/);
|
||||
assert.doesNotMatch(choiceCard, /rectification-choice-card__selected/);
|
||||
assert.match(choiceCard, /data-selected=\{selectedKey === option\.key \? "true" : "false"\}/);
|
||||
assert.match(choiceCard, /rectification-choice-card__pending/);
|
||||
assert.match(choiceCard, /InlineSpinner size=\{12\}/);
|
||||
assert.match(styles, /\.rectification-choice-card__selected \{/);
|
||||
assert.doesNotMatch(styles, /\.rectification-choice-card__selected/);
|
||||
assert.match(styles, /\.rectification-choice-card__pending \{/);
|
||||
// BUG-518: scoring cards do not render 先这样,先看当前范围; reverse_verify still has 这题跳过.
|
||||
assert.match(choiceCard, /stop_label !== CHOICE_STOP_LABEL/);
|
||||
// BUG-509
|
||||
assert.match(board, /declaredTime: string \| null;/);
|
||||
assert.match(board, /rectificationBoardEmptyCopy\(declaredTime\)/);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
RECTIFICATION_TIMELINE_SOURCE_LIMIT,
|
||||
rectificationTimelineRows,
|
||||
} from "../src/lib/rectification-timeline-adapter.ts";
|
||||
import { RECTIFICATION_ANALYZING_LIVE_LABEL } from "../src/lib/rectification-activity-labels.ts";
|
||||
import { PUBLIC_RECTIFICATION_METHODS } from "../src/lib/rectification-agentic/v9/public-receipt.ts";
|
||||
|
||||
const read = (path: string) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
|
||||
@@ -93,7 +94,8 @@ test("a phase label that names work under way becomes the live row when no tool
|
||||
});
|
||||
assert.equal(composing[0]?.kind, "write");
|
||||
|
||||
// A done label left on the activity after a tool completed is not a live step.
|
||||
// A done label left on the activity after a tool completed is not a live step
|
||||
// when there is no completed trail yet.
|
||||
const doneLabel = rectificationTimelineRows({
|
||||
trace: emptyActivityTrace(),
|
||||
receipt: undefined,
|
||||
@@ -112,6 +114,42 @@ test("a phase label that names work under way becomes the live row when no tool
|
||||
}).length, 1);
|
||||
});
|
||||
|
||||
test("a completed collect tool keeps a live analyzing row until the next tool or answer", () => {
|
||||
let trace = startActivityTraceStep(emptyActivityTrace(), "rectification-read-case", "正在读取校正记录…", 1);
|
||||
trace = completeActivityTraceStep(trace, "rectification-read-case", "读取校正记录");
|
||||
const rows = rectificationTimelineRows({
|
||||
trace,
|
||||
receipt: { steps: ["rectification-read-case"], methods: [] },
|
||||
activity: { phase: "loading-method", label: "读取校正记录" },
|
||||
settled: false,
|
||||
});
|
||||
assert.equal(rows.length, 2);
|
||||
assert.equal(rows[0]?.status, "done");
|
||||
assert.equal(rows[0]?.label, "读取校正记录");
|
||||
assert.deepEqual(rows[1], {
|
||||
id: RECTIFICATION_TIMELINE_LIVE_ID,
|
||||
kind: "think",
|
||||
status: "live",
|
||||
label: RECTIFICATION_ANALYZING_LIVE_LABEL,
|
||||
});
|
||||
|
||||
const withProgress = rectificationTimelineRows({
|
||||
trace,
|
||||
receipt: { steps: ["rectification-read-case"], methods: [] },
|
||||
activity: { phase: "evidence-validation", label: RECTIFICATION_ANALYZING_LIVE_LABEL },
|
||||
settled: false,
|
||||
});
|
||||
assert.equal(withProgress[1]?.kind, "think");
|
||||
assert.equal(withProgress[1]?.label, RECTIFICATION_ANALYZING_LIVE_LABEL);
|
||||
|
||||
assert.equal(rectificationTimelineRows({
|
||||
trace,
|
||||
receipt: { steps: ["rectification-read-case"], methods: [] },
|
||||
activity: { phase: "evidence-validation", label: RECTIFICATION_ANALYZING_LIVE_LABEL },
|
||||
settled: true,
|
||||
}).length, 1);
|
||||
});
|
||||
|
||||
test("after attempt.reset the timeline is empty, and a settled reply without a receipt renders no rows", () => {
|
||||
assert.deepEqual(rectificationTimelineRows({ trace: emptyActivityTrace(), receipt: undefined, activity: undefined, settled: false }), []);
|
||||
assert.deepEqual(rectificationTimelineRows({ trace: undefined, receipt: { steps: [], methods: [] }, activity: undefined, settled: true }), []);
|
||||
@@ -122,10 +160,13 @@ test("both chat surfaces render one timeline, one live marker, and no second rec
|
||||
assert.match(chatSource, /timeline: rectificationTimelineRows/);
|
||||
assert.doesNotMatch(chatSource, /rectification-activity-failure|<CompletedActivityReceipt/);
|
||||
assert.doesNotMatch(chatSource, /label: "正在组织回答…",\s*\}\),\s*\}\s*:\s*message;/);
|
||||
assert.match(chatSource, /rememberLiveActivity\(RECTIFICATION_ANALYZING_LIVE_LABEL, null\)/);
|
||||
assert.doesNotMatch(chatSource, /rectification-collect-stop|showCollectStop|submitCollectStop/);
|
||||
assert.doesNotMatch(rowSource, /ConsultationThinkingReport|showReport|activityTrace/);
|
||||
assert.doesNotMatch(activitySource, /thinking-orbs|ThinkingOrb|\bTraceStep\b|activityTrace/);
|
||||
assert.match(activitySource, /<InlineSpinner size=\{12\} \/>/);
|
||||
assert.doesNotMatch(packageJson, /thinking-orbs/);
|
||||
assert.doesNotMatch(globalStyles, /\.conversation\.is-rectification \.message-assistant \.agent-thinking-(step|marker)/);
|
||||
assert.doesNotMatch(globalStyles, /rectification-activity-receipt|rectification-activity-failure|consultation-step-tree/);
|
||||
assert.doesNotMatch(globalStyles, /rectification-collect-stop/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user