diff --git a/CHANGELOG.md b/CHANGELOG.md index 4698a75f343..a503f40407c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Docs: https://docs.openclaw.ai - Telegram/agents: suppress the phantom "Agent couldn't generate a response" fallback after a reply was already delivered through the messaging tool on clean non-error terminal turns. (#70623) Thanks @chinar-amrutkar. - Dashboard/security: avoid writing tokenized Control UI URLs or SSH hints to runtime logs, keeping gateway bearer fragments out of console-captured logs readable through `logs.tail`. (#70029) Thanks @Ziy1-Tan. - Providers/OpenRouter: treat DeepSeek refs as cache-TTL eligible without injecting Anthropic cache-control markers, aligning context pruning with OpenRouter-managed prompt caching. (#51983) Thanks @QuinnH496. +- Control UI/browser: defer temp-dir access-mode constants until Node-only temp-dir resolution runs, preventing browser bundles from crashing when `node:fs` constants are stubbed. (#48930) Thanks @Valentinws. - Discord/cron: deliver text-only isolated cron and heartbeat announce output from the canonical final assistant text once, avoiding duplicate Discord posts when streamed block payloads and the final answer contain the same content. Fixes #71406. Thanks @alexgross21. - macOS Gateway: wait for launchd to reload the exited Gateway LaunchAgent before bootstrapping repair fallback, preventing config-triggered restarts from leaving the service not loaded. Fixes #45178. Thanks @vincentkoc. - macOS Gateway: tolerate launchctl bootstrap's already-loaded exit during restart fallback and use non-killing kickstart after bootstrap, avoiding a second race that can unload the LaunchAgent. Fixes #41934. Thanks @zerone0x. diff --git a/src/infra/tmp-openclaw-dir.browser-import.test.ts b/src/infra/tmp-openclaw-dir.browser-import.test.ts new file mode 100644 index 00000000000..1b437d3a7c3 --- /dev/null +++ b/src/infra/tmp-openclaw-dir.browser-import.test.ts @@ -0,0 +1,67 @@ +import { Buffer } from "node:buffer"; +import crypto from "node:crypto"; +import { build, type Plugin } from "esbuild"; +import { describe, expect, it } from "vitest"; + +describe("tmp-openclaw-dir browser-safe import", () => { + it("loads when a browser fs shim omits constants", async () => { + const resultKey = `__openclawTmpDirBrowserImport_${crypto.randomUUID().replaceAll("-", "_")}`; + const nodeShimPlugin: Plugin = { + name: "node-browser-shims", + setup(pluginBuild) { + pluginBuild.onResolve({ filter: /^node:(fs|os|path)$/ }, (args) => ({ + path: args.path, + namespace: "node-browser-shim", + })); + pluginBuild.onLoad({ filter: /^node:fs$/, namespace: "node-browser-shim" }, () => ({ + contents: "export default { constants: undefined };", + loader: "js", + })); + pluginBuild.onLoad({ filter: /^node:os$/, namespace: "node-browser-shim" }, () => ({ + contents: 'export const tmpdir = () => "/tmp";', + loader: "js", + })); + pluginBuild.onLoad({ filter: /^node:path$/, namespace: "node-browser-shim" }, () => ({ + contents: "export default { join: (...parts) => parts.join('/') };", + loader: "js", + })); + }, + }; + + const bundled = await build({ + bundle: true, + format: "esm", + platform: "browser", + plugins: [nodeShimPlugin], + stdin: { + contents: ` + import { POSIX_OPENCLAW_TMP_DIR, resolvePreferredOpenClawTmpDir } from "./src/infra/tmp-openclaw-dir.ts"; + globalThis.${resultKey} = { + posixTmpDir: POSIX_OPENCLAW_TMP_DIR, + resolverType: typeof resolvePreferredOpenClawTmpDir, + }; + `, + loader: "ts", + resolveDir: process.cwd(), + sourcefile: "tmp-openclaw-dir-browser-entry.ts", + }, + write: false, + }); + + const bundledSource = bundled.outputFiles[0]?.text; + expect(bundledSource).toBeTruthy(); + + await import( + `data:text/javascript;base64,${Buffer.from(bundledSource ?? "").toString("base64")}` + ); + + try { + expect((globalThis as Record)[resultKey]).toEqual({ + posixTmpDir: "/tmp/openclaw", + resolverType: "function", + }); + } finally { + delete (globalThis as Record)[resultKey]; + } + }); +}); diff --git a/src/infra/tmp-openclaw-dir.ts b/src/infra/tmp-openclaw-dir.ts index cbbd6c4b58d..1c95aea0ae0 100644 --- a/src/infra/tmp-openclaw-dir.ts +++ b/src/infra/tmp-openclaw-dir.ts @@ -3,7 +3,6 @@ import { tmpdir as getOsTmpDir } from "node:os"; import path from "node:path"; export const POSIX_OPENCLAW_TMP_DIR = "/tmp/openclaw"; -const TMP_DIR_ACCESS_MODE = fs.constants.W_OK | fs.constants.X_OK; type ResolvePreferredOpenClawTmpDirOptions = { accessSync?: (path: string, mode?: number) => void; @@ -34,6 +33,8 @@ function isNodeErrorWithCode(err: unknown, code: string): err is MaybeNodeError export function resolvePreferredOpenClawTmpDir( options: ResolvePreferredOpenClawTmpDirOptions = {}, ): string { + // Evaluated here (not at module load) so this file is safe to import in browser bundles. + const TMP_DIR_ACCESS_MODE = fs.constants.W_OK | fs.constants.X_OK; const accessSync = options.accessSync ?? fs.accessSync; const chmodSync = options.chmodSync ?? fs.chmodSync; const lstatSync = options.lstatSync ?? fs.lstatSync;