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
+5 -8
View File
@@ -21,7 +21,6 @@ import type { LhMessage } from "../core/native-messages.ts";
import { newMessageId } from "../core/native-messages.ts";
import { QUESTION_TOOLS } from "../core/pending.ts";
import { planBlocksFor } from "../core/plan-blocks.ts";
import { projectNative } from "../core/projection.ts";
import { coverageOf } from "../core/coverage.ts";
import type { ChatStore, GraphStore } from "../core/ports.ts";
import type { ExtractionPipeline } from "../agent/subagents/extraction.ts";
@@ -72,12 +71,10 @@ export function persistTurn(
stamped.push(message.id ? message : ({ ...message, id: newMessageId() } as LhMessage));
}
const tree = record.tree;
const blocks = planBlocksFor(
record.messages,
stamped,
tree,
projectNative([...record.messages, ...stamped]),
);
const blocks = planBlocksFor(record.messages, stamped, tree, [
...record.messages,
...stamped,
]);
chatStore.saveMessages(chatId, [...record.messages, ...stamped, ...blocks]);
return stamped;
} catch (err) {
@@ -110,7 +107,7 @@ export function triggerExtraction(
try {
const record = deps.chatStore.getChat(chatId);
if (!record) return;
const covered = coverageOf(projectNative(record.messages), deps.graphStore.readCoverage(), chatId);
const covered = coverageOf(record.messages, deps.graphStore.readCoverage(), chatId);
if (covered.uncoveredTokens < chunkTokens) return;
void extraction.runExtraction(chatId).catch((err) => {
// BYOK (owner, 2026-09-22) + key presence (provider-key interlude,