@openclaw/diffs 2026.7.2-beta.7 → 2026.8.1-beta.3
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 +8 -8
- package/dist/assets/viewer-runtime.js +3 -3
- package/dist/browser.runtime-_hkQDewM.js +406 -0
- package/dist/index.js +29 -584
- package/dist/viewer-assets-DKR8WLAI.js +118 -0
- package/node_modules/playwright-core/lib/coreBundle.js +1 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-By56iMx7.js → codeMirrorModule-rXmQmLUY.js} +1 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B-dXF5JN.js +181 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/{xtermModule-COQkjINf.js → xtermModule-BuZfJS5v.js} +1 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/{index.Dl36UVQT.js → index.KZ4wOW1K.js} +1 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.D962mr9b.js → uiMode.Dzuouizj.js} +2 -2
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright-core/types/structs.d.ts +8 -3
- package/package.json +6 -6
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +0 -181
package/dist/index.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import { definePluginEntry, resolvePreferredOpenClawTmpDir } from "./api.js";
|
|
2
2
|
import { resolveRequestClientIp } from "./runtime-api.js";
|
|
3
|
+
import { a as getServedViewerAsset, r as VIEWER_RUNTIME_PATH } from "./viewer-assets-DKR8WLAI.js";
|
|
3
4
|
import { buildPluginConfigSchema } from "openclaw/plugin-sdk/plugin-entry";
|
|
4
5
|
import { mapPluginConfigIssues } from "openclaw/plugin-sdk/extension-shared";
|
|
5
6
|
import { z } from "zod";
|
|
6
|
-
import
|
|
7
|
+
import { resolveGatewayPublicOrigin } from "openclaw/plugin-sdk/config-contracts";
|
|
8
|
+
import { resolveGatewayPort } from "openclaw/plugin-sdk/core";
|
|
9
|
+
import fs from "node:fs";
|
|
7
10
|
import path from "node:path";
|
|
8
11
|
import { resolveLivePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
9
12
|
import { isLoopbackHost } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
10
|
-
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
|
+
import { asNonArrayRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
|
+
import { createAuthRateLimiter } from "openclaw/plugin-sdk/webhook-ingress";
|
|
11
15
|
import crypto from "node:crypto";
|
|
12
16
|
import fs$1 from "node:fs/promises";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
14
17
|
import { gunzip, gzip } from "node:zlib";
|
|
15
18
|
import { MAX_DATE_TIMESTAMP_MS, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
|
|
16
|
-
import { safeEqualSecret
|
|
19
|
+
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
17
20
|
import { optionalFiniteNumberSchema, stringEnum } from "openclaw/plugin-sdk/channel-actions";
|
|
18
21
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
22
|
+
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
19
23
|
import { readFiniteNumberParam } from "openclaw/plugin-sdk/param-readers";
|
|
20
24
|
import { Type } from "typebox";
|
|
21
|
-
import { chromium } from "playwright-core";
|
|
22
25
|
import { parsePatchFiles, resolveLanguage } from "@pierre/diffs";
|
|
23
26
|
import { preloadDiffHTML } from "@pierre/diffs/ssr";
|
|
24
27
|
import { escapeHtml } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
@@ -46,9 +49,8 @@ const DIFF_ARTIFACT_ID_PATTERN = /^[0-9a-f]{20}$/;
|
|
|
46
49
|
const DIFF_ARTIFACT_TOKEN_PATTERN = /^[0-9a-f]{48}$/;
|
|
47
50
|
//#endregion
|
|
48
51
|
//#region extensions/diffs/src/url.ts
|
|
49
|
-
const DEFAULT_GATEWAY_PORT = 18789;
|
|
50
52
|
function buildViewerUrl(params) {
|
|
51
|
-
const normalizedBase = normalizeViewerBaseUrl(params.baseUrl?.trim() || resolveGatewayBaseUrl(params.config));
|
|
53
|
+
const normalizedBase = normalizeViewerBaseUrl(params.baseUrl?.trim() || params.viewerBaseUrl?.trim() || resolveGatewayPublicOrigin(params.config) || resolveGatewayBaseUrl(params.config));
|
|
52
54
|
const viewerPath = params.viewerPath.startsWith("/") ? params.viewerPath : `/${params.viewerPath}`;
|
|
53
55
|
const parsedBase = new URL(normalizedBase);
|
|
54
56
|
parsedBase.pathname = `${parsedBase.pathname === "/" ? "" : parsedBase.pathname.replace(/\/+$/, "")}${viewerPath}`;
|
|
@@ -72,7 +74,7 @@ function normalizeViewerBaseUrl(raw, fieldName = "baseUrl") {
|
|
|
72
74
|
}
|
|
73
75
|
function resolveGatewayBaseUrl(config) {
|
|
74
76
|
const scheme = config.gateway?.tls?.enabled ? "https" : "http";
|
|
75
|
-
const port =
|
|
77
|
+
const port = resolveGatewayPort(config);
|
|
76
78
|
const customHost = config.gateway?.customBindHost?.trim();
|
|
77
79
|
if (config.gateway?.bind === "custom" && customHost) return `${scheme}://${customHost}:${port}`;
|
|
78
80
|
return `${scheme}://127.0.0.1:${port}`;
|
|
@@ -300,120 +302,6 @@ function resolveDiffImageRenderOptions(params) {
|
|
|
300
302
|
};
|
|
301
303
|
}
|
|
302
304
|
//#endregion
|
|
303
|
-
//#region extensions/diffs/src/viewer-assets.ts
|
|
304
|
-
const VIEWER_ASSET_PREFIX = "/plugins/diffs/assets/";
|
|
305
|
-
const VIEWER_LOADER_PATH = `${VIEWER_ASSET_PREFIX}viewer.js`;
|
|
306
|
-
const VIEWER_RUNTIME_PATH = `${VIEWER_ASSET_PREFIX}viewer-runtime.js`;
|
|
307
|
-
const LANGUAGE_PACK_VIEWER_ASSET_PREFIX = "/plugins/diffs-language-pack/assets/";
|
|
308
|
-
const LANGUAGE_PACK_VIEWER_LOADER_PATH = `${LANGUAGE_PACK_VIEWER_ASSET_PREFIX}viewer.js`;
|
|
309
|
-
const LANGUAGE_PACK_VIEWER_RUNTIME_PATH = `${LANGUAGE_PACK_VIEWER_ASSET_PREFIX}viewer-runtime.js`;
|
|
310
|
-
const VIEWER_RUNTIME_RELATIVE_IMPORT_PATH = "./viewer-runtime.js";
|
|
311
|
-
const VIEWER_RUNTIME_CANDIDATE_RELATIVE_PATHS = ["./assets/viewer-runtime.js", "../assets/viewer-runtime.js"];
|
|
312
|
-
const LANGUAGE_PACK_RUNTIME_CANDIDATE_RELATIVE_PATHS = ["../../diffs-language-pack/assets/viewer-runtime.js", "../diffs-language-pack/assets/viewer-runtime.js"];
|
|
313
|
-
let runtimeAssetCache = null;
|
|
314
|
-
let languagePackRuntimeAssetCache = null;
|
|
315
|
-
function isMissingFileError(error) {
|
|
316
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
317
|
-
}
|
|
318
|
-
async function resolveViewerRuntimeFileUrl(params = {}) {
|
|
319
|
-
const baseUrl = params.baseUrl ?? import.meta.url;
|
|
320
|
-
const stat = params.stat ?? ((path) => fs$1.stat(path));
|
|
321
|
-
let missingFileError = null;
|
|
322
|
-
for (const relativePath of VIEWER_RUNTIME_CANDIDATE_RELATIVE_PATHS) {
|
|
323
|
-
const candidateUrl = new URL(relativePath, baseUrl);
|
|
324
|
-
try {
|
|
325
|
-
await stat(fileURLToPath(candidateUrl));
|
|
326
|
-
return candidateUrl;
|
|
327
|
-
} catch (error) {
|
|
328
|
-
if (isMissingFileError(error)) {
|
|
329
|
-
missingFileError = error;
|
|
330
|
-
continue;
|
|
331
|
-
}
|
|
332
|
-
throw error;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
if (missingFileError) throw missingFileError;
|
|
336
|
-
throw new Error("viewer runtime asset candidates were not checked");
|
|
337
|
-
}
|
|
338
|
-
async function getServedViewerAsset(pathname) {
|
|
339
|
-
if (pathname !== VIEWER_LOADER_PATH && pathname !== VIEWER_RUNTIME_PATH) return null;
|
|
340
|
-
const assets = await loadViewerAssets();
|
|
341
|
-
if (pathname === VIEWER_LOADER_PATH) return {
|
|
342
|
-
body: assets.loaderBody,
|
|
343
|
-
contentType: "text/javascript; charset=utf-8"
|
|
344
|
-
};
|
|
345
|
-
if (pathname === VIEWER_RUNTIME_PATH) return {
|
|
346
|
-
body: assets.runtimeBody,
|
|
347
|
-
contentType: "text/javascript; charset=utf-8"
|
|
348
|
-
};
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
async function getServedLanguagePackViewerAsset(pathname) {
|
|
352
|
-
if (pathname !== LANGUAGE_PACK_VIEWER_LOADER_PATH && pathname !== LANGUAGE_PACK_VIEWER_RUNTIME_PATH) return null;
|
|
353
|
-
let assets;
|
|
354
|
-
try {
|
|
355
|
-
assets = await loadRuntimeAssets({
|
|
356
|
-
runtimeUrl: await resolveRuntimeFileUrl(LANGUAGE_PACK_RUNTIME_CANDIDATE_RELATIVE_PATHS),
|
|
357
|
-
cache: languagePackRuntimeAssetCache,
|
|
358
|
-
updateCache: (cache) => {
|
|
359
|
-
languagePackRuntimeAssetCache = cache;
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
} catch (error) {
|
|
363
|
-
if (isMissingFileError(error)) return null;
|
|
364
|
-
throw error;
|
|
365
|
-
}
|
|
366
|
-
if (pathname === LANGUAGE_PACK_VIEWER_LOADER_PATH) return {
|
|
367
|
-
body: assets.loaderBody,
|
|
368
|
-
contentType: "text/javascript; charset=utf-8"
|
|
369
|
-
};
|
|
370
|
-
return {
|
|
371
|
-
body: assets.runtimeBody,
|
|
372
|
-
contentType: "text/javascript; charset=utf-8"
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
async function loadViewerAssets() {
|
|
376
|
-
return loadRuntimeAssets({
|
|
377
|
-
runtimeUrl: await resolveViewerRuntimeFileUrl(),
|
|
378
|
-
cache: runtimeAssetCache,
|
|
379
|
-
updateCache: (cache) => {
|
|
380
|
-
runtimeAssetCache = cache;
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
async function loadRuntimeAssets(params) {
|
|
385
|
-
const runtimePath = fileURLToPath(params.runtimeUrl);
|
|
386
|
-
const runtimeStat = await fs$1.stat(runtimePath);
|
|
387
|
-
if (params.cache && params.cache.mtimeMs === runtimeStat.mtimeMs) return params.cache;
|
|
388
|
-
const runtimeBody = await fs$1.readFile(runtimePath);
|
|
389
|
-
const hash = crypto.createHash("sha1").update(runtimeBody).digest("hex").slice(0, 12);
|
|
390
|
-
const cache = {
|
|
391
|
-
mtimeMs: runtimeStat.mtimeMs,
|
|
392
|
-
runtimeBody,
|
|
393
|
-
loaderBody: `import "${VIEWER_RUNTIME_RELATIVE_IMPORT_PATH}?v=${hash}";\n`
|
|
394
|
-
};
|
|
395
|
-
params.updateCache(cache);
|
|
396
|
-
return cache;
|
|
397
|
-
}
|
|
398
|
-
async function resolveRuntimeFileUrl(relativePaths) {
|
|
399
|
-
let missingFileError = null;
|
|
400
|
-
for (const relativePath of relativePaths) {
|
|
401
|
-
const candidateUrl = new URL(relativePath, import.meta.url);
|
|
402
|
-
try {
|
|
403
|
-
await fs$1.stat(fileURLToPath(candidateUrl));
|
|
404
|
-
return candidateUrl;
|
|
405
|
-
} catch (error) {
|
|
406
|
-
if (isMissingFileError(error)) {
|
|
407
|
-
missingFileError = error;
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
throw error;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
if (missingFileError) throw missingFileError;
|
|
414
|
-
throw new Error("viewer runtime asset candidates were not checked");
|
|
415
|
-
}
|
|
416
|
-
//#endregion
|
|
417
305
|
//#region extensions/diffs/src/http.ts
|
|
418
306
|
const VIEW_PREFIX = "/plugins/diffs/view/";
|
|
419
307
|
const VIEWER_MAX_FAILURES_PER_WINDOW = 40;
|
|
@@ -433,7 +321,14 @@ const VIEWER_CONTENT_SECURITY_POLICY = [
|
|
|
433
321
|
].join("; ");
|
|
434
322
|
const IMMUTABLE_ASSET_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
435
323
|
function createDiffsHttpHandler(params) {
|
|
436
|
-
const viewerFailureLimiter =
|
|
324
|
+
const viewerFailureLimiter = createAuthRateLimiter({
|
|
325
|
+
maxAttempts: VIEWER_MAX_FAILURES_PER_WINDOW,
|
|
326
|
+
windowMs: VIEWER_FAILURE_WINDOW_MS,
|
|
327
|
+
lockoutMs: VIEWER_LOCKOUT_MS,
|
|
328
|
+
exemptLoopback: false,
|
|
329
|
+
pruneIntervalMs: 0,
|
|
330
|
+
maxEntries: VIEWER_LIMITER_MAX_KEYS
|
|
331
|
+
});
|
|
437
332
|
return async (req, res) => {
|
|
438
333
|
const parsed = parseRequestUrl(req.url);
|
|
439
334
|
if (!parsed) return false;
|
|
@@ -565,62 +460,6 @@ function recordRemoteFailure(limiter, access) {
|
|
|
565
460
|
function resetRemoteFailures(limiter, access) {
|
|
566
461
|
if (!access.localRequest) limiter.reset(access.remoteKey);
|
|
567
462
|
}
|
|
568
|
-
var ViewerFailureLimiter = class {
|
|
569
|
-
constructor() {
|
|
570
|
-
this.failures = /* @__PURE__ */ new Map();
|
|
571
|
-
}
|
|
572
|
-
check(key) {
|
|
573
|
-
this.prune();
|
|
574
|
-
const state = this.failures.get(key);
|
|
575
|
-
if (!state) return {
|
|
576
|
-
allowed: true,
|
|
577
|
-
retryAfterMs: 0
|
|
578
|
-
};
|
|
579
|
-
const now = Date.now();
|
|
580
|
-
if (state.lockUntilMs > now) return {
|
|
581
|
-
allowed: false,
|
|
582
|
-
retryAfterMs: state.lockUntilMs - now
|
|
583
|
-
};
|
|
584
|
-
if (now - state.windowStartMs >= VIEWER_FAILURE_WINDOW_MS) {
|
|
585
|
-
this.failures.delete(key);
|
|
586
|
-
return {
|
|
587
|
-
allowed: true,
|
|
588
|
-
retryAfterMs: 0
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
return {
|
|
592
|
-
allowed: true,
|
|
593
|
-
retryAfterMs: 0
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
recordFailure(key) {
|
|
597
|
-
this.prune();
|
|
598
|
-
const now = Date.now();
|
|
599
|
-
const current = this.failures.get(key);
|
|
600
|
-
const next = !current || now - current.windowStartMs >= VIEWER_FAILURE_WINDOW_MS ? {
|
|
601
|
-
windowStartMs: now,
|
|
602
|
-
failures: 1,
|
|
603
|
-
lockUntilMs: 0
|
|
604
|
-
} : {
|
|
605
|
-
...current,
|
|
606
|
-
failures: current.failures + 1
|
|
607
|
-
};
|
|
608
|
-
if (next.failures >= VIEWER_MAX_FAILURES_PER_WINDOW) next.lockUntilMs = now + VIEWER_LOCKOUT_MS;
|
|
609
|
-
this.failures.set(key, next);
|
|
610
|
-
}
|
|
611
|
-
reset(key) {
|
|
612
|
-
this.failures.delete(key);
|
|
613
|
-
}
|
|
614
|
-
prune() {
|
|
615
|
-
if (this.failures.size < VIEWER_LIMITER_MAX_KEYS) return;
|
|
616
|
-
const now = Date.now();
|
|
617
|
-
for (const [key, state] of this.failures) {
|
|
618
|
-
if (state.lockUntilMs <= now && now - state.windowStartMs >= VIEWER_FAILURE_WINDOW_MS) this.failures.delete(key);
|
|
619
|
-
if (this.failures.size < VIEWER_LIMITER_MAX_KEYS) return;
|
|
620
|
-
}
|
|
621
|
-
if (this.failures.size >= VIEWER_LIMITER_MAX_KEYS) this.failures.clear();
|
|
622
|
-
}
|
|
623
|
-
};
|
|
624
463
|
//#endregion
|
|
625
464
|
//#region extensions/diffs/src/prompt-guidance.ts
|
|
626
465
|
const DIFFS_AGENT_GUIDANCE = [
|
|
@@ -893,401 +732,6 @@ function isFileNotFound(error) {
|
|
|
893
732
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
894
733
|
}
|
|
895
734
|
//#endregion
|
|
896
|
-
//#region extensions/diffs/src/browser.ts
|
|
897
|
-
const DEFAULT_BROWSER_IDLE_MS = 3e4;
|
|
898
|
-
const SHARED_BROWSER_KEY = "__default__";
|
|
899
|
-
const IMAGE_SIZE_LIMIT_ERROR = "Diff frame did not render within image size limits.";
|
|
900
|
-
const PDF_REFERENCE_PAGE_HEIGHT_PX = 1056;
|
|
901
|
-
const MAX_PDF_PAGES = 50;
|
|
902
|
-
const LOCAL_VIEWER_BASE_HREF = "http://127.0.0.1/plugins/diffs/view/local/local";
|
|
903
|
-
let sharedBrowserState = null;
|
|
904
|
-
let executablePathCache = null;
|
|
905
|
-
var PlaywrightDiffScreenshotter = class {
|
|
906
|
-
constructor(params) {
|
|
907
|
-
this.config = params.config;
|
|
908
|
-
this.browserIdleMs = params.browserIdleMs ?? DEFAULT_BROWSER_IDLE_MS;
|
|
909
|
-
}
|
|
910
|
-
async screenshotHtml(params) {
|
|
911
|
-
let lease;
|
|
912
|
-
try {
|
|
913
|
-
lease = await acquireSharedBrowser({
|
|
914
|
-
config: this.config,
|
|
915
|
-
idleMs: this.browserIdleMs
|
|
916
|
-
});
|
|
917
|
-
} catch (error) {
|
|
918
|
-
throw buildBrowserUnavailableError(error);
|
|
919
|
-
}
|
|
920
|
-
let page;
|
|
921
|
-
let currentScale = params.image.scale;
|
|
922
|
-
const maxRetries = 2;
|
|
923
|
-
try {
|
|
924
|
-
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
925
|
-
try {
|
|
926
|
-
page = await lease.browser.newPage({
|
|
927
|
-
viewport: {
|
|
928
|
-
width: Math.max(Math.ceil(params.image.maxWidth + 240), 1200),
|
|
929
|
-
height: 900
|
|
930
|
-
},
|
|
931
|
-
deviceScaleFactor: currentScale,
|
|
932
|
-
colorScheme: params.theme
|
|
933
|
-
});
|
|
934
|
-
} catch (error) {
|
|
935
|
-
throw buildBrowserUnavailableError(error);
|
|
936
|
-
}
|
|
937
|
-
await page.route("**/*", async (route) => {
|
|
938
|
-
const requestUrl = route.request().url();
|
|
939
|
-
if (requestUrl === "about:blank" || requestUrl.startsWith("data:")) {
|
|
940
|
-
await route.continue();
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
let parsed;
|
|
944
|
-
try {
|
|
945
|
-
parsed = new URL(requestUrl);
|
|
946
|
-
} catch {
|
|
947
|
-
await route.abort();
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
if (parsed.protocol !== "http:" || parsed.hostname !== "127.0.0.1") {
|
|
951
|
-
await route.abort();
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
const isBaseViewerAsset = parsed.pathname.startsWith(VIEWER_ASSET_PREFIX);
|
|
955
|
-
const isLanguagePackViewerAsset = parsed.pathname.startsWith(LANGUAGE_PACK_VIEWER_ASSET_PREFIX);
|
|
956
|
-
if (!isBaseViewerAsset && !isLanguagePackViewerAsset) {
|
|
957
|
-
await route.abort();
|
|
958
|
-
return;
|
|
959
|
-
}
|
|
960
|
-
const pathname = parsed.pathname;
|
|
961
|
-
const asset = isLanguagePackViewerAsset ? await getServedLanguagePackViewerAsset(pathname) : await getServedViewerAsset(pathname);
|
|
962
|
-
if (!asset) {
|
|
963
|
-
await route.abort();
|
|
964
|
-
return;
|
|
965
|
-
}
|
|
966
|
-
await route.fulfill({
|
|
967
|
-
status: 200,
|
|
968
|
-
contentType: asset.contentType,
|
|
969
|
-
body: asset.body
|
|
970
|
-
});
|
|
971
|
-
});
|
|
972
|
-
await page.setContent(injectBaseHref(params.html), { waitUntil: "load" });
|
|
973
|
-
await page.waitForFunction(() => {
|
|
974
|
-
if (document.documentElement.dataset.openclawDiffsReady === "true") return true;
|
|
975
|
-
return [...document.querySelectorAll("[data-openclaw-diff-host]")].every((element) => {
|
|
976
|
-
return element instanceof HTMLElement && element.shadowRoot?.querySelector("[data-diffs]");
|
|
977
|
-
});
|
|
978
|
-
}, { timeout: 1e4 });
|
|
979
|
-
await page.evaluate(async () => {
|
|
980
|
-
await document.fonts.ready;
|
|
981
|
-
});
|
|
982
|
-
await page.evaluate(() => {
|
|
983
|
-
const frame = document.querySelector(".oc-frame");
|
|
984
|
-
if (frame instanceof HTMLElement) frame.dataset.renderMode = "image";
|
|
985
|
-
});
|
|
986
|
-
const frame = page.locator(".oc-frame");
|
|
987
|
-
await frame.waitFor();
|
|
988
|
-
const initialBox = await frame.boundingBox();
|
|
989
|
-
if (!initialBox) throw new Error("Diff frame did not render.");
|
|
990
|
-
const isPdf = params.image.format === "pdf";
|
|
991
|
-
const padding = isPdf ? 0 : 20;
|
|
992
|
-
const clipWidth = Math.ceil(initialBox.width + padding * 2);
|
|
993
|
-
const clipHeight = Math.ceil(Math.max(initialBox.height + padding * 2, 320));
|
|
994
|
-
await page.setViewportSize({
|
|
995
|
-
width: Math.max(clipWidth + padding, 900),
|
|
996
|
-
height: Math.max(clipHeight + padding, 700)
|
|
997
|
-
});
|
|
998
|
-
const box = await frame.boundingBox();
|
|
999
|
-
if (!box) throw new Error("Diff frame was lost after resizing.");
|
|
1000
|
-
if (isPdf) {
|
|
1001
|
-
await page.emulateMedia({ media: "screen" });
|
|
1002
|
-
await page.evaluate(() => {
|
|
1003
|
-
const html = document.documentElement;
|
|
1004
|
-
const body = document.body;
|
|
1005
|
-
const frameLocal = document.querySelector(".oc-frame");
|
|
1006
|
-
html.style.background = "transparent";
|
|
1007
|
-
body.style.margin = "0";
|
|
1008
|
-
body.style.padding = "0";
|
|
1009
|
-
body.style.background = "transparent";
|
|
1010
|
-
body.style.setProperty("-webkit-print-color-adjust", "exact");
|
|
1011
|
-
if (frameLocal instanceof HTMLElement) frameLocal.style.margin = "0";
|
|
1012
|
-
});
|
|
1013
|
-
const pdfBox = await frame.boundingBox();
|
|
1014
|
-
if (!pdfBox) throw new Error("Diff frame was lost before PDF render.");
|
|
1015
|
-
const pdfWidth = Math.max(Math.ceil(pdfBox.width), 1);
|
|
1016
|
-
const pdfHeight = Math.max(Math.ceil(pdfBox.height), 1);
|
|
1017
|
-
const estimatedPixels = pdfWidth * pdfHeight;
|
|
1018
|
-
const estimatedPages = Math.ceil(pdfHeight / PDF_REFERENCE_PAGE_HEIGHT_PX);
|
|
1019
|
-
if (estimatedPixels > params.image.maxPixels || estimatedPages > MAX_PDF_PAGES) throw new Error(IMAGE_SIZE_LIMIT_ERROR);
|
|
1020
|
-
const pageForPdf = page;
|
|
1021
|
-
await writeExternalArtifactFile({
|
|
1022
|
-
outputPath: params.outputPath,
|
|
1023
|
-
write: async (tempPath) => {
|
|
1024
|
-
await pageForPdf.pdf({
|
|
1025
|
-
path: tempPath,
|
|
1026
|
-
width: `${pdfWidth}px`,
|
|
1027
|
-
height: `${pdfHeight}px`,
|
|
1028
|
-
printBackground: true,
|
|
1029
|
-
margin: {
|
|
1030
|
-
top: "0",
|
|
1031
|
-
right: "0",
|
|
1032
|
-
bottom: "0",
|
|
1033
|
-
left: "0"
|
|
1034
|
-
}
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
});
|
|
1038
|
-
return params.outputPath;
|
|
1039
|
-
}
|
|
1040
|
-
const dpr = await page.evaluate(() => window.devicePixelRatio || 1);
|
|
1041
|
-
const rawX = Math.max(box.x - padding, 0);
|
|
1042
|
-
const rawY = Math.max(box.y - padding, 0);
|
|
1043
|
-
const rawRight = rawX + clipWidth;
|
|
1044
|
-
const rawBottom = rawY + clipHeight;
|
|
1045
|
-
const x = Math.floor(rawX * dpr) / dpr;
|
|
1046
|
-
const y = Math.floor(rawY * dpr) / dpr;
|
|
1047
|
-
const right = Math.ceil(rawRight * dpr) / dpr;
|
|
1048
|
-
const bottom = Math.ceil(rawBottom * dpr) / dpr;
|
|
1049
|
-
const cssWidth = Math.max(right - x, 1);
|
|
1050
|
-
const cssHeight = Math.max(bottom - y, 1);
|
|
1051
|
-
if (cssWidth * cssHeight * dpr * dpr > params.image.maxPixels) {
|
|
1052
|
-
if (currentScale > 1) {
|
|
1053
|
-
const maxScaleForPixels = Math.sqrt(params.image.maxPixels / (cssWidth * cssHeight));
|
|
1054
|
-
const reducedScale = Math.max(1, Math.round(Math.min(currentScale, maxScaleForPixels) * 100) / 100);
|
|
1055
|
-
if (reducedScale < currentScale - .01 && attempt < maxRetries) {
|
|
1056
|
-
await page.close().catch(() => {});
|
|
1057
|
-
page = void 0;
|
|
1058
|
-
currentScale = reducedScale;
|
|
1059
|
-
continue;
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
throw new Error(IMAGE_SIZE_LIMIT_ERROR);
|
|
1063
|
-
}
|
|
1064
|
-
const pageForScreenshot = page;
|
|
1065
|
-
await writeExternalArtifactFile({
|
|
1066
|
-
outputPath: params.outputPath,
|
|
1067
|
-
write: async (tempPath) => {
|
|
1068
|
-
await pageForScreenshot.screenshot({
|
|
1069
|
-
path: tempPath,
|
|
1070
|
-
type: "png",
|
|
1071
|
-
scale: "device",
|
|
1072
|
-
clip: {
|
|
1073
|
-
x,
|
|
1074
|
-
y,
|
|
1075
|
-
width: cssWidth,
|
|
1076
|
-
height: cssHeight
|
|
1077
|
-
}
|
|
1078
|
-
});
|
|
1079
|
-
}
|
|
1080
|
-
});
|
|
1081
|
-
return params.outputPath;
|
|
1082
|
-
}
|
|
1083
|
-
throw new Error(IMAGE_SIZE_LIMIT_ERROR);
|
|
1084
|
-
} finally {
|
|
1085
|
-
await page?.close().catch(() => {});
|
|
1086
|
-
await lease.release();
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
};
|
|
1090
|
-
function buildBrowserUnavailableError(error) {
|
|
1091
|
-
const reason = formatErrorMessage(error);
|
|
1092
|
-
return new Error(`Diff PNG/PDF rendering requires a Chromium-compatible browser. Set browser.executablePath or install Chrome/Chromium. ${reason}`, { cause: error });
|
|
1093
|
-
}
|
|
1094
|
-
async function writeExternalArtifactFile(params) {
|
|
1095
|
-
const rootDir = path.dirname(params.outputPath);
|
|
1096
|
-
await fs$1.mkdir(rootDir, { recursive: true });
|
|
1097
|
-
await writeExternalFileWithinRoot({
|
|
1098
|
-
rootDir,
|
|
1099
|
-
path: path.basename(params.outputPath),
|
|
1100
|
-
write: params.write
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
function injectBaseHref(html) {
|
|
1104
|
-
if (html.includes("<base ")) return html;
|
|
1105
|
-
return html.replace("<head>", `<head><base href="${LOCAL_VIEWER_BASE_HREF}" />`);
|
|
1106
|
-
}
|
|
1107
|
-
async function resolveBrowserExecutablePath(config) {
|
|
1108
|
-
const cacheKey = JSON.stringify({
|
|
1109
|
-
configPath: config.browser?.executablePath?.trim() || "",
|
|
1110
|
-
env: [
|
|
1111
|
-
process.env.OPENCLAW_BROWSER_EXECUTABLE_PATH ?? "",
|
|
1112
|
-
process.env.BROWSER_EXECUTABLE_PATH ?? "",
|
|
1113
|
-
process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH ?? ""
|
|
1114
|
-
],
|
|
1115
|
-
path: process.env.PATH ?? ""
|
|
1116
|
-
});
|
|
1117
|
-
if (executablePathCache?.key === cacheKey) return await executablePathCache.valuePromise;
|
|
1118
|
-
const valuePromise = resolveBrowserExecutablePathUncached(config).catch((error) => {
|
|
1119
|
-
if (executablePathCache?.valuePromise === valuePromise) executablePathCache = null;
|
|
1120
|
-
throw error;
|
|
1121
|
-
});
|
|
1122
|
-
executablePathCache = {
|
|
1123
|
-
key: cacheKey,
|
|
1124
|
-
valuePromise
|
|
1125
|
-
};
|
|
1126
|
-
return await valuePromise;
|
|
1127
|
-
}
|
|
1128
|
-
async function resolveBrowserExecutablePathUncached(config) {
|
|
1129
|
-
const configPath = config.browser?.executablePath?.trim();
|
|
1130
|
-
if (configPath) {
|
|
1131
|
-
await assertExecutable(configPath, "browser.executablePath");
|
|
1132
|
-
return configPath;
|
|
1133
|
-
}
|
|
1134
|
-
const envCandidates = [
|
|
1135
|
-
process.env.OPENCLAW_BROWSER_EXECUTABLE_PATH,
|
|
1136
|
-
process.env.BROWSER_EXECUTABLE_PATH,
|
|
1137
|
-
process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
|
|
1138
|
-
].map((value) => value?.trim()).filter((value) => Boolean(value));
|
|
1139
|
-
for (const candidate of envCandidates) if (await isExecutable(candidate)) return candidate;
|
|
1140
|
-
for (const candidate of await collectExecutableCandidates()) if (await isExecutable(candidate)) return candidate;
|
|
1141
|
-
}
|
|
1142
|
-
async function acquireSharedBrowser(params) {
|
|
1143
|
-
const executablePath = await resolveBrowserExecutablePath(params.config);
|
|
1144
|
-
const desiredKey = executablePath || SHARED_BROWSER_KEY;
|
|
1145
|
-
if (sharedBrowserState && sharedBrowserState.key !== desiredKey) await closeSharedBrowser();
|
|
1146
|
-
if (!sharedBrowserState) {
|
|
1147
|
-
const browserPromise = chromium.launch({
|
|
1148
|
-
headless: true,
|
|
1149
|
-
...executablePath ? { executablePath } : {},
|
|
1150
|
-
args: ["--disable-dev-shm-usage"]
|
|
1151
|
-
}).then((browser) => {
|
|
1152
|
-
if (sharedBrowserState?.browserPromise === browserPromise) {
|
|
1153
|
-
sharedBrowserState.browser = browser;
|
|
1154
|
-
browser.on("disconnected", () => {
|
|
1155
|
-
if (sharedBrowserState?.browser === browser) {
|
|
1156
|
-
clearIdleTimer(sharedBrowserState);
|
|
1157
|
-
sharedBrowserState = null;
|
|
1158
|
-
}
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
return browser;
|
|
1162
|
-
}).catch((error) => {
|
|
1163
|
-
if (sharedBrowserState?.browserPromise === browserPromise) sharedBrowserState = null;
|
|
1164
|
-
throw error;
|
|
1165
|
-
});
|
|
1166
|
-
sharedBrowserState = {
|
|
1167
|
-
browserPromise,
|
|
1168
|
-
idleTimer: null,
|
|
1169
|
-
key: desiredKey,
|
|
1170
|
-
users: 0
|
|
1171
|
-
};
|
|
1172
|
-
}
|
|
1173
|
-
clearIdleTimer(sharedBrowserState);
|
|
1174
|
-
const state = sharedBrowserState;
|
|
1175
|
-
const browser = await state.browserPromise;
|
|
1176
|
-
state.users += 1;
|
|
1177
|
-
let released = false;
|
|
1178
|
-
return {
|
|
1179
|
-
browser,
|
|
1180
|
-
release: async () => {
|
|
1181
|
-
if (released) return;
|
|
1182
|
-
released = true;
|
|
1183
|
-
state.users = Math.max(0, state.users - 1);
|
|
1184
|
-
if (state.users === 0) scheduleIdleBrowserClose(state, params.idleMs);
|
|
1185
|
-
}
|
|
1186
|
-
};
|
|
1187
|
-
}
|
|
1188
|
-
function scheduleIdleBrowserClose(state, idleMs) {
|
|
1189
|
-
clearIdleTimer(state);
|
|
1190
|
-
state.idleTimer = setTimeout(() => {
|
|
1191
|
-
if (sharedBrowserState === state && state.users === 0) closeSharedBrowser();
|
|
1192
|
-
}, idleMs);
|
|
1193
|
-
state.idleTimer.unref();
|
|
1194
|
-
}
|
|
1195
|
-
function clearIdleTimer(state) {
|
|
1196
|
-
if (!state.idleTimer) return;
|
|
1197
|
-
clearTimeout(state.idleTimer);
|
|
1198
|
-
state.idleTimer = null;
|
|
1199
|
-
}
|
|
1200
|
-
async function closeSharedBrowser() {
|
|
1201
|
-
const state = sharedBrowserState;
|
|
1202
|
-
if (!state) return;
|
|
1203
|
-
sharedBrowserState = null;
|
|
1204
|
-
clearIdleTimer(state);
|
|
1205
|
-
await (state.browser ?? await state.browserPromise.catch(() => null))?.close().catch(() => {});
|
|
1206
|
-
}
|
|
1207
|
-
async function collectExecutableCandidates() {
|
|
1208
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
1209
|
-
for (const command of pathCommandsForPlatform()) {
|
|
1210
|
-
const resolved = await findExecutableInPath(command);
|
|
1211
|
-
if (resolved) candidates.add(resolved);
|
|
1212
|
-
}
|
|
1213
|
-
for (const candidate of commonExecutablePathsForPlatform()) candidates.add(candidate);
|
|
1214
|
-
return [...candidates];
|
|
1215
|
-
}
|
|
1216
|
-
function pathCommandsForPlatform() {
|
|
1217
|
-
if (process.platform === "win32") return [
|
|
1218
|
-
"chrome.exe",
|
|
1219
|
-
"msedge.exe",
|
|
1220
|
-
"brave.exe"
|
|
1221
|
-
];
|
|
1222
|
-
if (process.platform === "darwin") return [
|
|
1223
|
-
"google-chrome",
|
|
1224
|
-
"chromium",
|
|
1225
|
-
"msedge",
|
|
1226
|
-
"brave-browser",
|
|
1227
|
-
"brave"
|
|
1228
|
-
];
|
|
1229
|
-
return [
|
|
1230
|
-
"chromium",
|
|
1231
|
-
"chromium-browser",
|
|
1232
|
-
"google-chrome",
|
|
1233
|
-
"google-chrome-stable",
|
|
1234
|
-
"msedge",
|
|
1235
|
-
"brave-browser",
|
|
1236
|
-
"brave"
|
|
1237
|
-
];
|
|
1238
|
-
}
|
|
1239
|
-
function commonExecutablePathsForPlatform() {
|
|
1240
|
-
if (process.platform === "darwin") return [
|
|
1241
|
-
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
1242
|
-
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
1243
|
-
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
1244
|
-
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
1245
|
-
];
|
|
1246
|
-
if (process.platform === "win32") {
|
|
1247
|
-
const localAppData = process.env.LOCALAPPDATA ?? "";
|
|
1248
|
-
const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
|
|
1249
|
-
const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
1250
|
-
return [
|
|
1251
|
-
path.join(localAppData, "Google", "Chrome", "Application", "chrome.exe"),
|
|
1252
|
-
path.join(programFiles, "Google", "Chrome", "Application", "chrome.exe"),
|
|
1253
|
-
path.join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"),
|
|
1254
|
-
path.join(programFiles, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
1255
|
-
path.join(programFilesX86, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
1256
|
-
path.join(programFiles, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
1257
|
-
path.join(programFilesX86, "BraveSoftware", "Brave-Browser", "Application", "brave.exe")
|
|
1258
|
-
];
|
|
1259
|
-
}
|
|
1260
|
-
return [
|
|
1261
|
-
"/usr/bin/chromium",
|
|
1262
|
-
"/usr/bin/chromium-browser",
|
|
1263
|
-
"/usr/bin/google-chrome",
|
|
1264
|
-
"/usr/bin/google-chrome-stable",
|
|
1265
|
-
"/usr/bin/msedge",
|
|
1266
|
-
"/usr/bin/brave-browser",
|
|
1267
|
-
"/snap/bin/chromium"
|
|
1268
|
-
];
|
|
1269
|
-
}
|
|
1270
|
-
async function findExecutableInPath(command) {
|
|
1271
|
-
const pathValue = process.env.PATH;
|
|
1272
|
-
if (!pathValue) return;
|
|
1273
|
-
for (const directory of pathValue.split(path.delimiter)) {
|
|
1274
|
-
if (!directory) continue;
|
|
1275
|
-
const candidate = path.join(directory, command);
|
|
1276
|
-
if (await isExecutable(candidate)) return candidate;
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
async function assertExecutable(candidate, label) {
|
|
1280
|
-
if (!await isExecutable(candidate)) throw new Error(`${label} not found or not executable: ${candidate}`);
|
|
1281
|
-
}
|
|
1282
|
-
async function isExecutable(candidate) {
|
|
1283
|
-
try {
|
|
1284
|
-
await fs$1.access(candidate, constants.X_OK);
|
|
1285
|
-
return true;
|
|
1286
|
-
} catch {
|
|
1287
|
-
return false;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
//#endregion
|
|
1291
735
|
//#region extensions/diffs/src/shiki-curated-languages.ts
|
|
1292
736
|
const javascript = () => import("@shikijs/langs/javascript");
|
|
1293
737
|
const typescript = () => import("@shikijs/langs/typescript");
|
|
@@ -1509,13 +953,12 @@ const BASE_DIFF_VIEWER_LANGUAGE_HINTS = [
|
|
|
1509
953
|
];
|
|
1510
954
|
const BASE_LANGUAGE_HINTS = new Set(BASE_DIFF_VIEWER_LANGUAGE_HINTS);
|
|
1511
955
|
const BASE_LANGUAGE_ALIASES = new Map(bundledLanguagesInfo.flatMap((language) => getBundledLanguageAliases(language).map((alias) => [alias, language.id])));
|
|
1512
|
-
function
|
|
956
|
+
function normalizeLanguageHint(value) {
|
|
1513
957
|
if (typeof value !== "string") return;
|
|
1514
|
-
|
|
1515
|
-
return normalized ? normalized : void 0;
|
|
958
|
+
return value.trim().toLowerCase() || void 0;
|
|
1516
959
|
}
|
|
1517
960
|
async function normalizeSupportedLanguageHint(value, options = {}) {
|
|
1518
|
-
const normalized =
|
|
961
|
+
const normalized = normalizeLanguageHint(value);
|
|
1519
962
|
if (!normalized) return;
|
|
1520
963
|
const baseAlias = BASE_LANGUAGE_ALIASES.get(normalized);
|
|
1521
964
|
if (baseAlias) return baseAlias;
|
|
@@ -2158,6 +1601,7 @@ const MAX_TITLE_BYTES = 1024;
|
|
|
2158
1601
|
const MAX_PATH_BYTES = 2048;
|
|
2159
1602
|
const MAX_LANG_BYTES = 128;
|
|
2160
1603
|
const MAX_DIFF_ARTIFACT_TTL_SECONDS = 21600;
|
|
1604
|
+
const loadDiffsBrowserRuntime = createLazyRuntimeModule(() => import("./browser.runtime-_hkQDewM.js"));
|
|
2161
1605
|
const DiffsToolSchema = Type.Object({
|
|
2162
1606
|
before: Type.Optional(Type.String({ description: "Original text content." })),
|
|
2163
1607
|
after: Type.Optional(Type.String({ description: "Updated text content." })),
|
|
@@ -2201,14 +1645,15 @@ const DiffsToolSchema = Type.Object({
|
|
|
2201
1645
|
baseUrl: Type.Optional(Type.String({ description: "Optional gateway base URL override used when building the viewer URL. Overrides configured viewerBaseUrl, for example https://gateway.example.com." }))
|
|
2202
1646
|
}, { additionalProperties: false });
|
|
2203
1647
|
function createDiffsTool(params) {
|
|
1648
|
+
const loadScreenshotter = async () => params.screenshotter ?? new (await (loadDiffsBrowserRuntime())).PlaywrightDiffScreenshotter({ config: params.api.config });
|
|
2204
1649
|
return {
|
|
2205
1650
|
name: "diffs",
|
|
2206
1651
|
label: "Diffs",
|
|
2207
1652
|
description: "Create a read-only diff viewer from before/after text or a unified patch. Returns a gateway viewer URL for interactive viewing and can also render the same diff to a PNG or PDF.",
|
|
2208
1653
|
parameters: DiffsToolSchema,
|
|
2209
1654
|
execute: async (_toolCallId, rawParams) => {
|
|
2210
|
-
const toolParams = rawParams;
|
|
2211
|
-
const rawRecord =
|
|
1655
|
+
const toolParams = asNonArrayRecord(rawParams);
|
|
1656
|
+
const rawRecord = toolParams;
|
|
2212
1657
|
const artifactContext = buildArtifactContext(params.context);
|
|
2213
1658
|
const input = normalizeDiffInput(toolParams);
|
|
2214
1659
|
if (input.kind === "before_after" && input.before === input.after) return {
|
|
@@ -2250,10 +1695,9 @@ function createDiffsTool(params) {
|
|
|
2250
1695
|
if (error instanceof DiffRenderInputError) throw new PluginToolInputError(error.message);
|
|
2251
1696
|
throw error;
|
|
2252
1697
|
});
|
|
2253
|
-
const screenshotter = params.screenshotter ?? new PlaywrightDiffScreenshotter({ config: params.api.config });
|
|
2254
1698
|
if (isArtifactOnlyMode(mode)) {
|
|
2255
1699
|
const artifactFile = await renderDiffArtifactFile({
|
|
2256
|
-
screenshotter,
|
|
1700
|
+
screenshotter: await loadScreenshotter(),
|
|
2257
1701
|
store: params.store,
|
|
2258
1702
|
html: requireRenderedHtml(rendered.imageHtml, "image"),
|
|
2259
1703
|
theme,
|
|
@@ -2296,7 +1740,8 @@ function createDiffsTool(params) {
|
|
|
2296
1740
|
const viewerUrl = buildViewerUrl({
|
|
2297
1741
|
config: params.api.config,
|
|
2298
1742
|
viewerPath: artifact.viewerPath,
|
|
2299
|
-
baseUrl: normalizeBaseUrl(toolParams.baseUrl)
|
|
1743
|
+
baseUrl: normalizeBaseUrl(toolParams.baseUrl),
|
|
1744
|
+
viewerBaseUrl: params.viewerBaseUrl
|
|
2300
1745
|
});
|
|
2301
1746
|
const baseDetails = {
|
|
2302
1747
|
changed: true,
|
|
@@ -2319,7 +1764,7 @@ function createDiffsTool(params) {
|
|
|
2319
1764
|
};
|
|
2320
1765
|
try {
|
|
2321
1766
|
const artifactFile = await renderDiffArtifactFile({
|
|
2322
|
-
screenshotter,
|
|
1767
|
+
screenshotter: await loadScreenshotter(),
|
|
2323
1768
|
store: params.store,
|
|
2324
1769
|
html: requireRenderedHtml(rendered.imageHtml, "image"),
|
|
2325
1770
|
theme,
|