@agent-native/core 0.118.0 → 0.119.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +32 -0
  3. package/corpus/core/docs/content/a2a-protocol.mdx +15 -0
  4. package/corpus/core/docs/content/template-chat.mdx +1 -1
  5. package/corpus/core/docs/content/template-plan.mdx +3 -3
  6. package/corpus/core/package.json +1 -1
  7. package/corpus/core/src/a2a/activity.ts +371 -0
  8. package/corpus/core/src/a2a/client.ts +35 -11
  9. package/corpus/core/src/a2a/index.ts +21 -0
  10. package/corpus/core/src/a2a/types.ts +8 -0
  11. package/corpus/core/src/agent/model-config.ts +19 -23
  12. package/corpus/core/src/agent/production-agent.ts +167 -100
  13. package/corpus/core/src/agent/types.ts +16 -1
  14. package/corpus/core/src/cli/index.ts +18 -10
  15. package/corpus/core/src/cli/multi-frontier-runs.ts +5 -3
  16. package/corpus/core/src/cli/templates-meta.ts +0 -12
  17. package/corpus/core/src/cli/workspace-dev.ts +1 -1
  18. package/corpus/core/src/client/AgentPanel.tsx +9 -1
  19. package/corpus/core/src/client/RunStuckBanner.tsx +39 -47
  20. package/corpus/core/src/client/chat/action-chat-ui-surface.tsx +32 -0
  21. package/corpus/core/src/client/chat/message-components.tsx +53 -8
  22. package/corpus/core/src/client/chat/tool-call-display.tsx +214 -44
  23. package/corpus/core/src/client/chat/widgets/InlineExtensionWidget.tsx +0 -1
  24. package/corpus/core/src/client/chat/widgets/builtin-tool-renderers.tsx +7 -3
  25. package/corpus/core/src/client/chat-model-groups.ts +67 -27
  26. package/corpus/core/src/client/conversation/AgentConversation.tsx +12 -1
  27. package/corpus/core/src/client/error-capture.ts +2 -6
  28. package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +20 -14
  29. package/corpus/core/src/client/resources/ResourcesPanel.tsx +5 -1
  30. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +7 -17
  31. package/corpus/core/src/client/resources/mcp-integration-logos.ts +2 -2
  32. package/corpus/core/src/client/route-chunk-recovery.ts +5 -3
  33. package/corpus/core/src/client/session-replay.ts +3 -3
  34. package/corpus/core/src/client/settings/SettingsPanel.tsx +15 -6
  35. package/corpus/core/src/client/sharing/ShareButton.tsx +1 -27
  36. package/corpus/core/src/client/sharing/ShareDialog.tsx +0 -16
  37. package/corpus/core/src/client/sharing/useShareButtonController.ts +0 -7
  38. package/corpus/core/src/client/sharing/useShareDialogController.ts +0 -2
  39. package/corpus/core/src/client/sse-event-processor.ts +85 -1
  40. package/corpus/core/src/client/transcription/use-live-transcription.ts +64 -45
  41. package/corpus/core/src/deploy/build.ts +2 -1
  42. package/corpus/core/src/guards/db-tool-scoping.ts +1 -1
  43. package/corpus/core/src/integrations/adapters/slack.ts +84 -5
  44. package/corpus/core/src/integrations/index.ts +5 -1
  45. package/corpus/core/src/localization/default-messages.ts +7 -1
  46. package/corpus/core/src/mcp-client/oauth-routes.ts +45 -12
  47. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -18
  48. package/corpus/core/src/scripts/call-agent.ts +49 -6
  49. package/corpus/core/src/server/agent-chat-plugin.ts +50 -10
  50. package/corpus/core/src/server/realtime-voice.ts +4 -1
  51. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  52. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +7 -5
  53. package/corpus/core/src/terminal/cli-registry.ts +1 -1
  54. package/corpus/core/src/vite/client.ts +166 -1
  55. package/corpus/templates/analytics/app/hooks/use-dashboard-views.ts +13 -1
  56. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewsMenu.tsx +10 -0
  57. package/corpus/templates/analytics/changelog/2026-07-22-show-a-useful-error-when-saving-a-dashboard-view-fails.md +6 -0
  58. package/corpus/templates/brain/app/routes/sources.tsx +181 -114
  59. package/corpus/templates/clips/AGENTS.md +8 -6
  60. package/corpus/templates/clips/actions/request-transcript.ts +90 -420
  61. package/corpus/templates/clips/actions/save-browser-transcript.ts +11 -28
  62. package/corpus/templates/clips/app/components/capture-install-options.tsx +5 -3
  63. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +3 -136
  64. package/corpus/templates/clips/app/lib/capture-install-options.ts +15 -4
  65. package/corpus/templates/clips/app/routes/download.tsx +3 -2
  66. package/corpus/templates/clips/changelog/2026-07-22-fixed-an-intermittent-hydration-error-on-the-download-page.md +6 -0
  67. package/corpus/templates/clips/changelog/2026-07-22-fixed-transcript-generation-when-native-speech-capture-retur.md +6 -0
  68. package/corpus/templates/clips/changelog/2026-07-22-transcript-generation-now-retries-with-backup-speech-recogni.md +6 -0
  69. package/corpus/templates/clips/changelog/2026-07-23-fixed-recordings-failing-to-save-with-a-cancelled-or-cleanup.md +6 -0
  70. package/corpus/templates/clips/server/lib/public-agent-context.ts +2 -2
  71. package/corpus/templates/clips/server/plugins/db.ts +9 -1
  72. package/corpus/templates/clips/server/register-secrets.ts +7 -6
  73. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +16 -84
  74. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +13 -7
  75. package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +2 -2
  76. package/corpus/templates/content/app/i18n-data.ts +10 -0
  77. package/corpus/templates/design/server/lib/fig-file-decoder.ts +1 -0
  78. package/corpus/templates/plan/app/entry.client.tsx +3 -0
  79. package/corpus/templates/plan/changelog/2026-07-22-recover-stale-pages-after-a-deployment-refresh-automatically.md +6 -0
  80. package/corpus/templates/slides/app/hooks/use-agent-generating.ts +10 -3
  81. package/corpus/templates/slides/app/pages/Index.tsx +1 -0
  82. package/corpus/templates/slides/changelog/2026-07-22-new-deck-prompts-now-open-in-their-own-visible-chat-thread.md +6 -0
  83. package/corpus/toolkit/CHANGELOG.md +9 -0
  84. package/corpus/toolkit/package.json +1 -1
  85. package/corpus/toolkit/src/composer/PromptComposer.tsx +5 -1
  86. package/corpus/toolkit/src/composer/RealtimeVoiceMode.tsx +119 -31
  87. package/corpus/toolkit/src/composer/useRealtimeVoiceMode.tsx +30 -0
  88. package/corpus/toolkit/src/conformance/runner.tsx +153 -1
  89. package/corpus/toolkit/src/editor/SharedRichEditor.tsx +13 -2
  90. package/corpus/toolkit/src/editor/SlashCommandMenu.tsx +20 -0
  91. package/corpus/toolkit/src/editor/index.ts +2 -0
  92. package/corpus/toolkit/src/styles.css +52 -23
  93. package/dist/a2a/activity.d.ts +50 -0
  94. package/dist/a2a/activity.d.ts.map +1 -0
  95. package/dist/a2a/activity.js +236 -0
  96. package/dist/a2a/activity.js.map +1 -0
  97. package/dist/a2a/client.d.ts +5 -1
  98. package/dist/a2a/client.d.ts.map +1 -1
  99. package/dist/a2a/client.js +18 -13
  100. package/dist/a2a/client.js.map +1 -1
  101. package/dist/a2a/index.d.ts +2 -1
  102. package/dist/a2a/index.d.ts.map +1 -1
  103. package/dist/a2a/index.js +1 -0
  104. package/dist/a2a/index.js.map +1 -1
  105. package/dist/a2a/types.d.ts +1 -0
  106. package/dist/a2a/types.d.ts.map +1 -1
  107. package/dist/a2a/types.js.map +1 -1
  108. package/dist/agent/engine/anthropic-engine.d.ts +1 -1
  109. package/dist/agent/engine/builder-engine.d.ts +2 -2
  110. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  111. package/dist/agent/model-config.d.ts +16 -16
  112. package/dist/agent/model-config.d.ts.map +1 -1
  113. package/dist/agent/model-config.js +19 -23
  114. package/dist/agent/model-config.js.map +1 -1
  115. package/dist/agent/production-agent.d.ts +26 -0
  116. package/dist/agent/production-agent.d.ts.map +1 -1
  117. package/dist/agent/production-agent.js +137 -80
  118. package/dist/agent/production-agent.js.map +1 -1
  119. package/dist/agent/types.d.ts +10 -0
  120. package/dist/agent/types.d.ts.map +1 -1
  121. package/dist/agent/types.js.map +1 -1
  122. package/dist/cli/index.js +18 -8
  123. package/dist/cli/index.js.map +1 -1
  124. package/dist/cli/multi-frontier-runs.js +3 -3
  125. package/dist/cli/multi-frontier-runs.js.map +1 -1
  126. package/dist/cli/templates-meta.d.ts.map +1 -1
  127. package/dist/cli/templates-meta.js +0 -12
  128. package/dist/cli/templates-meta.js.map +1 -1
  129. package/dist/cli/workspace-dev.js +1 -1
  130. package/dist/cli/workspace-dev.js.map +1 -1
  131. package/dist/client/AgentPanel.d.ts.map +1 -1
  132. package/dist/client/AgentPanel.js +4 -1
  133. package/dist/client/AgentPanel.js.map +1 -1
  134. package/dist/client/RunStuckBanner.d.ts +3 -5
  135. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  136. package/dist/client/RunStuckBanner.js +17 -18
  137. package/dist/client/RunStuckBanner.js.map +1 -1
  138. package/dist/client/chat/action-chat-ui-surface.d.ts +8 -0
  139. package/dist/client/chat/action-chat-ui-surface.d.ts.map +1 -0
  140. package/dist/client/chat/action-chat-ui-surface.js +10 -0
  141. package/dist/client/chat/action-chat-ui-surface.js.map +1 -0
  142. package/dist/client/chat/message-components.d.ts +3 -0
  143. package/dist/client/chat/message-components.d.ts.map +1 -1
  144. package/dist/client/chat/message-components.js +40 -7
  145. package/dist/client/chat/message-components.js.map +1 -1
  146. package/dist/client/chat/tool-call-display.d.ts +3 -1
  147. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  148. package/dist/client/chat/tool-call-display.js +76 -23
  149. package/dist/client/chat/tool-call-display.js.map +1 -1
  150. package/dist/client/chat/widgets/InlineExtensionWidget.d.ts.map +1 -1
  151. package/dist/client/chat/widgets/InlineExtensionWidget.js +1 -1
  152. package/dist/client/chat/widgets/InlineExtensionWidget.js.map +1 -1
  153. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  154. package/dist/client/chat/widgets/builtin-tool-renderers.js +5 -3
  155. package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
  156. package/dist/client/chat-model-groups.d.ts.map +1 -1
  157. package/dist/client/chat-model-groups.js +44 -21
  158. package/dist/client/chat-model-groups.js.map +1 -1
  159. package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
  160. package/dist/client/conversation/AgentConversation.js +3 -2
  161. package/dist/client/conversation/AgentConversation.js.map +1 -1
  162. package/dist/client/error-capture.d.ts.map +1 -1
  163. package/dist/client/error-capture.js +2 -2
  164. package/dist/client/error-capture.js.map +1 -1
  165. package/dist/client/resources/McpConnectionSuggestion.d.ts +1 -0
  166. package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
  167. package/dist/client/resources/McpConnectionSuggestion.js +9 -8
  168. package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
  169. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  170. package/dist/client/resources/ResourcesPanel.js +3 -1
  171. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  172. package/dist/client/resources/mcp-integration-catalog.d.ts +1 -0
  173. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  174. package/dist/client/resources/mcp-integration-catalog.js +4 -17
  175. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  176. package/dist/client/resources/mcp-integration-logos.js +2 -2
  177. package/dist/client/resources/mcp-integration-logos.js.map +1 -1
  178. package/dist/client/route-chunk-recovery.d.ts.map +1 -1
  179. package/dist/client/route-chunk-recovery.js +5 -3
  180. package/dist/client/route-chunk-recovery.js.map +1 -1
  181. package/dist/client/session-replay.js +3 -3
  182. package/dist/client/session-replay.js.map +1 -1
  183. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  184. package/dist/client/settings/SettingsPanel.js +2 -2
  185. package/dist/client/settings/SettingsPanel.js.map +1 -1
  186. package/dist/client/sharing/ShareButton.d.ts +1 -1
  187. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  188. package/dist/client/sharing/ShareButton.js +3 -4
  189. package/dist/client/sharing/ShareButton.js.map +1 -1
  190. package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
  191. package/dist/client/sharing/ShareDialog.js +1 -2
  192. package/dist/client/sharing/ShareDialog.js.map +1 -1
  193. package/dist/client/sharing/useShareButtonController.d.ts +0 -1
  194. package/dist/client/sharing/useShareButtonController.d.ts.map +1 -1
  195. package/dist/client/sharing/useShareButtonController.js +0 -6
  196. package/dist/client/sharing/useShareButtonController.js.map +1 -1
  197. package/dist/client/sharing/useShareDialogController.d.ts +0 -1
  198. package/dist/client/sharing/useShareDialogController.d.ts.map +1 -1
  199. package/dist/client/sharing/useShareDialogController.js +0 -1
  200. package/dist/client/sharing/useShareDialogController.js.map +1 -1
  201. package/dist/client/sse-event-processor.d.ts +12 -0
  202. package/dist/client/sse-event-processor.d.ts.map +1 -1
  203. package/dist/client/sse-event-processor.js +63 -1
  204. package/dist/client/sse-event-processor.js.map +1 -1
  205. package/dist/client/transcription/use-live-transcription.d.ts +3 -3
  206. package/dist/client/transcription/use-live-transcription.d.ts.map +1 -1
  207. package/dist/client/transcription/use-live-transcription.js +22 -11
  208. package/dist/client/transcription/use-live-transcription.js.map +1 -1
  209. package/dist/collab/awareness.d.ts +2 -2
  210. package/dist/collab/awareness.d.ts.map +1 -1
  211. package/dist/deploy/build.d.ts.map +1 -1
  212. package/dist/deploy/build.js +1 -1
  213. package/dist/deploy/build.js.map +1 -1
  214. package/dist/guards/db-tool-scoping.js +1 -1
  215. package/dist/guards/db-tool-scoping.js.map +1 -1
  216. package/dist/integrations/adapters/slack.d.ts +1 -0
  217. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  218. package/dist/integrations/adapters/slack.js +61 -5
  219. package/dist/integrations/adapters/slack.js.map +1 -1
  220. package/dist/integrations/index.d.ts +1 -1
  221. package/dist/integrations/index.d.ts.map +1 -1
  222. package/dist/integrations/index.js +1 -1
  223. package/dist/integrations/index.js.map +1 -1
  224. package/dist/localization/default-messages.d.ts +6 -0
  225. package/dist/localization/default-messages.d.ts.map +1 -1
  226. package/dist/localization/default-messages.js +7 -1
  227. package/dist/localization/default-messages.js.map +1 -1
  228. package/dist/mcp-client/oauth-routes.d.ts +3 -0
  229. package/dist/mcp-client/oauth-routes.d.ts.map +1 -1
  230. package/dist/mcp-client/oauth-routes.js +41 -12
  231. package/dist/mcp-client/oauth-routes.js.map +1 -1
  232. package/dist/observability/routes.d.ts +3 -3
  233. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  234. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  235. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  236. package/dist/scripts/agent-engines/list-agent-engines.js +1 -18
  237. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  238. package/dist/scripts/call-agent.d.ts.map +1 -1
  239. package/dist/scripts/call-agent.js +49 -6
  240. package/dist/scripts/call-agent.js.map +1 -1
  241. package/dist/secrets/routes.d.ts +6 -6
  242. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  243. package/dist/server/agent-chat-plugin.js +38 -10
  244. package/dist/server/agent-chat-plugin.js.map +1 -1
  245. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  246. package/dist/server/realtime-voice.d.ts.map +1 -1
  247. package/dist/server/realtime-voice.js +1 -1
  248. package/dist/server/realtime-voice.js.map +1 -1
  249. package/dist/server/transcribe-voice.d.ts +1 -1
  250. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  251. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +7 -5
  252. package/dist/terminal/cli-registry.js +1 -1
  253. package/dist/terminal/cli-registry.js.map +1 -1
  254. package/dist/vite/client.d.ts +8 -1
  255. package/dist/vite/client.d.ts.map +1 -1
  256. package/dist/vite/client.js +113 -2
  257. package/dist/vite/client.js.map +1 -1
  258. package/docs/content/a2a-protocol.mdx +15 -0
  259. package/docs/content/template-chat.mdx +1 -1
  260. package/docs/content/template-plan.mdx +3 -3
  261. package/package.json +2 -2
  262. package/src/a2a/activity.ts +371 -0
  263. package/src/a2a/client.ts +35 -11
  264. package/src/a2a/index.ts +21 -0
  265. package/src/a2a/types.ts +8 -0
  266. package/src/agent/model-config.ts +19 -23
  267. package/src/agent/production-agent.ts +167 -100
  268. package/src/agent/types.ts +16 -1
  269. package/src/cli/index.ts +18 -10
  270. package/src/cli/multi-frontier-runs.ts +5 -3
  271. package/src/cli/templates-meta.ts +0 -12
  272. package/src/cli/workspace-dev.ts +1 -1
  273. package/src/client/AgentPanel.tsx +9 -1
  274. package/src/client/RunStuckBanner.tsx +39 -47
  275. package/src/client/chat/action-chat-ui-surface.tsx +32 -0
  276. package/src/client/chat/message-components.tsx +53 -8
  277. package/src/client/chat/tool-call-display.tsx +214 -44
  278. package/src/client/chat/widgets/InlineExtensionWidget.tsx +0 -1
  279. package/src/client/chat/widgets/builtin-tool-renderers.tsx +7 -3
  280. package/src/client/chat-model-groups.ts +67 -27
  281. package/src/client/conversation/AgentConversation.tsx +12 -1
  282. package/src/client/error-capture.ts +2 -6
  283. package/src/client/resources/McpConnectionSuggestion.tsx +20 -14
  284. package/src/client/resources/ResourcesPanel.tsx +5 -1
  285. package/src/client/resources/mcp-integration-catalog.ts +7 -17
  286. package/src/client/resources/mcp-integration-logos.ts +2 -2
  287. package/src/client/route-chunk-recovery.ts +5 -3
  288. package/src/client/session-replay.ts +3 -3
  289. package/src/client/settings/SettingsPanel.tsx +15 -6
  290. package/src/client/sharing/ShareButton.tsx +1 -27
  291. package/src/client/sharing/ShareDialog.tsx +0 -16
  292. package/src/client/sharing/useShareButtonController.ts +0 -7
  293. package/src/client/sharing/useShareDialogController.ts +0 -2
  294. package/src/client/sse-event-processor.ts +85 -1
  295. package/src/client/transcription/use-live-transcription.ts +64 -45
  296. package/src/deploy/build.ts +2 -1
  297. package/src/guards/db-tool-scoping.ts +1 -1
  298. package/src/integrations/adapters/slack.ts +84 -5
  299. package/src/integrations/index.ts +5 -1
  300. package/src/localization/default-messages.ts +7 -1
  301. package/src/mcp-client/oauth-routes.ts +45 -12
  302. package/src/scripts/agent-engines/list-agent-engines.ts +1 -18
  303. package/src/scripts/call-agent.ts +49 -6
  304. package/src/server/agent-chat-plugin.ts +50 -10
  305. package/src/server/realtime-voice.ts +4 -1
  306. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  307. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +7 -5
  308. package/src/terminal/cli-registry.ts +1 -1
  309. package/src/vite/client.ts +166 -1
  310. package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
  311. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +0 -115
  312. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +0 -177
  313. package/corpus/templates/crm/.agents/skills/crm/SKILL.md +0 -128
  314. package/corpus/templates/crm/.agents/skills/customizing-agent-native/SKILL.md +0 -220
  315. package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +0 -169
  316. package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +0 -100
  317. package/corpus/templates/crm/.env.example +0 -5
  318. package/corpus/templates/crm/AGENTS.md +0 -120
  319. package/corpus/templates/crm/CHANGELOG.md +0 -1
  320. package/corpus/templates/crm/DEVELOPING.md +0 -66
  321. package/corpus/templates/crm/README.md +0 -106
  322. package/corpus/templates/crm/_gitignore +0 -12
  323. package/corpus/templates/crm/actions/_crm-action-utils.ts +0 -87
  324. package/corpus/templates/crm/actions/_crm-dashboard.ts +0 -69
  325. package/corpus/templates/crm/actions/_crm-data-program-actions.ts +0 -15
  326. package/corpus/templates/crm/actions/_crm-signal-utils.ts +0 -79
  327. package/corpus/templates/crm/actions/apply-crm-proposals.ts +0 -203
  328. package/corpus/templates/crm/actions/attach-call-evidence.ts +0 -170
  329. package/corpus/templates/crm/actions/configure-crm-connection.ts +0 -126
  330. package/corpus/templates/crm/actions/configure-native-crm.ts +0 -44
  331. package/corpus/templates/crm/actions/create-crm-record.ts +0 -148
  332. package/corpus/templates/crm/actions/create-crm-signal-tracker.ts +0 -58
  333. package/corpus/templates/crm/actions/delete-staged-dataset.ts +0 -14
  334. package/corpus/templates/crm/actions/get-crm-automation-recipe.ts +0 -36
  335. package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +0 -35
  336. package/corpus/templates/crm/actions/get-crm-dashboard.ts +0 -14
  337. package/corpus/templates/crm/actions/get-crm-overview.ts +0 -13
  338. package/corpus/templates/crm/actions/get-crm-pipeline-data.ts +0 -61
  339. package/corpus/templates/crm/actions/get-crm-record.ts +0 -75
  340. package/corpus/templates/crm/actions/install-crm-pipeline-dashboard.ts +0 -81
  341. package/corpus/templates/crm/actions/list-crm-dashboard-revisions.ts +0 -15
  342. package/corpus/templates/crm/actions/list-crm-dashboards.ts +0 -15
  343. package/corpus/templates/crm/actions/list-crm-proposals.ts +0 -31
  344. package/corpus/templates/crm/actions/list-crm-records.ts +0 -79
  345. package/corpus/templates/crm/actions/list-crm-saved-views.ts +0 -15
  346. package/corpus/templates/crm/actions/list-crm-signal-hits.ts +0 -43
  347. package/corpus/templates/crm/actions/list-crm-signal-trackers.ts +0 -32
  348. package/corpus/templates/crm/actions/list-crm-tasks.ts +0 -22
  349. package/corpus/templates/crm/actions/list-staged-datasets.ts +0 -14
  350. package/corpus/templates/crm/actions/list-workspace-connections.ts +0 -47
  351. package/corpus/templates/crm/actions/manage-crm-signal-tracker.ts +0 -87
  352. package/corpus/templates/crm/actions/manage-crm-task.ts +0 -132
  353. package/corpus/templates/crm/actions/navigate.ts +0 -63
  354. package/corpus/templates/crm/actions/provider-api-catalog.ts +0 -22
  355. package/corpus/templates/crm/actions/provider-api-docs.ts +0 -54
  356. package/corpus/templates/crm/actions/provider-api-request.ts +0 -92
  357. package/corpus/templates/crm/actions/query-staged-dataset.ts +0 -44
  358. package/corpus/templates/crm/actions/record-crm-call-insight.ts +0 -160
  359. package/corpus/templates/crm/actions/record-crm-smart-signal.ts +0 -150
  360. package/corpus/templates/crm/actions/restore-crm-dashboard-revision.ts +0 -23
  361. package/corpus/templates/crm/actions/review-crm-signal.ts +0 -31
  362. package/corpus/templates/crm/actions/run-crm-saved-view-program.ts +0 -103
  363. package/corpus/templates/crm/actions/run-crm-signal-trackers.ts +0 -275
  364. package/corpus/templates/crm/actions/run.ts +0 -7
  365. package/corpus/templates/crm/actions/save-crm-dashboard.ts +0 -51
  366. package/corpus/templates/crm/actions/save-crm-saved-view.ts +0 -119
  367. package/corpus/templates/crm/actions/sync-crm.ts +0 -146
  368. package/corpus/templates/crm/actions/update-crm-record.ts +0 -510
  369. package/corpus/templates/crm/actions/view-screen.ts +0 -218
  370. package/corpus/templates/crm/agent-native.app-skill.json +0 -58
  371. package/corpus/templates/crm/app/components/crm/CreateCrmRecordDialog.tsx +0 -158
  372. package/corpus/templates/crm/app/components/crm/CrmDashboardPanel.tsx +0 -78
  373. package/corpus/templates/crm/app/components/crm/CrmSignalsPanel.tsx +0 -226
  374. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +0 -414
  375. package/corpus/templates/crm/app/components/crm/RecordActions.tsx +0 -510
  376. package/corpus/templates/crm/app/components/crm/RecordGrid.tsx +0 -134
  377. package/corpus/templates/crm/app/components/crm/RecordWorkspace.tsx +0 -362
  378. package/corpus/templates/crm/app/components/crm/SavedViewDataProgram.tsx +0 -93
  379. package/corpus/templates/crm/app/components/crm/Surface.tsx +0 -97
  380. package/corpus/templates/crm/app/components/crm/WorkOverview.tsx +0 -180
  381. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +0 -112
  382. package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +0 -84
  383. package/corpus/templates/crm/app/components/ui/alert-dialog.tsx +0 -1
  384. package/corpus/templates/crm/app/components/ui/badge.tsx +0 -1
  385. package/corpus/templates/crm/app/components/ui/button.tsx +0 -1
  386. package/corpus/templates/crm/app/components/ui/card.tsx +0 -1
  387. package/corpus/templates/crm/app/components/ui/dialog.tsx +0 -1
  388. package/corpus/templates/crm/app/components/ui/input.tsx +0 -1
  389. package/corpus/templates/crm/app/components/ui/label.tsx +0 -1
  390. package/corpus/templates/crm/app/components/ui/select.tsx +0 -1
  391. package/corpus/templates/crm/app/components/ui/sheet.tsx +0 -1
  392. package/corpus/templates/crm/app/components/ui/skeleton.tsx +0 -1
  393. package/corpus/templates/crm/app/components/ui/switch.tsx +0 -1
  394. package/corpus/templates/crm/app/components/ui/table.tsx +0 -1
  395. package/corpus/templates/crm/app/components/ui/tabs.tsx +0 -1
  396. package/corpus/templates/crm/app/components/ui/textarea.tsx +0 -1
  397. package/corpus/templates/crm/app/components/ui/toolkit-provider.tsx +0 -17
  398. package/corpus/templates/crm/app/design-system.ts +0 -3
  399. package/corpus/templates/crm/app/entry.client.tsx +0 -16
  400. package/corpus/templates/crm/app/entry.server.tsx +0 -10
  401. package/corpus/templates/crm/app/global.css +0 -167
  402. package/corpus/templates/crm/app/hooks/use-navigation-state.ts +0 -60
  403. package/corpus/templates/crm/app/i18n/en-US.ts +0 -89
  404. package/corpus/templates/crm/app/i18n/index.ts +0 -34
  405. package/corpus/templates/crm/app/lib/dashboard.ts +0 -13
  406. package/corpus/templates/crm/app/lib/navigation.ts +0 -54
  407. package/corpus/templates/crm/app/lib/tab-id.ts +0 -1
  408. package/corpus/templates/crm/app/lib/types.ts +0 -172
  409. package/corpus/templates/crm/app/lib/utils.ts +0 -1
  410. package/corpus/templates/crm/app/root.tsx +0 -91
  411. package/corpus/templates/crm/app/routes/_index.tsx +0 -26
  412. package/corpus/templates/crm/app/routes/accounts.tsx +0 -41
  413. package/corpus/templates/crm/app/routes/agent.tsx +0 -5
  414. package/corpus/templates/crm/app/routes/ask.tsx +0 -53
  415. package/corpus/templates/crm/app/routes/dashboard.tsx +0 -118
  416. package/corpus/templates/crm/app/routes/opportunities.tsx +0 -41
  417. package/corpus/templates/crm/app/routes/people.tsx +0 -41
  418. package/corpus/templates/crm/app/routes/proposals.tsx +0 -266
  419. package/corpus/templates/crm/app/routes/records.$recordId.tsx +0 -35
  420. package/corpus/templates/crm/app/routes/settings.tsx +0 -63
  421. package/corpus/templates/crm/app/routes/setup.tsx +0 -308
  422. package/corpus/templates/crm/app/routes/tasks.tsx +0 -252
  423. package/corpus/templates/crm/app/routes/views.tsx +0 -333
  424. package/corpus/templates/crm/app/routes.ts +0 -17
  425. package/corpus/templates/crm/app/vite-env.d.ts +0 -6
  426. package/corpus/templates/crm/changelog/2026-07-21-agent-native-crm.md +0 -6
  427. package/corpus/templates/crm/changelog/2026-07-21-clips-call-evidence-review-recipe.md +0 -6
  428. package/corpus/templates/crm/changelog/2026-07-21-crm-can-now-find-and-review-evidence-grounded-call-signals-w.md +0 -6
  429. package/corpus/templates/crm/changelog/2026-07-21-crm-can-now-manage-keyword-and-smart-call-signal-trackers-fr.md +0 -6
  430. package/corpus/templates/crm/changelog/2026-07-21-delegated-local-crm-automation.md +0 -6
  431. package/corpus/templates/crm/changelog/2026-07-21-native-sql.md +0 -6
  432. package/corpus/templates/crm/changelog/2026-07-21-pipeline-dashboard.md +0 -6
  433. package/corpus/templates/crm/changelog/2026-07-21-pipeline-dashboards-now-install-correctly-from-the-crm-actio.md +0 -6
  434. package/corpus/templates/crm/changelog/2026-07-21-salesforce-connection.md +0 -6
  435. package/corpus/templates/crm/changelog/2026-07-22-crm-field-validation-now-explains-which-fields-cannot-be-edi.md +0 -6
  436. package/corpus/templates/crm/changelog/2026-07-22-fixed-hubspot-contact-syncs-missing-records-updated-after-th.md +0 -6
  437. package/corpus/templates/crm/changelog/2026-07-22-fixed-pipeline-dashboard-panels-so-their-opportunity-data-lo.md +0 -6
  438. package/corpus/templates/crm/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +0 -6
  439. package/corpus/templates/crm/components.json +0 -20
  440. package/corpus/templates/crm/docs/architecture/crm-contract.md +0 -180
  441. package/corpus/templates/crm/learnings.defaults.md +0 -5
  442. package/corpus/templates/crm/package.json +0 -59
  443. package/corpus/templates/crm/public/favicon.svg +0 -1
  444. package/corpus/templates/crm/public/icon-180.svg +0 -1
  445. package/corpus/templates/crm/public/manifest.json +0 -11
  446. package/corpus/templates/crm/react-router.config.ts +0 -7
  447. package/corpus/templates/crm/server/crm/adapter.ts +0 -32
  448. package/corpus/templates/crm/server/crm/crm-field-firewall.ts +0 -64
  449. package/corpus/templates/crm/server/crm/crm-mirror.ts +0 -711
  450. package/corpus/templates/crm/server/crm/hubspot-adapter.ts +0 -1146
  451. package/corpus/templates/crm/server/crm/native-adapter.ts +0 -1715
  452. package/corpus/templates/crm/server/crm/read-through.ts +0 -207
  453. package/corpus/templates/crm/server/crm/salesforce-adapter.ts +0 -1255
  454. package/corpus/templates/crm/server/db/crm-store.ts +0 -1170
  455. package/corpus/templates/crm/server/db/index.ts +0 -169
  456. package/corpus/templates/crm/server/db/schema.ts +0 -441
  457. package/corpus/templates/crm/server/lib/intelligence/default-detectors.ts +0 -65
  458. package/corpus/templates/crm/server/lib/intelligence/evidence.ts +0 -105
  459. package/corpus/templates/crm/server/lib/intelligence/keyword-detector.ts +0 -100
  460. package/corpus/templates/crm/server/lib/intelligence/smart-detector.ts +0 -144
  461. package/corpus/templates/crm/server/lib/intelligence/summary.ts +0 -141
  462. package/corpus/templates/crm/server/lib/provider-api.ts +0 -64
  463. package/corpus/templates/crm/server/middleware/auth.ts +0 -4
  464. package/corpus/templates/crm/server/plugins/agent-chat.ts +0 -68
  465. package/corpus/templates/crm/server/plugins/auth.ts +0 -14
  466. package/corpus/templates/crm/server/plugins/core-routes.ts +0 -17
  467. package/corpus/templates/crm/server/plugins/db.ts +0 -454
  468. package/corpus/templates/crm/server/routes/[...page].get.ts +0 -5
  469. package/corpus/templates/crm/shared/crm-automation-recipes.ts +0 -132
  470. package/corpus/templates/crm/shared/crm-contract.ts +0 -254
  471. package/corpus/templates/crm/shared/crm-sales-config.ts +0 -63
  472. package/corpus/templates/crm/ssr-entry.ts +0 -11
  473. package/corpus/templates/crm/tsconfig.json +0 -24
  474. package/corpus/templates/crm/vite.config.ts +0 -15
  475. package/corpus/templates/crm/vitest.config.ts +0 -5
  476. package/dist/observability/hosted-model-experiment.d.ts +0 -39
  477. package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
  478. package/dist/observability/hosted-model-experiment.js +0 -90
  479. package/dist/observability/hosted-model-experiment.js.map +0 -1
  480. package/src/observability/hosted-model-experiment.ts +0 -118
