@openclaw/diffs 2026.5.7-beta.1 → 2026.5.9-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +78 -60
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -10,12 +10,15 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "opencl
10
10
  import crypto from "node:crypto";
11
11
  import fs from "node:fs/promises";
12
12
  import { fileURLToPath } from "node:url";
13
+ import { root, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
14
+ import { stringEnum } from "openclaw/plugin-sdk/channel-actions";
13
15
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
14
16
  import { Type } from "typebox";
15
17
  import { constants } from "node:fs";
16
18
  import { chromium } from "playwright-core";
17
19
  import { RegisteredCustomThemes, ResolvedThemes, ResolvingThemes, parsePatchFiles, resolveLanguage } from "@pierre/diffs";
18
20
  import { preloadFileDiff, preloadMultiFileDiff } from "@pierre/diffs/ssr";
21
+ import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
19
22
  //#region extensions/diffs/src/types.ts
20
23
  const DIFF_LAYOUTS = ["unified", "split"];
21
24
  const DIFF_MODES = [
@@ -597,8 +600,9 @@ var DiffArtifactStore = class {
597
600
  htmlPath,
598
601
  ...params.context ? { context: params.context } : {}
599
602
  };
600
- await fs.mkdir(artifactDir, { recursive: true });
601
- await fs.writeFile(htmlPath, params.html, "utf8");
603
+ const root = await this.artifactRoot();
604
+ await root.mkdir(id);
605
+ await root.write(path.posix.join(id, "viewer.html"), params.html);
602
606
  await this.writeMeta(meta);
603
607
  this.scheduleCleanup();
604
608
  return meta;
@@ -617,7 +621,7 @@ var DiffArtifactStore = class {
617
621
  const meta = await this.readMeta(id);
618
622
  if (!meta) throw new Error(`Diff artifact not found: ${id}`);
619
623
  const htmlPath = this.normalizeStoredPath(meta.htmlPath, "htmlPath");
620
- return await fs.readFile(htmlPath, "utf8");
624
+ return await (await this.artifactRoot()).readText(this.relativeStoredPath(htmlPath));
621
625
  }
622
626
  async updateFilePath(id, filePath) {
623
627
  const meta = await this.readMeta(id);
@@ -654,7 +658,7 @@ var DiffArtifactStore = class {
654
658
  filePath: this.normalizeStoredPath(filePath, "filePath"),
655
659
  ...params.context ? { context: params.context } : {}
656
660
  };
657
- await fs.mkdir(artifactDir, { recursive: true });
661
+ await (await this.artifactRoot()).mkdir(id);
658
662
  await this.writeStandaloneMeta(meta);
659
663
  this.scheduleCleanup();
660
664
  return {
@@ -671,10 +675,9 @@ var DiffArtifactStore = class {
671
675
  this.maybeCleanupExpired();
672
676
  }
673
677
  async cleanupExpired() {
674
- await this.ensureRoot();
675
- const entries = await fs.readdir(this.rootDir, { withFileTypes: true }).catch(() => []);
678
+ const entries = await (await this.artifactRoot()).list("", { withFileTypes: true }).catch(() => []);
676
679
  const now = Date.now();
677
- await Promise.all(entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
680
+ await Promise.all(entries.filter((entry) => entry.isDirectory).map(async (entry) => {
678
681
  const id = entry.name;
679
682
  const meta = await this.readMeta(id);
680
683
  if (meta) {
@@ -686,15 +689,16 @@ var DiffArtifactStore = class {
686
689
  if (isExpired(standaloneMeta)) await this.deleteArtifact(id);
687
690
  return;
688
691
  }
689
- const artifactPath = this.artifactDir(id);
690
- const stat = await fs.stat(artifactPath).catch(() => null);
691
- if (!stat) return;
692
- if (now - stat.mtimeMs > SWEEP_FALLBACK_AGE_MS) await this.deleteArtifact(id);
692
+ if (now - entry.mtimeMs > SWEEP_FALLBACK_AGE_MS) await this.deleteArtifact(id);
693
693
  }));
694
694
  }
695
695
  async ensureRoot() {
696
696
  await fs.mkdir(this.rootDir, { recursive: true });
697
697
  }
698
+ async artifactRoot() {
699
+ await this.ensureRoot();
700
+ return await root(this.rootDir);
701
+ }
698
702
  maybeCleanupExpired() {
699
703
  const now = Date.now();
700
704
  if (this.cleanupInFlight || now < this.nextCleanupAt) return;
@@ -740,15 +744,12 @@ var DiffArtifactStore = class {
740
744
  return null;
741
745
  }
742
746
  }
743
- metaFilePath(id, fileName) {
744
- return path.join(this.artifactDir(id), fileName);
745
- }
746
747
  async writeJsonMeta(id, fileName, data) {
747
- await fs.writeFile(this.metaFilePath(id, fileName), JSON.stringify(data, null, 2), "utf8");
748
+ await (await this.artifactRoot()).writeJson(path.posix.join(id, fileName), data, { space: 2 });
748
749
  }
749
750
  async readJsonMeta(id, fileName, context) {
750
751
  try {
751
- const raw = await fs.readFile(this.metaFilePath(id, fileName), "utf8");
752
+ const raw = await (await this.artifactRoot()).readText(path.posix.join(id, fileName));
752
753
  return JSON.parse(raw);
753
754
  } catch (error) {
754
755
  if (isFileNotFound(error)) return null;
@@ -772,6 +773,9 @@ var DiffArtifactStore = class {
772
773
  this.assertWithinRoot(candidate, label);
773
774
  return candidate;
774
775
  }
776
+ relativeStoredPath(storedPath) {
777
+ return path.relative(this.rootDir, this.normalizeStoredPath(storedPath, "path")).split(path.sep).join(path.posix.sep);
778
+ }
775
779
  assertWithinRoot(candidate, label = "path") {
776
780
  const relative = path.relative(this.rootDir, candidate);
777
781
  if (relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative)) return;
@@ -790,7 +794,8 @@ function isExpired(meta) {
790
794
  return Date.now() >= expiresAt;
791
795
  }
792
796
  function isFileNotFound(error) {
793
- return error instanceof Error && "code" in error && error.code === "ENOENT";
797
+ const code = error instanceof Error && "code" in error ? error.code : void 0;
798
+ return code === "ENOENT" || code === "not-found";
794
799
  }
795
800
  function normalizeArtifactContext(value) {
796
801
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
@@ -819,7 +824,6 @@ var PlaywrightDiffScreenshotter = class {
819
824
  this.browserIdleMs = params.browserIdleMs ?? DEFAULT_BROWSER_IDLE_MS;
820
825
  }
821
826
  async screenshotHtml(params) {
822
- await fs.mkdir(path.dirname(params.outputPath), { recursive: true });
823
827
  const lease = await acquireSharedBrowser({
824
828
  config: this.config,
825
829
  idleMs: this.browserIdleMs
@@ -918,16 +922,22 @@ var PlaywrightDiffScreenshotter = class {
918
922
  const estimatedPixels = pdfWidth * pdfHeight;
919
923
  const estimatedPages = Math.ceil(pdfHeight / PDF_REFERENCE_PAGE_HEIGHT_PX);
920
924
  if (estimatedPixels > params.image.maxPixels || estimatedPages > MAX_PDF_PAGES) throw new Error(IMAGE_SIZE_LIMIT_ERROR);
921
- await page.pdf({
922
- path: params.outputPath,
923
- width: `${pdfWidth}px`,
924
- height: `${pdfHeight}px`,
925
- printBackground: true,
926
- margin: {
927
- top: "0",
928
- right: "0",
929
- bottom: "0",
930
- left: "0"
925
+ const pageForPdf = page;
926
+ await writeExternalArtifactFile({
927
+ outputPath: params.outputPath,
928
+ write: async (tempPath) => {
929
+ await pageForPdf.pdf({
930
+ path: tempPath,
931
+ width: `${pdfWidth}px`,
932
+ height: `${pdfHeight}px`,
933
+ printBackground: true,
934
+ margin: {
935
+ top: "0",
936
+ right: "0",
937
+ bottom: "0",
938
+ left: "0"
939
+ }
940
+ });
931
941
  }
932
942
  });
933
943
  return params.outputPath;
@@ -956,15 +966,21 @@ var PlaywrightDiffScreenshotter = class {
956
966
  }
957
967
  throw new Error(IMAGE_SIZE_LIMIT_ERROR);
958
968
  }
959
- await page.screenshot({
960
- path: params.outputPath,
961
- type: "png",
962
- scale: "device",
963
- clip: {
964
- x,
965
- y,
966
- width: cssWidth,
967
- height: cssHeight
969
+ const pageForScreenshot = page;
970
+ await writeExternalArtifactFile({
971
+ outputPath: params.outputPath,
972
+ write: async (tempPath) => {
973
+ await pageForScreenshot.screenshot({
974
+ path: tempPath,
975
+ type: "png",
976
+ scale: "device",
977
+ clip: {
978
+ x,
979
+ y,
980
+ width: cssWidth,
981
+ height: cssHeight
982
+ }
983
+ });
968
984
  }
969
985
  });
970
986
  return params.outputPath;
@@ -980,6 +996,15 @@ var PlaywrightDiffScreenshotter = class {
980
996
  }
981
997
  }
982
998
  };
999
+ async function writeExternalArtifactFile(params) {
1000
+ const rootDir = path.dirname(params.outputPath);
1001
+ await fs.mkdir(rootDir, { recursive: true });
1002
+ await writeExternalFileWithinRoot({
1003
+ rootDir,
1004
+ path: path.basename(params.outputPath),
1005
+ write: params.write
1006
+ });
1007
+ }
983
1008
  function injectBaseHref(html) {
984
1009
  if (html.includes("<base ")) return html;
985
1010
  return html.replace("<head>", `<head><base href="${LOCAL_VIEWER_BASE_HREF}" />`);
@@ -1169,13 +1194,8 @@ async function isExecutable(candidate) {
1169
1194
  //#endregion
1170
1195
  //#region extensions/diffs/src/language-hints.ts
1171
1196
  const PASSTHROUGH_LANGUAGE_HINTS = new Set(["ansi", "text"]);
1172
- function normalizeOptionalString$1(value) {
1173
- if (typeof value !== "string") return;
1174
- const trimmed = value.trim();
1175
- return trimmed ? trimmed : void 0;
1176
- }
1177
1197
  async function normalizeSupportedLanguageHint(value) {
1178
- const normalized = normalizeOptionalString$1(value);
1198
+ const normalized = normalizeOptionalString(value);
1179
1199
  if (!normalized) return;
1180
1200
  if (PASSTHROUGH_LANGUAGE_HINTS.has(normalized)) return normalized;
1181
1201
  try {
@@ -1246,9 +1266,9 @@ function createThemeLoader(themeName, themeSpecifier) {
1246
1266
  let cachedTheme;
1247
1267
  return async () => {
1248
1268
  if (cachedTheme) return cachedTheme;
1249
- const themePath = themeRequire.resolve(themeSpecifier);
1269
+ const { value: theme } = await readJsonFileWithFallback(themeRequire.resolve(themeSpecifier), {});
1250
1270
  cachedTheme = {
1251
- ...JSON.parse(await fs.readFile(themePath, "utf8")),
1271
+ ...theme,
1252
1272
  name: themeName
1253
1273
  };
1254
1274
  return cachedTheme;
@@ -1677,14 +1697,6 @@ const MAX_PATCH_BYTES = 2 * 1024 * 1024;
1677
1697
  const MAX_TITLE_BYTES = 1024;
1678
1698
  const MAX_PATH_BYTES = 2048;
1679
1699
  const MAX_LANG_BYTES = 128;
1680
- function stringEnum(values, description, options = {}) {
1681
- return Type.Unsafe({
1682
- type: "string",
1683
- enum: [...values],
1684
- description,
1685
- ...options
1686
- });
1687
- }
1688
1700
  const DiffsToolSchema = Type.Object({
1689
1701
  before: Type.Optional(Type.String({ description: "Original text content." })),
1690
1702
  after: Type.Optional(Type.String({ description: "Updated text content." })),
@@ -1704,11 +1716,11 @@ const DiffsToolSchema = Type.Object({
1704
1716
  description: "Optional title for the rendered diff.",
1705
1717
  maxLength: MAX_TITLE_BYTES
1706
1718
  })),
1707
- mode: Type.Optional(stringEnum(DIFF_MODES, "Output mode: view, file, image (deprecated alias for file), or both. Default: both.")),
1708
- theme: Type.Optional(stringEnum(DIFF_THEMES, "Viewer theme. Default: dark.")),
1709
- layout: Type.Optional(stringEnum(DIFF_LAYOUTS, "Diff layout. Default: unified.")),
1710
- fileQuality: Type.Optional(stringEnum(DIFF_IMAGE_QUALITY_PRESETS, "File quality preset: standard, hq, or print.")),
1711
- fileFormat: Type.Optional(stringEnum(DIFF_OUTPUT_FORMATS, "Rendered file format: png or pdf.")),
1719
+ mode: Type.Optional(stringEnum(DIFF_MODES, { description: "Output mode: view, file, image (deprecated alias for file), or both. Default: both." })),
1720
+ theme: Type.Optional(stringEnum(DIFF_THEMES, { description: "Viewer theme. Default: dark." })),
1721
+ layout: Type.Optional(stringEnum(DIFF_LAYOUTS, { description: "Diff layout. Default: unified." })),
1722
+ fileQuality: Type.Optional(stringEnum(DIFF_IMAGE_QUALITY_PRESETS, { description: "File quality preset: standard, hq, or print." })),
1723
+ fileFormat: Type.Optional(stringEnum(DIFF_OUTPUT_FORMATS, { description: "Rendered file format: png or pdf." })),
1712
1724
  fileScale: Type.Optional(Type.Number({
1713
1725
  description: "Optional rendered-file device scale factor override (1-4).",
1714
1726
  minimum: 1,
@@ -1720,9 +1732,15 @@ const DiffsToolSchema = Type.Object({
1720
1732
  maximum: 2400
1721
1733
  })),
1722
1734
  /** @deprecated Use fileQuality. */
1723
- imageQuality: Type.Optional(stringEnum(DIFF_IMAGE_QUALITY_PRESETS, "Deprecated alias for fileQuality.", { deprecated: true })),
1735
+ imageQuality: Type.Optional(stringEnum(DIFF_IMAGE_QUALITY_PRESETS, {
1736
+ description: "Deprecated alias for fileQuality.",
1737
+ deprecated: true
1738
+ })),
1724
1739
  /** @deprecated Use fileFormat. */
1725
- imageFormat: Type.Optional(stringEnum(DIFF_OUTPUT_FORMATS, "Deprecated alias for fileFormat.", { deprecated: true })),
1740
+ imageFormat: Type.Optional(stringEnum(DIFF_OUTPUT_FORMATS, {
1741
+ description: "Deprecated alias for fileFormat.",
1742
+ deprecated: true
1743
+ })),
1726
1744
  /** @deprecated Use fileScale. */
1727
1745
  imageScale: Type.Optional(Type.Number({
1728
1746
  description: "Deprecated alias for fileScale.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/diffs",
3
- "version": "2026.5.7-beta.1",
3
+ "version": "2026.5.9-beta.1",
4
4
  "description": "OpenClaw diff viewer plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,7 @@
14
14
  "@pierre/diffs": "1.1.20",
15
15
  "@pierre/theme": "0.0.29",
16
16
  "playwright-core": "1.59.1",
17
- "typebox": "1.1.37"
17
+ "typebox": "1.1.38"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@openclaw/plugin-sdk": "workspace:*"
@@ -30,10 +30,10 @@
30
30
  "minHostVersion": ">=2026.4.30"
31
31
  },
32
32
  "compat": {
33
- "pluginApi": ">=2026.5.7-beta.1"
33
+ "pluginApi": ">=2026.5.9-beta.1"
34
34
  },
35
35
  "build": {
36
- "openclawVersion": "2026.5.7-beta.1",
36
+ "openclawVersion": "2026.5.9-beta.1",
37
37
  "staticAssets": [
38
38
  {
39
39
  "source": "./assets/viewer-runtime.js",
@@ -56,7 +56,7 @@
56
56
  "skills/**"
57
57
  ],
58
58
  "peerDependencies": {
59
- "openclaw": ">=2026.5.7-beta.1"
59
+ "openclaw": ">=2026.5.9-beta.1"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "openclaw": {