@agent-native/core 0.114.0 → 0.114.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 (162) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +9 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/build-compatibility.ts +77 -0
  5. package/corpus/core/src/client/route-chunk-recovery/index.ts +1 -0
  6. package/corpus/core/src/client/use-action.ts +32 -1
  7. package/corpus/core/src/deploy/build.ts +4 -1
  8. package/corpus/core/src/org/context.ts +56 -30
  9. package/corpus/core/src/server/action-routes.ts +52 -2
  10. package/corpus/core/src/server/auth-plugin.ts +9 -4
  11. package/corpus/core/src/server/better-auth-instance.ts +5 -1
  12. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  13. package/corpus/core/src/vite/client.ts +17 -0
  14. package/corpus/templates/content/AGENTS.md +2 -0
  15. package/corpus/templates/content/actions/_content-database-personal-view.ts +49 -5
  16. package/corpus/templates/content/actions/_content-document-access.ts +21 -0
  17. package/corpus/templates/content/actions/_content-favorites.ts +91 -0
  18. package/corpus/templates/content/actions/_content-files.ts +79 -23
  19. package/corpus/templates/content/actions/_content-sidebar-state.ts +24 -0
  20. package/corpus/templates/content/actions/_content-space-access.ts +15 -17
  21. package/corpus/templates/content/actions/_content-space-catalog-guards.ts +20 -0
  22. package/corpus/templates/content/actions/_content-spaces.ts +364 -167
  23. package/corpus/templates/content/actions/_database-source-utils.ts +8 -0
  24. package/corpus/templates/content/actions/_database-utils.ts +249 -15
  25. package/corpus/templates/content/actions/_delete-content-space.ts +84 -0
  26. package/corpus/templates/content/actions/_files-system-properties.ts +499 -0
  27. package/corpus/templates/content/actions/_local-file-documents.ts +0 -1
  28. package/corpus/templates/content/actions/_property-utils.ts +18 -9
  29. package/corpus/templates/content/actions/bind-content-database-source-field.ts +3 -0
  30. package/corpus/templates/content/actions/configure-document-property.ts +3 -0
  31. package/corpus/templates/content/actions/create-content-database.ts +2 -2
  32. package/corpus/templates/content/actions/create-content-space.ts +34 -0
  33. package/corpus/templates/content/actions/create-document.ts +4 -1
  34. package/corpus/templates/content/actions/delete-content-space.ts +19 -0
  35. package/corpus/templates/content/actions/delete-database-items.ts +28 -1
  36. package/corpus/templates/content/actions/delete-document-property.ts +3 -0
  37. package/corpus/templates/content/actions/delete-document.ts +39 -2
  38. package/corpus/templates/content/actions/duplicate-database-item.ts +6 -0
  39. package/corpus/templates/content/actions/duplicate-database-items.ts +6 -0
  40. package/corpus/templates/content/actions/duplicate-document-property.ts +3 -0
  41. package/corpus/templates/content/actions/get-content-database.ts +11 -2
  42. package/corpus/templates/content/actions/get-content-sidebar-state.ts +23 -0
  43. package/corpus/templates/content/actions/get-document.ts +32 -7
  44. package/corpus/templates/content/actions/import-content-source.ts +33 -1
  45. package/corpus/templates/content/actions/list-content-spaces.ts +34 -2
  46. package/corpus/templates/content/actions/list-documents.ts +39 -7
  47. package/corpus/templates/content/actions/reorder-document-property.ts +9 -0
  48. package/corpus/templates/content/actions/set-document-property.ts +41 -23
  49. package/corpus/templates/content/actions/share-local-file-document.ts +15 -0
  50. package/corpus/templates/content/actions/submit-content-database-form.ts +7 -0
  51. package/corpus/templates/content/actions/sync-local-folder-source.ts +14 -0
  52. package/corpus/templates/content/actions/update-content-database-view.ts +1 -0
  53. package/corpus/templates/content/actions/update-content-sidebar-state.ts +27 -0
  54. package/corpus/templates/content/actions/update-document.ts +158 -66
  55. package/corpus/templates/content/app/components/EmptyState.tsx +4 -57
  56. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +8 -1
  57. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +519 -178
  58. package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +39 -0
  59. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
  60. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +266 -7
  61. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +430 -152
  62. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +467 -66
  63. package/corpus/templates/content/app/components/editor/database/view-config.ts +9 -3
  64. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +720 -196
  65. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +173 -1
  66. package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +1 -1
  67. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +91 -16
  68. package/corpus/templates/content/app/entry.client.tsx +5 -1
  69. package/corpus/templates/content/app/hooks/use-content-database.ts +84 -3
  70. package/corpus/templates/content/app/hooks/use-content-spaces.ts +60 -2
  71. package/corpus/templates/content/app/hooks/use-create-page.ts +30 -0
  72. package/corpus/templates/content/app/hooks/use-documents.ts +270 -19
  73. package/corpus/templates/content/app/i18n-data.ts +243 -20
  74. package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +54 -0
  75. package/corpus/templates/content/app/lib/local-folder-picker-support.ts +18 -0
  76. package/corpus/templates/content/app/routes/_app.favorites.tsx +30 -0
  77. package/corpus/templates/content/app/routes/_app.local-files.tsx +18 -6
  78. package/corpus/templates/content/changelog/2026-07-17-workspace-files-now-stay-in-a-loading-state-until-automatic-.md +6 -0
  79. package/corpus/templates/content/changelog/2026-07-17-workspace-switching-now-opens-the-selected-files-database-pr.md +6 -0
  80. package/corpus/templates/content/changelog/2026-07-17-workspaces-now-expand-above-their-own-files-list-without-dup.md +6 -0
  81. package/corpus/templates/content/changelog/2026-07-18-create-named-workspaces-each-with-its-own-private-files-data.md +6 -0
  82. package/corpus/templates/content/changelog/2026-07-18-favorites-align-with-workspace-and-file-rows-in-the-sidebar.md +6 -0
  83. package/corpus/templates/content/changelog/2026-07-18-local-folder-selection-no-longer-invokes-the-unsafe-native-p.md +6 -0
  84. package/corpus/templates/content/changelog/2026-07-18-made-content-workspaces-independently-expandable-restored-or.md +6 -0
  85. package/corpus/templates/content/changelog/2026-07-18-page-details-now-live-in-a-shared-info-and-comments-rail-dat.md +6 -0
  86. package/corpus/templates/content/changelog/2026-07-18-workspace-database-views-now-control-sidebar-workspace-navig.md +6 -0
  87. package/corpus/templates/content/changelog/2026-07-18-workspace-navigation-now-aligns-favorites-with-workspace-pag.md +6 -0
  88. package/corpus/templates/content/changelog/2026-07-19-favorites-now-open-as-a-table-and-compact-breadcrumbs-make-w.md +6 -0
  89. package/corpus/templates/content/changelog/2026-07-19-files-databases-now-start-unfiltered-sidebar-rows-exactly-fo.md +6 -0
  90. package/corpus/templates/content/changelog/2026-07-19-sidebar-page-trees-now-open-only-when-expanded-remember-thei.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-19-workspace-file-databases-now-use-the-workspace-name-open-as-.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-19-workspace-navigation-now-stays-stable-on-hover-uses-folder-i.md +6 -0
  93. package/corpus/templates/content/changelog/2026-07-19-workspace-rows-now-use-folder-icons-and-deleting-a-user-crea.md +6 -0
  94. package/corpus/templates/content/changelog/2026-07-19-workspaces-can-be-created-without-leaving-the-workspaces-dat.md +6 -0
  95. package/corpus/templates/content/e2e/global-setup.ts +3 -2
  96. package/corpus/templates/content/parity/matrix.md +28 -28
  97. package/corpus/templates/content/parity/matrix.ts +8 -2
  98. package/corpus/templates/content/server/db/index.ts +1 -0
  99. package/corpus/templates/content/server/db/schema.ts +10 -0
  100. package/corpus/templates/content/server/lib/documents.ts +12 -7
  101. package/corpus/templates/content/server/plugins/db.ts +17 -0
  102. package/corpus/templates/content/shared/api.ts +9 -0
  103. package/corpus/templates/content/shared/database-form.ts +11 -2
  104. package/corpus/templates/content/vite.config.ts +1 -0
  105. package/dist/client/build-compatibility.d.ts +11 -0
  106. package/dist/client/build-compatibility.d.ts.map +1 -0
  107. package/dist/client/build-compatibility.js +56 -0
  108. package/dist/client/build-compatibility.js.map +1 -0
  109. package/dist/client/route-chunk-recovery/index.d.ts +1 -0
  110. package/dist/client/route-chunk-recovery/index.d.ts.map +1 -1
  111. package/dist/client/route-chunk-recovery/index.js +1 -0
  112. package/dist/client/route-chunk-recovery/index.js.map +1 -1
  113. package/dist/client/use-action.d.ts.map +1 -1
  114. package/dist/client/use-action.js +19 -1
  115. package/dist/client/use-action.js.map +1 -1
  116. package/dist/collab/routes.d.ts +1 -1
  117. package/dist/collab/struct-routes.d.ts +1 -1
  118. package/dist/deploy/build.js +2 -1
  119. package/dist/deploy/build.js.map +1 -1
  120. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  121. package/dist/notifications/routes.d.ts +1 -1
  122. package/dist/observability/routes.d.ts +5 -5
  123. package/dist/org/context.d.ts +4 -2
  124. package/dist/org/context.d.ts.map +1 -1
  125. package/dist/org/context.js +44 -22
  126. package/dist/org/context.js.map +1 -1
  127. package/dist/progress/routes.d.ts +1 -1
  128. package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
  129. package/dist/provider-api/actions/provider-api.d.ts +12 -12
  130. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  131. package/dist/resources/handlers.d.ts +1 -1
  132. package/dist/secrets/routes.d.ts +9 -9
  133. package/dist/server/action-routes.d.ts.map +1 -1
  134. package/dist/server/action-routes.js +32 -2
  135. package/dist/server/action-routes.js.map +1 -1
  136. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  137. package/dist/server/auth-plugin.d.ts.map +1 -1
  138. package/dist/server/auth-plugin.js +9 -5
  139. package/dist/server/auth-plugin.js.map +1 -1
  140. package/dist/server/better-auth-instance.d.ts.map +1 -1
  141. package/dist/server/better-auth-instance.js +4 -1
  142. package/dist/server/better-auth-instance.js.map +1 -1
  143. package/dist/server/transcribe-voice.d.ts +1 -1
  144. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  145. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  146. package/dist/shared/mcp-embed-headers.js +1 -1
  147. package/dist/shared/mcp-embed-headers.js.map +1 -1
  148. package/dist/vite/client.d.ts +6 -0
  149. package/dist/vite/client.d.ts.map +1 -1
  150. package/dist/vite/client.js +8 -0
  151. package/dist/vite/client.js.map +1 -1
  152. package/package.json +1 -1
  153. package/src/client/build-compatibility.ts +77 -0
  154. package/src/client/route-chunk-recovery/index.ts +1 -0
  155. package/src/client/use-action.ts +32 -1
  156. package/src/deploy/build.ts +4 -1
  157. package/src/org/context.ts +56 -30
  158. package/src/server/action-routes.ts +52 -2
  159. package/src/server/auth-plugin.ts +9 -4
  160. package/src/server/better-auth-instance.ts +5 -1
  161. package/src/shared/mcp-embed-headers.ts +1 -1
  162. package/src/vite/client.ts +17 -0
