refactor: drop file-era transcript fixture coverage

This commit is contained in:
Peter Steinberger
2026-05-09 07:13:54 +01:00
parent 51e63f7681
commit 31dcfa362e
7 changed files with 28 additions and 107 deletions

View File

@@ -76,8 +76,8 @@ interfaces that still look like the old file world:
JSON as possible write targets.
- Agent-owned tables live in per-agent SQLite databases. The global DB keeps
registry/control-plane rows; transcript identity is a canonical
`sqlite-transcript://<agent>/<session>.jsonl` locator derived from the
per-agent transcript rows.
`sqlite-transcript://<agent>/<session>` locator derived from the per-agent
transcript rows.
- Doctor already imports several legacy files. The cleanup is to make that a
single explicit migration implementation that doctor calls, with a durable
migration report.
@@ -208,8 +208,8 @@ The remaining cleanup is mostly consolidation and deletion:
SQLite; JSONL is now a legacy doctor input or in-memory export
encoding, not a runtime state file.
- Runtime session path resolution now canonicalizes active sessions to
`sqlite-transcript://<agent>/<session>.jsonl` locators. Legacy absolute JSONL
paths are doctor migration inputs instead of active runtime identity.
`sqlite-transcript://<agent>/<session>` locators. Legacy absolute JSONL paths
are doctor migration inputs instead of active runtime identity.
- Gateway transcript-key lookup compares canonical transcript locators directly
and no longer realpaths or stats transcript filenames.
- Automatic compaction transcript rotation writes successor transcript rows
@@ -248,10 +248,9 @@ The remaining cleanup is mostly consolidation and deletion:
SQLite transcript rows directly. Runtime callers pass canonical SQLite
locators, not writable `.jsonl` paths.
- Fresh runtime session rows now use virtual
`sqlite-transcript://<agent>/<session>.jsonl` locators instead of fake
`sqlite-transcript://<agent>/<session>` locators instead of fake
`agents/<agentId>/sessions/*.jsonl` paths. The old path builders remain for
doctor imports, explicit debug/export artifacts, and path-compatibility
tests.
doctor imports and explicit debug/export artifacts.
- Starting a new persisted transcript session now always allocates a fresh
SQLite locator. The session manager no longer reuses a previous file-era
transcript path as the identity for the new session.
@@ -283,9 +282,9 @@ The remaining cleanup is mostly consolidation and deletion:
classification helpers; transcript filtering now derives from SQLite row
metadata during entry construction.
- Memory-host and QMD session-export tests default to virtual
`sqlite-transcript://<agent>/<session>.jsonl` locators. Old
`sqlite-transcript://<agent>/<session>` locators. Old
`agents/<agentId>/sessions/*.jsonl` paths stay covered only where a test is
intentionally proving legacy path compatibility.
intentionally proving doctor/import/export compatibility.
- QA-lab raw session inspection now uses `sessions.list` through the gateway
instead of reading `agents/qa/sessions/sessions.json`; MSteams feedback
appends directly to SQLite transcripts without fabricating a JSONL path.

View File

@@ -181,18 +181,6 @@ function expectUsageFields(
expect(record.total ?? record.totalTokens).toBe(expected.total);
}
function mockCallArg(mock: unknown, callIndex: number, argIndex: number, label: string) {
const calls = (mock as { mock?: { calls?: unknown[][] } }).mock?.calls;
if (!Array.isArray(calls)) {
throw new Error(`Expected ${label} mock calls`);
}
const call = calls[callIndex];
if (!call) {
throw new Error(`Expected ${label} call ${callIndex + 1}`);
}
return call[argIndex];
}
function findAgentEvent(
mock: unknown,
params: { stream: string; phase?: string; itemId?: string; name?: string },
@@ -1439,37 +1427,26 @@ describe("CodexAppServerEventProjector", () => {
);
expect(openSpy).not.toHaveBeenCalled();
const beforePayload = requireRecord(
mockCallArg(beforeCompaction, 0, 0, "beforeCompaction"),
"before payload",
expect(beforeCompaction).toHaveBeenCalledWith(
expect.objectContaining({
messageCount: 1,
messages: [expect.objectContaining({ role: "assistant" })],
}),
expect.objectContaining({
runId: "run-1",
sessionId: "session-1",
}),
);
expect(beforePayload.messageCount).toBe(1);
expect(String(beforePayload.sessionFile)).toMatch(
/^sqlite-transcript:\/\/main\/session-1-.+\.jsonl$/u,
expect(afterCompaction).toHaveBeenCalledWith(
expect.objectContaining({
messageCount: 1,
compactedCount: -1,
}),
expect.objectContaining({
runId: "run-1",
sessionId: "session-1",
}),
);
const beforeMessages = requireArray(beforePayload.messages, "before messages");
expect(requireRecord(beforeMessages[0], "before message").role).toBe("assistant");
const beforeContext = requireRecord(
mockCallArg(beforeCompaction, 0, 1, "beforeCompaction"),
"before context",
);
expect(beforeContext.runId).toBe("run-1");
expect(beforeContext.sessionId).toBe("session-1");
const afterPayload = requireRecord(
mockCallArg(afterCompaction, 0, 0, "afterCompaction"),
"after payload",
);
expect(afterPayload.messageCount).toBe(1);
expect(afterPayload.compactedCount).toBe(-1);
expect(String(afterPayload.sessionFile)).toMatch(
/^sqlite-transcript:\/\/main\/session-1-.+\.jsonl$/u,
);
const afterContext = requireRecord(
mockCallArg(afterCompaction, 0, 1, "afterCompaction"),
"after context",
);
expect(afterContext.runId).toBe("run-1");
expect(afterContext.sessionId).toBe("session-1");
});
it("projects codex hook started and completed notifications into agent events", async () => {

View File

@@ -274,7 +274,6 @@ describe("generateVoiceResponse", () => {
agentId: "main",
sandboxSessionKey: "agent:main:voice:15550001111",
workspaceDir: "/tmp/openclaw/workspace/main",
sessionFile: expect.stringMatching(/^sqlite-transcript:\/\/main\/.+\.jsonl$/),
}),
);
});
@@ -312,7 +311,6 @@ describe("generateVoiceResponse", () => {
agentId: "voice",
sandboxSessionKey: "agent:voice:voice:15550001111",
workspaceDir: "/tmp/openclaw/workspace/voice",
sessionFile: expect.stringMatching(/^sqlite-transcript:\/\/voice\/.+\.jsonl$/),
}),
);
});

