@agent-native/toolkit 0.201.1 → 0.202.0-nightly-20261006033736

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 (229) hide show
  1. package/agent-native.eject.json +39 -12
  2. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/components.js +292 -23
  4. package/dist/app/agentkit/react/components.js.map +1 -1
  5. package/dist/app/agentkit/react/context.d.ts +5 -0
  6. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  7. package/dist/app/agentkit/react/context.js +5 -0
  8. package/dist/app/agentkit/react/context.js.map +1 -1
  9. package/dist/app/agentkit/react/styles.css +78 -3
  10. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  11. package/dist/app/auth/AuthPage.js +7 -14
  12. package/dist/app/auth/AuthPage.js.map +1 -1
  13. package/dist/app/auth/server.d.ts +6 -0
  14. package/dist/app/auth/server.d.ts.map +1 -0
  15. package/dist/app/auth/server.js +19 -0
  16. package/dist/app/auth/server.js.map +1 -0
  17. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  18. package/dist/app/chat/AgentKitAssistantChat.js +164 -83
  19. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  20. package/dist/app/chat/AgentSidebar.d.ts +2 -1
  21. package/dist/app/chat/AgentSidebar.d.ts.map +1 -1
  22. package/dist/app/chat/AgentSidebar.js +2 -2
  23. package/dist/app/chat/AgentSidebar.js.map +1 -1
  24. package/dist/app/chat/agentkit-chat/guided-questions.d.ts.map +1 -1
  25. package/dist/app/chat/agentkit-chat/guided-questions.js +13 -3
  26. package/dist/app/chat/agentkit-chat/guided-questions.js.map +1 -1
  27. package/dist/app/chat/chat/surface-types.d.ts +2 -0
  28. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  29. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  30. package/dist/app/i18n/catalogs/ar-SA.js +41 -0
  31. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  32. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  33. package/dist/app/i18n/catalogs/de-DE.js +41 -0
  34. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  35. package/dist/app/i18n/catalogs/en-US.d.ts +41 -0
  36. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  37. package/dist/app/i18n/catalogs/en-US.js +41 -0
  38. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  39. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  40. package/dist/app/i18n/catalogs/es-ES.js +41 -0
  41. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  42. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  43. package/dist/app/i18n/catalogs/fr-FR.js +41 -0
  44. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  45. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  46. package/dist/app/i18n/catalogs/hi-IN.js +41 -0
  47. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  48. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  49. package/dist/app/i18n/catalogs/ja-JP.js +41 -0
  50. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  51. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  52. package/dist/app/i18n/catalogs/ko-KR.js +41 -0
  53. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  54. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  55. package/dist/app/i18n/catalogs/pt-BR.js +41 -0
  56. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  57. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  58. package/dist/app/i18n/catalogs/zh-CN.js +41 -0
  59. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  60. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  61. package/dist/app/i18n/catalogs/zh-TW.js +41 -0
  62. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  63. package/dist/app/shared/StarfieldBackground.d.ts +1 -6
  64. package/dist/app/shared/StarfieldBackground.d.ts.map +1 -1
  65. package/dist/app/shared/StarfieldBackground.js +1 -368
  66. package/dist/app/shared/StarfieldBackground.js.map +1 -1
  67. package/dist/app/shared/WaveBackground.d.ts +5 -0
  68. package/dist/app/shared/WaveBackground.d.ts.map +1 -0
  69. package/dist/app/shared/WaveBackground.js +34 -0
  70. package/dist/app/shared/WaveBackground.js.map +1 -0
  71. package/dist/app/shared/WebGlWaveBackground.d.ts +9 -0
  72. package/dist/app/shared/WebGlWaveBackground.d.ts.map +1 -0
  73. package/dist/app/shared/WebGlWaveBackground.js +433 -0
  74. package/dist/app/shared/WebGlWaveBackground.js.map +1 -0
  75. package/dist/app/shared/index.d.ts +1 -0
  76. package/dist/app/shared/index.d.ts.map +1 -1
  77. package/dist/app/shared/index.js +1 -0
  78. package/dist/app/shared/index.js.map +1 -1
  79. package/dist/app/shared/ocean/bloom-blur.wgsl +43 -0
  80. package/dist/app/shared/ocean/bloom-blur.wgsl.js +1 -0
  81. package/dist/app/shared/ocean/bloom-bright.wgsl +21 -0
  82. package/dist/app/shared/ocean/bloom-bright.wgsl.js +1 -0
  83. package/dist/app/shared/ocean/bloom-composite.wgsl +40 -0
  84. package/dist/app/shared/ocean/bloom-composite.wgsl.js +1 -0
  85. package/dist/app/shared/ocean/brand-colors.d.ts +9 -0
  86. package/dist/app/shared/ocean/brand-colors.d.ts.map +1 -0
  87. package/dist/app/shared/ocean/brand-colors.js +45 -0
  88. package/dist/app/shared/ocean/brand-colors.js.map +1 -0
  89. package/dist/app/shared/ocean/camera.d.ts +5 -0
  90. package/dist/app/shared/ocean/camera.d.ts.map +1 -0
  91. package/dist/app/shared/ocean/camera.js +24 -0
  92. package/dist/app/shared/ocean/camera.js.map +1 -0
  93. package/dist/app/shared/ocean/hero-ocean-background.d.ts +8 -0
  94. package/dist/app/shared/ocean/hero-ocean-background.d.ts.map +1 -0
  95. package/dist/app/shared/ocean/hero-ocean-background.js +186 -0
  96. package/dist/app/shared/ocean/hero-ocean-background.js.map +1 -0
  97. package/dist/app/shared/ocean/ifft-stage.wgsl +36 -0
  98. package/dist/app/shared/ocean/ifft-stage.wgsl.js +1 -0
  99. package/dist/app/shared/ocean/initial-spectrum.wgsl +36 -0
  100. package/dist/app/shared/ocean/initial-spectrum.wgsl.js +1 -0
  101. package/dist/app/shared/ocean/noise.wgsl +24 -0
  102. package/dist/app/shared/ocean/noise.wgsl.js +1 -0
  103. package/dist/app/shared/ocean/normal-foam.wgsl +32 -0
  104. package/dist/app/shared/ocean/normal-foam.wgsl.js +1 -0
  105. package/dist/app/shared/ocean/ocean-colors.d.ts +6 -0
  106. package/dist/app/shared/ocean/ocean-colors.d.ts.map +1 -0
  107. package/dist/app/shared/ocean/ocean-colors.js +6 -0
  108. package/dist/app/shared/ocean/ocean-colors.js.map +1 -0
  109. package/dist/app/shared/ocean/ocean-common.wgsl +15 -0
  110. package/dist/app/shared/ocean/ocean-common.wgsl.js +1 -0
  111. package/dist/app/shared/ocean/ocean-graph.d.ts +12 -0
  112. package/dist/app/shared/ocean/ocean-graph.d.ts.map +1 -0
  113. package/dist/app/shared/ocean/ocean-graph.js +16 -0
  114. package/dist/app/shared/ocean/ocean-graph.js.map +1 -0
  115. package/dist/app/shared/ocean/particles.wgsl +139 -0
  116. package/dist/app/shared/ocean/particles.wgsl.js +1 -0
  117. package/dist/app/shared/ocean/present.wgsl +40 -0
  118. package/dist/app/shared/ocean/present.wgsl.js +1 -0
  119. package/dist/app/shared/ocean/renderer.d.ts +66 -0
  120. package/dist/app/shared/ocean/renderer.d.ts.map +1 -0
  121. package/dist/app/shared/ocean/renderer.js +535 -0
  122. package/dist/app/shared/ocean/renderer.js.map +1 -0
  123. package/dist/app/shared/ocean/spectrum.wgsl +35 -0
  124. package/dist/app/shared/ocean/spectrum.wgsl.js +1 -0
  125. package/dist/app/shared/ocean/tuning.d.ts +48 -0
  126. package/dist/app/shared/ocean/tuning.d.ts.map +1 -0
  127. package/dist/app/shared/ocean/tuning.js +74 -0
  128. package/dist/app/shared/ocean/tuning.js.map +1 -0
  129. package/dist/app/shared/ocean/webgpu-support.d.ts +3 -0
  130. package/dist/app/shared/ocean/webgpu-support.d.ts.map +1 -0
  131. package/dist/app/shared/ocean/webgpu-support.js +13 -0
  132. package/dist/app/shared/ocean/webgpu-support.js.map +1 -0
  133. package/dist/app/sharing/AccessRequestApprovalPage.d.ts +23 -0
  134. package/dist/app/sharing/AccessRequestApprovalPage.d.ts.map +1 -0
  135. package/dist/app/sharing/AccessRequestApprovalPage.js +95 -0
  136. package/dist/app/sharing/AccessRequestApprovalPage.js.map +1 -0
  137. package/dist/app/sharing/AccessRequestsSection.d.ts +5 -0
  138. package/dist/app/sharing/AccessRequestsSection.d.ts.map +1 -0
  139. package/dist/app/sharing/AccessRequestsSection.js +47 -0
  140. package/dist/app/sharing/AccessRequestsSection.js.map +1 -0
  141. package/dist/app/sharing/ResourceAccessScreen.d.ts +53 -0
  142. package/dist/app/sharing/ResourceAccessScreen.d.ts.map +1 -0
  143. package/dist/app/sharing/ResourceAccessScreen.js +132 -0
  144. package/dist/app/sharing/ResourceAccessScreen.js.map +1 -0
  145. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  146. package/dist/app/sharing/ShareButton.js +4 -2
  147. package/dist/app/sharing/ShareButton.js.map +1 -1
  148. package/dist/app/sharing/ShareDialog.d.ts.map +1 -1
  149. package/dist/app/sharing/ShareDialog.js +7 -5
  150. package/dist/app/sharing/ShareDialog.js.map +1 -1
  151. package/dist/app/sharing/index.d.ts +2 -0
  152. package/dist/app/sharing/index.d.ts.map +1 -1
  153. package/dist/app/sharing/index.js +2 -0
  154. package/dist/app/sharing/index.js.map +1 -1
  155. package/dist/app/styles/agent-native.css +21 -1
  156. package/dist/app/styles/tokens/agent-kit.css +3 -1
  157. package/dist/composer/AgentSuggestionBar.d.ts +1 -1
  158. package/dist/composer/AgentSuggestionBar.d.ts.map +1 -1
  159. package/dist/composer/AgentSuggestionBar.js +75 -6
  160. package/dist/composer/AgentSuggestionBar.js.map +1 -1
  161. package/package.json +14 -4
  162. package/src/app/agentkit/react/components.tsx +492 -60
  163. package/src/app/agentkit/react/context.tsx +10 -0
  164. package/src/app/agentkit/react/lifecycle.spec.tsx +487 -1
  165. package/src/app/agentkit/react/styles.css +78 -3
  166. package/src/app/auth/AuthPage.spec.tsx +11 -7
  167. package/src/app/auth/AuthPage.tsx +16 -35
  168. package/src/app/auth/server.spec.ts +68 -0
  169. package/src/app/auth/server.ts +29 -0
  170. package/src/app/chat/AgentKitAssistantChat.spec.tsx +287 -290
  171. package/src/app/chat/AgentKitAssistantChat.tsx +219 -116
  172. package/src/app/chat/AgentSidebar.tsx +3 -0
  173. package/src/app/chat/agentkit-chat/guided-questions.flow.spec.tsx +85 -0
  174. package/src/app/chat/agentkit-chat/guided-questions.tsx +12 -5
  175. package/src/app/chat/chat/surface-types.ts +2 -0
  176. package/src/app/i18n/catalogs/ar-SA.ts +44 -0
  177. package/src/app/i18n/catalogs/de-DE.ts +55 -0
  178. package/src/app/i18n/catalogs/en-US.ts +46 -0
  179. package/src/app/i18n/catalogs/es-ES.ts +53 -0
  180. package/src/app/i18n/catalogs/fr-FR.ts +53 -0
  181. package/src/app/i18n/catalogs/hi-IN.ts +47 -0
  182. package/src/app/i18n/catalogs/ja-JP.ts +54 -0
  183. package/src/app/i18n/catalogs/ko-KR.ts +50 -0
  184. package/src/app/i18n/catalogs/pt-BR.ts +52 -0
  185. package/src/app/i18n/catalogs/zh-CN.ts +42 -0
  186. package/src/app/i18n/catalogs/zh-TW.ts +43 -0
  187. package/src/app/shared/StarfieldBackground.tsx +4 -423
  188. package/src/app/shared/WaveBackground.spec.tsx +104 -0
  189. package/src/app/shared/WaveBackground.tsx +60 -0
  190. package/src/app/shared/WebGlWaveBackground.spec.tsx +106 -0
  191. package/src/app/shared/WebGlWaveBackground.tsx +499 -0
  192. package/src/app/shared/index.ts +1 -0
  193. package/src/app/shared/ocean/bloom-blur.wgsl +43 -0
  194. package/src/app/shared/ocean/bloom-bright.wgsl +21 -0
  195. package/src/app/shared/ocean/bloom-composite.wgsl +40 -0
  196. package/src/app/shared/ocean/brand-colors.ts +49 -0
  197. package/src/app/shared/ocean/camera.ts +28 -0
  198. package/src/app/shared/ocean/hero-ocean-background.spec.tsx +369 -0
  199. package/src/app/shared/ocean/hero-ocean-background.tsx +238 -0
  200. package/src/app/shared/ocean/ifft-stage.wgsl +36 -0
  201. package/src/app/shared/ocean/initial-spectrum.wgsl +36 -0
  202. package/src/app/shared/ocean/noise.wgsl +24 -0
  203. package/src/app/shared/ocean/normal-foam.wgsl +32 -0
  204. package/src/app/shared/ocean/ocean-colors.ts +11 -0
  205. package/src/app/shared/ocean/ocean-common.wgsl +15 -0
  206. package/src/app/shared/ocean/ocean-graph.ts +29 -0
  207. package/src/app/shared/ocean/particles.wgsl +139 -0
  208. package/src/app/shared/ocean/present.wgsl +40 -0
  209. package/src/app/shared/ocean/renderer.spec.ts +100 -0
  210. package/src/app/shared/ocean/renderer.ts +693 -0
  211. package/src/app/shared/ocean/spectrum.wgsl +35 -0
  212. package/src/app/shared/ocean/tuning.ts +131 -0
  213. package/src/app/shared/ocean/webgpu-support.ts +12 -0
  214. package/src/app/sharing/AccessRequestApprovalPage.spec.tsx +276 -0
  215. package/src/app/sharing/AccessRequestApprovalPage.tsx +307 -0
  216. package/src/app/sharing/AccessRequestsSection.tsx +163 -0
  217. package/src/app/sharing/ResourceAccessScreen.spec.tsx +235 -0
  218. package/src/app/sharing/ResourceAccessScreen.tsx +331 -0
  219. package/src/app/sharing/ShareButton.spec.tsx +171 -5
  220. package/src/app/sharing/ShareButton.tsx +15 -2
  221. package/src/app/sharing/ShareDialog.spec.tsx +71 -2
  222. package/src/app/sharing/ShareDialog.tsx +24 -2
  223. package/src/app/sharing/index.ts +2 -0
  224. package/src/app/styles/agent-native.css +21 -1
  225. package/src/app/styles/agent-native.spec.ts +16 -3
  226. package/src/app/styles/tokens/agent-kit.css +3 -1
  227. package/src/composer/AgentSuggestionBar.overflow.spec.tsx +277 -0
  228. package/src/composer/AgentSuggestionBar.tsx +113 -8
  229. package/src/wgsl-env.d.ts +6 -0
