mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-24 07:01:49 +00:00
fix(memory): export archived qmd session transcripts (#57446)
* fix(memory): export archived qmd session transcripts * test(memory): separate qmd session listing describe
This commit is contained in:
@@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/MCP: reuse bundled MCP runtimes across turns in the same session, while recreating them when MCP config changes and disposing stale runtimes cleanly on session rollover. (#55090) Thanks @allan0509.
|
||||
- Memory/QMD: honor `memory.qmd.update.embedInterval` even when regular QMD update cadence is disabled or slower by arming a dedicated embed-cadence maintenance timer, while avoiding redundant timers when regular updates are already frequent enough. (#37326) Thanks @barronlroth.
|
||||
- Memory/QMD: add `memory.qmd.searchTool` as an exact mcporter tool override, so custom QMD MCP tools such as `hybrid_search` can be used without weakening the validated `searchMode` config surface. (#27801) Thanks @keramblock.
|
||||
- Memory/QMD: keep reset and deleted session transcripts in QMD session export so daily session resets do not silently drop most historical recall from `memory_search`. (#30220) Thanks @pushkarsingh32.
|
||||
- Memory/QMD: rebind collections when QMD reports a changed pattern but omits path metadata, so config pattern changes stop being silently ignored on restart. (#49897) Thanks @Madruru.
|
||||
- Agents/memory flush: keep daily memory flush files append-only during embedded attempts so compaction writes do not overwrite earlier notes. (#53725) Thanks @HPluseven.
|
||||
- Web UI/markdown: stop bare auto-links from swallowing adjacent CJK text while preserving valid mixed-script path and query characters in rendered links. (#48410) Thanks @jnuyao.
|
||||
|
||||
@@ -2,19 +2,55 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { buildSessionEntry } from "./session-files.js";
|
||||
import { buildSessionEntry, listSessionFilesForAgent } from "./session-files.js";
|
||||
|
||||
let tmpDir: string;
|
||||
let originalStateDir: string | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "session-entry-test-"));
|
||||
originalStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = tmpDir;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (originalStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = originalStateDir;
|
||||
}
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("listSessionFilesForAgent", () => {
|
||||
it("includes reset and deleted transcripts in session file listing", async () => {
|
||||
const sessionsDir = path.join(tmpDir, "agents", "main", "sessions");
|
||||
await fs.mkdir(path.join(sessionsDir, "archive"), { recursive: true });
|
||||
|
||||
const included = [
|
||||
"active.jsonl",
|
||||
"active.jsonl.reset.2026-02-16T22-26-33.000Z",
|
||||
"active.jsonl.deleted.2026-02-16T22-27-33.000Z",
|
||||
];
|
||||
const excluded = ["active.jsonl.bak.2026-02-16T22-28-33.000Z", "sessions.json", "notes.md"];
|
||||
|
||||
for (const fileName of [...included, ...excluded]) {
|
||||
await fs.writeFile(path.join(sessionsDir, fileName), "");
|
||||
}
|
||||
await fs.writeFile(
|
||||
path.join(sessionsDir, "archive", "nested.jsonl.deleted.2026-02-16T22-29-33.000Z"),
|
||||
"",
|
||||
);
|
||||
|
||||
const files = await listSessionFilesForAgent("main");
|
||||
|
||||
expect(files.map((filePath) => path.basename(filePath)).toSorted()).toEqual(
|
||||
included.toSorted(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildSessionEntry", () => {
|
||||
let tmpDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "session-entry-test-"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("returns lineMap tracking original JSONL line numbers", async () => {
|
||||
// Simulate a real session JSONL file with metadata records interspersed
|
||||
// Lines 1-3: non-message metadata records
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { isUsageCountedSessionTranscriptFileName } from "../../../../src/config/sessions/artifacts.js";
|
||||
import { resolveSessionTranscriptsDirForAgent } from "../../../../src/config/sessions/paths.js";
|
||||
import { redactSensitiveText } from "../../../../src/logging/redact.js";
|
||||
import { createSubsystemLogger } from "../../../../src/logging/subsystem.js";
|
||||
@@ -25,7 +26,7 @@ export async function listSessionFilesForAgent(agentId: string): Promise<string[
|
||||
return entries
|
||||
.filter((entry) => entry.isFile())
|
||||
.map((entry) => entry.name)
|
||||
.filter((name) => name.endsWith(".jsonl"))
|
||||
.filter((name) => isUsageCountedSessionTranscriptFileName(name))
|
||||
.map((name) => path.join(dir, name));
|
||||
} catch {
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user