Skip to content

feat(agents): add Web Performance agent#3119

Open
vibegui wants to merge 1 commit intomainfrom
vibegui/web-perf-agent
Open

feat(agents): add Web Performance agent#3119
vibegui wants to merge 1 commit intomainfrom
vibegui/web-perf-agent

Conversation

@vibegui
Copy link
Copy Markdown
Contributor

@vibegui vibegui commented Apr 14, 2026

Summary

  • Adds packages/web-perf/ — standalone MCP agent for website performance monitoring using Chrome UX Report (CrUX) field data and PageSpeed Insights lab data
  • 7 tools: SITE_ADD, SITE_LIST, SITE_GET, SITE_DELETE, PERF_SNAPSHOT, PERF_REPORT, CRUX_HISTORY
  • 2 guided prompts: initial-setup (full onboarding flow) and performance-audit (deep analysis with prioritized fixes)
  • Native UI with CWV gauges, CrUX histogram bars, 25-week trend sparklines, and PageSpeed opportunity tables
  • File-based storage at ~/.deco/web-perf/sites/
  • Registered as well-known agent template with one-click setup modal in home page, agents list, and sidebar

Test plan

  • Run bun run --cwd packages/web-perf dev to start the MCP server
  • Click "Web Performance" → "Add Web Performance" in the Mesh home screen
  • Use the initial-setup prompt with a URL and Google API key
  • Verify CrUX data, PageSpeed scores, and trend history are fetched and saved
  • Verify dashboard and site detail UIs render correctly in the agent view
  • Run bun run fmt and bun run check — both pass

🤖 Generated with Claude Code


Summary by cubic

Add a new Web Performance agent that monitors sites using CrUX field data and PageSpeed Insights lab tests, with native dashboards and a one‑click setup flow. Registers a new agent template and recruit modal across the home screen, agents list, and sidebar.

  • New Features

    • New package @decocms/web-perf: standalone MCP server at http://localhost:3002/mcp with a small REST API at /api/sites.
    • Tools: SITE_ADD, SITE_LIST, SITE_GET, SITE_DELETE, PERF_SNAPSHOT, PERF_REPORT, CRUX_HISTORY; prompts: initial-setup, performance-audit.
    • Native UI: CWV gauges, CrUX histograms, 25‑week trend sparklines, and PageSpeed opportunities.
    • File storage at ~/.deco/web-perf/sites/ for tracked sites and snapshots.
    • Mesh app integration: added Web Performance recruit modal, template in WELL_KNOWN_AGENT_TEMPLATES, and default pinned Dashboard view.
  • Migration

    • Start the MCP server: bun run --cwd packages/web-perf dev (defaults to port 3002).
    • In Mesh, click “Web Performance” → “Add Web Performance” to create the agent (reuses existing if found).
    • Provide a Google API key in the prompt or set GOOGLE_API_KEY (enable CrUX API and PageSpeed Insights API).
    • Use the initial-setup prompt to add a site, snapshot data, fetch history, and generate a report.

Written for commit eb0a0c6. Summary will update on new commits.

Adds a new standalone MCP agent package (`packages/web-perf/`) that monitors
website performance using Chrome UX Report field data and PageSpeed Insights
lab tests. Includes 7 tools (SITE_ADD, SITE_LIST, SITE_GET, SITE_DELETE,
PERF_SNAPSHOT, PERF_REPORT, CRUX_HISTORY), 2 prompts (initial-setup,
performance-audit), and native UI with CWV gauges, histogram bars, trend
sparklines, and opportunity tables.

Registers as a well-known agent template with a one-click setup modal in the
home page, agents list, and sidebar.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Benchmark

Should we run the Virtual MCP strategy benchmark for this PR?

React with 👍 to run the benchmark.

Reaction Action
👍 Run quick benchmark (10 & 128 tools)

Benchmark will run on the next push after you react.

@github-actions
Copy link
Copy Markdown
Contributor

Release Options

Suggested: Minor (2.265.0) — based on feat: prefix

React with an emoji to override the release type:

Reaction Type Next Version
👍 Prerelease 2.264.2-alpha.1
🎉 Patch 2.264.2
❤️ Minor 2.265.0
🚀 Major 3.0.0

Current version: 2.264.1

Note: If multiple reactions exist, the smallest bump wins. If no reactions, the suggested bump is used (default: patch).

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 issues found across 26 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/web/components/sidebar/agents-section.tsx">

<violation number="1" location="apps/mesh/src/web/components/sidebar/agents-section.tsx:360">
P2: Match Web Performance by title as well as metadata.type so already-installed agents aren’t missed and re-created.</violation>
</file>

<file name="packages/web-perf/server/ui/site-detail.ts">

