Add transcript size monitoring to /status and session_status tool. Displays file size and message count (e.g. '📄 Transcript: 1.2 MB, 627 messages'). Shows ⚠️ warning when transcript exceeds 1 MB, which helps catch sessions approaching the compaction death spiral described in #13624. - getTranscriptInfo() reads JSONL file stat + line count - Wired into both /status command and session_status tool - 8 new tests covering file reading, formatting, and edge cases
113 lines
3.4 KiB
TypeScript
113 lines
3.4 KiB
TypeScript
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("../config/sessions.js", () => ({
|
|
resolveSessionFilePath: vi.fn((_sessionId: string) => ""),
|
|
resolveSessionFilePathOptions: vi.fn(() => ({})),
|
|
resolveMainSessionKey: vi.fn(() => "main"),
|
|
}));
|
|
|
|
const sessions = await import("../config/sessions.js");
|
|
const resolveSessionFilePathMock = vi.mocked(sessions.resolveSessionFilePath);
|
|
|
|
const { getTranscriptInfo, buildStatusMessage } = await import("./status.js");
|
|
|
|
describe("getTranscriptInfo", () => {
|
|
let tmpDir: string;
|
|
let testFilePath: string;
|
|
|
|
beforeEach(() => {
|
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "transcript-test-"));
|
|
testFilePath = path.join(tmpDir, "test-session.jsonl");
|
|
resolveSessionFilePathMock.mockReturnValue(testFilePath);
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it("returns undefined when sessionId is missing", () => {
|
|
expect(getTranscriptInfo({})).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined when file does not exist", () => {
|
|
resolveSessionFilePathMock.mockReturnValue(path.join(tmpDir, "nonexistent.jsonl"));
|
|
expect(getTranscriptInfo({ sessionId: "abc" })).toBeUndefined();
|
|
});
|
|
|
|
it("returns size and message count for a transcript file", () => {
|
|
const lines = [
|
|
JSON.stringify({ message: { role: "user", content: "hello" } }),
|
|
JSON.stringify({ message: { role: "assistant", content: "hi" } }),
|
|
"",
|
|
];
|
|
fs.writeFileSync(testFilePath, lines.join("\n"));
|
|
const info = getTranscriptInfo({ sessionId: "abc" });
|
|
expect(info).toBeDefined();
|
|
expect(info!.messageCount).toBe(2);
|
|
expect(info!.sizeBytes).toBeGreaterThan(0);
|
|
expect(info!.filePath).toBe(testFilePath);
|
|
});
|
|
|
|
it("counts only non-empty lines", () => {
|
|
const content = '{"a":1}\n\n\n{"b":2}\n{"c":3}\n\n';
|
|
fs.writeFileSync(testFilePath, content);
|
|
const info = getTranscriptInfo({ sessionId: "abc" });
|
|
expect(info!.messageCount).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe("transcript line in buildStatusMessage", () => {
|
|
it("includes transcript line when transcriptInfo is provided", () => {
|
|
const info = {
|
|
sizeBytes: 512_000,
|
|
messageCount: 42,
|
|
filePath: "/tmp/test.jsonl",
|
|
};
|
|
const result = buildStatusMessage({
|
|
agent: {},
|
|
transcriptInfo: info,
|
|
});
|
|
expect(result).toContain("📄 Transcript:");
|
|
expect(result).toContain("500.0 KB");
|
|
expect(result).toContain("42 messages");
|
|
});
|
|
|
|
it("shows warning emoji for large transcripts", () => {
|
|
const info = {
|
|
sizeBytes: 2 * 1024 * 1024,
|
|
messageCount: 600,
|
|
filePath: "/tmp/test.jsonl",
|
|
};
|
|
const result = buildStatusMessage({
|
|
agent: {},
|
|
transcriptInfo: info,
|
|
});
|
|
expect(result).toContain("⚠️");
|
|
expect(result).toContain("2.0 MB");
|
|
});
|
|
|
|
it("omits transcript line when transcriptInfo is undefined", () => {
|
|
const result = buildStatusMessage({
|
|
agent: {},
|
|
});
|
|
expect(result).not.toContain("📄 Transcript:");
|
|
});
|
|
|
|
it("handles singular message count", () => {
|
|
const info = {
|
|
sizeBytes: 100,
|
|
messageCount: 1,
|
|
filePath: "/tmp/test.jsonl",
|
|
};
|
|
const result = buildStatusMessage({
|
|
agent: {},
|
|
transcriptInfo: info,
|
|
});
|
|
expect(result).toContain("1 message");
|
|
expect(result).not.toContain("1 messages");
|
|
});
|
|
});
|