Skip to content

feat: onboarding framework, MCP client, settings UI, analytics + misc#195

Merged
steve8708 merged 35 commits intomainfrom
updates-90
Apr 14, 2026
Merged

feat: onboarding framework, MCP client, settings UI, analytics + misc#195
steve8708 merged 35 commits intomainfrom
updates-90

Conversation

@steve8708
Copy link
Copy Markdown
Contributor

@steve8708 steve8708 commented Apr 13, 2026

Summary

Bundles a batch of features and fixes that landed on updates-90 since main:

Onboarding framework (new)

Shared first-run setup flow for every template — replaces ad-hoc per-template onboarding with one step-by-step checklist pinned to the agent sidebar. Templates register steps with their own completion detection and method choices (paste a key / connect Builder / ask the agent).

  • packages/core/src/onboarding/ — registry + plugin + default steps (LLM, DB, auth)
  • packages/core/src/client/onboarding/<OnboardingPanel>, useOnboarding hook
  • Routes: GET /_agent-native/onboarding/steps, POST /steps/:id/complete, POST /dismiss
  • Method kinds: link / form / builder-cli-auth / agent-task (sends a prompt to the agent chat — the agent can drive setup the same way it drives everything else)

MCP client support (new)

Agent-native can now consume tools from user-configured local MCP servers, not just expose its own. Drop an mcp.config.json (workspace-root or per-app) and every MCP tool shows up in the agent's tool registry with the mcp__<server>__ prefix.

  • packages/core/src/mcp-client/ — config, manager, stdio transport
  • Agent-chat plugin registers MCP tools alongside actions
  • Auto-detects claude-in-chrome if installed; opt out with AGENT_NATIVE_DISABLE_MCP_AUTODETECT=1

Settings UI + AgentPanel refactor

  • packages/core/src/client/settings/ — new modular settings panel (AgentsSection, LLMSection, BrowserSection, BackgroundAgentSection, ComingSoonSection)
  • AgentPanel.tsx slimmed down (~635 → leaner), CodeRequiredDialog, ResourcesPanel cleanups

Analytics — ad-hoc analysis

New reusable analysis artifact system in the analytics template (separate from pre-built dashboards). Lets users ask the agent one-off questions and pin the result.

Content template

  • AsyncLocalStorage for per-request owner email (fixes owner leakage between concurrent requests)
  • SQLite migration ordering fix

Auth

  • Dev-mode auto-local session restored (unblocked local dev after a refactor broke the fallback)

Docs

CF / deploy

  • Timer shim applied to all CF chunks so setTimeout shims work across the full bundle

Test plan

  • pnpm -w run prep green locally
  • Mail template sidebar shows the onboarding panel with LLM / DB / auth steps when the environment is fresh
  • Paste an Anthropic key via the LLM step's form → step checks itself off on next poll
  • With mcp.config.json pointing at claude-in-chrome, agent tool list includes mcp__claude-in-chrome__*
  • Settings panel opens from the sidebar and shows the LLM/Browser/Agents sections
  • Analytics — create an ad-hoc analysis artifact, confirm it's queryable
  • Content — concurrent requests don't bleed owner_email across sessions
  • Dev mode: start any template without AUTH_MODE set → still logged in as local@localhost

Design notes: /Users/steve/.claude/plans/sorted-yawning-castle.md

…ation

- documents.ts: getCurrentOwnerEmail() now reads from AsyncLocalStorage
  via getRequestUserEmail() instead of process.env (concurrent-safe)
- db.ts: migrations v5-v8 are now no-ops — the tables in v1-v4 already
  include owner_email, and ALTER TABLE ADD COLUMN IF NOT EXISTS doesn't
  work on SQLite, causing fresh installs to fail
- enterprise-workspace.md: use `create` (not `create-workspace`), show
  multi-select picker, document `add-app`, add unified-deploy section
  with same-origin auth/A2A benefits, add shared-env section, refresh
  "out of scope" now that cross-subpath SSO is solved by deploy.
- getting-started.md: lead with workspace + multi-select, mention
  `--standalone` and `add-app`.
- deployment.md: add "Workspace Deploy: One Origin, Many Apps" section
  at the top; document `APP_BASE_PATH` + workspace env layering.
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 13, 2026

Deploy Preview for nutritrack-daily-calories ready!

Name Link
🔨 Latest commit 5589cd8
🔍 Latest deploy log https://app.netlify.com/projects/nutritrack-daily-calories/deploys/69dd9193b46a560008049827
😎 Deploy Preview https://deploy-preview-195--nutritrack-daily-calories.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 13, 2026

Deploy Preview for agent-native-fw ready!

