@agent-native/core 0.112.0 → 0.114.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 (1902) hide show
  1. package/agent-native.eject.json +164 -0
  2. package/corpus/README.md +4 -2
  3. package/corpus/core/CHANGELOG.md +33 -0
  4. package/corpus/core/README.md +61 -174
  5. package/corpus/core/agent-native.eject.json +164 -0
  6. package/corpus/core/docs/AGENTS.md +7 -0
  7. package/corpus/core/docs/content/agent-native-toolkit.mdx +38 -0
  8. package/corpus/core/docs/content/integrations.mdx +63 -2
  9. package/corpus/core/docs/content/package-lifecycle.mdx +54 -4
  10. package/corpus/core/docs/design/provider-integration-authority.md +117 -0
  11. package/corpus/core/package.json +20 -2
  12. package/corpus/core/scripts/materialize-source-corpus.mjs +9 -2
  13. package/corpus/core/src/cli/atomic-json-file.ts +324 -0
  14. package/corpus/core/src/cli/claude-code-participant.ts +515 -0
  15. package/corpus/core/src/cli/code-agent-executor.ts +18 -10
  16. package/corpus/core/src/cli/code-agent-runs.concurrent-worker.ts +33 -0
  17. package/corpus/core/src/cli/code-agent-runs.ts +55 -62
  18. package/corpus/core/src/cli/codex-cli-participant.ts +496 -0
  19. package/corpus/core/src/cli/eject.ts +1594 -0
  20. package/corpus/core/src/cli/index.ts +16 -0
  21. package/corpus/core/src/cli/multi-frontier-runs.ts +1246 -135
  22. package/corpus/core/src/client/chat/ChatHistoryList.tsx +7 -404
  23. package/corpus/core/src/client/index.ts +4 -0
  24. package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +8 -2
  25. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +7 -2
  26. package/corpus/core/src/client/resources/ResourcesPanel.tsx +17 -3
  27. package/corpus/core/src/client/resources/index.ts +19 -0
  28. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +9 -1
  29. package/corpus/core/src/client/resources/runtime.ts +1 -0
  30. package/corpus/core/src/client/setup-connections/catalog.ts +6 -0
  31. package/corpus/core/src/client/setup-connections/index.ts +1 -0
  32. package/corpus/core/src/client/setup-connections/runtime.ts +1 -0
  33. package/corpus/core/src/connections/catalog.ts +41 -24
  34. package/corpus/core/src/connections/index.ts +1 -0
  35. package/corpus/core/src/connections/runtime.ts +1 -0
  36. package/corpus/core/src/eject/provider-api-definitions.ts +193 -0
  37. package/corpus/core/src/eject/remote-mcp-presets.ts +137 -0
  38. package/corpus/core/src/eject/setup-readiness-ui.tsx +29 -0
  39. package/corpus/core/src/eject/workspace-connections.ts +92 -0
  40. package/corpus/core/src/index.ts +1 -0
  41. package/corpus/core/src/integrations/adapter-overrides.ts +12 -0
  42. package/corpus/core/src/integrations/eject/messaging-adapters.ts +85 -0
  43. package/corpus/core/src/integrations/index.ts +15 -1
  44. package/corpus/core/src/integrations/plugin.ts +34 -12
  45. package/corpus/core/src/integrations/runtime.ts +1 -0
  46. package/corpus/core/src/integrations/slack-oauth.ts +1 -0
  47. package/corpus/core/src/integrations/types.ts +3 -1
  48. package/corpus/core/src/integrations/webhook-delivery.ts +55 -0
  49. package/corpus/core/src/notifications/channels.ts +4 -10
  50. package/corpus/core/src/package-lifecycle/eject-manifest.ts +345 -0
  51. package/corpus/core/src/package-lifecycle/index.ts +1 -0
  52. package/corpus/core/src/provider-api/actions/custom-provider-registration.ts +197 -0
  53. package/corpus/core/src/provider-api/actions/delete-staged-dataset.ts +3 -42
  54. package/corpus/core/src/provider-api/actions/list-staged-datasets.ts +3 -45
  55. package/corpus/core/src/provider-api/actions/provider-api-audit.ts +88 -0
  56. package/corpus/core/src/provider-api/actions/provider-api.ts +460 -0
  57. package/corpus/core/src/provider-api/actions/query-staged-dataset.ts +3 -140
  58. package/corpus/core/src/provider-api/actions/staged-datasets.ts +359 -0
  59. package/corpus/core/src/provider-api/gong.ts +108 -0
  60. package/corpus/core/src/provider-api/index.ts +46 -12
  61. package/corpus/core/src/provider-api/runtime.ts +1 -0
  62. package/corpus/core/src/scripts/docs/source-search.ts +2 -1
  63. package/corpus/core/src/server/google-oauth-credentials.ts +80 -3
  64. package/corpus/core/src/server/index.ts +6 -0
  65. package/corpus/core/src/setup-connections/index.ts +1 -0
  66. package/corpus/core/src/shared/merge-by-id.ts +14 -0
  67. package/corpus/core/src/styles/chat-history-list.css +1 -313
  68. package/corpus/core/src/templates/default/.agents/skills/actions/SKILL.md +32 -12
  69. package/corpus/core/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +21 -9
  70. package/corpus/core/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  71. package/corpus/core/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +323 -0
  72. package/corpus/core/src/templates/default/.agents/skills/onboarding/SKILL.md +56 -0
  73. package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +273 -0
  74. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +14 -2
  75. package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  76. package/corpus/core/src/templates/default/AGENTS.md +26 -18
  77. package/corpus/core/src/templates/headless/.agents/skills/actions/SKILL.md +512 -0
  78. package/corpus/core/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +21 -9
  79. package/corpus/core/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  80. package/corpus/core/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +323 -0
  81. package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +273 -0
  82. package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  83. package/corpus/core/src/templates/headless/AGENTS.md +8 -0
  84. package/corpus/core/src/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -12
  85. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +21 -9
  86. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +7 -0
  87. package/corpus/core/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  88. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +14 -2
  89. package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  90. package/corpus/core/src/templates/workspace-core/AGENTS.md +4 -0
  91. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
  92. package/corpus/core/src/vite/client.ts +25 -2
  93. package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +32 -12
  94. package/corpus/templates/analytics/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  95. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +14 -2
  96. package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  97. package/corpus/templates/analytics/actions/delete-staged-dataset.ts +3 -23
  98. package/corpus/templates/analytics/actions/list-staged-datasets.ts +3 -27
  99. package/corpus/templates/analytics/actions/provider-api-catalog.ts +16 -19
  100. package/corpus/templates/analytics/actions/provider-api-docs.ts +3 -5
  101. package/corpus/templates/analytics/actions/provider-api-request.ts +162 -185
  102. package/corpus/templates/analytics/actions/query-staged-dataset.ts +3 -47
  103. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +52 -161
  104. package/corpus/templates/assets/.agents/skills/actions/SKILL.md +32 -12
  105. package/corpus/templates/assets/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  106. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +14 -2
  107. package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  108. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +63 -160
  109. package/corpus/templates/brain/.agents/skills/actions/SKILL.md +32 -12
  110. package/corpus/templates/brain/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  111. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +14 -2
  112. package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  113. package/corpus/templates/brain/actions/_schemas.ts +13 -7
  114. package/corpus/templates/brain/actions/delete-staged-dataset.ts +3 -24
  115. package/corpus/templates/brain/actions/list-staged-datasets.ts +3 -28
  116. package/corpus/templates/brain/actions/provider-api-catalog.ts +5 -11
  117. package/corpus/templates/brain/actions/provider-api-docs.ts +3 -5
  118. package/corpus/templates/brain/actions/provider-api-request.ts +3 -37
  119. package/corpus/templates/brain/actions/query-staged-dataset.ts +3 -48
  120. package/corpus/templates/brain/actions/remediate-legacy-capture-audiences.ts +167 -0
  121. package/corpus/templates/brain/actions/update-source.ts +5 -1
  122. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +48 -154
  123. package/corpus/templates/brain/app/lib/brain-chat-readiness.ts +6 -0
  124. package/corpus/templates/brain/app/lib/brain.ts +55 -0
  125. package/corpus/templates/brain/app/lib/ingest-handoff.ts +35 -0
  126. package/corpus/templates/brain/app/routes/_index.tsx +8 -0
  127. package/corpus/templates/brain/app/routes.ts +1 -0
  128. package/corpus/templates/brain/changelog/2026-07-19-ask-starts-immediately-when-builder-s-managed-ai-connection-.md +6 -0
  129. package/corpus/templates/brain/changelog/2026-07-19-slack-and-granola-backfills-now-process-captures-concurrentl.md +6 -0
  130. package/corpus/templates/brain/changelog/2026-07-19-slack-backfills-can-now-join-configured-public-channels-befo.md +6 -0
  131. package/corpus/templates/brain/changelog/2026-07-19-slack-backfills-now-continue-across-the-configured-number-of.md +6 -0
  132. package/corpus/templates/brain/changelog/2026-07-19-slack-backfills-now-start-with-a-safe-four-week-window-and-r.md +6 -0
  133. package/corpus/templates/brain/changelog/2026-07-19-source-review-settings-now-control-knowledge-approval.md +6 -0
  134. package/corpus/templates/brain/changelog/2026-07-19-the-manage-agent-page-now-opens-correctly-from-brain-navigat.md +6 -0
  135. package/corpus/templates/brain/server/db/schema.ts +3 -0
  136. package/corpus/templates/brain/server/lib/brain.ts +41 -2
  137. package/corpus/templates/brain/server/lib/connectors.ts +432 -115
  138. package/corpus/templates/brain/server/lib/meeting-audience.ts +38 -0
  139. package/corpus/templates/brain/server/lib/slack-channel-membership.ts +44 -0
  140. package/corpus/templates/brain/server/plugins/db.ts +10 -0
  141. package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +6 -4
  142. package/corpus/templates/brain/shared/slack-source-config.ts +60 -0
  143. package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +32 -12
  144. package/corpus/templates/calendar/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  145. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +14 -2
  146. package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  147. package/corpus/templates/calendar/actions/delete-staged-dataset.ts +3 -24
  148. package/corpus/templates/calendar/actions/list-staged-datasets.ts +3 -28
  149. package/corpus/templates/calendar/actions/provider-api-catalog.ts +5 -11
  150. package/corpus/templates/calendar/actions/provider-api-docs.ts +3 -5
  151. package/corpus/templates/calendar/actions/provider-api-request.ts +3 -37
  152. package/corpus/templates/calendar/actions/query-staged-dataset.ts +3 -48
  153. package/corpus/templates/calendar/server/handlers/gong.ts +6 -61
  154. package/corpus/templates/calendar/server/handlers/google-auth.ts +10 -8
  155. package/corpus/templates/calendar/server/lib/google-calendar.ts +7 -45
  156. package/corpus/templates/chat/.agents/skills/actions/SKILL.md +32 -12
  157. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +21 -9
  158. package/corpus/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  159. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +14 -2
  160. package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  161. package/corpus/templates/chat/AGENTS.md +6 -2
  162. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +51 -157
  163. package/corpus/templates/clips/.agents/skills/actions/SKILL.md +32 -12
  164. package/corpus/templates/clips/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  165. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +14 -2
  166. package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  167. package/corpus/templates/clips/AGENTS.md +11 -0
  168. package/corpus/templates/clips/actions/export-to-brain.ts +505 -171
  169. package/corpus/templates/clips/changelog/2026-07-19-ready-clips-transcripts-can-be-backfilled-to-brain-for-a-bou.md +6 -0
  170. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +1 -1
  171. package/corpus/templates/clips/server/lib/google-calendar-client.ts +12 -22
  172. package/corpus/templates/clips/server/lib/google-calendar-oauth.ts +9 -6
  173. package/corpus/templates/clips/server/lib/slack-oauth.ts +12 -47
  174. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +2 -6
  175. package/corpus/templates/clips/server/routes/api/auth/google-calendar.get.ts +4 -4
  176. package/corpus/templates/content/.agents/skills/actions/SKILL.md +32 -12
  177. package/corpus/templates/content/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  178. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +14 -2
  179. package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  180. package/corpus/templates/content/actions/delete-staged-dataset.ts +3 -24
  181. package/corpus/templates/content/actions/list-staged-datasets.ts +3 -28
  182. package/corpus/templates/content/actions/provider-api-catalog.ts +5 -11
  183. package/corpus/templates/content/actions/provider-api-docs.ts +3 -5
  184. package/corpus/templates/content/actions/provider-api-request.ts +3 -37
  185. package/corpus/templates/content/actions/query-staged-dataset.ts +3 -48
  186. package/corpus/templates/design/.agents/skills/actions/SKILL.md +32 -12
  187. package/corpus/templates/design/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  188. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +14 -2
  189. package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  190. package/corpus/templates/design/actions/delete-staged-dataset.ts +4 -23
  191. package/corpus/templates/design/actions/list-staged-datasets.ts +4 -27
  192. package/corpus/templates/design/actions/provider-api-catalog.ts +15 -18
  193. package/corpus/templates/design/actions/provider-api-docs.ts +57 -48
  194. package/corpus/templates/design/actions/provider-api-request.ts +140 -164
  195. package/corpus/templates/design/actions/query-staged-dataset.ts +4 -47
  196. package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +32 -12
  197. package/corpus/templates/dispatch/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  198. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +14 -2
  199. package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  200. package/corpus/templates/dispatch/actions/delete-staged-dataset.ts +4 -23
  201. package/corpus/templates/dispatch/actions/list-staged-datasets.ts +4 -30
  202. package/corpus/templates/dispatch/actions/provider-api-catalog.ts +15 -18
  203. package/corpus/templates/dispatch/actions/provider-api-docs.ts +57 -48
  204. package/corpus/templates/dispatch/actions/provider-api-request.ts +138 -163
  205. package/corpus/templates/dispatch/actions/query-staged-dataset.ts +4 -47
  206. package/corpus/templates/forms/.agents/skills/actions/SKILL.md +32 -12
  207. package/corpus/templates/forms/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  208. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +14 -2
  209. package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  210. package/corpus/templates/forms/server/lib/integrations.ts +23 -22
  211. package/corpus/templates/macros/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  212. package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  213. package/corpus/templates/mail/.agents/skills/actions/SKILL.md +32 -12
  214. package/corpus/templates/mail/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  215. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +14 -2
  216. package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  217. package/corpus/templates/mail/actions/delete-staged-dataset.ts +4 -23
  218. package/corpus/templates/mail/actions/list-staged-datasets.ts +4 -27
  219. package/corpus/templates/mail/actions/provider-api-catalog.ts +15 -18
  220. package/corpus/templates/mail/actions/provider-api-docs.ts +57 -48
  221. package/corpus/templates/mail/actions/provider-api-request.ts +146 -173
  222. package/corpus/templates/mail/actions/query-staged-dataset.ts +4 -47
  223. package/corpus/templates/mail/server/handlers/gong.ts +6 -61
  224. package/corpus/templates/mail/server/lib/google-auth.ts +10 -13
  225. package/corpus/templates/plan/.agents/skills/actions/SKILL.md +32 -12
  226. package/corpus/templates/plan/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  227. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +14 -2
  228. package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  229. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +47 -156
  230. package/corpus/templates/slides/.agents/skills/actions/SKILL.md +32 -12
  231. package/corpus/templates/slides/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  232. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +14 -2
  233. package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  234. package/corpus/templates/slides/actions/delete-staged-dataset.ts +4 -23
  235. package/corpus/templates/slides/actions/list-staged-datasets.ts +4 -27
  236. package/corpus/templates/slides/actions/provider-api-catalog.ts +15 -18
  237. package/corpus/templates/slides/actions/provider-api-docs.ts +57 -48
  238. package/corpus/templates/slides/actions/provider-api-request.ts +145 -172
  239. package/corpus/templates/slides/actions/query-staged-dataset.ts +4 -47
  240. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +11 -36
  241. package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +32 -12
  242. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +21 -9
  243. package/corpus/templates/tasks/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  244. package/corpus/templates/tasks/.agents/skills/self-modifying-code/SKILL.md +14 -2
  245. package/corpus/templates/tasks/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  246. package/corpus/toolkit/CHANGELOG.md +131 -0
  247. package/corpus/toolkit/README.md +60 -0
  248. package/corpus/toolkit/agent-native.eject.json +411 -0
  249. package/corpus/toolkit/export-snapshot.json +35 -0
  250. package/corpus/toolkit/migration-manifest.json +4 -0
  251. package/corpus/toolkit/package.json +284 -0
  252. package/corpus/toolkit/scripts/finalize-build.mjs +55 -0
  253. package/corpus/toolkit/src/app-shell/header-actions.tsx +84 -0
  254. package/corpus/toolkit/src/app-shell/index.ts +1 -0
  255. package/corpus/toolkit/src/chat-history/ChatHistoryList.tsx +469 -0
  256. package/corpus/toolkit/src/chat-history/index.ts +7 -0
  257. package/corpus/toolkit/src/chat-history.css +314 -0
  258. package/corpus/toolkit/src/collab-ui/AgentPresenceChip.tsx +69 -0
  259. package/corpus/toolkit/src/collab-ui/LiveCursorOverlay.tsx +241 -0
  260. package/corpus/toolkit/src/collab-ui/PresenceBar.tsx +346 -0
  261. package/corpus/toolkit/src/collab-ui/RecentEditHighlights.tsx +225 -0
  262. package/corpus/toolkit/src/collab-ui/RemoteSelectionRings.tsx +227 -0
  263. package/corpus/toolkit/src/collab-ui/agent-identity.ts +16 -0
  264. package/corpus/toolkit/src/collab-ui/index.ts +39 -0
  265. package/corpus/toolkit/src/collab-ui/lead-client.ts +32 -0
  266. package/corpus/toolkit/src/collab-ui/types.ts +96 -0
  267. package/corpus/toolkit/src/composer/AgentComposerFrame.tsx +59 -0
  268. package/corpus/toolkit/src/composer/ComposerPlusMenu.tsx +983 -0
  269. package/corpus/toolkit/src/composer/MentionPopover.tsx +412 -0
  270. package/corpus/toolkit/src/composer/PastedTextChip.tsx +150 -0
  271. package/corpus/toolkit/src/composer/PromptComposer.tsx +718 -0
  272. package/corpus/toolkit/src/composer/RealtimeVoiceMode.tsx +856 -0
  273. package/corpus/toolkit/src/composer/TiptapComposer.tsx +2936 -0
  274. package/corpus/toolkit/src/composer/VoiceButton.tsx +255 -0
  275. package/corpus/toolkit/src/composer/asset-picker-url.ts +80 -0
  276. package/corpus/toolkit/src/composer/attachment-accept.ts +71 -0
  277. package/corpus/toolkit/src/composer/draft-key.ts +7 -0
  278. package/corpus/toolkit/src/composer/extensions/FileReference.tsx +55 -0
  279. package/corpus/toolkit/src/composer/extensions/MentionReference.tsx +93 -0
  280. package/corpus/toolkit/src/composer/extensions/SkillReference.tsx +56 -0
  281. package/corpus/toolkit/src/composer/index.ts +79 -0
  282. package/corpus/toolkit/src/composer/pasted-text.ts +160 -0
  283. package/corpus/toolkit/src/composer/prompt-attachments.ts +93 -0
  284. package/corpus/toolkit/src/composer/realtime-voice-audio-level.ts +64 -0
  285. package/corpus/toolkit/src/composer/realtime-voice-transcript.ts +134 -0
  286. package/corpus/toolkit/src/composer/runtime-adapters.tsx +343 -0
  287. package/corpus/toolkit/src/composer/types.ts +66 -0
  288. package/corpus/toolkit/src/composer/use-file-search.ts +64 -0
  289. package/corpus/toolkit/src/composer/use-mention-search.ts +90 -0
  290. package/corpus/toolkit/src/composer/use-skills.ts +45 -0
  291. package/corpus/toolkit/src/composer/useRealtimeVoiceMode.tsx +2117 -0
  292. package/corpus/toolkit/src/composer/useVoiceDictation.ts +1191 -0
  293. package/corpus/toolkit/src/context-ui/ContextMeter.tsx +101 -0
  294. package/corpus/toolkit/src/context-ui/ContextSegmentRow.tsx +184 -0
  295. package/corpus/toolkit/src/context-ui/ContextTreemap.tsx +125 -0
  296. package/corpus/toolkit/src/context-ui/ContextXRayPanel.tsx +377 -0
  297. package/corpus/toolkit/src/context-ui/format.ts +42 -0
  298. package/corpus/toolkit/src/context-ui/index.ts +15 -0
  299. package/corpus/toolkit/src/context-ui/types.ts +43 -0
  300. package/corpus/toolkit/src/design-tweaks/index.ts +1 -0
  301. package/corpus/toolkit/src/design-tweaks/visual-style-controls.tsx +1084 -0
  302. package/corpus/toolkit/src/editor/BubbleToolbar.tsx +253 -0
  303. package/corpus/toolkit/src/editor/CodeBlockNode.tsx +254 -0
  304. package/corpus/toolkit/src/editor/DragHandle.ts +1437 -0
  305. package/corpus/toolkit/src/editor/ImageExtension.ts +306 -0
  306. package/corpus/toolkit/src/editor/RegistryBlockContext.tsx +107 -0
  307. package/corpus/toolkit/src/editor/RegistryBlockNode.tsx +652 -0
  308. package/corpus/toolkit/src/editor/RichMarkdownEditor.tsx +81 -0
  309. package/corpus/toolkit/src/editor/RunId.ts +61 -0
  310. package/corpus/toolkit/src/editor/SharedRichEditor.tsx +321 -0
  311. package/corpus/toolkit/src/editor/SlashCommandMenu.tsx +310 -0
  312. package/corpus/toolkit/src/editor/extensions.ts +402 -0
  313. package/corpus/toolkit/src/editor/gfmDoc.ts +92 -0
  314. package/corpus/toolkit/src/editor/index.ts +93 -0
  315. package/corpus/toolkit/src/editor/registrySlashCommands.ts +131 -0
  316. package/corpus/toolkit/src/editor/surgical-apply.ts +192 -0
  317. package/corpus/toolkit/src/editor/useCollabReconcile.ts +589 -0
  318. package/corpus/toolkit/src/editor.css +510 -0
  319. package/corpus/toolkit/src/hooks/index.ts +2 -0
  320. package/corpus/toolkit/src/hooks/use-mobile.tsx +21 -0
  321. package/corpus/toolkit/src/hooks/use-toast.ts +188 -0
  322. package/corpus/toolkit/src/index.ts +8 -0
  323. package/corpus/toolkit/src/onboarding/index.ts +1 -0
  324. package/corpus/toolkit/src/provider.tsx +47 -0
  325. package/corpus/toolkit/src/sharing/VisibilityBadge.tsx +37 -0
  326. package/corpus/toolkit/src/sharing/index.ts +4 -0
  327. package/corpus/toolkit/src/styles.css +21 -0
  328. package/corpus/toolkit/src/ui/accordion.tsx +56 -0
  329. package/corpus/toolkit/src/ui/alert-dialog.tsx +141 -0
  330. package/corpus/toolkit/src/ui/alert.tsx +59 -0
  331. package/corpus/toolkit/src/ui/aspect-ratio.tsx +5 -0
  332. package/corpus/toolkit/src/ui/avatar.tsx +48 -0
  333. package/corpus/toolkit/src/ui/badge.tsx +37 -0
  334. package/corpus/toolkit/src/ui/breadcrumb.tsx +115 -0
  335. package/corpus/toolkit/src/ui/button.tsx +78 -0
  336. package/corpus/toolkit/src/ui/calendar.tsx +214 -0
  337. package/corpus/toolkit/src/ui/card.tsx +86 -0
  338. package/corpus/toolkit/src/ui/carousel.tsx +260 -0
  339. package/corpus/toolkit/src/ui/chart.tsx +375 -0
  340. package/corpus/toolkit/src/ui/checkbox.tsx +28 -0
  341. package/corpus/toolkit/src/ui/collapsible.tsx +9 -0
  342. package/corpus/toolkit/src/ui/command.tsx +174 -0
  343. package/corpus/toolkit/src/ui/context-menu.tsx +202 -0
  344. package/corpus/toolkit/src/ui/date-picker.tsx +64 -0
  345. package/corpus/toolkit/src/ui/dialog.tsx +146 -0
  346. package/corpus/toolkit/src/ui/drawer.tsx +126 -0
  347. package/corpus/toolkit/src/ui/dropdown-menu.tsx +218 -0
  348. package/corpus/toolkit/src/ui/form.tsx +177 -0
  349. package/corpus/toolkit/src/ui/hover-card.tsx +27 -0
  350. package/corpus/toolkit/src/ui/index.ts +52 -0
  351. package/corpus/toolkit/src/ui/input-otp.tsx +69 -0
  352. package/corpus/toolkit/src/ui/input.tsx +22 -0
  353. package/corpus/toolkit/src/ui/label.tsx +24 -0
  354. package/corpus/toolkit/src/ui/logo.tsx +36 -0
  355. package/corpus/toolkit/src/ui/menubar.tsx +235 -0
  356. package/corpus/toolkit/src/ui/navigation-menu.tsx +128 -0
  357. package/corpus/toolkit/src/ui/pagination.tsx +121 -0
  358. package/corpus/toolkit/src/ui/popover.tsx +44 -0
  359. package/corpus/toolkit/src/ui/progress.tsx +26 -0
  360. package/corpus/toolkit/src/ui/radio-group.tsx +42 -0
  361. package/corpus/toolkit/src/ui/resizable.tsx +43 -0
  362. package/corpus/toolkit/src/ui/scroll-area.tsx +46 -0
  363. package/corpus/toolkit/src/ui/select.tsx +158 -0
  364. package/corpus/toolkit/src/ui/separator.tsx +29 -0
  365. package/corpus/toolkit/src/ui/sheet.tsx +160 -0
  366. package/corpus/toolkit/src/ui/sidebar.tsx +777 -0
  367. package/corpus/toolkit/src/ui/skeleton.tsx +18 -0
  368. package/corpus/toolkit/src/ui/slider.tsx +26 -0
  369. package/corpus/toolkit/src/ui/sonner.tsx +58 -0
  370. package/corpus/toolkit/src/ui/spinner.tsx +17 -0
  371. package/corpus/toolkit/src/ui/switch.tsx +27 -0
  372. package/corpus/toolkit/src/ui/table.tsx +117 -0
  373. package/corpus/toolkit/src/ui/tabs.tsx +53 -0
  374. package/corpus/toolkit/src/ui/textarea.tsx +23 -0
  375. package/corpus/toolkit/src/ui/toast.tsx +127 -0
  376. package/corpus/toolkit/src/ui/toaster.tsx +33 -0
  377. package/corpus/toolkit/src/ui/toggle-group.tsx +59 -0
  378. package/corpus/toolkit/src/ui/toggle.tsx +43 -0
  379. package/corpus/toolkit/src/ui/tooltip.tsx +53 -0
  380. package/corpus/toolkit/src/ui/use-toast.ts +3 -0
  381. package/corpus/toolkit/src/utils.ts +8 -0
  382. package/corpus/toolkit/tsconfig.json +23 -0
  383. package/dist/cli/atomic-json-file.d.ts +30 -0
  384. package/dist/cli/atomic-json-file.d.ts.map +1 -0
  385. package/dist/cli/atomic-json-file.js +240 -0
  386. package/dist/cli/atomic-json-file.js.map +1 -0
  387. package/dist/cli/claude-code-participant.d.ts +77 -0
  388. package/dist/cli/claude-code-participant.d.ts.map +1 -0
  389. package/dist/cli/claude-code-participant.js +355 -0
  390. package/dist/cli/claude-code-participant.js.map +1 -0
  391. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  392. package/dist/cli/code-agent-executor.js +18 -10
  393. package/dist/cli/code-agent-executor.js.map +1 -1
  394. package/dist/cli/code-agent-runs.concurrent-worker.d.ts +2 -0
  395. package/dist/cli/code-agent-runs.concurrent-worker.d.ts.map +1 -0
  396. package/dist/cli/code-agent-runs.concurrent-worker.js +32 -0
  397. package/dist/cli/code-agent-runs.concurrent-worker.js.map +1 -0
  398. package/dist/cli/code-agent-runs.d.ts.map +1 -1
  399. package/dist/cli/code-agent-runs.js +43 -56
  400. package/dist/cli/code-agent-runs.js.map +1 -1
  401. package/dist/cli/codex-cli-participant.d.ts +77 -0
  402. package/dist/cli/codex-cli-participant.d.ts.map +1 -0
  403. package/dist/cli/codex-cli-participant.js +354 -0
  404. package/dist/cli/codex-cli-participant.js.map +1 -0
  405. package/dist/cli/eject.d.ts +21 -0
  406. package/dist/cli/eject.d.ts.map +1 -0
  407. package/dist/cli/eject.js +1145 -0
  408. package/dist/cli/eject.js.map +1 -0
  409. package/dist/cli/index.js +15 -0
  410. package/dist/cli/index.js.map +1 -1
  411. package/dist/cli/multi-frontier-runs.d.ts +115 -0
  412. package/dist/cli/multi-frontier-runs.d.ts.map +1 -1
  413. package/dist/cli/multi-frontier-runs.js +831 -113
  414. package/dist/cli/multi-frontier-runs.js.map +1 -1
  415. package/dist/client/chat/ChatHistoryList.d.ts +1 -87
  416. package/dist/client/chat/ChatHistoryList.d.ts.map +1 -1
  417. package/dist/client/chat/ChatHistoryList.js +1 -82
  418. package/dist/client/chat/ChatHistoryList.js.map +1 -1
  419. package/dist/client/index.d.ts +1 -1
  420. package/dist/client/index.d.ts.map +1 -1
  421. package/dist/client/index.js +1 -1
  422. package/dist/client/index.js.map +1 -1
  423. package/dist/client/resources/McpConnectionSuggestion.d.ts +4 -3
  424. package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
  425. package/dist/client/resources/McpConnectionSuggestion.js +3 -3
  426. package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
  427. package/dist/client/resources/McpIntegrationDialog.d.ts +4 -3
  428. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  429. package/dist/client/resources/McpIntegrationDialog.js +2 -2
  430. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  431. package/dist/client/resources/ResourcesPanel.d.ts +4 -1
  432. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  433. package/dist/client/resources/ResourcesPanel.js +9 -7
  434. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  435. package/dist/client/resources/index.d.ts +3 -0
  436. package/dist/client/resources/index.d.ts.map +1 -1
  437. package/dist/client/resources/index.js +3 -0
  438. package/dist/client/resources/index.js.map +1 -1
  439. package/dist/client/resources/mcp-integration-catalog.d.ts +2 -1
  440. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  441. package/dist/client/resources/mcp-integration-catalog.js +6 -2
  442. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  443. package/dist/client/resources/runtime.d.ts +2 -0
  444. package/dist/client/resources/runtime.d.ts.map +1 -0
  445. package/dist/client/resources/runtime.js +2 -0
  446. package/dist/client/resources/runtime.js.map +1 -0
  447. package/dist/client/setup-connections/catalog.d.ts +2 -0
  448. package/dist/client/setup-connections/catalog.d.ts.map +1 -0
  449. package/dist/client/setup-connections/catalog.js +7 -0
  450. package/dist/client/setup-connections/catalog.js.map +1 -0
  451. package/dist/client/setup-connections/index.d.ts +1 -0
  452. package/dist/client/setup-connections/index.d.ts.map +1 -1
  453. package/dist/client/setup-connections/index.js +1 -0
  454. package/dist/client/setup-connections/index.js.map +1 -1
  455. package/dist/client/setup-connections/runtime.d.ts +2 -0
  456. package/dist/client/setup-connections/runtime.d.ts.map +1 -0
  457. package/dist/client/setup-connections/runtime.js +2 -0
  458. package/dist/client/setup-connections/runtime.js.map +1 -0
  459. package/dist/collab/awareness.d.ts +2 -2
  460. package/dist/collab/awareness.d.ts.map +1 -1
  461. package/dist/collab/routes.d.ts +1 -1
  462. package/dist/connections/catalog.d.ts +5 -3
  463. package/dist/connections/catalog.d.ts.map +1 -1
  464. package/dist/connections/catalog.js +16 -8
  465. package/dist/connections/catalog.js.map +1 -1
  466. package/dist/connections/index.d.ts +1 -1
  467. package/dist/connections/index.d.ts.map +1 -1
  468. package/dist/connections/index.js +1 -1
  469. package/dist/connections/index.js.map +1 -1
  470. package/dist/connections/runtime.d.ts +2 -0
  471. package/dist/connections/runtime.d.ts.map +1 -0
  472. package/dist/connections/runtime.js +2 -0
  473. package/dist/connections/runtime.js.map +1 -0
  474. package/dist/eject/provider-api-definitions.d.ts +34 -0
  475. package/dist/eject/provider-api-definitions.d.ts.map +1 -0
  476. package/dist/eject/provider-api-definitions.js +88 -0
  477. package/dist/eject/provider-api-definitions.js.map +1 -0
  478. package/dist/eject/remote-mcp-presets.d.ts +17 -0
  479. package/dist/eject/remote-mcp-presets.d.ts.map +1 -0
  480. package/dist/eject/remote-mcp-presets.js +81 -0
  481. package/dist/eject/remote-mcp-presets.js.map +1 -0
  482. package/dist/eject/setup-readiness-ui.d.ts +7 -0
  483. package/dist/eject/setup-readiness-ui.d.ts.map +1 -0
  484. package/dist/eject/setup-readiness-ui.js +13 -0
  485. package/dist/eject/setup-readiness-ui.js.map +1 -0
  486. package/dist/eject/workspace-connections.d.ts +11 -0
  487. package/dist/eject/workspace-connections.d.ts.map +1 -0
  488. package/dist/eject/workspace-connections.js +47 -0
  489. package/dist/eject/workspace-connections.js.map +1 -0
  490. package/dist/index.d.ts +1 -1
  491. package/dist/index.d.ts.map +1 -1
  492. package/dist/index.js +1 -1
  493. package/dist/index.js.map +1 -1
  494. package/dist/integrations/adapter-overrides.d.ts +3 -0
  495. package/dist/integrations/adapter-overrides.d.ts.map +1 -0
  496. package/dist/integrations/adapter-overrides.js +7 -0
  497. package/dist/integrations/adapter-overrides.js.map +1 -0
  498. package/dist/integrations/eject/messaging-adapters.d.ts +12 -0
  499. package/dist/integrations/eject/messaging-adapters.d.ts.map +1 -0
  500. package/dist/integrations/eject/messaging-adapters.js +49 -0
  501. package/dist/integrations/eject/messaging-adapters.js.map +1 -0
  502. package/dist/integrations/index.d.ts +5 -3
  503. package/dist/integrations/index.d.ts.map +1 -1
  504. package/dist/integrations/index.js +4 -2
  505. package/dist/integrations/index.js.map +1 -1
  506. package/dist/integrations/plugin.d.ts +32 -1
  507. package/dist/integrations/plugin.d.ts.map +1 -1
  508. package/dist/integrations/plugin.js +19 -12
  509. package/dist/integrations/plugin.js.map +1 -1
  510. package/dist/integrations/runtime.d.ts +2 -0
  511. package/dist/integrations/runtime.d.ts.map +1 -0
  512. package/dist/integrations/runtime.js +2 -0
  513. package/dist/integrations/runtime.js.map +1 -0
  514. package/dist/integrations/slack-manifest.d.ts +1 -1
  515. package/dist/integrations/slack-oauth.d.ts +1 -1
  516. package/dist/integrations/slack-oauth.d.ts.map +1 -1
  517. package/dist/integrations/slack-oauth.js +1 -0
  518. package/dist/integrations/slack-oauth.js.map +1 -1
  519. package/dist/integrations/types.d.ts +3 -1
  520. package/dist/integrations/types.d.ts.map +1 -1
  521. package/dist/integrations/types.js.map +1 -1
  522. package/dist/integrations/webhook-delivery.d.ts +23 -0
  523. package/dist/integrations/webhook-delivery.d.ts.map +1 -0
  524. package/dist/integrations/webhook-delivery.js +32 -0
  525. package/dist/integrations/webhook-delivery.js.map +1 -0
  526. package/dist/notifications/channels.d.ts.map +1 -1
  527. package/dist/notifications/channels.js +4 -9
  528. package/dist/notifications/channels.js.map +1 -1
  529. package/dist/notifications/routes.d.ts +3 -3
  530. package/dist/package-lifecycle/eject-manifest.d.ts +43 -0
  531. package/dist/package-lifecycle/eject-manifest.d.ts.map +1 -0
  532. package/dist/package-lifecycle/eject-manifest.js +219 -0
  533. package/dist/package-lifecycle/eject-manifest.js.map +1 -0
  534. package/dist/package-lifecycle/index.d.ts +1 -0
  535. package/dist/package-lifecycle/index.d.ts.map +1 -1
  536. package/dist/package-lifecycle/index.js +1 -0
  537. package/dist/package-lifecycle/index.js.map +1 -1
  538. package/dist/progress/routes.d.ts +1 -1
  539. package/dist/provider-api/actions/custom-provider-registration.d.ts +136 -0
  540. package/dist/provider-api/actions/custom-provider-registration.d.ts.map +1 -0
  541. package/dist/provider-api/actions/custom-provider-registration.js +154 -0
  542. package/dist/provider-api/actions/custom-provider-registration.js.map +1 -0
  543. package/dist/provider-api/actions/delete-staged-dataset.d.ts +1 -1
  544. package/dist/provider-api/actions/delete-staged-dataset.d.ts.map +1 -1
  545. package/dist/provider-api/actions/delete-staged-dataset.js +3 -34
  546. package/dist/provider-api/actions/delete-staged-dataset.js.map +1 -1
  547. package/dist/provider-api/actions/list-staged-datasets.d.ts +2 -3
  548. package/dist/provider-api/actions/list-staged-datasets.d.ts.map +1 -1
  549. package/dist/provider-api/actions/list-staged-datasets.js +3 -40
  550. package/dist/provider-api/actions/list-staged-datasets.js.map +1 -1
  551. package/dist/provider-api/actions/provider-api-audit.d.ts +7 -0
  552. package/dist/provider-api/actions/provider-api-audit.d.ts.map +1 -0
  553. package/dist/provider-api/actions/provider-api-audit.js +74 -0
  554. package/dist/provider-api/actions/provider-api-audit.js.map +1 -0
  555. package/dist/provider-api/actions/provider-api.d.ts +370 -0
  556. package/dist/provider-api/actions/provider-api.d.ts.map +1 -0
  557. package/dist/provider-api/actions/provider-api.js +280 -0
  558. package/dist/provider-api/actions/provider-api.js.map +1 -0
  559. package/dist/provider-api/actions/query-staged-dataset.d.ts +1 -1
  560. package/dist/provider-api/actions/query-staged-dataset.d.ts.map +1 -1
  561. package/dist/provider-api/actions/query-staged-dataset.js +3 -116
  562. package/dist/provider-api/actions/query-staged-dataset.js.map +1 -1
  563. package/dist/provider-api/actions/staged-datasets.d.ts +190 -0
  564. package/dist/provider-api/actions/staged-datasets.d.ts.map +1 -0
  565. package/dist/provider-api/actions/staged-datasets.js +210 -0
  566. package/dist/provider-api/actions/staged-datasets.js.map +1 -0
  567. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  568. package/dist/provider-api/gong.d.ts +44 -0
  569. package/dist/provider-api/gong.d.ts.map +1 -0
  570. package/dist/provider-api/gong.js +56 -0
  571. package/dist/provider-api/gong.js.map +1 -0
  572. package/dist/provider-api/index.d.ts +6 -2
  573. package/dist/provider-api/index.d.ts.map +1 -1
  574. package/dist/provider-api/index.js +25 -12
  575. package/dist/provider-api/index.js.map +1 -1
  576. package/dist/provider-api/runtime.d.ts +2 -0
  577. package/dist/provider-api/runtime.d.ts.map +1 -0
  578. package/dist/provider-api/runtime.js +2 -0
  579. package/dist/provider-api/runtime.js.map +1 -0
  580. package/dist/scripts/docs/source-search.d.ts +2 -1
  581. package/dist/scripts/docs/source-search.d.ts.map +1 -1
  582. package/dist/scripts/docs/source-search.js +2 -1
  583. package/dist/scripts/docs/source-search.js.map +1 -1
  584. package/dist/secrets/routes.d.ts +9 -9
  585. package/dist/server/google-oauth-credentials.d.ts +35 -0
  586. package/dist/server/google-oauth-credentials.d.ts.map +1 -1
  587. package/dist/server/google-oauth-credentials.js +48 -2
  588. package/dist/server/google-oauth-credentials.js.map +1 -1
  589. package/dist/server/index.d.ts +1 -1
  590. package/dist/server/index.d.ts.map +1 -1
  591. package/dist/server/index.js +1 -1
  592. package/dist/server/index.js.map +1 -1
  593. package/dist/server/transcribe-voice.d.ts +1 -1
  594. package/dist/setup-connections/index.d.ts +1 -1
  595. package/dist/setup-connections/index.d.ts.map +1 -1
  596. package/dist/setup-connections/index.js +1 -1
  597. package/dist/setup-connections/index.js.map +1 -1
  598. package/dist/shared/merge-by-id.d.ts +5 -0
  599. package/dist/shared/merge-by-id.d.ts.map +1 -0
  600. package/dist/shared/merge-by-id.js +7 -0
  601. package/dist/shared/merge-by-id.js.map +1 -0
  602. package/dist/styles/chat-history-list.css +1 -313
  603. package/dist/templates/chat/.agents/skills/actions/SKILL.md +32 -12
  604. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +21 -9
  605. package/dist/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  606. package/dist/templates/chat/.agents/skills/self-modifying-code/SKILL.md +14 -2
  607. package/dist/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  608. package/dist/templates/chat/AGENTS.md +6 -2
  609. package/dist/templates/chat/app/components/layout/Sidebar.tsx +51 -157
  610. package/dist/templates/default/.agents/skills/actions/SKILL.md +32 -12
  611. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +21 -9
  612. package/dist/templates/default/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  613. package/dist/templates/default/.agents/skills/integration-webhooks/SKILL.md +323 -0
  614. package/dist/templates/default/.agents/skills/onboarding/SKILL.md +56 -0
  615. package/dist/templates/default/.agents/skills/secrets/SKILL.md +273 -0
  616. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +14 -2
  617. package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  618. package/dist/templates/default/AGENTS.md +26 -18
  619. package/dist/templates/headless/.agents/skills/actions/SKILL.md +512 -0
  620. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +21 -9
  621. package/dist/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  622. package/dist/templates/headless/.agents/skills/integration-webhooks/SKILL.md +323 -0
  623. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +273 -0
  624. package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  625. package/dist/templates/headless/AGENTS.md +8 -0
  626. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -12
  627. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +21 -9
  628. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +7 -0
  629. package/dist/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  630. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +14 -2
  631. package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  632. package/dist/templates/workspace-core/AGENTS.md +4 -0
  633. package/dist/templates/workspace-root/AGENTS.md +4 -0
  634. package/dist/vite/client.d.ts.map +1 -1
  635. package/dist/vite/client.js +22 -2
  636. package/dist/vite/client.js.map +1 -1
  637. package/docs/AGENTS.md +7 -0
  638. package/docs/content/agent-native-toolkit.mdx +38 -0
  639. package/docs/content/integrations.mdx +63 -2
  640. package/docs/content/package-lifecycle.mdx +54 -4
  641. package/docs/design/provider-integration-authority.md +117 -0
  642. package/package.json +20 -2
  643. package/src/a2a/agent-card.ts +83 -0
  644. package/src/a2a/artifact-response.ts +1648 -0
  645. package/src/a2a/auth-policy.ts +66 -0
  646. package/src/a2a/caller-auth.ts +102 -0
  647. package/src/a2a/client.ts +876 -0
  648. package/src/a2a/correlation.ts +50 -0
  649. package/src/a2a/handlers.ts +1285 -0
  650. package/src/a2a/index.ts +50 -0
  651. package/src/a2a/invoke.ts +348 -0
  652. package/src/a2a/response-text.ts +53 -0
  653. package/src/a2a/server.ts +586 -0
  654. package/src/a2a/task-store.ts +917 -0
  655. package/src/a2a/types.ts +221 -0
  656. package/src/a2a-claims.ts +52 -0
  657. package/src/action-change-marker.ts +74 -0
  658. package/src/action-ui.ts +38 -0
  659. package/src/action.ts +1406 -0
  660. package/src/adapters/cli/index.ts +6 -0
  661. package/src/adapters/cli/registry.ts +73 -0
  662. package/src/adapters/cli/shell-adapter.ts +92 -0
  663. package/src/adapters/cli/types.ts +27 -0
  664. package/src/agent/action-continuation-guidance.ts +38 -0
  665. package/src/agent/app-model-defaults.ts +201 -0
  666. package/src/agent/context-xray/actions/context-evict.ts +56 -0
  667. package/src/agent/context-xray/actions/context-manifest-get.ts +75 -0
  668. package/src/agent/context-xray/actions/context-pin.ts +56 -0
  669. package/src/agent/context-xray/actions/context-preview-get.ts +134 -0
  670. package/src/agent/context-xray/actions/context-report.ts +75 -0
  671. package/src/agent/context-xray/actions/context-restore.ts +51 -0
  672. package/src/agent/context-xray/actions/errors.ts +40 -0
  673. package/src/agent/context-xray/apply-directives.ts +137 -0
  674. package/src/agent/context-xray/directives-store.ts +178 -0
  675. package/src/agent/context-xray/identity.ts +37 -0
  676. package/src/agent/context-xray/manifest.ts +284 -0
  677. package/src/agent/context-xray/migrations.ts +32 -0
  678. package/src/agent/context-xray/plugin.ts +23 -0
  679. package/src/agent/context-xray/schema.ts +17 -0
  680. package/src/agent/context-xray/segments.ts +194 -0
  681. package/src/agent/context-xray/tokenize.ts +93 -0
  682. package/src/agent/default-model.ts +5 -0
  683. package/src/agent/durable-background.ts +547 -0
  684. package/src/agent/engine/ai-sdk-engine.ts +565 -0
  685. package/src/agent/engine/anthropic-engine.ts +320 -0
  686. package/src/agent/engine/builder-engine.ts +1110 -0
  687. package/src/agent/engine/builder-gateway-headers.ts +44 -0
  688. package/src/agent/engine/builtin.ts +122 -0
  689. package/src/agent/engine/context-directives-transform.ts +73 -0
  690. package/src/agent/engine/continuation-dispatch-retry.ts +168 -0
  691. package/src/agent/engine/credential-errors.ts +69 -0
  692. package/src/agent/engine/index.ts +54 -0
  693. package/src/agent/engine/openai-compatible-endpoint.ts +25 -0
  694. package/src/agent/engine/output-tokens.ts +181 -0
  695. package/src/agent/engine/provider-env-vars.ts +69 -0
  696. package/src/agent/engine/registry.ts +853 -0
  697. package/src/agent/engine/tool-call-journal-seed.ts +85 -0
  698. package/src/agent/engine/translate-ai-sdk.ts +407 -0
  699. package/src/agent/engine/translate-anthropic.ts +611 -0
  700. package/src/agent/engine/types.ts +310 -0
  701. package/src/agent/harness/acp-adapter.ts +893 -0
  702. package/src/agent/harness/acp-builtin.ts +70 -0
  703. package/src/agent/harness/ai-sdk-adapter.ts +509 -0
  704. package/src/agent/harness/background.ts +365 -0
  705. package/src/agent/harness/builtin.ts +50 -0
  706. package/src/agent/harness/index.ts +86 -0
  707. package/src/agent/harness/lifecycle.ts +498 -0
  708. package/src/agent/harness/registry.ts +97 -0
  709. package/src/agent/harness/runner.ts +182 -0
  710. package/src/agent/harness/store.ts +466 -0
  711. package/src/agent/harness/translate.ts +130 -0
  712. package/src/agent/harness/types.ts +129 -0
  713. package/src/agent/index.ts +32 -0
  714. package/src/agent/loop-settings.ts +186 -0
  715. package/src/agent/model-config.ts +363 -0
  716. package/src/agent/observational-memory/compactor.ts +78 -0
  717. package/src/agent/observational-memory/config.ts +78 -0
  718. package/src/agent/observational-memory/index.ts +79 -0
  719. package/src/agent/observational-memory/internal-run.ts +89 -0
  720. package/src/agent/observational-memory/message-text.ts +49 -0
  721. package/src/agent/observational-memory/migrations.ts +44 -0
  722. package/src/agent/observational-memory/observer.ts +128 -0
  723. package/src/agent/observational-memory/plugin.ts +35 -0
  724. package/src/agent/observational-memory/prompts.ts +53 -0
  725. package/src/agent/observational-memory/read.ts +131 -0
  726. package/src/agent/observational-memory/reflector.ts +119 -0
  727. package/src/agent/observational-memory/schema.ts +48 -0
  728. package/src/agent/observational-memory/store.ts +275 -0
  729. package/src/agent/observational-memory/types.ts +65 -0
  730. package/src/agent/processors.ts +209 -0
  731. package/src/agent/production-agent.ts +8254 -0
  732. package/src/agent/run-loop-with-resume.ts +394 -0
  733. package/src/agent/run-manager.ts +1825 -0
  734. package/src/agent/run-ownership.ts +75 -0
  735. package/src/agent/run-store.ts +2601 -0
  736. package/src/agent/runtime-context.ts +169 -0
  737. package/src/agent/thread-data-builder.ts +1584 -0
  738. package/src/agent/tool-call-journal.ts +368 -0
  739. package/src/agent/tool-error-redaction.ts +52 -0
  740. package/src/agent/tool-result-images.ts +178 -0
  741. package/src/agent/tool-search.ts +376 -0
  742. package/src/agent/types.ts +372 -0
  743. package/src/agent-native/index.ts +198 -0
  744. package/src/agent-web/config.ts +253 -0
  745. package/src/agent-web/generator.ts +382 -0
  746. package/src/agent-web/index.ts +36 -0
  747. package/src/appearance/actions/change-appearance.ts +35 -0
  748. package/src/application-state/emitter.ts +81 -0
  749. package/src/application-state/handlers.ts +121 -0
  750. package/src/application-state/index.ts +44 -0
  751. package/src/application-state/script-helpers.ts +155 -0
  752. package/src/application-state/store.ts +274 -0
  753. package/src/assets/branding/agent-native.icon/Assets/background.svg +3 -0
  754. package/src/assets/branding/agent-native.icon/Assets/foreground.svg +12 -0
  755. package/src/assets/branding/agent-native.icon/icon.json +49 -0
  756. package/src/assets/branding/favicon.png +0 -0
  757. package/src/assets/branding/favicon.svg +1 -0
  758. package/src/assets/branding/icon-on-dark.svg +10 -0
  759. package/src/assets/branding/icon-on-light.svg +10 -0
  760. package/src/assets/branding/mac-app-icon.svg +1 -0
  761. package/src/assets/branding/slack-bot/agent-native-1024.png +0 -0
  762. package/src/assets/branding/slack-bot/agent-native-512.png +0 -0
  763. package/src/assets/branding/tray-icon.svg +6 -0
  764. package/src/assets/branding/wordmark-on-dark.svg +21 -0
  765. package/src/assets/branding/wordmark-on-light.svg +21 -0
  766. package/src/assets/fonts/LICENSE_LIBERATION +96 -0
  767. package/src/assets/fonts/LICENSE_NOTO_NASKH_ARABIC +93 -0
  768. package/src/assets/fonts/LiberationSans-Bold.ttf +0 -0
  769. package/src/assets/fonts/LiberationSans-Regular.ttf +0 -0
  770. package/src/assets/fonts/NotoNaskhArabic-Variable.ttf +0 -0
  771. package/src/audit/actions/export-audit-events.ts +162 -0
  772. package/src/audit/actions/get-audit-event.ts +24 -0
  773. package/src/audit/actions/list-audit-events.ts +66 -0
  774. package/src/audit/cleanup-job.ts +100 -0
  775. package/src/audit/config.ts +90 -0
  776. package/src/audit/index.ts +43 -0
  777. package/src/audit/record.ts +180 -0
  778. package/src/audit/redact.ts +109 -0
  779. package/src/audit/store.ts +346 -0
  780. package/src/audit/types.ts +141 -0
  781. package/src/automation/index.ts +802 -0
  782. package/src/brand-kit/brand-signals.ts +124 -0
  783. package/src/brand-kit/fig/index.ts +64 -0
  784. package/src/brand-kit/index.ts +72 -0
  785. package/src/brand-kit/types.ts +113 -0
  786. package/src/browser-sessions/actions.ts +302 -0
  787. package/src/browser-sessions/routes.ts +234 -0
  788. package/src/browser-sessions/store.ts +679 -0
  789. package/src/browser-sessions/types.ts +84 -0
  790. package/src/changelog/parse.ts +328 -0
  791. package/src/chat-threads/emitter.ts +22 -0
  792. package/src/chat-threads/schema.ts +43 -0
  793. package/src/chat-threads/store.ts +1311 -0
  794. package/src/checkpoints/index.ts +15 -0
  795. package/src/checkpoints/service.ts +142 -0
  796. package/src/checkpoints/store.ts +153 -0
  797. package/src/cli/add.ts +425 -0
  798. package/src/cli/agent.ts +424 -0
  799. package/src/cli/agents.ts +122 -0
  800. package/src/cli/app-skill.ts +1455 -0
  801. package/src/cli/atomic-json-file.ts +324 -0
  802. package/src/cli/audit-agent-web.ts +324 -0
  803. package/src/cli/changelog.ts +199 -0
  804. package/src/cli/claude-code-participant.ts +515 -0
  805. package/src/cli/code-agent-commands.ts +270 -0
  806. package/src/cli/code-agent-connector.ts +1109 -0
  807. package/src/cli/code-agent-executor.ts +1970 -0
  808. package/src/cli/code-agent-output-smoother.ts +140 -0
  809. package/src/cli/code-agent-runs.concurrent-worker.ts +33 -0
  810. package/src/cli/code-agent-runs.ts +548 -0
  811. package/src/cli/code.ts +1805 -0
  812. package/src/cli/codex-cli-participant.ts +496 -0
  813. package/src/cli/connect.ts +2714 -0
  814. package/src/cli/content-local.ts +442 -0
  815. package/src/cli/context-xray-local.ts +2193 -0
  816. package/src/cli/create-workspace.ts +30 -0
  817. package/src/cli/create.ts +2247 -0
  818. package/src/cli/deploy-build.ts +50 -0
  819. package/src/cli/design-connect.ts +3187 -0
  820. package/src/cli/doctor.ts +472 -0
  821. package/src/cli/eject.ts +1594 -0
  822. package/src/cli/eval.ts +134 -0
  823. package/src/cli/gateway-helpers.ts +70 -0
  824. package/src/cli/index.ts +1217 -0
  825. package/src/cli/info.ts +105 -0
  826. package/src/cli/invoke.ts +288 -0
  827. package/src/cli/mcp-config-writers.ts +857 -0
  828. package/src/cli/mcp.ts +594 -0
  829. package/src/cli/migrate.ts +1813 -0
  830. package/src/cli/migration-codemod.ts +646 -0
  831. package/src/cli/multi-frontier-runs.ts +1952 -0
  832. package/src/cli/package-lifecycle.ts +823 -0
  833. package/src/cli/plan-blocks.ts +143 -0
  834. package/src/cli/plan-local.ts +3189 -0
  835. package/src/cli/plan-publish-store.ts +138 -0
  836. package/src/cli/pr-visual-recap-workflow.ts +2 -0
  837. package/src/cli/recap.ts +5 -0
  838. package/src/cli/setup-agents.ts +120 -0
  839. package/src/cli/skills-content/assets-skill.ts +83 -0
  840. package/src/cli/skills-content/canvas.ts +153 -0
  841. package/src/cli/skills-content/connection.ts +52 -0
  842. package/src/cli/skills-content/content-skill.ts +120 -0
  843. package/src/cli/skills-content/design-exploration-skill.ts +118 -0
  844. package/src/cli/skills-content/design-visual-edit-skill.ts +228 -0
  845. package/src/cli/skills-content/document-quality.ts +196 -0
  846. package/src/cli/skills-content/exemplar.ts +69 -0
  847. package/src/cli/skills-content/help.ts +77 -0
  848. package/src/cli/skills-content/index.ts +22 -0
  849. package/src/cli/skills-content/local-files.ts +100 -0
  850. package/src/cli/skills-content/plan-setup-auth.ts +80 -0
  851. package/src/cli/skills-content/visual-plan-skill.ts +493 -0
  852. package/src/cli/skills-content/visual-recap-skill.ts +553 -0
  853. package/src/cli/skills-content/visualize-repo-skill.ts +93 -0
  854. package/src/cli/skills-content/wireframe.ts +349 -0
  855. package/src/cli/skills.ts +4340 -0
  856. package/src/cli/sync-builder-starter-manifest.ts +631 -0
  857. package/src/cli/telemetry.ts +136 -0
  858. package/src/cli/templates-meta.ts +269 -0
  859. package/src/cli/upgrade.ts +956 -0
  860. package/src/cli/visualize-repo.ts +715 -0
  861. package/src/cli/workspace-dev.ts +1486 -0
  862. package/src/cli/workspacify.ts +258 -0
  863. package/src/client/AgentAskPopover.tsx +89 -0
  864. package/src/client/AgentChatHome.tsx +66 -0
  865. package/src/client/AgentNative.tsx +161 -0
  866. package/src/client/AgentNativeEmbedded.tsx +288 -0
  867. package/src/client/AgentNativeFrame.tsx +151 -0
  868. package/src/client/AgentPanel.tsx +3437 -0
  869. package/src/client/AgentTaskCard.tsx +273 -0
  870. package/src/client/AppearancePicker.tsx +79 -0
  871. package/src/client/AssistantChat.tsx +5793 -0
  872. package/src/client/ClientOnly.tsx +22 -0
  873. package/src/client/CommandMenu.tsx +726 -0
  874. package/src/client/ConnectBuilderCard.tsx +390 -0
  875. package/src/client/DefaultSpinner.tsx +47 -0
  876. package/src/client/ErrorBoundary.tsx +359 -0
  877. package/src/client/ErrorReportActions.tsx +69 -0
  878. package/src/client/FeedbackButton.tsx +553 -0
  879. package/src/client/HighlightedCodeBlock.tsx +173 -0
  880. package/src/client/IframeEmbed.tsx +151 -0
  881. package/src/client/KeepTabOpenNotice.tsx +181 -0
  882. package/src/client/MultiTabAssistantChat.tsx +2576 -0
  883. package/src/client/NewWorkspaceAppFlow.tsx +525 -0
  884. package/src/client/PoweredByBadge.tsx +284 -0
  885. package/src/client/RunStuckBanner.tsx +431 -0
  886. package/src/client/StarfieldBackground.tsx +466 -0
  887. package/src/client/Turnstile.tsx +125 -0
  888. package/src/client/active-run-state.ts +139 -0
  889. package/src/client/agent-chat/index.ts +279 -0
  890. package/src/client/agent-chat-adapter.ts +3692 -0
  891. package/src/client/agent-chat.ts +1233 -0
  892. package/src/client/agent-engine-key.ts +122 -0
  893. package/src/client/agent-page/AgentContextTab.tsx +487 -0
  894. package/src/client/agent-page/AgentEmptyState.tsx +44 -0
  895. package/src/client/agent-page/AgentJobsTab.tsx +441 -0
  896. package/src/client/agent-page/AgentTabFrame.tsx +39 -0
  897. package/src/client/agent-page/AgentTabsPage.tsx +1096 -0
  898. package/src/client/agent-page/types.ts +18 -0
  899. package/src/client/agent-page/use-jobs.ts +150 -0
  900. package/src/client/agent-sidebar-state.ts +166 -0
  901. package/src/client/analytics/index.ts +40 -0
  902. package/src/client/analytics-session.ts +70 -0
  903. package/src/client/analytics.ts +1757 -0
  904. package/src/client/api-path.ts +124 -0
  905. package/src/client/app-providers.tsx +230 -0
  906. package/src/client/appearance.ts +130 -0
  907. package/src/client/application-state.ts +154 -0
  908. package/src/client/assistant-ui-recovery.tsx +205 -0
  909. package/src/client/automation/index.ts +4 -0
  910. package/src/client/automation.ts +22 -0
  911. package/src/client/blocks/AiEditableField.tsx +30 -0
  912. package/src/client/blocks/BlockView.tsx +145 -0
  913. package/src/client/blocks/RegistryBlockDataProvider.tsx +121 -0
  914. package/src/client/blocks/SchemaBlockEditor.tsx +457 -0
  915. package/src/client/blocks/agent.ts +95 -0
  916. package/src/client/blocks/code-block-direction.ts +21 -0
  917. package/src/client/blocks/index.ts +308 -0
  918. package/src/client/blocks/library/AnnotatedCodeBlock.tsx +710 -0
  919. package/src/client/blocks/library/ApiEndpointBlock.tsx +1120 -0
  920. package/src/client/blocks/library/DataModelBlock.tsx +775 -0
  921. package/src/client/blocks/library/DiffBlock.tsx +1854 -0
  922. package/src/client/blocks/library/FileTreeBlock.tsx +868 -0
  923. package/src/client/blocks/library/HighlightedCode.tsx +260 -0
  924. package/src/client/blocks/library/JsonExplorerBlock.tsx +608 -0
  925. package/src/client/blocks/library/MermaidBlock.tsx +362 -0
  926. package/src/client/blocks/library/OpenApiSpecBlock.tsx +998 -0
  927. package/src/client/blocks/library/annotated-code.config.ts +101 -0
  928. package/src/client/blocks/library/annotation-rail.tsx +1020 -0
  929. package/src/client/blocks/library/api-endpoint.config.ts +207 -0
  930. package/src/client/blocks/library/block-copy.ts +399 -0
  931. package/src/client/blocks/library/callout.config.ts +57 -0
  932. package/src/client/blocks/library/callout.tsx +149 -0
  933. package/src/client/blocks/library/checklist.config.ts +56 -0
  934. package/src/client/blocks/library/checklist.tsx +208 -0
  935. package/src/client/blocks/library/code-filename-label.tsx +59 -0
  936. package/src/client/blocks/library/code-highlight.tsx +178 -0
  937. package/src/client/blocks/library/code-tabs.config.ts +70 -0
  938. package/src/client/blocks/library/code-tabs.tsx +606 -0
  939. package/src/client/blocks/library/code.config.ts +69 -0
  940. package/src/client/blocks/library/code.tsx +440 -0
  941. package/src/client/blocks/library/columns.config.ts +85 -0
  942. package/src/client/blocks/library/columns.tsx +334 -0
  943. package/src/client/blocks/library/data-model.config.ts +152 -0
  944. package/src/client/blocks/library/dev-doc-ui.tsx +191 -0
  945. package/src/client/blocks/library/diagram.config.ts +222 -0
  946. package/src/client/blocks/library/diagram.tsx +958 -0
  947. package/src/client/blocks/library/diff.config.ts +115 -0
  948. package/src/client/blocks/library/file-tree.config.ts +92 -0
  949. package/src/client/blocks/library/html.config.ts +79 -0
  950. package/src/client/blocks/library/html.tsx +258 -0
  951. package/src/client/blocks/library/json-explorer.config.ts +74 -0
  952. package/src/client/blocks/library/mermaid.config.ts +49 -0
  953. package/src/client/blocks/library/narrow-container.tsx +37 -0
  954. package/src/client/blocks/library/openapi-spec.config.ts +66 -0
  955. package/src/client/blocks/library/question-form.config.ts +127 -0
  956. package/src/client/blocks/library/question-form.tsx +944 -0
  957. package/src/client/blocks/library/sanitize-html.ts +343 -0
  958. package/src/client/blocks/library/server-specs.ts +338 -0
  959. package/src/client/blocks/library/specs.tsx +308 -0
  960. package/src/client/blocks/library/table.config.ts +61 -0
  961. package/src/client/blocks/library/table.tsx +449 -0
  962. package/src/client/blocks/library/tabs.config.ts +96 -0
  963. package/src/client/blocks/library/tabs.tsx +665 -0
  964. package/src/client/blocks/library/wireframe-icons.ts +210 -0
  965. package/src/client/blocks/library/wireframe-kit.tsx +1850 -0
  966. package/src/client/blocks/library/wireframe.config.ts +539 -0
  967. package/src/client/blocks/library/wireframe.tsx +535 -0
  968. package/src/client/blocks/mdx.ts +364 -0
  969. package/src/client/blocks/provider.tsx +51 -0
  970. package/src/client/blocks/registry.ts +83 -0
  971. package/src/client/blocks/schema-form/introspect.ts +201 -0
  972. package/src/client/blocks/server.ts +170 -0
  973. package/src/client/blocks/types.ts +415 -0
  974. package/src/client/browser-session-bridge.ts +548 -0
  975. package/src/client/browser-tab-id.ts +36 -0
  976. package/src/client/builder-frame.ts +194 -0
  977. package/src/client/builder-mark.tsx +21 -0
  978. package/src/client/changelog/Changelog.tsx +333 -0
  979. package/src/client/changelog/index.ts +1 -0
  980. package/src/client/chat/ChatHistoryList.tsx +7 -0
  981. package/src/client/chat/attachment-adapters.ts +426 -0
  982. package/src/client/chat/connectors.ts +1579 -0
  983. package/src/client/chat/index.ts +160 -0
  984. package/src/client/chat/markdown-renderer.tsx +693 -0
  985. package/src/client/chat/message-components.tsx +1326 -0
  986. package/src/client/chat/repo-helpers.ts +281 -0
  987. package/src/client/chat/run-recovery.tsx +994 -0
  988. package/src/client/chat/runtime.ts +1810 -0
  989. package/src/client/chat/tool-call-display.tsx +1270 -0
  990. package/src/client/chat/tool-render-registry.tsx +144 -0
  991. package/src/client/chat/use-agent-chat-lifecycle-tracking.ts +74 -0
  992. package/src/client/chat/use-reconnect-reader-owner.ts +28 -0
  993. package/src/client/chat/widgets/DataChartRenderer.tsx +242 -0
  994. package/src/client/chat/widgets/DataChartWidget.tsx +51 -0
  995. package/src/client/chat/widgets/DataInsightsWidget.tsx +59 -0
  996. package/src/client/chat/widgets/DataTableWidget.tsx +240 -0
  997. package/src/client/chat/widgets/InlineExtensionWidget.tsx +60 -0
  998. package/src/client/chat/widgets/builtin-tool-renderers.tsx +213 -0
  999. package/src/client/chat/widgets/data-widget-types.ts +1 -0
  1000. package/src/client/chat/widgets/inline-extension-result.ts +86 -0
  1001. package/src/client/chat-model-groups.ts +171 -0
  1002. package/src/client/chat-thread-url.ts +11 -0
  1003. package/src/client/chat-view-transition.ts +213 -0
  1004. package/src/client/client-action.ts +21 -0
  1005. package/src/client/client-bootstrap.ts +7 -0
  1006. package/src/client/client-surface.ts +41 -0
  1007. package/src/client/clipboard.ts +89 -0
  1008. package/src/client/code-agent-chat-adapter.ts +537 -0
  1009. package/src/client/collab/index.ts +60 -0
  1010. package/src/client/command-navigation/index.ts +15 -0
  1011. package/src/client/comments-review/index.ts +1 -0
  1012. package/src/client/components/AgentPresenceChip.tsx +4 -0
  1013. package/src/client/components/ApiKeySettings.tsx +309 -0
  1014. package/src/client/components/CodeAgentIndicator.tsx +59 -0
  1015. package/src/client/components/CodeRequiredDialog.tsx +470 -0
  1016. package/src/client/components/LiveCursorOverlay.tsx +5 -0
  1017. package/src/client/components/MissingKeyCard.tsx +61 -0
  1018. package/src/client/components/PresenceBar.tsx +4 -0
  1019. package/src/client/components/RecentEditHighlights.tsx +4 -0
  1020. package/src/client/components/RemoteSelectionRings.tsx +5 -0
  1021. package/src/client/components/icons/AgentNativeIcon.tsx +46 -0
  1022. package/src/client/components/ui/dialog.tsx +1 -0
  1023. package/src/client/components/ui/dropdown-menu.tsx +1 -0
  1024. package/src/client/components/ui/hover-card.tsx +1 -0
  1025. package/src/client/components/ui/message-scroller.tsx +131 -0
  1026. package/src/client/components/ui/popover.tsx +1 -0
  1027. package/src/client/components/ui/sheet.tsx +1 -0
  1028. package/src/client/components/ui/tooltip.tsx +1 -0
  1029. package/src/client/composer/index.ts +11 -0
  1030. package/src/client/composer/runtime-adapters.tsx +123 -0
  1031. package/src/client/composer/use-mention-search.ts +7 -0
  1032. package/src/client/composer/wired-components.tsx +53 -0
  1033. package/src/client/context-xray/ContextMeter.tsx +124 -0
  1034. package/src/client/context-xray/ContextSegmentRow.tsx +27 -0
  1035. package/src/client/context-xray/ContextTreemap.tsx +24 -0
  1036. package/src/client/context-xray/ContextXRayPanel.tsx +59 -0
  1037. package/src/client/context-xray/SegmentProvenancePopover.tsx +18 -0
  1038. package/src/client/context-xray/format.ts +27 -0
  1039. package/src/client/conversation/AgentConversation.tsx +555 -0
  1040. package/src/client/conversation/code-agent-transcript.ts +283 -0
  1041. package/src/client/conversation/index.ts +22 -0
  1042. package/src/client/conversation/types.ts +85 -0
  1043. package/src/client/conversation/use-near-bottom-autoscroll.ts +278 -0
  1044. package/src/client/create-query-client.ts +86 -0
  1045. package/src/client/db-admin/DataGrid.tsx +551 -0
  1046. package/src/client/db-admin/DbAdminPage.tsx +218 -0
  1047. package/src/client/db-admin/DevDatabaseLink.tsx +16 -0
  1048. package/src/client/db-admin/EditableCell.tsx +451 -0
  1049. package/src/client/db-admin/FilterBar.tsx +203 -0
  1050. package/src/client/db-admin/ResultsGrid.tsx +114 -0
  1051. package/src/client/db-admin/RowSidePanel.tsx +337 -0
  1052. package/src/client/db-admin/SqlEditor.tsx +840 -0
  1053. package/src/client/db-admin/TableBrowser.tsx +212 -0
  1054. package/src/client/db-admin/TableEditor.tsx +760 -0
  1055. package/src/client/db-admin/cell-format.ts +246 -0
  1056. package/src/client/db-admin/changeset.ts +296 -0
  1057. package/src/client/db-admin/export-utils.ts +78 -0
  1058. package/src/client/db-admin/index.ts +33 -0
  1059. package/src/client/db-admin/sql-storage.ts +134 -0
  1060. package/src/client/db-admin/storage.ts +68 -0
  1061. package/src/client/db-admin/useAgentSync.ts +226 -0
  1062. package/src/client/db-admin/useDbAdmin.ts +294 -0
  1063. package/src/client/dev-overlay/DevOverlay.tsx +576 -0
  1064. package/src/client/dev-overlay/builtins.ts +40 -0
  1065. package/src/client/dev-overlay/index.ts +23 -0
  1066. package/src/client/dev-overlay/registry.ts +67 -0
  1067. package/src/client/dev-overlay/types.ts +64 -0
  1068. package/src/client/dev-overlay/use-dev-option.ts +78 -0
  1069. package/src/client/dev-overlay/use-dev-overlay-shortcut.ts +31 -0
  1070. package/src/client/dynamic-suggestions.ts +455 -0
  1071. package/src/client/embed-auth.ts +544 -0
  1072. package/src/client/embed.ts +54 -0
  1073. package/src/client/error-capture.ts +690 -0
  1074. package/src/client/error-format.ts +211 -0
  1075. package/src/client/error-reporting.ts +100 -0
  1076. package/src/client/extensions/AgentNativeExtensionFrame.e2e-host.tsx +136 -0
  1077. package/src/client/extensions/AgentNativeExtensionFrame.tsx +708 -0
  1078. package/src/client/extensions/EmbeddedExtension.tsx +604 -0
  1079. package/src/client/extensions/ExtensionEditor.tsx +450 -0
  1080. package/src/client/extensions/ExtensionQueryErrorState.tsx +47 -0
  1081. package/src/client/extensions/ExtensionSlot.tsx +345 -0
  1082. package/src/client/extensions/ExtensionViewer.tsx +1502 -0
  1083. package/src/client/extensions/ExtensionViewerPage.tsx +32 -0
  1084. package/src/client/extensions/ExtensionsListPage.tsx +432 -0
  1085. package/src/client/extensions/ExtensionsSidebarSection.tsx +1259 -0
  1086. package/src/client/extensions/InlineExtensionFrame.tsx +660 -0
  1087. package/src/client/extensions/agent-native-extension-runtime.ts +495 -0
  1088. package/src/client/extensions/delete-extension.ts +76 -0
  1089. package/src/client/extensions/extension-load-error.ts +26 -0
  1090. package/src/client/extensions/extension-order.ts +49 -0
  1091. package/src/client/extensions/extension-popularity.ts +61 -0
  1092. package/src/client/extensions/iframe-bridge.ts +377 -0
  1093. package/src/client/extensions/index.ts +84 -0
  1094. package/src/client/extensions/portable-extension.ts +712 -0
  1095. package/src/client/feature-flags/FeatureFlagsPanel.tsx +519 -0
  1096. package/src/client/feature-flags/helpers.ts +77 -0
  1097. package/src/client/feature-flags/index.ts +13 -0
  1098. package/src/client/feature-flags/types.ts +29 -0
  1099. package/src/client/feature-flags/use-feature-flag.ts +26 -0
  1100. package/src/client/feedback-context.ts +90 -0
  1101. package/src/client/frame-protocol.ts +168 -0
  1102. package/src/client/frame.ts +329 -0
  1103. package/src/client/guided-questions.tsx +1152 -0
  1104. package/src/client/history/VersionHistoryPanel.tsx +175 -0
  1105. package/src/client/history/index.ts +21 -0
  1106. package/src/client/history/use-history.ts +114 -0
  1107. package/src/client/hooks/index.ts +46 -0
  1108. package/src/client/host/index.ts +170 -0
  1109. package/src/client/host-bridge.ts +1272 -0
  1110. package/src/client/host-tools.ts +190 -0
  1111. package/src/client/i18n.tsx +798 -0
  1112. package/src/client/index.ts +370 -0
  1113. package/src/client/integrations/IntegrationCard.tsx +186 -0
  1114. package/src/client/integrations/IntegrationsPanel.tsx +633 -0
  1115. package/src/client/integrations/api.ts +295 -0
  1116. package/src/client/integrations/index.ts +29 -0
  1117. package/src/client/integrations/useIntegrationStatus.ts +52 -0
  1118. package/src/client/mcp-app-host.ts +720 -0
  1119. package/src/client/mcp-apps/McpAppRenderer.tsx +760 -0
  1120. package/src/client/navigation/index.ts +54 -0
  1121. package/src/client/notifications/NotificationsBell.tsx +383 -0
  1122. package/src/client/notifications/index.ts +1 -0
  1123. package/src/client/observability/ObservabilityDashboard.tsx +890 -0
  1124. package/src/client/observability/ThumbsFeedback.tsx +160 -0
  1125. package/src/client/observability/index.ts +20 -0
  1126. package/src/client/observability/useObservability.ts +266 -0
  1127. package/src/client/onboarding/OnboardingBanner.tsx +73 -0
  1128. package/src/client/onboarding/OnboardingPanel.tsx +1036 -0
  1129. package/src/client/onboarding/SetupButton.tsx +64 -0
  1130. package/src/client/onboarding/index.ts +25 -0
  1131. package/src/client/onboarding/use-onboarding.ts +164 -0
  1132. package/src/client/onboarding/use-preview-mode.ts +40 -0
  1133. package/src/client/org/InvitationBanner.tsx +148 -0
  1134. package/src/client/org/OrgSwitcher.tsx +754 -0
  1135. package/src/client/org/RequireActiveOrg.tsx +337 -0
  1136. package/src/client/org/TeamPage.tsx +1432 -0
  1137. package/src/client/org/hooks.ts +359 -0
  1138. package/src/client/org/index.ts +70 -0
  1139. package/src/client/org/workspace-app-links.ts +367 -0
  1140. package/src/client/org-team/index.ts +1 -0
  1141. package/src/client/progress/RunsTray.tsx +680 -0
  1142. package/src/client/progress/index.ts +1 -0
  1143. package/src/client/require-session.tsx +142 -0
  1144. package/src/client/resources/BuiltinCapabilityDetail.tsx +258 -0
  1145. package/src/client/resources/McpConnectionSuggestion.tsx +276 -0
  1146. package/src/client/resources/McpIntegrationDialog.tsx +808 -0
  1147. package/src/client/resources/McpServerDetail.tsx +230 -0
  1148. package/src/client/resources/ResourceEditor.tsx +872 -0
  1149. package/src/client/resources/ResourceTree.tsx +860 -0
  1150. package/src/client/resources/ResourcesPanel.tsx +1960 -0
  1151. package/src/client/resources/index.ts +25 -0
  1152. package/src/client/resources/mcp-integration-catalog.ts +771 -0
  1153. package/src/client/resources/mcp-integration-logos.ts +108 -0
  1154. package/src/client/resources/runtime.ts +1 -0
  1155. package/src/client/resources/use-builtin-capabilities.ts +113 -0
  1156. package/src/client/resources/use-mcp-servers.ts +217 -0
  1157. package/src/client/resources/use-resources.ts +402 -0
  1158. package/src/client/review/ReviewCommentComposer.tsx +123 -0
  1159. package/src/client/review/ReviewStatusBadge.tsx +39 -0
  1160. package/src/client/review/ReviewThreadPanel.tsx +625 -0
  1161. package/src/client/review/index.ts +38 -0
  1162. package/src/client/review/use-review.ts +186 -0
  1163. package/src/client/route-chunk-recovery/index.ts +1 -0
  1164. package/src/client/route-chunk-recovery.ts +331 -0
  1165. package/src/client/route-state.ts +455 -0
  1166. package/src/client/route-warmup.tsx +543 -0
  1167. package/src/client/session-replay-iframe.e2e-host.tsx +100 -0
  1168. package/src/client/session-replay.ts +3417 -0
  1169. package/src/client/settings/AgentsSection.tsx +441 -0
  1170. package/src/client/settings/AutomationsSection.tsx +516 -0
  1171. package/src/client/settings/BackgroundAgentSection.tsx +189 -0
  1172. package/src/client/settings/BrowserSection.tsx +110 -0
  1173. package/src/client/settings/DemoModeSection.tsx +48 -0
  1174. package/src/client/settings/SecretsSection.tsx +875 -0
  1175. package/src/client/settings/SettingsPanel.tsx +3162 -0
  1176. package/src/client/settings/SettingsSection.tsx +283 -0
  1177. package/src/client/settings/SettingsTabsPage.tsx +592 -0
  1178. package/src/client/settings/UsageSection.tsx +401 -0
  1179. package/src/client/settings/VoiceTranscriptionSection.tsx +960 -0
  1180. package/src/client/settings/agent-settings-search.ts +220 -0
  1181. package/src/client/settings/index.ts +27 -0
  1182. package/src/client/settings/useBuilderStatus.ts +1055 -0
  1183. package/src/client/setup-connections/BuilderConnectCard.tsx +89 -0
  1184. package/src/client/setup-connections/ProviderReadinessBadge.tsx +73 -0
  1185. package/src/client/setup-connections/SetupConnectionsPage.tsx +94 -0
  1186. package/src/client/setup-connections/catalog.ts +6 -0
  1187. package/src/client/setup-connections/index.ts +47 -0
  1188. package/src/client/setup-connections/runtime.ts +1 -0
  1189. package/src/client/sharing/ShareButton.tsx +1748 -0
  1190. package/src/client/sharing/ShareDialog.tsx +846 -0
  1191. package/src/client/sharing/VisibilityBadge.tsx +4 -0
  1192. package/src/client/sharing/index.ts +2 -0
  1193. package/src/client/sse-event-processor.ts +2002 -0
  1194. package/src/client/terminal/AgentTerminal.tsx +488 -0
  1195. package/src/client/terminal/index.ts +7 -0
  1196. package/src/client/theme.ts +27 -0
  1197. package/src/client/tombstone/agent-presence-chip.ts +17 -0
  1198. package/src/client/tombstone/editor.ts +265 -0
  1199. package/src/client/tombstone/live-cursor-overlay.ts +21 -0
  1200. package/src/client/tombstone/presence-bar.ts +17 -0
  1201. package/src/client/tombstone/recent-edit-highlights.ts +17 -0
  1202. package/src/client/tombstone/remote-selection-rings.ts +21 -0
  1203. package/src/client/tombstone/rich-markdown-editor.ts +265 -0
  1204. package/src/client/tombstone/ui-dialog.ts +49 -0
  1205. package/src/client/tombstone/ui-dropdown-menu.ts +69 -0
  1206. package/src/client/tombstone/ui-hover-card.ts +21 -0
  1207. package/src/client/tombstone/ui-popover.ts +25 -0
  1208. package/src/client/tombstone/ui-sheet.ts +49 -0
  1209. package/src/client/tombstone/ui-tooltip.ts +29 -0
  1210. package/src/client/tombstone/visual-style-controls.ts +61 -0
  1211. package/src/client/tool-cells/BashCell.tsx +164 -0
  1212. package/src/client/tool-cells/EditCell.tsx +303 -0
  1213. package/src/client/tool-cells/FilesChangedSummary.tsx +208 -0
  1214. package/src/client/tool-cells/WriteCell.tsx +145 -0
  1215. package/src/client/tool-cells/index.ts +7 -0
  1216. package/src/client/tool-display.ts +41 -0
  1217. package/src/client/track.ts +57 -0
  1218. package/src/client/transcription/BuilderTranscriptionCta.tsx +120 -0
  1219. package/src/client/transcription/use-live-transcription.ts +292 -0
  1220. package/src/client/ui/index.ts +55 -0
  1221. package/src/client/uploads/index.ts +6 -0
  1222. package/src/client/uploads/upload-editor-image.ts +79 -0
  1223. package/src/client/uploads/use-upload-resource.ts +24 -0
  1224. package/src/client/url-scrub.ts +51 -0
  1225. package/src/client/use-action.ts +766 -0
  1226. package/src/client/use-agent-chat-context.ts +63 -0
  1227. package/src/client/use-agent-chat-home-handoff.ts +207 -0
  1228. package/src/client/use-agent-chat.ts +53 -0
  1229. package/src/client/use-agent-engine-configured.ts +211 -0
  1230. package/src/client/use-avatar.ts +127 -0
  1231. package/src/client/use-change-version.ts +150 -0
  1232. package/src/client/use-chat-models.ts +252 -0
  1233. package/src/client/use-chat-threads.ts +1262 -0
  1234. package/src/client/use-db-sync.ts +1112 -0
  1235. package/src/client/use-demo-mode-status.ts +30 -0
  1236. package/src/client/use-dev-mode.ts +177 -0
  1237. package/src/client/use-external-value.ts +40 -0
  1238. package/src/client/use-pausing-interval.ts +65 -0
  1239. package/src/client/use-pinch-zoom.ts +202 -0
  1240. package/src/client/use-run-stuck-detection.ts +427 -0
  1241. package/src/client/use-send-to-agent-chat.ts +85 -0
  1242. package/src/client/use-session.ts +114 -0
  1243. package/src/client/utils.ts +7 -0
  1244. package/src/client/visual-style-controls.tsx +1 -0
  1245. package/src/client/vite-dev-recovery-script.ts +226 -0
  1246. package/src/client/voice-provider-status.ts +44 -0
  1247. package/src/client/widgets/index.ts +41 -0
  1248. package/src/code-agents/background-controller.ts +398 -0
  1249. package/src/code-agents/background-run.ts +156 -0
  1250. package/src/code-agents/index.ts +95 -0
  1251. package/src/code-agents/prompt-attachments.ts +39 -0
  1252. package/src/code-agents/transcript-normalizer.ts +750 -0
  1253. package/src/code-agents/transcript-order.ts +73 -0
  1254. package/src/coding-tools/index.ts +742 -0
  1255. package/src/coding-tools/run-code.ts +1662 -0
  1256. package/src/coding-tools/sandbox/adapter.ts +82 -0
  1257. package/src/coding-tools/sandbox/background.ts +406 -0
  1258. package/src/coding-tools/sandbox/executions-store.ts +517 -0
  1259. package/src/coding-tools/sandbox/index.ts +132 -0
  1260. package/src/coding-tools/sandbox/local-child-process-adapter.ts +217 -0
  1261. package/src/collab/agent-identity.ts +5 -0
  1262. package/src/collab/agent-presence.ts +341 -0
  1263. package/src/collab/awareness-store.ts +196 -0
  1264. package/src/collab/awareness.ts +317 -0
  1265. package/src/collab/client-struct.ts +366 -0
  1266. package/src/collab/client.ts +1205 -0
  1267. package/src/collab/emitter.ts +31 -0
  1268. package/src/collab/follow-mode.ts +107 -0
  1269. package/src/collab/index.ts +151 -0
  1270. package/src/collab/json-to-yjs.ts +504 -0
  1271. package/src/collab/presence.ts +207 -0
  1272. package/src/collab/recent-edits.ts +215 -0
  1273. package/src/collab/routes.ts +191 -0
  1274. package/src/collab/storage.ts +228 -0
  1275. package/src/collab/struct-routes.ts +133 -0
  1276. package/src/collab/text-to-yjs.ts +79 -0
  1277. package/src/collab/undo.ts +432 -0
  1278. package/src/collab/xml-ops.ts +68 -0
  1279. package/src/collab/ydoc-manager.ts +544 -0
  1280. package/src/command-navigation/actions.ts +100 -0
  1281. package/src/command-navigation/index.ts +12 -0
  1282. package/src/comments-review/index.ts +1 -0
  1283. package/src/connections/catalog.ts +451 -0
  1284. package/src/connections/index.ts +48 -0
  1285. package/src/connections/reader.ts +911 -0
  1286. package/src/connections/runtime.ts +1 -0
  1287. package/src/credentials/index.ts +187 -0
  1288. package/src/data-programs/actions.ts +335 -0
  1289. package/src/data-programs/contract.ts +284 -0
  1290. package/src/data-programs/execute.ts +693 -0
  1291. package/src/data-programs/index.ts +72 -0
  1292. package/src/data-programs/schema.ts +148 -0
  1293. package/src/data-programs/store.ts +755 -0
  1294. package/src/data-widgets/index.ts +376 -0
  1295. package/src/db/client.ts +1627 -0
  1296. package/src/db/create-get-db.ts +579 -0
  1297. package/src/db/ddl-guard.ts +302 -0
  1298. package/src/db/drizzle-config.ts +190 -0
  1299. package/src/db/ensure-additive-columns.ts +430 -0
  1300. package/src/db/index.ts +57 -0
  1301. package/src/db/migrations.ts +691 -0
  1302. package/src/db/request-telemetry.ts +154 -0
  1303. package/src/db/runtime-diagnostics.ts +331 -0
  1304. package/src/db/schema.ts +121 -0
  1305. package/src/db/widen-columns.ts +75 -0
  1306. package/src/db-admin/agent-tools.ts +202 -0
  1307. package/src/db-admin/index.ts +26 -0
  1308. package/src/db-admin/operations.ts +803 -0
  1309. package/src/db-admin/routes.ts +174 -0
  1310. package/src/db-admin/types.ts +110 -0
  1311. package/src/demo/browser-state.ts +47 -0
  1312. package/src/demo/fetch-interceptor.ts +175 -0
  1313. package/src/demo/redact.ts +575 -0
  1314. package/src/deploy/build.ts +3935 -0
  1315. package/src/deploy/immutable-assets.ts +65 -0
  1316. package/src/deploy/route-discovery.ts +458 -0
  1317. package/src/deploy/workspace-core.ts +282 -0
  1318. package/src/deploy/workspace-deploy.ts +1488 -0
  1319. package/src/eject/provider-api-definitions.ts +193 -0
  1320. package/src/eject/remote-mcp-presets.ts +137 -0
  1321. package/src/eject/setup-readiness-ui.tsx +29 -0
  1322. package/src/eject/workspace-connections.ts +92 -0
  1323. package/src/embedding/agent.ts +159 -0
  1324. package/src/embedding/bridge.ts +297 -0
  1325. package/src/embedding/index.ts +43 -0
  1326. package/src/embedding/protocol.ts +185 -0
  1327. package/src/embedding/react.tsx +319 -0
  1328. package/src/embeddings/index.ts +233 -0
  1329. package/src/eval/agent-runner.ts +210 -0
  1330. package/src/eval/define-eval.ts +55 -0
  1331. package/src/eval/index.ts +49 -0
  1332. package/src/eval/report.ts +75 -0
  1333. package/src/eval/runner.ts +369 -0
  1334. package/src/eval/scorer.ts +244 -0
  1335. package/src/eval/types.ts +188 -0
  1336. package/src/event-bus/bus.ts +140 -0
  1337. package/src/event-bus/index.ts +3 -0
  1338. package/src/event-bus/registry.ts +79 -0
  1339. package/src/event-bus/types.ts +29 -0
  1340. package/src/extensions/actions.ts +1636 -0
  1341. package/src/extensions/change-marker.ts +54 -0
  1342. package/src/extensions/content-patch.ts +468 -0
  1343. package/src/extensions/fetch-tool.ts +461 -0
  1344. package/src/extensions/html-shell.ts +803 -0
  1345. package/src/extensions/local.ts +462 -0
  1346. package/src/extensions/path.ts +40 -0
  1347. package/src/extensions/proxy-security.ts +171 -0
  1348. package/src/extensions/routes.ts +1080 -0
  1349. package/src/extensions/schema.ts +269 -0
  1350. package/src/extensions/session-replay-iframe.ts +131 -0
  1351. package/src/extensions/slots/routes.ts +126 -0
  1352. package/src/extensions/slots/schema.ts +87 -0
  1353. package/src/extensions/slots/store.ts +450 -0
  1354. package/src/extensions/store.ts +1347 -0
  1355. package/src/extensions/theme.ts +114 -0
  1356. package/src/extensions/url-safety.ts +316 -0
  1357. package/src/extensions/web-content.ts +663 -0
  1358. package/src/extensions/web-search-tool.ts +437 -0
  1359. package/src/feature-flags/a2a-action-route.ts +56 -0
  1360. package/src/feature-flags/actions/get-feature-flags.ts +24 -0
  1361. package/src/feature-flags/actions/list-feature-flags.ts +69 -0
  1362. package/src/feature-flags/actions/set-feature-flag.ts +89 -0
  1363. package/src/feature-flags/index.ts +21 -0
  1364. package/src/feature-flags/permissions.ts +53 -0
  1365. package/src/feature-flags/plugin.ts +44 -0
  1366. package/src/feature-flags/registry.ts +96 -0
  1367. package/src/feature-flags/store.ts +176 -0
  1368. package/src/file-upload/actions/upload-image.ts +213 -0
  1369. package/src/file-upload/builder.ts +431 -0
  1370. package/src/file-upload/index.ts +24 -0
  1371. package/src/file-upload/pre-upload-attachments.ts +305 -0
  1372. package/src/file-upload/registry.ts +125 -0
  1373. package/src/file-upload/types.ts +89 -0
  1374. package/src/guards/db-tool-scoping.ts +129 -0
  1375. package/src/guards/index.ts +23 -0
  1376. package/src/guards/no-drizzle-push.ts +116 -0
  1377. package/src/guards/no-env-credentials.ts +229 -0
  1378. package/src/guards/no-env-mutation.ts +114 -0
  1379. package/src/guards/no-localhost-fallback.ts +124 -0
  1380. package/src/guards/no-unscoped-credentials.ts +227 -0
  1381. package/src/guards/no-unscoped-queries.ts +834 -0
  1382. package/src/guards/scan-utils.ts +105 -0
  1383. package/src/guards/types.ts +30 -0
  1384. package/src/history/actions/create-resource-version.ts +92 -0
  1385. package/src/history/actions/get-resource-version.ts +60 -0
  1386. package/src/history/actions/list-resource-history.ts +53 -0
  1387. package/src/history/actions/list-resource-versions.ts +43 -0
  1388. package/src/history/actions/restore-resource-version.ts +96 -0
  1389. package/src/history/index.ts +26 -0
  1390. package/src/history/registry.ts +111 -0
  1391. package/src/history/store.ts +379 -0
  1392. package/src/history/types.ts +69 -0
  1393. package/src/index.browser.ts +155 -0
  1394. package/src/index.ts +408 -0
  1395. package/src/ingestion/docx.ts +158 -0
  1396. package/src/ingestion/figma-node-to-html.ts +1860 -0
  1397. package/src/ingestion/figma.ts +548 -0
  1398. package/src/ingestion/index.ts +107 -0
  1399. package/src/ingestion/media.ts +264 -0
  1400. package/src/ingestion/notion.ts +121 -0
  1401. package/src/ingestion/office.ts +213 -0
  1402. package/src/ingestion/orchestration.ts +160 -0
  1403. package/src/ingestion/pptx.ts +331 -0
  1404. package/src/ingestion/selection.ts +81 -0
  1405. package/src/ingestion/website.ts +151 -0
  1406. package/src/integrations/a2a-continuation-marker.ts +2 -0
  1407. package/src/integrations/a2a-continuation-processor.ts +888 -0
  1408. package/src/integrations/a2a-continuations-store.ts +643 -0
  1409. package/src/integrations/adapter-overrides.ts +12 -0
  1410. package/src/integrations/adapters/discord.ts +364 -0
  1411. package/src/integrations/adapters/email.ts +1164 -0
  1412. package/src/integrations/adapters/google-docs.ts +371 -0
  1413. package/src/integrations/adapters/index.ts +29 -0
  1414. package/src/integrations/adapters/microsoft-teams.ts +413 -0
  1415. package/src/integrations/adapters/slack.ts +1914 -0
  1416. package/src/integrations/adapters/telegram.ts +386 -0
  1417. package/src/integrations/adapters/whatsapp.ts +358 -0
  1418. package/src/integrations/awaiting-input-store.ts +130 -0
  1419. package/src/integrations/catalog.ts +571 -0
  1420. package/src/integrations/computer-supervision-store.ts +423 -0
  1421. package/src/integrations/computer-supervision.ts +213 -0
  1422. package/src/integrations/config-store.ts +131 -0
  1423. package/src/integrations/controls-store.ts +187 -0
  1424. package/src/integrations/eject/messaging-adapters.ts +85 -0
  1425. package/src/integrations/google-docs-poller.ts +671 -0
  1426. package/src/integrations/identity-links-store.ts +210 -0
  1427. package/src/integrations/identity.ts +196 -0
  1428. package/src/integrations/index.ts +220 -0
  1429. package/src/integrations/installations-store.ts +677 -0
  1430. package/src/integrations/integration-memory.ts +187 -0
  1431. package/src/integrations/internal-token.ts +106 -0
  1432. package/src/integrations/pending-tasks-retry-job.ts +286 -0
  1433. package/src/integrations/pending-tasks-store.ts +468 -0
  1434. package/src/integrations/plugin.ts +3048 -0
  1435. package/src/integrations/remote-commands-store.ts +690 -0
  1436. package/src/integrations/remote-devices-store.ts +492 -0
  1437. package/src/integrations/remote-json-safety.ts +127 -0
  1438. package/src/integrations/remote-push-delivery-job.ts +48 -0
  1439. package/src/integrations/remote-push-delivery.ts +370 -0
  1440. package/src/integrations/remote-push-store.ts +664 -0
  1441. package/src/integrations/remote-retry-job.ts +55 -0
  1442. package/src/integrations/remote-run-events-store.ts +222 -0
  1443. package/src/integrations/remote-types.ts +186 -0
  1444. package/src/integrations/runtime.ts +1 -0
  1445. package/src/integrations/scope-store.ts +498 -0
  1446. package/src/integrations/slack-manifest.ts +78 -0
  1447. package/src/integrations/slack-oauth.ts +344 -0
  1448. package/src/integrations/task-queue-stats.ts +144 -0
  1449. package/src/integrations/thread-mapping-store.ts +134 -0
  1450. package/src/integrations/types.ts +485 -0
  1451. package/src/integrations/usage-budget-store.ts +906 -0
  1452. package/src/integrations/webhook-delivery.ts +55 -0
  1453. package/src/integrations/webhook-handler.ts +1939 -0
  1454. package/src/jobs/actions/list-recurring-jobs.ts +105 -0
  1455. package/src/jobs/actions/manage-recurring-job.ts +85 -0
  1456. package/src/jobs/cron.ts +126 -0
  1457. package/src/jobs/index.ts +9 -0
  1458. package/src/jobs/scheduler.ts +703 -0
  1459. package/src/jobs/tools.ts +367 -0
  1460. package/src/local-artifacts/index.ts +1372 -0
  1461. package/src/localization/actions/get-localization-preference.ts +24 -0
  1462. package/src/localization/actions/set-localization-preference.ts +32 -0
  1463. package/src/localization/default-messages.ts +1009 -0
  1464. package/src/localization/index.ts +2 -0
  1465. package/src/localization/server.ts +131 -0
  1466. package/src/localization/shared.ts +208 -0
  1467. package/src/mcp/actions/call-mcp-tool.ts +19 -0
  1468. package/src/mcp/actions/create-org-service-token.ts +87 -0
  1469. package/src/mcp/actions/list-mcp-tools.ts +18 -0
  1470. package/src/mcp/actions/list-org-service-tokens.ts +44 -0
  1471. package/src/mcp/actions/revoke-org-service-token.ts +30 -0
  1472. package/src/mcp/actions/service-token-access.ts +121 -0
  1473. package/src/mcp/ask-app-inline-tasks.ts +125 -0
  1474. package/src/mcp/build-server.ts +2356 -0
  1475. package/src/mcp/builtin-tools.ts +1506 -0
  1476. package/src/mcp/connect-route.ts +1460 -0
  1477. package/src/mcp/connect-store.ts +680 -0
  1478. package/src/mcp/embed-app.ts +2051 -0
  1479. package/src/mcp/embed-route.ts +84 -0
  1480. package/src/mcp/external-agent-policy.ts +18 -0
  1481. package/src/mcp/index.ts +47 -0
  1482. package/src/mcp/oauth-route.ts +1016 -0
  1483. package/src/mcp/oauth-store.ts +581 -0
  1484. package/src/mcp/oauth-token.ts +227 -0
  1485. package/src/mcp/org-directory.ts +343 -0
  1486. package/src/mcp/route-paths.ts +34 -0
  1487. package/src/mcp/screen-memory-stdio.ts +290 -0
  1488. package/src/mcp/server.ts +507 -0
  1489. package/src/mcp/stdio.ts +328 -0
  1490. package/src/mcp/workspace-resolve.ts +261 -0
  1491. package/src/mcp-client/app-api.ts +136 -0
  1492. package/src/mcp-client/app-result.ts +64 -0
  1493. package/src/mcp-client/builtin-capabilities.ts +146 -0
  1494. package/src/mcp-client/builtin-store.ts +93 -0
  1495. package/src/mcp-client/config.ts +281 -0
  1496. package/src/mcp-client/errors.ts +54 -0
  1497. package/src/mcp-client/hub-client.ts +172 -0
  1498. package/src/mcp-client/hub-routes.ts +198 -0
  1499. package/src/mcp-client/index.ts +504 -0
  1500. package/src/mcp-client/manager.ts +968 -0
  1501. package/src/mcp-client/oauth-client.ts +462 -0
  1502. package/src/mcp-client/oauth-routes.ts +307 -0
  1503. package/src/mcp-client/remote-store.ts +674 -0
  1504. package/src/mcp-client/remote-url.ts +97 -0
  1505. package/src/mcp-client/routes.ts +1093 -0
  1506. package/src/mcp-client/screen-memory-local.ts +461 -0
  1507. package/src/mcp-client/tool-policy.ts +182 -0
  1508. package/src/mcp-client/visibility.ts +56 -0
  1509. package/src/mcp-client/workspace-servers.ts +379 -0
  1510. package/src/navigation/index.ts +170 -0
  1511. package/src/notifications/actions.ts +151 -0
  1512. package/src/notifications/channels.ts +410 -0
  1513. package/src/notifications/index.ts +22 -0
  1514. package/src/notifications/registry.ts +216 -0
  1515. package/src/notifications/routes.ts +106 -0
  1516. package/src/notifications/store.ts +236 -0
  1517. package/src/notifications/types.ts +52 -0
  1518. package/src/oauth-tokens/google-refresh.ts +175 -0
  1519. package/src/oauth-tokens/index.ts +15 -0
  1520. package/src/oauth-tokens/store.ts +384 -0
  1521. package/src/observability/cleanup-job.ts +124 -0
  1522. package/src/observability/evals.ts +507 -0
  1523. package/src/observability/experiments.ts +359 -0
  1524. package/src/observability/feedback.ts +332 -0
  1525. package/src/observability/hosted-model-experiment.ts +118 -0
  1526. package/src/observability/index.ts +62 -0
  1527. package/src/observability/plugin.ts +31 -0
  1528. package/src/observability/routes.ts +431 -0
  1529. package/src/observability/sentiment.ts +261 -0
  1530. package/src/observability/store.ts +1347 -0
  1531. package/src/observability/traces.ts +789 -0
  1532. package/src/observability/tracing.ts +157 -0
  1533. package/src/observability/tracking-identity.ts +55 -0
  1534. package/src/observability/types.ts +211 -0
  1535. package/src/onboarding/default-steps.ts +428 -0
  1536. package/src/onboarding/index.ts +22 -0
  1537. package/src/onboarding/plugin.ts +262 -0
  1538. package/src/onboarding/registry.ts +47 -0
  1539. package/src/onboarding/types.ts +96 -0
  1540. package/src/org/accept-pending.ts +98 -0
  1541. package/src/org/auto-join-domain.ts +113 -0
  1542. package/src/org/context.ts +614 -0
  1543. package/src/org/free-email-providers.ts +124 -0
  1544. package/src/org/handlers.ts +1153 -0
  1545. package/src/org/index.ts +63 -0
  1546. package/src/org/migrations.ts +105 -0
  1547. package/src/org/permissions.ts +30 -0
  1548. package/src/org/plugin.ts +255 -0
  1549. package/src/org/schema.ts +28 -0
  1550. package/src/org/types.ts +57 -0
  1551. package/src/org-team/index.ts +1 -0
  1552. package/src/package-lifecycle/deprecated-imports.ts +179 -0
  1553. package/src/package-lifecycle/eject-manifest.ts +345 -0
  1554. package/src/package-lifecycle/index.ts +7 -0
  1555. package/src/package-lifecycle/manifest.ts +120 -0
  1556. package/src/package-lifecycle/migration-manifest.ts +159 -0
  1557. package/src/package-lifecycle/migration-message.ts +6 -0
  1558. package/src/package-lifecycle/tombstone.ts +58 -0
  1559. package/src/package-lifecycle/upgrade-error.ts +37 -0
  1560. package/src/private-blob/index.ts +18 -0
  1561. package/src/private-blob/registry.ts +242 -0
  1562. package/src/private-blob/types.ts +55 -0
  1563. package/src/progress/actions.ts +179 -0
  1564. package/src/progress/index.ts +16 -0
  1565. package/src/progress/registry.ts +127 -0
  1566. package/src/progress/routes.ts +89 -0
  1567. package/src/progress/store.ts +353 -0
  1568. package/src/progress/types.ts +59 -0
  1569. package/src/provider-api/actions/custom-provider-registration.ts +197 -0
  1570. package/src/provider-api/actions/delete-staged-dataset.ts +5 -0
  1571. package/src/provider-api/actions/github-repo-files.ts +266 -0
  1572. package/src/provider-api/actions/list-staged-datasets.ts +5 -0
  1573. package/src/provider-api/actions/provider-api-audit.ts +88 -0
  1574. package/src/provider-api/actions/provider-api.ts +460 -0
  1575. package/src/provider-api/actions/query-staged-dataset.ts +5 -0
  1576. package/src/provider-api/actions/staged-datasets.ts +359 -0
  1577. package/src/provider-api/corpus-jobs-store.ts +522 -0
  1578. package/src/provider-api/corpus-jobs.ts +1817 -0
  1579. package/src/provider-api/custom-registry.ts +456 -0
  1580. package/src/provider-api/github-repo.ts +675 -0
  1581. package/src/provider-api/gong.ts +108 -0
  1582. package/src/provider-api/index.ts +5444 -0
  1583. package/src/provider-api/quota-governor.ts +562 -0
  1584. package/src/provider-api/runtime.ts +1 -0
  1585. package/src/provider-api/staged-datasets-aggregate.ts +304 -0
  1586. package/src/provider-api/staged-datasets-store.ts +441 -0
  1587. package/src/provider-api/staging.ts +483 -0
  1588. package/src/resources/agents.ts +65 -0
  1589. package/src/resources/emitter.ts +54 -0
  1590. package/src/resources/handlers.ts +728 -0
  1591. package/src/resources/index.ts +4 -0
  1592. package/src/resources/metadata.ts +252 -0
  1593. package/src/resources/script-helpers.ts +160 -0
  1594. package/src/resources/store.ts +1618 -0
  1595. package/src/review/actions/consume-review-feedback.ts +48 -0
  1596. package/src/review/actions/create-review-comment.ts +108 -0
  1597. package/src/review/actions/delete-review-comment.ts +67 -0
  1598. package/src/review/actions/get-review-feedback.ts +49 -0
  1599. package/src/review/actions/list-review-comments.ts +87 -0
  1600. package/src/review/actions/reply-review-comment.ts +118 -0
  1601. package/src/review/actions/resolve-review-thread.ts +94 -0
  1602. package/src/review/actions/send-review-thread-to-agent.ts +62 -0
  1603. package/src/review/actions/set-review-status.ts +58 -0
  1604. package/src/review/identity.ts +101 -0
  1605. package/src/review/index.ts +45 -0
  1606. package/src/review/mentions.ts +44 -0
  1607. package/src/review/registry.ts +111 -0
  1608. package/src/review/store.ts +997 -0
  1609. package/src/review/types.ts +101 -0
  1610. package/src/router/index.ts +24 -0
  1611. package/src/scripts/agent-engines/list-agent-engines.ts +130 -0
  1612. package/src/scripts/agent-engines/manage-agent-engine.ts +197 -0
  1613. package/src/scripts/agent-engines/set-agent-engine.ts +84 -0
  1614. package/src/scripts/agent-engines/test-agent-engine.ts +174 -0
  1615. package/src/scripts/call-agent.ts +792 -0
  1616. package/src/scripts/chat/index.ts +8 -0
  1617. package/src/scripts/chat/open-chat.ts +56 -0
  1618. package/src/scripts/chat/search-chats.ts +121 -0
  1619. package/src/scripts/core-scripts.ts +22 -0
  1620. package/src/scripts/db/check-scoping.ts +212 -0
  1621. package/src/scripts/db/exec.ts +791 -0
  1622. package/src/scripts/db/index.ts +19 -0
  1623. package/src/scripts/db/migrate-encrypt-credentials.ts +223 -0
  1624. package/src/scripts/db/migrate-encrypt-oauth-tokens.ts +213 -0
  1625. package/src/scripts/db/migrate-user-api-keys.ts +257 -0
  1626. package/src/scripts/db/patch.ts +820 -0
  1627. package/src/scripts/db/query.ts +299 -0
  1628. package/src/scripts/db/reset-dev-owner.ts +285 -0
  1629. package/src/scripts/db/safety.ts +286 -0
  1630. package/src/scripts/db/schema.ts +364 -0
  1631. package/src/scripts/db/scoping.ts +341 -0
  1632. package/src/scripts/db/sqlite-client.ts +78 -0
  1633. package/src/scripts/db/tool-mode.ts +23 -0
  1634. package/src/scripts/db/tool-schemas.ts +31 -0
  1635. package/src/scripts/db/wipe-leaked-builder-keys.ts +201 -0
  1636. package/src/scripts/dev/index.ts +296 -0
  1637. package/src/scripts/dev/list-files.ts +119 -0
  1638. package/src/scripts/dev/read-file.ts +77 -0
  1639. package/src/scripts/dev/search-files.ts +151 -0
  1640. package/src/scripts/dev/shell.ts +111 -0
  1641. package/src/scripts/dev/write-file.ts +56 -0
  1642. package/src/scripts/dev-session.ts +96 -0
  1643. package/src/scripts/docs/index.ts +8 -0
  1644. package/src/scripts/docs/search.ts +274 -0
  1645. package/src/scripts/docs/source-search.ts +363 -0
  1646. package/src/scripts/index.ts +15 -0
  1647. package/src/scripts/manage-agent-loop-settings.ts +84 -0
  1648. package/src/scripts/parse-args.ts +46 -0
  1649. package/src/scripts/resources/delete-memory.ts +67 -0
  1650. package/src/scripts/resources/delete.ts +69 -0
  1651. package/src/scripts/resources/effective.ts +71 -0
  1652. package/src/scripts/resources/index.ts +18 -0
  1653. package/src/scripts/resources/list.ts +103 -0
  1654. package/src/scripts/resources/migrate-learnings.ts +40 -0
  1655. package/src/scripts/resources/read.ts +107 -0
  1656. package/src/scripts/resources/save-memory.ts +99 -0
  1657. package/src/scripts/resources/write.ts +158 -0
  1658. package/src/scripts/runner.ts +339 -0
  1659. package/src/scripts/utils.ts +130 -0
  1660. package/src/search/index.ts +413 -0
  1661. package/src/search-utils/index.ts +112 -0
  1662. package/src/secrets/crypto.ts +228 -0
  1663. package/src/secrets/index.ts +65 -0
  1664. package/src/secrets/onboarding.ts +101 -0
  1665. package/src/secrets/register-framework-secrets.ts +228 -0
  1666. package/src/secrets/register.ts +129 -0
  1667. package/src/secrets/routes.ts +697 -0
  1668. package/src/secrets/schema.ts +52 -0
  1669. package/src/secrets/storage.ts +561 -0
  1670. package/src/secrets/substitution.ts +305 -0
  1671. package/src/server/__snapshots__/agent-chat-plugin.surface.spec.ts.snap +150 -0
  1672. package/src/server/action-change.ts +66 -0
  1673. package/src/server/action-discovery.ts +774 -0
  1674. package/src/server/action-routes.ts +608 -0
  1675. package/src/server/agent-access.ts +72 -0
  1676. package/src/server/agent-chat/action-filters-a2a.ts +339 -0
  1677. package/src/server/agent-chat/browser-team-tools.ts +510 -0
  1678. package/src/server/agent-chat/context-tools.ts +599 -0
  1679. package/src/server/agent-chat/framework-prompts.ts +467 -0
  1680. package/src/server/agent-chat/lazy-fs.ts +11 -0
  1681. package/src/server/agent-chat/mcp-glue.ts +91 -0
  1682. package/src/server/agent-chat/plugin-options.ts +379 -0
  1683. package/src/server/agent-chat/process-run-failure.ts +62 -0
  1684. package/src/server/agent-chat/prompt-resources.ts +1004 -0
  1685. package/src/server/agent-chat/recurring-jobs-runtime.ts +80 -0
  1686. package/src/server/agent-chat/request-surface.ts +71 -0
  1687. package/src/server/agent-chat/run-code-tools.ts +67 -0
  1688. package/src/server/agent-chat/script-entries.ts +796 -0
  1689. package/src/server/agent-chat/shared-thread.ts +301 -0
  1690. package/src/server/agent-chat/skill-frontmatter.ts +41 -0
  1691. package/src/server/agent-chat-plugin.ts +5930 -0
  1692. package/src/server/agent-discovery.ts +706 -0
  1693. package/src/server/agent-engine-api-key-route.ts +236 -0
  1694. package/src/server/agent-run-context.ts +204 -0
  1695. package/src/server/agent-teams-run-queue.ts +344 -0
  1696. package/src/server/agent-teams.ts +2257 -0
  1697. package/src/server/agents-bundle.ts +464 -0
  1698. package/src/server/analytics.ts +105 -0
  1699. package/src/server/app-base-path.ts +79 -0
  1700. package/src/server/app-name.ts +53 -0
  1701. package/src/server/app-url.ts +184 -0
  1702. package/src/server/attachment-actions.ts +276 -0
  1703. package/src/server/attribution.ts +214 -0
  1704. package/src/server/auth-marketing.ts +260 -0
  1705. package/src/server/auth-plugin.ts +27 -0
  1706. package/src/server/auth.ts +3803 -0
  1707. package/src/server/better-auth-instance.ts +1197 -0
  1708. package/src/server/builder-browser.ts +1701 -0
  1709. package/src/server/builder-design-systems.ts +668 -0
  1710. package/src/server/builder-space.ts +176 -0
  1711. package/src/server/captcha.ts +63 -0
  1712. package/src/server/capture-error.ts +64 -0
  1713. package/src/server/cli-capture.ts +141 -0
  1714. package/src/server/collab-plugin.ts +391 -0
  1715. package/src/server/complete-text.ts +231 -0
  1716. package/src/server/cookie-namespace.ts +159 -0
  1717. package/src/server/core-routes-plugin.ts +4122 -0
  1718. package/src/server/cors-origins.ts +60 -0
  1719. package/src/server/create-server.ts +268 -0
  1720. package/src/server/credential-provider.ts +1202 -0
  1721. package/src/server/csrf.ts +229 -0
  1722. package/src/server/date-utils.ts +42 -0
  1723. package/src/server/deep-link.ts +119 -0
  1724. package/src/server/derived-secret.ts +32 -0
  1725. package/src/server/design-token-utils.ts +1032 -0
  1726. package/src/server/desktop-sso.ts +83 -0
  1727. package/src/server/edge.ts +69 -0
  1728. package/src/server/email-actions.ts +218 -0
  1729. package/src/server/email-template.ts +192 -0
  1730. package/src/server/email-templates.ts +148 -0
  1731. package/src/server/email.ts +219 -0
  1732. package/src/server/embed-route.ts +283 -0
  1733. package/src/server/embed-session.ts +805 -0
  1734. package/src/server/embedded.ts +252 -0
  1735. package/src/server/entry-server.tsx +154 -0
  1736. package/src/server/framework-request-handler.ts +848 -0
  1737. package/src/server/fusion-app.ts +447 -0
  1738. package/src/server/google-auth-mode.ts +34 -0
  1739. package/src/server/google-auth-plugin.ts +496 -0
  1740. package/src/server/google-oauth-credentials.ts +128 -0
  1741. package/src/server/google-oauth.ts +972 -0
  1742. package/src/server/google-realtime-session.ts +182 -0
  1743. package/src/server/h3-helpers.ts +174 -0
  1744. package/src/server/http-response-telemetry.ts +396 -0
  1745. package/src/server/identity-sso-store.ts +304 -0
  1746. package/src/server/identity-sso.ts +514 -0
  1747. package/src/server/index.ts +593 -0
  1748. package/src/server/missing-key.ts +36 -0
  1749. package/src/server/oauth-helpers.ts +44 -0
  1750. package/src/server/oauth-public-origin.ts +44 -0
  1751. package/src/server/oauth-return-url.ts +120 -0
  1752. package/src/server/og-fonts-data.ts +15 -0
  1753. package/src/server/og-fonts.ts +75 -0
  1754. package/src/server/onboarding-html.ts +4040 -0
  1755. package/src/server/open-route.ts +311 -0
  1756. package/src/server/org-admin.ts +28 -0
  1757. package/src/server/poll-events.ts +88 -0
  1758. package/src/server/poll.ts +1224 -0
  1759. package/src/server/prompts/framework-core-compact.ts +107 -0
  1760. package/src/server/prompts/framework-core.ts +143 -0
  1761. package/src/server/prompts/index.ts +16 -0
  1762. package/src/server/prompts/model-overlays.ts +54 -0
  1763. package/src/server/prompts/shared-rules.ts +104 -0
  1764. package/src/server/realtime-voice.ts +967 -0
  1765. package/src/server/recap-image-route.ts +242 -0
  1766. package/src/server/recap-image-store.ts +185 -0
  1767. package/src/server/request-context.ts +416 -0
  1768. package/src/server/request-origin.ts +67 -0
  1769. package/src/server/resources-plugin.ts +119 -0
  1770. package/src/server/schema-prompt.ts +429 -0
  1771. package/src/server/scoped-key-storage.ts +223 -0
  1772. package/src/server/security-headers.ts +170 -0
  1773. package/src/server/self-dispatch.ts +224 -0
  1774. package/src/server/sentry-config.ts +73 -0
  1775. package/src/server/sentry-plugin.ts +138 -0
  1776. package/src/server/sentry.ts +444 -0
  1777. package/src/server/short-lived-token.ts +170 -0
  1778. package/src/server/social-og-image.ts +454 -0
  1779. package/src/server/sse.ts +102 -0
  1780. package/src/server/ssr-handler.ts +466 -0
  1781. package/src/server/transcribe-voice.ts +1076 -0
  1782. package/src/server/voice-providers-status.ts +118 -0
  1783. package/src/server/workspace-oauth.ts +31 -0
  1784. package/src/server/workspace-provider-oauth.ts +970 -0
  1785. package/src/session-replay-iframe-protocol.ts +58 -0
  1786. package/src/settings/handlers.ts +44 -0
  1787. package/src/settings/index.ts +34 -0
  1788. package/src/settings/org-settings.ts +83 -0
  1789. package/src/settings/script-helpers.ts +24 -0
  1790. package/src/settings/store.ts +247 -0
  1791. package/src/settings/user-settings.ts +47 -0
  1792. package/src/setup-connections/index.ts +90 -0
  1793. package/src/setup-connections/onboarding.ts +47 -0
  1794. package/src/shared/agent-access.ts +112 -0
  1795. package/src/shared/agent-chat.ts +124 -0
  1796. package/src/shared/agent-env.ts +46 -0
  1797. package/src/shared/agent-readable-resource.ts +111 -0
  1798. package/src/shared/agent-sidebar-url.ts +39 -0
  1799. package/src/shared/cache-control.ts +25 -0
  1800. package/src/shared/cloudflare-globals.d.ts +34 -0
  1801. package/src/shared/context-xray.ts +172 -0
  1802. package/src/shared/embed-auth.ts +6 -0
  1803. package/src/shared/index.ts +83 -0
  1804. package/src/shared/llm-connection.ts +42 -0
  1805. package/src/shared/markdown-block-split.ts +129 -0
  1806. package/src/shared/mcp-connect-content.ts +130 -0
  1807. package/src/shared/mcp-embed-headers.ts +175 -0
  1808. package/src/shared/mcp-integration-config.ts +101 -0
  1809. package/src/shared/merge-by-id.ts +14 -0
  1810. package/src/shared/oauth-state.ts +39 -0
  1811. package/src/shared/reasoning-effort.ts +228 -0
  1812. package/src/shared/route-warmup-config.ts +128 -0
  1813. package/src/shared/runtime.ts +31 -0
  1814. package/src/shared/social-meta.ts +82 -0
  1815. package/src/shared/streaming-text-smoothing.ts +184 -0
  1816. package/src/shared/truncate.ts +13 -0
  1817. package/src/shared/workspace-app-audience.ts +177 -0
  1818. package/src/shared/workspace-app-id.ts +54 -0
  1819. package/src/sharing/access.ts +585 -0
  1820. package/src/sharing/actions/create-agent-resource-link.ts +91 -0
  1821. package/src/sharing/actions/extension-change.ts +22 -0
  1822. package/src/sharing/actions/list-resource-shares.ts +93 -0
  1823. package/src/sharing/actions/set-resource-visibility.ts +91 -0
  1824. package/src/sharing/actions/share-resource.ts +322 -0
  1825. package/src/sharing/actions/unshare-resource.ts +72 -0
  1826. package/src/sharing/index.ts +35 -0
  1827. package/src/sharing/registry.ts +201 -0
  1828. package/src/sharing/schema.ts +99 -0
  1829. package/src/styles/agent-conversation.css +766 -0
  1830. package/src/styles/agent-native.css +938 -0
  1831. package/src/styles/blocks.css +1717 -0
  1832. package/src/styles/chat-history-list.css +1 -0
  1833. package/src/styles/rich-markdown-editor.css +1 -0
  1834. package/src/tailwind.preset.ts +140 -0
  1835. package/src/templates/chat/.agents/skills/actions/SKILL.md +32 -12
  1836. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +21 -9
  1837. package/src/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  1838. package/src/templates/chat/.agents/skills/self-modifying-code/SKILL.md +14 -2
  1839. package/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  1840. package/src/templates/chat/AGENTS.md +6 -2
  1841. package/src/templates/chat/app/components/layout/Sidebar.tsx +51 -157
  1842. package/src/templates/default/.agents/skills/actions/SKILL.md +32 -12
  1843. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +21 -9
  1844. package/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  1845. package/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +323 -0
  1846. package/src/templates/default/.agents/skills/onboarding/SKILL.md +56 -0
  1847. package/src/templates/default/.agents/skills/secrets/SKILL.md +273 -0
  1848. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +14 -2
  1849. package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  1850. package/src/templates/default/AGENTS.md +26 -18
  1851. package/src/templates/headless/.agents/skills/actions/SKILL.md +512 -0
  1852. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +21 -9
  1853. package/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  1854. package/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +323 -0
  1855. package/src/templates/headless/.agents/skills/secrets/SKILL.md +273 -0
  1856. package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  1857. package/src/templates/headless/AGENTS.md +8 -0
  1858. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -12
  1859. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +21 -9
  1860. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +7 -0
  1861. package/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  1862. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +14 -2
  1863. package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  1864. package/src/templates/workspace-core/AGENTS.md +4 -0
  1865. package/src/templates/workspace-root/AGENTS.md +4 -0
  1866. package/src/terminal/cli-registry.ts +57 -0
  1867. package/src/terminal/index.ts +22 -0
  1868. package/src/terminal/pty-server.ts +389 -0
  1869. package/src/terminal/terminal-plugin.ts +265 -0
  1870. package/src/testing.ts +23 -0
  1871. package/src/tracking/index.ts +10 -0
  1872. package/src/tracking/providers.ts +422 -0
  1873. package/src/tracking/registry.ts +102 -0
  1874. package/src/tracking/route.ts +108 -0
  1875. package/src/tracking/types.ts +16 -0
  1876. package/src/transcription/builder-transcription.ts +118 -0
  1877. package/src/triggers/actions/list-automations.ts +99 -0
  1878. package/src/triggers/actions/manage-automation.ts +78 -0
  1879. package/src/triggers/actions.ts +333 -0
  1880. package/src/triggers/condition-evaluator.ts +163 -0
  1881. package/src/triggers/dispatcher.ts +536 -0
  1882. package/src/triggers/index.ts +13 -0
  1883. package/src/triggers/routes.ts +322 -0
  1884. package/src/triggers/types.ts +42 -0
  1885. package/src/types/pglite.d.ts +26 -0
  1886. package/src/usage/store.ts +698 -0
  1887. package/src/vite/action-types-plugin.ts +481 -0
  1888. package/src/vite/agent-web-plugin.ts +78 -0
  1889. package/src/vite/agents-bundle-plugin.ts +185 -0
  1890. package/src/vite/client.ts +3039 -0
  1891. package/src/vite/index.ts +21 -0
  1892. package/src/voice/index.ts +18 -0
  1893. package/src/voice/voice-cleanup-prompt.ts +38 -0
  1894. package/src/voice/voice-context.ts +217 -0
  1895. package/src/voice/voice-replacements.ts +75 -0
  1896. package/src/workspace-connections/credentials.ts +603 -0
  1897. package/src/workspace-connections/index.ts +72 -0
  1898. package/src/workspace-connections/lifecycle.ts +43 -0
  1899. package/src/workspace-connections/store.ts +1943 -0
  1900. package/src/workspace-files/index.ts +18 -0
  1901. package/src/workspace-files/store.ts +339 -0
  1902. package/src/workspace-files/tool.ts +266 -0
