122 lines
4.1 KiB
TypeScript
122 lines
4.1 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { telegramPlugin } from "../../extensions/telegram/src/channel.js";
|
|
import { setTelegramRuntime } from "../../extensions/telegram/src/runtime.js";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import { resolveMainSessionKey } from "../config/sessions.js";
|
|
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
|
import { createPluginRuntime } from "../plugins/runtime/index.js";
|
|
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
|
import { runHeartbeatOnce } from "./heartbeat-runner.js";
|
|
import {
|
|
seedSessionStore,
|
|
withTempTelegramHeartbeatSandbox,
|
|
} from "./heartbeat-runner.test-utils.js";
|
|
|
|
// Avoid pulling optional runtime deps during isolated runs.
|
|
vi.mock("jiti", () => ({ createJiti: () => () => ({}) }));
|
|
|
|
beforeEach(() => {
|
|
const runtime = createPluginRuntime();
|
|
setTelegramRuntime(runtime);
|
|
setActivePluginRegistry(
|
|
createTestRegistry([{ pluginId: "telegram", plugin: telegramPlugin, source: "test" }]),
|
|
);
|
|
});
|
|
|
|
describe("heartbeat transcript pruning", () => {
|
|
async function createTranscriptWithContent(transcriptPath: string, sessionId: string) {
|
|
const header = {
|
|
type: "session",
|
|
version: 3,
|
|
id: sessionId,
|
|
timestamp: new Date().toISOString(),
|
|
cwd: process.cwd(),
|
|
};
|
|
const existingContent = `${JSON.stringify(header)}\n{"role":"user","content":"Hello"}\n{"role":"assistant","content":"Hi there"}\n`;
|
|
await fs.mkdir(path.dirname(transcriptPath), { recursive: true });
|
|
await fs.writeFile(transcriptPath, existingContent);
|
|
return existingContent;
|
|
}
|
|
|
|
async function runTranscriptScenario(params: {
|
|
sessionId: string;
|
|
reply: {
|
|
text: string;
|
|
usage: {
|
|
inputTokens: number;
|
|
outputTokens: number;
|
|
cacheReadTokens: number;
|
|
cacheWriteTokens: number;
|
|
};
|
|
};
|
|
expectPruned: boolean;
|
|
}) {
|
|
await withTempTelegramHeartbeatSandbox(
|
|
async ({ tmpDir, storePath, replySpy }) => {
|
|
const sessionKey = resolveMainSessionKey(undefined);
|
|
const transcriptPath = path.join(tmpDir, `${params.sessionId}.jsonl`);
|
|
const originalContent = await createTranscriptWithContent(transcriptPath, params.sessionId);
|
|
const originalSize = (await fs.stat(transcriptPath)).size;
|
|
|
|
await seedSessionStore(storePath, sessionKey, {
|
|
sessionId: params.sessionId,
|
|
lastChannel: "telegram",
|
|
lastProvider: "telegram",
|
|
lastTo: "user123",
|
|
});
|
|
|
|
replySpy.mockResolvedValueOnce(params.reply);
|
|
|
|
const cfg = {
|
|
version: 1,
|
|
model: "test-model",
|
|
agent: { workspace: tmpDir },
|
|
sessionStore: storePath,
|
|
channels: { telegram: {} },
|
|
} as unknown as OpenClawConfig;
|
|
|
|
await runHeartbeatOnce({
|
|
agentId: undefined,
|
|
reason: "test",
|
|
cfg,
|
|
deps: { sendTelegram: vi.fn() },
|
|
});
|
|
|
|
const finalSize = (await fs.stat(transcriptPath)).size;
|
|
if (params.expectPruned) {
|
|
const finalContent = await fs.readFile(transcriptPath, "utf-8");
|
|
expect(finalContent).toBe(originalContent);
|
|
expect(finalSize).toBe(originalSize);
|
|
return;
|
|
}
|
|
expect(finalSize).toBeGreaterThanOrEqual(originalSize);
|
|
},
|
|
{ prefix: "openclaw-hb-prune-" },
|
|
);
|
|
}
|
|
|
|
it("prunes transcript when heartbeat returns HEARTBEAT_OK", async () => {
|
|
await runTranscriptScenario({
|
|
sessionId: "test-session-prune",
|
|
reply: {
|
|
text: "HEARTBEAT_OK",
|
|
usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },
|
|
},
|
|
expectPruned: true,
|
|
});
|
|
});
|
|
|
|
it("does not prune transcript when heartbeat returns meaningful content", async () => {
|
|
await runTranscriptScenario({
|
|
sessionId: "test-session-no-prune",
|
|
reply: {
|
|
text: "Alert: Something needs your attention!",
|
|
usage: { inputTokens: 10, outputTokens: 20, cacheReadTokens: 0, cacheWriteTokens: 0 },
|
|
},
|
|
expectPruned: false,
|
|
});
|
|
});
|
|
});
|