Files
Jyotisha/frontend/src/lib/rectification-agentic/v9/server-focus.ts
T
Jesse_Chen 75fc456d7e
Independent Staging Quality Gate / validate (push) Successful in 10m23s
Independent Staging Quality Gate / publish (push) Failing after 8m59s
fix(rectification): drop duplicate collect cards and false run_failed
Spoken collect no longer renders a second visual prompt; choice legends stay screen-reader only and live cards share the assistant inset. Exhaustion collect avoids colliding with the opening question id, and a successful billed turn no longer surfaces run_failed after the exit gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 15:19:00 +08:00

405 lines
12 KiB
TypeScript

import {
isPersistedFocusId,
parseAgentChoiceCopy,
serverOwnedChoiceCopy,
type RectificationChoiceFrame,
} from "./choice-card";
import {
askedProbeKeysFromReceipt,
stampChoiceSchemaWithProbe,
previousInferenceFromReceipt,
withNakshatraBoundaryProbe,
} from "./inference-adapter";
import { spokenFollowupForUser, type MethodFollowup } from "./method-followup";
import { refinementFromDecisionReceipt } from "./refinement-packet";
import {
setV10ConversationFocus,
RectificationToolServiceError,
safeToolErrorCode,
type AccountingClient,
type ConversationFocus,
} from "./tool-service";
export type PersistServerFocusStatus =
| "created"
| "already_open"
| "duplicate_focus"
| "probe_already_answered"
| "zero_information_gain"
| "invalid_choice_schema"
| "skipped";
export type PersistServerFocusResult = Readonly<{
status: PersistServerFocusStatus;
focus: ConversationFocus | null;
questionId: string | null;
prompt: string | null;
}>;
export function stableFollowupQuestionId(followup: MethodFollowup): string {
if (followup.semantic_key) return `probe:${followup.semantic_key}`.slice(0, 160);
if (followup.probe_year && followup.domain) {
return `${followup.method_id}:${followup.domain}:${followup.probe_year}`.slice(0, 160);
}
if (followup.intent === "collect_method_evidence" && !followup.choice_frame) {
const domain = followup.domain && followup.domain !== "active_focus"
? followup.domain
: "unknown";
return `collect:${domain}:${followup.intent}`.slice(0, 160);
}
return (followup.choice_frame?.question_id ?? `${followup.method_id}:${followup.ask_theme}`).slice(0, 160);
}
function schemaProbeId(schema: Readonly<Record<string, unknown>> | null | undefined): string | null {
const probeId = schema?.probe_id;
return typeof probeId === "string" && probeId.trim() ? probeId : null;
}
export function shouldSkipDiscriminatorFollowup(followup: MethodFollowup): PersistServerFocusStatus | null {
if (
followup.source === "event_probe"
&& (followup.information_gain ?? 0) <= 0
) {
return "zero_information_gain";
}
if (
followup.intent === "distinguish_candidates"
&& ((followup.candidate_ids?.length ?? 0) < 2 || (followup.expected_outcomes?.length ?? 0) < 2)
) {
return "zero_information_gain";
}
return null;
}
function expectedAnswerSchemaFor(
frame: RectificationChoiceFrame,
questionId: string,
decisionReceipt: Readonly<Record<string, unknown>> | null | undefined,
followup: MethodFollowup,
): Record<string, unknown> | null {
const copy = serverOwnedChoiceCopy(frame);
if (!copy) return null;
const schema: Record<string, unknown> = {
choice: {
prompt: copy.prompt,
option_a: copy.option_a,
option_b: copy.option_b,
option_c: copy.option_c,
option_d: copy.option_d,
options: copy.options,
},
semantic_key: followup.semantic_key ?? null,
candidate_split_hash: followup.candidate_split_hash ?? null,
choice_kind: frame.choice_kind ?? followup.choice_kind ?? "existence",
};
const receipt = decisionReceipt ?? null;
const state = withNakshatraBoundaryProbe(
previousInferenceFromReceipt(receipt),
refinementFromDecisionReceipt(receipt).nakshatra_boundary,
);
if (decisionReceipt?.inference_state !== undefined && !state) return null;
const stamped = stampChoiceSchemaWithProbe(
schema,
state,
questionId,
{
semantic_key: followup.semantic_key,
candidate_split_hash: followup.candidate_split_hash,
},
);
return state && stamped.scoring !== false && !schemaProbeId(stamped) ? null : stamped;
}
export type PersistedOpenQuestion = Readonly<{
question_id: string | null;
prompt: string | null;
status: PersistServerFocusStatus;
kind: "choice" | "collect_spoken";
focus_id?: string | null;
probe_id?: string | null;
intent?: string;
domain?: string | null;
unrenderable?: true;
reason?: string;
}>;
export function openQuestionFromPersistedFocus(result: PersistServerFocusResult): PersistedOpenQuestion | null {
if (
(result.status !== "created" && result.status !== "already_open")
|| !result.focus
|| !isPersistedFocusId(result.focus.id)
|| result.focus.questionId !== result.questionId
) return null;
const schema = result.focus.expectedAnswerSchema;
if (isCollectFocusSchema(schema) && schema) {
const prompt = (typeof result.prompt === "string" && result.prompt.trim()
? result.prompt.trim()
: typeof schema.prompt === "string" ? schema.prompt.trim() : "");
if (!prompt) return null;
return {
question_id: result.questionId,
prompt,
status: result.status,
kind: "collect_spoken",
focus_id: result.focus.id,
probe_id: typeof schema.probe_id === "string" ? schema.probe_id : null,
intent: result.focus.intent,
domain: result.focus.targetDomain ?? null,
};
}
if (!result.prompt || !parseAgentChoiceCopy(schema)) {
return {
question_id: result.questionId,
prompt: null,
status: result.status,
kind: "choice",
unrenderable: true,
reason: "invalid_choice_schema",
};
}
return {
question_id: result.questionId,
prompt: result.prompt,
status: result.status,
kind: "choice",
};
}
export function isRenderableChoiceOpenQuestion(
open: PersistedOpenQuestion | null | undefined,
): open is PersistedOpenQuestion & { kind: "choice" } {
return Boolean(open && open.kind === "choice" && open.unrenderable !== true);
}
export const COLLECT_FOCUS_SCHEMA_KEY = "collect";
export const COLLECT_FOCUS_RETRY_SUFFIX = "next";
const PERSISTABLE_FOCUS_DOMAINS = new Set([
"education",
"career",
"relationship",
"relocation",
"finance",
"health",
"family",
"other",
]);
export function persistableFocusDomain(domain: string | null | undefined): string | null {
if (!domain || domain === "unknown" || domain === "active_focus") return null;
if (domain === "health_pressure") return "health";
if (domain === "occupation") return "other";
if (PERSISTABLE_FOCUS_DOMAINS.has(domain)) return domain;
return domain;
}
function isFocusIdempotencyConflict(error: unknown): boolean {
const code = error instanceof RectificationToolServiceError
? error.code
: safeToolErrorCode(error);
return code === "focus_idempotency_conflict" || code.includes("focus_idempotency_conflict");
}
function collectFocusSchema(followup: MethodFollowup): Record<string, unknown> | null {
const prompt = spokenFollowupForUser({ ...followup, choice_frame: null });
if (!prompt) return null;
const schema: Record<string, unknown> = {
prompt,
[COLLECT_FOCUS_SCHEMA_KEY]: true,
};
if (followup.semantic_key) schema.semantic_key = followup.semantic_key;
return schema;
}
export function isCollectFocusSchema(schema: Readonly<Record<string, unknown>> | null | undefined): boolean {
return schema?.[COLLECT_FOCUS_SCHEMA_KEY] === true && typeof schema.prompt === "string";
}
async function persistCollectFocus(input: {
accounting: AccountingClient;
userId: string;
caseId: string;
activeFocus: ConversationFocus | null;
followup: MethodFollowup;
}): Promise<PersistServerFocusResult> {
const schema = collectFocusSchema(input.followup);
if (!schema) {
return {
status: "skipped",
focus: input.activeFocus,
questionId: null,
prompt: null,
};
}
const questionId = stableFollowupQuestionId(input.followup);
const prompt = typeof schema.prompt === "string" ? schema.prompt : null;
const active = input.activeFocus;
if (
active
&& active.questionId === questionId
&& active.questionId !== "active_focus:active_focus"
&& isCollectFocusSchema(active.expectedAnswerSchema)
) {
return { status: "already_open", focus: active, questionId: active.questionId, prompt };
}
const insertFocus = async (id: string): Promise<PersistServerFocusResult> => {
const result = await setV10ConversationFocus(input.accounting, input.userId, input.caseId, {
questionId: id,
intent: input.followup.intent,
targetEvidenceId: null,
targetDomain: persistableFocusDomain(input.followup.domain),
targetKind: null,
expectedAnswerSchema: schema,
});
return {
status: result.idempotent ? "already_open" : "created",
focus: result.focus,
questionId: result.focus.questionId,
prompt,
};
};
try {
return await insertFocus(questionId);
} catch (error) {
if (!isFocusIdempotencyConflict(error)) {
return {
status: "skipped",
focus: input.activeFocus,
questionId: null,
prompt: null,
};
}
const retryId = `${questionId}:${COLLECT_FOCUS_RETRY_SUFFIX}`.slice(0, 160);
if (retryId === questionId) {
return {
status: "duplicate_focus",
focus: input.activeFocus,
questionId,
prompt,
};
}
try {
return await insertFocus(retryId);
} catch (retryError) {
if (isFocusIdempotencyConflict(retryError)) {
return {
status: "duplicate_focus",
focus: input.activeFocus,
questionId: retryId,
prompt,
};
}
return {
status: "skipped",
focus: input.activeFocus,
questionId: null,
prompt: null,
};
}
}
}
export async function persistServerOwnedFocus(input: {
accounting: AccountingClient;
userId: string;
caseId: string;
activeFocus: ConversationFocus | null;
decisionReceipt: Readonly<Record<string, unknown>> | null | undefined;
followup: MethodFollowup | null;
}): Promise<PersistServerFocusResult> {
const followup = input.followup;
const frame = followup?.choice_frame ?? null;
if (!followup) {
return {
status: "skipped",
focus: input.activeFocus,
questionId: null,
prompt: null,
};
}
if (!frame) {
if (followup.intent === "distinguish_candidates") {
return {
status: "invalid_choice_schema",
focus: input.activeFocus,
questionId: null,
prompt: null,
};
}
if (followup.intent === "collect_method_evidence") {
return persistCollectFocus({
accounting: input.accounting,
userId: input.userId,
caseId: input.caseId,
activeFocus: input.activeFocus,
followup,
});
}
return {
status: "skipped",
focus: input.activeFocus,
questionId: null,
prompt: null,
};
}
const skip = shouldSkipDiscriminatorFollowup(followup);
if (skip) {
return { status: skip, focus: input.activeFocus, questionId: null, prompt: null };
}
const questionId = stableFollowupQuestionId(followup);
const answeredKeys = new Set(askedProbeKeysFromReceipt(input.decisionReceipt));
if (followup.semantic_key && answeredKeys.has(followup.semantic_key)) {
return {
status: "probe_already_answered",
focus: input.activeFocus,
questionId,
prompt: null,
};
}
const schema = expectedAnswerSchemaFor(frame, questionId, input.decisionReceipt, followup);
if (!schema?.choice) {
return { status: "invalid_choice_schema", focus: input.activeFocus, questionId, prompt: null };
}
const copy = serverOwnedChoiceCopy(frame);
const prompt = copy?.prompt ?? null;
const active = input.activeFocus;
if (
active
&& (
active.questionId === questionId
|| (schemaProbeId(schema) && schemaProbeId(active.expectedAnswerSchema) === schemaProbeId(schema))
)
) {
return { status: "already_open", focus: active, questionId: active.questionId, prompt };
}
try {
const result = await setV10ConversationFocus(input.accounting, input.userId, input.caseId, {
questionId,
intent: followup.intent,
targetEvidenceId: null,
targetDomain: followup.domain,
targetKind: null,
expectedAnswerSchema: schema,
});
return {
status: result.idempotent ? "already_open" : "created",
focus: result.focus,
questionId: result.focus.questionId,
prompt,
};
} catch (error) {
const code = error instanceof RectificationToolServiceError
? error.code
: safeToolErrorCode(error);
if (code === "focus_idempotency_conflict" || code.includes("focus_idempotency_conflict")) {
return {
status: "duplicate_focus",
focus: input.activeFocus,
questionId,
prompt,
};
}
throw error;
}
}