164 lines
5.4 KiB
TypeScript
164 lines
5.4 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
|
|
import {
|
|
buildPromptWithLearningContext,
|
|
generateSessionTitle,
|
|
shouldGenerateSessionTitle,
|
|
} from "../../src/routes/chatSession.js";
|
|
import { type SessionTurnRecord } from "../../src/history/store.js";
|
|
import { type MemoryStore } from "../../src/memory/store.js";
|
|
import { type OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js";
|
|
|
|
describe("shouldGenerateSessionTitle", () => {
|
|
it("allows auto-title generation for the first turn when the title was not edited", () => {
|
|
expect(
|
|
shouldGenerateSessionTitle({
|
|
recentTurnCount: 0,
|
|
isTitleManuallyEdited: false,
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("blocks auto-title generation after the user edits the title manually", () => {
|
|
expect(
|
|
shouldGenerateSessionTitle({
|
|
recentTurnCount: 0,
|
|
isTitleManuallyEdited: true,
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("only allows auto-title generation during the first two turns", () => {
|
|
expect(
|
|
shouldGenerateSessionTitle({
|
|
recentTurnCount: 1,
|
|
isTitleManuallyEdited: false,
|
|
}),
|
|
).toBe(true);
|
|
expect(
|
|
shouldGenerateSessionTitle({
|
|
recentTurnCount: 2,
|
|
isTitleManuallyEdited: false,
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("generateSessionTitle", () => {
|
|
it("uses the current user and assistant turn instead of reading wrapped runtime context", async () => {
|
|
let titlePrompt = "";
|
|
const runtime = {
|
|
createSession: async () => ({ id: "title-session" }),
|
|
prompt: async (_sessionId: string, prompt: string) => {
|
|
titlePrompt = prompt;
|
|
},
|
|
waitForSessionIdle: async () => undefined,
|
|
messages: async () => [
|
|
{
|
|
info: { role: "assistant" },
|
|
parts: [{ type: "text", text: "标题:泵站压力异常排查。" }],
|
|
},
|
|
],
|
|
abortSession: async () => undefined,
|
|
} as unknown as OpencodeRuntimeAdapter;
|
|
|
|
const title = await generateSessionTitle(runtime, {
|
|
sessionId: "chat-session",
|
|
latestUserMessage: "检查一下三号泵站最近压力波动的原因",
|
|
latestAssistantMessage: "三号泵站压力波动主要与夜间阀门开度变化有关。",
|
|
fallbackTitle: "新对话",
|
|
});
|
|
|
|
expect(title).toBe("泵站压力异常排查");
|
|
expect(titlePrompt).toContain("用户:检查一下三号泵站最近压力波动的原因");
|
|
expect(titlePrompt).toContain("助手:三号泵站压力波动主要与夜间阀门开度变化有关。");
|
|
});
|
|
});
|
|
|
|
describe("buildPromptWithLearningContext", () => {
|
|
const memoryStore = {
|
|
buildPromptSnapshot: async () => "",
|
|
} as unknown as MemoryStore;
|
|
|
|
it("prefers persisted frontend messages so aborted turns remain in restored context", async () => {
|
|
const prompt = await buildPromptWithLearningContext(
|
|
memoryStore,
|
|
"actor-1",
|
|
"project-1",
|
|
{
|
|
recentTurns: [],
|
|
persistedMessages: [
|
|
{ role: "user", content: "先分析 3 号泵站夜间压力波动" },
|
|
{
|
|
role: "assistant",
|
|
content: "已定位到夜间阀门开度变化与压力波动时间段重合,下一步准备对比相邻支路。",
|
|
isError: true,
|
|
},
|
|
{ role: "assistant", content: "⚠️ **请求已中断**", isError: true },
|
|
],
|
|
message: "继续刚才的分析,并补充相邻支路影响",
|
|
},
|
|
);
|
|
|
|
expect(prompt).toContain("用户:先分析 3 号泵站夜间压力波动");
|
|
expect(prompt).toContain(
|
|
"助手:已定位到夜间阀门开度变化与压力波动时间段重合,下一步准备对比相邻支路。",
|
|
);
|
|
expect(prompt).not.toContain("⚠️ **请求已中断**");
|
|
expect(prompt).toContain("[Current user request]\n继续刚才的分析,并补充相邻支路影响");
|
|
});
|
|
|
|
it("falls back to history turns when frontend state is unavailable", async () => {
|
|
const recentTurns: SessionTurnRecord[] = [
|
|
{
|
|
id: "turn-1",
|
|
userMessage: "检查 DMA-2 夜间漏损异常",
|
|
assistantMessage: "DMA-2 在 02:00-04:00 出现持续最小夜流抬升。",
|
|
timestamp: new Date().toISOString(),
|
|
toolCallCount: 1,
|
|
},
|
|
];
|
|
|
|
const prompt = await buildPromptWithLearningContext(
|
|
memoryStore,
|
|
"actor-1",
|
|
"project-1",
|
|
{
|
|
recentTurns,
|
|
message: "继续给出排查建议",
|
|
},
|
|
);
|
|
|
|
expect(prompt).toContain("用户:检查 DMA-2 夜间漏损异常");
|
|
expect(prompt).toContain("助手:DMA-2 在 02:00-04:00 出现持续最小夜流抬升。");
|
|
});
|
|
|
|
it("skips restored conversation injection when reusing an existing opencode session", async () => {
|
|
const prompt = await buildPromptWithLearningContext(
|
|
memoryStore,
|
|
"actor-1",
|
|
"project-1",
|
|
{
|
|
recentTurns: [
|
|
{
|
|
id: "turn-1",
|
|
userMessage: "上一轮问题",
|
|
assistantMessage: "上一轮回答",
|
|
timestamp: new Date().toISOString(),
|
|
toolCallCount: 0,
|
|
},
|
|
],
|
|
persistedMessages: [
|
|
{ role: "user", content: "旧问题" },
|
|
{ role: "assistant", content: "旧回答" },
|
|
],
|
|
message: "基于刚才结果继续分析",
|
|
restoreConversation: false,
|
|
},
|
|
);
|
|
|
|
expect(prompt).not.toContain("[Previous conversation context]");
|
|
expect(prompt).toBe("基于刚才结果继续分析");
|
|
});
|
|
});
|