The teaching agent no longer runs on pi. Stack: - ToolLoopAgent (ai@7) + OpenRouter (OPENROUTER_API_KEY), model via LH_MODEL, default anthropic/claude-sonnet-4.5 with reasoning tokens - quiz/ask_user_question = client-side tools (no server execute): the call lands as a typed tool part in input-available state; the card grades via src/core/question-core.ts; addToolOutput sends the verdict; the teacher's reaction continues the same assistant message - resume-mid-quiz is free: the pending tool part persists in the transcript - persistence: one JSON per chat (data/chats/), full UIMessage transcripts, saved onFinish (also on abort) - UI: AI Elements (owned copy-paste components, pruned to the five in use) + button-based QuizCard/AnsweredCard (ask answers never graded) - pi fully removed: TUI, extensions, Fastify+WS server, bridge, custom protocol all deleted (recoverable from git history) - tests: question-core units carry over; chat-flow integration drives handleChatRequest with MockLanguageModelV4 (48 tests, zero tokens); render smoke tests guard the cards (renderToStaticMarkup) Carried over unchanged: teacher system prompt, question-core, schema/ ledger/derive/extract pipeline, data model.
70 lines
2.2 KiB
JavaScript
70 lines
2.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* learning-harness CLI
|
|
*
|
|
* ingest <file> normalize a session JSONL and print the canonical document
|
|
* (Stage 1 only — extractor/merger not yet implemented)
|
|
* graph print graph/ledger stats
|
|
* what-next (not yet implemented)
|
|
*
|
|
* The web app (Next.js + AI SDK): npm run dev
|
|
*/
|
|
|
|
import * as fs from "node:fs";
|
|
import { normalizeSession } from "./extract/normalize.ts";
|
|
import { ledgers } from "./ledger.ts";
|
|
import { deriveAll } from "./derive.ts";
|
|
import { PATHS } from "../config.ts";
|
|
|
|
async function main() {
|
|
const [command, ...rest] = process.argv.slice(2);
|
|
|
|
switch (command) {
|
|
case "ingest": {
|
|
const file = rest[0];
|
|
if (!file) {
|
|
console.error("Usage: ingest <session.jsonl>");
|
|
process.exit(1);
|
|
}
|
|
const doc = normalizeSession(file);
|
|
ledgers.appendSession(doc.session);
|
|
console.log(`Session: ${doc.session.id}`);
|
|
console.log(`Topic: ${doc.session.topic}`);
|
|
console.log(`Entries: ${doc.entries.length}`);
|
|
const quizzes = doc.entries.filter((e) => e.kind === "quiz");
|
|
console.log(`Quizzes: ${quizzes.length}`);
|
|
// Full canonical document for inspection / later pipeline stages.
|
|
fs.mkdirSync(PATHS.graph, { recursive: true });
|
|
const out = `${PATHS.graph}/canonical-${doc.session.id.slice(0, 8)}.json`;
|
|
fs.writeFileSync(out, JSON.stringify(doc, null, "\t"), "utf-8");
|
|
console.log(`Canonical document written: ${out}`);
|
|
break;
|
|
}
|
|
case "graph": {
|
|
const nodes = ledgers.nodes();
|
|
const edges = ledgers.edges();
|
|
const events = ledgers.events();
|
|
const sessions = ledgers.sessions();
|
|
const derived = deriveAll();
|
|
console.log(`Sessions: ${sessions.length}`);
|
|
console.log(`Nodes: ${nodes.length} (known: ${derived.filter((d) => d.known).length})`);
|
|
console.log(`Edges: ${edges.length}`);
|
|
console.log(`Events: ${events.length}`);
|
|
break;
|
|
}
|
|
case "what-next": {
|
|
console.error("Not implemented yet — needs extractor + merger first.");
|
|
process.exit(1);
|
|
}
|
|
default: {
|
|
console.log("Commands: ingest <file> | graph | what-next");
|
|
process.exit(command ? 1 : 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|