@agent-native/core 0.69.0 → 0.70.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/README.md +15 -15
  2. package/corpus/README.md +2 -2
  3. package/corpus/core/CHANGELOG.md +109 -0
  4. package/corpus/core/docs/design/durable-agent-runs.md +217 -0
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/action.ts +89 -0
  7. package/corpus/core/src/agent/model-config.ts +0 -2
  8. package/corpus/core/src/agent/run-loop-with-resume.ts +48 -0
  9. package/corpus/core/src/changelog/parse.ts +245 -0
  10. package/corpus/core/src/cli/changelog.ts +199 -0
  11. package/corpus/core/src/cli/index.ts +19 -0
  12. package/corpus/core/src/cli/recap.ts +24 -1
  13. package/corpus/core/src/cli/skills.ts +74 -47
  14. package/corpus/core/src/client/AgentPanel.tsx +5 -3
  15. package/corpus/core/src/client/AssistantChat.tsx +8 -0
  16. package/corpus/core/src/client/CommandMenu.tsx +183 -75
  17. package/corpus/core/src/client/agent-chat-adapter.ts +44 -7
  18. package/corpus/core/src/client/analytics.ts +181 -0
  19. package/corpus/core/src/client/changelog/Changelog.tsx +321 -0
  20. package/corpus/core/src/client/client-surface.ts +41 -0
  21. package/corpus/core/src/client/composer/TiptapComposer.tsx +123 -12
  22. package/corpus/core/src/client/feedback-context.ts +4 -0
  23. package/corpus/core/src/client/index.ts +12 -0
  24. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +54 -0
  25. package/corpus/core/src/client/settings/useBuilderStatus.ts +82 -20
  26. package/corpus/core/src/client/sse-event-processor.ts +7 -1
  27. package/corpus/core/src/mcp/build-server.ts +57 -1
  28. package/corpus/core/src/mcp/embed-app.ts +197 -7
  29. package/corpus/core/src/mcp/oauth-route.ts +109 -13
  30. package/corpus/core/src/server/attribution.ts +211 -0
  31. package/corpus/core/src/server/auth.ts +6 -0
  32. package/corpus/core/src/server/better-auth-instance.ts +49 -5
  33. package/corpus/core/src/server/builder-browser.ts +27 -1
  34. package/corpus/core/src/server/core-routes-plugin.ts +136 -0
  35. package/corpus/core/src/server/google-oauth.ts +19 -6
  36. package/corpus/core/src/server/onboarding-html.ts +35 -0
  37. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  38. package/corpus/core/src/styles/agent-conversation.css +21 -0
  39. package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  40. package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  41. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  42. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +22 -0
  43. package/corpus/templates/analytics/AGENTS.md +8 -1
  44. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  45. package/corpus/templates/analytics/actions/install-dashboard-template.ts +99 -4
  46. package/corpus/templates/analytics/actions/open-traffic-dashboard.ts +1 -1
  47. package/corpus/templates/analytics/actions/update-dashboard.ts +39 -8
  48. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +30 -3
  49. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +180 -144
  50. package/corpus/templates/analytics/app/components/layout/Header.tsx +2 -3
  51. package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -5
  52. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +221 -215
  53. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +6 -6
  54. package/corpus/templates/analytics/app/lib/demo-dashboard-path.ts +1 -1
  55. package/corpus/templates/analytics/app/lib/last-opened.ts +2 -1
  56. package/corpus/templates/analytics/app/pages/Settings.tsx +4 -0
  57. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -3
  58. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +2 -2
  59. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -2
  60. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +101 -99
  61. package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +1 -1
  62. package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +18 -5
  63. package/corpus/templates/analytics/app/routes/catalog.tsx +1 -1
  64. package/corpus/templates/analytics/app/routes/dashboards.$id.tsx +9 -0
  65. package/corpus/templates/analytics/app/routes/traffic.tsx +1 -1
  66. package/corpus/templates/analytics/changelog/2026-06-23-long-dashboard-and-analysis-names-in-the-sidebar-now-truncat.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-06-23-open-any-dashboard-chart-full-screen-in-a-modal-from-the-pan.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-06-23-the-dashboards-and-analyses-sidebar-settings-buttons-now-sta.md +6 -0
  69. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +145 -0
  70. package/corpus/templates/analytics/server/db/index.ts +1 -1
  71. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +9 -2
  72. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +1 -1
  73. package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -2
  74. package/corpus/templates/analytics/server/plugins/core-routes.ts +6 -6
  75. package/corpus/templates/assets/CHANGELOG.md +10 -0
  76. package/corpus/templates/assets/actions/generate-image.ts +24 -0
  77. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +2 -2
  78. package/corpus/templates/assets/app/global.css +17 -1
  79. package/corpus/templates/assets/app/root.tsx +7 -1
  80. package/corpus/templates/assets/app/routes/_index.tsx +94 -14
  81. package/corpus/templates/assets/app/routes/settings.tsx +6 -0
  82. package/corpus/templates/assets/changelog/2026-06-23-chat-dates-in-the-sidebar-no-longer-wrap-onto-two-lines.md +6 -0
  83. package/corpus/templates/assets/changelog/2026-06-23-clicking-image-video-or-refine-on-the-start-screen-now-drops.md +6 -0
  84. package/corpus/templates/assets/changelog/2026-06-23-pick-your-image-generation-model-right-from-the-chat-model-m.md +6 -0
  85. package/corpus/templates/brain/CHANGELOG.md +10 -0
  86. package/corpus/templates/brain/app/root.tsx +7 -1
  87. package/corpus/templates/brain/app/routes/settings.tsx +8 -1
  88. package/corpus/templates/calendar/CHANGELOG.md +10 -0
  89. package/corpus/templates/calendar/app/pages/Settings.tsx +4 -0
  90. package/corpus/templates/calendar/app/root.tsx +7 -1
  91. package/corpus/templates/chat/CHANGELOG.md +10 -0
  92. package/corpus/templates/chat/app/root.tsx +7 -1
  93. package/corpus/templates/clips/CHANGELOG.md +10 -0
  94. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -20
  95. package/corpus/templates/clips/app/components/player/share-dialog.tsx +33 -7
  96. package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +8 -0
  97. package/corpus/templates/clips/app/components/player/video-player.tsx +150 -20
  98. package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +72 -48
  99. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +7 -11
  100. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +31 -0
  101. package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +71 -0
  102. package/corpus/templates/clips/app/lib/countdown-audio-cue.ts +35 -17
  103. package/corpus/templates/clips/app/root.tsx +7 -1
  104. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +15 -1
  105. package/corpus/templates/clips/app/routes/record.tsx +78 -5
  106. package/corpus/templates/clips/app/routes/share.$shareId.tsx +86 -5
  107. package/corpus/templates/clips/changelog/2026-06-23-clips-shared-in-slack-no-longer-show-a-could-not-start-playb.md +6 -0
  108. package/corpus/templates/clips/changelog/2026-06-23-made-the-play-button-on-shared-and-embedded-clips-scale-with.md +6 -0
  109. package/corpus/templates/clips/changelog/2026-06-23-public-clip-share-links-now-show-whether-they-ll-play-inline.md +6 -0
  110. package/corpus/templates/clips/changelog/2026-06-23-removed-select-button-from-library-hover-a-clip-to-select-it.md +6 -0
  111. package/corpus/templates/clips/changelog/2026-06-23-screen-camera-recordings-now-keep-the-presenter-s-face-in-th.md +6 -0
  112. package/corpus/templates/clips/changelog/2026-06-23-shared-video-links-now-play-for-anyone-without-signing-in.md +6 -0
  113. package/corpus/templates/clips/changelog/2026-06-23-skip-or-cancel-the-recording-countdown-with-buttons-beside-t.md +6 -0
  114. package/corpus/templates/clips/changelog/2026-06-23-the-desktop-camera-bubble-now-looks-sharp-the-instant-it-ope.md +6 -0
  115. package/corpus/templates/clips/changelog/2026-06-23-the-desktop-recording-widget-now-grows-downward-only-when-yo.md +6 -0
  116. package/corpus/templates/clips/changelog/2026-06-23-the-recording-start-sound-is-now-a-softer-more-modern-chime.md +6 -0
  117. package/corpus/templates/clips/chrome-extension/public/manifest.json +17 -8
  118. package/corpus/templates/clips/chrome-extension/src/background.ts +769 -180
  119. package/corpus/templates/clips/chrome-extension/src/content-script.ts +177 -0
  120. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +466 -329
  121. package/corpus/templates/clips/chrome-extension/src/overlay.css +377 -0
  122. package/corpus/templates/clips/chrome-extension/src/overlay.html +12 -0
  123. package/corpus/templates/clips/chrome-extension/src/overlay.ts +330 -0
  124. package/corpus/templates/clips/chrome-extension/src/permission.html +227 -0
  125. package/corpus/templates/clips/chrome-extension/src/permission.ts +105 -0
  126. package/corpus/templates/clips/chrome-extension/src/popup.html +3 -0
  127. package/corpus/templates/clips/chrome-extension/src/popup.ts +50 -2
  128. package/corpus/templates/clips/chrome-extension/src/styles.css +50 -0
  129. package/corpus/templates/clips/chrome-extension/vite.config.ts +3 -0
  130. package/corpus/templates/clips/desktop/src/lib/audio-cue.ts +41 -24
  131. package/corpus/templates/clips/desktop/src/lib/bubble-webrtc.ts +119 -2
  132. package/corpus/templates/clips/desktop/src/lib/recorder.ts +20 -0
  133. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  134. package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +21 -2
  135. package/corpus/templates/clips/desktop/src/overlays/region-record-border.tsx +76 -0
  136. package/corpus/templates/clips/desktop/src/styles.css +119 -2
  137. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
  138. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +140 -1
  139. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
  140. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +4 -1
  141. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +85 -6
  142. package/corpus/templates/clips/shared/share-attribution.ts +79 -0
  143. package/corpus/templates/content/AGENTS.md +20 -3
  144. package/corpus/templates/content/CHANGELOG.md +10 -0
  145. package/corpus/templates/content/actions/_database-utils.ts +15 -0
  146. package/corpus/templates/content/actions/_property-utils.ts +371 -4
  147. package/corpus/templates/content/actions/configure-document-property.ts +35 -1
  148. package/corpus/templates/content/actions/create-content-database.ts +5 -1
  149. package/corpus/templates/content/actions/delete-document-property.ts +52 -2
  150. package/corpus/templates/content/actions/duplicate-document-property.ts +34 -17
  151. package/corpus/templates/content/actions/reorder-document-property.ts +79 -0
  152. package/corpus/templates/content/actions/set-document-property.ts +39 -0
  153. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +806 -0
  154. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +226 -68
  155. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +59 -30
  156. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +25 -1
  157. package/corpus/templates/content/app/components/editor/blockFieldSaveController.ts +180 -0
  158. package/corpus/templates/content/app/components/editor/blockFieldSaveRegistry.ts +179 -0
  159. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +244 -0
  160. package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +132 -0
  161. package/corpus/templates/content/app/global.css +9 -0
  162. package/corpus/templates/content/app/hooks/use-document-properties.ts +21 -0
  163. package/corpus/templates/content/app/root.tsx +7 -1
  164. package/corpus/templates/content/server/db/schema.ts +30 -0
  165. package/corpus/templates/content/server/plugins/db.ts +87 -0
  166. package/corpus/templates/content/shared/api.ts +7 -0
  167. package/corpus/templates/content/shared/properties.ts +109 -0
  168. package/corpus/templates/design/CHANGELOG.md +10 -0
  169. package/corpus/templates/design/actions/generate-design.ts +4 -0
  170. package/corpus/templates/design/app/root.tsx +7 -1
  171. package/corpus/templates/design/app/routes/settings.tsx +10 -2
  172. package/corpus/templates/dispatch/CHANGELOG.md +10 -0
  173. package/corpus/templates/dispatch/app/root.tsx +7 -1
  174. package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +10 -0
  175. package/corpus/templates/forms/CHANGELOG.md +10 -0
  176. package/corpus/templates/forms/actions/export-responses.ts +6 -0
  177. package/corpus/templates/forms/actions/list-responses.ts +2 -0
  178. package/corpus/templates/forms/actions/response-insights.ts +2 -0
  179. package/corpus/templates/forms/app/pages/ResponsesPage.tsx +138 -1
  180. package/corpus/templates/forms/app/root.tsx +7 -1
  181. package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-the-page-each-submission-came-from-.md +6 -0
  182. package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-whether-feedback-came-from-the-web-.md +6 -0
  183. package/corpus/templates/forms/server/db/schema.ts +7 -0
  184. package/corpus/templates/forms/server/handlers/submissions.ts +19 -2
  185. package/corpus/templates/forms/server/lib/integrations.ts +76 -2
  186. package/corpus/templates/forms/server/plugins/db.ts +20 -0
  187. package/corpus/templates/forms/shared/types.ts +12 -0
  188. package/corpus/templates/macros/CHANGELOG.md +10 -0
  189. package/corpus/templates/macros/app/root.tsx +7 -1
  190. package/corpus/templates/mail/CHANGELOG.md +10 -0
  191. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +3 -0
  192. package/corpus/templates/mail/app/pages/SettingsPage.tsx +27 -0
  193. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +31 -17
  194. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -4
  195. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +34 -24
  196. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -4
  197. package/corpus/templates/plan/AGENTS.md +4 -0
  198. package/corpus/templates/plan/CHANGELOG.md +10 -0
  199. package/corpus/templates/plan/actions/view-screen.ts +5 -0
  200. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +98 -1
  201. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +3 -1
  202. package/corpus/templates/plan/app/pages/PlansPage.tsx +107 -7
  203. package/corpus/templates/plan/app/root.tsx +7 -1
  204. package/corpus/templates/plan/changelog/2026-06-23-large-diagrams-in-a-plan-now-scroll-within-their-block-inste.md +6 -0
  205. package/corpus/templates/plan/shared/share-attribution.ts +72 -0
  206. package/corpus/templates/slides/CHANGELOG.md +10 -0
  207. package/corpus/templates/slides/app/root.tsx +7 -1
  208. package/corpus/templates/videos/CHANGELOG.md +4 -151
  209. package/corpus/templates/videos/app/root.tsx +7 -1
  210. package/dist/action.js +87 -0
  211. package/dist/action.js.map +1 -1
  212. package/dist/agent/engine/builder-engine.d.ts +1 -1
  213. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  214. package/dist/agent/model-config.d.ts +2 -2
  215. package/dist/agent/model-config.d.ts.map +1 -1
  216. package/dist/agent/model-config.js +0 -2
  217. package/dist/agent/model-config.js.map +1 -1
  218. package/dist/agent/run-loop-with-resume.d.ts +13 -0
  219. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  220. package/dist/agent/run-loop-with-resume.js +44 -0
  221. package/dist/agent/run-loop-with-resume.js.map +1 -1
  222. package/dist/changelog/parse.d.ts +70 -0
  223. package/dist/changelog/parse.d.ts.map +1 -0
  224. package/dist/changelog/parse.js +200 -0
  225. package/dist/changelog/parse.js.map +1 -0
  226. package/dist/cli/changelog.d.ts +4 -0
  227. package/dist/cli/changelog.d.ts.map +1 -0
  228. package/dist/cli/changelog.js +166 -0
  229. package/dist/cli/changelog.js.map +1 -0
  230. package/dist/cli/index.js +18 -0
  231. package/dist/cli/index.js.map +1 -1
  232. package/dist/cli/recap.d.ts.map +1 -1
  233. package/dist/cli/recap.js +22 -1
  234. package/dist/cli/recap.js.map +1 -1
  235. package/dist/cli/skills.d.ts +3 -3
  236. package/dist/cli/skills.d.ts.map +1 -1
  237. package/dist/cli/skills.js +74 -47
  238. package/dist/cli/skills.js.map +1 -1
  239. package/dist/client/AgentPanel.d.ts.map +1 -1
  240. package/dist/client/AgentPanel.js +5 -3
  241. package/dist/client/AgentPanel.js.map +1 -1
  242. package/dist/client/AssistantChat.d.ts +6 -1
  243. package/dist/client/AssistantChat.d.ts.map +1 -1
  244. package/dist/client/AssistantChat.js +2 -2
  245. package/dist/client/AssistantChat.js.map +1 -1
  246. package/dist/client/CommandMenu.d.ts +17 -1
  247. package/dist/client/CommandMenu.d.ts.map +1 -1
  248. package/dist/client/CommandMenu.js +49 -13
  249. package/dist/client/CommandMenu.js.map +1 -1
  250. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  251. package/dist/client/agent-chat-adapter.js +43 -6
  252. package/dist/client/agent-chat-adapter.js.map +1 -1
  253. package/dist/client/analytics.d.ts +18 -0
  254. package/dist/client/analytics.d.ts.map +1 -1
  255. package/dist/client/analytics.js +177 -0
  256. package/dist/client/analytics.js.map +1 -1
  257. package/dist/client/changelog/Changelog.d.ts +47 -0
  258. package/dist/client/changelog/Changelog.d.ts.map +1 -0
  259. package/dist/client/changelog/Changelog.js +138 -0
  260. package/dist/client/changelog/Changelog.js.map +1 -0
  261. package/dist/client/client-surface.d.ts +17 -0
  262. package/dist/client/client-surface.d.ts.map +1 -0
  263. package/dist/client/client-surface.js +24 -0
  264. package/dist/client/client-surface.js.map +1 -0
  265. package/dist/client/composer/TiptapComposer.d.ts +26 -1
  266. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  267. package/dist/client/composer/TiptapComposer.js +27 -5
  268. package/dist/client/composer/TiptapComposer.js.map +1 -1
  269. package/dist/client/feedback-context.d.ts +3 -0
  270. package/dist/client/feedback-context.d.ts.map +1 -1
  271. package/dist/client/feedback-context.js +2 -0
  272. package/dist/client/feedback-context.js.map +1 -1
  273. package/dist/client/index.d.ts +3 -1
  274. package/dist/client/index.d.ts.map +1 -1
  275. package/dist/client/index.js +3 -1
  276. package/dist/client/index.js.map +1 -1
  277. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  278. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  279. package/dist/client/mcp-apps/McpAppRenderer.js +42 -1
  280. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  281. package/dist/client/settings/useBuilderStatus.d.ts +2 -0
  282. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  283. package/dist/client/settings/useBuilderStatus.js +58 -10
  284. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  285. package/dist/client/sse-event-processor.d.ts.map +1 -1
  286. package/dist/client/sse-event-processor.js +7 -1
  287. package/dist/client/sse-event-processor.js.map +1 -1
  288. package/dist/mcp/build-server.d.ts +21 -0
  289. package/dist/mcp/build-server.d.ts.map +1 -1
  290. package/dist/mcp/build-server.js +43 -1
  291. package/dist/mcp/build-server.js.map +1 -1
  292. package/dist/mcp/embed-app.d.ts.map +1 -1
  293. package/dist/mcp/embed-app.js +197 -7
  294. package/dist/mcp/embed-app.js.map +1 -1
  295. package/dist/mcp/oauth-route.d.ts.map +1 -1
  296. package/dist/mcp/oauth-route.js +95 -12
  297. package/dist/mcp/oauth-route.js.map +1 -1
  298. package/dist/server/attribution.d.ts +83 -0
  299. package/dist/server/attribution.d.ts.map +1 -0
  300. package/dist/server/attribution.js +178 -0
  301. package/dist/server/attribution.js.map +1 -0
  302. package/dist/server/auth.d.ts.map +1 -1
  303. package/dist/server/auth.js +7 -0
  304. package/dist/server/auth.js.map +1 -1
  305. package/dist/server/better-auth-instance.d.ts +9 -1
  306. package/dist/server/better-auth-instance.d.ts.map +1 -1
  307. package/dist/server/better-auth-instance.js +32 -2
  308. package/dist/server/better-auth-instance.js.map +1 -1
  309. package/dist/server/builder-browser.d.ts +4 -0
  310. package/dist/server/builder-browser.d.ts.map +1 -1
  311. package/dist/server/builder-browser.js +20 -1
  312. package/dist/server/builder-browser.js.map +1 -1
  313. package/dist/server/core-routes-plugin.d.ts +25 -0
  314. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  315. package/dist/server/core-routes-plugin.js +101 -0
  316. package/dist/server/core-routes-plugin.js.map +1 -1
  317. package/dist/server/google-oauth.d.ts.map +1 -1
  318. package/dist/server/google-oauth.js +16 -2
  319. package/dist/server/google-oauth.js.map +1 -1
  320. package/dist/server/onboarding-html.d.ts.map +1 -1
  321. package/dist/server/onboarding-html.js +35 -0
  322. package/dist/server/onboarding-html.js.map +1 -1
  323. package/dist/shared/mcp-embed-headers.js +1 -1
  324. package/dist/shared/mcp-embed-headers.js.map +1 -1
  325. package/dist/styles/agent-conversation.css +21 -0
  326. package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  327. package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  328. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  329. package/docs/design/durable-agent-runs.md +217 -0
  330. package/package.json +1 -1
  331. package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  332. package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  333. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  334. package/corpus/templates/analytics/app/pages/About.tsx +0 -108
  335. package/corpus/templates/analytics/app/routes/about.tsx +0 -9
  336. /package/corpus/templates/analytics/app/routes/{dashboards.tsx → dashboards._index.tsx} +0 -0
