mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 17:38:27 +00:00
fix: polish session picker filtering (#1271) (thanks @Whoaa512)
This commit is contained in:
@@ -3,308 +3,341 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import {
|
||||
readFirstUserMessageFromTranscript,
|
||||
readLastMessagePreviewFromTranscript,
|
||||
readFirstUserMessageFromTranscript,
|
||||
readLastMessagePreviewFromTranscript,
|
||||
} from "./session-utils.fs.js";
|
||||
|
||||
describe("readFirstUserMessageFromTranscript", () => {
|
||||
let tmpDir: string;
|
||||
let storePath: string;
|
||||
let tmpDir: string;
|
||||
let storePath: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-session-fs-test-"));
|
||||
storePath = path.join(tmpDir, "sessions.json");
|
||||
});
|
||||
beforeEach(() => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-session-fs-test-"));
|
||||
storePath = path.join(tmpDir, "sessions.json");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
afterEach(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("returns null when transcript file does not exist", () => {
|
||||
const result = readFirstUserMessageFromTranscript("nonexistent-session", storePath);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
test("returns null when transcript file does not exist", () => {
|
||||
const result = readFirstUserMessageFromTranscript("nonexistent-session", storePath);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns first user message from transcript with string content", () => {
|
||||
const sessionId = "test-session-1";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({ message: { role: "user", content: "Hello world" } }),
|
||||
JSON.stringify({ message: { role: "assistant", content: "Hi there" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
test("returns first user message from transcript with string content", () => {
|
||||
const sessionId = "test-session-1";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({ message: { role: "user", content: "Hello world" } }),
|
||||
JSON.stringify({ message: { role: "assistant", content: "Hi there" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Hello world");
|
||||
});
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Hello world");
|
||||
});
|
||||
|
||||
test("returns first user message from transcript with array content", () => {
|
||||
const sessionId = "test-session-2";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({
|
||||
message: {
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Array message content" }],
|
||||
},
|
||||
}),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
test("returns first user message from transcript with array content", () => {
|
||||
const sessionId = "test-session-2";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({
|
||||
message: {
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Array message content" }],
|
||||
},
|
||||
}),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Array message content");
|
||||
});
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Array message content");
|
||||
});
|
||||
|
||||
test("skips non-user messages to find first user message", () => {
|
||||
const sessionId = "test-session-3";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({ message: { role: "system", content: "System prompt" } }),
|
||||
JSON.stringify({ message: { role: "assistant", content: "Greeting" } }),
|
||||
JSON.stringify({ message: { role: "user", content: "First user question" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
test("returns first user message from transcript with input_text content", () => {
|
||||
const sessionId = "test-session-2b";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({
|
||||
message: {
|
||||
role: "user",
|
||||
content: [{ type: "input_text", text: "Input text content" }],
|
||||
},
|
||||
}),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("First user question");
|
||||
});
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Input text content");
|
||||
});
|
||||
test("skips non-user messages to find first user message", () => {
|
||||
const sessionId = "test-session-3";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({ message: { role: "system", content: "System prompt" } }),
|
||||
JSON.stringify({ message: { role: "assistant", content: "Greeting" } }),
|
||||
JSON.stringify({ message: { role: "user", content: "First user question" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
test("returns null when no user messages exist", () => {
|
||||
const sessionId = "test-session-4";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({ message: { role: "system", content: "System prompt" } }),
|
||||
JSON.stringify({ message: { role: "assistant", content: "Greeting" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("First user question");
|
||||
});
|
||||
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
test("returns null when no user messages exist", () => {
|
||||
const sessionId = "test-session-4";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({ message: { role: "system", content: "System prompt" } }),
|
||||
JSON.stringify({ message: { role: "assistant", content: "Greeting" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
test("handles malformed JSON lines gracefully", () => {
|
||||
const sessionId = "test-session-5";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
"not valid json",
|
||||
JSON.stringify({ message: { role: "user", content: "Valid message" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Valid message");
|
||||
});
|
||||
test("handles malformed JSON lines gracefully", () => {
|
||||
const sessionId = "test-session-5";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
"not valid json",
|
||||
JSON.stringify({ message: { role: "user", content: "Valid message" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
test("uses sessionFile parameter when provided", () => {
|
||||
const sessionId = "test-session-6";
|
||||
const customPath = path.join(tmpDir, "custom-transcript.jsonl");
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({ message: { role: "user", content: "Custom file message" } }),
|
||||
];
|
||||
fs.writeFileSync(customPath, lines.join("\n"), "utf-8");
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Valid message");
|
||||
});
|
||||
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath, customPath);
|
||||
expect(result).toBe("Custom file message");
|
||||
});
|
||||
test("uses sessionFile parameter when provided", () => {
|
||||
const sessionId = "test-session-6";
|
||||
const customPath = path.join(tmpDir, "custom-transcript.jsonl");
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({ message: { role: "user", content: "Custom file message" } }),
|
||||
];
|
||||
fs.writeFileSync(customPath, lines.join("\n"), "utf-8");
|
||||
|
||||
test("trims whitespace from message content", () => {
|
||||
const sessionId = "test-session-7";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [JSON.stringify({ message: { role: "user", content: " Padded message " } })];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath, customPath);
|
||||
expect(result).toBe("Custom file message");
|
||||
});
|
||||
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Padded message");
|
||||
});
|
||||
test("trims whitespace from message content", () => {
|
||||
const sessionId = "test-session-7";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [JSON.stringify({ message: { role: "user", content: " Padded message " } })];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
test("returns null for empty content", () => {
|
||||
const sessionId = "test-session-8";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "user", content: "" } }),
|
||||
JSON.stringify({ message: { role: "user", content: "Second message" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Padded message");
|
||||
});
|
||||
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Second message");
|
||||
});
|
||||
test("returns null for empty content", () => {
|
||||
const sessionId = "test-session-8";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "user", content: "" } }),
|
||||
JSON.stringify({ message: { role: "user", content: "Second message" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readFirstUserMessageFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Second message");
|
||||
});
|
||||
});
|
||||
|
||||
describe("readLastMessagePreviewFromTranscript", () => {
|
||||
let tmpDir: string;
|
||||
let storePath: string;
|
||||
let tmpDir: string;
|
||||
let storePath: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-session-fs-test-"));
|
||||
storePath = path.join(tmpDir, "sessions.json");
|
||||
});
|
||||
beforeEach(() => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-session-fs-test-"));
|
||||
storePath = path.join(tmpDir, "sessions.json");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
afterEach(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("returns null when transcript file does not exist", () => {
|
||||
const result = readLastMessagePreviewFromTranscript("nonexistent-session", storePath);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
test("returns null when transcript file does not exist", () => {
|
||||
const result = readLastMessagePreviewFromTranscript("nonexistent-session", storePath);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns null for empty file", () => {
|
||||
const sessionId = "test-last-empty";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
fs.writeFileSync(transcriptPath, "", "utf-8");
|
||||
test("returns null for empty file", () => {
|
||||
const sessionId = "test-last-empty";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
fs.writeFileSync(transcriptPath, "", "utf-8");
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("returns last user message from transcript", () => {
|
||||
const sessionId = "test-last-user";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "user", content: "First user" } }),
|
||||
JSON.stringify({ message: { role: "assistant", content: "First assistant" } }),
|
||||
JSON.stringify({ message: { role: "user", content: "Last user message" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
test("returns last user message from transcript", () => {
|
||||
const sessionId = "test-last-user";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "user", content: "First user" } }),
|
||||
JSON.stringify({ message: { role: "assistant", content: "First assistant" } }),
|
||||
JSON.stringify({ message: { role: "user", content: "Last user message" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Last user message");
|
||||
});
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Last user message");
|
||||
});
|
||||
|
||||
test("returns last assistant message from transcript", () => {
|
||||
const sessionId = "test-last-assistant";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "user", content: "User question" } }),
|
||||
JSON.stringify({ message: { role: "assistant", content: "Final assistant reply" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
test("returns last assistant message from transcript", () => {
|
||||
const sessionId = "test-last-assistant";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "user", content: "User question" } }),
|
||||
JSON.stringify({ message: { role: "assistant", content: "Final assistant reply" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Final assistant reply");
|
||||
});
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Final assistant reply");
|
||||
});
|
||||
|
||||
test("skips system messages to find last user/assistant", () => {
|
||||
const sessionId = "test-last-skip-system";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "user", content: "Real last" } }),
|
||||
JSON.stringify({ message: { role: "system", content: "System at end" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
test("skips system messages to find last user/assistant", () => {
|
||||
const sessionId = "test-last-skip-system";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "user", content: "Real last" } }),
|
||||
JSON.stringify({ message: { role: "system", content: "System at end" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Real last");
|
||||
});
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Real last");
|
||||
});
|
||||
|
||||
test("returns null when no user/assistant messages exist", () => {
|
||||
const sessionId = "test-last-no-match";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({ message: { role: "system", content: "Only system" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
test("returns null when no user/assistant messages exist", () => {
|
||||
const sessionId = "test-last-no-match";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 1, id: sessionId }),
|
||||
JSON.stringify({ message: { role: "system", content: "Only system" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("handles malformed JSON lines gracefully", () => {
|
||||
const sessionId = "test-last-malformed";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "user", content: "Valid first" } }),
|
||||
"not valid json at end",
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
test("handles malformed JSON lines gracefully", () => {
|
||||
const sessionId = "test-last-malformed";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "user", content: "Valid first" } }),
|
||||
"not valid json at end",
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Valid first");
|
||||
});
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Valid first");
|
||||
});
|
||||
|
||||
test("handles array content format", () => {
|
||||
const sessionId = "test-last-array";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Array content response" }],
|
||||
},
|
||||
}),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
test("handles array content format", () => {
|
||||
const sessionId = "test-last-array";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Array content response" }],
|
||||
},
|
||||
}),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Array content response");
|
||||
});
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Array content response");
|
||||
});
|
||||
|
||||
test("uses sessionFile parameter when provided", () => {
|
||||
const sessionId = "test-last-custom";
|
||||
const customPath = path.join(tmpDir, "custom-last.jsonl");
|
||||
const lines = [JSON.stringify({ message: { role: "user", content: "Custom file last" } })];
|
||||
fs.writeFileSync(customPath, lines.join("\n"), "utf-8");
|
||||
test("handles output_text content format", () => {
|
||||
const sessionId = "test-last-output-text";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "Output text response" }],
|
||||
},
|
||||
}),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath, customPath);
|
||||
expect(result).toBe("Custom file last");
|
||||
});
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Output text response");
|
||||
});
|
||||
test("uses sessionFile parameter when provided", () => {
|
||||
const sessionId = "test-last-custom";
|
||||
const customPath = path.join(tmpDir, "custom-last.jsonl");
|
||||
const lines = [JSON.stringify({ message: { role: "user", content: "Custom file last" } })];
|
||||
fs.writeFileSync(customPath, lines.join("\n"), "utf-8");
|
||||
|
||||
test("trims whitespace from message content", () => {
|
||||
const sessionId = "test-last-trim";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "assistant", content: " Padded response " } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath, customPath);
|
||||
expect(result).toBe("Custom file last");
|
||||
});
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Padded response");
|
||||
});
|
||||
test("trims whitespace from message content", () => {
|
||||
const sessionId = "test-last-trim";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "assistant", content: " Padded response " } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
test("skips empty content to find previous message", () => {
|
||||
const sessionId = "test-last-skip-empty";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "assistant", content: "Has content" } }),
|
||||
JSON.stringify({ message: { role: "user", content: "" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Padded response");
|
||||
});
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Has content");
|
||||
});
|
||||
test("skips empty content to find previous message", () => {
|
||||
const sessionId = "test-last-skip-empty";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const lines = [
|
||||
JSON.stringify({ message: { role: "assistant", content: "Has content" } }),
|
||||
JSON.stringify({ message: { role: "user", content: "" } }),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
test("reads from end of large file (16KB window)", () => {
|
||||
const sessionId = "test-last-large";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const padding = JSON.stringify({ message: { role: "user", content: "x".repeat(500) } });
|
||||
const lines: string[] = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
lines.push(padding);
|
||||
}
|
||||
lines.push(JSON.stringify({ message: { role: "assistant", content: "Last in large file" } }));
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Has content");
|
||||
});
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Last in large file");
|
||||
});
|
||||
test("reads from end of large file (16KB window)", () => {
|
||||
const sessionId = "test-last-large";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const padding = JSON.stringify({ message: { role: "user", content: "x".repeat(500) } });
|
||||
const lines: string[] = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
lines.push(padding);
|
||||
}
|
||||
lines.push(JSON.stringify({ message: { role: "assistant", content: "Last in large file" } }));
|
||||
fs.writeFileSync(transcriptPath, lines.join("\n"), "utf-8");
|
||||
|
||||
test("handles valid UTF-8 content", () => {
|
||||
const sessionId = "test-last-utf8";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const validLine = JSON.stringify({
|
||||
message: { role: "user", content: "Valid UTF-8: 你好世界 🌍" },
|
||||
});
|
||||
fs.writeFileSync(transcriptPath, validLine, "utf-8");
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Last in large file");
|
||||
});
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Valid UTF-8: 你好世界 🌍");
|
||||
});
|
||||
test("handles valid UTF-8 content", () => {
|
||||
const sessionId = "test-last-utf8";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const validLine = JSON.stringify({
|
||||
message: { role: "user", content: "Valid UTF-8: 你好世界 🌍" },
|
||||
});
|
||||
fs.writeFileSync(transcriptPath, validLine, "utf-8");
|
||||
|
||||
const result = readLastMessagePreviewFromTranscript(sessionId, storePath);
|
||||
expect(result).toBe("Valid UTF-8: 你好世界 🌍");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -91,8 +91,10 @@ function extractTextFromContent(content: TranscriptMessage["content"]): string |
|
||||
if (typeof content === "string") return content.trim() || null;
|
||||
if (!Array.isArray(content)) return null;
|
||||
for (const part of content) {
|
||||
if (part.type === "text" && typeof part.text === "string" && part.text.trim()) {
|
||||
return part.text.trim();
|
||||
if (!part || typeof part.text !== "string") continue;
|
||||
if (part.type === "text" || part.type === "output_text" || part.type === "input_text") {
|
||||
const trimmed = part.text.trim();
|
||||
if (trimmed) return trimmed;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -84,7 +84,8 @@ export function deriveSessionTitle(
|
||||
}
|
||||
|
||||
if (firstUserMessage?.trim()) {
|
||||
return truncateTitle(firstUserMessage.trim(), DERIVED_TITLE_MAX_LEN);
|
||||
const normalized = firstUserMessage.replace(/\s+/g, " ").trim();
|
||||
return truncateTitle(normalized, DERIVED_TITLE_MAX_LEN);
|
||||
}
|
||||
|
||||
if (entry.sessionId) {
|
||||
|
||||
Reference in New Issue
Block a user