From 62284b58bcdfb61c33a615d8628200ec7a808f58 Mon Sep 17 00:00:00 2001 From: jet Date: Thu, 9 Apr 2026 11:13:17 +0800 Subject: [PATCH] fix: cache tmp dir resolution, add test for TTS temp path Address review feedback: - Cache resolvePreferredOpenClawTmpDir() result to avoid sync I/O on every media path check and prevent mkdir side effects - Add test for TTS voice output from /tmp/openclaw/ path --- .../reply/reply-media-paths.test.ts | 20 +++++++++++++++++++ src/auto-reply/reply/reply-media-paths.ts | 6 ++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/auto-reply/reply/reply-media-paths.test.ts b/src/auto-reply/reply/reply-media-paths.test.ts index 8ada8839dc8..5f25e0f3d90 100644 --- a/src/auto-reply/reply/reply-media-paths.test.ts +++ b/src/auto-reply/reply/reply-media-paths.test.ts @@ -176,4 +176,24 @@ describe("createReplyMediaPathNormalizer", () => { mediaUrls: ["/Users/peter/.openclaw/media/outbound/persisted.png"], }); }); + + it("keeps TTS voice output from the OpenClaw temp directory", async () => { + // resolvePreferredOpenClawTmpDir() returns /tmp/openclaw on POSIX when it exists. + // We rely on the real function (no mock) since the test environment has /tmp/openclaw. + const normalize = createReplyMediaPathNormalizer({ + cfg: {}, + sessionKey: "session-key", + workspaceDir: "/tmp/agent-workspace", + }); + + const result = await normalize({ + mediaUrls: ["/tmp/openclaw/tts-abc123/voice-1234567890.opus"], + }); + + expect(result).toMatchObject({ + mediaUrl: "/tmp/openclaw/tts-abc123/voice-1234567890.opus", + mediaUrls: ["/tmp/openclaw/tts-abc123/voice-1234567890.opus"], + }); + expect(saveMediaSource).not.toHaveBeenCalled(); + }); }); diff --git a/src/auto-reply/reply/reply-media-paths.ts b/src/auto-reply/reply/reply-media-paths.ts index 1e213f3dd92..49f9753540d 100644 --- a/src/auto-reply/reply/reply-media-paths.ts +++ b/src/auto-reply/reply/reply-media-paths.ts @@ -60,10 +60,12 @@ function isAllowedAbsoluteReplyMediaPath(params: { * These are trusted paths written by OpenClaw's own tooling * (TTS, media processing, etc.) and should be deliverable as reply media. */ +let cachedTmpRoot: string | undefined; + function isOpenClawTmpPath(candidate: string): boolean { try { - const tmpRoot = resolvePreferredOpenClawTmpDir(); - return isPathInside(tmpRoot, candidate); + cachedTmpRoot ??= resolvePreferredOpenClawTmpDir(); + return isPathInside(cachedTmpRoot, candidate); } catch { return false; }