@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,331 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+ import { buildSignInReturnHref } from "@agent-native/core/client/sign-in-return";
3
+ import {
4
+ ActionButton,
5
+ Dialog,
6
+ TextArea,
7
+ } from "@agent-native/toolkit/design-system";
8
+ import { cn } from "@agent-native/toolkit/utils";
9
+ import {
10
+ IconFileUnknown,
11
+ IconLock,
12
+ IconLogin2,
13
+ IconTrash,
14
+ } from "@tabler/icons-react";
15
+ import {
16
+ useEffect,
17
+ useId,
18
+ useRef,
19
+ useState,
20
+ type ReactNode,
21
+ type RefObject,
22
+ } from "react";
23
+
24
+ export type ResourceAccessScreenState =
25
+ | "denied"
26
+ | "missing"
27
+ | "trashed"
28
+ | "signed-out";
29
+
30
+ /** Asking the owner for access, on the `denied` state. */
31
+ export interface ResourceAccessScreenRequest {
32
+ /** The viewer already asked, and their request is open. */
33
+ sent: boolean;
34
+ /** Sends the request with an optional note; rejects when it fails. */
35
+ onRequest: (note: string) => Promise<void>;
36
+ /** A request is being sent. */
37
+ sending?: boolean;
38
+ /** Why the last request failed. */
39
+ error?: { errorCode: string | null } | null;
40
+ }
41
+
42
+ export interface ResourceAccessScreenProps {
43
+ state: ResourceAccessScreenState;
44
+ /** The app's heading for this state, such as "This page doesn't exist". */
45
+ title?: ReactNode;
46
+ description?: ReactNode;
47
+ /** The account the viewer is signed in as, shown as text. */
48
+ signedInEmail?: string | null;
49
+ /** The app's own actions, such as a link back to the viewer's work. */
50
+ actions?: ReactNode;
51
+ /** Shows Switch account after the app's actions. */
52
+ onSwitchAccount?: () => void;
53
+ /**
54
+ * Replaces the signed-out state's Sign in, which by default goes to sign-in
55
+ * and comes back to this link.
56
+ */
57
+ onSignIn?: () => void;
58
+ /** Offers Request access on the `denied` state. */
59
+ request?: ResourceAccessScreenRequest;
60
+ /**
61
+ * The app takes access requests for this kind of resource, so the
62
+ * signed-out state says signing in is how to ask. It says nothing about
63
+ * this particular link.
64
+ */
65
+ acceptsRequests?: boolean;
66
+ /** Rendered above the screen, such as a sidebar toggle. */
67
+ header?: ReactNode;
68
+ /** Render as the page's `main` landmark when the app shell has none. */
69
+ landmark?: boolean;
70
+ className?: string;
71
+ }
72
+
73
+ const STATE_ICONS = {
74
+ denied: IconLock,
75
+ missing: IconFileUnknown,
76
+ trashed: IconTrash,
77
+ "signed-out": IconLogin2,
78
+ } satisfies Record<ResourceAccessScreenState, unknown>;
79
+
80
+ /**
81
+ * What a link to a resource the viewer can't open says, in place of the
82
+ * resource. It stays on the link's URL, never redirects, and states the
83
+ * situation as static content, so focus starts on the heading rather than a
84
+ * toast or live region.
85
+ */
86
+ export function ResourceAccessScreen({
87
+ state,
88
+ title,
89
+ description,
90
+ signedInEmail,
91
+ actions,
92
+ onSwitchAccount,
93
+ onSignIn,
94
+ request,
95
+ acceptsRequests = false,
96
+ header,
97
+ landmark = false,
98
+ className,
99
+ }: ResourceAccessScreenProps) {
100
+ const t = useT();
101
+ const headingId = useId();
102
+ const headingRef = useRef<HTMLHeadingElement>(null);
103
+ const [requestOpen, setRequestOpen] = useState(false);
104
+ const Icon = STATE_ICONS[state];
105
+ const Root = landmark ? "main" : "div";
106
+ const signIn =
107
+ state === "signed-out"
108
+ ? (onSignIn ?? (() => window.location.assign(buildSignInReturnHref())))
109
+ : undefined;
110
+ const offersRequest = state === "denied" && request !== undefined;
111
+
112
+ useEffect(() => {
113
+ headingRef.current?.focus({ preventScroll: true });
114
+ }, [state]);
115
+
116
+ const defaults = {
117
+ denied: {
118
+ title: t("agentChat.accessGate.deniedTitle", {
119
+ defaultValue: "You don't have access",
120
+ }),
121
+ description: request
122
+ ? request.sent
123
+ ? t("agentChat.accessGate.requestSent", {
124
+ defaultValue: "Request sent. The owner has been notified.",
125
+ })
126
+ : t("agentChat.accessGate.requestDescription", {
127
+ defaultValue: "Request access and the owner will be notified.",
128
+ })
129
+ : t("agentChat.accessGate.deniedDescription", {
130
+ defaultValue: "Ask the owner to share it with you.",
131
+ }),
132
+ },
133
+ missing: {
134
+ title: t("agentChat.accessGate.missingTitle", {
135
+ defaultValue: "This doesn't exist",
136
+ }),
137
+ description: t("agentChat.accessGate.missingDescription", {
138
+ defaultValue: "The link may be wrong, or it may have been deleted.",
139
+ }),
140
+ },
141
+ trashed: {
142
+ title: t("agentChat.accessGate.trashedTitle", {
143
+ defaultValue: "This is in the trash",
144
+ }),
145
+ description: t("agentChat.accessGate.trashedDescription", {
146
+ defaultValue: "Restore it to open it again.",
147
+ }),
148
+ },
149
+ "signed-out": {
150
+ title: t("agentChat.accessGate.signedOutTitle", {
151
+ defaultValue: "Sign in to continue",
152
+ }),
153
+ description: acceptsRequests
154
+ ? t("agentChat.accessGate.signedOutRequestDescription", {
155
+ defaultValue: "Sign in to request access.",
156
+ })
157
+ : t("agentChat.accessGate.signedOutDescription", {
158
+ defaultValue: "Sign in with an account that has access.",
159
+ }),
160
+ },
161
+ }[state];
162
+
163
+ return (
164
+ <Root
165
+ className={cn("flex min-h-0 flex-1 flex-col", className)}
166
+ data-access-state={state}
167
+ >
168
+ {header}
169
+ <div className="flex min-h-0 flex-1 items-center justify-center bg-background px-6 py-10">
170
+ <section
171
+ aria-labelledby={headingId}
172
+ className="flex w-full max-w-sm flex-col items-center text-center"
173
+ >
174
+ <div
175
+ aria-hidden="true"
176
+ className="mb-5 flex size-12 items-center justify-center rounded-xl border border-border bg-muted text-muted-foreground"
177
+ >
178
+ <Icon size={22} />
179
+ </div>
180
+ <h1
181
+ ref={headingRef}
182
+ id={headingId}
183
+ tabIndex={-1}
184
+ className="text-2xl font-semibold tracking-normal text-foreground outline-none"
185
+ >
186
+ {title ?? defaults.title}
187
+ </h1>
188
+ {/* A request's outcome is announced politely when it changes; the
189
+ state on arrival is read from the focused heading instead. */}
190
+ <p
191
+ role={offersRequest ? "status" : undefined}
192
+ className="mt-3 text-sm leading-6 text-muted-foreground"
193
+ >
194
+ {description ?? defaults.description}
195
+ </p>
196
+ {signedInEmail ? (
197
+ <p className="mt-4 break-all text-sm text-muted-foreground">
198
+ {t("agentChat.accessGate.signedInAs", {
199
+ email: signedInEmail,
200
+ defaultValue: "You're signed in as {{email}}",
201
+ })}
202
+ </p>
203
+ ) : null}
204
+ {signIn || actions || onSwitchAccount || offersRequest ? (
205
+ <div className="mt-6 flex flex-wrap items-center justify-center gap-2">
206
+ {signIn ? (
207
+ <ActionButton onPress={signIn}>
208
+ {t("agentChat.accessGate.signIn", {
209
+ defaultValue: "Sign in",
210
+ })}
211
+ </ActionButton>
212
+ ) : null}
213
+ {offersRequest && !request.sent ? (
214
+ <ActionButton onPress={() => setRequestOpen(true)}>
215
+ {t("agentChat.accessGate.requestAccess", {
216
+ defaultValue: "Request access",
217
+ })}
218
+ </ActionButton>
219
+ ) : null}
220
+ {actions}
221
+ {onSwitchAccount ? (
222
+ <ActionButton emphasis="outline" onPress={onSwitchAccount}>
223
+ {t("agentChat.accessGate.switchAccount", {
224
+ defaultValue: "Switch account",
225
+ })}
226
+ </ActionButton>
227
+ ) : null}
228
+ </div>
229
+ ) : null}
230
+ </section>
231
+ </div>
232
+ {offersRequest ? (
233
+ <RequestAccessDialog
234
+ open={requestOpen}
235
+ onOpenChange={setRequestOpen}
236
+ request={request}
237
+ sentFocusRef={headingRef}
238
+ />
239
+ ) : null}
240
+ </Root>
241
+ );
242
+ }
243
+
244
+ function RequestAccessDialog({
245
+ open,
246
+ onOpenChange,
247
+ request,
248
+ sentFocusRef,
249
+ }: {
250
+ open: boolean;
251
+ onOpenChange: (open: boolean) => void;
252
+ request: ResourceAccessScreenRequest;
253
+ sentFocusRef: RefObject<HTMLElement | null>;
254
+ }) {
255
+ const t = useT();
256
+ const [note, setNote] = useState("");
257
+ const [failed, setFailed] = useState(false);
258
+ // Once sent, Request access is gone, so focus can't return to it.
259
+ const restoreFocusRef = useRef<HTMLElement | null>(null);
260
+ const errorCode = failed ? (request.error?.errorCode ?? null) : null;
261
+
262
+ const send = async () => {
263
+ setFailed(false);
264
+ try {
265
+ await request.onRequest(note);
266
+ setNote("");
267
+ restoreFocusRef.current = sentFocusRef.current;
268
+ onOpenChange(false);
269
+ } catch {
270
+ setFailed(true);
271
+ }
272
+ };
273
+
274
+ return (
275
+ <Dialog
276
+ open={open}
277
+ onOpenChange={(next) => {
278
+ if (!next) setFailed(false);
279
+ onOpenChange(next);
280
+ }}
281
+ title={t("agentChat.accessGate.requestAccess", {
282
+ defaultValue: "Request access",
283
+ })}
284
+ closeLabel={t("agentChat.accessGate.cancel", { defaultValue: "Cancel" })}
285
+ size="small"
286
+ restoreFocusRef={restoreFocusRef}
287
+ footer={
288
+ <div className="flex justify-end gap-2">
289
+ <ActionButton emphasis="outline" onPress={() => onOpenChange(false)}>
290
+ {t("agentChat.accessGate.cancel", { defaultValue: "Cancel" })}
291
+ </ActionButton>
292
+ <ActionButton
293
+ pending={request.sending}
294
+ disabled={request.sending}
295
+ onPress={() => void send()}
296
+ >
297
+ {t("agentChat.accessGate.sendRequest", {
298
+ defaultValue: "Send request",
299
+ })}
300
+ </ActionButton>
301
+ </div>
302
+ }
303
+ >
304
+ <TextArea
305
+ label={t("agentChat.accessGate.requestNoteLabel", {
306
+ defaultValue: "Note (optional)",
307
+ })}
308
+ placeholder={t("agentChat.accessGate.requestNotePlaceholder", {
309
+ defaultValue: "Add a note for the owner",
310
+ })}
311
+ value={note}
312
+ onChange={setNote}
313
+ maxLength={500}
314
+ rows={3}
315
+ autoFocus
316
+ errorMessage={
317
+ failed
318
+ ? errorCode === "access_request_rate_limited"
319
+ ? t("agentChat.accessGate.requestRateLimited", {
320
+ defaultValue: "Too many requests right now. Try again later.",
321
+ })
322
+ : t("agentChat.accessGate.requestFailed", {
323
+ defaultValue: "Couldn't send your request. Try again.",
324
+ })
325
+ : undefined
326
+ }
327
+ invalid={failed}
328
+ />
329
+ </Dialog>
330
+ );
331
+ }
@@ -49,14 +49,37 @@ const sharesData = vi.hoisted(() => ({
49
49
  },
50
50
  }));
