@openclaw/diffs 2026.5.7 → 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.
- package/dist/index.js +78 -60
- 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
|
|
601
|
-
await
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
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
|
|
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
|
|
1269
|
+
const { value: theme } = await readJsonFileWithFallback(themeRequire.resolve(themeSpecifier), {});
|
|
1250
1270
|
cachedTheme = {
|
|
1251
|
-
...
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
33
|
+
"pluginApi": ">=2026.5.9-beta.1"
|
|
34
34
|
},
|
|
35
35
|
"build": {
|
|
36
|
-
"openclawVersion": "2026.5.
|
|
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.
|
|
59
|
+
"openclaw": ">=2026.5.9-beta.1"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
62
|
"openclaw": {
|