fix(rectification): stop re-asking covered domains after adopt (BUG-590, BUG-591)
Yearless-to-collect now skips domains already confirmed, declined, or asked; adopt-exhausted turns skip leftover collect persist. Collect focus collisions only retry with :next when collect_retry is set. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -82,12 +82,14 @@ import {
|
||||
buildNextUserAction,
|
||||
exhaustionSpokenCollectFollowup,
|
||||
isRemainingEvidenceCollect,
|
||||
remainingEvidenceCollectStillOpen,
|
||||
planWithDateReliability,
|
||||
spokenCollectFallbackFollowup,
|
||||
spokenFollowupForUser,
|
||||
ledgerHasConfirmedDatedEvent,
|
||||
type MethodCoverage,
|
||||
type MethodFollowup,
|
||||
type MethodFollowupEvidence,
|
||||
type MethodFollowupPlan,
|
||||
} from "./method-followup";
|
||||
import { followupCaseArgs, isBlockChoiceSchema, isWidenWindowSchema } from "./block-scan.ts";
|
||||
@@ -230,6 +232,10 @@ function shouldSkipFollowupPersist(input: {
|
||||
followup: MethodFollowup | null;
|
||||
methods?: readonly MethodCoverage[];
|
||||
accepted?: boolean;
|
||||
stopReason?: string | null;
|
||||
sessionOutcome?: string | null;
|
||||
evidence?: readonly MethodFollowupEvidence[];
|
||||
declinedTopics?: readonly Readonly<Record<string, unknown>>[];
|
||||
}): boolean {
|
||||
if (input.accepted) return false;
|
||||
if (!input.canAdopt) return false;
|
||||
@@ -244,7 +250,22 @@ function shouldSkipFollowupPersist(input: {
|
||||
}
|
||||
if (input.methods && !blockingMethodsCovered(input.methods)) return false;
|
||||
if (isRemainingDiscriminatorFollowup(input.followup)) return false;
|
||||
if (isRemainingEvidenceCollect(input.followup)) return false;
|
||||
if (isRemainingEvidenceCollect(input.followup)) {
|
||||
const adoptExhausted = (
|
||||
input.nextAction === "ready_to_adopt"
|
||||
|| input.sessionOutcome === "adopt_representative"
|
||||
) && input.stopReason === "probe_pool_exhausted";
|
||||
if (
|
||||
!adoptExhausted
|
||||
|| remainingEvidenceCollectStillOpen(
|
||||
input.followup,
|
||||
input.evidence ?? [],
|
||||
input.declinedTopics ?? [],
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -791,6 +812,10 @@ export async function persistNextInterviewAfterChoice(input: {
|
||||
followup,
|
||||
methods: plan.methods,
|
||||
accepted: Boolean(input.dossier.case.acceptedTime),
|
||||
stopReason: decision.stopReason ?? null,
|
||||
sessionOutcome: input.nextAction.session_outcome,
|
||||
evidence: input.dossier.evidence,
|
||||
declinedTopics: input.dossier.conversationSummary.declinedSkippedTopics,
|
||||
})) {
|
||||
const facts = adoptDeliveryFacts(decision, liveDossier);
|
||||
const fallback = adoptHostNarration({
|
||||
@@ -948,7 +973,12 @@ async function persistFocusAfterChoice(input: {
|
||||
prompt: null,
|
||||
};
|
||||
}
|
||||
if (persisted.status === "created" || persisted.status === "already_open" || !input.followup) {
|
||||
if (
|
||||
persisted.status === "created"
|
||||
|| persisted.status === "already_open"
|
||||
|| persisted.status === "duplicate_focus"
|
||||
|| !input.followup
|
||||
) {
|
||||
return persisted;
|
||||
}
|
||||
if (persisted.status === "skipped" && !followupHasPersistableDomain(input.followup)) {
|
||||
@@ -1224,6 +1254,10 @@ export async function persistNextInterviewIfIdle(input: {
|
||||
followup,
|
||||
methods: plan.methods,
|
||||
accepted: Boolean(dossier.case.acceptedTime),
|
||||
stopReason: decision.stopReason ?? null,
|
||||
sessionOutcome: decision.sessionOutcome,
|
||||
evidence: dossier.evidence,
|
||||
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
|
||||
})) {
|
||||
const facts = adoptDeliveryFacts(decision, dossier);
|
||||
const fallback = adoptHostNarration({
|
||||
|
||||
@@ -1292,6 +1292,67 @@ export function isRemainingEvidenceCollect(
|
||||
&& REMAINING_EVIDENCE_COLLECT_DOMAINS.has(followup.domain);
|
||||
}
|
||||
|
||||
function collectFocusAskedForDomain(
|
||||
topics: readonly Readonly<Record<string, unknown>>[],
|
||||
domain: string,
|
||||
): boolean {
|
||||
if (domain === "health" || domain === "health_pressure") {
|
||||
return domainCollectFocusAsked(topics, "health")
|
||||
|| domainCollectFocusAsked(topics, "health_pressure");
|
||||
}
|
||||
return domainCollectFocusAsked(topics, domain);
|
||||
}
|
||||
|
||||
function yearlessCollectDomainBlocked(
|
||||
domain: string,
|
||||
evidence: readonly MethodFollowupEvidence[],
|
||||
declined: ReadonlySet<string>,
|
||||
askedRows: readonly Readonly<Record<string, unknown>>[],
|
||||
): boolean {
|
||||
if (domain === "health" || domain === "health_pressure") {
|
||||
return declinedHealth(declined)
|
||||
|| hasConfirmedHealth(evidence)
|
||||
|| collectFocusAskedForDomain(askedRows, domain);
|
||||
}
|
||||
return declined.has(domain)
|
||||
|| hasConfirmedDomain(evidence, domain)
|
||||
|| collectFocusAskedForDomain(askedRows, domain);
|
||||
}
|
||||
|
||||
/** Remaining spoken collect that can still add a dated event in an uncovered domain. */
|
||||
export function remainingEvidenceCollectStillOpen(
|
||||
followup: Pick<MethodFollowup, "intent" | "domain" | "date_reliability_evidence_id"> | null | undefined,
|
||||
evidence: readonly MethodFollowupEvidence[] = [],
|
||||
declinedTopics: readonly Readonly<Record<string, unknown>>[] = [],
|
||||
): boolean {
|
||||
if (!isRemainingEvidenceCollect(followup) || !followup?.domain) return false;
|
||||
const declined = declinedDomains(declinedTopics);
|
||||
if (followup.domain === "occupation") {
|
||||
return !hasConfirmedDomain(evidence, "occupation") && !declined.has("occupation");
|
||||
}
|
||||
if ((DATED_COLLECT_ORDER as readonly string[]).includes(followup.domain)) {
|
||||
return !datedCollectDomainBlocked(
|
||||
followup.domain as (typeof DATED_COLLECT_ORDER)[number],
|
||||
evidence,
|
||||
declined,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function holdoutOccupiedDomains(evidence: readonly MethodFollowupEvidence[]): Set<string> {
|
||||
const occupied = new Set(
|
||||
evidence
|
||||
.filter((item) => isConfirmedDated(item) && evidenceYear(item) != null)
|
||||
.map((item) => item.domain),
|
||||
);
|
||||
if (hasConfirmedHealth(evidence) || occupied.has("health") || occupied.has("health_pressure")) {
|
||||
occupied.add("health");
|
||||
occupied.add("health_pressure");
|
||||
}
|
||||
return occupied;
|
||||
}
|
||||
|
||||
export function exhaustionSpokenCollectFollowup(input: {
|
||||
evidence: readonly MethodFollowupEvidence[];
|
||||
declinedTopics?: readonly Readonly<Record<string, unknown>>[];
|
||||
@@ -1682,17 +1743,22 @@ export function holdoutFollowupFor(
|
||||
declined: ReadonlySet<string>,
|
||||
): Omit<MethodFollowup, "must_not_label" | "choice_frame"> | null {
|
||||
if (!meetsAcceptanceEventQuality(input.evidence)) return null;
|
||||
const occupied = new Set(
|
||||
input.evidence
|
||||
.filter((item) => isConfirmedDated(item) && evidenceYear(item) != null)
|
||||
.map((item) => item.domain),
|
||||
);
|
||||
const prompt = (input.oosBlindPrompts ?? []).find((item) => (
|
||||
item.domain && !declined.has(item.domain) && !occupied.has(item.domain)
|
||||
)) ?? null;
|
||||
const reserved = (input.holdoutEvents ?? []).find((item) => (
|
||||
item.year != null && !declined.has(item.domain) && !occupied.has(item.domain)
|
||||
)) ?? null;
|
||||
const occupied = holdoutOccupiedDomains(input.evidence);
|
||||
const healthDeclined = declinedHealth(declined);
|
||||
const prompt = (input.oosBlindPrompts ?? []).find((item) => {
|
||||
if (!item.domain || occupied.has(item.domain)) return false;
|
||||
if (item.domain === "health" || item.domain === "health_pressure") {
|
||||
return !healthDeclined;
|
||||
}
|
||||
return !declined.has(item.domain);
|
||||
}) ?? null;
|
||||
const reserved = (input.holdoutEvents ?? []).find((item) => {
|
||||
if (item.year == null || occupied.has(item.domain)) return false;
|
||||
if (item.domain === "health" || item.domain === "health_pressure") {
|
||||
return !healthDeclined;
|
||||
}
|
||||
return !declined.has(item.domain);
|
||||
}) ?? null;
|
||||
return holdoutAskFields(prompt, reserved);
|
||||
}
|
||||
|
||||
@@ -2223,23 +2289,28 @@ export function buildMethodFollowupPlan(input: {
|
||||
}
|
||||
if (!next && dashaCovered && coverageComplete && occupationCovered) {
|
||||
const allowLowGainDiscriminator = !coverageComplete || !candidatesSeparated;
|
||||
const yearless = yearlessDiscriminators[0];
|
||||
if (yearless && (allowLowGainDiscriminator || yearless.score >= 0.08)) {
|
||||
const askedRows = [
|
||||
...(input.closedCollectFocuses ?? []),
|
||||
...(input.declinedTopics ?? []),
|
||||
];
|
||||
for (const yearless of yearlessDiscriminators) {
|
||||
if (!allowLowGainDiscriminator && yearless.score < 0.08) continue;
|
||||
const domain = contrastFollowupDomain(
|
||||
yearless.eventProbe?.domain ?? yearless.contrastProbe?.domain ?? null,
|
||||
);
|
||||
const lead = YEARLESS_COLLECT_LEAD[domain];
|
||||
if (lead && !declined.has(domain)) {
|
||||
next = makeFollowup({
|
||||
method_id: PROBE_METHOD_ID[domain],
|
||||
intent: "collect_method_evidence",
|
||||
ask_theme: REVERSE_VERIFY_THEME[domain],
|
||||
domain,
|
||||
kind_hint: REVERSE_VERIFY_KIND[domain],
|
||||
user_prompt_hint: collect(lead, REVERSE_VERIFY_VARGA[domain]),
|
||||
source: "method_coverage",
|
||||
});
|
||||
}
|
||||
if (!lead) continue;
|
||||
if (yearlessCollectDomainBlocked(domain, input.evidence, declined, askedRows)) continue;
|
||||
next = makeFollowup({
|
||||
method_id: PROBE_METHOD_ID[domain],
|
||||
intent: "collect_method_evidence",
|
||||
ask_theme: REVERSE_VERIFY_THEME[domain],
|
||||
domain,
|
||||
kind_hint: REVERSE_VERIFY_KIND[domain],
|
||||
user_prompt_hint: collect(lead, REVERSE_VERIFY_VARGA[domain]),
|
||||
source: "method_coverage",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (
|
||||
|
||||
@@ -356,6 +356,14 @@ async function persistCollectFocus(input: {
|
||||
prompt: null,
|
||||
};
|
||||
}
|
||||
if (input.followup.collect_retry !== true) {
|
||||
return {
|
||||
status: "duplicate_focus",
|
||||
focus: input.activeFocus,
|
||||
questionId,
|
||||
prompt,
|
||||
};
|
||||
}
|
||||
const retryId = `${questionId}:${COLLECT_FOCUS_RETRY_SUFFIX}`.slice(0, 160);
|
||||
if (retryId === questionId) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user