package/corpus/README.md CHANGED
@@ -28,6 +28,6 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
 
29
29
  ## Generated Counts
30
30
 
31
- - core files: 1513
31
+ - core files: 1514
32
32
  - toolkit files: 137
33
- - template files: 6055
33
+ - template files: 6086
@@ -1,5 +1,14 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.114.1
4
+
5
+ ### Patch Changes
6
+
7
+ - c913280: Keep action mutations pending until asynchronous success callbacks finish.
8
+ - c913280: Allow email-password signup without email verification on Netlify deploy previews while preserving explicit verification overrides, and hold auth requests until their serverless routes finish mounting.
9
+ - c913280: Fence incompatible cached browser clients from newer action backends and reload version-aware tabs with a cache-busted build.
10
+ - c913280: Honor an explicit Personal context for users who also belong to organizations.
11
+
3
12
  ## 0.114.0
4
13
 
5
14
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.114.0",
3
+ "version": "0.114.1",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -0,0 +1,77 @@
1
+ declare const __AGENT_NATIVE_BUILD_ID__: string | undefined;
2
+ declare const __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__: string | undefined;
3
+
4
+ const RELOAD_MARKER_KEY = "__agentNativeClientCompatibilityReload";
5
+ export const BUILD_CACHE_BUSTER_PARAM = "__an_build";
6
+
7
+ type CompatibilityWindow = Pick<Window, "location" | "history"> & {
8
+ sessionStorage?: Storage;
9
+ __agentNativeClientCompatibilityReload?: string;
10
+ };
11
+
12
+ export function clientBuildId(): string {
13
+ if (typeof __AGENT_NATIVE_BUILD_ID__ === "string") {
14
+ return __AGENT_NATIVE_BUILD_ID__;
15
+ }
16
+ return (
17
+ (globalThis as typeof globalThis & { __AGENT_NATIVE_BUILD_ID__?: string })
18
+ .__AGENT_NATIVE_BUILD_ID__ ?? ""
19
+ );
20
+ }
21
+
22
+ export function clientCompatibilityVersion(): string {
23
+ if (typeof __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__ === "string") {
24
+ return __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__;
25
+ }
26
+ return (
27
+ (
28
+ globalThis as typeof globalThis & {
29
+ __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__?: string;
30
+ }
31
+ ).__AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__ ?? ""
32
+ );
33
+ }
34
+
35
+ function readReloadMarker(win: CompatibilityWindow): string {
36
+ try {
37
+ return win.sessionStorage?.getItem(RELOAD_MARKER_KEY) ?? "";
38
+ } catch {
39
+ return win.__agentNativeClientCompatibilityReload ?? "";
40
+ }
41
+ }
42
+
43
+ function writeReloadMarker(win: CompatibilityWindow, marker: string): void {
44
+ win.__agentNativeClientCompatibilityReload = marker;
45
+ try {
46
+ win.sessionStorage?.setItem(RELOAD_MARKER_KEY, marker);
47
+ } catch {}
48
+ }
49
+
50
+ export function reloadForClientCompatibilityMismatch(
51
+ serverBuildId: string,
52
+ requiredCompatibility: string,
53
+ win: CompatibilityWindow | undefined = typeof window === "undefined"
54
+ ? undefined
55
+ : window,
56
+ ): boolean {
57
+ if (!win?.location) return false;
58
+ const marker = `${requiredCompatibility}:${serverBuildId}`;
59
+ if (readReloadMarker(win) === marker) return false;
60
+ writeReloadMarker(win, marker);
61
+ const target = new URL(win.location.href);
62
+ target.searchParams.set(BUILD_CACHE_BUSTER_PARAM, serverBuildId || "latest");
63
+ win.location.replace(target.href);
64
+ return true;
65
+ }
66
+
67
+ export function stripBuildCompatibilityCacheBuster(
68
+ win: CompatibilityWindow | undefined = typeof window === "undefined"
69
+ ? undefined
70
+ : window,
71
+ ): void {
72
+ if (!win?.location || !win.history) return;
73
+ const target = new URL(win.location.href);
74
+ if (!target.searchParams.has(BUILD_CACHE_BUSTER_PARAM)) return;
75
+ target.searchParams.delete(BUILD_CACHE_BUSTER_PARAM);
76
+ win.history.replaceState(win.history.state, "", target.href);
77
+ }
@@ -1 +1,2 @@
1
1
  export * from "../route-chunk-recovery.js";
