math: dollar delimiters end-to-end (exact answers, escaping examples, context rendering)

- switch the math authoring/rendering contract from \(...\)/\[...\] to the
  standard $...$ inline / $$...$$ display: prompts (20-question-tools,
  40-formatting), singleDollarTextMath on the renderer, legacy reader kept,
  and escape-debris recovery extended to dollar spans (mirroring micromark
  delimiter-run rules)
- exact answer membership now precedes debris cleanup in
  coerceCorrectAnswer (calculus incident: an exact sentence-final-period
  label was stripped as model debris, then rejected); option keys are
  passed at the boundary/grading seams, the dock no longer pre-coerces
- a complete, phase-correct JSON-escaping example rides the probe/quiz
  tool descriptions (serialized from one canonical body; quiz leads with
  type+nodeId), plus the one-sentence JSON-escaping rule in 40-formatting
- ask option descriptions render markdown/LaTeX in the pending dock;
  settled cards keep the details line and option descriptions
  retroactively, read from the persisted call arguments (chat passes
  part.input to AnsweredCard)

1566/1566 tests on both navigator configs; typecheck, lint, production
build, standalone packaging, and Electron-runtime smoke all green.
This commit is contained in:
amosblomqvist
2026-10-13 06:24:33 +03:00
parent a395f37727
commit bf29a2070f
19 changed files with 628 additions and 93 deletions
+43 -3
View File
@@ -106,12 +106,42 @@ export const askUserQuestionTool: AgentTool<typeof askInputSchema> = {
// quiz variants): duplicates stay honest by sharing SCHEMA ATOMS
// (question-core) + PROMPT CHUNKS, and each tool declares only its
// mechanical delta.
//
// ONE sanctioned exception (owner, 2026-10-12): each quiz tool's
// description ends with a COMPLETE example call — compact, phase-correct
// (probe carries no phase metadata; quiz leads with type+nodeId), doubling
// as the JSON-escaping exemplar (LaTeX backslashes double inside JSON
// strings). Placement ruling: WITH THE TOOL, beside its exact schema — a
// shared-prompt example is phase-ambiguous (the models fumbled exactly
// these metadata fields), per-agent prompt copies would drift. Serialized
// from ONE canonical body so the variants cannot disagree; craft doctrine
// stays in the prompts.
/** The canonical example body (owner-approved content, 2026-10-12): a
* compact applied question whose fields exercise the escaping hazards —
* braced commands (\frac, \cos), inline and display math, newlines —
* while doubling as an exemplar of good craft: uniform options, stable
* values, reasoning (explanation) committed before correctAnswer.
* JSON.stringify renders the wire text, so the escaping the model sees
* is by construction the correct one. Exported for the description pins. */
export const quizExampleBody = {
question: "A signal is $y=\\sin(2x)$ and $\\frac{dx}{dt}=3$. What is $\\frac{dy}{dt}$?",
options: [
{ label: "$6\\cos(2x)$", value: "a" },
{ label: "$2\\cos(2x)$", value: "b" },
{ label: "$3\\cos(2x)$", value: "c" },
],
explanation:
"The phase changes twice as fast as $x$; multiply by the sine's slope:\n$$\n\\frac{dy}{dt}=\\cos(2x)\\cdot2\\cdot3=6\\cos(2x)\n$$",
correctAnswer: "a",
};
/** The quiz mechanics every variant shares; `delta` appends the variant's
* own fields (its NAME among them — the probe-phase tools self-reference
* as probe/recall, owner 2026-10-09). All mechanics, zero doctrine. */
* as probe/recall, owner 2026-10-09). All mechanics, zero doctrine — plus
* the one sanctioned example (above). */
function quizDescription(delta: { name: "quiz" | "probe"; nodeId: boolean; types: boolean }): string {
return (
const base =
"Ask the learner a GRADED question with a known correct answer, then instantly grade and give feedback. " +
"Unlike ask_user_question (which collects preferences/decisions with no right answer), " +
delta.name +
@@ -129,7 +159,17 @@ function quizDescription(delta: { name: "quiz" | "probe"; nodeId: boolean; types
: "") +
"ONE question per turn: call a single " +
delta.name +
", then end your turn — his answer determines the next."
", then end your turn — his answer determines the next.";
// Phase-correct shape: the teach example LEADS with the required pair
// (type, nodeId — the 2026-10-09 drop-order ruling); the probe example
// carries no phase metadata at all, exactly like the probe schema.
const example = delta.nodeId ? { type: "check" as const, nodeId: "chain-rule", ...quizExampleBody } : quizExampleBody;
return (
base +
"\nExample arguments — copy the escaping exactly: inside JSON strings a LaTeX backslash is \\\\ and a newline is \\n.\n```json\n" +
JSON.stringify(example, null, 2) +
"\n```" +
(delta.nodeId ? " The example's nodeId is illustrative — copy an exact id from this session's plan_init." : "")
);
}