session kind: ChatRecord.kind teach|practice (absent = teach)

Practice sessions (owner, 2026-10-04): the marker rides the record and
the normalized summary (absent field reads as teach — pre-practice
records never migrate); POST /api/chats accepts {kind: 'practice'}.
The agent branch, tools, and UI land in the following slices.
This commit is contained in:
amosblomqvist
2026-10-04 21:36:14 +03:00
parent 814c2ad412
commit 7dcb006cf3
6 changed files with 71 additions and 4 deletions
+6 -1
View File
@@ -31,7 +31,7 @@ function chatFile(id: string): string {
* phase, busy, pendingQuestion) is already announced by turn-started /
* turn-settled, which the sidebar refetches on. Silence here is law,
* not oversight. */
function createChat(title: string): ChatRecord {
function createChat(title: string, kind?: "practice"): ChatRecord {
const now = new Date().toISOString();
const record: ChatRecord = {
id: `chat_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`,
@@ -39,6 +39,9 @@ function createChat(title: string): ChatRecord {
createdAt: now,
updatedAt: now,
messages: [],
// Practice sessions carry the marker; teach stays ABSENT (absence
// reads as teach — pre-practice records never migrate).
...(kind === "practice" ? { kind: "practice" as const } : {}),
};
fs.mkdirSync(PATHS.chats, { recursive: true });
fs.writeFileSync(chatFile(record.id), JSON.stringify(record, null, "\t"), "utf-8");
@@ -73,6 +76,8 @@ function listChats(): ChatSummary[] {
// summaries; usage aggregates ride the full record, the picker
// never needs them).
...summary,
// Normalized kind: absent record field reads as teach.
kind: summary.kind ?? "teach",
messageCount: messages.length,
// Record-derived status for the session picker: phase from the
// transcript's explicit events (probe_done concluded / verdict