2
+ export * from "../build-compatibility.js";
@@ -30,6 +30,11 @@ import type {
30
30
  import { trackEvent } from "./analytics.js";
31
31
  import { agentNativePath } from "./api-path.js";
32
32
  import { getBrowserTabId } from "./browser-tab-id.js";
33
+ import {
34
+ clientBuildId,
35
+ clientCompatibilityVersion,
36
+ reloadForClientCompatibilityMismatch,
37
+ } from "./build-compatibility.js";
33
38
  import { ensureEmbedAuthFetchInterceptor } from "./embed-auth.js";
34
39
 
35
40
  const ACTION_PREFIX = agentNativePath("/_agent-native/actions");
@@ -281,6 +286,12 @@ async function performActionFetch<T>(
281
286
  }
282
287
  : {}),
283
288
  };
289
+ const compatibilityVersion = clientCompatibilityVersion();
290
+ if (compatibilityVersion) {
291
+ headers["X-Agent-Native-Client-Compatibility"] = compatibilityVersion;
292
+ }
293
+ const buildId = clientBuildId();
294
+ if (buildId) headers["X-Agent-Native-Build-Id"] = buildId;
284
295
  const tz = resolveUserTimezone();
285
296
  if (tz) headers["x-user-timezone"] = tz;
286
297
  const init: RequestInit = {
@@ -349,6 +360,26 @@ async function performActionFetch<T>(
349
360
  throw new Error(`Action ${name} failed: ${cause}`);
350
361
  }
351
362
 
363
+ if (
364
+ res.status === 409 &&
365
+ res.headers.get("X-Agent-Native-Client-Mismatch") === "1"
366
+ ) {
367
+ const serverBuildId =
368
+ res.headers.get("X-Agent-Native-Build-Id") ?? "latest";
369
+ const requiredCompatibility =
370
+ res.headers.get("X-Agent-Native-Client-Compatibility") ?? "unknown";
371
+ reloadForClientCompatibilityMismatch(
372
+ serverBuildId,
373
+ requiredCompatibility,
374
+ );
375
+ const error = new Error(
376
+ `Action ${name} requires a refreshed browser client`,
377
+ );
378
+ (error as any).status = 409;
379
+ (error as any).code = "client_build_mismatch";
380
+ throw error;
381
+ }
382
+
352
383
  // 204 No Content — nothing to parse.
353
384
  if (res.status === 204) return null as T;
354
385
 
@@ -760,7 +791,7 @@ export function useActionMutation<
760
791
  if (!skipActionQueryInvalidation) {
761
792
  queryClient.invalidateQueries({ queryKey: ["action"] });
762
793
  }
763
- (onSuccess as Function)?.(...args);
794
+ return (onSuccess as Function)?.(...args);
764
795
  },
