@openclaw/diffs 2026.10.5-beta.4 → 2026.10.10-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -224,7 +224,7 @@ diff --git a/src/example.ts b/src/example.ts
224
224
  - Viewer HTML and metadata are ephemeral SQLite plugin blobs. The URL token is returned to the caller while SQLite stores only its SHA-256 hash.
225
225
  - Rendered PNG/PDF files remain temporary materializations in `$TMPDIR/openclaw-diffs` because delivery APIs require a file path. No JSON metadata sidecars are written or imported.
226
226
  - Default viewer URLs use `gateway.publicOrigin` when configured, then the existing bind-aware Gateway fallback. Plugin `viewerBaseUrl` and per-call `baseUrl` take precedence.
227
- - If `gateway.trustedProxies` includes loopback for a same-host proxy (for example Tailscale Serve), raw `127.0.0.1` viewer requests without forwarded client-IP headers fail closed by design.
227
+ - If `gateway.trustedProxies` includes loopback for a same-host proxy (for example Tailscale Serve), raw `127.0.0.1` viewer requests without forwarded client-IP headers are rejected by design.
228
228
  - In that topology, prefer `mode=file` / `mode=both` for attachments, or intentionally enable remote viewers and set plugin `viewerBaseUrl` (or pass a proxy/public `baseUrl`) when you need a shareable viewer URL.
229
229
  - Remote viewer misses are throttled to reduce token-guess abuse.
230
230
  - PNG or PDF rendering requires a Chromium-compatible browser. Set `browser.executablePath` if auto-detection is not enough.
@@ -1,4 +1,4 @@
1
- import { a as getServedViewerAsset, i as getServedLanguagePackViewerAsset, n as VIEWER_ASSET_PREFIX, t as LANGUAGE_PACK_VIEWER_ASSET_PREFIX } from "./viewer-assets-C646cpoV.mjs";
1
+ import { a as getServedViewerAsset, i as getServedLanguagePackViewerAsset, n as VIEWER_ASSET_PREFIX, t as LANGUAGE_PACK_VIEWER_ASSET_PREFIX } from "./viewer-assets-D_VrsDqE.mjs";
2
2
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import { constants } from "node:fs";
4
4
  import path from "node:path";
@@ -9,13 +9,11 @@ import os from "node:os";
9
9
  import { chromium } from "playwright-core";
10
10
  //#region extensions/diffs/src/browser.runtime.ts
11
11
  const BROWSER_IDLE_MS = 3e4;
12
- const SHARED_BROWSER_KEY = "__default__";
13
12
  const IMAGE_SIZE_LIMIT_ERROR = "Diff frame did not render within image size limits.";
14
13
  const PDF_REFERENCE_PAGE_HEIGHT_PX = 1056;
15
14
  const MAX_PDF_PAGES = 50;
16
15
  const LOCAL_VIEWER_BASE_HREF = "http://127.0.0.1/plugins/diffs/view/local/local";
17
16
  let sharedBrowserState = null;
