@openclaw/diffs 2026.7.2-beta.7 → 2026.7.33
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 +9 -2
- package/dist/assets/viewer-runtime.js +31 -31
- package/dist/index.js +307 -290
- package/node_modules/@pierre/diffs/node_modules/@shikijs/core/package.json +3 -3
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/package.json +2 -2
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-oniguruma/package.json +2 -2
- package/node_modules/@pierre/diffs/node_modules/@shikijs/primitive/dist/index.mjs +5 -6
- package/node_modules/@pierre/diffs/node_modules/@shikijs/primitive/package.json +2 -2
- package/node_modules/@pierre/diffs/node_modules/@shikijs/themes/package.json +2 -2
- package/node_modules/@pierre/diffs/node_modules/@shikijs/transformers/package.json +3 -3
- package/node_modules/@pierre/diffs/node_modules/@shikijs/types/dist/index.d.mts +1 -9
- package/node_modules/@pierre/diffs/node_modules/@shikijs/types/package.json +1 -1
- package/node_modules/@pierre/diffs/node_modules/@types/hast/README.md +2 -2
- package/node_modules/@pierre/diffs/node_modules/@types/hast/index.d.ts +0 -642
- package/node_modules/@pierre/diffs/node_modules/@types/hast/package.json +3 -9
- package/node_modules/@pierre/diffs/node_modules/@ungap/structured-clone/cjs/deserialize.js +1 -6
- package/node_modules/@pierre/diffs/node_modules/@ungap/structured-clone/cjs/serialize.js +2 -2
- package/node_modules/@pierre/diffs/node_modules/@ungap/structured-clone/esm/deserialize.js +1 -6
- package/node_modules/@pierre/diffs/node_modules/@ungap/structured-clone/esm/serialize.js +2 -2
- package/node_modules/@pierre/diffs/node_modules/@ungap/structured-clone/package.json +1 -1
- package/node_modules/@pierre/diffs/node_modules/@ungap/structured-clone/structured-json.js +1 -1
- package/node_modules/@pierre/diffs/node_modules/shiki/package.json +7 -7
- package/node_modules/@shikijs/langs/node_modules/@shikijs/types/dist/index.d.mts +1 -9
- package/node_modules/@shikijs/langs/node_modules/@shikijs/types/package.json +1 -1
- package/node_modules/@shikijs/langs/node_modules/@types/hast/README.md +2 -2
- package/node_modules/@shikijs/langs/node_modules/@types/hast/index.d.ts +0 -642
- package/node_modules/@shikijs/langs/node_modules/@types/hast/package.json +3 -9
- package/node_modules/@shikijs/langs/package.json +2 -2
- package/node_modules/playwright-core/browsers.json +18 -12
- package/node_modules/playwright-core/lib/bootstrap.js +1 -1
- package/node_modules/playwright-core/lib/coreBundle.js +12247 -13691
- package/node_modules/playwright-core/lib/server/deviceDescriptorsSource.json +2739 -0
- package/node_modules/playwright-core/lib/server/electron/loader.js +2 -5
- package/node_modules/playwright-core/lib/serverRegistry.js +1 -5
- package/node_modules/playwright-core/lib/tools/cli-client/help.json +6 -21
- package/node_modules/playwright-core/lib/tools/cli-client/program.js +1 -1
- package/node_modules/playwright-core/lib/tools/cli-client/session.js +0 -4
- package/node_modules/playwright-core/lib/tools/{skills/playwright-cli → cli-client/skill}/SKILL.md +5 -21
- package/node_modules/playwright-core/lib/tools/{skills/playwright-cli/references/test-generation.md → cli-client/skill/references/spec-driven-testing.md} +7 -135
- package/node_modules/playwright-core/lib/tools/cli-client/skill/references/test-generation.md +134 -0
- package/node_modules/playwright-core/lib/tools/utils/extension.js +8 -31
- package/node_modules/playwright-core/lib/utilsBundle.js +48599 -52627
- package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +268 -391
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-BY2S1tHT.css +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-C_5TMfeg.js +52 -0
- package/node_modules/playwright-core/lib/vite/dashboard/index.html +2 -2
- package/node_modules/playwright-core/lib/vite/htmlReport/report.css +1 -2
- package/node_modules/playwright-core/lib/vite/htmlReport/report.js +55 -15
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DYBRYzYX.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DeBYQozu.js +32 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-4ZiSSCmn.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-Bq-mQf8S.js +193 -0
- package/node_modules/playwright-core/lib/vite/recorder/index.html +2 -2
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-LEHpjmcn.js +32 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-BNmKHKpQ.js +264 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-BYQrIQwR.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-CsJ4vdCR.js +9 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.DYBRYzYX.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.CjdS-WJx.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.CzXZzn5A.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.DMMX1gXU.js +2 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.html +5 -5
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +2 -2
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.v8KI4P3m.js +2 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +4 -3
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.BZQ54Kgt.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.Ut8wwJNp.js +6 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +5 -5
- package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.DYP7pi_n.css +32 -0
- package/node_modules/playwright-core/lib/xdg-open +137 -338
- package/node_modules/playwright-core/package.json +2 -2
- package/node_modules/playwright-core/types/protocol.d.ts +36 -249
- package/node_modules/playwright-core/types/structs.d.ts +1 -3
- package/node_modules/playwright-core/types/types.d.ts +268 -2011
- package/node_modules/typebox/build/compile/validator.d.mts +4 -1
- package/node_modules/typebox/build/guard/guard.d.mts +1 -1
- package/node_modules/typebox/build/guard/guard.mjs +2 -5
- package/node_modules/typebox/build/guard/string.mjs +4 -16
- package/node_modules/typebox/build/schema/engine/_functions.mjs +15 -34
- package/node_modules/typebox/build/type/action/readonly_object.d.mts +0 -5
- package/node_modules/typebox/build/type/action/readonly_object.mjs +0 -8
- package/node_modules/typebox/build/typebox.d.mts +1 -1
- package/node_modules/typebox/build/typebox.mjs +1 -1
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +32 -29
- package/openclaw.plugin.json +0 -1
- package/package.json +7 -7
- package/skills/diffs/SKILL.md +2 -2
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +0 -143
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +0 -144
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +0 -85
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +0 -67
- package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +0 -75
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-CyWAfh-p.js +0 -11
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-DhC616m4.css +0 -1
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +0 -1
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +0 -32
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-DYjdXIbE.js +0 -129
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-l_lX622x.css +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-By56iMx7.js +0 -32
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +0 -181
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-COQkjINf.js +0 -7
- package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/index.Dl36UVQT.js +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.C7UW1sC9.css +0 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.D962mr9b.js +0 -5
- package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +0 -1
- package/node_modules/playwright-core/lib/webp_codec.LICENSE +0 -173
- package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
- /package/node_modules/playwright-core/lib/tools/{skills/playwright-cli → cli-client/skill}/references/element-attributes.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{skills/playwright-cli → cli-client/skill}/references/playwright-tests.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{skills/playwright-cli → cli-client/skill}/references/request-mocking.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{skills/playwright-cli → cli-client/skill}/references/running-code.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{skills/playwright-cli → cli-client/skill}/references/session-management.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{skills/playwright-cli → cli-client/skill}/references/storage-state.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{skills/playwright-cli → cli-client/skill}/references/tracing.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{skills/playwright-cli → cli-client/skill}/references/video-recording.md +0 -0
- /package/node_modules/playwright-core/lib/tools/{skills/playwright-trace → trace}/SKILL.md +0 -0
package/dist/index.js
CHANGED
|
@@ -6,22 +6,19 @@ import { z } from "zod";
|
|
|
6
6
|
import fs, { constants } from "node:fs";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { resolveLivePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
9
|
-
import { isLoopbackHost } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
10
9
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
11
10
|
import crypto from "node:crypto";
|
|
12
11
|
import fs$1 from "node:fs/promises";
|
|
13
12
|
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { gunzip, gzip } from "node:zlib";
|
|
15
13
|
import { MAX_DATE_TIMESTAMP_MS, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
|
|
16
|
-
import { safeEqualSecret, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
|
|
14
|
+
import { root, safeEqualSecret, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
|
|
17
15
|
import { optionalFiniteNumberSchema, stringEnum } from "openclaw/plugin-sdk/channel-actions";
|
|
18
16
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
19
17
|
import { readFiniteNumberParam } from "openclaw/plugin-sdk/param-readers";
|
|
20
18
|
import { Type } from "typebox";
|
|
21
19
|
import { chromium } from "playwright-core";
|
|
22
20
|
import { parsePatchFiles, resolveLanguage } from "@pierre/diffs";
|
|
23
|
-
import {
|
|
24
|
-
import { escapeHtml } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
21
|
+
import { preloadFileDiff, preloadMultiFileDiff } from "@pierre/diffs/ssr";
|
|
25
22
|
//#region extensions/diffs/src/types.ts
|
|
26
23
|
const DIFF_LAYOUTS = ["unified", "split"];
|
|
27
24
|
const DIFF_MODES = [
|
|
@@ -287,15 +284,15 @@ function normalizeTtlSeconds(ttlSeconds) {
|
|
|
287
284
|
return Math.min(Math.max(Math.floor(ttlSeconds), 1), 21600);
|
|
288
285
|
}
|
|
289
286
|
function resolveDiffImageRenderOptions(params) {
|
|
290
|
-
const format = normalizeFileFormat(params.fileFormat ?? params.defaults.fileFormat);
|
|
291
|
-
const qualityOverrideProvided = params.fileQuality !== void 0;
|
|
292
|
-
const qualityPreset = normalizeFileQuality$1(params.fileQuality ?? params.defaults.fileQuality);
|
|
287
|
+
const format = normalizeFileFormat(params.fileFormat ?? params.imageFormat ?? params.format ?? params.defaults.fileFormat);
|
|
288
|
+
const qualityOverrideProvided = params.fileQuality !== void 0 || params.imageQuality !== void 0;
|
|
289
|
+
const qualityPreset = normalizeFileQuality$1(params.fileQuality ?? params.imageQuality ?? params.defaults.fileQuality);
|
|
293
290
|
const profile = DEFAULT_IMAGE_QUALITY_PROFILES[qualityPreset];
|
|
294
291
|
return {
|
|
295
292
|
format,
|
|
296
293
|
qualityPreset,
|
|
297
|
-
scale: normalizeFileScale(params.fileScale, qualityOverrideProvided ? profile.scale : params.defaults.fileScale),
|
|
298
|
-
maxWidth: normalizeFileMaxWidth(params.fileMaxWidth, qualityOverrideProvided ? profile.maxWidth : params.defaults.fileMaxWidth),
|
|
294
|
+
scale: normalizeFileScale(params.fileScale ?? params.imageScale, qualityOverrideProvided ? profile.scale : params.defaults.fileScale),
|
|
295
|
+
maxWidth: normalizeFileMaxWidth(params.fileMaxWidth ?? params.imageMaxWidth, qualityOverrideProvided ? profile.maxWidth : params.defaults.fileMaxWidth),
|
|
299
296
|
maxPixels: profile.maxPixels
|
|
300
297
|
};
|
|
301
298
|
}
|
|
@@ -474,20 +471,19 @@ function createDiffsHttpHandler(params) {
|
|
|
474
471
|
respondText(res, 404, "Diff not found");
|
|
475
472
|
return true;
|
|
476
473
|
}
|
|
474
|
+
if (!await params.store.getArtifact(id, token)) {
|
|
475
|
+
recordRemoteFailure(viewerFailureLimiter, access);
|
|
476
|
+
respondText(res, 404, "Diff not found or expired");
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
477
479
|
try {
|
|
478
|
-
const
|
|
479
|
-
if (!viewer) {
|
|
480
|
-
recordRemoteFailure(viewerFailureLimiter, access);
|
|
481
|
-
respondText(res, 404, "Diff not found or expired");
|
|
482
|
-
return true;
|
|
483
|
-
}
|
|
480
|
+
const html = await params.store.readHtml(id);
|
|
484
481
|
resetRemoteFailures(viewerFailureLimiter, access);
|
|
485
482
|
res.statusCode = 200;
|
|
486
483
|
setSharedHeaders(res, "text/html; charset=utf-8");
|
|
487
484
|
res.setHeader("content-security-policy", VIEWER_CONTENT_SECURITY_POLICY);
|
|
488
|
-
res.setHeader("content-length", String(viewer.html.byteLength));
|
|
489
485
|
if (req.method === "HEAD") res.end();
|
|
490
|
-
else res.end(
|
|
486
|
+
else res.end(html);
|
|
491
487
|
return true;
|
|
492
488
|
} catch (error) {
|
|
493
489
|
recordRemoteFailure(viewerFailureLimiter, access);
|
|
@@ -544,7 +540,7 @@ function normalizeRemoteClientKey(remoteAddress) {
|
|
|
544
540
|
return normalized.startsWith("::ffff:") ? normalized.slice(7) : normalized;
|
|
545
541
|
}
|
|
546
542
|
function isLoopbackClientIp(clientIp) {
|
|
547
|
-
return
|
|
543
|
+
return clientIp === "127.0.0.1" || clientIp === "::1";
|
|
548
544
|
}
|
|
549
545
|
function hasProxyForwardingHints(req) {
|
|
550
546
|
const headers = req.headers ?? {};
|
|
@@ -627,177 +623,144 @@ const DIFFS_AGENT_GUIDANCE = [
|
|
|
627
623
|
"When you need to show edits as a real diff, prefer the `diffs` tool instead of writing a manual summary.",
|
|
628
624
|
"It accepts either `before` + `after` text or a unified `patch`.",
|
|
629
625
|
"Check `details.changed`: identical before/after input returns `false` without creating an artifact; rendered results return `true`.",
|
|
630
|
-
"`mode=view` returns `details.viewerUrl` for
|
|
631
|
-
"
|
|
626
|
+
"`mode=view` returns `details.viewerUrl` for canvas use; `mode=file` returns `details.filePath`; `mode=both` returns both.",
|
|
627
|
+
"If you need to send the rendered file, use the `message` tool with `path` or `filePath`.",
|
|
632
628
|
"Include `path` when you know the filename, and omit presentation overrides unless needed."
|
|
633
629
|
].join("\n");
|
|
634
630
|
//#endregion
|
|
635
631
|
//#region extensions/diffs/src/store.ts
|
|
636
|
-
const DEFAULT_TTL_MS =
|
|
637
|
-
const MAX_TTL_MS =
|
|
638
|
-
const SWEEP_FALLBACK_AGE_MS =
|
|
639
|
-
const DEFAULT_CLEANUP_INTERVAL_MS =
|
|
640
|
-
const MAX_DECODED_HTML_BYTES = 64 * 1024 * 1024;
|
|
641
|
-
const ARTIFACT_ID_ATTEMPTS = 8;
|
|
632
|
+
const DEFAULT_TTL_MS = 18e5;
|
|
633
|
+
const MAX_TTL_MS = 216e5;
|
|
634
|
+
const SWEEP_FALLBACK_AGE_MS = 864e5;
|
|
635
|
+
const DEFAULT_CLEANUP_INTERVAL_MS = 3e5;
|
|
642
636
|
const VIEWER_PREFIX = "/plugins/diffs/view";
|
|
643
|
-
const EMPTY_BLOB = /* @__PURE__ */ new Uint8Array();
|
|
644
|
-
function isBlobLimitError(error) {
|
|
645
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "PLUGIN_BLOB_LIMIT_EXCEEDED";
|
|
646
|
-
}
|
|
647
637
|
var DiffArtifactStore = class {
|
|
648
638
|
constructor(params) {
|
|
649
|
-
this.renderingFileIds = /* @__PURE__ */ new Set();
|
|
650
639
|
this.cleanupInFlight = null;
|
|
651
640
|
this.nextCleanupAt = 0;
|
|
652
641
|
this.rootDir = path.resolve(params.rootDir);
|
|
653
|
-
this.blobStore = params.blobStore;
|
|
654
642
|
this.logger = params.logger;
|
|
655
643
|
this.cleanupIntervalMs = params.cleanupIntervalMs === void 0 ? DEFAULT_CLEANUP_INTERVAL_MS : Math.max(0, Math.floor(params.cleanupIntervalMs));
|
|
656
644
|
}
|
|
657
645
|
async createArtifact(params) {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
const compressedHtml = await gzipAsync(html);
|
|
646
|
+
await this.ensureRoot();
|
|
647
|
+
const id = crypto.randomBytes(10).toString("hex");
|
|
661
648
|
const token = crypto.randomBytes(24).toString("hex");
|
|
649
|
+
const artifactDir = this.artifactDir(id);
|
|
650
|
+
const htmlPath = path.join(artifactDir, "viewer.html");
|
|
662
651
|
const ttlMs = normalizeTtlMs$1(params.ttlMs);
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
652
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
653
|
+
const createdAtIso = createdAt.toISOString();
|
|
654
|
+
const expiresAt = resolveExpiresAtIso(createdAt.getTime(), ttlMs);
|
|
655
|
+
const meta = {
|
|
656
|
+
id,
|
|
657
|
+
token,
|
|
668
658
|
title: params.title,
|
|
669
659
|
inputKind: params.inputKind,
|
|
670
660
|
fileCount: params.fileCount,
|
|
671
|
-
|
|
661
|
+
createdAt: createdAtIso,
|
|
662
|
+
expiresAt,
|
|
663
|
+
viewerPath: `${VIEWER_PREFIX}/${id}/${token}`,
|
|
664
|
+
htmlPath,
|
|
672
665
|
...params.context ? { context: params.context } : {}
|
|
673
666
|
};
|
|
674
|
-
const
|
|
667
|
+
const root = await this.artifactRoot();
|
|
668
|
+
await root.mkdir(id);
|
|
669
|
+
await root.write(path.posix.join(id, "viewer.html"), params.html);
|
|
670
|
+
await this.writeMeta(meta);
|
|
675
671
|
this.scheduleCleanup();
|
|
676
|
-
return
|
|
672
|
+
return meta;
|
|
677
673
|
}
|
|
678
|
-
async
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (!
|
|
682
|
-
|
|
683
|
-
|
|
674
|
+
async getArtifact(id, token) {
|
|
675
|
+
const meta = await this.readMeta(id);
|
|
676
|
+
if (!meta) return null;
|
|
677
|
+
if (!safeEqualSecret(token, meta.token)) return null;
|
|
678
|
+
if (isExpired(meta)) {
|
|
679
|
+
await this.deleteArtifact(id);
|
|
684
680
|
return null;
|
|
685
681
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
682
|
+
return meta;
|
|
683
|
+
}
|
|
684
|
+
async readHtml(id) {
|
|
685
|
+
const meta = await this.readMeta(id);
|
|
686
|
+
if (!meta) throw new Error(`Diff artifact not found: ${id}`);
|
|
687
|
+
const htmlPath = this.normalizeStoredPath(meta.htmlPath, "htmlPath");
|
|
688
|
+
return await (await this.artifactRoot()).readText(this.relativeStoredPath(htmlPath));
|
|
689
|
+
}
|
|
690
|
+
async updateFilePath(id, filePath) {
|
|
691
|
+
const meta = await this.readMeta(id);
|
|
692
|
+
if (!meta) throw new Error(`Diff artifact not found: ${id}`);
|
|
693
|
+
const normalizedFilePath = this.normalizeStoredPath(filePath, "filePath");
|
|
694
|
+
const next = {
|
|
695
|
+
...meta,
|
|
696
|
+
filePath: normalizedFilePath,
|
|
697
|
+
imagePath: normalizedFilePath
|
|
693
698
|
};
|
|
699
|
+
await this.writeMeta(next);
|
|
700
|
+
return next;
|
|
701
|
+
}
|
|
702
|
+
async updateImagePath(id, imagePath) {
|
|
703
|
+
return this.updateFilePath(id, imagePath);
|
|
704
|
+
}
|
|
705
|
+
allocateFilePath(id, format = "png") {
|
|
706
|
+
return path.join(this.artifactDir(id), `preview.${format}`);
|
|
694
707
|
}
|
|
695
708
|
async createStandaloneFileArtifact(params = {}) {
|
|
709
|
+
await this.ensureRoot();
|
|
710
|
+
const id = crypto.randomBytes(10).toString("hex");
|
|
711
|
+
const artifactDir = this.artifactDir(id);
|
|
696
712
|
const format = params.format ?? "png";
|
|
713
|
+
const filePath = path.join(artifactDir, `preview.${format}`);
|
|
697
714
|
const ttlMs = normalizeTtlMs$1(params.ttlMs);
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
kind: "
|
|
701
|
-
|
|
715
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
716
|
+
const meta = {
|
|
717
|
+
kind: "standalone_file",
|
|
718
|
+
id,
|
|
719
|
+
createdAt: createdAt.toISOString(),
|
|
720
|
+
expiresAt: resolveExpiresAtIso(createdAt.getTime(), ttlMs),
|
|
721
|
+
filePath: this.normalizeStoredPath(filePath, "filePath"),
|
|
702
722
|
...params.context ? { context: params.context } : {}
|
|
703
723
|
};
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
this.renderingFileIds.add(id);
|
|
714
|
-
this.scheduleCleanup();
|
|
715
|
-
return {
|
|
716
|
-
id,
|
|
717
|
-
filePath: path.join(artifactDir, `preview.${format}`),
|
|
718
|
-
expiresAt: resolveEntryExpiresAt(entry),
|
|
719
|
-
...params.context ? { context: params.context } : {}
|
|
720
|
-
};
|
|
721
|
-
} catch (error) {
|
|
722
|
-
await this.blobStore.delete(id).catch(() => false);
|
|
723
|
-
await fs$1.rm(artifactDir, {
|
|
724
|
-
recursive: true,
|
|
725
|
-
force: true
|
|
726
|
-
}).catch(() => {});
|
|
727
|
-
if (isFileExists(error)) continue;
|
|
728
|
-
throw error;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
throw new Error("Failed to allocate a unique diff file artifact id.");
|
|
732
|
-
}
|
|
733
|
-
async completeFileArtifact(id) {
|
|
734
|
-
try {
|
|
735
|
-
const entry = await this.blobStore.lookup(id);
|
|
736
|
-
if (!entry || !isRenderedFileMetadata(entry.metadata)) {
|
|
737
|
-
await fs$1.rm(this.artifactDir(id), {
|
|
738
|
-
recursive: true,
|
|
739
|
-
force: true
|
|
740
|
-
}).catch(() => {});
|
|
741
|
-
throw new Error(`Diff file artifact expired during rendering: ${id}`);
|
|
742
|
-
}
|
|
743
|
-
} finally {
|
|
744
|
-
this.renderingFileIds.delete(id);
|
|
745
|
-
}
|
|
724
|
+
await (await this.artifactRoot()).mkdir(id);
|
|
725
|
+
await this.writeStandaloneMeta(meta);
|
|
726
|
+
this.scheduleCleanup();
|
|
727
|
+
return {
|
|
728
|
+
id,
|
|
729
|
+
filePath: meta.filePath,
|
|
730
|
+
expiresAt: meta.expiresAt,
|
|
731
|
+
...meta.context ? { context: meta.context } : {}
|
|
732
|
+
};
|
|
746
733
|
}
|
|
747
|
-
|
|
748
|
-
this.
|
|
749
|
-
await this.blobStore.delete(id).catch(() => false);
|
|
750
|
-
await fs$1.rm(this.artifactDir(id), {
|
|
751
|
-
recursive: true,
|
|
752
|
-
force: true
|
|
753
|
-
}).catch(() => {});
|
|
734
|
+
allocateImagePath(id, format = "png") {
|
|
735
|
+
return this.allocateFilePath(id, format);
|
|
754
736
|
}
|
|
755
737
|
scheduleCleanup() {
|
|
756
738
|
this.maybeCleanupExpired();
|
|
757
739
|
}
|
|
758
740
|
async cleanupExpired() {
|
|
759
|
-
const
|
|
760
|
-
await Promise.all(expired.map(async (entry) => await this.deleteExpiredFile(entry)));
|
|
761
|
-
const entries = await fs$1.readdir(this.rootDir, { withFileTypes: true }).catch((error) => {
|
|
762
|
-
if (isFileNotFound(error)) return [];
|
|
763
|
-
throw error;
|
|
764
|
-
});
|
|
741
|
+
const entries = await (await this.artifactRoot()).list("", { withFileTypes: true }).catch(() => []);
|
|
765
742
|
const now = Date.now();
|
|
766
|
-
await Promise.all(entries.filter((entry) => entry.isDirectory
|
|
767
|
-
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
743
|
+
await Promise.all(entries.filter((entry) => entry.isDirectory).map(async (entry) => {
|
|
744
|
+
const id = entry.name;
|
|
745
|
+
const meta = await this.readMeta(id);
|
|
746
|
+
if (meta) {
|
|
747
|
+
if (isExpired(meta)) await this.deleteArtifact(id);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const standaloneMeta = await this.readStandaloneMeta(id);
|
|
751
|
+
if (standaloneMeta) {
|
|
752
|
+
if (isExpired(standaloneMeta)) await this.deleteArtifact(id);
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (now - entry.mtimeMs > SWEEP_FALLBACK_AGE_MS) await this.deleteArtifact(id);
|
|
774
756
|
}));
|
|
775
757
|
}
|
|
776
|
-
async
|
|
777
|
-
|
|
778
|
-
const id = crypto.randomBytes(10).toString("hex");
|
|
779
|
-
if (!await this.registerIfAbsentWithCleanup(id, bytes, metadata, ttlMs)) continue;
|
|
780
|
-
const entry = await this.blobStore.lookup(id);
|
|
781
|
-
if (entry) return entry;
|
|
782
|
-
}
|
|
783
|
-
throw new Error("Failed to allocate a unique diff artifact id.");
|
|
784
|
-
}
|
|
785
|
-
async registerIfAbsentWithCleanup(id, bytes, metadata, ttlMs) {
|
|
786
|
-
try {
|
|
787
|
-
return await this.blobStore.registerIfAbsent(id, bytes, metadata, { ttlMs });
|
|
788
|
-
} catch (error) {
|
|
789
|
-
if (!isBlobLimitError(error)) throw error;
|
|
790
|
-
await this.cleanupExpired();
|
|
791
|
-
return await this.blobStore.registerIfAbsent(id, bytes, metadata, { ttlMs });
|
|
792
|
-
}
|
|
758
|
+
async ensureRoot() {
|
|
759
|
+
await fs$1.mkdir(this.rootDir, { recursive: true });
|
|
793
760
|
}
|
|
794
|
-
async
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
await fs$1.rm(this.artifactDir(entry.key), {
|
|
798
|
-
recursive: true,
|
|
799
|
-
force: true
|
|
800
|
-
}).catch(() => {});
|
|
761
|
+
async artifactRoot() {
|
|
762
|
+
await this.ensureRoot();
|
|
763
|
+
return await root(this.rootDir);
|
|
801
764
|
}
|
|
802
765
|
maybeCleanupExpired() {
|
|
803
766
|
const now = Date.now();
|
|
@@ -812,85 +775,104 @@ var DiffArtifactStore = class {
|
|
|
812
775
|
this.cleanupInFlight = cleanupPromise;
|
|
813
776
|
}
|
|
814
777
|
artifactDir(id) {
|
|
815
|
-
|
|
816
|
-
|
|
778
|
+
return this.resolveWithinRoot(id);
|
|
779
|
+
}
|
|
780
|
+
async writeMeta(meta) {
|
|
781
|
+
await this.writeJsonMeta(meta.id, "meta.json", meta);
|
|
782
|
+
}
|
|
783
|
+
async readMeta(id) {
|
|
784
|
+
const parsed = await this.readJsonMeta(id, "meta.json", "diff artifact");
|
|
785
|
+
if (!parsed) return null;
|
|
786
|
+
return parsed;
|
|
787
|
+
}
|
|
788
|
+
async writeStandaloneMeta(meta) {
|
|
789
|
+
await this.writeJsonMeta(meta.id, "file-meta.json", meta);
|
|
790
|
+
}
|
|
791
|
+
async readStandaloneMeta(id) {
|
|
792
|
+
const parsed = await this.readJsonMeta(id, "file-meta.json", "standalone diff");
|
|
793
|
+
if (!parsed) return null;
|
|
794
|
+
try {
|
|
795
|
+
const value = parsed;
|
|
796
|
+
if (value.kind !== "standalone_file" || typeof value.id !== "string" || typeof value.createdAt !== "string" || typeof value.expiresAt !== "string" || typeof value.filePath !== "string") return null;
|
|
797
|
+
return {
|
|
798
|
+
kind: value.kind,
|
|
799
|
+
id: value.id,
|
|
800
|
+
createdAt: value.createdAt,
|
|
801
|
+
expiresAt: value.expiresAt,
|
|
802
|
+
filePath: this.normalizeStoredPath(value.filePath, "filePath"),
|
|
803
|
+
...value.context ? { context: normalizeArtifactContext(value.context) } : {}
|
|
804
|
+
};
|
|
805
|
+
} catch (error) {
|
|
806
|
+
this.logger?.warn(`Failed to normalize standalone diff metadata for ${id}: ${String(error)}`);
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
async writeJsonMeta(id, fileName, data) {
|
|
811
|
+
await (await this.artifactRoot()).writeJson(path.posix.join(id, fileName), data, { space: 2 });
|
|
812
|
+
}
|
|
813
|
+
async readJsonMeta(id, fileName, context) {
|
|
814
|
+
try {
|
|
815
|
+
const raw = await (await this.artifactRoot()).readText(path.posix.join(id, fileName));
|
|
816
|
+
return JSON.parse(raw);
|
|
817
|
+
} catch (error) {
|
|
818
|
+
if (isFileNotFound(error)) return null;
|
|
819
|
+
this.logger?.warn(`Failed to read ${context} metadata for ${id}: ${String(error)}`);
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
async deleteArtifact(id) {
|
|
824
|
+
await fs$1.rm(this.artifactDir(id), {
|
|
825
|
+
recursive: true,
|
|
826
|
+
force: true
|
|
827
|
+
}).catch(() => {});
|
|
828
|
+
}
|
|
829
|
+
resolveWithinRoot(...parts) {
|
|
830
|
+
const candidate = path.resolve(this.rootDir, ...parts);
|
|
831
|
+
this.assertWithinRoot(candidate);
|
|
832
|
+
return candidate;
|
|
833
|
+
}
|
|
834
|
+
normalizeStoredPath(rawPath, label) {
|
|
835
|
+
const candidate = path.isAbsolute(rawPath) ? path.resolve(rawPath) : path.resolve(this.rootDir, rawPath);
|
|
836
|
+
this.assertWithinRoot(candidate, label);
|
|
837
|
+
return candidate;
|
|
838
|
+
}
|
|
839
|
+
relativeStoredPath(storedPath) {
|
|
840
|
+
return path.relative(this.rootDir, this.normalizeStoredPath(storedPath, "path")).split(path.sep).join(path.posix.sep);
|
|
841
|
+
}
|
|
842
|
+
assertWithinRoot(candidate, label = "path") {
|
|
843
|
+
const relative = path.relative(this.rootDir, candidate);
|
|
844
|
+
if (relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative)) return;
|
|
845
|
+
throw new Error(`Diff artifact ${label} escapes store root: ${candidate}`);
|
|
817
846
|
}
|
|
818
847
|
};
|
|
819
|
-
function viewerEntryToMeta(entry, token) {
|
|
820
|
-
if (!isViewerMetadata(entry.metadata)) throw new Error(`Diff artifact ${entry.key} is not a viewer.`);
|
|
821
|
-
return {
|
|
822
|
-
id: entry.key,
|
|
823
|
-
token,
|
|
824
|
-
createdAt: timestampMsToIsoString(entry.createdAt) ?? "1970-01-01T00:00:00.000Z",
|
|
825
|
-
expiresAt: resolveEntryExpiresAt(entry),
|
|
826
|
-
title: entry.metadata.title,
|
|
827
|
-
inputKind: entry.metadata.inputKind,
|
|
828
|
-
fileCount: entry.metadata.fileCount,
|
|
829
|
-
viewerPath: `${VIEWER_PREFIX}/${entry.key}/${token}`,
|
|
830
|
-
...entry.metadata.context ? { context: entry.metadata.context } : {}
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
function resolveEntryExpiresAt(entry) {
|
|
834
|
-
return timestampMsToIsoString(entry.expiresAt ?? MAX_DATE_TIMESTAMP_MS) ?? timestampMsToIsoString(MAX_DATE_TIMESTAMP_MS) ?? "1970-01-01T00:00:00.000Z";
|
|
835
|
-
}
|
|
836
|
-
function hashToken(token) {
|
|
837
|
-
return crypto.createHash("sha256").update(token).digest("hex");
|
|
838
|
-
}
|
|
839
848
|
function normalizeTtlMs$1(value) {
|
|
840
|
-
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
return Math.min(
|
|
844
|
-
}
|
|
845
|
-
function isViewerMetadata(value) {
|
|
846
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
847
|
-
const metadata = value;
|
|
848
|
-
return metadata.version === 1 && metadata.kind === "viewer" && metadata.encoding === "gzip" && typeof metadata.tokenHash === "string" && /^[0-9a-f]{64}$/u.test(metadata.tokenHash) && typeof metadata.title === "string" && (metadata.inputKind === "before_after" || metadata.inputKind === "patch") && Number.isSafeInteger(metadata.fileCount) && typeof metadata.fileCount === "number" && metadata.fileCount >= 0 && Number.isSafeInteger(metadata.decodedBytes) && typeof metadata.decodedBytes === "number" && metadata.decodedBytes >= 0 && metadata.decodedBytes <= MAX_DECODED_HTML_BYTES && isArtifactContext(metadata.context);
|
|
849
|
-
}
|
|
850
|
-
function isRenderedFileMetadata(value) {
|
|
851
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
852
|
-
const metadata = value;
|
|
853
|
-
return metadata.version === 1 && metadata.kind === "rendered_file" && (metadata.format === "png" || metadata.format === "pdf") && isArtifactContext(metadata.context);
|
|
854
|
-
}
|
|
855
|
-
function isArtifactContext(value) {
|
|
856
|
-
if (value === void 0) return true;
|
|
857
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
858
|
-
const context = value;
|
|
859
|
-
const allowed = /* @__PURE__ */ new Set([
|
|
860
|
-
"agentId",
|
|
861
|
-
"sessionId",
|
|
862
|
-
"messageChannel",
|
|
863
|
-
"agentAccountId"
|
|
864
|
-
]);
|
|
865
|
-
return Object.entries(context).every(([key, entry]) => allowed.has(key) && (entry === void 0 || typeof entry === "string"));
|
|
866
|
-
}
|
|
867
|
-
async function gzipAsync(input) {
|
|
868
|
-
return await new Promise((resolve, reject) => {
|
|
869
|
-
gzip(input, (error, result) => {
|
|
870
|
-
if (error) {
|
|
871
|
-
reject(error);
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
resolve(result);
|
|
875
|
-
});
|
|
876
|
-
});
|
|
849
|
+
if (!Number.isFinite(value) || value === void 0) return DEFAULT_TTL_MS;
|
|
850
|
+
const rounded = Math.floor(value);
|
|
851
|
+
if (rounded <= 0) return DEFAULT_TTL_MS;
|
|
852
|
+
return Math.min(rounded, MAX_TTL_MS);
|
|
877
853
|
}
|
|
878
|
-
|
|
879
|
-
return
|
|
880
|
-
gunzip(input, { maxOutputLength }, (error, result) => {
|
|
881
|
-
if (error) {
|
|
882
|
-
reject(error);
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
resolve(result);
|
|
886
|
-
});
|
|
887
|
-
});
|
|
854
|
+
function resolveExpiresAtIso(createdAtMs, ttlMs) {
|
|
855
|
+
return timestampMsToIsoString(createdAtMs + ttlMs) ?? timestampMsToIsoString(MAX_DATE_TIMESTAMP_MS) ?? "1970-01-01T00:00:00.000Z";
|
|
888
856
|
}
|
|
889
|
-
function
|
|
890
|
-
|
|
857
|
+
function isExpired(meta) {
|
|
858
|
+
const expiresAt = Date.parse(meta.expiresAt);
|
|
859
|
+
if (!Number.isFinite(expiresAt)) return true;
|
|
860
|
+
return Date.now() >= expiresAt;
|
|
891
861
|
}
|
|
892
862
|
function isFileNotFound(error) {
|
|
893
|
-
|
|
863
|
+
const code = error instanceof Error && "code" in error ? error.code : void 0;
|
|
864
|
+
return code === "ENOENT" || code === "not-found";
|
|
865
|
+
}
|
|
866
|
+
function normalizeArtifactContext(value) {
|
|
867
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
868
|
+
const raw = value;
|
|
869
|
+
const context = {
|
|
870
|
+
agentId: normalizeOptionalString(raw.agentId),
|
|
871
|
+
sessionId: normalizeOptionalString(raw.sessionId),
|
|
872
|
+
messageChannel: normalizeOptionalString(raw.messageChannel),
|
|
873
|
+
agentAccountId: normalizeOptionalString(raw.agentAccountId)
|
|
874
|
+
};
|
|
875
|
+
return Object.values(context).some((entry) => entry !== void 0) ? context : void 0;
|
|
894
876
|
}
|
|
895
877
|
//#endregion
|
|
896
878
|
//#region extensions/diffs/src/browser.ts
|
|
@@ -1500,6 +1482,11 @@ const bundledLanguagesBase = Object.fromEntries(bundledLanguagesInfo.map((langua
|
|
|
1500
1482
|
function getBundledLanguageAliases(language) {
|
|
1501
1483
|
return "aliases" in language ? language.aliases : [];
|
|
1502
1484
|
}
|
|
1485
|
+
const bundledLanguagesAlias = Object.fromEntries(bundledLanguagesInfo.flatMap((language) => getBundledLanguageAliases(language).map((alias) => [alias, language.import])));
|
|
1486
|
+
({
|
|
1487
|
+
...bundledLanguagesBase,
|
|
1488
|
+
...bundledLanguagesAlias
|
|
1489
|
+
});
|
|
1503
1490
|
//#endregion
|
|
1504
1491
|
//#region extensions/diffs/src/language-hints.ts
|
|
1505
1492
|
const BASE_DIFF_VIEWER_LANGUAGE_HINTS = [
|
|
@@ -1603,6 +1590,9 @@ var DiffRenderInputError = class extends Error {
|
|
|
1603
1590
|
function escapeCssString(value) {
|
|
1604
1591
|
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
1605
1592
|
}
|
|
1593
|
+
function escapeHtml(value) {
|
|
1594
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
1595
|
+
}
|
|
1606
1596
|
function escapeJsonScript(value) {
|
|
1607
1597
|
return JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
1608
1598
|
}
|
|
@@ -2018,18 +2008,19 @@ async function renderBeforeAfterDiff(input, options, target) {
|
|
|
2018
2008
|
});
|
|
2019
2009
|
const preloadOptions = viewerOptions ?? imageOptions;
|
|
2020
2010
|
if (!preloadOptions) throw new Error(`Unsupported diff render target: ${target}`);
|
|
2021
|
-
const
|
|
2022
|
-
prerenderedHTML: await preloadDiffHTMLWithFallback({
|
|
2023
|
-
oldFile,
|
|
2024
|
-
newFile,
|
|
2025
|
-
options: preloadOptions
|
|
2026
|
-
}),
|
|
2011
|
+
const preloadResult = await preloadMultiFileDiffWithFallback({
|
|
2027
2012
|
oldFile,
|
|
2028
2013
|
newFile,
|
|
2014
|
+
options: preloadOptions
|
|
2015
|
+
});
|
|
2016
|
+
const normalizedPayload = await normalizeDiffViewerPayloadLanguages({
|
|
2017
|
+
prerenderedHTML: preloadResult.prerenderedHTML,
|
|
2018
|
+
oldFile: preloadResult.oldFile,
|
|
2019
|
+
newFile: preloadResult.newFile,
|
|
2029
2020
|
options: preloadOptions,
|
|
2030
2021
|
langs: collectDiffPayloadLanguageHints({
|
|
2031
|
-
oldFile,
|
|
2032
|
-
newFile
|
|
2022
|
+
oldFile: preloadResult.oldFile,
|
|
2023
|
+
newFile: preloadResult.newFile
|
|
2033
2024
|
})
|
|
2034
2025
|
}, { languagePackAvailable });
|
|
2035
2026
|
const viewerPayload = viewerOptions ? {
|
|
@@ -2072,14 +2063,15 @@ async function renderPatchDiff(input, options, target) {
|
|
|
2072
2063
|
stats: computeFileDiffStats(fileDiff)
|
|
2073
2064
|
}));
|
|
2074
2065
|
const sections = await Promise.all(files.map(async (fileDiff, index) => {
|
|
2075
|
-
const
|
|
2076
|
-
prerenderedHTML: await preloadDiffHTMLWithFallback({
|
|
2077
|
-
fileDiff,
|
|
2078
|
-
options: preloadOptions
|
|
2079
|
-
}),
|
|
2066
|
+
const preloadResult = await preloadFileDiffWithFallback({
|
|
2080
2067
|
fileDiff,
|
|
2068
|
+
options: preloadOptions
|
|
2069
|
+
});
|
|
2070
|
+
const normalizedPayload = await normalizeDiffViewerPayloadLanguages({
|
|
2071
|
+
prerenderedHTML: preloadResult.prerenderedHTML,
|
|
2072
|
+
fileDiff: preloadResult.fileDiff,
|
|
2081
2073
|
options: preloadOptions,
|
|
2082
|
-
langs: collectDiffPayloadLanguageHints({ fileDiff })
|
|
2074
|
+
langs: collectDiffPayloadLanguageHints({ fileDiff: preloadResult.fileDiff })
|
|
2083
2075
|
}, { languagePackAvailable });
|
|
2084
2076
|
const viewerPayload = viewerOptions ? {
|
|
2085
2077
|
...normalizedPayload,
|
|
@@ -2142,18 +2134,33 @@ async function renderDiffDocument(input, options, target = "both") {
|
|
|
2142
2134
|
function shouldFallbackToClientHydration(error) {
|
|
2143
2135
|
return error instanceof TypeError && error.message.includes("needs an import attribute of \"type: json\"");
|
|
2144
2136
|
}
|
|
2145
|
-
async function
|
|
2137
|
+
async function preloadFileDiffWithFallback(params) {
|
|
2146
2138
|
try {
|
|
2147
|
-
return await
|
|
2139
|
+
return await preloadFileDiff(params);
|
|
2148
2140
|
} catch (error) {
|
|
2149
2141
|
if (!shouldFallbackToClientHydration(error)) throw error;
|
|
2150
|
-
return
|
|
2142
|
+
return {
|
|
2143
|
+
fileDiff: params.fileDiff,
|
|
2144
|
+
prerenderedHTML: ""
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
async function preloadMultiFileDiffWithFallback(params) {
|
|
2149
|
+
try {
|
|
2150
|
+
return await preloadMultiFileDiff(params);
|
|
2151
|
+
} catch (error) {
|
|
2152
|
+
if (!shouldFallbackToClientHydration(error)) throw error;
|
|
2153
|
+
return {
|
|
2154
|
+
oldFile: params.oldFile,
|
|
2155
|
+
newFile: params.newFile,
|
|
2156
|
+
prerenderedHTML: ""
|
|
2157
|
+
};
|
|
2151
2158
|
}
|
|
2152
2159
|
}
|
|
2153
2160
|
//#endregion
|
|
2154
2161
|
//#region extensions/diffs/src/tool.ts
|
|
2155
|
-
const MAX_BEFORE_AFTER_BYTES =
|
|
2156
|
-
const MAX_PATCH_BYTES =
|
|
2162
|
+
const MAX_BEFORE_AFTER_BYTES = 524288;
|
|
2163
|
+
const MAX_PATCH_BYTES = 2097152;
|
|
2157
2164
|
const MAX_TITLE_BYTES = 1024;
|
|
2158
2165
|
const MAX_PATH_BYTES = 2048;
|
|
2159
2166
|
const MAX_LANG_BYTES = 128;
|
|
@@ -2192,6 +2199,30 @@ const DiffsToolSchema = Type.Object({
|
|
|
2192
2199
|
minimum: 640,
|
|
2193
2200
|
maximum: 2400
|
|
2194
2201
|
}),
|
|
2202
|
+
/** @deprecated Use fileQuality. */
|
|
2203
|
+
imageQuality: Type.Optional(stringEnum(DIFF_IMAGE_QUALITY_PRESETS, {
|
|
2204
|
+
description: "Deprecated alias for fileQuality.",
|
|
2205
|
+
deprecated: true
|
|
2206
|
+
})),
|
|
2207
|
+
/** @deprecated Use fileFormat. */
|
|
2208
|
+
imageFormat: Type.Optional(stringEnum(DIFF_OUTPUT_FORMATS, {
|
|
2209
|
+
description: "Deprecated alias for fileFormat.",
|
|
2210
|
+
deprecated: true
|
|
2211
|
+
})),
|
|
2212
|
+
/** @deprecated Use fileScale. */
|
|
2213
|
+
imageScale: optionalFiniteNumberSchema({
|
|
2214
|
+
description: "Deprecated alias for fileScale.",
|
|
2215
|
+
deprecated: true,
|
|
2216
|
+
minimum: 1,
|
|
2217
|
+
maximum: 4
|
|
2218
|
+
}),
|
|
2219
|
+
/** @deprecated Use fileMaxWidth. */
|
|
2220
|
+
imageMaxWidth: optionalFiniteNumberSchema({
|
|
2221
|
+
description: "Deprecated alias for fileMaxWidth.",
|
|
2222
|
+
deprecated: true,
|
|
2223
|
+
minimum: 640,
|
|
2224
|
+
maximum: 2400
|
|
2225
|
+
}),
|
|
2195
2226
|
expandUnchanged: Type.Optional(Type.Boolean({ description: "Expand unchanged sections instead of collapsing them." })),
|
|
2196
2227
|
ttlSeconds: optionalFiniteNumberSchema({
|
|
2197
2228
|
description: "Artifact lifetime in seconds. Default: 1800. Maximum: 21600.",
|
|
@@ -2204,7 +2235,7 @@ function createDiffsTool(params) {
|
|
|
2204
2235
|
return {
|
|
2205
2236
|
name: "diffs",
|
|
2206
2237
|
label: "Diffs",
|
|
2207
|
-
description: "Create a read-only diff viewer from before/after text or a unified patch. Returns a gateway viewer URL for
|
|
2238
|
+
description: "Create a read-only diff viewer from before/after text or a unified patch. Returns a gateway viewer URL for canvas use and can also render the same diff to a PNG or PDF.",
|
|
2208
2239
|
parameters: DiffsToolSchema,
|
|
2209
2240
|
execute: async (_toolCallId, rawParams) => {
|
|
2210
2241
|
const toolParams = rawParams;
|
|
@@ -2226,13 +2257,13 @@ function createDiffsTool(params) {
|
|
|
2226
2257
|
const layout = normalizeLayout(toolParams.layout, params.defaults.layout);
|
|
2227
2258
|
const expandUnchanged = toolParams.expandUnchanged === true;
|
|
2228
2259
|
const ttlSeconds = readFiniteNumberParam(rawRecord, "ttlSeconds") ?? params.defaults.ttlSeconds;
|
|
2229
|
-
const fileScale = readFiniteNumberParam(rawRecord, "fileScale");
|
|
2230
|
-
const fileMaxWidth = readFiniteNumberParam(rawRecord, "fileMaxWidth");
|
|
2260
|
+
const fileScale = readFiniteNumberParam(rawRecord, "fileScale") ?? readFiniteNumberParam(rawRecord, "imageScale");
|
|
2261
|
+
const fileMaxWidth = readFiniteNumberParam(rawRecord, "fileMaxWidth") ?? readFiniteNumberParam(rawRecord, "imageMaxWidth");
|
|
2231
2262
|
const ttlMs = normalizeTtlMs(ttlSeconds);
|
|
2232
2263
|
const image = resolveDiffImageRenderOptions({
|
|
2233
2264
|
defaults: params.defaults,
|
|
2234
|
-
fileFormat: normalizeOutputFormat(toolParams.fileFormat),
|
|
2235
|
-
fileQuality: normalizeFileQuality(toolParams.fileQuality),
|
|
2265
|
+
fileFormat: normalizeOutputFormat(toolParams.fileFormat ?? toolParams.imageFormat ?? toolParams.format),
|
|
2266
|
+
fileQuality: normalizeFileQuality(toolParams.fileQuality ?? toolParams.imageQuality),
|
|
2236
2267
|
fileScale,
|
|
2237
2268
|
fileMaxWidth
|
|
2238
2269
|
});
|
|
@@ -2321,12 +2352,12 @@ function createDiffsTool(params) {
|
|
|
2321
2352
|
const artifactFile = await renderDiffArtifactFile({
|
|
2322
2353
|
screenshotter,
|
|
2323
2354
|
store: params.store,
|
|
2355
|
+
artifactId: artifact.id,
|
|
2324
2356
|
html: requireRenderedHtml(rendered.imageHtml, "image"),
|
|
2325
2357
|
theme,
|
|
2326
|
-
image
|
|
2327
|
-
ttlMs,
|
|
2328
|
-
context: artifactContext
|
|
2358
|
+
image
|
|
2329
2359
|
});
|
|
2360
|
+
await params.store.updateFilePath(artifact.id, artifactFile.path);
|
|
2330
2361
|
return {
|
|
2331
2362
|
content: [{
|
|
2332
2363
|
type: "text",
|
|
@@ -2352,7 +2383,8 @@ function createDiffsTool(params) {
|
|
|
2352
2383
|
}],
|
|
2353
2384
|
details: {
|
|
2354
2385
|
...baseDetails,
|
|
2355
|
-
fileError: errorMessage
|
|
2386
|
+
fileError: errorMessage,
|
|
2387
|
+
imageError: errorMessage
|
|
2356
2388
|
}
|
|
2357
2389
|
};
|
|
2358
2390
|
}
|
|
@@ -2383,59 +2415,55 @@ function buildArtifactDetails(params) {
|
|
|
2383
2415
|
return {
|
|
2384
2416
|
...params.baseDetails,
|
|
2385
2417
|
filePath: params.artifactFile.path,
|
|
2418
|
+
imagePath: params.artifactFile.path,
|
|
2386
2419
|
path: params.artifactFile.path,
|
|
2387
2420
|
fileBytes: params.artifactFile.bytes,
|
|
2421
|
+
imageBytes: params.artifactFile.bytes,
|
|
2422
|
+
format: params.image.format,
|
|
2388
2423
|
fileFormat: params.image.format,
|
|
2389
2424
|
fileQuality: params.image.qualityPreset,
|
|
2425
|
+
imageQuality: params.image.qualityPreset,
|
|
2390
2426
|
fileScale: params.image.scale,
|
|
2391
|
-
|
|
2427
|
+
imageScale: params.image.scale,
|
|
2428
|
+
fileMaxWidth: params.image.maxWidth,
|
|
2429
|
+
imageMaxWidth: params.image.maxWidth
|
|
2392
2430
|
};
|
|
2393
2431
|
}
|
|
2394
2432
|
function buildFileArtifactMessage(params) {
|
|
2395
2433
|
const lines = params.viewerUrl ? [`Diff viewer: ${params.viewerUrl}`] : [];
|
|
2396
2434
|
lines.push(`Diff ${params.format.toUpperCase()} generated at: ${params.filePath}`);
|
|
2397
|
-
lines.push("
|
|
2435
|
+
lines.push("Use the `message` tool with `path` or `filePath` to send this file.");
|
|
2398
2436
|
return lines.join("\n");
|
|
2399
2437
|
}
|
|
2400
2438
|
async function renderDiffArtifactFile(params) {
|
|
2401
|
-
const
|
|
2439
|
+
const standaloneArtifact = params.artifactId ? void 0 : await params.store.createStandaloneFileArtifact({
|
|
2402
2440
|
format: params.image.format,
|
|
2403
2441
|
ttlMs: params.ttlMs,
|
|
2404
2442
|
context: params.context
|
|
2405
2443
|
});
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
expiresAt: fileArtifact.expiresAt
|
|
2420
|
-
};
|
|
2421
|
-
} catch (error) {
|
|
2422
|
-
await params.store.deleteFileArtifact(fileArtifact.id);
|
|
2423
|
-
throw error;
|
|
2424
|
-
}
|
|
2444
|
+
const outputPath = params.artifactId ? params.store.allocateFilePath(params.artifactId, params.image.format) : standaloneArtifact.filePath;
|
|
2445
|
+
await params.screenshotter.screenshotHtml({
|
|
2446
|
+
html: params.html,
|
|
2447
|
+
outputPath,
|
|
2448
|
+
theme: params.theme,
|
|
2449
|
+
image: params.image
|
|
2450
|
+
});
|
|
2451
|
+
return {
|
|
2452
|
+
path: outputPath,
|
|
2453
|
+
bytes: (await fs$1.stat(outputPath)).size,
|
|
2454
|
+
...standaloneArtifact?.id ? { artifactId: standaloneArtifact.id } : {},
|
|
2455
|
+
...standaloneArtifact?.expiresAt ? { expiresAt: standaloneArtifact.expiresAt } : {}
|
|
2456
|
+
};
|
|
2425
2457
|
}
|
|
2426
2458
|
function buildArtifactContext(context) {
|
|
2427
2459
|
if (!context) return;
|
|
2428
|
-
const agentId = normalizeOptionalString(context.agentId);
|
|
2429
|
-
const sessionId = normalizeOptionalString(context.sessionId);
|
|
2430
|
-
const messageChannel = normalizeOptionalString(context.messageChannel);
|
|
2431
|
-
const agentAccountId = normalizeOptionalString(context.agentAccountId);
|
|
2432
2460
|
const artifactContext = {
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2461
|
+
agentId: normalizeOptionalString(context.agentId),
|
|
2462
|
+
sessionId: normalizeOptionalString(context.sessionId),
|
|
2463
|
+
messageChannel: normalizeOptionalString(context.messageChannel),
|
|
2464
|
+
agentAccountId: normalizeOptionalString(context.agentAccountId)
|
|
2437
2465
|
};
|
|
2438
|
-
return Object.
|
|
2466
|
+
return Object.values(artifactContext).some((value) => value !== void 0) ? artifactContext : void 0;
|
|
2439
2467
|
}
|
|
2440
2468
|
function normalizeDiffInput(params) {
|
|
2441
2469
|
const patch = params.patch?.trim();
|
|
@@ -2505,20 +2533,9 @@ var PluginToolInputError = class extends Error {
|
|
|
2505
2533
|
//#endregion
|
|
2506
2534
|
//#region extensions/diffs/src/plugin.ts
|
|
2507
2535
|
const DIFFS_LANGUAGE_PACK_PLUGIN_ID = "diffs-language-pack";
|
|
2508
|
-
const DIFF_ARTIFACT_NAMESPACE = "diff-artifacts";
|
|
2509
|
-
const DIFF_ARTIFACT_MAX_ENTRIES = 2048;
|
|
2510
|
-
const DIFF_ARTIFACT_MAX_BYTES_PER_ENTRY = 32 * 1024 * 1024;
|
|
2511
|
-
const DIFF_ARTIFACT_MAX_BYTES_PER_NAMESPACE = 256 * 1024 * 1024;
|
|
2512
2536
|
function registerDiffsPlugin(api) {
|
|
2513
2537
|
const store = new DiffArtifactStore({
|
|
2514
2538
|
rootDir: path.join(resolvePreferredOpenClawTmpDir(), "openclaw-diffs"),
|
|
2515
|
-
blobStore: api.runtime.state.openBlobStore({
|
|
2516
|
-
namespace: DIFF_ARTIFACT_NAMESPACE,
|
|
2517
|
-
maxEntries: DIFF_ARTIFACT_MAX_ENTRIES,
|
|
2518
|
-
maxBytesPerEntry: DIFF_ARTIFACT_MAX_BYTES_PER_ENTRY,
|
|
2519
|
-
maxBytesPerNamespace: DIFF_ARTIFACT_MAX_BYTES_PER_NAMESPACE,
|
|
2520
|
-
overflowPolicy: "reject-new"
|
|
2521
|
-
}),
|
|
2522
2539
|
logger: api.logger
|
|
2523
2540
|
});
|
|
2524
2541
|
const resolveCurrentPluginConfig = () => resolveLivePluginConfigObject(api.runtime.config?.current ? () => api.runtime.config.current() : void 0, "diffs", api.pluginConfig) ?? {};
|