View File

@@ -3,7 +3,6 @@ export {
HEARTBEAT_TOKEN,
SILENT_REPLY_TOKEN,
hasInterSessionUserProvenance,
isCompactionCheckpointTranscriptFileName,
isCronRunSessionKey,
isExecCompletionEvent,
isHeartbeatUserMessage,

View File

@@ -48,7 +48,6 @@ export {
} from "../../../../src/config/config.js";
export type { OpenClawConfig } from "../../../../src/config/config.js";
export { resolveStateDir } from "../../../../src/config/paths.js";
export { isCompactionCheckpointTranscriptFileName } from "../../../../src/config/sessions/artifacts.js";
export {
listSqliteSessionTranscripts,
loadSqliteSessionTranscriptEvents,

View File

@@ -194,33 +194,9 @@ describe("buildSessionTranscriptEntry", () => {
expect(entry.lineMap).toEqual([]);
});
it("skips checkpoint artifacts so snapshots do not double-index session content", async () => {
const checkpointPath = path.join(
tmpDir,
"agents",
"main",
"sessions",
"ordinary.checkpoint.11111111-1111-4111-8111-111111111111.jsonl",
);
seedTranscript({
sessionId: "ordinary.checkpoint.11111111-1111-4111-8111-111111111111",
transcriptPath: checkpointPath,
events: [
{
type: "message",
message: { role: "user", content: "Archived hello" },
},
],
});
await expect(buildSessionTranscriptEntry(checkpointPath)).resolves.toBeNull();
});
it("keeps cron-run deleted archives opaque when the live session store entry is gone", async () => {
const archivePath = path.join(tmpDir, "cron-run.jsonl.deleted.2026-02-16T22-27-33.000Z");
it("keeps cron-run transcripts opaque when the live session store entry is gone", async () => {
const transcriptRef = seedTranscript({
sessionId: "cron-run-deleted",
transcriptPath: archivePath,
events: [
{
type: "message",
@@ -243,11 +219,9 @@ describe("buildSessionTranscriptEntry", () => {
expect(entry.generatedByCronRun).toBe(true);
});
it("keeps cron-run reset archives opaque when session metadata preserves the cron key", async () => {
const archivePath = path.join(tmpDir, "cron-run.jsonl.reset.2026-02-16T22-26-33.000Z");
it("keeps cron-run transcripts opaque when session metadata preserves the cron key", async () => {
const transcriptRef = seedTranscript({
sessionId: "cron-run-reset",
transcriptPath: archivePath,
events: [
{
type: "session-meta",

View File

@@ -5,7 +5,6 @@ import {
HEARTBEAT_PROMPT,
HEARTBEAT_TOKEN,
hasInterSessionUserProvenance,
isCompactionCheckpointTranscriptFileName,
isCronRunSessionKey,
isExecCompletionEvent,
isHeartbeatUserMessage,
@@ -60,16 +59,6 @@ export type SessionTranscriptDeltaStats = {
updatedAt: number;
};
function shouldSkipTranscriptFileForDreaming(absPath: string): boolean {
const fileName = path.basename(absPath);
// Compaction checkpoints are always skipped: they are derived snapshots of an
// active session and would double-index the same content.
if (isCompactionCheckpointTranscriptFileName(fileName)) {
return true;
}
return false;
}
function isDreamingNarrativeBootstrapRecord(record: unknown): boolean {
if (!record || typeof record !== "object" || Array.isArray(record)) {
return false;
@@ -208,7 +197,6 @@ export function sessionPathForTranscript(absPath: string): string {
export function resolveSessionTranscriptScope(locator: string): {
agentId: string;
sessionId: string;
transcriptPath?: string;
} | null {
const sqliteRef = parseSqliteSessionTranscriptRef(locator);
if (sqliteRef) {
@@ -481,19 +469,6 @@ export async function buildSessionTranscriptEntry(
(total, entry) => total + JSON.stringify(entry.event).length + 1,
0,
);
if (shouldSkipTranscriptFileForDreaming(absPath)) {
return {
path: sessionPathForTranscript(absPath),
absPath,
mtimeMs,
size,
messageCount,
hash: hashText("\n\n"),
content: "",
lineMap: [],
messageTimestampsMs: [],
};
}
const collected: string[] = [];
const lineMap: number[] = [];
const messageTimestampsMs: number[] = [];