























@@ -417,6 +417,7 @@ function createChatContext(): Pick<
417417logGateway: {
418418warn: vi.fn(),
419419debug: vi.fn(),
420+error: vi.fn(),
420421} as unknown as GatewayRequestContext["logGateway"],
421422};
422423}
@@ -2772,9 +2773,12 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
27722773{ path: "/home/user/.openclaw/media/inbound/report.pdf", contentType: "application/pdf" },
27732774];
27742775mockState.sandboxWorkspace = { workspaceDir: "/sandbox/workspace" };
2775-mockState.stageSandboxMediaError = Object.assign(new Error("ENOSPC: no space left on device"), {
2776+const stageError = Object.assign(new Error("ENOSPC: no space left on device"), {
27762777code: "ENOSPC",
27772778});
2779+stageError.stack =
2780+"Error: ENOSPC: no space left on device\n at stageSandboxMedia (stage-sandbox-media.ts:1:1)";
2781+mockState.stageSandboxMediaError = stageError;
27782782const respond = vi.fn();
27792783const context = createChatContext();
27802784const pdf = Buffer.from("%PDF-1.4\n%µ¶\n1 0 obj\n<<>>\nendobj\n").toString("base64");
@@ -2808,10 +2812,68 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
28082812expect(payload).toBeUndefined();
28092813expect(error?.code).toBe(ErrorCodes.UNAVAILABLE);
28102814expect(error?.message ?? String(error)).toMatch(/ENOSPC|non-image attachments/i);
2815+expect(context.logGateway.error).toHaveBeenCalledWith(
2816+"chat.send attachment parse/stage failed",
2817+expect.objectContaining({
2818+consoleMessage: expect.stringContaining(
2819+"chat.send attachment parse/stage failed: MediaOffloadError",
2820+),
2821+error: expect.stringContaining(
2822+"Caused by: Error: ENOSPC: no space left on device\n at stageSandboxMedia",
2823+),
2824+}),
2825+);
28112826// Orphaned media-store files are cleaned up before the 5xx surfaces.
28122827expect(mockState.deleteMediaBufferCalls).toEqual([{ id: "saved-media", subdir: "inbound" }]);
28132828});
281428292830+it("logs chat.send attachment parse failures with stack details", async () => {
2831+createTranscriptFixture("openclaw-chat-send-attachment-parse-stack-");
2832+const respond = vi.fn();
2833+const context = createChatContext();
2834+2835+await runNonStreamingChatSend({
2836+ context,
2837+ respond,
2838+idempotencyKey: "idem-chat-send-attachment-parse-stack",
2839+message: "inspect this",
2840+requestParams: {
2841+attachments: [
2842+{
2843+type: "file",
2844+mimeType: "image/png",
2845+fileName: "broken.png",
2846+content: "not-base64",
2847+},
2848+],
2849+},
2850+expectBroadcast: false,
2851+waitFor: "none",
2852+});
2853+2854+expect(mockState.lastDispatchCtx).toBeUndefined();
2855+expect(respond).toHaveBeenCalledWith(
2856+false,
2857+undefined,
2858+expect.objectContaining({
2859+code: ErrorCodes.INVALID_REQUEST,
2860+message: expect.stringContaining("attachment broken.png: invalid base64 content"),
2861+}),
2862+);
2863+expect(context.logGateway.error).toHaveBeenCalledWith(
2864+"chat.send attachment parse/stage failed",
2865+expect.objectContaining({
2866+consoleMessage: expect.stringContaining(
2867+"chat.send attachment parse/stage failed: Error: attachment broken.png",
2868+),
2869+error: expect.stringContaining("Error: attachment broken.png: invalid base64 content"),
2870+}),
2871+);
2872+const logMeta = (context.logGateway.error as unknown as ReturnType<typeof vi.fn>).mock
2873+.calls[0]?.[1] as { error?: string } | undefined;
2874+expect(logMeta?.error).toContain("\n at ");
2875+});
2876+28152877it("surfaces partial non-image staging failures as 5xx UNAVAILABLE", async () => {
28162878// Regression: stageSandboxMedia keeps unstaged entries as their original
28172879// absolute path, so a simple `stagedPaths.length === nonImage.length`
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。