@@ -0,0 +1,131 @@
1
+ export interface OceanTuning {
2
+ readonly simulation: {
3
+ readonly oceanSize: number;
4
+ readonly worldSize: number;
5
+ readonly timeScale: number;
6
+ readonly spectrumTimeScale: number;
7
+ readonly windSpeed: number;
8
+ readonly windAngle: number;
9
+ readonly amplitude: number;
10
+ readonly choppiness: number;
11
+ readonly displacementScale: number;
12
+ readonly foamThreshold: number;
13
+ };
14
+ readonly particles: {
15
+ readonly pointSize: number;
16
+ readonly fadeNear: number;
17
+ readonly fadeFar: number;
18
+ readonly fadePower: number;
19
+ readonly oceanColor: readonly [number, number, number, number];
20
+ readonly neonColor: readonly [number, number, number, number];
21
+ readonly foamColor: readonly [number, number, number, number];
22
+ };
23
+ readonly camera: {
24
+ readonly eye: readonly [number, number, number];
25
+ readonly target: readonly [number, number, number];
26
+ readonly pitchDegrees: number;
27
+ readonly fovDegrees: number;
28
+ readonly near: number;
29
+ readonly far: number;
30
+ };
31
+ readonly present: {
32
+ readonly fgColor: readonly [number, number, number];
33
+ readonly bgColor: readonly [number, number, number];
34
+ readonly brightness: number;
35
+ };
36
+ readonly bloom: {
37
+ readonly threshold: number;
38
+ readonly smoothWidth: number;
39
+ readonly strength: number;
40
+ readonly radius: number;
41
+ readonly levels: number;
42
+ readonly kernelRadii: readonly number[];
43
+ };
44
+ readonly bottomFadeStartPercent: number;
45
+ }
46
+
47
+ const UPSTREAM_TUNING: OceanTuning = {
48
+ simulation: {
49
+ oceanSize: 200,
50
+ worldSize: 400,
51
+ timeScale: 0.6,
52
+ spectrumTimeScale: 0.5,
53
+ windSpeed: 12.9,
54
+ windAngle: 4.83,
55
+ amplitude: 1.3,
56
+ choppiness: 1.51,
57
+ displacementScale: 0.005,
58
+ foamThreshold: 0,
59
+ },
60
+ particles: {
61
+ pointSize: 0.75,
62
+ fadeNear: 60,
63
+ fadeFar: 250,
64
+ fadePower: 3.2,
65
+ oceanColor: [
66
+ 0.003035269835488375, 0.003035269835488375, 0.003035269835488375, 0,
67
+ ],
68
+ neonColor: [1, 1, 1, 0],
69
+ foamColor: [1, 1, 1, 0],
70
+ },
71
+ camera: {
72
+ eye: [0, 30, 90],
73
+ target: [0, 5, 55],
74
+ pitchDegrees: -10,
75
+ fovDegrees: 90,
76
+ near: 0.1,
77
+ far: 2000,
78
+ },
79
+ present: {
80
+ fgColor: [0.682, 0.678, 0.671],
81
+ bgColor: [0.039, 0.039, 0.039],
82
+ brightness: 2.1,
83
+ },
84
+ bloom: {
85
+ threshold: 0.3,
86
+ smoothWidth: 0.01,
87
+ strength: 0.08,
88
+ radius: 0.46,
89
+ levels: 5,
90
+ kernelRadii: [6, 10, 14, 18, 22],
91
+ },
92
+ bottomFadeStartPercent: 100,
93
+ };
94
+
95
+ type Overrides = {
96
+ [K in keyof OceanTuning]?: OceanTuning[K] extends object
97
+ ? Partial<OceanTuning[K]>
98
+ : OceanTuning[K];
99
+ };
100
+
101
+ const HERO_OVERRIDES: Overrides = {
102
+ simulation: { worldSize: 700, displacementScale: 0.035 },
103
+ particles: { pointSize: 1.1, fadeFar: 520, fadePower: 1.8 },
104
+ camera: {
105
+ eye: [0, 14, 78],
106
+ target: [0, 0, -60],
107
+ pitchDegrees: 21,
108
+ fovDegrees: 95,
109
+ },
110
+ bottomFadeStartPercent: 62,
111
+ };
112
+
113
+ export const OCEAN_TUNING: OceanTuning = {
114
+ simulation: { ...UPSTREAM_TUNING.simulation, ...HERO_OVERRIDES.simulation },
115
+ particles: { ...UPSTREAM_TUNING.particles, ...HERO_OVERRIDES.particles },
116
+ camera: { ...UPSTREAM_TUNING.camera, ...HERO_OVERRIDES.camera },
117
+ present: { ...UPSTREAM_TUNING.present, ...HERO_OVERRIDES.present },
118
+ bloom: { ...UPSTREAM_TUNING.bloom, ...HERO_OVERRIDES.bloom },
119
+ bottomFadeStartPercent:
120
+ HERO_OVERRIDES.bottomFadeStartPercent ??
121
+ UPSTREAM_TUNING.bottomFadeStartPercent,
122
+ };
123
+
124
+ export function gaussianCoefficients(kernelRadius: number): readonly number[] {
125
+ return Array.from({ length: 24 }, (_, index) =>
126
+ index < kernelRadius
127
+ ? (0.39894 * Math.exp((-0.5 * index * index) / (kernelRadius / 3) ** 2)) /
128
+ (kernelRadius / 3)
129
+ : 0,
130
+ );
131
+ }
@@ -0,0 +1,12 @@
1
+ export type WebgpuSupport = "supported" | "unsupported" | "probe-failed";
2
+
3
+ export async function probeWebgpuSupport(): Promise<WebgpuSupport> {
4
+ const gpu = (navigator as Navigator & { gpu?: GPU }).gpu;
5
+ if (!gpu || typeof gpu.requestAdapter !== "function") return "unsupported";
6
+ try {
7
+ const adapter = await gpu.requestAdapter();
8
+ return adapter ? "supported" : "unsupported";
9
+ } catch {
10
+ return "probe-failed";
11
+ }
12
+ }
@@ -0,0 +1,276 @@
1
+ // @vitest-environment happy-dom
2
+ import { AgentNativeI18nProvider } from "@agent-native/core/client/i18n";
3
+ import { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { MemoryRouter, Route, Routes } from "react-router";
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
+
8
+ import { createToolkitI18nCatalog } from "../i18n.js";
9
+ import { AccessRequestApprovalPage } from "./AccessRequestApprovalPage.js";
10
+
11
+ const mocks = vi.hoisted(() => ({
12
+ query: {} as Record<string, unknown>,
13
+ queryParams: [] as unknown[],
14
+ approve: vi.fn(),
15
+ decline: vi.fn(),
16
+ refetch: vi.fn(async () => undefined),
17
+ signOut: vi.fn(async () => undefined),
18
+ }));
19
+
20
+ vi.mock("@agent-native/core/client/use-action", () => ({
21
+ useActionQuery: (_name: string, params: unknown) => {
22
+ mocks.queryParams.push(params);
23
+ return { refetch: mocks.refetch, ...mocks.query };
24
+ },
25
+ useActionMutation: (name: string) => ({
26
+ mutateAsync:
27
+ name === "approve-resource-access-request"
28
+ ? mocks.approve
29
+ : mocks.decline,
30
+ }),
31
+ }));
32
+ vi.mock("@agent-native/core/client/hooks", () => ({
33
+ useSession: () => ({ session: { email: "owner@example.test" } }),
34
+ signOut: mocks.signOut,
35
+ }));
36
+ vi.mock("@agent-native/core/client/sign-in-return", () => ({
37
+ buildSignInReturnHref: () => "/_agent-native/sign-in",
38
+ }));
39
+
40
+ const catalog = createToolkitI18nCatalog({ messages: {} });
41
+
42
+ const pendingReview = {
43
+ id: "req-1",
44
+ generation: 2,
45
+ state: "pending",
46
+ requester: { email: "requester@example.test", name: "Pat Example" },
47
+ note: "Need this for the launch review.",
48
+ requestedAt: "2026-10-01T10:00:00.000Z",
49
+ decidedAt: null,
50
+ grantedRole: null,
51
+ resource: {
52
+ type: "document",
53
+ id: "doc-1",
54
+ label: "Document",
55
+ title: "Launch plan",
56
+ path: "/page/doc-1",
57
+ },
58
+ };
59
+
60
+ function actionError(status: number, fields: Record<string, unknown> = {}) {
61
+ return Object.assign(new Error("Action failed"), { status, ...fields });
62
+ }
63
+
64
+ describe("AccessRequestApprovalPage", () => {
65
+ let container: HTMLDivElement;
66
+ let root: Root;
67
+
68
+ beforeEach(() => {
69
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
70
+ container = document.createElement("div");
71
+ document.body.appendChild(container);
72
+ root = createRoot(container);
73
+ mocks.query = {};
74
+ mocks.queryParams = [];
75
+ mocks.approve.mockReset().mockResolvedValue(undefined);
76
+ mocks.decline.mockReset().mockResolvedValue(undefined);
77
+ });
78
+
79
+ afterEach(() => {
80
+ act(() => root.unmount());
81
+ container.remove();
82
+ vi.unstubAllGlobals();
83
+ vi.clearAllMocks();
84
+ });
85
+
86
+ function render() {
87
+ act(() => {
88
+ root.render(
89
+ <AgentNativeI18nProvider
90
+ catalog={catalog}
91
+ initialLocale="en-US"
92
+ initialPreference="en-US"
93
+ persistPreference={false}
94
+ >
95
+ <MemoryRouter initialEntries={["/access-requests/req-1"]}>
96
+ <Routes>
97
+ <Route
98
+ path="/access-requests/:requestId"
99
+ element={<AccessRequestApprovalPage />}
100
+ />
101
+ </Routes>
102
+ </MemoryRouter>
103
+ </AgentNativeI18nProvider>,
104
+ );
105
+ });
106
+ }
107
+
108
+ function button(label: string) {
109
+ return [...container.querySelectorAll("button")].find(
110
+ (candidate) => candidate.textContent === label,
111
+ );
112
+ }
113
+
114
+ it("reads the request from the route without deciding anything", () => {
115
+ mocks.query = { isLoading: true };
116
+ render();
117
+
118
+ expect(mocks.queryParams.at(-1)).toEqual({ requestId: "req-1" });
119
+ expect(container.querySelector('[aria-hidden="true"]')).not.toBeNull();
120
+ expect(mocks.approve).not.toHaveBeenCalled();
121
+ expect(mocks.decline).not.toHaveBeenCalled();
122
+ });
123
+
124
+ it("shows who is asking, for which page, and starts the role at Viewer", () => {
125
+ mocks.query = { data: pendingReview };
126
+ render();
127
+
128
+ const heading = container.querySelector("h1");
129
+ expect(heading?.textContent).toBe("Pat Example is asking for access");
130
+ expect(document.activeElement).toBe(heading);
131
+ expect(container.textContent).toContain("requester@example.test");
132
+ expect(container.textContent).toContain("Need this for the launch review.");
133
+ const link = container.querySelector("a");
134
+ expect(link?.getAttribute("href")).toBe("/page/doc-1");
135
+ expect(link?.textContent).toBe("Launch plan");
136
+ expect(container.textContent).toContain("Viewer");
137
+
138
+ act(() => button("Allow")?.click());
139
+ expect(mocks.approve).toHaveBeenCalledWith({
140
+ requestId: "req-1",
141
+ generation: 2,
142
+ role: "viewer",
143
+ });
144
+ });
145
+
146
+ it("declines with the request's generation", () => {
147
+ mocks.query = { data: pendingReview };
148
+ render();
149
+
150
+ act(() => button("Decline")?.click());
151
+ expect(mocks.decline).toHaveBeenCalledWith({
152
+ requestId: "req-1",
153
+ generation: 2,
154
+ });
155
+ });
156
+
157
+ it("explains a sharing rule that refused and keeps the request open", async () => {
158
+ mocks.query = { data: pendingReview };
159
+ mocks.approve.mockRejectedValue(
160
+ actionError(403, {
161
+ actionMessage: "This workspace doesn't allow sharing outside it.",
162
+ }),
163
+ );
164
+ render();
165
+
166
+ await act(async () => button("Allow")?.click());
167
+
168
+ expect(container.querySelector('[role="alert"]')?.textContent).toBe(
169
+ "This workspace doesn't allow sharing outside it.",
170
+ );
171
+ expect(button("Allow")).toBeDefined();
172
+ });
173
+
174
+ it("reloads a request someone else already handled", async () => {
175
+ mocks.query = { data: pendingReview };
176
+ mocks.approve.mockRejectedValue(
177
+ actionError(409, {
178
+ errorCode: "access_request_stale",
179
+ actionMessage: "Someone already handled this request.",
180
+ }),
181
+ );
182
+ render();
183
+
184
+ await act(async () => button("Allow")?.click());
185
+
186
+ expect(container.querySelector('[role="alert"]')?.textContent).toBe(
187
+ "Someone already handled this request, or it changed.",
188
+ );
189
+ expect(mocks.refetch).toHaveBeenCalled();
190
+ });
191
+
192
+ it("says when the requester couldn't be emailed that they're in", async () => {
193
+ mocks.query = { data: pendingReview };
194
+ mocks.approve.mockResolvedValue({
195
+ state: "approved",
196
+ role: "viewer",
197
+ email: "failed",
198
+ });
199
+ render();
200
+
201
+ await act(async () => button("Allow")?.click());
202
+
203
+ expect(container.querySelector('[role="status"]')?.textContent).toMatch(
204
+ /has access, but we couldn't email them\.$/,
205
+ );
206
+ });
207
+
208
+ it("doesn't show an unexpected server message", async () => {
209
+ mocks.query = { data: pendingReview };
210
+ mocks.approve.mockRejectedValue(
211
+ actionError(500, { actionMessage: "Internal stack detail" }),
212
+ );
213
+ render();
214
+
215
+ await act(async () => button("Allow")?.click());
216
+
217
+ expect(container.querySelector('[role="alert"]')?.textContent).toBe(
218
+ "Couldn't save your decision. Try again.",
219
+ );
220
+ });
221
+
222
+ it("shows the outcome once the request is decided", () => {
223
+ mocks.query = {
224
+ data: {
225
+ ...pendingReview,
226
+ state: "approved",
227
+ grantedRole: "editor",
228
+ decidedAt: "2026-10-01T11:00:00.000Z",
229
+ },
230
+ };
231
+ render();
232
+
233
+ expect(container.querySelector("h1")?.textContent).toBe("Access allowed");
234
+ expect(container.textContent).toContain("Editor");
235
+ expect(button("Allow")).toBeUndefined();
236
+ expect(button("Decline")).toBeUndefined();
237
+ });
238
+
239
+ it("shows nothing about the request to someone who can't review it", () => {
240
+ mocks.query = { isError: true, error: actionError(404) };
241
+ render();
242
+
243
+ expect(container.querySelector("h1")?.textContent).toBe(
244
+ "You can't review this request",
245
+ );
246
+ expect(container.textContent).toContain(
247
+ "You're signed in as owner@example.test",
248
+ );
249
+ expect(container.textContent).not.toContain("Launch plan");
250
+ expect(button("Allow")).toBeUndefined();
251
+
252
+ act(() => button("Switch account")?.click());
253
+ expect(mocks.signOut).toHaveBeenCalledTimes(1);
254
+ });
255
+
256
+ it("asks a signed-out visitor to sign in", () => {
257
+ mocks.query = { isError: true, error: actionError(401) };
258
+ render();
259
+
260
+ expect(container.querySelector("h1")?.textContent).toBe(
261
+ "Sign in to continue",
262
+ );
263
+ expect(button("Sign in")).toBeDefined();
264
+ });
265
+
266
+ it("offers Retry when the request couldn't be read", () => {
267
+ mocks.query = { isError: true, error: actionError(500) };
268
+ render();
269
+
270
+ expect(container.querySelector('[role="alert"]')?.textContent).toBe(
271
+ "Couldn't load this request.",
272
+ );
273
+ act(() => button("Retry")?.click());
274
+ expect(mocks.refetch).toHaveBeenCalled();
275
+ });
276
+ });