quiz nodeId required (owner ruling 2026-09-26): no untagged teach quizzes — boundary rejects, model retries tagged; off-map checks route to ask_user_question; anti-rush doctrine in schema+prompt; docs+comments updated
This commit is contained in:
@@ -25,7 +25,7 @@ import {
|
||||
} from "../src/agent/tools.ts";
|
||||
import { TOGGLEABLE_TOOLS } from "../src/core/pending.ts";
|
||||
import { jobStore } from "../src/store/jobs.ts";
|
||||
import { probeQuizInputSchema } from "../src/core/question-core.ts";
|
||||
import { probeQuizInputSchema, quizInputObject } from "../src/core/question-core.ts";
|
||||
import { chatStore } from "../src/store/chats.ts";
|
||||
import { PATHS } from "../config.ts";
|
||||
|
||||
@@ -57,6 +57,7 @@ const validQuiz = {
|
||||
],
|
||||
correctAnswer: "4",
|
||||
explanation: "Arithmetic.",
|
||||
nodeId: "arith",
|
||||
};
|
||||
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
@@ -119,15 +120,33 @@ describe("teachQuizToolFor: the dynamic plan-nodeId validation (position signal)
|
||||
assert.match(msg, /arith/);
|
||||
});
|
||||
|
||||
it("no tree yet: ANY nodeId is rejected — omit it for ad-hoc checks", async () => {
|
||||
it("no tree yet: ANY nodeId is rejected — quizzes wait for plan_init", async () => {
|
||||
const chatId = newChat("NodeId: no tree");
|
||||
const tool = teachQuizToolFor({ chatId, chatStore });
|
||||
const res = await boundaryParse(tool, { ...validQuiz, nodeId: "anything" });
|
||||
assert.equal(res.success, false);
|
||||
assert.match(problemLines(res.error).at(-1)!, /No plan map exists yet/);
|
||||
// …while an untagged ad-hoc check is fine
|
||||
const plain = await boundaryParse(tool, validQuiz);
|
||||
assert.equal(plain.success, true);
|
||||
assert.match(problemLines(res.error).at(-1)!, /No plan map exists yet — call plan_init/);
|
||||
});
|
||||
|
||||
it("missing nodeId is rejected at the boundary (owner ruling 2026-09-26: no untagged quizzes)", async () => {
|
||||
const chatId = newChat("NodeId: required");
|
||||
chatStore.saveTree(chatId, {
|
||||
nodes: [{ id: "arith", title: "Arithmetic" }],
|
||||
edges: [],
|
||||
version: 1,
|
||||
});
|
||||
const tool = teachQuizToolFor({ chatId, chatStore });
|
||||
const { nodeId: _dropped, ...untagged } = validQuiz;
|
||||
const res = await boundaryParse(tool, untagged);
|
||||
assert.equal(res.success, false, "an untagged quiz must be rejected back to the model");
|
||||
// The structural pin: the SCHEMA itself names the missing field (pi's
|
||||
// boundary formats Value.Errors as the tool-input-error the model sees).
|
||||
const schemaErrors = [...Value.Errors(quizInputObject, untagged)].map((e) => e.message);
|
||||
assert.match(
|
||||
schemaErrors.join("\n"),
|
||||
/nodeId/,
|
||||
"the schema rejection names nodeId so the model can retry tagged",
|
||||
);
|
||||
});
|
||||
|
||||
it("reads the LIVE record — a plan_init mutation mid-turn is honored immediately", async () => {
|
||||
|
||||
Reference in New Issue
Block a user