51
51
 
52
+ const accessRequestsData = vi.hoisted(() => ({
53
+ current: [] as unknown[],
54
+ hasMore: false,
55
+ isError: false,
56
+ }));
57
+ const refetchRequests = vi.hoisted(() => vi.fn(async () => undefined));
58
+ const approveRequest = vi.hoisted(() => vi.fn());
59
+ const queriedActions = vi.hoisted(() => [] as string[]);
60
+
52
61
  vi.mock("@agent-native/core/client/use-action", () => ({
53
- useActionQuery: () => ({
54
- data: sharesData.current,
55
- isError: sharesError.current,
56
- refetch: refetchShares,
57
- }),
62
+ useActionQuery: (name: string) => {
63
+ queriedActions.push(name);
64
+ return name === "list-resource-access-requests"
65
+ ? {
66
+ data: {
67
+ requests: accessRequestsData.current,
68
+ hasMore: accessRequestsData.hasMore,
69
+ },
70
+ isError: accessRequestsData.isError,
71
+ refetch: refetchRequests,
72
+ }
73
+ : {
74
+ data: sharesData.current,
75
+ isError: sharesError.current,
76
+ refetch: refetchShares,
77
+ };
78
+ },
58
79
  useActionMutation: (name: string) => ({
59
80
  mutate: name === "share-resource" ? shareMutate : otherMutate,
81
+ mutateAsync:
82
+ name === "approve-resource-access-request" ? approveRequest : otherMutate,
60
83
  }),
61
84
  }));