Name Link
🔨 Latest commit 5589cd8
🔍 Latest deploy log https://app.netlify.com/projects/agent-native-fw/deploys/69dd919356f4ef0008af5170
😎 Deploy Preview https://deploy-preview-195--agent-native-fw.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 13, 2026

Deploying agent-native-mail with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5589cd8
Status: ✅  Deploy successful!
Preview URL: https://e045a423.agent-native.pages.dev
Branch Preview URL: https://updates-90.agent-native.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 13, 2026

Deploying agent-native-calendar with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5589cd8
Status: ✅  Deploy successful!
Preview URL: https://81dfcdb5.agent-native-calendar.pages.dev
Branch Preview URL: https://updates-90.agent-native-calendar.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 13, 2026

Deploying agent-native-forms with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5589cd8
Status: ✅  Deploy successful!
Preview URL: https://fd890bd0.agent-native-forms.pages.dev
Branch Preview URL: https://updates-90.agent-native-forms.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 13, 2026

Deploying agent-native-analytics with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5589cd8
Status: ✅  Deploy successful!
Preview URL: https://b1d670d0.agent-native-analytics.pages.dev
Branch Preview URL: https://updates-90.agent-native-analytics.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 13, 2026

Deploying agent-native-content with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5589cd8
Status: ✅  Deploy successful!
Preview URL: https://17dab5be.agent-native-67d.pages.dev
Branch Preview URL: https://updates-90.agent-native-67d.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 13, 2026

Deploying agent-native-videos with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5589cd8
Status: ✅  Deploy successful!
Preview URL: https://384f3f77.agent-native-videos.pages.dev
Branch Preview URL: https://updates-90.agent-native-videos.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 13, 2026

Deploying agent-native-slides with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5589cd8
Status: ✅  Deploy successful!
Preview URL: https://9f9e21fa.agent-native-slides.pages.dev
Branch Preview URL: https://updates-90.agent-native-slides.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 13, 2026

Deploying agent-native-dispatcher with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5589cd8
Status: ✅  Deploy successful!
Preview URL: https://850d3ff0.agent-native-dispatcher.pages.dev
Branch Preview URL: https://updates-90.agent-native-dispatcher.pages.dev

View logs

Copy link
Copy Markdown

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 2 potential issues.

Review Details

Code Review Summary

I've reviewed PR #195, which updates three user-facing docs (getting-started, deployment, enterprise-workspace) and refactors the Cloudflare Pages build process for multi-app workspace deployment (Risk: Standard).

Architectural Approach

The PR successfully pivots the framework from CLI-first onboarding to workspace-by-default, with the agent-native deploy command consolidating multiple apps behind a single origin. The unified deploy story is sound and solves real problems (shared auth sessions, zero-config cross-app A2A). The Cloudflare build refactor is complex but well-intentioned — using esbuild --splitting to reduce entry point size and applying surgical post-build patches to all generated chunks.

Key Findings

Critical Issues Found:

  • 🔴 HIGH — Database migrations v5–v8 replaced with no-ops, breaking upgrades for older content databases
  • 🟡 MEDIUM — Timer module-scope shim not applied to esbuild-split chunks
  • 🟢 LOW — Getting-started docs advertise pnpm dev but workspace scaffold doesn't support it

What Works Well:

  • ✅ Data scoping correctly uses getRequestUserEmail() from request context
  • ✅ Documentation is generally accurate with good cross-page anchor setup
  • ✅ Post-build patch loop thoughtfully applies patches to all JS files (with one exception noted)

🧪 Browser testing: Skipped — PR modifies documentation and build tooling only, no frontend/UI changes.

Code review by Builder.io

Add a complete ad-hoc analysis workflow: the agent gathers data from
multiple sources (HubSpot, Gong, Slack, BigQuery, etc.), synthesizes
findings into a Markdown report, and saves a reusable artifact with
re-run instructions. Anyone can hit "Re-run" to get fresh results
delegated back to the agent.

- Actions: save-analysis, get-analysis, list-analyses, delete-analysis
- API routes: GET/DELETE /api/analyses, /api/analyses/:id
- UI: /analyses list page + /analyses/:id detail with re-run button
- Sidebar: Analyses nav link with IconReportAnalytics
- Navigation: analyses view support in navigate action + nav state hook
- Skill: .agents/skills/adhoc-analysis/SKILL.md with full workflow guide
- AGENTS.md: updated with analysis actions, common tasks, skill ref
- Also includes dev-all.ts fix for templates.ts port config path
- Restore v5-v8 ALTER TABLE migrations in content template that were
  incorrectly replaced with SELECT 1 — breaks upgrade path for databases
  created before owner_email was added to CREATE TABLE statements.
- Apply CF Workers setInterval shim to all JS chunks, not just entry —
  with ESM code splitting, chunks evaluate before entry module body,
  so the shim must be present in every file.
