@chatman-media/conversation-engine 1.7.0 → 1.9.0

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 +1 @@
1
- {"version":3,"file":"conversation-resolver.d.ts","sourceRoot":"","sources":["../src/conversation-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAqBzE;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,iBAAiB,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC;IAAE,YAAY,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAa/D"}
1
+ {"version":3,"file":"conversation-resolver.d.ts","sourceRoot":"","sources":["../src/conversation-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AA0BzE;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,iBAAiB,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC;IAAE,YAAY,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAa/D"}
package/dist/index.js CHANGED
@@ -25,6 +25,8 @@ function channelKindToSource(kind) {
25
25
  return "bot";
26
26
  case "telegram_userbot":
27
27
  return "userbot";
28
+ case "self_play":
29
+ return "self_play";
28
30
  default:
29
31
  return "bot";
30
32
  }
@@ -1018,7 +1020,8 @@ class NotificationService {
1018
1020
  human_takeover: "\uD83C\uDD98",
1019
1021
  document_uploaded: "\uD83D\uDCF8",
1020
1022
  high_value_deal: "\uD83D\uDC8E",
1021
- lead_stale: "⏰"
1023
+ lead_stale: "⏰",
1024
+ operator_confirm_needed: "✋"
1022
1025
  };
1023
1026
  return map[type] ?? "\uD83D\uDD14";
1024
1027
  }
@@ -1029,7 +1032,8 @@ class NotificationService {
1029
1032
  human_takeover: "Нужна помощь оператора",
1030
1033
  document_uploaded: "Загружен документ",
1031
1034
  high_value_deal: "Крупная сделка",
1032
- lead_stale: "Лид завис"
1035
+ lead_stale: "Лид завис",
1036
+ operator_confirm_needed: "Нужно подтверждение оператора"
1033
1037
  };
1034
1038
  return map[type] ?? "Уведомление";
1035
1039
  }
@@ -2331,6 +2335,70 @@ async function generateReplyAndEnqueue(deps) {
2331
2335
  });
2332
2336
  return { outboundEnqueued: count };
2333
2337
  }
2338
+ // src/lead-advance.ts
2339
+ import { funnels, leads as leadsTable2, stageDefinitions } from "@chatman-media/storage";
2340
+ import { and as and14, asc, eq as eq14 } from "drizzle-orm";
2341
+ var SALES_STAGE_TO_PHASE = {
2342
+ qualify: "qualify",
2343
+ pitch: "offer",
2344
+ objection: "clear",
2345
+ close: "fulfill"
2346
+ };
2347
+ async function ensureAndAdvanceLeadByPhase(opts) {
2348
+ const { db, tenantId, contactId, salesStage, nowEpoch } = opts;
2349
+ const stages = await db.select({
2350
+ id: stageDefinitions.id,
2351
+ slug: stageDefinitions.slug,
2352
+ phase: stageDefinitions.phase,
2353
+ kind: stageDefinitions.kind,
2354
+ position: stageDefinitions.position
2355
+ }).from(stageDefinitions).innerJoin(funnels, eq14(stageDefinitions.funnelId, funnels.id)).where(and14(eq14(funnels.tenantId, tenantId), eq14(funnels.isActive, true))).orderBy(asc(stageDefinitions.position));
2356
+ if (stages.length === 0)
2357
+ return null;
2358
+ const initial = stages.find((s) => s.kind === "intake") ?? stages[0];
2359
+ const [existing] = await db.select({ id: leadsTable2.id, state: leadsTable2.state, stageDefinitionId: leadsTable2.stageDefinitionId }).from(leadsTable2).where(and14(eq14(leadsTable2.tenantId, tenantId), eq14(leadsTable2.userId, contactId))).limit(1);
2360
+ let leadId;
2361
+ let curStageId;
2362
+ let curSlug;
2363
+ let created = false;
2364
+ if (existing) {
2365
+ leadId = existing.id;
2366
+ curStageId = existing.stageDefinitionId ?? null;
2367
+ curSlug = existing.state;
2368
+ } else {
2369
+ const [row] = await db.insert(leadsTable2).values({
2370
+ tenantId,
2371
+ userId: contactId,
2372
+ state: initial.slug,
2373
+ stageDefinitionId: initial.id,
2374
+ createdAt: nowEpoch,
2375
+ updatedAt: nowEpoch
2376
+ }).returning({ id: leadsTable2.id });
2377
+ if (!row)
2378
+ return null;
2379
+ leadId = row.id;
2380
+ curStageId = initial.id;
2381
+ curSlug = initial.slug;
2382
+ created = true;
2383
+ }
2384
+ const targetPhase = SALES_STAGE_TO_PHASE[salesStage];
2385
+ if (!targetPhase)
2386
+ return { leadId, stageSlug: curSlug, created, advanced: false };
2387
+ const curStage = curStageId != null ? stages.find((s) => s.id === curStageId) : undefined;
2388
+ const curPos = curStage?.position ?? -1;
2389
+ let target = salesStage === "close" && curStage?.phase === "fulfill" ? stages.find((s) => s.kind === "terminal_won") : undefined;
2390
+ if (!target) {
2391
+ target = stages.find((s) => s.phase === targetPhase && s.kind !== "terminal_won" && s.kind !== "terminal_lost");
2392
+ }
2393
+ if (!target)
2394
+ return { leadId, stageSlug: curSlug, created, advanced: false };
2395
+ if (target.position > curPos) {
2396
+ await db.update(leadsTable2).set({ stageDefinitionId: target.id, state: target.slug, updatedAt: nowEpoch }).where(and14(eq14(leadsTable2.id, leadId), eq14(leadsTable2.tenantId, tenantId)));
2397
+ return { leadId, stageSlug: target.slug, created, advanced: true };
2398
+ }
2399
+ return { leadId, stageSlug: curSlug, created, advanced: false };
2400
+ }
2401
+
2334
2402
  // src/process-inbound.ts
2335
2403
  function inboundText(inbound) {
2336
2404
  const textParts = [];
@@ -2516,6 +2584,33 @@ async function processInbound(inbound, deps) {
2516
2584
  to: newStage
2517
2585
  });
2518
2586
  }
2587
+ if (newStage && newStage !== "opener" && deps.leads && deps.db) {
2588
+ try {
2589
+ const res = await ensureAndAdvanceLeadByPhase({
2590
+ db: deps.db,
2591
+ tenantId: deps.tenant.tenantId,
2592
+ contactId: contact.id,
2593
+ salesStage: newStage,
2594
+ nowEpoch: now
2595
+ });
2596
+ if (res && (res.created || res.advanced)) {
2597
+ deps.sink?.log?.("info", res.created ? "lead auto-created" : "lead advanced", {
2598
+ tenantId: deps.tenant.tenantId,
2599
+ conversationId: conversation.id,
2600
+ contactId: contact.id,
2601
+ leadId: res.leadId,
2602
+ stage: res.stageSlug,
2603
+ salesStage: newStage
2604
+ });
2605
+ }
2606
+ } catch (err) {
2607
+ deps.sink?.log?.("warn", "lead auto-advance failed", {
2608
+ tenantId: deps.tenant.tenantId,
2609
+ conversationId: conversation.id,
2610
+ error: err instanceof Error ? err.message : String(err)
2611
+ });
2612
+ }
2613
+ }
2519
2614
  } catch (err) {
2520
2615
  deps.sink?.log?.("warn", "stage classifier failed", {
2521
2616
  tenantId: deps.tenant.tenantId,
@@ -2607,6 +2702,7 @@ async function processInbound(inbound, deps) {
2607
2702
  };
2608
2703
  }
2609
2704
  // src/reply-strategy/llm-reply.ts
2705
+ import { DEFAULT_MAX_TOOL_CYCLES, runToolLoop } from "@chatman-media/kb";
2610
2706
  var BASE_SYSTEM_PROMPT = "Ты — операционный бот платформы lead-engine. Отвечай кратко, " + "уважительно, по делу. Никогда не выдумывай факты которых нет в " + "контексте — лучше скажи «уточню у партнёра» и поставь сообщение в очередь оператора.";
2611
2707
  function messagesToChatHistory2(history) {
2612
2708
  const out = [];
@@ -2640,14 +2736,47 @@ class LlmReplyStrategy {
2640
2736
  const messages = this.messagesRepoFor(input.tenant.tenantId);
2641
2737
  const history = await messages.recent(input.conversationId, this.opts.historyLimit ?? 20);
2642
2738
  const historyMessages = messagesToChatHistory2(history);
2643
- const systemPrompt = [BASE_SYSTEM_PROMPT, this.opts.template.systemPromptFragment].filter(Boolean).join(`
2644
-
2645
- `);
2646
2739
  const chat = this.opts.resolveChat(input.tenant.tenantId);
2647
- const reply = await chat.complete([{ role: "system", content: systemPrompt }, ...historyMessages], {
2740
+ const llmOpts = {
2648
2741
  temperature: this.opts.temperature ?? 0.7,
2649
2742
  numPredict: this.opts.maxOutputTokens ?? 600
2650
- });
2743
+ };
2744
+ let tools = [];
2745
+ if (this.opts.resolveTools) {
2746
+ try {
2747
+ tools = await this.opts.resolveTools({
2748
+ tenantId: input.tenant.tenantId,
2749
+ conversationId: input.conversationId
2750
+ });
2751
+ } catch {
2752
+ tools = [];
2753
+ }
2754
+ }
2755
+ const toolsActive = tools.length > 0 && typeof chat.completeWithTools === "function";
2756
+ const systemPrompt = [
2757
+ BASE_SYSTEM_PROMPT,
2758
+ toolsActive ? "Если для ответа есть подходящий инструмент (например, расчёт курса обмена) — " + "ОБЯЗАТЕЛЬНО вызови его и дай конкретные числа. Не отсылай к оператору, если можешь " + "ответить инструментом." : "",
2759
+ this.opts.template.systemPromptFragment
2760
+ ].filter(Boolean).join(`
2761
+
2762
+ `);
2763
+ const msgs = [
2764
+ { role: "system", content: systemPrompt },
2765
+ ...historyMessages
2766
+ ];
2767
+ let reply;
2768
+ if (toolsActive) {
2769
+ const loop = await runToolLoop({
2770
+ chat,
2771
+ messages: msgs,
2772
+ tools,
2773
+ llmOpts,
2774
+ maxCycles: DEFAULT_MAX_TOOL_CYCLES
2775
+ });
2776
+ reply = loop.content ?? await chat.complete(msgs, llmOpts);
2777
+ } else {
2778
+ reply = await chat.complete(msgs, llmOpts);
2779
+ }
2651
2780
  if (reply.trim().length === 0)
2652
2781
  return null;
2653
2782
  return [
@@ -0,0 +1,26 @@
1
+ import type { Db } from "./dal/types.ts";
2
+ /**
3
+ * Find-or-create лид для (tenant, contact) на активной воронке тенанта и
4
+ * продвигает его вперёд до фазы, соответствующей текущей sales-стадии диалога.
5
+ * Двигает ТОЛЬКО вперёд (по position); назад не откатывает.
6
+ *
7
+ * Обновляет lead.state + lead.stageDefinitionId синхронно (как admin-leads /
8
+ * field-extractor). Работает поверх DB-воронки (stage_definitions), а не
9
+ * template.funnelStages — это «живая» позиция в кастомной воронке тенанта.
10
+ *
11
+ * Возвращает null если у тенанта нет активной воронки со стадиями.
12
+ */
13
+ export declare function ensureAndAdvanceLeadByPhase(opts: {
14
+ db: Db;
15
+ tenantId: number;
16
+ contactId: number;
17
+ /** sales-стадия из stage-classifier (opener|qualify|pitch|objection|close). */
18
+ salesStage: string;
19
+ nowEpoch: number;
20
+ }): Promise<{
21
+ leadId: number;
22
+ stageSlug: string;
23
+ created: boolean;
24
+ advanced: boolean;
25
+ } | null>;
26
+ //# sourceMappingURL=lead-advance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lead-advance.d.ts","sourceRoot":"","sources":["../src/lead-advance.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAoBzC;;;;;;;;;;GAUG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE;IACtD,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,CAmF7F"}
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAElF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,qBAAa,mBAAmB;IAI5B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAX5B,OAAO,CAAC,MAAM,CAA+B;gBAG1B,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;IAC/B;;;;OAIG;IACc,QAAQ,CAAC,EAAE,aAAa,YAAA;IAOrC,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmD/C,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;YAcjE,WAAW;IAazB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO;IAa3E,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM;IAc9D,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM;IAsB/C,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,SAAS;CAUlB"}
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAElF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,qBAAa,mBAAmB;IAI5B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAX5B,OAAO,CAAC,MAAM,CAA+B;gBAG1B,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;IAC/B;;;;OAIG;IACc,QAAQ,CAAC,EAAE,aAAa,YAAA;IAOrC,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmD/C,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;YAcjE,WAAW;IAazB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO;IAa3E,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM;IAc9D,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM;IAsB/C,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,SAAS;CAUlB"}
@@ -1,6 +1,6 @@
1
1
  import type { Inbound, OutboundEnvelope } from "@chatman-media/channel-core";
2
2
  import type { VerticalTemplate } from "@chatman-media/verticals";
3
- import type { ChannelIdentitiesRepo, ContactsRepo, ConversationsRepo, MessagesRepo, OutboundQueueRepo } from "./dal/index.ts";
3
+ import type { ChannelIdentitiesRepo, ContactsRepo, ConversationsRepo, LeadsRepo, MessagesRepo, OutboundQueueRepo } from "./dal/index.ts";
4
4
  import { type MemoryExtractor } from "./memory-extractor.ts";
5
5
  import { type StageClassifier } from "./stage-classifier.ts";
6
6
  import type { ITranscriber } from "./transcriber.ts";
@@ -37,6 +37,13 @@ export interface ProcessInboundDeps {
37
37
  conversations: ConversationsRepo;
38
38
  messages: MessagesRepo;
39
39
  outbound: OutboundQueueRepo;
40
+ /**
41
+ * Опциональный LeadsRepo. Если задан вместе с `template` и `stageClassifier`,
42
+ * pipeline авто-создаёт Lead, как только цель/интент клиента определён
43
+ * (stage classifier вернул стадию ≠ "opener"). Без него лиды не создаются
44
+ * из диалога (legacy-поведение).
45
+ */
46
+ leads?: LeadsRepo;
40
47
  /** Стратегия ответа. null = pipeline сохраняет inbound и не отвечает. */
41
48
  reply?: ReplyStrategy | null;
42
49
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"process-inbound.d.ts","sourceRoot":"","sources":["../src/process-inbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,KAAK,EACV,qBAAqB,EACrB,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,eAAe,EAAuB,MAAM,uBAAuB,CAAC;AAElF,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,cAAc,EACd,KAAK,KAAK,EACV,KAAK,YAAY,EACjB,KAAK,oBAAoB,EAEzB,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE;QACb,MAAM,EAAE,aAAa,CAAC;QACtB,OAAO,EAAE,cAAc,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,qBAAqB,CAAC;IAClC,aAAa,EAAE,iBAAiB,CAAC;IACjC,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,yEAAyE;IACzE,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,EAAE,CAAC,EAAE,OAAO,gBAAgB,EAAE,EAAE,CAAC;IACjC;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,yFAAyF;IACzF,aAAa,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,OAAO,6BAA6B,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC;CAClI;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,oBAAoB,CAAC,CA6T/B"}
1
+ {"version":3,"file":"process-inbound.d.ts","sourceRoot":"","sources":["../src/process-inbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,KAAK,EACV,qBAAqB,EACrB,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,KAAK,eAAe,EAAuB,MAAM,uBAAuB,CAAC;AAElF,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,cAAc,EACd,KAAK,KAAK,EACV,KAAK,YAAY,EACjB,KAAK,oBAAoB,EAEzB,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE;QACb,MAAM,EAAE,aAAa,CAAC;QACtB,OAAO,EAAE,cAAc,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,qBAAqB,CAAC;IAClC,aAAa,EAAE,iBAAiB,CAAC;IACjC,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,yEAAyE;IACzE,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,EAAE,CAAC,EAAE,OAAO,gBAAgB,EAAE,EAAE,CAAC;IACjC;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,yFAAyF;IACzF,aAAa,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,OAAO,6BAA6B,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC;CAClI;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,oBAAoB,CAAC,CA4V/B"}
@@ -1,4 +1,5 @@
1
1
  import type { OutboundEnvelope } from "@chatman-media/channel-core";
2
+ import { type AnyRagTool } from "@chatman-media/kb";
2
3
  import type { ChatClient } from "@chatman-media/llm-router";
3
4
  import type { VerticalTemplate } from "@chatman-media/verticals";
4
5
  import type { MessagesRepo } from "../dal/messages.ts";
@@ -46,6 +47,16 @@ export interface LlmReplyStrategyOpts {
46
47
  tenantId: number;
47
48
  contactId: number;
48
49
  }) => Promise<boolean>;
50
+ /**
51
+ * Опциональный резолвер agentic-инструментов (напр. расчёт курса обмена).
52
+ * Если задан и вернул непустой список, а ChatClient умеет completeWithTools —
53
+ * strategy прогоняет tool-loop, чтобы бот мог дать конкретный ответ
54
+ * (курс/сумму) даже без RAG/эмбеддингов, а не уходить в «уточню у партнёра».
55
+ */
56
+ resolveTools?: (input: {
57
+ tenantId: number;
58
+ conversationId: number;
59
+ }) => Promise<AnyRagTool[]> | AnyRagTool[];
49
60
  }
50
61
  export declare class LlmReplyStrategy implements ReplyStrategy {
51
62
  private readonly opts;
@@ -1 +1 @@
1
- {"version":3,"file":"llm-reply.d.ts","sourceRoot":"","sources":["../../src/reply-strategy/llm-reply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,CAAC;IAC9C,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACzF;AAkBD,qBAAa,gBAAiB,YAAW,aAAa;IAElD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,eAAe;gBADf,IAAI,EAAE,oBAAoB,EAC1B,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,YAAY;IAGhE,QAAQ,CAAC,KAAK,EAAE;QACpB,MAAM,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QAC7B,OAAO,EAAE;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/B,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE;YAAE,cAAc,EAAE,MAAM,CAAA;SAAE,CAAC;QACpC,eAAe,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC;CAqCvC"}
1
+ {"version":3,"file":"llm-reply.d.ts","sourceRoot":"","sources":["../../src/reply-strategy/llm-reply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,KAAK,UAAU,EAAwC,MAAM,mBAAmB,CAAC;AAC1F,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,CAAC;IAC9C,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxF;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC;CAC5C;AAkBD,qBAAa,gBAAiB,YAAW,aAAa;IAElD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,eAAe;gBADf,IAAI,EAAE,oBAAoB,EAC1B,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,YAAY;IAGhE,QAAQ,CAAC,KAAK,EAAE;QACpB,MAAM,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QAC7B,OAAO,EAAE;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/B,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE;YAAE,cAAc,EAAE,MAAM,CAAA;SAAE,CAAC;QACpC,eAAe,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC;CA8EvC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatman-media/conversation-engine",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Channel-agnostic pipeline обработки inbound сообщений: contact-resolve → conversation lookup → mode routing → AI-reply / queued / human → outbound. Сердце data plane.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -37,8 +37,8 @@
37
37
  "@chatman-media/channel-core": "1.2.0",
38
38
  "@chatman-media/channel-telegram": "1.1.0",
39
39
  "@chatman-media/llm-router": "1.1.1",
40
- "@chatman-media/kb": "1.3.1",
41
- "@chatman-media/storage": "1.6.0",
40
+ "@chatman-media/kb": "1.5.0",
41
+ "@chatman-media/storage": "1.9.0",
42
42
  "@chatman-media/verticals": "1.2.0"
43
43
  },
44
44
  "peerDependencies": {