fix(rectification): stop discriminator followup from dropping user evidence
Independent Staging Quality Gate / validate (push) Successful in 10m10s
Independent Staging Quality Gate / publish (push) Successful in 7m25s

Decision and question ranking now share contrast option completion, so a
missing style card cannot deadlock the interview with a dead-end reply.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-29 10:23:34 +08:00
co-authored by Cursor
parent 31b54aa71d
commit 86d6923e6e
15 changed files with 1054 additions and 124 deletions
@@ -4,7 +4,13 @@
*/
import type { AnswerClass, ConflictProbe } from "./types.ts";
import { d9StyleLabel, d10StyleLabel } from "../v9/varga-type-tables.ts";
import {
D9_TYPE_TABLE,
D10_TYPE_TABLE,
d9StyleLabel,
d10StyleLabel,
signKey,
} from "../v9/varga-type-tables.ts";
import {
completeStyleOptions,
isRenderableProbe,
@@ -469,7 +475,7 @@ export function selectDiscriminatorProbe(
return inspectDiscriminatorProbes(packet, options).selected;
}
function withCompletedContrastOptions(
export function withCompletedContrastOptions(
probe: CandidateDiscriminatorProbe,
): { ok: true; probe: CandidateDiscriminatorProbe } | { ok: false; reason: DroppedProbe["reason"] } {
const mapped = probe.styleOptions?.map((item) => ({
@@ -522,22 +528,37 @@ function inferredVargaStyleIncoming(
});
}
function knownVargaSigns(layer: "d9" | "d10", tokens: readonly string[]): string[] {
const table = layer === "d9" ? D9_TYPE_TABLE : D10_TYPE_TABLE;
return tokens.flatMap((token) => {
const key = signKey(token);
return table[key] ? [key] : [];
});
}
function signsFromVargaProbe(
probe: CandidateDiscriminatorProbe,
): { layer: "d9" | "d10"; signs: string[] } | null {
const match = probe.semanticKey.match(/^varga\.(d9|d10)\.(.+)$/);
const layer = match?.[1] === "d9" || match?.[1] === "d10" ? match[1] : null;
const fromKey = match?.[2]
?.split(/[|/]/)
.map((item) => item.trim())
.filter((item) => item && !/^\d{1,2}:\d{2}$/.test(item))
?? [];
const fromOutcomes = probe.expectedOutcomes.flatMap((row) => {
const token = row.outcomeId.replace(/^supports_/, "").trim();
return token && !/^\d{1,2}:\d{2}$/.test(token) ? [token] : [];
});
if (!layer) return null;
const fromKey = knownVargaSigns(
layer,
match?.[2]
?.split(/[|/]/)
.map((item) => item.trim())
.filter((item) => item && !/^\d{1,2}:\d{2}$/.test(item))
?? [],
);
const fromOutcomes = knownVargaSigns(
layer,
probe.expectedOutcomes.flatMap((row) => {
const token = row.outcomeId.replace(/^supports_/, "").trim();
return token && !ANSWER_CLASSES.has(token) && !/^\d{1,2}:\d{2}$/.test(token) ? [token] : [];
}),
);
const signs = (fromKey.length >= 2 ? fromKey : fromOutcomes).slice(0, 3);
if (!layer || signs.length < 2) return null;
if (signs.length < 2) return null;
return { layer, signs };
}
@@ -568,12 +589,29 @@ function withUnsureOutcome(
return rows;
}
function conflictStyleOptions(
probe: CandidateDiscriminatorProbe,
): ConflictProbe["style_options"] {
const rows = probe.styleOptions?.flatMap((item) => {
const label = item.label.trim();
if (!label) return [];
return [{
label,
answer_class: item.answerClass,
...(item.sign ? { sign: item.sign } : {}),
}];
}) ?? [];
return rows.length > 0 ? rows : undefined;
}
export function conflictProbesFromContrast(
packet: CandidateContrastPacket | null | undefined,
): ConflictProbe[] {
return (packet?.probes ?? []).flatMap((probe) => {
if (!probe.semanticKey.startsWith("varga.")) return [];
const outcomes = probe.expectedOutcomes.flatMap((row, index) => {
const completed = withCompletedContrastOptions(probe);
const working = completed.ok ? completed.probe : probe;
const outcomes = working.expectedOutcomes.flatMap((row, index) => {
const answer = ANSWER_CLASSES.has(row.outcomeId)
? row.outcomeId as AnswerClass
: (["yes", "weak_yes", "no"][index] as AnswerClass | undefined);
@@ -585,29 +623,33 @@ export function conflictProbesFromContrast(
}];
});
if (outcomes.length < 2) return [];
if (probe.informationGain <= 0) return [];
const candidateIds = [...new Set(probe.expectedOutcomes.flatMap((row) => [
if (working.informationGain <= 0) return [];
const candidateIds = [...new Set(working.expectedOutcomes.flatMap((row) => [
...row.supportsCandidateIds,
...row.conflictsCandidateIds,
]))];
if (candidateIds.length < 2) return [];
const choiceKind = effectiveContrastChoiceKind(probe);
const choiceKind = completed.ok
? (working.choiceKind ?? effectiveContrastChoiceKind(working))
: effectiveContrastChoiceKind(probe);
const styleOptions = conflictStyleOptions(working);
return [{
id: probe.probeId,
semantic_key: probe.semanticKey,
candidate_split_hash: probe.candidateSplitHash,
domain: probe.domain ?? "career",
year: probe.year ?? 0,
question: probe.question,
id: working.probeId,
semantic_key: working.semanticKey,
candidate_split_hash: working.candidateSplitHash,
domain: working.domain ?? "career",
year: working.year ?? 0,
question: working.question,
candidate_ids: candidateIds,
expected_outcomes: outcomes,
information_gain: probe.informationGain,
information_gain: working.informationGain,
source: "varga_contrast",
...(choiceKind === "varga_style"
|| choiceKind === "event_quality"
|| choiceKind === "existence"
? { choice_kind: choiceKind }
: {}),
...(styleOptions ? { style_options: styleOptions } : {}),
}];
});
}
@@ -20,6 +20,7 @@ export function probeFromEngine(probe: EngineProbeFields): ConflictProbe | null
if (outcomes.length < 2 || candidateIds.length < 2 || (probe.information_gain ?? 0) <= 0) {
return null;
}
const styleOptions = styleOptionsFromEngine(probe.style_options);
return {
id: `probe:${semanticKey}:${splitHash}`,
semantic_key: semanticKey,
@@ -36,9 +37,23 @@ export function probeFromEngine(probe: EngineProbeFields): ConflictProbe | null
|| probe.choice_kind === "existence"
? { choice_kind: probe.choice_kind }
: {}),
...(styleOptions.length > 0 ? { style_options: styleOptions } : {}),
};
}
function styleOptionsFromEngine(
rows: DiscriminatingEventProbe["style_options"],
): NonNullable<ConflictProbe["style_options"]> {
return (rows ?? []).flatMap((row) => {
if (!ANSWER_CLASSES.has(row.answer_class)) return [];
return [{
label: row.label,
answer_class: row.answer_class as AnswerClass,
...(row.sign ? { sign: row.sign } : {}),
}];
});
}
function outcomesFromEngine(
rows: DiscriminatingEventProbe["expected_outcomes"],
): ProbeOutcome[] {
@@ -85,6 +85,11 @@ export type ConflictProbe = Readonly<{
information_gain: number;
source: string;
choice_kind?: ProbeChoiceKind;
style_options?: readonly Readonly<{
label: string;
answer_class: AnswerClass;
sign?: string;
}>[];
}>;
export type ProbeAnswer = Readonly<{
@@ -39,7 +39,6 @@ import {
import type { ChoiceKey } from "./choice-card";
import { persistServerOwnedFocus, openQuestionFromPersistedFocus } from "./server-focus";
import { buildMethodFollowupPlan, spokenFollowupForUser } from "./method-followup";
import { meetsAcceptanceEventQuality } from "./evidence-model";
import type { SessionOutcomeKind } from "./confirmation-gate";
export type ApplyChoiceCommand = Readonly<{
@@ -283,28 +282,83 @@ async function persistNextInterviewAfterChoice(input: {
&& input.nextAction.type !== "ask_holdout_validation",
});
const followup = plan.next_followup;
const persistedFocus = await persistServerOwnedFocus({
const persistedFocus = await persistFocusAfterChoice({
accounting: input.accounting,
userId: input.userId,
caseId: input.caseId,
activeFocus: null,
decisionReceipt: latest.decisionReceipt,
followup,
});
const open = openQuestionFromPersistedFocus(persistedFocus);
if (open) {
if (open && open.unrenderable !== true) {
return { hostNarration: "接下来请点选下面这一问。", choiceReady: true };
}
if (followup?.choice_frame) {
const spoken = "请再说一件记得大概时间的经历。";
const fallback = await persistFocusAfterChoice({
accounting: input.accounting,
userId: input.userId,
caseId: input.caseId,
decisionReceipt: latest.decisionReceipt,
followup: {
...followup,
intent: "collect_method_evidence",
choice_frame: null,
source: "method_coverage",
user_prompt_hint: spoken,
},
});
if (fallback.status === "created" || fallback.status === "already_open") {
return { hostNarration: spoken, choiceReady: false };
}
return { hostNarration: spoken, choiceReady: false };
}
if (followup?.intent === "collect_method_evidence") {
const spoken = spokenFollowupForUser(followup);
if (
spoken
&& (persistedFocus.status === "created" || persistedFocus.status === "already_open")
) {
return { hostNarration: spoken, choiceReady: false };
}
return { hostNarration: null, choiceReady: false };
}
if (
followup?.intent === "collect_method_evidence"
&& meetsAcceptanceEventQuality(input.dossier.evidence)
) {
return { hostNarration: spokenFollowupForUser(followup), choiceReady: false };
if (!followup) {
return {
hostNarration: "当前几个候选已经构成可信区间。你可以再说一件记得住时间的经历,也可以先按这个区间看盘。",
choiceReady: false,
};
}
return {
hostNarration: spokenFollowupForUser(followup) ?? "请再说一件记得大概时间的经历。",
choiceReady: false,
};
}
async function persistFocusAfterChoice(input: {
accounting: AccountingClient;
userId: string;
caseId: string;
decisionReceipt: Readonly<Record<string, unknown>> | null | undefined;
followup: ReturnType<typeof buildMethodFollowupPlan>["next_followup"];
}) {
try {
return await persistServerOwnedFocus({
accounting: input.accounting,
userId: input.userId,
caseId: input.caseId,
activeFocus: null,
decisionReceipt: input.decisionReceipt,
followup: input.followup,
});
} catch {
return {
status: "skipped" as const,
focus: null,
questionId: null,
prompt: null,
};
}
return { hostNarration: null, choiceReady: false };
}
async function persistApplied(
@@ -12,6 +12,7 @@ import {
mentionedVargaKeysFromLedgerEvidence,
volunteeredDomainsFromEvidence,
type CandidateContrastPacket,
type CandidateDiscriminatorProbe,
type EngineContrastProbe,
} from "../core/candidate-contrast-packet.ts";
import {
@@ -38,6 +39,7 @@ import {
} from "./evidence-model";
import { refinementFromDecisionReceipt } from "./refinement-packet";
import { windowScanFromDecisionReceipt } from "./varga-observations";
import type { DroppedProbe } from "./probe-question-contract.ts";
import { evidenceLedgerFingerprint } from "./tool-service";
import {
candidateSnapshotSource,
@@ -183,6 +185,7 @@ export function contrastPacketFromLatestResult(
|| probe.choice_kind === "existence"
? { choice_kind: probe.choice_kind }
: {}),
...(probe.style_options?.length ? { style_options: probe.style_options } : {}),
}];
});
const merged = mergeEngineProbes(
@@ -285,6 +288,7 @@ function contrastPacketFromState(state: InferenceState): CandidateContrastPacket
|| item.choice_kind === "existence"
? { choice_kind: item.choice_kind }
: {}),
...(item.style_options?.length ? { style_options: item.style_options } : {}),
})),
candidateTimes: state.candidates
.filter((item) => item.status !== "eliminated")
@@ -316,6 +320,50 @@ function scoreableSnapshotCurrentFromDossier(
return storedSnapshotIsCurrent(stored, current);
}
function mergeDroppedProbes(
...groups: readonly (readonly DroppedProbe[] | undefined)[]
): DroppedProbe[] {
const byKey = new Map<string, DroppedProbe>();
for (const group of groups) {
for (const item of group ?? []) {
if (!byKey.has(item.semantic_key)) byKey.set(item.semantic_key, item);
}
}
return [...byKey.values()];
}
export function followupAsksRenderableDiscriminator(
followup: { intent?: string; choice_frame?: unknown } | null | undefined,
): boolean {
return followup?.intent === "distinguish_candidates" && Boolean(followup.choice_frame);
}
function discriminatorProbeIfFollowupCanAsk(input: {
dossier: DecisionDossier;
inspected: ReturnType<typeof inspectDiscriminatorProbes>;
contrastPacket?: CandidateContrastPacket;
askedKeys?: readonly string[];
}): {
probe: CandidateDiscriminatorProbe | null;
dropped: DroppedProbe[];
} {
const catalog = rectificationFollowupCatalog(input.dossier.latestResult, input.dossier.evidence);
const plan = buildMethodFollowupPlan({
evidence: input.dossier.evidence,
declinedTopics: input.dossier.conversationSummary.declinedSkippedTopics,
sessionOutcome: "discriminate_candidates",
...catalog,
...(input.contrastPacket ? { contrastPacket: input.contrastPacket } : {}),
...(input.askedKeys ? { askedProbeKeys: input.askedKeys } : {}),
candidatesSeparated: false,
});
const dropped = mergeDroppedProbes(input.inspected.dropped, plan.dropped_probes);
if (!followupAsksRenderableDiscriminator(plan.next_followup)) {
return { probe: null, dropped };
}
return { probe: input.inspected.selected, dropped };
}
export function decideFromDossier(
dossier: DecisionDossier,
options?: { currentEvidenceFingerprint?: string | null },
@@ -347,6 +395,7 @@ export function decideFromDossier(
askedKeys,
mentionedKeys,
});
const gated = discriminatorProbeIfFollowupCanAsk({ dossier, inspected, askedKeys });
return {
...decideRectification({
methodCoverageAll: blockingMethodsCovered(collecting.methods),
@@ -355,12 +404,12 @@ export function decideFromDossier(
userStopped: dossier.case.status === "paused",
snapshotCurrent,
candidateScores: candidateScoresFromDossier(dossier.latestResult),
discriminatorProbe: inspected.selected,
discriminatorProbe: gated.probe,
holdoutValidation: holdoutStatusFromInference(inference, oosBlindPrompts),
accepted: Boolean(dossier.case.acceptedTime),
inferenceCredibleRange: inference?.credible_range ?? null,
}),
droppedProbes: inspected.dropped,
droppedProbes: gated.dropped,
};
}
@@ -384,8 +433,16 @@ export function decideAfterInferenceChange(input: {
}
const training = input.state.events.filter((item) => item.usage === "training");
const trainingDomains = new Set(training.map((item) => item.domain));
const inspected = inspectDiscriminatorProbes(contrastPacketFromState(input.state), {
const contrastPacket = contrastPacketFromState(input.state);
const inspected = inspectDiscriminatorProbes(contrastPacket, {
mentionedKeys: mentionedVargaKeysFromLedgerEvidence(input.dossier.evidence),
askedKeys: input.state.answered_probes.map((item) => item.semantic_key),
});
const gated = discriminatorProbeIfFollowupCanAsk({
dossier: input.dossier,
inspected,
contrastPacket,
askedKeys: input.state.answered_probes.map((item) => item.semantic_key),
});
return {
...decideRectification({
@@ -395,7 +452,7 @@ export function decideAfterInferenceChange(input: {
candidateScores: input.state.candidates
.filter((item) => item.status !== "eliminated")
.map((item) => ({ time: item.time, score: item.posterior_score })),
discriminatorProbe: inspected.selected,
discriminatorProbe: gated.probe,
holdoutValidation: holdoutStatusFromState(
input.state,
refinementFromDecisionReceipt(input.dossier.latestResult?.decisionReceipt ?? null).oos_blind_prompts,
@@ -404,7 +461,7 @@ export function decideAfterInferenceChange(input: {
userStopped: input.userStopped,
accepted: Boolean(input.dossier.case.acceptedTime),
}),
droppedProbes: inspected.dropped,
droppedProbes: gated.dropped,
};
}
@@ -66,6 +66,7 @@ import {
mentionedVargaKeysFromLedgerEvidence,
vargaLayerCovered,
vargaLayerFromSemanticKey,
withCompletedContrastOptions,
type CandidateContrastPacket,
type CandidateDiscriminatorProbe,
} from "../core/candidate-contrast-packet.ts";
@@ -75,6 +76,7 @@ import {
isRenderableProbe,
rankDiscriminatorScore,
EXISTENCE_STYLE_OPTIONS,
type DroppedProbe,
type ProbeStyleOption,
} from "./probe-question-contract.ts";
import type { SessionOutcomeKind } from "./confirmation-gate.ts";
@@ -147,6 +149,7 @@ export type MethodFollowupPlan = Readonly<{
stop_domain_rotation: true;
do_not_poll: readonly [];
not_in_rotation: readonly ["relocation"];
dropped_probes: readonly DroppedProbe[];
}>;
export type MethodFollowupEvidence = Readonly<{
@@ -374,43 +377,42 @@ function contrastFollowupDomain(
function eventProbeFromContrast(probe: CandidateDiscriminatorProbe): DiscriminatingEventProbe | null {
const domain = contrastFollowupDomain(probe.domain);
if (!EVENT_PROBE_DOMAINS.includes(domain as EventProbeDomain)) return null;
const choiceKind = probe.choiceKind ?? "existence";
const styleOptions = completeStyleOptions({
choiceKind,
styleOptions: probe.styleOptions?.map((item) => ({
label: item.label,
answer_class: item.answerClass,
...(item.sign ? { sign: item.sign } : {}),
})),
});
if (!styleOptions.ok) return null;
const candidateIds = [...new Set(probe.expectedOutcomes.flatMap((row) => [
const completed = withCompletedContrastOptions(probe);
if (!completed.ok) return null;
const working = completed.probe;
const choiceKind = working.choiceKind ?? "existence";
const styleOptions = (working.styleOptions ?? []).map((item) => ({
label: item.label,
answer_class: item.answerClass,
...(item.sign ? { sign: item.sign } : {}),
}));
const candidateIds = [...new Set(working.expectedOutcomes.flatMap((row) => [
...row.supportsCandidateIds,
...row.conflictsCandidateIds,
]))];
return {
year: probe.year ?? 0,
year_label: probe.year ? `${probe.year} 年前后` : "当前这几个候选",
year: working.year ?? 0,
year_label: working.year ? `${working.year} 年前后` : "当前这几个候选",
domain: domain as EventProbeDomain,
event_family: followupEventFamily(domain, choiceKind),
source: "dasha_activation",
tracks: ["vimshottari", "narayana"],
tracks_agree: true,
unique_minute_claim: false,
user_meaning: probe.question,
user_meaning: working.question,
role: "distinguish",
phase: "candidate_discriminator",
information_gain: probe.informationGain,
semantic_key: probe.semanticKey,
candidate_split_hash: probe.candidateSplitHash,
information_gain: working.informationGain,
semantic_key: working.semanticKey,
candidate_split_hash: working.candidateSplitHash,
candidate_ids: candidateIds,
expected_outcomes: probe.expectedOutcomes.map((row) => ({
expected_outcomes: working.expectedOutcomes.map((row) => ({
answer_class: row.outcomeId,
supports: row.supportsCandidateIds,
conflicts: row.conflictsCandidateIds,
})),
choice_kind: choiceKind,
style_options: styleOptions.options,
style_options: styleOptions,
};
}
@@ -521,40 +523,63 @@ type RankedDiscriminator = Readonly<{
styleOptions: ProbeStyleOption[];
}>;
function droppedFromProbe(
semanticKey: string,
informationGain: number,
reason: DroppedProbe["reason"],
): DroppedProbe {
return {
semantic_key: semanticKey,
information_gain: informationGain,
reason,
};
}
function renderableEventProbe(
probe: DiscriminatingEventProbe,
askedKeys: ReadonlySet<string>,
topCandidateTimes: readonly string[],
mentionedKeys: ReadonlySet<string> = new Set(),
): RankedDiscriminator | null {
): { row: RankedDiscriminator | null; dropped: DroppedProbe | null } {
const key = probe.semantic_key ?? `${probe.domain}.${probe.year}`;
const candidateIds = probe.candidate_ids ?? candidateIdsFromProbe(probe);
const styleOptions = completeStyleOptions({
choiceKind: probe.choice_kind,
styleOptions: probe.style_options,
});
if (!styleOptions.ok || !isValidDistinguishProbe({ ...probe, role: "distinguish" })) return null;
if (!isRenderableProbe({
if (!styleOptions.ok) {
return { row: null, dropped: droppedFromProbe(key, probe.information_gain ?? 0, styleOptions.reason) };
}
if (!isValidDistinguishProbe({ ...probe, role: "distinguish" })) {
return { row: null, dropped: droppedFromProbe(key, probe.information_gain ?? 0, "not_renderable") };
}
const renderable = isRenderableProbe({
informationGain: probe.information_gain,
candidateIds,
expectedOutcomeCount: probe.expected_outcomes?.length,
choiceKind: probe.choice_kind,
styleOptions: styleOptions.options,
}).ok) return null;
const key = probe.semantic_key ?? `${probe.domain}.${probe.year}`;
});
if (!renderable.ok) {
return { row: null, dropped: droppedFromProbe(key, probe.information_gain ?? 0, renderable.reason) };
}
const layer = vargaLayerFromSemanticKey(key);
const asked = askedKeys.has(key)
|| Boolean(probe.candidate_split_hash && askedKeys.has(probe.candidate_split_hash))
|| (layer ? vargaLayerCovered(mentionedKeys, layer) : false);
return {
kind: "event",
eventProbe: probe,
styleOptions: styleOptions.options,
score: rankDiscriminatorScore({
informationGain: probe.information_gain ?? 0,
asked,
candidateIds,
topCandidateTimes,
}),
row: {
kind: "event",
eventProbe: probe,
styleOptions: styleOptions.options,
score: rankDiscriminatorScore({
informationGain: probe.information_gain ?? 0,
asked,
candidateIds,
topCandidateTimes,
}),
},
dropped: null,
};
}
@@ -563,41 +588,55 @@ function renderableContrastProbe(
askedKeys: ReadonlySet<string>,
topCandidateTimes: readonly string[],
mentionedKeys: ReadonlySet<string> = new Set(),
): RankedDiscriminator | null {
const candidateIds = [...new Set(probe.expectedOutcomes.flatMap((row) => [
): { row: RankedDiscriminator | null; dropped: DroppedProbe | null } {
const completed = withCompletedContrastOptions(probe);
if (!completed.ok) {
return {
row: null,
dropped: droppedFromProbe(probe.semanticKey, probe.informationGain, completed.reason),
};
}
const working = completed.probe;
const candidateIds = [...new Set(working.expectedOutcomes.flatMap((row) => [
...row.supportsCandidateIds,
...row.conflictsCandidateIds,
]))];
const styleOptions = completeStyleOptions({
choiceKind: probe.choiceKind,
styleOptions: probe.styleOptions?.map((item) => ({
label: item.label,
answer_class: item.answerClass,
...(item.sign ? { sign: item.sign } : {}),
})),
});
if (!styleOptions.ok || !isRenderableProbe({
informationGain: probe.informationGain,
const styleOptions = (working.styleOptions ?? []).map((item) => ({
label: item.label,
answer_class: item.answerClass,
...(item.sign ? { sign: item.sign } : {}),
}));
const renderable = isRenderableProbe({
informationGain: working.informationGain,
candidateIds,
expectedOutcomeCount: probe.expectedOutcomes.length,
choiceKind: probe.choiceKind,
styleOptions: styleOptions.options,
}).ok) return null;
const layer = vargaLayerFromSemanticKey(probe.semanticKey);
const asked = askedKeys.has(probe.semanticKey)
|| askedKeys.has(probe.candidateSplitHash)
|| askedKeys.has(probe.probeId)
expectedOutcomeCount: working.expectedOutcomes.length,
choiceKind: working.choiceKind,
styleOptions,
});
if (!renderable.ok) {
return {
row: null,
dropped: droppedFromProbe(working.semanticKey, working.informationGain, renderable.reason),
};
}
const layer = vargaLayerFromSemanticKey(working.semanticKey);
const asked = askedKeys.has(working.semanticKey)
|| askedKeys.has(working.candidateSplitHash)
|| askedKeys.has(working.probeId)
|| (layer ? vargaLayerCovered(mentionedKeys, layer) : false);
return {
kind: "contrast",
contrastProbe: probe,
styleOptions: styleOptions.options,
score: rankDiscriminatorScore({
informationGain: probe.informationGain,
asked,
candidateIds,
topCandidateTimes,
}),
row: {
kind: "contrast",
contrastProbe: working,
styleOptions,
score: rankDiscriminatorScore({
informationGain: working.informationGain,
asked,
candidateIds,
topCandidateTimes,
}),
},
dropped: null,
};
}
@@ -714,13 +753,16 @@ function rankRenderableDiscriminators(input: {
topCandidateTimes?: readonly string[];
providedDomains?: readonly string[];
evidence?: readonly MethodFollowupEvidence[];
}): { locked: RankedDiscriminator[]; yearless: RankedDiscriminator[] } {
}): { locked: RankedDiscriminator[]; yearless: RankedDiscriminator[]; dropped: DroppedProbe[] } {
const top = input.topCandidateTimes ?? [];
const provided = new Set(input.providedDomains ?? []);
const mentioned = input.mentionedKeys ?? new Set();
const rows: RankedDiscriminator[] = [];
const dropped: DroppedProbe[] = [];
const seen = new Set<string>();
const push = (row: RankedDiscriminator | null) => {
const push = (result: { row: RankedDiscriminator | null; dropped: DroppedProbe | null }) => {
if (result.dropped) dropped.push(result.dropped);
const row = result.row;
if (!row) return;
const key = row.eventProbe?.semantic_key
?? row.contrastProbe?.semanticKey
@@ -740,6 +782,7 @@ function rankRenderableDiscriminators(input: {
return {
locked: sorted.filter((row) => discriminatorLocksScoringPeriod(row)),
yearless: sorted.filter((row) => !discriminatorLocksScoringPeriod(row) && discriminatorChoiceKind(row) !== "varga_style"),
dropped,
};
}
@@ -1174,7 +1217,7 @@ export function buildMethodFollowupPlan(input: {
providedDomains: datedDomainsFromEvidence(input.evidence),
evidence: input.evidence,
})
: { locked: [] as RankedDiscriminator[], yearless: [] as RankedDiscriminator[] };
: { locked: [] as RankedDiscriminator[], yearless: [] as RankedDiscriminator[], dropped: [] as DroppedProbe[] };
const rankedDiscriminators = rankedCatalog.locked;
const yearlessDiscriminators = rankedCatalog.yearless;
const bestDiscriminator = rankedDiscriminators[0] ?? null;
@@ -1259,6 +1302,7 @@ export function buildMethodFollowupPlan(input: {
stop_domain_rotation: true,
do_not_poll: DO_NOT_POLL,
not_in_rotation: NOT_IN_ROTATION,
dropped_probes: rankedCatalog.dropped,
};
}
}
@@ -1276,6 +1320,7 @@ export function buildMethodFollowupPlan(input: {
stop_domain_rotation: true,
do_not_poll: DO_NOT_POLL,
not_in_rotation: NOT_IN_ROTATION,
dropped_probes: rankedCatalog.dropped,
};
}
@@ -1304,6 +1349,7 @@ export function buildMethodFollowupPlan(input: {
stop_domain_rotation: true,
do_not_poll: DO_NOT_POLL,
not_in_rotation: NOT_IN_ROTATION,
dropped_probes: rankedCatalog.dropped,
};
}
@@ -1715,6 +1761,7 @@ export function buildMethodFollowupPlan(input: {
stop_domain_rotation: true,
do_not_poll: DO_NOT_POLL,
not_in_rotation: NOT_IN_ROTATION,
dropped_probes: rankedCatalog.dropped,
};
}
@@ -9,7 +9,7 @@ import {
stampChoiceSchemaWithProbe,
previousInferenceFromReceipt,
} from "./inference-adapter";
import type { MethodFollowup } from "./method-followup";
import { spokenFollowupForUser, type MethodFollowup } from "./method-followup";
import {
setV10ConversationFocus,
RectificationToolServiceError,
@@ -112,6 +112,7 @@ export function openQuestionFromPersistedFocus(result: PersistServerFocusResult)
|| result.focus.questionId !== result.questionId
) return null;
if (!result.prompt || !parseAgentChoiceCopy(result.focus.expectedAnswerSchema)) {
if (result.focus.expectedAnswerSchema?.collect === true) return null;
return {
question_id: result.questionId,
prompt: null,
@@ -127,6 +128,80 @@ export function openQuestionFromPersistedFocus(result: PersistServerFocusResult)
};
}
export const COLLECT_FOCUS_SCHEMA_KEY = "collect";
function collectFocusSchema(followup: MethodFollowup): Record<string, unknown> | null {
const prompt = spokenFollowupForUser({ ...followup, choice_frame: null });
if (!prompt) return null;
return {
prompt,
[COLLECT_FOCUS_SCHEMA_KEY]: true,
semantic_key: followup.semantic_key ?? null,
};
}
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 && isCollectFocusSchema(active.expectedAnswerSchema)) {
return { status: "already_open", focus: active, questionId: active.questionId, prompt };
}
try {
const result = await setV10ConversationFocus(input.accounting, input.userId, input.caseId, {
questionId,
intent: input.followup.intent,
targetEvidenceId: null,
targetDomain: input.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,
};
}
return {
status: "skipped",
focus: input.activeFocus,
questionId: null,
prompt: null,
};
}
}
export async function persistServerOwnedFocus(input: {
accounting: AccountingClient;
userId: string;
@@ -137,9 +212,34 @@ export async function persistServerOwnedFocus(input: {
}): Promise<PersistServerFocusResult> {
const followup = input.followup;
const frame = followup?.choice_frame ?? null;
if (!followup || !frame) {
if (!followup) {
return {
status: followup?.intent === "distinguish_candidates" ? "invalid_choice_schema" : "skipped",
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,
@@ -87,6 +87,17 @@ export function projectCurrentQuestion(
domain: focus.targetDomain ?? null,
};
}
const collectPrompt = typeof schema?.prompt === "string" ? schema.prompt.trim() : "";
if (focus.intent === "collect_method_evidence" && collectPrompt && !looksLikeChoiceSchema(schema)) {
return {
question_id: focus.questionId ?? null,
focus_id: focus.id ?? null,
probe_id: probeId,
prompt: collectPrompt,
intent: focus.intent,
domain: focus.targetDomain ?? null,
};
}
if (!looksLikeChoiceSchema(schema)) return null;
return {
question_id: focus.questionId ?? null,