fix(rectification): keep a live analyzing step and drop the range-stop exits
Independent Staging Quality Gate / validate (push) Successful in 9m26s
Independent Staging Quality Gate / publish (push) Successful in 1m57s

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:
Jesse_Chen
2026-09-04 12:46:04 +08:00
parent fc5eca8c00
commit cfa824994e
13 changed files with 172 additions and 90 deletions
-25
View File
@@ -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;
}