mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-14 08:04:17 +00:00
fix: preserve sqlite transcript locator identity
This commit is contained in:
@@ -264,6 +264,9 @@ The remaining cleanup is mostly consolidation and deletion:
|
||||
- Bootstrap continuation detection now checks SQLite transcript locators through
|
||||
`hasCompletedBootstrapTranscriptTurn`; it no longer exposes a
|
||||
session-file-shaped helper name.
|
||||
- Embedded-runner tests now use virtual SQLite transcript locators, and opening
|
||||
a new locator without a duplicate `sessionId` uses the locator's session id
|
||||
as the database row identity.
|
||||
- Memory indexing helpers now use SQLite transcript terminology end to end:
|
||||
host exports list/build session transcript entries, targeted sync queues
|
||||
`sessionTranscripts`, and QMD/builtin indexers no longer expose session-file
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import path from "node:path";
|
||||
import "./test-helpers/fast-coding-tools.js";
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createSqliteSessionTranscriptLocator,
|
||||
parseSqliteSessionTranscriptLocator,
|
||||
} from "../config/sessions/paths.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
|
||||
import {
|
||||
buildEmbeddedRunnerAssistant,
|
||||
cleanupEmbeddedPiRunnerTestWorkspace,
|
||||
@@ -158,18 +163,27 @@ let agentDir: string;
|
||||
let workspaceDir: string;
|
||||
let sessionCounter = 0;
|
||||
let runCounter = 0;
|
||||
let previousStateDir: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
vi.useRealTimers();
|
||||
vi.resetModules();
|
||||
installRunEmbeddedMocks();
|
||||
({ runEmbeddedPiAgent } = await import("./pi-embedded-runner/run.js"));
|
||||
({ SessionManager } = await import("./transcript/session-transcript-contract.js"));
|
||||
e2eWorkspace = await createEmbeddedPiRunnerTestWorkspace("openclaw-embedded-agent-");
|
||||
({ agentDir, workspaceDir } = e2eWorkspace);
|
||||
previousStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
process.env.OPENCLAW_STATE_DIR = e2eWorkspace.stateDir;
|
||||
({ runEmbeddedPiAgent } = await import("./pi-embedded-runner/run.js"));
|
||||
({ SessionManager } = await import("./transcript/session-transcript-contract.js"));
|
||||
}, 180_000);
|
||||
|
||||
afterAll(async () => {
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
if (previousStateDir === undefined) {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
} else {
|
||||
process.env.OPENCLAW_STATE_DIR = previousStateDir;
|
||||
}
|
||||
await cleanupEmbeddedPiRunnerTestWorkspace(e2eWorkspace);
|
||||
e2eWorkspace = undefined;
|
||||
});
|
||||
@@ -195,8 +209,13 @@ beforeEach(() => {
|
||||
|
||||
const nextSessionFile = () => {
|
||||
sessionCounter += 1;
|
||||
return path.join(workspaceDir, `session-${sessionCounter}.jsonl`);
|
||||
return createSqliteSessionTranscriptLocator({
|
||||
agentId: "test",
|
||||
sessionId: `session-${sessionCounter}`,
|
||||
});
|
||||
};
|
||||
const sessionIdFromLocator = (sessionFile: string) =>
|
||||
parseSqliteSessionTranscriptLocator(sessionFile)?.sessionId ?? "session:test";
|
||||
const nextRunId = (prefix = "run-embedded-test") => `${prefix}-${++runCounter}`;
|
||||
const nextSessionKey = () => `agent:test:embedded:${nextRunId("session-key")}`;
|
||||
|
||||
@@ -220,7 +239,7 @@ const runWithOrphanedSingleUserMessage = async (text: string, sessionKey: string
|
||||
|
||||
const cfg = createEmbeddedPiRunnerOpenAiConfig(["mock-1"]);
|
||||
return await runEmbeddedPiAgent({
|
||||
sessionId: "session:test",
|
||||
sessionId: sessionIdFromLocator(sessionFile),
|
||||
sessionKey,
|
||||
sessionFile,
|
||||
workspaceDir,
|
||||
@@ -272,7 +291,7 @@ const runDefaultEmbeddedTurn = async (sessionFile: string, prompt: string, sessi
|
||||
}),
|
||||
);
|
||||
await runEmbeddedPiAgent({
|
||||
sessionId: "session:test",
|
||||
sessionId: sessionIdFromLocator(sessionFile),
|
||||
sessionKey,
|
||||
sessionFile,
|
||||
workspaceDir,
|
||||
@@ -482,6 +501,7 @@ describe("runEmbeddedPiAgent", () => {
|
||||
|
||||
it("disposes bundle MCP once when a one-shot local run completes", async () => {
|
||||
const sessionFile = nextSessionFile();
|
||||
const sessionId = sessionIdFromLocator(sessionFile);
|
||||
const cfg = createEmbeddedPiRunnerOpenAiConfig(["mock-1"]);
|
||||
const sessionKey = nextSessionKey();
|
||||
runEmbeddedAttemptMock.mockResolvedValueOnce(
|
||||
@@ -494,7 +514,7 @@ describe("runEmbeddedPiAgent", () => {
|
||||
);
|
||||
|
||||
await runEmbeddedPiAgent({
|
||||
sessionId: "session:test",
|
||||
sessionId,
|
||||
sessionKey,
|
||||
sessionFile,
|
||||
workspaceDir,
|
||||
@@ -511,12 +531,13 @@ describe("runEmbeddedPiAgent", () => {
|
||||
|
||||
expect(runEmbeddedAttemptMock).toHaveBeenCalledTimes(1);
|
||||
expect(disposeSessionMcpRuntimeMock).toHaveBeenCalledTimes(1);
|
||||
expect(disposeSessionMcpRuntimeMock).toHaveBeenCalledWith("session:test");
|
||||
expect(disposeSessionMcpRuntimeMock).toHaveBeenCalledWith(sessionId);
|
||||
});
|
||||
|
||||
it("preserves bundle MCP state across retries within one local run", async () => {
|
||||
refreshRuntimeAuthOnFirstPromptError = true;
|
||||
const sessionFile = nextSessionFile();
|
||||
const sessionId = sessionIdFromLocator(sessionFile);
|
||||
const cfg = createEmbeddedPiRunnerOpenAiConfig(["mock-1"]);
|
||||
const sessionKey = nextSessionKey();
|
||||
runEmbeddedAttemptMock
|
||||
@@ -537,7 +558,7 @@ describe("runEmbeddedPiAgent", () => {
|
||||
});
|
||||
|
||||
const result = await runEmbeddedPiAgent({
|
||||
sessionId: "session:test",
|
||||
sessionId,
|
||||
sessionKey,
|
||||
sessionFile,
|
||||
workspaceDir,
|
||||
@@ -555,7 +576,7 @@ describe("runEmbeddedPiAgent", () => {
|
||||
expect(runEmbeddedAttemptMock).toHaveBeenCalledTimes(2);
|
||||
expect(result.payloads?.[0]).toMatchObject({ text: "ok" });
|
||||
expect(disposeSessionMcpRuntimeMock).toHaveBeenCalledTimes(1);
|
||||
expect(disposeSessionMcpRuntimeMock).toHaveBeenCalledWith("session:test");
|
||||
expect(disposeSessionMcpRuntimeMock).toHaveBeenCalledWith(sessionId);
|
||||
});
|
||||
|
||||
it("retries a planning-only GPT turn once with an act-now steer", async () => {
|
||||
@@ -593,7 +614,7 @@ describe("runEmbeddedPiAgent", () => {
|
||||
});
|
||||
|
||||
const result = await runEmbeddedPiAgent({
|
||||
sessionId: "session:test",
|
||||
sessionId: sessionIdFromLocator(sessionFile),
|
||||
sessionKey,
|
||||
sessionFile,
|
||||
workspaceDir,
|
||||
@@ -622,7 +643,7 @@ describe("runEmbeddedPiAgent", () => {
|
||||
);
|
||||
await expect(
|
||||
runEmbeddedPiAgent({
|
||||
sessionId: "session:test",
|
||||
sessionId: sessionIdFromLocator(sessionFile),
|
||||
sessionKey,
|
||||
sessionFile,
|
||||
workspaceDir,
|
||||
@@ -669,7 +690,6 @@ describe("runEmbeddedPiAgent", () => {
|
||||
usage: createMockUsage(1, 1),
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
await runDefaultEmbeddedTurn(sessionFile, "hello", sessionKey);
|
||||
|
||||
const messages = await readSessionMessages(sessionFile);
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { EmbeddedRunAttemptResult } from "../pi-embedded-runner/run/types.j
|
||||
export type EmbeddedPiRunnerTestWorkspace = {
|
||||
tempRoot: string;
|
||||
agentDir: string;
|
||||
stateDir: string;
|
||||
workspaceDir: string;
|
||||
};
|
||||
|
||||
@@ -17,10 +18,12 @@ export async function createEmbeddedPiRunnerTestWorkspace(
|
||||
): Promise<EmbeddedPiRunnerTestWorkspace> {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||
const agentDir = path.join(tempRoot, "agent");
|
||||
const stateDir = path.join(tempRoot, "state");
|
||||
const workspaceDir = path.join(tempRoot, "workspace");
|
||||
await fs.mkdir(agentDir, { recursive: true });
|
||||
await fs.mkdir(stateDir, { recursive: true });
|
||||
await fs.mkdir(workspaceDir, { recursive: true });
|
||||
return { tempRoot, agentDir, workspaceDir };
|
||||
return { tempRoot, agentDir, stateDir, workspaceDir };
|
||||
}
|
||||
|
||||
export async function cleanupEmbeddedPiRunnerTestWorkspace(
|
||||
|
||||
@@ -127,6 +127,33 @@ describe("TranscriptSessionManager", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses the virtual sqlite transcript locator session id when no explicit id is supplied", async () => {
|
||||
await makeTempSessionFile();
|
||||
const sessionFile = createSqliteSessionTranscriptLocator({
|
||||
agentId: "main",
|
||||
sessionId: "locator-session",
|
||||
});
|
||||
|
||||
const sessionManager = openTranscriptSessionManager({
|
||||
sessionFile,
|
||||
cwd: "/tmp/workspace",
|
||||
});
|
||||
sessionManager.appendMessage({ role: "user", content: "seed", timestamp: 1 });
|
||||
|
||||
expect(sessionManager.getSessionId()).toBe("locator-session");
|
||||
expect(readSessionEntries(sessionFile)).toMatchObject([
|
||||
{
|
||||
type: "session",
|
||||
id: "locator-session",
|
||||
cwd: "/tmp/workspace",
|
||||
},
|
||||
{
|
||||
type: "message",
|
||||
message: { role: "user", content: "seed" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("creates, branches, lists, and forks default sessions with virtual sqlite locators", async () => {
|
||||
await makeTempSessionFile();
|
||||
const sessionManager = SessionManager.create("/tmp/sqlite-workspace");
|
||||
|
||||
@@ -140,7 +140,7 @@ function loadTranscriptState(params: { sessionFile: string; sessionId?: string;
|
||||
}
|
||||
|
||||
const header = createSessionHeader({
|
||||
id: params.sessionId,
|
||||
id: params.sessionId ?? scope.sessionId,
|
||||
cwd: params.cwd ?? process.cwd(),
|
||||
});
|
||||
const state = new TranscriptState({ header, entries: [] });
|
||||
|
||||
Reference in New Issue
Block a user