/** * PRACTICE RECORD MECHANICS (practice sessions, owner 2026-10-04): the * native shapes beneath the two practice tools — the active-problem fold * (conclude-before-next-problem), the thinking marker (label registers), * the ask-style dismissal (UI-only marker; the conclude pair rides the * NEXT input), and the model-facing flattening (markers labeled, the * dismissal filtered, the synthesized pair native). */ import assert from "node:assert/strict"; import { describe, it } from "node:test"; import type { AssistantMessage, ToolResultMessage } from "@earendil-works/pi-ai"; import { activeProblemOf, asAgentMessages, concludedByDismissalPair, newProblemDismissedMarker, newThinkingMessage, newUserMessage, pendingProblemDismissalOf, toLlmMessages, PRACTICE_CONCLUDE_TOOL, PRACTICE_PROBLEM_TOOL, type LhMessage, } from "../src/core/native-messages.ts"; import { CONCLUDE_ACK_TEXT } from "../src/core/contracts/chat.ts"; import { dismissAppends, inputAppends, parseChatInput, parseDismissInput } from "../src/core/input-events.ts"; // ── builders (native-shaped test fixtures) ─────────────────────────────────── const STAMPS = { api: "faux", provider: "test", model: "test-model", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }, stopReason: "toolUse", } as const; function problemPose(callId = "call_p1", statement = "Show that…"): LhMessage[] { const assistant: LhMessage & AssistantMessage = { id: `msg_a_${callId}`, role: "assistant", content: [ { type: "toolCall", id: callId, name: PRACTICE_PROBLEM_TOOL, arguments: { statement, mode: "recall" } }, ], timestamp: 1, ...STAMPS, }; const ack: LhMessage & ToolResultMessage = { id: `msg_r_${callId}`, role: "toolResult", toolCallId: callId, toolName: PRACTICE_PROBLEM_TOOL, content: [{ type: "text", text: "Problem posed — the turn ends here." }], details: null, isError: false, timestamp: 2, }; return [assistant, ack]; } function concludeCall(outcome: string): LhMessage & AssistantMessage { return { id: `msg_c_${outcome}`, role: "assistant", content: [ { type: "toolCall", id: `call_conclude_${outcome}`, name: PRACTICE_CONCLUDE_TOOL, arguments: { outcome }, }, ], timestamp: 3, ...STAMPS, }; } // ── the active-problem fold ────────────────────────────────────────────────── describe("activeProblemOf (the conclude-before-next-problem gate's fold)", () => { it("no messages → nothing active", () => { assert.equal(activeProblemOf([]), null); }); it("a posed problem (call + ack) is ACTIVE — the ack does not conclude", () => { const messages = problemPose(); const active = activeProblemOf(messages); assert.equal(active?.toolCallId, "call_p1"); assert.deepEqual(active?.input, { statement: "Show that…", mode: "recall" }); }); it("thinking submissions keep it active (nudges never conclude)", () => { const messages = [ ...problemPose(), newThinkingMessage("call_p1", false, "stuck at the second step"), ]; assert.equal(activeProblemOf(messages)?.toolCallId, "call_p1"); }); it("a conclude call deactivates", () => { const messages = [...problemPose(), concludeCall("solved")]; assert.equal(activeProblemOf(messages), null); }); it("a dismissal marker deactivates immediately (the card closes at Esc)", () => { const messages = [...problemPose(), newProblemDismissedMarker("call_p1")]; assert.equal(activeProblemOf(messages), null); }); it("a NEWER problem after a conclude is the active one", () => { const messages = [ ...problemPose("call_p1"), concludeCall("solved"), ...problemPose("call_p2", "second"), ]; assert.equal(activeProblemOf(messages)?.toolCallId, "call_p2"); }); }); // ── the dismissal's deferred conclude ──────────────────────────────────────── describe("pendingProblemDismissalOf (the marker waiting for its synthesis)", () => { it("a trailing dismissal marker pends", () => { const messages = [...problemPose(), newProblemDismissedMarker("call_p1")]; assert.equal(pendingProblemDismissalOf(messages), "call_p1"); }); it("the synthesized pair clears it", () => { const messages = [...problemPose(), newProblemDismissedMarker("call_p1"), ...concludedByDismissalPair("call_p1")]; assert.equal(pendingProblemDismissalOf(messages), null); }); it("a plain record pends nothing", () => { assert.equal(pendingProblemDismissalOf(problemPose()), null); }); }); // ── the input channel ──────────────────────────────────────────────────────── describe("the thinking input", () => { it("parses and appends the marker against the active problem", () => { const input = parseChatInput({ type: "thinking", problemCallId: "call_p1", commit: false, text: "I think the inactivity constraint applies here", }); assert.deepEqual(input, { type: "thinking", problemCallId: "call_p1", commit: false, text: "I think the inactivity constraint applies here", }); const appends = inputAppends({ messages: problemPose() }, input); assert.equal(appends.length, 1); const marker = appends[0] as { customType: string; content: string; details: unknown }; assert.equal(marker.customType, "practice-thinking"); assert.match(marker.content, /Thinking trace \(working, not committed\):\n/); assert.deepEqual(marker.details, { problemCallId: "call_p1", commit: false, text: "I think the inactivity constraint applies here", }); }); it("commit=true carries the committed register", () => { const appends = inputAppends( { messages: problemPose() }, { type: "thinking", problemCallId: "call_p1", commit: true, text: "The answer is 42 because…" }, ); assert.match((appends[0] as { content: string }).content, /Committed answer:\n/); }); it("400s against a stale or unknown problem call", () => { assert.throws( () => inputAppends({ messages: problemPose() }, { type: "thinking", problemCallId: "call_ghost", commit: true, text: "x" }), /targets no active problem/, ); assert.throws( () => inputAppends({ messages: [] }, { type: "thinking", problemCallId: "call_p1", commit: true, text: "x" }), /targets no active problem/, ); }); it("empty text 400s at parse", () => { assert.throws(() => parseChatInput({ type: "thinking", problemCallId: "c", commit: false, text: " " }), /non-empty/); }); }); describe("the dismissal's conclude synthesis rides the next input", () => { it("text after a dismissal prepends the conclude pair before the user message", () => { const record = { messages: [...problemPose(), newProblemDismissedMarker("call_p1")] }; const appends = inputAppends(record, { type: "text", text: "that one was over my head" }); assert.equal(appends.length, 3); const [call, ack, user] = appends as [AssistantMessage & { id: string }, ToolResultMessage & { id: string }, { role: string; content: string }]; assert.equal(call.content[0]!.type, "toolCall"); assert.equal((call.content[0] as { name: string }).name, PRACTICE_CONCLUDE_TOOL); assert.deepEqual((call.content[0] as { arguments: unknown }).arguments, { outcome: "dismissed" }); assert.equal(ack.toolCallId, (call.content[0] as { id: string }).id); assert.equal((ack.content[0] as { text: string }).text, CONCLUDE_ACK_TEXT); assert.equal(user.role, "user"); assert.equal(user.content, "that one was over my head"); }); it("continue after a dismissal carries the pair alone", () => { const record = { messages: [...problemPose(), newProblemDismissedMarker("call_p1")] }; const appends = inputAppends(record, { type: "continue" }); assert.equal(appends.length, 2); }); it("with nothing pending, text appends only the user message", () => { const appends = inputAppends({ messages: problemPose() }, { type: "text", text: "hello" }); assert.equal(appends.length, 1); }); }); describe("the dismiss-problem input (ask-style discard)", () => { it("parses and appends the UI-only marker against the active problem", () => { const input = parseDismissInput({ type: "dismiss-problem", problemCallId: "call_p1" }); assert.deepEqual(input, { type: "dismiss-problem", problemCallId: "call_p1" }); const appends = dismissAppends({ messages: problemPose() }, input); assert.equal(appends.length, 1); assert.equal((appends[0] as { customType: string }).customType, "problem-dismissed"); }); it("400s when nothing active or a stale id", () => { assert.throws(() => dismissAppends({ messages: [] }, { type: "dismiss-problem", problemCallId: "x" }), /no active problem/); const concluded = [...problemPose(), concludeCall("solved")]; assert.throws(() => dismissAppends({ messages: concluded }, { type: "dismiss-problem", problemCallId: "call_p1" }), /no active problem/); }); }); // ── the model-facing seam ──────────────────────────────────────────────────── describe("toLlmMessages (practice shapes)", () => { it("the thinking marker flattens to a labeled user message", () => { const out = toLlmMessages(asAgentMessages([newThinkingMessage("call_p1", true, "It is 42")])); assert.equal(out.length, 1); const user = out[0] as { role: string; content: Array<{ type: string; text: string }> }; assert.equal(user.role, "user"); assert.match(user.content[0]!.text, /^Committed answer:\nIt is 42$/); }); it("the dismissal marker is UI-only (the synthesized pair carries the face)", () => { const out = toLlmMessages(asAgentMessages([newProblemDismissedMarker("call_p1")])); assert.equal(out.length, 0); }); it("the synthesized pair passes through natively", () => { const out = toLlmMessages(asAgentMessages(concludedByDismissalPair("call_p1"))); assert.equal(out.length, 2); assert.equal(out[0]!.role, "assistant"); assert.equal(out[1]!.role, "toolResult"); }); });