765
796
  });
766
797
  }
@@ -1240,7 +1240,7 @@ async function getHandler() {
1240
1240
  headers: {
1241
1241
  "Access-Control-Allow-Origin": "*",
1242
1242
  "Access-Control-Allow-Methods": "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS",
1243
- "Access-Control-Allow-Headers": "Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-User-Timezone,X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend,X-Agent-Native-Embed-Target",
1243
+ "Access-Control-Allow-Headers": "Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-User-Timezone,X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,X-Agent-Native-Embed-Target",
1244
1244
  },
1245
1245
  });
1246
1246
  }
@@ -3479,6 +3479,9 @@ export default bundle;
3479
3479
  "process.env.AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID": JSON.stringify(
3480
3480
  process.env.GA_MEASUREMENT_ID?.trim() || "",
3481
3481
  ),
3482
+ "process.env.AGENT_NATIVE_BUILD_DEPLOY_CONTEXT": JSON.stringify(
3483
+ process.env.CONTEXT?.trim() || "",
3484
+ ),
3482
3485
  },
3483
3486
  // Replace browser-only renderers (Excalidraw/Mermaid) with an inert proxy in
3484
3487
  // the server bundle. Without this, Nitro's Rolldown build pulls the real
@@ -41,7 +41,8 @@ const nanoid = (): string =>
41
41
  /**
42
42
  * Resolve the current user's organization context from their session.
43
43
  *
44
- * - For users in multiple orgs, honors their `active-org-id` user setting.
44
+ * - Honors the user's `active-org-id` setting, including an explicit Personal
45
+ * context represented by `{ orgId: null }`.
45
46
  * - Falls back to the user's first membership.
46
47
  * - When the authenticated user has zero memberships, provisions a default org
47
48
  * named after the user ({name}'s workspace, falling back to the email
@@ -63,6 +64,34 @@ export async function getOrgContext(event: H3Event): Promise<OrgContext> {
63
64
  type MembershipRow = { orgId: string; role: OrgRole; orgName: string };
64
65
 
65
66
  const MEMBERSHIPS_CACHE_KEY = "__anOrgMembershipsCache";
67
+ const ACTIVE_ORG_SETTING_CACHE_KEY = "__anActiveOrgSettingCache";
68
+
69
+ type ActiveOrgSetting = { orgId: string | null } | null;
70
+
71
+ function loadActiveOrgSettingForEvent(
72
+ event: H3Event,
73
+ email: string,
74
+ ): Promise<ActiveOrgSetting> {
75
+ const ctx = event.context as Record<string, unknown>;
76
+ const cache = ((ctx[ACTIVE_ORG_SETTING_CACHE_KEY] as
77
+ | Map<string, Promise<ActiveOrgSetting>>
78
+ | undefined) ??
79
+ (ctx[ACTIVE_ORG_SETTING_CACHE_KEY] = new Map<
80
+ string,
81
+ Promise<ActiveOrgSetting>
82
+ >())) as Map<string, Promise<ActiveOrgSetting>>;
83
+ const normalizedEmail = email.toLowerCase();
84
+ let promise = cache.get(normalizedEmail);
85
+ if (!promise) {
86
+ promise = getUserSetting(email, "active-org-id").then((value) => {
87
+ if (!value || !("orgId" in value)) return null;
88
+ if (value.orgId === null) return { orgId: null };
89
+ return typeof value.orgId === "string" ? { orgId: value.orgId } : null;
90
+ });
91
+ cache.set(normalizedEmail, promise);
92
+ }
93
+ return promise;
94
+ }
66
95
 
67
96
  /**
68
97
  * Per-request memoization of the org_members lookup, keyed by email on
@@ -128,32 +157,29 @@ async function resolveOrgContextUncached(event: H3Event): Promise<OrgContext> {
128
157
  return { email, orgId: null, orgName: null, role: null };
129
158
  }
130
159
 
131
- if (memberships.length > 1) {
132
- const activeOrgSetting = (await getUserSetting(email, "active-org-id")) as {
133
- orgId: string;
134
- } | null;
135
- if (activeOrgSetting?.orgId) {
136
- const active = memberships.find(
137
- (m) => m.orgId === activeOrgSetting.orgId,
138
- );
139
- if (active) {
140
- return {
141
- email,
142
- orgId: active.orgId,
143
- orgName: active.orgName,
144
- role: active.role,
145
- };
146
- }
160
+ const shouldTryDomainAutoJoin =
161
+ memberships.length === 0 ||
162
+ (memberships.length === 1 &&
163
+ isLikelyPersonalWorkspace(memberships[0], email, session));
164
+ const activeOrgSetting = await loadActiveOrgSettingForEvent(event, email);
165
+ if (activeOrgSetting?.orgId === null) {
166
+ return { email, orgId: null, orgName: null, role: null };
167
+ }
168
+ if (activeOrgSetting?.orgId) {
169
+ const active = memberships.find((m) => m.orgId === activeOrgSetting.orgId);
170
+ if (active && !shouldTryDomainAutoJoin) {
171
+ return {
172
+ email,
173
+ orgId: active.orgId,
174
+ orgName: active.orgName,
175
+ role: active.role,
176
+ };
147
177
  }
148
178
  }
149
179
 
150
180
  const sessionMembership = sessionOrgId
151
181
  ? memberships.find((m) => m.orgId === sessionOrgId)
152
182
  : null;
153
- const shouldTryDomainAutoJoin =
154
- memberships.length === 0 ||
155
- (memberships.length === 1 &&
156
- isLikelyPersonalWorkspace(memberships[0], email, session));
157
183
 
158
184
  if (shouldTryDomainAutoJoin) {
159
185
  const joined = await autoJoinDomainMatchingOrgs(email, {
@@ -248,7 +274,8 @@ async function loadMemberships(
248
274
  /**
249
275
  * Resolve the active org ID for a given email — for non-HTTP contexts like
250
276
  * the integration webhook handler where we have an email but no event/session.
251
- * Picks the user's active-org-id setting if set, otherwise the first membership.
277
+ * Picks the user's active-org-id setting if set, including explicit Personal,
278
+ * otherwise the first membership.
252
279
  * Returns null if the user has no memberships.
253
280
  */
