Files
learnharness/tests/stream-activity.test.ts
T
amosblomqvist f3003d357a 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).
2026-09-26 01:52:47 +03:00

60 lines
2.9 KiB
TypeScript

/**
* streamingToolActivity (2026-09-19, the "Writing a question…" fix): a
* tool part stays input-streaming for its WHOLE execution (the bridge
* defers input-available to tool_execution_end), so the transcript
* activity line must name the ACTUAL running tool — the old code shimmered
* "Writing a question…" over minutes-long greps.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import type { LhViewMessage } from "../src/core/view.ts";
import { streamingToolActivity } from "../src/core/pending.ts";
function msg(role: string, parts: unknown[]): LhViewMessage {
return { id: "m1", role, parts } as unknown as LhViewMessage;
}
function toolPart(tool: string, state: string) {
return { type: `tool-${tool}`, toolCallId: "c1", state, input: {} };
}
describe("streamingToolActivity (the turn-activity label)", () => {
it("question tools keep their question shimmer", () => {
assert.equal(streamingToolActivity([msg("assistant", [toolPart("quiz", "input-streaming")])]), "Writing a question…");
assert.equal(
streamingToolActivity([msg("assistant", [toolPart("ask_user_question", "input-streaming")])]),
"Writing a question…",
);
});
it("local tools name their actual activity", () => {
assert.equal(streamingToolActivity([msg("assistant", [toolPart("grep", "input-streaming")])]), "Searching file contents…");
assert.equal(streamingToolActivity([msg("assistant", [toolPart("read", "input-streaming")])]), "Reading a file…");
assert.equal(streamingToolActivity([msg("assistant", [toolPart("find", "input-streaming")])]), "Finding files…");
assert.equal(streamingToolActivity([msg("assistant", [toolPart("ls", "input-streaming")])]), "Listing a directory…");
});
it("job dispatch tools get their own labels; unknown tools stay generic", () => {
assert.equal(streamingToolActivity([msg("assistant", [toolPart("pdf", "input-streaming")])]), "Reading a PDF…");
assert.equal(
streamingToolActivity([msg("assistant", [toolPart("mystery_tool", "input-streaming")])]),
"Working…",
);
});
it("no activity once the call settles or the turn ends", () => {
// output-available = done executing → no shimmer (the transcript
// shows the settled card instead).
assert.equal(streamingToolActivity([msg("assistant", [toolPart("grep", "output-available")])]), null);
// the last message being the user's = nothing running.
assert.equal(streamingToolActivity([msg("user", [{ type: "text", text: "hi" }])]), null);
assert.equal(streamingToolActivity([]), null);
});
it("the SECOND streaming part wins only if the first settles — parts scan forward", () => {
// A settled call followed by a live one: the live one is the activity.
const parts = [toolPart("read", "output-available"), toolPart("grep", "input-streaming")];
assert.equal(streamingToolActivity([msg("assistant", parts)]), "Searching file contents…");
});
});