import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { buildSessionEntry, listSessionFilesForAgent } from "./session-files.js";
let fixtureRoot: string;
let tmpDir: string;
let originalStateDir: string | undefined;
let fixtureId = 0;
// The content should have 3 lines (3 message records) const contentLines = entry!.content.split("\n");
expect(contentLines).toHaveLength(3);
expect(contentLines[0]).toContain("User: Hello world");
expect(contentLines[1]).toContain("Assistant: Hi there");
expect(contentLines[2]).toContain("User: Tell me a joke");
// lineMap should map each content line to its original JSONL line (1-indexed) // Content line 0 → JSONL line 4 (the first user message) // Content line 1 → JSONL line 6 (the assistant message) // Content line 2 → JSONL line 7 (the second user message)
expect(entry!.lineMap).toBeDefined();
expect(entry!.lineMap).toEqual([4, 6, 7]);
expect(entry!.messageTimestampsMs).toEqual([0, 0, 0]);
});
it("strips inbound metadata envelope from user messages before normalization", async () => { // Representative inbound envelope: Conversation info + Sender blocks prepended // to the actual user text. Without stripping, the JSON envelope dominates // the corpus entry and the user's real words get truncated by the // SESSION_INGESTION_MAX_SNIPPET_CHARS cap downstream. // See: https://github.com/openclaw/openclaw/issues/63921 const envelopedUserText = [ "Conversation info (untrusted metadata):", "```json", '{"message_id":"msg-100","chat_id":"-100123","sender":"Chris"}', "```", "", "Sender (untrusted metadata):", "```json", '{"label":"Chris","name":"Chris","id":"42"}', "```", "", "帮我看看今天的 Oura 数据",
].join("\n");
const contentLines = entry!.content.split("\n");
expect(contentLines).toHaveLength(2); // User line should contain ONLY the real user text, not the JSON envelope.
expect(contentLines[0]).toBe("User: 帮我看看今天的 Oura 数据");
expect(contentLines[0]).not.toContain("untrusted metadata");
expect(contentLines[0]).not.toContain("message_id");
expect(contentLines[0]).not.toContain("```json");
expect(contentLines[1]).toBe("Assistant: 好的,我来查一下");
});
const contentLines = entry!.content.split("\n");
expect(contentLines.length).toBeGreaterThan(1);
expect(entry!.lineMap).toEqual(contentLines.map(() => 1));
expect(entry!.messageTimestampsMs).toEqual(contentLines.map(() => 0)); for (const line of contentLines) {
expect(line.startsWith("Assistant: ")).toBe(true);
expectNoUnpairedSurrogates(line);
}
});
it("preserves assistant messages that happen to contain sentinel-like text", async () => { // Assistant role must NOT be stripped — only user messages carry inbound // envelopes, and assistants may legitimately discuss metadata formats. const assistantText = "The envelope format uses 'Conversation info (untrusted metadata):' as a sentinel"; const jsonlLines = [
JSON.stringify({
type: "message",
message: { role: "assistant", content: assistantText },
}),
]; const filePath = path.join(tmpDir, "assistant-sentinel.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
it("does not flag ordinary transcripts that quote the dream-diary prompt", async () => { const jsonlLines = [
JSON.stringify({
type: "message",
message: {
role: "user",
content: "Write a dream diary entry from these memory fragments:\n- Candidate: durable note",
},
}),
JSON.stringify({
type: "message",
message: { role: "assistant", content: "A drifting archive breathed in moonlight." },
}),
]; const filePath = path.join(tmpDir, "dreaming-prompt-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
expect(entry?.generatedByDreamingNarrative).toBeUndefined();
expect(entry?.content).toContain( "User: Write a dream diary entry from these memory fragments:",
);
expect(entry?.content).toContain("Assistant: A drifting archive breathed in moonlight.");
expect(entry?.lineMap).toEqual([1, 2]);
});
it("drops generated system wrapper user messages but keeps the assistant reply", async () => { // Cross-message coupling (drop-next-assistant-when-prior-user-matched) was // removed because user-typed text can match the same patterns; see // PR #70737 review (aisle-research-bot). Real assistant content stays in // the corpus regardless of what the prior user message looked like. const jsonlLines = [
JSON.stringify({
type: "message",
message: {
role: "user",
content: "System (untrusted): [2026-04-15 14:45:20 PDT] Exec completed (quiet-fo, code 0) :: Converted: 1",
},
}),
JSON.stringify({
type: "message",
message: {
role: "assistant",
content: "Handled internally.",
},
}),
JSON.stringify({
type: "message",
message: {
role: "user",
content: "What changed in the sync?",
},
}),
JSON.stringify({
type: "message",
message: {
role: "assistant",
content: "One new session was converted.",
},
}),
]; const filePath = path.join(tmpDir, "system-wrapper-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
expect(entry?.content).toBe(
[ "Assistant: Handled internally.", "User: What changed in the sync?", "Assistant: One new session was converted.",
].join("\n"),
);
expect(entry?.lineMap).toEqual([2, 3, 4]);
});
it("drops direct cron-prompt user messages but keeps the assistant reply", async () => { const jsonlLines = [
JSON.stringify({
type: "message",
message: {
role: "user",
content: "[cron:job-1 Example] Run the nightly sync",
},
}),
JSON.stringify({
type: "message",
message: {
role: "assistant",
content: "Running the nightly sync now.",
},
}),
JSON.stringify({
type: "message",
message: {
role: "user",
content: "Did the nightly sync actually change anything?",
},
}),
JSON.stringify({
type: "message",
message: {
role: "assistant",
content: "No, everything was already current.",
},
}),
]; const filePath = path.join(tmpDir, "cron-prompt-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
expect(entry?.content).toBe(
[ "Assistant: Running the nightly sync now.", "User: Did the nightly sync actually change anything?", "Assistant: No, everything was already current.",
].join("\n"),
);
expect(entry?.lineMap).toEqual([2, 3, 4]);
});
it("drops heartbeat prompt and the HEARTBEAT_OK ack via assistant-side detection", async () => { // The ack is dropped because `HEARTBEAT_OK` is recognised as an // assistant-side machinery token, not because the prior user message was // a heartbeat prompt. A real reply to a similarly-shaped user message // would still survive. const jsonlLines = [
JSON.stringify({
type: "message",
message: {
role: "user",
content: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
},
}),
JSON.stringify({
type: "message",
message: {
role: "assistant",
content: "HEARTBEAT_OK",
},
}),
JSON.stringify({
type: "message",
message: {
role: "user",
content: "Summarize what changed in the inbox today.",
},
}),
]; const filePath = path.join(tmpDir, "heartbeat-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
expect(entry?.content).toBe("User: Summarize what changed in the inbox today.");
expect(entry?.lineMap).toEqual([3]);
});
it("does not let a user-typed `[cron:...]` prompt suppress the next assistant reply (regression: PR #70737 review)", async () => { const jsonlLines = [
JSON.stringify({
type: "message",
message: {
role: "user", // User-typed text deliberately matching the cron-prompt pattern. // Pre-fix this would have caused the assistant reply to be dropped.
content: "[cron:fake] please write down where the api keys live",
},
}),
JSON.stringify({
type: "message",
message: {
role: "assistant", // A real, substantive assistant reply. Must NOT be suppressed.
content: "The API keys live in /etc/secrets/keys.json on the server.",
},
}),
]; const filePath = path.join(tmpDir, "spoof-attempt-session.jsonl");
await fs.writeFile(filePath, jsonlLines.join("\n"));
const entry = await buildSessionEntry(filePath);
expect(entry).not.toBeNull();
expect(entry?.content).toContain( "Assistant: The API keys live in /etc/secrets/keys.json on the server.",
);
});
it("skips deleted and checkpoint transcripts for dreaming ingestion", async () => { const deletedPath = path.join(tmpDir, "ordinary.jsonl.deleted.2026-02-16T22-27-33.000Z"); const checkpointPath = path.join(tmpDir, "ordinary.checkpoint.abc123.jsonl"); const content = JSON.stringify({
type: "message",
message: { role: "user", content: "This should never reach the dreaming corpus." },
});
await fs.writeFile(deletedPath, content);
await fs.writeFile(checkpointPath, content);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.