























@@ -19,6 +19,7 @@ const hoisted = await vi.hoisted(async () => {
1919writeFileSyncMock: vi.fn(),
2020mkdirSyncMock: vi.fn(),
2121existsSyncMock: vi.fn(() => true),
22+exportHtmlTemplateContents: new Map<string, string>(),
2223};
2324});
2425@@ -54,8 +55,13 @@ vi.mock("node:fs", async () => {
5455mkdirSync: hoisted.mkdirSyncMock,
5556writeFileSync: hoisted.writeFileSyncMock,
5657readFileSync: vi.fn((filePath: string) => {
57-if (filePath.endsWith("template.html")) {
58-return "<html>{{CSS}}{{JS}}{{SESSION_DATA}}{{MARKED_JS}}{{HIGHLIGHT_JS}}</html>";
58+for (const [suffix, contents] of hoisted.exportHtmlTemplateContents) {
59+if (filePath.endsWith(suffix)) {
60+return contents;
61+}
62+}
63+if (filePath.includes("/export-html/")) {
64+return actual.readFileSync(filePath, "utf8");
5965}
6066return "";
6167}),
@@ -124,6 +130,7 @@ describe("buildExportSessionReply", () => {
124130sandboxRuntime: { sandboxed: false, mode: "off" },
125131});
126132hoisted.existsSyncMock.mockReturnValue(true);
133+hoisted.exportHtmlTemplateContents.clear();
127134});
128135129136it("resolves store and transcript paths from the target session agent", async () => {
@@ -185,4 +192,60 @@ describe("buildExportSessionReply", () => {
185192}),
186193);
187194});
195+196+it("injects scripts and session data through the real export template", async () => {
197+const { buildExportSessionReply } = await import("./commands-export-session.js");
198+199+await buildExportSessionReply(makeParams());
200+201+const html = hoisted.writeFileSyncMock.mock.calls[0]?.[1];
202+expect(typeof html).toBe("string");
203+expect(html).not.toContain("{{CSS}}");
204+expect(html).not.toContain("{{JS}}");
205+expect(html).not.toContain("{{SESSION_DATA}}");
206+expect(html).not.toContain("{{MARKED_JS}}");
207+expect(html).not.toContain("{{HIGHLIGHT_JS}}");
208+expect(html).not.toContain("data-openclaw-export-placeholder");
209+expect(html).toContain(
210+Buffer.from(
211+JSON.stringify({
212+header: null,
213+entries: [],
214+leafId: null,
215+systemPrompt: "system prompt",
216+tools: [],
217+}),
218+).toString("base64"),
219+);
220+expect(html).toContain('const base64 = document.getElementById("session-data").textContent;');
221+});
222+223+it("preserves replacement text with dollar sequences", async () => {
224+const { buildExportSessionReply } = await import("./commands-export-session.js");
225+hoisted.exportHtmlTemplateContents.set(
226+"template.html",
227+[
228+'<style data-openclaw-export-placeholder="CSS"></style>',
229+'<script id="session-data" type="application/json" data-openclaw-export-placeholder="SESSION_DATA"></script>',
230+'<script data-openclaw-export-placeholder="MARKED_JS"></script>',
231+'<script data-openclaw-export-placeholder="HIGHLIGHT_JS"></script>',
232+'<script data-openclaw-export-placeholder="JS"></script>',
233+].join(""),
234+);
235+hoisted.exportHtmlTemplateContents.set("template.css", "/* {{THEME_VARS}} */$&$1");
236+hoisted.exportHtmlTemplateContents.set("template.js", "const marker = '$&$1';");
237+hoisted.exportHtmlTemplateContents.set("vendor/marked.min.js", "const markedMarker = '$&$1';");
238+hoisted.exportHtmlTemplateContents.set(
239+"vendor/highlight.min.js",
240+"const highlightMarker = '$&$1';",
241+);
242+243+await buildExportSessionReply(makeParams());
244+245+const html = hoisted.writeFileSyncMock.mock.calls[0]?.[1];
246+expect(html).toContain("$&$1");
247+expect(html).toContain("const marker = '$&$1';");
248+expect(html).toContain("const markedMarker = '$&$1';");
249+expect(html).toContain("const highlightMarker = '$&$1';");
250+});
188251});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。