@agent-native/core 0.114.12 → 0.114.15

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 (241) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +21 -0
  3. package/corpus/core/docs/content/deployment.mdx +1 -1
  4. package/corpus/core/docs/content/frames.mdx +1 -1
  5. package/corpus/core/docs/content/integrations.mdx +27 -27
  6. package/corpus/core/docs/content/pr-visual-recap.mdx +17 -13
  7. package/corpus/core/docs/content/workspace-management.mdx +1 -1
  8. package/corpus/core/package.json +2 -1
  9. package/corpus/core/src/agent/engine/builder-engine.ts +4 -0
  10. package/corpus/core/src/application-state/index.ts +3 -0
  11. package/corpus/core/src/application-state/script-helpers.ts +12 -1
  12. package/corpus/core/src/application-state/store.ts +216 -15
  13. package/corpus/core/src/client/AgentPanel.tsx +10 -4
  14. package/corpus/core/src/client/ConnectBuilderCard.tsx +5 -1
  15. package/corpus/core/src/client/chat/markdown-renderer.tsx +3 -5
  16. package/corpus/core/src/client/error-format.ts +2 -1
  17. package/corpus/core/src/client/index.ts +1 -0
  18. package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +2 -18
  19. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +43 -32
  20. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +4 -2
  21. package/corpus/core/src/client/settings/BackgroundAgentSection.tsx +5 -1
  22. package/corpus/core/src/client/settings/SettingsPanel.tsx +4 -4
  23. package/corpus/core/src/client/settings/useBuilderStatus.ts +3 -0
  24. package/corpus/core/src/db/client.ts +44 -1
  25. package/corpus/core/src/localization/default-messages.ts +1 -7
  26. package/corpus/core/src/server/builder-browser.ts +4 -0
  27. package/corpus/core/src/server/builder-design-systems.ts +6 -1
  28. package/corpus/core/src/server/fusion-app.ts +5 -1
  29. package/corpus/core/src/shared/builder-link-tracking.ts +41 -0
  30. package/corpus/core/src/shared/index.ts +1 -0
  31. package/corpus/templates/analytics/AGENTS.md +10 -1
  32. package/corpus/templates/analytics/actions/save-dashboard-report-subscription.ts +10 -2
  33. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +5 -4
  34. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +20 -32
  35. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-panel-window.ts +62 -0
  36. package/corpus/templates/analytics/changelog/2026-07-21-chart-series-controls-stay-open-while-moving-from-the-legend.md +6 -0
  37. package/corpus/templates/analytics/changelog/2026-07-21-dashboard-email-captures-complete-large-dashboards-in-chunks.md +6 -0
  38. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +4 -4
  39. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +1 -8
  40. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +39 -7
  41. package/corpus/templates/analytics/server/lib/dashboard-report.ts +341 -251
  42. package/corpus/templates/analytics/server/lib/first-party-dashboard-repair.ts +114 -0
  43. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +84 -6
  44. package/corpus/templates/analytics/server/plugins/db.ts +13 -0
  45. package/corpus/templates/clips/app/components/player/video-player.tsx +10 -4
  46. package/corpus/templates/clips/changelog/2026-07-21-choose-which-whisper-model-to-use-in-settings.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-07-21-fixed-organization-recording-visibility-default.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-21-live-dictation-text-now-appears-above-the-desktop-recording-.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-21-meeting-notes-stop-when-calls-end.md +6 -0
  50. package/corpus/templates/clips/desktop/src/app.tsx +66 -75
  51. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +2 -0
  52. package/corpus/templates/clips/desktop/src/hooks/useWhisperSettings.ts +172 -0
  53. package/corpus/templates/clips/desktop/src/lib/meeting-call-app.ts +41 -0
  54. package/corpus/templates/clips/desktop/src/lib/silence-events.ts +1 -0
  55. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +14 -3
  56. package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +25 -0
  57. package/corpus/templates/clips/desktop/src/shared/config.ts +1 -0
  58. package/corpus/templates/clips/desktop/src/styles.css +100 -7
  59. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +3 -4
  60. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +32 -0
  61. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
  62. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +11 -4
  63. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +268 -20
  64. package/corpus/templates/clips/desktop/src-tauri/src/whisper_model.rs +285 -164
  65. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +18 -7
  66. package/corpus/templates/clips/learnings.defaults.md +1 -1
  67. package/corpus/templates/clips/server/plugins/db.ts +11 -0
  68. package/corpus/templates/clips/shared/builder-credits.ts +1 -1
  69. package/corpus/templates/design/AGENTS.md +7 -3
  70. package/corpus/templates/design/actions/begin-node-rewrite-request.ts +78 -0
  71. package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +17 -7
  72. package/corpus/templates/design/actions/propose-node-rewrite.ts +26 -18
  73. package/corpus/templates/design/actions/resolve-node-rewrite.ts +67 -19
  74. package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +9 -1
  75. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +5 -1
  76. package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +1 -4
  77. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +2 -6
  78. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +9 -1
  79. package/corpus/templates/design/shared/node-rewrite.ts +32 -0
  80. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +6 -3
  81. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +11 -18
  82. package/corpus/templates/forms/app/pages/FormsListPage.tsx +9 -5
  83. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +9 -1
  84. package/corpus/templates/slides/app/context/DeckContext.tsx +18 -0
  85. package/corpus/templates/slides/changelog/2026-07-21-decks-list-now-refreshes-when-you-switch-organizations.md +6 -0
  86. package/corpus/templates/tasks/app/components/custom-fields/FieldCreateBar.tsx +13 -19
  87. package/corpus/templates/tasks/app/components/custom-fields/FieldListRow.tsx +27 -22
  88. package/corpus/templates/tasks/app/components/custom-fields/FieldsList.tsx +26 -22
  89. package/corpus/templates/tasks/app/components/custom-fields/FieldsListSkeleton.tsx +4 -1
  90. package/corpus/templates/tasks/app/components/custom-fields/FieldsPage.tsx +4 -2
  91. package/corpus/templates/tasks/app/components/custom-fields/editor/FieldEditorSidebar.tsx +12 -3
  92. package/corpus/templates/tasks/app/components/custom-fields/editor/FieldTitleSection.tsx +6 -3
  93. package/corpus/templates/tasks/app/components/custom-fields/editor/config/CurrencyConfigControl.tsx +5 -2
  94. package/corpus/templates/tasks/app/components/custom-fields/editor/config/NumberConfigControl.tsx +5 -2
  95. package/corpus/templates/tasks/app/components/custom-fields/editor/config/PercentConfigControl.tsx +4 -1
  96. package/corpus/templates/tasks/app/components/custom-fields/editor/config/SelectConfigControl.tsx +17 -7
  97. package/corpus/templates/tasks/app/components/custom-fields/editor/config/select-colors.ts +37 -9
  98. package/corpus/templates/tasks/app/components/custom-fields/field-types.ts +23 -0
  99. package/corpus/templates/tasks/app/components/inbox/InboxList.tsx +24 -16
  100. package/corpus/templates/tasks/app/components/inbox/InboxListPage.tsx +5 -3
  101. package/corpus/templates/tasks/app/components/inbox/InboxListRow.tsx +7 -5
  102. package/corpus/templates/tasks/app/components/layout/Header.tsx +10 -10
  103. package/corpus/templates/tasks/app/components/layout/Layout.tsx +11 -7
  104. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +17 -10
  105. package/corpus/templates/tasks/app/components/shared/AddListItemInput.tsx +14 -8
  106. package/corpus/templates/tasks/app/components/shared/AlertDialog.tsx +6 -2
  107. package/corpus/templates/tasks/app/components/shared/BulkDeleteDialog.tsx +13 -6
  108. package/corpus/templates/tasks/app/components/shared/ChipSelect.tsx +12 -5
  109. package/corpus/templates/tasks/app/components/shared/DeleteItemDialog.tsx +6 -6
  110. package/corpus/templates/tasks/app/components/shared/ListViewHeader.tsx +3 -1
  111. package/corpus/templates/tasks/app/components/shared/SidePanel.tsx +5 -2
  112. package/corpus/templates/tasks/app/components/shared/list/ListRowDragHandle.tsx +3 -1
  113. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionBar.tsx +46 -20
  114. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionHeaderToggle.tsx +3 -1
  115. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionToolbar.tsx +10 -6
  116. package/corpus/templates/tasks/app/components/tasks/TaskList.tsx +24 -13
  117. package/corpus/templates/tasks/app/components/tasks/TaskListHeaderRow.tsx +4 -1
  118. package/corpus/templates/tasks/app/components/tasks/TaskListPage.tsx +5 -3
  119. package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +15 -6
  120. package/corpus/templates/tasks/app/components/tasks/fields/TaskFieldsSidebar.tsx +11 -6
  121. package/corpus/templates/tasks/app/components/tasks/fields/TaskTitleSection.tsx +8 -3
  122. package/corpus/templates/tasks/app/components/tasks/fields/controls/RichTextValueControl.tsx +12 -6
  123. package/corpus/templates/tasks/app/components/tasks/fields/controls/SingleSelectValueControl.tsx +6 -1
  124. package/corpus/templates/tasks/app/i18n/en-US.ts +209 -0
  125. package/corpus/templates/tasks/app/i18n/index.ts +34 -0
  126. package/corpus/templates/tasks/app/root.tsx +34 -2
  127. package/corpus/templates/tasks/app/routes/extensions.$id.tsx +2 -1
  128. package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -1
  129. package/corpus/templates/tasks/app/routes/fields.tsx +2 -1
  130. package/corpus/templates/tasks/app/routes/inbox.tsx +2 -1
  131. package/corpus/templates/tasks/app/routes/tasks.tsx +2 -1
  132. package/corpus/templates/tasks/app/routes/team.tsx +6 -3
  133. package/corpus/templates/tasks/changelog/2026-07-20-tasks-is-now-available-in-10-additional-languages.md +6 -0
  134. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  135. package/dist/agent/engine/builder-engine.js +4 -0
  136. package/dist/agent/engine/builder-engine.js.map +1 -1
  137. package/dist/application-state/index.d.ts +2 -2
  138. package/dist/application-state/index.d.ts.map +1 -1
  139. package/dist/application-state/index.js +2 -2
  140. package/dist/application-state/index.js.map +1 -1
  141. package/dist/application-state/script-helpers.d.ts +3 -1
  142. package/dist/application-state/script-helpers.d.ts.map +1 -1
  143. package/dist/application-state/script-helpers.js +7 -1
  144. package/dist/application-state/script-helpers.js.map +1 -1
  145. package/dist/application-state/store.d.ts +7 -1
  146. package/dist/application-state/store.d.ts.map +1 -1
  147. package/dist/application-state/store.js +143 -17
  148. package/dist/application-state/store.js.map +1 -1
  149. package/dist/client/AgentPanel.d.ts.map +1 -1
  150. package/dist/client/AgentPanel.js +10 -3
  151. package/dist/client/AgentPanel.js.map +1 -1
  152. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  153. package/dist/client/ConnectBuilderCard.js +5 -1
  154. package/dist/client/ConnectBuilderCard.js.map +1 -1
  155. package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
  156. package/dist/client/chat/markdown-renderer.js +3 -2
  157. package/dist/client/chat/markdown-renderer.js.map +1 -1
  158. package/dist/client/error-format.d.ts +1 -1
  159. package/dist/client/error-format.d.ts.map +1 -1
  160. package/dist/client/error-format.js +1 -1
  161. package/dist/client/error-format.js.map +1 -1
  162. package/dist/client/index.d.ts +1 -0
  163. package/dist/client/index.d.ts.map +1 -1
  164. package/dist/client/index.js +1 -0
  165. package/dist/client/index.js.map +1 -1
  166. package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
  167. package/dist/client/integrations/IntegrationsPanel.js +3 -4
  168. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  169. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  170. package/dist/client/resources/McpIntegrationDialog.js +21 -7
  171. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  172. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  173. package/dist/client/resources/mcp-integration-catalog.js +2 -2
  174. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  175. package/dist/client/settings/BackgroundAgentSection.d.ts.map +1 -1
  176. package/dist/client/settings/BackgroundAgentSection.js +5 -1
  177. package/dist/client/settings/BackgroundAgentSection.js.map +1 -1
  178. package/dist/client/settings/SettingsPanel.js +1 -1
  179. package/dist/client/settings/SettingsPanel.js.map +1 -1
  180. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  181. package/dist/client/settings/useBuilderStatus.js +2 -0
  182. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  183. package/dist/collab/routes.d.ts +1 -1
  184. package/dist/db/client.d.ts +7 -0
  185. package/dist/db/client.d.ts.map +1 -1
  186. package/dist/db/client.js +39 -1
  187. package/dist/db/client.js.map +1 -1
  188. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  189. package/dist/localization/default-messages.d.ts +0 -6
  190. package/dist/localization/default-messages.d.ts.map +1 -1
  191. package/dist/localization/default-messages.js +1 -7
  192. package/dist/localization/default-messages.js.map +1 -1
  193. package/dist/notifications/routes.d.ts +2 -2
  194. package/dist/observability/routes.d.ts +3 -3
  195. package/dist/resources/handlers.d.ts +1 -1
  196. package/dist/server/builder-browser.d.ts.map +1 -1
  197. package/dist/server/builder-browser.js +4 -0
  198. package/dist/server/builder-browser.js.map +1 -1
  199. package/dist/server/builder-design-systems.d.ts.map +1 -1
  200. package/dist/server/builder-design-systems.js +6 -1
  201. package/dist/server/builder-design-systems.js.map +1 -1
  202. package/dist/server/fusion-app.d.ts.map +1 -1
  203. package/dist/server/fusion-app.js +2 -1
  204. package/dist/server/fusion-app.js.map +1 -1
  205. package/dist/server/transcribe-voice.d.ts +1 -1
  206. package/dist/shared/builder-link-tracking.d.ts +14 -0
  207. package/dist/shared/builder-link-tracking.d.ts.map +1 -0
  208. package/dist/shared/builder-link-tracking.js +26 -0
  209. package/dist/shared/builder-link-tracking.js.map +1 -0
  210. package/dist/shared/index.d.ts +1 -0
  211. package/dist/shared/index.d.ts.map +1 -1
  212. package/dist/shared/index.js +1 -0
  213. package/dist/shared/index.js.map +1 -1
  214. package/docs/content/deployment.mdx +1 -1
  215. package/docs/content/frames.mdx +1 -1
  216. package/docs/content/integrations.mdx +27 -27
  217. package/docs/content/pr-visual-recap.mdx +17 -13
  218. package/docs/content/workspace-management.mdx +1 -1
  219. package/package.json +3 -2
  220. package/src/agent/engine/builder-engine.ts +4 -0
  221. package/src/application-state/index.ts +3 -0
  222. package/src/application-state/script-helpers.ts +12 -1
  223. package/src/application-state/store.ts +216 -15
  224. package/src/client/AgentPanel.tsx +10 -4
  225. package/src/client/ConnectBuilderCard.tsx +5 -1
  226. package/src/client/chat/markdown-renderer.tsx +3 -5
  227. package/src/client/error-format.ts +2 -1
  228. package/src/client/index.ts +1 -0
  229. package/src/client/integrations/IntegrationsPanel.tsx +2 -18
  230. package/src/client/resources/McpIntegrationDialog.tsx +43 -32
  231. package/src/client/resources/mcp-integration-catalog.ts +4 -2
  232. package/src/client/settings/BackgroundAgentSection.tsx +5 -1
  233. package/src/client/settings/SettingsPanel.tsx +4 -4
  234. package/src/client/settings/useBuilderStatus.ts +3 -0
  235. package/src/db/client.ts +44 -1
  236. package/src/localization/default-messages.ts +1 -7
  237. package/src/server/builder-browser.ts +4 -0
  238. package/src/server/builder-design-systems.ts +6 -1
  239. package/src/server/fusion-app.ts +5 -1
  240. package/src/shared/builder-link-tracking.ts +41 -0
  241. package/src/shared/index.ts +1 -0
