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>
This commit is contained in:
@@ -3,7 +3,9 @@ import test from "node:test";
|
||||
|
||||
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
|
||||
import {
|
||||
COLLECT_FOCUS_RETRY_SUFFIX,
|
||||
openQuestionFromPersistedFocus,
|
||||
persistableFocusDomain,
|
||||
persistServerOwnedFocus,
|
||||
shouldSkipDiscriminatorFollowup,
|
||||
stableFollowupQuestionId,
|
||||
@@ -599,3 +601,123 @@ test("degraded spoken collect does not keep discriminator identity or block a la
|
||||
assert.ok(open);
|
||||
assert.notEqual(open?.unrenderable, true);
|
||||
});
|
||||
|
||||
function collectFollowup(overrides: Partial<MethodFollowup> = {}): MethodFollowup {
|
||||
return {
|
||||
method_id: "dasha_events",
|
||||
intent: "collect_method_evidence",
|
||||
ask_theme: "dated_event",
|
||||
domain: "relationship",
|
||||
kind_hint: null,
|
||||
user_prompt_hint: "collect",
|
||||
must_not_label: false,
|
||||
choice_frame: null,
|
||||
source: "method_coverage",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function focusRowFromArgs(args: Record<string, unknown>) {
|
||||
return {
|
||||
id: FOCUS_ID,
|
||||
case_id: CASE_ID,
|
||||
question_id: args.p_question_id,
|
||||
intent: args.p_intent,
|
||||
target_evidence_id: args.p_target_evidence_id,
|
||||
target_domain: args.p_target_domain,
|
||||
target_kind: args.p_target_kind,
|
||||
expected_answer_schema: args.p_expected_answer_schema,
|
||||
status: "active",
|
||||
asked_at: "2026-08-31T00:00:00.000Z",
|
||||
resolved_at: null,
|
||||
idempotent: false,
|
||||
};
|
||||
}
|
||||
|
||||
test("collect persist maps occupation to other and health_pressure to health", async () => {
|
||||
assert.equal(persistableFocusDomain("occupation"), "other");
|
||||
assert.equal(persistableFocusDomain("health_pressure"), "health");
|
||||
assert.equal(persistableFocusDomain("education"), "education");
|
||||
assert.equal(persistableFocusDomain("horary"), "horary");
|
||||
assert.equal(persistableFocusDomain(null), null);
|
||||
assert.equal(persistableFocusDomain("unknown"), null);
|
||||
|
||||
const occupation = collectFollowup({
|
||||
method_id: "occupation",
|
||||
ask_theme: "occupation",
|
||||
domain: "occupation",
|
||||
kind_hint: "occupation_note",
|
||||
});
|
||||
assert.equal(stableFollowupQuestionId(occupation), "collect:occupation:collect_method_evidence");
|
||||
const health = collectFollowup({
|
||||
method_id: "d30_health",
|
||||
ask_theme: "health_pressure",
|
||||
domain: "health_pressure",
|
||||
});
|
||||
const generic = collectFollowup({ domain: "other" });
|
||||
assert.equal(stableFollowupQuestionId(generic), "collect:other:collect_method_evidence");
|
||||
assert.doesNotMatch(stableFollowupQuestionId(generic), /unknown/);
|
||||
|
||||
const accounting = fakeAccounting({
|
||||
set_agentic_rectification_conversation_focus: (_fn, args) => focusRowFromArgs(args),
|
||||
});
|
||||
const occupationPersisted = await persistServerOwnedFocus({
|
||||
accounting: accounting.client,
|
||||
userId: USER_ID,
|
||||
caseId: CASE_ID,
|
||||
activeFocus: null,
|
||||
decisionReceipt: null,
|
||||
followup: occupation,
|
||||
});
|
||||
const healthPersisted = await persistServerOwnedFocus({
|
||||
accounting: accounting.client,
|
||||
userId: USER_ID,
|
||||
caseId: CASE_ID,
|
||||
activeFocus: null,
|
||||
decisionReceipt: null,
|
||||
followup: health,
|
||||
});
|
||||
const occupationWrite = accounting.calls.find((item) => (
|
||||
item.fn === "set_agentic_rectification_conversation_focus"
|
||||
&& String(item.args.p_question_id).startsWith("collect:occupation:")
|
||||
));
|
||||
const healthWrite = accounting.calls.find((item) => (
|
||||
item.fn === "set_agentic_rectification_conversation_focus"
|
||||
&& String(item.args.p_question_id).startsWith("collect:health_pressure:")
|
||||
));
|
||||
assert.equal(occupationPersisted.status, "created");
|
||||
assert.equal(healthPersisted.status, "created");
|
||||
assert.equal(occupationWrite?.args.p_target_domain, "other");
|
||||
assert.equal(healthWrite?.args.p_target_domain, "health");
|
||||
});
|
||||
|
||||
test("collect focus unique conflict retries with a :next question id", async () => {
|
||||
const followup = collectFollowup();
|
||||
let writes = 0;
|
||||
const accounting = fakeAccounting({
|
||||
set_agentic_rectification_conversation_focus: (_fn, args) => {
|
||||
writes += 1;
|
||||
if (writes === 1) throw new Error("agentic_rectification_focus_idempotency_conflict");
|
||||
return focusRowFromArgs(args);
|
||||
},
|
||||
});
|
||||
const persisted = await persistServerOwnedFocus({
|
||||
accounting: accounting.client,
|
||||
userId: USER_ID,
|
||||
caseId: CASE_ID,
|
||||
activeFocus: null,
|
||||
decisionReceipt: null,
|
||||
followup,
|
||||
});
|
||||
assert.equal(persisted.status, "created");
|
||||
assert.equal(writes, 2);
|
||||
assert.equal(
|
||||
persisted.questionId,
|
||||
`collect:relationship:collect_method_evidence:${COLLECT_FOCUS_RETRY_SUFFIX}`,
|
||||
);
|
||||
const retryWrite = accounting.calls.at(-1);
|
||||
assert.equal(
|
||||
retryWrite?.args.p_question_id,
|
||||
`collect:relationship:collect_method_evidence:${COLLECT_FOCUS_RETRY_SUFFIX}`,
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user