The owner's zero-configuration ruling (2026-09-23): nothing configures the graph beyond chat models. The prior-knowledge injection — its budget dial, its runner, its marker part, its dock/dispatch entries — is removed across all bands; the prober now reads the learner's persistent knowledge map on demand through two gated tools instead: - list_sessions: every birth session, names + kinds + counts + dates (graphSessionIndex, Platform's core seam) - list_propositions(session): what was born there + the session's own latest understanding level + that evaluation's date (graphSessionPropositions) — names/levels/dating ONLY, no statement text (owner ruling: the map's organization is the prober's business, its prose is not) Registered via createProberTools when chat-handler passes graphStore (the graphToolsEnabled gate decided server-side, per-turn fresh); the prober doctrine gains the map-review nudge at session start. Also: runner's JobKind special-casing collapses (KIND_ROLE/KIND_RUNNERS are plain records again), semantic-clusters.ts dies with its test, the embedder dep's doc tells the wave-7 stub story, and ports.ts's readClusterLabels/readNamingRuns surface stays untouched for Platform's ledger-port interlude. 848/848 tests, typecheck/lint/build green.
74 lines
3.1 KiB
TypeScript
74 lines
3.1 KiB
TypeScript
/**
|
|
* mergeAdopted (core/ui-message.ts) — the append-only record-adoption seam
|
|
* (owner ground rules, 2026-09-15): server-added steer messages must become
|
|
* visible mid-turn without the full snapshot swap that breaks the SDK's
|
|
* continuation merge. Pure, no React.
|
|
*/
|
|
|
|
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { mergeAdopted, type TeacherUIMessage } from "../src/core/index.ts";
|
|
|
|
function userText(id: string, text: string): TeacherUIMessage {
|
|
return { id, role: "user", parts: [{ type: "text", text }] };
|
|
}
|
|
function steer(id: string): TeacherUIMessage {
|
|
// Wave 7 killed the graph-context injection; its marker part is gone.
|
|
// The steer-shaped user message here exercises mergeAdopted's
|
|
// non-text user parts path with the handoff marker instead.
|
|
return { id, role: "user", parts: [{ type: "data-probe-handoff", data: { chatId: "c" } }] };
|
|
}
|
|
function assistant(id: string): TeacherUIMessage {
|
|
return { id, role: "assistant", parts: [{ type: "text", text: "reply" }] };
|
|
}
|
|
|
|
describe("mergeAdopted — append-only adoption", () => {
|
|
it("inserts a server-added steer between the persisted core and the in-flight tail", () => {
|
|
const local = [userText("u1", "topic"), userText("u2", "answer"), assistant("a_inflight")];
|
|
const record = [userText("u1", "topic"), userText("u2", "answer"), steer("steer_ctx_c")];
|
|
const merged = mergeAdopted(local, record);
|
|
assert.ok(merged);
|
|
assert.deepEqual(
|
|
merged.map((m) => m.id),
|
|
["u1", "u2", "steer_ctx_c", "a_inflight"],
|
|
"marker between the answer and the streaming bubble",
|
|
);
|
|
});
|
|
|
|
it("adopts into an idle client (no in-flight tail)", () => {
|
|
const local = [userText("u1", "topic")];
|
|
const record = [userText("u1", "topic"), steer("s1"), assistant("a1")];
|
|
const merged = mergeAdopted(local, record);
|
|
assert.deepEqual(
|
|
merged?.map((m) => m.id),
|
|
["u1", "s1", "a1"],
|
|
);
|
|
});
|
|
|
|
it("nothing new → null (no needless re-render)", () => {
|
|
const local = [userText("u1", "topic")];
|
|
assert.equal(mergeAdopted(local, [userText("u1", "topic")]), null);
|
|
// The tail alone is not 'new' — nothing to adopt.
|
|
assert.equal(mergeAdopted([assistant("a_x")], []), null);
|
|
});
|
|
|
|
it("a missing core id (record rewrite) → null: defer to the full sync at idle", () => {
|
|
// A local message vanished from the record — mid-turn this means a
|
|
// rewrite, and the append-only shortcut must not paper over it.
|
|
const local = [userText("u1", "a"), userText("u2", "b"), assistant("tail")];
|
|
const record = [userText("u2", "b"), steer("s1")];
|
|
assert.equal(mergeAdopted(local, record), null);
|
|
});
|
|
|
|
it("only the LAST local message may be the absent in-flight tail", () => {
|
|
// Two absent local messages: not a simple append — bail.
|
|
const local = [userText("u1", "a"), userText("u2", "gone"), assistant("tail")];
|
|
const record = [userText("u1", "a"), steer("s1")];
|
|
assert.equal(mergeAdopted(local, record), null);
|
|
});
|
|
|
|
it("an empty local list adopts the whole record", () => {
|
|
const record = [steer("s1"), assistant("a1")];
|
|
assert.deepEqual(mergeAdopted([], record)?.map((m) => m.id), ["s1", "a1"]);
|
|
});
|
|
}); |