@open-mercato/core 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3043.1a796c3920
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +13 -1
- package/dist/helpers/integration/api.js +29 -16
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/helpers/integration/auth.js +11 -6
- package/dist/helpers/integration/auth.js.map +3 -3
- package/dist/modules/auth/commands/roles.js +9 -12
- package/dist/modules/auth/commands/roles.js.map +2 -2
- package/dist/modules/catalog/ai-agents-context.js +147 -0
- package/dist/modules/catalog/ai-agents-context.js.map +7 -0
- package/dist/modules/catalog/ai-agents.js +383 -0
- package/dist/modules/catalog/ai-agents.js.map +7 -0
- package/dist/modules/catalog/ai-tools/_shared.js +318 -0
- package/dist/modules/catalog/ai-tools/_shared.js.map +7 -0
- package/dist/modules/catalog/ai-tools/authoring-pack.js +391 -0
- package/dist/modules/catalog/ai-tools/authoring-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/categories-pack.js +167 -0
- package/dist/modules/catalog/ai-tools/categories-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/configuration-pack.js +120 -0
- package/dist/modules/catalog/ai-tools/configuration-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/media-tags-pack.js +107 -0
- package/dist/modules/catalog/ai-tools/media-tags-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/merchandising-pack.js +429 -0
- package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/mutation-pack.js +576 -0
- package/dist/modules/catalog/ai-tools/mutation-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js +208 -0
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/products-pack.js +298 -0
- package/dist/modules/catalog/ai-tools/products-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/stats-pack.js +57 -0
- package/dist/modules/catalog/ai-tools/stats-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/types.js +10 -0
- package/dist/modules/catalog/ai-tools/types.js.map +7 -0
- package/dist/modules/catalog/ai-tools/variants-pack.js +75 -0
- package/dist/modules/catalog/ai-tools/variants-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools.js +28 -0
- package/dist/modules/catalog/ai-tools.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +466 -0
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/page.js +7 -1
- package/dist/modules/catalog/backend/catalog/products/page.js.map +2 -2
- package/dist/modules/catalog/components/CatalogStatsCard.js +91 -0
- package/dist/modules/catalog/components/CatalogStatsCard.js.map +7 -0
- package/dist/modules/catalog/components/products/ProductsDataTable.js +23 -3
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/events.js +7 -4
- package/dist/modules/catalog/events.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +59 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js +17 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js +1 -1
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js.map +2 -2
- package/dist/modules/catalog/widgets/injection-table.js +13 -1
- package/dist/modules/catalog/widgets/injection-table.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js +94 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js +17 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js +9 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/ai-agents-context.js +96 -0
- package/dist/modules/customers/ai-agents-context.js.map +7 -0
- package/dist/modules/customers/ai-agents.js +244 -0
- package/dist/modules/customers/ai-agents.js.map +7 -0
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js +1015 -0
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/addresses-tags-pack.js +134 -0
- package/dist/modules/customers/ai-tools/addresses-tags-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/companies-pack.js +249 -0
- package/dist/modules/customers/ai-tools/companies-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/deals-pack.js +348 -0
- package/dist/modules/customers/ai-tools/deals-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/people-pack.js +261 -0
- package/dist/modules/customers/ai-tools/people-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/settings-pack.js +102 -0
- package/dist/modules/customers/ai-tools/settings-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/types.js +10 -0
- package/dist/modules/customers/ai-tools/types.js.map +7 -0
- package/dist/modules/customers/ai-tools.js +20 -0
- package/dist/modules/customers/ai-tools.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +469 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +117 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection-table.js +26 -0
- package/dist/modules/customers/widgets/injection-table.js.map +7 -0
- package/dist/modules/inbox_ops/ai-tools.js +4 -0
- package/dist/modules/inbox_ops/ai-tools.js.map +2 -2
- package/dist/modules/inbox_ops/lib/llmProvider.js +52 -7
- package/dist/modules/inbox_ops/lib/llmProvider.js.map +2 -2
- package/dist/modules/notifications/setup.js +13 -0
- package/dist/modules/notifications/setup.js.map +7 -0
- package/jest.config.cjs +1 -0
- package/jest.setup.ts +18 -0
- package/package.json +5 -3
- package/src/helpers/integration/api.ts +38 -16
- package/src/helpers/integration/auth.ts +13 -6
- package/src/modules/auth/commands/roles.ts +10 -12
- package/src/modules/catalog/AGENTS.md +11 -0
- package/src/modules/catalog/ai-agents-context.ts +239 -0
- package/src/modules/catalog/ai-agents.ts +525 -0
- package/src/modules/catalog/ai-tools/_shared.ts +487 -0
- package/src/modules/catalog/ai-tools/authoring-pack.ts +600 -0
- package/src/modules/catalog/ai-tools/categories-pack.ts +192 -0
- package/src/modules/catalog/ai-tools/configuration-pack.ts +218 -0
- package/src/modules/catalog/ai-tools/media-tags-pack.ts +127 -0
- package/src/modules/catalog/ai-tools/merchandising-pack.ts +608 -0
- package/src/modules/catalog/ai-tools/mutation-pack.ts +761 -0
- package/src/modules/catalog/ai-tools/prices-offers-pack.ts +376 -0
- package/src/modules/catalog/ai-tools/products-pack.ts +387 -0
- package/src/modules/catalog/ai-tools/stats-pack.ts +84 -0
- package/src/modules/catalog/ai-tools/types.ts +81 -0
- package/src/modules/catalog/ai-tools/variants-pack.ts +147 -0
- package/src/modules/catalog/ai-tools.ts +78 -0
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +597 -0
- package/src/modules/catalog/backend/catalog/products/page.tsx +23 -2
- package/src/modules/catalog/components/CatalogStatsCard.tsx +118 -0
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +54 -6
- package/src/modules/catalog/events.ts +7 -4
- package/src/modules/catalog/i18n/de.json +17 -0
- package/src/modules/catalog/i18n/en.json +17 -0
- package/src/modules/catalog/i18n/es.json +17 -0
- package/src/modules/catalog/i18n/pl.json +17 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +109 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.ts +29 -0
- package/src/modules/catalog/widgets/injection/product-seo/widget.client.tsx +1 -1
- package/src/modules/catalog/widgets/injection-table.ts +12 -0
- package/src/modules/customer_accounts/i18n/de.json +5 -0
- package/src/modules/customer_accounts/i18n/en.json +5 -0
- package/src/modules/customer_accounts/i18n/es.json +5 -0
- package/src/modules/customer_accounts/i18n/pl.json +5 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.tsx +136 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.ts +43 -0
- package/src/modules/customer_accounts/widgets/injection-table.ts +9 -0
- package/src/modules/customers/AGENTS.md +13 -0
- package/src/modules/customers/ai-agents-context.ts +150 -0
- package/src/modules/customers/ai-agents.ts +355 -0
- package/src/modules/customers/ai-tools/activities-tasks-pack.ts +1248 -0
- package/src/modules/customers/ai-tools/addresses-tags-pack.ts +145 -0
- package/src/modules/customers/ai-tools/companies-pack.ts +362 -0
- package/src/modules/customers/ai-tools/deals-pack.ts +505 -0
- package/src/modules/customers/ai-tools/people-pack.ts +369 -0
- package/src/modules/customers/ai-tools/settings-pack.ts +121 -0
- package/src/modules/customers/ai-tools/types.ts +76 -0
- package/src/modules/customers/ai-tools.ts +34 -0
- package/src/modules/customers/i18n/de.json +25 -0
- package/src/modules/customers/i18n/en.json +25 -0
- package/src/modules/customers/i18n/es.json +25 -0
- package/src/modules/customers/i18n/pl.json +25 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +580 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +36 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +191 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.ts +37 -0
- package/src/modules/customers/widgets/injection-table.ts +41 -0
- package/src/modules/inbox_ops/ai-tools.ts +4 -0
- package/src/modules/inbox_ops/lib/llmProvider.ts +83 -7
- package/src/modules/notifications/setup.ts +11 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `customers.list_people` + `customers.get_person` (Phase 1 WS-C, Step 3.9).
|
|
3
|
+
*
|
|
4
|
+
* Read-only tools scoped to `ctx.tenantId` / `ctx.organizationId` that wrap
|
|
5
|
+
* the existing customers query engine + encryption helpers. Mutation tools
|
|
6
|
+
* are deferred to Step 5.13+ under the pending-action contract.
|
|
7
|
+
*
|
|
8
|
+
* Phase 3a of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
9
|
+
* `customers.list_people` is now an API-backed wrapper over
|
|
10
|
+
* `GET /api/customers/people`. The `companyId` AI input has no inclusion
|
|
11
|
+
* equivalent on the route (the route exposes `excludeLinkedCompanyId` only)
|
|
12
|
+
* so it is pre-resolved against `CustomerPersonProfile.company` and threaded
|
|
13
|
+
* through the route's `ids` filter.
|
|
14
|
+
*
|
|
15
|
+
* Phase 3c of the same spec migrates `customers.get_person` to a single
|
|
16
|
+
* in-process call to `GET /api/customers/people/<id>?include=...` (the
|
|
17
|
+
* documented aggregate detail route). Tool name, schema, requiredFeatures,
|
|
18
|
+
* and output shape are unchanged.
|
|
19
|
+
*/
|
|
20
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
21
|
+
import { z } from 'zod'
|
|
22
|
+
import { defineApiBackedAiTool } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/api-backed-tool'
|
|
23
|
+
import {
|
|
24
|
+
createAiApiOperationRunner,
|
|
25
|
+
type AiApiOperationRequest,
|
|
26
|
+
type AiToolExecutionContext,
|
|
27
|
+
} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'
|
|
28
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
29
|
+
import {
|
|
30
|
+
CustomerPersonProfile,
|
|
31
|
+
} from '../data/entities'
|
|
32
|
+
import { assertTenantScope, type CustomersAiToolDefinition, type CustomersToolContext } from './types'
|
|
33
|
+
|
|
34
|
+
const NIL_UUID = '00000000-0000-0000-0000-000000000000'
|
|
35
|
+
|
|
36
|
+
function resolveEm(ctx: CustomersToolContext | AiToolExecutionContext): EntityManager {
|
|
37
|
+
return ctx.container.resolve<EntityManager>('em')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function buildScope(ctx: CustomersToolContext | AiToolExecutionContext, tenantId: string) {
|
|
41
|
+
return {
|
|
42
|
+
tenantId,
|
|
43
|
+
organizationId: ctx.organizationId,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const listPeopleInput = z
|
|
48
|
+
.object({
|
|
49
|
+
q: z.string().trim().optional().describe('Optional search text matched against display name / email / phone. Omit or leave empty to list all.'),
|
|
50
|
+
limit: z.number().int().min(1).max(100).optional().describe('Maximum rows to return (default 50, max 100).'),
|
|
51
|
+
offset: z.number().int().min(0).optional().describe('Number of rows to skip (default 0).'),
|
|
52
|
+
tags: z.array(z.string().uuid()).optional().describe('Restrict to persons carrying at least one of these tag ids.'),
|
|
53
|
+
companyId: z.string().uuid().optional().describe('Restrict to persons linked to the given company entity.'),
|
|
54
|
+
})
|
|
55
|
+
.passthrough()
|
|
56
|
+
|
|
57
|
+
type ListPeopleInput = z.infer<typeof listPeopleInput>
|
|
58
|
+
|
|
59
|
+
type ListPeopleApiItem = {
|
|
60
|
+
id?: string
|
|
61
|
+
display_name?: string | null
|
|
62
|
+
displayName?: string | null
|
|
63
|
+
primary_email?: string | null
|
|
64
|
+
primaryEmail?: string | null
|
|
65
|
+
primary_phone?: string | null
|
|
66
|
+
primaryPhone?: string | null
|
|
67
|
+
status?: string | null
|
|
68
|
+
lifecycle_stage?: string | null
|
|
69
|
+
lifecycleStage?: string | null
|
|
70
|
+
source?: string | null
|
|
71
|
+
owner_user_id?: string | null
|
|
72
|
+
ownerUserId?: string | null
|
|
73
|
+
organization_id?: string | null
|
|
74
|
+
organizationId?: string | null
|
|
75
|
+
tenant_id?: string | null
|
|
76
|
+
tenantId?: string | null
|
|
77
|
+
created_at?: string | null
|
|
78
|
+
createdAt?: string | null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type ListPeopleApiResponse = {
|
|
82
|
+
items?: ListPeopleApiItem[]
|
|
83
|
+
total?: number
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type ListPeopleOutput = {
|
|
87
|
+
items: Array<Record<string, unknown>>
|
|
88
|
+
total: number
|
|
89
|
+
limit: number
|
|
90
|
+
offset: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const listPeopleTool = defineApiBackedAiTool<ListPeopleInput, ListPeopleApiResponse, ListPeopleOutput>({
|
|
94
|
+
name: 'customers.list_people',
|
|
95
|
+
displayName: 'List people',
|
|
96
|
+
description:
|
|
97
|
+
'Search / list people (CRM persons) for the caller tenant + organization. Returns { items, total, limit, offset }.',
|
|
98
|
+
inputSchema: listPeopleInput,
|
|
99
|
+
requiredFeatures: ['customers.people.view'],
|
|
100
|
+
toOperation: async (input, ctx) => {
|
|
101
|
+
const { tenantId } = assertTenantScope(ctx as unknown as CustomersToolContext)
|
|
102
|
+
const limit = input.limit ?? 50
|
|
103
|
+
const offset = input.offset ?? 0
|
|
104
|
+
const page = Math.floor(offset / limit) + 1
|
|
105
|
+
|
|
106
|
+
const query: Record<string, string | number | boolean | null | undefined> = {
|
|
107
|
+
page,
|
|
108
|
+
pageSize: limit,
|
|
109
|
+
}
|
|
110
|
+
if (input.q?.trim()) query.search = input.q.trim()
|
|
111
|
+
if (input.tags && input.tags.length > 0) query.tagIds = input.tags.join(',')
|
|
112
|
+
|
|
113
|
+
if (input.companyId) {
|
|
114
|
+
const em = resolveEm(ctx)
|
|
115
|
+
const profiles = await findWithDecryption<CustomerPersonProfile>(
|
|
116
|
+
em,
|
|
117
|
+
CustomerPersonProfile,
|
|
118
|
+
{ tenantId, company: input.companyId } as never,
|
|
119
|
+
undefined,
|
|
120
|
+
buildScope(ctx, tenantId),
|
|
121
|
+
)
|
|
122
|
+
const ids = profiles
|
|
123
|
+
.map((profile) => {
|
|
124
|
+
const entity = (profile as { entity?: unknown }).entity
|
|
125
|
+
if (!entity) return null
|
|
126
|
+
if (typeof entity === 'string') return entity
|
|
127
|
+
const candidate = (entity as { id?: unknown }).id
|
|
128
|
+
return typeof candidate === 'string' ? candidate : null
|
|
129
|
+
})
|
|
130
|
+
.filter((value): value is string => typeof value === 'string' && value.length > 0)
|
|
131
|
+
// Empty match — feed a non-existent uuid so the route returns
|
|
132
|
+
// { items: [], total: 0 } without us bypassing the API.
|
|
133
|
+
query.ids = ids.length ? ids.join(',') : NIL_UUID
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const operation: AiApiOperationRequest = {
|
|
137
|
+
method: 'GET',
|
|
138
|
+
path: '/customers/people',
|
|
139
|
+
query,
|
|
140
|
+
}
|
|
141
|
+
return operation
|
|
142
|
+
},
|
|
143
|
+
mapResponse: (response, input) => {
|
|
144
|
+
const limit = input.limit ?? 50
|
|
145
|
+
const offset = input.offset ?? 0
|
|
146
|
+
const data = (response.data ?? {}) as ListPeopleApiResponse
|
|
147
|
+
const rawItems: ListPeopleApiItem[] = Array.isArray(data.items) ? data.items : []
|
|
148
|
+
return {
|
|
149
|
+
items: rawItems.map((row) => {
|
|
150
|
+
const createdAtRaw = row.created_at ?? row.createdAt ?? null
|
|
151
|
+
const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null
|
|
152
|
+
return {
|
|
153
|
+
id: row.id,
|
|
154
|
+
displayName: row.display_name ?? row.displayName ?? null,
|
|
155
|
+
primaryEmail: row.primary_email ?? row.primaryEmail ?? null,
|
|
156
|
+
primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
|
|
157
|
+
status: row.status ?? null,
|
|
158
|
+
lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
|
|
159
|
+
source: row.source ?? null,
|
|
160
|
+
ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
|
|
161
|
+
organizationId: row.organization_id ?? row.organizationId ?? null,
|
|
162
|
+
tenantId: row.tenant_id ?? row.tenantId ?? null,
|
|
163
|
+
createdAt,
|
|
164
|
+
}
|
|
165
|
+
}),
|
|
166
|
+
total: typeof data.total === 'number' ? data.total : 0,
|
|
167
|
+
limit,
|
|
168
|
+
offset,
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
}) as unknown as CustomersAiToolDefinition
|
|
172
|
+
|
|
173
|
+
const getPersonInput = z.object({
|
|
174
|
+
personId: z.string().uuid().describe('Person entity id (UUID).'),
|
|
175
|
+
includeRelated: z
|
|
176
|
+
.boolean()
|
|
177
|
+
.optional()
|
|
178
|
+
.describe('When true, include notes, activities, deals, addresses, tasks, and tags (each capped at 100).'),
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
type GetPersonInput = z.infer<typeof getPersonInput>
|
|
182
|
+
|
|
183
|
+
type ApiPersonDetailRow = Record<string, unknown> | null | undefined
|
|
184
|
+
|
|
185
|
+
function toIso(value: unknown): string | null {
|
|
186
|
+
if (!value) return null
|
|
187
|
+
const dt = value instanceof Date ? value : new Date(String(value))
|
|
188
|
+
if (Number.isNaN(dt.getTime())) return null
|
|
189
|
+
return dt.toISOString()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const getPersonTool: CustomersAiToolDefinition = {
|
|
193
|
+
name: 'customers.get_person',
|
|
194
|
+
displayName: 'Get person',
|
|
195
|
+
description:
|
|
196
|
+
'Fetch a person customer record by id with profile fields and (optionally) notes, activities, deals, addresses, tasks, tags, and custom fields. Returns { found: false } when the record is outside tenant/org scope or missing.',
|
|
197
|
+
inputSchema: getPersonInput,
|
|
198
|
+
requiredFeatures: ['customers.people.view'],
|
|
199
|
+
tags: ['read', 'customers'],
|
|
200
|
+
handler: async (rawInput, ctx) => {
|
|
201
|
+
const { tenantId } = assertTenantScope(ctx)
|
|
202
|
+
const input: GetPersonInput = getPersonInput.parse(rawInput)
|
|
203
|
+
const includeRelated = !!input.includeRelated
|
|
204
|
+
|
|
205
|
+
const operation: AiApiOperationRequest = {
|
|
206
|
+
method: 'GET',
|
|
207
|
+
path: `/customers/people/${input.personId}`,
|
|
208
|
+
}
|
|
209
|
+
if (includeRelated) {
|
|
210
|
+
operation.query = { include: 'addresses,comments,activities,interactions,deals,todos' }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const runner = createAiApiOperationRunner(ctx as unknown as AiToolExecutionContext)
|
|
214
|
+
const response = await runner.run<Record<string, unknown>>(operation)
|
|
215
|
+
if (!response.success) {
|
|
216
|
+
if (response.statusCode === 404 || response.statusCode === 403) {
|
|
217
|
+
return { found: false as const, personId: input.personId }
|
|
218
|
+
}
|
|
219
|
+
throw new Error(response.error ?? `Failed to fetch person ${input.personId}`)
|
|
220
|
+
}
|
|
221
|
+
const data = (response.data ?? {}) as Record<string, unknown>
|
|
222
|
+
const personRow = (data.person ?? null) as ApiPersonDetailRow
|
|
223
|
+
if (!personRow) {
|
|
224
|
+
return { found: false as const, personId: input.personId }
|
|
225
|
+
}
|
|
226
|
+
const profileRow = (data.profile ?? null) as ApiPersonDetailRow
|
|
227
|
+
const customFields = (data.customFields ?? {}) as Record<string, unknown>
|
|
228
|
+
|
|
229
|
+
let related: Record<string, unknown> | null = null
|
|
230
|
+
if (includeRelated) {
|
|
231
|
+
const addresses = Array.isArray(data.addresses) ? (data.addresses as Array<Record<string, unknown>>) : []
|
|
232
|
+
const activities = Array.isArray(data.activities) ? (data.activities as Array<Record<string, unknown>>) : []
|
|
233
|
+
const notes = Array.isArray(data.comments) ? (data.comments as Array<Record<string, unknown>>) : []
|
|
234
|
+
const todos = Array.isArray(data.todos) ? (data.todos as Array<Record<string, unknown>>) : []
|
|
235
|
+
const interactions = Array.isArray(data.interactions) ? (data.interactions as Array<Record<string, unknown>>) : []
|
|
236
|
+
const tagsRows = Array.isArray(data.tags) ? (data.tags as Array<Record<string, unknown>>) : []
|
|
237
|
+
const dealsRows = Array.isArray(data.deals) ? (data.deals as Array<Record<string, unknown>>) : []
|
|
238
|
+
related = {
|
|
239
|
+
addresses: addresses.map((address) => ({
|
|
240
|
+
id: address.id,
|
|
241
|
+
name: address.name ?? null,
|
|
242
|
+
purpose: address.purpose ?? null,
|
|
243
|
+
addressLine1: address.addressLine1 ?? null,
|
|
244
|
+
addressLine2: address.addressLine2 ?? null,
|
|
245
|
+
city: address.city ?? null,
|
|
246
|
+
region: address.region ?? null,
|
|
247
|
+
postalCode: address.postalCode ?? null,
|
|
248
|
+
country: address.country ?? null,
|
|
249
|
+
isPrimary: !!address.isPrimary,
|
|
250
|
+
})),
|
|
251
|
+
activities: activities.map((activity) => ({
|
|
252
|
+
id: activity.id,
|
|
253
|
+
activityType: activity.activityType,
|
|
254
|
+
subject: activity.subject ?? null,
|
|
255
|
+
body: activity.body ?? null,
|
|
256
|
+
occurredAt: toIso(activity.occurredAt),
|
|
257
|
+
createdAt: toIso(activity.createdAt),
|
|
258
|
+
})),
|
|
259
|
+
notes: notes.map((comment) => ({
|
|
260
|
+
id: comment.id,
|
|
261
|
+
body: comment.body,
|
|
262
|
+
authorUserId: comment.authorUserId ?? null,
|
|
263
|
+
createdAt: toIso(comment.createdAt),
|
|
264
|
+
})),
|
|
265
|
+
tasks: todos.map((task) => ({
|
|
266
|
+
id: task.id,
|
|
267
|
+
todoId: task.todoId ?? task.id,
|
|
268
|
+
todoSource: task.todoSource ?? null,
|
|
269
|
+
createdAt: toIso(task.createdAt),
|
|
270
|
+
})),
|
|
271
|
+
interactions: interactions.map((interaction) => ({
|
|
272
|
+
id: interaction.id,
|
|
273
|
+
interactionType: interaction.interactionType,
|
|
274
|
+
title: interaction.title ?? null,
|
|
275
|
+
status: interaction.status,
|
|
276
|
+
scheduledAt: toIso(interaction.scheduledAt),
|
|
277
|
+
occurredAt: toIso(interaction.occurredAt),
|
|
278
|
+
})),
|
|
279
|
+
tags: tagsRows
|
|
280
|
+
.map((tag) => {
|
|
281
|
+
if (!tag || typeof tag !== 'object') return null
|
|
282
|
+
const id = typeof tag.id === 'string' ? tag.id : null
|
|
283
|
+
const label = typeof tag.label === 'string' ? tag.label : null
|
|
284
|
+
if (!id || !label) return null
|
|
285
|
+
const slug = typeof tag.slug === 'string' ? tag.slug : label
|
|
286
|
+
const color = typeof tag.color === 'string' ? tag.color : null
|
|
287
|
+
return { id, slug, label, color }
|
|
288
|
+
})
|
|
289
|
+
.filter(
|
|
290
|
+
(entry): entry is { id: string; slug: string; label: string; color: string | null } =>
|
|
291
|
+
entry !== null,
|
|
292
|
+
),
|
|
293
|
+
deals: dealsRows
|
|
294
|
+
.map((deal) => {
|
|
295
|
+
if (!deal || typeof deal !== 'object') return null
|
|
296
|
+
const id = typeof deal.id === 'string' ? deal.id : null
|
|
297
|
+
if (!id) return null
|
|
298
|
+
return {
|
|
299
|
+
id,
|
|
300
|
+
title: typeof deal.title === 'string' ? deal.title : '',
|
|
301
|
+
status: typeof deal.status === 'string' ? deal.status : null,
|
|
302
|
+
pipelineStageId:
|
|
303
|
+
typeof deal.pipelineStageId === 'string' ? deal.pipelineStageId : null,
|
|
304
|
+
valueAmount:
|
|
305
|
+
typeof deal.valueAmount === 'string'
|
|
306
|
+
? deal.valueAmount
|
|
307
|
+
: deal.valueAmount === null || deal.valueAmount === undefined
|
|
308
|
+
? null
|
|
309
|
+
: String(deal.valueAmount),
|
|
310
|
+
valueCurrency:
|
|
311
|
+
typeof deal.valueCurrency === 'string' ? deal.valueCurrency : null,
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
.filter(
|
|
315
|
+
(
|
|
316
|
+
value,
|
|
317
|
+
): value is {
|
|
318
|
+
id: string
|
|
319
|
+
title: string
|
|
320
|
+
status: string | null
|
|
321
|
+
pipelineStageId: string | null
|
|
322
|
+
valueAmount: string | null
|
|
323
|
+
valueCurrency: string | null
|
|
324
|
+
} => value !== null,
|
|
325
|
+
),
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
found: true as const,
|
|
331
|
+
person: {
|
|
332
|
+
id: personRow.id,
|
|
333
|
+
displayName: personRow.displayName ?? null,
|
|
334
|
+
description: personRow.description ?? null,
|
|
335
|
+
primaryEmail: personRow.primaryEmail ?? null,
|
|
336
|
+
primaryPhone: personRow.primaryPhone ?? null,
|
|
337
|
+
status: personRow.status ?? null,
|
|
338
|
+
lifecycleStage: personRow.lifecycleStage ?? null,
|
|
339
|
+
source: personRow.source ?? null,
|
|
340
|
+
ownerUserId: personRow.ownerUserId ?? null,
|
|
341
|
+
organizationId: personRow.organizationId ?? null,
|
|
342
|
+
tenantId: personRow.tenantId ?? null,
|
|
343
|
+
createdAt: toIso(personRow.createdAt),
|
|
344
|
+
updatedAt: toIso(personRow.updatedAt),
|
|
345
|
+
},
|
|
346
|
+
profile: profileRow
|
|
347
|
+
? {
|
|
348
|
+
id: profileRow.id,
|
|
349
|
+
firstName: profileRow.firstName ?? null,
|
|
350
|
+
lastName: profileRow.lastName ?? null,
|
|
351
|
+
preferredName: profileRow.preferredName ?? null,
|
|
352
|
+
jobTitle: profileRow.jobTitle ?? null,
|
|
353
|
+
department: profileRow.department ?? null,
|
|
354
|
+
seniority: profileRow.seniority ?? null,
|
|
355
|
+
timezone: profileRow.timezone ?? null,
|
|
356
|
+
linkedInUrl: profileRow.linkedInUrl ?? null,
|
|
357
|
+
twitterUrl: profileRow.twitterUrl ?? null,
|
|
358
|
+
companyEntityId: profileRow.companyEntityId ?? null,
|
|
359
|
+
}
|
|
360
|
+
: null,
|
|
361
|
+
customFields,
|
|
362
|
+
related,
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export const peopleAiTools: CustomersAiToolDefinition[] = [listPeopleTool, getPersonTool]
|
|
368
|
+
|
|
369
|
+
export default peopleAiTools
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `customers.get_settings` (Phase 1 WS-C, Step 3.9).
|
|
3
|
+
*
|
|
4
|
+
* Aggregates the four settings surfaces the spec calls out: pipelines,
|
|
5
|
+
* pipeline stages, dictionaries, and address-format settings. All reads are
|
|
6
|
+
* tenant + organization scoped through the existing encryption helpers.
|
|
7
|
+
*/
|
|
8
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
9
|
+
import { z } from 'zod'
|
|
10
|
+
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
11
|
+
import {
|
|
12
|
+
CustomerDictionaryEntry,
|
|
13
|
+
CustomerPipeline,
|
|
14
|
+
CustomerPipelineStage,
|
|
15
|
+
CustomerSettings,
|
|
16
|
+
} from '../data/entities'
|
|
17
|
+
import { assertTenantScope, type CustomersAiToolDefinition, type CustomersToolContext } from './types'
|
|
18
|
+
|
|
19
|
+
function resolveEm(ctx: CustomersToolContext): EntityManager {
|
|
20
|
+
return ctx.container.resolve<EntityManager>('em')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function buildScope(ctx: CustomersToolContext, tenantId: string) {
|
|
24
|
+
return { tenantId, organizationId: ctx.organizationId }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const getSettingsInput = z.object({}).passthrough()
|
|
28
|
+
|
|
29
|
+
const getSettingsTool: CustomersAiToolDefinition = {
|
|
30
|
+
name: 'customers.get_settings',
|
|
31
|
+
displayName: 'Get customers module settings',
|
|
32
|
+
description:
|
|
33
|
+
'Return the customers module settings for the caller scope: pipelines, pipeline stages, dictionaries (grouped by kind), and address format.',
|
|
34
|
+
inputSchema: getSettingsInput,
|
|
35
|
+
requiredFeatures: ['customers.settings.manage'],
|
|
36
|
+
tags: ['read', 'customers'],
|
|
37
|
+
handler: async (_rawInput, ctx) => {
|
|
38
|
+
const { tenantId } = assertTenantScope(ctx)
|
|
39
|
+
const em = resolveEm(ctx)
|
|
40
|
+
const where: Record<string, unknown> = { tenantId }
|
|
41
|
+
if (ctx.organizationId) where.organizationId = ctx.organizationId
|
|
42
|
+
const [pipelines, stages, dictionaryEntries, settings] = await Promise.all([
|
|
43
|
+
findWithDecryption<CustomerPipeline>(
|
|
44
|
+
em,
|
|
45
|
+
CustomerPipeline,
|
|
46
|
+
where as any,
|
|
47
|
+
{ orderBy: { createdAt: 'asc' } as any } as any,
|
|
48
|
+
buildScope(ctx, tenantId),
|
|
49
|
+
),
|
|
50
|
+
findWithDecryption<CustomerPipelineStage>(
|
|
51
|
+
em,
|
|
52
|
+
CustomerPipelineStage,
|
|
53
|
+
where as any,
|
|
54
|
+
{ orderBy: { pipelineId: 'asc', order: 'asc' } as any } as any,
|
|
55
|
+
buildScope(ctx, tenantId),
|
|
56
|
+
),
|
|
57
|
+
findWithDecryption<CustomerDictionaryEntry>(
|
|
58
|
+
em,
|
|
59
|
+
CustomerDictionaryEntry,
|
|
60
|
+
where as any,
|
|
61
|
+
{ orderBy: { kind: 'asc', label: 'asc' } as any } as any,
|
|
62
|
+
buildScope(ctx, tenantId),
|
|
63
|
+
),
|
|
64
|
+
ctx.organizationId
|
|
65
|
+
? findOneWithDecryption<CustomerSettings>(
|
|
66
|
+
em,
|
|
67
|
+
CustomerSettings,
|
|
68
|
+
{ tenantId, organizationId: ctx.organizationId } as any,
|
|
69
|
+
undefined,
|
|
70
|
+
buildScope(ctx, tenantId),
|
|
71
|
+
)
|
|
72
|
+
: null,
|
|
73
|
+
])
|
|
74
|
+
const pipelineRows = pipelines.filter((row) => row.tenantId === tenantId)
|
|
75
|
+
const stageRows = stages.filter((row) => row.tenantId === tenantId)
|
|
76
|
+
const dictionaryRows = dictionaryEntries.filter((row) => row.tenantId === tenantId)
|
|
77
|
+
const dictionaries: Record<string, Array<{
|
|
78
|
+
id: string
|
|
79
|
+
value: string
|
|
80
|
+
label: string
|
|
81
|
+
normalizedValue: string
|
|
82
|
+
color: string | null
|
|
83
|
+
icon: string | null
|
|
84
|
+
}>> = {}
|
|
85
|
+
for (const row of dictionaryRows) {
|
|
86
|
+
const bucket = dictionaries[row.kind] ?? (dictionaries[row.kind] = [])
|
|
87
|
+
bucket.push({
|
|
88
|
+
id: row.id,
|
|
89
|
+
value: row.value,
|
|
90
|
+
label: row.label,
|
|
91
|
+
normalizedValue: row.normalizedValue,
|
|
92
|
+
color: row.color ?? null,
|
|
93
|
+
icon: row.icon ?? null,
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
pipelines: pipelineRows.map((row) => ({
|
|
98
|
+
id: row.id,
|
|
99
|
+
name: row.name,
|
|
100
|
+
isDefault: !!row.isDefault,
|
|
101
|
+
organizationId: row.organizationId ?? null,
|
|
102
|
+
tenantId: row.tenantId ?? null,
|
|
103
|
+
createdAt: row.createdAt ? new Date(row.createdAt).toISOString() : null,
|
|
104
|
+
})),
|
|
105
|
+
pipelineStages: stageRows.map((row) => ({
|
|
106
|
+
id: row.id,
|
|
107
|
+
pipelineId: row.pipelineId,
|
|
108
|
+
label: row.label,
|
|
109
|
+
order: row.order,
|
|
110
|
+
organizationId: row.organizationId ?? null,
|
|
111
|
+
tenantId: row.tenantId ?? null,
|
|
112
|
+
})),
|
|
113
|
+
dictionaries,
|
|
114
|
+
addressFormat: settings?.addressFormat ?? 'line_first',
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const settingsAiTools: CustomersAiToolDefinition[] = [getSettingsTool]
|
|
120
|
+
|
|
121
|
+
export default settingsAiTools
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local AI tool shape for the customers module (Phase 1 WS-C, Step 3.9).
|
|
3
|
+
*
|
|
4
|
+
* The customers module declares its read-only tool pack directly as plain
|
|
5
|
+
* objects so jest can load it without pulling `@open-mercato/ai-assistant`
|
|
6
|
+
* into the core package's module graph. This mirrors the pattern used by
|
|
7
|
+
* `packages/core/src/modules/inbox_ops/ai-tools.ts`. The shape is a strict
|
|
8
|
+
* subset of `AiToolDefinition` from `@open-mercato/ai-assistant`; the
|
|
9
|
+
* generator walks every module root for a default/aiTools export with this
|
|
10
|
+
* shape.
|
|
11
|
+
*/
|
|
12
|
+
import type { z } from 'zod'
|
|
13
|
+
import type { AwilixContainer } from 'awilix'
|
|
14
|
+
|
|
15
|
+
export interface CustomersToolContext {
|
|
16
|
+
tenantId: string | null
|
|
17
|
+
organizationId: string | null
|
|
18
|
+
userId: string | null
|
|
19
|
+
container: AwilixContainer
|
|
20
|
+
userFeatures: string[]
|
|
21
|
+
isSuperAdmin: boolean
|
|
22
|
+
apiKeySecret?: string
|
|
23
|
+
sessionId?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Shape returned by `loadBeforeRecord` on a mutation tool. Mirrors
|
|
28
|
+
* `AiToolLoadBeforeSingleRecord` from `@open-mercato/ai-assistant/lib/types`;
|
|
29
|
+
* the customers module deliberately does not import that package so we keep a
|
|
30
|
+
* local prefix-compatible declaration (same rule as `CustomersAiToolDefinition`).
|
|
31
|
+
*/
|
|
32
|
+
export interface CustomersToolLoadBeforeSingleRecord {
|
|
33
|
+
recordId: string
|
|
34
|
+
entityType: string
|
|
35
|
+
recordVersion: string | null
|
|
36
|
+
before: Record<string, unknown>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CustomersAiToolDefinition<TInput = unknown, TOutput = unknown> {
|
|
40
|
+
name: string
|
|
41
|
+
displayName?: string
|
|
42
|
+
description: string
|
|
43
|
+
inputSchema: z.ZodType<TInput>
|
|
44
|
+
requiredFeatures?: string[]
|
|
45
|
+
tags?: string[]
|
|
46
|
+
isMutation?: boolean
|
|
47
|
+
/**
|
|
48
|
+
* Marks the mutation as destructive — gates the call through the
|
|
49
|
+
* approval card under `destructive-confirm-required` policy. Accepts a
|
|
50
|
+
* static boolean (whole tool destructive) or a predicate
|
|
51
|
+
* `(input) => boolean` evaluated at every call so a multi-operation
|
|
52
|
+
* tool (e.g. `manage_deal_comment` with create/update/delete) gates
|
|
53
|
+
* only the destructive branches. Default `false`. See
|
|
54
|
+
* `@open-mercato/ai-assistant` framework `AiToolDefinition.isDestructive`
|
|
55
|
+
* for the canonical definition; this surface mirrors the contract for
|
|
56
|
+
* type-safe authoring inside the customers module.
|
|
57
|
+
*/
|
|
58
|
+
isDestructive?: boolean | ((input: TInput) => boolean)
|
|
59
|
+
maxCallsPerTurn?: number
|
|
60
|
+
supportsAttachments?: boolean
|
|
61
|
+
handler: (input: TInput, context: CustomersToolContext) => Promise<TOutput>
|
|
62
|
+
loadBeforeRecord?: (
|
|
63
|
+
input: TInput,
|
|
64
|
+
context: CustomersToolContext,
|
|
65
|
+
) => Promise<CustomersToolLoadBeforeSingleRecord | null>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function assertTenantScope(ctx: CustomersToolContext): {
|
|
69
|
+
tenantId: string
|
|
70
|
+
organizationId: string | null
|
|
71
|
+
} {
|
|
72
|
+
if (!ctx.tenantId) {
|
|
73
|
+
throw new Error('Tenant context is required for customers.* tools')
|
|
74
|
+
}
|
|
75
|
+
return { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
76
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-root AI tool contribution for the customers module
|
|
3
|
+
* (Phase 1 WS-C, Step 3.9 — read-only Phase 1 surface).
|
|
4
|
+
*
|
|
5
|
+
* The generator walks every module for a top-level `ai-tools.ts` and takes
|
|
6
|
+
* the default/`aiTools` export as the contribution. This file aggregates the
|
|
7
|
+
* six customers packs (people, companies, deals, activities+tasks,
|
|
8
|
+
* addresses+tags, settings) so they all flow through the existing
|
|
9
|
+
* `ai-tools.generated.ts` pipeline without any generator changes.
|
|
10
|
+
*
|
|
11
|
+
* Mutation tools are deferred to Step 5.13+ under the pending-action contract;
|
|
12
|
+
* every tool here is read-only and enforces tenant + organization scoping via
|
|
13
|
+
* the existing encryption helpers. See
|
|
14
|
+
* `.ai/runs/2026-04-18-ai-framework-unification/step-3.9-checks.md` for the
|
|
15
|
+
* matrix of required features and decisions.
|
|
16
|
+
*/
|
|
17
|
+
import peopleAiTools from './ai-tools/people-pack'
|
|
18
|
+
import companiesAiTools from './ai-tools/companies-pack'
|
|
19
|
+
import dealsAiTools from './ai-tools/deals-pack'
|
|
20
|
+
import activitiesTasksAiTools from './ai-tools/activities-tasks-pack'
|
|
21
|
+
import addressesTagsAiTools from './ai-tools/addresses-tags-pack'
|
|
22
|
+
import settingsAiTools from './ai-tools/settings-pack'
|
|
23
|
+
import type { CustomersAiToolDefinition } from './ai-tools/types'
|
|
24
|
+
|
|
25
|
+
export const aiTools: CustomersAiToolDefinition[] = [
|
|
26
|
+
...peopleAiTools,
|
|
27
|
+
...companiesAiTools,
|
|
28
|
+
...dealsAiTools,
|
|
29
|
+
...activitiesTasksAiTools,
|
|
30
|
+
...addressesTagsAiTools,
|
|
31
|
+
...settingsAiTools,
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
export default aiTools
|
|
@@ -80,6 +80,31 @@
|
|
|
80
80
|
"customers.ai.actions.translate": "Translate",
|
|
81
81
|
"customers.ai.comingSoon": "Coming soon",
|
|
82
82
|
"customers.ai.prefix": "KI:",
|
|
83
|
+
"customers.ai_assistant.agents.account.label": "CRM Assistant",
|
|
84
|
+
"customers.ai_assistant.context.matchingPeople": "{count} contacts in view",
|
|
85
|
+
"customers.ai_assistant.context.selectedPeople": "{count} contacts selected",
|
|
86
|
+
"customers.ai_assistant.dealDetail.sheet.composerPlaceholder": "Frage nach diesem Deal, der Stage, Pipeline...",
|
|
87
|
+
"customers.ai_assistant.dealDetail.sheet.description": "Frage zu diesem Deal. Mit aktiviertem Mutations-Richtlinien-Override kann der Assistent auch eine Stage-Änderung vorschlagen, die Sie bestätigen, bevor etwas gespeichert wird.",
|
|
88
|
+
"customers.ai_assistant.dealDetail.sheet.title": "Kunden-AI-Assistent — Deal",
|
|
89
|
+
"customers.ai_assistant.dealDetail.trigger.ariaLabel": "KI-Assistent für diesen Deal öffnen",
|
|
90
|
+
"customers.ai_assistant.dealDetail.trigger.label": "KI fragen",
|
|
91
|
+
"customers.ai_assistant.dock.subtitle": "Customers",
|
|
92
|
+
"customers.ai_assistant.popover.heading": "AI assistants",
|
|
93
|
+
"customers.ai_assistant.sheet.composerPlaceholder": "Frage nach Personen, Firmen, Deals...",
|
|
94
|
+
"customers.ai_assistant.sheet.description": "Read-only-Assistent. Fragen zu Personen, Firmen, Deals und Aktivitäten im Umfang dieser Liste.",
|
|
95
|
+
"customers.ai_assistant.sheet.dock": "Dock to side",
|
|
96
|
+
"customers.ai_assistant.sheet.selectionPill": "Wirkt auf {count} Ausgewählte",
|
|
97
|
+
"customers.ai_assistant.sheet.title": "Kunden-AI-Assistent",
|
|
98
|
+
"customers.ai_assistant.sheet.welcomeTitle": "CRM Assistant",
|
|
99
|
+
"customers.ai_assistant.suggestions.activityOverview": "Activity overview",
|
|
100
|
+
"customers.ai_assistant.suggestions.findCompanies": "Find related companies",
|
|
101
|
+
"customers.ai_assistant.suggestions.findDeals": "Show deals for selected people",
|
|
102
|
+
"customers.ai_assistant.suggestions.recentDeals": "Show recent deals",
|
|
103
|
+
"customers.ai_assistant.suggestions.searchPeople": "Search for a contact",
|
|
104
|
+
"customers.ai_assistant.suggestions.summarizeSelected": "Summarize selected contacts",
|
|
105
|
+
"customers.ai_assistant.suggestions.topCompanies": "List top companies",
|
|
106
|
+
"customers.ai_assistant.trigger.ariaLabel": "KI-Assistent für Personen öffnen",
|
|
107
|
+
"customers.ai_assistant.trigger.label": "AI",
|
|
83
108
|
"customers.assignableStaff.loadError": "Teammitglieder konnten nicht geladen werden. Prüfen Sie Ihre Berechtigungen und versuchen Sie es erneut.",
|
|
84
109
|
"customers.audit.activities.create": "Aktivität erstellen",
|
|
85
110
|
"customers.audit.activities.delete": "Aktivität löschen",
|
|
@@ -80,6 +80,31 @@
|
|
|
80
80
|
"customers.ai.actions.translate": "Translate",
|
|
81
81
|
"customers.ai.comingSoon": "Coming soon",
|
|
82
82
|
"customers.ai.prefix": "AI:",
|
|
83
|
+
"customers.ai_assistant.agents.account.label": "CRM Assistant",
|
|
84
|
+
"customers.ai_assistant.context.matchingPeople": "{count} contacts in view",
|
|
85
|
+
"customers.ai_assistant.context.selectedPeople": "{count} contacts selected",
|
|
86
|
+
"customers.ai_assistant.dealDetail.sheet.composerPlaceholder": "Ask about this deal, the stage, pipeline...",
|
|
87
|
+
"customers.ai_assistant.dealDetail.sheet.description": "Ask about this deal. With the per-tenant mutation-policy override enabled, the assistant can also propose a stage change that you confirm before anything is saved.",
|
|
88
|
+
"customers.ai_assistant.dealDetail.sheet.title": "Customers AI assistant — deal",
|
|
89
|
+
"customers.ai_assistant.dealDetail.trigger.ariaLabel": "Open AI assistant for this deal",
|
|
90
|
+
"customers.ai_assistant.dealDetail.trigger.label": "Ask AI",
|
|
91
|
+
"customers.ai_assistant.dock.subtitle": "Customers",
|
|
92
|
+
"customers.ai_assistant.popover.heading": "AI assistants",
|
|
93
|
+
"customers.ai_assistant.sheet.composerPlaceholder": "Ask about people, companies, deals...",
|
|
94
|
+
"customers.ai_assistant.sheet.description": "Read-only assistant. Ask about people, companies, deals, and activities scoped to this list.",
|
|
95
|
+
"customers.ai_assistant.sheet.dock": "Dock to side",
|
|
96
|
+
"customers.ai_assistant.sheet.selectionPill": "Acting on {count} selected",
|
|
97
|
+
"customers.ai_assistant.sheet.title": "Customers AI assistant",
|
|
98
|
+
"customers.ai_assistant.sheet.welcomeTitle": "CRM Assistant",
|
|
99
|
+
"customers.ai_assistant.suggestions.activityOverview": "Activity overview",
|
|
100
|
+
"customers.ai_assistant.suggestions.findCompanies": "Find related companies",
|
|
101
|
+
"customers.ai_assistant.suggestions.findDeals": "Show deals for selected people",
|
|
102
|
+
"customers.ai_assistant.suggestions.recentDeals": "Show recent deals",
|
|
103
|
+
"customers.ai_assistant.suggestions.searchPeople": "Search for a contact",
|
|
104
|
+
"customers.ai_assistant.suggestions.summarizeSelected": "Summarize selected contacts",
|
|
105
|
+
"customers.ai_assistant.suggestions.topCompanies": "List top companies",
|
|
106
|
+
"customers.ai_assistant.trigger.ariaLabel": "Open AI assistant for people",
|
|
107
|
+
"customers.ai_assistant.trigger.label": "AI",
|
|
83
108
|
"customers.assignableStaff.loadError": "Unable to load team members. Check your permissions and try again.",
|
|
84
109
|
"customers.audit.activities.create": "Create activity",
|
|
85
110
|
"customers.audit.activities.delete": "Delete activity",
|