254
281
  export async function resolveOrgIdForEmail(
@@ -263,10 +290,11 @@ export async function resolveOrgIdForEmail(
263
290
  });
264
291
  if (rows.length === 0) return null;
265
292
  const ids = rows.map((r: any) => String(r.org_id));
266
- if (ids.length === 1) return ids[0];
267
- const activeOrgSetting = (await getUserSetting(email, "active-org-id")) as {
268
- orgId: string;
269
- } | null;
293
+ const activeOrgSetting = (await getUserSetting(
294
+ email,
295
+ "active-org-id",
296
+ )) as ActiveOrgSetting;
297
+ if (activeOrgSetting?.orgId === null) return null;
270
298
  if (activeOrgSetting?.orgId && ids.includes(activeOrgSetting.orgId)) {
271
299
  return activeOrgSetting.orgId;
272
300
  }
@@ -291,10 +319,8 @@ export async function resolveOrgIdForEmailViaEvent(
291
319
  if (!exec) return null;
292
320
  const memberships = await loadMembershipsForEvent(event, exec, email);
293
321
  if (!memberships || memberships.length === 0) return null;
294
- if (memberships.length === 1) return memberships[0].orgId;
295
- const activeOrgSetting = (await getUserSetting(email, "active-org-id")) as {
296
- orgId: string;
297
- } | null;
322
+ const activeOrgSetting = await loadActiveOrgSettingForEvent(event, email);
323
+ if (activeOrgSetting?.orgId === null) return null;
298
324
  if (
299
325
  activeOrgSetting?.orgId &&
300
326
  memberships.some((m) => m.orgId === activeOrgSetting.orgId)
@@ -30,6 +30,26 @@ import {
30
30
  getAllowedCorsOrigin as resolveAllowedCorsOrigin,
31
31
  readCorsAllowedOrigins,
32
32
  } from "./cors-origins.js";
33
+
34
+ declare const __AGENT_NATIVE_BUILD_ID__: string | undefined;
35
+ declare const __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__: string | undefined;
36
+
37
+ function requiredClientCompatibilityVersion(): string {
38
+ const configured =
39
+ typeof __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__ === "string"
40
+ ? __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__
41
+ : process.env.AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION;
42
+ return configured?.trim() ?? "";
43
+ }
44
+
45
+ function currentBuildId(): string {
46
+ const configured =
47
+ typeof __AGENT_NATIVE_BUILD_ID__ === "string"
48
+ ? __AGENT_NATIVE_BUILD_ID__
49
+ : process.env.AGENT_NATIVE_BUILD_ID;
50
+ return configured?.trim() || "unknown";
51
+ }
52
+
33
53
  /**
34
54
  * Auto-mount actions as HTTP endpoints under /_agent-native/actions/:name.
35
55
  *
@@ -187,8 +207,8 @@ function handleOptionsRequest(event: any): string {
187
207
  event,
188
208
  "Access-Control-Allow-Headers",
189
209
  cors.credentials
190
- ? `Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-User-Timezone,X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend,${EMBED_TARGET_HEADER}`
191
- : `${MCP_EMBED_CORS_ALLOW_HEADERS},X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend`,
210
+ ? `Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-User-Timezone,X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,${EMBED_TARGET_HEADER}`
211
+ : `${MCP_EMBED_CORS_ALLOW_HEADERS},X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id`,
192
212
  );
193
213
  }
194
214
 
@@ -320,6 +340,11 @@ export function mountActionRoutes(
320
340
  }
321
341
 
322
342
  setResponseHeader(event, "Cache-Control", "no-store");
343
+ setResponseHeader(
344
+ event,
345
+ "Access-Control-Expose-Headers",
346
+ "X-Agent-Native-Client-Mismatch,X-Agent-Native-Build-Id,X-Agent-Native-Client-Compatibility",
347
+ );
323
348
 
324
349
  // Allow the declared method
325
350
  if (effectiveMethod !== method) {
@@ -327,6 +352,31 @@ export function mountActionRoutes(
327
352
  return { error: `Method not allowed. Use ${method}.` };
328
353
  }
329
354
 
355
+ const requiredCompatibility = requiredClientCompatibilityVersion();
356
+ if (isFrontendActionRequest(event) && requiredCompatibility) {
357
+ const receivedCompatibility = getHeader(
358
+ event,
359
+ "x-agent-native-client-compatibility",
360
+ );
361
+ if (receivedCompatibility !== requiredCompatibility) {
362
+ const serverBuildId = currentBuildId();
363
+ setResponseStatus(event, 409);
364
+ setResponseHeader(event, "X-Agent-Native-Client-Mismatch", "1");
365
+ setResponseHeader(event, "X-Agent-Native-Build-Id", serverBuildId);
366
+ setResponseHeader(
367
+ event,
368
+ "X-Agent-Native-Client-Compatibility",
369
+ requiredCompatibility,
370
+ );
371
+ return {
372
+ error: "This browser tab must reload before it can use this app.",
373
+ code: "client_build_mismatch",
374
+ serverBuildId,
375
+ requiredCompatibility,
376
+ };
377
+ }
378
+ }
379
+
330
380
  // (audit H5) Per-action `toolCallable` opt-out for the tools-iframe
331
381
  // bridge. The bridge tags every outbound action call with
332
382
  // X-Agent-Native-Tool-Bridge: 1. When that header is present and the
@@ -4,16 +4,21 @@ import {
4
4
  getH3App,
5
5
  awaitBootstrap,
6
6
  markDefaultPluginProvided,
7
+ trackPluginInit,
7
8
  } from "./framework-request-handler.js";
8
9
 
9
10
  type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
10
11
 
11
12
  export function createAuthPlugin(options?: AuthOptions): NitroPluginDef {
12
- return async (nitroApp: any) => {
13
+ return (nitroApp: any) => {
13
14
  markDefaultPluginProvided(nitroApp, "auth");
14
- // Wait for any other default plugins to finish mounting first.
15
- await awaitBootstrap(nitroApp);
16
- await autoMountAuth(getH3App(nitroApp), options);
15
+ const initPromise = (async () => {
16
+ await awaitBootstrap(nitroApp);
17
+ await autoMountAuth(getH3App(nitroApp), options);
18
+ })();
19
+ trackPluginInit(nitroApp, initPromise, {
20
+ paths: ["/_agent-native/auth"],
21
+ });
17
22
  };
18
23
  }
19
24
 
@@ -317,8 +317,12 @@ export function resolveSignupTrackingProperties(): Record<string, string> {
317
317
  export function shouldSkipEmailVerification(): boolean {
318
318
  const value = process.env.AUTH_SKIP_EMAIL_VERIFICATION;
319
319
  if (value == null) {
320
+ const deployContext =
321
+ process.env.AGENT_NATIVE_BUILD_DEPLOY_CONTEXT || process.env.CONTEXT;
320
322
  return (
321
- process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test"
323
+ process.env.NODE_ENV === "development" ||
324
+ process.env.NODE_ENV === "test" ||
325
+ deployContext === "deploy-preview"
322
326
  );
323
327
  }
324
328
  const normalized = value.trim().toLowerCase();
@@ -1,5 +1,5 @@
1
1
  export const MCP_EMBED_CORS_ALLOW_HEADERS =
2
- "Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-Agent-Native-Frontend,X-User-Timezone,X-Agent-Native-Embed-Target,X-Agent-Native-Embed-Transplant";
2
+ "Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,X-User-Timezone,X-Agent-Native-Embed-Target,X-Agent-Native-Embed-Transplant";
3
3
  export const EMBED_TRANSPLANT_HEADER = "x-agent-native-embed-transplant";
4
4
 
5
5
  const CLAUDE_MCP_CONTENT_HOST_RE = /^[a-f0-9]{32}\.claudemcpcontent\.com$/i;
@@ -1351,6 +1351,12 @@ export interface ClientConfigOptions {
1351
1351
  optimizeDeps?: NonNullable<UserConfig["optimizeDeps"]>;
1352
1352
  /** Additional Vite define constants. */
1353
1353
  define?: UserConfig["define"];
1354
+ /**
1355
+ * Browser/server compatibility epoch for app changes that cannot safely run
1356
+ * across a cached client and a newer action backend. Bump only for an
1357
+ * incompatible protocol or data-model transition, not for every deploy.
1358
+ */
1359
+ clientCompatibilityVersion?: string;
1354
1360
  /**
1355
1361
  * Framework route warmup behavior mounted by AgentSidebar.
1356
1362
  *
@@ -2685,6 +2691,13 @@ function createAgentNativeConfig(
2685
2691
  userConfig: UserConfig = {},
2686
2692
  ): UserConfig {
2687
2693
  const cwd = process.cwd();
2694
+ const buildId =
2695
+ process.env.DEPLOY_ID?.trim() ||
2696
+ process.env.COMMIT_REF?.trim() ||
2697
+ process.env.VERCEL_GIT_COMMIT_SHA?.trim() ||
2698
+ process.env.CF_PAGES_COMMIT_SHA?.trim() ||
2699
+ process.env.AGENT_NATIVE_BUILD_SHA?.trim() ||
2700
+ "development";
2688
2701
 
2689
2702
  // Workspace env fallback. If this app is inside a workspace, tell Vite to
2690
2703
  // also look for .env files at the workspace root. Per-app .env still wins
@@ -2769,6 +2782,10 @@ function createAgentNativeConfig(
2769
2782
  define: {
2770
2783
  ...(userConfig.define ?? {}),
2771
2784
  ...(options.define ?? {}),
2785
+ __AGENT_NATIVE_BUILD_ID__: JSON.stringify(buildId),
2786
+ __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__: JSON.stringify(
2787
+ options.clientCompatibilityVersion?.trim() || "",
2788
+ ),
2772
2789
  __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__: JSON.stringify(
2773
2790
  process.env.GA_MEASUREMENT_ID?.trim() || "",
2774
2791
  ),
@@ -134,6 +134,8 @@ cd templates/content && pnpm action <name> [args]
134
134
  | `ensure-content-spaces` | | Idempotently provision the signed-in user's personal and current organization Content spaces, system databases, and catalog references |
135
135
  | `backfill-content-files` | | Assign legacy pages to Content spaces and reconcile exactly one canonical Files database membership per non-system page |
136
136
  | `list-content-spaces` | | List already-provisioned Content spaces authorized by the signed-in user's current ownership or organization memberships |
137
+ | `create-content-space` | `--name <name> --requestId <opaque-id>` | Idempotently create a private named Content workspace with its own canonical Files database |
138
+ | `delete-content-space` | `--spaceId <id>` | Permanently delete a user-created Content workspace and all of its content; Personal and organization workspaces are protected |
137
139
  | `create-content-database` | `[--documentId <id>] [--spaceId <id>] [--parentId <id>] [--title <text>] [--description <text>]` | Create a described database page in a Content space or convert an existing page into a database |
138
140
  | `create-inline-content-database` | `--hostDocumentId <id> [--title <text>] [--description <text>]` | Create a described database owned by an inline database block in the host document |
139
141
  | `get-content-database` | `--databaseId <id>` or `--documentId <id>` | Get a database with its description, property/option schema guidance, item pages, and computed ancestry context |
@@ -4,8 +4,12 @@ import { and, eq, isNull } from "drizzle-orm";
4
4
  import { z } from "zod";
5
5
 
6
6
  import { getDb, schema } from "../server/db/index.js";
7
+ import { CONTENT_DATABASE_PERSONAL_VIEW_OVERRIDES_VERSION } from "../shared/api.js";
8
+ import { resolveContentSpaceAccess } from "./_content-space-access.js";
9
+ import { filesParentPropertyId } from "./_files-system-properties.js";
7
10
 
8
- export const PERSONAL_DATABASE_VIEW_OVERRIDES_VERSION = 1;
11
+ export const PERSONAL_DATABASE_VIEW_OVERRIDES_VERSION =
12
+ CONTENT_DATABASE_PERSONAL_VIEW_OVERRIDES_VERSION;
9
13
 
10
14
  export const personalDatabaseViewSettingKey = (databaseId: string) =>
11
15
  `content-database-personal-view:${databaseId}`;
@@ -38,8 +42,7 @@ export const filterSchema = z.object({
38
42
  parentFilterGroupId: z.string().optional(),
39
43
  });
40
44
 
41
- export const personalViewOverridesSchema = z.object({
42
- version: z.literal(PERSONAL_DATABASE_VIEW_OVERRIDES_VERSION),
45
+ const personalViewOverridesFields = {
43
46
  activeViewId: z.string().optional(),
44
47
  views: z.array(
45
48
  z.object({
@@ -49,6 +52,16 @@ export const personalViewOverridesSchema = z.object({
49
52
  filterMode: z.enum(["and", "or"]).default("and"),
50
53
  }),
51
54
  ),
55
+ };
56
+
57
+ export const personalViewOverridesSchema = z.object({
58
+ version: z.literal(PERSONAL_DATABASE_VIEW_OVERRIDES_VERSION),
59
+ ...personalViewOverridesFields,
60
+ });
61
+
62
+ const legacyPersonalViewOverridesSchema = z.object({
63
+ version: z.literal(1),
64
+ ...personalViewOverridesFields,
52
65
  });
53
66
 
54
67
  export async function assertContentDatabaseViewerAccess(databaseId: string) {
@@ -64,7 +77,12 @@ export async function assertContentDatabaseViewerAccess(databaseId: string) {
64
77
  );
65
78
  if (!database) throw new Error(`Database "${databaseId}" not found`);
66
79
 
67
- await assertAccess("document", database.documentId, "viewer");
80
+ try {
81
+ await assertAccess("document", database.documentId, "viewer");
82
+ } catch (error) {
83
+ if (database.systemRole !== "files" || !database.spaceId) throw error;
84
+ await resolveContentSpaceAccess(database.spaceId);
85
+ }
68
86
  }
69
87
 
70
88
  export async function readPersonalDatabaseViewOverrides(
@@ -76,5 +94,31 @@ export async function readPersonalDatabaseViewOverrides(
76
94
  personalDatabaseViewSettingKey(databaseId),
77
95
  );
78
96
  const parsed = personalViewOverridesSchema.safeParse(stored);
79
- return parsed.success ? parsed.data : null;
97
+ if (parsed.success) return parsed.data;
98
+
99
+ const legacy = legacyPersonalViewOverridesSchema.safeParse(stored);
100
+ if (!legacy.success) return null;
101
+ const [database] = await getDb()
102
+ .select({ systemRole: schema.contentDatabases.systemRole })
103
+ .from(schema.contentDatabases)
104
+ .where(eq(schema.contentDatabases.id, databaseId));
105
+ const legacyParentKey = filesParentPropertyId(databaseId);
106
+ return {
107
+ ...legacy.data,
108
+ version: PERSONAL_DATABASE_VIEW_OVERRIDES_VERSION,
109
+ views: legacy.data.views.map((view) => ({
110
+ ...view,
111
+ filters:
112
+ database?.systemRole === "files"
113
+ ? view.filters.filter(
114
+ (filter) =>
115
+ !(
116
+ filter.key === legacyParentKey &&
117
+ filter.operator === "is_empty" &&
118
+ filter.value === ""
119
+ ),
120
+ )
121
+ : view.filters,
122
+ })),
123
+ };
80
124
  }
@@ -0,0 +1,21 @@
1
+ import { getRequestUserEmail } from "@agent-native/core/server/request-context";
2
+ import { resolveAccess } from "@agent-native/core/sharing";
3
+
4
+ import { listContentOrganizationMemberships } from "./_content-space-access.js";
5
+
6
+ export async function resolveContentDocumentAccess(documentId: string) {
7
+ const current = await resolveAccess("document", documentId);
8
+ if (current) return current;
9
+
10
+ const userEmail = getRequestUserEmail();
11
+ if (!userEmail) return null;
12
+ const memberships = await listContentOrganizationMemberships(userEmail);
13
+ for (const membership of memberships) {
14
+ const access = await resolveAccess("document", documentId, {
15
+ userEmail,
16
+ orgId: membership.orgId,
17
+ });
18
+ if (access) return access;
19
+ }
20
+ return null;
21
+ }