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:
@@ -1,5 +1,4 @@
|
||||
import * as crypto from "node:crypto";
|
||||
import type { UIMessage } from "ai";
|
||||
import type { Usage } from "@earendil-works/pi-ai";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { Type, type Static, type TSchema } from "typebox";
|
||||
@@ -28,7 +27,6 @@ import { getGraph, mergeFindings, type EmissionStats, type GraphDeps } from "../
|
||||
import type { GraphStore } from "../../core/ports.ts";
|
||||
import { settleGraphLayout } from "../../core/layout-settle.ts";
|
||||
import { batchRuns, batchTag, coverageOf, messageViewLines } from "../../core/coverage.ts";
|
||||
import { projectNative } from "../../core/projection.ts";
|
||||
|
||||
/**
|
||||
* The extraction pipeline (vision §1 construction, 2026-09-02 redesign) — the
|
||||
@@ -93,12 +91,11 @@ import { projectNative } from "../../core/projection.ts";
|
||||
export function transcriptView(record: ChatRecord): string {
|
||||
// The shared seam (core/coverage.ts messageViewLines): the LLM's prompt
|
||||
// view and the coverage fingerprint use ONE line builder, so "covered"
|
||||
// means — exactly — what the extractor saw. Wave B: the record is native
|
||||
// history — the extraction view reads through the one-way projection.
|
||||
// means — exactly — what the extractor saw. Wave D: the record is native
|
||||
// history — the view reads the native messages directly.
|
||||
const lines = [`SESSION: ${record.title}`];
|
||||
const projected = projectNative(record.messages);
|
||||
projected.forEach((message, index) => {
|
||||
lines.push(...messageViewLines(message, message.id || `m${index}`));
|
||||
record.messages.forEach((message, index) => {
|
||||
lines.push(...messageViewLines(message, (message as { id?: string }).id || `m${index}`));
|
||||
});
|
||||
return lines.join("\n");
|
||||
}
|
||||
@@ -871,14 +868,16 @@ export function createExtractionPipeline(deps: ExtractionPipelineDeps): Extracti
|
||||
// engines AND their providers' keys; a refusal lands BEFORE any
|
||||
// stage runs (typed — the trigger skips, routes surface 503).
|
||||
refuseIfStagesUnready();
|
||||
const projected = projectNative(record.messages);
|
||||
const covered = coverageOf(projected, graphStore.readCoverage(), chatId);
|
||||
const covered = coverageOf(record.messages, graphStore.readCoverage(), chatId);
|
||||
if (covered.uncovered.length === 0) {
|
||||
console.info(`[extraction ${chatId}] nothing uncovered — no-op`);
|
||||
return { nodes: 0, edges: 0, events: 0 };
|
||||
}
|
||||
const chunkTokens = deps.getChunkTokens?.() ?? FACTORY_SETTINGS.graph.extractionChunkTokens;
|
||||
const batches = batchRuns(covered.uncovered, chunkTokens);
|
||||
// The batcher's one rule (owner, Wave D): a tool-call message and
|
||||
// its result message(s) are INSEPARABLE — one chunk, covered
|
||||
// together, read together (core/coverage.ts batchRuns).
|
||||
const batches = batchRuns(covered.slots, covered.uncovered, chunkTokens);
|
||||
console.info(
|
||||
`[extraction ${chatId}] pipeline start — ${covered.uncoveredTokens} uncovered tok, ` +
|
||||
`${covered.uncoveredCount} messages, ${batches.length} batch(es) of ≤${chunkTokens} tok`,
|
||||
@@ -887,13 +886,9 @@ export function createExtractionPipeline(deps: ExtractionPipelineDeps): Extracti
|
||||
// The batch's prompt view: SESSION header + the batch's lines,
|
||||
// same provenance [id] tags the fingerprints used (coverage
|
||||
// and prompt share ONE line builder — core/coverage.ts).
|
||||
const mids = projected.map((m, i) => (m.id || `m${i}`) as string);
|
||||
const view = [
|
||||
`SESSION: ${record.title}`,
|
||||
...batch.flatMap((slot) => {
|
||||
const idx = projected.indexOf(slot.message);
|
||||
return messageViewLines(slot.message, mids[idx] ?? slot.message.id);
|
||||
}),
|
||||
...batch.flatMap((slot) => messageViewLines(slot.message, slot.message.id)),
|
||||
].join("\n");
|
||||
setPhase(chatId, "extracting", "chat");
|
||||
const findings = await extract(chatId, view);
|
||||
@@ -918,7 +913,9 @@ export function createExtractionPipeline(deps: ExtractionPipelineDeps): Extracti
|
||||
const stats = mergeFindings(chatId, auditResult, { chatStore, graphStore }, {
|
||||
messages: batch.map((slot) => slot.message),
|
||||
foldedAt,
|
||||
coverage: batch.map((slot) => ({ chatId, fp: slot.fp, tokens: slot.tokens, foldedAt })),
|
||||
coverage: batch
|
||||
.filter((slot) => messageViewLines(slot.message, slot.message.id).length > 0)
|
||||
.map((slot) => ({ chatId, fp: slot.fp, tokens: slot.tokens, foldedAt })),
|
||||
batchTag: batchTag(batch),
|
||||
});
|
||||
total.nodes += stats.nodes;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { isToolUIPart } from "ai";
|
||||
import type { PiEngine } from "../models.ts";
|
||||
import type { SearchProvider } from "../../core/search.ts";
|
||||
import {
|
||||
@@ -23,8 +22,6 @@ import { registerJobAbort, unregisterJobAbort } from "./aborts.ts";
|
||||
import type { ModelRole } from "../../../config.ts";
|
||||
import { ROLE_SLOT, modelsNotConfigured, type ModelSlotName, type ProviderKeyMissing } from "../../core/settings.ts";
|
||||
import type { ChatStore, JobStore } from "../../core/ports.ts";
|
||||
import { projectNative } from "../../core/projection.ts";
|
||||
import type { UIMessage } from "ai";
|
||||
import { runDemoBuilder } from "./demo-builder.ts";
|
||||
import { runPdfReaderAgent } from "./pdf-reader.ts";
|
||||
import { runResearcherAgent } from "./researcher.ts";
|
||||
@@ -276,22 +273,18 @@ export function createJobRunner(deps: JobRunnerDeps): JobRunner {
|
||||
if (input) return input;
|
||||
const chat = chatStore.getChat(job.chatId);
|
||||
if (!chat) return undefined;
|
||||
// Wave B: the record is native history — the transcript scan reads
|
||||
// through the projection (same shape the old record held).
|
||||
return (findToolPart(projectNative(chat.messages), job.toolCallId)?.input ?? undefined) as
|
||||
| JobToolInput
|
||||
| undefined;
|
||||
}
|
||||
|
||||
// ─── shared plumbing ────────────────────────────────────────────────────
|
||||
|
||||
function findToolPart(messages: UIMessage[], toolCallId: string) {
|
||||
for (const m of messages) {
|
||||
for (const part of m.parts) {
|
||||
if (isToolUIPart(part) && part.toolCallId === toolCallId) return part;
|
||||
// Wave D: the record is native history — the scan reads the native
|
||||
// assistant blocks directly (a toolCall's arguments are the input).
|
||||
for (const m of chat.messages) {
|
||||
if ((m as { role?: string }).role !== "assistant") continue;
|
||||
const content = (m as { content?: unknown }).content;
|
||||
if (!Array.isArray(content)) continue;
|
||||
for (const block of content as Array<{ type?: string; id?: string; arguments?: unknown }>) {
|
||||
if (block.type !== "toolCall" || block.id !== job.toolCallId) continue;
|
||||
return block.arguments as JobToolInput | undefined;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user