@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,272 @@
1
+ import { defineAction, type ActionRunContext } from "@agent-native/core/action";
2
+ import { accessFilter } from "@agent-native/core/sharing";
3
+ import { and, asc, eq } from "drizzle-orm";
4
+ import { z } from "zod";
5
+
6
+ import { getDb, schema } from "../server/db/index.js";
7
+ import { ATTRIBUTE_TYPE_SPECS } from "../shared/crm-attributes.js";
8
+ import { requireCrmScope } from "./_crm-action-utils.js";
9
+ import {
10
+ CrmAttributeValueError,
11
+ loadAttributeOptions,
12
+ requireEditableAttribute,
13
+ toAttributeDefinition,
14
+ type CrmAttributeOptionRow,
15
+ } from "./_crm-attribute-utils.js";
16
+
17
+ const inputSchema = z
18
+ .object({
19
+ attributeId: z.string().trim().min(1).max(128),
20
+ operation: z.enum(["add", "update", "archive", "reorder"]),
21
+ optionId: z.string().trim().min(1).max(128).optional(),
22
+ value: z
23
+ .string()
24
+ .trim()
25
+ .min(1)
26
+ .max(200)
27
+ .optional()
28
+ .describe(
29
+ "add only. The stored value; immutable afterwards because record values reference it.",
30
+ ),
31
+ title: z.string().trim().min(1).max(200).optional(),
32
+ color: z.string().trim().min(1).max(64).nullish(),
33
+ position: z.number().int().min(0).max(10_000).optional(),
34
+ targetDays: z
35
+ .number()
36
+ .int()
37
+ .min(0)
38
+ .max(3650)
39
+ .nullish()
40
+ .describe("status attributes only: the stage SLA in days."),
41
+ celebrate: z.boolean().optional().describe("status attributes only."),
42
+ archived: z.boolean().default(true),
43
+ optionIds: z
44
+ .array(z.string().trim().min(1).max(128))
45
+ .min(1)
46
+ .max(200)
47
+ .optional()
48
+ .describe("reorder only. Options take the position of their index."),
49
+ })
50
+ .superRefine((value, ctx) => {
51
+ if (value.operation === "add" && !value.value) {
52
+ ctx.addIssue({
53
+ code: "custom",
54
+ path: ["value"],
55
+ message: "value is required when adding an option.",
56
+ });
57
+ }
58
+ if (
59
+ (value.operation === "update" || value.operation === "archive") &&
60
+ !value.optionId
61
+ ) {
62
+ ctx.addIssue({
63
+ code: "custom",
64
+ path: ["optionId"],
65
+ message: `optionId is required when you ${value.operation} an option.`,
66
+ });
67
+ }
68
+ if (value.operation === "reorder" && !value.optionIds) {
69
+ ctx.addIssue({
70
+ code: "custom",
71
+ path: ["optionIds"],
72
+ message: "optionIds is required when reordering options.",
73
+ });
74
+ }
75
+ if (value.operation !== "add" && value.value) {
76
+ ctx.addIssue({
77
+ code: "custom",
78
+ path: ["value"],
79
+ message:
80
+ "An option value is immutable — stored record values reference it. Archive this option and add a new one instead.",
81
+ });
82
+ }
83
+ });
84
+
85
+ export default defineAction({
86
+ description:
87
+ "Add, edit, reorder, or archive one managed option on a status or select CRM attribute. Options are never auto-created by a value write, so add the option here before writing it. Status options may carry targetDays (the stage SLA) and celebrate. Archiving an option removes it from the picker only: records already holding that value keep it untouched.",
88
+ schema: inputSchema,
89
+ audit: {
90
+ target: (args) => ({ type: "crm-field-policy", id: args.attributeId }),
91
+ summary: (args) =>
92
+ `${args.operation} option on CRM attribute ${args.attributeId}`,
93
+ },
94
+ run: async (args, ctx?: ActionRunContext) => {
95
+ const attribute = await requireEditableAttribute(args.attributeId);
96
+ const spec = ATTRIBUTE_TYPE_SPECS[attribute.attributeType];
97
+ if (!spec.usesOptions) {
98
+ throw new CrmAttributeValueError(
99
+ "crm-attribute-options-unsupported",
100
+ `Attribute "${attribute.apiSlug ?? attribute.fieldName}" is a ${attribute.attributeType} and does not use managed options. Only status and select attributes take options.`,
101
+ );
102
+ }
103
+ if (
104
+ attribute.attributeType !== "status" &&
105
+ (args.targetDays != null || args.celebrate !== undefined)
106
+ ) {
107
+ throw new CrmAttributeValueError(
108
+ "crm-attribute-option-stage-fields",
109
+ `targetDays and celebrate describe a pipeline stage and apply to status attributes only; this attribute is a ${attribute.attributeType}.`,
110
+ );
111
+ }
112
+
113
+ const db = getDb();
114
+ const now = new Date().toISOString();
115
+
116
+ if (args.operation === "add") {
117
+ const siblings = await loadOptionSlots(args.attributeId);
118
+ if (siblings.some((option) => option.value === args.value)) {
119
+ throw new CrmAttributeValueError(
120
+ "crm-attribute-option-duplicate",
121
+ `Option "${args.value}" already exists on this attribute.`,
122
+ );
123
+ }
124
+ // guard:allow-unscoped — requireEditableAttribute() above already ran
125
+ // assertAccess("crm-field-policy", …, "editor"), and requireCrmScope(ctx)
126
+ // below sets ownerEmail/orgId from the request context.
127
+ await db.insert(schema.crmAttributeOptions).values({
128
+ id: crypto.randomUUID(),
129
+ attributeId: args.attributeId,
130
+ value: args.value!,
131
+ title: args.title ?? args.value!,
132
+ color: args.color ?? null,
133
+ position:
134
+ args.position ??
135
+ siblings.reduce((max, option) => Math.max(max, option.position), -1) +
136
+ 1,
137
+ targetDays: args.targetDays ?? null,
138
+ celebrate: args.celebrate ?? false,
139
+ createdAt: now,
140
+ updatedAt: now,
141
+ ...requireCrmScope(ctx),
142
+ });
143
+ } else if (args.operation === "reorder") {
144
+ const known = new Set(
145
+ (await loadOptionRows(args.attributeId)).map((option) => option.id),
146
+ );
147
+ const unknown = args.optionIds!.find((id) => !known.has(id));
148
+ if (unknown) {
149
+ throw new CrmAttributeValueError(
150
+ "crm-attribute-option-unknown",
151
+ `Option ${unknown} does not belong to attribute ${args.attributeId}.`,
152
+ );
153
+ }
154
+ for (const [index, optionId] of args.optionIds!.entries()) {
155
+ // guard:allow-unscoped — editableOption() carries the editor
156
+ // accessFilter; the guard cannot follow it out of the helper.
157
+ await db
158
+ .update(schema.crmAttributeOptions)
159
+ .set({ position: index, updatedAt: now })
160
+ .where(editableOption(args.attributeId, optionId));
161
+ }
162
+ } else {
163
+ const patch =
164
+ args.operation === "archive"
165
+ ? { archived: args.archived }
166
+ : {
167
+ ...(args.title === undefined ? {} : { title: args.title }),
168
+ ...(args.color === undefined ? {} : { color: args.color }),
169
+ ...(args.position === undefined
170
+ ? {}
171
+ : { position: args.position }),
172
+ ...(args.targetDays === undefined
173
+ ? {}
174
+ : { targetDays: args.targetDays }),
175
+ ...(args.celebrate === undefined
176
+ ? {}
177
+ : { celebrate: args.celebrate }),
178
+ };
179
+ const options = await loadOptionRows(args.attributeId);
180
+ if (!options.some((option) => option.id === args.optionId)) {
181
+ throw new CrmAttributeValueError(
182
+ "crm-attribute-option-unknown",
183
+ `Option ${args.optionId} does not belong to attribute ${args.attributeId}.`,
184
+ );
185
+ }
186
+ if (Object.keys(patch).length > 0) {
187
+ // Only `crm_attribute_options` is touched. Archiving an option in use
188
+ // must leave every stored value alone: the value stays, the picker
189
+ // stops offering it.
190
+ // guard:allow-unscoped — editableOption() carries the editor
191
+ // accessFilter; the guard cannot follow it out of the helper.
192
+ await db
193
+ .update(schema.crmAttributeOptions)
194
+ .set({ ...patch, updatedAt: now })
195
+ .where(editableOption(args.attributeId, args.optionId!));
196
+ }
197
+ }
198
+
199
+ const [row] = await db
200
+ .select()
201
+ .from(schema.crmFieldPolicies)
202
+ .where(
203
+ and(
204
+ eq(schema.crmFieldPolicies.id, args.attributeId),
205
+ accessFilter(
206
+ schema.crmFieldPolicies,
207
+ schema.crmFieldPolicyShares,
208
+ undefined,
209
+ "editor",
210
+ ),
211
+ ),
212
+ )
213
+ .limit(1);
214
+ if (!row) {
215
+ throw new Error("CRM attribute could not be verified after saving.");
216
+ }
217
+ const options = await loadAttributeOptions({
218
+ attributeIds: [row.id],
219
+ includeArchived: true,
220
+ });
221
+ return toAttributeDefinition(row, options.get(row.id) ?? []);
222
+ },
223
+ });
224
+
225
+ function editableOption(attributeId: string, optionId: string) {
226
+ return and(
227
+ eq(schema.crmAttributeOptions.id, optionId),
228
+ eq(schema.crmAttributeOptions.attributeId, attributeId),
229
+ accessFilter(
230
+ schema.crmAttributeOptions,
231
+ schema.crmAttributeOptionShares,
232
+ undefined,
233
+ "editor",
234
+ ),
235
+ );
236
+ }
237
+
238
+ /**
239
+ * Value and position of every option row, deliberately not access-filtered: the
240
+ * unique index on (attribute_id, value) spans rows this caller cannot see, so a
241
+ * scoped check would report a clean insert and then hit a constraint violation.
242
+ */
243
+ function loadOptionSlots(attributeId: string) {
244
+ // guard:allow-unscoped — see the note above: callers are already gated on
245
+ // editor access to the attribute, and only value/position are read.
246
+ return getDb()
247
+ .select({
248
+ value: schema.crmAttributeOptions.value,
249
+ position: schema.crmAttributeOptions.position,
250
+ })
251
+ .from(schema.crmAttributeOptions)
252
+ .where(eq(schema.crmAttributeOptions.attributeId, attributeId));
253
+ }
254
+
255
+ /** Every option the caller may edit, archived ones included. */
256
+ function loadOptionRows(attributeId: string): Promise<CrmAttributeOptionRow[]> {
257
+ return getDb()
258
+ .select()
259
+ .from(schema.crmAttributeOptions)
260
+ .where(
261
+ and(
262
+ eq(schema.crmAttributeOptions.attributeId, attributeId),
263
+ accessFilter(
264
+ schema.crmAttributeOptions,
265
+ schema.crmAttributeOptionShares,
266
+ undefined,
267
+ "editor",
268
+ ),
269
+ ),
270
+ )
271
+ .orderBy(asc(schema.crmAttributeOptions.position));
272
+ }
@@ -0,0 +1,87 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import { assertAccess } from "@agent-native/core/sharing";
3
+ import { eq } from "drizzle-orm";
4
+ import { z } from "zod";
5
+
6
+ import { getDb, schema } from "../server/db/index.js";
7
+
8
+ const inputSchema = z
9
+ .object({
10
+ trackerId: z.string().trim().min(1).max(128),
11
+ operation: z.enum(["set-enabled", "delete"]),
12
+ enabled: z.boolean().optional(),
13
+ })
14
+ .superRefine((value, ctx) => {
15
+ if (value.operation === "set-enabled" && value.enabled === undefined) {
16
+ ctx.addIssue({
17
+ code: "custom",
18
+ path: ["enabled"],
19
+ message: "enabled is required when changing a tracker state.",
20
+ });
21
+ }
22
+ if (value.operation === "delete" && value.enabled !== undefined) {
23
+ ctx.addIssue({
24
+ code: "custom",
25
+ path: ["enabled"],
26
+ message: "enabled cannot be provided when deleting a tracker.",
27
+ });
28
+ }
29
+ });
30
+
31
+ export default defineAction({
32
+ description:
33
+ "Enable, disable, or delete one access-scoped local CRM signal tracker. This never invokes a model or changes a connected provider.",
34
+ schema: inputSchema,
35
+ audit: {
36
+ target: (_args, result) => {
37
+ const tracker = result as {
38
+ id: string;
39
+ ownerEmail: string;
40
+ orgId: string | null;
41
+ visibility: "private" | "org";
42
+ };
43
+ return {
44
+ type: "crm-signal-tracker",
45
+ id: tracker.id,
46
+ ownerEmail: tracker.ownerEmail,
47
+ orgId: tracker.orgId,
48
+ visibility: tracker.visibility,
49
+ };
50
+ },
51
+ summary: (args) =>
52
+ args.operation === "delete"
53
+ ? `Deleted CRM signal tracker ${args.trackerId}`
54
+ : `${args.enabled ? "Enabled" : "Disabled"} CRM signal tracker ${args.trackerId}`,
55
+ },
56
+ run: async (args) => {
57
+ await assertAccess("crm-signal-tracker", args.trackerId, "editor");
58
+ const db = getDb();
59
+ const [tracker] = await db
60
+ .select()
61
+ .from(schema.crmSignalTrackers)
62
+ .where(eq(schema.crmSignalTrackers.id, args.trackerId))
63
+ .limit(1);
64
+ if (!tracker) throw new Error("CRM signal tracker was not found.");
65
+
66
+ if (args.operation === "delete") {
67
+ await db
68
+ .delete(schema.crmSignalTrackers)
69
+ .where(eq(schema.crmSignalTrackers.id, args.trackerId));
70
+ return { ...tracker, deleted: true };
71
+ }
72
+
73
+ const now = new Date().toISOString();
74
+ await db
75
+ .update(schema.crmSignalTrackers)
76
+ .set({ enabled: args.enabled!, updatedAt: now })
77
+ .where(eq(schema.crmSignalTrackers.id, args.trackerId));
78
+ const [updated] = await db
79
+ .select()
80
+ .from(schema.crmSignalTrackers)
81
+ .where(eq(schema.crmSignalTrackers.id, args.trackerId))
82
+ .limit(1);
83
+ if (!updated)
84
+ throw new Error("CRM signal tracker could not be verified after saving.");
85
+ return updated;
86
+ },
87
+ });
@@ -0,0 +1,132 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import { assertAccess } from "@agent-native/core/sharing";
3
+ import { and, eq } from "drizzle-orm";
4
+ import { z } from "zod";
5
+
6
+ import { getDb, schema } from "../server/db/index.js";
7
+ import { requireCrmScope } from "./_crm-action-utils.js";
8
+
9
+ export default defineAction({
10
+ description:
11
+ "Create or update a local CRM follow-up task. Mirrored provider tasks remain read-only in this phase so they cannot be changed without an explicit provider proposal.",
12
+ schema: z
13
+ .object({
14
+ taskId: z.string().trim().min(1).max(128).optional(),
15
+ recordId: z.string().trim().min(1).max(128).optional(),
16
+ title: z.string().trim().min(1).max(300).optional(),
17
+ description: z.string().trim().max(2_000).optional(),
18
+ status: z.enum(["open", "done", "cancelled"]).optional(),
19
+ dueAt: z.string().datetime({ offset: true }).optional(),
20
+ assignedTo: z.string().trim().max(240).optional(),
21
+ })
22
+ .superRefine((value, issue) => {
23
+ if (!value.taskId && !value.title) {
24
+ issue.addIssue({
25
+ code: "custom",
26
+ message: "title is required when creating a task",
27
+ path: ["title"],
28
+ });
29
+ }
30
+ }),
31
+ audit: {
32
+ target: (_args, result) => {
33
+ const task = result as {
34
+ id: string;
35
+ ownerEmail: string;
36
+ orgId: string | null;
37
+ visibility: "private" | "org";
38
+ };
39
+ return {
40
+ type: "crm-task",
41
+ id: task.id,
42
+ ownerEmail: task.ownerEmail,
43
+ orgId: task.orgId,
44
+ visibility: task.visibility,
45
+ };
46
+ },
47
+ summary: (args) =>
48
+ args.taskId
49
+ ? `Updated CRM task ${args.taskId}`
50
+ : `Created CRM task ${args.title}`,
51
+ },
52
+ run: async (args, ctx) => {
53
+ const db = getDb();
54
+ const now = new Date().toISOString();
55
+ if (args.taskId) {
56
+ await assertAccess("crm-task", args.taskId, "editor");
57
+ const [task] = await db
58
+ .select()
59
+ .from(schema.crmTasks)
60
+ .where(eq(schema.crmTasks.id, args.taskId))
61
+ .limit(1);
62
+ if (!task) throw new Error("CRM task was not found.");
63
+ if (task.authority !== "local") {
64
+ throw new Error(
65
+ "This is a mirrored provider task. Provider task writeback is not available in the initial release; create a CRM proposal or complete it upstream.",
66
+ );
67
+ }
68
+ if (args.recordId && args.recordId !== task.recordId) {
69
+ await assertAccess("crm-record", args.recordId, "editor");
70
+ }
71
+ await db
72
+ .update(schema.crmTasks)
73
+ .set({
74
+ ...(args.recordId !== undefined ? { recordId: args.recordId } : {}),
75
+ ...(args.title !== undefined ? { title: args.title } : {}),
76
+ ...(args.description !== undefined
77
+ ? { description: args.description }
78
+ : {}),
79
+ ...(args.status !== undefined ? { status: args.status } : {}),
80
+ ...(args.dueAt !== undefined ? { dueAt: args.dueAt } : {}),
81
+ ...(args.assignedTo !== undefined
82
+ ? { assignedTo: args.assignedTo }
83
+ : {}),
84
+ ...(args.status === "done" ? { completedAt: now } : {}),
85
+ ...(args.status && args.status !== "done"
86
+ ? { completedAt: null }
87
+ : {}),
88
+ updatedAt: now,
89
+ })
90
+ .where(eq(schema.crmTasks.id, args.taskId));
91
+ const [updated] = await db
92
+ .select()
93
+ .from(schema.crmTasks)
94
+ .where(eq(schema.crmTasks.id, args.taskId))
95
+ .limit(1);
96
+ if (!updated)
97
+ throw new Error("CRM task could not be verified after updating.");
98
+ return updated;
99
+ }
100
+
101
+ if (args.recordId)
102
+ await assertAccess("crm-record", args.recordId, "editor");
103
+ const scope = requireCrmScope(ctx);
104
+ const id = crypto.randomUUID();
105
+ await db.insert(schema.crmTasks).values({
106
+ id,
107
+ recordId: args.recordId ?? null,
108
+ title: args.title!,
109
+ description: args.description ?? "",
110
+ status: args.status ?? "open",
111
+ dueAt: args.dueAt ?? null,
112
+ assignedTo: args.assignedTo ?? null,
113
+ authority: "local",
114
+ completedAt: args.status === "done" ? now : null,
115
+ ...scope,
116
+ createdAt: now,
117
+ updatedAt: now,
118
+ });
119
+ const [task] = await db
120
+ .select()
121
+ .from(schema.crmTasks)
122
+ .where(
123
+ and(
124
+ eq(schema.crmTasks.id, id),
125
+ eq(schema.crmTasks.ownerEmail, scope.ownerEmail),
126
+ ),
127
+ )
128
+ .limit(1);
129
+ if (!task) throw new Error("CRM task could not be verified after saving.");
130
+ return task;
131
+ },
132
+ });