fix(rectification): ask spoken collect questions in the agent body
The standalone collect prompt bar reused the choice-card chrome and duplicated the question. Visibility now comes from the spoken reply, with an empty-body fallback that posts the persisted focus prompt. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -54,6 +54,7 @@ import {
|
||||
openQuestionPromptFromToolResult,
|
||||
publicNarrationDtoFromDossier,
|
||||
} from "./turn-narration";
|
||||
import { emptyAnswerCollectSpokenFallback, projectCurrentQuestion } from "./turn-decision";
|
||||
import {
|
||||
defaultMessageOrigin,
|
||||
isRectificationMessageOrigin,
|
||||
@@ -916,7 +917,12 @@ export async function runV9AgentTurn(options: V9AgentRunOptions): Promise<V9Agen
|
||||
if (!answerText.trim()) {
|
||||
try {
|
||||
const latest = await loadV9CaseDossier(accounting, userId, caseId);
|
||||
const narration = composeRectificationTurnNarration(publicNarrationDtoFromDossier(latest));
|
||||
const collect = emptyAnswerCollectSpokenFallback(
|
||||
"",
|
||||
projectCurrentQuestion(latest.conversationSummary.activeFocus),
|
||||
);
|
||||
const narration = collect
|
||||
?? composeRectificationTurnNarration(publicNarrationDtoFromDossier(latest));
|
||||
if (narration.trim()) {
|
||||
answerText = narration;
|
||||
answerDeltas.push(narration);
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
spokenFollowupForUser,
|
||||
} from "./method-followup";
|
||||
import type { SessionOutcomeKind } from "./confirmation-gate";
|
||||
import { emptyAnswerCollectSpokenFallback, projectCurrentQuestion } from "./turn-decision";
|
||||
|
||||
export type ApplyChoiceCommand = Readonly<{
|
||||
userId: string;
|
||||
@@ -499,6 +500,28 @@ export async function persistNextInterviewIfIdle(input: {
|
||||
};
|
||||
}
|
||||
|
||||
export async function persistEmptyCollectSpokenAssistant(input: {
|
||||
accounting: AccountingClient;
|
||||
userId: string;
|
||||
caseId: string;
|
||||
requestId: string;
|
||||
answerText: string;
|
||||
}): Promise<string | null> {
|
||||
if (input.answerText.trim()) return null;
|
||||
const dossier = await loadV9CaseDossier(input.accounting, input.userId, input.caseId);
|
||||
const prompt = emptyAnswerCollectSpokenFallback(
|
||||
input.answerText,
|
||||
projectCurrentQuestion(dossier.conversationSummary.activeFocus),
|
||||
);
|
||||
if (!prompt) return null;
|
||||
await persistV9DeterministicTurn(input.accounting, input.userId, input.caseId, {
|
||||
requestId: input.requestId,
|
||||
userMessage: null,
|
||||
assistantMessage: prompt,
|
||||
});
|
||||
return prompt;
|
||||
}
|
||||
|
||||
async function persistApplied(
|
||||
accounting: AccountingClient,
|
||||
command: ApplyChoiceCommand,
|
||||
|
||||
@@ -516,30 +516,6 @@ export function choiceCardUserMessage(
|
||||
return card.scoring ? line : `${HOLDOUT_MESSAGE_PREFIX}:${line}`;
|
||||
}
|
||||
|
||||
export type RectificationSpokenCollect = Readonly<{
|
||||
kind: "collect_spoken";
|
||||
question_id: string | null;
|
||||
prompt: string;
|
||||
domain: string | null;
|
||||
}>;
|
||||
|
||||
export function parseRectificationSpokenCollect(value: unknown): RectificationSpokenCollect | null {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
||||
const row = value as Record<string, unknown>;
|
||||
if (row.kind !== "collect_spoken") return null;
|
||||
if (row.choice_mode === CHOICE_MODE || Array.isArray(row.options) || row.choice) return null;
|
||||
const prompt = clippedProbeLabel(row.prompt, 2, 400);
|
||||
if (!prompt) return null;
|
||||
return {
|
||||
kind: "collect_spoken",
|
||||
question_id: typeof row.question_id === "string" && row.question_id.trim()
|
||||
? row.question_id.trim()
|
||||
: null,
|
||||
prompt,
|
||||
domain: typeof row.domain === "string" && row.domain.trim() ? row.domain.trim() : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function parseRectificationChoiceCard(value: unknown): RectificationChoiceCard | null {
|
||||
if (!value || typeof value !== "object") return null;
|
||||
const row = value as Record<string, unknown>;
|
||||
|
||||
@@ -67,6 +67,16 @@ function looksLikeChoiceSchema(schema: Readonly<Record<string, unknown>> | null
|
||||
);
|
||||
}
|
||||
|
||||
export function emptyAnswerCollectSpokenFallback(
|
||||
answerText: string,
|
||||
question: Pick<CurrentQuestionProjection, "kind" | "prompt"> | null | undefined,
|
||||
): string | null {
|
||||
if (answerText.trim()) return null;
|
||||
if (question?.kind !== "collect_spoken") return null;
|
||||
const prompt = typeof question.prompt === "string" ? question.prompt.trim() : "";
|
||||
return prompt || null;
|
||||
}
|
||||
|
||||
export function projectCurrentQuestion(
|
||||
focus: {
|
||||
id?: string;
|
||||
|
||||
Reference in New Issue
Block a user