@agent-native/core 0.31.2 → 0.32.1

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 (37) hide show
  1. package/dist/client/AssistantChat.d.ts.map +1 -1
  2. package/dist/client/AssistantChat.js +7 -0
  3. package/dist/client/AssistantChat.js.map +1 -1
  4. package/dist/client/MultiTabAssistantChat.js +5 -5
  5. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  6. package/dist/client/use-chat-threads.d.ts.map +1 -1
  7. package/dist/client/use-chat-threads.js +8 -1
  8. package/dist/client/use-chat-threads.js.map +1 -1
  9. package/dist/deploy/build.d.ts +2 -1
  10. package/dist/deploy/build.d.ts.map +1 -1
  11. package/dist/deploy/build.js +104 -42
  12. package/dist/deploy/build.js.map +1 -1
  13. package/dist/deploy/immutable-assets.d.ts.map +1 -1
  14. package/dist/deploy/immutable-assets.js +5 -0
  15. package/dist/deploy/immutable-assets.js.map +1 -1
  16. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  17. package/dist/server/core-routes-plugin.js +1719 -1685
  18. package/dist/server/core-routes-plugin.js.map +1 -1
  19. package/dist/server/framework-request-handler.js +8 -6
  20. package/dist/server/framework-request-handler.js.map +1 -1
  21. package/dist/server/index.d.ts +1 -1
  22. package/dist/server/index.d.ts.map +1 -1
  23. package/dist/server/index.js +1 -1
  24. package/dist/server/index.js.map +1 -1
  25. package/dist/server/request-context.d.ts +8 -0
  26. package/dist/server/request-context.d.ts.map +1 -1
  27. package/dist/server/request-context.js +25 -4
  28. package/dist/server/request-context.js.map +1 -1
  29. package/dist/server/ssr-handler.d.ts +1 -1
  30. package/dist/server/ssr-handler.d.ts.map +1 -1
  31. package/dist/server/ssr-handler.js +54 -15
  32. package/dist/server/ssr-handler.js.map +1 -1
  33. package/dist/shared/cache-control.d.ts +6 -0
  34. package/dist/shared/cache-control.d.ts.map +1 -1
  35. package/dist/shared/cache-control.js +6 -0
  36. package/dist/shared/cache-control.js.map +1 -1
  37. package/package.json +1 -1
@@ -22,10 +22,10 @@ import { getWorkspaceCoreExports, } from "./workspace-core.js";
22
22
  import { generateActionRegistryForProject } from "../vite/action-types-plugin.js";
23
23
  import { mcpEmbedStaticAssetRouteRules } from "../shared/mcp-embed-headers.js";
24
24
  import { AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE } from "../shared/social-meta.js";
25
+ import { DEFAULT_SPECULATION_RULES_PATH, DEFAULT_SSR_CACHE_CONTROL, } from "../shared/cache-control.js";
25
26
  import { collectImmutableAssetPaths, IMMUTABLE_ASSET_CACHE_CONTROL, IMMUTABLE_ASSET_CACHE_HEADERS, prefixAssetPath, } from "./immutable-assets.js";
26
27
  const cwd = process.cwd();
27
28
  const preset = process.env.NITRO_PRESET || "node";