@@ -67,6 +67,8 @@ export function embedApp(
67
67
  .title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 700; color: color-mix(in srgb, CanvasText 72%, Canvas); }
68
68
  .actions { display: flex; align-items: center; gap: 6px; }
69
69
  button { min-height: 28px; border: 1px solid color-mix(in srgb, CanvasText 14%, Canvas); border-radius: 7px; background: Canvas; color: CanvasText; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 0 9px; }
70
+ button.primary { min-height: 32px; padding: 0 14px; background: CanvasText; color: Canvas; border-color: CanvasText; }
71
+ button.primary:hover:not(:disabled) { background: color-mix(in srgb, CanvasText 86%, Canvas); }
70
72
  button:disabled { opacity: .55; cursor: default; }
71
73
  .stage { position: relative; min-height: var(--agent-native-viewport-height); }
72
74
  iframe { display: block; width: 100%; height: var(--agent-native-viewport-height); border: 0; background: Canvas; }
@@ -455,6 +457,49 @@ export function embedApp(
455
457
  };
456
458
  }
457
459
 
460
+ function externalOpenUrlForAppUrl(appUrl) {
461
+ if (openUrl) return openUrl;
462
+ try {
463
+ const url = new URL(appUrl.href);
464
+ url.searchParams.delete("embedded");
465
+ url.searchParams.delete("__an_embed_token");
466
+ url.searchParams.delete(chatBridgeParam);
467
+ return url.toString();
468
+ } catch (_err) {
469
+ return "";
470
+ }
471
+ }
472
+
473
+ function installExternalOpenControl(appUrl) {
474
+ const href = externalOpenUrlForAppUrl(appUrl);
475
+ if (!href || !document.body || document.getElementById("agent-native-external-open-control")) {
476
+ return;
477
+ }
478
+ const host = document.createElement("div");
479
+ host.id = "agent-native-external-open-control";
480
+ host.style.position = "fixed";
481
+ host.style.top = "10px";
482
+ host.style.right = "10px";
483
+ host.style.zIndex = "2147483647";
484
+ host.style.pointerEvents = "auto";
485
+ const root = host.attachShadow ? host.attachShadow({ mode: "open" }) : host;
486
+ root.innerHTML =
487
+ '<style>' +
488
+ ':host{all:initial;}' +
489
+ 'button{box-sizing:border-box;min-height:30px;border:1px solid color-mix(in srgb, CanvasText 18%, Canvas);border-radius:7px;background:Canvas;color:CanvasText;box-shadow:0 6px 22px color-mix(in srgb, CanvasText 14%, transparent);cursor:pointer;font:700 12px ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:0 10px;white-space:nowrap;}' +
490
+ 'button:hover{background:color-mix(in srgb, CanvasText 6%, Canvas);}' +
491
+ '</style>' +
492
+ '<button type="button">Open in new tab</button>';
493
+ const button = root.querySelector("button");
494
+ if (button) {
495
+ button.addEventListener("click", (event) => {
496
+ event.preventDefault();
497
+ void openHostLink({ url: href });
498
+ });
499
+ }
500
+ document.body.appendChild(host);
501
+ }
502
+
458
503
  function installReactRefreshPreambleFallback() {
459
504
  window.__vite_plugin_react_preamble_installed__ = true;
460
505
  if (typeof window.$RefreshReg$ !== "function") {
@@ -795,6 +840,7 @@ export function embedApp(
795
840
  base.href = config.baseHref;
796
841
  document.head.prepend(base);
797
842
  importChildren(parsed.body, document.body);
843
+ installExternalOpenControl(appUrl);
798
844
  for (const script of scripts) {
799
845
  if (isRunnableClassicScript(script)) runClassicScript(script);
800
846
  }
@@ -851,9 +897,21 @@ export function embedApp(
851
897
  });
852
898
  if (!response.ok) {
853
899
  if (response.status === 401 && isEmbedStartUrl(src)) {
900
+ reportEmbedError(
901
+ "transplant-auth",
902
+ "Embedded app session expired (HTTP 401).",
903
+ "Opaque-origin sandbox could not authenticate the embed.",
904
+ 401
905
+ );
854
906
  refreshExpiredEmbedSession();
855
907
  return;
856
908
  }
909
+ reportEmbedError(
910
+ "transplant-http",
911
+ "Embedded app returned HTTP " + response.status + ".",
912
+ undefined,
913
+ response.status
914
+ );
857
915
  throw new Error("Embedded app returned HTTP " + response.status + ".");
858
916
  }
859
917
  const html = await response.text();
@@ -954,6 +1012,52 @@ export function embedApp(
954
1012
  }, frameReadyTimeoutMs);
955
1013
  }
