mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-22 06:08:13 +00:00
refactor: fork sessions with sqlite transcript locators
This commit is contained in:
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createSqliteSessionTranscriptLocator } from "../../config/sessions/paths.js";
|
||||
import {
|
||||
loadSqliteSessionTranscriptEvents,
|
||||
replaceSqliteSessionTranscriptEvents,
|
||||
@@ -61,10 +62,9 @@ describe("resolveParentForkTokenCountRuntime", () => {
|
||||
it("falls back to recent transcript usage when cached totals are stale", async () => {
|
||||
const root = await makeRoot("openclaw-parent-fork-token-estimate-");
|
||||
useStateRoot(root);
|
||||
const transcriptDir = path.join(root, "transcript-fixtures");
|
||||
|
||||
const sessionId = "parent-overflow-transcript";
|
||||
const sessionFile = path.join(transcriptDir, `${sessionId}.jsonl`);
|
||||
const sessionFile = createSqliteSessionTranscriptLocator({ agentId: "main", sessionId });
|
||||
const events: unknown[] = [
|
||||
{
|
||||
type: "session",
|
||||
@@ -118,10 +118,9 @@ describe("resolveParentForkTokenCountRuntime", () => {
|
||||
it("falls back to a conservative byte estimate when stale parent transcript has no usage", async () => {
|
||||
const root = await makeRoot("openclaw-parent-fork-byte-estimate-");
|
||||
useStateRoot(root);
|
||||
const transcriptDir = path.join(root, "transcript-fixtures");
|
||||
|
||||
const sessionId = "parent-no-usage-transcript";
|
||||
const sessionFile = path.join(transcriptDir, `${sessionId}.jsonl`);
|
||||
const sessionFile = createSqliteSessionTranscriptLocator({ agentId: "main", sessionId });
|
||||
const events: unknown[] = [
|
||||
{
|
||||
type: "session",
|
||||
@@ -160,10 +159,9 @@ describe("resolveParentForkTokenCountRuntime", () => {
|
||||
it("uses the latest usage snapshot instead of tail aggregates for parent fork checks", async () => {
|
||||
const root = await makeRoot("openclaw-parent-fork-latest-usage-");
|
||||
useStateRoot(root);
|
||||
const transcriptDir = path.join(root, "transcript-fixtures");
|
||||
|
||||
const sessionId = "parent-multiple-usage-transcript";
|
||||
const sessionFile = path.join(transcriptDir, `${sessionId}.jsonl`);
|
||||
const sessionFile = createSqliteSessionTranscriptLocator({ agentId: "main", sessionId });
|
||||
seedTranscript({
|
||||
sessionId,
|
||||
transcriptPath: sessionFile,
|
||||
@@ -210,10 +208,9 @@ describe("resolveParentForkTokenCountRuntime", () => {
|
||||
it("keeps parent fork checks conservative for content appended after latest usage", async () => {
|
||||
const root = await makeRoot("openclaw-parent-fork-post-usage-tail-");
|
||||
useStateRoot(root);
|
||||
const transcriptDir = path.join(root, "transcript-fixtures");
|
||||
|
||||
const sessionId = "parent-post-usage-tail";
|
||||
const sessionFile = path.join(transcriptDir, `${sessionId}.jsonl`);
|
||||
const sessionFile = createSqliteSessionTranscriptLocator({ agentId: "main", sessionId });
|
||||
seedTranscript({
|
||||
sessionId,
|
||||
transcriptPath: sessionFile,
|
||||
@@ -261,11 +258,13 @@ describe("forkSessionFromParentRuntime", () => {
|
||||
it("forks the active branch without synchronously opening the session manager", async () => {
|
||||
const root = await makeRoot("openclaw-parent-fork-");
|
||||
useStateRoot(root);
|
||||
const transcriptDir = path.join(root, "transcript-fixtures");
|
||||
const cwd = path.join(root, "workspace");
|
||||
await fs.mkdir(cwd);
|
||||
const parentSessionId = "parent-session";
|
||||
const parentSessionFile = path.join(transcriptDir, `${parentSessionId}.jsonl`);
|
||||
const parentSessionFile = createSqliteSessionTranscriptLocator({
|
||||
agentId: "main",
|
||||
sessionId: parentSessionId,
|
||||
});
|
||||
const events = [
|
||||
{
|
||||
type: "session",
|
||||
@@ -319,14 +318,16 @@ describe("forkSessionFromParentRuntime", () => {
|
||||
if (fork === null) {
|
||||
throw new Error("Expected forked session");
|
||||
}
|
||||
expect(fork.sessionFile).toBe(fork.sessionId);
|
||||
expect(fork.sessionFile).toBe(
|
||||
createSqliteSessionTranscriptLocator({ agentId: "main", sessionId: fork.sessionId }),
|
||||
);
|
||||
expect(fork.sessionId).not.toBe(parentSessionId);
|
||||
const forkedEntries = readTranscript("main", fork.sessionId) as Array<Record<string, unknown>>;
|
||||
expect(forkedEntries[0]).toMatchObject({
|
||||
type: "session",
|
||||
id: fork.sessionId,
|
||||
cwd,
|
||||
parentSession: path.resolve(parentSessionFile),
|
||||
parentSession: parentSessionFile,
|
||||
});
|
||||
expect(forkedEntries.map((entry) => entry.type)).toEqual([
|
||||
"session",
|
||||
@@ -344,9 +345,11 @@ describe("forkSessionFromParentRuntime", () => {
|
||||
it("creates a header-only child when the parent has no entries", async () => {
|
||||
const root = await makeRoot("openclaw-parent-fork-empty-");
|
||||
useStateRoot(root);
|
||||
const transcriptDir = path.join(root, "transcript-fixtures");
|
||||
const parentSessionId = "parent-empty";
|
||||
const parentSessionFile = path.join(transcriptDir, `${parentSessionId}.jsonl`);
|
||||
const parentSessionFile = createSqliteSessionTranscriptLocator({
|
||||
agentId: "main",
|
||||
sessionId: parentSessionId,
|
||||
});
|
||||
seedTranscript({
|
||||
sessionId: parentSessionId,
|
||||
transcriptPath: parentSessionFile,
|
||||
@@ -378,7 +381,7 @@ describe("forkSessionFromParentRuntime", () => {
|
||||
expect(entries[0]).toMatchObject({
|
||||
type: "session",
|
||||
id: fork.sessionId,
|
||||
parentSession: path.resolve(parentSessionFile),
|
||||
parentSession: parentSessionFile,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import crypto from "node:crypto";
|
||||
import path from "node:path";
|
||||
import {
|
||||
CURRENT_SESSION_VERSION,
|
||||
migrateSessionEntries,
|
||||
@@ -9,8 +8,8 @@ import {
|
||||
} from "../../agents/transcript/session-transcript-contract.js";
|
||||
import { derivePromptTokens } from "../../agents/usage.js";
|
||||
import {
|
||||
resolveSessionFilePath,
|
||||
resolveSessionFilePathOptions,
|
||||
createSqliteSessionTranscriptLocator,
|
||||
isSqliteSessionTranscriptLocator,
|
||||
} from "../../config/sessions/paths.js";
|
||||
import {
|
||||
loadSqliteSessionTranscriptEvents,
|
||||
@@ -76,14 +75,10 @@ async function estimateParentTranscriptTokensFromSqlite(params: {
|
||||
|
||||
function resolveForkParentSessionFile(parentEntry: StoreSessionEntry, agentId: string): string {
|
||||
const sessionFile = parentEntry.sessionFile?.trim();
|
||||
if (sessionFile && path.isAbsolute(sessionFile)) {
|
||||
return path.resolve(sessionFile);
|
||||
if (sessionFile && isSqliteSessionTranscriptLocator(sessionFile)) {
|
||||
return sessionFile;
|
||||
}
|
||||
return resolveSessionFilePath(
|
||||
parentEntry.sessionId,
|
||||
parentEntry,
|
||||
resolveSessionFilePathOptions({ agentId }),
|
||||
);
|
||||
return createSqliteSessionTranscriptLocator({ agentId, sessionId: parentEntry.sessionId });
|
||||
}
|
||||
|
||||
export async function resolveParentForkTokenCountRuntime(params: {
|
||||
@@ -248,7 +243,10 @@ async function writeForkHeaderOnly(params: {
|
||||
}): Promise<{ sessionId: string; sessionFile: string }> {
|
||||
const sessionId = crypto.randomUUID();
|
||||
const timestamp = new Date().toISOString();
|
||||
const sessionFile = sessionId;
|
||||
const sessionFile = createSqliteSessionTranscriptLocator({
|
||||
agentId: params.agentId,
|
||||
sessionId,
|
||||
});
|
||||
const header = {
|
||||
type: "session",
|
||||
version: CURRENT_SESSION_VERSION,
|
||||
@@ -272,7 +270,10 @@ async function writeBranchedSession(params: {
|
||||
}): Promise<{ sessionId: string; sessionFile: string }> {
|
||||
const sessionId = crypto.randomUUID();
|
||||
const timestamp = new Date().toISOString();
|
||||
const sessionFile = sessionId;
|
||||
const sessionFile = createSqliteSessionTranscriptLocator({
|
||||
agentId: params.source.agentId,
|
||||
sessionId,
|
||||
});
|
||||
const pathWithoutLabels = params.source.branchEntries.filter((entry) => entry.type !== "label");
|
||||
const pathEntryIds = new Set(pathWithoutLabels.map((entry) => entry.id));
|
||||
const labelEntries = buildBranchLabelEntries({
|
||||
|
||||
Reference in New Issue
Block a user