@aexol/spectral 0.9.125 → 0.9.127
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/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +5 -0
- package/dist/cli.js +6 -0
- package/dist/commands/update.d.ts +17 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +292 -0
- package/dist/extensions/seo/analyzers/content.d.ts +29 -6
- package/dist/extensions/seo/analyzers/content.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/content.js +243 -8
- package/dist/extensions/seo/analyzers/crawler.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/crawler.js +133 -7
- package/dist/extensions/seo/analyzers/drift.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/drift.js +86 -29
- package/dist/extensions/seo/analyzers/images.d.ts +10 -4
- package/dist/extensions/seo/analyzers/images.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/images.js +153 -5
- package/dist/extensions/seo/analyzers/performance.d.ts +3 -3
- package/dist/extensions/seo/analyzers/performance.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/performance.js +77 -6
- package/dist/extensions/seo/analyzers/readability.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/readability.js +17 -9
- package/dist/extensions/seo/analyzers/schema.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/schema.js +99 -10
- package/dist/extensions/seo/analyzers/sitemap.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/sitemap.js +57 -16
- package/dist/extensions/seo/analyzers/technical.d.ts +0 -7
- package/dist/extensions/seo/analyzers/technical.d.ts.map +1 -1
- package/dist/extensions/seo/analyzers/technical.js +408 -37
- package/dist/extensions/seo/config.d.ts +2 -0
- package/dist/extensions/seo/config.d.ts.map +1 -1
- package/dist/extensions/seo/config.js +2 -0
- package/dist/extensions/seo/tools/batch.js +1 -1
- package/dist/extensions/seo/tools/content.d.ts.map +1 -1
- package/dist/extensions/seo/tools/content.js +5 -3
- package/dist/extensions/seo/tools/full-audit.d.ts +1 -1
- package/dist/extensions/seo/tools/full-audit.d.ts.map +1 -1
- package/dist/extensions/seo/tools/full-audit.js +391 -66
- package/dist/extensions/seo/tools/helpers.d.ts +15 -4
- package/dist/extensions/seo/tools/helpers.d.ts.map +1 -1
- package/dist/extensions/seo/tools/helpers.js +57 -6
- package/dist/extensions/seo/tools/performance.d.ts.map +1 -1
- package/dist/extensions/seo/tools/performance.js +17 -11
- package/dist/extensions/seo/tools/report.d.ts.map +1 -1
- package/dist/extensions/seo/tools/report.js +513 -47
- package/dist/extensions/seo/types.d.ts +295 -3
- package/dist/extensions/seo/types.d.ts.map +1 -1
- package/dist/extensions/seo/utils/fetcher.d.ts +4 -0
- package/dist/extensions/seo/utils/fetcher.d.ts.map +1 -1
- package/dist/extensions/seo/utils/fetcher.js +77 -11
- package/dist/extensions/seo/utils/parser.d.ts +3 -1
- package/dist/extensions/seo/utils/parser.d.ts.map +1 -1
- package/dist/extensions/seo/utils/parser.js +105 -25
- package/dist/extensions/seo/utils/renderer.d.ts +6 -1
- package/dist/extensions/seo/utils/renderer.d.ts.map +1 -1
- package/dist/extensions/seo/utils/renderer.js +207 -1
- package/dist/extensions/seo/utils/url-safety.d.ts.map +1 -1
- package/dist/extensions/seo/utils/url-safety.js +19 -3
- package/dist/extensions/web/tools/fetch.js +2 -2
- package/dist/memory/tools/compact-context.d.ts.map +1 -1
- package/dist/memory/tools/compact-context.js +10 -3
- package/dist/sdk/agent-core/agent-loop.d.ts.map +1 -1
- package/dist/sdk/agent-core/agent-loop.js +87 -4
- package/dist/sdk/agent-core/types.d.ts +1 -0
- package/dist/sdk/agent-core/types.d.ts.map +1 -1
- package/dist/sdk/ai/utils/validation.d.ts +0 -7
- package/dist/sdk/ai/utils/validation.d.ts.map +1 -1
- package/dist/sdk/ai/utils/validation.js +21 -1
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +41 -0
- package/dist/server/storage.d.ts +9 -0
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +35 -9
- package/dist/server/wire.d.ts +2 -0
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/utils/parser.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAMV,QAAQ,EAGT,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/utils/parser.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAMV,QAAQ,EAGT,MAAM,aAAa,CAAC;AA0HrB,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAuKhF;AAED,uDAAuD;AACvD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKhD;AACD,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAavD;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAwBrD"}
|
|
@@ -45,6 +45,18 @@ function resolveUrl(href, baseUrl) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
/** Parse JSON-LD blocks from the HTML. */
|
|
48
|
+
/** Expand JSON-LD @graph arrays into individual entities. */
|
|
49
|
+
function flattenGraph(item) {
|
|
50
|
+
if (!item || typeof item !== "object")
|
|
51
|
+
return [item];
|
|
52
|
+
const obj = item;
|
|
53
|
+
const graph = obj["@graph"];
|
|
54
|
+
if (graph === undefined)
|
|
55
|
+
return [item];
|
|
56
|
+
if (Array.isArray(graph))
|
|
57
|
+
return graph;
|
|
58
|
+
return [graph];
|
|
59
|
+
}
|
|
48
60
|
function parseJsonLd($, baseUrl) {
|
|
49
61
|
const blocks = [];
|
|
50
62
|
$('script[type="application/ld+json"]').each((_, el) => {
|
|
@@ -55,25 +67,41 @@ function parseJsonLd($, baseUrl) {
|
|
|
55
67
|
const parsed = JSON.parse(raw);
|
|
56
68
|
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
57
69
|
for (const item of items) {
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
// Expand @graph entries into individual blocks.
|
|
71
|
+
const expanded = flattenGraph(item);
|
|
72
|
+
for (const entity of expanded) {
|
|
73
|
+
if (!entity || typeof entity !== "object") {
|
|
74
|
+
blocks.push({
|
|
75
|
+
type: "Invalid",
|
|
76
|
+
format: "json-ld",
|
|
77
|
+
raw,
|
|
78
|
+
parsed: entity,
|
|
79
|
+
issues: ["Invalid schema object (not an object)"],
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const e = entity;
|
|
84
|
+
const rawType = e["@type"];
|
|
85
|
+
const type = typeof rawType === "string"
|
|
86
|
+
? rawType.replace("https://schema.org/", "")
|
|
87
|
+
: Array.isArray(rawType) && rawType.length > 0
|
|
88
|
+
? rawType[0].replace("https://schema.org/", "")
|
|
89
|
+
: "Unknown";
|
|
90
|
+
const issues = [];
|
|
91
|
+
if (!e["@context"])
|
|
92
|
+
issues.push("Missing @context");
|
|
93
|
+
const jsonStr = JSON.stringify(e);
|
|
94
|
+
if (/("url"\s*:\s*"\/)[^/]/.test(jsonStr)) {
|
|
95
|
+
issues.push("Contains relative URL (should be absolute)");
|
|
96
|
+
}
|
|
97
|
+
blocks.push({
|
|
98
|
+
type,
|
|
99
|
+
format: "json-ld",
|
|
100
|
+
raw,
|
|
101
|
+
parsed: entity,
|
|
102
|
+
issues,
|
|
103
|
+
});
|
|
69
104
|
}
|
|
70
|
-
blocks.push({
|
|
71
|
-
type: typeof type === "string" ? type : "Composite",
|
|
72
|
-
format: "json-ld",
|
|
73
|
-
raw,
|
|
74
|
-
parsed: item,
|
|
75
|
-
issues,
|
|
76
|
-
});
|
|
77
105
|
}
|
|
78
106
|
}
|
|
79
107
|
catch {
|
|
@@ -155,16 +183,40 @@ export function parsePage(html, url, baseUrl) {
|
|
|
155
183
|
const src = $(el).attr("src") ?? $(el).attr("data-src") ?? "";
|
|
156
184
|
if (!src || src.startsWith("data:"))
|
|
157
185
|
return;
|
|
186
|
+
const resolvedSrc = resolveUrl(src, effectiveBaseUrl);
|
|
187
|
+
const rawAlt = $(el).attr("alt");
|
|
188
|
+
const altValue = rawAlt ?? "";
|
|
189
|
+
const hasAltAttribute = rawAlt !== undefined;
|
|
190
|
+
const role = $(el).attr("role");
|
|
191
|
+
const ariaHidden = $(el).attr("aria-hidden");
|
|
158
192
|
images.push({
|
|
159
|
-
src:
|
|
160
|
-
alt:
|
|
193
|
+
src: resolvedSrc,
|
|
194
|
+
alt: altValue,
|
|
161
195
|
width: $(el).attr("width"),
|
|
162
196
|
height: $(el).attr("height"),
|
|
163
197
|
loading: $(el).attr("loading"),
|
|
164
198
|
lazyMethod: detectLazyMethod($, el),
|
|
165
199
|
fetchpriority: $(el).attr("fetchpriority"),
|
|
200
|
+
srcset: $(el).attr("srcset"),
|
|
201
|
+
sizes: $(el).attr("sizes"),
|
|
202
|
+
decoding: $(el).attr("decoding"),
|
|
203
|
+
isDecorative: (hasAltAttribute && altValue === "") || role === "presentation" || ariaHidden === "true",
|
|
204
|
+
isInPicture: $(el).parent("picture").length > 0,
|
|
205
|
+
isSvg: resolvedSrc.endsWith(".svg") || resolvedSrc.startsWith("data:image/svg"),
|
|
166
206
|
});
|
|
167
207
|
});
|
|
208
|
+
const pictureCount = $("picture").length;
|
|
209
|
+
let svgCount = 0;
|
|
210
|
+
let svgMissingTitle = 0;
|
|
211
|
+
$("svg").each((_, el) => {
|
|
212
|
+
svgCount++;
|
|
213
|
+
const hasTitle = $(el).find("title").length > 0;
|
|
214
|
+
const hasRoleImg = $(el).attr("role") === "img";
|
|
215
|
+
const hasAriaLabel = $(el).attr("aria-label");
|
|
216
|
+
if (!hasTitle && !hasRoleImg && !hasAriaLabel) {
|
|
217
|
+
svgMissingTitle++;
|
|
218
|
+
}
|
|
219
|
+
});
|
|
168
220
|
// Links
|
|
169
221
|
const links = [];
|
|
170
222
|
let internalLinkCount = 0;
|
|
@@ -233,6 +285,9 @@ export function parsePage(html, url, baseUrl) {
|
|
|
233
285
|
language,
|
|
234
286
|
viewport,
|
|
235
287
|
charset,
|
|
288
|
+
pictureCount,
|
|
289
|
+
svgCount,
|
|
290
|
+
svgMissingTitle,
|
|
236
291
|
};
|
|
237
292
|
}
|
|
238
293
|
/** Extract the visible text content from HTML body. */
|
|
@@ -242,7 +297,22 @@ export function extractText(html) {
|
|
|
242
297
|
$("script, style, noscript").remove();
|
|
243
298
|
return $("body").text().replace(/\s+/g, " ").trim();
|
|
244
299
|
}
|
|
245
|
-
/** Extract
|
|
300
|
+
/** Extract main content text, skipping nav/footer/aside/header. */
|
|
301
|
+
export function extractContentText(html) {
|
|
302
|
+
const $ = cheerio.load(html);
|
|
303
|
+
$("nav, footer, aside, header").remove();
|
|
304
|
+
$("script, style, noscript").remove();
|
|
305
|
+
const main = $("main");
|
|
306
|
+
const article = $("article");
|
|
307
|
+
if (article.length > 0) {
|
|
308
|
+
return article.text().replace(/\s+/g, " ").trim();
|
|
309
|
+
}
|
|
310
|
+
if (main.length > 0) {
|
|
311
|
+
return main.text().replace(/\s+/g, " ").trim();
|
|
312
|
+
}
|
|
313
|
+
return $("body").text().replace(/\s+/g, " ").trim();
|
|
314
|
+
}
|
|
315
|
+
/** Extract raw JSON-LD blocks as parsed objects, expanding @graph arrays. */
|
|
246
316
|
export function extractJsonLd(html) {
|
|
247
317
|
const $ = cheerio.load(html);
|
|
248
318
|
const results = [];
|
|
@@ -252,10 +322,20 @@ export function extractJsonLd(html) {
|
|
|
252
322
|
return;
|
|
253
323
|
try {
|
|
254
324
|
const parsed = JSON.parse(raw);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
325
|
+
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
326
|
+
for (const item of items) {
|
|
327
|
+
const obj = item;
|
|
328
|
+
if (obj && typeof obj === "object" && "@graph" in obj) {
|
|
329
|
+
const graph = obj["@graph"];
|
|
330
|
+
if (Array.isArray(graph))
|
|
331
|
+
results.push(...graph);
|
|
332
|
+
else
|
|
333
|
+
results.push(graph);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
results.push(item);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
259
339
|
}
|
|
260
340
|
catch {
|
|
261
341
|
// skip invalid JSON
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the SEO extension self-contained.
|
|
7
7
|
*/
|
|
8
8
|
import type { SeoConfig } from "../config.js";
|
|
9
|
-
import type { FetchResult } from "../types.js";
|
|
9
|
+
import type { FetchResult, MeasuredPerformance } from "../types.js";
|
|
10
10
|
export interface RenderOptions {
|
|
11
11
|
viewport?: "desktop" | "mobile";
|
|
12
12
|
timeout?: number;
|
|
@@ -15,8 +15,12 @@ export interface RenderOptions {
|
|
|
15
15
|
captureA11y?: boolean;
|
|
16
16
|
/** Whether to capture console errors. */
|
|
17
17
|
captureConsole?: boolean;
|
|
18
|
+
/** Whether to capture Web Vitals and real browser performance metrics. */
|
|
19
|
+
capturePerformance?: boolean;
|
|
18
20
|
/** User agent override. */
|
|
19
21
|
userAgent?: string;
|
|
22
|
+
/** Optional AbortSignal to cancel an in-flight render. */
|
|
23
|
+
signal?: AbortSignal;
|
|
20
24
|
}
|
|
21
25
|
export interface RenderResult extends FetchResult {
|
|
22
26
|
isSpa: boolean;
|
|
@@ -24,6 +28,7 @@ export interface RenderResult extends FetchResult {
|
|
|
24
28
|
consoleErrors: string[];
|
|
25
29
|
renderMs: number;
|
|
26
30
|
accessibilityTree: unknown | null;
|
|
31
|
+
measuredPerformance?: MeasuredPerformance;
|
|
27
32
|
}
|
|
28
33
|
/** Close the shared browser instance (called on session shutdown). */
|
|
29
34
|
export declare function closeRenderer(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/utils/renderer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/utils/renderer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAe,MAAM,aAAa,CAAC;AAEjF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yCAAyC;IACzC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,0EAA0E;IAC1E,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C;AAeD,sEAAsE;AACtE,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAUnD;AAED,sDAAsD;AACtD,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO5D;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,SAAS,EACjB,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,CAyMvB"}
|
|
@@ -42,15 +42,23 @@ export async function renderPage(url, config, opts = {}) {
|
|
|
42
42
|
const timeout = opts.timeout ?? config.timeout;
|
|
43
43
|
const blockResources = opts.blockResources ?? config.blockRenderResources;
|
|
44
44
|
const captureA11y = opts.captureA11y ?? false;
|
|
45
|
+
const capturePerformance = opts.capturePerformance ?? false;
|
|
45
46
|
const captureConsole = opts.captureConsole ?? true;
|
|
46
47
|
const startTime = Date.now();
|
|
48
|
+
if (opts.signal?.aborted) {
|
|
49
|
+
throw new Error("Render aborted before launch.");
|
|
50
|
+
}
|
|
47
51
|
const browser = await getBrowser();
|
|
52
|
+
if (opts.signal?.aborted) {
|
|
53
|
+
throw new Error("Render aborted before context creation.");
|
|
54
|
+
}
|
|
48
55
|
const context = await browser.newContext({
|
|
49
56
|
viewport: viewport === "mobile" ? MOBILE_VIEWPORT : DESKTOP_VIEWPORT,
|
|
50
57
|
userAgent: opts.userAgent ?? config.userAgent,
|
|
51
58
|
});
|
|
52
59
|
// Block resource types to speed up rendering.
|
|
53
|
-
|
|
60
|
+
// When capturing performance, we need all resources to load for real metrics.
|
|
61
|
+
if (!capturePerformance && blockResources.length > 0) {
|
|
54
62
|
await context.route("**/*", (route) => {
|
|
55
63
|
const resourceType = route.request().resourceType();
|
|
56
64
|
if (blockResources.includes(resourceType)) {
|
|
@@ -74,6 +82,26 @@ export async function renderPage(url, config, opts = {}) {
|
|
|
74
82
|
consoleErrors.push(err.message);
|
|
75
83
|
});
|
|
76
84
|
}
|
|
85
|
+
// Track failed requests for performance audit.
|
|
86
|
+
const failedRequests = [];
|
|
87
|
+
if (capturePerformance) {
|
|
88
|
+
page.on("requestfailed", (request) => {
|
|
89
|
+
failedRequests.push({
|
|
90
|
+
url: request.url(),
|
|
91
|
+
status: 0,
|
|
92
|
+
type: request.resourceType(),
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
page.on("response", (response) => {
|
|
96
|
+
if (response.status() >= 400) {
|
|
97
|
+
failedRequests.push({
|
|
98
|
+
url: response.url(),
|
|
99
|
+
status: response.status(),
|
|
100
|
+
type: response.request().resourceType(),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
77
105
|
let response = null;
|
|
78
106
|
let finalUrl = url;
|
|
79
107
|
try {
|
|
@@ -81,6 +109,9 @@ export async function renderPage(url, config, opts = {}) {
|
|
|
81
109
|
waitUntil: "networkidle",
|
|
82
110
|
timeout,
|
|
83
111
|
});
|
|
112
|
+
if (opts.signal?.aborted) {
|
|
113
|
+
throw new Error("Render aborted after navigation.");
|
|
114
|
+
}
|
|
84
115
|
if (response) {
|
|
85
116
|
// Track redirects by walking the redirectedFrom() chain.
|
|
86
117
|
let current = response.request().redirectedFrom();
|
|
@@ -113,6 +144,7 @@ export async function renderPage(url, config, opts = {}) {
|
|
|
113
144
|
consoleErrors,
|
|
114
145
|
renderMs: Date.now() - startTime,
|
|
115
146
|
accessibilityTree: null,
|
|
147
|
+
measuredPerformance: undefined,
|
|
116
148
|
};
|
|
117
149
|
}
|
|
118
150
|
const rawContent = (await response?.text()) ?? "";
|
|
@@ -120,6 +152,15 @@ export async function renderPage(url, config, opts = {}) {
|
|
|
120
152
|
// SPA heuristic: if the rendered DOM is significantly larger than the raw
|
|
121
153
|
// initial HTML, content was likely injected by JavaScript.
|
|
122
154
|
const isSpa = rawContent.length > 0 && content.length > rawContent.length * 1.5;
|
|
155
|
+
let measuredPerformance;
|
|
156
|
+
if (capturePerformance) {
|
|
157
|
+
try {
|
|
158
|
+
measuredPerformance = await capturePagePerformance(page, failedRequests);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
measuredPerformance = undefined;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
123
164
|
let accessibilityTree = null;
|
|
124
165
|
if (captureA11y) {
|
|
125
166
|
try {
|
|
@@ -186,5 +227,170 @@ export async function renderPage(url, config, opts = {}) {
|
|
|
186
227
|
consoleErrors,
|
|
187
228
|
renderMs,
|
|
188
229
|
accessibilityTree,
|
|
230
|
+
measuredPerformance,
|
|
189
231
|
};
|
|
190
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Web Vitals rating thresholds (Google's CWV thresholds).
|
|
235
|
+
*/
|
|
236
|
+
const LCP_GOOD = 2500;
|
|
237
|
+
const LCP_POOR = 4000;
|
|
238
|
+
const CLS_GOOD = 0.1;
|
|
239
|
+
const CLS_POOR = 0.25;
|
|
240
|
+
const TBT_GOOD = 200;
|
|
241
|
+
const TBT_POOR = 600;
|
|
242
|
+
const FCP_GOOD = 1800;
|
|
243
|
+
const FCP_POOR = 3000;
|
|
244
|
+
const TTFB_GOOD = 800;
|
|
245
|
+
const TTFB_POOR = 1800;
|
|
246
|
+
function vitalRating(value, good, poor) {
|
|
247
|
+
if (value <= good)
|
|
248
|
+
return "good";
|
|
249
|
+
if (value <= poor)
|
|
250
|
+
return "needs-improvement";
|
|
251
|
+
return "poor";
|
|
252
|
+
}
|
|
253
|
+
async function capturePagePerformance(page, failedRequests) {
|
|
254
|
+
// Capture navigation and resource timings.
|
|
255
|
+
const timings = await page.evaluate(() => {
|
|
256
|
+
const nav = performance.getEntriesByType("navigation")[0];
|
|
257
|
+
const resources = performance.getEntriesByType("resource");
|
|
258
|
+
const navTiming = nav
|
|
259
|
+
? {
|
|
260
|
+
dnsMs: Math.round(nav.domainLookupEnd - nav.domainLookupStart),
|
|
261
|
+
tcpMs: Math.round(nav.connectEnd - nav.connectStart),
|
|
262
|
+
sslMs: Math.round(nav.secureConnectionStart > 0 ? nav.connectEnd - nav.secureConnectionStart : 0),
|
|
263
|
+
ttfbMs: Math.round(nav.responseStart - nav.requestStart),
|
|
264
|
+
downloadMs: Math.round(nav.responseEnd - nav.responseStart),
|
|
265
|
+
}
|
|
266
|
+
: undefined;
|
|
267
|
+
const totalTransfer = resources.reduce((s, r) => s + (r.transferSize || 0), 0);
|
|
268
|
+
const heavyResources = resources
|
|
269
|
+
.filter((r) => (r.transferSize || 0) > 50000)
|
|
270
|
+
.sort((a, b) => (b.transferSize || 0) - (a.transferSize || 0))
|
|
271
|
+
.slice(0, 10)
|
|
272
|
+
.map((r) => ({
|
|
273
|
+
url: r.name,
|
|
274
|
+
type: r.initiatorType,
|
|
275
|
+
transferSize: r.transferSize || 0,
|
|
276
|
+
encodedSize: r.encodedBodySize || 0,
|
|
277
|
+
}));
|
|
278
|
+
return {
|
|
279
|
+
navTiming,
|
|
280
|
+
totalTransferKb: Math.round(totalTransfer / 1024),
|
|
281
|
+
resourceCount: resources.length,
|
|
282
|
+
heavyResources,
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
// Capture Web Vitals via PerformanceObserver.
|
|
286
|
+
const webVitals = await page.evaluate(async () => {
|
|
287
|
+
return new Promise((resolve) => {
|
|
288
|
+
const results = {};
|
|
289
|
+
// LCP
|
|
290
|
+
const lcpObs = new PerformanceObserver((list) => {
|
|
291
|
+
const entries = list.getEntries();
|
|
292
|
+
if (entries.length > 0) {
|
|
293
|
+
const last = entries[entries.length - 1];
|
|
294
|
+
const el = last.element;
|
|
295
|
+
results.lcp = {
|
|
296
|
+
value: Math.round(last.startTime),
|
|
297
|
+
element: el ? {
|
|
298
|
+
tagName: el.tagName,
|
|
299
|
+
id: el.id || undefined,
|
|
300
|
+
src: el.src || el.currentSrc || undefined,
|
|
301
|
+
renderTime: last.renderTime || last.startTime,
|
|
302
|
+
size: `${el.width || el.naturalWidth || 0}x${el.height || el.naturalHeight || 0}`,
|
|
303
|
+
} : undefined,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
lcpObs.disconnect();
|
|
307
|
+
});
|
|
308
|
+
lcpObs.observe({ type: "largest-contentful-paint", buffered: true });
|
|
309
|
+
// CLS
|
|
310
|
+
let clsValue = 0;
|
|
311
|
+
const layoutShifts = [];
|
|
312
|
+
const clsObs = new PerformanceObserver((list) => {
|
|
313
|
+
for (const entry of list.getEntries()) {
|
|
314
|
+
if (!entry.hadRecentInput)
|
|
315
|
+
clsValue += entry.value;
|
|
316
|
+
layoutShifts.push({ value: entry.value, nodeCount: entry.sources?.length || 0 });
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
clsObs.observe({ type: "layout-shift", buffered: true });
|
|
320
|
+
// Long tasks
|
|
321
|
+
const longTasks = [];
|
|
322
|
+
const longTaskDetails = [];
|
|
323
|
+
const ltObs = new PerformanceObserver((list) => {
|
|
324
|
+
for (const entry of list.getEntries()) {
|
|
325
|
+
longTasks.push({ duration: entry.duration });
|
|
326
|
+
longTaskDetails.push({
|
|
327
|
+
startTime: entry.startTime,
|
|
328
|
+
duration: entry.duration,
|
|
329
|
+
attribution: entry.attribution?.[0]?.containerSrc || entry.attribution?.[0]?.containerName || undefined,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
ltObs.observe({ type: "longtask", buffered: true });
|
|
334
|
+
// FCP
|
|
335
|
+
const fcpObs = new PerformanceObserver((list) => {
|
|
336
|
+
const entries = list.getEntries();
|
|
337
|
+
if (entries.length > 0) {
|
|
338
|
+
results.fcp = { value: Math.round(entries[0].startTime) };
|
|
339
|
+
}
|
|
340
|
+
fcpObs.disconnect();
|
|
341
|
+
});
|
|
342
|
+
fcpObs.observe({ type: "paint", buffered: true });
|
|
343
|
+
// Wait 3 seconds for metrics to settle
|
|
344
|
+
setTimeout(() => {
|
|
345
|
+
clsObs.disconnect();
|
|
346
|
+
ltObs.disconnect();
|
|
347
|
+
results.cls = { value: Math.round(clsValue * 1000) / 1000 };
|
|
348
|
+
results.layoutShiftCount = layoutShifts.length;
|
|
349
|
+
results.layoutShiftSources = layoutShifts.slice(0, 5);
|
|
350
|
+
results.tbt = {
|
|
351
|
+
value: Math.round(longTasks.reduce((s, t) => s + Math.max(0, t.duration - 50), 0)),
|
|
352
|
+
};
|
|
353
|
+
results.longTasks = {
|
|
354
|
+
count: longTasks.length,
|
|
355
|
+
totalDurationMs: Math.round(longTasks.reduce((s, t) => s + t.duration, 0)),
|
|
356
|
+
details: longTaskDetails,
|
|
357
|
+
};
|
|
358
|
+
resolve(results);
|
|
359
|
+
}, 3000);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
// Assemble MeasuredPerformance.
|
|
363
|
+
const mp = {
|
|
364
|
+
navigationTiming: timings.navTiming,
|
|
365
|
+
resourceCount: timings.resourceCount,
|
|
366
|
+
totalTransferKb: timings.totalTransferKb,
|
|
367
|
+
topHeavyResources: timings.heavyResources,
|
|
368
|
+
failedRequests: failedRequests.length > 0 ? failedRequests : undefined,
|
|
369
|
+
};
|
|
370
|
+
if (webVitals.lcp) {
|
|
371
|
+
mp.lcp = { value: webVitals.lcp.value, rating: vitalRating(webVitals.lcp.value, LCP_GOOD, LCP_POOR), element: webVitals.lcp.element };
|
|
372
|
+
}
|
|
373
|
+
if (webVitals.cls) {
|
|
374
|
+
mp.cls = { value: webVitals.cls.value, rating: vitalRating(webVitals.cls.value, CLS_GOOD, CLS_POOR) };
|
|
375
|
+
}
|
|
376
|
+
if (webVitals.fcp) {
|
|
377
|
+
mp.fcp = { value: webVitals.fcp.value, rating: vitalRating(webVitals.fcp.value, FCP_GOOD, FCP_POOR) };
|
|
378
|
+
}
|
|
379
|
+
if (webVitals.tbt) {
|
|
380
|
+
mp.tbt = { value: webVitals.tbt.value, rating: vitalRating(webVitals.tbt.value, TBT_GOOD, TBT_POOR) };
|
|
381
|
+
}
|
|
382
|
+
if (webVitals.longTasks) {
|
|
383
|
+
mp.longTasks = webVitals.longTasks;
|
|
384
|
+
}
|
|
385
|
+
if (webVitals.layoutShiftCount !== undefined) {
|
|
386
|
+
mp.layoutShiftCount = webVitals.layoutShiftCount;
|
|
387
|
+
mp.layoutShiftSources = webVitals.layoutShiftSources;
|
|
388
|
+
}
|
|
389
|
+
if (timings.navTiming) {
|
|
390
|
+
mp.ttfb = {
|
|
391
|
+
value: timings.navTiming.ttfbMs,
|
|
392
|
+
rating: vitalRating(timings.navTiming.ttfbMs, TTFB_GOOD, TTFB_POOR),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
return mp;
|
|
396
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url-safety.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/utils/url-safety.ts"],"names":[],"mappings":"AAUA,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAiDD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAC5D,OAAO,CAAC,YAAY,CAAC,CA8EvB;AAED,+DAA+D;AAC/D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CA8B7D;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"url-safety.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/utils/url-safety.ts"],"names":[],"mappings":"AAUA,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAiDD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAC5D,OAAO,CAAC,YAAY,CAAC,CA8EvB;AAED,+DAA+D;AAC/D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CA8B7D;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA2ChD;AAED,wDAAwD;AACxD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C"}
|
|
@@ -168,7 +168,19 @@ export function validateUrlBasic(urlStr) {
|
|
|
168
168
|
}
|
|
169
169
|
/** Normalize a URL for baseline matching (strip UTM, sort params, lowercase host). */
|
|
170
170
|
export function normalizeUrl(url) {
|
|
171
|
-
const
|
|
171
|
+
const TRACKING_PARAMS = new Set([
|
|
172
|
+
"utm_source",
|
|
173
|
+
"utm_medium",
|
|
174
|
+
"utm_campaign",
|
|
175
|
+
"utm_term",
|
|
176
|
+
"utm_content",
|
|
177
|
+
// Click-IDs and marketing tracking params beyond UTM.
|
|
178
|
+
"gclid",
|
|
179
|
+
"fbclid",
|
|
180
|
+
"msclkid",
|
|
181
|
+
"mc_eid",
|
|
182
|
+
"mc_cid",
|
|
183
|
+
]);
|
|
172
184
|
let parsed;
|
|
173
185
|
try {
|
|
174
186
|
parsed = new URL(url);
|
|
@@ -176,15 +188,19 @@ export function normalizeUrl(url) {
|
|
|
176
188
|
catch {
|
|
177
189
|
return url;
|
|
178
190
|
}
|
|
191
|
+
// Always strip fragments (hash).
|
|
192
|
+
parsed.hash = "";
|
|
193
|
+
// Lowercase scheme and host.
|
|
194
|
+
parsed.protocol = parsed.protocol.toLowerCase();
|
|
179
195
|
parsed.hostname = parsed.hostname.toLowerCase();
|
|
180
196
|
// Strip default ports.
|
|
181
197
|
if ((parsed.protocol === "http:" && parsed.port === "80") || (parsed.protocol === "https:" && parsed.port === "443")) {
|
|
182
198
|
parsed.port = "";
|
|
183
199
|
}
|
|
184
|
-
// Remove
|
|
200
|
+
// Remove tracking params.
|
|
185
201
|
const params = new URLSearchParams(parsed.search);
|
|
186
202
|
for (const key of [...params.keys()]) {
|
|
187
|
-
if (
|
|
203
|
+
if (TRACKING_PARAMS.has(key.toLowerCase())) {
|
|
188
204
|
params.delete(key);
|
|
189
205
|
}
|
|
190
206
|
}
|
|
@@ -10,10 +10,10 @@ const MAX_OUTPUT_BYTES = 200 * 1024; // 200 KB
|
|
|
10
10
|
const webFetchSchema = Type.Object({
|
|
11
11
|
url: Type.String({
|
|
12
12
|
description: "The URL to fetch. Must begin with http:// or https://.",
|
|
13
|
-
pattern: "^https?://",
|
|
14
13
|
}),
|
|
15
|
-
format: Type.Optional(Type.
|
|
14
|
+
format: Type.Optional(Type.String({
|
|
16
15
|
default: "markdown",
|
|
16
|
+
enum: ["text", "markdown", "html", "json"],
|
|
17
17
|
description: "Output format: text, markdown (default), html, or json.",
|
|
18
18
|
})),
|
|
19
19
|
timeout: Type.Optional(Type.Number({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compact-context.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/compact-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EASN,KAAK,gBAAgB,EACrB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAc,KAAK,YAAY,EAAyB,MAAM,iCAAiC,CAAC;AAKvG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAErD,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAoO3D;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,GAAE;IAC1D,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,iCAAiC,GAAG,wBAAwB,CAAC,CAAC;IACpF,MAAM,CAAC,EAAE,gBAAgB,CAAC;CACrB,GAAG,WAAW,CAAC,OAAO,uBAAuB,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAiBrF;AA+MD,wFAAwF;AACxF,wBAAgB,sCAAsC,CACrD,eAAe,EAAE,YAAY,EAAE,EAC/B,MAAM,GAAE,gBAA4C,EACpD,gBAAgB,CAAC,EAAE,MAAM,GACvB,YAAY,EAAE,CAchB;AAsQD,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"compact-context.d.ts","sourceRoot":"","sources":["../../../src/memory/tools/compact-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EASN,KAAK,gBAAgB,EACrB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAc,KAAK,YAAY,EAAyB,MAAM,iCAAiC,CAAC;AAKvG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAErD,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAoO3D;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,GAAE;IAC1D,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,iCAAiC,GAAG,wBAAwB,CAAC,CAAC;IACpF,MAAM,CAAC,EAAE,gBAAgB,CAAC;CACrB,GAAG,WAAW,CAAC,OAAO,uBAAuB,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAiBrF;AA+MD,wFAAwF;AACxF,wBAAgB,sCAAsC,CACrD,eAAe,EAAE,YAAY,EAAE,EAC/B,MAAM,GAAE,gBAA4C,EACpD,gBAAgB,CAAC,EAAE,MAAM,GACvB,YAAY,EAAE,CAchB;AAsQD,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CA0SpF"}
|
|
@@ -617,7 +617,17 @@ export function registerCompactContextTool(ext, runtime) {
|
|
|
617
617
|
// 1. Explicit request (agent-driven tool call or turn-start auto-schedule).
|
|
618
618
|
// These set state.request before this context event fires, so pruning
|
|
619
619
|
// applies to the current provider request with no lag.
|
|
620
|
+
// Compute mid-run status first so we can promote a premature after_turn
|
|
621
|
+
// request (scheduled at before_agent_start while the agent was transiently
|
|
622
|
+
// idle) to request_time when the agent is now actively mid-run.
|
|
623
|
+
const isMidRun = _event.providerRequest === true || hasPendingModelRequest(ctx);
|
|
620
624
|
const request = state.request;
|
|
625
|
+
if (request && request.mode === "after_turn" && isMidRun) {
|
|
626
|
+
notifyDcp(ctx, "DCP-lite after-turn compaction promoted to request-time pruning — agent is mid-run", "info", {
|
|
627
|
+
version: 1, event: "scheduled", source: request.source, mode: "request_time", scope: request.scope, timestamp: Date.now(),
|
|
628
|
+
});
|
|
629
|
+
request.mode = "request_time";
|
|
630
|
+
}
|
|
621
631
|
if (request && request.mode === "request_time") {
|
|
622
632
|
state.request = null;
|
|
623
633
|
if (runtime.compactInFlight) {
|
|
@@ -653,9 +663,6 @@ export function registerCompactContextTool(ext, runtime) {
|
|
|
653
663
|
// The context hook is the last zero-lag seam before conversion/provider send.
|
|
654
664
|
// Treat an explicit provider-preparation event as in-run even if the session
|
|
655
665
|
// state has not flipped to streaming/pending yet.
|
|
656
|
-
const isMidRun = _event.providerRequest === true || hasPendingModelRequest(ctx);
|
|
657
|
-
if (!isMidRun)
|
|
658
|
-
return;
|
|
659
666
|
const autoSettings = getAutoOlderHistoryRequestSettings(policy);
|
|
660
667
|
const autoRawTokens = safeRawTokensForContext(ctx);
|
|
661
668
|
const autoUsage = ctx.getContextUsage();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../../../src/sdk/agent-core/agent-loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAGL,WAAW,EAIZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAIZ,QAAQ,EACT,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../../../src/sdk/agent-core/agent-loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAGL,WAAW,EAIZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAIZ,QAAQ,EACT,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAYzE;;;GAGG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GAClB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CA2BzC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GAClB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CAkCzC;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,YAAY,EAAE,CAAC,CAgBzB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,YAAY,EAAE,CAAC,CAiBzB"}
|