@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,1170 +0,0 @@
1
- import { accessFilter, assertAccess } from "@agent-native/core/sharing";
2
- import { and, asc, desc, eq, exists, inArray, isNull, like } from "drizzle-orm";
3
-
4
- import type {
5
- CrmAccessScope,
6
- CrmRelationship,
7
- CrmValue,
8
- } from "../../shared/crm-contract.js";
9
- import {
10
- createConnectedCrmAdapter,
11
- isConnectedCrmProvider,
12
- } from "../crm/adapter.js";
13
- import {
14
- isBoundedCrmValue,
15
- isSafeCrmMutationFieldName,
16
- } from "../crm/crm-field-firewall.js";
17
- import { resolveNativeCrmAccessScope } from "../crm/native-adapter.js";
18
- import {
19
- parseCrmAccessScope,
20
- relatedSummaries,
21
- scopesAreCompatible,
22
- type RelatedRecordSummary,
23
- } from "../crm/read-through.js";
24
- import { getDb, schema } from "./index.js";
25
-
26
- const MAX_RECORD_LIMIT = 100;
27
- const RECORD_DETAIL_LIMIT = 20;
28
- const MAX_SCOPE_VALIDATIONS = 20;
29
- const SAFE_VIEW_COLUMNS = new Set([
30
- "displayName",
31
- "primaryEmail",
32
- "domain",
33
- "stage",
34
- "ownerName",
35
- "nextContactAt",
36
- "remoteUpdatedAt",
37
- "updatedAt",
38
- ]);
39
- const SAFE_VIEW_SORTS = new Set([
40
- "displayName",
41
- "stage",
42
- "ownerName",
43
- "nextContactAt",
44
- "remoteUpdatedAt",
45
- "updatedAt",
46
- ]);
47
-
48
- export type CrmListKind =
49
- | "account"
50
- | "person"
51
- | "opportunity"
52
- | "activity"
53
- | "task"
54
- | "custom";
55
-
56
- type Primitive = string | number | boolean | null;
57
-
58
- type SavedViewConfig = {
59
- id: string;
60
- name: string;
61
- kind?: CrmListKind;
62
- dataProgramId?: string;
63
- query?: string;
64
- fieldEquals: Record<string, Primitive>;
65
- columns: string[];
66
- sort: Array<{ field: string; direction: "asc" | "desc" }>;
67
- };
68
-
69
- type ScopeValidationTarget = {
70
- connectionId: string;
71
- workspaceConnectionId: string | null;
72
- provider: string;
73
- objectType: string;
74
- };
75
-
76
- export type CrmScopeResolver = (
77
- target: ScopeValidationTarget,
78
- ) => Promise<CrmAccessScope | null>;
79
-
80
- function decodeCursor(cursor: string | undefined): number {
81
- if (!cursor) return 0;
82
- const value = Number.parseInt(cursor, 10);
83
- return Number.isSafeInteger(value) && value >= 0 ? value : 0;
84
- }
85
-
86
- function page<T>(rows: T[], limit: number) {
87
- return {
88
- rows: rows.slice(0, limit),
89
- nextCursor:
90
- rows.length > limit ? String(decodeCursor(undefined) + limit) : undefined,
91
- };
92
- }
93
-
94
- function toRecordSummary(
95
- row: {
96
- id: string;
97
- displayName: string;
98
- kind: string;
99
- primaryEmail: string | null;
100
- domain: string | null;
101
- stage: string | null;
102
- ownerName: string | null;
103
- nextContactAt: string | null;
104
- remoteUpdatedAt: string | null;
105
- updatedAt: string;
106
- },
107
- columns?: string[],
108
- ) {
109
- const included = new Set(columns ?? SAFE_VIEW_COLUMNS);
110
- return {
111
- id: row.id,
112
- displayName: row.displayName,
113
- kind: row.kind,
114
- ...(included.has("domain") || included.has("primaryEmail")
115
- ? { subtitle: row.domain ?? row.primaryEmail ?? undefined }
116
- : {}),
117
- ...(included.has("ownerName") ? { owner: row.ownerName ?? undefined } : {}),
118
- ...(included.has("stage") ? { stage: row.stage ?? undefined } : {}),
119
- ...(included.has("nextContactAt")
120
- ? { nextStep: row.nextContactAt ?? undefined }
121
- : {}),
122
- ...(included.has("remoteUpdatedAt") || included.has("updatedAt")
123
- ? { updatedAt: row.remoteUpdatedAt ?? row.updatedAt }
124
- : {}),
125
- };
126
- }
127
-
128
- function parseStoredValue(row: {
129
- valueType: string;
130
- stringValue: string | null;
131
- numberValue: number | null;
132
- booleanValue: boolean | null;
133
- jsonValue: string | null;
134
- }): unknown {
135
- if (row.booleanValue !== null) return row.booleanValue;
136
- if (row.numberValue !== null) return row.numberValue;
137
- if (row.stringValue !== null) return row.stringValue;
138
- if (!row.jsonValue) return null;
139
- try {
140
- return JSON.parse(row.jsonValue) as unknown;
141
- } catch {
142
- return null;
143
- }
144
- }
145
-
146
- function parsePrimitive(value: unknown): value is Primitive {
147
- return (
148
- value === null ||
149
- typeof value === "boolean" ||
150
- (typeof value === "number" && Number.isFinite(value)) ||
151
- (typeof value === "string" && value.length <= 2_000)
152
- );
153
- }
154
-
155
- function parseJsonObject(value: string): Record<string, unknown> | null {
156
- try {
157
- const parsed = JSON.parse(value) as unknown;
158
- return parsed && typeof parsed === "object" && !Array.isArray(parsed)
159
- ? (parsed as Record<string, unknown>)
160
- : null;
161
- } catch {
162
- return null;
163
- }
164
- }
165
-
166
- function parseSavedViewConfig(input: {
167
- id: string;
168
- name: string;
169
- kind: string | null;
170
- filtersJson: string;
171
- columnsJson: string;
172
- sortJson: string;
173
- dataProgramId: string | null;
174
- }): SavedViewConfig {
175
- if (
176
- input.kind !== null &&
177
- input.kind !== "account" &&
178
- input.kind !== "person" &&
179
- input.kind !== "opportunity"
180
- ) {
181
- throw new Error("CRM saved view has an unsupported record kind.");
182
- }
183
- const filters = parseJsonObject(input.filtersJson);
184
- if (!filters) throw new Error("CRM saved view filters are invalid.");
185
- const filterKeys = Object.keys(filters);
186
- if (filterKeys.some((key) => key !== "query" && key !== "fieldEquals")) {
187
- throw new Error("CRM saved view contains unsupported filters.");
188
- }
189
- const query = filters.query;
190
- if (
191
- query !== undefined &&
192
- (typeof query !== "string" || query.length > 120)
193
- ) {
194
- throw new Error("CRM saved view query is invalid.");
195
- }
196
- const rawFieldEquals = filters.fieldEquals;
197
- if (
198
- rawFieldEquals !== undefined &&
199
- (!rawFieldEquals ||
200
- typeof rawFieldEquals !== "object" ||
201
- Array.isArray(rawFieldEquals))
202
- ) {
203
- throw new Error("CRM saved view field filters are invalid.");
204
- }
205
- const fieldEquals = Object.entries(
206
- (rawFieldEquals ?? {}) as Record<string, unknown>,
207
- );
208
- if (
209
- fieldEquals.length > 12 ||
210
- fieldEquals.some(
211
- ([fieldName, value]) =>
212
- !fieldName.trim() || fieldName.length > 120 || !parsePrimitive(value),
213
- )
214
- ) {
215
- throw new Error("CRM saved view field filters must be bounded primitives.");
216
- }
217
- const columns = JSON.parse(input.columnsJson) as unknown;
218
- if (
219
- !Array.isArray(columns) ||
220
- columns.length > 20 ||
221
- columns.some(
222
- (column) => typeof column !== "string" || !SAFE_VIEW_COLUMNS.has(column),
223
- )
224
- ) {
225
- throw new Error("CRM saved view columns are unsupported.");
226
- }
227
- const sort = JSON.parse(input.sortJson) as unknown;
228
- if (
229
- !Array.isArray(sort) ||
230
- sort.length > 2 ||
231
- sort.some(
232
- (entry) =>
233
- !entry ||
234
- typeof entry !== "object" ||
235
- !SAFE_VIEW_SORTS.has((entry as { field?: string }).field ?? "") ||
236
- ((entry as { direction?: string }).direction !== "asc" &&
237
- (entry as { direction?: string }).direction !== "desc"),
238
- )
239
- ) {
240
- throw new Error("CRM saved view sort is unsupported.");
241
- }
242
- return {
243
- id: input.id,
244
- name: input.name,
245
- ...(input.kind ? { kind: input.kind } : {}),
246
- ...(input.dataProgramId ? { dataProgramId: input.dataProgramId } : {}),
247
- ...(query ? { query } : {}),
248
- fieldEquals: Object.fromEntries(fieldEquals) as Record<string, Primitive>,
249
- columns,
250
- sort: sort as Array<{ field: string; direction: "asc" | "desc" }>,
251
- };
252
- }
253
-
254
- function fieldEqualsCondition(fieldName: string, value: Primitive) {
255
- const scalarCondition =
256
- value === null
257
- ? and(
258
- isNull(schema.crmRecordFields.stringValue),
259
- isNull(schema.crmRecordFields.numberValue),
260
- isNull(schema.crmRecordFields.booleanValue),
261
- isNull(schema.crmRecordFields.jsonValue),
262
- )
263
- : typeof value === "string"
264
- ? eq(schema.crmRecordFields.stringValue, value)
265
- : typeof value === "number"
266
- ? eq(schema.crmRecordFields.numberValue, value)
267
- : eq(schema.crmRecordFields.booleanValue, value);
268
- return exists(
269
- getDb()
270
- .select({ id: schema.crmRecordFields.id })
271
- .from(schema.crmRecordFields)
272
- .where(
273
- and(
274
- eq(schema.crmRecordFields.recordId, schema.crmRecords.id),
275
- eq(schema.crmRecordFields.fieldName, fieldName),
276
- inArray(schema.crmRecordFields.storagePolicy, [
277
- "mirrored",
278
- "derived-local",
279
- "local-authoritative",
280
- ]),
281
- accessFilter(schema.crmRecordFields, schema.crmRecordFieldShares),
282
- scalarCondition,
283
- ),
284
- ),
285
- );
286
- }
287
-
288
- function orderForSavedView(view: SavedViewConfig | undefined) {
289
- const sort = view?.sort[0];
290
- const column =
291
- sort?.field === "displayName"
292
- ? schema.crmRecords.displayName
293
- : sort?.field === "stage"
294
- ? schema.crmRecords.stage
295
- : sort?.field === "ownerName"
296
- ? schema.crmRecords.ownerName
297
- : sort?.field === "nextContactAt"
298
- ? schema.crmRecords.nextContactAt
299
- : sort?.field === "updatedAt"
300
- ? schema.crmRecords.updatedAt
301
- : schema.crmRecords.remoteUpdatedAt;
302
- const direction = sort?.direction === "asc" ? asc : desc;
303
- return [direction(column), desc(schema.crmRecords.id)];
304
- }
305
-
306
- async function defaultScopeResolver(
307
- target: ScopeValidationTarget,
308
- ): Promise<CrmAccessScope | null> {
309
- if (target.provider === "native") {
310
- return resolveNativeCrmAccessScope({
311
- connectionId: target.connectionId,
312
- objectType: target.objectType,
313
- });
314
- }
315
- if (!isConnectedCrmProvider(target.provider) || !target.workspaceConnectionId)
316
- return null;
317
- const adapter = await createConnectedCrmAdapter({
318
- provider: target.provider,
319
- connectionId: target.workspaceConnectionId,
320
- });
321
- return adapter.getAccessScope(target.objectType);
322
- }
323
-
324
- export async function listCrmRecords(
325
- input: {
326
- kind?: CrmListKind;
327
- connectionId?: string;
328
- query?: string;
329
- viewId?: string;
330
- limit: number;
331
- cursor?: string;
332
- },
333
- options: { resolveScope?: CrmScopeResolver } = {},
334
- ) {
335
- const db = getDb();
336
- const offset = decodeCursor(input.cursor);
337
- const limit = Math.min(input.limit, MAX_RECORD_LIMIT);
338
- const viewRow = input.viewId
339
- ? await db
340
- .select({
341
- id: schema.crmSavedViews.id,
342
- name: schema.crmSavedViews.name,
343
- kind: schema.crmSavedViews.kind,
344
- filtersJson: schema.crmSavedViews.filtersJson,
345
- columnsJson: schema.crmSavedViews.columnsJson,
346
- sortJson: schema.crmSavedViews.sortJson,
347
- dataProgramId: schema.crmSavedViews.dataProgramId,
348
- })
349
- .from(schema.crmSavedViews)
350
- .where(
351
- and(
352
- eq(schema.crmSavedViews.id, input.viewId),
353
- accessFilter(schema.crmSavedViews, schema.crmSavedViewShares),
354
- ),
355
- )
356
- .limit(1)
357
- : [];
358
- if (input.viewId && !viewRow[0])
359
- throw new Error("CRM saved view was not found.");
360
- const view = viewRow[0] ? parseSavedViewConfig(viewRow[0]) : undefined;
361
- if (input.kind && view?.kind && input.kind !== view.kind) {
362
- throw new Error("CRM saved view kind cannot be overridden.");
363
- }
364
- if (input.query && view?.query && input.query !== view.query) {
365
- throw new Error("CRM saved view query cannot be overridden.");
366
- }
367
- const kind = view?.kind ?? input.kind;
368
- const query = view?.query ?? input.query;
369
- const conditions = [
370
- accessFilter(schema.crmRecords, schema.crmRecordShares),
371
- accessFilter(schema.crmConnections, schema.crmConnectionShares),
372
- eq(schema.crmRecords.tombstone, false),
373
- ];
374
-
375
- if (kind) conditions.push(eq(schema.crmRecords.kind, kind));
376
- if (input.connectionId) {
377
- conditions.push(eq(schema.crmRecords.connectionId, input.connectionId));
378
- }
379
- if (query) {
380
- conditions.push(like(schema.crmRecords.displayName, `%${query}%`));
381
- }
382
- for (const [fieldName, value] of Object.entries(view?.fieldEquals ?? {})) {
383
- conditions.push(fieldEqualsCondition(fieldName, value));
384
- }
385
-
386
- const rows = await db
387
- .select({
388
- id: schema.crmRecords.id,
389
- displayName: schema.crmRecords.displayName,
390
- kind: schema.crmRecords.kind,
391
- primaryEmail: schema.crmRecords.primaryEmail,
392
- domain: schema.crmRecords.domain,
393
- stage: schema.crmRecords.stage,
394
- ownerName: schema.crmRecords.ownerName,
395
- nextContactAt: schema.crmRecords.nextContactAt,
396
- remoteUpdatedAt: schema.crmRecords.remoteUpdatedAt,
397
- updatedAt: schema.crmRecords.updatedAt,
398
- connectionId: schema.crmRecords.connectionId,
399
- objectType: schema.crmRecords.objectType,
400
- provider: schema.crmRecords.provider,
401
- accessScopeJson: schema.crmRecords.accessScopeJson,
402
- workspaceConnectionId: schema.crmConnections.workspaceConnectionId,
403
- })
404
- .from(schema.crmRecords)
405
- .innerJoin(
406
- schema.crmConnections,
407
- eq(schema.crmRecords.connectionId, schema.crmConnections.id),
408
- )
409
- .where(and(...conditions))
410
- .orderBy(...orderForSavedView(view))
411
- .limit(limit + 1)
412
- .offset(offset);
413
- const result = page(rows, limit);
414
- const scopeResolver = options.resolveScope ?? defaultScopeResolver;
415
- const scopeTargets = Array.from(
416
- new Map(
417
- result.rows.map((row) => [
418
- `${row.connectionId}:${row.objectType}`,
419
- {
420
- connectionId: row.connectionId,
421
- workspaceConnectionId: row.workspaceConnectionId,
422
- provider: row.provider,
423
- objectType: row.objectType,
424
- },
425
- ]),
426
- ).values(),
427
- ).slice(0, MAX_SCOPE_VALIDATIONS);
428
- const currentScopes = new Map(
429
- await Promise.all(
430
- scopeTargets.map(
431
- async (target) =>
432
- [
433
- `${target.connectionId}:${target.objectType}`,
434
- await scopeResolver(target).catch(() => null),
435
- ] as const,
436
- ),
437
- ),
438
- );
439
- const records = result.rows
440
- .filter((row) => {
441
- const current = currentScopes.get(
442
- `${row.connectionId}:${row.objectType}`,
443
- );
444
- return Boolean(
445
- current &&
446
- scopesAreCompatible(parseCrmAccessScope(row.accessScopeJson), current),
447
- );
448
- })
449
- .map((row) => toRecordSummary(row, view?.columns));
450
-
451
- return {
452
- records,
453
- nextCursor: result.nextCursor ? String(offset + limit) : undefined,
454
- complete: !result.nextCursor,
455
- ...(view
456
- ? {
457
- appliedView: {
458
- id: view.id,
459
- name: view.name,
460
- ...(view.kind ? { kind: view.kind } : {}),
461
- ...(view.query ? { query: view.query } : {}),
462
- ...(view.dataProgramId
463
- ? { dataProgramId: view.dataProgramId }
464
- : {}),
465
- fieldEquals: view.fieldEquals,
466
- columns: view.columns,
467
- sort: view.sort,
468
- },
469
- }
470
- : {}),
471
- };
472
- }
473
-
474
- export type CrmRecordReadContext = {
475
- id: string;
476
- connectionId: string;
477
- workspaceConnectionId: string | null;
478
- provider: string;
479
- objectType: string;
480
- kind: CrmListKind;
481
- remoteId: string;
482
- accessScopeJson: string;
483
- ownerEmail: string;
484
- orgId: string | null;
485
- visibility: "private" | "org" | "public";
486
- fieldPolicies: Array<{
487
- fieldName: string;
488
- storagePolicy:
489
- | "mirrored"
490
- | "remote-only"
491
- | "redacted"
492
- | "derived-local"
493
- | "local-authoritative";
494
- readable: boolean;
495
- sensitive: boolean;
496
- }>;
497
- };
498
-
499
- export async function getCrmRecordReadContext(
500
- recordId: string,
501
- ): Promise<CrmRecordReadContext | null> {
502
- const db = getDb();
503
- const [record] = await db
504
- .select({
505
- id: schema.crmRecords.id,
506
- connectionId: schema.crmRecords.connectionId,
507
- workspaceConnectionId: schema.crmConnections.workspaceConnectionId,
508
- provider: schema.crmRecords.provider,
509
- objectType: schema.crmRecords.objectType,
510
- kind: schema.crmRecords.kind,
511
- remoteId: schema.crmRecords.remoteId,
512
- accessScopeJson: schema.crmRecords.accessScopeJson,
513
- ownerEmail: schema.crmRecords.ownerEmail,
514
- orgId: schema.crmRecords.orgId,
515
- visibility: schema.crmRecords.visibility,
516
- })
517
- .from(schema.crmRecords)
518
- .innerJoin(
519
- schema.crmConnections,
520
- eq(schema.crmRecords.connectionId, schema.crmConnections.id),
521
- )
522
- .where(
523
- and(
524
- eq(schema.crmRecords.id, recordId),
525
- eq(schema.crmRecords.tombstone, false),
526
- accessFilter(schema.crmRecords, schema.crmRecordShares),
527
- accessFilter(schema.crmConnections, schema.crmConnectionShares),
528
- ),
529
- )
530
- .limit(1);
531
- if (!record) return null;
532
- const fieldPolicies = await db
533
- .select({
534
- fieldName: schema.crmFieldPolicies.fieldName,
535
- storagePolicy: schema.crmFieldPolicies.storagePolicy,
536
- readable: schema.crmFieldPolicies.readable,
537
- sensitive: schema.crmFieldPolicies.sensitive,
538
- })
539
- .from(schema.crmFieldPolicies)
540
- .where(
541
- and(
542
- eq(schema.crmFieldPolicies.connectionId, record.connectionId),
543
- eq(schema.crmFieldPolicies.objectType, record.objectType),
544
- accessFilter(schema.crmFieldPolicies, schema.crmFieldPolicyShares),
545
- ),
546
- );
547
- return {
548
- ...record,
549
- kind: record.kind as CrmListKind,
550
- visibility: record.visibility as "private" | "org" | "public",
551
- fieldPolicies: fieldPolicies as CrmRecordReadContext["fieldPolicies"],
552
- };
553
- }
554
-
555
- type ReadThroughRelationshipLocalRecord = {
556
- id: string;
557
- remoteId: string;
558
- objectType: string;
559
- displayName: string;
560
- kind: string;
561
- primaryEmail: string | null;
562
- domain: string | null;
563
- accessScopeJson: string;
564
- };
565
-
566
- type ReadThroughRelationshipData = {
567
- relationships: CrmRelationship[];
568
- localRecords: ReadThroughRelationshipLocalRecord[];
569
- summaries: Array<
570
- RelatedRecordSummary & { localId: string; remoteId: string }
571
- >;
572
- };
573
-
574
- async function readThroughRelationshipData(input: {
575
- context: CrmRecordReadContext;
576
- relationships: CrmRelationship[];
577
- currentScopes: Map<string, CrmAccessScope>;
578
- }): Promise<ReadThroughRelationshipData> {
579
- const relationships = input.relationships
580
- .filter(
581
- (relationship) =>
582
- relationship.from.connectionId === input.context.connectionId &&
583
- relationship.from.objectType === input.context.objectType &&
584
- relationship.from.remoteId === input.context.remoteId &&
585
- relationship.to.connectionId === input.context.connectionId,
586
- )
587
- .slice(0, 100);
588
- if (!relationships.length) {
589
- return { relationships: [], localRecords: [], summaries: [] };
590
- }
591
-
592
- const db = getDb();
593
- const remoteIds = Array.from(
594
- new Set(relationships.map((relationship) => relationship.to.remoteId)),
595
- );
596
- const objectTypes = Array.from(
597
- new Set(relationships.map((relationship) => relationship.to.objectType)),
598
- );
599
- const candidates = await db
600
- .select({
601
- id: schema.crmRecords.id,
602
- remoteId: schema.crmRecords.remoteId,
603
- objectType: schema.crmRecords.objectType,
604
- displayName: schema.crmRecords.displayName,
605
- kind: schema.crmRecords.kind,
606
- primaryEmail: schema.crmRecords.primaryEmail,
607
- domain: schema.crmRecords.domain,
608
- accessScopeJson: schema.crmRecords.accessScopeJson,
609
- })
610
- .from(schema.crmRecords)
611
- .where(
612
- and(
613
- eq(schema.crmRecords.connectionId, input.context.connectionId),
614
- eq(schema.crmRecords.tombstone, false),
615
- inArray(schema.crmRecords.remoteId, remoteIds),
616
- inArray(schema.crmRecords.objectType, objectTypes),
617
- accessFilter(schema.crmRecords, schema.crmRecordShares),
618
- ),
619
- );
620
- const localRecords = candidates.filter((record) => {
621
- const current = input.currentScopes.get(record.objectType);
622
- return Boolean(
623
- current &&
624
- scopesAreCompatible(parseCrmAccessScope(record.accessScopeJson), current),
625
- );
626
- });
627
- const summaries = relatedSummaries(
628
- input.context.remoteId,
629
- relationships,
630
- localRecords,
631
- );
632
- return { relationships, localRecords, summaries };
633
- }
634
-
635
- export async function getReadThroughRelationshipSummaries(input: {
636
- context: CrmRecordReadContext;
637
- relationships: CrmRelationship[];
638
- currentScopes: Map<string, CrmAccessScope>;
639
- }): Promise<RelatedRecordSummary[]> {
640
- const { summaries } = await readThroughRelationshipData(input);
641
- return summaries;
642
- }
643
-
644
- export async function persistReadThroughRelationships(input: {
645
- context: CrmRecordReadContext;
646
- relationships: CrmRelationship[];
647
- currentScopes: Map<string, CrmAccessScope>;
648
- }): Promise<RelatedRecordSummary[]> {
649
- await assertAccess("crm-record", input.context.id, "editor");
650
- const { relationships, localRecords, summaries } =
651
- await readThroughRelationshipData(input);
652
- if (!summaries.length) return [];
653
-
654
- const db = getDb();
655
- const relationshipByTarget = new Map(
656
- relationships.map((relationship) => [
657
- `${relationship.to.objectType}:${relationship.to.remoteId}:${relationship.relationshipType}`,
658
- relationship,
659
- ]),
660
- );
661
- const now = new Date().toISOString();
662
- await db.transaction(async (tx) => {
663
- for (const summary of summaries) {
664
- const target = localRecords.find((record) => record.id === summary.id);
665
- const relationship = target
666
- ? relationshipByTarget.get(
667
- `${target.objectType}:${summary.remoteId}:${summary.relationshipType}`,
668
- )
669
- : undefined;
670
- if (!relationship) continue;
671
- const [existing] = await tx
672
- .select({ id: schema.crmRelationships.id })
673
- .from(schema.crmRelationships)
674
- .where(
675
- and(
676
- eq(
677
- schema.crmRelationships.connectionId,
678
- input.context.connectionId,
679
- ),
680
- eq(schema.crmRelationships.fromRecordId, input.context.id),
681
- eq(schema.crmRelationships.toRecordId, summary.id),
682
- eq(
683
- schema.crmRelationships.relationshipType,
684
- relationship.relationshipType,
685
- ),
686
- accessFilter(schema.crmRelationships, schema.crmRelationshipShares),
687
- ),
688
- )
689
- .limit(1);
690
- const values = {
691
- label: relationship.label ?? null,
692
- inverseLabel: relationship.inverseLabel ?? null,
693
- sourceField: relationship.sourceField ?? null,
694
- tombstone: false,
695
- lastSyncedAt: now,
696
- updatedAt: now,
697
- };
698
- if (existing) {
699
- await tx
700
- .update(schema.crmRelationships)
701
- .set(values)
702
- .where(
703
- and(
704
- eq(schema.crmRelationships.id, existing.id),
705
- accessFilter(
706
- schema.crmRelationships,
707
- schema.crmRelationshipShares,
708
- undefined,
709
- "editor",
710
- ),
711
- ),
712
- );
713
- } else {
714
- await tx.insert(schema.crmRelationships).values({
715
- id: crypto.randomUUID(),
716
- connectionId: input.context.connectionId,
717
- fromRecordId: input.context.id,
718
- toRecordId: summary.id,
719
- relationshipType: relationship.relationshipType,
720
- ...values,
721
- ownerEmail: input.context.ownerEmail,
722
- orgId: input.context.orgId,
723
- visibility: input.context.visibility,
724
- createdAt: now,
725
- });
726
- }
727
- }
728
- });
729
- return summaries;
730
- }
731
-
732
- export async function getCrmRecord(
733
- recordId: string,
734
- readThrough?: {
735
- displayName?: string;
736
- fields?: Record<string, CrmValue>;
737
- remoteRevision?: string;
738
- remoteUpdatedAt?: string;
739
- relatedRecords?: RelatedRecordSummary[];
740
- accessScope?: CrmAccessScope;
741
- },
742
- ) {
743
- const db = getDb();
744
- const [record] = await db
745
- .select({
746
- id: schema.crmRecords.id,
747
- provider: schema.crmRecords.provider,
748
- displayName: schema.crmRecords.displayName,
749
- kind: schema.crmRecords.kind,
750
- primaryEmail: schema.crmRecords.primaryEmail,
751
- domain: schema.crmRecords.domain,
752
- stage: schema.crmRecords.stage,
753
- ownerName: schema.crmRecords.ownerName,
754
- desiredCadenceDays: schema.crmRecords.desiredCadenceDays,
755
- nextContactAt: schema.crmRecords.nextContactAt,
756
- remoteRevision: schema.crmRecords.remoteRevision,
757
- remoteUpdatedAt: schema.crmRecords.remoteUpdatedAt,
758
- updatedAt: schema.crmRecords.updatedAt,
759
- accessScopeJson: schema.crmRecords.accessScopeJson,
760
- })
761
- .from(schema.crmRecords)
762
- .where(
763
- and(
764
- accessFilter(schema.crmRecords, schema.crmRecordShares),
765
- eq(schema.crmRecords.id, recordId),
766
- eq(schema.crmRecords.tombstone, false),
767
- ),
768
- )
769
- .limit(1);
770
-
771
- if (!record) return null;
772
-
773
- const [fieldRows, activityRows, evidenceRows, taskRows] = await Promise.all([
774
- db
775
- .select({
776
- fieldName: schema.crmRecordFields.fieldName,
777
- valueType: schema.crmRecordFields.valueType,
778
- stringValue: schema.crmRecordFields.stringValue,
779
- numberValue: schema.crmRecordFields.numberValue,
780
- booleanValue: schema.crmRecordFields.booleanValue,
781
- jsonValue: schema.crmRecordFields.jsonValue,
782
- accessScopeJson: schema.crmRecordFields.accessScopeJson,
783
- })
784
- .from(schema.crmRecordFields)
785
- .where(
786
- and(
787
- accessFilter(schema.crmRecordFields, schema.crmRecordFieldShares),
788
- eq(schema.crmRecordFields.recordId, recordId),
789
- ),
790
- )
791
- .orderBy(desc(schema.crmRecordFields.updatedAt))
792
- .limit(MAX_RECORD_LIMIT),
793
- db
794
- .select({
795
- id: schema.crmInteractions.id,
796
- title: schema.crmInteractions.title,
797
- summary: schema.crmInteractions.summary,
798
- occurredAt: schema.crmInteractions.occurredAt,
799
- sourceApp: schema.crmInteractions.sourceApp,
800
- })
801
- .from(schema.crmInteractions)
802
- .where(
803
- and(
804
- accessFilter(schema.crmInteractions, schema.crmInteractionShares),
805
- eq(schema.crmInteractions.recordId, recordId),
806
- ),
807
- )
808
- .orderBy(desc(schema.crmInteractions.occurredAt))
809
- .limit(RECORD_DETAIL_LIMIT),
810
- db
811
- .select({
812
- id: schema.crmCallEvidence.id,
813
- artifactType: schema.crmCallEvidence.artifactType,
814
- quote: schema.crmCallEvidence.quote,
815
- sourceUrl: schema.crmCallEvidence.sourceUrl,
816
- capturedAt: schema.crmCallEvidence.capturedAt,
817
- summary: schema.crmCallEvidence.summary,
818
- })
819
- .from(schema.crmCallEvidence)
820
- .where(
821
- and(
822
- accessFilter(schema.crmCallEvidence, schema.crmCallEvidenceShares),
823
- eq(schema.crmCallEvidence.recordId, recordId),
824
- ),
825
- )
826
- .orderBy(desc(schema.crmCallEvidence.capturedAt))
827
- .limit(RECORD_DETAIL_LIMIT),
828
- db
829
- .select({
830
- id: schema.crmTasks.id,
831
- title: schema.crmTasks.title,
832
- status: schema.crmTasks.status,
833
- dueAt: schema.crmTasks.dueAt,
834
- recordId: schema.crmTasks.recordId,
835
- })
836
- .from(schema.crmTasks)
837
- .where(
838
- and(
839
- accessFilter(schema.crmTasks, schema.crmTaskShares),
840
- eq(schema.crmTasks.recordId, recordId),
841
- ),
842
- )
843
- .orderBy(desc(schema.crmTasks.updatedAt))
844
- .limit(RECORD_DETAIL_LIMIT),
845
- ]);
846
-
847
- return {
848
- ...toRecordSummary(record),
849
- provider: record.provider,
850
- ...(readThrough?.displayName
851
- ? { displayName: readThrough.displayName }
852
- : {}),
853
- ...(readThrough?.remoteUpdatedAt
854
- ? { updatedAt: readThrough.remoteUpdatedAt }
855
- : {}),
856
- ...(readThrough?.remoteRevision
857
- ? { remoteRevision: readThrough.remoteRevision }
858
- : record.remoteRevision
859
- ? { remoteRevision: record.remoteRevision }
860
- : {}),
861
- cadence: record.desiredCadenceDays
862
- ? `Every ${record.desiredCadenceDays} days`
863
- : undefined,
864
- fields: {
865
- ...Object.fromEntries(
866
- fieldRows.flatMap((field) => {
867
- const scope =
868
- readThrough?.accessScope ??
869
- parseCrmAccessScope(record.accessScopeJson);
870
- if (
871
- !scope ||
872
- !scopesAreCompatible(
873
- parseCrmAccessScope(field.accessScopeJson),
874
- scope,
875
- )
876
- ) {
877
- return [];
878
- }
879
- const value = parseStoredValue(field);
880
- return value === null ? [] : [[field.fieldName, value]];
881
- }),
882
- ),
883
- ...(readThrough?.fields ?? {}),
884
- },
885
- activity: activityRows.map((activity) => ({
886
- ...activity,
887
- actor: activity.sourceApp ?? undefined,
888
- })),
889
- evidence: evidenceRows.map((evidence) => ({
890
- id: evidence.id,
891
- label: evidence.summary || evidence.artifactType,
892
- quote: evidence.quote || undefined,
893
- url: evidence.sourceUrl,
894
- observedAt: evidence.capturedAt,
895
- })),
896
- tasks: taskRows,
897
- relatedRecords: readThrough?.relatedRecords ?? [],
898
- };
899
- }
900
-
901
- export async function listCrmTasks(input: {
902
- recordId?: string;
903
- status?: "open" | "done" | "cancelled";
904
- limit: number;
905
- cursor?: string;
906
- }) {
907
- const db = getDb();
908
- const offset = decodeCursor(input.cursor);
909
- const limit = Math.min(input.limit, MAX_RECORD_LIMIT);
910
- const conditions = [accessFilter(schema.crmTasks, schema.crmTaskShares)];
911
- if (input.recordId)
912
- conditions.push(eq(schema.crmTasks.recordId, input.recordId));
913
- if (input.status) conditions.push(eq(schema.crmTasks.status, input.status));
914
-
915
- const rows = await db
916
- .select({
917
- id: schema.crmTasks.id,
918
- title: schema.crmTasks.title,
919
- status: schema.crmTasks.status,
920
- dueAt: schema.crmTasks.dueAt,
921
- recordId: schema.crmTasks.recordId,
922
- assignedTo: schema.crmTasks.assignedTo,
923
- authority: schema.crmTasks.authority,
924
- updatedAt: schema.crmTasks.updatedAt,
925
- })
926
- .from(schema.crmTasks)
927
- .where(and(...conditions))
928
- .orderBy(desc(schema.crmTasks.dueAt), desc(schema.crmTasks.id))
929
- .limit(limit + 1)
930
- .offset(offset);
931
- const result = page(rows, limit);
932
-
933
- return {
934
- tasks: result.rows,
935
- nextCursor: result.nextCursor ? String(offset + limit) : undefined,
936
- complete: !result.nextCursor,
937
- };
938
- }
939
-
940
- export async function listCrmSavedViews(input: { limit: number }) {
941
- const rows = await getDb()
942
- .select({
943
- id: schema.crmSavedViews.id,
944
- name: schema.crmSavedViews.name,
945
- description: schema.crmSavedViews.description,
946
- kind: schema.crmSavedViews.kind,
947
- filtersJson: schema.crmSavedViews.filtersJson,
948
- dataProgramId: schema.crmSavedViews.dataProgramId,
949
- pinned: schema.crmSavedViews.pinned,
950
- updatedAt: schema.crmSavedViews.updatedAt,
951
- })
952
- .from(schema.crmSavedViews)
953
- .where(accessFilter(schema.crmSavedViews, schema.crmSavedViewShares))
954
- .orderBy(
955
- desc(schema.crmSavedViews.pinned),
956
- desc(schema.crmSavedViews.updatedAt),
957
- )
958
- .limit(Math.min(input.limit, MAX_RECORD_LIMIT));
959
-
960
- return {
961
- views: rows.map((view) => ({
962
- id: view.id,
963
- name: view.name,
964
- description: view.description || undefined,
965
- kind: view.kind ?? undefined,
966
- query: view.filtersJson === "{}" ? undefined : view.filtersJson,
967
- dataProgramId: view.dataProgramId ?? undefined,
968
- pinned: view.pinned,
969
- updatedAt: view.updatedAt,
970
- })),
971
- };
972
- }
973
-
974
- export async function listCrmSignals(input: {
975
- recordId: string;
976
- limit: number;
977
- }) {
978
- return getDb()
979
- .select({
980
- id: schema.crmSignals.id,
981
- label: schema.crmSignals.label,
982
- kind: schema.crmSignals.kind,
983
- confidence: schema.crmSignals.confidence,
984
- reviewStatus: schema.crmSignals.reviewStatus,
985
- evidenceId: schema.crmSignals.evidenceId,
986
- startSeconds: schema.crmSignals.startSeconds,
987
- createdAt: schema.crmSignals.createdAt,
988
- })
989
- .from(schema.crmSignals)
990
- .where(
991
- and(
992
- eq(schema.crmSignals.recordId, input.recordId),
993
- accessFilter(schema.crmSignals, schema.crmSignalShares),
994
- ),
995
- )
996
- .orderBy(desc(schema.crmSignals.createdAt))
997
- .limit(Math.min(Math.max(input.limit, 1), 100));
998
- }
999
-
1000
- export function safeProposalValues(value: string): Record<string, Primitive> {
1001
- const parsed = parseJsonObject(value);
1002
- const fields = parsed?.fields;
1003
- const source =
1004
- fields && typeof fields === "object" && !Array.isArray(fields)
1005
- ? (fields as Record<string, unknown>)
1006
- : (parsed ?? {});
1007
- return Object.fromEntries(
1008
- Object.entries(source)
1009
- .filter(
1010
- ([fieldName, fieldValue]) =>
1011
- fieldName.length <= 120 &&
1012
- isSafeCrmMutationFieldName(fieldName) &&
1013
- isBoundedCrmValue(fieldValue) &&
1014
- parsePrimitive(fieldValue),
1015
- )
1016
- .slice(0, 20),
1017
- ) as Record<string, Primitive>;
1018
- }
1019
-
1020
- export async function listCrmProposals(input: {
1021
- recordId?: string;
1022
- status?:
1023
- | "pending"
1024
- | "approved"
1025
- | "applied"
1026
- | "rejected"
1027
- | "conflict"
1028
- | "failed";
1029
- limit: number;
1030
- cursor?: string;
1031
- }) {
1032
- const db = getDb();
1033
- const offset = decodeCursor(input.cursor);
1034
- const limit = Math.min(input.limit, MAX_RECORD_LIMIT);
1035
- const conditions = [
1036
- accessFilter(schema.crmMutations, schema.crmMutationShares),
1037
- ];
1038
- if (input.recordId) {
1039
- conditions.push(eq(schema.crmMutations.recordId, input.recordId));
1040
- }
1041
- if (input.status)
1042
- conditions.push(eq(schema.crmMutations.status, input.status));
1043
-
1044
- const rows = await db
1045
- .select({
1046
- id: schema.crmMutations.id,
1047
- recordId: schema.crmMutations.recordId,
1048
- operation: schema.crmMutations.operation,
1049
- initiatedBy: schema.crmMutations.initiatedBy,
1050
- target: schema.crmMutations.target,
1051
- policyDecision: schema.crmMutations.policyDecision,
1052
- risk: schema.crmMutations.risk,
1053
- status: schema.crmMutations.status,
1054
- expectedRemoteRevision: schema.crmMutations.expectedRemoteRevision,
1055
- patchJson: schema.crmMutations.patchJson,
1056
- beforeJson: schema.crmMutations.beforeJson,
1057
- afterJson: schema.crmMutations.afterJson,
1058
- createdAt: schema.crmMutations.createdAt,
1059
- appliedAt: schema.crmMutations.appliedAt,
1060
- })
1061
- .from(schema.crmMutations)
1062
- .where(and(...conditions))
1063
- .orderBy(desc(schema.crmMutations.createdAt), desc(schema.crmMutations.id))
1064
- .limit(limit + 1)
1065
- .offset(offset);
1066
- const result = page(rows, limit);
1067
- const recordIds = result.rows
1068
- .map((row) => row.recordId)
1069
- .filter((recordId): recordId is string => Boolean(recordId));
1070
- const records = recordIds.length
1071
- ? await db
1072
- .select({
1073
- id: schema.crmRecords.id,
1074
- displayName: schema.crmRecords.displayName,
1075
- provider: schema.crmRecords.provider,
1076
- })
1077
- .from(schema.crmRecords)
1078
- .where(
1079
- and(
1080
- inArray(schema.crmRecords.id, recordIds),
1081
- accessFilter(schema.crmRecords, schema.crmRecordShares),
1082
- ),
1083
- )
1084
- : [];
1085
- const displayNameByRecordId = new Map(
1086
- records.map((record) => [record.id, record.displayName]),
1087
- );
1088
- const providerByRecordId = new Map(
1089
- records.map((record) => [record.id, record.provider]),
1090
- );
1091
-
1092
- return {
1093
- proposals: result.rows.map((proposal) => {
1094
- const patch = safeProposalValues(proposal.patchJson);
1095
- const before = safeProposalValues(proposal.beforeJson);
1096
- const after = safeProposalValues(proposal.afterJson);
1097
- const fieldNames = Array.from(
1098
- new Set([
1099
- ...Object.keys(before),
1100
- ...Object.keys(after),
1101
- ...Object.keys(patch),
1102
- ]),
1103
- ).slice(0, 20);
1104
- return {
1105
- id: proposal.id,
1106
- recordId: proposal.recordId,
1107
- ...(proposal.recordId && displayNameByRecordId.get(proposal.recordId)
1108
- ? { recordName: displayNameByRecordId.get(proposal.recordId) }
1109
- : {}),
1110
- ...(proposal.recordId && providerByRecordId.get(proposal.recordId)
1111
- ? { provider: providerByRecordId.get(proposal.recordId) }
1112
- : {}),
1113
- operation: proposal.operation,
1114
- initiatedBy: proposal.initiatedBy,
1115
- target: proposal.target,
1116
- policyDecision: proposal.policyDecision,
1117
- risk: proposal.risk,
1118
- status: proposal.status,
1119
- expectedRemoteRevision: proposal.expectedRemoteRevision,
1120
- createdAt: proposal.createdAt,
1121
- appliedAt: proposal.appliedAt,
1122
- fields: fieldNames.map((name) => ({
1123
- name,
1124
- ...(Object.prototype.hasOwnProperty.call(before, name)
1125
- ? { before: before[name] }
1126
- : {}),
1127
- ...(Object.prototype.hasOwnProperty.call(
1128
- Object.keys(after).length ? after : patch,
1129
- name,
1130
- )
1131
- ? { after: (Object.keys(after).length ? after : patch)[name] }
1132
- : {}),
1133
- })),
1134
- };
1135
- }),
1136
- nextCursor: result.nextCursor ? String(offset + limit) : undefined,
1137
- complete: !result.nextCursor,
1138
- };
1139
- }
1140
-
1141
- export async function getCrmOverview() {
1142
- const [taskResult, recordResult, proposalResult] = await Promise.all([
1143
- listCrmTasks({ status: "open", limit: 8 }),
1144
- listCrmRecords({ limit: 5 }),
1145
- listCrmProposals({ status: "pending", limit: 1 }),
1146
- ]);
1147
-
1148
- return {
1149
- tasks: taskResult.tasks,
1150
- records: recordResult.records,
1151
- focus: [
1152
- {
1153
- label: "Open follow-up",
1154
- value: String(taskResult.tasks.length),
1155
- detail: taskResult.complete
1156
- ? "Tasks currently due for attention."
1157
- : "Showing the first 8 tasks due for attention.",
1158
- },
1159
- ...(proposalResult.proposals.length
1160
- ? [
1161
- {
1162
- label: "Pending proposal",
1163
- value: "1+",
1164
- detail: "A CRM write is waiting for review.",
1165
- },
1166
- ]
1167
- : []),
1168
- ],
1169
- };
1170
- }