



























@@ -174,6 +174,30 @@ function readEncodedRequestFromCommand(command: string): Record<string, unknown>
174174return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")) as Record<string, unknown>;
175175}
176176177+function requireRecord(value: unknown): Record<string, unknown> {
178+expect(value).toBeTruthy();
179+expect(typeof value).toBe("object");
180+expect(Array.isArray(value)).toBe(false);
181+return value as Record<string, unknown>;
182+}
183+184+function exportBundleParams(): Record<string, unknown> {
185+const calls = hoisted.exportTrajectoryBundleMock.mock.calls as unknown[][];
186+return requireRecord(calls[0]?.[0]);
187+}
188+189+function execCallRecord(
190+execCalls: Array<{ defaults: unknown; params: unknown }>,
191+index = 0,
192+): { defaults: Record<string, unknown>; params: Record<string, unknown> } {
193+const call = execCalls[index];
194+expect(call).toBeTruthy();
195+return {
196+defaults: requireRecord(call?.defaults),
197+params: requireRecord(call?.params),
198+};
199+}
200+177201describe("buildExportTrajectoryReply", () => {
178202beforeEach(() => {
179203vi.clearAllMocks();
@@ -205,20 +229,19 @@ describe("buildExportTrajectoryReply", () => {
205229});
206230207231it("builds a trajectory bundle from the target session", async () => {
208-const reply = await buildExportTrajectoryReply(makeParams());
232+const params = makeParams();
233+const reply = await buildExportTrajectoryReply(params);
209234210235expect(reply.text).toContain("✅ Trajectory exported!");
211236expect(reply.text).toContain("session-branch.json");
212237expect(reply.text).not.toContain("session.jsonl");
213238expect(reply.text).not.toContain("runtime.jsonl");
214239expect(hoisted.resolveDefaultSessionStorePathMock).toHaveBeenCalledWith("target");
215-expect(hoisted.exportTrajectoryBundleMock).toHaveBeenCalledWith(
216-expect.objectContaining({
217-sessionId: "session-1",
218-sessionKey: "agent:target:session",
219-workspaceDir: expect.stringContaining("openclaw-export-command-"),
220-}),
221-);
240+const exportParams = exportBundleParams();
241+expect(exportParams.sessionId).toBe("session-1");
242+expect(exportParams.sessionKey).toBe("agent:target:session");
243+expect(exportParams.workspaceDir).toBe(params.workspaceDir);
244+expect(String(exportParams.workspaceDir)).toContain("openclaw-export-command-");
222245});
223246224247it("keeps user-named output paths inside the workspace trajectory export directory", async () => {
@@ -227,10 +250,8 @@ describe("buildExportTrajectoryReply", () => {
227250228251await buildExportTrajectoryReply(params);
229252230-expect(hoisted.exportTrajectoryBundleMock).toHaveBeenCalledWith(
231-expect.objectContaining({
232-outputDir: path.join(params.workspaceDir, ".openclaw", "trajectory-exports", "my-bundle"),
233-}),
253+expect(exportBundleParams().outputDir).toBe(
254+path.join(params.workspaceDir, ".openclaw", "trajectory-exports", "my-bundle"),
234255);
235256});
236257@@ -328,8 +349,9 @@ describe("buildExportTrajectoryCommandReply", () => {
328349329350it("requests per-run exec approval for trajectory exports", async () => {
330351const { execCalls, deps } = createExecDeps();
352+const params = makeParams();
331353332-const reply = await buildExportTrajectoryCommandReply(makeParams(), deps);
354+const reply = await buildExportTrajectoryCommandReply(params, deps);
333355334356expect(reply.text).toContain(
335357"Trajectory exports can include prompts, model messages, tool schemas",
@@ -338,31 +360,27 @@ describe("buildExportTrajectoryCommandReply", () => {
338360expect(reply.text).toContain("do not use allow-all");
339361expect(reply.text).toContain("Allowed decisions: allow-once, deny");
340362expect(execCalls).toHaveLength(1);
341-expect(execCalls[0]?.defaults).toMatchObject({
342-host: "gateway",
343-security: "allowlist",
344-ask: "always",
345-trigger: "export-trajectory",
346-currentChannelId: "bot",
347-accountId: "account-1",
348-});
349-expect(execCalls[0]?.params).toMatchObject({
350-security: "allowlist",
351-ask: "always",
352-background: true,
353-});
354-const command = (execCalls[0]?.params as { command?: string }).command ?? "";
363+const execCall = execCallRecord(execCalls);
364+expect(execCall.defaults.host).toBe("gateway");
365+expect(execCall.defaults.security).toBe("allowlist");
366+expect(execCall.defaults.ask).toBe("always");
367+expect(execCall.defaults.trigger).toBe("export-trajectory");
368+expect(execCall.defaults.currentChannelId).toBe("bot");
369+expect(execCall.defaults.accountId).toBe("account-1");
370+expect(execCall.params.security).toBe("allowlist");
371+expect(execCall.params.ask).toBe("always");
372+expect(execCall.params.background).toBe(true);
373+const command = typeof execCall.params.command === "string" ? execCall.params.command : "";
355374expect(command).toContain("sessions");
356375expect(command).toContain("export-trajectory");
357376expect(command).toContain("--request-json-base64");
358377expect(command).toContain("--json");
359378expect(command).not.toContain("--session-key");
360379expect(command).not.toContain("openclaw sessions export-trajectory");
361380const request = readEncodedRequestFromCommand(command);
362-expect(request).toMatchObject({
363-sessionKey: "agent:target:session",
364-workspace: expect.stringContaining("openclaw-export-command-"),
365-});
381+expect(request.sessionKey).toBe("agent:target:session");
382+expect(request.workspace).toBe(params.workspaceDir);
383+expect(String(request.workspace)).toContain("openclaw-export-command-");
366384});
367385368386it("uses the originating Telegram route for native trajectory export followups", async () => {
@@ -389,11 +407,10 @@ describe("buildExportTrajectoryCommandReply", () => {
389407await buildExportTrajectoryCommandReply(params, deps);
390408391409expect(execCalls).toHaveLength(1);
392-expect(execCalls[0]?.defaults).toMatchObject({
393-messageProvider: "telegram",
394-currentChannelId: "telegram:8460800771",
395-accountId: "account-1",
396-});
410+const execCall = execCallRecord(execCalls);
411+expect(execCall.defaults.messageProvider).toBe("telegram");
412+expect(execCall.defaults.currentChannelId).toBe("telegram:8460800771");
413+expect(execCall.defaults.accountId).toBe("account-1");
397414});
398415399416it("keeps user-controlled export values out of the shell command", async () => {
@@ -403,14 +420,13 @@ describe("buildExportTrajectoryCommandReply", () => {
403420404421await buildExportTrajectoryCommandReply(params, deps);
405422406-const command = (execCalls[0]?.params as { command?: string }).command ?? "";
423+const commandValue = execCallRecord(execCalls).params.command;
424+const command = typeof commandValue === "string" ? commandValue : "";
407425expect(command).toMatch(/'?sessions'?\s+'?export-trajectory'?/u);
408426expect(command).toMatch(/'?--request-json-base64'?\s+'?[A-Za-z0-9_-]+'?/u);
409427expect(command).toMatch(/'?--json'?$/u);
410428expect(command).not.toContain("Invoke-Expression");
411-expect(readEncodedRequestFromCommand(command)).toMatchObject({
412-output: "bad';",
413-});
429+expect(readEncodedRequestFromCommand(command).output).toBe("bad';");
414430});
415431416432it("rejects oversized output paths before requesting exec approval", async () => {
@@ -460,11 +476,10 @@ describe("buildExportTrajectoryCommandReply", () => {
460476expect(privateReplies[0]?.text).toContain("openclaw sessions export-trajectory");
461477expect(privateReplies[0]?.text).toContain("Session: agent:target:session");
462478expect(execCalls).toHaveLength(1);
463-expect(execCalls[0]?.defaults).toMatchObject({
464-messageProvider: "telegram",
465-currentChannelId: "owner-dm",
466-accountId: "account-1",
467-});
479+const execCall = execCallRecord(execCalls);
480+expect(execCall.defaults.messageProvider).toBe("telegram");
481+expect(execCall.defaults.currentChannelId).toBe("owner-dm");
482+expect(execCall.defaults.accountId).toBe("account-1");
468483});
469484470485it("fails closed in groups when no private owner route is available", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。