62
85
 
@@ -185,6 +208,12 @@ describe("ShareButton", () => {
185
208
  );
186
209
  shareMutate.mockReset();
187
210
  otherMutate.mockReset();
211
+ approveRequest.mockReset().mockResolvedValue(undefined);
212
+ accessRequestsData.current = [];
213
+ accessRequestsData.hasMore = false;
214
+ accessRequestsData.isError = false;
215
+ refetchRequests.mockClear();
216
+ queriedActions.length = 0;
188
217
  refetchShares.mockClear();
189
218
  popoverInteractOutsideHandlers.length = 0;
190
219
  sheetInteractOutsideHandlers.length = 0;
@@ -1207,6 +1236,143 @@ describe("ShareButton", () => {
1207
1236
  expect(container.textContent).not.toContain("owner@example.com");
1208
1237
  });
1209
1238
 
1239
+ const patRequest = {
1240
+ id: "req-1",
1241
+ generation: 3,
1242
+ state: "pending",
1243
+ requester: { email: "requester@example.test", name: "Pat Example" },
1244
+ note: "Need this for the launch review.",
1245
+ requestedAt: "2026-10-01T10:00:00.000Z",
1246
+ decidedAt: null,
1247
+ grantedRole: null,
1248
+ resource: {
1249
+ type: "document",
1250
+ id: "doc-1",
1251
+ label: "Document",
1252
+ title: "Launch plan",
1253
+ path: "/page/doc-1",
1254
+ },
1255
+ };
1256
+
1257
+ async function renderWithRequests() {
1258
+ await act(async () => {
1259
+ root.render(
1260
+ <TooltipProvider>
1261
+ <QueryClientProvider client={queryClient}>
1262
+ <ShareButton
1263
+ resourceType="document"
1264
+ resourceId="doc-1"
1265
+ peopleTabLabel="People"
1266
+ agentsTabLabel="Agents"
1267
+ peopleAccessLabel="Who has access"
1268
+ agentTabContent={<button type="button">Copy agent prompt</button>}
1269
+ />
1270
+ </QueryClientProvider>
1271
+ </TooltipProvider>,
1272
+ );
1273
+ });
1274
+ }
1275
+
1276
+ async function allowPat() {
1277
+ const allow = container.querySelector<HTMLButtonElement>(
1278
+ 'button[aria-label="Allow Pat Example"]',
1279
+ );
1280
+ await act(async () => allow!.click());
1281
+ }
1282
+
1283
+ it("lists access requests above who has access, and Allow grants Viewer", async () => {
1284
+ accessRequestsData.current = [patRequest];
1285
+ await renderWithRequests();
1286
+
1287
+ const text = container.textContent ?? "";
1288
+ expect(text).toContain("Need this for the launch review.");
1289
+ expect(text.indexOf("Access requests")).toBeGreaterThan(-1);
1290
+ expect(text.indexOf("Access requests")).toBeLessThan(
1291
+ text.indexOf("Who has access"),
1292
+ );
1293
+
1294
+ await allowPat();
1295
+ expect(approveRequest).toHaveBeenCalledWith({
1296
+ requestId: "req-1",
1297
+ generation: 3,
1298
+ role: "viewer",
1299
+ });
1300
+ });
1301
+
1302
+ it("reloads the requests when someone else already handled one", async () => {
1303
+ accessRequestsData.current = [patRequest];
1304
+ approveRequest.mockRejectedValueOnce(
1305
+ Object.assign(new Error("stale"), {
1306
+ status: 409,
1307
+ errorCode: "access_request_stale",
1308
+ }),
1309
+ );
1310
+ await renderWithRequests();
1311
+
1312
+ await allowPat();
1313
+
1314
+ expect(refetchRequests).toHaveBeenCalledTimes(1);
1315
+ expect(container.textContent).toContain(
1316
+ "Someone already handled this request, or it changed.",
1317
+ );
1318
+ });
1319
+
1320
+ it("says when the requester couldn't be emailed that they're in", async () => {
1321
+ accessRequestsData.current = [patRequest];
1322
+ approveRequest.mockResolvedValueOnce({
1323
+ state: "approved",
1324
+ role: "viewer",
1325
+ email: "failed",
1326
+ });
1327
+ await renderWithRequests();
1328
+
1329
+ await allowPat();
1330
+
1331
+ expect(container.querySelector('[role="status"]')?.textContent).toBe(
1332
+ "Pat Example has access, but we couldn't email them.",
1333
+ );
1334
+ });
1335
+
1336
+ it("keeps saying the email failed when the list can't reload", async () => {
1337
+ accessRequestsData.current = [patRequest];
1338
+ approveRequest.mockImplementationOnce(async () => {
1339
+ accessRequestsData.isError = true;
1340
+ return { state: "approved", role: "viewer", email: "failed" };
1341
+ });
1342
+ await renderWithRequests();
1343
+
1344
+ await allowPat();
1345
+
1346
+ expect(container.textContent).toContain("Couldn't load access requests.");
1347
+ expect(container.querySelector('[role="status"]')?.textContent).toBe(
1348
+ "Pat Example has access, but we couldn't email them.",
1349
+ );
1350
+ });
1351
+
1352
+ it("says when older requests are past the list", async () => {
1353
+ accessRequestsData.current = [patRequest];
1354
+ accessRequestsData.hasMore = true;
1355
+ await renderWithRequests();
1356
+
1357
+ expect(container.textContent).toContain("Showing the 1 newest requests.");
1358
+ });
1359
+
1360
+ it("doesn't read access requests for someone who can't manage access", async () => {
1361
+ sharesData.current = { ...sharesData.current, role: "viewer" };
1362
+ await act(async () => {
1363
+ root.render(
1364
+ <TooltipProvider>
1365
+ <QueryClientProvider client={queryClient}>
1366
+ <ShareButton resourceType="document" resourceId="doc-1" />
1367
+ </QueryClientProvider>
1368
+ </TooltipProvider>,
1369
+ );
1370
+ });
1371
+
1372
+ expect(container.textContent).toContain("People with access");
1373
+ expect(queriedActions).not.toContain("list-resource-access-requests");
1374
+ });
1375
+
1210
1376
  // Keep the non-source-locale provider test last: react-i18next's global
1211
1377
  // fallback instance otherwise leaks the selected language into tests that
1212
1378
  // intentionally exercise providerless compatibility.
@@ -57,6 +57,7 @@ import type {
57
57
  UIEvent as ReactUIEvent,
58
58
  } from "react";
59
59
 
60
+ import { AccessRequestsSection } from "./AccessRequestsSection.js";
60
61
  import { AgentShareSection } from "./AgentShareSection.js";
61
62
 
62
63
  export interface ShareButtonProps {
@@ -462,6 +463,12 @@ function SharePanel(
462
463
  const generalAccessLabel =
463
464
  props.generalAccessLabel ??
464
465
  t("agentChat.share.generalAccess", { defaultValue: "General access" });
466
+ const accessRequests = canManage ? (
467
+ <AccessRequestsSection
468
+ resourceType={props.resourceType}
469
+ resourceId={props.resourceId}
470
+ />
471
+ ) : null;
465
472
  const shareLinks = (
466
473
  <>
467
474
  {props.shareUrl ? (
@@ -586,7 +593,10 @@ function SharePanel(
586
593
 
587
594
  <div className={cn("mb-4 space-y-4", props.agentTabContent && "order-1")}>
588
595
  {!props.agentTabContent ? (
589
- <div className="text-sm font-semibold">{peopleAccessLabel}</div>
596
+ <>
597
+ {accessRequests}
598
+ <div className="text-sm font-semibold">{peopleAccessLabel}</div>
599
+ </>
590
600
  ) : null}
591
601
  {canManage ? (
592
602
  <div className="space-y-2">
@@ -711,7 +721,10 @@ function SharePanel(
711
721
  ) : null}
712
722
 
713
723
  {props.agentTabContent ? (
714
- <div className="text-sm font-semibold">{peopleAccessLabel}</div>
724
+ <>
725
+ {accessRequests}
726
+ <div className="text-sm font-semibold">{peopleAccessLabel}</div>
727
+ </>
715
728
  ) : null}
716
729
  <ul className="flex list-none flex-col gap-1 p-0 m-0">
717
730
  {data?.ownerEmail ? (
@@ -19,9 +19,27 @@ const sharesQuery = {
19
19
  refetch: vi.fn(),
20
20
  };
21
21
 
22
+ const requestsQuery = {
23
+ data: [] as unknown[],
24
+ isError: false,
25
+ refetch: vi.fn(),
26
+ };
27
+ const approveRequest = vi.fn(async () => undefined);
28
+
22
29
  vi.mock("@agent-native/core/client/use-action", () => ({
23
- useActionQuery: () => sharesQuery,
24
- useActionMutation: () => ({ mutate: vi.fn(), isPending: false }),
30
+ useActionQuery: (name: string) =>
31
+ name === "list-resource-access-requests"
32
+ ? {
33
+ ...requestsQuery,
34
+ data: { requests: requestsQuery.data, hasMore: false },
35
+ }
36
+ : sharesQuery,
37
+ useActionMutation: (name: string) => ({
38
+ mutate: vi.fn(),
39
+ mutateAsync:
40
+ name === "approve-resource-access-request" ? approveRequest : vi.fn(),
41
+ isPending: false,
42
+ }),
25
43
  }));
26
44
  vi.mock("@agent-native/core/client/i18n", () => ({
27
45
  useT: () => (key: string, values?: Record<string, string>) =>
@@ -51,6 +69,7 @@ beforeEach(() => {
51
69
  });
52
70
 
53
71
  afterEach(() => {
72
+ requestsQuery.data = [];
54
73
  act(() => root.unmount());
55
74
  queryClient.clear();
56
75
  container.remove();
@@ -195,3 +214,53 @@ describe("ShareDialog primitive normalization", () => {
195
214
  expect(onClose).toHaveBeenCalledTimes(1);
196
215
  });
197
216
  });
217
+
218
+ describe("ShareDialog access requests", () => {
219
+ it("lists pending requests above people with access and allows as Viewer", async () => {
220
+ requestsQuery.data = [
221
+ {
222
+ id: "req-1",
223
+ generation: 3,
224
+ state: "pending",
225
+ requester: { email: "requester@example.test", name: "Pat Example" },
226
+ note: "Need this for the launch review.",
227
+ requestedAt: "2026-10-01T10:00:00.000Z",
228
+ decidedAt: null,
229
+ grantedRole: null,
230
+ resource: {
231
+ type: "document",
232
+ id: "doc-1",
233
+ label: "Document",
234
+ title: "Quarterly plan",
235
+ path: "/page/doc-1",
236
+ },
237
+ },
238
+ ];
239
+ await renderDialog();
240
+
241
+ const text = document.body.textContent ?? "";
242
+ expect(text).toContain("Pat Example");
243
+ expect(text).toContain("requester@example.test");
244
+ expect(text).toContain("Need this for the launch review.");
245
+ expect(text.indexOf("share.accessRequests")).toBeGreaterThan(-1);
246
+ expect(text.indexOf("share.accessRequests")).toBeLessThan(
247
+ text.indexOf("share.peopleWithAccess"),
248
+ );
249
+
250
+ const allow = document.body.querySelector<HTMLButtonElement>(
251
+ 'button[aria-label="agentChat.share.allowRequestFrom"]',
252
+ );
253
+ await act(async () => allow?.click());
254
+ expect(approveRequest).toHaveBeenCalledWith({
255
+ requestId: "req-1",
256
+ generation: 3,
257
+ role: "viewer",
258
+ });
259
+ });
260
+
261
+ it("shows no requests section when nothing is pending", async () => {
262
+ await renderDialog();
263
+
264
+ expect(document.body.textContent).not.toContain("share.accessRequests");
265
+ });
266
+ });