@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +16 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/templates-meta.ts +12 -0
- package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +115 -0
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +177 -0
- package/corpus/templates/crm/.agents/skills/crm/SKILL.md +205 -0
- package/corpus/templates/crm/.agents/skills/customizing-agent-native/SKILL.md +220 -0
- package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +228 -0
- package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +178 -0
- package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +122 -0
- package/corpus/templates/crm/.env.example +5 -0
- package/corpus/templates/crm/AGENTS.md +82 -0
- package/corpus/templates/crm/CHANGELOG.md +25 -0
- package/corpus/templates/crm/DEVELOPING.md +66 -0
- package/corpus/templates/crm/README.md +122 -0
- package/corpus/templates/crm/_gitignore +12 -0
- package/corpus/templates/crm/actions/_crm-action-utils.ts +98 -0
- package/corpus/templates/crm/actions/_crm-attribute-utils.ts +219 -0
- package/corpus/templates/crm/actions/_crm-dashboard.ts +69 -0
- package/corpus/templates/crm/actions/_crm-data-program-actions.ts +15 -0
- package/corpus/templates/crm/actions/_crm-list-utils.ts +775 -0
- package/corpus/templates/crm/actions/_crm-signal-utils.ts +79 -0
- package/corpus/templates/crm/actions/add-crm-record-to-list.ts +164 -0
- package/corpus/templates/crm/actions/apply-crm-proposals.ts +291 -0
- package/corpus/templates/crm/actions/archive-crm-attribute.ts +69 -0
- package/corpus/templates/crm/actions/attach-call-evidence.ts +170 -0
- package/corpus/templates/crm/actions/configure-crm-connection.ts +126 -0
- package/corpus/templates/crm/actions/configure-native-crm.ts +44 -0
- package/corpus/templates/crm/actions/create-crm-attribute.ts +350 -0
- package/corpus/templates/crm/actions/create-crm-list.ts +194 -0
- package/corpus/templates/crm/actions/create-crm-record.ts +148 -0
- package/corpus/templates/crm/actions/create-crm-signal-tracker.ts +58 -0
- package/corpus/templates/crm/actions/delete-crm-saved-view.ts +68 -0
- package/corpus/templates/crm/actions/delete-staged-dataset.ts +14 -0
- package/corpus/templates/crm/actions/estimate-crm-enrichment.ts +229 -0
- package/corpus/templates/crm/actions/find-crm-duplicates.ts +87 -0
- package/corpus/templates/crm/actions/get-crm-automation-recipe.ts +36 -0
- package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +35 -0
- package/corpus/templates/crm/actions/get-crm-dashboard.ts +14 -0
- package/corpus/templates/crm/actions/get-crm-overview.ts +13 -0
- package/corpus/templates/crm/actions/get-crm-pipeline-data.ts +61 -0
- package/corpus/templates/crm/actions/get-crm-record-page.ts +287 -0
- package/corpus/templates/crm/actions/get-crm-record.ts +75 -0
- package/corpus/templates/crm/actions/get-crm-workspace.ts +364 -0
- package/corpus/templates/crm/actions/install-crm-pipeline-dashboard.ts +81 -0
- package/corpus/templates/crm/actions/list-crm-attributes.ts +71 -0
- package/corpus/templates/crm/actions/list-crm-connections.ts +68 -0
- package/corpus/templates/crm/actions/list-crm-dashboard-revisions.ts +15 -0
- package/corpus/templates/crm/actions/list-crm-dashboards.ts +15 -0
- package/corpus/templates/crm/actions/list-crm-enrichment-slots.ts +28 -0
- package/corpus/templates/crm/actions/list-crm-list-entries.ts +192 -0
- package/corpus/templates/crm/actions/list-crm-lists.ts +80 -0
- package/corpus/templates/crm/actions/list-crm-proposals.ts +55 -0
- package/corpus/templates/crm/actions/list-crm-record-field-history.ts +201 -0
- package/corpus/templates/crm/actions/list-crm-record-values.ts +200 -0
- package/corpus/templates/crm/actions/list-crm-records.ts +103 -0
- package/corpus/templates/crm/actions/list-crm-saved-views.ts +66 -0
- package/corpus/templates/crm/actions/list-crm-signal-hits.ts +43 -0
- package/corpus/templates/crm/actions/list-crm-signal-trackers.ts +32 -0
- package/corpus/templates/crm/actions/list-crm-tasks.ts +22 -0
- package/corpus/templates/crm/actions/list-staged-datasets.ts +14 -0
- package/corpus/templates/crm/actions/list-workspace-connections.ts +47 -0
- package/corpus/templates/crm/actions/manage-crm-attribute-option.ts +272 -0
- package/corpus/templates/crm/actions/manage-crm-signal-tracker.ts +87 -0
- package/corpus/templates/crm/actions/manage-crm-task.ts +132 -0
- package/corpus/templates/crm/actions/merge-crm-records.ts +691 -0
- package/corpus/templates/crm/actions/navigate.ts +54 -0
- package/corpus/templates/crm/actions/provider-api-catalog.ts +22 -0
- package/corpus/templates/crm/actions/provider-api-docs.ts +54 -0
- package/corpus/templates/crm/actions/provider-api-request.ts +92 -0
- package/corpus/templates/crm/actions/query-staged-dataset.ts +44 -0
- package/corpus/templates/crm/actions/record-crm-call-insight.ts +160 -0
- package/corpus/templates/crm/actions/record-crm-smart-signal.ts +150 -0
- package/corpus/templates/crm/actions/remove-crm-list-entry.ts +79 -0
- package/corpus/templates/crm/actions/restore-crm-dashboard-revision.ts +23 -0
- package/corpus/templates/crm/actions/review-crm-signal.ts +31 -0
- package/corpus/templates/crm/actions/run-crm-attribute-fill.ts +443 -0
- package/corpus/templates/crm/actions/run-crm-enrichment.ts +647 -0
- package/corpus/templates/crm/actions/run-crm-saved-view-program.ts +103 -0
- package/corpus/templates/crm/actions/run-crm-signal-trackers.ts +265 -0
- package/corpus/templates/crm/actions/run.ts +7 -0
- package/corpus/templates/crm/actions/save-crm-dashboard.ts +51 -0
- package/corpus/templates/crm/actions/save-crm-saved-view.ts +296 -0
- package/corpus/templates/crm/actions/sync-crm.ts +146 -0
- package/corpus/templates/crm/actions/update-crm-attribute.ts +111 -0
- package/corpus/templates/crm/actions/update-crm-list-entry.ts +102 -0
- package/corpus/templates/crm/actions/update-crm-list.ts +97 -0
- package/corpus/templates/crm/actions/update-crm-record.ts +543 -0
- package/corpus/templates/crm/actions/view-screen.ts +324 -0
- package/corpus/templates/crm/agent-native.app-skill.json +69 -0
- package/corpus/templates/crm/app/components/crm/CreateCrmRecordDialog.tsx +158 -0
- package/corpus/templates/crm/app/components/crm/CrmDashboardPanel.tsx +78 -0
- package/corpus/templates/crm/app/components/crm/CrmSignalsPanel.tsx +220 -0
- package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +414 -0
- package/corpus/templates/crm/app/components/crm/RecordActions.tsx +648 -0
- package/corpus/templates/crm/app/components/crm/RecordGrid.tsx +488 -0
- package/corpus/templates/crm/app/components/crm/RecordWorkspace.tsx +232 -0
- package/corpus/templates/crm/app/components/crm/SavedViewDataProgram.tsx +93 -0
- package/corpus/templates/crm/app/components/crm/Surface.tsx +97 -0
- package/corpus/templates/crm/app/components/crm/WorkOverview.tsx +180 -0
- package/corpus/templates/crm/app/components/crm/board/CrmBoard.tsx +988 -0
- package/corpus/templates/crm/app/components/crm/board/board-model.ts +399 -0
- package/corpus/templates/crm/app/components/crm/board/entry-filter.ts +150 -0
- package/corpus/templates/crm/app/components/crm/board/view-draft.ts +227 -0
- package/corpus/templates/crm/app/components/crm/grid/CrmGrid.tsx +1033 -0
- package/corpus/templates/crm/app/components/crm/grid/GridCell.tsx +620 -0
- package/corpus/templates/crm/app/components/crm/grid/clipboard.ts +84 -0
- package/corpus/templates/crm/app/components/crm/grid/model.ts +304 -0
- package/corpus/templates/crm/app/components/crm/grid/navigation.ts +220 -0
- package/corpus/templates/crm/app/components/crm/grid/query.ts +214 -0
- package/corpus/templates/crm/app/components/crm/record/AttributePanel.tsx +192 -0
- package/corpus/templates/crm/app/components/crm/record/FieldHistory.tsx +179 -0
- package/corpus/templates/crm/app/components/crm/record/RecordHeader.tsx +103 -0
- package/corpus/templates/crm/app/components/crm/record/RecordLists.tsx +235 -0
- package/corpus/templates/crm/app/components/crm/record/RecordTabs.tsx +206 -0
- package/corpus/templates/crm/app/components/crm/record/field-editors.tsx +245 -0
- package/corpus/templates/crm/app/components/crm/record/record-data.ts +603 -0
- package/corpus/templates/crm/app/components/crm/settings/AdvancedSettings.tsx +49 -0
- package/corpus/templates/crm/app/components/crm/settings/AttributeOptionsEditor.tsx +441 -0
- package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +169 -0
- package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +935 -0
- package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +339 -0
- package/corpus/templates/crm/app/components/crm/settings/settings-admin.ts +377 -0
- package/corpus/templates/crm/app/components/crm/shared/AttributeValueParts.tsx +69 -0
- package/corpus/templates/crm/app/components/crm/shared/attribute-value.ts +591 -0
- package/corpus/templates/crm/app/components/layout/CrmCommandMenu.tsx +556 -0
- package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +130 -0
- package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +407 -0
- package/corpus/templates/crm/app/components/layout/command-palette-search.ts +126 -0
- package/corpus/templates/crm/app/components/layout/crm-ui-intents.ts +41 -0
- package/corpus/templates/crm/app/components/layout/sidebar-collapse.ts +40 -0
- package/corpus/templates/crm/app/components/layout/sidebar-lists.ts +109 -0
- package/corpus/templates/crm/app/components/ui/alert-dialog.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/badge.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/button.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/card.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/checkbox.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/dialog.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/dropdown-menu.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/hover-card.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/input.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/label.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/popover.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/select.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/sheet.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/skeleton.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/switch.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/table.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/tabs.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/textarea.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/toolkit-provider.tsx +17 -0
- package/corpus/templates/crm/app/components/ui/tooltip.tsx +1 -0
- package/corpus/templates/crm/app/design-system.ts +3 -0
- package/corpus/templates/crm/app/entry.client.tsx +19 -0
- package/corpus/templates/crm/app/entry.server.tsx +10 -0
- package/corpus/templates/crm/app/global.css +167 -0
- package/corpus/templates/crm/app/hooks/use-keyboard-shortcuts.ts +206 -0
- package/corpus/templates/crm/app/hooks/use-navigation-state.ts +48 -0
- package/corpus/templates/crm/app/i18n/en-US.ts +684 -0
- package/corpus/templates/crm/app/i18n/index.ts +34 -0
- package/corpus/templates/crm/app/lib/dashboard.ts +13 -0
- package/corpus/templates/crm/app/lib/navigation.ts +17 -0
- package/corpus/templates/crm/app/lib/tab-id.ts +1 -0
- package/corpus/templates/crm/app/lib/types.ts +172 -0
- package/corpus/templates/crm/app/lib/utils.ts +1 -0
- package/corpus/templates/crm/app/root.tsx +93 -0
- package/corpus/templates/crm/app/routes/_index.tsx +26 -0
- package/corpus/templates/crm/app/routes/accounts.tsx +41 -0
- package/corpus/templates/crm/app/routes/agent.tsx +5 -0
- package/corpus/templates/crm/app/routes/ask.tsx +53 -0
- package/corpus/templates/crm/app/routes/dashboard.tsx +118 -0
- package/corpus/templates/crm/app/routes/lists.tsx +228 -0
- package/corpus/templates/crm/app/routes/opportunities.tsx +41 -0
- package/corpus/templates/crm/app/routes/people.tsx +41 -0
- package/corpus/templates/crm/app/routes/proposals.tsx +388 -0
- package/corpus/templates/crm/app/routes/records.$recordId.tsx +53 -0
- package/corpus/templates/crm/app/routes/records.tsx +64 -0
- package/corpus/templates/crm/app/routes/settings.tsx +118 -0
- package/corpus/templates/crm/app/routes/setup.tsx +308 -0
- package/corpus/templates/crm/app/routes/tasks.tsx +252 -0
- package/corpus/templates/crm/app/routes/views.tsx +683 -0
- package/corpus/templates/crm/app/routes.ts +19 -0
- package/corpus/templates/crm/app/vite-env.d.ts +6 -0
- 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
- package/corpus/templates/crm/changelog/2026-07-26-crm-now-finds-likely-duplicate-records-and-tells-you-exactly.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-enrichment-now-shows-what-a-run-will-cost-before-it-starts-g.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-lists-turn-any-set-of-records-into-a-pipeline-with-its-own-s.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-moving-a-record-or-list-entry-into-a-stage-that-was-retired-.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-opening-a-record-now-shows-one-page-with-its-fields-the-list.md +6 -0
- 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
- package/corpus/templates/crm/changelog/2026-07-26-provider-record-edits-now-show-an-exact-before-after-diff-an.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-record-page-attribute-labels-now-read-in-full-instead-of-bei.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-records-now-use-typed-fields-currency-dates-ratings-status-s.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-retyping-a-value-on-the-record-page-now-replaces-it-instead-.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-settings-now-has-fields-and-lists-tabs-for-adding-fields-edi.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-the-proposal-review-now-shows-the-field-that-actually-change.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-the-record-grid-is-now-a-real-spreadsheet-filter-sort-and-pa.md +6 -0
- package/corpus/templates/crm/components.json +20 -0
- package/corpus/templates/crm/docs/architecture/crm-contract.md +214 -0
- package/corpus/templates/crm/docs/features/README.md +55 -0
- package/corpus/templates/crm/docs/features/f1-records-and-fields.mdx +52 -0
- package/corpus/templates/crm/docs/features/f2-lists-pipelines-and-views.mdx +48 -0
- package/corpus/templates/crm/docs/features/f3-work-and-record-quality.mdx +40 -0
- package/corpus/templates/crm/docs/features/f4-evidence-and-chat.mdx +47 -0
- package/corpus/templates/crm/learnings.defaults.md +5 -0
- package/corpus/templates/crm/netlify.toml +11 -0
- package/corpus/templates/crm/package.json +61 -0
- package/corpus/templates/crm/public/favicon.svg +1 -0
- package/corpus/templates/crm/public/icon-180.svg +1 -0
- package/corpus/templates/crm/public/manifest.json +11 -0
- package/corpus/templates/crm/react-router.config.ts +7 -0
- package/corpus/templates/crm/server/crm/adapter.ts +32 -0
- package/corpus/templates/crm/server/crm/crm-field-firewall.ts +64 -0
- package/corpus/templates/crm/server/crm/crm-mirror.ts +721 -0
- package/corpus/templates/crm/server/crm/field-policy-attributes.ts +48 -0
- package/corpus/templates/crm/server/crm/hubspot-adapter.ts +1146 -0
- package/corpus/templates/crm/server/crm/native-adapter.ts +1810 -0
- package/corpus/templates/crm/server/crm/provider-record-link.ts +264 -0
- package/corpus/templates/crm/server/crm/read-through.ts +207 -0
- package/corpus/templates/crm/server/crm/salesforce-adapter.ts +1255 -0
- package/corpus/templates/crm/server/db/crm-store.ts +1112 -0
- package/corpus/templates/crm/server/db/index.ts +191 -0
- package/corpus/templates/crm/server/db/schema.ts +647 -0
- package/corpus/templates/crm/server/lib/crm-query.ts +1558 -0
- package/corpus/templates/crm/server/lib/dedupe.ts +714 -0
- package/corpus/templates/crm/server/lib/enrichment-cost.ts +381 -0
- package/corpus/templates/crm/server/lib/enrichment-slots.ts +519 -0
- package/corpus/templates/crm/server/lib/intelligence/default-detectors.ts +65 -0
- package/corpus/templates/crm/server/lib/intelligence/evidence.ts +105 -0
- package/corpus/templates/crm/server/lib/intelligence/keyword-detector.ts +94 -0
- package/corpus/templates/crm/server/lib/intelligence/smart-detector.ts +39 -0
- package/corpus/templates/crm/server/lib/intelligence/summary.ts +19 -0
- package/corpus/templates/crm/server/lib/lifecycle.ts +634 -0
- package/corpus/templates/crm/server/lib/provider-api.ts +64 -0
- package/corpus/templates/crm/server/middleware/auth.ts +4 -0
- package/corpus/templates/crm/server/plugins/agent-chat.ts +65 -0
- package/corpus/templates/crm/server/plugins/auth.ts +14 -0
- package/corpus/templates/crm/server/plugins/core-routes.ts +17 -0
- package/corpus/templates/crm/server/plugins/db.ts +738 -0
- package/corpus/templates/crm/server/routes/[...page].get.ts +5 -0
- package/corpus/templates/crm/shared/crm-attributes.ts +469 -0
- package/corpus/templates/crm/shared/crm-automation-recipes.ts +132 -0
- package/corpus/templates/crm/shared/crm-contract.ts +417 -0
- package/corpus/templates/crm/shared/crm-navigation.ts +190 -0
- package/corpus/templates/crm/shared/crm-sales-config.ts +63 -0
- package/corpus/templates/crm/ssr-entry.ts +11 -0
- package/corpus/templates/crm/tsconfig.json +24 -0
- package/corpus/templates/crm/vite.config.ts +15 -0
- package/corpus/templates/crm/vitest.config.ts +5 -0
- package/corpus/templates/design/app/i18n-data.ts +12 -12
- package/corpus/templates/design/changelog/2026-07-26-apply-design-updates-from-a-centered-top-toolbar-without-cov.md +6 -0
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +12 -0
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
- 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
|
+
});
|