refactor(session): centralize transcript path option resolution

This commit is contained in:
Peter Steinberger
2026-02-22 12:00:54 +01:00
parent 37f12eb7ee
commit b77e53da67
5 changed files with 58 additions and 14 deletions

View File

@@ -8,6 +8,7 @@ import { hasNonzeroUsage } from "../../agents/usage.js";
import {
resolveAgentIdFromSessionKey,
resolveSessionFilePath,
resolveSessionFilePathOptions,
resolveSessionTranscriptPath,
type SessionEntry,
updateSessionStore,
@@ -324,7 +325,11 @@ export async function runReplyAgent(params: {
defaultRuntime.error(buildLogMessage(nextSessionId));
if (cleanupTranscripts && prevSessionId) {
const transcriptCandidates = new Set<string>();
const resolved = resolveSessionFilePath(prevSessionId, prevEntry, { agentId });
const resolved = resolveSessionFilePath(
prevSessionId,
prevEntry,
resolveSessionFilePathOptions({ agentId, storePath }),
);
if (resolved) {
transcriptCandidates.add(resolved);
}

View File

@@ -6,6 +6,7 @@ import { SessionManager } from "@mariozechner/pi-coding-agent";
import {
resolveDefaultSessionStorePath,
resolveSessionFilePath,
resolveSessionFilePathOptions,
} from "../../config/sessions/paths.js";
import { loadSessionStore } from "../../config/sessions/store.js";
import type { SessionEntry } from "../../config/sessions/types.js";
@@ -126,10 +127,11 @@ export async function buildExportSessionReply(params: HandleCommandsParams): Pro
let sessionFile: string;
try {
sessionFile = resolveSessionFilePath(entry.sessionId, entry, {
agentId: params.agentId,
sessionsDir: path.dirname(storePath),
});
sessionFile = resolveSessionFilePath(
entry.sessionId,
entry,
resolveSessionFilePathOptions({ agentId: params.agentId, storePath }),
);
} catch (err) {
return {
text: `❌ Failed to resolve session file: ${err instanceof Error ? err.message : String(err)}`,

View File

@@ -5,10 +5,12 @@ import { telegramPlugin } from "../../extensions/telegram/src/channel.js";
import "../cron/isolated-agent.mocks.js";
import { setTelegramRuntime } from "../../extensions/telegram/src/runtime.js";
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
import * as cliRunnerModule from "../agents/cli-runner.js";
import { loadModelCatalog } from "../agents/model-catalog.js";
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
import type { OpenClawConfig } from "../config/config.js";
import * as configModule from "../config/config.js";
import * as sessionsModule from "../config/sessions.js";
import { emitAgentEvent, onAgentEvent } from "../infra/agent-events.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { createPluginRuntime } from "../plugins/runtime/index.js";
@@ -25,6 +27,7 @@ const runtime: RuntimeEnv = {
};
const configSpy = vi.spyOn(configModule, "loadConfig");
const runCliAgentSpy = vi.spyOn(cliRunnerModule, "runCliAgent");
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
return withTempHomeBase(fn, { prefix: "openclaw-agent-" });
@@ -64,6 +67,13 @@ function writeSessionStoreSeed(
beforeEach(() => {
vi.clearAllMocks();
runCliAgentSpy.mockResolvedValue({
payloads: [{ text: "ok" }],
meta: {
durationMs: 5,
agentMeta: { sessionId: "s", provider: "p", model: "m" },
},
} as never);
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
payloads: [{ text: "ok" }],
meta: {
@@ -131,6 +141,28 @@ describe("agentCommand", () => {
});
});
it("resolves resumed session transcript path from custom session store directory", async () => {
await withTempHome(async (home) => {
const customStoreDir = path.join(home, "custom-state");
const store = path.join(customStoreDir, "sessions.json");
writeSessionStoreSeed(store, {});
mockConfig(home, store);
const resolveSessionFilePathSpy = vi.spyOn(sessionsModule, "resolveSessionFilePath");
await agentCommand({ message: "resume me", sessionId: "session-custom-123" }, runtime);
const matchingCall = resolveSessionFilePathSpy.mock.calls.find(
(call) => call[0] === "session-custom-123",
);
expect(matchingCall?.[2]).toEqual(
expect.objectContaining({
agentId: "main",
sessionsDir: customStoreDir,
}),
);
});
});
it("does not duplicate agent events from embedded runs", async () => {
await withTempHome(async (home) => {
const store = path.join(home, "sessions.json");

View File

@@ -1,4 +1,3 @@
import path from "node:path";
import {
listAgentIds,
resolveAgentDir,
@@ -45,6 +44,7 @@ import {
resolveAndPersistSessionFile,
resolveAgentIdFromSessionKey,
resolveSessionFilePath,
resolveSessionFilePathOptions,
resolveSessionTranscriptPath,
type SessionEntry,
updateSessionStore,
@@ -510,10 +510,11 @@ export async function agentCommand(
});
}
}
let sessionFile = resolveSessionFilePath(sessionId, sessionEntry, {
const sessionPathOpts = resolveSessionFilePathOptions({
agentId: sessionAgentId,
sessionsDir: path.dirname(storePath),
storePath,
});
let sessionFile = resolveSessionFilePath(sessionId, sessionEntry, sessionPathOpts);
if (sessionStore && sessionKey) {
const threadIdFromSessionKey = parseSessionThreadInfo(sessionKey).threadId;
const fallbackSessionFile = !sessionEntry?.sessionFile
@@ -529,8 +530,8 @@ export async function agentCommand(
sessionStore,
storePath,
sessionEntry,
agentId: sessionAgentId,
sessionsDir: path.dirname(storePath),
agentId: sessionPathOpts?.agentId,
sessionsDir: sessionPathOpts?.sessionsDir,
fallbackSessionFile,
});
sessionFile = resolvedSessionFile.sessionFile;

View File

@@ -8,6 +8,7 @@ import {
loadSessionStore,
resolveMainSessionKey,
resolveSessionFilePath,
resolveSessionFilePathOptions,
resolveSessionTranscriptsDirForAgent,
resolveStorePath,
} from "../config/sessions.js";
@@ -386,6 +387,7 @@ export async function noteStateIntegrity(
}
const store = loadSessionStore(storePath);
const sessionPathOpts = resolveSessionFilePathOptions({ agentId, storePath });
const entries = Object.entries(store).filter(([, entry]) => entry && typeof entry === "object");
if (entries.length > 0) {
const recent = entries
@@ -401,9 +403,7 @@ export async function noteStateIntegrity(
if (!sessionId) {
return false;
}
const transcriptPath = resolveSessionFilePath(sessionId, entry, {
agentId,
});
const transcriptPath = resolveSessionFilePath(sessionId, entry, sessionPathOpts);
return !existsFile(transcriptPath);
});
if (missing.length > 0) {
@@ -415,7 +415,11 @@ export async function noteStateIntegrity(
const mainKey = resolveMainSessionKey(cfg);
const mainEntry = store[mainKey];
if (mainEntry?.sessionId) {
const transcriptPath = resolveSessionFilePath(mainEntry.sessionId, mainEntry, { agentId });
const transcriptPath = resolveSessionFilePath(
mainEntry.sessionId,
mainEntry,
sessionPathOpts,
);
if (!existsFile(transcriptPath)) {
warnings.push(
`- Main session transcript missing (${shortenHomePath(transcriptPath)}). History will appear to reset.`,