package/corpus/README.md CHANGED
@@ -28,6 +28,6 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
 
29
29
  ## Generated Counts
30
30
 
31
- - core files: 1519
31
+ - core files: 1520
32
32
  - toolkit files: 137
33
- - template files: 6178
33
+ - template files: 6194
@@ -1,5 +1,26 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.114.15
4
+
5
+ ### Patch Changes
6
+
7
+ - f8fe58b: Add `VISUAL_RECAP_REQUIRED_LABELS` so PR Visual Recap can run only when a pull request has an opt-in label.
8
+ - Updated dependencies [f8fe58b]
9
+ - @agent-native/recap-cli@0.5.0
10
+
11
+ ## 0.114.14
12
+
13
+ ### Patch Changes
14
+
15
+ - 8df32f6: Improve the integrations picker by removing dead discovery controls, clearly showing the available catalog size, and simplifying provider setup guidance.
16
+ - 8df32f6: Add UTM attribution to Builder.io links opened from Agent Native.
17
+
18
+ ## 0.114.13
19
+
20
+ ### Patch Changes
21
+
22
+ - 7112c17: Add portable transactional multi-key application-state compare-and-set operations, including atomic D1 batch support and missing-key creation for race-safe UI workflows.
23
+
3
24
  ## 0.114.12
