fix(voice): speak first, then keep Level 2 skeleton and boundary semantics

Natal answers were opening on parameter tables, and rectification turns were one-sentence legal copy. Centralize user-facing strings, keep representative-minute and question-slot red lines, and stop duplicating the opening collect prompt as a second assistant message.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-02 02:27:50 +08:00
co-authored by Cursor
parent 8617eb565c
commit ed9497e76a
35 changed files with 652 additions and 118 deletions
+1 -1
View File
@@ -1195,7 +1195,7 @@ export async function POST(request: Request) {
content: [
currentTimeContext(requestTime),
name ? `用户称呼:${name}` : "",
"按 skill Level 2 模板回答下面的问题:原始结构、六步宫位、Yoga 表、时机、综合、文末技法审计表,后才是现代生活措辞。星盘事实只使用系统里已经注入的计算结果,不要复述内部字段、JSON 或再跑一遍咨询流程。",
"先用 3–6 句口语直接回答下面的问题,不要加标题;然后再按 skill Level 2 骨架写:原始结构、六步宫位、Yoga 表、时机、综合、文末技法审计表,后才是现代生活。骨架不可省略。星盘事实只使用系统里已经注入的计算结果,不要复述内部字段、JSON 或再跑一遍咨询流程。",
resolvedQuestion.modelQuestion,
].filter(Boolean).join("\n"),
},
@@ -40,6 +40,7 @@ import {
import { persistServerOwnedFocus, openQuestionFromPersistedFocus, isCollectFocusSchema, isRenderableChoiceOpenQuestion } from "@/lib/rectification-agentic/v9/server-focus";
import { buildMethodFollowupPlan } from "@/lib/rectification-agentic/v9/method-followup";
import { isNonConvergingRangeOffer, nonConvergingRangeNarration } from "@/lib/rectification-agentic/core/rectification-decision";
import { RECTIFICATION_USER_COPY } from "@/lib/rectification-agentic/user-copy";
import {
awaitTurnExitBeforeResponse,
finalizeSuccessfulTurnExit,
@@ -351,7 +352,7 @@ export async function POST(request: Request) {
classified = null;
}
if (!classified || classified.intent === "unclear") {
const narration = "我没能确定这句话是否在回答当前问题。请点选下面的选项,或换一种说法。";
const narration = RECTIFICATION_USER_COPY.unclearFocusReply;
await persistV9DeterministicTurn(accounting, userId, caseId, {
requestId,
userMessage: parsed.data.message ?? null,
@@ -361,11 +362,11 @@ export async function POST(request: Request) {
}
if (classified.intent === "answer_current_focus") {
if (!classified.answer_class) {
return completedMessageResponse("当前问题已更新,请刷新后重新作答。", requestId, caseId);
return completedMessageResponse(RECTIFICATION_USER_COPY.questionUpdated, requestId, caseId);
}
const optionId = optionIdForAnswerClass(focus, classified.answer_class);
if (!optionId) {
return completedMessageResponse("当前问题已更新,请刷新后重新作答。", requestId, caseId);
return completedMessageResponse(RECTIFICATION_USER_COPY.questionUpdated, requestId, caseId);
}
const continueToAgent = shouldContinueAgentForDatedEvent(classified);
const previous = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
@@ -514,7 +515,7 @@ export async function POST(request: Request) {
return completedMessageResponse(applied.narration, requestId, caseId);
}
}
const narration = "请点选下面的选项作答。点选和一句话回答会按同一规则计入当前区分题。";
const narration = RECTIFICATION_USER_COPY.choicePrompt;
await persistV9DeterministicTurn(accounting, userId, caseId, {
requestId,
userMessage: parsed.data.message ?? null,
@@ -722,6 +723,7 @@ export async function POST(request: Request) {
userId,
caseId,
action,
assistantBodyPresent: Boolean(result.answerText?.trim()),
});
} catch (error) {
const code = error instanceof RectificationToolServiceError ? error.code : "run_failed";