956
1014
 
1015
+ // Best-effort telemetry sink so inline-embed failures are visible in
1016
+ // Sentry (handshake timeout, transplant fetch status/CORS, auth, CSP).
1017
+ // The shell is a sandboxed opaque-origin iframe, so it POSTs to the app
1018
+ // origin's CORS-open /_agent-native/mcp/embed-error route, derived from the
1019
+ // known app URLs. Deduped per stage+message so a retry loop can't spam.
1020
+ const reportedEmbedErrorKeys = new Set();
1021
+ let embedErrorReportCount = 0;
1022
+ function embedReportOrigin() {
1023
+ for (const value of [openStartUrl, openUrl, lastFrameSrc]) {
1024
+ if (typeof value === "string" && value) {
1025
+ try { return new URL(value, window.location.href).origin; } catch {}
1026
+ }
1027
+ }
1028
+ return "";
1029
+ }
1030
+ function reportEmbedError(stage, message, detail, status) {
1031
+ try {
1032
+ const key = String(stage) + "|" + String(message || "");
1033
+ if (reportedEmbedErrorKeys.has(key) || embedErrorReportCount >= 8) return;
1034
+ reportedEmbedErrorKeys.add(key);
1035
+ embedErrorReportCount += 1;
1036
+ const origin = embedReportOrigin();
1037
+ if (!origin) return;
1038
+ let renderMode = "";
1039
+ try { renderMode = renderModeSource().mode || ""; } catch {}
1040
+ const body = JSON.stringify({
1041
+ stage: String(stage || ""),
1042
+ message: String(message || "").slice(0, 500),
1043
+ detail: detail ? String(detail).slice(0, 1200) : undefined,
1044
+ url: openStartUrl || openUrl || lastFrameSrc || "",
1045
+ status: typeof status === "number" ? status : undefined,
1046
+ host: window.location.hostname || "",
1047
+ renderMode: renderMode,
1048
+ bridge: openAiBridge ? "openai" : app ? "native" : "none",
1049
+ userAgent: navigator.userAgent || ""
1050
+ });
1051
+ fetch(origin + "/_agent-native/mcp/embed-error", {
1052
+ method: "POST",
1053
+ credentials: "omit",
1054
+ keepalive: true,
1055
+ headers: { "Content-Type": "application/json" },
1056
+ body: body
1057
+ }).catch(() => {});
1058
+ } catch {}
1059
+ }
1060
+
957
1061
  function renderFrameFallback() {
958
1062
  clearFrameReadyTimer();
959
1063
  clearFrameLoadTimer();
@@ -961,12 +1065,13 @@ export function embedApp(
961
1065
  const fallbackCopy = openUrl
962
1066
  ? "This chat host did not allow the embedded app frame to load inline. You can still open the same app route through the host or use the URL below."
963
1067
  : "This chat host did not allow the embedded app frame to load inline.";
1068
+ reportEmbedError("frame-fallback", fallbackCopy);
964
1069
  stage.innerHTML =
965
1070
  '<div class="fallback">' +
966
1071
  '<div class="fallback-title">Open this app in its own tab</div>' +
967
1072
  '<div class="fallback-copy">' + esc(fallbackCopy) + '</div>' +
968
1073
  '<div class="fallback-actions">' +
969
- '<button type="button" data-fallback-open>Open app</button>' +
1074
+ '<button type="button" class="primary" data-fallback-open>Open in new tab</button>' +
970
1075
  '<button type="button" data-fallback-retry>Try inline again</button>' +
971
1076
  '</div>' +
972
1077
  (openUrl ? '<a class="fallback-url" href="' + esc(openUrl) + '" target="_blank" rel="noreferrer">' + esc(openUrl) + '</a>' : '') +
@@ -987,6 +1092,43 @@ export function embedApp(
987
1092
  }
988
1093
  }
989
1094
 
1095
+ function renderAppLaunchError(message) {
1096
+ clearFrameReadyTimer();
1097
+ clearFrameLoadTimer();
1098
+ appFrame = null;
1099
+ const fallbackCopy = openUrl
1100
+ ? "The inline MCP app could not load in this chat host. You can open the same app route in a new tab or retry the inline load."
1101
+ : "The inline MCP app could not load in this chat host.";
1102
+ reportEmbedError("app-launch-error", message || fallbackCopy, message);
1103
+ const copyHtml = message
1104
+ ? '<div>' + esc(message) + '</div><div>' + esc(fallbackCopy) + '</div>'
1105
+ : esc(fallbackCopy);
1106
+ stage.innerHTML =
1107
+ '<div class="fallback">' +
1108
+ '<div class="fallback-title">App did not load</div>' +
1109
+ '<div class="fallback-copy">' + copyHtml + '</div>' +
1110
+ '<div class="fallback-actions">' +
1111
+ '<button type="button" class="primary" data-fallback-open>Open in new tab</button>' +
1112
+ '<button type="button" data-fallback-retry>Retry</button>' +
1113
+ '</div>' +
1114
+ (openUrl ? '<a class="fallback-url" href="' + esc(openUrl) + '" target="_blank" rel="noreferrer">' + esc(openUrl) + '</a>' : '') +
1115
+ '</div>';
1116
+ const fallbackOpen = stage.querySelector("[data-fallback-open]");
1117
+ const fallbackRetry = stage.querySelector("[data-fallback-retry]");
1118
+ if (fallbackOpen) {
1119
+ fallbackOpen.disabled = !openUrl;
1120
+ fallbackOpen.onclick = () => {
1121
+ if (openUrl) void openHostLink({ url: openUrl });
1122
+ };
1123
+ }
1124
+ if (fallbackRetry) {
1125
+ fallbackRetry.onclick = () => {
1126
+ startedFor = "";
1127
+ void launchEmbed();
1128
+ };
1129
+ }
1130
+ }
1131
+
990
1132
  async function openFallbackExternal() {
991
1133
  if (!openUrl) return;
992
1134
  let url = withChatBridgeParam(openUrl);
@@ -1051,12 +1193,29 @@ export function embedApp(
1051
1193
  return true;
1052
1194
  }
1053
1195
 
1196
+ // We have connected to a standards-track MCP Apps host (Codex, Cursor,
1197
+ // Claude over the SDK, our own renderer, …) through the postMessage
1198
+ // \`ui/*\` bridge rather than ChatGPT's \`window.openai\` global. These hosts
1199
+ // render the resource in a strict sandboxed iframe (typically
1200
+ // \`sandbox="allow-scripts"\`, opaque origin). Self-navigating that iframe to
1201
+ // the real app origin tears down the host bridge and loses the opaque-origin
1202
+ // auth context, which shows up as a permanent / flashing loading state.
1203
+ // Transplanting the app document into the shell keeps the bridge alive and
1204
+ // works under the opaque origin via embed-token auth, exactly like Claude.
1205
+ function isNativeMcpAppsBridgeHost() {
1206
+ return !!app && !openAiBridge;
1207
+ }
1208
+
1054
1209
  function shouldTransplantAppDocument() {
1055
1210
  const render = renderModeSource();
1056
1211
  const mode = render.mode;
1212
+ if (mode === "iframe" || mode === "nested" || render.frame === "iframe" || render.nested) {
1213
+ return false;
1214
+ }
1057
1215
  return (
1058
1216
  isClaudeMcpContentHost() ||
1059
1217
  isChatGptSandboxHost() ||
1218
+ isNativeMcpAppsBridgeHost() ||
1060
1219
  mode === "transplant" ||
1061
1220
  render.frame === "transplant"
1062
1221
  );
@@ -1074,7 +1233,7 @@ export function embedApp(
1074
1233
  try {
1075
1234
  const host = window.location.hostname || "";
1076
1235
  const appParam = new URL(window.location.href).searchParams.get("app");
1077
- return /^[^.]+\\.web-sandbox\\.oaiusercontent\\.com$/i.test(host) || appParam === "chatgpt";
1236
+ return /^(?:[^.]+\\.)?web-sandbox\\.oaiusercontent\\.com$/i.test(host) || appParam === "chatgpt";
1078
1237
  } catch {
1079
1238
  return false;
1080
1239
  }
@@ -1297,7 +1456,7 @@ export function embedApp(
1297
1456
  async function launchEmbed() {
1298
1457
  const launchUrl = openStartUrl || openUrl;
1299
1458
  if (!launchUrl) {
1300
- setMessage("Open link was not available.");
1459
+ renderAppLaunchError("Open link was not available.");
1301
1460
  return;
1302
1461
  }
1303
1462
  if (!wantsEmbed()) {
@@ -1328,7 +1487,7 @@ export function embedApp(
1328
1487
  const data = parseToolResult(result);
1329
1488
  if (typeof data.startUrl !== "string" || !data.startUrl) {
1330
1489
  startedFor = "";
1331
- setMessage(data.error || "This app can be opened, but not embedded from this MCP server.");
1490
+ renderAppLaunchError(data.error || "This app can be opened, but not embedded from this MCP server.");
1332
1491
  return;
1333
1492
  }
1334
1493
  const startUrl = withChatBridgeParam(data.startUrl);
@@ -1345,7 +1504,7 @@ export function embedApp(
1345
1504
  }
1346
1505
  } catch (err) {
1347
1506
  startedFor = "";
1348
- setMessage(err && err.message ? err.message : "Could not launch embedded app.");
1507
+ renderAppLaunchError(err && err.message ? err.message : "Could not launch embedded app.");
1349
1508
  }
1350
1509
  }
1351
1510
 
@@ -1395,6 +1554,9 @@ export function embedApp(
1395
1554
  try {
1396
1555
  if (new URLSearchParams(window.location.search).get("app") === "chatgpt") return true;
1397
1556
  } catch (_err) {}
1557
+ try {
1558
+ if (/^(?:[^.]+\\.)?web-sandbox\\.oaiusercontent\\.com$/i.test(window.location.hostname || "")) return true;
1559
+ } catch (_err) {}
1398
1560
  try {
1399
1561
  return /chatgpt/i.test(String(navigator.userAgent || ""));
1400
1562
  } catch (_err) {
@@ -1578,8 +1740,13 @@ export function embedApp(
1578
1740
  async openLink(params) {
1579
1741
  const url = typeof (params && params.url) === "string" ? params.url : "";
1580
1742
  if (!url) return { isError: true };
1581
- window.open(url, "_blank", "noopener,noreferrer");
1582
- return { ok: true };
1743
+ try {
1744
+ return await rpcRequest("ui/open-link", { url }, wrapperRequestTimeoutMs);
1745
+ } catch (err) {
1746
+ console.warn("[agent-native] MCP host open-link request failed", err);
1747
+ }
1748
+ const opened = window.open(url, "_blank", "noopener,noreferrer");
1749
+ return opened ? { ok: true } : { isError: true };
1583
1750
  },
1584
1751
  async requestDisplayMode(params) {
1585
1752
  return await rpcRequest("ui/request-display-mode", objectValue(params));
@@ -1615,6 +1782,7 @@ export function embedApp(
1615
1782
  return;
1616
1783
  }
1617
1784
  if (
1785
+ message.method === "ui/notifications/host-context-changed" ||
1618
1786
  message.method === "ui/notifications/host-context" ||
1619
1787
  message.method === "ui/notifications/context"
1620
1788
  ) {
@@ -1695,8 +1863,30 @@ export function embedApp(
1695
1863
  }
1696
1864
  } catch (err) {
1697
1865
  console.error("[agent-native] MCP app shell failed", err);
1866
+ reportEmbedError(
1867
+ "bridge-init",
1868
+ err && err.message ? err.message : "Could not initialize app.",
1869
+ err && err.stack ? String(err.stack) : undefined
1870
+ );
1698
1871
  setMessage(err && err.message ? err.message : "Could not initialize app.");
1699
1872
  }
1873
+
1874
+ window.addEventListener("error", (event) => {
1875
+ const err = event && event.error;
1876
+ reportEmbedError(
1877
+ "window-error",
1878
+ (err && err.message) || (event && event.message) || "Uncaught error in MCP app embed.",
1879
+ err && err.stack ? String(err.stack) : undefined
1880
+ );
1881
+ });
1882
+ window.addEventListener("unhandledrejection", (event) => {
1883
+ const reason = event && event.reason;
1884
+ reportEmbedError(
1885
+ "unhandled-rejection",
1886
+ (reason && reason.message) || String(reason || "Unhandled promise rejection in MCP app embed."),
1887
+ reason && reason.stack ? String(reason.stack) : undefined
1888
+ );
1889
+ });
1700
1890
  })();
1701
1891
  </script>
1702
1892
  </body>
@@ -423,15 +423,23 @@ function redirectWithOAuthError(params: {
423
423
  return redirect(url.toString());
424
424
  }
425
425
 
426
- function redirectWithCode(params: {
426
+ function buildCodeRedirectUrl(params: {
427
427
  redirectUri: string;
428
428
  code: string;
429
429
  state?: string;
430
- }): Response {
430
+ }): string {
431
431
  const url = new URL(params.redirectUri);
432
432
  url.searchParams.set("code", params.code);
433
433
  if (params.state) url.searchParams.set("state", params.state);
434
- return redirect(url.toString());
434
+ return url.toString();
435
+ }
436
+
437
+ function redirectWithCode(params: {
438
+ redirectUri: string;
439
+ code: string;
440
+ state?: string;
441
+ }): Response {
442
+ return redirect(buildCodeRedirectUrl(params));
435
443
  }
436
444
 
437
445
  function codeChallengeForVerifier(verifier: string): string {
@@ -530,6 +538,19 @@ function isValidCodeVerifier(value: unknown): value is string {
530
538
  );
531
539
  }
532
540
 
541
+ // Shared styling for the browser-facing OAuth pages (consent + post-authorize
542
+ // confirmation) so they read as one coherent dark surface.
543
+ const OAUTH_PAGE_BASE_STYLE = `
544
+ :root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #09090b; color: #f4f4f5; }
545
+ body { min-height: 100vh; display: grid; place-items: center; margin: 0; padding: 24px; }
546
+ main { width: min(520px, 100%); border: 1px solid #27272a; border-radius: 8px; background: #111113; padding: 24px; box-shadow: 0 24px 80px rgba(0,0,0,.35); }
547
+ h1 { font-size: 22px; line-height: 1.2; margin: 0 0 10px; }
548
+ p { color: #a1a1aa; line-height: 1.5; margin: 0 0 18px; }
549
+ .actions { display: flex; gap: 10px; justify-content: flex-end; }
550
+ button, .btn { border: 0; border-radius: 6px; padding: 10px 14px; font: inherit; font-weight: 650; cursor: pointer; text-decoration: none; display: inline-block; }
551
+ .primary { background: #f4f4f5; color: #09090b; }
552
+ .secondary { background: #27272a; color: #f4f4f5; }`;
553
+
533
554
  function renderConsentPage(params: {
534
555
  appName: string;
535
556
  email: string;
@@ -552,18 +573,9 @@ function renderConsentPage(params: {
552
573
  <meta charset="utf-8">
553
574
  <meta name="viewport" content="width=device-width, initial-scale=1">
554
575
  <title>Authorize ${escapeHtml(params.appName)}</title>
555
- <style>
556
- :root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #09090b; color: #f4f4f5; }
557
- body { min-height: 100vh; display: grid; place-items: center; margin: 0; padding: 24px; }
558
- main { width: min(520px, 100%); border: 1px solid #27272a; border-radius: 8px; background: #111113; padding: 24px; box-shadow: 0 24px 80px rgba(0,0,0,.35); }
559
- h1 { font-size: 22px; line-height: 1.2; margin: 0 0 10px; }
560
- p { color: #a1a1aa; line-height: 1.5; margin: 0 0 18px; }
576
+ <style>${OAUTH_PAGE_BASE_STYLE}
561
577
  ul { margin: 0 0 22px; padding-left: 22px; color: #d4d4d8; }
562
578
  code { color: #67e8f9; }
563
- .actions { display: flex; gap: 10px; justify-content: flex-end; }
564
- button { border: 0; border-radius: 6px; padding: 10px 14px; font-weight: 650; cursor: pointer; }
565
- .primary { background: #f4f4f5; color: #09090b; }
566
- .secondary { background: #27272a; color: #f4f4f5; }
567
579
  </style>
568
580
  </head>
569
581
  <body>
@@ -583,6 +595,61 @@ function renderConsentPage(params: {
583
595
  </html>`;
584
596
  }
585
597
 
598
+ // Shown after the user approves a native/desktop client (cursor://, vscode://, …)
599
+ // whose redirect is a private-use scheme. A bare 302 to a custom scheme hands the
600
+ // code to the OS app but leaves the browser tab dangling on a blank/error page, so
601
+ // we render a friendly confirmation that also re-fires the deep link (so the client
602
+ // still receives the code) and tells the user they can return to their agent.
603
+ function renderAuthorizedPage(params: {
604
+ appName: string;
605
+ clientName: string | null;
606
+ redirectUrl: string;
607
+ }): string {
608
+ const friendlyClient = params.clientName?.trim() || null;
609
+ const returnTarget = friendlyClient ?? "your agent";
610
+ const openLabel = friendlyClient
611
+ ? `Open ${escapeHtml(friendlyClient)}`
612
+ : "Return to your agent";
613
+ return `<!doctype html>
614
+ <html lang="en">
615
+ <head>
616
+ <meta charset="utf-8">
617
+ <meta name="viewport" content="width=device-width, initial-scale=1">
618
+ <title>Authorized</title>
619
+ <style>${OAUTH_PAGE_BASE_STYLE}
620
+ main.success { text-align: center; }
621
+ .check { width: 56px; height: 56px; margin: 4px auto 16px; display: grid; place-items: center; border-radius: 999px; background: rgba(34,197,94,.12); }
622
+ .check svg { width: 30px; height: 30px; }
623
+ .success .actions { justify-content: center; margin-top: 4px; }
624
+ .hint { color: #71717a; font-size: 13px; margin: 18px 0 0; }
625
+ </style>
626
+ </head>
627
+ <body>
628
+ <main class="success">
629
+ <div class="check">
630
+ <svg viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
631
+ <path d="M20 6 9 17l-5-5"></path>
632
+ </svg>
633
+ </div>
634
+ <h1>You're all set</h1>
635
+ <p>${escapeHtml(params.appName)} has authorized ${escapeHtml(returnTarget)}. You can return to ${escapeHtml(returnTarget)} to continue.</p>
636
+ <div class="actions">
637
+ <a class="btn primary" id="return-link" href="${escapeHtml(params.redirectUrl)}">${openLabel}</a>
638
+ </div>
639
+ <p class="hint">You can close this tab.</p>
640
+ </main>
641
+ <script>
642
+ (function () {
643
+ var link = document.getElementById("return-link");
644
+ if (link && link.href) {
645
+ try { window.location.href = link.href; } catch (e) {}
646
+ }
647
+ })();
648
+ </script>
649
+ </body>
650
+ </html>`;
651
+ }
652
+
586
653
  async function resolveOrgDomain(
587
654
  orgId: string | undefined,
588
655
  ): Promise<string | undefined> {
@@ -744,6 +811,35 @@ async function handleAuthorize(
744
811
  scope,
745
812
  resource,
746
813
  });
814
+
815
+ // Native/desktop clients register a private-use scheme (cursor://, vscode://, …).
816
+ // A 302 to that scheme opens the app but leaves the browser tab dangling, so we
817
+ // render a friendly confirmation page that re-fires the deep link instead. For
818
+ // https/loopback callbacks the client (or its local server) renders its own page,
819
+ // so keep the standard redirect there.
820
+ let isDeepLinkRedirect = false;
821
+ try {
822
+ const protocol = new URL(redirectUri).protocol;
823
+ isDeepLinkRedirect =
824
+ protocol !== "http:" &&
825
+ protocol !== "https:" &&
826
+ isPrivateUseRedirectScheme(protocol);
827
+ } catch {
828
+ isDeepLinkRedirect = false;
829
+ }
830
+ if (isDeepLinkRedirect) {
831
+ return html(
832
+ renderAuthorizedPage({
833
+ appName: options.appName || options.appId || "Agent Native",
834
+ clientName: client.clientName ?? null,
835
+ redirectUrl: buildCodeRedirectUrl({
836
+ redirectUri,
837
+ state,
838
+ code: code.code,
839
+ }),
840
+ }),
841
+ );
842
+ }
747
843
  return redirectWithCode({ redirectUri, state, code: code.code });
748
844
  }
749
845