4
25
 
5
26
  ### Patch Changes
@@ -567,7 +567,7 @@ In a standard production deployment with [production code execution](#production
567
567
 
568
568
  ### Builder.io: Visual Editing in Production {#builderio}
569
569
 
570
- [Builder.io](https://www.builder.io) solves this by providing a managed cloud environment where the agent retains the ability to modify your app's UI in production. Connect your repo to Builder.io and prompt for UI changes directly — no redeploy needed.
570
+ [Builder.io](https://www.builder.io?utm_source=agent-native&utm_medium=docs&utm_campaign=framework&utm_content=deployment) solves this by providing a managed cloud environment where the agent retains the ability to modify your app's UI in production. Connect your repo to Builder.io and prompt for UI changes directly — no redeploy needed.
571
571
 
572
572
  **How it works:**
573
573
 
@@ -186,7 +186,7 @@ time.
186
186
 
187
187
  ## Builder.io cloud frame {#cloud-frame}
188
188
 
189
- [Builder.io](https://www.builder.io) provides a managed frame that hosts the
189
+ [Builder.io](https://www.builder.io?utm_source=agent-native&utm_medium=docs&utm_campaign=framework&utm_content=frames) provides a managed frame that hosts the
190
190
  same app and the same agent panel, in the cloud:
191
191
 
192
192
  - Real-time collaboration — multiple users can watch and interact at once.
@@ -322,33 +322,33 @@ endpoint is reachable or cataloged, but a live provider authorization or tool
322
322
  session still needs to be completed. **Restricted** entries remain discoverable
323
323
  but need provider or administrator setup before a generic connection can work.
324
324
 
325
- | Integration | Connection mode | Status | Endpoint | Setup and notes |
326
- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | ---------------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
327
- | [Context7](https://context7.com/) | No auth | Ready · verified | `https://mcp.context7.com/mcp` | Fetch current library and framework docs. |
328
- | [Sentry](https://docs.sentry.io/product/sentry-mcp/) | Header / token | Ready · preflight only | `https://mcp.sentry.dev/mcp` | Add a Sentry bearer token as the `Authorization` header. |
329
- | [Notion](https://developers.notion.com/guides/mcp/get-started-with-mcp) | OAuth | Ready · preflight only | `https://mcp.notion.com/mcp` | User OAuth; enterprise admins can audit or restrict MCP clients. |
330
- | [Semgrep](https://github.com/semgrep/mcp#readme) | No auth | Ready · preflight only | `https://mcp.semgrep.ai/mcp` | Scan code for security findings. |
331
- | [Linear](https://linear.app/docs/mcp) | OAuth | Ready · preflight only | `https://mcp.linear.app/mcp` | Authorize the workspace and issue permissions required by the workflow. |
332
- | [Atlassian Rovo MCP](https://developer.atlassian.com/cloud/rovo-mcp/guides/getting-started/) | OAuth + provider setup | Restricted | `https://mcp.atlassian.com/v1/mcp/authv2` | Admins may need to trust the app origin and enable Rovo Read, Write, and Search permissions. |
333
- | [Supabase](https://www.builder.io/c/docs/fusion-connect-to-supabase) | OAuth | Ready · preflight only | `https://mcp.supabase.com/mcp` | Authorize the Supabase project and requested backend scopes. |
334
- | [Neon](https://www.builder.io/c/docs/fusion-connect-to-neon) | OAuth | Ready · preflight only | `https://mcp.neon.tech/sse` | Authorize access to the serverless Postgres projects needed by the app. |
335
- | [Stripe](https://docs.stripe.com/mcp) | OAuth | Ready · preflight only | `https://mcp.stripe.com` | Review payment, customer, and subscription permissions before authorizing. |
336
- | [Cloudflare](https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/) | OAuth | Ready · preflight only | `https://mcp.cloudflare.com/mcp` | Choose the narrowest Cloudflare server and review scopes. |
337
- | [GitLab](https://docs.gitlab.com/user/model_context_protocol/mcp_server/) | OAuth + provider setup | Beta · restricted | `https://gitlab.com/api/v4/mcp` | A top-level group admin, or the equivalent self-managed instance admin, must allow MCP. |
338
- | [Figma](https://developers.figma.com/docs/figma-mcp-server/) | Manual setup | Approved clients only · restricted | `https://mcp.figma.com/mcp` | Figma only allows clients in its MCP Catalog. Use the [Figma REST API personal access token fallback](https://developers.figma.com/docs/rest-api/personal-access-tokens/) for file and node reads until Agent Native is approved. |
339
- | [Canva](https://www.canva.dev/docs/mcp/) | Manual setup | Approved domains · restricted | `https://mcp.canva.com/mcp` | Per-user OAuth; clients must allow Canva's canva.com and canva.ai domains and follow the current redirect setup. |
340
- | [Vercel](https://vercel.com/docs/agent-resources/vercel-mcp) | Manual setup | Approved clients only · restricted | `https://mcp.vercel.com` | Vercel must list the client as approved before a generic connection will work. |
341
- | [GitHub](https://github.com/github/github-mcp-server) | Manual setup | Provider setup · restricted | `https://api.githubcopilot.com/mcp/` | Use a registered OAuth app; organizations may also enforce OAuth App Access Policies. |
342
- | [Slack](https://docs.slack.dev/ai/slack-mcp-server/) | Manual setup | Approved clients only · restricted | `https://mcp.slack.com/mcp` | Requires a fixed registered Slack app; dynamic client registration is not supported. |
343
- | [Asana](https://developers.asana.com/docs/integrating-with-asanas-mcp-server) | Manual setup | Provider setup · restricted | `https://mcp.asana.com/v2/mcp` | Pre-register an Asana MCP OAuth app before connecting. |
344
- | [HubSpot](https://developers.hubspot.com/docs/apps/developer-platform/build-apps/integrate-with-the-remote-hubspot-mcp-server) | Manual setup | Provider setup · restricted | `https://mcp.hubspot.com` | Create a HubSpot MCP Auth App with PKCE; the existing HubSpot OAuth connector remains available to app actions. |
345
- | [Intercom](https://developers.intercom.com/docs/guides/mcp) | OAuth | Ready · preflight only | `https://mcp.intercom.com/mcp` | Available for US-hosted workspaces; confirm region and scopes during authorization. |
346
- | [monday.com](https://developer.monday.com/api-reference/docs/build-on-monday-with-ai) | OAuth | Ready · preflight only | `https://mcp.monday.com/mcp` | Choose the workspace and permissions to share during authorization. |
347
- | [Webflow](https://developers.webflow.com/mcp/reference/getting-started) | OAuth | Ready · preflight only | `https://mcp.webflow.com/mcp` | Designer capabilities may install Webflow's Bridge App; Data API access is separate. |
348
- | [PayPal](https://developer.paypal.com/ai-tools/mcp-server/) | OAuth | Ready · preflight only | `https://mcp.paypal.com/sse` | Review merchant permissions before authorizing the remote server. |
349
- | [Box](https://developer.box.com/guides/box-mcp) | Manual setup | Beta · restricted | `https://mcp.box.com` | An administrator must enable Box MCP; custom clients also need credentials, a redirect URI, and approved scopes. |
350
- | [Netlify](https://docs.netlify.com/build/build-with-ai/agent-setup-guides/agent-setup-overview/) | OAuth | Ready · preflight only | `https://netlify-mcp.netlify.app/mcp` | Review site and team permissions before completing OAuth. |
351
- | [Zapier MCP](https://help.zapier.com/hc/en-us/articles/36265392843917-Use-Zapier-MCP-with-your-client) | Header / token | User-created server · restricted | `https://mcp.zapier.com/api/v1/connect` | Create the MCP server in Zapier, then paste its generated bearer token into the header field. |
325
+ | Integration | Connection mode | Status | Endpoint | Setup and notes |
326
+ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---------------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
327
+ | [Context7](https://context7.com/) | No auth | Ready · verified | `https://mcp.context7.com/mcp` | Fetch current library and framework docs. |
328
+ | [Sentry](https://docs.sentry.io/product/sentry-mcp/) | Header / token | Ready · preflight only | `https://mcp.sentry.dev/mcp` | Add a Sentry bearer token as the `Authorization` header. |
329
+ | [Notion](https://developers.notion.com/guides/mcp/get-started-with-mcp) | OAuth | Ready · preflight only | `https://mcp.notion.com/mcp` | User OAuth; enterprise admins can audit or restrict MCP clients. |
330
+ | [Semgrep](https://github.com/semgrep/mcp#readme) | No auth | Ready · preflight only | `https://mcp.semgrep.ai/mcp` | Scan code for security findings. |
331
+ | [Linear](https://linear.app/docs/mcp) | OAuth | Ready · preflight only | `https://mcp.linear.app/mcp` | Authorize the workspace and issue permissions required by the workflow. |
332
+ | [Atlassian Rovo MCP](https://developer.atlassian.com/cloud/rovo-mcp/guides/getting-started/) | OAuth + provider setup | Restricted | `https://mcp.atlassian.com/v1/mcp/authv2` | Admins may need to trust the app origin and enable Rovo Read, Write, and Search permissions. |
333
+ | [Supabase](https://www.builder.io/c/docs/fusion-connect-to-supabase?utm_source=agent-native&utm_medium=docs&utm_campaign=framework&utm_content=fusion_connect_supabase) | OAuth | Ready · preflight only | `https://mcp.supabase.com/mcp` | Authorize the Supabase project and requested backend scopes. |
334
+ | [Neon](https://www.builder.io/c/docs/fusion-connect-to-neon?utm_source=agent-native&utm_medium=docs&utm_campaign=framework&utm_content=fusion_connect_neon) | OAuth | Ready · preflight only | `https://mcp.neon.tech/sse` | Authorize access to the serverless Postgres projects needed by the app. |
335
+ | [Stripe](https://docs.stripe.com/mcp) | OAuth | Ready · preflight only | `https://mcp.stripe.com` | Review payment, customer, and subscription permissions before authorizing. |
336
+ | [Cloudflare](https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/) | OAuth | Ready · preflight only | `https://mcp.cloudflare.com/mcp` | Choose the narrowest Cloudflare server and review scopes. |
337
+ | [GitLab](https://docs.gitlab.com/user/model_context_protocol/mcp_server/) | OAuth + provider setup | Beta · restricted | `https://gitlab.com/api/v4/mcp` | A top-level group admin, or the equivalent self-managed instance admin, must allow MCP. |
338
+ | [Figma](https://developers.figma.com/docs/figma-mcp-server/) | Manual setup | Approved clients only · restricted | `https://mcp.figma.com/mcp` | Figma only allows clients in its MCP Catalog. Use the [Figma REST API personal access token fallback](https://developers.figma.com/docs/rest-api/personal-access-tokens/) for file and node reads until Agent Native is approved. |
339
+ | [Canva](https://www.canva.dev/docs/mcp/) | Manual setup | Approved domains · restricted | `https://mcp.canva.com/mcp` | Per-user OAuth; clients must allow Canva's canva.com and canva.ai domains and follow the current redirect setup. |
340
+ | [Vercel](https://vercel.com/docs/agent-resources/vercel-mcp) | Manual setup | Approved clients only · restricted | `https://mcp.vercel.com` | Vercel must list the client as approved before a generic connection will work. |
341
+ | [GitHub](https://github.com/github/github-mcp-server) | Manual setup | Provider setup · restricted | `https://api.githubcopilot.com/mcp/` | Use a registered OAuth app; organizations may also enforce OAuth App Access Policies. |
342
+ | [Slack](https://docs.slack.dev/ai/slack-mcp-server/) | Manual setup | Approved clients only · restricted | `https://mcp.slack.com/mcp` | Requires a fixed registered Slack app; dynamic client registration is not supported. |
343
+ | [Asana](https://developers.asana.com/docs/integrating-with-asanas-mcp-server) | Manual setup | Provider setup · restricted | `https://mcp.asana.com/v2/mcp` | Pre-register an Asana MCP OAuth app before connecting. |
344
+ | [HubSpot](https://developers.hubspot.com/docs/apps/developer-platform/build-apps/integrate-with-the-remote-hubspot-mcp-server) | Manual setup | Provider setup · restricted | `https://mcp.hubspot.com` | Create a HubSpot MCP Auth App with PKCE; the existing HubSpot OAuth connector remains available to app actions. |
345
+ | [Intercom](https://developers.intercom.com/docs/guides/mcp) | OAuth | Ready · preflight only | `https://mcp.intercom.com/mcp` | Available for US-hosted workspaces; confirm region and scopes during authorization. |
346
+ | [monday.com](https://developer.monday.com/api-reference/docs/build-on-monday-with-ai) | OAuth | Ready · preflight only | `https://mcp.monday.com/mcp` | Choose the workspace and permissions to share during authorization. |
347
+ | [Webflow](https://developers.webflow.com/mcp/reference/getting-started) | OAuth | Ready · preflight only | `https://mcp.webflow.com/mcp` | Designer capabilities may install Webflow's Bridge App; Data API access is separate. |
348
+ | [PayPal](https://developer.paypal.com/ai-tools/mcp-server/) | OAuth | Ready · preflight only | `https://mcp.paypal.com/sse` | Review merchant permissions before authorizing the remote server. |
349
+ | [Box](https://developer.box.com/guides/box-mcp) | Manual setup | Beta · restricted | `https://mcp.box.com` | An administrator must enable Box MCP; custom clients also need credentials, a redirect URI, and approved scopes. |
350
+ | [Netlify](https://docs.netlify.com/build/build-with-ai/agent-setup-guides/agent-setup-overview/) | OAuth | Ready · preflight only | `https://netlify-mcp.netlify.app/mcp` | Review site and team permissions before completing OAuth. |
351
+ | [Zapier MCP](https://help.zapier.com/hc/en-us/articles/36265392843917-Use-Zapier-MCP-with-your-client) | Header / token | User-created server · restricted | `https://mcp.zapier.com/api/v1/connect` | Create the MCP server in Zapier, then paste its generated bearer token into the header field. |
352
352
 
353
353
  ### Repeatable preflight verification
354
354
 
@@ -209,7 +209,8 @@ To reduce cost:
209
209
  - **Pin a cheaper model.** For Claude, set `VISUAL_RECAP_MODEL=claude-haiku-4-5` for simple or small repos — it's the cheapest Claude tier; `claude-sonnet-5` (the default) is the balanced tier.
210
210
  - **Switch to Codex with a cheaper model.** Set `VISUAL_RECAP_AGENT=codex` with `VISUAL_RECAP_MODEL=gpt-5.6-luna` (the cheapest GPT-5.6 tier) or `gpt-5.6-terra` (mid-tier), and set `VISUAL_RECAP_REASONING=low` to cut reasoning-token spend further. Reasoning depth only applies to Codex.
211
211
  - **Point at a cheaper OpenAI-compatible provider.** Set `VISUAL_RECAP_AGENT=openai-compatible` with `VISUAL_RECAP_BASE_URL`/`VISUAL_RECAP_MODEL`/`VISUAL_RECAP_API_KEY` targeting DeepSeek, Kimi, or any other OpenAI-compatible endpoint.
212
- - **Recap less often.** Edit the installed workflow's `on.pull_request.types` from `[opened, synchronize, reopened, ready_for_review, closed]` to `[opened, ready_for_review, closed]` so it recaps only when a PR opens and when it merges, instead of on every push.
212
+ - **Require an explicit PR label.** Set `VISUAL_RECAP_REQUIRED_LABELS=visual recap` to keep the workflow installed but skip recaps until a PR has that label. Use a comma-separated list, such as `visual recap,recap`, when several labels should opt in. Applying a listed label wakes the workflow immediately.
213
+ - **Recap less often.** Edit the installed workflow's `on.pull_request.types` from `[opened, synchronize, reopened, ready_for_review, labeled, closed]` to `[opened, ready_for_review, closed]` so it recaps only when a PR opens and when it merges, instead of on every push.
213
214
 
214
215
  Every run records what it actually spent: the workflow's `recap usage` step attaches token and cost usage to the published plan, so you can look at real spend per run instead of guessing.
215
216
 
@@ -256,16 +257,17 @@ real token.
256
257
 
257
258
  ### Optional (only if you change defaults)
258
259
 
259
- | Secret / variable | Default | When you need it |
260
- | ------------------------ | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
261
- | `OPENAI_API_KEY` | — | Secret. Set together with `VISUAL_RECAP_AGENT=codex` to run the recap with Codex instead. |
262
- | `VISUAL_RECAP_API_KEY` | — | Secret. Set with `VISUAL_RECAP_AGENT=openai-compatible` for DeepSeek, Kimi, or another OpenAI-compatible provider. |
263
- | `VISUAL_RECAP_AGENT` | `claude` | Variable. Selects the coding-agent backend (`claude`, `codex`, or `openai-compatible`). |
264
- | `VISUAL_RECAP_BASE_URL` | required for compatible backend | Variable. HTTP(S) base URL for the OpenAI-compatible backend; credentials are rejected. |
265
- | `VISUAL_RECAP_MODEL` | `claude-sonnet-5` for Claude; required for compatible backend | Variable. Provider model id for OpenAI-compatible backends (required there). For Claude, unset now defaults to `claude-sonnet-5` — set it to override, e.g. `claude-haiku-4-5` for the cheapest tier. For Codex, unset uses the Codex CLI's own default. |
266
- | `VISUAL_RECAP_REASONING` | each model's default | Variable. Reasoning depth: `none`, `minimal`, `low`, `medium`, `high`, or `xhigh`. Applies to the Codex backend. |
267
- | `RECAP_CLI_VERSION` | `latest` | Variable. Pins the `@agent-native/recap-cli` version the workflow installs e.g. `1.5.0`. See [Version pinning](#version-pinning-copy-variant). |
268
- | `PLAN_RECAP_APP_URL` | `https://plan.agent-native.com` | Secret. Only when self-hosting the Plans app at a different origin. |
260
+ | Secret / variable | Default | When you need it |
261
+ | ------------------------------ | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
262
+ | `OPENAI_API_KEY` | — | Secret. Set together with `VISUAL_RECAP_AGENT=codex` to run the recap with Codex instead. |
263
+ | `VISUAL_RECAP_API_KEY` | — | Secret. Set with `VISUAL_RECAP_AGENT=openai-compatible` for DeepSeek, Kimi, or another OpenAI-compatible provider. |
264
+ | `VISUAL_RECAP_AGENT` | `claude` | Variable. Selects the coding-agent backend (`claude`, `codex`, or `openai-compatible`). |
265
+ | `VISUAL_RECAP_BASE_URL` | required for compatible backend | Variable. HTTP(S) base URL for the OpenAI-compatible backend; credentials are rejected. |
266
+ | `VISUAL_RECAP_MODEL` | `claude-sonnet-5` for Claude; required for compatible backend | Variable. Provider model id for OpenAI-compatible backends (required there). For Claude, unset now defaults to `claude-sonnet-5` — set it to override, e.g. `claude-haiku-4-5` for the cheapest tier. For Codex, unset uses the Codex CLI's own default. |
267
+ | `VISUAL_RECAP_REASONING` | each model's default | Variable. Reasoning depth: `none`, `minimal`, `low`, `medium`, `high`, or `xhigh`. Applies to the Codex backend. |
268
+ | `VISUAL_RECAP_REQUIRED_LABELS` | | Variable. Comma-separated PR labels. When set, the gate skips until the PR has at least one listed label, for example `visual recap`. |
269
+ | `RECAP_CLI_VERSION` | `latest` | Variable. Pins the `@agent-native/recap-cli` version the workflow installs e.g. `1.5.0`. See [Version pinning](#version-pinning-copy-variant). |
270
+ | `PLAN_RECAP_APP_URL` | `https://plan.agent-native.com` | Secret. Only when self-hosting the Plans app at a different origin. |
269
271
 
270
272
  The workflow auto-detects how to invoke its helper CLI (local source inside this monorepo, the published `@agent-native/recap-cli` elsewhere), so there is no `RECAP_CLI` variable to set.
271
273
 
@@ -510,10 +512,11 @@ name: PR Visual Recap
510
512
 
511
513
  on:
512
514
  pull_request:
513
- types: [opened, synchronize, reopened, ready_for_review, closed]
515
+ types: [opened, synchronize, reopened, ready_for_review, labeled, closed]
514
516
 
515
517
  jobs:
516
518
  visual-recap:
519
+ if: github.event.action != 'labeled' || vars.VISUAL_RECAP_REQUIRED_LABELS != ''
517
520
  permissions:
518
521
  actions: write
519
522
  contents: read
@@ -535,10 +538,11 @@ jobs:
535
538
  skill-source: ${{ vars.VISUAL_RECAP_SKILL_SOURCE || 'auto' }}
536
539
  runs-on: ${{ vars.VISUAL_RECAP_RUNS_ON || '"ubuntu-latest"' }}
537
540
  gate-runs-on: ${{ vars.VISUAL_RECAP_GATE_RUNS_ON || 'ubuntu-latest' }}
541
+ required-labels: ${{ vars.VISUAL_RECAP_REQUIRED_LABELS || '' }}
538
542
  # cli-version: "latest" # pin to a specific @agent-native/recap-cli version
539
543
  ```
540
544
 
541
- The same secrets and variables described in [Secrets and variables](#secrets-and-variables) apply — set them in your repo settings the same way as for the copy variant. The reusable caller forwards `VISUAL_RECAP_RUNS_ON` through its JSON `runs-on` input for the full recap job and `VISUAL_RECAP_GATE_RUNS_ON` through its plain-label `gate-runs-on` input for the metadata-only gate. The latter is used only for trusted same-repository authors; other PRs use GitHub-hosted `ubuntu-latest`. Omitting either input preserves its `ubuntu-latest` default.
545
+ The same secrets and variables described in [Secrets and variables](#secrets-and-variables) apply — set them in your repo settings the same way as for the copy variant. The reusable caller forwards `VISUAL_RECAP_RUNS_ON` through its JSON `runs-on` input for the full recap job, `VISUAL_RECAP_GATE_RUNS_ON` through its plain-label `gate-runs-on` input for the metadata-only gate, and `VISUAL_RECAP_REQUIRED_LABELS` through `required-labels` for optional PR-label gating. The gate runner input is used only for trusted same-repository authors; other PRs use GitHub-hosted `ubuntu-latest`. Omitting the runner inputs preserves their `ubuntu-latest` defaults; omitting required labels preserves automatic recap behavior.
542
546
 
543
547
  ### Installing via the CLI
544
548
 
@@ -91,7 +91,7 @@ Keep branches short-lived. Long-lived branches diverge from main and create pain
91
91
 
92
92
  ### Non-Developer Branching
93
93
 
94
- Not everyone who needs to make changes is comfortable with git. [Builder.io](https://www.builder.io) supports a visual branching model that maps to git branches under the hood — useful for content and copy changes, layout adjustments, design iterations, and A/B testing without a dev environment.
94
+ Not everyone who needs to make changes is comfortable with git. [Builder.io](https://www.builder.io?utm_source=agent-native&utm_medium=docs&utm_campaign=framework&utm_content=workspace_management) supports a visual branching model that maps to git branches under the hood — useful for content and copy changes, layout adjustments, design iterations, and A/B testing without a dev environment.
95
95
 
96
96
  ## Code Ownership
97
97
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.114.12",
3
+ "version": "0.114.15",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -150,6 +150,7 @@
150
150
  "./embedding/react": "./dist/embedding/react.js",
151
151
  "./onboarding": "./dist/onboarding/index.js",
152
152
  "./shared": "./dist/shared/index.js",
153
+ "./shared/builder-link-tracking": "./dist/shared/builder-link-tracking.js",
153
154
  "./voice": "./dist/voice/index.js",
154
155
  "./scripts": "./dist/scripts/index.js",
155
156
  "./guards": {
@@ -20,6 +20,7 @@ import {
20
20
  getBuilderGatewayBaseUrl,
21
21
  recordBuilderCredentialAuthFailure,
22
22
  } from "../../server/credential-provider.js";
23
+ import { applyBuilderUtmTrackingParams } from "../../shared/builder-link-tracking.js";
23
24
  import {
24
25
  normalizeReasoningEffortForModel,
25
26
  type ReasoningEffort,
@@ -112,6 +113,9 @@ async function buildUpgradeUrl(): Promise<string> {
112
113
  url.searchParams.set("agentNativeConnectSource", "gateway_quota_upgrade");
113
114
  url.searchParams.set("agentNativeFlow", "connect_llm");
114
115
  url.searchParams.set("framework", "agent-native");
116
+ applyBuilderUtmTrackingParams(url.searchParams, {
117
+ content: "gateway_quota_upgrade",
118
+ });
115
119
  return url.toString();
116
120
  }
117
121
 
@@ -5,8 +5,10 @@ export {
5
5
  appStatePut,
6
6
  appStateDelete,
7
7
  appStateCompareAndSet,
8
+ appStateCompareAndSetMany,
8
9
  appStateList,
9
10
  appStateDeleteByPrefix,
11
+ type AppStateCompareAndSetOperation,
10
12
  } from "./store.js";
11
13
 
12
14
  // Emitter (for SSE wiring)
@@ -35,6 +37,7 @@ export {
35
37
  writeAppState,
36
38
  deleteAppState,
37
39
  compareAndSetAppState,
40
+ compareAndSetManyAppState,
38
41
  listAppState,
39
42
  deleteAppStateByPrefix,
40
43
  readAppStateForCurrentTab,
@@ -18,8 +18,10 @@ import {
18
18
  appStatePut,
19
19
  appStateDelete,
20
20
  appStateCompareAndSet,
21
+ appStateCompareAndSetMany,
21
22
  appStateList,
22
23
  appStateDeleteByPrefix,
24
+ type AppStateCompareAndSetOperation,
23
25
  } from "./store.js";
24
26
 
25
27
  /**
@@ -74,7 +76,7 @@ export async function deleteAppState(key: string): Promise<boolean> {
74
76
 
75
77
  export async function compareAndSetAppState(
76
78
  key: string,
77
- expectedValue: Record<string, unknown>,
79
+ expectedValue: Record<string, unknown> | null,
78
80
  nextValue: Record<string, unknown> | null,
79
81
  ): Promise<boolean> {
80
82
  const sessionId = await resolveSessionId();
@@ -83,6 +85,15 @@ export async function compareAndSetAppState(
83
85
  });
84
86
  }
85
87
 
88
+ export async function compareAndSetManyAppState(
89
+ operations: readonly AppStateCompareAndSetOperation[],
90
+ ): Promise<boolean> {
91
+ const sessionId = await resolveSessionId();
92
+ return appStateCompareAndSetMany(sessionId, operations, {
93
+ requestSource: "agent",
94
+ });
95
+ }
96
+
86
97
  export async function listAppState(
87
98
  prefix: string,
88
99
  ): Promise<Array<{ key: string; value: Record<string, unknown> }>> {
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  getDbExec,
3
+ getDialect,
3
4
  isLocalDatabase,
4
5
  isConnectionError,
5
6
  isPostgres,
6
7
  intType,
8
+ type DbExec,
7
9
  } from "../db/client.js";
8
10
  import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
9
11
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
@@ -194,39 +196,238 @@ export async function appStateDelete(
194
196
  export async function appStateCompareAndSet(
195
197
  sessionId: string,
196
198
  key: string,
197
- expectedValue: Record<string, unknown>,
199
+ expectedValue: Record<string, unknown> | null,
198
200
  nextValue: Record<string, unknown> | null,
199
201
  options?: StoreWriteOptions,
200
202
  ): Promise<boolean> {
201
203
  await ensureTable();
202
204
  const client = getDbExec();
205
+ const changed = await executeAppStateCompareAndSet(
206
+ client,
207
+ sessionId,
208
+ key,
209
+ expectedValue,
210
+ nextValue,
211
+ );
212
+ if (changed) {
213
+ if (nextValue === null) {
214
+ emitAppStateDelete(key, options?.requestSource, sessionId);
215
+ } else {
216
+ emitAppStateChange(key, options?.requestSource, sessionId);
217
+ }
218
+ }
219
+ return changed;
220
+ }
221
+
222
+ export interface AppStateCompareAndSetOperation {
223
+ key: string;
224
+ expectedValue: Record<string, unknown> | null;
225
+ nextValue: Record<string, unknown> | null;
226
+ }
227
+
228
+ const APP_STATE_CAS_MISMATCH = Symbol("app-state-cas-mismatch");
229
+
230
+ async function executeAppStateCompareAndSet(
231
+ client: DbExec,
232
+ sessionId: string,
233
+ key: string,
234
+ expectedValue: Record<string, unknown> | null,
235
+ nextValue: Record<string, unknown> | null,
236
+ ): Promise<boolean> {
237
+ const statement = buildAppStateCompareAndSetStatement(
238
+ sessionId,
239
+ key,
240
+ expectedValue,
241
+ nextValue,
242
+ );
243
+ const result = await client.execute(statement);
244
+ return result.rowsAffected > 0;
245
+ }
246
+
247
+ function buildAppStateCompareAndSetStatement(
248
+ sessionId: string,
249
+ key: string,
250
+ expectedValue: Record<string, unknown> | null,
251
+ nextValue: Record<string, unknown> | null,
252
+ ): { sql: string; args: unknown[] } {
253
+ if (expectedValue === null) {
254
+ if (nextValue === null) {
255
+ throw new Error(
256
+ "Application state CAS cannot replace absence with absence.",
257
+ );
258
+ }
259
+ const next = serializeAppStateValue(key, nextValue);
260
+ return {
261
+ sql: isPostgres()
262
+ ? `INSERT INTO application_state (session_id, key, value, updated_at) SELECT ?, ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ?) ON CONFLICT (session_id, key) DO NOTHING`
263
+ : `INSERT OR IGNORE INTO application_state (session_id, key, value, updated_at) SELECT ?, ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ?)`,
264
+ args: [sessionId, key, next, Date.now(), sessionId, key],
265
+ };
266
+ }
267
+
203
268
  const expected = JSON.stringify(expectedValue);
204
269
  if (nextValue === null) {
205
- const result = await client.execute({
270
+ return {
206
271
  sql: `DELETE FROM application_state WHERE session_id = ? AND key = ? AND value = ?`,
207
272
  args: [sessionId, key, expected],
208
- });
209
- const changed = result.rowsAffected > 0;
210
- if (changed) emitAppStateDelete(key, options?.requestSource, sessionId);
211
- return changed;
273
+ };
212
274
  }
213
275
 
214
- const next = JSON.stringify(nextValue);
276
+ const next = serializeAppStateValue(key, nextValue);
277
+ return {
278
+ sql: `UPDATE application_state SET value = ?, updated_at = ? WHERE session_id = ? AND key = ? AND value = ?`,
279
+ args: [next, Date.now(), sessionId, key, expected],
280
+ };
281
+ }
282
+
283
+ function buildD1CompareAndSetGuard(
284
+ sessionId: string,
285
+ guardKey: string,
286
+ operation: AppStateCompareAndSetOperation,
287
+ ): { sql: string; args: unknown[] } {
288
+ const expected = operation.expectedValue;
289
+ const condition =
290
+ expected === null
291
+ ? "NOT EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ?)"
292
+ : "EXISTS (SELECT 1 FROM application_state WHERE session_id = ? AND key = ? AND value = ?)";
293
+ return {
294
+ sql: `INSERT INTO application_state (session_id, key, value, updated_at) SELECT ?, ?, '{}', ? WHERE NOT (${condition})`,
295
+ args:
296
+ expected === null
297
+ ? [sessionId, guardKey, Date.now(), sessionId, operation.key]
298
+ : [
299
+ sessionId,
300
+ guardKey,
301
+ Date.now(),
302
+ sessionId,
303
+ operation.key,
304
+ JSON.stringify(expected),
305
+ ],
306
+ };
307
+ }
308
+
309
+ function isD1CompareAndSetMismatch(error: unknown): boolean {
310
+ const message = error instanceof Error ? error.message : String(error);
311
+ return /unique constraint failed:\s*application_state\.session_id,\s*application_state\.key/i.test(
312
+ message,
313
+ );
314
+ }
315
+
316
+ function serializeAppStateValue(
317
+ key: string,
318
+ value: Record<string, unknown>,
319
+ ): string {
320
+ const serialized = JSON.stringify(value);
215
321
  if (
216
322
  !isLocalDatabase() &&
217
- Buffer.byteLength(next, "utf8") > MAX_HOSTED_APP_STATE_VALUE_BYTES
323
+ Buffer.byteLength(serialized, "utf8") > MAX_HOSTED_APP_STATE_VALUE_BYTES
218
324
  ) {
219
325
  throw new Error(
220
326
  `application_state value "${key}" is too large for hosted SQL storage. Store large files, base64, or blobs in file storage and write only a URL or handle.`,
221
327
  );
222
328
  }
223
- const result = await client.execute({
224
- sql: `UPDATE application_state SET value = ?, updated_at = ? WHERE session_id = ? AND key = ? AND value = ?`,
225
- args: [next, Date.now(), sessionId, key, expected],
226
- });
227
- const changed = result.rowsAffected > 0;
228
- if (changed) emitAppStateChange(key, options?.requestSource, sessionId);
229
- return changed;
329
+ return serialized;
330
+ }
331
+
332
+ export async function appStateCompareAndSetMany(
333
+ sessionId: string,
334
+ operations: readonly AppStateCompareAndSetOperation[],
335
+ options?: StoreWriteOptions,
336
+ ): Promise<boolean> {
337
+ if (operations.length === 0) return true;
338
+ const keys = new Set(operations.map(({ key }) => key));
339
+ if (keys.size !== operations.length) {
340
+ throw new Error("Application state multi-key CAS requires unique keys.");
341
+ }
342
+ for (const { key, nextValue } of operations) {
343
+ if (nextValue) serializeAppStateValue(key, nextValue);
344
+ }
345
+ const orderedOperations = [...operations].sort((a, b) =>
346
+ a.key.localeCompare(b.key),
347
+ );
348
+
349
+ await ensureTable();
350
+ const client = getDbExec();
351
+ if (getDialect() === "d1") {
352
+ if (!client.atomicBatch) {
353
+ throw new Error(
354
+ "D1 application-state CAS requires atomic batch support.",
355
+ );
356
+ }
357
+ const guardKey = `__agent_native_cas_guard__:${Date.now()}:${Math.random().toString(36).slice(2)}`;
358
+ const guardInsert = {
359
+ sql: `INSERT INTO application_state (session_id, key, value, updated_at) VALUES (?, ?, '{}', ?)`,
360
+ args: [sessionId, guardKey, Date.now()],
361
+ };
362
+ const guards = orderedOperations.map((operation) =>
363
+ buildD1CompareAndSetGuard(sessionId, guardKey, operation),
364
+ );
365
+ const mutations = orderedOperations.map((operation) =>
366
+ buildAppStateCompareAndSetStatement(
367
+ sessionId,
368
+ operation.key,
369
+ operation.expectedValue,
370
+ operation.nextValue,
371
+ ),
372
+ );
373
+ let results;
374
+ try {
375
+ results = await client.atomicBatch([
376
+ guardInsert,
377
+ ...guards,
378
+ ...mutations,
379
+ {
380
+ sql: `DELETE FROM application_state WHERE session_id = ? AND key = ?`,
381
+ args: [sessionId, guardKey],
382
+ },
383
+ ]);
384
+ } catch (error) {
385
+ if (isD1CompareAndSetMismatch(error)) return false;
386
+ throw error;
387
+ }
388
+ const mutationResults = results.slice(
389
+ 1 + guards.length,
390
+ 1 + guards.length + mutations.length,
391
+ );
392
+ if (
393
+ mutationResults.length !== mutations.length ||
394
+ mutationResults.some((result) => result.rowsAffected !== 1)
395
+ ) {
396
+ throw new Error(
397
+ "D1 application-state CAS completed without applying every mutation.",
398
+ );
399
+ }
400
+ } else {
401
+ if (!client.transaction) {
402
+ throw new Error("Application state multi-key CAS requires transactions.");
403
+ }
404
+ try {
405
+ await client.transaction(async (tx) => {
406
+ for (const operation of orderedOperations) {
407
+ const changed = await executeAppStateCompareAndSet(
408
+ tx,
409
+ sessionId,
410
+ operation.key,
411
+ operation.expectedValue,
412
+ operation.nextValue,
413
+ );
414
+ if (!changed) throw APP_STATE_CAS_MISMATCH;
415
+ }
416
+ });
417
+ } catch (error) {
418
+ if (error === APP_STATE_CAS_MISMATCH) return false;
419
+ throw error;
420
+ }
421
+ }
422
+
423
+ for (const { key, nextValue } of operations) {
424
+ if (nextValue === null) {
425
+ emitAppStateDelete(key, options?.requestSource, sessionId);
426
+ } else {
427
+ emitAppStateChange(key, options?.requestSource, sessionId);
428
+ }
429
+ }
430
+ return true;
230
431
  }
231
432
 
232
433
  export async function appStateList(
@@ -81,6 +81,7 @@ const MultiTabAssistantChatLazy = lazy(() =>
81
81
  import { useQuery, useQueryClient } from "@tanstack/react-query";
82
82
  import { Link, useLocation, useNavigate } from "react-router";
83
83
 
84
+ import { withBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
84
85
  import type { AgentChatSurfaceKind } from "./agent-chat-adapter.js";
85
86
  import {
86
87
  consumeAgentSidebarUrlOpenOverride,
@@ -670,14 +671,19 @@ function CodeAccessUnavailablePanel({
670
671
  compact?: boolean;
671
672
  }) {
672
673
  const { connectUrl: builderConnectUrl } = useBuilderConnectUrl();
673
- const builderHref =
674
- secondaryCtaHref ??
675
- (builderConnectUrl
674
+ const builderHref = secondaryCtaHref
675
+ ? withBuilderUtmTrackingParams(secondaryCtaHref, {
676
+ campaign: "product",
677
+ content: "code_access_unavailable_panel",
678
+ })
679
+ : builderConnectUrl
676
680
  ? withBuilderConnectTrackingParams(builderConnectUrl, {
677
681
  source: "code_access_unavailable_panel",
678
682
  flow: "background_agent",
679
683
  })
680
- : "https://builder.io");
684
+ : withBuilderUtmTrackingParams("https://builder.io", {
685
+ content: "code_access_unavailable_panel",
686
+ });
681
687
 
682
688
  return (
683
689
  <div