@agent-native/core 0.8.2 → 0.10.0

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 (305) hide show
  1. package/README.md +4 -4
  2. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  3. package/dist/agent/engine/builder-engine.js +5 -4
  4. package/dist/agent/engine/builder-engine.js.map +1 -1
  5. package/dist/agent/engine/registry.d.ts +6 -3
  6. package/dist/agent/engine/registry.d.ts.map +1 -1
  7. package/dist/agent/engine/registry.js +8 -17
  8. package/dist/agent/engine/registry.js.map +1 -1
  9. package/dist/agent/production-agent.d.ts +1 -1
  10. package/dist/agent/production-agent.d.ts.map +1 -1
  11. package/dist/agent/production-agent.js +28 -11
  12. package/dist/agent/production-agent.js.map +1 -1
  13. package/dist/agent/run-manager.d.ts +10 -0
  14. package/dist/agent/run-manager.d.ts.map +1 -1
  15. package/dist/agent/run-manager.js +89 -7
  16. package/dist/agent/run-manager.js.map +1 -1
  17. package/dist/agent/run-store.d.ts +4 -1
  18. package/dist/agent/run-store.d.ts.map +1 -1
  19. package/dist/agent/run-store.js +6 -5
  20. package/dist/agent/run-store.js.map +1 -1
  21. package/dist/agent/thread-data-builder.d.ts +12 -0
  22. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  23. package/dist/agent/thread-data-builder.js +96 -0
  24. package/dist/agent/thread-data-builder.js.map +1 -1
  25. package/dist/cli/create.d.ts +9 -0
  26. package/dist/cli/create.d.ts.map +1 -1
  27. package/dist/cli/create.js +29 -11
  28. package/dist/cli/create.js.map +1 -1
  29. package/dist/cli/index.js +177 -22
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/cli/workspace-dev.js +66 -5
  32. package/dist/cli/workspace-dev.js.map +1 -1
  33. package/dist/client/AgentPanel.d.ts.map +1 -1
  34. package/dist/client/AgentPanel.js +6 -20
  35. package/dist/client/AgentPanel.js.map +1 -1
  36. package/dist/client/AssistantChat.d.ts.map +1 -1
  37. package/dist/client/AssistantChat.js +146 -107
  38. package/dist/client/AssistantChat.js.map +1 -1
  39. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  40. package/dist/client/agent-chat-adapter.js +143 -22
  41. package/dist/client/agent-chat-adapter.js.map +1 -1
  42. package/dist/client/agent-sidebar-state.d.ts +3 -0
  43. package/dist/client/agent-sidebar-state.d.ts.map +1 -0
  44. package/dist/client/agent-sidebar-state.js +24 -0
  45. package/dist/client/agent-sidebar-state.js.map +1 -0
  46. package/dist/client/analytics.d.ts +39 -0
  47. package/dist/client/analytics.d.ts.map +1 -1
  48. package/dist/client/analytics.js +74 -0
  49. package/dist/client/analytics.js.map +1 -1
  50. package/dist/client/components/PresenceBar.d.ts.map +1 -1
  51. package/dist/client/components/PresenceBar.js +21 -15
  52. package/dist/client/components/PresenceBar.js.map +1 -1
  53. package/dist/client/components/ui/tooltip.d.ts +2 -1
  54. package/dist/client/components/ui/tooltip.d.ts.map +1 -1
  55. package/dist/client/components/ui/tooltip.js +9 -2
  56. package/dist/client/components/ui/tooltip.js.map +1 -1
  57. package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
  58. package/dist/client/composer/ComposerPlusMenu.js +51 -17
  59. package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
  60. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  61. package/dist/client/composer/PromptComposer.js +30 -0
  62. package/dist/client/composer/PromptComposer.js.map +1 -1
  63. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  64. package/dist/client/composer/TiptapComposer.js +31 -5
  65. package/dist/client/composer/TiptapComposer.js.map +1 -1
  66. package/dist/client/composer/VoiceButton.d.ts.map +1 -1
  67. package/dist/client/composer/VoiceButton.js +9 -8
  68. package/dist/client/composer/VoiceButton.js.map +1 -1
  69. package/dist/client/dev-overlay/DevOverlay.d.ts.map +1 -1
  70. package/dist/client/dev-overlay/DevOverlay.js +4 -3
  71. package/dist/client/dev-overlay/DevOverlay.js.map +1 -1
  72. package/dist/client/error-format.d.ts.map +1 -1
  73. package/dist/client/error-format.js +6 -0
  74. package/dist/client/error-format.js.map +1 -1
  75. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  76. package/dist/client/extensions/EmbeddedExtension.js +14 -3
  77. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  78. package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -1
  79. package/dist/client/extensions/ExtensionEditor.js +6 -5
  80. package/dist/client/extensions/ExtensionEditor.js.map +1 -1
  81. package/dist/client/extensions/ExtensionSlot.d.ts.map +1 -1
  82. package/dist/client/extensions/ExtensionSlot.js +2 -1
  83. package/dist/client/extensions/ExtensionSlot.js.map +1 -1
  84. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  85. package/dist/client/extensions/ExtensionViewer.js +40 -19
  86. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  87. package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
  88. package/dist/client/extensions/ExtensionsSidebarSection.js +52 -51
  89. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  90. package/dist/client/index.d.ts +2 -1
  91. package/dist/client/index.d.ts.map +1 -1
  92. package/dist/client/index.js +2 -1
  93. package/dist/client/index.js.map +1 -1
  94. package/dist/client/integrations/IntegrationCard.d.ts.map +1 -1
  95. package/dist/client/integrations/IntegrationCard.js +2 -1
  96. package/dist/client/integrations/IntegrationCard.js.map +1 -1
  97. package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
  98. package/dist/client/integrations/IntegrationsPanel.js +3 -2
  99. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  100. package/dist/client/notifications/NotificationsBell.d.ts.map +1 -1
  101. package/dist/client/notifications/NotificationsBell.js +42 -6
  102. package/dist/client/notifications/NotificationsBell.js.map +1 -1
  103. package/dist/client/onboarding/OnboardingPanel.d.ts.map +1 -1
  104. package/dist/client/onboarding/OnboardingPanel.js +3 -2
  105. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  106. package/dist/client/onboarding/SetupButton.d.ts.map +1 -1
  107. package/dist/client/onboarding/SetupButton.js +14 -13
  108. package/dist/client/onboarding/SetupButton.js.map +1 -1
  109. package/dist/client/org/InvitationBanner.d.ts +8 -2
  110. package/dist/client/org/InvitationBanner.d.ts.map +1 -1
  111. package/dist/client/org/InvitationBanner.js +28 -7
  112. package/dist/client/org/InvitationBanner.js.map +1 -1
  113. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  114. package/dist/client/org/OrgSwitcher.js +29 -5
  115. package/dist/client/org/OrgSwitcher.js.map +1 -1
  116. package/dist/client/org/TeamPage.d.ts.map +1 -1
  117. package/dist/client/org/TeamPage.js +9 -7
  118. package/dist/client/org/TeamPage.js.map +1 -1
  119. package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
  120. package/dist/client/resources/ResourceEditor.js +2 -1
  121. package/dist/client/resources/ResourceEditor.js.map +1 -1
  122. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  123. package/dist/client/resources/ResourcesPanel.js +48 -14
  124. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  125. package/dist/client/resources/use-mcp-servers.d.ts +2 -0
  126. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  127. package/dist/client/resources/use-mcp-servers.js +59 -3
  128. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  129. package/dist/client/settings/AgentsSection.d.ts.map +1 -1
  130. package/dist/client/settings/AgentsSection.js +8 -7
  131. package/dist/client/settings/AgentsSection.js.map +1 -1
  132. package/dist/client/settings/AutomationsSection.d.ts.map +1 -1
  133. package/dist/client/settings/AutomationsSection.js +4 -3
  134. package/dist/client/settings/AutomationsSection.js.map +1 -1
  135. package/dist/client/settings/SecretsSection.d.ts.map +1 -1
  136. package/dist/client/settings/SecretsSection.js +11 -1
  137. package/dist/client/settings/SecretsSection.js.map +1 -1
  138. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  139. package/dist/client/settings/SettingsPanel.js +15 -12
  140. package/dist/client/settings/SettingsPanel.js.map +1 -1
  141. package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -1
  142. package/dist/client/settings/VoiceTranscriptionSection.js +13 -30
  143. package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
  144. package/dist/client/settings/index.d.ts +1 -1
  145. package/dist/client/settings/index.d.ts.map +1 -1
  146. package/dist/client/settings/index.js.map +1 -1
  147. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  148. package/dist/client/settings/useBuilderStatus.js +27 -1
  149. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  150. package/dist/client/sharing/ShareButton.d.ts +4 -0
  151. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  152. package/dist/client/sharing/ShareButton.js +5 -1
  153. package/dist/client/sharing/ShareButton.js.map +1 -1
  154. package/dist/client/sse-event-processor.d.ts +1 -1
  155. package/dist/client/sse-event-processor.d.ts.map +1 -1
  156. package/dist/client/sse-event-processor.js +59 -11
  157. package/dist/client/sse-event-processor.js.map +1 -1
  158. package/dist/client/use-db-sync.d.ts.map +1 -1
  159. package/dist/client/use-db-sync.js +100 -19
  160. package/dist/client/use-db-sync.js.map +1 -1
  161. package/dist/client/use-session.d.ts.map +1 -1
  162. package/dist/client/use-session.js +14 -2
  163. package/dist/client/use-session.js.map +1 -1
  164. package/dist/collab/client.d.ts +1 -0
  165. package/dist/collab/client.d.ts.map +1 -1
  166. package/dist/collab/client.js +18 -1
  167. package/dist/collab/client.js.map +1 -1
  168. package/dist/deploy/build.d.ts.map +1 -1
  169. package/dist/deploy/build.js +5 -0
  170. package/dist/deploy/build.js.map +1 -1
  171. package/dist/deploy/route-discovery.d.ts.map +1 -1
  172. package/dist/deploy/route-discovery.js +1 -0
  173. package/dist/deploy/route-discovery.js.map +1 -1
  174. package/dist/deploy/workspace-core.d.ts +1 -1
  175. package/dist/deploy/workspace-core.d.ts.map +1 -1
  176. package/dist/deploy/workspace-core.js +1 -0
  177. package/dist/deploy/workspace-core.js.map +1 -1
  178. package/dist/extensions/actions.d.ts.map +1 -1
  179. package/dist/extensions/actions.js +17 -3
  180. package/dist/extensions/actions.js.map +1 -1
  181. package/dist/extensions/routes.js +1 -1
  182. package/dist/extensions/routes.js.map +1 -1
  183. package/dist/extensions/schema.d.ts +14 -14
  184. package/dist/extensions/schema.d.ts.map +1 -1
  185. package/dist/extensions/schema.js +4 -4
  186. package/dist/extensions/schema.js.map +1 -1
  187. package/dist/extensions/store.d.ts.map +1 -1
  188. package/dist/extensions/store.js +23 -0
  189. package/dist/extensions/store.js.map +1 -1
  190. package/dist/extensions/theme.d.ts +8 -1
  191. package/dist/extensions/theme.d.ts.map +1 -1
  192. package/dist/extensions/theme.js +43 -34
  193. package/dist/extensions/theme.js.map +1 -1
  194. package/dist/mcp-client/routes.d.ts +1 -0
  195. package/dist/mcp-client/routes.d.ts.map +1 -1
  196. package/dist/mcp-client/routes.js +28 -1
  197. package/dist/mcp-client/routes.js.map +1 -1
  198. package/dist/org/auto-join-domain.d.ts +28 -0
  199. package/dist/org/auto-join-domain.d.ts.map +1 -0
  200. package/dist/org/auto-join-domain.js +92 -0
  201. package/dist/org/auto-join-domain.js.map +1 -0
  202. package/dist/org/index.d.ts +2 -0
  203. package/dist/org/index.d.ts.map +1 -1
  204. package/dist/org/index.js +1 -0
  205. package/dist/org/index.js.map +1 -1
  206. package/dist/scripts/db/exec.d.ts.map +1 -1
  207. package/dist/scripts/db/exec.js +27 -1
  208. package/dist/scripts/db/exec.js.map +1 -1
  209. package/dist/scripts/db/index.d.ts.map +1 -1
  210. package/dist/scripts/db/index.js +1 -0
  211. package/dist/scripts/db/index.js.map +1 -1
  212. package/dist/scripts/db/reset-dev-owner.d.ts +27 -0
  213. package/dist/scripts/db/reset-dev-owner.d.ts.map +1 -0
  214. package/dist/scripts/db/reset-dev-owner.js +225 -0
  215. package/dist/scripts/db/reset-dev-owner.js.map +1 -0
  216. package/dist/scripts/db/scoping.d.ts.map +1 -1
  217. package/dist/scripts/db/scoping.js +15 -30
  218. package/dist/scripts/db/scoping.js.map +1 -1
  219. package/dist/scripts/dev-session.d.ts +46 -0
  220. package/dist/scripts/dev-session.d.ts.map +1 -0
  221. package/dist/scripts/dev-session.js +81 -0
  222. package/dist/scripts/dev-session.js.map +1 -0
  223. package/dist/scripts/runner.d.ts.map +1 -1
  224. package/dist/scripts/runner.js +21 -0
  225. package/dist/scripts/runner.js.map +1 -1
  226. package/dist/secrets/register.d.ts +1 -1
  227. package/dist/secrets/register.d.ts.map +1 -1
  228. package/dist/secrets/register.js +4 -2
  229. package/dist/secrets/register.js.map +1 -1
  230. package/dist/secrets/routes.d.ts.map +1 -1
  231. package/dist/secrets/routes.js +32 -0
  232. package/dist/secrets/routes.js.map +1 -1
  233. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  234. package/dist/server/agent-chat-plugin.js +77 -102
  235. package/dist/server/agent-chat-plugin.js.map +1 -1
  236. package/dist/server/auth.d.ts.map +1 -1
  237. package/dist/server/auth.js +33 -0
  238. package/dist/server/auth.js.map +1 -1
  239. package/dist/server/better-auth-instance.d.ts.map +1 -1
  240. package/dist/server/better-auth-instance.js +11 -0
  241. package/dist/server/better-auth-instance.js.map +1 -1
  242. package/dist/server/builder-browser.d.ts.map +1 -1
  243. package/dist/server/builder-browser.js +169 -68
  244. package/dist/server/builder-browser.js.map +1 -1
  245. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  246. package/dist/server/core-routes-plugin.js +56 -13
  247. package/dist/server/core-routes-plugin.js.map +1 -1
  248. package/dist/server/credential-provider.d.ts +49 -6
  249. package/dist/server/credential-provider.d.ts.map +1 -1
  250. package/dist/server/credential-provider.js +133 -38
  251. package/dist/server/credential-provider.js.map +1 -1
  252. package/dist/server/design-token-utils.d.ts +13 -2
  253. package/dist/server/design-token-utils.d.ts.map +1 -1
  254. package/dist/server/design-token-utils.js +48 -16
  255. package/dist/server/design-token-utils.js.map +1 -1
  256. package/dist/server/framework-request-handler.d.ts.map +1 -1
  257. package/dist/server/framework-request-handler.js +31 -0
  258. package/dist/server/framework-request-handler.js.map +1 -1
  259. package/dist/server/google-realtime-session.d.ts.map +1 -1
  260. package/dist/server/google-realtime-session.js +19 -6
  261. package/dist/server/google-realtime-session.js.map +1 -1
  262. package/dist/server/index.d.ts +2 -0
  263. package/dist/server/index.d.ts.map +1 -1
  264. package/dist/server/index.js +2 -0
  265. package/dist/server/index.js.map +1 -1
  266. package/dist/server/onboarding-html.d.ts.map +1 -1
  267. package/dist/server/onboarding-html.js +142 -14
  268. package/dist/server/onboarding-html.js.map +1 -1
  269. package/dist/server/request-context.d.ts +17 -0
  270. package/dist/server/request-context.d.ts.map +1 -1
  271. package/dist/server/request-context.js +40 -1
  272. package/dist/server/request-context.js.map +1 -1
  273. package/dist/server/sentry-plugin.d.ts +11 -0
  274. package/dist/server/sentry-plugin.d.ts.map +1 -0
  275. package/dist/server/sentry-plugin.js +116 -0
  276. package/dist/server/sentry-plugin.js.map +1 -0
  277. package/dist/server/sentry.d.ts +92 -0
  278. package/dist/server/sentry.d.ts.map +1 -0
  279. package/dist/server/sentry.js +287 -0
  280. package/dist/server/sentry.js.map +1 -0
  281. package/dist/server/transcribe-voice.d.ts +2 -4
  282. package/dist/server/transcribe-voice.d.ts.map +1 -1
  283. package/dist/server/transcribe-voice.js +4 -16
  284. package/dist/server/transcribe-voice.js.map +1 -1
  285. package/dist/server/voice-providers-status.d.ts.map +1 -1
  286. package/dist/server/voice-providers-status.js +19 -35
  287. package/dist/server/voice-providers-status.js.map +1 -1
  288. package/dist/styles/agent-native.css +15 -0
  289. package/docs/content/cloneable-saas.md +7 -9
  290. package/docs/content/deployment.md +6 -2
  291. package/docs/content/dispatch.md +1 -1
  292. package/docs/content/extensions.md +177 -142
  293. package/docs/content/faq.md +2 -2
  294. package/docs/content/getting-started.md +13 -11
  295. package/docs/content/multi-app-workspace.md +2 -2
  296. package/docs/content/observability.md +47 -0
  297. package/docs/content/pure-agent-apps.md +1 -1
  298. package/docs/content/template-clips.md +3 -3
  299. package/docs/content/template-design.md +3 -3
  300. package/docs/content/template-dispatch.md +1 -1
  301. package/docs/content/template-forms.md +1 -1
  302. package/docs/content/template-mail.md +1 -1
  303. package/docs/content/what-is-agent-native.md +4 -4
  304. package/docs/content/workspace.md +1 -1
  305. package/package.json +1 -1
