@agent-native/core 0.146.4 → 0.146.6

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.
Files changed (113) hide show
  1. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +1 -1
  2. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
  3. package/corpus/templates/analytics/app/pages/Settings.tsx +5 -1
  4. package/corpus/templates/analytics/app/routes/agent.tsx +7 -5
  5. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +13 -9
  6. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +69 -33
  7. package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -1
  8. package/corpus/templates/assets/app/routes/agent.tsx +7 -5
  9. package/corpus/templates/assets/app/routes/settings.tsx +7 -2
  10. package/corpus/templates/assets/server/plugins/creative-context.ts +1 -1
  11. package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
  12. package/corpus/templates/brain/app/lib/brain.ts +4 -2
  13. package/corpus/templates/brain/app/root.tsx +1 -1
  14. package/corpus/templates/brain/app/routes/agent.tsx +10 -2
  15. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -1
  16. package/corpus/templates/calendar/app/root.tsx +1 -2
  17. package/corpus/templates/calendar/app/routes/_app.agent.tsx +8 -16
  18. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +5 -2
  19. package/corpus/templates/chat/app/routes/agent.tsx +7 -31
  20. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  21. package/corpus/templates/clips/app/root.tsx +1 -2
  22. package/corpus/templates/clips/app/routes/_app.agent.tsx +9 -7
  23. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
  24. package/corpus/templates/content/app/root.tsx +1 -2
  25. package/corpus/templates/content/app/routes/_app.agent.tsx +9 -8
  26. package/corpus/templates/content/app/routes/_app.settings.tsx +7 -2
  27. package/corpus/templates/content/server/plugins/creative-context.ts +1 -1
  28. package/corpus/templates/crm/app/components/crm/RecordActions.tsx +4 -1
  29. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +1 -1
  30. package/corpus/templates/crm/app/routes/agent.tsx +10 -2
  31. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -1
  32. package/corpus/templates/design/app/root.tsx +1 -1
  33. package/corpus/templates/design/app/routes/agent.tsx +9 -7
  34. package/corpus/templates/design/app/routes/settings.tsx +7 -2
  35. package/corpus/templates/design/server/plugins/creative-context.ts +1 -1
  36. package/corpus/templates/dispatch/app/root.tsx +5 -2
  37. package/corpus/templates/dispatch/app/routes/agent.tsx +7 -7
  38. package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
  39. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +5 -2
  40. package/corpus/templates/factory/app/root.tsx +1 -1
  41. package/corpus/templates/factory/app/routes/agent.tsx +9 -14
  42. package/corpus/templates/forms/app/components/layout/Layout.tsx +1 -1
  43. package/corpus/templates/forms/app/root.tsx +1 -1
  44. package/corpus/templates/forms/app/routes/_app.agent.tsx +9 -7
  45. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +5 -3
  46. package/corpus/templates/macros/app/root.tsx +1 -1
  47. package/corpus/templates/macros/app/routes/agent.tsx +9 -6
  48. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +8 -2
  49. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +1 -1
  50. package/corpus/templates/mail/app/routes/agent.tsx +9 -6
  51. package/corpus/templates/plan/app/components/layout/Layout.tsx +1 -1
  52. package/corpus/templates/plan/app/root.tsx +1 -1
  53. package/corpus/templates/plan/app/routes/agent.tsx +9 -6
  54. package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +8 -12
  55. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +39 -2
  56. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  57. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  58. package/corpus/templates/slides/app/lib/create-deck-generation.ts +22 -5
  59. package/corpus/templates/slides/app/pages/Index.tsx +20 -5
  60. package/corpus/templates/slides/app/root.tsx +1 -1
  61. package/corpus/templates/slides/app/routes/agent.tsx +6 -9
  62. package/corpus/templates/slides/app/routes/settings.tsx +7 -2
  63. package/corpus/templates/slides/server/plugins/creative-context.ts +1 -1
  64. package/corpus/templates/tasks/app/components/layout/Layout.tsx +1 -1
  65. package/corpus/templates/tasks/app/routes/agent.tsx +10 -2
  66. package/dist/client/index.d.ts +1 -1
  67. package/dist/client/navigation/index.d.ts +1 -1
  68. package/dist/client/navigation/index.js +1 -1
  69. package/dist/client/org/OrgSwitcher.js +1 -1
  70. package/dist/client/settings/SettingsPanel.d.ts +8 -0
  71. package/dist/client/settings/SettingsPanel.js +16 -1
  72. package/dist/client/settings/index.d.ts +1 -1
  73. package/dist/collab/awareness.d.ts +2 -2
  74. package/dist/collab/routes.d.ts +1 -1
  75. package/dist/deploy/build.d.ts +21 -0
  76. package/dist/deploy/build.js +185 -10
  77. package/dist/jobs/background-automation-runner.d.ts +1 -1
  78. package/dist/jobs/background-automation-runner.js +1 -1
  79. package/dist/jobs/scheduler-health.d.ts +4 -0
  80. package/dist/jobs/scheduler-health.js +33 -0
  81. package/dist/localization/server.d.ts +0 -1
  82. package/dist/localization/server.js +3 -1
  83. package/dist/mcp-client/index.d.ts +1 -1
  84. package/dist/mcp-client/index.js +1 -1
  85. package/dist/mcp-client/routes.d.ts +11 -0
  86. package/dist/mcp-client/routes.js +26 -1
  87. package/dist/navigation/index.d.ts +1 -0
  88. package/dist/navigation/index.js +34 -0
  89. package/dist/notifications/routes.d.ts +2 -2
  90. package/dist/observability/routes.d.ts +3 -3
  91. package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
  92. package/dist/provider-api/actions/provider-api.d.ts +5 -5
  93. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  94. package/dist/secrets/routes.d.ts +6 -6
  95. package/dist/server/agent-chat/mcp-glue.js +12 -3
  96. package/dist/server/agent-chat-plugin.js +73 -48
  97. package/dist/server/http-response-telemetry.js +112 -5
  98. package/dist/server/realtime-token.d.ts +1 -1
  99. package/dist/server/release-migrations.js +2 -0
  100. package/dist/server/ssr-handler.js +8 -0
  101. package/dist/server/transcribe-voice.d.ts +1 -1
  102. package/dist/templates/chat/app/hooks/use-navigation-state.ts +5 -2
  103. package/dist/templates/chat/app/routes/agent.tsx +7 -31
  104. package/dist/templates/default/app/routes/_index.tsx +1 -1
  105. package/dist/templates/default/app/routes/agent.tsx +7 -25
  106. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +54 -3
  107. package/dist/vite/client.js +5 -1
  108. package/package.json +1 -1
  109. package/src/templates/chat/app/hooks/use-navigation-state.ts +5 -2
  110. package/src/templates/chat/app/routes/agent.tsx +7 -31
  111. package/src/templates/default/app/routes/_index.tsx +1 -1
  112. package/src/templates/default/app/routes/agent.tsx +7 -25
  113. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +54 -3