28
- const DEFAULT_SSR_CACHE_CONTROL = "public, max-age=5, stale-while-revalidate=604800, stale-if-error=3600";
29
29
  function normalizeConfiguredAppBasePath() {
30
30
  const raw = process.env.VITE_APP_BASE_PATH || process.env.APP_BASE_PATH;
31
31
  if (!raw || raw === "/")
@@ -49,6 +49,31 @@ function isNodeOnlyPlugin(filePath) {
49
49
  const basename = path.basename(filePath, path.extname(filePath));
50
50
  return NODE_ONLY_PLUGINS.has(basename);
51
51
  }
52
+ export function generateProvidedPluginsNitroPluginSource(pluginStems) {
53
+ const stems = [...new Set(pluginStems.filter(Boolean))].sort();
54
+ return `// AUTO-GENERATED by @agent-native/core deploy build
55
+ import { markDefaultPluginProvided } from "@agent-native/core/server";
56
+
57
+ const pluginStems = ${JSON.stringify(stems)};
58
+
59
+ export default function markBuildDiscoveredPlugins(nitroApp) {
60
+ for (const stem of pluginStems) {
61
+ markDefaultPluginProvided(nitroApp, stem);
62
+ }
63
+ }
64
+ `;
65
+ }
66
+ async function writeProvidedPluginsNitroPlugin() {
67
+ const plugins = await discoverPlugins(cwd);
68
+ const stems = plugins.map((plugin) => path.basename(plugin, path.extname(plugin)));
69
+ if (stems.length === 0)
70
+ return null;
71
+ const tmpDir = path.join(cwd, ".deploy-tmp");
72
+ fs.mkdirSync(tmpDir, { recursive: true });
73
+ const pluginPath = path.join(tmpDir, "agent-native-provided-plugins.mjs");
74
+ fs.writeFileSync(pluginPath, generateProvidedPluginsNitroPluginSource(stems));
75
+ return pluginPath;
76
+ }
52
77
  function addImmutableAssetRouteRule(routeRules, pathname) {
53
78
  const existing = routeRules[pathname] ?? {};
54
79
  routeRules[pathname] = {
@@ -77,7 +102,7 @@ export function addImmutableAssetRouteRulesForClientBuild(routeRules, clientDir,
77
102
  * `@agent-native/core/server`. This is the middle layer of the three-layer
78
103
  * inheritance model: app local > workspace core > framework default.
79
104
  */
80
- export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = [], actions = [], workspaceCore = null, immutableAssetPaths = []) {
105
+ export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = [], actions = [], workspaceCore = null, immutableAssetPaths = [], builtAppBasePath = normalizeConfiguredAppBasePath()) {
81
106
  const routeImports = [];
82
107
  const routeRegistrations = [];
83
108
  for (let i = 0; i < routes.length; i++) {
@@ -126,8 +151,10 @@ export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = []
126
151
  const edgePlugins = pluginPaths.filter((p) => !isNodeOnlyPlugin(p));
127
152
  const pluginImports = [];
128
153
  const pluginCalls = [];
154
+ const providedPluginStems = new Set();
129
155
  for (let i = 0; i < edgePlugins.length; i++) {
130
156
  const varName = `plugin_${i}`;
157
+ providedPluginStems.add(path.basename(edgePlugins[i], path.extname(edgePlugins[i])));
131
158
  pluginImports.push(`import ${varName} from ${JSON.stringify(edgePlugins[i])};`);
132
159
  pluginCalls.push(` if (typeof ${varName} === "function") {
133
160
  await ${varName}(nitroApp);
@@ -139,6 +166,7 @@ export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = []
139
166
  const edgeDefaultStems = defaultPluginStems.filter((stem) => !NODE_ONLY_PLUGINS.has(stem));
140
167
  for (let i = 0; i < edgeDefaultStems.length; i++) {
141
168
  const stem = edgeDefaultStems[i];
169
+ providedPluginStems.add(stem);
142
170
  const varName = `defaultPlugin_${i}`;
143
171
  const workspaceExportName = workspaceCore?.plugins?.[stem];
144
172
  if (workspaceCore && workspaceExportName) {
@@ -156,6 +184,17 @@ export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = []
156
184
  await ${varName}(nitroApp);
157
185
  }`);
158
186
  }
187
+ const generatedPluginMarks = providedPluginStems.size > 0
188
+ ? [
189
+ ...new Set([
190
+ ...Object.keys(DEFAULT_PLUGIN_REGISTRY),
191
+ ...providedPluginStems,
192
+ ]),
193
+ ]
194
+ : [];
195
+ if (generatedPluginMarks.length > 0) {
196
+ pluginImports.unshift(`import { markDefaultPluginProvided as markGeneratedPluginProvided } from "@agent-native/core/server";`);
197
+ }
159
198
  return `
160
199
  // Auto-generated worker entry point for ${preset}
161
200
  import { H3, defineEventHandler, readBody, toResponse } from "h3";
@@ -170,9 +209,11 @@ function normalizeAppBasePath(value) {
170
209
  }
171
210
 
172
211
  function getAppBasePath() {
212
+ const builtAppBasePath = ${JSON.stringify(builtAppBasePath)};
173
213
  return normalizeAppBasePath(
174
214
  globalThis.process?.env?.VITE_APP_BASE_PATH ||
175
- globalThis.process?.env?.APP_BASE_PATH,
215
+ globalThis.process?.env?.APP_BASE_PATH ||
216
+ builtAppBasePath,
176
217
  );
177
218
  }
178
219
 
@@ -283,6 +324,7 @@ function injectHeadScript(html, script) {
283
324
  }
284
325
 
285
326
  const DEFAULT_SSR_CACHE_CONTROL = ${JSON.stringify(DEFAULT_SSR_CACHE_CONTROL)};
327
+ const DEFAULT_SPECULATION_RULES_PATH = ${JSON.stringify(DEFAULT_SPECULATION_RULES_PATH)};
286
328
  const IMMUTABLE_ASSET_CACHE_CONTROL = ${JSON.stringify(IMMUTABLE_ASSET_CACHE_CONTROL)};
287
329
  const IMMUTABLE_ASSET_PATHS = new Set(${JSON.stringify([...new Set(immutableAssetPaths)].sort())});
288
330
  const AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE = ${JSON.stringify(AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE)};
@@ -312,50 +354,54 @@ function injectDefaultSocialImageMeta(html) {
312
354
  return html.slice(0, headCloseIdx) + tags.join("") + html.slice(headCloseIdx);
313
355
  }
314
356
 
315
- const ANONYMOUS_SESSION_COOKIE_NAMES = new Set(["an_docs_session"]);
316
- const AUTH_SESSION_COOKIE_RE = /^(?:an_session(?:_[^=;]+)?|an_embed_session|[^=;]+\\.session_(?:token|data))$/;
317
-
318
- function requestHasAuthenticatedCookie(cookieHeader) {
319
- if (!cookieHeader) return false;
320
- return cookieHeader
321
- .split(";")
322
- .map((cookie) => cookie.trim().split("=", 1)[0]?.trim())
323
- .filter(Boolean)
324
- .map((name) => name.replace(/^__(?:Secure|Host)-/, ""))
325
- .some((name) => !ANONYMOUS_SESSION_COOKIE_NAMES.has(name) && AUTH_SESSION_COOKIE_RE.test(name));
326
- }
327
-
328
- function requestHasAuthSignal(request) {
329
- const url = new URL(request.url);
330
- return Boolean(
331
- request.headers.get("authorization") ||
332
- requestHasAuthenticatedCookie(request.headers.get("cookie")) ||
333
- url.searchParams.has("__an_embed_token") ||
334
- url.searchParams.has("_session")
335
- );
336
- }
337
-
338
- function shouldUseDefaultSsrCacheHeader(headers, status, pathname, hasAuthSignal) {
357
+ function shouldUseDefaultSsrCacheHeader(headers, status, pathname) {
339
358
  if (status < 200 || status >= 400) return false;
340
359
 
341
360
  const contentType = (headers.get("content-type") || "").toLowerCase();
342
361
  if (contentType.includes("text/html")) {
343
- return !headers.has("cache-control");
362
+ // SSR HTML is public app shell in this framework; any per-user state is
363
+ // fetched after hydration. Always enforce the framework SWR default here;
364
+ // route-level no-cache/private headers on SSR HTML recreate the same
365
+ // origin stampede this cache policy is meant to prevent.
366
+ return true;
344
367
  }
345
368
 
346
369
  if (!pathname.endsWith(".data")) return false;
347
- if (hasAuthSignal) return false;
348
370
  if (!contentType.includes("text/x-script")) return false;
349
371
 
350
- const cacheControl = headers.get("cache-control");
351
- return !cacheControl || cacheControl.trim().toLowerCase() === "no-cache";
372
+ // React Router marks loader .data responses no-cache by default. Agent-Native
373
+ // SSR is public app shell, even for browsers carrying auth-looking cookies;
374
+ // user/org-specific data is loaded client-side through actions/API routes
375
+ // after hydration. Keep .data on the same SWR policy as HTML so normal route
376
+ // data fetches fill CDN cache instead of bypassing it and slamming origin.
377
+ // Do not re-add a blanket auth-signal bypass here; logged-in browsers still
378
+ // need CDN-cached public route data. Worker SSR does not populate
379
+ // getRequestUserEmail()/accessFilter() context for private loaders, and
380
+ // Node/H3 has an auth-context access guard for older loaders that still do.
381
+ // Also do not preserve route-level private/no-store for React Router .data:
382
+ // if a route needs per-user data, it belongs behind a client-side action/API
383
+ // call rather than in the shared SSR payload.
384
+ return true;
352
385
  }
353
386
 
354
- function applyDefaultSsrCacheHeader(headers, status, pathname, hasAuthSignal) {
355
- if (!shouldUseDefaultSsrCacheHeader(headers, status, pathname, hasAuthSignal)) return;
387
+ function applyDefaultSsrCacheHeader(headers, status, pathname) {
388
+ if (!shouldUseDefaultSsrCacheHeader(headers, status, pathname)) return;
356
389
  headers.set("cache-control", DEFAULT_SSR_CACHE_CONTROL);
357
390
  }
358
391
 
392
+ function applyDefaultSpeculationRulesHeader(headers, status, basePath) {
393
+ if (status < 200 || status >= 400) return;
394
+ if (headers.has("speculation-rules")) return;
395
+
396
+ const contentType = (headers.get("content-type") || "").toLowerCase();
397
+ if (!contentType.includes("text/html")) return;
398
+
399
+ // Cloudflare Speed Brain injects Speculation-Rules when origin omits this
400
+ // header. Those browser prefetches carry Sec-Purpose: prefetch and
401
+ // Cloudflare can return 503 before the request reaches origin. Publish an
402
+ // explicit no-op ruleset by default; apps can still provide their own header.
403
+ headers.set("speculation-rules", '"' + prefixMountedPath(DEFAULT_SPECULATION_RULES_PATH, basePath) + '"');
404
+ }
359
405
  function isImmutableAssetRequest(request) {
360
406
  const pathname = stripAppBasePath(new URL(request.url).pathname);
361
407
  return IMMUTABLE_ASSET_PATHS.has(pathname);
@@ -376,10 +422,11 @@ function applyImmutableAssetCacheHeaders(response, request) {
376
422
  });
377
423
  }
378
424
 
379
- async function rewriteMountedResponse(response, basePath, pathname, hasAuthSignal) {
425
+ async function rewriteMountedResponse(response, basePath, pathname) {
380
426
  const sentryClientConfigScript = getSentryClientConfigScript();
381
427
  const headers = new Headers(response.headers);
382
- applyDefaultSsrCacheHeader(headers, response.status, pathname, hasAuthSignal);
428
+ applyDefaultSsrCacheHeader(headers, response.status, pathname);
429
+ applyDefaultSpeculationRulesHeader(headers, response.status, basePath);
383
430
 
384
431
  const location = headers.get("location");
385
432
  if (location?.startsWith("/") && !location.startsWith("//")) {
@@ -466,6 +513,12 @@ async function getHandler() {
466
513
 
467
514
  // Run plugins — they call getH3App(nitroApp).use(path, handler) which
468
515
  // pushes path-prefix middleware onto app["~middleware"].
516
+ // Pre-mark every build-time plugin slot before any plugin awaits the runtime
517
+ // default bootstrap. Bundled serverless workers often lack server/plugins/
518
+ // on disk, so runtime discovery would otherwise auto-mount duplicate
519
+ // framework defaults before later custom plugins get a chance to mark
520
+ // themselves as provided.
521
+ ${generatedPluginMarks.map((stem) => ` markGeneratedPluginProvided(nitroApp, ${JSON.stringify(stem)});`).join("\n")}
469
522
  ${pluginCalls.join("\n")}
470
523
 
471
524
  // Register API routes
@@ -490,7 +543,6 @@ ${actionRegistrations.join("\n")}
490
543
  return new Response(null, { status: 404 });
491
544
  }
492
545
  const request = requestWithPathname(event.req, p);
493
- const hasAuthSignal = requestHasAuthSignal(event.req);
494
546
  if (event.req.method === "HEAD") {
495
547
  const getRequest = requestWithMethod(request, "GET");
496
548
  const response = await rrHandler(getRequest);
@@ -501,11 +553,10 @@ ${actionRegistrations.join("\n")}
501
553
  headers: response.headers,
502
554
  }),
503
555
  basePath,
504
- p,
505
- hasAuthSignal,
556
+ p
506
557
  );
507
558
  }
508
- return rewriteMountedResponse(await rrHandler(request), basePath, p, hasAuthSignal);
559
+ return rewriteMountedResponse(await rrHandler(request), basePath, p);
509
560
  }));
510
561
 
511
562
  _handler = app.fetch.bind(app);
@@ -1137,15 +1188,22 @@ function createDanglingOptionalDepStubs() {
1137
1188
  */
1138
1189
  export async function runNitroBuildPipeline(opts) {
1139
1190
  const { nitro, hooks, clientDir, publicOutputDir, appBasePath, cwd } = opts;
1191
+ const hasClientBuild = fs.existsSync(clientDir) && Boolean(publicOutputDir);
1192
+ if (hasClientBuild) {
1193
+ // Install hashed-asset route rules before Nitro prepares platform output.
1194
+ // Some presets materialize headers during prepare/copy phases, not only in
1195
+ // nitroBuild; adding these later leaves Netlify/Vercel static assets without
1196
+ // the one-year immutable CDN policy even though the runtime manifest works.
1197
+ nitro.options.routeRules ??= {};
1198
+ addImmutableAssetRouteRulesForClientBuild(nitro.options.routeRules, clientDir, appBasePath);
1199
+ }
1140
1200
  await hooks.prepare(nitro);
1141
1201
  await hooks.copyPublicAssets(nitro);
1142
- if (fs.existsSync(clientDir) && publicOutputDir) {
1202
+ if (hasClientBuild && publicOutputDir) {
1143
1203
  copyDir(clientDir, publicOutputDir);
1144
1204
  if (appBasePath) {
1145
1205
  copyDir(clientDir, path.join(publicOutputDir, appBasePath.slice(1)));
1146
1206
  }
1147
- nitro.options.routeRules ??= {};
1148
- addImmutableAssetRouteRulesForClientBuild(nitro.options.routeRules, clientDir, appBasePath);
1149
1207
  console.log(`[deploy] Copied client assets to ${path.relative(cwd, publicOutputDir)}`);
1150
1208
  }
1151
1209
  await hooks.nitroBuild(nitro);
@@ -1212,6 +1270,7 @@ export default bundle;
1212
1270
  pathAliases["@"] = appDir;
1213
1271
  if (fs.existsSync(sharedDir))
1214
1272
  pathAliases["@shared"] = sharedDir;
1273
+ const providedPluginsNitroPlugin = await writeProvidedPluginsNitroPlugin();
1215
1274
  const nitro = await createNitro({
1216
1275
  rootDir: cwd,
1217
1276
  dev: false,
@@ -1228,6 +1287,9 @@ export default bundle;
1228
1287
  virtual: {
1229
1288
  "virtual:agents-bundle": agentsBundleModuleSource,
1230
1289
  },
1290
+ ...(providedPluginsNitroPlugin
1291
+ ? { plugins: [providedPluginsNitroPlugin] }
1292
+ : {}),
1231
1293
  routeRules: mcpEmbedStaticAssetRouteRules(appBasePath),
1232
1294
  // For edge presets (cloudflare, deno), bundle all deps — node_modules
1233
1295
  // aren't available at runtime. Netlify/Vercel/Node have node_modules.