18
- let executablePathCache = null;
19
17
  var PlaywrightDiffScreenshotter = class {
20
18
  constructor(params) {
21
19
  this.config = params.config;
@@ -209,27 +207,6 @@ function injectBaseHref(html) {
209
207
  return html.replace("<head>", `<head><base href="${LOCAL_VIEWER_BASE_HREF}" />`);
210
208
  }
211
209
  async function resolveBrowserExecutablePath(config) {
212
- const cacheKey = JSON.stringify({
213
- configPath: config.browser?.executablePath?.trim() || "",
214
- env: [
215
- process.env.OPENCLAW_BROWSER_EXECUTABLE_PATH ?? "",
216
- process.env.BROWSER_EXECUTABLE_PATH ?? "",
217
- process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH ?? ""
218
- ],
219
- path: process.env.PATH ?? ""
220
- });
221
- if (executablePathCache?.key === cacheKey) return await executablePathCache.valuePromise;
222
- const valuePromise = resolveBrowserExecutablePathUncached(config).catch((error) => {
223
- if (executablePathCache?.valuePromise === valuePromise) executablePathCache = null;
224
- throw error;
225
- });
226
- executablePathCache = {
227
- key: cacheKey,
228
- valuePromise
229
- };
230
- return await valuePromise;
231
- }
232
- async function resolveBrowserExecutablePathUncached(config) {
233
210
  const configPath = config.browser?.executablePath?.trim();
234
211
  if (configPath) {
235
212
  await assertExecutable(configPath, "browser.executablePath");
@@ -244,33 +221,26 @@ async function resolveBrowserExecutablePathUncached(config) {
244
221
  for (const candidate of await collectExecutableCandidates()) if (await isExecutable(candidate)) return candidate;
245
222
  }
246
223
  async function acquireSharedBrowser(config) {
247
- const executablePath = await resolveBrowserExecutablePath(config);
248
- const desiredKey = executablePath || SHARED_BROWSER_KEY;
249
- if (sharedBrowserState && sharedBrowserState.key !== desiredKey) await closeSharedBrowser();
250
224
  if (!sharedBrowserState) {
251
- const browserPromise = chromium.launch({
225
+ const browserPromise = resolveBrowserExecutablePath(config).then((executablePath) => chromium.launch({
252
226
  headless: true,
253
227
  ...executablePath ? { executablePath } : {},
254
228
  args: ["--disable-dev-shm-usage"]
255
- }).then((browser) => {
256
- if (sharedBrowserState?.browserPromise === browserPromise) {
257
- sharedBrowserState.browser = browser;
258
- browser.on("disconnected", () => {
259
- if (sharedBrowserState?.browser === browser) {
260
- clearIdleTimer(sharedBrowserState);
261
- sharedBrowserState = null;
262
- }
263
- });
264
- }
229
+ })).then((browser) => {
230
+ browser.on("disconnected", () => {
231
+ if (sharedBrowserState?.browserPromise === browserPromise) {
232
+ clearIdleTimer(sharedBrowserState);
233
+ sharedBrowserState = null;
234
+ }
235
+ });
265
236
  return browser;
266
237
  }).catch((error) => {
267
- if (sharedBrowserState?.browserPromise === browserPromise) sharedBrowserState = null;
238
+ sharedBrowserState = null;
268
239
  throw error;
269
240
  });
270
241
  sharedBrowserState = {
271
242
  browserPromise,
272
243
  idleTimer: null,
273
- key: desiredKey,
274
244
  users: 0
275
245
  };
276
246
  }
@@ -278,13 +248,10 @@ async function acquireSharedBrowser(config) {
278
248
  const state = sharedBrowserState;
279
249
  const browser = await state.browserPromise;
280
250
  state.users += 1;
281
- let released = false;
282
251
  return {
283
252
  browser,
284
253
  release: async () => {
285
- if (released) return;
286
- released = true;
287
- state.users = Math.max(0, state.users - 1);
254
+ state.users -= 1;
288
255
  if (state.users === 0) scheduleIdleBrowserClose(state);
289
256
  }
290
257
  };
@@ -306,7 +273,7 @@ async function closeSharedBrowser() {
306
273
  if (!state) return;
307
274
  sharedBrowserState = null;
308
275
  clearIdleTimer(state);
309
- await (state.browser ?? await state.browserPromise.catch(() => null))?.close().catch(() => {});
276
+ await (await state.browserPromise.catch(() => null))?.close().catch(() => {});
310
277
  }
311
278
  async function collectExecutableCandidates() {
312
279
  const candidates = /* @__PURE__ */ new Set();
@@ -25,25 +25,20 @@ function createViewerAssetHandler(prefix, relativePaths, optional = false) {
25
25
  const loaderPath = `${prefix}viewer.js`;
26
26
  const runtimePath = `${prefix}viewer-runtime.js`;
27
27
  let cache = null;
28
- const loadRuntimeAssets = async (runtimeUrl) => {
29
- const cached = cache;
28
+ const loadRuntimeAssets = async () => {
29
+ const runtimeUrl = await resolveRuntimeFileUrl(relativePaths);
30
30
  const runtimeFilePath = fileURLToPath(runtimeUrl);
31
- const runtimeStat = await fs.stat(runtimeFilePath);
32
- if (cached && cached.mtimeMs === runtimeStat.mtimeMs) return cached;
33
31
  const runtimeBody = await fs.readFile(runtimeFilePath);
34
32
  const hash = crypto.createHash("sha1").update(runtimeBody).digest("hex").slice(0, 12);
35
- cache = {
36
- mtimeMs: runtimeStat.mtimeMs,
33
+ return {
37
34
  runtimeBody,
38
35
  loaderBody: `import "${VIEWER_RUNTIME_RELATIVE_IMPORT_PATH}?v=${hash}";\n`
39
36
  };
40
- return cache;
41
37
  };
42
38
  return async (pathname) => {
43
39
  if (pathname !== loaderPath && pathname !== runtimePath) return null;
44
40
  try {
45
- const runtimeUrl = await resolveRuntimeFileUrl(relativePaths);
46
- const assets = await loadRuntimeAssets(runtimeUrl);
41
+ const assets = cache ??= await loadRuntimeAssets();
47
42
  return {
48
43
  body: pathname === loaderPath ? assets.loaderBody : assets.runtimeBody,
49
44
  contentType: "text/javascript; charset=utf-8"