Copy link
Copy Markdown

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 4 potential issues.

Review Details

Incremental Review: PR #195 Updated

The PR has been updated with new analytics template code. I've reviewed the new additions and found 4 new issues (3 high, 1 medium) — critical security vulnerabilities in the analytics Markdown renderer and data scoping in the new HTTP action.

Previous Issues (Still Unfixed)

The three issues from my initial review remain:

  • 🔴 HIGH — Database migrations v5–v8 replaced with no-ops (breaks upgrades)
  • 🟡 MEDIUM — Timer shim only on entry file, not split chunks
  • 🟢 LOW — Docs advertise pnpm dev without scaffold support

NEW Issues Found

Security-Critical:

  • 🔴 HIGH — Markdown XSS: Link URLs directly inserted into href without escaping (javascript: protocol attacks)
  • 🔴 HIGH — Markdown XSS: Code block language attribute not escaped (event handler injection)
  • 🔴 HIGH — list-analyses HTTP action leaks analyses from all users/orgs (uses getAllSettings() unscoped)
  • 🟡 MEDIUM — Analyses saved in local mode not migrated on sign-in (remain globally visible after upgrade)

What Works Well

✅ Data scoping helpers in the new handlers are correct
✅ Authorization checks are properly implemented
✅ Settings hierarchy (org → user → global) follows the pattern

🧪 Browser testing: Skipped — new analytics code is backend/component logic, visual/UX verification deferred to specific analytics testing phase.

Code review by Builder.io

.replace(/`([^`]+)`/g, "<code>$1</code>")
// Links
.replace(
/\[([^\]]+)\]\(([^)]+)\)/g,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 XSS vulnerability: Link URLs not escaped in href attribute

The markdown link handler directly inserts user-controlled URLs into href without HTML entity escaping. An attacker can use markdown like [click](javascript:alert('XSS')) to execute arbitrary JavaScript. The resultMarkdown comes from the AI agent and persists in the database (stored XSS).


React with 👍 or 👎 to help me improve.

codeLines.push(escapeHtml(lines[i]));
i++;
}
i++; // skip closing ```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 XSS vulnerability: Code block language attribute not escaped

The code block language attribute is directly inserted into the class attribute without escaping. A fence like ```" onclick="alert('XSS') becomes <code class="language-" onclick="...>, allowing event handler injection through markdown.


React with 👍 or 👎 to help me improve.

Comment on lines +20 to +25
const all = await getAllSettings();
const analyses: Record<string, unknown>[] = [];

// Collect from all scopes (org > user > global)
for (const [key, value] of Object.entries(all)) {
if (!key.includes(KEY_PREFIX)) continue;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 HTTP list-analyses action returns analyses from every user and org

The action uses getAllSettings() which scans the entire settings table unscoped, then filters only by key substring. In multi-user/multi-org deployments, any authenticated caller can enumerate other users' saved analyses via GET /_agent-native/actions/list-analyses.


React with 👍 or 👎 to help me improve.

Comment on lines +100 to +105
if (orgId) {
await putOrgSetting(orgId, key, analysis);
} else if (email !== "local@localhost") {
await putUserSetting(email, key, analysis);
} else {
await putSetting(key, analysis);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Analyses saved in local mode not migrated on sign-in

Analyses created with AGENT_USER_EMAIL=local@localhost are stored in global settings scope. The existing local-to-user migration route does not include the new adhoc-analysis- prefix, so these analyses never move to the signed-in user and remain globally visible after upgrade.


React with 👍 or 👎 to help me improve.

Copy link
Copy Markdown

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 4 potential issues.

Review Details

Incremental Review Update: PR #195 Status

The PR has been updated with fixes for 2 of the 6 previously reported issues. 4 critical security issues remain unfixed.

✅ Issues FIXED

Both deployment-related issues have been properly addressed:

  1. Database migrations v5–v8 — Restored proper ALTER TABLE statements that add owner_email columns to pre-existing tables. Upgrade path is now correct.

  2. Timer shim on split chunks — The setInterval/setTimeout workaround now applies to ALL .js files in the worker output directory, not just the entry. Solves the Cloudflare Pages ESM chunk evaluation timing issue.

❌ Critical Issues REMAINING (4 issues)

All security vulnerabilities in the analytics template are still present:

XSS Vulnerabilities (2):

  • Markdown link URLs directly inserted into href without escaping — javascript: protocol attacks possible
  • Markdown code block language attribute unescaped — event handler injection (onclick=...) possible
  • Both are stored XSS since resultMarkdown persists in the database

Data Leak:

  • list-analyses HTTP action uses getAllSettings() unscoped — any authenticated user can enumerate ALL users' saved analyses across the entire system

Local Mode Migration:

  • No migration added for analyses saved in AUTH_MODE=local — they remain in global scope after sign-in, potentially visible to other users

These 4 issues must be resolved before this PR can be merged. The Markdown XSS vulnerabilities are particularly critical as they enable arbitrary JavaScript execution in analysis viewers' browsers.

Code review by Builder.io

.replace(/`([^`]+)`/g, "<code>$1</code>")
// Links
.replace(
/\[([^\]]+)\]\(([^)]+)\)/g,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 XSS vulnerability: Link URLs not escaped in href attribute

