@openclaw/diffs 2026.10.5-beta.4 → 2026.10.10-beta.1
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 +1 -1
- package/dist/.setup/{browser.runtime-BfvyJ76b.mjs → browser.runtime-DP8xInXM.mjs} +12 -45
- package/dist/.setup/{viewer-assets-C646cpoV.mjs → viewer-assets-D_VrsDqE.mjs} +4 -9
- package/dist/assets/viewer-runtime.js +30 -30
- package/dist/index.js +24 -40
- package/node_modules/typebox/build/value/clean/from_object.mjs +1 -1
- package/node_modules/typebox/package.json +1 -1
- package/package.json +5 -5
- package/skills/diffs/SKILL.md +1 -1
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
|
|
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-
|
|
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
|
-
|
|
257
|
-
sharedBrowserState
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
29
|
-
const
|
|
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
|
-
|
|
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
|
|
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"
|