Wave D: native AgentEvent wire + client session rewrite (SDK purged)

Server: ui-stream.ts is verbatim pi AgentEvent passthrough (SSE data
lines + [DONE]); only app-level events are the loop-level error and
turn-settled terminal; onError carries raw provider text (owner ruling).

Client: useChat + the AI-SDK transport replaced by components/session-core.ts
(SessionModel: 409 identity-retry, optimistic c_ appends, absorbed()
adoption, needsContinuation, shared consume for POST + reattach) and the
session.ts hook; the golden-master grouping canon moved client-side into
core/view.ts (projectView); scanners read the native record (phaseAt3Lh,
danglingQuestionOf, graph toolParts, plan-blocks, runner, extraction).

Coverage: the native message is the unit (fork 3) with the call+result
inseparability bond rule; view-line-less messages (markers, error
results) are invisible to coverage and never checkpointed.

Deletes: chat-transport, projection, ui-message, their tests (ported to
tests/session-core.test.ts), and the ai + @ai-sdk/react dependencies —
the Vercel SDK is fully out of the tree. Wire pins in chat-flow rewritten
onto the AgentEvent dialect (eventsOf parser).

Gates: tsc clean, 994/994 tests, eslint clean, build green; live smoke
on PORT=3004 (verbatim wire → native record).
This commit is contained in:
amosblomqvist
2026-09-26 01:52:47 +03:00
parent 7c0e8d9a50
commit f3003d357a
44 changed files with 2299 additions and 2205 deletions
+10 -11
View File
@@ -13,9 +13,8 @@ import { PATHS } from "../../config.ts";
import type { ChatStore } from "../core/ports.ts";
import type { ChatRecord, ChatSettings, ChatSummary, ExtractionRunUsage, PlanTree, TurnUsage } from "../core/contracts.ts";
import type { LhMessage } from "../core/native-messages.ts";
import { findPendingToolCall } from "../core/pending.ts";
import { phaseAt3 } from "../core/phases.ts";
import { projectNative } from "../core/projection.ts";
import { danglingQuestionOf } from "../core/native-messages.ts";
import { phaseAt3Lh } from "../core/phases.ts";
function chatFile(id: string): string {
if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new Error(`Invalid chat id: ${id}`);
@@ -58,7 +57,6 @@ function listChats(): ChatSummary[] {
// zero-back-compat wipe makes these "corrupt") must not blank the
// whole sidebar — same isolation getChat's per-file catch gives.
try {
const projected = projectNative(messages);
return {
// turns and extractions stay out of the list payload (list =
// summaries; usage aggregates ride the full record, the picker
@@ -66,13 +64,14 @@ function listChats(): ChatSummary[] {
...summary,
messageCount: messages.length,
// Record-derived status for the session picker: phase from the
// transcript's explicit markers (probe_done / plan_init — the
// event-driven phases, docs/probe-teach-split.md §4), pending
// question from the last unanswered quiz/ask part — both read
// through the one-way projection. Liveness (busy) is NOT
// record-derived — the API route merges it (ChatListItem).
phase: phaseAt3(projected),
pendingQuestion: findPendingToolCall(projected) !== null,
// transcript's explicit events (probe_done concluded / verdict
// accepted — docs/probe-teach-split.md §4), pending question
// from the native dangling-call scan. Wave D: both folds read
// the NATIVE record (core/phases.ts phaseAt3Lh +
// native-messages.ts danglingQuestionOf). Liveness (busy) is
// NOT record-derived — the API route merges it (ChatListItem).
phase: phaseAt3Lh(messages),
pendingQuestion: danglingQuestionOf(messages) !== null,
};
} catch (err) {
console.error(`[chat ${summary.id}] summary projection failed:`, err);