|
@@ -4,6 +4,7 @@ import type { ReferenceToken } from "@/lib/reference/types"
|
|
|
import {
|
|
import {
|
|
|
agentToolCallsToMessageReferences,
|
|
agentToolCallsToMessageReferences,
|
|
|
getReferenceTokensForConversation,
|
|
getReferenceTokensForConversation,
|
|
|
|
|
+ normalizeReferencePath,
|
|
|
setReferenceTokensForConversation,
|
|
setReferenceTokensForConversation,
|
|
|
} from "./agent-message-metadata"
|
|
} from "./agent-message-metadata"
|
|
|
|
|
|
|
@@ -11,22 +12,37 @@ function toolCall(
|
|
|
name: string,
|
|
name: string,
|
|
|
params: Record<string, unknown>,
|
|
params: Record<string, unknown>,
|
|
|
status: "done" | "error" = "done",
|
|
status: "done" | "error" = "done",
|
|
|
|
|
+ result = status === "done" ? "ok" : "错误",
|
|
|
): AgentRunRecord["toolCalls"][number] {
|
|
): AgentRunRecord["toolCalls"][number] {
|
|
|
return {
|
|
return {
|
|
|
id: `${name}-${String(params.name ?? params.path ?? params.conversationId ?? "x")}`,
|
|
id: `${name}-${String(params.name ?? params.path ?? params.conversationId ?? "x")}`,
|
|
|
name,
|
|
name,
|
|
|
params,
|
|
params,
|
|
|
- result: status === "done" ? "ok" : "错误",
|
|
|
|
|
|
|
+ result,
|
|
|
status,
|
|
status,
|
|
|
startedAt: 1,
|
|
startedAt: 1,
|
|
|
finishedAt: 2,
|
|
finishedAt: 2,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+describe("normalizeReferencePath", () => {
|
|
|
|
|
+ it("maps QM knowledge-dir segments to wiki for UI virtualization", () => {
|
|
|
|
|
+ expect(normalizeReferencePath("C:/Book/QM/outlines/设定/写作通则.md")).toBe(
|
|
|
|
|
+ "wiki/outlines/设定/写作通则.md",
|
|
|
|
|
+ )
|
|
|
|
|
+ expect(normalizeReferencePath("/Users/a/QM/outlines/章纲/第1章.md")).toBe(
|
|
|
|
|
+ "wiki/outlines/章纲/第1章.md",
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
describe("agentToolCallsToMessageReferences", () => {
|
|
describe("agentToolCallsToMessageReferences", () => {
|
|
|
it("converts successful Agent read tools into assistant message references", () => {
|
|
it("converts successful Agent read tools into assistant message references", () => {
|
|
|
const references = agentToolCallsToMessageReferences([
|
|
const references = agentToolCallsToMessageReferences([
|
|
|
- toolCall("read_chapter", { name: "第一章" }),
|
|
|
|
|
|
|
+ toolCall("read_chapter", {
|
|
|
|
|
+ name: "第40章-三百人",
|
|
|
|
|
+ path: "/Users/omi/book/QM/chapters/第40章-三百人.md",
|
|
|
|
|
+ }),
|
|
|
toolCall("read_outline", { path: "C:/Book/wiki/outlines/主线.md" }),
|
|
toolCall("read_outline", { path: "C:/Book/wiki/outlines/主线.md" }),
|
|
|
toolCall("read_memory", { name: "主角记忆" }),
|
|
toolCall("read_memory", { name: "主角记忆" }),
|
|
|
toolCall("read_deduction", { name: "framework_1" }),
|
|
toolCall("read_deduction", { name: "framework_1" }),
|
|
@@ -35,16 +51,74 @@ describe("agentToolCallsToMessageReferences", () => {
|
|
|
])
|
|
])
|
|
|
|
|
|
|
|
expect(references).toEqual([
|
|
expect(references).toEqual([
|
|
|
- { title: "第一章", path: "wiki/chapters/第一章.md" },
|
|
|
|
|
|
|
+ { title: "第40章-三百人", path: "wiki/chapters/第40章-三百人.md" },
|
|
|
{ title: "主线", path: "wiki/outlines/主线.md" },
|
|
{ title: "主线", path: "wiki/outlines/主线.md" },
|
|
|
{ title: "主角记忆", path: "wiki/memory/主角记忆.md" },
|
|
{ title: "主角记忆", path: "wiki/memory/主角记忆.md" },
|
|
|
{ title: "framework_1", path: ".qmai/simulations/framework_1.json" },
|
|
{ title: "framework_1", path: ".qmai/simulations/framework_1.json" },
|
|
|
])
|
|
])
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ it("does not invent a shallow chapter path from a bare chapter number name", () => {
|
|
|
|
|
+ const references = agentToolCallsToMessageReferences([
|
|
|
|
|
+ toolCall("read_chapter", { name: "第40章" }, "done", "# 第40章-三百人\n正文"),
|
|
|
|
|
+ ])
|
|
|
|
|
+ expect(references).toEqual([])
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("skips outline snapshot fallback results that are not a single file", () => {
|
|
|
|
|
+ const references = agentToolCallsToMessageReferences([
|
|
|
|
|
+ toolCall(
|
|
|
|
|
+ "read_outline",
|
|
|
|
|
+ { path: "大纲/章纲" },
|
|
|
|
|
+ "done",
|
|
|
|
|
+ "未找到 wiki/outlines 下的独立大纲文件,已读取大纲快照:\n\n## outline-1.snapshot.md\n\n# 快照",
|
|
|
|
|
+ ),
|
|
|
|
|
+ ])
|
|
|
|
|
+ expect(references).toEqual([])
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("keeps nested outline folders from resolved absolute paths", () => {
|
|
|
|
|
+ const references = agentToolCallsToMessageReferences([
|
|
|
|
|
+ toolCall("read_outline", {
|
|
|
|
|
+ name: "写作通则",
|
|
|
|
|
+ path: "/Users/omi/book/QM/outlines/设定/写作通则.md",
|
|
|
|
|
+ }),
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ expect(references).toEqual([
|
|
|
|
|
+ { title: "写作通则", path: "wiki/outlines/设定/写作通则.md" },
|
|
|
|
|
+ ])
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("skips directory-only outline reads such as 卷纲/章纲", () => {
|
|
|
|
|
+ const references = agentToolCallsToMessageReferences([
|
|
|
|
|
+ toolCall(
|
|
|
|
|
+ "read_outline",
|
|
|
|
|
+ { name: "卷纲" },
|
|
|
|
|
+ "done",
|
|
|
|
|
+ "「卷纲」是目录,不是单个大纲。可读取以下条目:\n1. 第一卷",
|
|
|
|
|
+ ),
|
|
|
|
|
+ toolCall(
|
|
|
|
|
+ "read_outline",
|
|
|
|
|
+ { name: "章纲", path: "/Users/omi/book/QM/outlines/章纲" },
|
|
|
|
|
+ "done",
|
|
|
|
|
+ "「章纲」是目录,不是单个大纲。可读取以下条目:\n1. 第1章-分手",
|
|
|
|
|
+ ),
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ expect(references).toEqual([])
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("does not invent a shallow outline path from a bare name", () => {
|
|
|
|
|
+ const references = agentToolCallsToMessageReferences([
|
|
|
|
|
+ toolCall("read_outline", { name: "写作通则" }, "done", "# 通则正文"),
|
|
|
|
|
+ ])
|
|
|
|
|
+ expect(references).toEqual([])
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
it("deduplicates references by path", () => {
|
|
it("deduplicates references by path", () => {
|
|
|
const references = agentToolCallsToMessageReferences([
|
|
const references = agentToolCallsToMessageReferences([
|
|
|
- toolCall("read_chapter", { name: "第一章" }),
|
|
|
|
|
|
|
+ toolCall("read_chapter", { path: "C:/Book/wiki/chapters/第一章.md", name: "第一章" }),
|
|
|
toolCall("read_chapter", { path: "C:/Book/wiki/chapters/第一章.md" }),
|
|
toolCall("read_chapter", { path: "C:/Book/wiki/chapters/第一章.md" }),
|
|
|
])
|
|
])
|
|
|
|
|
|