<violation number="1" location="packages/web-perf/server/ui/site-detail.ts:310">
P1: Unsanitized user/API strings are injected via `innerHTML`, enabling XSS in the site detail view.</violation>
</file>

<file name="packages/web-perf/server/ui/dashboard.ts">

<violation number="1" location="packages/web-perf/server/ui/dashboard.ts:116">
P1: Escape `s.name` and `s.origin` before concatenating into HTML. They are currently injected into `innerHTML` unsafely, which allows script/markup injection.</violation>
</file>

<file name="packages/web-perf/server/tools/site-add.ts">

<violation number="1" location="packages/web-perf/server/tools/site-add.ts:28">
P2: `SITE_ADD` validates a URL but does not normalize it to an actual origin. Paths/query/hash are persisted as `origin`, which breaks downstream CrUX origin requests.</violation>
</file>

<file name="packages/web-perf/server/lib/report.ts">

<violation number="1" location="packages/web-perf/server/lib/report.ts:53">
P2: Do not require LCP to exist before building trend summary; it suppresses valid trends for other available metrics.</violation>

<violation number="2" location="packages/web-perf/server/lib/report.ts:68">
P2: Guard against zero historical averages before calculating percentage change to avoid NaN/Infinity trend output.</violation>

<violation number="3" location="packages/web-perf/server/lib/report.ts:108">
P1: Use an explicit undefined check for `performanceScore`; the current truthy check drops valid score `0`.</violation>
</file>

<file name="packages/web-perf/server/lib/types.ts">

<violation number="1" location="packages/web-perf/server/lib/types.ts:153">
P1: Expose a public site shape here instead of `SiteConfig`; the report currently returns the full `TrackedSite`, which can leak the stored API key and unnecessary snapshot history.</violation>
</file>

<file name="packages/web-perf/server/ui/shared-styles.ts">

<violation number="1" location="packages/web-perf/server/ui/shared-styles.ts:109">
P1: Validate `postMessage` origin/source before processing JSON-RPC messages; currently any origin can inject events into the bridge.</violation>
</file>

<file name="packages/web-perf/server/lib/storage.ts">

<violation number="1" location="packages/web-perf/server/lib/storage.ts:13">
P1: Validate `id` before building the filename; current path construction allows traversal outside the sites directory when `siteId` contains `..` or separators.</violation>
</file>

<file name="packages/web-perf/server/tools/site-get.ts">

<violation number="1" location="packages/web-perf/server/tools/site-get.ts:24">
P0: Strip the persisted API key before returning the site object; otherwise SITE_GET leaks the saved Google API key to callers.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

if (!site) {
throw new Error(`Site not found: ${context.siteId}`);
}
return { site };
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Strip the persisted API key before returning the site object; otherwise SITE_GET leaks the saved Google API key to callers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web-perf/server/tools/site-get.ts, line 24:

<comment>Strip the persisted API key before returning the site object; otherwise SITE_GET leaks the saved Google API key to callers.</comment>

<file context>
@@ -0,0 +1,26 @@
+    if (!site) {
+      throw new Error(`Site not found: ${context.siteId}`);
+    }
+    return { site };
+  },
+});
</file context>
Fix with Cubic

}
html += '</div>';

root.innerHTML = html;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Unsanitized user/API strings are injected via innerHTML, enabling XSS in the site detail view.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web-perf/server/ui/site-detail.ts, line 310:

<comment>Unsanitized user/API strings are injected via `innerHTML`, enabling XSS in the site detail view.</comment>

<file context>
@@ -0,0 +1,346 @@
+  }
+  html += '</div>';
+
+  root.innerHTML = html;
+}
+
</file context>
Fix with Cubic

}

h += '<div style="flex:1;min-width:0">' +
'<div class="font-semibold truncate">' + s.name + '</div>' +
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Escape s.name and s.origin before concatenating into HTML. They are currently injected into innerHTML unsafely, which allows script/markup injection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web-perf/server/ui/dashboard.ts, line 116:

<comment>Escape `s.name` and `s.origin` before concatenating into HTML. They are currently injected into `innerHTML` unsafely, which allows script/markup injection.</comment>

<file context>
@@ -0,0 +1,156 @@
+    }
+
+    h += '<div style="flex:1;min-width:0">' +
+      '<div class="font-semibold truncate">' + s.name + '</div>' +
+      '<div class="site-origin truncate">' + s.origin + '</div>';
+
</file context>
Fix with Cubic


const performanceScore = latest.pagespeed?.performanceScore;
const metricRatings = metrics.map((m) => m.rating);
const scoreRating = performanceScore
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Use an explicit undefined check for performanceScore; the current truthy check drops valid score 0.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web-perf/server/lib/report.ts, line 108:

