@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/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 fs, { constants } from "node:fs";
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, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
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 = typeof config.gateway?.port === "number" ? config.gateway.port : DEFAULT_GATEWAY_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 = new 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 normalizeOptionalString$1(value) {
956
+ function normalizeLanguageHint(value) {
1513
957
  if (typeof value !== "string") return;
1514
- const normalized = value.trim().toLowerCase();
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 = normalizeOptionalString$1(value);
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 = rawParams;
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) ?? params.viewerBaseUrl
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,