@@ -1925,6 +1925,13 @@ const SERVERLESS_BROWSER_RUNTIME_PACKAGES = [
1925
1925
  "@sparticuz/chromium",
1926
1926
  "playwright-core",
1927
1927
  ];
1928
+ const SERVERLESS_BROWSER_RUNTIME_CONSUMER = "@agent-native/creative-context";
1929
+ const PACKAGE_DEPENDENCY_FIELDS = [
1930
+ "dependencies",
1931
+ "optionalDependencies",
1932
+ "devDependencies",
1933
+ "peerDependencies",
1934
+ ];
1928
1935
  // Serverless functions only ever run on 64-bit Linux. The darwin/win32/android
1929
1936
  // and 32-bit-arm prebuilds of these native packages are ~100MB that can never
1930
1937
  // execute there, and Netlify copies the whole server dir again for every extra
@@ -1941,6 +1948,11 @@ const SERVERLESS_NATIVE_PACKAGE_SUFFIXES = [
1941
1948
  export function isServerlessNativePlatformPackage(packageName) {
1942
1949
  return SERVERLESS_NATIVE_PACKAGE_SUFFIXES.some((suffix) => packageName.endsWith(suffix));
1943
1950
  }
1951
+ // Names a prebuild package by the platform it targets: `darwin-arm64`,
1952
+ // `resvg-js-win32-ia32-msvc`, `linux-x64-gnu`. Matching only says "this is a
1953
+ // per-platform prebuild"; isServerlessNativePlatformPackage decides whether it
1954
+ // can run in the function.
1955
+ const SERVERLESS_PLATFORM_PACKAGE_NAME = /(?:^|-)(?:darwin|win32|linux|android|freebsd)-[a-z0-9]+(?:-[a-z0-9]+)?$/;
1944
1956
  const FFMPEG_STATIC_BINARY_NAMES = process.platform === "win32" ? ["ffmpeg.exe", "ffmpeg"] : ["ffmpeg"];
1945
1957
  const SERVERLESS_FFMPEG_STATIC_PLATFORM = "linux";
1946
1958
  const SERVERLESS_FFMPEG_STATIC_ARCHES = new Set([
@@ -1993,6 +2005,37 @@ function readPackageManifest(packageDir) {
1993
2005
  }
1994
2006
  return manifest;
1995
2007
  }
2008
+ function manifestDeclaresDependency(manifest, packageName) {
2009
+ if (!manifest)
2010
+ return false;
2011
+ return PACKAGE_DEPENDENCY_FIELDS.some((field) => {
2012
+ const deps = manifest[field];
2013
+ if (!deps || typeof deps !== "object" || Array.isArray(deps))
2014
+ return false;
2015
+ return Object.prototype.hasOwnProperty.call(deps, packageName);
2016
+ });
2017
+ }
2018
+ /**
2019
+ * Which dependency gives this app a path to the serverless browser runtime, or
2020
+ * null when it has none. Resolution cannot answer this: findInstalledPackageRoot
2021
+ * falls back to an ancestor + .pnpm store walk, so in a workspace every app
2022
+ * "resolves" the sibling package that actually declares Chromium and ships its
2023
+ * ~80MB into every emitted function.
2024
+ */
2025
+ export function findServerlessBrowserRuntimeConsumer(projectCwd = cwd) {
2026
+ const manifest = readPackageManifest(projectCwd);
2027
+ for (const packageName of [
2028
+ ...SERVERLESS_BROWSER_RUNTIME_PACKAGES,
2029
+ SERVERLESS_BROWSER_RUNTIME_CONSUMER,
2030
+ ]) {
2031
+ if (manifestDeclaresDependency(manifest, packageName))
2032
+ return packageName;
2033
+ const linked = path.join(projectCwd, "node_modules", ...packageName.split("/"));
2034
+ if (fs.existsSync(linked))
2035
+ return packageName;
2036
+ }
2037
+ return null;
2038
+ }
1996
2039
  function packageRootFromResolvedPath(packageName, resolvedPath) {
1997
2040
  let current = path.dirname(resolvedPath);
1998
2041
  while (true) {
@@ -2062,6 +2105,16 @@ export function copyInstalledBrowserRuntimePackages(serverDir, projectCwd = cwd)
2062
2105
  if (!serverDir || !fs.existsSync(serverDir))
2063
2106
  return 0;
2064
2107
  const nodeModulesRoots = nodeModulesAncestors(projectCwd);
2108
+ const consumer = findServerlessBrowserRuntimeConsumer(projectCwd);
2109
+ if (!consumer) {
2110
+ const skippedBytes = SERVERLESS_BROWSER_RUNTIME_PACKAGES.reduce((total, packageName) => {
2111
+ const packageDir = findInstalledPackageRoot(packageName, nodeModulesRoots);
2112
+ return packageDir ? total + getDirSize(packageDir) : total;
2113
+ }, 0);
2114
+ console.log(`[deploy] Skipped the serverless browser runtime (${SERVERLESS_BROWSER_RUNTIME_PACKAGES.join(", ")}): this app depends on neither ${SERVERLESS_BROWSER_RUNTIME_CONSUMER} ` +
2115
+ `nor a browser package. Kept ${(skippedBytes / 1024 / 1024).toFixed(1)}MB out of every emitted function.`);
2116
+ return 0;
2117
+ }
2065
2118
  const copiedPackages = new Set();
2066
2119
  let copiedCount = 0;
2067
2120
  for (const packageName of SERVERLESS_BROWSER_RUNTIME_PACKAGES) {
@@ -2070,9 +2123,15 @@ export function copyInstalledBrowserRuntimePackages(serverDir, projectCwd = cwd)
2070
2123
  continue;
2071
2124
  copiedCount += copyRuntimePackageTree(packageName, packageDir, serverDir, nodeModulesRoots, copiedPackages);
2072
2125
  }
2073
- if (copiedCount > 0) {
2074
- console.log(`[deploy] Copied ${copiedCount} serverless browser runtime package(s) into the server bundle.`);
2126
+ if (copiedCount === 0) {
2127
+ // Not the same as the skip above: the app asked for the browser runtime and
2128
+ // the build could not find it, so anything that opens a browser will fail
2129
+ // at runtime instead of at build time.
2130
+ console.warn(`[deploy] ${consumer} needs the serverless browser runtime but none of ` +
2131
+ `${SERVERLESS_BROWSER_RUNTIME_PACKAGES.join(", ")} is installed; the function ships without it.`);
2132
+ return 0;
2075
2133
  }
2134
+ console.log(`[deploy] Copied ${copiedCount} serverless browser runtime package(s) into the server bundle (required by ${consumer}).`);
2076
2135
  return copiedCount;
2077
2136
  }
2078
2137
  function findInstalledLibsqlNativePackage(nodeModulesRoots, packageName) {
@@ -2847,6 +2906,60 @@ export function bundleYjsRuntimeForServerlessOutput(serverDir, projectCwd) {
2847
2906
  });
2848
2907
  return bareImports;
2849
2908
  }
2909
+ // Netlify's hard limit is 250MB unzipped per function; a bundle this far under
2910
+ // it still leaves room for growth, and today's server functions land near 70MB
2911
+ // once the dead weight is out. Apps that legitimately ship the browser runtime
2912
+ // get its ~85MB on top, so the budget stays a regression signal rather than a
2913
+ // tax on Chromium-backed templates.
2914
+ const NETLIFY_FUNCTION_SIZE_BUDGET_BYTES = 120 * 1024 * 1024;
2915
+ const NETLIFY_BROWSER_RUNTIME_SIZE_ALLOWANCE_BYTES = 100 * 1024 * 1024;
2916
+ function reportNetlifyFunctionSizes(projectCwd, internalDir, failures) {
2917
+ if (!fs.existsSync(internalDir))
2918
+ return;
2919
+ const functions = fs
2920
+ .readdirSync(internalDir, { withFileTypes: true })
2921
+ .filter((entry) => entry.isDirectory())
2922
+ .map((entry) => ({
2923
+ name: entry.name,
2924
+ dir: path.join(internalDir, entry.name),
2925
+ size: getDirSize(path.join(internalDir, entry.name)),
2926
+ }))
2927
+ .sort((a, b) => b.size - a.size);
2928
+ if (functions.length === 0)
2929
+ return;
2930
+ const toMb = (bytes) => (bytes / 1024 / 1024).toFixed(1);
2931
+ const total = functions.reduce((sum, fn) => sum + fn.size, 0);
2932
+ // Every extra function is a full second copy of the bundle: the clone is
2933
+ // hard-linked on disk, but zip-it-and-ship-it sees regular files and uploads
2934
+ // each one whole, so the total is what the deploy actually pays.
2935
+ console.log(`[deploy] Netlify functions: ${functions.length} (${functions
2936
+ .map((fn) => `${fn.name} ${toMb(fn.size)}MB`)
2937
+ .join(", ")}) — ${toMb(total)}MB uploaded in total.`);
2938
+ const budget = NETLIFY_FUNCTION_SIZE_BUDGET_BYTES +
2939
+ (findServerlessBrowserRuntimeConsumer(projectCwd)
2940
+ ? NETLIFY_BROWSER_RUNTIME_SIZE_ALLOWANCE_BYTES
2941
+ : 0);
2942
+ for (const fn of functions) {
2943
+ if (fn.size <= budget)
2944
+ continue;
2945
+ // node_modules is always the biggest child and names nothing useful; the
2946
+ // packages inside it are what a regression actually adds.
2947
+ const nodeModulesDir = path.join(fn.dir, "node_modules");
2948
+ const inspectDir = fs.existsSync(nodeModulesDir) ? nodeModulesDir : fn.dir;
2949
+ const largest = fs
2950
+ .readdirSync(inspectDir, { withFileTypes: true })
2951
+ .filter((entry) => entry.isDirectory())
2952
+ .map((entry) => ({
2953
+ name: entry.name,
2954
+ size: getDirSize(path.join(inspectDir, entry.name)),
2955
+ }))
2956
+ .sort((a, b) => b.size - a.size)
2957
+ .slice(0, 3)
2958
+ .map((child) => `${child.name} ${toMb(child.size)}MB`)
2959
+ .join(", ");
2960
+ failures.push(`function ${fn.name} is ${toMb(fn.size)}MB, over the ${toMb(budget)}MB budget — largest: ${largest}`);
2961
+ }
2962
+ }
2850
2963
  export function assertSingleTemplateNetlifyBuildOutput(projectCwd) {
2851
2964
  const failures = [];
2852
2965
  const publishDir = path.join(projectCwd, "dist");
@@ -2988,9 +3101,10 @@ export function assertSingleTemplateNetlifyBuildOutput(projectCwd) {
2988
3101
  }
2989
3102
  }
2990
3103
  }
3104
+ reportNetlifyFunctionSizes(projectCwd, internalDir, failures);
2991
3105
  if (failures.length > 0) {
2992
3106
  throw new Error("[deploy] Netlify deploy guard failed; refusing to publish an output " +
2993
- "that would likely serve Netlify 404s:\n" +
3107
+ "that would likely serve Netlify 404s or blow the function size limit:\n" +
2994
3108
  failures.map((failure) => `- ${failure}`).join("\n"));
2995
3109
  }
2996
3110
  console.log("[deploy] Netlify deploy guard passed: publish dir and catch-all server function are present.");
@@ -3111,13 +3225,7 @@ export function sanitizeServerlessFunctionPackageManifest(functionDir) {
3111
3225
  return;
3112
3226
  }
3113
3227
  let removed = 0;
3114
- const depFields = [
3115
- "dependencies",
3116
- "optionalDependencies",
3117
- "devDependencies",
3118
- "peerDependencies",
3119
- ];
3120
- for (const field of depFields) {
3228
+ for (const field of PACKAGE_DEPENDENCY_FIELDS) {
3121
3229
  const deps = packageJson[field];
3122
3230
  if (!deps || typeof deps !== "object" || Array.isArray(deps))
3123
3231
  continue;
@@ -3145,6 +3253,70 @@ export function sanitizeServerlessFunctionPackageManifest(functionDir) {
3145
3253
  console.log(`[deploy] Removed ${removed} desktop-only package reference(s) from ${path.relative(cwd, functionDir)}.`);
3146
3254
  }
3147
3255
  }
3256
+ /**
3257
+ * Nitro's dependency tracer is not subject to the copy-time filters above, so
3258
+ * it ships every prebuild of a multi-platform native package plus whatever the
3259
+ * local dev server left in `data/`. Prune both from the function dir before it
3260
+ * is cloned for the extra functions, since each clone is zipped and uploaded in
3261
+ * full.
3262
+ *
3263
+ * Only prunes inside a directory that still holds a runnable prebuild under the
3264
+ * `isServerlessNativePlatformPackage` naming (`@libsql`, `@resvg`). Packages
3265
+ * that name prebuilds differently — `@img/sharp-linux-x64` has no gnu/musl
3266
+ * suffix — read as entirely dead and must be left alone.
3267
+ */
3268
+ export function pruneServerlessFunctionDeadWeight(functionDir) {
3269
+ if (!functionDir || !fs.existsSync(functionDir))
3270
+ return 0;
3271
+ let removedBytes = 0;
3272
+ const removedNames = [];
3273
+ const nodeModulesDir = path.join(functionDir, "node_modules");
3274
+ const packageDirs = [];
3275
+ if (fs.existsSync(nodeModulesDir)) {
3276
+ packageDirs.push(nodeModulesDir);
3277
+ for (const entry of fs.readdirSync(nodeModulesDir, {
3278
+ withFileTypes: true,
3279
+ })) {
3280
+ if (entry.isDirectory() && entry.name.startsWith("@")) {
3281
+ packageDirs.push(path.join(nodeModulesDir, entry.name));
3282
+ }
3283
+ }
3284
+ }
3285
+ for (const packageDir of packageDirs) {
3286
+ const prebuilds = fs
3287
+ .readdirSync(packageDir, { withFileTypes: true })
3288
+ .filter((entry) => entry.isDirectory() &&
3289
+ SERVERLESS_PLATFORM_PACKAGE_NAME.test(entry.name))
3290
+ .map((entry) => entry.name);
3291
+ const runnable = prebuilds.filter(isServerlessNativePlatformPackage);
3292
+ if (runnable.length === 0)
3293
+ continue;
3294
+ for (const name of prebuilds) {
3295
+ if (isServerlessNativePlatformPackage(name))
3296
+ continue;
3297
+ const deadDir = path.join(packageDir, name);
3298
+ removedBytes += getDirSize(deadDir);
3299
+ removedNames.push(path.relative(functionDir, deadDir));
3300
+ fs.rmSync(deadDir, { recursive: true, force: true });
3301
+ }
3302
+ const survivors = runnable.filter((name) => fs.existsSync(path.join(packageDir, name)));
3303
+ if (survivors.length === 0) {
3304
+ throw new Error(`[deploy] Pruning dead-platform prebuilds from ${path.relative(cwd, packageDir)} removed every runnable Linux prebuild (had: ${runnable.join(", ")}).`);
3305
+ }
3306
+ }
3307
+ const dataDir = path.join(functionDir, "data");
3308
+ if (fs.existsSync(dataDir)) {
3309
+ removedBytes += getDirSize(dataDir);
3310
+ removedNames.push("data");
3311
+ fs.rmSync(dataDir, { recursive: true, force: true });
3312
+ }
3313
+ if (removedNames.length > 0) {
3314
+ console.log(`[deploy] Pruned ${removedNames.length} unrunnable path(s) (${(removedBytes /
3315
+ 1024 /
3316
+ 1024).toFixed(1)}MB) from ${path.relative(cwd, functionDir)}: ${removedNames.join(", ")}.`);
3317
+ }
3318
+ return removedBytes;
3319
+ }
3148
3320
  /**
3149
3321
  * Create stub directories for dangling platform-specific optional dependency
3150
3322
  * symlinks in the pnpm store.
@@ -3612,6 +3784,9 @@ export default bundle;
3612
3784
  copyInstalledFfmpegStaticPackage(nitro.options.output.serverDir);
3613
3785
  copyInstalledBrowserRuntimePackages(nitro.options.output.serverDir);
3614
3786
  sanitizeServerlessFunctionPackageManifest(nitro.options.output.serverDir);
3787
+ // Before the Netlify block below clones this dir into the extra functions,
3788
+ // so they inherit the pruned bundle instead of a second full copy.
3789
+ pruneServerlessFunctionDeadWeight(nitro.options.output.serverDir);
3615
3790
  bundleYjsRuntimeForServerlessOutput(nitro.options.output.serverDir, cwd);
3616
3791
  }
3617
3792
  if (isCloudflareModulePreset(preset)) {
@@ -12,7 +12,7 @@ export interface BackgroundAutomationContext {
12
12
  resource: Resource;
13
13
  }
14
14
  export interface BackgroundAutomationDeps {
15
- getActions: (automation?: BackgroundAutomationContext) => Record<string, ActionEntry>;
15
+ getActions: (automation?: BackgroundAutomationContext) => Record<string, ActionEntry> | Promise<Record<string, ActionEntry>>;
16
16
  getSystemPrompt: (owner: string) => Promise<string>;
17
17
  getInitialToolNames?: (automation?: BackgroundAutomationContext) => string[] | undefined;
18
18
  engine?: AgentEngine;
@@ -221,7 +221,7 @@ async function executeBackgroundAutomation(options, deps, historyId) {
221
221
  userEmail: ownerEmail,
222
222
  orgId,
223
223
  }, async () => {
224
- const baseActions = deps.getActions(automation);
224
+ const baseActions = await deps.getActions(automation);
225
225
  assertRequestedMcpToolsAvailable(automation, baseActions);
226
226
  const configuredInitialTools = deps.getInitialToolNames?.(automation);
227
227
  const initialToolNames = configuredInitialTools
@@ -1,3 +1,7 @@
1
+ import { type MigrationEntry } from "../db/migrations.js";
2
+ /** Authoritative release-time schema for recurring scheduler health. */
3
+ export declare const AUTOMATION_SCHEDULER_HEALTH_MIGRATIONS: MigrationEntry[];
4
+ export declare function runAutomationSchedulerHealthMigrations(nitroApp: unknown): Promise<void>;
1
5
  export interface AutomationSchedulerHealth {
2
6
  appId: string;
3
7
  orgId: string | null;
@@ -1,8 +1,41 @@
1
1
  import { getDbExec, intType, isPostgres } from "../db/client.js";
2
2
  import { ensureColumnExists, ensureIndexExists, ensureTableExists, } from "../db/ddl-guard.js";
3
+ import { runMigrations } from "../db/migrations.js";
3
4
  const TABLE = "automation_scheduler_health";
4
5
  const DEFAULT_APP_ID = "default";
5
6
  const MAX_ERROR_LENGTH = 500;
7
+ /** Authoritative release-time schema for recurring scheduler health. */
8
+ export const AUTOMATION_SCHEDULER_HEALTH_MIGRATIONS = [
9
+ {
10
+ version: 1,
11
+ name: "automation-scheduler-health-table",
12
+ sql: `
13
+ CREATE TABLE IF NOT EXISTS ${TABLE} (
14
+ id TEXT PRIMARY KEY,
15
+ app_id TEXT NOT NULL DEFAULT 'default',
16
+ org_id TEXT,
17
+ last_checked_at INTEGER,
18
+ last_dispatched_at INTEGER,
19
+ last_error TEXT,
20
+ runtime TEXT,
21
+ updated_at INTEGER NOT NULL DEFAULT 0
22
+ );
23
+ ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS app_id TEXT NOT NULL DEFAULT 'default';
24
+ ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS org_id TEXT;
25
+ ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS last_checked_at INTEGER;
26
+ ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS last_dispatched_at INTEGER;
27
+ ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS last_error TEXT;
28
+ ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS runtime TEXT;
29
+ ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS updated_at INTEGER NOT NULL DEFAULT 0;
30
+ CREATE INDEX IF NOT EXISTS idx_${TABLE}_updated ON ${TABLE} (updated_at)
31
+ `,
32
+ },
33
+ ];
34
+ export async function runAutomationSchedulerHealthMigrations(nitroApp) {
35
+ await runMigrations(AUTOMATION_SCHEDULER_HEALTH_MIGRATIONS, {
36
+ table: "_automation_scheduler_health_migrations",
37
+ })(nitroApp);
38
+ }
6
39
  function normalizeAppId(appId) {
7
40
  const normalized = appId?.trim();
8
41
  return normalized || DEFAULT_APP_ID;
@@ -16,7 +16,6 @@ export interface ResolveLocaleFromRequestOptions {
16
16
  export interface LocaleInitScriptOptions {
17
17
  locale?: LocaleCode;
18
18
  preference?: LocalizationPreference | LocalePreference;
19
- messages?: Record<string, unknown> | null;
20
19
  }
21
20
  export declare function parseAcceptLanguage(header: string | null | undefined): string[];
22
21
  export declare function resolveLocaleFromRequest(options?: ResolveLocaleFromRequestOptions): ResolvedRequestLocale;
@@ -58,11 +58,13 @@ export function getLocaleInitScript(options = {}) {
58
58
  const safeLocale = normalizeLocaleCode(options.locale) ?? DEFAULT_LOCALE;
59
59
  const shouldStorePreference = options.preference !== undefined;
60
60
  const safePreference = normalizeLocalizationPreference(options.preference ?? { locale: "system" });
61
+ // Translations are deliberately absent here: this script is render-blocking in
62
+ // <head>, and the provider already receives the same catalog through loader
63
+ // data as `initialMessages`. Adding them back doubles every localized payload.
61
64
  const payload = {
62
65
  locale: safeLocale,
63
66
  preference: safePreference,
64
67
  dir: localeDirection(safeLocale),
65
- messages: options.messages ?? undefined,
66
68
  };
67
69
  return `(function(){try{var supported=${JSON.stringify(SUPPORTED_LOCALES)};var payload=${JSON.stringify(payload)};var shouldStorePreference=${JSON.stringify(shouldStorePreference)};function valid(x){return supported.indexOf(x)>=0}function canon(x){if(typeof x!=='string'||!x)return null;try{var c=Intl.getCanonicalLocales(x)[0];if(valid(c))return c;var lang=c&&c.split('-')[0].toLowerCase();for(var i=0;i<supported.length;i++){if(supported[i].split('-')[0].toLowerCase()===lang)return supported[i]}}catch(e){}return null}function storageGet(k){try{return window.localStorage.getItem(k)}catch(e){return null}}function storageSet(k,v){try{window.localStorage.setItem(k,v)}catch(e){}}var stored=storageGet(${JSON.stringify(LOCALE_STORAGE_KEY)});var pref=payload.preference&&payload.preference.locale;var locale=payload.locale;if(!valid(locale)){locale=null}if(!locale&&stored&&stored!=='system'){locale=canon(stored)}if(!locale&&pref&&pref!=='system'){locale=canon(pref)}if(!locale){var langs=navigator.languages&&navigator.languages.length?navigator.languages:[navigator.language];for(var j=0;j<langs.length&&!locale;j++){locale=canon(langs[j])}}if(!locale)locale=${JSON.stringify(DEFAULT_LOCALE)};var root=document.documentElement;root.setAttribute('lang',locale);root.setAttribute('dir',locale==='ar-SA'?'rtl':'ltr');root.setAttribute('data-locale',locale);payload.locale=locale;payload.dir=locale==='ar-SA'?'rtl':'ltr';window[${JSON.stringify(LOCALE_HYDRATION_GLOBAL)}]=payload;if(shouldStorePreference&&pref){storageSet(${JSON.stringify(LOCALE_STORAGE_KEY)},pref)}}catch(e){}})();`;
68
70
  }
@@ -10,7 +10,7 @@ export { listRemoteServers, addRemoteServer, addOAuthRemoteServer, addFirstParty
10
10
  export { finishMcpOAuthAuthorization, getMcpOAuthAccessToken, readMcpOAuthCredentials, saveMcpOAuthCredentials, startMcpOAuthAuthorization, type McpOAuthCallbackResult, type McpOAuthCredentialBundle, type McpOAuthDiscoveryState, type McpOAuthProviderOptions, type McpOAuthStartResult, } from "./oauth-client.js";
11
11
  export { areBuiltinMcpCapabilitiesSupported, BUILTIN_MCP_CAPABILITIES, getBuiltinMcpCapability, isBuiltinMcpCapabilityAvailable, listSupportedBuiltinMcpCapabilities, normalizeBuiltinMcpCapabilityIds, toBuiltinMcpServerConfig, type BuiltinMcpCapability, type BuiltinMcpCapabilityId, } from "./builtin-capabilities.js";
12
12
  export { builtinMcpCapabilitiesSettingsKey, listEnabledBuiltinMcpCapabilities, setEnabledBuiltinMcpCapabilities, setBuiltinMcpCapabilityEnabled, type StoredBuiltinMcpCapabilities, } from "./builtin-store.js";
13
- export { mountMcpServersRoutes, buildMergedConfig, builtinMergedConfigKey, startMcpConfigRefresh, type ClientBuiltinCapability, } from "./routes.js";
13
+ export { mountMcpServersRoutes, buildMergedConfig, builtinMergedConfigKey, startMcpConfigRefresh, McpConfigUnreadableError, type ClientBuiltinCapability, } from "./routes.js";
14
14
  export { mountMcpHubRoutes, listHubServers, getHubStatus, isHubServeEnabled, isHubConsumeEnabled, type HubServerRecord, type HubServersResponse, } from "./hub-routes.js";
15
15
  export { fetchHubServers } from "./hub-client.js";
16
16
  export { isMcpToolAllowedForRequest } from "./visibility.js";
@@ -10,7 +10,7 @@ export { listRemoteServers, addRemoteServer, addOAuthRemoteServer, addFirstParty
10
10
  export { finishMcpOAuthAuthorization, getMcpOAuthAccessToken, readMcpOAuthCredentials, saveMcpOAuthCredentials, startMcpOAuthAuthorization, } from "./oauth-client.js";
11
11
  export { areBuiltinMcpCapabilitiesSupported, BUILTIN_MCP_CAPABILITIES, getBuiltinMcpCapability, isBuiltinMcpCapabilityAvailable, listSupportedBuiltinMcpCapabilities, normalizeBuiltinMcpCapabilityIds, toBuiltinMcpServerConfig, } from "./builtin-capabilities.js";
12
12
  export { builtinMcpCapabilitiesSettingsKey, listEnabledBuiltinMcpCapabilities, setEnabledBuiltinMcpCapabilities, setBuiltinMcpCapabilityEnabled, } from "./builtin-store.js";
13
- export { mountMcpServersRoutes, buildMergedConfig, builtinMergedConfigKey, startMcpConfigRefresh, } from "./routes.js";
13
+ export { mountMcpServersRoutes, buildMergedConfig, builtinMergedConfigKey, startMcpConfigRefresh, McpConfigUnreadableError, } from "./routes.js";
14
14
  export { mountMcpHubRoutes, listHubServers, getHubStatus, isHubServeEnabled, isHubConsumeEnabled, } from "./hub-routes.js";
15
15
  export { fetchHubServers } from "./hub-client.js";
16
16
  export { isMcpToolAllowedForRequest } from "./visibility.js";
@@ -22,6 +22,17 @@ import type { McpConfig } from "./config.js";
22
22
  import { type McpClientManager } from "./manager.js";
23
23
  import { type RemoteMcpScope } from "./remote-store.js";
24
24
  export { formatMcpConnectError } from "./errors.js";
25
+ /**
26
+ * The settings table backing remote/built-in MCP servers could not be read.
27
+ *
28
+ * Distinct from `buildMergedConfig()` returning `null`, which means the app is
29
+ * genuinely configured with zero MCP servers. Coercing an unreachable database
30
+ * into an empty settings map made every caller report "no MCP servers
31
+ * configured" while the real answer was "could not read the configuration".
32
+ */
33
+ export declare class McpConfigUnreadableError extends Error {
34
+ constructor(cause: unknown);
35
+ }
25
36
  export interface ClientServer {
26
37
  id: string;
27
38
  scope: RemoteMcpScope;
@@ -24,6 +24,7 @@ import { getSession } from "../server/auth.js";
24
24
  import { getH3App } from "../server/framework-request-handler.js";
25
25
  import { readBody } from "../server/h3-helpers.js";
26
26
  import { runWithRequestContext } from "../server/request-context.js";
27
+ import { shouldDisableInProcessSweeps } from "../server/sweep-runtime.js";
27
28
  import { getAllSettings, getSettingsEmitter } from "../settings/store.js";
28
29
  import { areBuiltinMcpCapabilitiesSupported, BUILTIN_MCP_CAPABILITIES, getBuiltinMcpCapability, isBuiltinMcpCapabilityAvailable, listSupportedBuiltinMcpCapabilities, normalizeBuiltinMcpCapabilityIds, toBuiltinMcpServerConfig, } from "./builtin-capabilities.js";
29
30
  import { builtinMcpCapabilitiesSettingsKey, listEnabledBuiltinMcpCapabilities, setBuiltinMcpCapabilityEnabled, setEnabledBuiltinMcpCapabilities, } from "./builtin-store.js";
@@ -36,6 +37,21 @@ import { addRemoteServer, listRemoteServers, mergedConfigKey, removeRemoteServer
36
37
  import { isMcpToolAllowedForRequest } from "./visibility.js";
37
38
  import { loadWorkspaceMcpServers } from "./workspace-servers.js";
38
39
  export { formatMcpConnectError } from "./errors.js";
40
+ /**
41
+ * The settings table backing remote/built-in MCP servers could not be read.
42
+ *
43
+ * Distinct from `buildMergedConfig()` returning `null`, which means the app is
44
+ * genuinely configured with zero MCP servers. Coercing an unreachable database
45
+ * into an empty settings map made every caller report "no MCP servers
46
+ * configured" while the real answer was "could not read the configuration".
47
+ */
48
+ export class McpConfigUnreadableError extends Error {
49
+ constructor(cause) {
50
+ super(`Could not read MCP configuration from settings: ${cause?.message ?? cause}`);
51
+ this.name = "McpConfigUnreadableError";
52
+ this.cause = cause;
53
+ }
54
+ }
39
55
  /** Redact obvious auth header values before sending to the client. */
40
56
  function redactHeaders(headers) {
41
57
  if (!headers)
@@ -114,7 +130,10 @@ export function builtinMergedConfigKey(scope, capability, ownerId) {
114
130
  export async function buildMergedConfig() {
115
131
  const base = loadMcpConfig() ?? autoDetectMcpConfig();
116
132
  const servers = { ...(base?.servers ?? {}) };
117
- const all = await getAllSettings().catch(() => ({}));
133
+ const all = await getAllSettings().catch((err) => {
134
+ console.warn(`[mcp-client] settings read failed: ${err?.message ?? err}`);
135
+ throw new McpConfigUnreadableError(err);
136
+ });
118
137
  for (const [fullKey, value] of Object.entries(all)) {
119
138
  const userMatch = /^u:([^:]+):mcp-servers-remote$/.exec(fullKey);
120
139
  const orgMatch = /^o:([^:]+):mcp-servers-remote$/.exec(fullKey);
@@ -222,6 +241,12 @@ export function startMcpConfigRefresh(manager) {
222
241
  const intervalMs = mcpConfigRefreshIntervalMs();
223
242
  if (intervalMs <= 0 || typeof setInterval !== "function")
224
243
  return null;
244
+ // Billed per warm container on serverless, and the first tick always runs a
245
+ // full settings scan because `settingsDirty` starts true. Request-driven
246
+ // reconfigures (`waitUntilReady` on the MCP routes, `refreshGlobalMcpManager`)
247
+ // already cover config changes there, and a fresh container re-reads config.
248
+ if (shouldDisableInProcessSweeps())
249
+ return null;
225
250
  let currentSignature = sortedConfigSignature(manager.getConfig());
226
251
  let refreshing = false;
227
252
  // `buildMergedConfig` reads the entire settings table just to diff a
@@ -38,6 +38,7 @@ export interface StandardOpenPathResolverOptions {
38
38
  fallback?: StandardOpenPathRoute;
39
39
  }
40
40
  export declare function buildSettingsRoute(tab?: StandardSettingsTabId | string | null, basePath?: "/settings"): string;
41
+ export declare function buildLegacyAgentSettingsRoute(hash?: string | null, search?: string): string;
41
42
  export declare function buildTeamRoute(options?: Pick<BuildStandardAppRouteOptions, "teamInSettings">): string;
42
43
  export declare function buildStandardAppRoute(route: StandardAppRouteId, options?: BuildStandardAppRouteOptions): string;
43
44
  export declare function buildResourceRoute(collection: string, resourceId: string, options?: BuildResourceRouteOptions): string;
@@ -15,6 +15,21 @@ export const STANDARD_SETTINGS_TABS = {
15
15
  language: "language",
16
16
  whatsNew: "whats-new",
17
17
  };
18
+ const LEGACY_AGENT_RESOURCE_TABS = new Set([
19
+ "files",
20
+ "instructions",
21
+ "agents",
22
+ "memory",
23
+ "skills",
24
+ "learnings",
25
+ ]);
26
+ const LEGACY_AGENT_SETTINGS_TABS = new Set([
27
+ "llm",
28
+ "app-models",
29
+ "limits",
30
+ "voice",
31
+ "background",
32
+ ]);
18
33
  function normalizeLeadingPath(path) {
19
34
  const trimmed = path.trim();
20
35
  if (!trimmed || trimmed === "/")
@@ -56,6 +71,25 @@ export function buildSettingsRoute(tab, basePath = STANDARD_APP_ROUTES.settings)
56
71
  .filter(Boolean);
57
72
  return `${path}/${segments.join("/")}`;
58
73
  }
74
+ export function buildLegacyAgentSettingsRoute(hash, search = "") {
75
+ const legacyTab = hash?.replace(/^#/, "").toLowerCase() ?? "";
76
+ const destination = LEGACY_AGENT_RESOURCE_TABS.has(legacyTab)
77
+ ? buildSettingsRoute(`agent:resources:${legacyTab}`)
78
+ : legacyTab === "remote-agents"
79
+ ? buildSettingsRoute("agent:agents")
80
+ : legacyTab === "connections"
81
+ ? buildSettingsRoute("connections")
82
+ : legacyTab === "jobs"
83
+ ? buildSettingsRoute("agent:automations")
84
+ : legacyTab === "library"
85
+ ? buildSettingsRoute("library")
86
+ : legacyTab === "access"
87
+ ? buildSettingsRoute("agent")
88
+ : LEGACY_AGENT_SETTINGS_TABS.has(legacyTab)
89
+ ? buildSettingsRoute(`agent:${legacyTab}`)
90
+ : buildSettingsRoute("agent");
91
+ return `${destination}${search}`;
92
+ }
59
93
  export function buildTeamRoute(options = {}) {
60
94
  return options.teamInSettings
61
95
  ? buildSettingsRoute(STANDARD_SETTINGS_TABS.team)
@@ -13,13 +13,13 @@
13
13
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
14
14
  count: number;
15
15
  updated?: undefined;
16
- ok?: undefined;
17
16
  error?: undefined;
17
+ ok?: undefined;
18
18
  } | {
19
19
  count?: undefined;
20
20
  updated: number;
21
- ok?: undefined;
22
21
  error?: undefined;
22
+ ok?: undefined;
23
23
  } | {
24
24
  count?: undefined;
25
25
  updated?: undefined;
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- error?: undefined;
45
44
  summary: import("./types.js").TraceSummary;
46
45
  spans: import("./types.js").TraceSpan[];
47
46
  id?: undefined;
47
+ error?: undefined;
48
48
  ok?: undefined;
49
49
  } | {
50
- error?: undefined;
51
50
  summary?: undefined;
52
51
  spans?: undefined;
53
52
  id: string;
53
+ error?: undefined;
54
54
  ok?: undefined;
55
55
  } | {
56
56
  summary?: undefined;
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
59
59
  error: any;
60
60
  ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
66
65
  ok: boolean;
66
+ error?: undefined;
67
67
  }>>;
@@ -75,8 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- id?: undefined;
79
78
  deleted?: undefined;
79
+ id?: undefined;
80
80
  providers: {
81
81
  id: string;
82
82
  label: string;
@@ -93,10 +93,10 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
93
93
  label?: undefined;
94
94
  message?: undefined;
95
95
  } | {
96
- id?: undefined;
97
96
  deleted?: undefined;
98
- providers?: undefined;
99
97
  count?: undefined;
98
+ id?: undefined;
99
+ providers?: undefined;
100
100
  found: boolean;
101
101
  provider: import("../custom-registry.js").CustomProviderConfig;
102
102
  registered?: undefined;
@@ -104,8 +104,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
104
104
  message?: undefined;
105
105
  } | {
106
106
  deleted?: undefined;
107
- providers?: undefined;
108
107
  count?: undefined;
108
+ providers?: undefined;
109
109
  provider?: undefined;
110
110
  found: boolean;
111
111
  id: string;
@@ -113,8 +113,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
113
113
  label?: undefined;
114
114
  message?: undefined;
115
115
  } | {
116
- providers?: undefined;
117
116
  count?: undefined;
117
+ providers?: undefined;
118
118
  provider?: undefined;
119
119
  found?: undefined;
120
120
  deleted: boolean;
@@ -124,8 +124,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
124
124
  message?: undefined;
125
125
  } | {
126
126
  deleted?: undefined;
127
- providers?: undefined;
128
127
  count?: undefined;
128
+ providers?: undefined;
129
129
  provider?: undefined;
130
130
  found?: undefined;
131
131
  registered: boolean;
@@ -311,10 +311,10 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
311
311
  notes?: string | undefined;
312
312
  scope?: "org" | "user" | undefined;
313
313
  }, {
314
+ message?: undefined;
314
315
  id?: undefined;
315
316
  provider?: undefined;
316
317
  deleted?: undefined;
317
- message?: undefined;
318
318
  providers: {
319
319
  id: string;
320
320
  label: string;
@@ -329,29 +329,29 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
329
329
  registered?: undefined;
330
330
  label?: undefined;
331
331
  } | {
332
- id?: undefined;
332
+ message?: undefined;
333
333
  count?: undefined;
334
+ id?: undefined;
334
335
  deleted?: undefined;
335
- message?: undefined;
336
336
  providers?: undefined;
337
337
  found: boolean;
338
338
  provider: import("../custom-registry.js").CustomProviderConfig;
339
339
  registered?: undefined;
340
340
  label?: undefined;
341
341
  } | {
342
+ message?: undefined;
342
343
  count?: undefined;
343
344
  provider?: undefined;
344
345
  deleted?: undefined;
345
- message?: undefined;
346
346
  providers?: undefined;
347
347
  found: boolean;
348
348
  id: string;
349
349
  registered?: undefined;
350
350
  label?: undefined;
351
351
  } | {
352
+ message?: undefined;
352
353
  count?: undefined;
353
354
  provider?: undefined;
354
- message?: undefined;
355
355
  providers?: undefined;
356
356
  found?: undefined;
357
357
  deleted: boolean;