Wave 0: shared contracts + extension points

- src/core/contracts.ts: plan tree / graph / ledger / job protocol / artifact
  registry types — the single source of truth for all workstreams
- tools: per-request factory createTeacherTools({chatId}) composing
  questionTools + planTools (stub) + jobTools (stub); quiz gains nodeId
- prompt composition: system-prompt.md + sorted src/agent/prompts/*.md
- ChatRecord.tree + saveTree; pending gate scoped to quiz/ask (+findPendingJobCall)
- chat.tsx: artifact renderer extension point + ChatActionsContext.syncFromServer
This commit is contained in:
Amos
2026-08-22 09:06:24 +03:00
parent c338f468f8
commit c55bae0de6
11 changed files with 429 additions and 23 deletions
+13
View File
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
import * as path from "node:path";
import type { UIMessage } from "ai";
import { PATHS } from "../../config.ts";
import type { PlanTree } from "../core/contracts.ts";
/**
* Chat store — one JSON file per chat in data/chats/.
@@ -19,6 +20,8 @@ export interface ChatRecord {
createdAt: string;
updatedAt: string;
messages: UIMessage[];
/** The session's plan tree (vision §2) — written by plan tools via saveTree. */
tree?: PlanTree;
}
export interface ChatSummary {
@@ -80,6 +83,16 @@ export function saveMessages(id: string, messages: UIMessage[]): void {
fs.writeFileSync(chatFile(id), JSON.stringify(record, null, "\t"), "utf-8");
}
/** Persist the session plan tree (plan tools call this in execute). */
export function saveTree(id: string, tree: PlanTree): void {
const record = getChat(id);
if (!record) throw new Error(`Chat not found: ${id}`);
record.tree = tree;
// Deliberately does NOT bump updatedAt: tree mutations piggyback on the
// turn's saveMessages, and sidebar ordering shouldn't churn on tree edits.
fs.writeFileSync(chatFile(id), JSON.stringify(record, null, "\t"), "utf-8");
}
/** Derive a short title from the first user message. */
export function titleFromPrompt(prompt: string): string {
const oneLine = prompt.replace(/\s+/g, " ").trim();