重构 改为标准化输出
This commit is contained in:
@@ -440,6 +440,7 @@ button:disabled {
|
||||
.cad-status-label { color: var(--ui-text-subtle); font-size: 10px; font-weight: 500; text-transform: uppercase; }
|
||||
.cad-tool-name { overflow: hidden; max-width: 46%; border: 1px solid var(--ui-border-muted); border-radius: 3px; background: var(--ui-control-bg); color: var(--ui-accent-text); font: 10px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; padding: 1px 4px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.cad-message-copy { margin-left: 21px; color: var(--ui-text-muted); font-size: 11px; line-height: 1.45; overflow-wrap: anywhere; }
|
||||
.cad-waiting-questions { display: grid; gap: 3px; margin: 3px 0 0 21px; padding-left: 16px; color: var(--ui-text-muted); font-size: 11px; line-height: 1.45; }
|
||||
.cad-event-details { margin: 4px 0 0 21px; color: var(--ui-text-subtle); font-size: 10px; }
|
||||
.cad-event-details summary { cursor: pointer; width: fit-content; }
|
||||
.cad-event-details pre { max-height: 180px; overflow: auto; margin: 4px 0 0; border-left: 2px solid var(--ui-border-muted); padding-left: 8px; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--ui-text-muted); font: 10px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
@@ -462,6 +463,7 @@ button:disabled {
|
||||
.json-tree { max-height: 260px; overflow: auto; margin-top: 6px; border: 1px solid var(--ui-border); border-radius: 4px; background: var(--ui-control-bg); padding: 7px; color: var(--ui-text); font: 10px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
.json-tree .json-view--property { color: var(--ui-accent-text); }.json-tree .json-view--string { color: var(--ui-success-text); }.json-tree .json-view--number, .json-tree .json-view--boolean { color: var(--ui-secondary-text); }
|
||||
.cad-progress.is-error .cad-message-heading, .cad-progress.is-error .cad-message-heading svg, .cad-error .cad-message-heading, .cad-error .cad-message-heading svg { color: var(--ui-error-text); }
|
||||
.cad-progress.is-waiting .cad-message-heading, .cad-progress.is-waiting .cad-message-heading svg { color: var(--ui-secondary-text); }
|
||||
.cad-result { margin-top: 12px; }
|
||||
.cad-result-title { margin-left: 21px; color: var(--ui-accent-text); font-size: 12px; font-weight: 600; line-height: 1.45; overflow-wrap: anywhere; }
|
||||
.cad-result-meta { display: flex; flex-wrap: wrap; gap: 3px 10px; margin-left: 21px; color: var(--ui-text-muted); font-size: 10px; line-height: 1.4; overflow-wrap: anywhere; }
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DefaultChatTransport } from "ai";
|
||||
import { AlertCircle, Box, Loader2, Moon, Sun } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { activeCheckpointPreview, latestSuccessfulResult } from "@/lib/cad-artifacts";
|
||||
import { normalizeCadMessages } from "@/lib/cad-messages";
|
||||
import { normalizeCadMessages, restoreTaskProjection } from "@/lib/cad-messages";
|
||||
import type { ViewerSelectionContext } from "@/lib/viewer-selection";
|
||||
import type {
|
||||
BackendConfig,
|
||||
@@ -89,7 +89,7 @@ export function AgentStudio() {
|
||||
setConfig(nextConfig);
|
||||
setConversationId(nextConversationId);
|
||||
setSelectedTaskId(taskId);
|
||||
setInitialMessages(normalizeCadMessages(conversation.messages));
|
||||
setInitialMessages(restoreTaskProjection(normalizeCadMessages(conversation.messages), restoredTask));
|
||||
setAttachments(conversation.attachments || []);
|
||||
const defaultProvider = nextConfig.providers.find((provider) => provider.id === nextConfig.default_provider) ?? nextConfig.providers[0];
|
||||
setProviderId(defaultProvider?.id || "");
|
||||
@@ -156,7 +156,7 @@ export function AgentStudio() {
|
||||
});
|
||||
}
|
||||
}
|
||||
if (progress.step === "requirements_document" && progress.status === "running") setTaskRunning(true);
|
||||
if (progress.step === "task_started" && progress.status === "running") setTaskRunning(true);
|
||||
if (progress.step === "task_terminal") setTaskRunning(progress.lifecycle === "running");
|
||||
}, [conversationId, syncUrl]);
|
||||
|
||||
|
||||
@@ -14,13 +14,14 @@ export function CadProgressPart({ data }: { data: CadProgress }) {
|
||||
const status = String(data.status || "").toLowerCase();
|
||||
const isRunning = status === "running";
|
||||
const isError = status === "error";
|
||||
const statusLabel = isRunning ? "进行中" : isError ? "失败" : status === "success" ? "完成" : data.status;
|
||||
const Icon = data.step === "tool_call" ? Wrench : data.step.includes("review") || data.step === "final_review" ? Eye : data.step === "rollback" ? RotateCcw : data.step.includes("requirements") || data.step.includes("checklist") ? FileCheck : data.step.includes("diagnostic") ? Search : isError ? AlertTriangle : Check;
|
||||
const isWaiting = status === "waiting";
|
||||
const statusLabel = isRunning ? "进行中" : isWaiting ? data.lifecycle === "waiting_retry" ? "等待重试" : "等待确认" : isError ? "失败" : status === "success" ? "完成" : data.status;
|
||||
const Icon = data.step === "tool_call" ? Wrench : data.step.includes("review") || data.step === "final_review" ? Eye : data.step === "rollback" ? RotateCcw : data.step.includes("requirements") || data.step.includes("checklist") ? FileCheck : data.step.includes("diagnostic") ? Search : isError || isWaiting ? AlertTriangle : Check;
|
||||
const evidence = data.evidence || (Array.isArray(data.review?.evidence) ? data.review.evidence.map(String) : []);
|
||||
const documentTitle = data.step === "requirements_document" ? "冻结需求内容" : data.step === "completion_checklist" ? "完成清单内容" : "";
|
||||
const documentTitle = data.step === "requirements_review" ? "需求合同复核" : "";
|
||||
const documentMarkdown = data.step === "requirements_document" ? withoutRequirementsFilename(data.markdown || "") : data.markdown || "";
|
||||
return (
|
||||
<div className={`cad-message cad-progress${isError ? " is-error" : ""}`} role="status" aria-live="polite">
|
||||
<div className={`cad-message cad-progress${isError ? " is-error" : ""}${isWaiting ? " is-waiting" : ""}`} role="status" aria-live="polite">
|
||||
<div className="cad-message-heading">
|
||||
{isRunning ? <Loader2 className="spin" size={14} aria-hidden="true" /> : <Icon size={14} aria-hidden="true" />}
|
||||
<span>{data.label || data.step}</span>
|
||||
@@ -28,6 +29,10 @@ export function CadProgressPart({ data }: { data: CadProgress }) {
|
||||
<span className="cad-status-label">{statusLabel}</span>
|
||||
</div>
|
||||
{data.message ? <div className="cad-message-copy">{data.message}</div> : null}
|
||||
{data.questions?.length ? <ul className="cad-waiting-questions">{data.questions.map((question, index) => <li key={`${question}-${index}`}>{question}</li>)}</ul> : null}
|
||||
{data.issues?.length ? <ul className="cad-waiting-questions">{data.issues.map((issue, index) => <li key={`${issue}-${index}`}>{issue}</li>)}</ul> : null}
|
||||
{data.verificationWarnings?.length ? <details className="cad-event-details" open><summary>验证风险 ({data.verificationWarnings.length})</summary><ul>{data.verificationWarnings.map((warning, index) => <li key={`${warning}-${index}`}>{warning}</li>)}</ul></details> : null}
|
||||
{data.appliedNormalizations?.length ? <details className="cad-event-details"><summary>自动归一化 ({data.appliedNormalizations.length})</summary><JsonTree data={data.appliedNormalizations} /></details> : null}
|
||||
{documentMarkdown ? <details className="cad-event-details cad-document-details" open={data.step === "requirements_document"}><summary>{documentTitle || "文档内容"}</summary><MarkdownDocument>{documentMarkdown}</MarkdownDocument></details> : null}
|
||||
{data.arguments ? <details className="cad-event-details"><summary>调用参数</summary><JsonTree data={data.arguments} /></details> : null}
|
||||
{data.result !== undefined ? <details className="cad-event-details"><summary>执行结果</summary><JsonTree data={data.result} /></details> : null}
|
||||
@@ -63,6 +68,8 @@ export function CadResultPart({ data }: { data: CadResult }) {
|
||||
<span>{data.referenceIds.length} 个参考</span>
|
||||
</div>
|
||||
{data.referenceIds.length ? <div className="cad-result-notes"><strong>参考</strong><span>{data.referenceIds.join(";")}</span></div> : null}
|
||||
{data.verificationWarnings?.length ? <div className="cad-result-notes"><strong>验证风险</strong><span>{data.verificationWarnings.join(";")}</span></div> : null}
|
||||
{data.appliedNormalizations?.length ? <details className="cad-event-details"><summary>自动归一化 ({data.appliedNormalizations.length})</summary><JsonTree data={data.appliedNormalizations} /></details> : null}
|
||||
{downloads.length ? <div className="download-row">
|
||||
{downloads.map(([label, path]) => (
|
||||
<a key={label} className="download-link" href={encodeArtifactUrl(data.taskId, path)} download aria-label={`下载 ${label}`}>
|
||||
@@ -87,6 +94,8 @@ export function CadErrorPart({ data }: { data: CadError }) {
|
||||
<div className="cad-message cad-error" role="alert">
|
||||
<div className="cad-message-heading"><AlertTriangle size={14} aria-hidden="true" /><span>{stage}失败</span></div>
|
||||
<div className="cad-message-copy">{data.message}</div>
|
||||
{data.tool ? <code className="cad-tool-name">{data.tool}</code> : null}
|
||||
{data.fieldErrors?.length ? <details className="cad-event-details" open><summary>字段错误 ({data.fieldErrors.length})</summary><ul>{data.fieldErrors.map((error, index) => <li key={`${error.path || "field"}-${index}`}><code>{error.path || "/"}</code>{error.message ? `: ${error.message}` : ""}</li>)}</ul></details> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ function resultForRevision(task: TaskRecord, revisionId: string, checkpoint: boo
|
||||
engine: current.engine || "cdsl_only",
|
||||
checkpoint,
|
||||
lifecycle: task.lifecycle || "completed",
|
||||
verificationStatus: task.verification_status,
|
||||
verificationWarnings: task.verification_warnings || [],
|
||||
appliedNormalizations: task.applied_normalizations || [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CadError, CadProgress, CadResult, CadUIMessage } from "./cad-types";
|
||||
import type { CadError, CadProgress, CadResult, CadUIMessage, TaskRecord } from "./cad-types";
|
||||
|
||||
type AnyPart = { type?: unknown; text?: unknown; data?: unknown; id?: unknown };
|
||||
type AnyMessage = { id?: unknown; role?: unknown; parts?: unknown };
|
||||
@@ -50,6 +50,42 @@ export function messagesForBackend(messages: CadUIMessage[]) {
|
||||
}));
|
||||
}
|
||||
|
||||
export function restoreTaskProjection(messages: CadUIMessage[], task: TaskRecord | null): CadUIMessage[] {
|
||||
if (!task?.task_id || !task.lifecycle) return messages;
|
||||
const terminal = task.lifecycle !== "running";
|
||||
const step = terminal ? "task_terminal" : "task_started";
|
||||
const alreadyVisible = messages.some((message) => message.parts.some((part) =>
|
||||
part.type === "data-cad-progress"
|
||||
&& part.data.taskId === task.task_id
|
||||
&& part.data.step === step
|
||||
&& (!terminal || part.data.lifecycle === task.lifecycle)
|
||||
));
|
||||
if (alreadyVisible) return messages;
|
||||
const status = task.lifecycle === "failed" ? "error"
|
||||
: task.lifecycle === "waiting_for_user" || task.lifecycle === "waiting_retry" ? "waiting"
|
||||
: task.lifecycle === "completed" ? "success" : "running";
|
||||
const progress: CadProgress = {
|
||||
step,
|
||||
label: terminal ? "生成任务" : "Agent",
|
||||
status,
|
||||
taskId: task.task_id,
|
||||
lifecycle: task.lifecycle,
|
||||
message: task.message || (terminal ? "CAD 任务已停止。" : "CAD 任务正在运行。"),
|
||||
questions: task.questions || [],
|
||||
issues: task.issues || [],
|
||||
blockerType: task.blocker_type,
|
||||
userActionRequired: Boolean(task.user_action_required),
|
||||
verificationStatus: task.verification_status,
|
||||
verificationWarnings: task.verification_warnings || [],
|
||||
appliedNormalizations: task.applied_normalizations || [],
|
||||
};
|
||||
return [...messages, {
|
||||
id: `projection_${task.task_id}_${task.state_version || 0}`,
|
||||
role: "assistant",
|
||||
parts: [{ type: "data-cad-progress", id: `projection_${task.task_id}_${step}`, data: progress }],
|
||||
}];
|
||||
}
|
||||
|
||||
function stringId(value: unknown) {
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import test from "node:test";
|
||||
import { backendEventToUiChunk } from "./cad-stream";
|
||||
import { activeCheckpointPreview, latestSuccessfulResult } from "./cad-artifacts";
|
||||
import { messagesForBackend } from "./cad-messages";
|
||||
import { restoreTaskProjection } from "./cad-messages";
|
||||
import type { CadUIMessage } from "./cad-types";
|
||||
|
||||
test("maps backend cad_result SSE into an AI SDK data part", () => {
|
||||
@@ -20,53 +21,107 @@ test("keeps progressive revisions as separate data parts", () => {
|
||||
assert.notEqual(first?.id, second?.id);
|
||||
});
|
||||
|
||||
test("maps final visual repair review into a blocking progress state", () => {
|
||||
test("maps final repair review into a blocking progress state", () => {
|
||||
const chunk = backendEventToUiChunk({
|
||||
event: "final_review",
|
||||
data: { taskId: "cad_abc", review: { verdict: "repair", confidence: 0.92, evidence: ["missing round"] } },
|
||||
event: "final_review", data: { taskId: "cad_abc", result: { status: "repair", evidence: ["missing round"] } },
|
||||
}, "text_1");
|
||||
assert.equal(chunk?.type, "data-cad-progress");
|
||||
assert.deepEqual("data" in chunk! ? chunk.data : null, {
|
||||
step: "final_review", label: "最终视觉复核", status: "error", message: "missing round", taskId: "cad_abc",
|
||||
step: "final_review", label: "最终独立复核", status: "error", message: "", taskId: "cad_abc", result: { status: "repair", evidence: ["missing round"] },
|
||||
});
|
||||
});
|
||||
|
||||
test("maps a rejected independent candidate review into a blocking progress state", () => {
|
||||
const chunk = backendEventToUiChunk({
|
||||
event: "candidate_review",
|
||||
data: { taskId: "cad_abc", review: { verdict: "reject", evidence: ["base is disconnected"] } },
|
||||
event: "candidate_review", data: { taskId: "cad_abc", result: { status: "rejected", evidence: ["base is disconnected"] } },
|
||||
}, "text_1");
|
||||
assert.equal(chunk?.type, "data-cad-progress");
|
||||
assert.deepEqual("data" in chunk! ? chunk.data : null, {
|
||||
step: "candidate_review", label: "候选独立复核", status: "error", message: "base is disconnected", taskId: "cad_abc",
|
||||
step: "candidate_review", label: "候选独立复核", status: "error", message: "", taskId: "cad_abc", result: { status: "rejected", evidence: ["base is disconnected"] },
|
||||
});
|
||||
});
|
||||
|
||||
test("maps a modeling plan revision request into a blocking progress state", () => {
|
||||
test("keeps terminal schema field errors visible to the CAD error part", () => {
|
||||
const chunk = backendEventToUiChunk({
|
||||
event: "modeling_plan_review",
|
||||
data: { taskId: "cad_abc", review: { verdict: "revise", issues: [{ message: "split unrelated finish" }] } },
|
||||
event: "cad_error",
|
||||
data: {
|
||||
stage: "generation",
|
||||
tool: "patch_requirements_draft",
|
||||
message: "Author repeatedly failed the schema.",
|
||||
fieldErrors: [{ path: "/patches", message: "Field required" }],
|
||||
},
|
||||
}, "text_1");
|
||||
assert.equal(chunk?.type, "data-cad-progress");
|
||||
assert.equal("data" in chunk! ? (chunk.data as { label?: string }).label : null, "计划独立复核");
|
||||
assert.equal("data" in chunk! ? (chunk.data as { status?: string }).status : null, "error");
|
||||
assert.equal(chunk?.type, "data-cad-error");
|
||||
assert.deepEqual("data" in chunk! ? chunk.data : null, {
|
||||
stage: "generation",
|
||||
tool: "patch_requirements_draft",
|
||||
message: "Author repeatedly failed the schema.",
|
||||
fieldErrors: [{ path: "/patches", message: "Field required" }],
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps a server-verified plan skip visible in the timeline", () => {
|
||||
test("maps a waiting terminal into a visible user-decision state", () => {
|
||||
const question = "Should the spacing be corrected to 45 degrees or should the count change?";
|
||||
const chunk = backendEventToUiChunk({
|
||||
event: "plan_step_skipped",
|
||||
event: "task_terminal",
|
||||
data: {
|
||||
taskId: "cad_abc",
|
||||
planStepId: "step_6",
|
||||
nextPlanStepId: "",
|
||||
evidenceRef: "completion_ledger:rev_008",
|
||||
status: "success",
|
||||
message: "The current revision already satisfies this plan step.",
|
||||
lifecycle: "waiting_for_user",
|
||||
message: "Requirements need a user decision.",
|
||||
questions: [question],
|
||||
reviewPath: "documents/requirements-review.json",
|
||||
},
|
||||
}, "text_1");
|
||||
assert.equal(chunk?.type, "data-cad-progress");
|
||||
assert.equal("data" in chunk! ? (chunk.data as { label?: string }).label : null, "计划步骤已满足");
|
||||
assert.equal("data" in chunk! ? (chunk.data as { status?: string }).status : null, "success");
|
||||
assert.deepEqual("data" in chunk! ? chunk.data : null, {
|
||||
step: "task_terminal",
|
||||
label: "生成任务",
|
||||
status: "waiting",
|
||||
message: "Requirements need a user decision.",
|
||||
taskId: "cad_abc",
|
||||
lifecycle: "waiting_for_user",
|
||||
questions: [question],
|
||||
reviewPath: "documents/requirements-review.json",
|
||||
});
|
||||
});
|
||||
|
||||
test("restores a complete waiting terminal from the task projection", () => {
|
||||
const restored = restoreTaskProjection([], {
|
||||
task_id: "cad_abc", current_revision: "", lifecycle: "waiting_for_user", phase: "WAITING_FOR_USER",
|
||||
state_version: 7, message: "A decision is required.", questions: ["Four holes or six?"],
|
||||
blocker_type: "requirements_ambiguity", user_action_required: true,
|
||||
verification_status: "verified", verification_warnings: [], applied_normalizations: [], revisions: [],
|
||||
});
|
||||
const part = restored[0].parts[0];
|
||||
assert.equal(part.type, "data-cad-progress");
|
||||
if (part.type === "data-cad-progress") {
|
||||
assert.equal(part.data.lifecycle, "waiting_for_user");
|
||||
assert.deepEqual(part.data.questions, ["Four holes or six?"]);
|
||||
assert.equal(part.data.userActionRequired, true);
|
||||
}
|
||||
});
|
||||
|
||||
test("keeps unmet review issues visible when requirements review cannot converge", () => {
|
||||
const issue = "The counterbores need a feature-scoped front-side claim.";
|
||||
const chunk = backendEventToUiChunk({
|
||||
event: "task_terminal",
|
||||
data: {
|
||||
taskId: "cad_abc",
|
||||
lifecycle: "waiting_for_user",
|
||||
message: "Requirements review did not converge.",
|
||||
issues: [issue],
|
||||
},
|
||||
}, "text_1");
|
||||
assert.equal(chunk?.type, "data-cad-progress");
|
||||
assert.deepEqual("data" in chunk! ? chunk.data : null, {
|
||||
step: "task_terminal",
|
||||
label: "生成任务",
|
||||
status: "waiting",
|
||||
message: "Requirements review did not converge.",
|
||||
taskId: "cad_abc",
|
||||
lifecycle: "waiting_for_user",
|
||||
issues: [issue],
|
||||
});
|
||||
});
|
||||
|
||||
test("gives repeated tool events unique ordered parts", () => {
|
||||
@@ -83,15 +138,6 @@ test("reuses an invocation id so tool completion updates its running card", () =
|
||||
assert.equal(running?.id, complete?.id);
|
||||
});
|
||||
|
||||
test("keeps frozen requirement markdown visible in the timeline", () => {
|
||||
const chunk = backendEventToUiChunk({
|
||||
event: "requirements_document",
|
||||
data: { taskId: "cad_abc", status: "frozen", markdown: "# 冻结需求\n\n- 创建底座" },
|
||||
}, "text_1", 2);
|
||||
assert.equal(chunk?.type, "data-cad-progress");
|
||||
assert.equal("data" in chunk! ? (chunk.data as { markdown?: string }).markdown : null, "# 冻结需求\n\n- 创建底座");
|
||||
});
|
||||
|
||||
test("restores the latest successful task revision for the viewer", () => {
|
||||
const result = latestSuccessfulResult({
|
||||
task_id: "cad_abc",
|
||||
|
||||
@@ -20,15 +20,15 @@ export function backendEventToUiChunk(
|
||||
data: { ...item.data, sequence },
|
||||
};
|
||||
}
|
||||
if (["requirements_document", "completion_checklist", "completion_audit", "modeling_plan", "modeling_plan_review", "plan_step_skipped", "agent_thinking", "tool_call", "candidate_result", "candidate_review", "geometry_diagnostic", "geometry_conclusion", "step_review", "checkpoint", "rollback", "final_review", "task_terminal"].includes(item.event)) {
|
||||
if (["requirements_review", "action_selection", "tool_call", "candidate_result", "candidate_review", "final_review", "task_terminal"].includes(item.event)) {
|
||||
const review = item.data.review && typeof item.data.review === "object"
|
||||
? item.data.review as Record<string, unknown>
|
||||
: null;
|
||||
const lifecycle = String(item.data.lifecycle || "");
|
||||
const status = item.event === "task_terminal"
|
||||
? (String(item.data.lifecycle || "") === "failed" ? "error" : "success")
|
||||
: (item.event === "modeling_plan_review" && String(review?.verdict || "") === "revise")
|
||||
|| (item.event === "candidate_review" && String(review?.verdict || "") === "reject")
|
||||
|| (item.event === "final_review" && String(review?.verdict || "") === "repair" && Number(review?.confidence || 0) >= 0.85)
|
||||
? (lifecycle === "failed" ? "error" : lifecycle === "waiting_for_user" || lifecycle === "waiting_retry" ? "waiting" : "success")
|
||||
: String((item.data.result as Record<string, unknown> | undefined)?.status || "") === "rejected"
|
||||
|| String((item.data.result as Record<string, unknown> | undefined)?.status || "") === "repair"
|
||||
? "error"
|
||||
: String(item.data.status || "running");
|
||||
const taskId = String(item.data.taskId || "task");
|
||||
@@ -44,15 +44,25 @@ export function backendEventToUiChunk(
|
||||
type: "data-cad-progress",
|
||||
id: `event_${eventId}`,
|
||||
data: { step: item.event, label: ({
|
||||
requirements_document: "冻结需求", completion_checklist: "完成清单", completion_audit: "完成审计", modeling_plan: "建模计划", modeling_plan_review: "计划独立复核", plan_step_skipped: "计划步骤已满足", agent_thinking: "建模判断", tool_call: "建模工具", candidate_result: "候选构建", candidate_review: "候选独立复核", geometry_diagnostic: "几何诊断", geometry_conclusion: "几何结论", step_review: "步骤审查", checkpoint: "构建检查点", rollback: "回滚检查点", final_review: "最终视觉复核", task_terminal: "生成任务",
|
||||
requirements_review: "需求合同独立复核", action_selection: "动作选择", tool_call: "建模工具", candidate_result: "候选构建", candidate_review: "候选独立复核", final_review: "最终独立复核", task_terminal: "生成任务",
|
||||
} as Record<string, string>)[item.event], status, ...metadata, message: String(
|
||||
item.data.message || item.data.reason
|
||||
|| (Array.isArray(item.data.questions) ? item.data.questions.map(String).filter(Boolean).join(";") : "")
|
||||
|| (review?.evidence instanceof Array ? review.evidence.join(";") : "")
|
||||
|| (review?.issues instanceof Array ? review.issues.map((issue) => typeof issue === "object" && issue ? String((issue as Record<string, unknown>).message || "") : String(issue)).filter(Boolean).join(";") : ""),
|
||||
),
|
||||
...(item.data.taskId ? { taskId } : {}),
|
||||
...(item.data.nodeId ? { nodeId: String(item.data.nodeId) } : {}),
|
||||
...(item.data.lifecycle ? { lifecycle: String(item.data.lifecycle) } : {}),
|
||||
...(Array.isArray(item.data.questions) ? { questions: item.data.questions.map(String).filter(Boolean) } : {}),
|
||||
...(Array.isArray(item.data.issues) ? { issues: item.data.issues.map(String).filter(Boolean) } : {}),
|
||||
...(Array.isArray(item.data.unresolved) ? { unresolved: item.data.unresolved } : {}),
|
||||
...(item.data.blockerType ? { blockerType: String(item.data.blockerType) } : {}),
|
||||
...(typeof item.data.userActionRequired === "boolean" ? { userActionRequired: item.data.userActionRequired } : {}),
|
||||
...(item.data.verificationStatus ? { verificationStatus: String(item.data.verificationStatus) } : {}),
|
||||
...(Array.isArray(item.data.verificationWarnings) ? { verificationWarnings: item.data.verificationWarnings.map(String).filter(Boolean) } : {}),
|
||||
...(Array.isArray(item.data.appliedNormalizations) ? { appliedNormalizations: item.data.appliedNormalizations } : {}),
|
||||
...(item.data.reviewPath ? { reviewPath: String(item.data.reviewPath) } : {}),
|
||||
...(item.data.timestamp ? { timestamp: String(item.data.timestamp) } : {}),
|
||||
...(item.data.markdown ? { markdown: String(item.data.markdown) } : {}),
|
||||
...(item.data.tool ? { tool: String(item.data.tool) } : {}),
|
||||
@@ -78,7 +88,10 @@ export function backendEventToUiChunk(
|
||||
return {
|
||||
type: "data-cad-error",
|
||||
id: `error_${Date.now()}`,
|
||||
data: item.data,
|
||||
data: {
|
||||
...item.data,
|
||||
...(Array.isArray(item.data.fieldErrors) ? { fieldErrors: item.data.fieldErrors } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -16,8 +16,17 @@ export type CadProgress = {
|
||||
arguments?: Record<string, unknown>;
|
||||
result?: unknown;
|
||||
evidence?: string[];
|
||||
questions?: string[];
|
||||
issues?: string[];
|
||||
unresolved?: Array<{ draftId?: string; reasonCode?: string; question?: string }>;
|
||||
blockerType?: string;
|
||||
userActionRequired?: boolean;
|
||||
verificationStatus?: "verified" | "completed_with_risks" | string;
|
||||
verificationWarnings?: string[];
|
||||
appliedNormalizations?: Array<Record<string, unknown>>;
|
||||
review?: Record<string, unknown>;
|
||||
lifecycle?: "running" | "completed" | "failed" | string;
|
||||
reviewPath?: string;
|
||||
lifecycle?: "running" | "completed" | "failed" | "waiting_retry" | "waiting_for_user" | string;
|
||||
attempt?: number;
|
||||
maxAttempts?: number;
|
||||
path?: string;
|
||||
@@ -36,11 +45,16 @@ export type CadResult = {
|
||||
engine: string;
|
||||
checkpoint?: boolean;
|
||||
lifecycle?: "running" | "completed" | "failed" | string;
|
||||
verificationStatus?: "verified" | "completed_with_risks" | string;
|
||||
verificationWarnings?: string[];
|
||||
appliedNormalizations?: Array<Record<string, unknown>>;
|
||||
};
|
||||
|
||||
export type CadError = {
|
||||
stage: string;
|
||||
message: string;
|
||||
tool?: string;
|
||||
fieldErrors?: Array<{ path?: string; message?: string }>;
|
||||
};
|
||||
|
||||
export type CadDataParts = {
|
||||
@@ -95,38 +109,43 @@ export type TaskRevision = {
|
||||
};
|
||||
|
||||
export type TaskRecord = {
|
||||
schema_version?: string;
|
||||
task_id: string;
|
||||
current_revision: string;
|
||||
active_revision?: string;
|
||||
published_revision?: string;
|
||||
lifecycle?: "running" | "completed" | "failed" | string;
|
||||
lifecycle?: "running" | "completed" | "failed" | "cancelled" | "waiting_retry" | "waiting_for_user" | string;
|
||||
phase?: string;
|
||||
state_version?: number;
|
||||
active_candidate_id?: string;
|
||||
active_branch_id?: string;
|
||||
requirements_path?: string;
|
||||
requirements_draft?: Record<string, unknown> | null;
|
||||
requirements_draft_path?: string;
|
||||
requirements_review?: Record<string, unknown> | null;
|
||||
requirements_review_path?: string;
|
||||
requirements_contract?: Record<string, unknown> | null;
|
||||
requirements_contract_path?: string;
|
||||
requirements_markdown?: string | null;
|
||||
completion_checklist_path?: string;
|
||||
completion_checklist_markdown?: string | null;
|
||||
modeling_plan_path?: string;
|
||||
modeling_plan_review_path?: string;
|
||||
modeling_plan_version?: number;
|
||||
modeling_plan_markdown?: string | null;
|
||||
modeling_plan_review?: Record<string, unknown> | null;
|
||||
agent_state?: {
|
||||
no_progress?: number;
|
||||
cycle_tool_calls?: number;
|
||||
last_diagnostic?: string;
|
||||
last_review?: { candidate_id?: string; path?: string; decision?: string; recorded_at?: string };
|
||||
last_candidate_review?: { verdict?: "accept" | "reject" | string; batch_goal?: string; batch_goal_status?: string; evidence?: string[]; recorded_at?: string };
|
||||
modeling_plan_status?: "missing" | "pending_review" | "revise" | "approved" | "stale" | string;
|
||||
active_plan_step_id?: string;
|
||||
plan_step_status?: Record<string, "pending" | "complete" | string>;
|
||||
completion_ledger?: {
|
||||
verified_revision?: string;
|
||||
items?: Array<{ item?: string; status?: "complete" | "missing" | "uncertain" | string; evidence?: string }>;
|
||||
};
|
||||
recent_events?: Array<{ kind?: string; at?: string; message?: string; tool?: string }>;
|
||||
} | null;
|
||||
completion_markdown?: string | null;
|
||||
claim_summary?: Array<{
|
||||
requirement_id: string;
|
||||
claim_id: string;
|
||||
claim_kind: string;
|
||||
deterministic: boolean;
|
||||
status: "pass" | "pending" | "fail" | "unavailable" | string;
|
||||
evidence?: Record<string, unknown>;
|
||||
}>;
|
||||
pending_action?: { action_id: string; working_head: string; intent: string; requirement_ids: string[]; atomic_id: string; expected_change: string; contract_hash: string } | null;
|
||||
action_ledger_summary?: Array<Record<string, unknown>>;
|
||||
usage?: { calls: number; prompt_tokens: number; completion_tokens: number; context_chars: number };
|
||||
preview_revision?: string;
|
||||
message?: string;
|
||||
questions?: string[];
|
||||
issues?: string[];
|
||||
blocker_type?: string;
|
||||
user_action_required?: boolean;
|
||||
verification_status?: "verified" | "completed_with_risks" | string;
|
||||
verification_warnings?: string[];
|
||||
applied_normalizations?: Array<Record<string, unknown>>;
|
||||
revisions: TaskRevision[];
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user