@@ -0,0 +1,3803 @@
1
+ import crypto from "node:crypto";
2
+
3
+ import {
4
+ defineEventHandler,
5
+ getMethod,
6
+ getQuery,
7
+ getRequestIP,
8
+ setResponseHeader,
9
+ setResponseStatus,
10
+ getCookie,
11
+ setCookie,
12
+ deleteCookie,
13
+ getHeader,
14
+ } from "h3";
15
+ import type { H3Event } from "h3";
16
+
17
+ import { EMBED_START_PATH } from "../shared/embed-auth.js";
18
+ import { EMBED_TARGET_HEADER } from "../shared/embed-auth.js";
19
+ import {
20
+ EMBED_TRANSPLANT_HEADER,
21
+ isMcpEmbedCorsOrigin,
22
+ MCP_EMBED_CORS_ALLOW_HEADERS,
23
+ shouldAllowMcpEmbedCredentials,
24
+ } from "../shared/mcp-embed-headers.js";
25
+ import {
26
+ resolveEmbedSessionFromRequest,
27
+ requestHasEmbedAuthMarker,
28
+ } from "./embed-session.js";
29
+ import type { H3AppShim } from "./framework-request-handler.js";
30
+
31
+ // In h3 v2, `event.req` IS the web Request — but in Nitro's dev server (srvx
32
+ // runtime), event.url and event.req share the same underlying URL object.
33
+ // When registerMiddleware strips the mount prefix from event.url.pathname, it
34
+ // also mutates event.req.url (NodeRequestURL setter updates nodeReq.url).
35
+ // Better Auth's router uses new URL(request.url).pathname to extract the
36
+ // sub-route, so it must receive the original full URL — not the stripped one.
37
+ // registerMiddleware saves the original pathname in event.context so we can
38
+ // reconstruct a fresh Request with the correct URL here.
39
+ function toWebRequest(event: H3Event): Request {
40
+ const req = (event as any).req as Request;
41
+ const ctx = (event as any).context as
42
+ | { _mountedPathname?: string; _mountPrefix?: string }
43
+ | undefined;
44
+ if (ctx?._mountedPathname && ctx._mountPrefix) {
45
+ try {
46
+ const url = new URL(req.url);
47
+ const mountedPathname = stripAppBasePath(ctx._mountedPathname);
48
+ if (url.pathname !== mountedPathname) {
49
+ url.pathname = mountedPathname;
50
+ const method = req.method.toUpperCase();
51
+ const hasBody = method !== "GET" && method !== "HEAD";
52
+ return new Request(url.href, {
53
+ method: req.method,
54
+ headers: req.headers,
55
+ // Body may already be partially consumed; pass through as-is.
56
+ // GET/HEAD cannot have a body — omit to avoid spec errors.
57
+ ...(hasBody ? { body: req.body, duplex: "half" } : {}),
58
+ } as any);
59
+ }
60
+ } catch {
61
+ // URL reconstruction failed — fall through and use original req.
62
+ }
63
+ }
64
+ return req;
65
+ }
66
+
67
+ type H3App = H3AppShim;
68
+ import {
69
+ getDbExec,
70
+ isPostgres,
71
+ intType,
72
+ retryOnDdlRace,
73
+ describeDbError,
74
+ } from "../db/client.js";
75
+ import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
76
+ import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
77
+ import {
78
+ MCP_LEGACY_ROUTE_PREFIX,
79
+ MCP_PUBLIC_ROUTE_PREFIX,
80
+ isMcpProtocolPath,
81
+ } from "../mcp/route-paths.js";
82
+ import { readBody } from "../server/h3-helpers.js";
83
+ import { putSetting } from "../settings/store.js";
84
+ import { DEFAULT_SSR_CACHE_HEADERS } from "../shared/cache-control.js";
85
+ import { extractOAuthStateAppId } from "../shared/oauth-state.js";
86
+ import {
87
+ AGENT_NATIVE_SOCIAL_IMAGE_ALT,
88
+ AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT,
89
+ AGENT_NATIVE_SOCIAL_IMAGE_PATH,
90
+ AGENT_NATIVE_SOCIAL_IMAGE_TYPE,
91
+ AGENT_NATIVE_SOCIAL_IMAGE_WIDTH,
92
+ withAgentNativeSocialImageCacheBuster,
93
+ } from "../shared/social-meta.js";
94
+ import {
95
+ normalizeWorkspaceAppAudience,
96
+ workspaceAppAudienceFromEnv,
97
+ workspaceAppRouteAccessFromEnv,
98
+ type WorkspaceAppAudience,
99
+ } from "../shared/workspace-app-audience.js";
100
+ import { isValidWorkspaceAppIdFormat } from "../shared/workspace-app-id.js";
101
+ import { signupAttributionFromCookieHeader } from "./attribution.js";
102
+ import { getBetterAuth, getBetterAuthSync } from "./better-auth-instance.js";
103
+ import type { BetterAuthConfig } from "./better-auth-instance.js";
104
+ import {
105
+ BUILDER_CONNECT_OWNER_COOKIE,
106
+ BUILDER_CONNECT_PARAM,
107
+ BUILDER_RELAY_PATH,
108
+ BUILDER_RELAY_STATE_PARAM,
109
+ BUILDER_STATE_PARAM,
110
+ verifyBuilderCallbackStateAndGetOwner,
111
+ verifyBuilderConnectTokenAndGetOwner,
112
+ verifyBuilderPreviewRelayStateForCallback,
113
+ } from "./builder-browser.js";
114
+ import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
115
+ import {
116
+ getAllowedCorsOrigin,
117
+ readCorsAllowedOrigins,
118
+ } from "./cors-origins.js";
119
+ import {
120
+ readDesktopSso,
121
+ writeDesktopSso,
122
+ clearDesktopSso,
123
+ } from "./desktop-sso.js";
124
+ import type { GoogleAuthMode } from "./google-auth-mode.js";
125
+ import { resolveGoogleSignInCredentials } from "./google-oauth-credentials.js";
126
+ import {
127
+ isElectron as isElectronRequest,
128
+ getAppBasePath,
129
+ getAppUrl,
130
+ getOrigin,
131
+ encodeOAuthState,
132
+ decodeOAuthState,
133
+ createOAuthSession,
134
+ oauthCallbackResponse,
135
+ oauthErrorPage,
136
+ resolveOAuthRedirectUri,
137
+ isAllowedOAuthRedirectUri,
138
+ } from "./google-oauth.js";
139
+ // Pure env-read feature switch from a leaf module (no dependency back on
140
+ // auth.ts), so the guard and the SSO route handler share one validator and
141
+ // can never disagree about whether federated SSO is enabled.
142
+ import { isIdentitySsoEnabled } from "./identity-sso-store.js";
143
+ import { safeOAuthReturnUrl } from "./oauth-return-url.js";
144
+ import {
145
+ getOnboardingHtml,
146
+ getResetPasswordHtml,
147
+ type OnboardingHtmlOptions,
148
+ } from "./onboarding-html.js";
149
+ import { captureAuthError } from "./sentry.js";
150
+ import { isWorkspaceOAuthCallbackRelayEnabled } from "./workspace-oauth.js";
151
+
152
+ /**
153
+ * Get the configured session max age. Desktop SSO broker writes from
154
+ * OAuth flows read this so expiration stays consistent with the cookie.
155
+ */
156
+ export function getSessionMaxAge(): number {
157
+ return sessionMaxAge;
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // Types
162
+ // ---------------------------------------------------------------------------
163
+
164
+ export interface AuthSession {
165
+ email: string;
166
+ userId?: string;
167
+ token?: string;
168
+ /** Display name from the auth provider, when available (Better Auth user.name). */
169
+ name?: string;
170
+ /** Profile image from the auth provider, when available. */
171
+ image?: string;
172
+ /** Active organization ID (resolved by getOrgContext from the framework's org_members table + the user's active-org-id setting; NOT the Better Auth organization plugin, which is intentionally not registered) */
173
+ orgId?: string;
174
+ /** User's role in the active organization (owner/admin/member) */
175
+ orgRole?: string;
176
+ }
177
+
178
+ export interface AuthOptions {
179
+ /** Session max age in seconds. Default: 30 days */
180
+ maxAge?: number;
181
+ /**
182
+ * Custom getSession implementation (for BYOA — Auth.js, Clerk, etc.).
183
+ * When provided, Better Auth is bypassed entirely.
184
+ */
185
+ getSession?: (event: H3Event) => Promise<AuthSession | null>;
186
+ /**
187
+ * Paths that are accessible without authentication.
188
+ * Supports prefix matching: "/book" matches /book/anything.
189
+ * Both page routes and API routes can be made public.
190
+ */
191
+ publicPaths?: string[];
192
+ /**
193
+ * Workspace-level audience for the app.
194
+ *
195
+ * "internal" keeps the existing behavior: every app page requires an
196
+ * authenticated workspace member unless listed in publicPaths.
197
+ *
198
+ * "public" lets unauthenticated visitors load page routes, while framework
199
+ * and API routes remain protected unless explicitly listed in publicPaths.
200
+ */
201
+ workspaceAppAudience?: WorkspaceAppAudience;
202
+ /**
203
+ * Workspace app page paths that anonymous visitors can load.
204
+ * Uses the same prefix matching as publicPaths, but only for page routes:
205
+ * framework, API, and .well-known routes stay protected.
206
+ */
207
+ workspaceAppPublicPaths?: string[];
208
+ /**
209
+ * Workspace app page paths that still require auth when the app audience is
210
+ * public. Useful for public sites with login-only admin/management pages.
211
+ */
212
+ workspaceAppProtectedPaths?: string[];
213
+ /**
214
+ * Custom login page HTML. When provided, this HTML is served to
215
+ * unauthenticated page requests instead of the built-in login form.
216
+ * Use this for custom login flows (e.g., "Sign in with Google" button).
217
+ */
218
+ loginHtml?: string;
219
+ /**
220
+ * Hide email/password forms on the built-in login page and show only the
221
+ * Google sign-in button. Use this for templates (mail, calendar) where
222
+ * Google connection is required anyway. Has no effect when `loginHtml`
223
+ * is provided.
224
+ */
225
+ googleOnly?: boolean;
226
+ /**
227
+ * Mount the framework's generic Google sign-in routes.
228
+ *
229
+ * Set this to false when a template owns `/_agent-native/google/auth-url`
230
+ * and `/_agent-native/google/callback` itself because it needs broader
231
+ * product scopes and persisted API tokens, not just identity sign-in.
232
+ */
233
+ mountGoogleOAuthRoutes?: boolean;
234
+ /**
235
+ * Additional Google OAuth scopes to request beyond the default identity
236
+ * scopes (`openid`, `email`, `profile`). When set, Better Auth's Google
237
+ * social provider asks for these up front, requests a refresh token
238
+ * (`access_type=offline`), and forces the consent screen so the refresh
239
+ * token is reissued on every sign-in.
240
+ *
241
+ * Tokens land in Better Auth's `account` table, and a database hook
242
+ * mirrors them into `oauth_tokens` so template code (mail's Gmail client,
243
+ * calendar's events fetcher, etc.) can pick them up without a separate
244
+ * "Connect Google" round-trip.
245
+ *
246
+ * Example for the mail template:
247
+ * ```ts
248
+ * googleScopes: [
249
+ * "https://www.googleapis.com/auth/gmail.readonly",
250
+ * "https://www.googleapis.com/auth/gmail.send",
251
+ * ],
252
+ * ```
253
+ */
254
+ googleScopes?: string[];
255
+ /**
256
+ * Product marketing content shown alongside the sign-in form.
257
+ * When provided, the page uses a split layout: marketing on the left,
258
+ * sign-in form on the right.
259
+ */
260
+ marketing?: {
261
+ appName: string;
262
+ tagline: string;
263
+ description?: string;
264
+ features?: string[];
265
+ runLocalCommand?: string;
266
+ };
267
+ /**
268
+ * Optional host-scoped notice shown before the built-in Google sign-in
269
+ * redirects to Google.
270
+ */
271
+ googleSignInNotice?: {
272
+ host?: string;
273
+ title: string;
274
+ body: string | string[];
275
+ continueLabel?: string;
276
+ cancelLabel?: string;
277
+ };
278
+ /**
279
+ * Optional email signup legal copy for the built-in login page.
280
+ * Leave unset to use Agent Native links only on `*.agent-native.com` hosts,
281
+ * pass false to suppress, or pass URLs for custom/self-hosted policies.
282
+ */
283
+ signupLegalNotice?: OnboardingHtmlOptions["signupLegalNotice"];
284
+ /**
285
+ * Google sign-in flow: `'popup'`, `'redirect'`, or `'auto'` (default).
286
+ *
287
+ * - `'auto'` — popup in normal browsers and Builder web iframes, redirect in
288
+ * Electron and Builder desktop preview/editor surfaces.
289
+ * - `'popup'` — force popup everywhere.
290
+ * - `'redirect'` — force redirect everywhere.
291
+ *
292
+ * Falls back to the `GOOGLE_AUTH_MODE` env var, then `'auto'`.
293
+ */
294
+ googleAuthMode?: GoogleAuthMode;
295
+ /**
296
+ * Additional Better Auth configuration (social providers, plugins, etc.)
297
+ */
298
+ betterAuth?: BetterAuthConfig;
299
+ }
300
+
301
+ // ---------------------------------------------------------------------------
302
+ // Constants
303
+ // ---------------------------------------------------------------------------
304
+
305
+ /**
306
+ * Cookie name for the framework's session cookie.
307
+ *
308
+ * Browsers scope cookies by host (NOT host+port — RFC 6265), so two apps
309
+ * running on different localhost ports share one cookie jar. When multiple
310
+ * templates run side-by-side (eager repo dev, the desktop app, multi-template
311
+ * deploys on a shared domain), they would otherwise stomp on each other's
312
+ * `an_session` cookie and ping-pong each other into a logged-out state.
313
+ *
314
+ * When an isolated app slug is resolved, suffix the cookie so each app gets
315
+ * its own slot.
316
+ *
317
+ * Workspace exception: in workspace mode (`AGENT_NATIVE_WORKSPACE=1`),
318
+ * every app shares the same origin AND the same DB, and cross-app SSO is
319
+ * the desired behavior — signing into Dispatch should mean you're signed
320
+ * in across the workspace's other apps too. Per-app suffixes break that.
321
+ * Use a single workspace-wide cookie so the legacy `an_session_*` token
322
+ * flow set by `setFrameworkSessionCookie` (which the Builder OAuth popup
323
+ * exchange relies on — see `desktop-exchange` and `oauthCallbackResponse`)
324
+ * is recognised by every app in the workspace.
325
+ *
326
+ * Cross-subdomain exception: when `COOKIE_DOMAIN` is set for a custom domain,
327
+ * use the unsuffixed `an_session` and emit `Domain=<COOKIE_DOMAIN>` so the
328
+ * cookie is shared across every subdomain. First-party `*.agent-native.com`
329
+ * apps are deliberately excluded from that behavior by default because each
330
+ * hosted app has its own auth database; they use Dispatch identity federation
331
+ * instead of a shared browser cookie.
332
+ */
333
+ const AUTH_COOKIE_NAMESPACE = resolveAuthCookieNamespace();
334
+
335
+ /**
336
+ * When set, the framework session cookie is shared across every subdomain
337
+ * matching this domain. Returns undefined when unset or deliberately ignored
338
+ * for first-party hosted apps, so cookies stay scoped to the origin host.
339
+ */
340
+ export function getCookieDomain(): string | undefined {
341
+ return AUTH_COOKIE_NAMESPACE.frameworkCookieDomain;
342
+ }
343
+
344
+ export const COOKIE_NAME = AUTH_COOKIE_NAMESPACE.frameworkCookieName;
345
+ export const BETTER_AUTH_COOKIE_PREFIX =
346
+ AUTH_COOKIE_NAMESPACE.betterAuthCookiePrefix;
347
+
348
+ /**
349
+ * Cookie domain attribute spread into every `setCookie`/`deleteCookie`.
350
+ * Empty when `COOKIE_DOMAIN` isn't set so the cookie stays scoped to the
351
+ * single origin (current production default for non-first-party apps).
352
+ */
353
+ export function cookieDomainAttrs(): { domain?: string } {
354
+ const domain = getCookieDomain();
355
+ return domain ? { domain } : {};
356
+ }
357
+
358
+ function getCookieValues(event: H3Event, name: string): string[] {
359
+ const values: string[] = [];
360
+ const raw = getHeader(event, "cookie");
361
+
362
+ if (raw) {
363
+ for (const part of String(raw).split(";")) {
364
+ const trimmed = part.trim();
365
+ if (!trimmed) continue;
366
+ const eq = trimmed.indexOf("=");
367
+ if (eq <= 0) continue;
368
+ if (trimmed.slice(0, eq).trim() !== name) continue;
369
+
370
+ let value = trimmed.slice(eq + 1).trim();
371
+ if (value.startsWith('"') && value.endsWith('"')) {
372
+ value = value.slice(1, -1);
373
+ }
374
+ try {
375
+ value = decodeURIComponent(value);
376
+ } catch {
377
+ // Keep the raw cookie value if it was not percent-encoded.
378
+ }
379
+ if (value && !values.includes(value)) values.push(value);
380
+ }
381
+ }
382
+
383
+ // H3's cookie parser keeps only the first duplicate name. Preserve it as a
384
+ // fallback for mock/runtime shapes that do not expose the raw Cookie header.
385
+ const parsed = getCookie(event, name);
386
+ if (parsed && !values.includes(parsed)) values.push(parsed);
387
+
388
+ return values;
389
+ }
390
+
391
+ export function getFrameworkSessionCookieValues(event: H3Event): string[] {
392
+ return getFrameworkSessionCookieEntries(event).map((entry) => entry.value);
393
+ }
394
+
395
+ function getFrameworkSessionCookieEntries(
396
+ event: H3Event,
397
+ ): Array<{ name: string; value: string }> {
398
+ const entries: Array<{ name: string; value: string }> = [];
399
+ const seenValues = new Set<string>();
400
+
401
+ for (const name of frameworkSessionCookieNamesToClear()) {
402
+ for (const value of getCookieValues(event, name)) {
403
+ if (seenValues.has(value)) continue;
404
+ seenValues.add(value);
405
+ entries.push({ name, value });
406
+ }
407
+ }
408
+
409
+ return entries;
410
+ }
411
+
412
+ function frameworkSessionCookieNamesToClear(): string[] {
413
+ return AUTH_COOKIE_NAMESPACE.frameworkCookieNamesToClear;
414
+ }
415
+
416
+ function deleteCookieFromEveryScope(event: H3Event, name: string): void {
417
+ // Clear host-only cookies first. Then clear any configured domain scope so
418
+ // stale shared cookies stop shadowing isolated app sessions.
419
+ deleteCookie(event, name, { path: "/" });
420
+ for (const domain of AUTH_COOKIE_NAMESPACE.frameworkCookieDomainsToClear) {
421
+ deleteCookie(event, name, { path: "/", domain });
422
+ }
423
+ }
424
+
425
+ export function clearFrameworkSessionCookies(event: H3Event): void {
426
+ for (const name of frameworkSessionCookieNamesToClear()) {
427
+ deleteCookieFromEveryScope(event, name);
428
+ }
429
+ }
430
+
431
+ async function getLegacyCookieSession(
432
+ event: H3Event,
433
+ ): Promise<AuthSession | null> {
434
+ for (const { name, value } of getFrameworkSessionCookieEntries(event)) {
435
+ const email = await getSessionEmail(value);
436
+ if (email) {
437
+ if (name !== COOKIE_NAME) setFrameworkSessionCookie(event, value);
438
+ return { email, token: value };
439
+ }
440
+ }
441
+ return null;
442
+ }
443
+ function getOAuthStateAppId(): string | undefined {
444
+ const raw = process.env.APP_NAME || process.env.npm_package_name;
445
+ if (!raw) return undefined;
446
+ const slug = raw
447
+ .toLowerCase()
448
+ .replace(/[^a-z0-9-]+/g, "-")
449
+ .replace(/^-+|-+$/g, "");
450
+ return slug || undefined;
451
+ }
452
+
453
+ function oauthDebugFlowId(flowId: unknown): string | undefined {
454
+ return typeof flowId === "string" && flowId ? flowId.slice(-10) : undefined;
455
+ }
456
+
457
+ function oauthDebugUrlPath(value: unknown): string | undefined {
458
+ if (typeof value !== "string" || !value) return undefined;
459
+ try {
460
+ const url = new URL(value);
461
+ return url.pathname;
462
+ } catch {
463
+ return undefined;
464
+ }
465
+ }
466
+
467
+ function isBuilderOAuthRequest(event: H3Event): boolean {
468
+ const userAgent = getHeader(event, "user-agent") || "";
469
+ const referer = getHeader(event, "referer") || "";
470
+ return (
471
+ /Electron/i.test(userAgent) ||
472
+ /builder\.(io|my)|builderio\.(xyz|dev)|builder\.codes/i.test(referer)
473
+ );
474
+ }
475
+
476
+ function builderPreviewReturnOrigin(event: H3Event): string | undefined {
477
+ const referer = getHeader(event, "referer") || "";
478
+ if (!referer) return undefined;
479
+ try {
480
+ const url = new URL(referer);
481
+ const hostname = url.hostname.toLowerCase();
482
+ if (
483
+ url.protocol === "https:" &&
484
+ (hostname === "builderio.xyz" ||
485
+ hostname.endsWith(".builderio.xyz") ||
486
+ hostname === "builderio.dev" ||
487
+ hostname.endsWith(".builderio.dev") ||
488
+ hostname === "builder.codes" ||
489
+ hostname.endsWith(".builder.codes") ||
490
+ hostname === "builder.my" ||
491
+ hostname.endsWith(".builder.my"))
492
+ ) {
493
+ return url.origin;
494
+ }
495
+ } catch {}
496
+ return undefined;
497
+ }
498
+
499
+ function logGoogleOAuthDebug(
500
+ event: H3Event,
501
+ phase: string,
502
+ details: Record<string, unknown> = {},
503
+ ): void {
504
+ const { flowId, ...rest } = details;
505
+ const reqUrl = event.node?.req?.url ?? event.path ?? "";
506
+ const path = reqUrl.split("?")[0] || undefined;
507
+ const userAgent = getHeader(event, "user-agent") || "";
508
+ const referer = getHeader(event, "referer") || "";
509
+ console.info("[agent-native][google-oauth]", {
510
+ phase,
511
+ app: getOAuthStateAppId(),
512
+ path,
513
+ flow: oauthDebugFlowId(flowId),
514
+ electron: /Electron/i.test(userAgent),
515
+ agentNativeDesktop: /AgentNativeDesktop/i.test(userAgent),
516
+ builderReferrer:
517
+ /builder\.(io|my)|builderio\.(xyz|dev)|builder\.codes/i.test(referer),
518
+ ...rest,
519
+ });
520
+ }
521
+ const DEFAULT_MAX_AGE = 60 * 60 * 24 * 30; // 30 days
522
+
523
+ // ---------------------------------------------------------------------------
524
+ // Environment helpers
525
+ // ---------------------------------------------------------------------------
526
+
527
+ /**
528
+ * Check if we're in a development/test environment.
529
+ * Used for cookie security settings, not for auth bypass.
530
+ */
531
+ export function isDevEnvironment(): boolean {
532
+ const env = process.env.NODE_ENV;
533
+ return env === "development" || env === "test";
534
+ }
535
+
536
+ /**
537
+ * Validate a `?return=` URL for the /_agent-native/sign-in entrypoint.
538
+ *
539
+ * Parses the candidate against a sentinel base origin; any input that
540
+ * resolves to a different origin (network-path references, absolute URLs,
541
+ * `data:` / `javascript:` schemes, backslash-bypass tricks WHATWG normalises
542
+ * to `//`) gets rejected and falls back to "/". Control characters are
543
+ * stripped up front to defend against header-injection. Returns the
544
+ * normalised path the parser produced — never the raw input.
545
+ *
546
+ * Exported for unit tests.
547
+ */
548
+ export function safeReturnPath(raw: string | null | undefined): string {
549
+ if (!raw) return "/";
550
+ if (/[\x00-\x1f]/.test(raw)) return "/";
551
+ try {
552
+ const parsed = new URL(raw, "http://safe-base.invalid");
553
+ if (parsed.origin !== "http://safe-base.invalid") return "/";
554
+ // Never return to the sign-in entry point itself. A `return` that resolves
555
+ // back to `…/_agent-native/sign-in` re-enters the redirect loop — each hop
556
+ // nests the prior sign-in URL as a fresh `?return=`. Collapse it to "/" so
557
+ // the post-sign-in 302 lands on the app, not another sign-in page. Matches
558
+ // with or without an app base-path prefix (`/<app>/_agent-native/sign-in`).
559
+ if (parsed.pathname.endsWith("/_agent-native/sign-in")) return "/";
560
+ return parsed.pathname + parsed.search + parsed.hash;
561
+ } catch {
562
+ return "/";
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Return the configured login HTML for this request, or `null` when no auth
568
+ * guard is installed. Used by the `/_agent-native/open` deep-link route to
569
+ * serve the same sign-in form the auth guard would — at the original deep
570
+ * link URL — so the login form's `window.location.replace(href)` success
571
+ * handler reloads the same URL and the (now authenticated) open route
572
+ * proceeds. Mirrors the rawPath/getLoginHtml resolution in the auth guard.
573
+ */
574
+ export function getConfiguredLoginHtml(event: H3Event): string | null {
575
+ const config = _authGuardConfig;
576
+ if (!config) return null;
577
+ const url = event.node?.req?.url ?? event.path ?? "/";
578
+ const queryStart = url.indexOf("?");
579
+ const rawPath = queryStart >= 0 ? url.slice(0, queryStart) : url;
580
+ const loginHtml =
581
+ config.getLoginHtml?.(event, rawPath) ?? config.loginHtml ?? null;
582
+ return loginHtml ? injectLoginSocialImageMeta(loginHtml, event) : null;
583
+ }
584
+
585
+ /**
586
+ * True only when the request originates from the local machine — the raw
587
+ * socket peer is `127.0.0.0/8`, `::1`, or the IPv4-mapped `::ffff:127.0.0.1`
588
+ * (an optional IPv6 zone id like `fe80::1%en0` is stripped first).
589
+ *
590
+ * `getRequestIP(event)` is called WITHOUT `{ xForwardedFor: true }`, so it
591
+ * returns the real connection peer and never an attacker-controlled
592
+ * `X-Forwarded-For` value — a remote client cannot spoof its way past this.
593
+ * Used to scope local-only conveniences (the desktop SSO broker and the dev
594
+ * auto-account) so a directly network-reachable dev server never exposes
595
+ * them to a remote visitor. NOTE: a reverse proxy / tunnel that connects to
596
+ * the dev server over localhost still appears as loopback, so this is a
597
+ * necessary but not sufficient gate — callers pair it with NODE_ENV and,
598
+ * for the dev account, a throwaway per-DB password.
599
+ */
600
+ export function isLoopbackAddress(ip: string | undefined): boolean {
601
+ // Strip an optional IPv6 zone id (e.g. "fe80::1%en0") before comparing.
602
+ const normalised = (ip ?? "").split("%")[0];
603
+ return (
604
+ normalised === "127.0.0.1" ||
605
+ normalised === "::1" ||
606
+ normalised === "::ffff:127.0.0.1" ||
607
+ normalised.startsWith("127.")
608
+ );
609
+ }
610
+
611
+ /**
612
+ * True when the request's actual socket peer is loopback. Uses
613
+ * `getRequestIP(event)` WITHOUT `{ xForwardedFor: true }`, so it reflects the
614
+ * real connecting IP and a remote client cannot spoof it via the `Host` /
615
+ * `X-Forwarded-*` headers. Use this — not a parsed `Host`-header origin — for
616
+ * any "is this local dev?" security gate (MCP/connect dev-open).
617
+ */
618
+ export function isLoopbackRequest(event: H3Event): boolean {
619
+ let ip: string | undefined;
620
+ try {
621
+ ip = getRequestIP(event) ?? undefined;
622
+ } catch {
623
+ ip = undefined;
624
+ }
625
+ return isLoopbackAddress(ip);
626
+ }
627
+
628
+ /**
629
+ * Read the desktop-SSO broker file, but only if the request is plausibly
630
+ * from the Electron desktop app *and* coming from the local machine.
631
+ *
632
+ * The broker file lives in the user's home directory and trusts the local
633
+ * trust boundary — a non-loopback request that pretends to be Electron
634
+ * via User-Agent must NEVER be allowed to read it. We additionally refuse
635
+ * any read in production builds: the desktop app launches with
636
+ * `NODE_ENV=development` (or unset), and any web-hosted production deploy
637
+ * has no business consulting a per-user file on the server's homedir
638
+ * even if one exists.
639
+ *
640
+ * Returns null when the safety checks fail or the file isn't present.
641
+ */
642
+ async function readDesktopSsoSafely(
643
+ event: H3Event,
644
+ ): Promise<Awaited<ReturnType<typeof readDesktopSso>>> {
645
+ if (process.env.NODE_ENV === "production") return null;
646
+ if (!isElectronRequest(event)) return null;
647
+ if (!isLoopbackRequest(event)) return null;
648
+ return await readDesktopSso();
649
+ }
650
+
651
+ /**
652
+ * Extract the framework session token from a Better Auth response's
653
+ * Set-Cookie headers, if any. Used by the password-reset path to skip
654
+ * the freshly-minted session when revoking sibling sessions for the
655
+ * user. Returns undefined if no session cookie was minted (the common
656
+ * case — Better Auth's reset doesn't auto-sign-in by default).
657
+ */
658
+ function extractSessionTokenFromSetCookies(
659
+ response: Response,
660
+ ): string | undefined {
661
+ try {
662
+ // Headers may have multiple Set-Cookie entries; iterate via getSetCookie
663
+ // when available (Node 20+ / undici), else fall back to comma split.
664
+ const headers = response.headers as Headers & {
665
+ getSetCookie?: () => string[];
666
+ };
667
+ const setCookies =
668
+ typeof headers.getSetCookie === "function"
669
+ ? headers.getSetCookie()
670
+ : (headers.get("set-cookie") ?? "")
671
+ .split(/,(?=[^;]+=)/)
672
+ .map((s) => s.trim())
673
+ .filter(Boolean);
674
+ for (const sc of setCookies) {
675
+ // Better Auth's session cookie name is configurable but defaults to
676
+ // `<prefix>.session_token`. Match either the Better Auth default or
677
+ // our COOKIE_NAME (`an_session`) on the same line.
678
+ const match = sc.match(
679
+ /(?:^|\s|;)(an_session|[\w.-]*session_token)=([^;]+)/i,
680
+ );
681
+ if (match) return match[2];
682
+ }
683
+ } catch {
684
+ // Best-effort; treat as no token.
685
+ }
686
+ return undefined;
687
+ }
688
+
689
+ // ---------------------------------------------------------------------------
690
+ // ACCESS_TOKEN resolution
691
+ // ---------------------------------------------------------------------------
692
+
693
+ function getAccessTokens(): string[] {
694
+ const single = process.env.ACCESS_TOKEN;
695
+ const multi = process.env.ACCESS_TOKENS;
696
+ const tokens: string[] = [];
697
+ if (single) tokens.push(single);
698
+ if (multi) {
699
+ for (const t of multi.split(",")) {
700
+ const trimmed = t.trim();
701
+ if (trimmed && !tokens.includes(trimmed)) tokens.push(trimmed);
702
+ }
703
+ }
704
+ return tokens;
705
+ }
706
+
707
+ function getBearerSessionToken(event: H3Event): string | undefined {
708
+ const auth = getHeader(event, "authorization");
709
+ if (!auth) return undefined;
710
+ const match = /^Bearer\s+(.+)$/i.exec(auth.trim());
711
+ return match?.[1]?.trim() || undefined;
712
+ }
713
+
714
+ async function getBearerLegacySession(
715
+ event: H3Event,
716
+ ): Promise<AuthSession | null> {
717
+ const bearerToken = getBearerSessionToken(event);
718
+ if (!bearerToken) return null;
719
+ const email = await getSessionEmail(bearerToken);
720
+ return email ? { email, token: bearerToken } : null;
721
+ }
722
+
723
+ /**
724
+ * Verify a connect-minted MCP OAuth access token presented as
725
+ * `Authorization: Bearer <jwt>` and resolve it to a session.
726
+ *
727
+ * `agent-native connect` mints this token for the local Plans publish flow and
728
+ * POSTs it to the HOSTED action route
729
+ * `/_agent-native/actions/import-visual-plan-source`. That token is audience-
730
+ * bound to the app's canonical MCP resource (`{appUrl}/mcp`; the legacy
731
+ * `/_agent-native/mcp` resource is also accepted), not to the legacy `sessions`
732
+ * table — so the legacy bearer lookup above never matches it.
733
+ * Reuse the MCP surface's canonical `verifyAuth` here so the HTTP action surface
734
+ * honors EXACTLY the tokens the MCP endpoint honors: same signature check, same
735
+ * audience binding to THIS app's resource, same connect-token revocation gate.
736
+ * It resolves to the same `{ userEmail, orgId }` identity the MCP path uses, so
737
+ * downstream `accessFilter` / ownable-data scoping is identical.
738
+ *
739
+ * `allowDevOpen: false` and the `userEmail` guard ensure an invalid token (or a
740
+ * bare ACCESS_TOKEN with no owner hint) never escalates to an unauthenticated
741
+ * or unscoped identity on this path — it strictly adds acceptance of verified,
742
+ * audience-bound caller tokens, nothing more.
743
+ */
744
+ async function getMcpOAuthBearerSession(
745
+ event: H3Event,
746
+ ): Promise<AuthSession | null> {
747
+ const authHeader = getHeader(event, "authorization");
748
+ if (!authHeader) return null;
749
+ const bearerToken = getBearerSessionToken(event);
750
+ if (!bearerToken) return null;
751
+
752
+ try {
753
+ const [{ getMcpOAuthAudiences }, { verifyAuth, resolveOrgIdFromDomain }] =
754
+ await Promise.all([
755
+ import("../mcp/oauth-route.js"),
756
+ import("../mcp/build-server.js"),
757
+ ]);
758
+ const result = await verifyAuth(authHeader, undefined, {
759
+ resourceUrl: getMcpOAuthAudiences(event),
760
+ allowDevOpen: false,
761
+ });
762
+ const identity = result.authed ? result.identity : undefined;
763
+ if (!identity?.userEmail) return null;
764
+ const orgId =
765
+ identity.orgId ?? (await resolveOrgIdFromDomain(identity.orgDomain));
766
+ return {
767
+ email: identity.userEmail,
768
+ token: bearerToken,
769
+ ...(orgId ? { orgId } : {}),
770
+ };
771
+ } catch (e) {
772
+ console.error("[auth] MCP OAuth bearer verification error:", e);
773
+ return null;
774
+ }
775
+ }
776
+
777
+ function isFrameworkActionRoute(event: H3Event): boolean {
778
+ const { rawPath } = getRequestPathAndSearch(event);
779
+ const path = stripAppBasePath(rawPath);
780
+ return (
781
+ path === "/_agent-native/actions" ||
782
+ path.startsWith("/_agent-native/actions/")
783
+ );
784
+ }
785
+
786
+ /**
787
+ * Resolve an `Authorization: Bearer` token to a session: first the legacy
788
+ * `sessions` table (desktop/native persisted tokens), then, only on the
789
+ * framework HTTP action surface, a connect-minted MCP OAuth access token (the
790
+ * local Plans publish credential).
791
+ */
792
+ async function getBearerSession(event: H3Event): Promise<AuthSession | null> {
793
+ const legacy = await getBearerLegacySession(event);
794
+ if (legacy) return legacy;
795
+ if (!isFrameworkActionRoute(event)) return null;
796
+ return getMcpOAuthBearerSession(event);
797
+ }
798
+
799
+ function shouldExposeSessionTokenInBody(event: H3Event): boolean {
800
+ const origin = getHeader(event, "origin");
801
+ if (origin && DESKTOP_AUTH_TOKEN_BODY_ORIGINS.has(origin)) return true;
802
+
803
+ // Some native WebViews do not consistently emit an Origin header for
804
+ // programmatic fetches. The desktop app marks same-server requests with
805
+ // X-Request-Source; browsers can only use that cross-origin after our CORS
806
+ // allowlist has approved the origin, and same-origin pages already receive
807
+ // an equivalent httpOnly session cookie on successful login.
808
+ return !origin && getHeader(event, "x-request-source") === "clips-desktop";
809
+ }
810
+
811
+ function authLoginResponse(
812
+ event: H3Event,
813
+ token: string,
814
+ email?: string,
815
+ ): { ok: true; token?: string; email?: string } {
816
+ if (!shouldExposeSessionTokenInBody(event)) return { ok: true };
817
+ return email ? { ok: true, token, email } : { ok: true, token };
818
+ }
819
+
820
+ function decodeEmailVerificationTokenEmail(request: Request): string | null {
821
+ try {
822
+ const token = new URL(request.url).searchParams.get("token");
823
+ const payloadSegment = token?.split(".")[1];
824
+ if (!payloadSegment) return null;
825
+ const payload = JSON.parse(
826
+ Buffer.from(payloadSegment, "base64url").toString("utf8"),
827
+ ) as { email?: unknown; updateTo?: unknown };
828
+ return normalizeAuthEmail(payload.updateTo ?? payload.email);
829
+ } catch {
830
+ return null;
831
+ }
832
+ }
833
+
834
+ function verifyEmailRedirectHasError(
835
+ location: string,
836
+ requestUrl: string,
837
+ ): boolean {
838
+ try {
839
+ return new URL(location, requestUrl).searchParams.has("error");
840
+ } catch {
841
+ return /[?&]error=/.test(location);
842
+ }
843
+ }
844
+
845
+ function appendVerifiedParamToLocation(location: string): string {
846
+ const hashIndex = location.indexOf("#");
847
+ const beforeHash = hashIndex >= 0 ? location.slice(0, hashIndex) : location;
848
+ const hash = hashIndex >= 0 ? location.slice(hashIndex) : "";
849
+ const sep = beforeHash.includes("?") ? "&" : "?";
850
+ return `${beforeHash}${sep}verified=1${hash}`;
851
+ }
852
+
853
+ async function ensureEmailVerifiedForRedirect(
854
+ request: Request,
855
+ response: Response,
856
+ ): Promise<void> {
857
+ const email =
858
+ (await emailFromVerificationResponseSession(response)) ??
859
+ decodeEmailVerificationTokenEmail(request);
860
+ if (!email) return;
861
+ try {
862
+ const db = getDbExec();
863
+ await db.execute({
864
+ sql: 'UPDATE "user" SET email_verified = TRUE WHERE email = ? AND (email_verified = FALSE OR email_verified IS NULL)',
865
+ args: [email],
866
+ });
867
+ } catch {
868
+ // Better Auth already handled the verification route. This repair is
869
+ // best-effort so response cookies/redirects are never lost to DB noise.
870
+ }
871
+ }
872
+
873
+ async function emailFromVerificationResponseSession(
874
+ response: Response,
875
+ ): Promise<string | null> {
876
+ const sessionToken = extractSessionTokenFromSetCookies(response);
877
+ if (!sessionToken) return null;
878
+ try {
879
+ const db = getDbExec();
880
+ const { rows } = await db.execute({
881
+ sql: 'SELECT u.email FROM "session" s JOIN "user" u ON u.id = s.user_id WHERE s.token = ? LIMIT 1',
882
+ args: [sessionToken],
883
+ });
884
+ return normalizeAuthEmail(rows[0]?.email ?? rows[0]?.[0]);
885
+ } catch {
886
+ return null;
887
+ }
888
+ }
889
+
890
+ /**
891
+ * Bad-credential / already-registered errors are normal user behavior, not
892
+ * bugs we want to investigate. Filtering them out keeps Sentry signal
893
+ * actionable — a real anomaly (DB error, Better Auth init crash, missing
894
+ * table) shows up clearly because it doesn't match any of these patterns.
895
+ */
896
+ const EXPECTED_AUTH_FAILURE_PATTERNS: RegExp[] = [
897
+ /invalid\s+(email|password|credentials)/i,
898
+ /\[?body\.email\]?\s+invalid input/i,
899
+ /password.*incorrect/i,
900
+ /user\s+(not\s+found|already\s+exists)/i,
901
+ /email\s+already/i,
902
+ /already\s+(exists|registered|in\s+use)/i,
903
+ /not\s+verified/i,
904
+ ];
905
+
906
+ const VALID_AUTH_EMAIL_MESSAGE =
907
+ "Enter a valid email address, like you@example.com.";
908
+ const AUTH_EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
909
+
910
+ function normalizeAuthEmail(value: unknown): string | null {
911
+ if (typeof value !== "string") return null;
912
+ const email = value.trim().toLowerCase();
913
+ return AUTH_EMAIL_PATTERN.test(email) ? email : null;
914
+ }
915
+
916
+ function publicAuthError(
917
+ error: unknown,
918
+ fallback: string,
919
+ ): { message: string; statusCode?: number } {
920
+ const message = (error as { message?: unknown })?.message;
921
+ if (typeof message === "string") {
922
+ if (isAuthEmailValidationMessage(message)) {
923
+ return { message: VALID_AUTH_EMAIL_MESSAGE, statusCode: 400 };
924
+ }
925
+ if (message.trim()) return { message };
926
+ }
927
+ return { message: fallback };
928
+ }
929
+
930
+ function isAuthEmailValidationMessage(message: string): boolean {
931
+ // Credential failures (e.g. Better Auth's "Invalid email or password") mention
932
+ // "email" + "invalid" but are NOT email-format errors — don't rewrite them to
933
+ // the "enter a valid email" message, or every wrong-password attempt looks like
934
+ // a malformed-email error.
935
+ if (/password|credential/i.test(message)) return false;
936
+ return (
937
+ /\bemail\b/i.test(message) &&
938
+ /(invalid|input|required|format)/i.test(message)
939
+ );
940
+ }
941
+
942
+ export function isExpectedAuthFailure(error: unknown): boolean {
943
+ const msg = (error as { message?: unknown })?.message;
944
+ if (typeof msg !== "string") return false;
945
+ return EXPECTED_AUTH_FAILURE_PATTERNS.some((re) => re.test(msg));
946
+ }
947
+
948
+ // ---------------------------------------------------------------------------
949
+ // Legacy session store — kept for backward compat (addSession/getSessionEmail)
950
+ // Used by google-oauth.ts for mobile deep linking session creation.
951
+ // ---------------------------------------------------------------------------
952
+
953
+ let _sessionInitPromise: Promise<void> | undefined;
954
+ let sessionMaxAge = DEFAULT_MAX_AGE;
955
+
956
+ async function ensureSessionTable(): Promise<void> {
957
+ if (!_sessionInitPromise) {
958
+ _sessionInitPromise = (async () => {
959
+ const client = getDbExec();
960
+ const createSql = `
961
+ CREATE TABLE IF NOT EXISTS sessions (
962
+ token TEXT PRIMARY KEY,
963
+ email TEXT,
964
+ created_at ${intType()} NOT NULL
965
+ )
966
+ `;
967
+
968
+ // PG guard: probe information_schema first (no lock), run DDL only when
969
+ // missing, bounded by a transaction-scoped lock_timeout.
970
+ if (isPostgres()) {
971
+ await ensureTableExists("sessions", createSql);
972
+ await ensureColumnExists(
973
+ "sessions",
974
+ "email",
975
+ `ALTER TABLE sessions ADD COLUMN IF NOT EXISTS email TEXT`,
976
+ );
977
+ // Older deployments have a 32-bit `created_at`; on Postgres the
978
+ // `Date.now()` written on session create overflows int4. Widen in place
979
+ // (no-op once done / on fresh DBs).
980
+ await widenIntColumnsToBigInt("sessions", ["created_at"]);
981
+ return;
982
+ }
983
+
984
+ // SQLite (local dev): no lock problem — keep the original behaviour.
985
+ await retryOnDdlRace(() => client.execute(createSql));
986
+ try {
987
+ await client.execute(`ALTER TABLE sessions ADD COLUMN email TEXT`);
988
+ } catch {
989
+ // Column already exists
990
+ }
991
+ // Older deployments have a 32-bit `created_at`; on Postgres the
992
+ // `Date.now()` written on session create overflows int4. Widen in place
993
+ // (no-op once done / on fresh DBs).
994
+ await widenIntColumnsToBigInt("sessions", ["created_at"]);
995
+ })().catch((err) => {
996
+ // Don't cache the rejection — let the next caller retry a fresh init.
997
+ _sessionInitPromise = undefined;
998
+ throw err;
999
+ });
1000
+ }
1001
+ return _sessionInitPromise;
1002
+ }
1003
+
1004
+ /**
1005
+ * Re-run any `sessions`-table op once if Postgres reports the relation is
1006
+ * missing. Covers the case where a prior `ensureSessionTable()` resolved but
1007
+ * the table wasn't actually present (e.g. a race where the CREATE was dropped
1008
+ * on a reused pool connection, or a cached resolved promise from a prior
1009
+ * DB URL). Forces a fresh init, then retries the caller's op.
1010
+ */
1011
+ async function retryIfSessionsMissing<T>(op: () => Promise<T>): Promise<T> {
1012
+ try {
1013
+ return await op();
1014
+ } catch (e: any) {
1015
+ if (e?.code !== "42P01") throw e;
1016
+ const msg = String(e?.message ?? "");
1017
+ if (!msg.includes("sessions")) throw e;
1018
+ _sessionInitPromise = undefined;
1019
+ await ensureSessionTable();
1020
+ return await op();
1021
+ }
1022
+ }
1023
+
1024
+ /**
1025
+ * Create a new session in the legacy sessions table.
1026
+ * Used by google-oauth.ts for mobile deep linking.
1027
+ */
1028
+ export async function addSession(token: string, email?: string): Promise<void> {
1029
+ await ensureSessionTable();
1030
+ const client = getDbExec();
1031
+ await retryIfSessionsMissing(() =>
1032
+ client.execute({
1033
+ sql: isPostgres()
1034
+ ? `INSERT INTO sessions (token, email, created_at) VALUES (?, ?, ?) ON CONFLICT (token) DO UPDATE SET email=EXCLUDED.email, created_at=EXCLUDED.created_at`
1035
+ : `INSERT OR REPLACE INTO sessions (token, email, created_at) VALUES (?, ?, ?)`,
1036
+ args: [token, email ?? null, Date.now()],
1037
+ }),
1038
+ );
1039
+ }
1040
+
1041
+ export async function hasLegacySessionForEmail(
1042
+ email: string,
1043
+ ): Promise<boolean> {
1044
+ await ensureSessionTable();
1045
+ const client = getDbExec();
1046
+ const result = await retryIfSessionsMissing(() =>
1047
+ client.execute({
1048
+ sql: `SELECT 1 FROM sessions WHERE email = ? LIMIT 1`,
1049
+ args: [email],
1050
+ }),
1051
+ );
1052
+ return result.rows.length > 0;
1053
+ }
1054
+
1055
+ /** Remove a session from the legacy sessions table. */
1056
+ export async function removeSession(token: string): Promise<void> {
1057
+ await ensureSessionTable();
1058
+ const client = getDbExec();
1059
+ await retryIfSessionsMissing(() =>
1060
+ client.execute({
1061
+ sql: `DELETE FROM sessions WHERE token = ?`,
1062
+ args: [token],
1063
+ }),
1064
+ );
1065
+ }
1066
+
1067
+ /**
1068
+ * Look up the email associated with a legacy session token.
1069
+ * Returns null if the session doesn't exist, is expired, or has no email.
1070
+ */
1071
+ export async function getSessionEmail(token: string): Promise<string | null> {
1072
+ await ensureSessionTable();
1073
+ const client = getDbExec();
1074
+ const { rows } = await retryIfSessionsMissing(() =>
1075
+ client.execute({
1076
+ sql: `SELECT email, created_at FROM sessions WHERE token = ?`,
1077
+ args: [token],
1078
+ }),
1079
+ );
1080
+ if (rows.length === 0) return null;
1081
+ const createdAt = rows[0].created_at as number;
1082
+ if (Date.now() - createdAt > sessionMaxAge * 1000) {
1083
+ await client.execute({
1084
+ sql: `DELETE FROM sessions WHERE token = ?`,
1085
+ args: [token],
1086
+ });
1087
+ return null;
1088
+ }
1089
+ return (rows[0].email as string) ?? null;
1090
+ }
1091
+
1092
+ // ---------------------------------------------------------------------------
1093
+ // getSession — the auth contract
1094
+ // ---------------------------------------------------------------------------
1095
+
1096
+ let customGetSession: ((event: H3Event) => Promise<AuthSession | null>) | null =
1097
+ null;
1098
+
1099
+ /**
1100
+ * Mutable config for the auth guard. Stored separately from the guard function
1101
+ * so that a custom auth plugin can update the login HTML / public paths even
1102
+ * after the default plugin has already installed the middleware (a race that
1103
+ * occurs in production serverless environments where the default plugin is
1104
+ * auto-mounted before the template's custom auth plugin runs).
1105
+ */
1106
+ interface AuthGuardConfig {
1107
+ loginHtml: string;
1108
+ getLoginHtml?: (event: H3Event, rawPath: string) => string;
1109
+ publicPaths: string[];
1110
+ workspaceAppAudience: WorkspaceAppAudience;
1111
+ workspaceAppPublicPaths: string[];
1112
+ workspaceAppProtectedPaths: string[];
1113
+ }
1114
+ let _authGuardConfig: AuthGuardConfig | null = null;
1115
+ const _genericGoogleOAuthRoutesEnabled = new WeakMap<object, boolean>();
1116
+
1117
+ function getRequestHost(event: H3Event): string | undefined {
1118
+ return (
1119
+ getHeader(event, "x-forwarded-host") ??
1120
+ getHeader(event, "host") ??
1121
+ undefined
1122
+ );
1123
+ }
1124
+
1125
+ function getOnboardingHtmlOptions(
1126
+ options: AuthOptions,
1127
+ event?: H3Event,
1128
+ rawPath?: string,
1129
+ ): OnboardingHtmlOptions {
1130
+ return {
1131
+ googleOnly: options.googleOnly,
1132
+ marketing: options.marketing,
1133
+ googleSignInNotice: options.googleSignInNotice,
1134
+ signupLegalNotice: options.signupLegalNotice,
1135
+ googleAuthMode: options.googleAuthMode,
1136
+ requestHost: event ? getRequestHost(event) : undefined,
1137
+ requestPath: rawPath,
1138
+ requestOrigin: event ? getOrigin(event) : undefined,
1139
+ };
1140
+ }
1141
+
1142
+ function getAuthOnboardingHtml(
1143
+ options: AuthOptions,
1144
+ event?: H3Event,
1145
+ rawPath?: string,
1146
+ ): string {
1147
+ return getOnboardingHtml(getOnboardingHtmlOptions(options, event, rawPath));
1148
+ }
1149
+
1150
+ function getOnboardingLoginHtmlConfig(
1151
+ options: AuthOptions,
1152
+ ): Pick<AuthGuardConfig, "loginHtml" | "getLoginHtml"> {
1153
+ if (options.loginHtml) return { loginHtml: options.loginHtml };
1154
+ return {
1155
+ loginHtml: getAuthOnboardingHtml(options),
1156
+ getLoginHtml: (event, rawPath) =>
1157
+ getAuthOnboardingHtml(options, event, rawPath),
1158
+ };
1159
+ }
1160
+
1161
+ function resolveWorkspaceAppAudience(
1162
+ options: Pick<AuthOptions, "workspaceAppAudience"> = {},
1163
+ ): WorkspaceAppAudience {
1164
+ return normalizeWorkspaceAppAudience(
1165
+ options.workspaceAppAudience ?? workspaceAppAudienceFromEnv(),
1166
+ );
1167
+ }
1168
+
1169
+ function resolveWorkspaceAppRouteAccess(
1170
+ options: Pick<
1171
+ AuthOptions,
1172
+ "workspaceAppPublicPaths" | "workspaceAppProtectedPaths"
1173
+ > = {},
1174
+ ): { publicPaths: string[]; protectedPaths: string[] } {
1175
+ const env = workspaceAppRouteAccessFromEnv();
1176
+ return {
1177
+ publicPaths: options.workspaceAppPublicPaths ?? env.publicPaths,
1178
+ protectedPaths: options.workspaceAppProtectedPaths ?? env.protectedPaths,
1179
+ };
1180
+ }
1181
+
1182
+ function setGenericGoogleOAuthRoutesEnabled(
1183
+ app: H3App,
1184
+ enabled: boolean,
1185
+ ): void {
1186
+ if (app && typeof app === "object") {
1187
+ _genericGoogleOAuthRoutesEnabled.set(app, enabled);
1188
+ }
1189
+ }
1190
+
1191
+ function areGenericGoogleOAuthRoutesEnabled(app: H3App): boolean {
1192
+ return _genericGoogleOAuthRoutesEnabled.get(app as object) !== false;
1193
+ }
1194
+
1195
+ // Desktop OAuth exchange store — holds session tokens keyed by a unique flow
1196
+ // ID so native apps (Tauri, Electron) that open OAuth in the system browser
1197
+ // can retrieve the token after the callback completes on the server.
1198
+ //
1199
+ // Primary: in-memory Map (fast, works for single-instance dev/preview builds).
1200
+ // Fallback: sessions table with a "dex:" prefixed key for cross-instance
1201
+ // durability (Cloudflare Workers, multi-region deployments). The value stored
1202
+ // in the `email` column is "{realToken}::{userEmail}" so both can be recovered
1203
+ // from a single DB lookup.
1204
+ export interface DesktopExchangeErrorPayload {
1205
+ message: string;
1206
+ code?: string;
1207
+ accountId?: string;
1208
+ existingOwner?: string;
1209
+ attemptedOwner?: string;
1210
+ }
1211
+
1212
+ type DesktopExchangeEntry =
1213
+ | { token: string; email: string; expiresAt: number }
1214
+ | { error: DesktopExchangeErrorPayload; expiresAt: number };
1215
+ type DesktopExchangeStoredEntry =
1216
+ | { token: string; email: string }
1217
+ | { error: DesktopExchangeErrorPayload };
1218
+
1219
+ const _desktopExchanges = new Map<string, DesktopExchangeEntry>();
1220
+ const DESKTOP_EXCHANGE_ERROR_PREFIX = "__error__::";
1221
+ const DESKTOP_AUTH_TOKEN_BODY_ORIGINS = new Set([
1222
+ "tauri://localhost",
1223
+ "http://tauri.localhost",
1224
+ "https://tauri.localhost",
1225
+ "http://localhost:1420",
1226
+ ]);
1227
+
1228
+ // 5-minute TTL for exchange entries (short — single-use tokens).
1229
+ const DESKTOP_EXCHANGE_TTL_MS = 5 * 60 * 1000;
1230
+
1231
+ export function setDesktopExchange(
1232
+ flowId: string,
1233
+ token: string,
1234
+ email: string,
1235
+ ) {
1236
+ _desktopExchanges.set(flowId, {
1237
+ token,
1238
+ email,
1239
+ expiresAt: Date.now() + DESKTOP_EXCHANGE_TTL_MS,
1240
+ });
1241
+ // Persist to DB so the token survives cross-instance routing (e.g. when
1242
+ // templates call this helper directly instead of going through the OAuth
1243
+ // callback path).
1244
+ void persistDesktopExchangeToDB(flowId, token, email);
1245
+ }
1246
+
1247
+ export function setDesktopExchangeError(
1248
+ flowId: string,
1249
+ error: DesktopExchangeErrorPayload,
1250
+ ) {
1251
+ _desktopExchanges.set(flowId, {
1252
+ error,
1253
+ expiresAt: Date.now() + DESKTOP_EXCHANGE_TTL_MS,
1254
+ });
1255
+ void persistDesktopExchangeErrorToDB(flowId, error);
1256
+ }
1257
+
1258
+ /**
1259
+ * Persist a desktop exchange entry to the sessions table so it survives
1260
+ * cross-instance routing (e.g. Cloudflare Workers). Stored under a synthetic
1261
+ * token key "dex:{flowId}"; the `email` column packs both the real session
1262
+ * token and the user email so they can be recovered in one query.
1263
+ * Non-fatal — if the DB isn't ready yet the in-memory Map still works for
1264
+ * same-instance requests.
1265
+ */
1266
+ async function persistDesktopExchangeToDB(
1267
+ flowId: string,
1268
+ token: string,
1269
+ email: string,
1270
+ ): Promise<void> {
1271
+ try {
1272
+ await addSession(`dex:${flowId}`, `${token}::${email}`);
1273
+ } catch {
1274
+ // non-fatal — in-memory Map is the primary path
1275
+ }
1276
+ }
1277
+
1278
+ async function persistDesktopExchangeErrorToDB(
1279
+ flowId: string,
1280
+ error: DesktopExchangeErrorPayload,
1281
+ ): Promise<void> {
1282
+ try {
1283
+ const payload = Buffer.from(JSON.stringify(error)).toString("base64url");
1284
+ await addSession(
1285
+ `dex:${flowId}`,
1286
+ `${DESKTOP_EXCHANGE_ERROR_PREFIX}${payload}`,
1287
+ );
1288
+ } catch {
1289
+ // non-fatal — in-memory Map is the primary path
1290
+ }
1291
+ }
1292
+
1293
+ /**
1294
+ * Retrieve and consume a desktop exchange entry from the DB fallback.
1295
+ * Returns null if not found or already consumed.
1296
+ */
1297
+ async function consumeDesktopExchangeFromDB(
1298
+ flowId: string,
1299
+ ): Promise<DesktopExchangeStoredEntry | null> {
1300
+ try {
1301
+ // Atomic DELETE...RETURNING prevents token replay: two concurrent polls
1302
+ // cannot both retrieve the token because only one DELETE will match the row.
1303
+ // SQLite ≥3.35 and PostgreSQL both support this syntax.
1304
+ // The created_at predicate enforces the 5-minute TTL so stale DB entries
1305
+ // (e.g. the desktop app never polled) are rejected rather than silently
1306
+ // redeemed with the session table's default 30-day TTL.
1307
+ const client = getDbExec();
1308
+ const { rows } = await client.execute({
1309
+ sql: `DELETE FROM sessions WHERE token = ? AND created_at > ? RETURNING email`,
1310
+ args: [`dex:${flowId}`, Date.now() - DESKTOP_EXCHANGE_TTL_MS],
1311
+ });
1312
+ if (rows.length === 0) return null;
1313
+ const packed = (rows[0].email ?? rows[0][0]) as string | null;
1314
+ if (!packed) return null;
1315
+ if (packed.startsWith(DESKTOP_EXCHANGE_ERROR_PREFIX)) {
1316
+ const raw = packed.slice(DESKTOP_EXCHANGE_ERROR_PREFIX.length);
1317
+ return {
1318
+ error: JSON.parse(Buffer.from(raw, "base64url").toString()),
1319
+ };
1320
+ }
1321
+ const sepIdx = packed.indexOf("::");
1322
+ if (sepIdx === -1) return null;
1323
+ return { token: packed.slice(0, sepIdx), email: packed.slice(sepIdx + 2) };
1324
+ } catch {
1325
+ return null;
1326
+ }
1327
+ }
1328
+
1329
+ setInterval(() => {
1330
+ const now = Date.now();
1331
+ for (const [k, v] of _desktopExchanges) {
1332
+ if (v.expiresAt < now) _desktopExchanges.delete(k);
1333
+ }
1334
+ }, 60_000).unref?.();
1335
+
1336
+ /**
1337
+ * Module-level auth guard function. Set by autoMountAuth() when auth is active.
1338
+ * Called by the server middleware to enforce auth on ALL requests (not just
1339
+ * /_agent-native/* routes).
1340
+ */
1341
+ let _authGuardFn:
1342
+ | ((event: H3Event) => Promise<Response | object | string | void>)
1343
+ | null = null;
1344
+
1345
+ /**
1346
+ * The H3 app the auth routes + guard were last mounted on. Module-level
1347
+ * state survives Vite HMR restarts, but each HMR cycle creates a fresh
1348
+ * nitroApp/H3 instance whose middleware array is empty again. Tracking the
1349
+ * app here lets autoMountAuth detect "same module state, new app" and
1350
+ * re-mount routes instead of silently skipping them because `_authGuardFn`
1351
+ * looks populated from a previous cycle.
1352
+ */
1353
+ let _mountedApp: H3App | null = null;
1354
+
1355
+ /**
1356
+ * Run the auth guard on an event. Returns a Response/object to block the
1357
+ * request (login page or 401), or undefined to allow it through.
1358
+ *
1359
+ * Called by the default server middleware (server/middleware/auth.ts) to
1360
+ * enforce auth on page routes and API routes — not just framework routes.
1361
+ */
1362
+ export async function runAuthGuard(
1363
+ event: H3Event,
1364
+ ): Promise<Response | object | string | void> {
1365
+ if (!_authGuardFn) return; // Auth not mounted (local mode, etc.)
1366
+ return _authGuardFn(event);
1367
+ }
1368
+
1369
+ // ---------------------------------------------------------------------------
1370
+ // Auth guard factory
1371
+ // ---------------------------------------------------------------------------
1372
+
1373
+ /**
1374
+ * Create an auth guard function that checks session and blocks
1375
+ * unauthenticated requests. Returns the login HTML for page routes
1376
+ * or a 401 JSON response for API routes.
1377
+ *
1378
+ * Reads loginHtml and publicPaths from _authGuardConfig on every request
1379
+ * so that a custom plugin can update them after the default has already
1380
+ * installed this middleware (the production race condition fix).
1381
+ */
1382
+ function applyCorsHeaders(event: H3Event): {
1383
+ hasOrigin: boolean;
1384
+ allowed: boolean;
1385
+ } {
1386
+ // Framework-level CORS. The auth guard runs before any of the app's own
1387
+ // route handlers, so we need to set CORS here too — otherwise a 401
1388
+ // response would be missing the Allow-Origin header and the browser
1389
+ // blocks the response body (making it look like a network error
1390
+ // rather than "unauthenticated").
1391
+ const origin = getHeader(event, "origin");
1392
+ if (!origin) return { hasOrigin: false, allowed: true };
1393
+ const requestedHeaders = String(
1394
+ getHeader(event, "access-control-request-headers") ?? "",
1395
+ )
1396
+ .toLowerCase()
1397
+ .split(",")
1398
+ .map((header) => header.trim());
1399
+ const mcpEmbedCorsRequest =
1400
+ isMcpEmbedCorsOrigin(origin) &&
1401
+ (requestHasEmbedAuthMarker(event) ||
1402
+ requestedHeaders.includes(EMBED_TARGET_HEADER.toLowerCase()) ||
1403
+ requestedHeaders.includes(EMBED_TRANSPLANT_HEADER) ||
1404
+ Boolean(getHeader(event, EMBED_TARGET_HEADER)) ||
1405
+ Boolean(getHeader(event, EMBED_TRANSPLANT_HEADER)) ||
1406
+ Boolean(getHeader(event, "authorization")));
1407
+ const allowedOrigin = getAllowedCorsOrigin(origin, {
1408
+ allowedOrigins: readCorsAllowedOrigins(),
1409
+ allowLocalhostWhenNoAllowlist: true,
1410
+ });
1411
+ const responseOrigin = mcpEmbedCorsRequest ? origin : allowedOrigin;
1412
+ if (!responseOrigin) return { hasOrigin: true, allowed: false };
1413
+ setResponseHeader(event, "Access-Control-Allow-Origin", responseOrigin);
1414
+ setResponseHeader(event, "Vary", "Origin");
1415
+ if (!mcpEmbedCorsRequest || shouldAllowMcpEmbedCredentials(responseOrigin)) {
1416
+ setResponseHeader(event, "Access-Control-Allow-Credentials", "true");
1417
+ }
1418
+ setResponseHeader(
1419
+ event,
1420
+ "Access-Control-Allow-Methods",
1421
+ "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS",
1422
+ );
1423
+ setResponseHeader(
1424
+ event,
1425
+ "Access-Control-Allow-Headers",
1426
+ mcpEmbedCorsRequest
1427
+ ? MCP_EMBED_CORS_ALLOW_HEADERS
1428
+ : [
1429
+ "Content-Type",
1430
+ "Authorization",
1431
+ "X-Requested-With",
1432
+ "X-Request-Source",
1433
+ "X-Agent-Native-CSRF",
1434
+ "X-User-Timezone",
1435
+ EMBED_TARGET_HEADER,
1436
+ ].join(","),
1437
+ );
1438
+ return { hasOrigin: true, allowed: true };
1439
+ }
1440
+
1441
+ function createAuthCorsHandler() {
1442
+ return defineEventHandler((event) => {
1443
+ const cors = applyCorsHeaders(event);
1444
+ if (getMethod(event) !== "OPTIONS") return;
1445
+
1446
+ if (cors.hasOrigin && !cors.allowed) {
1447
+ setResponseStatus(event, 403);
1448
+ return "";
1449
+ }
1450
+
1451
+ setResponseStatus(event, 204);
1452
+ return "";
1453
+ });
1454
+ }
1455
+
1456
+ function mountAuthCorsMiddleware(app: H3App): void {
1457
+ const handler = createAuthCorsHandler();
1458
+ app.use("/_agent-native/auth", handler);
1459
+ app.use("/_agent-native/google", handler);
1460
+ }
1461
+
1462
+ function isFrameworkOAuthCallbackPath(pathname: string): boolean {
1463
+ return (
1464
+ pathname.startsWith("/_agent-native/") &&
1465
+ (pathname.endsWith("/callback") || pathname.includes("/callback/"))
1466
+ );
1467
+ }
1468
+
1469
+ function getRequestPathAndSearch(event: H3Event): {
1470
+ rawPath: string;
1471
+ search: string;
1472
+ } {
1473
+ const mountedPathname = (event as any).context?._mountedPathname;
1474
+ if (typeof mountedPathname === "string" && mountedPathname) {
1475
+ return { rawPath: mountedPathname, search: event.url?.search || "" };
1476
+ }
1477
+ const url = event.node?.req?.url ?? event.path ?? "/";
1478
+ const queryStart = url.indexOf("?");
1479
+ return {
1480
+ rawPath: queryStart >= 0 ? url.slice(0, queryStart) : url,
1481
+ search: queryStart >= 0 ? url.slice(queryStart) : "",
1482
+ };
1483
+ }
1484
+
1485
+ function workspaceOAuthCallbackRelayResponse(
1486
+ event: H3Event,
1487
+ ): Response | undefined {
1488
+ const { rawPath, search } = getRequestPathAndSearch(event);
1489
+ const normalizedPath = stripAppBasePath(rawPath);
1490
+ const basePath = getAppBasePath();
1491
+ if (
1492
+ !basePath ||
1493
+ !isWorkspaceOAuthCallbackRelayEnabled() ||
1494
+ !isFrameworkOAuthCallbackPath(normalizedPath) ||
1495
+ rawPath === `${basePath}/_agent-native` ||
1496
+ rawPath.startsWith(`${basePath}/_agent-native/`)
1497
+ ) {
1498
+ return undefined;
1499
+ }
1500
+
1501
+ const state = new URLSearchParams(
1502
+ search.startsWith("?") ? search.slice(1) : search,
1503
+ ).get("state");
1504
+ const appId = extractOAuthStateAppId(state);
1505
+ if (
1506
+ !appId ||
1507
+ appId === getOAuthStateAppId() ||
1508
+ !isValidWorkspaceAppIdFormat(appId)
1509
+ ) {
1510
+ return undefined;
1511
+ }
1512
+
1513
+ return new Response("", {
1514
+ status: 302,
1515
+ headers: { Location: `/${appId}${normalizedPath}${search}` },
1516
+ });
1517
+ }
1518
+
1519
+ function verifiedBuilderConnectOwnerFromUrl(url: string): string | null {
1520
+ const queryStart = url.indexOf("?");
1521
+ if (queryStart < 0) return null;
1522
+ const token = new URLSearchParams(url.slice(queryStart + 1)).get(
1523
+ BUILDER_CONNECT_PARAM,
1524
+ );
1525
+ return verifyBuilderConnectTokenAndGetOwner(token);
1526
+ }
1527
+
1528
+ export function shouldBypassAuthForBuilderConnect(
1529
+ event: H3Event,
1530
+ p: string,
1531
+ ): boolean {
1532
+ // The preview-safe second hop is authenticated by its timestamped HMAC and
1533
+ // one-shot pending row. It cannot carry a browser session from the corporate
1534
+ // callback deployment, so let the route perform that stronger check itself.
1535
+ if (p === BUILDER_RELAY_PATH) return true;
1536
+
1537
+ if (p === "/_agent-native/builder/connect") {
1538
+ const url = event.node?.req?.url ?? event.path ?? "/";
1539
+ return Boolean(verifiedBuilderConnectOwnerFromUrl(url));
1540
+ }
1541
+
1542
+ if (p === "/_agent-native/builder/callback") {
1543
+ const url = event.node?.req?.url ?? event.path ?? "/";
1544
+ const queryStart = url.indexOf("?");
1545
+ const state =
1546
+ queryStart >= 0
1547
+ ? new URLSearchParams(url.slice(queryStart + 1)).get(
1548
+ BUILDER_STATE_PARAM,
1549
+ )
1550
+ : null;
1551
+ const relayState =
1552
+ queryStart >= 0
1553
+ ? new URLSearchParams(url.slice(queryStart + 1)).get(
1554
+ BUILDER_RELAY_STATE_PARAM,
1555
+ )
1556
+ : null;
1557
+ if (relayState) {
1558
+ try {
1559
+ if (verifyBuilderPreviewRelayStateForCallback(relayState)) return true;
1560
+ } catch {
1561
+ // Dedicated relay secret missing: let the auth guard fail closed.
1562
+ }
1563
+ }
1564
+ // The signed `_an_state` authenticates this specific Builder callback
1565
+ // flow back to our app. A stale localhost session cookie can otherwise
1566
+ // make the global guard reject the callback before the handler gets to
1567
+ // validate the state and owner. This only bypasses to the callback route;
1568
+ // the callback handler still verifies the signed owner / pending flow.
1569
+ if (verifyBuilderCallbackStateAndGetOwner(state)) return true;
1570
+
1571
+ // The legacy owner cookie is broader and can be stale across shared
1572
+ // browser sessions, so keep it limited to the session-lost popup case.
1573
+ const hasSession = getFrameworkSessionCookieValues(event).length > 0;
1574
+ if (hasSession) return false;
1575
+ return Boolean(
1576
+ verifyBuilderConnectTokenAndGetOwner(
1577
+ getCookie(event, BUILDER_CONNECT_OWNER_COOKIE),
1578
+ ),
1579
+ );
1580
+ }
1581
+
1582
+ return false;
1583
+ }
1584
+
1585
+ const LOGIN_OG_IMAGE_META_RE =
1586
+ /<meta\b(?=[^>]*\bproperty=(["'])og:image\1)[^>]*>/i;
1587
+ const LOGIN_TWITTER_CARD_META_RE =
1588
+ /<meta\b(?=[^>]*\bname=(["'])twitter:card\1)[^>]*>/i;
1589
+ const LOGIN_TWITTER_IMAGE_META_RE =
1590
+ /<meta\b(?=[^>]*\bname=(["'])twitter:image\1)[^>]*>/i;
1591
+
1592
+ function escapeHtmlAttr(value: string): string {
1593
+ return value
1594
+ .replace(/&/g, "&amp;")
1595
+ .replace(/</g, "&lt;")
1596
+ .replace(/>/g, "&gt;")
1597
+ .replace(/"/g, "&quot;");
1598
+ }
1599
+
1600
+ function injectLoginSocialImageMeta(loginHtml: string, event: H3Event): string {
1601
+ const headCloseIdx = loginHtml.indexOf("</head>");
1602
+ if (headCloseIdx === -1) return loginHtml;
1603
+
1604
+ const hasAnySocialImage =
1605
+ LOGIN_OG_IMAGE_META_RE.test(loginHtml) ||
1606
+ LOGIN_TWITTER_IMAGE_META_RE.test(loginHtml);
1607
+ const imageUrl = escapeHtmlAttr(
1608
+ withAgentNativeSocialImageCacheBuster(
1609
+ getAppUrl(event, AGENT_NATIVE_SOCIAL_IMAGE_PATH),
1610
+ ),
1611
+ );
1612
+ const tags: string[] = [];
1613
+
1614
+ if (!hasAnySocialImage) {
1615
+ tags.push(`<meta property="og:image" content="${imageUrl}">`);
1616
+ tags.push(`<meta property="og:image:secure_url" content="${imageUrl}">`);
1617
+ tags.push(
1618
+ `<meta property="og:image:type" content="${AGENT_NATIVE_SOCIAL_IMAGE_TYPE}">`,
1619
+ );
1620
+ tags.push(
1621
+ `<meta property="og:image:width" content="${AGENT_NATIVE_SOCIAL_IMAGE_WIDTH}">`,
1622
+ );
1623
+ tags.push(
1624
+ `<meta property="og:image:height" content="${AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT}">`,
1625
+ );
1626
+ tags.push(
1627
+ `<meta property="og:image:alt" content="${AGENT_NATIVE_SOCIAL_IMAGE_ALT}">`,
1628
+ );
1629
+ }
1630
+ if (!LOGIN_TWITTER_CARD_META_RE.test(loginHtml)) {
1631
+ tags.push(`<meta name="twitter:card" content="summary_large_image">`);
1632
+ }
1633
+ if (!hasAnySocialImage) {
1634
+ tags.push(`<meta name="twitter:image" content="${imageUrl}">`);
1635
+ tags.push(
1636
+ `<meta name="twitter:image:alt" content="${AGENT_NATIVE_SOCIAL_IMAGE_ALT}">`,
1637
+ );
1638
+ }
1639
+
1640
+ if (tags.length === 0) return loginHtml;
1641
+ return (
1642
+ loginHtml.slice(0, headCloseIdx) +
1643
+ tags.join("") +
1644
+ loginHtml.slice(headCloseIdx)
1645
+ );
1646
+ }
1647
+
1648
+ function loginHtmlResponse(loginHtml: string, event: H3Event): Response {
1649
+ return new Response(injectLoginSocialImageMeta(loginHtml, event), {
1650
+ status: 200,
1651
+ headers: {
1652
+ "Content-Type": "text/html; charset=utf-8",
1653
+ // The sign-in document is part of the public server shell. Keep it on the
1654
+ // same long-fresh/long-SWR CDN policy as React Router SSR so hosted
1655
+ // template roots do not invoke origin just to render anonymous login UI.
1656
+ // The login HTML is env-INDEPENDENT (a Google-only app always renders a
1657
+ // working button), so a cached copy is never "wrong" — never downgrade
1658
+ // this to private/no-store.
1659
+ ...DEFAULT_SSR_CACHE_HEADERS,
1660
+ "X-Robots-Tag": "noindex, nofollow",
1661
+ },
1662
+ });
1663
+ }
1664
+
1665
+ function isHtmlDocumentRequest(event: H3Event, pathname: string): boolean {
1666
+ if (!isReadMethod(event)) return false;
1667
+ if (pathname.endsWith(".data")) return false;
1668
+
1669
+ const fetchDest = getHeader(event, "sec-fetch-dest")?.toLowerCase();
1670
+ if (fetchDest === "document" || fetchDest === "iframe") return true;
1671
+
1672
+ const accept = getHeader(event, "accept")?.toLowerCase();
1673
+ return !accept || accept.includes("text/html") || accept.includes("*/*");
1674
+ }
1675
+
1676
+ function createAuthGuardFn(): (
1677
+ event: H3Event,
1678
+ ) => Promise<Response | object | string | void> {
1679
+ return async (event: H3Event) => {
1680
+ const config = _authGuardConfig;
1681
+ if (!config) return;
1682
+ const { publicPaths } = config;
1683
+
1684
+ const url = event.node?.req?.url ?? event.path ?? "/";
1685
+ const queryStart = url.indexOf("?");
1686
+ const rawPath = queryStart >= 0 ? url.slice(0, queryStart) : url;
1687
+ const loginHtml = config.getLoginHtml?.(event, rawPath) ?? config.loginHtml;
1688
+ const p = stripAppBasePath(rawPath);
1689
+ const normalizedUrl = queryStart >= 0 ? `${p}${url.slice(queryStart)}` : p;
1690
+ const callbackRelay = workspaceOAuthCallbackRelayResponse(event);
1691
+ if (callbackRelay) return callbackRelay;
1692
+
1693
+ // Emit CORS headers on every request the guard sees so that even
1694
+ // error responses (401) reach the browser.
1695
+ const cors = applyCorsHeaders(event);
1696
+ // Preflight short-circuit: the browser sends OPTIONS before the real
1697
+ // credentialed request. Must return success without invoking auth.
1698
+ if (getMethod(event) === "OPTIONS") {
1699
+ if (cors.hasOrigin && !cors.allowed) {
1700
+ setResponseStatus(event, 403);
1701
+ return "";
1702
+ }
1703
+ setResponseStatus(event, 204);
1704
+ return "";
1705
+ }
1706
+
1707
+ // Skip auth routes and specific Google OAuth endpoints that must be public
1708
+ // (callback and auth-url). Other Google endpoints like /status require auth.
1709
+ if (
1710
+ p.startsWith("/_agent-native/auth/") ||
1711
+ p === "/_agent-native/google/callback" ||
1712
+ p === "/_agent-native/google/auth-url" ||
1713
+ p === "/_agent-native/google/add-account/callback"
1714
+ ) {
1715
+ return;
1716
+ }
1717
+
1718
+ // The deep-link route resolves the *browser* session itself and serves
1719
+ // the sign-in form inline when unauthenticated (so the post-login reload
1720
+ // returns to the same deep link). It must bypass the guard's blanket
1721
+ // 401-for-/_agent-native/* so an external-agent "Open in … →" link
1722
+ // clicked in any browser/webview lands correctly.
1723
+ if (p === "/_agent-native/open" || p === EMBED_START_PATH) {
1724
+ return;
1725
+ }
1726
+
1727
+ // Integration webhook endpoints verify authenticity via platform-specific
1728
+ // signature verification (Slack HMAC, Telegram token, etc.), not sessions.
1729
+ if (/^\/_agent-native\/integrations\/[^/]+\/webhook$/.test(p)) {
1730
+ return;
1731
+ }
1732
+
1733
+ // Internal processor endpoint for the integration webhook fanout. The
1734
+ // webhook handler enqueues a task to SQL and dispatches a fresh HTTP POST
1735
+ // to this endpoint so the agent loop runs in its own function execution
1736
+ // (cross-platform serverless-safe — see `integrations/webhook-handler.ts`).
1737
+ // Authenticity is verified via an HMAC token signed with A2A_SECRET, plus
1738
+ // an atomic SQL claim that prevents duplicate processing.
1739
+ if (p === "/_agent-native/integrations/process-task") {
1740
+ return;
1741
+ }
1742
+
1743
+ // Internal processor endpoint for deferred A2A continuations created by
1744
+ // integration tasks. It uses the same HMAC internal-token scheme as the
1745
+ // primary integration processor, so it must bypass cookie/session auth.
1746
+ if (p === "/_agent-native/integrations/process-a2a-continuation") {
1747
+ return;
1748
+ }
1749
+
1750
+ // Agent Teams durable sub-agent processor. Self-fired by `spawnTask` to run
1751
+ // a queued sub-agent in a fresh function invocation; authenticity is
1752
+ // verified by the same HMAC internal-token scheme plus an atomic SQL claim,
1753
+ // so it bypasses cookie/session auth (mirrors the integration processor).
1754
+ if (p === "/_agent-native/agent-teams/_process-run") {
1755
+ return;
1756
+ }
1757
+
1758
+ // Durable-background AGENT-CHAT processor. The foreground POST self-dispatches
1759
+ // a long chat turn here (through the Netlify `-background` function, which
1760
+ // rewrites its default url to this path); the route HMAC-verifies the
1761
+ // dispatch (same internal-token scheme as agent-teams above) plus an atomic
1762
+ // SQL claim. The self-dispatch carries ONLY a Bearer HMAC token and NO
1763
+ // session cookie, so without this bypass the blanket 401-for-/_agent-native/*
1764
+ // gate below blocks the worker before `prepareProcessRunRequest` ever runs —
1765
+ // the run is never claimed, its heartbeat never starts, and it times out with
1766
+ // no visible progress. Exact path only (mirrors agent-teams).
1767
+ if (p === "/_agent-native/agent-chat/_process-run") {
1768
+ return;
1769
+ }
1770
+
1771
+ // Durable sandbox-execution processor (run-code background queue). The
1772
+ // enqueueing request self-dispatches here so long compute runs in a fresh
1773
+ // invocation with its own budget; the dispatch carries ONLY a Bearer HMAC
1774
+ // internal token (verified by the route) and NO session cookie, plus an
1775
+ // atomic SQL claim prevents double execution — same scheme as the
1776
+ // agent-teams/_process-run bypass above. Exact path only.
1777
+ if (p === "/_agent-native/sandbox/_process-execution") {
1778
+ return;
1779
+ }
1780
+
1781
+ // Read-only agent chat share links. The random token is the bearer secret;
1782
+ // the route returns a sanitized transcript plus bounded run summaries and
1783
+ // exposes no write surface, live event stream, tool payloads, or owner APIs.
1784
+ if (p.startsWith("/_agent-native/agent-chat/shared/")) {
1785
+ return;
1786
+ }
1787
+
1788
+ // A2A endpoint verifies authenticity via JWT signed with the org's A2A
1789
+ // secret (or the global A2A_SECRET fallback), not via session cookies.
1790
+ if (p === "/_agent-native/a2a") {
1791
+ return;
1792
+ }
1793
+
1794
+ // MCP protocol endpoint. `mountMCP` runs its own `verifyAuth` (Bearer
1795
+ // ACCESS_TOKEN/ACCESS_TOKENS or A2A_SECRET JWT, open in dev) and is the
1796
+ // authoritative gate — exactly like A2A above. Without this bypass the
1797
+ // guard's blanket 401-for-/_agent-native/* below shadows that check, so
1798
+ // an external coding agent (Claude Code / Codex / Cowork) connecting via
1799
+ // the stdio proxy or HTTP can never reach it. Exact protocol endpoint only:
1800
+ // tolerate the common trailing slash, but keep
1801
+ // `/_agent-native/mcp/*` management subroutes on normal session auth.
1802
+ if (
1803
+ isMcpProtocolPath(p) ||
1804
+ p === `${MCP_PUBLIC_ROUTE_PREFIX}/` ||
1805
+ p === `${MCP_LEGACY_ROUTE_PREFIX}/`
1806
+ ) {
1807
+ return;
1808
+ }
1809
+
1810
+ // MCP connect — frictionless external-agent connection. Like /open
1811
+ // above, the connect *page* resolves the browser session itself and
1812
+ // serves its own login form when unauthenticated (so the post-login
1813
+ // reload returns to the same URL, carrying the device user_code in the
1814
+ // query). The two unauthenticated device endpoints below are the CLI's
1815
+ // OAuth-style polling pair: `device/start` (mint a device+user code) and
1816
+ // `device/poll` (exchange an approved code for the token) — both must be
1817
+ // reachable without a browser session because the CLI has none. They are
1818
+ // protected by short-TTL, single-use, crypto-random codes + a creation
1819
+ // rate-limit, not cookies.
1820
+ //
1821
+ // The standard remote-MCP OAuth endpoints also bypass here: metadata and
1822
+ // dynamic client registration are public by design; `/oauth/token` is
1823
+ // protected by single-use auth codes / refresh tokens; and
1824
+ // `/oauth/authorize` resolves the browser session itself so it can serve
1825
+ // the login form at the original authorization URL.
1826
+ //
1827
+ // The legacy Connect endpoints that MINT or MUTATE on behalf of the user
1828
+ // (`/connect/token`, `/device/authorize`, `/tokens`, `/tokens/revoke`) are
1829
+ // intentionally NOT bypassed: they are POSTed by the in-page fetch with a
1830
+ // session cookie and the handler re-checks the session itself.
1831
+ if (
1832
+ p === "/_agent-native/mcp/connect" ||
1833
+ p === "/_agent-native/mcp/connect/device/start" ||
1834
+ p === "/_agent-native/mcp/connect/device/poll" ||
1835
+ p === "/_agent-native/mcp/oauth/authorize" ||
1836
+ p === "/_agent-native/mcp/oauth/token" ||
1837
+ p === "/_agent-native/mcp/oauth/register" ||
1838
+ p === `${MCP_PUBLIC_ROUTE_PREFIX}/connect` ||
1839
+ p === `${MCP_PUBLIC_ROUTE_PREFIX}/connect/device/start` ||
1840
+ p === `${MCP_PUBLIC_ROUTE_PREFIX}/connect/device/poll` ||
1841
+ p === `${MCP_PUBLIC_ROUTE_PREFIX}/oauth/authorize` ||
1842
+ p === `${MCP_PUBLIC_ROUTE_PREFIX}/oauth/token` ||
1843
+ p === `${MCP_PUBLIC_ROUTE_PREFIX}/oauth/register`
1844
+ ) {
1845
+ return;
1846
+ }
1847
+
1848
+ // Cross-app SSO ("Sign in with Agent-Native") — CLIENT side. Both the
1849
+ // `/login` entry point and the `/callback` (hit by a user who is, by
1850
+ // definition, NOT yet signed in to THIS app) must bypass the blanket
1851
+ // 401-for-/_agent-native/*: they resolve / mint the browser session
1852
+ // themselves and verify a signature-bound, single-use, CSRF-stated
1853
+ // hub token — not a cookie. This bypass is GATED on the opt-in env var
1854
+ // so an unset `AGENT_NATIVE_IDENTITY_HUB_URL` is a true no-op (the
1855
+ // guard's behaviour is byte-for-byte unchanged when SSO is off). The
1856
+ // handler itself 404s when disabled as defence in depth.
1857
+ if (
1858
+ isIdentitySsoEnabled() &&
1859
+ (p === "/_agent-native/identity/login" ||
1860
+ p === "/_agent-native/identity/callback")
1861
+ ) {
1862
+ return;
1863
+ }
1864
+
1865
+ // Internal processor endpoint for the A2A async-mode fanout. Mirrors the
1866
+ // integration webhook fanout: when `message/send` is called with
1867
+ // `async: true`, the JSON-RPC handler enqueues to a2a_tasks and self-
1868
+ // fires a POST here so the handler runs in a fresh function execution.
1869
+ // Authenticity is verified via an HMAC token signed with A2A_SECRET
1870
+ // (same scheme as /_agent-native/integrations/process-task).
1871
+ if (p === "/_agent-native/a2a/_process-task") {
1872
+ return;
1873
+ }
1874
+
1875
+ // A2A secret receive endpoint — verifies authenticity via JWT signed
1876
+ // with the calling app's A2A secret, not via session cookies. Used to
1877
+ // sync the org A2A secret across connected apps.
1878
+ if (p === "/_agent-native/org/a2a-secret/receive") {
1879
+ return;
1880
+ }
1881
+
1882
+ // Recap-image upload (POST /_agent-native/recap-image). The PR visual-recap
1883
+ // GitHub Action uploads a PNG here with the SAME `agent-native connect`
1884
+ // bearer token the MCP / action surface accepts — a connect-minted MCP
1885
+ // OAuth access token that `getSession` only honors on the action surface.
1886
+ // The handler re-runs the canonical `verifyAuth` itself (audience-bound to
1887
+ // this app's MCP resource) and 401s unauthenticated callers, so — exactly
1888
+ // like /_agent-native/a2a and the MCP endpoints above — it must bypass the
1889
+ // guard's blanket 401-for-/_agent-native/*. The anonymous read route
1890
+ // (`/recap-image/<token>.png`) is already public via the `.png` static-asset
1891
+ // branch below; this bypass is for the upload path only.
1892
+ if (p === "/_agent-native/recap-image") {
1893
+ return;
1894
+ }
1895
+
1896
+ // Force-sign-in entrypoint. Templates send viewers from public pages
1897
+ // (share links, embeds) here with a `?return=<path>` query. The cached
1898
+ // login document validates that return path in the browser and redirects
1899
+ // there after sign-in or when its client-side session check finds an
1900
+ // existing session.
1901
+ if (p === "/_agent-native/sign-in") {
1902
+ // Preserve the zero-setup localhost experience without putting a
1903
+ // session lookup back on the cacheable app-shell URL. The client gate
1904
+ // reaches this explicit entrypoint after discovering there is no
1905
+ // session; only a fresh local development DB can take this redirect.
1906
+ if (getMethod(event) === "GET") {
1907
+ const queryStr = queryStart >= 0 ? url.slice(queryStart + 1) : "";
1908
+ const safeReturn = safeReturnPath(
1909
+ new URLSearchParams(queryStr).get("return"),
1910
+ );
1911
+ const autoSession = await maybeAutoCreateDevSession(
1912
+ event,
1913
+ safeReturn === "/" ? getAppBasePath() || "/" : safeReturn,
1914
+ );
1915
+ if (autoSession) return autoSession;
1916
+ }
1917
+ return loginHtmlResponse(loginHtml, event);
1918
+ }
1919
+
1920
+ // Auth entry pages are framework-owned pages, not app routes. Always serve
1921
+ // the same public, cacheable login document here. Its client-side session
1922
+ // check redirects signed-in visitors to the validated return path. Keeping
1923
+ // session-dependent decisions out of this server response prevents the CDN
1924
+ // from caching two different representations for the same URL.
1925
+ if (p === "/login" || p === "/signup") {
1926
+ return loginHtmlResponse(loginHtml, event);
1927
+ }
1928
+
1929
+ // Skip static assets (Vite chunks, fonts, images, etc.)
1930
+ if (
1931
+ p.startsWith("/assets/") ||
1932
+ p.startsWith("/_build/") ||
1933
+ p.endsWith(".js") ||
1934
+ p.endsWith(".css") ||
1935
+ p.endsWith(".map") ||
1936
+ p.endsWith(".ico") ||
1937
+ p.endsWith(".png") ||
1938
+ p.endsWith(".svg") ||
1939
+ p.endsWith(".woff2") ||
1940
+ p.endsWith(".woff")
1941
+ ) {
1942
+ return;
1943
+ }
1944
+
1945
+ // React Router's lazy route discovery fetches `/__manifest?p=...` to
1946
+ // resolve manifest patches for `<Link>`s the user might click. The
1947
+ // auth fallback returning loginHtml here makes RR fail to parse the
1948
+ // body as RSC, surfacing as a console error and (when the visitor
1949
+ // already errored elsewhere) blocking the app from rendering. Let it
1950
+ // through — it returns a tiny RSC-encoded manifest of the public
1951
+ // route tree, no per-user data.
1952
+ if (p === "/__manifest") return;
1953
+ if (p === "/_agent-native/speculation-rules.json") return;
1954
+ // Liveness probe: always public so uptime monitors and the keep-warm cron
1955
+ // can reach the DB-warmup route without a session. It exposes no per-user
1956
+ // data (just ok/db/ms) and runs a trivial `SELECT 1`. Without this bypass
1957
+ // the gate below 401s anonymous /_agent-native/* requests before any DB
1958
+ // query, so the database would never get warmed.
1959
+ if (p === "/_agent-native/health") return;
1960
+ if (getMethod(event) === "GET" && p.startsWith("/_agent-native/avatar/")) {
1961
+ return;
1962
+ }
1963
+ if (isPublicPath(normalizedUrl, publicPaths)) return;
1964
+ if (shouldBypassAuthForBuilderConnect(event, p)) return;
1965
+ if (isPublicWorkspacePageRequest(event, p, config)) {
1966
+ return;
1967
+ }
1968
+
1969
+ // Normal app documents and React Router page-data requests are an
1970
+ // impersonal SSR shell. `createH3SSRHandler` renders both under an
1971
+ // explicitly anonymous request context and gives them one shared public
1972
+ // cache policy, so production requests must not vary either response by
1973
+ // cookie. `AppProviders` resolves the browser session and gates private UI
1974
+ // after hydration.
1975
+ const isAppPageRequest =
1976
+ p !== "/api" &&
1977
+ !p.startsWith("/api/") &&
1978
+ p !== "/_agent-native" &&
1979
+ !p.startsWith("/_agent-native/") &&
1980
+ (isHtmlDocumentRequest(event, p) ||
1981
+ (isReadMethod(event) && p.endsWith(".data")));
1982
+ if (isAppPageRequest) {
1983
+ // A freshly scaffolded, loopback-only development app needs its initial
1984
+ // session before Vite starts optimizing and potentially reloading the
1985
+ // client. Waiting for the hydrated client gate to reach sign-in can lose
1986
+ // that one-time redirect after the dev account is created, leaving the
1987
+ // browser at the login page with no way to recreate its random password.
1988
+ // `maybeAutoCreateDevSession` is strictly development + loopback scoped,
1989
+ // does not read an existing session, and returns null for every existing
1990
+ // dev account, so production SSR remains one cacheable anonymous shell
1991
+ // and explicit sign-out still works.
1992
+ if (getMethod(event) === "GET") {
1993
+ const autoSession = await maybeAutoCreateDevSession(event, url);
1994
+ if (autoSession) return autoSession;
1995
+ }
1996
+ return;
1997
+ }
1998
+
1999
+ const session = await getSession(event);
2000
+ if (session) return;
2001
+
2002
+ if (p.startsWith("/api/") || p.startsWith("/_agent-native/")) {
2003
+ setResponseStatus(event, 401);
2004
+ return { error: "Unauthorized" };
2005
+ }
2006
+
2007
+ setResponseStatus(event, 401);
2008
+ return { error: "Unauthorized" };
2009
+ };
2010
+ }
2011
+
2012
+ // `.test` is an RFC 6761 reserved TLD that never resolves, so this stays a
2013
+ // safe local-only address while still passing better-auth's `z.email()`
2014
+ // validator (a bare `dev@local` has no TLD and is rejected as INVALID_EMAIL,
2015
+ // which silently broke the zero-setup auto-sign-in on every fresh dev DB).
2016
+ const AUTO_DEV_ACCOUNT_EMAIL = "dev@local.test";
2017
+ // No fixed password: maybeAutoCreateDevSession mints a random one per DB and
2018
+ // never emits it to logs.
2019
+
2020
+ // Pre-fix local dev DBs may already contain a `dev@local` user. Treat that
2021
+ // legacy address as the dev account too, so the "any real users?" check
2022
+ // below doesn't mistake the old auto-account for a real signup (which would
2023
+ // permanently disable auto-create) and the post-logout guard still fires.
2024
+ const LEGACY_AUTO_DEV_ACCOUNT_EMAIL = "dev@local";
2025
+
2026
+ let authDisabledWarningLogged = false;
2027
+
2028
+ function isAuthDisabled(): boolean {
2029
+ const value = process.env.AUTH_DISABLED?.trim().toLowerCase();
2030
+ return value === "1" || value === "true";
2031
+ }
2032
+
2033
+ function getAuthDisabledSession(): AuthSession | null {
2034
+ if (!isAuthDisabled()) return null;
2035
+ if (!authDisabledWarningLogged) {
2036
+ authDisabledWarningLogged = true;
2037
+ console.warn(
2038
+ `[agent-native] AUTH_DISABLED — login/signup disabled; all requests run as ${AUTO_DEV_ACCOUNT_EMAIL}`,
2039
+ );
2040
+ }
2041
+ return { email: AUTO_DEV_ACCOUNT_EMAIL };
2042
+ }
2043
+
2044
+ async function hasAutoDevAccountUser(
2045
+ db: ReturnType<typeof getDbExec>,
2046
+ ): Promise<boolean> {
2047
+ const { rows } = await db.execute({
2048
+ sql: 'SELECT 1 FROM "user" WHERE email IN (?, ?) LIMIT 1',
2049
+ args: [AUTO_DEV_ACCOUNT_EMAIL, LEGACY_AUTO_DEV_ACCOUNT_EMAIL],
2050
+ });
2051
+ return rows.length > 0;
2052
+ }
2053
+
2054
+ type AutoDevAccountCreationResult = { password: string } | null;
2055
+
2056
+ const autoDevAccountCreationPromises = new Map<
2057
+ string,
2058
+ Promise<AutoDevAccountCreationResult>
2059
+ >();
2060
+
2061
+ function getAutoDevAccountCreationKey(): string {
2062
+ return `${process.cwd()}:${process.env.APP_BASE_PATH ?? ""}`;
2063
+ }
2064
+
2065
+ async function createAutoDevAccountForSession(
2066
+ auth: NonNullable<Awaited<ReturnType<typeof getBetterAuth>>>,
2067
+ db: ReturnType<typeof getDbExec>,
2068
+ ): Promise<string | null> {
2069
+ const key = getAutoDevAccountCreationKey();
2070
+ let creationPromise = autoDevAccountCreationPromises.get(key);
2071
+
2072
+ if (!creationPromise) {
2073
+ const devPassword = crypto.randomBytes(18).toString("base64url");
2074
+
2075
+ creationPromise = (async () => {
2076
+ try {
2077
+ await auth.api.signUpEmail({
2078
+ body: {
2079
+ email: AUTO_DEV_ACCOUNT_EMAIL,
2080
+ password: devPassword,
2081
+ name: "Dev",
2082
+ },
2083
+ });
2084
+ } catch (e) {
2085
+ // Another process can still win the create race after our SELECT.
2086
+ // In-process first-page races share this promise and do not issue a
2087
+ // duplicate Better Auth signup, which keeps local SQLite logs quiet.
2088
+ if (await hasAutoDevAccountUser(db)) return null;
2089
+ if (!isExpectedAuthFailure(e)) throw e;
2090
+ return null;
2091
+ }
2092
+
2093
+ // Confirm the convenience path without emitting the generated email or
2094
+ // password. Terminal output is frequently captured and shared in bug
2095
+ // reports, CI artifacts, and remote development sessions.
2096
+ console.log(
2097
+ "[agent-native] Local dev auto-login ready. " +
2098
+ "Set AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT=1 to disable.",
2099
+ );
2100
+
2101
+ return { password: devPassword };
2102
+ })();
2103
+
2104
+ autoDevAccountCreationPromises.set(key, creationPromise);
2105
+ creationPromise
2106
+ .finally(() => {
2107
+ if (autoDevAccountCreationPromises.get(key) === creationPromise) {
2108
+ autoDevAccountCreationPromises.delete(key);
2109
+ }
2110
+ })
2111
+ .catch(() => {});
2112
+ }
2113
+
2114
+ const result = await creationPromise;
2115
+ return result?.password ?? null;
2116
+ }
2117
+
2118
+ /**
2119
+ * Local-dev convenience: skip the sign-up wall on first run.
2120
+ *
2121
+ * When NODE_ENV=development AND the `user` table has no rows for any
2122
+ * email other than the dev account (`dev@local.test`, or the legacy
2123
+ * `dev@local` on pre-fix DBs), transparently sign up (or sign back in
2124
+ * to) the auto-managed dev account and return a 302 to the original URL
2125
+ * with a session cookie set. A developer who just ran `pnpm dev` lands
2126
+ * in the app immediately instead of being asked to fill in name + email
2127
+ * + password to try the framework.
2128
+ *
2129
+ * Auto-create fires exactly once per local DB: as soon as the dev
2130
+ * account (or any real user) exists in the `user` table, the helper
2131
+ * returns null and the normal login flow takes over. Signing out then
2132
+ * leaves the user on the regular sign-in form; without this guard the
2133
+ * post-logout reload would silently re-create the session.
2134
+ *
2135
+ * Hardening (this is a convenience, not an auth bypass — it uses the
2136
+ * real Better Auth sign-up/sign-in, but a known-credential local account
2137
+ * is still worth not shipping):
2138
+ * - **Loopback only.** Gated on `isLoopbackRequest`, so a tunnelled /
2139
+ * reverse-proxied / misconfigured-non-prod dev server never auto-signs
2140
+ * in a directly-remote visitor (mirrors the desktop SSO broker).
2141
+ * - **Random per-DB password.** The account password is freshly
2142
+ * generated on creation and never logged — there is no
2143
+ * source-code-known credential. After logout the auto-flow won't refire
2144
+ * (dev row exists); use normal signup or reset the local DB to start over.
2145
+ * - **NODE_ENV.** Still gated on development/test.
2146
+ *
2147
+ * Set `AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT=1` to opt out entirely
2148
+ * (useful for tests that exercise the unauthenticated branch).
2149
+ */
2150
+ async function maybeAutoCreateDevSession(
2151
+ event: H3Event,
2152
+ redirectTo: string,
2153
+ ): Promise<Response | null> {
2154
+ if (!isDevEnvironment()) return null;
2155
+ if (process.env.AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT === "1") return null;
2156
+ // Local machine only: never auto-sign-in a remote visitor, even if a
2157
+ // dev server is exposed (tunnel, reverse proxy, misconfigured NODE_ENV).
2158
+ if (!isLoopbackRequest(event)) return null;
2159
+
2160
+ try {
2161
+ const db = getDbExec();
2162
+ // Exclude BOTH the current and the legacy dev-account email so a
2163
+ // pre-fix local DB that still holds a `dev@local` row isn't treated
2164
+ // as having a "real user" (which would permanently disable
2165
+ // auto-create on that DB).
2166
+ const { rows: realUsers } = await db.execute({
2167
+ sql: 'SELECT 1 FROM "user" WHERE email NOT IN (?, ?) LIMIT 1',
2168
+ args: [AUTO_DEV_ACCOUNT_EMAIL, LEGACY_AUTO_DEV_ACCOUNT_EMAIL],
2169
+ });
2170
+ if (realUsers.length > 0) return null;
2171
+
2172
+ // If the dev account already exists, this is not a freshly-scaffolded
2173
+ // app — the user has been through the auto-create flow at least
2174
+ // once. Skip auto-create so signing out actually works: without
2175
+ // this guard, the post-logout reload immediately re-creates the
2176
+ // session and the user is stuck in the dev account forever (or has
2177
+ // to set AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT=1). To get the demo
2178
+ // experience back, drop the row or wipe the local DB. The legacy
2179
+ // `dev@local` address is matched too so pre-fix DBs still suppress
2180
+ // re-create after logout.
2181
+ if (await hasAutoDevAccountUser(db)) return null;
2182
+
2183
+ const auth = await getBetterAuth();
2184
+ if (!auth) return null;
2185
+
2186
+ // The dev account does not exist at this point (the devUsers check
2187
+ // above returned early otherwise). Concurrent in-process first page
2188
+ // loads share one signup promise so the losing request never asks Better
2189
+ // Auth to insert the same email and therefore never emits a SQLite
2190
+ // unique-constraint log.
2191
+ const devPassword = await createAutoDevAccountForSession(auth, db);
2192
+ if (!devPassword) return null;
2193
+
2194
+ const result = await auth.api.signInEmail({
2195
+ body: {
2196
+ email: AUTO_DEV_ACCOUNT_EMAIL,
2197
+ password: devPassword,
2198
+ },
2199
+ });
2200
+ if (!result?.token) return null;
2201
+
2202
+ setFrameworkSessionCookie(event, result.token);
2203
+ await addSession(result.token, AUTO_DEV_ACCOUNT_EMAIL);
2204
+
2205
+ // Emit the session cookie ON the 302 itself. Returning a bare
2206
+ // `new Response(...)` here drops the cookie staged on event.node.res
2207
+ // (see redirectWithStagedCookies), so the developer would 302 to the
2208
+ // app and immediately bounce back to the login form.
2209
+ return redirectWithStagedCookies(event, redirectTo);
2210
+ } catch (e) {
2211
+ // Local-dev only — log to console for debugging, but don't surface
2212
+ // through Sentry. Falling back to the regular login form is the
2213
+ // correct user-facing behavior when this path fails.
2214
+ console.warn("[agent-native] auto dev account skipped:", e);
2215
+ return null;
2216
+ }
2217
+ }
2218
+
2219
+ /**
2220
+ * Map a Better Auth session to our AuthSession type.
2221
+ */
2222
+ function mapBetterAuthSession(baSession: {
2223
+ user: { id: string; email: string; name?: string; image?: string | null };
2224
+ session: { token: string };
2225
+ }): AuthSession {
2226
+ return {
2227
+ email: baSession.user.email,
2228
+ userId: baSession.user.id,
2229
+ name: baSession.user.name,
2230
+ ...(baSession.user.image ? { image: baSession.user.image } : {}),
2231
+ token: baSession.session?.token,
2232
+ };
2233
+ }
2234
+
2235
+ /**
2236
+ * Backfill `orgId` onto a resolved session using the canonical
2237
+ * `resolveOrgIdForEmail` (org_members + active-org-id user setting), so
2238
+ * every consumer of `session.orgId` agrees with `getOrgContext` on which
2239
+ * org is active.
2240
+ *
2241
+ */
2242
+ async function backfillSessionOrg(
2243
+ session: AuthSession,
2244
+ event: H3Event,
2245
+ ): Promise<AuthSession> {
2246
+ if (session.orgId) return session;
2247
+ // Event-aware variant: shares the per-request org_members lookup with
2248
+ // getOrgContext so one request never pays the membership query twice.
2249
+ const { resolveOrgIdForEmailViaEvent } = await import("../org/context.js");
2250
+ const orgId = await resolveOrgIdForEmailViaEvent(event, session.email).catch(
2251
+ () => null,
2252
+ );
2253
+ return orgId ? { ...session, orgId } : session;
2254
+ }
2255
+
2256
+ /**
2257
+ * Get the current auth session for a request.
2258
+ *
2259
+ * Resolution chain:
2260
+ * 1. ACCESS_TOKEN → check legacy cookie-based token sessions
2261
+ * 2. Embed session → short-lived token minted by /_agent-native/embed/start
2262
+ * 3. BYOA custom getSession → delegate to template callback
2263
+ * 4. Bearer legacy session → check Authorization: Bearer against sessions
2264
+ * 5. Better Auth → check session via Better Auth API (cookie or Bearer)
2265
+ * 6. Legacy cookie → check an_session cookie in legacy sessions table
2266
+ * 7. Desktop SSO broker (Electron loopback only)
2267
+ * 8. Mobile _session query param → promote to cookie
2268
+ *
2269
+ * Returns `null` for unauthenticated requests. There is no dev-mode bypass:
2270
+ * local development uses the same Better Auth signup flow as production. The
2271
+ * onboarding/sign-in page is served by `runAuthGuard` for any unauthenticated
2272
+ * page load.
2273
+ */
2274
+ export async function getSession(event: H3Event): Promise<AuthSession | null> {
2275
+ // Per-request memoization. The wider codebase calls `getSession` many
2276
+ // times per request (auth guard, action wrapper, route handler, plus the
2277
+ // org-backfill query inside `backfillSessionOrg`). Cache the resolved
2278
+ // session on `event.context` so the chain runs once per request.
2279
+ const ctx = event.context as {
2280
+ __anSessionCache?: Promise<AuthSession | null>;
2281
+ };
2282
+ return (ctx.__anSessionCache ??= (async () => {
2283
+ const session = await resolveSessionUncached(event);
2284
+ return session?.email ? backfillSessionOrg(session, event) : session;
2285
+ })());
2286
+ }
2287
+
2288
+ async function resolveSessionUncached(
2289
+ event: H3Event,
2290
+ ): Promise<AuthSession | null> {
2291
+ // 1. MCP App embed session. This is a short-lived browser session minted
2292
+ // from a one-time ticket that was scoped to the authenticated MCP caller.
2293
+ // It lets an inline MCP App iframe load the real app without reusing the
2294
+ // MCP bearer token as a browser cookie. Resolve it FIRST: the token is
2295
+ // HMAC-verified and carries its own identity + org scope, and is the most
2296
+ // specific intent for an embed request. Checking it before the legacy
2297
+ // an_session cookie prevents a stale cookie (common when an ACCESS_TOKEN is
2298
+ // configured) from shadowing the embed identity.
2299
+ const embedSession = await resolveEmbedSessionFromRequest(event);
2300
+ if (embedSession) {
2301
+ return {
2302
+ email: embedSession.email,
2303
+ token: embedSession.token,
2304
+ ...(embedSession.orgId ? { orgId: embedSession.orgId } : {}),
2305
+ };
2306
+ }
2307
+
2308
+ // 2. ACCESS_TOKEN check (programmatic/agent access)
2309
+ const accessTokens = getAccessTokens();
2310
+ if (accessTokens.length > 0) {
2311
+ const cookieSession = await getLegacyCookieSession(event);
2312
+ if (cookieSession) return cookieSession;
2313
+ }
2314
+
2315
+ // 3. BYOA custom getSession
2316
+ if (customGetSession) {
2317
+ const session = await customGetSession(event);
2318
+ if (session) return session;
2319
+
2320
+ const bearerSession = await getBearerSession(event);
2321
+ if (bearerSession) return bearerSession;
2322
+
2323
+ // Desktop SSO broker: even with BYOA auth, fall back to the broker
2324
+ // for Electron requests so cross-template SSO works for custom-auth
2325
+ // templates too. Gated on `readDesktopSsoSafely` so a non-loopback
2326
+ // request that spoofs `User-Agent: ... Electron/...` cannot read the
2327
+ // home-dir broker file (and so production builds never consult it).
2328
+ const sso = await readDesktopSsoSafely(event);
2329
+ if (sso?.email) return { email: sso.email, token: sso.token };
2330
+ // Fall through to mobile _session check
2331
+ } else {
2332
+ // 4. Bearer session. Desktop/native clients can persist a legacy session
2333
+ // token outside the WebView cookie jar and attach it to all app requests.
2334
+ // `agent-native connect` clients may present a connect-minted MCP OAuth
2335
+ // token, but only the framework action route accepts that fallback.
2336
+ const bearerSession = await getBearerSession(event);
2337
+ if (bearerSession) return bearerSession;
2338
+
2339
+ // 5. Better Auth session (cookie or Bearer token)
2340
+ try {
2341
+ const ba = getBetterAuthSync();
2342
+ if (ba) {
2343
+ const baSession = await ba.api.getSession({
2344
+ headers: event.headers,
2345
+ });
2346
+ if (baSession?.user?.email) {
2347
+ return mapBetterAuthSession(baSession);
2348
+ }
2349
+ }
2350
+ } catch (e) {
2351
+ console.error("[auth] ba.api.getSession error:", e);
2352
+ }
2353
+
2354
+ // 6. Legacy cookie fallback (for sessions created before migration)
2355
+ const cookieSession = await getLegacyCookieSession(event);
2356
+ if (cookieSession) return cookieSession;
2357
+
2358
+ // 7. Desktop SSO broker fallback.
2359
+ // Each template in the Electron desktop app has its own database, so
2360
+ // a session token created by one template doesn't resolve in another.
2361
+ // When an Electron request has no resolvable session, trust the
2362
+ // home-dir SSO record written by whichever template the user signed
2363
+ // into. Gated on `readDesktopSsoSafely`: requires Electron User-Agent,
2364
+ // a loopback (127.0.0.1 / ::1) source IP, and a non-production NODE_ENV
2365
+ // — anything else is rejected so a hostile network request cannot
2366
+ // impersonate whichever email last signed into the desktop app.
2367
+ const sso = await readDesktopSsoSafely(event);
2368
+ if (sso?.email) {
2369
+ return { email: sso.email, token: sso.token };
2370
+ }
2371
+ }
2372
+
2373
+ // 8. Mobile WebView bridge — _session query param
2374
+ const querySession = await promoteQuerySession(event);
2375
+ if (querySession) return querySession;
2376
+
2377
+ // 9. AUTH_DISABLED fallback — only when no session resolved above.
2378
+ // Must run after BYOA customGetSession so infrastructure/custom auth keeps
2379
+ // caller identity instead of collapsing to the shared preview user.
2380
+ const authDisabledSession = getAuthDisabledSession();
2381
+ if (authDisabledSession) return authDisabledSession;
2382
+
2383
+ return null;
2384
+ }
2385
+
2386
+ async function promoteQuerySession(
2387
+ event: H3Event,
2388
+ ): Promise<AuthSession | null> {
2389
+ const qToken = getQuery(event)?._session as string | undefined;
2390
+ if (!qToken) return null;
2391
+ const email = await getSessionEmail(qToken);
2392
+ if (!email) return null;
2393
+ setFrameworkSessionCookie(event, qToken);
2394
+ setResponseHeader(event, "Referrer-Policy", "no-referrer");
2395
+ return { email, token: qToken };
2396
+ }
2397
+
2398
+ function isReadMethod(event: H3Event): boolean {
2399
+ const method = getMethod(event);
2400
+ return method === "GET" || method === "HEAD";
2401
+ }
2402
+
2403
+ /**
2404
+ * Cookie attributes that work in both same-site and third-party iframe
2405
+ * contexts. Over HTTPS we emit `SameSite=None; Secure; Partitioned` —
2406
+ * `None`+`Secure` is required by browsers to ship the cookie back inside a
2407
+ * cross-origin iframe at all; `Partitioned` keeps the cookie working under
2408
+ * Chrome's third-party-cookie deprecation by binding it to the embedding
2409
+ * site's storage partition. (Better Auth already sets the same trio on its
2410
+ * own session cookie; this matches so the framework's legacy cookie —
2411
+ * which the Builder OAuth popup exchange writes via
2412
+ * `setFrameworkSessionCookie` — survives iframe contexts too.) Plain-HTTP
2413
+ * dev keeps the default `SameSite=Lax`; `None` requires Secure, and
2414
+ * `Partitioned` only takes effect alongside `Secure`.
2415
+ */
2416
+ function crossSiteCookieAttrs(event: H3Event): {
2417
+ sameSite: "lax" | "none";
2418
+ secure: boolean;
2419
+ partitioned?: boolean;
2420
+ } {
2421
+ return isHttpsRequest(event)
2422
+ ? { sameSite: "none", secure: true, partitioned: true }
2423
+ : { sameSite: "lax", secure: false };
2424
+ }
2425
+
2426
+ export function setFrameworkSessionCookie(event: H3Event, token: string): void {
2427
+ clearFrameworkSessionCookies(event);
2428
+ setCookie(event, COOKIE_NAME, token, {
2429
+ httpOnly: true,
2430
+ ...crossSiteCookieAttrs(event),
2431
+ ...cookieDomainAttrs(),
2432
+ path: "/",
2433
+ maxAge: sessionMaxAge,
2434
+ });
2435
+ }
2436
+
2437
+ /**
2438
+ * Build a redirect `Response` that carries whatever `Set-Cookie` headers were
2439
+ * just staged on the event (e.g. by `setFrameworkSessionCookie`).
2440
+ *
2441
+ * h3 v2's `setCookie` appends the cookie onto `event.res.headers`. When a
2442
+ * handler returns a plain object/string, h3's `prepareResponse` merges those
2443
+ * staged headers into the synthesized response, so the cookie survives. But
2444
+ * when a handler returns a web `Response`, `prepareResponse` only merges the
2445
+ * staged headers if the Response is 2xx — its `!val.ok` early-return hands a
2446
+ * non-2xx Response (like a 302) straight back WITHOUT merging. A bare
2447
+ * `new Response("", { status: 302, headers: { Location } })` therefore 302s
2448
+ * the browser with no session cookie, so the zero-setup dev auto-sign-in
2449
+ * bounces straight back to the login form.
2450
+ *
2451
+ * Mirroring the staged cookies onto the redirect Response's own headers makes
2452
+ * them part of the Response that's returned as-is, so the 302 actually
2453
+ * carries the session cookie. (`event.res.headers` is also left intact for
2454
+ * any non-Response continuation path; h3 only skips the merge for the
2455
+ * Response branch, so there's no double-emit.)
2456
+ */
2457
+ function redirectWithStagedCookies(
2458
+ event: H3Event,
2459
+ location: string,
2460
+ status = 302,
2461
+ ): Response {
2462
+ const headers = new Headers({ Location: location });
2463
+ const staged = event.res?.headers?.getSetCookie?.() ?? [];
2464
+ for (const cookie of staged) headers.append("set-cookie", cookie);
2465
+ return new Response("", { status, headers });
2466
+ }
2467
+
2468
+ function isHttpsRequest(event: H3Event): boolean {
2469
+ try {
2470
+ const xfProto = getHeader(event, "x-forwarded-proto");
2471
+ if (xfProto && String(xfProto).split(",")[0].trim() === "https") {
2472
+ return true;
2473
+ }
2474
+ const req: any = (event as any).req ?? event.node?.req;
2475
+ const url: string | undefined = req?.url;
2476
+ if (typeof url === "string" && url.startsWith("https://")) return true;
2477
+ const appUrl = process.env.APP_URL || process.env.BETTER_AUTH_URL || "";
2478
+ if (appUrl.startsWith("https://")) return true;
2479
+ } catch {
2480
+ // ignore
2481
+ }
2482
+ return false;
2483
+ }
2484
+
2485
+ // ---------------------------------------------------------------------------
2486
+ // Public path matching
2487
+ // ---------------------------------------------------------------------------
2488
+
2489
+ function isPublicPath(url: string, publicPaths: string[]): boolean {
2490
+ const p = url.split("?")[0];
2491
+ return matchesPathList(p, publicPaths);
2492
+ }
2493
+
2494
+ function matchesPathList(path: string, paths: string[]): boolean {
2495
+ return paths.some((candidate) => {
2496
+ const normalized =
2497
+ candidate.length > 1 && candidate.endsWith("/")
2498
+ ? candidate.slice(0, -1)
2499
+ : candidate;
2500
+ return path === normalized || path.startsWith(normalized + "/");
2501
+ });
2502
+ }
2503
+
2504
+ function isPublicWorkspacePageRequest(
2505
+ event: H3Event,
2506
+ path: string,
2507
+ config: AuthGuardConfig,
2508
+ ): boolean {
2509
+ if (!isReadMethod(event)) return false;
2510
+ if (
2511
+ path === "/_agent-native" ||
2512
+ path.startsWith("/_agent-native/") ||
2513
+ path === "/api" ||
2514
+ path.startsWith("/api/") ||
2515
+ path === "/.well-known" ||
2516
+ path.startsWith("/.well-known/")
2517
+ ) {
2518
+ return false;
2519
+ }
2520
+ if (matchesPathList(path, config.workspaceAppProtectedPaths)) return false;
2521
+ if (matchesPathList(path, config.workspaceAppPublicPaths)) return true;
2522
+ return config.workspaceAppAudience === "public";
2523
+ }
2524
+
2525
+ function stripAppBasePath(pathname: string): string {
2526
+ const basePath = getAppBasePath();
2527
+ if (!basePath) return pathname;
2528
+ if (pathname === basePath) return "/";
2529
+ if (pathname.startsWith(`${basePath}/`)) {
2530
+ return pathname.slice(basePath.length) || "/";
2531
+ }
2532
+ return pathname;
2533
+ }
2534
+
2535
+ // ---------------------------------------------------------------------------
2536
+ // Fallback login page HTML (custom auth with no login page configured)
2537
+ // ---------------------------------------------------------------------------
2538
+
2539
+ function getCustomAuthRequiredHtml(): string {
2540
+ return `<!DOCTYPE html>
2541
+ <html lang="en">
2542
+ <head>
2543
+ <meta charset="UTF-8">
2544
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
2545
+ <title>Authentication required</title>
2546
+ <style>
2547
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
2548
+ :root {
2549
+ color-scheme: dark;
2550
+ --bg: #09090b;
2551
+ --panel: #141417;
2552
+ --panel-soft: #1b1b20;
2553
+ --border: rgba(255,255,255,0.1);
2554
+ --border-strong: rgba(255,255,255,0.18);
2555
+ --text: #f4f4f5;
2556
+ --muted: #a1a1aa;
2557
+ --subtle: #71717a;
2558
+ }
2559
+ body {
2560
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2561
+ background: linear-gradient(180deg, #111114 0%, var(--bg) 58%);
2562
+ color: var(--text);
2563
+ display: flex;
2564
+ align-items: center;
2565
+ justify-content: center;
2566
+ min-height: 100vh;
2567
+ padding: 1rem;
2568
+ }
2569
+ .card {
2570
+ width: 100%;
2571
+ max-width: 420px;
2572
+ padding: 2rem;
2573
+ background: color-mix(in srgb, var(--panel) 94%, transparent);
2574
+ border: 1px solid var(--border);
2575
+ border-radius: 12px;
2576
+ box-shadow: 0 24px 80px rgba(0,0,0,0.35);
2577
+ }
2578
+ .eyebrow {
2579
+ display: inline-flex;
2580
+ align-items: center;
2581
+ min-height: 1.5rem;
2582
+ padding: 0 0.625rem;
2583
+ margin-bottom: 1rem;
2584
+ border: 1px solid var(--border);
2585
+ border-radius: 999px;
2586
+ color: var(--muted);
2587
+ background: rgba(255,255,255,0.04);
2588
+ font-size: 0.75rem;
2589
+ font-weight: 500;
2590
+ }
2591
+ h1 {
2592
+ font-size: 1.375rem;
2593
+ line-height: 1.2;
2594
+ font-weight: 650;
2595
+ margin-bottom: 0.5rem;
2596
+ color: var(--text);
2597
+ letter-spacing: 0;
2598
+ }
2599
+ .intro {
2600
+ margin-bottom: 1.5rem;
2601
+ color: var(--muted);
2602
+ font-size: 0.9375rem;
2603
+ line-height: 1.55;
2604
+ }
2605
+ .hint {
2606
+ margin-top: 1rem;
2607
+ color: var(--subtle);
2608
+ font-size: 0.8125rem;
2609
+ line-height: 1.45;
2610
+ }
2611
+ @media (max-width: 480px) {
2612
+ .card { padding: 1.5rem; }
2613
+ h1 { font-size: 1.25rem; }
2614
+ }
2615
+ </style>
2616
+ </head>
2617
+ <body>
2618
+ <div class="card">
2619
+ <div class="eyebrow">Authentication required</div>
2620
+ <h1>Sign in is not configured</h1>
2621
+ <p class="intro">This route requires an authenticated session, but this app's custom auth plugin did not provide a sign-in page.</p>
2622
+ <p class="hint">If this route should be public, add it to the auth plugin's public route configuration. Otherwise configure a custom sign-in page for this app.</p>
2623
+ </div>
2624
+ </body>
2625
+ </html>`;
2626
+ }
2627
+
2628
+ // ---------------------------------------------------------------------------
2629
+ // mountBetterAuthRoutes — Better Auth powered auth with backward-compat routes
2630
+ // ---------------------------------------------------------------------------
2631
+
2632
+ async function mountBetterAuthRoutes(
2633
+ app: H3App,
2634
+ options: AuthOptions,
2635
+ ): Promise<void> {
2636
+ const publicPaths = [...(options.publicPaths ?? [])];
2637
+ const workspaceAppAudience = resolveWorkspaceAppAudience(options);
2638
+ const workspaceAppRouteAccess = resolveWorkspaceAppRouteAccess(options);
2639
+
2640
+ // The A2A agent card is part of an open protocol — other agents must be
2641
+ // able to discover it without auth. Same for favicons and similar probes.
2642
+ for (const pp of ["/.well-known", "/favicon.ico", "/favicon.png"]) {
2643
+ if (!publicPaths.includes(pp)) publicPaths.push(pp);
2644
+ }
2645
+
2646
+ // Auto-add Google OAuth routes when credentials are configured. Templates
2647
+ // that need broader product scopes (mail/calendar) opt out and provide
2648
+ // their own Nitro routes at these paths.
2649
+ const googleSignInCredentials = resolveGoogleSignInCredentials();
2650
+ if (googleSignInCredentials && options.mountGoogleOAuthRoutes !== false) {
2651
+ setGenericGoogleOAuthRoutesEnabled(app, true);
2652
+ for (const gp of [
2653
+ "/_agent-native/google/callback",
2654
+ "/_agent-native/google/auth-url",
2655
+ ]) {
2656
+ if (!publicPaths.includes(gp)) publicPaths.push(gp);
2657
+ }
2658
+
2659
+ const googleScopes = [
2660
+ "openid",
2661
+ "https://www.googleapis.com/auth/userinfo.email",
2662
+ "https://www.googleapis.com/auth/userinfo.profile",
2663
+ ].join(" ");
2664
+
2665
+ app.use(
2666
+ "/_agent-native/google/auth-url",
2667
+ defineEventHandler((event) => {
2668
+ if (!areGenericGoogleOAuthRoutesEnabled(app)) return undefined;
2669
+ if (getMethod(event) !== "GET") {
2670
+ setResponseStatus(event, 405);
2671
+ return { error: "Method not allowed" };
2672
+ }
2673
+ // Validate the user-supplied `redirect_uri` against the framework's
2674
+ // server-side allowlist (must be same-origin and under
2675
+ // `/_agent-native/...`). Reject anything else so an attacker can't
2676
+ // smuggle a different already-registered redirect URI past Google's
2677
+ // host-prefix matching. See HIGH-1 in 09-oauth-session.md.
2678
+ const redirectUri = resolveOAuthRedirectUri(event);
2679
+ if (redirectUri === null) {
2680
+ setResponseStatus(event, 400);
2681
+ return { error: "Invalid redirect_uri" };
2682
+ }
2683
+ const q = getQuery(event);
2684
+ const desktop =
2685
+ isElectronRequest(event) || q.desktop === "1" || q.desktop === "true";
2686
+ const flowId = desktop ? (q.flow_id as string) || undefined : undefined;
2687
+ // Validate the caller's return param up front and only embed it
2688
+ // into the OAuth state when it normalises to a non-root path —
2689
+ // skip embedding "/" (the default fallback) so the state stays
2690
+ // small for the common case.
2691
+ const returnQuery = q.return;
2692
+ const validated =
2693
+ typeof returnQuery === "string"
2694
+ ? safeOAuthReturnUrl(returnQuery, {
2695
+ allowDefaultLoopback: isBuilderOAuthRequest(event),
2696
+ allowedOrigins: [builderPreviewReturnOrigin(event)],
2697
+ })
2698
+ : "/";
2699
+ const returnUrl = validated !== "/" ? validated : undefined;
2700
+ const signupAttribution = signupAttributionFromCookieHeader(
2701
+ getHeader(event, "cookie") ?? null,
2702
+ );
2703
+ const state = encodeOAuthState({
2704
+ redirectUri,
2705
+ desktop,
2706
+ addAccount: false,
2707
+ app: getOAuthStateAppId(),
2708
+ returnUrl,
2709
+ flowId,
2710
+ signupAttribution,
2711
+ });
2712
+ logGoogleOAuthDebug(event, "auth-url", {
2713
+ flowId,
2714
+ desktop,
2715
+ redirectPath: oauthDebugUrlPath(redirectUri),
2716
+ returnUrl,
2717
+ redirect: q.redirect === "1",
2718
+ workspace:
2719
+ process.env.AGENT_NATIVE_WORKSPACE === "1" ||
2720
+ process.env.VITE_AGENT_NATIVE_WORKSPACE === "1",
2721
+ });
2722
+ const params = new URLSearchParams({
2723
+ client_id: googleSignInCredentials.clientId,
2724
+ redirect_uri: redirectUri,
2725
+ response_type: "code",
2726
+ scope: googleScopes,
2727
+ access_type: "online",
2728
+ prompt: "select_account",
2729
+ state,
2730
+ });
2731
+ const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
2732
+ if (q.redirect === "1") {
2733
+ // Return a native web Response — NOT h3 v2's `sendRedirect`. Under
2734
+ // h3 `2.0.1-rc.20`, `sendRedirect = (_, loc, code) => redirect(...)`
2735
+ // ignores the event and returns a non-standard `HTTPResponse` class
2736
+ // instance; the framework request-handler shim doesn't unwrap it and
2737
+ // String()-coerces it to the literal text "[object Object]" with a
2738
+ // 200 status (no Location header), which broke the popup-based
2739
+ // Google sign-in in production. Web `Response` is the proven idiom
2740
+ // here — `oauthCallbackResponse`/`oauthErrorPage` use it and work.
2741
+ return new Response(null, {
2742
+ status: 302,
2743
+ headers: { Location: authUrl },
2744
+ });
2745
+ }
2746
+ return { url: authUrl };
2747
+ }),
2748
+ );
2749
+
2750
+ app.use(
2751
+ "/_agent-native/google/callback",
2752
+ defineEventHandler(async (event) => {
2753
+ if (!areGenericGoogleOAuthRoutesEnabled(app)) return undefined;
2754
+ if (getMethod(event) !== "GET") {
2755
+ setResponseStatus(event, 405);
2756
+ return { error: "Method not allowed" };
2757
+ }
2758
+ const callbackRelay = workspaceOAuthCallbackRelayResponse(event);
2759
+ if (callbackRelay) return callbackRelay;
2760
+ let callbackFlowId: string | undefined;
2761
+ let callbackDesktop = false;
2762
+ try {
2763
+ const query = getQuery(event);
2764
+ const code = query.code as string;
2765
+ const { redirectUri, desktop, returnUrl, flowId, signupAttribution } =
2766
+ decodeOAuthState(
2767
+ query.state as string | undefined,
2768
+ getAppUrl(event, "/_agent-native/google/callback"),
2769
+ );
2770
+ callbackFlowId = flowId;
2771
+ callbackDesktop = desktop ?? false;
2772
+ logGoogleOAuthDebug(event, "callback-start", {
2773
+ flowId,
2774
+ desktop,
2775
+ redirectPath: oauthDebugUrlPath(redirectUri),
2776
+ hasCode: !!code,
2777
+ returnUrl,
2778
+ });
2779
+ if (!code) {
2780
+ const providerError =
2781
+ typeof query.error === "string" && query.error
2782
+ ? query.error
2783
+ : undefined;
2784
+ const providerDescription =
2785
+ typeof query.error_description === "string" &&
2786
+ query.error_description
2787
+ ? query.error_description
2788
+ : undefined;
2789
+ const msg =
2790
+ providerDescription ||
2791
+ providerError ||
2792
+ "Missing authorization code";
2793
+ if (flowId) {
2794
+ setDesktopExchangeError(flowId, {
2795
+ message: `Google sign-in failed: ${msg}`,
2796
+ code: providerError || "missing_authorization_code",
2797
+ });
2798
+ }
2799
+ logGoogleOAuthDebug(event, "callback-error", {
2800
+ flowId,
2801
+ desktop,
2802
+ message: msg,
2803
+ code: providerError,
2804
+ });
2805
+ return oauthErrorPage(`Connection failed: ${msg}`);
2806
+ }
2807
+ // Defence in depth: the state is HMAC-signed, but if the signing
2808
+ // key ever leaked an attacker could mint state with their own
2809
+ // redirect_uri. Re-validate against the same allowlist used at
2810
+ // auth-url time so the token exchange is always sent to a URI we
2811
+ // own.
2812
+ if (!isAllowedOAuthRedirectUri(redirectUri, event)) {
2813
+ const msg =
2814
+ "Invalid Google OAuth redirect URI in state. Restart sign-in from this app.";
2815
+ if (flowId) {
2816
+ setDesktopExchangeError(flowId, {
2817
+ message: msg,
2818
+ code: "invalid_redirect_uri",
2819
+ });
2820
+ }
2821
+ logGoogleOAuthDebug(event, "callback-error", {
2822
+ flowId,
2823
+ desktop,
2824
+ message: msg,
2825
+ });
2826
+ return oauthErrorPage(`Connection failed: ${msg}`);
2827
+ }
2828
+
2829
+ const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
2830
+ method: "POST",
2831
+ headers: {
2832
+ "Content-Type": "application/x-www-form-urlencoded",
2833
+ },
2834
+ body: new URLSearchParams({
2835
+ code,
2836
+ client_id: googleSignInCredentials.clientId,
2837
+ client_secret: googleSignInCredentials.clientSecret,
2838
+ redirect_uri: redirectUri,
2839
+ grant_type: "authorization_code",
2840
+ }),
2841
+ });
2842
+ const tokens = await tokenRes.json();
2843
+ if (!tokenRes.ok) {
2844
+ throw new Error(
2845
+ tokens.error_description ||
2846
+ tokens.error ||
2847
+ "Token exchange failed",
2848
+ );
2849
+ }
2850
+
2851
+ const userRes = await fetch(
2852
+ "https://www.googleapis.com/oauth2/v2/userinfo",
2853
+ { headers: { Authorization: `Bearer ${tokens.access_token}` } },
2854
+ );
2855
+ const user = await userRes.json();
2856
+ const email = user.email as string;
2857
+ if (!email) throw new Error("Could not get email from Google");
2858
+ // Reject unverified Google addresses. Google returns
2859
+ // `verified_email: false` for accounts where ownership of the
2860
+ // address hasn't been proven (rare on consumer accounts but
2861
+ // reachable on Workspace tenants that allow it). Without this
2862
+ // check, an attacker could sign up as `victim@example.com` on
2863
+ // Google without controlling the inbox and take over a local
2864
+ // password account that already exists at that address (Better
2865
+ // Auth's accountLinking auto-merges trusted-provider sign-ins).
2866
+ if (user.verified_email !== true) {
2867
+ throw new Error(
2868
+ "Google account email is not verified. Please verify your email with Google and try again.",
2869
+ );
2870
+ }
2871
+ if (typeof user.picture === "string" && user.picture.trim()) {
2872
+ await putSetting(`avatar:${email}`, {
2873
+ image: user.picture,
2874
+ }).catch((error) => {
2875
+ console.warn(
2876
+ "[auth] failed to store Google profile image:",
2877
+ error,
2878
+ );
2879
+ });
2880
+ }
2881
+
2882
+ const { sessionToken } = await createOAuthSession(event, email, {
2883
+ hasProductionSession: false,
2884
+ desktop,
2885
+ trackSignup: {
2886
+ authProvider: "google",
2887
+ authUserId: typeof user.id === "string" ? user.id : undefined,
2888
+ name: typeof user.name === "string" ? user.name : undefined,
2889
+ attribution: signupAttribution,
2890
+ },
2891
+ });
2892
+ logGoogleOAuthDebug(event, "callback-session-created", {
2893
+ flowId,
2894
+ desktop,
2895
+ hasSessionToken: !!sessionToken,
2896
+ emailDomain: email.split("@")[1] || "",
2897
+ });
2898
+
2899
+ if (flowId && sessionToken) {
2900
+ _desktopExchanges.set(flowId, {
2901
+ token: sessionToken,
2902
+ email,
2903
+ expiresAt: Date.now() + DESKTOP_EXCHANGE_TTL_MS,
2904
+ });
2905
+ // Also persist to DB for cross-instance durability (Cloudflare
2906
+ // Workers, multi-region). Fire-and-forget — in-memory Map is
2907
+ // still the primary fast path for same-instance requests.
2908
+ void persistDesktopExchangeToDB(flowId, sessionToken, email);
2909
+ logGoogleOAuthDebug(event, "callback-exchange-stored", {
2910
+ flowId,
2911
+ desktop,
2912
+ });
2913
+ }
2914
+
2915
+ return oauthCallbackResponse(event, email, {
2916
+ sessionToken,
2917
+ desktop,
2918
+ returnUrl,
2919
+ flowId,
2920
+ });
2921
+ } catch (error: any) {
2922
+ const msg = error.message || "Unknown error";
2923
+ if (callbackFlowId) {
2924
+ setDesktopExchangeError(callbackFlowId, {
2925
+ message: `Google sign-in failed: ${msg}`,
2926
+ code: "callback_error",
2927
+ });
2928
+ }
2929
+ logGoogleOAuthDebug(event, "callback-error", {
2930
+ flowId: callbackFlowId,
2931
+ desktop: callbackDesktop,
2932
+ message: msg,
2933
+ });
2934
+ return oauthErrorPage(`Connection failed: ${msg}`);
2935
+ }
2936
+ }),
2937
+ );
2938
+ }
2939
+
2940
+ // Desktop OAuth exchange — native apps (Tauri tray, Electron) open OAuth
2941
+ // in the system browser but need a way to retrieve the session token
2942
+ // afterwards since they don't share a cookie jar with the browser.
2943
+ app.use(
2944
+ "/_agent-native/auth/desktop-exchange",
2945
+ defineEventHandler(async (event) => {
2946
+ if (getMethod(event) !== "GET") {
2947
+ setResponseStatus(event, 405);
2948
+ return { error: "Method not allowed" };
2949
+ }
2950
+ const query = getQuery(event);
2951
+ const flowId = query.flow_id as string | undefined;
2952
+ if (!flowId) {
2953
+ setResponseStatus(event, 400);
2954
+ return { error: "Missing flow_id" };
2955
+ }
2956
+ let entry = _desktopExchanges.get(flowId);
2957
+ if (!entry || entry.expiresAt < Date.now()) {
2958
+ // In-memory miss — fall back to the DB-persisted entry. This handles
2959
+ // cross-instance routing (Cloudflare Workers, multi-region) where the
2960
+ // OAuth callback and the polling request may hit different isolates.
2961
+ const fromDb = await consumeDesktopExchangeFromDB(flowId);
2962
+ if (!fromDb) {
2963
+ // Don't log on the pending path — clients poll every second for up
2964
+ // to 5 minutes, so logging here floods telemetry. The auth-url,
2965
+ // callback-start, callback-session-created, exchange-success, and
2966
+ // exchange-error breadcrumbs already cover every meaningful state
2967
+ // transition.
2968
+ return { pending: true, flow: oauthDebugFlowId(flowId) };
2969
+ }
2970
+ entry =
2971
+ "error" in fromDb
2972
+ ? { error: fromDb.error, expiresAt: Date.now() + 1 }
2973
+ : {
2974
+ token: fromDb.token,
2975
+ email: fromDb.email,
2976
+ expiresAt: Date.now() + 1,
2977
+ };
2978
+ }
2979
+ _desktopExchanges.delete(flowId);
2980
+ // Also wipe the DB-persisted entry so it cannot be replayed via the
2981
+ // DB fallback path after in-memory consumption. Best-effort: a dropped
2982
+ // Neon WebSocket rejects with a raw ErrorEvent, and a floating
2983
+ // rejection here surfaces as an unhandled promise rejection.
2984
+ removeSession(`dex:${flowId}`).catch((err) => {
2985
+ console.warn(
2986
+ "[auth] desktop-exchange DB cleanup failed:",
2987
+ describeDbError(err),
2988
+ );
2989
+ });
2990
+ if ("error" in entry) {
2991
+ logGoogleOAuthDebug(event, "exchange-error", {
2992
+ flowId,
2993
+ message: entry.error.message,
2994
+ code: entry.error.code,
2995
+ });
2996
+ return { error: entry.error.message, ...entry.error };
2997
+ }
2998
+ // Make the exchange itself establish the app session. Older clients
2999
+ // still make a follow-up /auth/session?_session=... request, but the
3000
+ // OAuth handoff should not depend on that second request succeeding.
3001
+ setFrameworkSessionCookie(event, entry.token);
3002
+ setResponseHeader(event, "Referrer-Policy", "no-referrer");
3003
+ logGoogleOAuthDebug(event, "exchange-success", {
3004
+ flowId,
3005
+ emailDomain: entry.email.split("@")[1] || "",
3006
+ });
3007
+ return { token: entry.token, email: entry.email };
3008
+ }),
3009
+ );
3010
+
3011
+ // Initialize Better Auth. Forward `googleScopes` into the BetterAuthConfig
3012
+ // so the social provider requests the broader product scopes (Gmail,
3013
+ // Calendar, etc.) up front during the primary sign-in — eliminating the
3014
+ // need for a separate "Connect Google" page.
3015
+ const betterAuthConfig: BetterAuthConfig = {
3016
+ ...(options.betterAuth ?? {}),
3017
+ ...(options.googleScopes ? { googleScopes: options.googleScopes } : {}),
3018
+ };
3019
+ const auth = await getBetterAuth(betterAuthConfig);
3020
+
3021
+ // Mount Better Auth catch-all handler at /_agent-native/auth/ba/*
3022
+ app.use(
3023
+ "/_agent-native/auth/ba",
3024
+ defineEventHandler(async (event) => {
3025
+ const reqPath = event.url?.pathname ?? event.path ?? "";
3026
+ const isResetPassword =
3027
+ reqPath.includes("reset-password") && getMethod(event) === "POST";
3028
+ const isSendVerificationEmail =
3029
+ reqPath.includes("send-verification-email") &&
3030
+ getMethod(event) === "POST";
3031
+ const authRequest = toWebRequest(event);
3032
+ let requestForAuth = authRequest;
3033
+
3034
+ // Pre-read the body for reset-password so we can auto-verify the
3035
+ // user's email after they save the new password. CRUCIAL: clone
3036
+ // the Request first — h3 v2 `event.req` is the live web Request,
3037
+ // and `.text()`/`.json()` consume the stream. The same `event.req`
3038
+ // is handed to Better Auth below; without the clone, Better Auth
3039
+ // sees an empty body, fails Zod validation, and returns 400 —
3040
+ // which the reset page renders as "the link may have expired".
3041
+ let resetToken: string | undefined;
3042
+ let resetUserId: string | undefined;
3043
+ if (isResetPassword) {
3044
+ try {
3045
+ const cloned = authRequest.clone();
3046
+ const body = (await cloned.json().catch(() => undefined)) as
3047
+ | { token?: string }
3048
+ | undefined;
3049
+ resetToken = body?.token;
3050
+ } catch {
3051
+ // ignore — Better Auth will handle validation
3052
+ }
3053
+ // Look up userId BEFORE calling auth.handler — Better Auth deletes
3054
+ // the verification row as part of the reset, so by the time the
3055
+ // handler returns 200 the row is gone and we can't recover the user.
3056
+ if (resetToken) {
3057
+ try {
3058
+ const { getDbExec } = await import("../db/client.js");
3059
+ const db = getDbExec();
3060
+ const rows = await db.execute({
3061
+ sql: "SELECT value FROM verification WHERE identifier = ?",
3062
+ args: [`reset-password:${resetToken}`],
3063
+ });
3064
+ resetUserId = rows.rows[0]?.value as string | undefined;
3065
+ } catch {
3066
+ // Best-effort — if we can't read the verification row we just
3067
+ // skip auto-verify; the user can verify normally.
3068
+ }
3069
+ }
3070
+ }
3071
+
3072
+ // The signup wrapper sanitizes callbackURL before calling Better Auth,
3073
+ // but the resend endpoint is exposed directly so users can request a
3074
+ // fresh link while unauthenticated. Keep that path equally strict:
3075
+ // only same-origin relative return paths survive into the email.
3076
+ if (isSendVerificationEmail) {
3077
+ try {
3078
+ const body = (await authRequest
3079
+ .clone()
3080
+ .json()
3081
+ .catch(() => undefined)) as Record<string, unknown> | undefined;
3082
+ if (body && typeof body.callbackURL === "string") {
3083
+ const callbackURL = safeReturnPath(body.callbackURL);
3084
+ if (callbackURL !== body.callbackURL) {
3085
+ const headers = new Headers(authRequest.headers);
3086
+ headers.delete("content-length");
3087
+ headers.set("content-type", "application/json");
3088
+ requestForAuth = new Request(authRequest.url, {
3089
+ method: authRequest.method,
3090
+ headers,
3091
+ body: JSON.stringify({ ...body, callbackURL }),
3092
+ duplex: "half",
3093
+ } as RequestInit & { duplex: "half" });
3094
+ }
3095
+ }
3096
+ } catch {
3097
+ // Let Better Auth handle malformed bodies and return its normal
3098
+ // validation error.
3099
+ }
3100
+ }
3101
+
3102
+ const response = await auth.handler(requestForAuth);
3103
+ const isResponse =
3104
+ response != null &&
3105
+ typeof (response as any).status === "number" &&
3106
+ typeof (response as any).headers?.get === "function";
3107
+
3108
+ // After email verification, add ?verified=1 to the redirect so the
3109
+ // login page can show "Email verified!". MUTATE the response in
3110
+ // place — `new Response(null, { headers: new Headers(response.headers) })`
3111
+ // collapses multiple Set-Cookie headers into one comma-joined value,
3112
+ // which browsers reject. With `autoSignInAfterVerification: true`
3113
+ // Better Auth emits 2–3 Set-Cookie headers (session token + cookie
3114
+ // cache + dontRememberToken); losing them strands the user on the
3115
+ // login page even though verification succeeded.
3116
+ if (
3117
+ reqPath.includes("verify-email") &&
3118
+ isResponse &&
3119
+ (response as Response).status >= 300 &&
3120
+ (response as Response).status < 400
3121
+ ) {
3122
+ const loc = response.headers.get("location");
3123
+ if (
3124
+ loc &&
3125
+ !/[?&]verified=/.test(loc) &&
3126
+ !verifyEmailRedirectHasError(loc, authRequest.url)
3127
+ ) {
3128
+ await ensureEmailVerifiedForRedirect(
3129
+ authRequest,
3130
+ response as Response,
3131
+ );
3132
+ response.headers.set("location", appendVerifiedParamToLocation(loc));
3133
+ }
3134
+ }
3135
+
3136
+ // Auto-verify email after a successful password reset. The user
3137
+ // proved email ownership by receiving and using the reset link, so
3138
+ // we don't want them stuck behind `requireEmailVerification` after
3139
+ // resetting — that's the exact escape hatch they just used.
3140
+ if (
3141
+ isResetPassword &&
3142
+ resetUserId &&
3143
+ isResponse &&
3144
+ (response as Response).status >= 200 &&
3145
+ (response as Response).status < 300
3146
+ ) {
3147
+ try {
3148
+ const { getDbExec } = await import("../db/client.js");
3149
+ const db = getDbExec();
3150
+ // Use boolean literals for cross-dialect portability: Postgres
3151
+ // stores `email_verified` as BOOLEAN and rejects integer 1/0,
3152
+ // SQLite accepts TRUE/FALSE as aliases for 1/0 (since 3.23).
3153
+ // Quote `"user"` because it's a reserved keyword in Postgres.
3154
+ await db.execute({
3155
+ sql: 'UPDATE "user" SET email_verified = TRUE WHERE id = ? AND (email_verified = FALSE OR email_verified IS NULL)',
3156
+ args: [resetUserId],
3157
+ });
3158
+
3159
+ // Revoke every existing session for this user so a stolen
3160
+ // cookie doesn't outlive the password it was paired with. We
3161
+ // do this AFTER Better Auth's response has been generated so
3162
+ // the freshly-minted post-reset session (if any) is captured
3163
+ // by the response's Set-Cookie header — but `auth.handler` for
3164
+ // reset-password does not auto-sign-in by default, so the
3165
+ // common path is "wipe everything; user signs in with new
3166
+ // password." The legacy `sessions` table is also wiped by
3167
+ // joining through the `user.email` column.
3168
+ //
3169
+ // Skip the freshly-minted Better Auth session id when present
3170
+ // (auto-sign-in plugins / future config). Reading it from the
3171
+ // response avoids racing against Better Auth's own writes.
3172
+ const newSessionToken = extractSessionTokenFromSetCookies(
3173
+ response as Response,
3174
+ );
3175
+
3176
+ // 1. Better Auth `session` table — keyed by user_id.
3177
+ if (newSessionToken) {
3178
+ await db.execute({
3179
+ sql: 'DELETE FROM "session" WHERE user_id = ? AND token <> ?',
3180
+ args: [resetUserId, newSessionToken],
3181
+ });
3182
+ } else {
3183
+ await db.execute({
3184
+ sql: 'DELETE FROM "session" WHERE user_id = ?',
3185
+ args: [resetUserId],
3186
+ });
3187
+ }
3188
+
3189
+ // 2. Legacy `sessions` table — keyed by `email` column. The
3190
+ // reset-password verification row holds the user's id, not
3191
+ // their email, so we look up the email first. Best-effort —
3192
+ // skip silently if the lookup fails so the response still ships.
3193
+ try {
3194
+ const { rows } = await db.execute({
3195
+ sql: 'SELECT email FROM "user" WHERE id = ?',
3196
+ args: [resetUserId],
3197
+ });
3198
+ const userEmail = (rows[0]?.email ?? rows[0]?.[0]) as
3199
+ | string
3200
+ | undefined;
3201
+ if (userEmail) {
3202
+ if (newSessionToken) {
3203
+ await db.execute({
3204
+ sql: "DELETE FROM sessions WHERE email = ? AND token <> ?",
3205
+ args: [userEmail, newSessionToken],
3206
+ });
3207
+ } else {
3208
+ await db.execute({
3209
+ sql: "DELETE FROM sessions WHERE email = ?",
3210
+ args: [userEmail],
3211
+ });
3212
+ }
3213
+ }
3214
+ } catch {
3215
+ // Best-effort — don't block the response
3216
+ }
3217
+ } catch {
3218
+ // Best-effort — don't block the response
3219
+ }
3220
+ }
3221
+
3222
+ return response;
3223
+ }),
3224
+ );
3225
+
3226
+ // Backward-compat: POST /_agent-native/auth/login
3227
+ app.use(
3228
+ "/_agent-native/auth/login",
3229
+ defineEventHandler(async (event) => {
3230
+ if (getMethod(event) !== "POST") {
3231
+ setResponseStatus(event, 405);
3232
+ return { error: "Method not allowed" };
3233
+ }
3234
+
3235
+ const body = await readBody(event);
3236
+
3237
+ // Email/password login via Better Auth
3238
+ const rawEmail = typeof body?.email === "string" ? body.email : "";
3239
+ const email = normalizeAuthEmail(rawEmail);
3240
+ const password = body?.password;
3241
+
3242
+ if (!rawEmail.trim() || !password) {
3243
+ setResponseStatus(event, 400);
3244
+ return { error: "Email and password are required" };
3245
+ }
3246
+ if (!email) {
3247
+ setResponseStatus(event, 400);
3248
+ return { error: VALID_AUTH_EMAIL_MESSAGE };
3249
+ }
3250
+
3251
+ try {
3252
+ const result = await auth.api.signInEmail({
3253
+ body: { email, password },
3254
+ });
3255
+ if (result?.token) {
3256
+ setFrameworkSessionCookie(event, result.token);
3257
+ await addSession(result.token, email);
3258
+ if (isElectronRequest(event)) {
3259
+ await writeDesktopSso({
3260
+ email,
3261
+ token: result.token,
3262
+ expiresAt: Date.now() + sessionMaxAge * 1000,
3263
+ });
3264
+ }
3265
+ return authLoginResponse(event, result.token, email);
3266
+ }
3267
+ // signInEmail succeeded but returned no token — typically means the
3268
+ // email isn't verified yet. Don't return { ok: true } without a
3269
+ // session or the frontend will reload into a dead end.
3270
+ setResponseStatus(event, 403);
3271
+ return {
3272
+ error:
3273
+ "Email not verified. Check your inbox for a verification link.",
3274
+ };
3275
+ } catch (e: any) {
3276
+ if (!isExpectedAuthFailure(e)) {
3277
+ captureAuthError(e, { route: "login", email });
3278
+ }
3279
+ const authError = publicAuthError(e, "Invalid email or password");
3280
+ setResponseStatus(event, authError.statusCode ?? 401);
3281
+ return { error: authError.message };
3282
+ }
3283
+ }),
3284
+ );
3285
+
3286
+ // Backward-compat: POST /_agent-native/auth/register
3287
+ app.use(
3288
+ "/_agent-native/auth/register",
3289
+ defineEventHandler(async (event) => {
3290
+ if (getMethod(event) !== "POST") {
3291
+ setResponseStatus(event, 405);
3292
+ return { error: "Method not allowed" };
3293
+ }
3294
+
3295
+ const body = await readBody(event);
3296
+ const rawEmail = typeof body?.email === "string" ? body.email : "";
3297
+ const email = normalizeAuthEmail(rawEmail);
3298
+ const password = body?.password;
3299
+ const callbackURL =
3300
+ typeof body?.callbackURL === "string"
3301
+ ? safeReturnPath(body.callbackURL)
3302
+ : "/";
3303
+
3304
+ if (!email) {
3305
+ setResponseStatus(event, 400);
3306
+ return { error: VALID_AUTH_EMAIL_MESSAGE };
3307
+ }
3308
+ if (!password || typeof password !== "string" || password.length < 8) {
3309
+ setResponseStatus(event, 400);
3310
+ return { error: "Password must be at least 8 characters" };
3311
+ }
3312
+
3313
+ try {
3314
+ await auth.api.signUpEmail({
3315
+ body: { email, password, name: email.split("@")[0], callbackURL },
3316
+ headers: event.headers,
3317
+ });
3318
+ return { ok: true };
3319
+ } catch (e: any) {
3320
+ if (!isExpectedAuthFailure(e)) {
3321
+ captureAuthError(e, { route: "signup", email });
3322
+ }
3323
+ const authError = publicAuthError(e, "Registration failed");
3324
+ setResponseStatus(event, authError.statusCode ?? 409);
3325
+ return { error: authError.message };
3326
+ }
3327
+ }),
3328
+ );
3329
+
3330
+ // Backward-compat: POST /_agent-native/auth/logout
3331
+ app.use(
3332
+ "/_agent-native/auth/logout",
3333
+ defineEventHandler(async (event) => {
3334
+ for (const cookie of getFrameworkSessionCookieValues(event)) {
3335
+ await removeSession(cookie);
3336
+ }
3337
+ const bearerToken = getBearerSessionToken(event);
3338
+ if (bearerToken) await removeSession(bearerToken);
3339
+ clearFrameworkSessionCookies(event);
3340
+
3341
+ try {
3342
+ await auth.api.signOut({ headers: event.headers });
3343
+ } catch {
3344
+ // Ignore if no Better Auth session
3345
+ }
3346
+
3347
+ if (isElectronRequest(event)) await clearDesktopSso();
3348
+
3349
+ return { ok: true };
3350
+ }),
3351
+ );
3352
+
3353
+ // POST /_agent-native/auth/logout-all — revoke every session row for
3354
+ // the authenticated user across both auth tables. Companion to the
3355
+ // password-reset session-revocation logic; lets a user sign out
3356
+ // everywhere from one device. Requires an authenticated session.
3357
+ app.use(
3358
+ "/_agent-native/auth/logout-all",
3359
+ defineEventHandler(async (event) => {
3360
+ if (getMethod(event) !== "POST") {
3361
+ setResponseStatus(event, 405);
3362
+ return { error: "Method not allowed" };
3363
+ }
3364
+ const session = await getSession(event);
3365
+ if (!session?.email) {
3366
+ setResponseStatus(event, 401);
3367
+ return { error: "Not authenticated" };
3368
+ }
3369
+ try {
3370
+ const db = getDbExec();
3371
+ // 1. Resolve user_id from email so we can wipe Better Auth sessions
3372
+ // by their FK column.
3373
+ let userId: string | undefined;
3374
+ try {
3375
+ const { rows } = await db.execute({
3376
+ sql: 'SELECT id FROM "user" WHERE email = ?',
3377
+ args: [session.email],
3378
+ });
3379
+ userId = (rows[0]?.id ?? rows[0]?.[0]) as string | undefined;
3380
+ } catch {
3381
+ // User table may not exist on token-only deployments — skip.
3382
+ }
3383
+ if (userId) {
3384
+ try {
3385
+ await db.execute({
3386
+ sql: 'DELETE FROM "session" WHERE user_id = ?',
3387
+ args: [userId],
3388
+ });
3389
+ } catch {
3390
+ // Best-effort.
3391
+ }
3392
+ }
3393
+
3394
+ // 2. Legacy `sessions` table — keyed by `email` column.
3395
+ try {
3396
+ await db.execute({
3397
+ sql: "DELETE FROM sessions WHERE email = ?",
3398
+ args: [session.email],
3399
+ });
3400
+ } catch {
3401
+ // Best-effort.
3402
+ }
3403
+
3404
+ // 3. Drop the current request's cookie and best-effort sign out
3405
+ // of Better Auth (so the response sets the proper expiry header).
3406
+ clearFrameworkSessionCookies(event);
3407
+ try {
3408
+ await auth.api.signOut({ headers: event.headers });
3409
+ } catch {
3410
+ // Ignore — sessions are already gone in DB.
3411
+ }
3412
+
3413
+ if (isElectronRequest(event)) await clearDesktopSso();
3414
+ return { ok: true };
3415
+ } catch (e: any) {
3416
+ setResponseStatus(event, 500);
3417
+ return { error: e?.message || "Failed to revoke sessions" };
3418
+ }
3419
+ }),
3420
+ );
3421
+
3422
+ // GET /_agent-native/auth/session
3423
+ app.use(
3424
+ "/_agent-native/auth/session",
3425
+ defineEventHandler(async (event) => {
3426
+ if (!isReadMethod(event)) {
3427
+ setResponseStatus(event, 405);
3428
+ return { error: "Method not allowed" };
3429
+ }
3430
+ const session = await getSession(event);
3431
+ return session ?? { error: "Not authenticated" };
3432
+ }),
3433
+ );
3434
+
3435
+ // GET /_agent-native/auth/reset — HTML page shown when a user clicks the
3436
+ // reset link in their email. Reads ?token=... and POSTs to Better Auth's
3437
+ // /reset-password endpoint on submit.
3438
+ app.use(
3439
+ "/_agent-native/auth/reset",
3440
+ defineEventHandler((event) => {
3441
+ if (!isReadMethod(event)) {
3442
+ setResponseStatus(event, 405);
3443
+ return { error: "Method not allowed" };
3444
+ }
3445
+ return new Response(getResetPasswordHtml(), {
3446
+ headers: { "Content-Type": "text/html; charset=utf-8" },
3447
+ });
3448
+ }),
3449
+ );
3450
+
3451
+ // Auth guard — stored both in framework middleware registry AND in
3452
+ // _authGuardFn so the server middleware can enforce it on ALL routes.
3453
+ const loginHtmlConfig = getOnboardingLoginHtmlConfig(options);
3454
+ _authGuardConfig = {
3455
+ ...loginHtmlConfig,
3456
+ publicPaths,
3457
+ workspaceAppAudience,
3458
+ workspaceAppPublicPaths: workspaceAppRouteAccess.publicPaths,
3459
+ workspaceAppProtectedPaths: workspaceAppRouteAccess.protectedPaths,
3460
+ };
3461
+ const guardFn = createAuthGuardFn();
3462
+ _authGuardFn = guardFn;
3463
+ app.use(defineEventHandler(guardFn));
3464
+ }
3465
+
3466
+ // ---------------------------------------------------------------------------
3467
+ // mountAuthFallbackRoutes — minimal auth endpoints when Better Auth init fails
3468
+ // ---------------------------------------------------------------------------
3469
+
3470
+ function mountAuthFallbackRoutes(app: H3App): void {
3471
+ app.use(
3472
+ "/_agent-native/auth/login",
3473
+ defineEventHandler(async (event) => {
3474
+ if (getMethod(event) !== "POST") {
3475
+ setResponseStatus(event, 405);
3476
+ return { error: "Method not allowed" };
3477
+ }
3478
+
3479
+ const body = await readBody(event);
3480
+ const rawEmail = typeof body?.email === "string" ? body.email : "";
3481
+ const email = normalizeAuthEmail(rawEmail);
3482
+ const password = body?.password;
3483
+
3484
+ if (!rawEmail.trim() || !password) {
3485
+ setResponseStatus(event, 400);
3486
+ return { error: "Email and password are required" };
3487
+ }
3488
+ if (!email) {
3489
+ setResponseStatus(event, 400);
3490
+ return { error: VALID_AUTH_EMAIL_MESSAGE };
3491
+ }
3492
+
3493
+ try {
3494
+ const auth = await getBetterAuth();
3495
+ const result = await auth.api.signInEmail({
3496
+ body: { email, password },
3497
+ });
3498
+ if (result?.token) {
3499
+ setFrameworkSessionCookie(event, result.token);
3500
+ await addSession(result.token, email);
3501
+ if (isElectronRequest(event)) {
3502
+ await writeDesktopSso({
3503
+ email,
3504
+ token: result.token,
3505
+ expiresAt: Date.now() + sessionMaxAge * 1000,
3506
+ });
3507
+ }
3508
+ return authLoginResponse(event, result.token, email);
3509
+ }
3510
+ setResponseStatus(event, 403);
3511
+ return {
3512
+ error:
3513
+ "Email not verified. Check your inbox for a verification link.",
3514
+ };
3515
+ } catch (e: any) {
3516
+ if (!isExpectedAuthFailure(e)) {
3517
+ captureAuthError(e, { route: "login", email });
3518
+ }
3519
+ const authError = publicAuthError(e, "Invalid email or password");
3520
+ setResponseStatus(event, authError.statusCode ?? 401);
3521
+ return { error: authError.message };
3522
+ }
3523
+ }),
3524
+ );
3525
+
3526
+ app.use(
3527
+ "/_agent-native/auth/register",
3528
+ defineEventHandler(async (event) => {
3529
+ if (getMethod(event) !== "POST") {
3530
+ setResponseStatus(event, 405);
3531
+ return { error: "Method not allowed" };
3532
+ }
3533
+
3534
+ const body = await readBody(event);
3535
+ const rawEmail = typeof body?.email === "string" ? body.email : "";
3536
+ const email = normalizeAuthEmail(rawEmail);
3537
+ const password = body?.password;
3538
+
3539
+ if (!email) {
3540
+ setResponseStatus(event, 400);
3541
+ return { error: VALID_AUTH_EMAIL_MESSAGE };
3542
+ }
3543
+ if (!password || typeof password !== "string" || password.length < 8) {
3544
+ setResponseStatus(event, 400);
3545
+ return { error: "Password must be at least 8 characters" };
3546
+ }
3547
+
3548
+ try {
3549
+ const auth = await getBetterAuth();
3550
+ await auth.api.signUpEmail({
3551
+ body: { email, password, name: email.split("@")[0] },
3552
+ headers: event.headers,
3553
+ });
3554
+ return { ok: true };
3555
+ } catch (e: any) {
3556
+ if (!isExpectedAuthFailure(e)) {
3557
+ captureAuthError(e, { route: "signup", email });
3558
+ }
3559
+ const authError = publicAuthError(e, "Registration failed");
3560
+ setResponseStatus(event, authError.statusCode ?? 409);
3561
+ return { error: authError.message };
3562
+ }
3563
+ }),
3564
+ );
3565
+
3566
+ app.use(
3567
+ "/_agent-native/auth/logout",
3568
+ defineEventHandler(async (event) => {
3569
+ for (const cookie of getFrameworkSessionCookieValues(event)) {
3570
+ await removeSession(cookie);
3571
+ }
3572
+ const bearerToken = getBearerSessionToken(event);
3573
+ if (bearerToken) await removeSession(bearerToken);
3574
+ clearFrameworkSessionCookies(event);
3575
+
3576
+ try {
3577
+ const auth = await getBetterAuth();
3578
+ await auth.api.signOut({ headers: event.headers });
3579
+ } catch {
3580
+ // Ignore if Better Auth is still unavailable
3581
+ }
3582
+
3583
+ if (isElectronRequest(event)) await clearDesktopSso();
3584
+
3585
+ return { ok: true };
3586
+ }),
3587
+ );
3588
+
3589
+ app.use(
3590
+ "/_agent-native/auth/session",
3591
+ defineEventHandler(async (event) => {
3592
+ if (!isReadMethod(event)) {
3593
+ setResponseStatus(event, 405);
3594
+ return { error: "Method not allowed" };
3595
+ }
3596
+ const session = await getSession(event);
3597
+ return session ?? { error: "Not authenticated" };
3598
+ }),
3599
+ );
3600
+ }
3601
+
3602
+ // ---------------------------------------------------------------------------
3603
+ // autoMountAuth — the recommended entry point
3604
+ // ---------------------------------------------------------------------------
3605
+
3606
+ /**
3607
+ * Automatically configure auth based on environment and configuration:
3608
+ *
3609
+ * - **BYOA (custom getSession)**: Template-provided auth callback handles everything.
3610
+ * - **Default**: Better Auth with email/password, social providers, organizations, and JWT.
3611
+ * Users see an onboarding page to create an account on first visit.
3612
+ *
3613
+ * Local development uses the same Better Auth flow as production. Email
3614
+ * verification is automatically skipped in dev/test environments and when
3615
+ * no email provider is configured (see `shouldSkipEmailVerification`), so a
3616
+ * fresh local clone only needs an email + password to get started.
3617
+ *
3618
+ * Returns true if auth was mounted, false if skipped.
3619
+ */
3620
+ export async function autoMountAuth(
3621
+ app: H3App,
3622
+ options: AuthOptions = {},
3623
+ ): Promise<boolean> {
3624
+ // If auth is already mounted on THIS app (e.g., default plugin ran before
3625
+ // custom plugin in the same server boot), don't re-mount routes — but DO
3626
+ // update the live config if custom options like googleOnly or loginHtml
3627
+ // were provided. createAuthGuardFn() reads from _authGuardConfig on every
3628
+ // request, so updating it here takes effect immediately.
3629
+ //
3630
+ // We gate on `_mountedApp === app` because module-level state survives
3631
+ // Vite HMR — without this check, an HMR-restarted Nitro instance (fresh
3632
+ // H3 app, empty middleware) would short-circuit here and end up with no
3633
+ // auth routes mounted at all.
3634
+ if (_authGuardFn && _mountedApp === app) {
3635
+ if (options.mountGoogleOAuthRoutes === false) {
3636
+ setGenericGoogleOAuthRoutesEnabled(app, false);
3637
+ }
3638
+ // A custom getSession always wins — even if the default auth plugin
3639
+ // mounted first (which happens in production where bootstrapDefaultPlugins
3640
+ // can't see the template's server/plugins/ dir and auto-mounts defaults).
3641
+ if (options.getSession) {
3642
+ customGetSession = options.getSession;
3643
+ }
3644
+ if (_authGuardConfig) {
3645
+ if (
3646
+ options.googleOnly ||
3647
+ options.loginHtml ||
3648
+ options.marketing ||
3649
+ options.googleSignInNotice
3650
+ ) {
3651
+ const loginHtmlConfig = getOnboardingLoginHtmlConfig(options);
3652
+ _authGuardConfig.loginHtml = loginHtmlConfig.loginHtml;
3653
+ _authGuardConfig.getLoginHtml = loginHtmlConfig.getLoginHtml;
3654
+ }
3655
+ if (options.publicPaths) {
3656
+ _authGuardConfig.publicPaths = [
3657
+ ...(_authGuardConfig.publicPaths ?? []),
3658
+ ...options.publicPaths,
3659
+ ];
3660
+ }
3661
+ if (options.workspaceAppAudience) {
3662
+ _authGuardConfig.workspaceAppAudience =
3663
+ resolveWorkspaceAppAudience(options);
3664
+ }
3665
+ if (options.workspaceAppPublicPaths) {
3666
+ _authGuardConfig.workspaceAppPublicPaths =
3667
+ options.workspaceAppPublicPaths;
3668
+ }
3669
+ if (options.workspaceAppProtectedPaths) {
3670
+ _authGuardConfig.workspaceAppProtectedPaths =
3671
+ options.workspaceAppProtectedPaths;
3672
+ }
3673
+ }
3674
+ return true;
3675
+ }
3676
+
3677
+ // Fresh app (first boot, or HMR created a new Nitro instance) — reset
3678
+ // the guard so the mount path below installs it on the new app.
3679
+ _authGuardFn = null;
3680
+ _authGuardConfig = null;
3681
+ _mountedApp = app;
3682
+
3683
+ if (!app) {
3684
+ if (isDevEnvironment()) {
3685
+ customGetSession = null;
3686
+ return false;
3687
+ }
3688
+ throw new Error(
3689
+ "autoMountAuth: H3 app is required. In Nitro plugins, pass nitroApp.h3App.",
3690
+ );
3691
+ }
3692
+
3693
+ // Reset globals
3694
+ customGetSession = null;
3695
+ sessionMaxAge = options.maxAge ?? DEFAULT_MAX_AGE;
3696
+ const publicPaths = options.publicPaths ?? [];
3697
+ const workspaceAppAudience = resolveWorkspaceAppAudience(options);
3698
+ const workspaceAppRouteAccess = resolveWorkspaceAppRouteAccess(options);
3699
+
3700
+ mountAuthCorsMiddleware(app);
3701
+
3702
+ if (options.getSession) {
3703
+ customGetSession = options.getSession;
3704
+ }
3705
+
3706
+ // BYOA — custom getSession provider
3707
+ if (customGetSession) {
3708
+ app.use(
3709
+ "/_agent-native/auth/session",
3710
+ defineEventHandler(async (event) => {
3711
+ if (!isReadMethod(event)) {
3712
+ setResponseStatus(event, 405);
3713
+ return { error: "Method not allowed" };
3714
+ }
3715
+ const session = await getSession(event);
3716
+ return session ?? { error: "Not authenticated" };
3717
+ }),
3718
+ );
3719
+ app.use(
3720
+ "/_agent-native/auth/login",
3721
+ defineEventHandler(() => ({ ok: true })),
3722
+ );
3723
+ app.use(
3724
+ "/_agent-native/auth/logout",
3725
+ defineEventHandler(async (event) => {
3726
+ for (const cookie of getFrameworkSessionCookieValues(event)) {
3727
+ await removeSession(cookie);
3728
+ }
3729
+ const bearerToken = getBearerSessionToken(event);
3730
+ if (bearerToken) await removeSession(bearerToken);
3731
+ clearFrameworkSessionCookies(event);
3732
+ if (isElectronRequest(event)) await clearDesktopSso();
3733
+ return { ok: true };
3734
+ }),
3735
+ );
3736
+
3737
+ const byoaLoginHtml = options.loginHtml ?? getCustomAuthRequiredHtml();
3738
+ _authGuardConfig = {
3739
+ loginHtml: byoaLoginHtml,
3740
+ ...(options.loginHtml
3741
+ ? {}
3742
+ : {
3743
+ getLoginHtml: () => getCustomAuthRequiredHtml(),
3744
+ }),
3745
+ publicPaths,
3746
+ workspaceAppAudience,
3747
+ workspaceAppPublicPaths: workspaceAppRouteAccess.publicPaths,
3748
+ workspaceAppProtectedPaths: workspaceAppRouteAccess.protectedPaths,
3749
+ };
3750
+ const guardFn = createAuthGuardFn();
3751
+ _authGuardFn = guardFn;
3752
+ app.use(defineEventHandler(guardFn));
3753
+
3754
+ if (process.env.DEBUG)
3755
+ console.log("[agent-native] Auth enabled — custom getSession provider.");
3756
+ return true;
3757
+ }
3758
+
3759
+ // Default: Better Auth (account-first)
3760
+ try {
3761
+ await mountBetterAuthRoutes(app, options);
3762
+ if (process.env.DEBUG)
3763
+ console.log(
3764
+ "[agent-native] Auth enabled — Better Auth (accounts + organizations).",
3765
+ );
3766
+ } catch (err) {
3767
+ console.error("[agent-native] Failed to initialize Better Auth:", err);
3768
+ mountAuthFallbackRoutes(app);
3769
+ // CRITICAL: Even if Better Auth fails, register the auth guard so
3770
+ // unauthenticated users can't access the app. They'll see the login
3771
+ // page but won't be able to sign in until the DB is available.
3772
+ const loginHtmlConfig = getOnboardingLoginHtmlConfig(options);
3773
+ _authGuardConfig = {
3774
+ ...loginHtmlConfig,
3775
+ publicPaths,
3776
+ workspaceAppAudience,
3777
+ workspaceAppPublicPaths: workspaceAppRouteAccess.publicPaths,
3778
+ workspaceAppProtectedPaths: workspaceAppRouteAccess.protectedPaths,
3779
+ };
3780
+ const guardFn = createAuthGuardFn();
3781
+ _authGuardFn = guardFn;
3782
+ app.use(defineEventHandler(guardFn));
3783
+ console.log(
3784
+ "[agent-native] Auth guard registered despite init failure — app is locked.",
3785
+ );
3786
+ }
3787
+ return true;
3788
+ }
3789
+
3790
+ // ---------------------------------------------------------------------------
3791
+ // Deprecated — kept for backward compat
3792
+ // ---------------------------------------------------------------------------
3793
+
3794
+ /**
3795
+ * @deprecated Use `autoMountAuth(app, options?)` instead.
3796
+ */
3797
+ export function mountAuthMiddleware(app: H3App, accessToken: string): void {
3798
+ void app;
3799
+ void accessToken;
3800
+ throw new Error(
3801
+ "mountAuthMiddleware(accessToken) has been removed. Use createAuthPlugin() or autoMountAuth() with Better Auth, or a custom getSession provider.",
3802
+ );
3803
+ }