fix(rectification): prevent collect focus dead-end after choice answers

This commit is contained in:
Jesse_Chen
2026-08-30 19:47:30 +08:00
parent 85b14b13ae
commit 40684eec6d
4 changed files with 76 additions and 17 deletions
@@ -1944,13 +1944,15 @@ export function buildMethodFollowupPlan(input: {
const deferAdoption = sessionOutcome === "adopt_representative"
|| sessionOutcome === "validated_range"
|| sessionOutcome === "exact_minute_confirmed"
|| sessionOutcome === "provisional_range"
|| sessionOutcome === "provisional_range_user_stopped"
|| sessionOutcome === "completed_with_range";
const deferProvisionalDiscriminator = sessionOutcome === "provisional_range"
&& next?.intent === "distinguish_candidates";
const deferFollowup = deferAdoption || deferProvisionalDiscriminator;
return {
methods,
next_followup: deferAdoption ? null : next,
deferred_followup: deferAdoption ? next : null,
next_followup: deferFollowup ? null : next,
deferred_followup: deferFollowup ? next : null,
session_outcome: sessionOutcome ?? "collect_evidence",
stop_domain_rotation: true,
do_not_poll: DO_NOT_POLL,
@@ -76,6 +76,7 @@ import {
stampChoiceSchemaWithProbe,
} from "@/lib/rectification-agentic/v9/inference-adapter";
import {
isCollectFocusSchema,
openQuestionFromPersistedFocus,
persistServerOwnedFocus,
} from "@/lib/rectification-agentic/v9/server-focus";
@@ -738,6 +739,7 @@ export function createRectificationV9ReadOnlyTools(ctx: RectificationV9Context)
export function createRectificationV9Tools(ctx: RectificationV9Context) {
const { accounting, userId, caseId, turnId, attemptId, userMessage } = ctx;
const engineVersion = v9EngineVersion();
let hasReadCase = false;
const receipt = async (
toolName: string,
@@ -1109,6 +1111,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
)
: projectTurnDecision(refreshed);
await receipt("rectification-read-case", "case.loaded", "completed", { inputFingerprint, resultFingerprint: hashResult(projection) });
hasReadCase = true;
return projection;
}
}
@@ -1120,6 +1123,7 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
)
: projectTurnDecision(dossier);
await receipt("rectification-read-case", "case.loaded", "completed", { inputFingerprint, resultFingerprint: hashResult(projection) });
hasReadCase = true;
return projection;
} catch (error) {
await receipt("rectification-read-case", "case.loaded", "failed", { inputFingerprint, safeErrorCode: safeToolErrorCode(error) });
@@ -1360,6 +1364,10 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
}).strict(),
execute: async (input) => {
assertCaseRef(input);
if (!hasReadCase) {
await loadV9CaseDossier(accounting, userId, input.caseId);
hasReadCase = true;
}
for (const item of input.items) {
if (!isEvidenceKind(item.proposedKind)) throw new RectificationToolServiceError("invalid_event_kind");
if (!isEvidenceDomain(item.domain)) throw new RectificationToolServiceError("invalid_domain");
@@ -1459,6 +1467,22 @@ export function createRectificationV9Tools(ctx: RectificationV9Context) {
}));
if (result.acceptedCount > 0) {
const dossier = await loadV9CaseDossier(accounting, userId, input.caseId);
const acceptedEvidenceId = result.items.find(
(item) => item.outcome === "accepted" && item.evidenceId,
)?.evidenceId ?? null;
const activeFocus = dossier.conversationSummary.activeFocus;
if (
acceptedEvidenceId
&& activeFocus
&& activeFocus.intent === "collect_method_evidence"
&& isCollectFocusSchema(activeFocus.expectedAnswerSchema)
) {
await resolveV10ConversationFocus(accounting, userId, input.caseId, {
focusId: activeFocus.id,
status: "resolved",
evidenceId: acceptedEvidenceId,
});
}
if (isResumableStatus(dossier.case.status as RectificationCaseStatus)) {
await transitionV9CaseStatus(accounting, userId, input.caseId, "collecting_evidence");
}