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
+16 -10
View File
@@ -26,7 +26,7 @@ import { createFauxCore, fauxAssistantMessage, fauxToolCall, type FauxResponseSt
import type { PiEngine } from "../src/agent/models.ts";
import type { LhMessage } from "../src/core/native-messages.ts";
import { newPlanBlockMessage } from "../src/core/native-messages.ts";
import { projectNative } from "../src/core/projection.ts";
import { projectView as projectNative } from "../src/core/view.ts";
import type {
AuditResult,
CoverageEntry,
@@ -42,7 +42,7 @@ import type {
import type { GraphStore } from "../src/core/ports.ts";
import { abortExtraction, createExtractionPipeline, extractionStatuses, transcriptView } from "../src/agent/subagents/extraction.ts";
import { ModelsNotConfiguredError, ProviderKeyMissingError, providerKeyMissing, type ModelSlotName } from "../src/core/settings.ts";
import { batchTag, coverageOf, coverageSlots } from "../src/core/coverage.ts";
import { batchTag, coverageOf, coverageSlots, messageViewLines } from "../src/core/coverage.ts";
import { chatStore } from "../src/store/chats.ts";
import { PATHS } from "../config.ts";
@@ -399,7 +399,7 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
// a new event (vision-extraction-coverage.md).
const events = ledgers.readEvents();
const record = chatStore.getChat(chatId)!;
const expectedTag = batchTag(coverageSlots(projectNative(record.messages)));
const expectedTag = batchTag(coverageSlots(record.messages));
assert.deepEqual(
events.map((e) => [e.kind, e.ref, e.nodeId]),
[
@@ -415,12 +415,18 @@ describe("the extraction pipeline (extractor → auditor → merger)", () => {
// one entry per transcript message, fingerprinted content each —
// the same turns are never extracted twice.
const covered = ledgers.readCoverage();
assert.equal(covered.length, projectNative(record.messages).length);
// Wave D: the coverage unit is the VISIBLE native message — bonded
// riders with no view lines (a textless call message) are context,
// never checkpointed.
const visible = record.messages.filter(
(m) => messageViewLines(m, m.id).length > 0,
).length;
assert.equal(covered.length, visible);
assert.ok(covered.every((c) => c.chatId === chatId && c.fp.length === 64 && c.tokens > 0));
assert.equal(ledgers.readCheckpoints().length, 0, "the chat path writes coverage, not the legacy checkpoint");
assert.equal(
coverageOf(projectNative(record.messages), covered, chatId).uncoveredCount,
coverageOf(record.messages, covered, chatId).uncoveredCount,
0,
"every message is now covered",
);
@@ -1020,9 +1026,9 @@ describe("the chunked pipeline (extraction coverage)", () => {
// Per-batch coverage: every message checkpointed, nothing legacy.
const coverage = ledgers.readCoverage();
assert.equal(coverage.length, projectNative(record.messages).length);
assert.equal(coverage.length, record.messages.length);
assert.equal(ledgers.readCheckpoints().length, 0);
assert.equal(coverageOf(projectNative(record.messages), coverage, chatId).uncoveredCount, 0);
assert.equal(coverageOf(record.messages, coverage, chatId).uncoveredCount, 0);
// A second run is a NO-OP: nothing uncovered → no model calls, no
// new usage entry (no money spent on covered ground).
@@ -1075,7 +1081,7 @@ describe("the chunked pipeline (extraction coverage)", () => {
// Batch 1's emissions are kept and its messages stay covered.
assert.deepEqual(stats, { nodes: 1, edges: 0, events: 1 });
assert.equal(ledgers.readCoverage().length, 3, "batch 1 checkpointed its three messages");
const cov = coverageOf(projectNative(record.messages), ledgers.readCoverage(), chatId);
const cov = coverageOf(record.messages, ledgers.readCoverage(), chatId);
assert.equal(cov.uncoveredCount, 2, "the in-flight batch's messages stay uncovered");
// The re-run — the manual extract button — picks up ONLY the
@@ -1109,9 +1115,9 @@ describe("the chunked pipeline (extraction coverage)", () => {
auditor: fauxEngine([passThroughAuditStep()]),
});
await pipeline.runExtraction(chatId);
// Wave B: the record is native — coverage reads the projection.
// Wave D: the record IS native history — coverage reads it directly.
const record = () => chatStore.getChat(chatId)!.messages;
const messages = () => projectNative(record());
const messages = () => record();
assert.equal(coverageOf(messages(), ledgers.readCoverage(), chatId).uncoveredCount, 0);
// Data parts are invisible to the extraction view — a plan-block