@agent-native/core 0.123.0 → 0.123.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/templates-meta.ts +12 -0
  5. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +115 -0
  6. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +177 -0
  7. package/corpus/templates/crm/.agents/skills/crm/SKILL.md +205 -0
  8. package/corpus/templates/crm/.agents/skills/customizing-agent-native/SKILL.md +220 -0
  9. package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +169 -0
  10. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +228 -0
  11. package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +178 -0
  12. package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +122 -0
  13. package/corpus/templates/crm/.env.example +5 -0
  14. package/corpus/templates/crm/AGENTS.md +82 -0
  15. package/corpus/templates/crm/CHANGELOG.md +25 -0
  16. package/corpus/templates/crm/DEVELOPING.md +66 -0
  17. package/corpus/templates/crm/README.md +122 -0
  18. package/corpus/templates/crm/_gitignore +12 -0
  19. package/corpus/templates/crm/actions/_crm-action-utils.ts +98 -0
  20. package/corpus/templates/crm/actions/_crm-attribute-utils.ts +219 -0
  21. package/corpus/templates/crm/actions/_crm-dashboard.ts +69 -0
  22. package/corpus/templates/crm/actions/_crm-data-program-actions.ts +15 -0
  23. package/corpus/templates/crm/actions/_crm-list-utils.ts +775 -0
  24. package/corpus/templates/crm/actions/_crm-signal-utils.ts +79 -0
  25. package/corpus/templates/crm/actions/add-crm-record-to-list.ts +164 -0
  26. package/corpus/templates/crm/actions/apply-crm-proposals.ts +291 -0
  27. package/corpus/templates/crm/actions/archive-crm-attribute.ts +69 -0
  28. package/corpus/templates/crm/actions/attach-call-evidence.ts +170 -0
  29. package/corpus/templates/crm/actions/configure-crm-connection.ts +126 -0
  30. package/corpus/templates/crm/actions/configure-native-crm.ts +44 -0
  31. package/corpus/templates/crm/actions/create-crm-attribute.ts +350 -0
  32. package/corpus/templates/crm/actions/create-crm-list.ts +194 -0
  33. package/corpus/templates/crm/actions/create-crm-record.ts +148 -0
  34. package/corpus/templates/crm/actions/create-crm-signal-tracker.ts +58 -0
  35. package/corpus/templates/crm/actions/delete-crm-saved-view.ts +68 -0
  36. package/corpus/templates/crm/actions/delete-staged-dataset.ts +14 -0
  37. package/corpus/templates/crm/actions/estimate-crm-enrichment.ts +229 -0
  38. package/corpus/templates/crm/actions/find-crm-duplicates.ts +87 -0
  39. package/corpus/templates/crm/actions/get-crm-automation-recipe.ts +36 -0
  40. package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +35 -0
  41. package/corpus/templates/crm/actions/get-crm-dashboard.ts +14 -0
  42. package/corpus/templates/crm/actions/get-crm-overview.ts +13 -0
  43. package/corpus/templates/crm/actions/get-crm-pipeline-data.ts +61 -0
  44. package/corpus/templates/crm/actions/get-crm-record-page.ts +287 -0
  45. package/corpus/templates/crm/actions/get-crm-record.ts +75 -0
  46. package/corpus/templates/crm/actions/get-crm-workspace.ts +364 -0
  47. package/corpus/templates/crm/actions/install-crm-pipeline-dashboard.ts +81 -0
  48. package/corpus/templates/crm/actions/list-crm-attributes.ts +71 -0
  49. package/corpus/templates/crm/actions/list-crm-connections.ts +68 -0
  50. package/corpus/templates/crm/actions/list-crm-dashboard-revisions.ts +15 -0
  51. package/corpus/templates/crm/actions/list-crm-dashboards.ts +15 -0
  52. package/corpus/templates/crm/actions/list-crm-enrichment-slots.ts +28 -0
  53. package/corpus/templates/crm/actions/list-crm-list-entries.ts +192 -0
  54. package/corpus/templates/crm/actions/list-crm-lists.ts +80 -0
  55. package/corpus/templates/crm/actions/list-crm-proposals.ts +55 -0
  56. package/corpus/templates/crm/actions/list-crm-record-field-history.ts +201 -0
  57. package/corpus/templates/crm/actions/list-crm-record-values.ts +200 -0
  58. package/corpus/templates/crm/actions/list-crm-records.ts +103 -0
  59. package/corpus/templates/crm/actions/list-crm-saved-views.ts +66 -0
  60. package/corpus/templates/crm/actions/list-crm-signal-hits.ts +43 -0
  61. package/corpus/templates/crm/actions/list-crm-signal-trackers.ts +32 -0
  62. package/corpus/templates/crm/actions/list-crm-tasks.ts +22 -0
  63. package/corpus/templates/crm/actions/list-staged-datasets.ts +14 -0
  64. package/corpus/templates/crm/actions/list-workspace-connections.ts +47 -0
  65. package/corpus/templates/crm/actions/manage-crm-attribute-option.ts +272 -0
  66. package/corpus/templates/crm/actions/manage-crm-signal-tracker.ts +87 -0
  67. package/corpus/templates/crm/actions/manage-crm-task.ts +132 -0
  68. package/corpus/templates/crm/actions/merge-crm-records.ts +691 -0
  69. package/corpus/templates/crm/actions/navigate.ts +54 -0
  70. package/corpus/templates/crm/actions/provider-api-catalog.ts +22 -0
  71. package/corpus/templates/crm/actions/provider-api-docs.ts +54 -0
  72. package/corpus/templates/crm/actions/provider-api-request.ts +92 -0
  73. package/corpus/templates/crm/actions/query-staged-dataset.ts +44 -0
  74. package/corpus/templates/crm/actions/record-crm-call-insight.ts +160 -0
  75. package/corpus/templates/crm/actions/record-crm-smart-signal.ts +150 -0
  76. package/corpus/templates/crm/actions/remove-crm-list-entry.ts +79 -0
  77. package/corpus/templates/crm/actions/restore-crm-dashboard-revision.ts +23 -0
  78. package/corpus/templates/crm/actions/review-crm-signal.ts +31 -0
  79. package/corpus/templates/crm/actions/run-crm-attribute-fill.ts +443 -0
  80. package/corpus/templates/crm/actions/run-crm-enrichment.ts +647 -0
  81. package/corpus/templates/crm/actions/run-crm-saved-view-program.ts +103 -0
  82. package/corpus/templates/crm/actions/run-crm-signal-trackers.ts +265 -0
  83. package/corpus/templates/crm/actions/run.ts +7 -0
  84. package/corpus/templates/crm/actions/save-crm-dashboard.ts +51 -0
  85. package/corpus/templates/crm/actions/save-crm-saved-view.ts +296 -0
  86. package/corpus/templates/crm/actions/sync-crm.ts +146 -0
  87. package/corpus/templates/crm/actions/update-crm-attribute.ts +111 -0
  88. package/corpus/templates/crm/actions/update-crm-list-entry.ts +102 -0
  89. package/corpus/templates/crm/actions/update-crm-list.ts +97 -0
  90. package/corpus/templates/crm/actions/update-crm-record.ts +543 -0
  91. package/corpus/templates/crm/actions/view-screen.ts +324 -0
  92. package/corpus/templates/crm/agent-native.app-skill.json +69 -0
  93. package/corpus/templates/crm/app/components/crm/CreateCrmRecordDialog.tsx +158 -0
  94. package/corpus/templates/crm/app/components/crm/CrmDashboardPanel.tsx +78 -0
  95. package/corpus/templates/crm/app/components/crm/CrmSignalsPanel.tsx +220 -0
  96. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +414 -0
  97. package/corpus/templates/crm/app/components/crm/RecordActions.tsx +648 -0
  98. package/corpus/templates/crm/app/components/crm/RecordGrid.tsx +488 -0
  99. package/corpus/templates/crm/app/components/crm/RecordWorkspace.tsx +232 -0
  100. package/corpus/templates/crm/app/components/crm/SavedViewDataProgram.tsx +93 -0
  101. package/corpus/templates/crm/app/components/crm/Surface.tsx +97 -0
  102. package/corpus/templates/crm/app/components/crm/WorkOverview.tsx +180 -0
  103. package/corpus/templates/crm/app/components/crm/board/CrmBoard.tsx +988 -0
  104. package/corpus/templates/crm/app/components/crm/board/board-model.ts +399 -0
  105. package/corpus/templates/crm/app/components/crm/board/entry-filter.ts +150 -0
  106. package/corpus/templates/crm/app/components/crm/board/view-draft.ts +227 -0
  107. package/corpus/templates/crm/app/components/crm/grid/CrmGrid.tsx +1033 -0
  108. package/corpus/templates/crm/app/components/crm/grid/GridCell.tsx +620 -0
  109. package/corpus/templates/crm/app/components/crm/grid/clipboard.ts +84 -0
  110. package/corpus/templates/crm/app/components/crm/grid/model.ts +304 -0
  111. package/corpus/templates/crm/app/components/crm/grid/navigation.ts +220 -0
  112. package/corpus/templates/crm/app/components/crm/grid/query.ts +214 -0
  113. package/corpus/templates/crm/app/components/crm/record/AttributePanel.tsx +192 -0
  114. package/corpus/templates/crm/app/components/crm/record/FieldHistory.tsx +179 -0
  115. package/corpus/templates/crm/app/components/crm/record/RecordHeader.tsx +103 -0
  116. package/corpus/templates/crm/app/components/crm/record/RecordLists.tsx +235 -0
  117. package/corpus/templates/crm/app/components/crm/record/RecordTabs.tsx +206 -0
  118. package/corpus/templates/crm/app/components/crm/record/field-editors.tsx +245 -0
  119. package/corpus/templates/crm/app/components/crm/record/record-data.ts +603 -0
  120. package/corpus/templates/crm/app/components/crm/settings/AdvancedSettings.tsx +49 -0
  121. package/corpus/templates/crm/app/components/crm/settings/AttributeOptionsEditor.tsx +441 -0
  122. package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +169 -0
  123. package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +935 -0
  124. package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +339 -0
  125. package/corpus/templates/crm/app/components/crm/settings/settings-admin.ts +377 -0
  126. package/corpus/templates/crm/app/components/crm/shared/AttributeValueParts.tsx +69 -0
  127. package/corpus/templates/crm/app/components/crm/shared/attribute-value.ts +591 -0
  128. package/corpus/templates/crm/app/components/layout/CrmCommandMenu.tsx +556 -0
  129. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +130 -0
  130. package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +407 -0
  131. package/corpus/templates/crm/app/components/layout/command-palette-search.ts +126 -0
  132. package/corpus/templates/crm/app/components/layout/crm-ui-intents.ts +41 -0
  133. package/corpus/templates/crm/app/components/layout/sidebar-collapse.ts +40 -0
  134. package/corpus/templates/crm/app/components/layout/sidebar-lists.ts +109 -0
  135. package/corpus/templates/crm/app/components/ui/alert-dialog.tsx +1 -0
  136. package/corpus/templates/crm/app/components/ui/badge.tsx +1 -0
  137. package/corpus/templates/crm/app/components/ui/button.tsx +1 -0
  138. package/corpus/templates/crm/app/components/ui/card.tsx +1 -0
  139. package/corpus/templates/crm/app/components/ui/checkbox.tsx +1 -0
  140. package/corpus/templates/crm/app/components/ui/dialog.tsx +1 -0
  141. package/corpus/templates/crm/app/components/ui/dropdown-menu.tsx +1 -0
  142. package/corpus/templates/crm/app/components/ui/hover-card.tsx +1 -0
  143. package/corpus/templates/crm/app/components/ui/input.tsx +1 -0
  144. package/corpus/templates/crm/app/components/ui/label.tsx +1 -0
  145. package/corpus/templates/crm/app/components/ui/popover.tsx +1 -0
  146. package/corpus/templates/crm/app/components/ui/select.tsx +1 -0
  147. package/corpus/templates/crm/app/components/ui/sheet.tsx +1 -0
  148. package/corpus/templates/crm/app/components/ui/skeleton.tsx +1 -0
  149. package/corpus/templates/crm/app/components/ui/switch.tsx +1 -0
  150. package/corpus/templates/crm/app/components/ui/table.tsx +1 -0
  151. package/corpus/templates/crm/app/components/ui/tabs.tsx +1 -0
  152. package/corpus/templates/crm/app/components/ui/textarea.tsx +1 -0
  153. package/corpus/templates/crm/app/components/ui/toolkit-provider.tsx +17 -0
  154. package/corpus/templates/crm/app/components/ui/tooltip.tsx +1 -0
  155. package/corpus/templates/crm/app/design-system.ts +3 -0
  156. package/corpus/templates/crm/app/entry.client.tsx +19 -0
  157. package/corpus/templates/crm/app/entry.server.tsx +10 -0
  158. package/corpus/templates/crm/app/global.css +167 -0
  159. package/corpus/templates/crm/app/hooks/use-keyboard-shortcuts.ts +206 -0
  160. package/corpus/templates/crm/app/hooks/use-navigation-state.ts +48 -0
  161. package/corpus/templates/crm/app/i18n/en-US.ts +684 -0
  162. package/corpus/templates/crm/app/i18n/index.ts +34 -0
  163. package/corpus/templates/crm/app/lib/dashboard.ts +13 -0
  164. package/corpus/templates/crm/app/lib/navigation.ts +17 -0
  165. package/corpus/templates/crm/app/lib/tab-id.ts +1 -0
  166. package/corpus/templates/crm/app/lib/types.ts +172 -0
  167. package/corpus/templates/crm/app/lib/utils.ts +1 -0
  168. package/corpus/templates/crm/app/root.tsx +93 -0
  169. package/corpus/templates/crm/app/routes/_index.tsx +26 -0
  170. package/corpus/templates/crm/app/routes/accounts.tsx +41 -0
  171. package/corpus/templates/crm/app/routes/agent.tsx +5 -0
  172. package/corpus/templates/crm/app/routes/ask.tsx +53 -0
  173. package/corpus/templates/crm/app/routes/dashboard.tsx +118 -0
  174. package/corpus/templates/crm/app/routes/lists.tsx +228 -0
  175. package/corpus/templates/crm/app/routes/opportunities.tsx +41 -0
  176. package/corpus/templates/crm/app/routes/people.tsx +41 -0
  177. package/corpus/templates/crm/app/routes/proposals.tsx +388 -0
  178. package/corpus/templates/crm/app/routes/records.$recordId.tsx +53 -0
  179. package/corpus/templates/crm/app/routes/records.tsx +64 -0
  180. package/corpus/templates/crm/app/routes/settings.tsx +118 -0
  181. package/corpus/templates/crm/app/routes/setup.tsx +308 -0
  182. package/corpus/templates/crm/app/routes/tasks.tsx +252 -0
  183. package/corpus/templates/crm/app/routes/views.tsx +683 -0
  184. package/corpus/templates/crm/app/routes.ts +19 -0
  185. package/corpus/templates/crm/app/vite-env.d.ts +6 -0
  186. package/corpus/templates/crm/changelog/2026-07-26-any-list-or-view-can-be-shown-as-a-board-grouped-by-stage-so.md +6 -0
  187. package/corpus/templates/crm/changelog/2026-07-26-crm-now-finds-likely-duplicate-records-and-tells-you-exactly.md +6 -0
  188. package/corpus/templates/crm/changelog/2026-07-26-enrichment-now-shows-what-a-run-will-cost-before-it-starts-g.md +6 -0
  189. package/corpus/templates/crm/changelog/2026-07-26-lists-turn-any-set-of-records-into-a-pipeline-with-its-own-s.md +6 -0
  190. package/corpus/templates/crm/changelog/2026-07-26-moving-a-record-or-list-entry-into-a-stage-that-was-retired-.md +6 -0
  191. package/corpus/templates/crm/changelog/2026-07-26-opening-a-record-now-shows-one-page-with-its-fields-the-list.md +6 -0
  192. package/corpus/templates/crm/changelog/2026-07-26-press-cmd-k-to-jump-to-any-record-list-view-or-setting-and-u.md +6 -0
  193. package/corpus/templates/crm/changelog/2026-07-26-provider-record-edits-now-show-an-exact-before-after-diff-an.md +6 -0
  194. package/corpus/templates/crm/changelog/2026-07-26-record-page-attribute-labels-now-read-in-full-instead-of-bei.md +6 -0
  195. package/corpus/templates/crm/changelog/2026-07-26-records-now-use-typed-fields-currency-dates-ratings-status-s.md +6 -0
  196. package/corpus/templates/crm/changelog/2026-07-26-retyping-a-value-on-the-record-page-now-replaces-it-instead-.md +6 -0
  197. package/corpus/templates/crm/changelog/2026-07-26-settings-now-has-fields-and-lists-tabs-for-adding-fields-edi.md +6 -0
  198. package/corpus/templates/crm/changelog/2026-07-26-the-proposal-review-now-shows-the-field-that-actually-change.md +6 -0
  199. package/corpus/templates/crm/changelog/2026-07-26-the-record-grid-is-now-a-real-spreadsheet-filter-sort-and-pa.md +6 -0
  200. package/corpus/templates/crm/components.json +20 -0
  201. package/corpus/templates/crm/docs/architecture/crm-contract.md +214 -0
  202. package/corpus/templates/crm/docs/features/README.md +55 -0
  203. package/corpus/templates/crm/docs/features/f1-records-and-fields.mdx +52 -0
  204. package/corpus/templates/crm/docs/features/f2-lists-pipelines-and-views.mdx +48 -0
  205. package/corpus/templates/crm/docs/features/f3-work-and-record-quality.mdx +40 -0
  206. package/corpus/templates/crm/docs/features/f4-evidence-and-chat.mdx +47 -0
  207. package/corpus/templates/crm/learnings.defaults.md +5 -0
  208. package/corpus/templates/crm/netlify.toml +11 -0
  209. package/corpus/templates/crm/package.json +61 -0
  210. package/corpus/templates/crm/public/favicon.svg +1 -0
  211. package/corpus/templates/crm/public/icon-180.svg +1 -0
  212. package/corpus/templates/crm/public/manifest.json +11 -0
  213. package/corpus/templates/crm/react-router.config.ts +7 -0
  214. package/corpus/templates/crm/server/crm/adapter.ts +32 -0
  215. package/corpus/templates/crm/server/crm/crm-field-firewall.ts +64 -0
  216. package/corpus/templates/crm/server/crm/crm-mirror.ts +721 -0
  217. package/corpus/templates/crm/server/crm/field-policy-attributes.ts +48 -0
  218. package/corpus/templates/crm/server/crm/hubspot-adapter.ts +1146 -0
  219. package/corpus/templates/crm/server/crm/native-adapter.ts +1810 -0
  220. package/corpus/templates/crm/server/crm/provider-record-link.ts +264 -0
  221. package/corpus/templates/crm/server/crm/read-through.ts +207 -0
  222. package/corpus/templates/crm/server/crm/salesforce-adapter.ts +1255 -0
  223. package/corpus/templates/crm/server/db/crm-store.ts +1112 -0
  224. package/corpus/templates/crm/server/db/index.ts +191 -0
  225. package/corpus/templates/crm/server/db/schema.ts +647 -0
  226. package/corpus/templates/crm/server/lib/crm-query.ts +1558 -0
  227. package/corpus/templates/crm/server/lib/dedupe.ts +714 -0
  228. package/corpus/templates/crm/server/lib/enrichment-cost.ts +381 -0
  229. package/corpus/templates/crm/server/lib/enrichment-slots.ts +519 -0
  230. package/corpus/templates/crm/server/lib/intelligence/default-detectors.ts +65 -0
  231. package/corpus/templates/crm/server/lib/intelligence/evidence.ts +105 -0
  232. package/corpus/templates/crm/server/lib/intelligence/keyword-detector.ts +94 -0
  233. package/corpus/templates/crm/server/lib/intelligence/smart-detector.ts +39 -0
  234. package/corpus/templates/crm/server/lib/intelligence/summary.ts +19 -0
  235. package/corpus/templates/crm/server/lib/lifecycle.ts +634 -0
  236. package/corpus/templates/crm/server/lib/provider-api.ts +64 -0
  237. package/corpus/templates/crm/server/middleware/auth.ts +4 -0
  238. package/corpus/templates/crm/server/plugins/agent-chat.ts +65 -0
  239. package/corpus/templates/crm/server/plugins/auth.ts +14 -0
  240. package/corpus/templates/crm/server/plugins/core-routes.ts +17 -0
  241. package/corpus/templates/crm/server/plugins/db.ts +738 -0
  242. package/corpus/templates/crm/server/routes/[...page].get.ts +5 -0
  243. package/corpus/templates/crm/shared/crm-attributes.ts +469 -0
  244. package/corpus/templates/crm/shared/crm-automation-recipes.ts +132 -0
  245. package/corpus/templates/crm/shared/crm-contract.ts +417 -0
  246. package/corpus/templates/crm/shared/crm-navigation.ts +190 -0
  247. package/corpus/templates/crm/shared/crm-sales-config.ts +63 -0
  248. package/corpus/templates/crm/ssr-entry.ts +11 -0
  249. package/corpus/templates/crm/tsconfig.json +24 -0
  250. package/corpus/templates/crm/vite.config.ts +15 -0
  251. package/corpus/templates/crm/vitest.config.ts +5 -0
  252. package/corpus/templates/design/app/i18n-data.ts +12 -12
  253. package/corpus/templates/design/changelog/2026-07-26-apply-design-updates-from-a-centered-top-toolbar-without-cov.md +6 -0
  254. package/dist/cli/templates-meta.d.ts.map +1 -1
  255. package/dist/cli/templates-meta.js +12 -0
  256. package/dist/cli/templates-meta.js.map +1 -1
  257. package/dist/collab/routes.d.ts +1 -1
  258. package/dist/observability/routes.d.ts +3 -3
  259. package/dist/resources/handlers.d.ts +1 -1
  260. package/dist/server/realtime-token.d.ts +1 -1
  261. package/dist/server/transcribe-voice.d.ts +1 -1
  262. package/package.json +1 -1
  263. package/src/cli/templates-meta.ts +12 -0