@@ -1,5 +1,25 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import { agentNativePath } from "./api-path.js";
3
+ const POLL_ABORT_MIN_MS = 10_000;
4
+ function getPollAbortMs(interval) {
5
+ return Math.max(POLL_ABORT_MIN_MS, interval * 4);
6
+ }
7
+ async function fetchPollJson(pollUrl, since, interval) {
8
+ const controller = typeof AbortController === "undefined" ? null : new AbortController();
9
+ const timeout = controller
10
+ ? setTimeout(() => controller.abort(), getPollAbortMs(interval))
11
+ : null;
12
+ try {
13
+ const res = await fetch(`${pollUrl}?since=${since}`, controller ? { signal: controller.signal } : undefined);
14
+ if (!res.ok)
15
+ throw new Error("HTTP " + res.status);
16
+ return res.json();
17
+ }
18
+ finally {
19
+ if (timeout)
20
+ clearTimeout(timeout);
21
+ }
22
+ }
3
23
  /**
4
24
  * Hook that polls /_agent-native/poll for DB change events and invalidates
5
25
  * react-query caches when changes are detected.
@@ -28,14 +48,23 @@ export function useDbSync(options = {}) {
28
48
  let versionRef = 0;
29
49
  let timer = null;
30
50
  let stopped = false;
31
- async function poll() {
51
+ let inFlight = false;
52
+ function schedulePoll() {
32
53
  if (stopped)
33
54
  return;
55
+ if (timer)
56
+ clearTimeout(timer);
57
+ timer = setTimeout(() => {
58
+ timer = null;
59
+ void poll();
60
+ }, interval);
61
+ }
62
+ async function poll() {
63
+ if (stopped || inFlight)
64
+ return;
65
+ inFlight = true;
34
66
  try {
35
- const res = await fetch(`${pollUrl}?since=${versionRef}`);
36
- if (!res.ok)
37
- throw new Error("HTTP " + res.status);
38
- const data = await res.json();
67
+ const data = await fetchPollJson(pollUrl, versionRef, interval);
39
68
  const { version, events } = data;
40
69
  if (events.length > 0 && queryClient) {
41
70
  const ignore = ignoreSourceRef.current;
@@ -51,6 +80,8 @@ export function useDbSync(options = {}) {
51
80
  // (agent or HTTP) auto-refresh the UI — regardless of how the
52
81
  // template configured queryKeys / onEvent.
53
82
  queryClient.invalidateQueries({ queryKey: ["action"] });
83
+ queryClient.invalidateQueries({ queryKey: ["extension"] });
84
+ queryClient.invalidateQueries({ queryKey: ["extensions"] });
54
85
  queryClient.invalidateQueries({ queryKey: ["tool"] });
55
86
  queryClient.invalidateQueries({ queryKey: ["tools"] });
56
87
  }
@@ -66,16 +97,36 @@ export function useDbSync(options = {}) {
66
97
  catch {
67
98
  // Network error — will retry on next interval
68
99
  }
69
- if (!stopped) {
70
- timer = setTimeout(poll, interval);
100
+ finally {
101
+ inFlight = false;
102
+ schedulePoll();
103
+ }
104
+ }
105
+ function pollNow() {
106
+ if (typeof document !== "undefined" &&
107
+ document.visibilityState === "hidden") {
108
+ return;
71
109
  }
110
+ if (timer) {
111
+ clearTimeout(timer);
112
+ timer = null;
113
+ }
114
+ void poll();
115
+ }
116
+ function handleVisibilityChange() {
117
+ if (document.visibilityState === "visible")
118
+ pollNow();
72
119
  }
73
120
  // Initial poll immediately
74
- poll();
121
+ void poll();
122
+ window.addEventListener("focus", pollNow);
123
+ document.addEventListener("visibilitychange", handleVisibilityChange);
75
124
  return () => {
76
125
  stopped = true;
77
126
  if (timer)
78
127
  clearTimeout(timer);
128
+ window.removeEventListener("focus", pollNow);
129
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
79
130
  };
80
131
  }, [pollUrl, queryClient, interval]);
81
132
  }
@@ -107,30 +158,60 @@ export function useScreenRefreshKey(options = {}) {
107
158
  let versionRef = 0;
108
159
  let timer = null;
109
160
  let stopped = false;
110
- async function poll() {
161
+ let inFlight = false;
162
+ function schedulePoll() {
111
163
  if (stopped)
112
164
  return;
165
+ if (timer)
166
+ clearTimeout(timer);
167
+ timer = setTimeout(() => {
168
+ timer = null;
169
+ void poll();
170
+ }, interval);
171
+ }
172
+ async function poll() {
173
+ if (stopped || inFlight)
174
+ return;
175
+ inFlight = true;
113
176
  try {
114
- const res = await fetch(`${pollUrl}?since=${versionRef}`);
115
- if (res.ok) {
116
- const data = (await res.json());
117
- if (data.events?.some((e) => e.source === "screen-refresh")) {
118
- setKey((k) => k + 1);
119
- }
120
- versionRef = Math.max(versionRef, data.version);
177
+ const data = await fetchPollJson(pollUrl, versionRef, interval);
178
+ if (data.events?.some((e) => e.source === "screen-refresh")) {
179
+ setKey((k) => k + 1);
121
180
  }
181
+ versionRef = Math.max(versionRef, data.version);
122
182
  }
123
183
  catch {
124
184
  // Network error — retry on next interval.
125
185
  }
126
- if (!stopped)
127
- timer = setTimeout(poll, interval);
186
+ finally {
187
+ inFlight = false;
188
+ schedulePoll();
189
+ }
190
+ }
191
+ function pollNow() {
192
+ if (typeof document !== "undefined" &&
193
+ document.visibilityState === "hidden") {
194
+ return;
195
+ }
196
+ if (timer) {
197
+ clearTimeout(timer);
198
+ timer = null;
199
+ }
200
+ void poll();
201
+ }
202
+ function handleVisibilityChange() {
203
+ if (document.visibilityState === "visible")
204
+ pollNow();
128
205
  }
129
- poll();
206
+ void poll();
207
+ window.addEventListener("focus", pollNow);
208
+ document.addEventListener("visibilitychange", handleVisibilityChange);
130
209
  return () => {
131
210
  stopped = true;
132
211
  if (timer)
133
212
  clearTimeout(timer);
213
+ window.removeEventListener("focus", pollNow);
214
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
134
215
  };
135
216
  }, [pollUrl, interval]);
136
217
  return key;
@@ -1 +1 @@
1
- {"version":3,"file":"use-db-sync.js","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { agentNativePath } from \"./api-path.js\";\n\ninterface QueryClient {\n invalidateQueries(opts?: { queryKey?: string[] }): void;\n}\n\n/**\n * Hook that polls /_agent-native/poll for DB change events and invalidates\n * react-query caches when changes are detected.\n *\n * Works in all deployment environments (serverless, edge, long-lived server).\n *\n * @param options.queryClient - The react-query QueryClient instance\n * @param options.queryKeys - Array of query key prefixes to invalidate on change.\n * Default: [\"data\"]\n * @param options.pollUrl - Poll endpoint URL. Default: \"/_agent-native/poll\"\n * @param options.onEvent - Optional callback for each change event\n * @param options.interval - Poll interval in ms. Default: 2000\n * @param options.ignoreSource - Skip events whose `requestSource` matches this\n * value. Use a per-tab ID so the UI ignores its own writes while still\n * picking up changes from other tabs, agents, and scripts.\n */\nexport function useDbSync(\n options: {\n queryClient?: QueryClient;\n queryKeys?: string[];\n pollUrl?: string;\n /** @deprecated Use pollUrl instead */\n eventsUrl?: string;\n onEvent?: (data: any) => void;\n interval?: number;\n ignoreSource?: string;\n } = {},\n): void {\n const {\n queryClient,\n queryKeys = [\"data\"],\n pollUrl = agentNativePath(options.eventsUrl ?? \"/_agent-native/poll\"),\n interval = 2000,\n } = options;\n\n const onEventRef = useRef(options.onEvent);\n onEventRef.current = options.onEvent;\n\n const keysRef = useRef(queryKeys);\n keysRef.current = queryKeys;\n\n const ignoreSourceRef = useRef(options.ignoreSource);\n ignoreSourceRef.current = options.ignoreSource;\n\n useEffect(() => {\n let versionRef = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n\n async function poll() {\n if (stopped) return;\n try {\n const res = await fetch(`${pollUrl}?since=${versionRef}`);\n if (!res.ok) throw new Error(\"HTTP \" + res.status);\n const data = await res.json();\n const { version, events } = data as {\n version: number;\n events: Array<{ source: string; type: string; key?: string }>;\n };\n\n if (events.length > 0 && queryClient) {\n const ignore = ignoreSourceRef.current;\n const relevant = ignore\n ? events.filter((e: any) => e.requestSource !== ignore)\n : events;\n\n if (relevant.length > 0) {\n for (const key of keysRef.current) {\n queryClient.invalidateQueries({ queryKey: [key] });\n }\n\n // Framework-level invalidation: always invalidate framework query\n // keys on any non-own change event so that mutating actions\n // (agent or HTTP) auto-refresh the UI — regardless of how the\n // template configured queryKeys / onEvent.\n queryClient.invalidateQueries({ queryKey: [\"action\"] });\n queryClient.invalidateQueries({ queryKey: [\"tool\"] });\n queryClient.invalidateQueries({ queryKey: [\"tools\"] });\n }\n\n // Always forward all events to onEvent — templates can decide\n for (const evt of events) {\n onEventRef.current?.(evt);\n }\n }\n\n // Never decrease — protects against serverless instances with\n // slightly different version counters.\n versionRef = Math.max(versionRef, version);\n } catch {\n // Network error — will retry on next interval\n }\n if (!stopped) {\n timer = setTimeout(poll, interval);\n }\n }\n\n // Initial poll immediately\n poll();\n\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n };\n }, [pollUrl, queryClient, interval]);\n}\n\n/** @deprecated Use useDbSync instead */\nexport const useFileWatcher = useDbSync;\n\n/**\n * Subscribe to `refresh-screen` events from the agent. Returns an integer\n * that increments every time the agent invokes the framework's `refresh-screen`\n * tool. Apply it as a React `key` on the main content wrapper (the part\n * OUTSIDE the agent chat sidebar) so that region remounts and re-fetches its\n * data while the chat, sidebar, and any other persistent chrome keep their\n * in-flight state.\n *\n * Usage in a template's root:\n *\n * const screenKey = useScreenRefreshKey();\n * return (\n * <AppLayout>\n * <div key={screenKey}>\n * <Outlet />\n * </div>\n * </AppLayout>\n * );\n */\nexport function useScreenRefreshKey(\n options: { pollUrl?: string; interval?: number } = {},\n): number {\n const {\n pollUrl = agentNativePath(options.pollUrl ?? \"/_agent-native/poll\"),\n interval = 2000,\n } = options;\n const [key, setKey] = useState(0);\n\n useEffect(() => {\n let versionRef = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n\n async function poll() {\n if (stopped) return;\n try {\n const res = await fetch(`${pollUrl}?since=${versionRef}`);\n if (res.ok) {\n const data = (await res.json()) as {\n version: number;\n events: Array<{ source: string }>;\n };\n if (data.events?.some((e) => e.source === \"screen-refresh\")) {\n setKey((k) => k + 1);\n }\n versionRef = Math.max(versionRef, data.version);\n }\n } catch {\n // Network error — retry on next interval.\n }\n if (!stopped) timer = setTimeout(poll, interval);\n }\n\n poll();\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n };\n }, [pollUrl, interval]);\n\n return key;\n}\n"]}
1
+ {"version":3,"file":"use-db-sync.js","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { agentNativePath } from \"./api-path.js\";\n\ninterface QueryClient {\n invalidateQueries(opts?: { queryKey?: string[] }): void;\n}\n\nconst POLL_ABORT_MIN_MS = 10_000;\n\nfunction getPollAbortMs(interval: number): number {\n return Math.max(POLL_ABORT_MIN_MS, interval * 4);\n}\n\nasync function fetchPollJson<T>(\n pollUrl: string,\n since: number,\n interval: number,\n): Promise<T> {\n const controller =\n typeof AbortController === \"undefined\" ? null : new AbortController();\n const timeout = controller\n ? setTimeout(() => controller.abort(), getPollAbortMs(interval))\n : null;\n\n try {\n const res = await fetch(\n `${pollUrl}?since=${since}`,\n controller ? { signal: controller.signal } : undefined,\n );\n if (!res.ok) throw new Error(\"HTTP \" + res.status);\n return res.json();\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\n/**\n * Hook that polls /_agent-native/poll for DB change events and invalidates\n * react-query caches when changes are detected.\n *\n * Works in all deployment environments (serverless, edge, long-lived server).\n *\n * @param options.queryClient - The react-query QueryClient instance\n * @param options.queryKeys - Array of query key prefixes to invalidate on change.\n * Default: [\"data\"]\n * @param options.pollUrl - Poll endpoint URL. Default: \"/_agent-native/poll\"\n * @param options.onEvent - Optional callback for each change event\n * @param options.interval - Poll interval in ms. Default: 2000\n * @param options.ignoreSource - Skip events whose `requestSource` matches this\n * value. Use a per-tab ID so the UI ignores its own writes while still\n * picking up changes from other tabs, agents, and scripts.\n */\nexport function useDbSync(\n options: {\n queryClient?: QueryClient;\n queryKeys?: string[];\n pollUrl?: string;\n /** @deprecated Use pollUrl instead */\n eventsUrl?: string;\n onEvent?: (data: any) => void;\n interval?: number;\n ignoreSource?: string;\n } = {},\n): void {\n const {\n queryClient,\n queryKeys = [\"data\"],\n pollUrl = agentNativePath(options.eventsUrl ?? \"/_agent-native/poll\"),\n interval = 2000,\n } = options;\n\n const onEventRef = useRef(options.onEvent);\n onEventRef.current = options.onEvent;\n\n const keysRef = useRef(queryKeys);\n keysRef.current = queryKeys;\n\n const ignoreSourceRef = useRef(options.ignoreSource);\n ignoreSourceRef.current = options.ignoreSource;\n\n useEffect(() => {\n let versionRef = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n let inFlight = false;\n\n function schedulePoll() {\n if (stopped) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n void poll();\n }, interval);\n }\n\n async function poll() {\n if (stopped || inFlight) return;\n inFlight = true;\n try {\n const data = await fetchPollJson<{\n version: number;\n events: Array<{\n source: string;\n type: string;\n key?: string;\n requestSource?: string;\n }>;\n }>(pollUrl, versionRef, interval);\n const { version, events } = data as {\n version: number;\n events: Array<{\n source: string;\n type: string;\n key?: string;\n requestSource?: string;\n }>;\n };\n\n if (events.length > 0 && queryClient) {\n const ignore = ignoreSourceRef.current;\n const relevant = ignore\n ? events.filter((e: any) => e.requestSource !== ignore)\n : events;\n\n if (relevant.length > 0) {\n for (const key of keysRef.current) {\n queryClient.invalidateQueries({ queryKey: [key] });\n }\n\n // Framework-level invalidation: always invalidate framework query\n // keys on any non-own change event so that mutating actions\n // (agent or HTTP) auto-refresh the UI — regardless of how the\n // template configured queryKeys / onEvent.\n queryClient.invalidateQueries({ queryKey: [\"action\"] });\n queryClient.invalidateQueries({ queryKey: [\"extension\"] });\n queryClient.invalidateQueries({ queryKey: [\"extensions\"] });\n queryClient.invalidateQueries({ queryKey: [\"tool\"] });\n queryClient.invalidateQueries({ queryKey: [\"tools\"] });\n }\n\n // Always forward all events to onEvent — templates can decide\n for (const evt of events) {\n onEventRef.current?.(evt);\n }\n }\n\n // Never decrease — protects against serverless instances with\n // slightly different version counters.\n versionRef = Math.max(versionRef, version);\n } catch {\n // Network error — will retry on next interval\n } finally {\n inFlight = false;\n schedulePoll();\n }\n }\n\n function pollNow() {\n if (\n typeof document !== \"undefined\" &&\n document.visibilityState === \"hidden\"\n ) {\n return;\n }\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n void poll();\n }\n\n function handleVisibilityChange() {\n if (document.visibilityState === \"visible\") pollNow();\n }\n\n // Initial poll immediately\n void poll();\n window.addEventListener(\"focus\", pollNow);\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n window.removeEventListener(\"focus\", pollNow);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [pollUrl, queryClient, interval]);\n}\n\n/** @deprecated Use useDbSync instead */\nexport const useFileWatcher = useDbSync;\n\n/**\n * Subscribe to `refresh-screen` events from the agent. Returns an integer\n * that increments every time the agent invokes the framework's `refresh-screen`\n * tool. Apply it as a React `key` on the main content wrapper (the part\n * OUTSIDE the agent chat sidebar) so that region remounts and re-fetches its\n * data while the chat, sidebar, and any other persistent chrome keep their\n * in-flight state.\n *\n * Usage in a template's root:\n *\n * const screenKey = useScreenRefreshKey();\n * return (\n * <AppLayout>\n * <div key={screenKey}>\n * <Outlet />\n * </div>\n * </AppLayout>\n * );\n */\nexport function useScreenRefreshKey(\n options: { pollUrl?: string; interval?: number } = {},\n): number {\n const {\n pollUrl = agentNativePath(options.pollUrl ?? \"/_agent-native/poll\"),\n interval = 2000,\n } = options;\n const [key, setKey] = useState(0);\n\n useEffect(() => {\n let versionRef = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n let inFlight = false;\n\n function schedulePoll() {\n if (stopped) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n void poll();\n }, interval);\n }\n\n async function poll() {\n if (stopped || inFlight) return;\n inFlight = true;\n try {\n const data = await fetchPollJson<{\n version: number;\n events: Array<{ source: string }>;\n }>(pollUrl, versionRef, interval);\n if (data.events?.some((e) => e.source === \"screen-refresh\")) {\n setKey((k) => k + 1);\n }\n versionRef = Math.max(versionRef, data.version);\n } catch {\n // Network error — retry on next interval.\n } finally {\n inFlight = false;\n schedulePoll();\n }\n }\n\n function pollNow() {\n if (\n typeof document !== \"undefined\" &&\n document.visibilityState === \"hidden\"\n ) {\n return;\n }\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n void poll();\n }\n\n function handleVisibilityChange() {\n if (document.visibilityState === \"visible\") pollNow();\n }\n\n void poll();\n window.addEventListener(\"focus\", pollNow);\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n window.removeEventListener(\"focus\", pollNow);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [pollUrl, interval]);\n\n return key;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"use-session.d.ts","sourceRoot":"","sources":["../../src/client/use-session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,UAAU,gBAAgB;IACxB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,gBAAgB,CA8C7C"}
1
+ {"version":3,"file":"use-session.d.ts","sourceRoot":"","sources":["../../src/client/use-session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,UAAU,gBAAgB;IACxB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,gBAAgB,CA4D7C"}
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect, useRef } from "react";
2
- import { trackSessionStatus } from "./analytics.js";
2
+ import { setSentryUser, trackSessionStatus } from "./analytics.js";
3
3
  import { agentNativePath } from "./api-path.js";
4
4
  /**
5
5
  * Client-side hook to get the current auth session.
@@ -17,6 +17,7 @@ export function useSession() {
17
17
  let cancelled = false;
18
18
  async function fetchSession() {
19
19
  let signedIn = false;
20
+ let resolved = null;
20
21
  try {
21
22
  const res = await fetch(agentNativePath("/_agent-native/auth/session"));
22
23
  if (!res.ok) {
@@ -30,7 +31,8 @@ export function useSession() {
30
31
  setSession(null);
31
32
  }
32
33
  else {
33
- setSession(data);
34
+ resolved = data;
35
+ setSession(resolved);
34
36
  signedIn = true;
35
37
  }
36
38
  }
@@ -42,6 +44,16 @@ export function useSession() {
42
44
  finally {
43
45
  if (!cancelled) {
44
46
  setIsLoading(false);
47
+ if (resolved) {
48
+ setSentryUser({
49
+ id: resolved.userId,
50
+ email: resolved.email,
51
+ username: resolved.name,
52
+ }, resolved.orgId ?? null);
53
+ }
54
+ else {
55
+ setSentryUser(null, null);
56
+ }
45
57
  if (!trackedRef.current) {
46
58
  trackedRef.current = true;
47
59
  trackSessionStatus(signedIn);
@@ -1 +1 @@
1
- {"version":3,"file":"use-session.js","sourceRoot":"","sources":["../../src/client/use-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAEpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAShD;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC,CAAC;IACjE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,KAAK,CAAC;QAEtB,KAAK,UAAU,YAAY;YACzB,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,6BAA6B,CAAC,CAAC,CAAC;gBACxE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;oBACZ,UAAU,CAAC,IAAI,CAAC,CAAC;oBACjB,OAAO;gBACT,CAAC;gBACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBAC9B,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,+DAA+D;oBAC/D,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;wBACf,UAAU,CAAC,IAAI,CAAC,CAAC;oBACnB,CAAC;yBAAM,CAAC;wBACN,UAAU,CAAC,IAAmB,CAAC,CAAC;wBAChC,QAAQ,GAAG,IAAI,CAAC;oBAClB,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,SAAS;oBAAE,UAAU,CAAC,IAAI,CAAC,CAAC;YACnC,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;wBACxB,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;wBAC1B,kBAAkB,CAAC,QAAQ,CAAC,CAAC;oBAC/B,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,YAAY,EAAE,CAAC;QACf,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAChC,CAAC","sourcesContent":["import { useState, useEffect, useRef } from \"react\";\nimport type { AuthSession } from \"../server/auth.js\";\nimport { trackSessionStatus } from \"./analytics.js\";\nimport { agentNativePath } from \"./api-path.js\";\n\nexport type { AuthSession };\n\ninterface UseSessionResult {\n session: AuthSession | null;\n isLoading: boolean;\n}\n\n/**\n * Client-side hook to get the current auth session.\n *\n * - In dev mode: immediately returns { email: \"local@localhost\" }\n * - In production: fetches /_agent-native/auth/session and returns the result\n *\n * Templates should use this instead of building their own auth context.\n */\nexport function useSession(): UseSessionResult {\n const [session, setSession] = useState<AuthSession | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const trackedRef = useRef(false);\n\n useEffect(() => {\n let cancelled = false;\n\n async function fetchSession() {\n let signedIn = false;\n try {\n const res = await fetch(agentNativePath(\"/_agent-native/auth/session\"));\n if (!res.ok) {\n setSession(null);\n return;\n }\n const data = await res.json();\n if (!cancelled) {\n // The endpoint returns { error: \"...\" } when not authenticated\n if (data.error) {\n setSession(null);\n } else {\n setSession(data as AuthSession);\n signedIn = true;\n }\n }\n } catch {\n if (!cancelled) setSession(null);\n } finally {\n if (!cancelled) {\n setIsLoading(false);\n if (!trackedRef.current) {\n trackedRef.current = true;\n trackSessionStatus(signedIn);\n }\n }\n }\n }\n\n fetchSession();\n return () => {\n cancelled = true;\n };\n }, []);\n\n return { session, isLoading };\n}\n"]}
1
+ {"version":3,"file":"use-session.js","sourceRoot":"","sources":["../../src/client/use-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAShD;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC,CAAC;IACjE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,KAAK,CAAC;QAEtB,KAAK,UAAU,YAAY;YACzB,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,IAAI,QAAQ,GAAuB,IAAI,CAAC;YACxC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,6BAA6B,CAAC,CAAC,CAAC;gBACxE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;oBACZ,UAAU,CAAC,IAAI,CAAC,CAAC;oBACjB,OAAO;gBACT,CAAC;gBACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBAC9B,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,+DAA+D;oBAC/D,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;wBACf,UAAU,CAAC,IAAI,CAAC,CAAC;oBACnB,CAAC;yBAAM,CAAC;wBACN,QAAQ,GAAG,IAAmB,CAAC;wBAC/B,UAAU,CAAC,QAAQ,CAAC,CAAC;wBACrB,QAAQ,GAAG,IAAI,CAAC;oBAClB,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,SAAS;oBAAE,UAAU,CAAC,IAAI,CAAC,CAAC;YACnC,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,IAAI,QAAQ,EAAE,CAAC;wBACb,aAAa,CACX;4BACE,EAAE,EAAE,QAAQ,CAAC,MAAM;4BACnB,KAAK,EAAE,QAAQ,CAAC,KAAK;4BACrB,QAAQ,EAAE,QAAQ,CAAC,IAAI;yBACxB,EACD,QAAQ,CAAC,KAAK,IAAI,IAAI,CACvB,CAAC;oBACJ,CAAC;yBAAM,CAAC;wBACN,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBAC5B,CAAC;oBACD,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;wBACxB,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;wBAC1B,kBAAkB,CAAC,QAAQ,CAAC,CAAC;oBAC/B,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,YAAY,EAAE,CAAC;QACf,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAChC,CAAC","sourcesContent":["import { useState, useEffect, useRef } from \"react\";\nimport type { AuthSession } from \"../server/auth.js\";\nimport { setSentryUser, trackSessionStatus } from \"./analytics.js\";\nimport { agentNativePath } from \"./api-path.js\";\n\nexport type { AuthSession };\n\ninterface UseSessionResult {\n session: AuthSession | null;\n isLoading: boolean;\n}\n\n/**\n * Client-side hook to get the current auth session.\n *\n * - In dev mode: immediately returns { email: \"local@localhost\" }\n * - In production: fetches /_agent-native/auth/session and returns the result\n *\n * Templates should use this instead of building their own auth context.\n */\nexport function useSession(): UseSessionResult {\n const [session, setSession] = useState<AuthSession | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const trackedRef = useRef(false);\n\n useEffect(() => {\n let cancelled = false;\n\n async function fetchSession() {\n let signedIn = false;\n let resolved: AuthSession | null = null;\n try {\n const res = await fetch(agentNativePath(\"/_agent-native/auth/session\"));\n if (!res.ok) {\n setSession(null);\n return;\n }\n const data = await res.json();\n if (!cancelled) {\n // The endpoint returns { error: \"...\" } when not authenticated\n if (data.error) {\n setSession(null);\n } else {\n resolved = data as AuthSession;\n setSession(resolved);\n signedIn = true;\n }\n }\n } catch {\n if (!cancelled) setSession(null);\n } finally {\n if (!cancelled) {\n setIsLoading(false);\n if (resolved) {\n setSentryUser(\n {\n id: resolved.userId,\n email: resolved.email,\n username: resolved.name,\n },\n resolved.orgId ?? null,\n );\n } else {\n setSentryUser(null, null);\n }\n if (!trackedRef.current) {\n trackedRef.current = true;\n trackSessionStatus(signedIn);\n }\n }\n }\n }\n\n fetchSession();\n return () => {\n cancelled = true;\n };\n }, []);\n\n return { session, isLoading };\n}\n"]}
@@ -47,5 +47,6 @@ export interface UseCollaborativeDocResult {
47
47
  export declare function emailToColor(email: string): string;
48
48
  /** Derive a display name from an email address. */
49
49
  export declare function emailToName(email: string): string;
50
+ export declare function dedupeCollabUsersByEmail(users: CollabUser[]): CollabUser[];
50
51
  export declare function useCollaborativeDoc(options: UseCollaborativeDocOptions): UseCollaborativeDocResult;
51
52
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/collab/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGlD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,4EAA4E;IAC5E,IAAI,EAAE,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC;IACnB,uDAAuD;IACvD,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,kEAAkE;IAClE,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,6EAA6E;IAC7E,WAAW,EAAE,OAAO,CAAC;IACrB,+EAA+E;IAC/E,YAAY,EAAE,OAAO,CAAC;CACvB;AAgBD,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjD;AAoBD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,yBAAyB,CAqQ3B"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/collab/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGlD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,4EAA4E;IAC5E,IAAI,EAAE,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC;IACnB,uDAAuD;IACvD,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,kEAAkE;IAClE,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,6EAA6E;IAC7E,WAAW,EAAE,OAAO,CAAC;IACrB,+EAA+E;IAC/E,YAAY,EAAE,OAAO,CAAC;CACvB;AAgBD,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjD;AAMD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAY1E;AAoBD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,yBAAyB,CAqQ3B"}
@@ -38,6 +38,23 @@ export function emailToName(email) {
38
38
  const local = email.split("@")[0] || email;
39
39
  return local.charAt(0).toUpperCase() + local.slice(1);
40
40
  }
41
+ function normalizeCollabEmail(email) {
42
+ return email.trim().toLowerCase();
43
+ }
44
+ export function dedupeCollabUsersByEmail(users) {
45
+ const byEmail = new Map();
46
+ for (const user of users) {
47
+ const email = normalizeCollabEmail(user.email);
48
+ if (!email || byEmail.has(email))
49
+ continue;
50
+ byEmail.set(email, {
51
+ name: user.name || emailToName(email),
52
+ email,
53
+ color: user.color || emailToColor(email),
54
+ });
55
+ }
56
+ return Array.from(byEmail.values());
57
+ }
41
58
  // Base64 helpers
42
59
  function uint8ArrayToBase64(arr) {
43
60
  let binary = "";
@@ -106,7 +123,7 @@ export function useCollaborativeDoc(options) {
106
123
  }
107
124
  }
108
125
  });
109
- setActiveUsers(users);
126
+ setActiveUsers(dedupeCollabUsersByEmail(users));
110
127
  setAgentPresent(hasAgent);
111
128
  };
112
129
  awareness.on("change", updateUsers);
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/collab/client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Client-side hook for collaborative document editing via Yjs.\n *\n * Creates a STABLE Y.Doc per docId that never changes identity. This allows\n * TipTap's Collaboration extension to bind once without editor recreation.\n * Server state is applied to the existing doc when it arrives.\n *\n * Also manages Yjs Awareness for cursor positions and user presence,\n * synced via polling to the server's awareness endpoint.\n */\n\nimport { useEffect, useRef, useState, useMemo } from \"react\";\nimport * as Y from \"yjs\";\nimport { Awareness } from \"y-protocols/awareness\";\nimport { agentNativePath } from \"../client/api-path.js\";\n\nexport interface CollabUser {\n name: string;\n email: string;\n color: string;\n}\n\nexport interface UseCollaborativeDocOptions {\n /** Document ID to collaborate on. Pass null to disable. */\n docId: string | null;\n /** Poll interval in ms. Default: 2000 */\n pollInterval?: number;\n /** Base URL for collab endpoints. Default: \"/_agent-native/collab\" */\n baseUrl?: string;\n /** Request source ID for jitter prevention (e.g., tab ID). */\n requestSource?: string;\n /** Current user info for cursor labels. */\n user?: CollabUser;\n}\n\nexport interface UseCollaborativeDocResult {\n /** The Yjs document instance. Stable per docId — never changes identity. */\n ydoc: Y.Doc | null;\n /** Yjs Awareness instance for cursor/presence sync. */\n awareness: Awareness | null;\n /** Whether the initial state is still loading from the server. */\n isLoading: boolean;\n /** Whether the doc is synced with the server. */\n isSynced: boolean;\n /** Active users on this document (from awareness). */\n activeUsers: CollabUser[];\n /** True briefly when the AI agent makes an edit (for presence indicator). */\n agentActive: boolean;\n /** True when the AI agent has an active awareness entry (durable presence). */\n agentPresent: boolean;\n}\n\n// Consistent color palette for user cursors\nconst CURSOR_COLORS = [\n \"#f87171\",\n \"#fb923c\",\n \"#fbbf24\",\n \"#a3e635\",\n \"#34d399\",\n \"#22d3ee\",\n \"#60a5fa\",\n \"#a78bfa\",\n \"#f472b6\",\n \"#e879f9\",\n];\n\n/** Hash a string to a consistent color from the palette. */\nexport function emailToColor(email: string): string {\n let hash = 0;\n for (let i = 0; i < email.length; i++) {\n hash = ((hash << 5) - hash + email.charCodeAt(i)) | 0;\n }\n return CURSOR_COLORS[Math.abs(hash) % CURSOR_COLORS.length];\n}\n\n/** Derive a display name from an email address. */\nexport function emailToName(email: string): string {\n const local = email.split(\"@\")[0] || email;\n return local.charAt(0).toUpperCase() + local.slice(1);\n}\n\n// Base64 helpers\nfunction uint8ArrayToBase64(arr: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < arr.length; i++) {\n binary += String.fromCharCode(arr[i]);\n }\n return btoa(binary);\n}\n\nfunction base64ToUint8Array(b64: string): Uint8Array {\n const binary = atob(b64);\n const arr = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n arr[i] = binary.charCodeAt(i);\n }\n return arr;\n}\n\nexport function useCollaborativeDoc(\n options: UseCollaborativeDocOptions,\n): UseCollaborativeDocResult {\n const {\n docId,\n pollInterval = 2000,\n baseUrl = agentNativePath(\"/_agent-native/collab\"),\n requestSource,\n user,\n } = options;\n\n // Stable Y.Doc per docId\n const ydoc = useMemo(() => {\n if (!docId) return null;\n return new Y.Doc();\n }, [docId]);\n\n // Stable Awareness per ydoc\n const awareness = useMemo(() => {\n if (!ydoc) return null;\n return new Awareness(ydoc);\n }, [ydoc]);\n\n const [isLoading, setIsLoading] = useState(!!docId);\n const [isSynced, setIsSynced] = useState(false);\n const [activeUsers, setActiveUsers] = useState<CollabUser[]>([]);\n const [agentActive, setAgentActive] = useState(false);\n const [agentPresent, setAgentPresent] = useState(false);\n // Set when the initial state fetch returns 404/403 — stops the awareness\n // poll so we don't spam the console with errors against a doc that doesn't\n // exist or isn't accessible.\n const [docMissing, setDocMissing] = useState(false);\n const agentTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pollVersionRef = useRef(0);\n\n // Set local awareness state (user info for cursor labels)\n useEffect(() => {\n if (!awareness || !user) return;\n awareness.setLocalStateField(\"user\", {\n name: user.name,\n email: user.email,\n color: user.color,\n });\n }, [awareness, user?.name, user?.email, user?.color]);\n\n // Track active users from awareness changes\n useEffect(() => {\n if (!awareness) return;\n\n const updateUsers = () => {\n const users: CollabUser[] = [];\n let hasAgent = false;\n awareness.getStates().forEach((state, clientId) => {\n if (clientId === ydoc?.clientID) return; // Skip self\n if (state.user) {\n users.push(state.user as CollabUser);\n if ((state.user as CollabUser).email === \"agent@system\") {\n hasAgent = true;\n }\n }\n });\n setActiveUsers(users);\n setAgentPresent(hasAgent);\n };\n\n awareness.on(\"change\", updateUsers);\n return () => {\n awareness.off(\"change\", updateUsers);\n };\n }, [awareness, ydoc]);\n\n // Clean up on unmount or docId change\n useEffect(() => {\n return () => {\n awareness?.destroy();\n ydoc?.destroy();\n };\n }, [ydoc, awareness]);\n\n // Fetch server state and apply to existing doc\n useEffect(() => {\n if (!ydoc || !docId) {\n setIsLoading(false);\n return;\n }\n\n let cancelled = false;\n setIsLoading(true);\n setIsSynced(false);\n setDocMissing(false);\n\n fetch(`${baseUrl}/${docId}/state`)\n .then(async (res) => {\n if (cancelled) return;\n if (res.status === 404 || res.status === 403) {\n setDocMissing(true);\n setIsLoading(false);\n setIsSynced(true);\n return;\n }\n const data = (await res.json().catch(() => null)) as {\n state?: string;\n } | null;\n if (data?.state) {\n const binary = base64ToUint8Array(data.state);\n if (binary.length > 4) {\n Y.applyUpdate(ydoc, binary);\n }\n }\n setIsLoading(false);\n setIsSynced(true);\n })\n .catch(() => {\n if (cancelled) return;\n setIsLoading(false);\n setIsSynced(true);\n });\n\n return () => {\n cancelled = true;\n };\n }, [ydoc, docId, baseUrl]);\n\n // Send local updates to server\n useEffect(() => {\n if (!ydoc || !docId || docMissing) return;\n\n const handler = (update: Uint8Array, origin: unknown) => {\n if (origin === \"remote\") return;\n\n fetch(`${baseUrl}/${docId}/update`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n update: uint8ArrayToBase64(update),\n requestSource,\n }),\n });\n };\n\n ydoc.on(\"update\", handler);\n return () => {\n ydoc.off(\"update\", handler);\n };\n }, [ydoc, docId, baseUrl, requestSource, docMissing]);\n\n // Poll for remote doc updates + awareness sync\n useEffect(() => {\n if (!ydoc || !docId || docMissing) return;\n\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n async function poll() {\n if (stopped) return;\n try {\n // Poll for document updates\n const res = await fetch(\n agentNativePath(\n `/_agent-native/poll?since=${pollVersionRef.current}`,\n ),\n );\n if (!res.ok) throw new Error(\"HTTP \" + res.status);\n const data = await res.json();\n const { version, events } = data as {\n version: number;\n events: Array<{\n source: string;\n docId?: string;\n update?: string;\n requestSource?: string;\n }>;\n };\n\n for (const evt of events) {\n if (evt.source === \"collab\" && evt.docId === docId && evt.update) {\n if (requestSource && evt.requestSource === requestSource) continue;\n Y.applyUpdate(ydoc, base64ToUint8Array(evt.update), \"remote\");\n\n // Show agent presence indicator briefly\n if (evt.requestSource === \"agent\") {\n setAgentActive(true);\n if (agentTimerRef.current) clearTimeout(agentTimerRef.current);\n agentTimerRef.current = setTimeout(\n () => setAgentActive(false),\n 3000,\n );\n }\n }\n }\n\n pollVersionRef.current = version;\n\n // Sync awareness (cursor positions)\n if (awareness) {\n const localState = awareness.getLocalState();\n if (localState) {\n const awarenessRes = await fetch(`${baseUrl}/${docId}/awareness`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n clientId: ydoc.clientID,\n state: JSON.stringify(localState),\n }),\n });\n if (awarenessRes.ok) {\n const awarenessData = await awarenessRes.json();\n // Apply remote awareness states\n for (const remote of awarenessData.states || []) {\n try {\n const remoteState = JSON.parse(remote.state);\n awareness.setLocalStateField(\n `remote_${remote.clientId}`,\n null,\n );\n // Manually update the awareness state map for remote clients\n const states = awareness.getStates();\n states.set(remote.clientId, remoteState);\n // Trigger awareness change event\n awareness.emit(\"change\", [\n { added: [], updated: [remote.clientId], removed: [] },\n \"remote\",\n ]);\n } catch {\n // Invalid state — skip\n }\n }\n }\n }\n }\n } catch {\n // Network error — retry next interval\n }\n if (!stopped) {\n timer = setTimeout(poll, pollInterval);\n }\n }\n\n poll();\n\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n };\n }, [\n ydoc,\n awareness,\n docId,\n pollInterval,\n requestSource,\n baseUrl,\n docMissing,\n ]);\n\n return {\n ydoc,\n awareness,\n isLoading,\n isSynced,\n activeUsers,\n agentActive,\n agentPresent,\n };\n}\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/collab/client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Client-side hook for collaborative document editing via Yjs.\n *\n * Creates a STABLE Y.Doc per docId that never changes identity. This allows\n * TipTap's Collaboration extension to bind once without editor recreation.\n * Server state is applied to the existing doc when it arrives.\n *\n * Also manages Yjs Awareness for cursor positions and user presence,\n * synced via polling to the server's awareness endpoint.\n */\n\nimport { useEffect, useRef, useState, useMemo } from \"react\";\nimport * as Y from \"yjs\";\nimport { Awareness } from \"y-protocols/awareness\";\nimport { agentNativePath } from \"../client/api-path.js\";\n\nexport interface CollabUser {\n name: string;\n email: string;\n color: string;\n}\n\nexport interface UseCollaborativeDocOptions {\n /** Document ID to collaborate on. Pass null to disable. */\n docId: string | null;\n /** Poll interval in ms. Default: 2000 */\n pollInterval?: number;\n /** Base URL for collab endpoints. Default: \"/_agent-native/collab\" */\n baseUrl?: string;\n /** Request source ID for jitter prevention (e.g., tab ID). */\n requestSource?: string;\n /** Current user info for cursor labels. */\n user?: CollabUser;\n}\n\nexport interface UseCollaborativeDocResult {\n /** The Yjs document instance. Stable per docId — never changes identity. */\n ydoc: Y.Doc | null;\n /** Yjs Awareness instance for cursor/presence sync. */\n awareness: Awareness | null;\n /** Whether the initial state is still loading from the server. */\n isLoading: boolean;\n /** Whether the doc is synced with the server. */\n isSynced: boolean;\n /** Active users on this document (from awareness). */\n activeUsers: CollabUser[];\n /** True briefly when the AI agent makes an edit (for presence indicator). */\n agentActive: boolean;\n /** True when the AI agent has an active awareness entry (durable presence). */\n agentPresent: boolean;\n}\n\n// Consistent color palette for user cursors\nconst CURSOR_COLORS = [\n \"#f87171\",\n \"#fb923c\",\n \"#fbbf24\",\n \"#a3e635\",\n \"#34d399\",\n \"#22d3ee\",\n \"#60a5fa\",\n \"#a78bfa\",\n \"#f472b6\",\n \"#e879f9\",\n];\n\n/** Hash a string to a consistent color from the palette. */\nexport function emailToColor(email: string): string {\n let hash = 0;\n for (let i = 0; i < email.length; i++) {\n hash = ((hash << 5) - hash + email.charCodeAt(i)) | 0;\n }\n return CURSOR_COLORS[Math.abs(hash) % CURSOR_COLORS.length];\n}\n\n/** Derive a display name from an email address. */\nexport function emailToName(email: string): string {\n const local = email.split(\"@\")[0] || email;\n return local.charAt(0).toUpperCase() + local.slice(1);\n}\n\nfunction normalizeCollabEmail(email: string): string {\n return email.trim().toLowerCase();\n}\n\nexport function dedupeCollabUsersByEmail(users: CollabUser[]): CollabUser[] {\n const byEmail = new Map<string, CollabUser>();\n for (const user of users) {\n const email = normalizeCollabEmail(user.email);\n if (!email || byEmail.has(email)) continue;\n byEmail.set(email, {\n name: user.name || emailToName(email),\n email,\n color: user.color || emailToColor(email),\n });\n }\n return Array.from(byEmail.values());\n}\n\n// Base64 helpers\nfunction uint8ArrayToBase64(arr: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < arr.length; i++) {\n binary += String.fromCharCode(arr[i]);\n }\n return btoa(binary);\n}\n\nfunction base64ToUint8Array(b64: string): Uint8Array {\n const binary = atob(b64);\n const arr = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n arr[i] = binary.charCodeAt(i);\n }\n return arr;\n}\n\nexport function useCollaborativeDoc(\n options: UseCollaborativeDocOptions,\n): UseCollaborativeDocResult {\n const {\n docId,\n pollInterval = 2000,\n baseUrl = agentNativePath(\"/_agent-native/collab\"),\n requestSource,\n user,\n } = options;\n\n // Stable Y.Doc per docId\n const ydoc = useMemo(() => {\n if (!docId) return null;\n return new Y.Doc();\n }, [docId]);\n\n // Stable Awareness per ydoc\n const awareness = useMemo(() => {\n if (!ydoc) return null;\n return new Awareness(ydoc);\n }, [ydoc]);\n\n const [isLoading, setIsLoading] = useState(!!docId);\n const [isSynced, setIsSynced] = useState(false);\n const [activeUsers, setActiveUsers] = useState<CollabUser[]>([]);\n const [agentActive, setAgentActive] = useState(false);\n const [agentPresent, setAgentPresent] = useState(false);\n // Set when the initial state fetch returns 404/403 — stops the awareness\n // poll so we don't spam the console with errors against a doc that doesn't\n // exist or isn't accessible.\n const [docMissing, setDocMissing] = useState(false);\n const agentTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pollVersionRef = useRef(0);\n\n // Set local awareness state (user info for cursor labels)\n useEffect(() => {\n if (!awareness || !user) return;\n awareness.setLocalStateField(\"user\", {\n name: user.name,\n email: user.email,\n color: user.color,\n });\n }, [awareness, user?.name, user?.email, user?.color]);\n\n // Track active users from awareness changes\n useEffect(() => {\n if (!awareness) return;\n\n const updateUsers = () => {\n const users: CollabUser[] = [];\n let hasAgent = false;\n awareness.getStates().forEach((state, clientId) => {\n if (clientId === ydoc?.clientID) return; // Skip self\n if (state.user) {\n users.push(state.user as CollabUser);\n if ((state.user as CollabUser).email === \"agent@system\") {\n hasAgent = true;\n }\n }\n });\n setActiveUsers(dedupeCollabUsersByEmail(users));\n setAgentPresent(hasAgent);\n };\n\n awareness.on(\"change\", updateUsers);\n return () => {\n awareness.off(\"change\", updateUsers);\n };\n }, [awareness, ydoc]);\n\n // Clean up on unmount or docId change\n useEffect(() => {\n return () => {\n awareness?.destroy();\n ydoc?.destroy();\n };\n }, [ydoc, awareness]);\n\n // Fetch server state and apply to existing doc\n useEffect(() => {\n if (!ydoc || !docId) {\n setIsLoading(false);\n return;\n }\n\n let cancelled = false;\n setIsLoading(true);\n setIsSynced(false);\n setDocMissing(false);\n\n fetch(`${baseUrl}/${docId}/state`)\n .then(async (res) => {\n if (cancelled) return;\n if (res.status === 404 || res.status === 403) {\n setDocMissing(true);\n setIsLoading(false);\n setIsSynced(true);\n return;\n }\n const data = (await res.json().catch(() => null)) as {\n state?: string;\n } | null;\n if (data?.state) {\n const binary = base64ToUint8Array(data.state);\n if (binary.length > 4) {\n Y.applyUpdate(ydoc, binary);\n }\n }\n setIsLoading(false);\n setIsSynced(true);\n })\n .catch(() => {\n if (cancelled) return;\n setIsLoading(false);\n setIsSynced(true);\n });\n\n return () => {\n cancelled = true;\n };\n }, [ydoc, docId, baseUrl]);\n\n // Send local updates to server\n useEffect(() => {\n if (!ydoc || !docId || docMissing) return;\n\n const handler = (update: Uint8Array, origin: unknown) => {\n if (origin === \"remote\") return;\n\n fetch(`${baseUrl}/${docId}/update`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n update: uint8ArrayToBase64(update),\n requestSource,\n }),\n });\n };\n\n ydoc.on(\"update\", handler);\n return () => {\n ydoc.off(\"update\", handler);\n };\n }, [ydoc, docId, baseUrl, requestSource, docMissing]);\n\n // Poll for remote doc updates + awareness sync\n useEffect(() => {\n if (!ydoc || !docId || docMissing) return;\n\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n async function poll() {\n if (stopped) return;\n try {\n // Poll for document updates\n const res = await fetch(\n agentNativePath(\n `/_agent-native/poll?since=${pollVersionRef.current}`,\n ),\n );\n if (!res.ok) throw new Error(\"HTTP \" + res.status);\n const data = await res.json();\n const { version, events } = data as {\n version: number;\n events: Array<{\n source: string;\n docId?: string;\n update?: string;\n requestSource?: string;\n }>;\n };\n\n for (const evt of events) {\n if (evt.source === \"collab\" && evt.docId === docId && evt.update) {\n if (requestSource && evt.requestSource === requestSource) continue;\n Y.applyUpdate(ydoc, base64ToUint8Array(evt.update), \"remote\");\n\n // Show agent presence indicator briefly\n if (evt.requestSource === \"agent\") {\n setAgentActive(true);\n if (agentTimerRef.current) clearTimeout(agentTimerRef.current);\n agentTimerRef.current = setTimeout(\n () => setAgentActive(false),\n 3000,\n );\n }\n }\n }\n\n pollVersionRef.current = version;\n\n // Sync awareness (cursor positions)\n if (awareness) {\n const localState = awareness.getLocalState();\n if (localState) {\n const awarenessRes = await fetch(`${baseUrl}/${docId}/awareness`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n clientId: ydoc.clientID,\n state: JSON.stringify(localState),\n }),\n });\n if (awarenessRes.ok) {\n const awarenessData = await awarenessRes.json();\n // Apply remote awareness states\n for (const remote of awarenessData.states || []) {\n try {\n const remoteState = JSON.parse(remote.state);\n awareness.setLocalStateField(\n `remote_${remote.clientId}`,\n null,\n );\n // Manually update the awareness state map for remote clients\n const states = awareness.getStates();\n states.set(remote.clientId, remoteState);\n // Trigger awareness change event\n awareness.emit(\"change\", [\n { added: [], updated: [remote.clientId], removed: [] },\n \"remote\",\n ]);\n } catch {\n // Invalid state — skip\n }\n }\n }\n }\n }\n } catch {\n // Network error — retry next interval\n }\n if (!stopped) {\n timer = setTimeout(poll, pollInterval);\n }\n }\n\n poll();\n\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n };\n }, [\n ydoc,\n awareness,\n docId,\n pollInterval,\n requestSource,\n baseUrl,\n docMissing,\n ]);\n\n return {\n ydoc,\n awareness,\n isLoading,\n isSynced,\n activeUsers,\n agentActive,\n agentPresent,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/deploy/build.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG;AAOH,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAyB7B;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,EAAE,EACzB,WAAW,EAAE,MAAM,EAAE,EACrB,kBAAkB,GAAE,MAAM,EAAO,EACjC,OAAO,GAAE,gBAAgB,EAAO,EAChC,aAAa,GAAE,oBAAoB,GAAG,IAAW,GAChD,MAAM,CAkVR;AAiXD,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/deploy/build.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG;AAOH,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AA8B7B;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,EAAE,EACzB,WAAW,EAAE,MAAM,EAAE,EACrB,kBAAkB,GAAE,MAAM,EAAO,EACjC,OAAO,GAAE,gBAAgB,EAAO,EAChC,aAAa,GAAE,oBAAoB,GAAG,IAAW,GAChD,MAAM,CAkVR;AAiXD,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C"}
@@ -35,6 +35,11 @@ function normalizeConfiguredAppBasePath() {
35
35
  /** Plugins that require Node.js runtime and cannot run on edge/serverless */
36
36
  const NODE_ONLY_PLUGINS = new Set([
37
37
  "terminal", // PTY requires child_process
38
+ // @sentry/node ships node:fs / node:async_hooks bindings that don't load
39
+ // on workerd / Cloudflare Workers. Templates running on edge presets can
40
+ // mount their own edge-compatible Sentry wrapper if they want server
41
+ // observability there; the framework default is the Node SDK.
42
+ "sentry",
38
43
  ]);
39
44
  function isNodeOnlyPlugin(filePath) {
40
45
  const basename = path.basename(filePath, path.extname(filePath));