mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-16 18:34:18 +00:00
refactor: drop file-era transcript fixture coverage
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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$/),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ export {
|
||||
HEARTBEAT_TOKEN,
|
||||
SILENT_REPLY_TOKEN,
|
||||
hasInterSessionUserProvenance,
|
||||
isCompactionCheckpointTranscriptFileName,
|
||||
isCronRunSessionKey,
|
||||
isExecCompletionEvent,
|
||||
isHeartbeatUserMessage,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
Reference in New Issue
Block a user