refactor(session): centralize transcript path option resolution
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)}`,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.`,
|
||||
|
||||
Reference in New Issue
Block a user