mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 17:51:22 +00:00
fix: use target agent for session exports
This commit is contained in:
143
src/auto-reply/reply/commands-export-session.test.ts
Normal file
143
src/auto-reply/reply/commands-export-session.test.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { HandleCommandsParams } from "./commands-types.js";
|
||||
|
||||
const hoisted = vi.hoisted(() => ({
|
||||
resolveDefaultSessionStorePathMock: vi.fn(() => "/tmp/target-store/sessions.json"),
|
||||
resolveSessionFilePathMock: vi.fn(() => "/tmp/target-store/session.jsonl"),
|
||||
resolveSessionFilePathOptionsMock: vi.fn(
|
||||
(params: { agentId: string; storePath: string }) => params,
|
||||
),
|
||||
loadSessionStoreMock: vi.fn(() => ({
|
||||
"agent:target:session": {
|
||||
sessionId: "session-1",
|
||||
updatedAt: 1,
|
||||
},
|
||||
})),
|
||||
resolveCommandsSystemPromptBundleMock: vi.fn(async () => ({
|
||||
systemPrompt: "system prompt",
|
||||
tools: [],
|
||||
skillsPrompt: "",
|
||||
bootstrapFiles: [],
|
||||
injectedFiles: [],
|
||||
sandboxRuntime: { sandboxed: false, mode: "off" },
|
||||
})),
|
||||
getEntriesMock: vi.fn(() => []),
|
||||
getHeaderMock: vi.fn(() => null),
|
||||
getLeafIdMock: vi.fn(() => null),
|
||||
writeFileSyncMock: vi.fn(),
|
||||
mkdirSyncMock: vi.fn(),
|
||||
existsSyncMock: vi.fn(() => true),
|
||||
}));
|
||||
|
||||
vi.mock("@mariozechner/pi-coding-agent", () => ({
|
||||
SessionManager: {
|
||||
open: vi.fn(() => ({
|
||||
getEntries: hoisted.getEntriesMock,
|
||||
getHeader: hoisted.getHeaderMock,
|
||||
getLeafId: hoisted.getLeafIdMock,
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../../config/sessions/paths.js", () => ({
|
||||
resolveDefaultSessionStorePath: hoisted.resolveDefaultSessionStorePathMock,
|
||||
resolveSessionFilePath: hoisted.resolveSessionFilePathMock,
|
||||
resolveSessionFilePathOptions: hoisted.resolveSessionFilePathOptionsMock,
|
||||
}));
|
||||
|
||||
vi.mock("../../config/sessions/store.js", () => ({
|
||||
loadSessionStore: hoisted.loadSessionStoreMock,
|
||||
}));
|
||||
|
||||
vi.mock("./commands-system-prompt.js", () => ({
|
||||
resolveCommandsSystemPromptBundle: hoisted.resolveCommandsSystemPromptBundleMock,
|
||||
}));
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
|
||||
return {
|
||||
...actual,
|
||||
existsSync: hoisted.existsSyncMock,
|
||||
mkdirSync: hoisted.mkdirSyncMock,
|
||||
writeFileSync: hoisted.writeFileSyncMock,
|
||||
readFileSync: vi.fn((filePath: string) => {
|
||||
if (String(filePath).endsWith("template.html")) {
|
||||
return "<html>{{CSS}}{{JS}}{{SESSION_DATA}}{{MARKED_JS}}{{HIGHLIGHT_JS}}</html>";
|
||||
}
|
||||
return "";
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
function makeParams(): HandleCommandsParams {
|
||||
return {
|
||||
cfg: {},
|
||||
ctx: {
|
||||
SessionKey: "agent:main:slash-session",
|
||||
},
|
||||
command: {
|
||||
commandBodyNormalized: "/export-session",
|
||||
isAuthorizedSender: true,
|
||||
senderIsOwner: true,
|
||||
senderId: "sender-1",
|
||||
channel: "telegram",
|
||||
surface: "telegram",
|
||||
ownerList: [],
|
||||
rawBodyNormalized: "/export-session",
|
||||
},
|
||||
sessionEntry: {
|
||||
sessionId: "session-1",
|
||||
updatedAt: 1,
|
||||
},
|
||||
sessionKey: "agent:target:session",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
directives: {},
|
||||
elevated: { enabled: true, allowed: true, failures: [] },
|
||||
defaultGroupActivation: () => "mention",
|
||||
resolvedVerboseLevel: "off",
|
||||
resolvedReasoningLevel: "off",
|
||||
resolveDefaultThinkingLevel: async () => undefined,
|
||||
provider: "openai",
|
||||
model: "gpt-5.4",
|
||||
contextTokens: 0,
|
||||
isGroup: false,
|
||||
} as unknown as HandleCommandsParams;
|
||||
}
|
||||
|
||||
describe("buildExportSessionReply", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
hoisted.resolveDefaultSessionStorePathMock.mockReturnValue("/tmp/target-store/sessions.json");
|
||||
hoisted.resolveSessionFilePathMock.mockReturnValue("/tmp/target-store/session.jsonl");
|
||||
hoisted.resolveSessionFilePathOptionsMock.mockImplementation(
|
||||
(params: { agentId: string; storePath: string }) => params,
|
||||
);
|
||||
hoisted.loadSessionStoreMock.mockReturnValue({
|
||||
"agent:target:session": {
|
||||
sessionId: "session-1",
|
||||
updatedAt: 1,
|
||||
},
|
||||
});
|
||||
hoisted.resolveCommandsSystemPromptBundleMock.mockResolvedValue({
|
||||
systemPrompt: "system prompt",
|
||||
tools: [],
|
||||
skillsPrompt: "",
|
||||
bootstrapFiles: [],
|
||||
injectedFiles: [],
|
||||
sandboxRuntime: { sandboxed: false, mode: "off" },
|
||||
});
|
||||
hoisted.existsSyncMock.mockReturnValue(true);
|
||||
});
|
||||
|
||||
it("resolves store and transcript paths from the target session agent", async () => {
|
||||
const { buildExportSessionReply } = await import("./commands-export-session.js");
|
||||
|
||||
await buildExportSessionReply(makeParams());
|
||||
|
||||
expect(hoisted.resolveDefaultSessionStorePathMock).toHaveBeenCalledWith("target");
|
||||
expect(hoisted.resolveSessionFilePathOptionsMock).toHaveBeenCalledWith({
|
||||
agentId: "target",
|
||||
storePath: "/tmp/target-store/sessions.json",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { loadSessionStore } from "../../config/sessions/store.js";
|
||||
import type { SessionEntry } from "../../config/sessions/types.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { resolveAgentIdFromSessionKey } from "../../routing/session-key.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
import { resolveCommandsSystemPromptBundle } from "./commands-system-prompt.js";
|
||||
import type { HandleCommandsParams } from "./commands-types.js";
|
||||
@@ -119,7 +120,8 @@ export async function buildExportSessionReply(params: HandleCommandsParams): Pro
|
||||
return { text: "❌ No active session found." };
|
||||
}
|
||||
|
||||
const storePath = resolveDefaultSessionStorePath(params.agentId);
|
||||
const targetAgentId = resolveAgentIdFromSessionKey(params.sessionKey) || params.agentId;
|
||||
const storePath = resolveDefaultSessionStorePath(targetAgentId);
|
||||
const store = loadSessionStore(storePath, { skipCache: true });
|
||||
const entry = store[params.sessionKey] as SessionEntry | undefined;
|
||||
if (!entry?.sessionId) {
|
||||
@@ -131,7 +133,7 @@ export async function buildExportSessionReply(params: HandleCommandsParams): Pro
|
||||
sessionFile = resolveSessionFilePath(
|
||||
entry.sessionId,
|
||||
entry,
|
||||
resolveSessionFilePathOptions({ agentId: params.agentId, storePath }),
|
||||
resolveSessionFilePathOptions({ agentId: targetAgentId, storePath }),
|
||||
);
|
||||
} catch (err) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user