Files
learnharness/src/shared/protocol.ts
T
Amos 296cc1b7b6 v2 web interface: fix delivery races, real URLs, optimistic echo, thinking stream, button-based quiz UI
Root causes of the v1 browser bugs (all fixed + covered by deterministic tests):
- duplicated messages: per-socket snapshot + global broadcast marking raced;
  replaced with a single append-only entry log (exactly-once per socket,
  structurally) — covered by tests/server.test.ts
- delayed/missing echo of own messages: optimistic append with sending/failed
  status, reconciled by text when the real entry arrives
- no thinking shown: thinking deltas streamed live (kind: thinking) and
  persisted in assistant entries so they survive reloads
- ask answers graded as wrong: AnsweredCard no longer applies quiz grading to
  ask answers (no ✓/✗, no verdict)
- broken option selection: native radio/checkbox inputs fighting React state
  replaced with button-based options (single source of truth, one-click select)
- no URLs: History API routing — / home, /s/<file> per session (deep links,
  refresh, back button); session view resumes its file on mount
- lost connection = dead UI: exponential-backoff reconnect with full resync
- stick-to-bottom transcript with jump-to-latest pill

Server: startServer() with injectable runtime factory (scripted model in
tests); createSession returns file for routing; scripted model gained thinking
turns. 51 deterministic tests green.
2026-08-22 00:57:16 +03:00

66 lines
2.3 KiB
TypeScript

/**
* 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; thinking?: 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;
file: 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"; kind: "thinking" | "text"; 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" };