The markdown link handler directly inserts user-controlled URLs into href without HTML entity escaping. An attacker can use markdown like [click](javascript:alert('XSS')) to execute arbitrary JavaScript. The resultMarkdown comes from the AI agent and persists in the database (stored XSS).


React with 👍 or 👎 to help me improve.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 10527d8 — URLs are now run through sanitizeUrl() (blocks javascript:/data:/vbscript:/file: protocols) and escapeHtml() before insertion into href attributes.

codeLines.push(escapeHtml(lines[i]));
i++;
}
i++; // skip closing ```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 XSS vulnerability: Code block language attribute not escaped

The code block language attribute is directly inserted into the class attribute without escaping. A fence like ```" onclick="alert('XSS') becomes <code class="language-" onclick="...>, allowing event handler injection through markdown.


React with 👍 or 👎 to help me improve.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 10527d8 — code fence language hints are now stripped to safe characters ([a-zA-Z0-9_+#.-]) and HTML-escaped before insertion into class attribute.

Comment on lines +20 to +25
const all = await getAllSettings();
const analyses: Record<string, unknown>[] = [];

// Collect from all scopes (org > user > global)
for (const [key, value] of Object.entries(all)) {
if (!key.includes(KEY_PREFIX)) continue;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 HTTP list-analyses action returns analyses from every user and org

The action uses getAllSettings() which scans the entire settings table unscoped, then filters only by key substring. In multi-user/multi-org deployments, any authenticated caller can enumerate other users' saved analyses via GET /_agent-native/actions/list-analyses.


React with 👍 or 👎 to help me improve.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 10527d8 — list-analyses now uses listOrgSettings(orgId, prefix) for org scope and exact u:<email>: prefix matching for user scope instead of unscoped getAllSettings() with substring filter.

Comment on lines +100 to +105
if (orgId) {
await putOrgSetting(orgId, key, analysis);
} else if (email !== "local@localhost") {
await putUserSetting(email, key, analysis);
} else {
await putSetting(key, analysis);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Analyses saved in local mode not migrated on sign-in

Analyses created with AGENT_USER_EMAIL=local@localhost are stored in global settings scope. The existing local-to-user migration route does not include the new adhoc-analysis- prefix, so these analyses never move to the signed-in user and remain globally visible after upgrade.


React with 👍 or 👎 to help me improve.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 10527d8 — local-mode analyses now use putUserSetting(email, key) which stores under u:local@localhost:adhoc-analysis-*. The existing migrateLocalUserData flow renames these to the real account on sign-in.

Another agent's refactor removed the dev auto-local fallback from
isLocalModeEnabled(). Without it, dev mode required real auth (Better
Auth or Google OAuth), causing constant logouts on server restart since
the session cookie pointed to a DB that may not persist.

Restored: in dev mode (NODE_ENV=development) without ACCESS_TOKEN or
custom auth, getSession() returns local@localhost automatically. This
is the expected dev experience — no auth setup needed.
Adds the "Connect Gmail via Builder" onramp so new users can reach mail's
aha moment without ever opening Google Cloud Console. When
BUILDER_PRIVATE_KEY is set and the user completes the Builder-hosted
OAuth flow, Gmail/People/Calendar calls go through
ai-services.builder.io/google/* instead of calling Google directly.

Agent-native side (this PR):

- credential-provider.ts — generic helpers for Builder-proxy auth:
  FeatureNotConfiguredError, hasBuilderPrivateKey, getBuilderAuthHeader,
  getBuilderProxyOrigin. Shaped so the LLM gateway layer can reuse it.
- google-proxy.ts — resolveGoogleTarget + per-workspace ledger of
  accounts connected via Builder (stored in the shared settings table
  so every sibling app in a workspace sees the same accounts).
- builder-browser.ts — getBuilderGoogleConnectUrl for the cli-auth
  handoff with the full gmail/calendar/contacts scope set.
- core-routes-plugin.ts — adds /_agent-native/builder/google/status
  (banner data) and /_agent-native/builder/google/callback (records
  the connected account after Builder finishes the OAuth exchange).
- templates/mail/server/lib/google-api.ts — gmail/people/calendar
  functions now accept GoogleAuth = string | GoogleProxyTarget. Direct
  string tokens keep the existing Google endpoints; proxy targets
  rewrite to ai-services.../google/{api}/*.
- templates/mail/server/lib/google-auth.ts — getClient / getClients /
  getAuthStatus / isConnected all now surface Builder-connected
  accounts alongside direct-OAuth accounts, de-duped by email.
- templates/mail/app/components/GoogleConnectBanner.tsx — hero layout
  gets a prominent "Connect Gmail via Builder" primary CTA (visible
  when BUILDER_PRIVATE_KEY is set) with the BYO-keys wizard below it
  as a secondary option. "Free while in beta" copy.

Builder side (separate PRs): multi-tenant Google OAuth app, token
storage in organizations_private, and /google/* proxy in ai-services
still to land. The agent-native side degrades to BYO-keys until they do.

Plan: /Users/steve/.claude/plans/sorted-yawning-castle.md
…rding

Removes the Builder-hosted multi-tenant Gmail OAuth proxy design
(resolveGoogleTarget / builder-google-accounts / GoogleConnectBanner
"Connect Gmail via Builder" CTA / /google/* proxy scaffolding).

Why: Google's CASA review for "restricted" Gmail scopes (readonly,
send, modify) would very likely reject a multi-tenant passthrough
where one verified OAuth client proxies tokens for arbitrary
downstream apps. Rather than bet Gmail availability on that review,
we're pivoting to a per-user BYO-keys flow, optionally bootstrapped
by the agent running a browser-automation onboarding (separate PR)
so users never have to touch Google Cloud Console by hand.

Kept:
- packages/core/src/server/credential-provider.ts — the generic
  FeatureNotConfiguredError / hasBuilderPrivateKey /
  getBuilderAuthHeader / getBuilderProxyOrigin helpers are still
  useful for the LLM gateway and future Builder-hosted credential
  integrations. JSDoc updated to drop the Google-proxy framing.
- The existing Builder browser / cli-auth flow
  (getBuilderBrowserConnectUrl, /builder/callback, /builder/status).
- Another agent's /builder/agents-run route, which was unstaged on
  this branch at revert time.

Removed:
- packages/core/src/server/google-proxy.ts (entire file).
- BUILDER_GOOGLE_* constants and getBuilderGoogleConnectUrl() in
  builder-browser.ts.
- /_agent-native/builder/google/status and
  /_agent-native/builder/google/callback in core-routes-plugin.ts.
- Proxy re-exports in packages/core/src/server/index.ts.
- GoogleProxyTarget / GoogleAuth plumbing in
  templates/mail/server/lib/google-api.ts and google-auth.ts;
  Gmail/People/Calendar functions go back to taking a plain
  accessToken: string.
- "Connect Gmail via Builder" button + builderStatus fetch in
  templates/mail/app/components/GoogleConnectBanner.tsx.
- New MCP client module and settings UI components
- AgentPanel, CodeRequiredDialog, ResourcesPanel improvements
- Macros agent-chat plugin updates
Introduces a shared onboarding checklist surfaced in the agent chat
sidebar of every template. Steps are registered via a module-level
registry and served over auto-mounted `/_agent-native/onboarding/*`
routes. Templates can register app-specific steps (Gmail, Slack, etc.)
while the framework ships three defaults (LLM, database, auth).

- New subpath exports: `@agent-native/core/onboarding` (types,
  registry, plugin) and `@agent-native/core/client/onboarding`
  (useOnboarding hook, OnboardingPanel, OnboardingBanner).
- Auto-mounts alongside the other framework plugins via the bootstrap
  path in framework-request-handler.
- OnboardingPanel is lazy-mounted inside AgentPanel — it hides itself
  once required steps are complete or the user dismisses.
- Four method kinds: link, form, builder-cli-auth, agent-task.
  builder-cli-auth reuses the existing builder-status endpoint and
  polls for credentials to appear; agent-task uses sendToAgentChat.
- State (overrides + dismissal) lives in application-state so it
  scopes per user and syncs across tabs.
@steve8708 steve8708 changed the title docs: workspace-by-default, multi-select picker, unified deploy feat: MCP client + onboarding framework + Gmail-proxy revert Apr 13, 2026
@steve8708 steve8708 changed the title feat: MCP client + onboarding framework + Gmail-proxy revert feat: onboarding framework, MCP client, settings UI, analytics + misc Apr 13, 2026
Registers a "Connect Gmail" step with the framework-level onboarding
registry so it appears in the agent sidebar's setup checklist.

The step offers two completion methods:
- Manual wizard (primary): links to the app root where the existing
  GoogleConnectBanner auto-detects missing credentials and guides the
  user through Google Cloud Console setup.
- Agent task (beta): hands the task to the agent, which drives the
  browser or falls back to step-by-step instructions and writes the
  resulting credentials to .env via /_agent-native/env-vars.

Completion is detected when GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET
are both set in the environment.
- Log when no config is found and no server is auto-detectable, so
  users can debug 'why isn't my MCP tool showing up' without tailing
  the whole startup.
- Tighten the auto-detect success log to explicitly mention registration.
- Add autoDetectMcpConfig unit tests (PATH discovery, miss, opt-out).
- Add mcpToolsToActionEntries unit tests covering agent-only http,
  text flattening, error-flagged results, and graceful degrade on
  underlying call failures.
…rdening

Inline UseBuilderCard/ManualSetupCard/LLMSectionInner into SettingsPanel
to remove unused separate component files. Make Setup tab icon-only.

Analytics actions now use scoped settings (org or user) exclusively,
closing a data leakage vector where list-analyses could expose other
users' saved analyses via unscoped getAllSettings iteration.

Markdown renderer hardens against XSS: sanitizeUrl blocks javascript:/
data:/vbscript: protocols, escapes single quotes, and normalizes code
fence language hints.
Deploy-time route discovery references defaultOnboardingPlugin as a
default plugin, but the onboarding plugin's exports were never added to
@agent-native/core/server. Cloudflare Pages builds failed for every
template with 'No matching export for import "defaultOnboardingPlugin"'.

Also includes in-flight calendar EventDescription extraction and settings
panel cleanups from concurrent work on this branch.
With Cloudflare Pages' nodejs_compat_v2 (implicit for compatibility_date
≥ 2024-09-23), plain 'fs' imports are rejected — only 'node:fs' works.
Before code splitting, the banner was only in the entry; main's CF
deploys happened to pass. Once splitting was turned on, every chunk got
the same banner and Pages rejected them with
  No such module 'node:fs' imported from chunks/...

Change the banner to emit 'node:$mod' imports and drop the post-build
pass that stripped the prefix.

Also carries in-flight settings panel props (builderEnabled/comingSoon)
so UseBuilderCard typechecks against its call sites.
Code splitting produced chunks with a banner that imported node:fs etc.
Cloudflare Pages rejected those chunks at deploy-time:
  'No such module "node:fs" imported from chunks/..'
even with nodejs_compat + compat_date 2025-01-01 in wrangler.toml.

Reverting to a single-file bundle matches what main shipped and gets
CF Pages green again. The entry grows back to ~9MB (same total size we
had split across 300 chunks — no new code, just not split).
The prior regex only matched `from "module"`; with the node: prefix
now preserved (needed for nodejs_compat_v2), esbuild emits
`from "node:module"` instead. That slipped past the patch and the
resulting bundle called createRequire(undefined) at worker init, which
crashed CF Pages deploy with:
  The argument 'path' must be a file URL object, ... Received 'undefined'
Without splitting, the single-file bundle hit Cloudflare Pages'
25 MiB Functions size limit (26.3 MiB). With splitting back on, we
need to strip the `node:` prefix from every import because Pages
Functions runs under nodejs_compat v1 (bare names only) and rejects
`node:fs` at worker init with 'No such module "node:fs"'.

Workers-on-the-edge with a 2024-09-23+ compat date use v2 (prefix
required); Pages Functions lags behind. Strip handles both cases —
esbuild-emitted imports and dependency code — and the require shim
banner also emits bare names to match.
…calls

The static-import regex missed dynamic `import("node:fs")` calls that
Nitro/h3 emit in some chunks. CF Pages' loader walks every chunk and
fails on the first unresolved node: specifier regardless of whether
the import is static or dynamic.

Extend the post-build strip to cover both dynamic imports and require().
Anthropic SDK source has `import('node:buffer').File` inside an error
string. esbuild preserves the single quotes, so the outer double-quoted
literal stays valid. My earlier strip rewrote to `import("buffer")`
and the inner double quotes terminated the outer string, producing:
  Uncaught SyntaxError: Unexpected identifier 'buffer'
at CF Pages deploy time.

Fix: capture the original quote char and preserve it in the replacement.

Verified locally with `wrangler pages dev` — chunk now reads
`import('buffer').File` and the worker boots (no syntax error).
CF Pages' Functions loader scans chunks for "node:*" string literals
and pre-resolves them as module specs — whether or not the string is
ever passed to import()/require() at runtime. Keeping "node:fs" as
an object key in the require shim, or as a string arg to a minified
wrapper like Ut("node:fs"), still triggered:
  Uncaught Error: No such module "node:fs"

Replace the targeted import/require regexes with a single pass that
rewrites "node:X" -> "X" for X in a known-builtins list (fs, path,
os, crypto, http, ..., async_hooks, sqlite, worker_threads). Scoping
to known builtins avoids touching user strings that happen to start
with 'node:'.

Also drop the 'node:NAME' keys from the require shim's lookup table
since no runtime lookup uses them anymore.

Verified locally with `wrangler pages dev dist` — no module resolution
errors; no "node:*" strings remain in any chunk.
Switched from post-build string strip to esbuild's --alias flag so
node: prefix never lands in chunks in the first place. CF Pages
Functions (wrangler 3.x, nodejs_compat v1) rejects `import from
"node:fs"` in non-entry chunks, and the post-build regex strip was
apparently not reliable across build environments — CF's produced
chunks with node:fs intact while the identical local run produced
bare names. --alias operates at bundle resolution, not text
replacement, so it's environment-independent.

Keep the post-build strip as belt & suspenders for any dependency
that emits node: through an unusual path (dynamic imports inside
template literals, etc.).
esbuild doesn't apply --alias to specifiers that are already matched
by --external. With both `--external:node:fs` and `--alias:node:fs=fs`,
the external wins and esbuild emits the node: prefix verbatim. Remove
the node:* externals so the alias fires first, resolving every
`from "node:fs"` to the bare `fs` external.

Verified: chunks now contain `import from "fs"` — no node: prefix
anywhere.
…ons, and agents

Add the ability to manage workspace-wide skills, instructions, and reusable
agent profiles from the dispatcher control plane. Resources can be scoped to
"all apps" (pushed everywhere) or "selected" (granted per-app), mirroring
the vault secret grant model.

New tables: workspace_resources, workspace_resource_grants
New store: workspace-resources-store.ts (CRUD, grants, sync via /_agent-native/resources)
9 new actions for resource management and cross-app sync
New workspace.tsx UI with tabbed skills/instructions/agents view
…antics

Since compat_date >= 2024-09-23, `nodejs_compat` implies v2 behavior
(node: prefix required). But CF Pages Functions' wrangler pipeline
keeps rejecting deploys with:
  No such module "node:fs" imported from chunks/...

Local builds emit bare 'fs' imports (v1 form), wrangler pages dev
accepts them, yet CF Pages' production validator fails. Pages Functions
has lagged behind Workers on v2 adoption; pinning to the day before v2
(2024-09-22) keeps us on v1 where bare names are the expected form,
matching what esbuild emits here.

Long-term we should move back to 2025-01-01 once Pages v2 support
stabilizes — or once we move off `_worker.js/` chunk output to a
single-file bundle or Workers (not Pages).
Subagent-validated: wrangler 3.x with nodejs_compat flag needs date
>= 2024-09-23 for bare builtin imports ('fs', 'path', etc.) to
auto-resolve. Pre-2024-09-23 errors out at bundle time with:
  Could not resolve 'util'. Make sure to prefix the module name
  with 'node:' or update your compatibility_date to 2024-09-23 or later.

Previous commit pinned to 2024-09-22, which was the wrong direction
for our bare-name strategy — wrangler was 1 day off v2 and forced us
back to v1 (node: prefix required).

Local `wrangler pages dev` now returns HTTP 200 cleanly.
…ndle

CF Pages' deploy validator consistently rejects chunked _worker.js/
bundles with 'No such module "node:fs" imported from chunks/...',
even when the chunks contain only bare-name imports (which local
`wrangler pages dev` + workerd accept fine — HTTP 200).

Same chunk hash 'chunk-2UYMX2L7.js' appeared in the failing error
across 5 pushes, suggesting CF's upload cache/dedup by content-hash
combined with Pages' wrangler 3.101.0 bundler doing something
different than local wrangler does. Rather than continue fighting it,
revert to single-file bundle like main.

Local CF build: 9.1 MB total — well under the 25 MiB limit. Local
wrangler pages dev: HTTP 200, no module-resolution errors.

Keeps the node: prefix strip + alias + require shim infrastructure in
place as belt-and-suspenders, but without splitting none of that is
load-bearing anymore.
Slides' single-file CF Pages bundle was 19 MB locally / 26 MB after
CF's wrangler polyfills — over the 25 MiB Pages Functions limit.

The bloat came from mermaid, @excalidraw/excalidraw, pdf-parse, and
@google/genai being bundled into the SSR server chunk via
`ssr.noExternal`. These only render in the browser or run from Node
action scripts — SSR never executes them — so marking them external
cuts the bundle to 8.7 MB without runtime impact.

Local wrangler pages dev still returns HTTP 200.
Copy link
Copy Markdown

@builder-io-integration builder-io-integration bot left a comment

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 2 potential issues.

Review Details

Code Review: PR #195 Bundle Optimization

What changed: This PR externalizes 5 heavy libraries (mermaid, @excalidraw/excalidraw, @excalidraw/mermaid-to-excalidraw, pdf-parse, @google/genai) from the Cloudflare Pages SSR bundle to reduce the slides template's bundle size from 26MB to 8.7MB.

Risk Assessment: 🔴 HIGH — Bundle configuration change affecting production deployments to Cloudflare Pages. Incorrect externalization can cause runtime failures in deployed apps.

Architectural Concern: The PR correctly identifies a real problem (exceeding CF Pages' 25MB Functions limit), but the solution is unsafe for two of the five externalized libraries:

🔴 Critical Issues Found

Issue 1: pdf-parse is incorrectly externalized from the recruiting template

  • Root Cause: pdf-parse is statically imported in templates/recruiting/actions/filter-candidates.ts (line 5), which imports it from templates/recruiting/server/lib/resume-filter.ts (line 2)
  • Impact: When the filter-candidates action executes in the CF Pages worker, it will fail with a module-not-found error when attempting to parse PDF resumes (line 89 of resume-filter.ts: const pdf = new PDFParse(...))
  • Why it matters: Resume filtering is a core recruiting feature; it will silently fail in production whenever a resume PDF needs to be analyzed

Issue 2: mermaid is incorrectly externalized from the slides template

  • Root Cause: mermaid is statically imported in templates/slides/app/components/deck/MermaidRenderer.tsx (line 2), which is imported by SlideRenderer.tsx (line 6). SlideRenderer is used throughout the application (deck cards, presentation view, slide editor) and is part of the React Router route import graph
  • Impact: Although MermaidRenderer only executes in the browser, the static import means mermaid is part of the SSR bundle at compile time. Externalizing it will cause the esbuild bundler to emit a bare import in the worker, leading to module-not-found errors if Mermaid slides are encountered during SSR or route resolution
  • Why it matters: Any slide with Mermaid diagrams could fail to load properly in production

✅ Acceptable Externalization

@excalidraw/excalidraw and @excalidraw/mermaid-to-excalidraw appear safe — they're only imported via dynamic await import() calls in UI components, keeping them outside the static import graph.

@google/genai is dynamically imported in action handlers, which is safer, though it still carries risk if any code path references it statically.

Recommended Fix

  1. Keep pdf-parse bundled in the recruiting template — it's required by the filter-candidates action
  2. Keep mermaid bundled in the slides template — it's part of the SlideRenderer component graph
  3. Consider alternative bundle-size reduction strategies:
    • Lazy-load only the specific features (mermaid rendering, PDF parsing) when actually needed
    • Split the worker bundle into multiple chunks if CF Pages supports it
    • Trim unused dependencies from package.json

🧪 Browser testing: Skipped — PR touches build configuration only, no UI files modified. Testing should focus on verifying that both the recruiting filter-candidates action and slides presentation views work correctly after deployment.

Code review by Builder.io

Previous approach used `ssr.external`, which tells vite to leave
import specifiers untransformed in the server bundle. Wrangler then
re-resolved them from node_modules at deploy time and pulled the full
mermaid/excalidraw/pdf-parse source into the CF Pages Functions bundle
— inflating slides to 34 MiB (over the 25 MiB limit).

Replace with a vite plugin that intercepts SSR resolution for these
packages and returns a tiny Proxy-based stub instead. The stub answers
any property access with another stub, so dead import/re-export chains
parse cleanly, but nothing real ships in the server bundle.

Client build is unaffected — only SSR hits the plugin.

Slides local build: 8.7 MB. wrangler pages dev: HTTP 200.
Register docs-search as an agent tool so every app's agent can look up
framework documentation. Docs are bundled in @agent-native/core and
read via fs at runtime — always the right version, no network needed.

Bump minor version for the new docs-search tool, workspace-management
doc, and docs-as-source-of-truth move to core.
Hardcoding mermaid/excalidraw/pdf-parse in the framework's vite helper
was wrong — those are application-specific concerns and the core should
never know their names.

Expose 'ssrStubs: string[]' on defineConfig instead. Templates that have
heavy browser-only deps (currently only slides) opt in by listing them:

  defineConfig({
    ssrStubs: ['mermaid', '@excalidraw/excalidraw'],
  })

Slides opts in for mermaid + excalidraw + mermaid-to-excalidraw.
Removes pdf-parse and @google/genai from the list entirely — pdf-parse
is used server-side by recruiting's filter-candidates action and must
resolve normally; stubbing it would break production PDF parsing.

Framework core no longer mentions any package name.
@steve8708 steve8708 merged commit 4c62b4c into main Apr 14, 2026
18 checks passed
@steve8708 steve8708 deleted the updates-90 branch April 14, 2026 01:05
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