@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,229 @@
|
|
|
1
|
+
import { defineAction, type ActionRunContext } from "@agent-native/core/action";
|
|
2
|
+
import { accessFilter } from "@agent-native/core/sharing";
|
|
3
|
+
import { and, eq, gte, inArray } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import {
|
|
8
|
+
CrmEnrichmentScopeError,
|
|
9
|
+
MAX_ENRICHMENT_RECORDS_PER_RUN,
|
|
10
|
+
assertRecordCountWithinCap,
|
|
11
|
+
currentSpendPeriodStart,
|
|
12
|
+
estimateEnrichment,
|
|
13
|
+
evaluateEnrichmentBudget,
|
|
14
|
+
resolveEnrichmentBudgetUnits,
|
|
15
|
+
resolveEnrichmentScope,
|
|
16
|
+
type CrmEnrichmentScopeKind,
|
|
17
|
+
type CrmSpendToDate,
|
|
18
|
+
} from "../server/lib/enrichment-cost.js";
|
|
19
|
+
import {
|
|
20
|
+
CRM_ENRICHMENT_SLOTS,
|
|
21
|
+
type CrmEnrichmentSlot,
|
|
22
|
+
} from "../server/lib/enrichment-slots.js";
|
|
23
|
+
import { requireCrmScope } from "./_crm-action-utils.js";
|
|
24
|
+
|
|
25
|
+
export const scopeInput = {
|
|
26
|
+
scopeKind: z.enum(["object", "list", "records"]).default("records"),
|
|
27
|
+
targetId: z
|
|
28
|
+
.string()
|
|
29
|
+
.trim()
|
|
30
|
+
.min(1)
|
|
31
|
+
.max(200)
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Object type for scopeKind=object, list id for scopeKind=list."),
|
|
34
|
+
recordIds: z
|
|
35
|
+
.array(z.string().trim().min(1).max(128))
|
|
36
|
+
.max(MAX_ENRICHMENT_RECORDS_PER_RUN)
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Required for scopeKind=records."),
|
|
39
|
+
slots: z
|
|
40
|
+
.array(z.enum(CRM_ENRICHMENT_SLOTS))
|
|
41
|
+
.min(1)
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Defaults to every slot. The phase decides which of them run."),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default defineAction({
|
|
47
|
+
description:
|
|
48
|
+
"Estimate what an enrichment run will cost BEFORE spending anything, with a per-slot line-item breakdown and your period-to-date spend. Read-only: it starts nothing. Phase verify prices the free evidence pass; phase spend prices the paid contact pass over the records you intend to approve. Spend to date is reported twice — actorUnits is yours and is what the budget cap applies to, workspaceUnits is everyone's and is context only. Quote the estimate to the user before calling run-crm-enrichment, which echoes back this same estimate.",
|
|
49
|
+
schema: z.object({
|
|
50
|
+
phase: z.enum(["verify", "spend"]),
|
|
51
|
+
...scopeInput,
|
|
52
|
+
}),
|
|
53
|
+
http: { method: "GET" },
|
|
54
|
+
readOnly: true,
|
|
55
|
+
run: async (args, ctx?: ActionRunContext) => {
|
|
56
|
+
const ownership = requireCrmScope(ctx);
|
|
57
|
+
const recordIds = await resolveScopeRecordIds(args);
|
|
58
|
+
assertRecordCountWithinCap(recordIds.length);
|
|
59
|
+
const scope = await resolveEnrichmentScope({
|
|
60
|
+
kind: args.scopeKind,
|
|
61
|
+
targetId: args.targetId,
|
|
62
|
+
recordIds,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const estimate = estimateEnrichment({
|
|
66
|
+
phase: args.phase,
|
|
67
|
+
slots: (args.slots ?? CRM_ENRICHMENT_SLOTS) as CrmEnrichmentSlot[],
|
|
68
|
+
recordCount: recordIds.length,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const periodStart = currentSpendPeriodStart();
|
|
72
|
+
const spendToDate = await readEnrichmentSpendToDate({
|
|
73
|
+
ownerEmail: ownership.ownerEmail,
|
|
74
|
+
periodStart,
|
|
75
|
+
});
|
|
76
|
+
const budget = evaluateEnrichmentBudget({
|
|
77
|
+
estimatedUnits: estimate.totalCost,
|
|
78
|
+
spendToDate,
|
|
79
|
+
capUnits: resolveEnrichmentBudgetUnits(),
|
|
80
|
+
periodStart,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return { scope, recordCount: recordIds.length, estimate, budget };
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The records a scope resolves to, access-scoped.
|
|
89
|
+
*
|
|
90
|
+
* An explicit record id that does not resolve is an error, not a silent
|
|
91
|
+
* omission: quoting a price for four records when the user named five is a
|
|
92
|
+
* quote for a different job.
|
|
93
|
+
*/
|
|
94
|
+
export async function resolveScopeRecordIds(args: {
|
|
95
|
+
scopeKind: CrmEnrichmentScopeKind;
|
|
96
|
+
targetId?: string;
|
|
97
|
+
recordIds?: string[];
|
|
98
|
+
}): Promise<string[]> {
|
|
99
|
+
const db = getDb();
|
|
100
|
+
if (args.scopeKind === "records") {
|
|
101
|
+
const requested = [...new Set(args.recordIds ?? [])];
|
|
102
|
+
if (requested.length === 0) {
|
|
103
|
+
throw new CrmEnrichmentScopeError(
|
|
104
|
+
"crm-enrichment-scope-empty",
|
|
105
|
+
"scopeKind=records needs at least one record id.",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const rows = await db
|
|
109
|
+
.select({ id: schema.crmRecords.id })
|
|
110
|
+
.from(schema.crmRecords)
|
|
111
|
+
.where(
|
|
112
|
+
and(
|
|
113
|
+
inArray(schema.crmRecords.id, requested),
|
|
114
|
+
eq(schema.crmRecords.tombstone, false),
|
|
115
|
+
accessFilter(schema.crmRecords, schema.crmRecordShares),
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
const found = new Set(rows.map((row) => row.id));
|
|
119
|
+
const missing = requested.filter((id) => !found.has(id));
|
|
120
|
+
if (missing.length > 0) {
|
|
121
|
+
throw new CrmEnrichmentScopeError(
|
|
122
|
+
"crm-enrichment-scope-record-not-readable",
|
|
123
|
+
`These CRM records were not found or are not readable by you: ${missing.join(", ")}.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return requested;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (args.scopeKind === "list") {
|
|
130
|
+
if (!args.targetId) {
|
|
131
|
+
throw new CrmEnrichmentScopeError(
|
|
132
|
+
"crm-enrichment-scope-target-required",
|
|
133
|
+
"scopeKind=list needs a targetId.",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const rows = await db
|
|
137
|
+
.select({ recordId: schema.crmListEntries.recordId })
|
|
138
|
+
.from(schema.crmListEntries)
|
|
139
|
+
.where(
|
|
140
|
+
and(
|
|
141
|
+
eq(schema.crmListEntries.listId, args.targetId),
|
|
142
|
+
accessFilter(schema.crmListEntries, schema.crmListEntryShares),
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
.limit(MAX_ENRICHMENT_RECORDS_PER_RUN + 1);
|
|
146
|
+
// A record may hold several entries in one list; it is still one record to enrich.
|
|
147
|
+
return [...new Set(rows.map((row) => row.recordId))];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!args.targetId) {
|
|
151
|
+
throw new CrmEnrichmentScopeError(
|
|
152
|
+
"crm-enrichment-scope-target-required",
|
|
153
|
+
"scopeKind=object needs a targetId.",
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const rows = await db
|
|
157
|
+
.select({ id: schema.crmRecords.id })
|
|
158
|
+
.from(schema.crmRecords)
|
|
159
|
+
.where(
|
|
160
|
+
and(
|
|
161
|
+
eq(schema.crmRecords.objectType, args.targetId),
|
|
162
|
+
eq(schema.crmRecords.tombstone, false),
|
|
163
|
+
accessFilter(schema.crmRecords, schema.crmRecordShares),
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
.limit(MAX_ENRICHMENT_RECORDS_PER_RUN + 1);
|
|
167
|
+
return rows.map((row) => row.id);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Spend since the period start, per actor and across the workspace.
|
|
172
|
+
*
|
|
173
|
+
* A run still in flight counts at its estimate, not at zero: otherwise ten
|
|
174
|
+
* concurrent launches all see the same pre-spend total and every one of them
|
|
175
|
+
* passes a cap they collectively blow through.
|
|
176
|
+
*/
|
|
177
|
+
export async function readEnrichmentSpendToDate(input: {
|
|
178
|
+
ownerEmail: string;
|
|
179
|
+
periodStart: string;
|
|
180
|
+
}): Promise<CrmSpendToDate> {
|
|
181
|
+
const rows = await getDb()
|
|
182
|
+
.select({
|
|
183
|
+
id: schema.crmEnrichmentRuns.id,
|
|
184
|
+
ownerEmail: schema.crmEnrichmentRuns.ownerEmail,
|
|
185
|
+
status: schema.crmEnrichmentRuns.status,
|
|
186
|
+
costUnits: schema.crmEnrichmentRuns.costUnits,
|
|
187
|
+
estimateJson: schema.crmEnrichmentRuns.estimateJson,
|
|
188
|
+
})
|
|
189
|
+
.from(schema.crmEnrichmentRuns)
|
|
190
|
+
.where(
|
|
191
|
+
and(
|
|
192
|
+
gte(schema.crmEnrichmentRuns.startedAt, input.periodStart),
|
|
193
|
+
accessFilter(schema.crmEnrichmentRuns, schema.crmEnrichmentRunShares),
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
let actorUnits = 0;
|
|
198
|
+
let workspaceUnits = 0;
|
|
199
|
+
for (const row of rows) {
|
|
200
|
+
if (row.status === "failed") continue;
|
|
201
|
+
const units = row.costUnits ?? estimatedUnitsOf(row.id, row.estimateJson);
|
|
202
|
+
workspaceUnits += units;
|
|
203
|
+
if (row.ownerEmail === input.ownerEmail) actorUnits += units;
|
|
204
|
+
}
|
|
205
|
+
return { actorUnits, workspaceUnits };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** An unreadable estimate throws — an unknown cost must not be counted as zero. */
|
|
209
|
+
function estimatedUnitsOf(runId: string, estimateJson: string): number {
|
|
210
|
+
let parsed: unknown;
|
|
211
|
+
try {
|
|
212
|
+
parsed = JSON.parse(estimateJson);
|
|
213
|
+
} catch {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Enrichment run ${runId} has an unreadable estimate_json, so period-to-date spend cannot be totalled. Repair or fail the run.`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const total =
|
|
219
|
+
parsed && typeof parsed === "object"
|
|
220
|
+
? (parsed as Record<string, unknown>).totalCost
|
|
221
|
+
: undefined;
|
|
222
|
+
if (total === undefined || total === null) return 0;
|
|
223
|
+
if (typeof total !== "number" || !Number.isFinite(total)) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Enrichment run ${runId} has a non-numeric estimate total, so period-to-date spend cannot be totalled.`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return total;
|
|
229
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { accessFilter } from "@agent-native/core/sharing";
|
|
3
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import {
|
|
8
|
+
findCrmDuplicateCandidates,
|
|
9
|
+
CRM_DUPLICATE_MATCH_REASONS,
|
|
10
|
+
} from "../server/lib/dedupe.js";
|
|
11
|
+
|
|
12
|
+
const MAX_SEEDS = 25;
|
|
13
|
+
|
|
14
|
+
export default defineAction({
|
|
15
|
+
description:
|
|
16
|
+
"Find likely duplicate CRM records and say WHY each pair matched. Checks exact email, company domain, shared email root domain, and normalized name plus location, using indexed sub-field columns. Returns scored candidates with a match reason and confidence per candidate; it never merges anything and never changes a record. Pass recordIds to check specific records, or omit them to check the most recently updated records of an object type.",
|
|
17
|
+
schema: z.object({
|
|
18
|
+
recordIds: z
|
|
19
|
+
.array(z.string().trim().min(1).max(128))
|
|
20
|
+
.max(MAX_SEEDS)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Records to check. Omit to scan the most recent records."),
|
|
23
|
+
objectType: z
|
|
24
|
+
.string()
|
|
25
|
+
.trim()
|
|
26
|
+
.min(1)
|
|
27
|
+
.max(120)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Restrict an automatic scan to one object type."),
|
|
30
|
+
limit: z.coerce
|
|
31
|
+
.number()
|
|
32
|
+
.int()
|
|
33
|
+
.min(1)
|
|
34
|
+
.max(MAX_SEEDS)
|
|
35
|
+
.default(10)
|
|
36
|
+
.describe("How many records to check when recordIds is omitted."),
|
|
37
|
+
candidatesPerRecord: z.coerce.number().int().min(1).max(20).default(5),
|
|
38
|
+
minConfidence: z.coerce.number().min(0).max(1).default(0.4),
|
|
39
|
+
}),
|
|
40
|
+
http: { method: "GET" },
|
|
41
|
+
readOnly: true,
|
|
42
|
+
run: async (args) => {
|
|
43
|
+
const db = getDb();
|
|
44
|
+
|
|
45
|
+
let recordIds = args.recordIds ?? [];
|
|
46
|
+
if (!recordIds.length) {
|
|
47
|
+
const recent = await db
|
|
48
|
+
.select({ id: schema.crmRecords.id })
|
|
49
|
+
.from(schema.crmRecords)
|
|
50
|
+
.where(
|
|
51
|
+
and(
|
|
52
|
+
eq(schema.crmRecords.tombstone, false),
|
|
53
|
+
...(args.objectType
|
|
54
|
+
? [eq(schema.crmRecords.objectType, args.objectType)]
|
|
55
|
+
: []),
|
|
56
|
+
accessFilter(schema.crmRecords, schema.crmRecordShares),
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
.orderBy(desc(schema.crmRecords.updatedAt))
|
|
60
|
+
.limit(args.limit);
|
|
61
|
+
recordIds = recent.map((row) => row.id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const seeds = await findCrmDuplicateCandidates({
|
|
65
|
+
db,
|
|
66
|
+
recordIds,
|
|
67
|
+
limit: args.candidatesPerRecord,
|
|
68
|
+
minConfidence: args.minConfidence,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Requested ids that produced no seed were not readable by this caller.
|
|
72
|
+
// Reporting them as "no duplicates" would be the same failure this action
|
|
73
|
+
// exists to prevent, so they come back named.
|
|
74
|
+
const found = new Set(seeds.map((seed) => seed.recordId));
|
|
75
|
+
const unreadableRecordIds = recordIds.filter((id) => !found.has(id));
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
checked: seeds.length,
|
|
79
|
+
matchReasons: [...CRM_DUPLICATE_MATCH_REASONS],
|
|
80
|
+
unreadableRecordIds,
|
|
81
|
+
records: seeds.filter((seed) => seed.candidates.length > 0),
|
|
82
|
+
cleanRecordIds: seeds
|
|
83
|
+
.filter((seed) => seed.candidates.length === 0)
|
|
84
|
+
.map((seed) => seed.recordId),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
import {
|
|
8
|
+
CLIPS_CALL_EVIDENCE_RECIPE_ID,
|
|
9
|
+
buildClipsCallEvidenceRecipe,
|
|
10
|
+
} from "../shared/crm-automation-recipes.js";
|
|
11
|
+
|
|
12
|
+
export default defineAction({
|
|
13
|
+
description:
|
|
14
|
+
"Return the default-off Clips call-evidence review recipe for one explicitly selected CRM record. The recipe never passes media, transcripts, or an event URL into CRM.",
|
|
15
|
+
schema: z.object({
|
|
16
|
+
recipeId: z
|
|
17
|
+
.literal(CLIPS_CALL_EVIDENCE_RECIPE_ID)
|
|
18
|
+
.default(CLIPS_CALL_EVIDENCE_RECIPE_ID),
|
|
19
|
+
recordId: z.string().trim().min(1).max(128),
|
|
20
|
+
}),
|
|
21
|
+
http: { method: "GET" },
|
|
22
|
+
readOnly: true,
|
|
23
|
+
run: async ({ recordId }) => {
|
|
24
|
+
await assertAccess("crm-record", recordId, "viewer");
|
|
25
|
+
const [record] = await getDb()
|
|
26
|
+
.select({ displayName: schema.crmRecords.displayName })
|
|
27
|
+
.from(schema.crmRecords)
|
|
28
|
+
.where(eq(schema.crmRecords.id, recordId))
|
|
29
|
+
.limit(1);
|
|
30
|
+
if (!record) throw new Error("CRM record was not found.");
|
|
31
|
+
return buildClipsCallEvidenceRecipe({
|
|
32
|
+
recordId,
|
|
33
|
+
recordLabel: record.displayName,
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineAction, type ActionRunContext } from "@agent-native/core/action";
|
|
2
|
+
import { createProgramPanelSourceResolver } from "@agent-native/core/dashboard-storage";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import { crmDashboardStore } from "../server/db/index.js";
|
|
6
|
+
import { CRM_APP_ID } from "../server/lib/provider-api.js";
|
|
7
|
+
import { requireDashboardAccess } from "./_crm-dashboard.js";
|
|
8
|
+
import { initCrmDataPrograms } from "./_crm-data-program-actions.js";
|
|
9
|
+
|
|
10
|
+
const programResolver = createProgramPanelSourceResolver({ appId: CRM_APP_ID });
|
|
11
|
+
|
|
12
|
+
export default defineAction({
|
|
13
|
+
description:
|
|
14
|
+
"Resolve one program-backed panel from an access-scoped CRM dashboard for the dashboard UI.",
|
|
15
|
+
schema: z.object({
|
|
16
|
+
dashboardId: z.string().trim().min(1).max(200),
|
|
17
|
+
panelId: z.string().trim().min(1).max(120),
|
|
18
|
+
}),
|
|
19
|
+
http: { method: "GET" },
|
|
20
|
+
readOnly: true,
|
|
21
|
+
agentTool: false,
|
|
22
|
+
toolCallable: false,
|
|
23
|
+
run: async ({ dashboardId, panelId }, ctx?: ActionRunContext) => {
|
|
24
|
+
const access = requireDashboardAccess(ctx);
|
|
25
|
+
const dashboard = await crmDashboardStore.get(dashboardId, access);
|
|
26
|
+
if (!dashboard) throw new Error("CRM dashboard was not found.");
|
|
27
|
+
const panel = dashboard.config.panels.find((item) => item.id === panelId);
|
|
28
|
+
if (!panel) throw new Error("CRM dashboard panel was not found.");
|
|
29
|
+
initCrmDataPrograms();
|
|
30
|
+
return programResolver.resolve(
|
|
31
|
+
{ source: panel.source, query: panel.query },
|
|
32
|
+
{ userEmail: access.userEmail, orgId: access.orgId ?? null },
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineAction, type ActionRunContext } from "@agent-native/core/action";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { crmDashboardStore } from "../server/db/index.js";
|
|
5
|
+
import { requireDashboardAccess } from "./_crm-dashboard.js";
|
|
6
|
+
|
|
7
|
+
export default defineAction({
|
|
8
|
+
description: "Get one access-scoped CRM dashboard by ID.",
|
|
9
|
+
schema: z.object({ id: z.string().trim().min(1).max(200) }),
|
|
10
|
+
http: { method: "GET" },
|
|
11
|
+
readOnly: true,
|
|
12
|
+
run: ({ id }, ctx?: ActionRunContext) =>
|
|
13
|
+
crmDashboardStore.get(id, requireDashboardAccess(ctx)),
|
|
14
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { getCrmOverview } from "../server/db/crm-store.js";
|
|
5
|
+
|
|
6
|
+
export default defineAction({
|
|
7
|
+
description:
|
|
8
|
+
"Return a compact, access-scoped CRM work overview: open follow-up tasks, recently active mirrored records, and pending write proposals.",
|
|
9
|
+
schema: z.object({}),
|
|
10
|
+
http: { method: "GET" },
|
|
11
|
+
readOnly: true,
|
|
12
|
+
run: () => getCrmOverview(),
|
|
13
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
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 { requireDashboardAccess } from "./_crm-dashboard.js";
|
|
8
|
+
|
|
9
|
+
const MAX_PIPELINE_RECORDS = 500;
|
|
10
|
+
|
|
11
|
+
export default defineAction({
|
|
12
|
+
description:
|
|
13
|
+
"Return bounded, access-scoped pipeline totals grouped by opportunity stage. This is the CRM-owned read source for the Pipeline dashboard data program.",
|
|
14
|
+
schema: z.object({}),
|
|
15
|
+
http: { method: "GET" },
|
|
16
|
+
readOnly: true,
|
|
17
|
+
run: async (_args, ctx?: ActionRunContext) => {
|
|
18
|
+
const access = requireDashboardAccess(ctx);
|
|
19
|
+
const records = await getDb()
|
|
20
|
+
.select({
|
|
21
|
+
stage: schema.crmRecords.stage,
|
|
22
|
+
amount: schema.crmRecords.amount,
|
|
23
|
+
})
|
|
24
|
+
.from(schema.crmRecords)
|
|
25
|
+
.where(
|
|
26
|
+
and(
|
|
27
|
+
eq(schema.crmRecords.kind, "opportunity"),
|
|
28
|
+
eq(schema.crmRecords.tombstone, false),
|
|
29
|
+
accessFilter(schema.crmRecords, schema.crmRecordShares, access),
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
.orderBy(asc(schema.crmRecords.stage))
|
|
33
|
+
.limit(MAX_PIPELINE_RECORDS);
|
|
34
|
+
|
|
35
|
+
const stages = new Map<
|
|
36
|
+
string,
|
|
37
|
+
{ stage: string; pipelineValue: number; opportunities: number }
|
|
38
|
+
>();
|
|
39
|
+
for (const record of records) {
|
|
40
|
+
const stage = record.stage?.trim() || "Unstaged";
|
|
41
|
+
const total = stages.get(stage) ?? {
|
|
42
|
+
stage,
|
|
43
|
+
pipelineValue: 0,
|
|
44
|
+
opportunities: 0,
|
|
45
|
+
};
|
|
46
|
+
total.pipelineValue += Number.isFinite(record.amount)
|
|
47
|
+
? record.amount!
|
|
48
|
+
: 0;
|
|
49
|
+
total.opportunities += 1;
|
|
50
|
+
stages.set(stage, total);
|
|
51
|
+
}
|
|
52
|
+
const rows = [...stages.values()].sort((a, b) =>
|
|
53
|
+
a.stage.localeCompare(b.stage),
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
rows,
|
|
57
|
+
truncated: records.length === MAX_PIPELINE_RECORDS,
|
|
58
|
+
sourceRecordLimit: MAX_PIPELINE_RECORDS,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|