fix(web): isolate rectification answers from tool-step planning text
Mastra intermediate text-delta was published as answer.delta, then set-focus domain errors reset the attempt and replayed evidence. Publish only the terminal no-tool step, persist the next probe on the server, and ground batch quotes in the source turn. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { persistServerOwnedFocus, shouldSkipDiscriminatorFollowup, stableFollowupQuestionId } from "../src/lib/rectification-agentic/v9/server-focus.ts";
|
||||
import { buildChoiceFrame, serverOwnedChoiceCopy } from "../src/lib/rectification-agentic/v9/choice-card.ts";
|
||||
import type { MethodFollowup } from "../src/lib/rectification-agentic/v9/method-followup.ts";
|
||||
import { fakeAccounting, CASE_ID, FOCUS_ID, USER_ID, activeFocusFixture } from "./rectification-v9-test-support.ts";
|
||||
|
||||
function discriminatorFollowup(overrides: Partial<MethodFollowup> = {}): MethodFollowup {
|
||||
const frame = buildChoiceFrame({
|
||||
method_id: "dasha_events",
|
||||
ask_theme: "dated_event",
|
||||
domain: "education",
|
||||
user_prompt_hint: "ask",
|
||||
}, {
|
||||
probes: [{
|
||||
year: 2016,
|
||||
year_label: "2016 年前后",
|
||||
domain: "education",
|
||||
event_family: "高考或重要考试发挥明显失常",
|
||||
source: "dasha_activation",
|
||||
tracks: ["vimshottari", "narayana"],
|
||||
tracks_agree: true,
|
||||
unique_minute_claim: false,
|
||||
user_meaning: "2016 年前后高考或重要考试发挥明显失常",
|
||||
role: "distinguish",
|
||||
information_gain: 0.4,
|
||||
semantic_key: "education:2016",
|
||||
}],
|
||||
});
|
||||
return {
|
||||
method_id: "dasha_events",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "dated_event",
|
||||
domain: "education",
|
||||
kind_hint: null,
|
||||
user_prompt_hint: "ask",
|
||||
must_not_label: false,
|
||||
choice_frame: frame,
|
||||
source: "event_probe",
|
||||
information_gain: 0.4,
|
||||
semantic_key: "education:2016",
|
||||
probe_year: 2016,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test("does not ask an already-answered discriminator probe again", async () => {
|
||||
const followup = discriminatorFollowup();
|
||||
const active = activeFocusFixture({
|
||||
expectedAnswerSchema: { probe_id: "education:2016", choice: serverOwnedChoiceCopy(followup.choice_frame!) },
|
||||
});
|
||||
const accounting = fakeAccounting({
|
||||
set_agentic_rectification_conversation_focus: () => {
|
||||
throw new Error("should not create a second focus");
|
||||
},
|
||||
});
|
||||
const result = await persistServerOwnedFocus({
|
||||
accounting: accounting.client,
|
||||
userId: USER_ID,
|
||||
caseId: CASE_ID,
|
||||
activeFocus: {
|
||||
...active,
|
||||
id: FOCUS_ID,
|
||||
caseId: CASE_ID,
|
||||
questionId: stableFollowupQuestionId(followup),
|
||||
intent: "distinguish_candidates",
|
||||
targetEvidenceId: null,
|
||||
targetDomain: "education",
|
||||
targetKind: null,
|
||||
expectedAnswerSchema: { probe_id: "education:2016" },
|
||||
status: "active",
|
||||
askedAt: "2026-08-24T00:00:00.000Z",
|
||||
resolvedAt: null,
|
||||
},
|
||||
decisionReceipt: {
|
||||
inference_state: {
|
||||
answered_probes: [{ probe_id: "education:2016", semantic_key: "education:2016", answer_class: "yes" }],
|
||||
probes: [],
|
||||
},
|
||||
},
|
||||
followup,
|
||||
});
|
||||
assert.equal(result.status, "probe_already_answered");
|
||||
assert.equal(accounting.calls.length, 0);
|
||||
});
|
||||
|
||||
test("zero information gain does not open a discriminator", () => {
|
||||
assert.equal(
|
||||
shouldSkipDiscriminatorFollowup(discriminatorFollowup({ information_gain: 0 })),
|
||||
"zero_information_gain",
|
||||
);
|
||||
});
|
||||
|
||||
test("duplicate focus conflict does not throw", async () => {
|
||||
const followup = discriminatorFollowup({ source: "precision_stage", information_gain: 0.2 });
|
||||
const accounting = fakeAccounting({
|
||||
set_agentic_rectification_conversation_focus: () => {
|
||||
throw new Error("agentic_rectification_focus_idempotency_conflict");
|
||||
},
|
||||
});
|
||||
const result = await persistServerOwnedFocus({
|
||||
accounting: accounting.client,
|
||||
userId: USER_ID,
|
||||
caseId: CASE_ID,
|
||||
activeFocus: null,
|
||||
decisionReceipt: null,
|
||||
followup,
|
||||
});
|
||||
assert.equal(result.status, "duplicate_focus");
|
||||
});
|
||||
Reference in New Issue
Block a user