@@ -0,0 +1,287 @@
1
+ /**
2
+ * The typed data the record page needs and no existing action exposes: the
3
+ * object's attribute schema, the CURRENT bitemporal values, the record's list
4
+ * memberships with their entry values, and the upstream deep link.
5
+ *
6
+ * It deliberately does not replace `get-crm-record`. That action is the one
7
+ * that verifies provider read-through permission for a mirrored record, so the
8
+ * page calls both: this one for the typed surface, that one for the verified
9
+ * remote view, evidence, tasks, and relationships.
10
+ */
11
+
12
+ import { defineAction } from "@agent-native/core/action";
13
+ import { accessFilter } from "@agent-native/core/sharing";
14
+ import { and, asc, eq, isNull } from "drizzle-orm";
15
+ import { z } from "zod";
16
+
17
+ import { resolveProviderRecordLinks } from "../server/crm/provider-record-link.js";
18
+ import { getDb, schema } from "../server/db/index.js";
19
+ import { CrmAttributeValueError } from "../server/lib/record-fields.js";
20
+ import { storageColumnFor } from "../shared/crm-attributes.js";
21
+ import type {
22
+ CrmAttributeDefinition,
23
+ CrmValue,
24
+ } from "../shared/crm-contract.js";
25
+ import {
26
+ loadAttributeOptions,
27
+ toAttributeDefinition,
28
+ } from "./_crm-attribute-utils.js";
29
+ import {
30
+ attributeSummary,
31
+ indexAttributes,
32
+ loadCrmEntryValues,
33
+ loadCrmListAttributes,
34
+ } from "./_crm-list-utils.js";
35
+
36
+ const MAX_ATTRIBUTES = 200;
37
+ const MAX_LIST_MEMBERSHIPS = 100;
38
+ const MAX_MEMBERSHIP_LISTS = 25;
39
+
40
+ interface StoredValueRow {
41
+ stringValue: string | null;
42
+ numberValue: number | null;
43
+ booleanValue: boolean | null;
44
+ jsonValue: string | null;
45
+ }
46
+
47
+ /**
48
+ * Decode using the attribute's declared storage column, never by sniffing which
49
+ * column is non-null — a `false` checkbox and an empty text field are otherwise
50
+ * the same row. Unreadable JSON is a typed failure, not an absent value.
51
+ */
52
+ function decodeValue(
53
+ attribute: Pick<
54
+ CrmAttributeDefinition,
55
+ "apiSlug" | "attributeType" | "multi"
56
+ >,
57
+ row: StoredValueRow,
58
+ ): CrmValue {
59
+ const column = storageColumnFor(attribute.attributeType, attribute.multi);
60
+ if (column === "numberValue") return row.numberValue;
61
+ if (column === "booleanValue") return row.booleanValue;
62
+ if (column === "jsonValue") {
63
+ if (row.jsonValue === null) return null;
64
+ try {
65
+ return JSON.parse(row.jsonValue) as CrmValue;
66
+ } catch {
67
+ throw new CrmAttributeValueError(
68
+ "crm-attribute-value-unreadable",
69
+ `Stored value for "${attribute.apiSlug}" is not readable JSON. It was written outside the attribute writer.`,
70
+ );
71
+ }
72
+ }
73
+ return row.stringValue;
74
+ }
75
+
76
+ export default defineAction({
77
+ description:
78
+ "Return the typed attribute panel for one CRM record: the object's attribute definitions with their managed options, the record's current attribute values with when and by whom each was last set, every list the record belongs to with that entry's own attribute values, and the upstream provider deep link. Values are the current bitemporal rows only. Use list-crm-record-field-history for how a value changed.",
79
+ schema: z.object({
80
+ recordId: z.string().trim().min(1).max(128),
81
+ }),
82
+ http: { method: "GET" },
83
+ readOnly: true,
84
+ run: async (args) => {
85
+ const db = getDb();
86
+ const [record] = await db
87
+ .select({
88
+ id: schema.crmRecords.id,
89
+ connectionId: schema.crmRecords.connectionId,
90
+ provider: schema.crmRecords.provider,
91
+ objectType: schema.crmRecords.objectType,
92
+ kind: schema.crmRecords.kind,
93
+ displayName: schema.crmRecords.displayName,
94
+ remoteRevision: schema.crmRecords.remoteRevision,
95
+ updatedAt: schema.crmRecords.updatedAt,
96
+ })
97
+ .from(schema.crmRecords)
98
+ .where(
99
+ and(
100
+ eq(schema.crmRecords.id, args.recordId),
101
+ eq(schema.crmRecords.tombstone, false),
102
+ accessFilter(schema.crmRecords, schema.crmRecordShares),
103
+ ),
104
+ )
105
+ .limit(1);
106
+ if (!record) {
107
+ const error = new Error("CRM record not found") as Error & {
108
+ statusCode?: number;
109
+ };
110
+ error.statusCode = 404;
111
+ throw error;
112
+ }
113
+
114
+ const attributeRows = await db
115
+ .select()
116
+ .from(schema.crmFieldPolicies)
117
+ .where(
118
+ and(
119
+ eq(schema.crmFieldPolicies.target, "object"),
120
+ // `object_type` is populated on every row including the ones the
121
+ // provider adapters write; `target_id` is not.
122
+ eq(schema.crmFieldPolicies.objectType, record.objectType),
123
+ eq(schema.crmFieldPolicies.connectionId, record.connectionId),
124
+ eq(schema.crmFieldPolicies.archived, false),
125
+ accessFilter(schema.crmFieldPolicies, schema.crmFieldPolicyShares),
126
+ ),
127
+ )
128
+ .orderBy(
129
+ asc(schema.crmFieldPolicies.position),
130
+ asc(schema.crmFieldPolicies.label),
131
+ )
132
+ .limit(MAX_ATTRIBUTES);
133
+
134
+ const options = await loadAttributeOptions({
135
+ attributeIds: attributeRows.map((row) => row.id),
136
+ includeArchived: false,
137
+ });
138
+ const attributes = attributeRows.map((row) =>
139
+ toAttributeDefinition(row, options.get(row.id) ?? []),
140
+ );
141
+ const attributeBySlug = new Map(
142
+ attributes.map((attribute) => [attribute.apiSlug, attribute]),
143
+ );
144
+
145
+ const valueRows = await db
146
+ .select({
147
+ fieldName: schema.crmRecordFields.fieldName,
148
+ stringValue: schema.crmRecordFields.stringValue,
149
+ numberValue: schema.crmRecordFields.numberValue,
150
+ booleanValue: schema.crmRecordFields.booleanValue,
151
+ jsonValue: schema.crmRecordFields.jsonValue,
152
+ activeFrom: schema.crmRecordFields.activeFrom,
153
+ actorType: schema.crmRecordFields.actorType,
154
+ actorId: schema.crmRecordFields.actorId,
155
+ })
156
+ .from(schema.crmRecordFields)
157
+ .where(
158
+ and(
159
+ eq(schema.crmRecordFields.recordId, record.id),
160
+ // `record_id` is populated on entry rows too, so the record-vs-entry
161
+ // discriminator is `entry_id IS NULL`.
162
+ isNull(schema.crmRecordFields.entryId),
163
+ isNull(schema.crmRecordFields.activeUntil),
164
+ accessFilter(schema.crmRecordFields, schema.crmRecordFieldShares),
165
+ ),
166
+ )
167
+ .limit(MAX_ATTRIBUTES);
168
+
169
+ const values: Record<string, CrmValue> = {};
170
+ const valueMeta: Record<
171
+ string,
172
+ { since: string; actorType: string; actorId: string | null }
173
+ > = {};
174
+ for (const row of valueRows) {
175
+ const attribute = attributeBySlug.get(row.fieldName);
176
+ if (!attribute) continue;
177
+ values[attribute.apiSlug] = decodeValue(attribute, row);
178
+ valueMeta[attribute.apiSlug] = {
179
+ since: row.activeFrom,
180
+ actorType: row.actorType,
181
+ actorId: row.actorId,
182
+ };
183
+ }
184
+
185
+ const membershipRows = await db
186
+ .select({
187
+ entryId: schema.crmListEntries.id,
188
+ listId: schema.crmListEntries.listId,
189
+ position: schema.crmListEntries.position,
190
+ createdAt: schema.crmListEntries.createdAt,
191
+ createdByActorType: schema.crmListEntries.createdByActorType,
192
+ createdByActorId: schema.crmListEntries.createdByActorId,
193
+ listName: schema.crmLists.name,
194
+ listApiSlug: schema.crmLists.apiSlug,
195
+ listParentObjectType: schema.crmLists.parentObjectType,
196
+ listArchived: schema.crmLists.archived,
197
+ })
198
+ .from(schema.crmListEntries)
199
+ .innerJoin(
200
+ schema.crmLists,
201
+ eq(schema.crmLists.id, schema.crmListEntries.listId),
202
+ )
203
+ .where(
204
+ and(
205
+ eq(schema.crmListEntries.recordId, record.id),
206
+ eq(schema.crmLists.archived, false),
207
+ accessFilter(schema.crmListEntries, schema.crmListEntryShares),
208
+ accessFilter(schema.crmLists, schema.crmListShares),
209
+ ),
210
+ )
211
+ .orderBy(
212
+ asc(schema.crmLists.position),
213
+ asc(schema.crmLists.name),
214
+ asc(schema.crmListEntries.position),
215
+ asc(schema.crmListEntries.createdAt),
216
+ )
217
+ .limit(MAX_LIST_MEMBERSHIPS);
218
+
219
+ const listIds = [...new Set(membershipRows.map((row) => row.listId))].slice(
220
+ 0,
221
+ MAX_MEMBERSHIP_LISTS,
222
+ );
223
+ const lists = [];
224
+ for (const listId of listIds) {
225
+ const listRows = membershipRows.filter((row) => row.listId === listId);
226
+ const first = listRows[0]!;
227
+ const listAttributes = await loadCrmListAttributes(db, listId);
228
+ const entryValues = await loadCrmEntryValues(
229
+ db,
230
+ listRows.map((row) => row.entryId),
231
+ indexAttributes(listAttributes),
232
+ );
233
+ lists.push({
234
+ id: listId,
235
+ name: first.listName,
236
+ apiSlug: first.listApiSlug,
237
+ parentObjectType: first.listParentObjectType,
238
+ attributes: listAttributes.map(attributeSummary),
239
+ // A record may hold more than one entry in the same list; each entry is
240
+ // its own row here rather than being collapsed into a membership flag.
241
+ entries: listRows.map((row) => {
242
+ const entry = entryValues.get(row.entryId) ?? {
243
+ values: {},
244
+ valuesSince: {},
245
+ };
246
+ return {
247
+ id: row.entryId,
248
+ listId,
249
+ recordId: record.id,
250
+ position: row.position,
251
+ createdAt: row.createdAt,
252
+ createdByActorType: row.createdByActorType,
253
+ createdByActorId: row.createdByActorId,
254
+ values: entry.values,
255
+ valuesSince: entry.valuesSince,
256
+ };
257
+ }),
258
+ });
259
+ }
260
+
261
+ const link = (await resolveProviderRecordLinks([record.id])).get(record.id);
262
+
263
+ return {
264
+ record: {
265
+ id: record.id,
266
+ connectionId: record.connectionId,
267
+ provider: record.provider,
268
+ objectType: record.objectType,
269
+ kind: record.kind,
270
+ displayName: record.displayName,
271
+ remoteRevision: record.remoteRevision,
272
+ updatedAt: record.updatedAt,
273
+ },
274
+ attributes,
275
+ values,
276
+ valueMeta,
277
+ lists,
278
+ listMembershipsTruncated:
279
+ membershipRows.length >= MAX_LIST_MEMBERSHIPS ||
280
+ new Set(membershipRows.map((row) => row.listId)).size > listIds.length,
281
+ // Absent link and unavailable link are different states: a native record
282
+ // has no upstream record at all, and both fields stay null for it.
283
+ recordUrl: link?.available ? link.url : null,
284
+ recordUrlUnavailableReason: link && !link.available ? link.reason : null,
285
+ };
286
+ },
287
+ });
@@ -0,0 +1,75 @@
1
+ import { defineAction, type ActionRunContext } from "@agent-native/core/action";
2
+ import { z } from "zod";
3
+
4
+ import {
5
+ createConnectedCrmAdapter,
6
+ isConnectedCrmProvider,
7
+ } from "../server/crm/adapter.js";
8
+ import { createNativeCrmAdapter } from "../server/crm/native-adapter.js";
9
+ import { loadVerifiedReadThroughRecord } from "../server/crm/read-through.js";
10
+ import {
11
+ getCrmRecord,
12
+ getCrmRecordReadContext,
13
+ getReadThroughRelationshipSummaries,
14
+ } from "../server/db/crm-store.js";
15
+
16
+ export default defineAction({
17
+ description:
18
+ "Return one access-scoped CRM record with bounded mirrored fields, recent interaction metadata, call-evidence references, and tasks. It never returns raw provider payloads, media, or transcripts.",
19
+ schema: z.object({
20
+ recordId: z.string().min(1).describe("Local CRM record ID."),
21
+ }),
22
+ http: { method: "GET" },
23
+ readOnly: true,
24
+ run: async ({ recordId }, ctx?: ActionRunContext) => {
25
+ const context = await getCrmRecordReadContext(recordId);
26
+ if (!context) {
27
+ const error = new Error("CRM record not found") as Error & {
28
+ statusCode?: number;
29
+ };
30
+ error.statusCode = 404;
31
+ throw error;
32
+ }
33
+ const adapter =
34
+ context.provider === "native"
35
+ ? await createNativeCrmAdapter({
36
+ connectionId: context.connectionId,
37
+ accessTier: "viewer",
38
+ })
39
+ : isConnectedCrmProvider(context.provider) &&
40
+ context.workspaceConnectionId
41
+ ? await createConnectedCrmAdapter({
42
+ provider: context.provider,
43
+ connectionId: context.workspaceConnectionId,
44
+ ...(ctx?.userEmail ? { userEmail: ctx.userEmail } : {}),
45
+ ...(ctx?.orgId !== undefined ? { orgId: ctx.orgId } : {}),
46
+ })
47
+ : null;
48
+ if (!adapter) {
49
+ throw new Error("CRM access cannot be verified for this record.");
50
+ }
51
+ const readThrough = await loadVerifiedReadThroughRecord({
52
+ adapter,
53
+ context,
54
+ });
55
+ const relatedRecords = await getReadThroughRelationshipSummaries({
56
+ context,
57
+ relationships: readThrough.relationships,
58
+ currentScopes: readThrough.currentScopes,
59
+ });
60
+ const record = await getCrmRecord(recordId, {
61
+ displayName: readThrough.remote.displayName,
62
+ fields: readThrough.remote.fields,
63
+ remoteRevision: readThrough.remote.remoteRevision,
64
+ remoteUpdatedAt: readThrough.remote.remoteUpdatedAt,
65
+ relatedRecords,
66
+ accessScope: readThrough.currentScope,
67
+ });
68
+ if (record) return record;
69
+ const error = new Error("CRM record not found") as Error & {
70
+ statusCode?: number;
71
+ };
72
+ error.statusCode = 404;
73
+ throw error;
74
+ },
75
+ });