<comment>Use an explicit undefined check for `performanceScore`; the current truthy check drops valid score `0`.</comment>

<file context>
@@ -0,0 +1,164 @@
+
+  const performanceScore = latest.pagespeed?.performanceScore;
+  const metricRatings = metrics.map((m) => m.rating);
+  const scoreRating = performanceScore
+    ? ratePerformanceScore(performanceScore)
+    : undefined;
</file context>
Fix with Cubic

}

export interface ReportData {
site: SiteConfig;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Expose a public site shape here instead of SiteConfig; the report currently returns the full TrackedSite, which can leak the stored API key and unnecessary snapshot history.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web-perf/server/lib/types.ts, line 153:

<comment>Expose a public site shape here instead of `SiteConfig`; the report currently returns the full `TrackedSite`, which can leak the stored API key and unnecessary snapshot history.</comment>

<file context>
@@ -0,0 +1,168 @@
+}
+
+export interface ReportData {
+  site: SiteConfig;
+  overallRating: Rating;
+  performanceScore?: number;
</file context>
Fix with Cubic

}

function sitePath(id: string): string {
return join(SITES_DIR, `${id}.json`);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Validate id before building the filename; current path construction allows traversal outside the sites directory when siteId contains .. or separators.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web-perf/server/lib/storage.ts, line 13:

<comment>Validate `id` before building the filename; current path construction allows traversal outside the sites directory when `siteId` contains `..` or separators.</comment>

<file context>
@@ -0,0 +1,77 @@
+}
+
+function sitePath(id: string): string {
+  return join(SITES_DIR, `${id}.json`);
+}
+
</file context>
Fix with Cubic

const webPerfTemplate = WELL_KNOWN_AGENT_TEMPLATES.find(
(t) => t.id === "web-perf",
);
const existingWebPerf = webPerfTemplate
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Match Web Performance by title as well as metadata.type so already-installed agents aren’t missed and re-created.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/components/sidebar/agents-section.tsx, line 360:

<comment>Match Web Performance by title as well as metadata.type so already-installed agents aren’t missed and re-created.</comment>

<file context>
@@ -350,6 +353,18 @@ function PinAgentPopoverContent({
+  const webPerfTemplate = WELL_KNOWN_AGENT_TEMPLATES.find(
+    (t) => t.id === "web-perf",
+  );
+  const existingWebPerf = webPerfTemplate
+    ? allAgents.find(
+        (a) =>
</file context>
Fix with Cubic

}),
execute: async ({ context }) => {
// Normalize origin: remove trailing slash
const origin = context.origin.replace(/\/+$/, "");
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: SITE_ADD validates a URL but does not normalize it to an actual origin. Paths/query/hash are persisted as origin, which breaks downstream CrUX origin requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web-perf/server/tools/site-add.ts, line 28:

<comment>`SITE_ADD` validates a URL but does not normalize it to an actual origin. Paths/query/hash are persisted as `origin`, which breaks downstream CrUX origin requests.</comment>

<file context>
@@ -0,0 +1,59 @@
+  }),
+  execute: async ({ context }) => {
+    // Normalize origin: remove trailing slash
+    const origin = context.origin.replace(/\/+$/, "");
+
+    try {
</file context>
Fix with Cubic


function describeTrend(site: TrackedSite): string | undefined {
const history = site.cruxHistory;
if (!history || !history.record.lcp) return undefined;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Do not require LCP to exist before building trend summary; it suppresses valid trends for other available metrics.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web-perf/server/lib/report.ts, line 53:

<comment>Do not require LCP to exist before building trend summary; it suppresses valid trends for other available metrics.</comment>

<file context>
@@ -0,0 +1,164 @@
+
+function describeTrend(site: TrackedSite): string | undefined {
+  const history = site.cruxHistory;
+  if (!history || !history.record.lcp) return undefined;
+
+  const lines: string[] = [];
</file context>
Fix with Cubic


const recentAvg = recent.reduce((s, v) => s + v, 0) / recent.length;
const olderAvg = older.reduce((s, v) => s + v, 0) / older.length;
const change = ((recentAvg - olderAvg) / olderAvg) * 100;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard against zero historical averages before calculating percentage change to avoid NaN/Infinity trend output.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web-perf/server/lib/report.ts, line 68:

<comment>Guard against zero historical averages before calculating percentage change to avoid NaN/Infinity trend output.</comment>

<file context>
@@ -0,0 +1,164 @@
+
+    const recentAvg = recent.reduce((s, v) => s + v, 0) / recent.length;
+    const olderAvg = older.reduce((s, v) => s + v, 0) / older.length;
+    const change = ((recentAvg - olderAvg) / olderAvg) * 100;
+
+    let direction: string;
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant