@wrcb/cb-common 1.0.876 → 1.0.878

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.
@@ -1,6 +1,6 @@
1
1
  import { Subjects } from '../subjects';
2
2
  export type AIProvider = 'openai' | 'cerebras';
3
- export type AIRequestType = 'chat' | 'tts' | 'transcribe' | 'image-generate' | 'image-edit';
3
+ export type AIRequestType = 'chat' | 'tts' | 'transcribe' | 'image-generate' | 'image-edit' | 'embeddings';
4
4
  export interface AIRequestMadeEvent {
5
5
  subject: Subjects.AIRequestMade;
6
6
  data: {
package/build/index.d.ts CHANGED
@@ -56,6 +56,7 @@ export * from './types/betOutcome';
56
56
  export * from './types/betStatus';
57
57
  export * from './types/predictionType';
58
58
  export * from './types/trabalhoHomeOffice';
59
+ export * from './types/crm';
59
60
  export * from './events/subjects';
60
61
  export * from './events/baseListener';
61
62
  export * from './events/basePublisher';
package/build/index.js CHANGED
@@ -83,6 +83,8 @@ __exportStar(require("./types/betStatus"), exports);
83
83
  __exportStar(require("./types/predictionType"), exports);
84
84
  // trabalho-home-office
85
85
  __exportStar(require("./types/trabalhoHomeOffice"), exports);
86
+ // crm — núcleo multi-tenant (pipeline configurável + agente de IA)
87
+ __exportStar(require("./types/crm"), exports);
86
88
  __exportStar(require("./events/subjects"), exports);
87
89
  __exportStar(require("./events/baseListener"), exports);
88
90
  __exportStar(require("./events/basePublisher"), exports);
@@ -1,3 +1,11 @@
1
+ export type WasabiAcl = 'public-read' | 'private';
2
+ export interface WasabiCredentials {
3
+ accessKeyId: string;
4
+ secretAccessKey: string;
5
+ bucketName: string;
6
+ region?: string;
7
+ endpoint?: string;
8
+ }
1
9
  export interface UploadToWasabiParams {
2
10
  fileBuffer: Buffer;
3
11
  originalName: string;
@@ -10,5 +18,47 @@ export interface UploadToWasabiParams {
10
18
  contentType?: string;
11
19
  extension?: string;
12
20
  cacheControl?: string;
21
+ acl?: WasabiAcl;
22
+ }
23
+ export interface WasabiObject {
24
+ key: string;
25
+ bucket: string;
26
+ url: string | null;
27
+ contentType: string;
28
+ sizeBytes: number;
29
+ }
30
+ /**
31
+ * Sobe um arquivo e devolve o objeto completo (chave, bucket, tamanho).
32
+ *
33
+ * Prefira esta função a `uploadToWasabi` em código novo: ela devolve a chave,
34
+ * que é o que permite assinar URL, apagar e aplicar retenção depois.
35
+ */
36
+ export declare function uploadFileToWasabi({ fileBuffer, originalName, accessKeyId, secretAccessKey, bucketName, region, endpoint, folder, contentType: contentTypeOverride, extension: extensionOverride, cacheControl, acl }: UploadToWasabiParams): Promise<WasabiObject>;
37
+ /**
38
+ * Assinatura histórica: sobe público e devolve só a URL.
39
+ *
40
+ * Mantida para não quebrar os serviços que já a usam. Em código novo use
41
+ * `uploadFileToWasabi`, e para dado pessoal use-a com `acl: 'private'`.
42
+ */
43
+ export declare function uploadToWasabi(params: UploadToWasabiParams): Promise<string>;
44
+ export interface SignedUrlParams extends WasabiCredentials {
45
+ key: string;
46
+ expiresInSeconds?: number;
47
+ downloadFileName?: string;
48
+ }
49
+ /**
50
+ * Gera uma URL temporária de leitura para um objeto privado.
51
+ *
52
+ * É assim que a foto de documento chega ao painel: o operador abre o lead, o
53
+ * backend assina na hora, o link morre em minutos.
54
+ */
55
+ export declare function getWasabiSignedUrl({ key, expiresInSeconds, downloadFileName, ...credentials }: SignedUrlParams): Promise<string>;
56
+ export interface DeleteFromWasabiParams extends WasabiCredentials {
57
+ key: string;
13
58
  }
14
- export declare function uploadToWasabi({ fileBuffer, originalName, accessKeyId, secretAccessKey, bucketName, region, endpoint, folder, contentType: contentTypeOverride, extension: extensionOverride, cacheControl }: UploadToWasabiParams): Promise<string>;
59
+ /**
60
+ * Apaga um objeto. Usado pelo expurgo por retenção (LGPD): o arquivo some, o
61
+ * registro no banco fica marcado como expurgado para a auditoria continuar
62
+ * rastreável.
63
+ */
64
+ export declare function deleteFromWasabi({ key, ...credentials }: DeleteFromWasabiParams): Promise<void>;
@@ -8,31 +8,113 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __rest = (this && this.__rest) || function (s, e) {
12
+ var t = {};
13
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
+ t[p] = s[p];
15
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
+ t[p[i]] = s[p[i]];
19
+ }
20
+ return t;
21
+ };
11
22
  var __importDefault = (this && this.__importDefault) || function (mod) {
12
23
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
24
  };
14
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.uploadFileToWasabi = uploadFileToWasabi;
15
27
  exports.uploadToWasabi = uploadToWasabi;
28
+ exports.getWasabiSignedUrl = getWasabiSignedUrl;
29
+ exports.deleteFromWasabi = deleteFromWasabi;
16
30
  // cb-common/server/services/wasabiUploader.ts
17
31
  const client_s3_1 = require("@aws-sdk/client-s3");
32
+ const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
18
33
  const uuid_1 = require("uuid");
19
34
  const mime_types_1 = __importDefault(require("mime-types"));
20
- function uploadToWasabi(_a) {
21
- return __awaiter(this, arguments, void 0, function* ({ fileBuffer, originalName, accessKeyId, secretAccessKey, bucketName, region = 'us-east-1', endpoint = 'https://s3.us-east-1.wasabisys.com', folder = 'uploads', contentType: contentTypeOverride, extension: extensionOverride, cacheControl }) {
35
+ const DEFAULT_REGION = 'us-east-1';
36
+ const DEFAULT_ENDPOINT = 'https://s3.us-east-1.wasabisys.com';
37
+ function buildClient({ accessKeyId, secretAccessKey, region = DEFAULT_REGION, endpoint = DEFAULT_ENDPOINT }) {
38
+ return new client_s3_1.S3Client({
39
+ region,
40
+ endpoint,
41
+ forcePathStyle: true,
42
+ credentials: { accessKeyId, secretAccessKey }
43
+ });
44
+ }
45
+ /**
46
+ * Sobe um arquivo e devolve o objeto completo (chave, bucket, tamanho).
47
+ *
48
+ * Prefira esta função a `uploadToWasabi` em código novo: ela devolve a chave,
49
+ * que é o que permite assinar URL, apagar e aplicar retenção depois.
50
+ */
51
+ function uploadFileToWasabi(_a) {
52
+ return __awaiter(this, arguments, void 0, function* ({ fileBuffer, originalName, accessKeyId, secretAccessKey, bucketName, region = DEFAULT_REGION, endpoint = DEFAULT_ENDPOINT, folder = 'uploads', contentType: contentTypeOverride, extension: extensionOverride, cacheControl, acl = 'public-read' }) {
22
53
  const extension = extensionOverride || originalName.split('.').pop() || 'jpg';
23
54
  const contentType = contentTypeOverride || mime_types_1.default.lookup(extension) || 'application/octet-stream';
24
- const filename = `${folder}/${(0, uuid_1.v4)()}.${extension}`;
25
- const wasabiClient = new client_s3_1.S3Client({
55
+ const key = `${folder}/${(0, uuid_1.v4)()}.${extension}`;
56
+ const wasabiClient = buildClient({
57
+ accessKeyId,
58
+ secretAccessKey,
59
+ bucketName,
26
60
  region,
27
- endpoint,
28
- forcePathStyle: true,
29
- credentials: {
30
- accessKeyId,
31
- secretAccessKey
32
- }
61
+ endpoint
33
62
  });
34
- const params = Object.assign({ Bucket: bucketName, Key: filename, Body: fileBuffer, ContentType: contentType, ACL: 'public-read' }, (cacheControl ? { CacheControl: cacheControl } : {}));
63
+ const params = Object.assign({ Bucket: bucketName, Key: key, Body: fileBuffer, ContentType: contentType, ACL: acl }, (cacheControl ? { CacheControl: cacheControl } : {}));
35
64
  yield wasabiClient.send(new client_s3_1.PutObjectCommand(params));
36
- return `${endpoint.replace(/\/$/, '')}/${bucketName}/${filename}`;
65
+ return {
66
+ key,
67
+ bucket: bucketName,
68
+ url: acl === 'public-read'
69
+ ? `${endpoint.replace(/\/$/, '')}/${bucketName}/${key}`
70
+ : null,
71
+ contentType,
72
+ sizeBytes: fileBuffer.length
73
+ };
74
+ });
75
+ }
76
+ /**
77
+ * Assinatura histórica: sobe público e devolve só a URL.
78
+ *
79
+ * Mantida para não quebrar os serviços que já a usam. Em código novo use
80
+ * `uploadFileToWasabi`, e para dado pessoal use-a com `acl: 'private'`.
81
+ */
82
+ function uploadToWasabi(params) {
83
+ return __awaiter(this, void 0, void 0, function* () {
84
+ const object = yield uploadFileToWasabi(params);
85
+ if (!object.url) {
86
+ throw new Error('uploadToWasabi: objeto privado não tem URL pública — use uploadFileToWasabi + getWasabiSignedUrl');
87
+ }
88
+ return object.url;
89
+ });
90
+ }
91
+ /**
92
+ * Gera uma URL temporária de leitura para um objeto privado.
93
+ *
94
+ * É assim que a foto de documento chega ao painel: o operador abre o lead, o
95
+ * backend assina na hora, o link morre em minutos.
96
+ */
97
+ function getWasabiSignedUrl(_a) {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ var { key, expiresInSeconds = 300, downloadFileName } = _a, credentials = __rest(_a, ["key", "expiresInSeconds", "downloadFileName"]);
100
+ const wasabiClient = buildClient(credentials);
101
+ const command = new client_s3_1.GetObjectCommand(Object.assign({ Bucket: credentials.bucketName, Key: key }, (downloadFileName
102
+ ? {
103
+ ResponseContentDisposition: `attachment; filename="${downloadFileName}"`
104
+ }
105
+ : {})));
106
+ return (0, s3_request_presigner_1.getSignedUrl)(wasabiClient, command, { expiresIn: expiresInSeconds });
107
+ });
108
+ }
109
+ /**
110
+ * Apaga um objeto. Usado pelo expurgo por retenção (LGPD): o arquivo some, o
111
+ * registro no banco fica marcado como expurgado para a auditoria continuar
112
+ * rastreável.
113
+ */
114
+ function deleteFromWasabi(_a) {
115
+ return __awaiter(this, void 0, void 0, function* () {
116
+ var { key } = _a, credentials = __rest(_a, ["key"]);
117
+ const wasabiClient = buildClient(credentials);
118
+ yield wasabiClient.send(new client_s3_1.DeleteObjectCommand({ Bucket: credentials.bucketName, Key: key }));
37
119
  });
38
120
  }
@@ -10,6 +10,14 @@ export interface ChatParams {
10
10
  responseFormat?: {
11
11
  type: 'json_object' | 'text';
12
12
  };
13
+ tools?: OpenAI.ChatCompletionTool[];
14
+ toolChoice?: OpenAI.ChatCompletionToolChoiceOption;
15
+ account?: string;
16
+ }
17
+ export interface EmbeddingsParams {
18
+ model: string;
19
+ input: string[];
20
+ dimensions?: number;
13
21
  account?: string;
14
22
  }
15
23
  export interface TTSParams {
@@ -44,10 +52,16 @@ export interface ImageEditParams {
44
52
  }
45
53
  export interface ChatResult {
46
54
  content: string;
55
+ toolCalls: OpenAI.ChatCompletionMessageToolCall[];
56
+ finishReason: string | null;
47
57
  promptTokens: number;
48
58
  completionTokens: number;
49
59
  totalTokens: number;
50
60
  }
61
+ export interface EmbeddingsResult {
62
+ vectors: number[][];
63
+ promptTokens: number;
64
+ }
51
65
  export declare class AIClient {
52
66
  private readonly natsClient;
53
67
  private readonly service;
@@ -60,6 +74,14 @@ export declare class AIClient {
60
74
  private get cerebras();
61
75
  private emit;
62
76
  chat(params: ChatParams): Promise<ChatResult>;
77
+ /**
78
+ * Gera embeddings para indexar a base de conhecimento do agente.
79
+ *
80
+ * Passa pelo mesmo caminho de auditoria das outras chamadas: indexar uma
81
+ * apostila grande custa dinheiro e precisa aparecer no relatório junto com
82
+ * o resto.
83
+ */
84
+ embeddings(params: EmbeddingsParams): Promise<EmbeddingsResult>;
63
85
  tts(params: TTSParams): Promise<Buffer>;
64
86
  transcribe(params: TranscribeParams): Promise<string>;
65
87
  generateImage(params: ImageGenerateParams): Promise<string>;
@@ -46,12 +46,42 @@ const COST_PER_1K = {
46
46
  'dall-e-3': { input: 0.040, output: 0 },
47
47
  'dall-e-2': { input: 0.020, output: 0 },
48
48
  'gpt-image-1': { input: 0.067, output: 0 },
49
+ // embeddings — cobrados só na entrada (USD por 1K tokens)
50
+ 'text-embedding-3-small': { input: 0.00002, output: 0 },
51
+ 'text-embedding-3-large': { input: 0.00013, output: 0 },
49
52
  };
50
53
  function calcCost(model, promptTokens, completionTokens) {
51
54
  var _a;
52
55
  const rates = (_a = COST_PER_1K[model]) !== null && _a !== void 0 ? _a : { input: 0, output: 0 };
53
56
  return (promptTokens / 1000) * rates.input + (completionTokens / 1000) * rates.output;
54
57
  }
58
+ // Resumo do prompt para o evento de auditoria.
59
+ //
60
+ // O conteúdo de uma mensagem pode ser string ou lista de partes (texto +
61
+ // imagem). Uma imagem vira só o rótulo `[image]`: além de não caber, um data
62
+ // URI de foto de documento no log de auditoria seria exatamente o vazamento
63
+ // que o armazenamento privado existe para evitar.
64
+ function summarizeMessages(messages) {
65
+ return messages
66
+ .map((message) => {
67
+ const { content } = message;
68
+ let text;
69
+ if (typeof content === 'string') {
70
+ text = content;
71
+ }
72
+ else if (Array.isArray(content)) {
73
+ text = content
74
+ .map((part) => (part.type === 'text' ? part.text : `[${part.type}]`))
75
+ .join(' ');
76
+ }
77
+ else {
78
+ text = '';
79
+ }
80
+ return `[${message.role}] ${text.slice(0, 200)}`;
81
+ })
82
+ .join(' | ')
83
+ .slice(0, 500);
84
+ }
55
85
  class AIClient {
56
86
  constructor(natsClient, service, openaiApiKey, cerebrasApiKey) {
57
87
  this.natsClient = natsClient;
@@ -99,21 +129,24 @@ class AIClient {
99
129
  }
100
130
  chat(params) {
101
131
  return __awaiter(this, void 0, void 0, function* () {
102
- var _a, _b, _c, _d, _e, _f, _g;
103
- const { provider, model, messages, maxTokens, temperature, responseFormat, account } = params;
132
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
133
+ const { provider, model, messages, maxTokens, temperature, responseFormat, tools, toolChoice, account } = params;
104
134
  const client = provider === 'cerebras' ? this.cerebras : this.openai;
105
135
  const requestedAt = new Date().toISOString();
106
136
  const t0 = Date.now();
107
- const promptSummary = messages.map(m => { var _a; return `[${m.role}] ${String((_a = m.content) !== null && _a !== void 0 ? _a : '').slice(0, 200)}`; }).join(' | ').slice(0, 500);
137
+ const promptSummary = summarizeMessages(messages);
108
138
  try {
109
- const response = yield client.chat.completions.create(Object.assign(Object.assign(Object.assign({ model,
110
- messages }, (maxTokens !== undefined && { max_tokens: maxTokens })), (temperature !== undefined && { temperature })), (responseFormat !== undefined && { response_format: responseFormat })));
139
+ const response = yield client.chat.completions.create(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ model,
140
+ messages }, (maxTokens !== undefined && { max_tokens: maxTokens })), (temperature !== undefined && { temperature })), (responseFormat !== undefined && { response_format: responseFormat })), (tools !== undefined && { tools })), (toolChoice !== undefined && { tool_choice: toolChoice })));
111
141
  const promptTokens = (_b = (_a = response.usage) === null || _a === void 0 ? void 0 : _a.prompt_tokens) !== null && _b !== void 0 ? _b : 0;
112
142
  const completionTokens = (_d = (_c = response.usage) === null || _c === void 0 ? void 0 : _c.completion_tokens) !== null && _d !== void 0 ? _d : 0;
113
143
  const costUSD = calcCost(model, promptTokens, completionTokens);
114
- yield this.emit({ provider, type: 'chat', model, promptTokens, completionTokens, costUSD, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, success: true });
144
+ yield this.emit({ provider, type: 'chat', model, promptTokens, completionTokens, costUSD, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, account, success: true });
145
+ const choice = response.choices[0];
115
146
  return {
116
- content: (_g = (_f = (_e = response.choices[0]) === null || _e === void 0 ? void 0 : _e.message) === null || _f === void 0 ? void 0 : _f.content) !== null && _g !== void 0 ? _g : '',
147
+ content: (_f = (_e = choice === null || choice === void 0 ? void 0 : choice.message) === null || _e === void 0 ? void 0 : _e.content) !== null && _f !== void 0 ? _f : '',
148
+ toolCalls: (_h = (_g = choice === null || choice === void 0 ? void 0 : choice.message) === null || _g === void 0 ? void 0 : _g.tool_calls) !== null && _h !== void 0 ? _h : [],
149
+ finishReason: (_j = choice === null || choice === void 0 ? void 0 : choice.finish_reason) !== null && _j !== void 0 ? _j : null,
117
150
  promptTokens,
118
151
  completionTokens,
119
152
  totalTokens: promptTokens + completionTokens,
@@ -121,7 +154,42 @@ class AIClient {
121
154
  }
122
155
  catch (err) {
123
156
  const errorMessage = err instanceof Error ? err.message : String(err);
124
- yield this.emit({ provider, type: 'chat', model, promptTokens: 0, completionTokens: 0, costUSD: 0, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, success: false, errorMessage });
157
+ yield this.emit({ provider, type: 'chat', model, promptTokens: 0, completionTokens: 0, costUSD: 0, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, account, success: false, errorMessage });
158
+ throw err;
159
+ }
160
+ });
161
+ }
162
+ /**
163
+ * Gera embeddings para indexar a base de conhecimento do agente.
164
+ *
165
+ * Passa pelo mesmo caminho de auditoria das outras chamadas: indexar uma
166
+ * apostila grande custa dinheiro e precisa aparecer no relatório junto com
167
+ * o resto.
168
+ */
169
+ embeddings(params) {
170
+ return __awaiter(this, void 0, void 0, function* () {
171
+ var _a, _b;
172
+ const { model, input, dimensions, account } = params;
173
+ const requestedAt = new Date().toISOString();
174
+ const t0 = Date.now();
175
+ const promptSummary = `${input.length} textos, ${input.reduce((total, text) => total + text.length, 0)} chars`;
176
+ try {
177
+ const response = yield this.openai.embeddings.create(Object.assign({ model,
178
+ input }, (dimensions !== undefined && { dimensions })));
179
+ const promptTokens = (_b = (_a = response.usage) === null || _a === void 0 ? void 0 : _a.prompt_tokens) !== null && _b !== void 0 ? _b : 0;
180
+ const costUSD = calcCost(model, promptTokens, 0);
181
+ yield this.emit({ provider: 'openai', type: 'embeddings', model, promptTokens, completionTokens: 0, costUSD, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, account, success: true });
182
+ // A API não garante a ordem da resposta; `index` é quem amarra cada
183
+ // vetor ao texto que o originou.
184
+ const vectors = new Array(input.length);
185
+ for (const item of response.data) {
186
+ vectors[item.index] = item.embedding;
187
+ }
188
+ return { vectors, promptTokens };
189
+ }
190
+ catch (err) {
191
+ const errorMessage = err instanceof Error ? err.message : String(err);
192
+ yield this.emit({ provider: 'openai', type: 'embeddings', model, promptTokens: 0, completionTokens: 0, costUSD: 0, durationMs: Date.now() - t0, requestedAt, prompt: promptSummary, account, success: false, errorMessage });
125
193
  throw err;
126
194
  }
127
195
  });
@@ -0,0 +1,198 @@
1
+ export declare enum CrmChannel {
2
+ WhatsAppCloud = "WhatsAppCloud",
3
+ WhatsAppEvolution = "WhatsAppEvolution"
4
+ }
5
+ export declare enum CrmActor {
6
+ Lead = "Lead",
7
+ Ai = "Ai",
8
+ Human = "Human",
9
+ System = "System"
10
+ }
11
+ export declare enum CrmDirection {
12
+ Inbound = "Inbound",
13
+ Outbound = "Outbound"
14
+ }
15
+ export declare enum CrmMessageType {
16
+ Text = "Text",
17
+ Image = "Image",
18
+ Audio = "Audio",
19
+ Video = "Video",
20
+ Document = "Document",
21
+ Template = "Template",
22
+ InternalNote = "InternalNote"
23
+ }
24
+ export declare enum CrmMessageStatus {
25
+ Queued = "Queued",
26
+ Sent = "Sent",
27
+ Delivered = "Delivered",
28
+ Read = "Read",
29
+ Failed = "Failed"
30
+ }
31
+ export declare enum CrmWindowState {
32
+ Open = "Open",
33
+ Closed = "Closed"
34
+ }
35
+ export interface CrmStage {
36
+ key: string;
37
+ label: string;
38
+ order: number;
39
+ color: string;
40
+ aiDescription: string;
41
+ aiEnabled: boolean;
42
+ isTerminal: boolean;
43
+ isWon: boolean;
44
+ isLost: boolean;
45
+ slaMinutes: number | null;
46
+ }
47
+ export declare enum CrmStageChangedBy {
48
+ Ai = "Ai",
49
+ Human = "Human",
50
+ Automation = "Automation"
51
+ }
52
+ export declare enum CrmLeadSource {
53
+ Form = "Form",
54
+ InboundMessage = "InboundMessage",
55
+ Manual = "Manual",
56
+ Import = "Import"
57
+ }
58
+ export type CrmFieldValue = string | number | boolean | null | string[];
59
+ export type CrmCustomFields = Record<string, CrmFieldValue>;
60
+ export declare enum CrmControl {
61
+ Ai = "Ai",
62
+ Human = "Human"
63
+ }
64
+ export declare enum CrmHandoffReason {
65
+ LeadRequested = "LeadRequested",
66
+ OutOfScope = "OutOfScope",
67
+ Sensitive = "Sensitive",
68
+ DocumentBlocked = "DocumentBlocked",
69
+ ManualTakeover = "ManualTakeover",
70
+ SlaBreach = "SlaBreach"
71
+ }
72
+ export declare enum CrmAgentTool {
73
+ SearchKnowledge = "SearchKnowledge",
74
+ MoveStage = "MoveStage",
75
+ ScheduleFollowUp = "ScheduleFollowUp",
76
+ RecordField = "RecordField",
77
+ RequestDocument = "RequestDocument",
78
+ RequestHuman = "RequestHuman"
79
+ }
80
+ export interface CrmAgentToolCall {
81
+ tool: CrmAgentTool;
82
+ arguments: Record<string, CrmFieldValue>;
83
+ result: string;
84
+ succeeded: boolean;
85
+ }
86
+ export interface CrmGuardrails {
87
+ forbiddenTopics: string[];
88
+ blockUnsourcedRates: boolean;
89
+ handoffOnSensitiveData: boolean;
90
+ mandatoryDisclaimer: string | null;
91
+ maxAiTurnsBeforeHandoff: number | null;
92
+ }
93
+ export declare enum CrmKnowledgeStatus {
94
+ Uploaded = "Uploaded",
95
+ Processing = "Processing",
96
+ Ready = "Ready",
97
+ Failed = "Failed"
98
+ }
99
+ export declare enum CrmKnowledgeFormat {
100
+ Pdf = "Pdf",
101
+ Docx = "Docx",
102
+ Txt = "Txt",
103
+ Markdown = "Markdown",
104
+ Inline = "Inline"
105
+ }
106
+ export interface CrmDocumentRequirement {
107
+ key: string;
108
+ label: string;
109
+ aiInstruction: string;
110
+ validationHints: string[];
111
+ required: boolean;
112
+ acceptedMimeTypes: string[];
113
+ maxFiles: number;
114
+ maxRejections: number;
115
+ }
116
+ export declare enum CrmLeadDocumentStatus {
117
+ Received = "Received",
118
+ Validating = "Validating",
119
+ Approved = "Approved",
120
+ Rejected = "Rejected",
121
+ PendingReview = "PendingReview",
122
+ Purged = "Purged"
123
+ }
124
+ export declare enum CrmDocumentRejectionReason {
125
+ Unreadable = "Unreadable",
126
+ WrongDocument = "WrongDocument",
127
+ Incomplete = "Incomplete",
128
+ Expired = "Expired",
129
+ SuspectedTampering = "SuspectedTampering"
130
+ }
131
+ export interface CrmCadenceStep {
132
+ afterHours: number;
133
+ stageKeys: string[];
134
+ template: string | null;
135
+ aiInstruction: string | null;
136
+ whatsappTemplateName: string | null;
137
+ }
138
+ export interface CrmFirstContact {
139
+ enabled: boolean;
140
+ afterMinutes: number;
141
+ text: string | null;
142
+ followUpText: string | null;
143
+ whatsappTemplateName: string | null;
144
+ }
145
+ export declare enum CrmCadenceStopReason {
146
+ LeadReplied = "LeadReplied",
147
+ StepsExhausted = "StepsExhausted",
148
+ StageTerminal = "StageTerminal",
149
+ OptedOut = "OptedOut",
150
+ HumanTookOver = "HumanTookOver"
151
+ }
152
+ export interface CrmSchedule {
153
+ timezone: string;
154
+ activeDays: number[];
155
+ windowStart: number;
156
+ windowEnd: number;
157
+ holidays: string[];
158
+ }
159
+ export interface CrmTypingProfile {
160
+ minSeconds: number;
161
+ maxSeconds: number;
162
+ perCharMs: number;
163
+ jitterPercent: number;
164
+ splitLongMessages: boolean;
165
+ splitThreshold: number;
166
+ }
167
+ export interface CrmLeadIngestPayload {
168
+ externalId: string;
169
+ sourceSystem: string;
170
+ sourceTable: string;
171
+ name: string;
172
+ phone: string;
173
+ email: string | null;
174
+ customFields: CrmCustomFields;
175
+ utmSource: string | null;
176
+ utmMedium: string | null;
177
+ utmCampaign: string | null;
178
+ utmTerm: string | null;
179
+ utmContent: string | null;
180
+ trackingCode: string | null;
181
+ occurredAt: string;
182
+ }
183
+ export declare enum CrmEventType {
184
+ LeadCreated = "LeadCreated",
185
+ StageChanged = "StageChanged",
186
+ MessageSent = "MessageSent",
187
+ MessageReceived = "MessageReceived",
188
+ ControlChanged = "ControlChanged",
189
+ DocumentReceived = "DocumentReceived",
190
+ DocumentReviewed = "DocumentReviewed",
191
+ CadenceStepFired = "CadenceStepFired",
192
+ CadenceStopped = "CadenceStopped",
193
+ OptedOut = "OptedOut",
194
+ AgentToolCalled = "AgentToolCalled",
195
+ GuardrailTriggered = "GuardrailTriggered",
196
+ DailyLimitReached = "DailyLimitReached",
197
+ FirstContactSent = "FirstContactSent"
198
+ }
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ // crm — núcleo de CRM multi-tenant com agente de IA
3
+ //
4
+ // Convenção: prefixo Crm em tudo, para não colidir com os tipos do
5
+ // leadAutomator (LeadStatus, LeadMessageDirection...), que continuam existindo
6
+ // enquanto o gn-social-media não for portado para este núcleo.
7
+ //
8
+ // Regra de ouro deste módulo: o que varia por tenant é DADO, não código.
9
+ // Por isso estágio de funil, passo de cadência, requisito de documento e
10
+ // ferramenta do agente são interfaces/configuração — nunca enum fechado.
11
+ // Enum aqui é só para o que é genuinamente fixo (canal, direção, ator).
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.CrmEventType = exports.CrmCadenceStopReason = exports.CrmDocumentRejectionReason = exports.CrmLeadDocumentStatus = exports.CrmKnowledgeFormat = exports.CrmKnowledgeStatus = exports.CrmAgentTool = exports.CrmHandoffReason = exports.CrmControl = exports.CrmLeadSource = exports.CrmStageChangedBy = exports.CrmWindowState = exports.CrmMessageStatus = exports.CrmMessageType = exports.CrmDirection = exports.CrmActor = exports.CrmChannel = void 0;
14
+ // ============================================
15
+ // CANAL E MENSAGEM
16
+ // ============================================
17
+ var CrmChannel;
18
+ (function (CrmChannel) {
19
+ // WhatsApp Business Platform (Cloud API da Meta). Canal padrão para lead
20
+ // inbound: número não corre risco de banimento e tem status de entrega.
21
+ CrmChannel["WhatsAppCloud"] = "WhatsAppCloud";
22
+ // Evolution API / Baileys. Não-oficial. Mantido para tenant que faz
23
+ // prospecção fria ou não tem conta Meta Business verificada.
24
+ CrmChannel["WhatsAppEvolution"] = "WhatsAppEvolution";
25
+ })(CrmChannel || (exports.CrmChannel = CrmChannel = {}));
26
+ var CrmActor;
27
+ (function (CrmActor) {
28
+ CrmActor["Lead"] = "Lead";
29
+ CrmActor["Ai"] = "Ai";
30
+ CrmActor["Human"] = "Human";
31
+ // Mensagem gerada por regra (cadência, template de reengajamento) sem
32
+ // passar pelo modelo.
33
+ CrmActor["System"] = "System";
34
+ })(CrmActor || (exports.CrmActor = CrmActor = {}));
35
+ var CrmDirection;
36
+ (function (CrmDirection) {
37
+ CrmDirection["Inbound"] = "Inbound";
38
+ CrmDirection["Outbound"] = "Outbound";
39
+ })(CrmDirection || (exports.CrmDirection = CrmDirection = {}));
40
+ var CrmMessageType;
41
+ (function (CrmMessageType) {
42
+ CrmMessageType["Text"] = "Text";
43
+ CrmMessageType["Image"] = "Image";
44
+ CrmMessageType["Audio"] = "Audio";
45
+ CrmMessageType["Video"] = "Video";
46
+ CrmMessageType["Document"] = "Document";
47
+ // Template aprovado (HSM). Único tipo que pode ser enviado fora da
48
+ // janela de 24h do WhatsApp.
49
+ CrmMessageType["Template"] = "Template";
50
+ // Nota interna do operador — não é enviada ao lead, aparece só no painel.
51
+ CrmMessageType["InternalNote"] = "InternalNote";
52
+ })(CrmMessageType || (exports.CrmMessageType = CrmMessageType = {}));
53
+ var CrmMessageStatus;
54
+ (function (CrmMessageStatus) {
55
+ CrmMessageStatus["Queued"] = "Queued";
56
+ CrmMessageStatus["Sent"] = "Sent";
57
+ CrmMessageStatus["Delivered"] = "Delivered";
58
+ CrmMessageStatus["Read"] = "Read";
59
+ CrmMessageStatus["Failed"] = "Failed";
60
+ })(CrmMessageStatus || (exports.CrmMessageStatus = CrmMessageStatus = {}));
61
+ // Estado da janela de atendimento de 24h da Meta. Fora dela, só template.
62
+ var CrmWindowState;
63
+ (function (CrmWindowState) {
64
+ CrmWindowState["Open"] = "Open";
65
+ CrmWindowState["Closed"] = "Closed";
66
+ })(CrmWindowState || (exports.CrmWindowState = CrmWindowState = {}));
67
+ var CrmStageChangedBy;
68
+ (function (CrmStageChangedBy) {
69
+ CrmStageChangedBy["Ai"] = "Ai";
70
+ CrmStageChangedBy["Human"] = "Human";
71
+ // Regra do sistema (ex.: checklist de documentos completou).
72
+ CrmStageChangedBy["Automation"] = "Automation";
73
+ })(CrmStageChangedBy || (exports.CrmStageChangedBy = CrmStageChangedBy = {}));
74
+ // ============================================
75
+ // LEAD
76
+ // ============================================
77
+ var CrmLeadSource;
78
+ (function (CrmLeadSource) {
79
+ // Formulário do site, via rota de ingestão. O payload carrega o id da
80
+ // linha no Postgres para casar os dois lados.
81
+ CrmLeadSource["Form"] = "Form";
82
+ // Lead que iniciou a conversa no WhatsApp sem ter passado pelo formulário
83
+ // (ou cujo telefone não casou com nenhum registro).
84
+ CrmLeadSource["InboundMessage"] = "InboundMessage";
85
+ // Criado à mão no painel.
86
+ CrmLeadSource["Manual"] = "Manual";
87
+ // Carga em lote (CSV, prospecção).
88
+ CrmLeadSource["Import"] = "Import";
89
+ })(CrmLeadSource || (exports.CrmLeadSource = CrmLeadSource = {}));
90
+ // Quem está conduzindo a conversa agora.
91
+ var CrmControl;
92
+ (function (CrmControl) {
93
+ CrmControl["Ai"] = "Ai";
94
+ CrmControl["Human"] = "Human";
95
+ })(CrmControl || (exports.CrmControl = CrmControl = {}));
96
+ // Motivo pelo qual o agente pediu um humano. Vira etiqueta no kanban, então
97
+ // é fechado de propósito: lista aberta viraria texto livre inútil para filtro.
98
+ var CrmHandoffReason;
99
+ (function (CrmHandoffReason) {
100
+ // O lead pediu explicitamente falar com uma pessoa.
101
+ CrmHandoffReason["LeadRequested"] = "LeadRequested";
102
+ // A pergunta saiu do que a base de conhecimento cobre.
103
+ CrmHandoffReason["OutOfScope"] = "OutOfScope";
104
+ // Assunto sensível (reclamação, ameaça de processo, dado bancário).
105
+ CrmHandoffReason["Sensitive"] = "Sensitive";
106
+ // Documento reprovado mais vezes que o limite configurado.
107
+ CrmHandoffReason["DocumentBlocked"] = "DocumentBlocked";
108
+ // Um operador assumiu pelo painel, sem a IA ter pedido.
109
+ CrmHandoffReason["ManualTakeover"] = "ManualTakeover";
110
+ // Estourou o SLA do estágio.
111
+ CrmHandoffReason["SlaBreach"] = "SlaBreach";
112
+ })(CrmHandoffReason || (exports.CrmHandoffReason = CrmHandoffReason = {}));
113
+ // ============================================
114
+ // AGENTE
115
+ // ============================================
116
+ // Ferramentas que o agente pode receber. Habilitadas por tenant: um CRM de
117
+ // empréstimo usa coleta de documento, um de agendamento não.
118
+ var CrmAgentTool;
119
+ (function (CrmAgentTool) {
120
+ // Busca vetorial na base de conhecimento do tenant (apostila, tabela de
121
+ // produtos, script de objeções).
122
+ CrmAgentTool["SearchKnowledge"] = "SearchKnowledge";
123
+ // Move o lead de estágio, informando o motivo.
124
+ CrmAgentTool["MoveStage"] = "MoveStage";
125
+ // Agenda um novo contato para uma data/hora.
126
+ CrmAgentTool["ScheduleFollowUp"] = "ScheduleFollowUp";
127
+ // Grava um dado coletado na conversa em customFields.
128
+ CrmAgentTool["RecordField"] = "RecordField";
129
+ // Pede um documento da checklist ao lead.
130
+ CrmAgentTool["RequestDocument"] = "RequestDocument";
131
+ // Devolve a conversa para um humano.
132
+ CrmAgentTool["RequestHuman"] = "RequestHuman";
133
+ })(CrmAgentTool || (exports.CrmAgentTool = CrmAgentTool = {}));
134
+ // ============================================
135
+ // BASE DE CONHECIMENTO (RAG)
136
+ // ============================================
137
+ var CrmKnowledgeStatus;
138
+ (function (CrmKnowledgeStatus) {
139
+ // Arquivo no Wasabi, ainda não processado.
140
+ CrmKnowledgeStatus["Uploaded"] = "Uploaded";
141
+ // Extraindo texto / gerando embeddings.
142
+ CrmKnowledgeStatus["Processing"] = "Processing";
143
+ // Chunks indexados, disponível para busca.
144
+ CrmKnowledgeStatus["Ready"] = "Ready";
145
+ // Falhou (PDF protegido, formato não suportado, arquivo corrompido).
146
+ CrmKnowledgeStatus["Failed"] = "Failed";
147
+ })(CrmKnowledgeStatus || (exports.CrmKnowledgeStatus = CrmKnowledgeStatus = {}));
148
+ var CrmKnowledgeFormat;
149
+ (function (CrmKnowledgeFormat) {
150
+ CrmKnowledgeFormat["Pdf"] = "Pdf";
151
+ CrmKnowledgeFormat["Docx"] = "Docx";
152
+ CrmKnowledgeFormat["Txt"] = "Txt";
153
+ CrmKnowledgeFormat["Markdown"] = "Markdown";
154
+ // Texto colado direto no painel, sem arquivo.
155
+ CrmKnowledgeFormat["Inline"] = "Inline";
156
+ })(CrmKnowledgeFormat || (exports.CrmKnowledgeFormat = CrmKnowledgeFormat = {}));
157
+ var CrmLeadDocumentStatus;
158
+ (function (CrmLeadDocumentStatus) {
159
+ // Recebido e armazenado, ainda não analisado.
160
+ CrmLeadDocumentStatus["Received"] = "Received";
161
+ // Em análise pelo modelo de visão.
162
+ CrmLeadDocumentStatus["Validating"] = "Validating";
163
+ // Aprovado automaticamente ou por um operador.
164
+ CrmLeadDocumentStatus["Approved"] = "Approved";
165
+ // Ilegível, documento errado ou fora de validade.
166
+ CrmLeadDocumentStatus["Rejected"] = "Rejected";
167
+ // Aguardando conferência humana (o modelo não teve confiança suficiente).
168
+ CrmLeadDocumentStatus["PendingReview"] = "PendingReview";
169
+ // Removido por retenção/pedido do titular (LGPD). O registro permanece
170
+ // para auditoria; o arquivo no Wasabi já não existe.
171
+ CrmLeadDocumentStatus["Purged"] = "Purged";
172
+ })(CrmLeadDocumentStatus || (exports.CrmLeadDocumentStatus = CrmLeadDocumentStatus = {}));
173
+ var CrmDocumentRejectionReason;
174
+ (function (CrmDocumentRejectionReason) {
175
+ CrmDocumentRejectionReason["Unreadable"] = "Unreadable";
176
+ CrmDocumentRejectionReason["WrongDocument"] = "WrongDocument";
177
+ CrmDocumentRejectionReason["Incomplete"] = "Incomplete";
178
+ CrmDocumentRejectionReason["Expired"] = "Expired";
179
+ CrmDocumentRejectionReason["SuspectedTampering"] = "SuspectedTampering";
180
+ })(CrmDocumentRejectionReason || (exports.CrmDocumentRejectionReason = CrmDocumentRejectionReason = {}));
181
+ var CrmCadenceStopReason;
182
+ (function (CrmCadenceStopReason) {
183
+ CrmCadenceStopReason["LeadReplied"] = "LeadReplied";
184
+ CrmCadenceStopReason["StepsExhausted"] = "StepsExhausted";
185
+ CrmCadenceStopReason["StageTerminal"] = "StageTerminal";
186
+ CrmCadenceStopReason["OptedOut"] = "OptedOut";
187
+ CrmCadenceStopReason["HumanTookOver"] = "HumanTookOver";
188
+ })(CrmCadenceStopReason || (exports.CrmCadenceStopReason = CrmCadenceStopReason = {}));
189
+ // ============================================
190
+ // EVENTOS DE AUDITORIA
191
+ // ============================================
192
+ // Tudo que muda um lead vira um CrmEvent. É o que permite responder "por que
193
+ // este lead foi para 'perdido' às 3h da manhã" — e é exigência prática de
194
+ // LGPD quando há decisão automatizada sobre crédito.
195
+ var CrmEventType;
196
+ (function (CrmEventType) {
197
+ CrmEventType["LeadCreated"] = "LeadCreated";
198
+ CrmEventType["StageChanged"] = "StageChanged";
199
+ CrmEventType["MessageSent"] = "MessageSent";
200
+ CrmEventType["MessageReceived"] = "MessageReceived";
201
+ CrmEventType["ControlChanged"] = "ControlChanged";
202
+ CrmEventType["DocumentReceived"] = "DocumentReceived";
203
+ CrmEventType["DocumentReviewed"] = "DocumentReviewed";
204
+ CrmEventType["CadenceStepFired"] = "CadenceStepFired";
205
+ CrmEventType["CadenceStopped"] = "CadenceStopped";
206
+ CrmEventType["OptedOut"] = "OptedOut";
207
+ CrmEventType["AgentToolCalled"] = "AgentToolCalled";
208
+ CrmEventType["GuardrailTriggered"] = "GuardrailTriggered";
209
+ // Envio ativo barrado pelo limite diário do canal (proteção de warmup).
210
+ CrmEventType["DailyLimitReached"] = "DailyLimitReached";
211
+ CrmEventType["FirstContactSent"] = "FirstContactSent";
212
+ })(CrmEventType || (exports.CrmEventType = CrmEventType = {}));
@@ -195,3 +195,14 @@ export interface ThoSettingsData {
195
195
  stripeMode: ThoPaymentMode;
196
196
  pixMode: ThoPaymentMode;
197
197
  }
198
+ export declare enum ThoAutopostNetwork {
199
+ Facebook = "Facebook",
200
+ Telegram = "Telegram"
201
+ }
202
+ export interface ThoAutopostNextData {
203
+ jobListing: JobListingDTO;
204
+ network: ThoAutopostNetwork;
205
+ caption: string;
206
+ imageUrl: string;
207
+ jobUrl: string;
208
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ThoPaymentMode = exports.ThoRatingCriterion = exports.ThoOrderSettlementReason = exports.ThoDisputeResolution = exports.ThoOrderLineKind = exports.ThoOrderStatus = exports.ConversationContextType = exports.ApplicationStatus = exports.DeliveryUnit = exports.ServicePriceType = exports.ThoReportStatus = exports.ThoReportType = exports.ThoFavoriteType = exports.JobApplyMode = exports.JobListingSort = exports.RemoteScope = exports.JobSeniority = exports.JobListingStatus = exports.JobListingSource = exports.JobListingOrigin = exports.JobPostingStatus = exports.WorkRegime = exports.JobContractType = void 0;
3
+ exports.ThoAutopostNetwork = exports.ThoPaymentMode = exports.ThoRatingCriterion = exports.ThoOrderSettlementReason = exports.ThoDisputeResolution = exports.ThoOrderLineKind = exports.ThoOrderStatus = exports.ConversationContextType = exports.ApplicationStatus = exports.DeliveryUnit = exports.ServicePriceType = exports.ThoReportStatus = exports.ThoReportType = exports.ThoFavoriteType = exports.JobApplyMode = exports.JobListingSort = exports.RemoteScope = exports.JobSeniority = exports.JobListingStatus = exports.JobListingSource = exports.JobListingOrigin = exports.JobPostingStatus = exports.WorkRegime = exports.JobContractType = void 0;
4
4
  var JobContractType;
5
5
  (function (JobContractType) {
6
6
  JobContractType["Clt"] = "Clt";
@@ -226,3 +226,23 @@ var ThoPaymentMode;
226
226
  ThoPaymentMode["Live"] = "live";
227
227
  ThoPaymentMode["Test"] = "test";
228
228
  })(ThoPaymentMode || (exports.ThoPaymentMode = ThoPaymentMode = {}));
229
+ // ---------------------------------------------------------------------------
230
+ // Autopost — divulgação automática de vagas nas redes sociais.
231
+ //
232
+ // Quem publica não conhece o banco: pede uma vaga ao backend, que devolve a
233
+ // mais recente ainda não divulgada NAQUELA rede, já com a legenda pronta e a
234
+ // URL da arte, e a marca como publicada na mesma chamada. Cada rede tem a sua
235
+ // fila: a mesma vaga sai no Facebook e no Telegram, cada uma no seu momento.
236
+ //
237
+ // Dois consumidores hoje, mesma rota:
238
+ // Facebook — agente dirigindo o Chrome fora do cluster, porque grupo do
239
+ // Facebook não tem API.
240
+ // Telegram — workflow do n8n, que tem API e não precisa de navegador.
241
+ // ---------------------------------------------------------------------------
242
+ // Redes atendidas pelo autopost. Instagram e TikTok entram aqui quando a
243
+ // página tiver audiência.
244
+ var ThoAutopostNetwork;
245
+ (function (ThoAutopostNetwork) {
246
+ ThoAutopostNetwork["Facebook"] = "Facebook";
247
+ ThoAutopostNetwork["Telegram"] = "Telegram";
248
+ })(ThoAutopostNetwork || (exports.ThoAutopostNetwork = ThoAutopostNetwork = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrcb/cb-common",
3
- "version": "1.0.876",
3
+ "version": "1.0.878",
4
4
  "description": "Common resources between services",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",