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