v1 web interface: dispatch-mode quiz/ask, Fastify+WS server, React frontend, deterministic test suite
- extensions/core/question-core.ts: pure quiz/ask logic (normalize/shuffle/resolve/grade/format) shared by both interfaces - extensions/core/bridge.ts: in-process server<->extension bridge (answer/dismiss/re-arm) - extensions/quiz-web.ts + ask-web.ts: web dispatch mode — tool returns immediately, graded answer arrives as next user message; sessions resumable mid-question via lh_question/lh_question_answered custom entries (never in LLM context) - extensions/quiz.ts: refactored onto shared core (terminal blocking mode unchanged) - src/server/index.ts: Fastify + WebSocket; REST (list/create/resume), entry sync (diff-based, all message kinds), streaming deltas, pending-question push - web/: Vite + React — home view (graph placeholder, session list, new-session input), session view (markdown+KaTeX+mermaid transcript, quiz cards, streaming) - src/testing/scripted-model.ts: deterministic fake LLM (scripted model runtime injected via factory overrides) - tests/: 48 deterministic tests (unit: question-core + bridge; integration: full pi loop with scripted model incl. resume re-arm) - scripts/e2e.ts: live smoke test (manual, real LLM, never a gate) - teacher/system-prompt.md: end-your-turn rule for question tools
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* protocol — shared types between the web server and the browser client.
|
||||
* One source of truth for the WS protocol and REST DTOs.
|
||||
*/
|
||||
|
||||
import type { ClientQuestion, QuestionAnswerData } from "../../extensions/core/question-core.ts";
|
||||
|
||||
export type { ClientQuestion, QuestionAnswerData } from "../../extensions/core/question-core.ts";
|
||||
|
||||
// ─── entries (session → client) ─────────────────────────────────────────────
|
||||
|
||||
export interface ClientToolCall {
|
||||
name: string;
|
||||
args: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type ClientEntry =
|
||||
| { type: "user"; id: string; text: string }
|
||||
| { type: "assistant"; id: string; text: string; toolCalls: ClientToolCall[] }
|
||||
| { type: "toolResult"; id: string; toolName: string; text: string }
|
||||
| { type: "question"; id: string; data: ClientQuestion }
|
||||
| { type: "questionAnswered"; id: string; data: QuestionAnswerData }
|
||||
| { type: "meta"; id: string; kind: string };
|
||||
|
||||
// ─── REST DTOs ──────────────────────────────────────────────────────────────
|
||||
|
||||
export interface SessionSummary {
|
||||
id: string;
|
||||
file: string;
|
||||
name?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messageCount: number;
|
||||
preview?: string;
|
||||
}
|
||||
|
||||
export interface NewSessionResponse {
|
||||
id: string;
|
||||
}
|
||||
|
||||
// ─── WS protocol ────────────────────────────────────────────────────────────
|
||||
|
||||
export type ServerMessage =
|
||||
| { type: "hello"; sessionId: string | null }
|
||||
| { type: "backfill"; entries: ClientEntry[] }
|
||||
| { type: "entry"; entry: ClientEntry }
|
||||
| { type: "pending"; question: ClientQuestion | null }
|
||||
| { type: "delta"; delta: string }
|
||||
| { type: "streaming"; value: boolean }
|
||||
| { type: "error"; message: string };
|
||||
|
||||
export type ClientMessage =
|
||||
| { type: "prompt"; text: string }
|
||||
| {
|
||||
type: "answer";
|
||||
questionId: string;
|
||||
dontKnow?: boolean;
|
||||
values?: string[];
|
||||
other?: string;
|
||||
text?: string;
|
||||
note?: string;
|
||||
}
|
||||
| { type: "dismiss"; questionId: string }
|
||||
| { type: "abort" };
|
||||
Reference in New Issue
Block a user