返回 DeepSeek-Reasonix
transcript-tool-association.test.ts
根目录 / desktop / frontend / src / __tests__ / transcript-tool-association.test.ts
1 import assert from "node:assert/strict";
2 import test from "node:test";
3 import { TranscriptStore } from "../lib/transcriptStore";
4 import type { Item } from "../lib/useController";
5 import { FakeBackend } from "./helpers/transcriptFakeBackend";
6
7 const toolsOf = (items: Item[]) => items.filter((item): item is Extract<Item, { kind: "tool" }> => item.kind === "tool");
8
9 test("a persisted background-output read does not restart when its job snapshot was running", () => {
10 const store = new TranscriptStore(new FakeBackend([]));
11 const projection = store.installSlice("poll", "session-id:poll", {
12 entries: [
13 { entryId: "m:call", turn: 1, order: 0, message: { role: "assistant", content: "", toolCalls: [{ id: "poll", name: "job_output", arguments: "{}", resultObservation: { state: "completed", messageId: "result" } }] }, refs: [] },
14 { entryId: "m:result", turn: 1, order: 1, message: { role: "tool", toolCallId: "poll", toolName: "job_output", messageId: "result", content: "[status: running]", execution: { state: "running" } }, refs: [] },
15 ], nextCursor: "", newerCursor: "", hasOlder: false, hasNewer: false, totalTurns: 1, startTurn: 1, endTurn: 1,
16 revision: 1, revisionKnown: true, digest: "poll", stale: false,
17 });
18 assert.equal(toolsOf(projection.items)[0].status, "done");
19 });
20
21 test("formal message aliases retain one canonical result", () => {
22 const store = new TranscriptStore(new FakeBackend([]));
23 const projection = store.installSlice("formal-representation-alias", "/formal-representation-alias", {
24 entries: [
25 { entryId: "m:owner", turn: 1, order: 0, message: { role: "assistant", messageId: "owner", content: "", toolCalls: [{
26 id: "call", name: "bash", arguments: "echo ok", resultObservation: { messageId: "result", state: "completed" },
27 }] }, refs: [] },
28 { entryId: "m:result", turn: 1, order: 1, message: { role: "tool", messageId: "result", toolCallId: "call", content: "ok" }, refs: [] },
29 { entryId: "snapshot:result", turn: 1, order: 2, message: { role: "tool", messageId: "result", toolCallId: "call", content: "stale duplicate" }, refs: [] },
30 ], nextCursor: "", newerCursor: "", hasOlder: false, hasNewer: false, totalTurns: 1, startTurn: 1, endTurn: 1,
31 revision: 1, revisionKnown: true, digest: "formal-alias", stale: false,
32 });
33 const tools = toolsOf(projection.items);
34 assert.equal(tools.length, 1);
35 assert.equal(tools[0].id, "call");
36 assert.equal(tools[0].output, "ok");
37 });
38
39 test("conflicting formal identities stay visible and independently readable", async () => {
40 const store = new TranscriptStore(new FakeBackend([]));
41 const projection = store.installSlice("identity-conflict", "/identity-conflict", {
42 entries: [
43 { entryId: "m:result-a", turn: 1, order: 0, message: { role: "tool", messageId: "result-a", toolCallId: "conflict", content: "a" }, refs: [] },
44 { entryId: "m:result-b", turn: 1, order: 1, message: { role: "tool", messageId: "result-b", toolCallId: "conflict", content: "b" }, refs: [] },
45 ], nextCursor: "", newerCursor: "", hasOlder: false, hasNewer: false, totalTurns: 1, startTurn: 1, endTurn: 1,
46 revision: 1, revisionKnown: true, digest: "conflict", stale: false,
47 });
48 const tools = toolsOf(projection.items);
49 assert.equal(store.residentWindowEntries(), 2);
50 assert.equal(tools.length, 2);
51 assert.equal(new Set(tools.map(item => item.id)).size, 2);
52 assert.ok(tools.every(item => item.identityConflict));
53 const details = await Promise.all(tools.map(item => store.requestToolContent("identity-conflict", item, { output: item.output })));
54 assert.deepEqual(details.map(detail => JSON.parse(detail!).output).sort(), ["a", "b"]);
55 });
56
57 test("an ambiguous call and both formal results remain distinct", () => {
58 const store = new TranscriptStore(new FakeBackend([]));
59 const projection = store.installSlice("call-conflict", "/call-conflict", {
60 entries: [
61 { entryId: "m:owner", turn: 1, order: 0, message: { role: "assistant", messageId: "owner", content: "", toolCalls: [{ id: "conflict", name: "bash", arguments: "echo ok" }] }, refs: [] },
62 { entryId: "m:result-a", turn: 1, order: 1, message: { role: "tool", messageId: "result-a", toolCallId: "conflict", content: "a" }, refs: [] },
63 { entryId: "m:result-b", turn: 1, order: 2, message: { role: "tool", messageId: "result-b", toolCallId: "conflict", content: "b" }, refs: [] },
64 ], nextCursor: "", newerCursor: "", hasOlder: false, hasNewer: false, totalTurns: 1, startTurn: 1, endTurn: 1,
65 revision: 1, revisionKnown: true, digest: "call-conflict", stale: false,
66 });
67 const tools = toolsOf(projection.items);
68 assert.equal(tools.length, 3);
69 assert.equal(new Set(tools.map(item => item.id)).size, 3);
70 assert.ok(tools.every(item => item.identityConflict));
71 });
72
73 test("a reused call id stays isolated by turn", () => {
74 const store = new TranscriptStore(new FakeBackend([]));
75 const projection = store.installSlice("reused-call", "/reused-call", {
76 entries: [
77 { entryId: "m:owner-1", turn: 1, order: 0, message: { role: "assistant", messageId: "owner-1", content: "", toolCalls: [{ id: "reused", name: "bash", arguments: "one" }] }, refs: [] },
78 { entryId: "m:result-1", turn: 1, order: 1, message: { role: "tool", messageId: "result-1", toolCallId: "reused", content: "first" }, refs: [] },
79 { entryId: "m:owner-2", turn: 2, order: 2, message: { role: "assistant", messageId: "owner-2", content: "", toolCalls: [{ id: "reused", name: "bash", arguments: "two" }] }, refs: [] },
80 { entryId: "m:result-2", turn: 2, order: 3, message: { role: "tool", messageId: "result-2", toolCallId: "reused", content: "second" }, refs: [] },
81 ], nextCursor: "", newerCursor: "", hasOlder: false, hasNewer: false, totalTurns: 2, startTurn: 1, endTurn: 2,
82 revision: 1, revisionKnown: true, digest: "reused-call", stale: false,
83 });
84 const tools = toolsOf(projection.items);
85 assert.equal(tools.length, 2);
86 assert.equal(new Set(tools.map(item => item.id)).size, 2);
87 assert.deepEqual(tools.map(item => [item.args, item.output]), [["one", "first"], ["two", "second"]]);
88 assert.ok(tools.every(item => !item.identityConflict));
89 });
90
91 test("paging a formal result replaces its resident event alias", async () => {
92 const backend = new FakeBackend([]);
93 backend.HistorySliceForTab = async () => ({
94 entries: [
95 { entryId: "m:result", turn: 1, order: 1, message: { role: "tool", messageId: "result", toolCallId: "page-call", toolName: "write_file", content: "full", execution: { state: "completed", durationMs: 99 } }, refs: [] },
96 { entryId: "m:final", turn: 1, order: 2, message: { role: "assistant", messageId: "final", content: "done", turnFinal: true }, refs: [] },
97 ], nextCursor: "", newerCursor: "", hasOlder: false, hasNewer: false, totalTurns: 1, startTurn: 1, endTurn: 1,
98 revision: 1, revisionKnown: true, digest: "page-alias", stale: false,
99 });
100 const store = new TranscriptStore(backend);
101 store.installSlice("page-alias", "/page-alias", {
102 entries: [
103 { entryId: "m:owner", turn: 1, order: 0, message: { role: "assistant", messageId: "owner", content: "", toolCalls: [{ id: "page-call", name: "write_file", arguments: '{"path":"page.html"}' }] }, refs: [] },
104 { entryId: "tool:page-call", turn: 1, order: 1, message: { role: "tool", toolCallId: "page-call", toolName: "write_file", content: "preview" }, refs: [] },
105 ], nextCursor: "", newerCursor: "next", hasOlder: false, hasNewer: true, totalTurns: 1, startTurn: 1, endTurn: 1,
106 revision: 1, revisionKnown: true, digest: "page-alias", stale: false,
107 });
108 const tools = toolsOf((await store.loadNewer("page-alias", "/page-alias"))!.items);
109 assert.equal(tools.length, 1);
110 assert.equal(tools[0].args, '{"path":"page.html"}');
111 assert.equal(tools[0].execution?.durationMs, 99);
112 });
113
114 test("a lazy formal result joins through its observation before body load", () => {
115 const store = new TranscriptStore(new FakeBackend([]));
116 const projection = store.installSlice("lazy-result-identity", "/lazy-result-identity", {
117 entries: [
118 { entryId: "m:owner", turn: 1, order: 0, message: { role: "assistant", messageId: "owner", content: "", toolCalls: [{
119 id: "lazy-result", name: "write_file", arguments: '{"path":"lazy.html"}',
120 resultObservation: { state: "completed", messageId: "result", version: 1, contentRef: { digest: "body", bytes: 8192 } },
121 }] }, refs: [] },
122 { entryId: "m:result", turn: 1, order: 1, message: { role: "tool", messageId: "result", content: "written" },
123 refs: [{ entryId: "m:result", field: "canonicalMessage", size: 8192, chunks: 1, revision: 1, digest: "body" }] },
124 { entryId: "m:final", turn: 1, order: 2, message: { role: "assistant", messageId: "final", content: "done", turnFinal: true }, refs: [] },
125 { entryId: "tool:lazy-result", turn: 1, order: 3, message: { role: "tool", toolCallId: "lazy-result", toolName: "write_file", content: "written" }, refs: [] },
126 ], nextCursor: "", newerCursor: "", hasOlder: false, hasNewer: false, totalTurns: 1, startTurn: 1, endTurn: 1,
127 revision: 1, revisionKnown: true, digest: "lazy-result", stale: false,
128 });
129 const tools = toolsOf(projection.items);
130 assert.equal(tools.length, 1);
131 assert.equal(tools[0].id, "lazy-result");
132 assert.ok(projection.items.findIndex(item => item.id === "lazy-result") < projection.items.findIndex(item => item.id === "m:final"));
133 });
134
134 lines TYPESCRIPT