@@ -1,1146 +0,0 @@
1
- import {
2
- resolveWorkspaceConnectionCredentialForApp,
3
- resolveWorkspaceConnectionForApp,
4
- type ResolvedWorkspaceConnectionForApp,
5
- type WorkspaceConnectionForApp,
6
- } from "@agent-native/core/workspace-connections";
7
-
8
- import type {
9
- CrmAccessScope,
10
- CrmAdapter,
11
- CrmAdapterCapabilities,
12
- CrmConnectionRef,
13
- CrmFieldDefinition,
14
- CrmObjectDefinition,
15
- CrmObjectKind,
16
- CrmObjectRef,
17
- CrmProvenance,
18
- CrmRecord,
19
- CrmRecordRef,
20
- CrmRelationship,
21
- CrmSyncPage,
22
- CrmValue,
23
- } from "../../shared/crm-contract.js";
24
-
25
- const HUBSPOT_API_BASE = "https://api.hubapi.com";
26
- const CRM_APP_ID = "crm";
27
- const HUBSPOT_CREDENTIAL_KEYS = [
28
- "HUBSPOT_ACCESS_TOKEN",
29
- "HUBSPOT_PRIVATE_APP_TOKEN",
30
- ] as const;
31
- const CORE_OBJECT_TYPES = ["companies", "contacts", "deals"] as const;
32
- const MAX_PAGE_SIZE = 100;
33
- const MAX_RETRY_ATTEMPTS = 3;
34
- const MAX_RETRY_DELAY_MS = 1_500;
35
- const MAX_SINGLE_RETRY_DELAY_MS = 1_000;
36
- const REQUEST_TIMEOUT_MS = 10_000;
37
-
38
- type HubSpotObjectType = (typeof CORE_OBJECT_TYPES)[number] | string;
39
-
40
- export interface HubSpotTransportRequest {
41
- path: string;
42
- method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
43
- body?: unknown;
44
- headers?: Record<string, string>;
45
- retrySafe?: boolean;
46
- }
47
-
48
- export interface HubSpotTransportResponse {
49
- status: number;
50
- body?: unknown;
51
- headers?: Record<string, string | undefined>;
52
- }
53
-
54
- export interface HubSpotTransport {
55
- request(input: HubSpotTransportRequest): Promise<HubSpotTransportResponse>;
56
- }
57
-
58
- export interface CreateHubSpotCrmAdapterOptions {
59
- connectionId?: string;
60
- userEmail?: string;
61
- orgId?: string | null;
62
- transport?: HubSpotTransport;
63
- }
64
-
65
- export interface HubSpotCrmAdapterOptions {
66
- connection: WorkspaceConnectionForApp;
67
- transport: HubSpotTransport;
68
- accessScope?: CrmAccessScope;
69
- }
70
-
71
- type HubSpotProperty = {
72
- name?: string;
73
- label?: string;
74
- type?: string;
75
- fieldType?: string;
76
- hidden?: boolean;
77
- readOnlyValue?: boolean;
78
- calculated?: boolean;
79
- required?: boolean;
80
- options?: Array<{ value?: string; label?: string; hidden?: boolean }>;
81
- referencedObjectType?: string;
82
- sensitiveDataCategory?: string;
83
- };
84
-
85
- type HubSpotSchema = {
86
- objectTypeId?: string | number;
87
- name?: string;
88
- labels?: { singular?: string; plural?: string };
89
- metaType?: string;
90
- properties?: HubSpotProperty[];
91
- };
92
-
93
- type HubSpotRecord = {
94
- id?: string | number;
95
- properties?: Record<string, unknown>;
96
- createdAt?: string;
97
- updatedAt?: string;
98
- archived?: boolean;
99
- };
100
-
101
- type HubSpotListResponse = {
102
- results?: HubSpotRecord[];
103
- paging?: { next?: { after?: string } };
104
- };
105
-
106
- type HubSpotAssociation = {
107
- toObjectId?: string | number;
108
- id?: string | number;
109
- associationTypes?: Array<{
110
- associationCategory?: string;
111
- associationTypeId?: string | number;
112
- label?: string | null;
113
- }>;
114
- };
115
-
116
- type HubSpotAssociationResponse = {
117
- results?: HubSpotAssociation[];
118
- paging?: { next?: { after?: string } };
119
- };
120
-
121
- function nonEmpty(value: string | undefined | null): string | undefined {
122
- const trimmed = value?.trim();
123
- return trimmed || undefined;
124
- }
125
-
126
- function uniqueStrings(values: readonly string[]): string[] {
127
- return Array.from(
128
- new Set(values.map((value) => value.trim()).filter(Boolean)),
129
- );
130
- }
131
-
132
- function objectKind(objectType: string): CrmObjectKind {
133
- switch (objectType.toLowerCase()) {
134
- case "companies":
135
- return "account";
136
- case "contacts":
137
- return "person";
138
- case "deals":
139
- return "opportunity";
140
- case "tasks":
141
- return "task";
142
- case "calls":
143
- case "emails":
144
- case "meetings":
145
- case "notes":
146
- return "activity";
147
- default:
148
- return "custom";
149
- }
150
- }
151
-
152
- function objectDisplayProperty(objectType: string): string[] {
153
- switch (objectType.toLowerCase()) {
154
- case "companies":
155
- return ["name", "domain"];
156
- case "contacts":
157
- return ["firstname", "lastname", "email"];
158
- case "deals":
159
- return ["dealname"];
160
- case "tickets":
161
- return ["subject"];
162
- default:
163
- return ["name", "hs_object_id"];
164
- }
165
- }
166
-
167
- function propertyValueType(
168
- property: HubSpotProperty,
169
- ): CrmFieldDefinition["valueType"] {
170
- const type = property.type?.toLowerCase();
171
- const fieldType = property.fieldType?.toLowerCase();
172
- if (fieldType === "checkbox") return "multi-enum";
173
- if (type === "enumeration") return "enum";
174
- if (type === "bool" || type === "boolean") return "boolean";
175
- if (type === "number") return "number";
176
- if (type === "date") return "date";
177
- if (type === "datetime") return "datetime";
178
- if (type === "json") return "json";
179
- if (fieldType === "currency" || property.name === "amount") return "currency";
180
- if (fieldType === "percentage") return "percent";
181
- if (type === "object_coordinates" || type === "reference") return "reference";
182
- return "string";
183
- }
184
-
185
- function propertyIsSensitive(property: HubSpotProperty): boolean {
186
- const category = property.sensitiveDataCategory?.trim().toLowerCase();
187
- return Boolean(
188
- category && category !== "none" && category !== "not_sensitive",
189
- );
190
- }
191
-
192
- function scalarValue(value: unknown): CrmValue | undefined {
193
- if (
194
- value === null ||
195
- typeof value === "string" ||
196
- typeof value === "number" ||
197
- typeof value === "boolean"
198
- ) {
199
- return value;
200
- }
201
- if (Array.isArray(value)) {
202
- const values = value.filter(
203
- (item): item is string | number | boolean | null =>
204
- item === null ||
205
- typeof item === "string" ||
206
- typeof item === "number" ||
207
- typeof item === "boolean",
208
- );
209
- return values.length === value.length ? values : undefined;
210
- }
211
- return undefined;
212
- }
213
-
214
- function toQuery(params: Record<string, string | undefined>): string {
215
- const query = new URLSearchParams();
216
- for (const [key, value] of Object.entries(params)) {
217
- if (value) query.set(key, value);
218
- }
219
- const value = query.toString();
220
- return value ? `?${value}` : "";
221
- }
222
-
223
- function encodeCursorState(value: { index: number; after?: string }): string {
224
- const bytes = new TextEncoder().encode(JSON.stringify(value));
225
- let binary = "";
226
- for (const byte of bytes) binary += String.fromCharCode(byte);
227
- return btoa(binary)
228
- .replace(/\+/g, "-")
229
- .replace(/\//g, "_")
230
- .replace(/=+$/, "");
231
- }
232
-
233
- function decodedCursor(cursor: string | undefined): {
234
- index: number;
235
- after?: string;
236
- } {
237
- if (!cursor) return { index: 0 };
238
- try {
239
- const padded = cursor
240
- .replace(/-/g, "+")
241
- .replace(/_/g, "/")
242
- .padEnd(Math.ceil(cursor.length / 4) * 4, "=");
243
- const binary = atob(padded);
244
- const decoded = JSON.parse(
245
- new TextDecoder().decode(
246
- Uint8Array.from(binary, (character) => character.charCodeAt(0)),
247
- ),
248
- ) as { index?: unknown; after?: unknown };
249
- const index = typeof decoded.index === "number" ? decoded.index : 0;
250
- return {
251
- index: Number.isInteger(index) && index >= 0 ? index : 0,
252
- ...(typeof decoded.after === "string" && decoded.after
253
- ? { after: decoded.after }
254
- : {}),
255
- };
256
- } catch {
257
- // HubSpot's `after` values are already opaque; accept them for one-object callers.
258
- return { index: 0, after: cursor };
259
- }
260
- }
261
-
262
- function encodedCursor(index: number, after?: string): string | undefined {
263
- if (!after) return undefined;
264
- return encodeCursorState({ index, after });
265
- }
266
-
267
- function scopeAllows(
268
- connection: WorkspaceConnectionForApp,
269
- objectType: string,
270
- operation: "read" | "write",
271
- ): boolean {
272
- const scopes = [
273
- ...connection.scopes,
274
- ...(connection.explicitGrant?.scopes ?? []),
275
- ].map((scope) => scope.toLowerCase());
276
- if (scopes.length === 0) return operation === "read";
277
- const singular = objectType.toLowerCase().replace(/s$/, "");
278
- return scopes.some(
279
- (scope) =>
280
- scope === "all" ||
281
- scope === "crm" ||
282
- scope === `crm.${operation}` ||
283
- scope === `crm.objects.${operation}` ||
284
- scope === `crm.objects.${objectType.toLowerCase()}.${operation}` ||
285
- scope === `crm.objects.${singular}.${operation}` ||
286
- (operation === "write" && scope.endsWith(".write")),
287
- );
288
- }
289
-
290
- function accessScopeFor(
291
- connection: WorkspaceConnectionForApp,
292
- objectType: string,
293
- ): CrmAccessScope {
294
- const grantId = connection.appAccess.grantId ?? undefined;
295
- const mode = [
296
- ...connection.credentialRefs,
297
- ...(connection.explicitGrant?.credentialRefs ?? []),
298
- ].some((ref) => ref.scope === "user")
299
- ? "user"
300
- : "service-account";
301
- const readable = scopeAllows(connection, objectType, "read");
302
- const writable = scopeAllows(connection, objectType, "write");
303
- return {
304
- key: `${connection.id}:${grantId ?? connection.appAccess.mode}`,
305
- actorId: connection.ownerEmail,
306
- grantId,
307
- mode,
308
- objectReadable: readable,
309
- objectCreateable: writable,
310
- objectUpdateable: writable,
311
- objectDeleteable: false,
312
- recordVisibility: mode === "user" ? "actor" : "unknown",
313
- };
314
- }
315
-
316
- function definitionFrom(
317
- connection: WorkspaceConnectionForApp,
318
- objectType: string,
319
- schema: HubSpotSchema | undefined,
320
- properties: HubSpotProperty[],
321
- ): CrmObjectDefinition {
322
- const scope = accessScopeFor(connection, objectType);
323
- const custom =
324
- !CORE_OBJECT_TYPES.includes(
325
- objectType as (typeof CORE_OBJECT_TYPES)[number],
326
- ) && schema?.metaType !== "HUBSPOT";
327
- return {
328
- connectionId: connection.id,
329
- provider: "hubspot",
330
- ...(connection.accountId ? { accountId: connection.accountId } : {}),
331
- actorId: connection.ownerEmail,
332
- objectType,
333
- kind: objectKind(objectType),
334
- label: schema?.labels?.singular || objectType,
335
- pluralLabel: schema?.labels?.plural || objectType,
336
- custom,
337
- queryable: scope.objectReadable,
338
- searchable: scope.objectReadable,
339
- createable: scope.objectCreateable,
340
- updateable: scope.objectUpdateable,
341
- deleteable: false,
342
- fields: properties
343
- .map((property): CrmFieldDefinition | null => {
344
- const name = nonEmpty(property.name);
345
- if (!name) return null;
346
- const sensitive = propertyIsSensitive(property);
347
- return {
348
- name,
349
- label: property.label || name,
350
- valueType: propertyValueType(property),
351
- storagePolicy: sensitive ? "redacted" : "remote-only",
352
- sensitive,
353
- readable: !property.hidden && scope.objectReadable,
354
- createable:
355
- !sensitive &&
356
- !property.readOnlyValue &&
357
- !property.calculated &&
358
- scope.objectCreateable,
359
- updateable:
360
- !sensitive &&
361
- !property.readOnlyValue &&
362
- !property.calculated &&
363
- scope.objectUpdateable,
364
- required: property.required === true,
365
- ...(property.options
366
- ? {
367
- options: property.options
368
- .filter((option) => nonEmpty(option.value))
369
- .map((option) => ({
370
- value: option.value!.trim(),
371
- label: option.label || option.value!.trim(),
372
- ...(option.hidden ? { active: false } : {}),
373
- })),
374
- }
375
- : {}),
376
- ...(property.referencedObjectType
377
- ? { referencedObjectType: property.referencedObjectType }
378
- : {}),
379
- };
380
- })
381
- .filter((field): field is CrmFieldDefinition => field !== null),
382
- };
383
- }
384
-
385
- function recordRef(
386
- connection: WorkspaceConnectionForApp,
387
- objectType: string,
388
- remoteId: string,
389
- ): CrmRecordRef {
390
- return {
391
- connectionId: connection.id,
392
- provider: "hubspot",
393
- ...(connection.accountId ? { accountId: connection.accountId } : {}),
394
- actorId: connection.ownerEmail,
395
- objectType,
396
- kind: objectKind(objectType),
397
- remoteId,
398
- };
399
- }
400
-
401
- function provenance(
402
- connection: WorkspaceConnectionForApp,
403
- objectType: string,
404
- remoteId: string,
405
- remoteRevision: string | undefined,
406
- fieldNames: string[],
407
- ): CrmProvenance[] {
408
- const observedAt = new Date().toISOString();
409
- return fieldNames.map((fieldName) => ({
410
- provider: "hubspot",
411
- connectionId: connection.id,
412
- objectType,
413
- remoteId,
414
- fieldName,
415
- ...(remoteRevision ? { remoteRevision } : {}),
416
- observedAt,
417
- }));
418
- }
419
-
420
- function projectRecord(
421
- connection: WorkspaceConnectionForApp,
422
- objectType: string,
423
- source: HubSpotRecord,
424
- fieldAllowList: string[],
425
- ): CrmRecord | null {
426
- const remoteId = source.id == null ? undefined : String(source.id);
427
- if (!remoteId) return null;
428
- const allowed = uniqueStrings(fieldAllowList);
429
- const fields: Record<string, CrmValue> = {};
430
- for (const fieldName of allowed) {
431
- const value = scalarValue(source.properties?.[fieldName]);
432
- if (value !== undefined) fields[fieldName] = value;
433
- }
434
- const displayValues = objectDisplayProperty(objectType)
435
- .map((fieldName) => fields[fieldName])
436
- .filter((value): value is CrmValue => value !== undefined)
437
- .filter(
438
- (value): value is string | number | boolean =>
439
- typeof value === "string" ||
440
- typeof value === "number" ||
441
- typeof value === "boolean",
442
- );
443
- const displayName =
444
- displayValues.map(String).filter(Boolean).join(" ") || remoteId;
445
- const remoteRevision = source.updatedAt;
446
- return {
447
- ref: recordRef(connection, objectType, remoteId),
448
- displayName,
449
- fields,
450
- ...(remoteRevision ? { remoteRevision } : {}),
451
- ...(source.updatedAt ? { remoteUpdatedAt: source.updatedAt } : {}),
452
- deleted: source.archived === true,
453
- accessScope: accessScopeFor(connection, objectType),
454
- provenance: provenance(
455
- connection,
456
- objectType,
457
- remoteId,
458
- remoteRevision,
459
- Object.keys(fields),
460
- ),
461
- };
462
- }
463
-
464
- function isNotFound(error: unknown): boolean {
465
- return error instanceof HubSpotApiError && error.status === 404;
466
- }
467
-
468
- class HubSpotApiError extends Error {
469
- constructor(
470
- readonly status: number,
471
- message: string,
472
- ) {
473
- super(message);
474
- }
475
- }
476
-
477
- function hubSpotErrorMessage(status: number): string {
478
- if (status === 401 || status === 403)
479
- return "HubSpot authentication or authorization failed.";
480
- if (status === 404) return "HubSpot resource was not found.";
481
- if (status === 429) return "HubSpot rate limit exceeded.";
482
- if (status >= 500) return "HubSpot service is temporarily unavailable.";
483
- return "HubSpot request failed.";
484
- }
485
-
486
- function headerValue(
487
- headers: Record<string, string | undefined> | undefined,
488
- name: string,
489
- ): string | undefined {
490
- if (!headers) return undefined;
491
- const expected = name.toLowerCase();
492
- return Object.entries(headers).find(
493
- ([key]) => key.toLowerCase() === expected,
494
- )?.[1];
495
- }
496
-
497
- export function hubSpotRetryDelayMs(
498
- response: HubSpotTransportResponse,
499
- attempt: number,
500
- elapsedMs: number,
501
- now = Date.now(),
502
- ): number {
503
- const retryAfter = headerValue(response.headers, "retry-after")?.trim();
504
- const numericRetryAfter = retryAfter ? Number(retryAfter) : Number.NaN;
505
- const requestedDelay = Number.isFinite(numericRetryAfter)
506
- ? Math.max(0, numericRetryAfter * 1_000)
507
- : retryAfter
508
- ? Math.max(0, Date.parse(retryAfter) - now)
509
- : 100 * 2 ** attempt;
510
- return Math.max(
511
- 0,
512
- Math.min(
513
- requestedDelay,
514
- MAX_SINGLE_RETRY_DELAY_MS,
515
- MAX_RETRY_DELAY_MS - elapsedMs,
516
- ),
517
- );
518
- }
519
-
520
- function canRetryHubSpotResponse(status: number): boolean {
521
- return status === 429 || status >= 500;
522
- }
523
-
524
- function assertOwnedRecord(
525
- connection: WorkspaceConnectionForApp,
526
- record: CrmRecordRef,
527
- ): void {
528
- if (record.provider !== "hubspot" || record.connectionId !== connection.id) {
529
- throw new Error(
530
- "HubSpot record does not belong to this workspace connection.",
531
- );
532
- }
533
- }
534
-
535
- class FetchHubSpotTransport implements HubSpotTransport {
536
- constructor(private readonly token: string) {}
537
-
538
- async request(
539
- input: HubSpotTransportRequest,
540
- ): Promise<HubSpotTransportResponse> {
541
- const response = await fetch(`${HUBSPOT_API_BASE}${input.path}`, {
542
- method: input.method ?? "GET",
543
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
544
- headers: {
545
- Authorization: `Bearer ${this.token}`,
546
- ...(input.body === undefined
547
- ? {}
548
- : { "Content-Type": "application/json" }),
549
- ...input.headers,
550
- },
551
- ...(input.body === undefined ? {} : { body: JSON.stringify(input.body) }),
552
- });
553
- const text = await response.text();
554
- let body: unknown;
555
- if (text) {
556
- try {
557
- body = JSON.parse(text);
558
- } catch {
559
- body = text;
560
- }
561
- }
562
- return {
563
- status: response.status,
564
- body,
565
- headers: {
566
- "retry-after": response.headers.get("retry-after") ?? undefined,
567
- },
568
- };
569
- }
570
- }
571
-
572
- export class HubSpotCrmAdapter implements CrmAdapter {
573
- readonly connection: CrmConnectionRef;
574
- readonly capabilities: CrmAdapterCapabilities = {
575
- schemaDiscovery: true,
576
- customObjects: true,
577
- search: true,
578
- incrementalSync: true,
579
- deletedRecordSync: true,
580
- conditionalMutations: false,
581
- labeledRelationships: true,
582
- perFieldPermissions: false,
583
- perRecordPermissions: false,
584
- };
585
-
586
- private readonly workspaceConnection: WorkspaceConnectionForApp;
587
- private readonly transport: HubSpotTransport;
588
-
589
- constructor(options: HubSpotCrmAdapterOptions) {
590
- this.workspaceConnection = options.connection;
591
- this.transport = options.transport;
592
- this.connection = {
593
- connectionId: options.connection.id,
594
- provider: "hubspot",
595
- ...(options.connection.accountId
596
- ? { accountId: options.connection.accountId }
597
- : {}),
598
- actorId: options.connection.ownerEmail,
599
- };
600
- }
601
-
602
- private async request<T>(input: HubSpotTransportRequest): Promise<T> {
603
- const readRetrySafe =
604
- input.retrySafe === true ||
605
- input.method === undefined ||
606
- input.method === "GET";
607
- let elapsedDelayMs = 0;
608
- for (let attempt = 0; attempt < MAX_RETRY_ATTEMPTS; attempt++) {
609
- const response = await this.transport.request(input);
610
- if (response.status >= 200 && response.status < 300) {
611
- return (response.body ?? {}) as T;
612
- }
613
- const mayRetry =
614
- attempt + 1 < MAX_RETRY_ATTEMPTS &&
615
- readRetrySafe &&
616
- canRetryHubSpotResponse(response.status) &&
617
- elapsedDelayMs < MAX_RETRY_DELAY_MS;
618
- if (!mayRetry) {
619
- throw new HubSpotApiError(
620
- response.status,
621
- `HubSpot API error ${response.status}: ${hubSpotErrorMessage(response.status)}`,
622
- );
623
- }
624
- const delay = hubSpotRetryDelayMs(response, attempt, elapsedDelayMs);
625
- if (delay <= 0) {
626
- continue;
627
- }
628
- await new Promise<void>((resolve) => setTimeout(resolve, delay));
629
- elapsedDelayMs += delay;
630
- }
631
- throw new Error("HubSpot retry budget was exhausted.");
632
- }
633
-
634
- private async schema(objectType: string): Promise<HubSpotSchema | undefined> {
635
- try {
636
- return await this.request<HubSpotSchema>({
637
- path: `/crm/v3/schemas/${encodeURIComponent(objectType)}`,
638
- });
639
- } catch (error) {
640
- if (isNotFound(error)) return undefined;
641
- throw error;
642
- }
643
- }
644
-
645
- private async properties(objectType: string): Promise<HubSpotProperty[]> {
646
- const response = await this.request<{ results?: HubSpotProperty[] }>({
647
- path: `/crm/v3/properties/${encodeURIComponent(objectType)}`,
648
- });
649
- return response.results ?? [];
650
- }
651
-
652
- private async objectDefinition(
653
- objectType: string,
654
- ): Promise<CrmObjectDefinition> {
655
- const [schema, properties] = await Promise.all([
656
- this.schema(objectType),
657
- this.properties(objectType),
658
- ]);
659
- return definitionFrom(
660
- this.workspaceConnection,
661
- objectType,
662
- schema,
663
- properties,
664
- );
665
- }
666
-
667
- async discoverObjects(): Promise<CrmObjectDefinition[]> {
668
- const schemas = await this.request<{ results?: HubSpotSchema[] }>({
669
- path: "/crm/v3/schemas",
670
- });
671
- const objectTypes = uniqueStrings([
672
- ...CORE_OBJECT_TYPES,
673
- ...(schemas.results ?? []).map(
674
- (schema) =>
675
- nonEmpty(schema.name) ??
676
- (schema.objectTypeId == null ? "" : String(schema.objectTypeId)),
677
- ),
678
- ]);
679
- return Promise.all(
680
- objectTypes.map((objectType) => this.objectDefinition(objectType)),
681
- );
682
- }
683
-
684
- async describeObject(objectType: string): Promise<CrmObjectDefinition> {
685
- const normalized = nonEmpty(objectType);
686
- if (!normalized) throw new Error("HubSpot objectType is required.");
687
- return this.objectDefinition(normalized);
688
- }
689
-
690
- getAccessScope(objectType: string): CrmAccessScope {
691
- const normalized = nonEmpty(objectType);
692
- if (!normalized) throw new Error("HubSpot objectType is required.");
693
- return accessScopeFor(this.workspaceConnection, normalized);
694
- }
695
-
696
- private async listPage(input: {
697
- objectType: HubSpotObjectType;
698
- fields: string[];
699
- limit: number;
700
- cursor?: string;
701
- archived?: boolean;
702
- filterGroups?: unknown[];
703
- }): Promise<HubSpotListResponse> {
704
- const objectType = encodeURIComponent(input.objectType);
705
- if (input.filterGroups?.length) {
706
- return this.request<HubSpotListResponse>({
707
- method: "POST",
708
- retrySafe: true,
709
- path: `/crm/v3/objects/${objectType}/search`,
710
- body: {
711
- limit: input.limit,
712
- ...(input.cursor ? { after: input.cursor } : {}),
713
- ...(input.fields.length ? { properties: input.fields } : {}),
714
- filterGroups: input.filterGroups,
715
- },
716
- });
717
- }
718
- return this.request<HubSpotListResponse>({
719
- path: `/crm/v3/objects/${objectType}${toQuery({
720
- limit: String(input.limit),
721
- ...(input.cursor ? { after: input.cursor } : {}),
722
- ...(input.fields.length ? { properties: input.fields.join(",") } : {}),
723
- ...(input.archived ? { archived: "true" } : {}),
724
- })}`,
725
- });
726
- }
727
-
728
- private filtersForScope(scope: {
729
- objectType: string;
730
- pipelineIds?: string[];
731
- ownerIds?: string[];
732
- updatedAfter?: string;
733
- }): unknown[] {
734
- const filters: Array<Record<string, unknown>> = [];
735
- if (
736
- scope.objectType.toLowerCase() === "deals" &&
737
- scope.pipelineIds?.length
738
- ) {
739
- filters.push({
740
- propertyName: "pipeline",
741
- operator: "IN",
742
- values: uniqueStrings(scope.pipelineIds),
743
- });
744
- }
745
- if (scope.ownerIds?.length) {
746
- filters.push({
747
- propertyName: "hubspot_owner_id",
748
- operator: "IN",
749
- values: uniqueStrings(scope.ownerIds),
750
- });
751
- }
752
- if (scope.updatedAfter) {
753
- filters.push({
754
- propertyName:
755
- scope.objectType.toLowerCase() === "contacts"
756
- ? "lastmodifieddate"
757
- : "hs_lastmodifieddate",
758
- operator: "GTE",
759
- value: scope.updatedAfter,
760
- });
761
- }
762
- return filters.length ? [{ filters }] : [];
763
- }
764
-
765
- async syncPage(input: {
766
- scope: {
767
- objectType: string;
768
- pipelineIds?: string[];
769
- ownerIds?: string[];
770
- recordIds?: string[];
771
- associatedRecordIds?: string[];
772
- updatedAfter?: string;
773
- includeDeleted?: boolean;
774
- };
775
- fieldAllowList: string[];
776
- cursor?: string;
777
- limit: number;
778
- }): Promise<CrmSyncPage> {
779
- const objectType = nonEmpty(input.scope.objectType);
780
- if (!objectType)
781
- throw new Error("HubSpot sync scope objectType is required.");
782
- const fields = uniqueStrings(input.fieldAllowList);
783
- const limit = Math.max(1, Math.min(MAX_PAGE_SIZE, input.limit));
784
- const recordIds = uniqueStrings(input.scope.recordIds ?? []);
785
- let page: HubSpotListResponse;
786
- if (recordIds.length) {
787
- const state = decodedCursor(input.cursor);
788
- const offset = Math.min(state.index, recordIds.length);
789
- page = await this.request<HubSpotListResponse>({
790
- method: "POST",
791
- retrySafe: true,
792
- path: `/crm/v3/objects/${encodeURIComponent(objectType)}/batch/read`,
793
- body: {
794
- inputs: recordIds.slice(offset, offset + limit).map((id) => ({ id })),
795
- ...(fields.length ? { properties: fields } : {}),
796
- ...(input.scope.includeDeleted ? { archived: true } : {}),
797
- },
798
- });
799
- } else {
800
- page = await this.listPage({
801
- objectType,
802
- fields,
803
- limit,
804
- cursor: input.cursor,
805
- archived: input.scope.includeDeleted,
806
- filterGroups: this.filtersForScope(input.scope),
807
- });
808
- }
809
- const associatedIds = new Set(
810
- uniqueStrings(input.scope.associatedRecordIds ?? []),
811
- );
812
- const records = (page.results ?? [])
813
- .filter(
814
- (record) => !associatedIds.size || associatedIds.has(String(record.id)),
815
- )
816
- .map((record) =>
817
- projectRecord(this.workspaceConnection, objectType, record, fields),
818
- )
819
- .filter((record): record is CrmRecord => record !== null);
820
- const nextCursor = recordIds.length
821
- ? (() => {
822
- const offset = decodedCursor(input.cursor).index + limit;
823
- return offset < recordIds.length
824
- ? encodeCursorState({ index: offset })
825
- : undefined;
826
- })()
827
- : page.paging?.next?.after;
828
- return {
829
- records,
830
- relationships: [],
831
- ...(nextCursor ? { nextCursor } : {}),
832
- complete: !nextCursor,
833
- };
834
- }
835
-
836
- async getRecord(input: {
837
- record: CrmRecordRef;
838
- fields: string[];
839
- }): Promise<CrmRecord | null> {
840
- assertOwnedRecord(this.workspaceConnection, input.record);
841
- const fields = uniqueStrings(input.fields);
842
- try {
843
- const record = await this.request<HubSpotRecord>({
844
- path: `/crm/v3/objects/${encodeURIComponent(input.record.objectType)}/${encodeURIComponent(input.record.remoteId)}${toQuery(
845
- {
846
- ...(fields.length ? { properties: fields.join(",") } : {}),
847
- },
848
- )}`,
849
- });
850
- return projectRecord(
851
- this.workspaceConnection,
852
- input.record.objectType,
853
- record,
854
- fields,
855
- );
856
- } catch (error) {
857
- if (isNotFound(error)) return null;
858
- throw error;
859
- }
860
- }
861
-
862
- async search(input: {
863
- objectTypes: string[];
864
- query: string;
865
- fields: string[];
866
- limit: number;
867
- cursor?: string;
868
- }): Promise<CrmSyncPage> {
869
- const objectTypes = uniqueStrings(input.objectTypes);
870
- if (!objectTypes.length)
871
- return { records: [], relationships: [], complete: true };
872
- const fields = uniqueStrings(input.fields);
873
- const limit = Math.max(1, Math.min(MAX_PAGE_SIZE, input.limit));
874
- const state = decodedCursor(input.cursor);
875
- const index = Math.min(state.index, objectTypes.length - 1);
876
- const objectType = objectTypes[index]!;
877
- const page = await this.request<HubSpotListResponse>({
878
- method: "POST",
879
- path: `/crm/v3/objects/${encodeURIComponent(objectType)}/search`,
880
- body: {
881
- query: input.query,
882
- limit,
883
- ...(state.after ? { after: state.after } : {}),
884
- ...(fields.length ? { properties: fields } : {}),
885
- },
886
- });
887
- const records = (page.results ?? [])
888
- .map((record) =>
889
- projectRecord(this.workspaceConnection, objectType, record, fields),
890
- )
891
- .filter((record): record is CrmRecord => record !== null);
892
- const after = page.paging?.next?.after;
893
- const nextCursor = after
894
- ? encodedCursor(index, after)
895
- : index + 1 < objectTypes.length
896
- ? encodeCursorState({ index: index + 1 })
897
- : undefined;
898
- return {
899
- records,
900
- relationships: [],
901
- ...(nextCursor ? { nextCursor } : {}),
902
- complete: !nextCursor,
903
- };
904
- }
905
-
906
- async listRelationships(input: {
907
- record: CrmRecordRef;
908
- targetObjectTypes?: string[];
909
- limit: number;
910
- cursor?: string;
911
- }): Promise<{
912
- relationships: CrmRelationship[];
913
- nextCursor?: string;
914
- complete: boolean;
915
- }> {
916
- assertOwnedRecord(this.workspaceConnection, input.record);
917
- const targetObjectTypes = uniqueStrings(
918
- input.targetObjectTypes?.length
919
- ? input.targetObjectTypes
920
- : CORE_OBJECT_TYPES,
921
- ).filter((objectType) => objectType !== input.record.objectType);
922
- if (!targetObjectTypes.length) {
923
- return { relationships: [], complete: true };
924
- }
925
- const state = decodedCursor(input.cursor);
926
- const index = Math.min(state.index, targetObjectTypes.length - 1);
927
- const targetObjectType = targetObjectTypes[index]!;
928
- const response = await this.request<HubSpotAssociationResponse>({
929
- path: `/crm/v4/objects/${encodeURIComponent(input.record.objectType)}/${encodeURIComponent(input.record.remoteId)}/associations/${encodeURIComponent(targetObjectType)}${toQuery(
930
- {
931
- limit: String(Math.max(1, Math.min(MAX_PAGE_SIZE, input.limit))),
932
- ...(state.after ? { after: state.after } : {}),
933
- },
934
- )}`,
935
- });
936
- const relationships: CrmRelationship[] = [];
937
- for (const association of response.results ?? []) {
938
- const remoteId = association.toObjectId ?? association.id;
939
- if (remoteId == null) continue;
940
- const types = association.associationTypes?.length
941
- ? association.associationTypes
942
- : [
943
- {
944
- associationCategory: "HUBSPOT_DEFINED",
945
- associationTypeId: "default",
946
- },
947
- ];
948
- for (const type of types) {
949
- const relationshipType = [
950
- type.associationCategory ?? "HUBSPOT_DEFINED",
951
- type.associationTypeId == null
952
- ? "default"
953
- : String(type.associationTypeId),
954
- ].join(":");
955
- relationships.push({
956
- from: input.record,
957
- to: recordRef(
958
- this.workspaceConnection,
959
- targetObjectType,
960
- String(remoteId),
961
- ),
962
- relationshipType,
963
- ...(type.label ? { label: type.label } : {}),
964
- });
965
- }
966
- }
967
- const after = response.paging?.next?.after;
968
- const nextCursor = after
969
- ? encodedCursor(index, after)
970
- : index + 1 < targetObjectTypes.length
971
- ? encodeCursorState({ index: index + 1 })
972
- : undefined;
973
- return {
974
- relationships,
975
- ...(nextCursor ? { nextCursor } : {}),
976
- complete: !nextCursor,
977
- };
978
- }
979
-
980
- async applyMutation(mutation: Parameters<CrmAdapter["applyMutation"]>[0]) {
981
- assertOwnedRecord(this.workspaceConnection, mutation.record);
982
- if (!mutation.idempotencyKey.trim()) {
983
- return {
984
- status: "rejected" as const,
985
- message: "An idempotency key is required.",
986
- };
987
- }
988
- if (mutation.operation === "delete") {
989
- return {
990
- status: "rejected" as const,
991
- message:
992
- "HubSpot deletion is disabled during the phase-1 CRM transport rollout.",
993
- };
994
- }
995
- if (
996
- mutation.operation === "associate" ||
997
- mutation.operation === "disassociate"
998
- ) {
999
- return {
1000
- status: "rejected" as const,
1001
- message:
1002
- "HubSpot association mutation requires an explicit association definition and is not enabled in phase 1.",
1003
- };
1004
- }
1005
- if (
1006
- !scopeAllows(
1007
- this.workspaceConnection,
1008
- mutation.record.objectType,
1009
- "write",
1010
- )
1011
- ) {
1012
- return {
1013
- status: "rejected" as const,
1014
- message:
1015
- "The workspace connection does not prove write access for this HubSpot object.",
1016
- };
1017
- }
1018
- const fields = Object.entries(mutation.fields ?? {}).reduce<
1019
- Record<string, CrmValue>
1020
- >((result, [key, value]) => {
1021
- if (key.trim()) result[key] = value;
1022
- return result;
1023
- }, {});
1024
- if (!Object.keys(fields).length) {
1025
- return {
1026
- status: "rejected" as const,
1027
- message: "At least one field is required.",
1028
- };
1029
- }
1030
- if (mutation.operation === "update" && !mutation.expectedRemoteRevision) {
1031
- return {
1032
- status: "rejected" as const,
1033
- message: "HubSpot updates require a current remote revision.",
1034
- };
1035
- }
1036
- if (mutation.expectedRemoteRevision) {
1037
- const current = await this.getRecord({
1038
- record: mutation.record,
1039
- fields: [],
1040
- });
1041
- if (
1042
- !current ||
1043
- current.remoteRevision !== mutation.expectedRemoteRevision
1044
- ) {
1045
- return {
1046
- status: "conflict" as const,
1047
- ...(current?.remoteRevision
1048
- ? { remoteRevision: current.remoteRevision }
1049
- : {}),
1050
- message:
1051
- "The HubSpot record changed before this mutation could be applied.",
1052
- };
1053
- }
1054
- if (!this.capabilities.conditionalMutations) {
1055
- return {
1056
- status: "rejected" as const,
1057
- remoteRevision: current.remoteRevision,
1058
- message:
1059
- "HubSpot does not support an atomic conditional update for this record.",
1060
- };
1061
- }
1062
- }
1063
- try {
1064
- const objectType = encodeURIComponent(mutation.record.objectType);
1065
- const response = await this.request<HubSpotRecord>({
1066
- method: mutation.operation === "create" ? "POST" : "PATCH",
1067
- path:
1068
- mutation.operation === "create"
1069
- ? `/crm/v3/objects/${objectType}`
1070
- : `/crm/v3/objects/${objectType}/${encodeURIComponent(mutation.record.remoteId)}`,
1071
- body: { properties: fields },
1072
- headers: { "X-Idempotency-Key": mutation.idempotencyKey },
1073
- });
1074
- const record = projectRecord(
1075
- this.workspaceConnection,
1076
- mutation.record.objectType,
1077
- response,
1078
- Object.keys(fields),
1079
- );
1080
- if (!record) {
1081
- return {
1082
- status: "rejected" as const,
1083
- message: "HubSpot returned no record identity.",
1084
- };
1085
- }
1086
- return {
1087
- status: "applied" as const,
1088
- record,
1089
- ...(record.remoteRevision
1090
- ? { remoteRevision: record.remoteRevision }
1091
- : {}),
1092
- };
1093
- } catch (error) {
1094
- return {
1095
- status: "rejected" as const,
1096
- message:
1097
- error instanceof Error ? error.message : "HubSpot mutation failed.",
1098
- };
1099
- }
1100
- }
1101
- }
1102
-
1103
- function requireResolvedConnection(
1104
- result: ResolvedWorkspaceConnectionForApp,
1105
- ): WorkspaceConnectionForApp {
1106
- if (!result.available || !result.connection) {
1107
- throw new Error(
1108
- `HubSpot workspace connection is unavailable: ${result.reason}`,
1109
- );
1110
- }
1111
- return result.connection;
1112
- }
1113
-
1114
- export async function createHubSpotCrmAdapter(
1115
- options: CreateHubSpotCrmAdapterOptions = {},
1116
- ): Promise<HubSpotCrmAdapter> {
1117
- const resolved = await resolveWorkspaceConnectionForApp({
1118
- appId: CRM_APP_ID,
1119
- provider: "hubspot",
1120
- ...(options.connectionId ? { connectionId: options.connectionId } : {}),
1121
- requireConnected: true,
1122
- });
1123
- const connection = requireResolvedConnection(resolved);
1124
- if (options.transport) {
1125
- return new HubSpotCrmAdapter({ connection, transport: options.transport });
1126
- }
1127
- for (const key of HUBSPOT_CREDENTIAL_KEYS) {
1128
- const credential = await resolveWorkspaceConnectionCredentialForApp({
1129
- appId: CRM_APP_ID,
1130
- provider: "hubspot",
1131
- key,
1132
- connectionId: connection.id,
1133
- ...(options.userEmail ? { userEmail: options.userEmail } : {}),
1134
- ...(options.orgId !== undefined ? { orgId: options.orgId } : {}),
1135
- });
1136
- if (!credential.available || !credential.value) continue;
1137
- if (credential.provenance?.connectionId !== connection.id) continue;
1138
- return new HubSpotCrmAdapter({
1139
- connection,
1140
- transport: new FetchHubSpotTransport(credential.value),
1141
- });
1142
- }
1143
- throw new Error(
1144
- "HubSpot workspace connection is unavailable: no scoped credential was resolved for the granted connection.",
1145
- );
1146
- }