@chatman-media/conversation-engine 1.2.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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -0
  3. package/dist/compact-conversation.d.ts +16 -0
  4. package/dist/compact-conversation.d.ts.map +1 -0
  5. package/dist/compact-conversation.test.d.ts +2 -0
  6. package/dist/compact-conversation.test.d.ts.map +1 -0
  7. package/dist/contact-resolver.d.ts +16 -0
  8. package/dist/contact-resolver.d.ts.map +1 -0
  9. package/dist/conversation-resolver.d.ts +17 -0
  10. package/dist/conversation-resolver.d.ts.map +1 -0
  11. package/dist/dal/channel-identities.d.ts +26 -0
  12. package/dist/dal/channel-identities.d.ts.map +1 -0
  13. package/dist/dal/contacts.d.ts +30 -0
  14. package/dist/dal/contacts.d.ts.map +1 -0
  15. package/dist/dal/conversations.d.ts +67 -0
  16. package/dist/dal/conversations.d.ts.map +1 -0
  17. package/dist/dal/experiments.d.ts +47 -0
  18. package/dist/dal/experiments.d.ts.map +1 -0
  19. package/dist/dal/index.d.ts +14 -0
  20. package/dist/dal/index.d.ts.map +1 -0
  21. package/dist/dal/kb-store.d.ts +58 -0
  22. package/dist/dal/kb-store.d.ts.map +1 -0
  23. package/dist/dal/kb-suggestions.d.ts +26 -0
  24. package/dist/dal/kb-suggestions.d.ts.map +1 -0
  25. package/dist/dal/leads.d.ts +38 -0
  26. package/dist/dal/leads.d.ts.map +1 -0
  27. package/dist/dal/messages.d.ts +48 -0
  28. package/dist/dal/messages.d.ts.map +1 -0
  29. package/dist/dal/notifications.d.ts +32 -0
  30. package/dist/dal/notifications.d.ts.map +1 -0
  31. package/dist/dal/outbound.d.ts +70 -0
  32. package/dist/dal/outbound.d.ts.map +1 -0
  33. package/dist/dal/skill-outcomes.d.ts +58 -0
  34. package/dist/dal/skill-outcomes.d.ts.map +1 -0
  35. package/dist/dal/styles.d.ts +44 -0
  36. package/dist/dal/styles.d.ts.map +1 -0
  37. package/dist/dal/types.d.ts +27 -0
  38. package/dist/dal/types.d.ts.map +1 -0
  39. package/dist/dispatch-reply.d.ts +49 -0
  40. package/dist/dispatch-reply.d.ts.map +1 -0
  41. package/dist/dispatch-reply.test.d.ts +2 -0
  42. package/dist/dispatch-reply.test.d.ts.map +1 -0
  43. package/dist/experiment-router.d.ts +15 -0
  44. package/dist/experiment-router.d.ts.map +1 -0
  45. package/dist/experiment-router.test.d.ts +2 -0
  46. package/dist/experiment-router.test.d.ts.map +1 -0
  47. package/dist/extract-fields.test.d.ts +2 -0
  48. package/dist/extract-fields.test.d.ts.map +1 -0
  49. package/dist/funnel-machine.d.ts +43 -0
  50. package/dist/funnel-machine.d.ts.map +1 -0
  51. package/dist/funnel-machine.test.d.ts +2 -0
  52. package/dist/funnel-machine.test.d.ts.map +1 -0
  53. package/dist/index.d.ts +21 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +2024 -0
  56. package/dist/lead-lifecycle.d.ts +46 -0
  57. package/dist/lead-lifecycle.d.ts.map +1 -0
  58. package/dist/lead-lifecycle.test.d.ts +2 -0
  59. package/dist/lead-lifecycle.test.d.ts.map +1 -0
  60. package/dist/memory-extractor.d.ts +62 -0
  61. package/dist/memory-extractor.d.ts.map +1 -0
  62. package/dist/memory-extractor.test.d.ts +2 -0
  63. package/dist/memory-extractor.test.d.ts.map +1 -0
  64. package/dist/notifications.d.ts +32 -0
  65. package/dist/notifications.d.ts.map +1 -0
  66. package/dist/notifications.test.d.ts +2 -0
  67. package/dist/notifications.test.d.ts.map +1 -0
  68. package/dist/operator-bot-handler.d.ts +13 -0
  69. package/dist/operator-bot-handler.d.ts.map +1 -0
  70. package/dist/operator-bot-handler.test.d.ts +2 -0
  71. package/dist/operator-bot-handler.test.d.ts.map +1 -0
  72. package/dist/outbound-dispatch.d.ts +17 -0
  73. package/dist/outbound-dispatch.d.ts.map +1 -0
  74. package/dist/process-inbound.d.ts +126 -0
  75. package/dist/process-inbound.d.ts.map +1 -0
  76. package/dist/process-inbound.test.d.ts +2 -0
  77. package/dist/process-inbound.test.d.ts.map +1 -0
  78. package/dist/reply-strategy/index.d.ts +3 -0
  79. package/dist/reply-strategy/index.d.ts.map +1 -0
  80. package/dist/reply-strategy/llm-reply.d.ts +69 -0
  81. package/dist/reply-strategy/llm-reply.d.ts.map +1 -0
  82. package/dist/reply-strategy/llm-reply.test.d.ts +2 -0
  83. package/dist/reply-strategy/llm-reply.test.d.ts.map +1 -0
  84. package/dist/reply-strategy/rag-reply.d.ts +175 -0
  85. package/dist/reply-strategy/rag-reply.d.ts.map +1 -0
  86. package/dist/rls-guard.d.ts +23 -0
  87. package/dist/rls-guard.d.ts.map +1 -0
  88. package/dist/rls-guard.integration.test.d.ts +2 -0
  89. package/dist/rls-guard.integration.test.d.ts.map +1 -0
  90. package/dist/secrets.d.ts +27 -0
  91. package/dist/secrets.d.ts.map +1 -0
  92. package/dist/secrets.test.d.ts +2 -0
  93. package/dist/secrets.test.d.ts.map +1 -0
  94. package/dist/stage-classifier.d.ts +48 -0
  95. package/dist/stage-classifier.d.ts.map +1 -0
  96. package/dist/testkit.d.ts +82 -0
  97. package/dist/testkit.d.ts.map +1 -0
  98. package/dist/transcriber.d.ts +15 -0
  99. package/dist/transcriber.d.ts.map +1 -0
  100. package/dist/types.d.ts +98 -0
  101. package/dist/types.d.ts.map +1 -0
  102. package/dist/with-tenant.d.ts +25 -0
  103. package/dist/with-tenant.d.ts.map +1 -0
  104. package/package.json +66 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rag-reply.d.ts","sourceRoot":"","sources":["../../src/reply-strategy/rag-reply.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Db } from "./dal/types.ts";
2
+ export interface RlsRoleCheck {
3
+ /** current_user в этом connection'е. */
4
+ role: string;
5
+ /** rolsuper — superuser bypass'ит RLS всегда. */
6
+ isSuperuser: boolean;
7
+ /** rolbypassrls — explicit BYPASSRLS attribute. */
8
+ hasBypassRls: boolean;
9
+ /** Resultant: будет ли RLS реально enforce'иться для этой роли. */
10
+ isEnforced: boolean;
11
+ }
12
+ /**
13
+ * Возвращает информацию о том, enforce'ится ли RLS на текущем connection'е.
14
+ * apps/api / apps/worker зовут это в boot и логируют warning если RLS
15
+ * по факту выключен (superuser / BYPASSRLS).
16
+ *
17
+ * Миграция 0004 включает FORCE ROW LEVEL SECURITY на всех tenant-scoped
18
+ * таблицах, но Postgres exempt'ит superuser-роли и роли с BYPASSRLS=true.
19
+ * Production deploy ДОЛЖЕН использовать ordinary role'ью (NOSUPERUSER
20
+ * NOBYPASSRLS) — иначе RLS бесполезен как defense-in-depth.
21
+ */
22
+ export declare function checkRlsEnforcement(db: Db): Promise<RlsRoleCheck>;
23
+ //# sourceMappingURL=rls-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls-guard.d.ts","sourceRoot":"","sources":["../src/rls-guard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAEzC,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,WAAW,EAAE,OAAO,CAAC;IACrB,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAC;IACtB,mEAAmE;IACnE,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAyBvE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rls-guard.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls-guard.integration.test.d.ts","sourceRoot":"","sources":["../src/rls-guard.integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import type { Db } from "./dal/types.ts";
2
+ export declare class SecretCryptoError extends Error {
3
+ constructor(reason: string);
4
+ }
5
+ export declare function encryptSecret(masterKeyHex: string, plaintext: string): string;
6
+ export declare function decryptSecret(masterKeyHex: string, ciphertext: string): string;
7
+ /**
8
+ * Прочитать и расшифровать secret. Возвращает null если ключ не существует
9
+ * для данного tenant'а. Бросает SecretCryptoError если ciphertext битый или
10
+ * master-key неверный (auth tag mismatch).
11
+ */
12
+ export declare function getDecryptedSecret(opts: {
13
+ db: Db;
14
+ tenantId: number;
15
+ key: string;
16
+ masterKeyHex: string;
17
+ }): Promise<string | null>;
18
+ /** Upsert зашифрованного secret'а. */
19
+ export declare function setEncryptedSecret(opts: {
20
+ db: Db;
21
+ tenantId: number;
22
+ key: string;
23
+ value: string;
24
+ masterKeyHex: string;
25
+ nowEpoch: number;
26
+ }): Promise<void>;
27
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAkBzC,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,MAAM,EAAE,MAAM;CAI3B;AAeD,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAc7E;AAED,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAoB9E;AAID;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOzB;AAED,sCAAsC;AACtC,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAehB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=secrets.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.test.d.ts","sourceRoot":"","sources":["../src/secrets.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ import type { FunnelStage } from "@chatman-media/kb";
2
+ import type { Db } from "./dal/types.ts";
3
+ /**
4
+ * Pipeline contract: что pipeline ожидает от любого stage-classifier
5
+ * (regex/LLM/external). Implementations живут в `@chatman-media/sales`
6
+ * как sales-domain logic — sales имеет access к ChatClient и регекспам
7
+ * для русских sales-cues.
8
+ *
9
+ * Здесь только TYPE — чтобы processInbound и apps/api могли референсить
10
+ * без создания circular dep (sales → conv-engine для Db типов).
11
+ *
12
+ * НЕ путать с vertical-template'овским lead.state (intake_pending → docs →
13
+ * visa) — это разные оси:
14
+ * - lead.state: операционный pipeline найма (один на лида)
15
+ * - conversation.current_stage: micro-state диалога продажи (per turn)
16
+ */
17
+ export interface StageClassifier {
18
+ /**
19
+ * Классифицирует stage по тексту user-message + опц. контексту.
20
+ * Возвращает FunnelStage или null если уверенности нет.
21
+ */
22
+ classify(input: {
23
+ /**
24
+ * Tenant контекст — нужен LLM-based реализациям для resolveChat
25
+ * (per-tenant LLM-config). Regex-impl игнорирует, но в interface
26
+ * required чтобы pipeline call-site всегда передавал реальный
27
+ * tenantId из processInbound deps.
28
+ */
29
+ tenantId: number;
30
+ userMessageText: string;
31
+ previousStage: string | null;
32
+ isFirstUserMessage: boolean;
33
+ }): Promise<FunnelStage | null> | FunnelStage | null;
34
+ }
35
+ /**
36
+ * Обновляет `conversations.current_stage` если new стадия отличается от
37
+ * существующей. Idempotent: same-stage → no UPDATE. Pipeline-side persistence
38
+ * step — живёт в conv-engine (где DAL), а не в sales (где stage classifier
39
+ * impls). Чтобы избежать circular dep, sales-impl'ы возвращают FunnelStage,
40
+ * conv-engine берёт результат и применяет через applyClassifiedStage.
41
+ */
42
+ export declare function applyClassifiedStage(opts: {
43
+ db: Db;
44
+ tenantId: number;
45
+ conversationId: number;
46
+ newStage: FunnelStage | null;
47
+ }): Promise<boolean>;
48
+ //# sourceMappingURL=stage-classifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stage-classifier.d.ts","sourceRoot":"","sources":["../src/stage-classifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAEzC;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE;QACd;;;;;WAKG;QACH,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;QACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,kBAAkB,EAAE,OAAO,CAAC;KAC7B,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,WAAW,GAAG,IAAI,CAAC;CACtD;AAID;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,OAAO,CAAC,CAsBnB"}
@@ -0,0 +1,82 @@
1
+ import type { ChannelIdentityRow, ContactRow, ConversationRow, MessageRow, OutboundQueueRow } from "./dal/index.ts";
2
+ export declare class FakeContactsRepo {
3
+ readonly tenantId: number;
4
+ private nextId;
5
+ private readonly rows;
6
+ constructor(tenantId: number);
7
+ byId(id: number): Promise<ContactRow | null>;
8
+ create(opts: {
9
+ displayName?: string;
10
+ attributesJson?: string;
11
+ }): Promise<ContactRow>;
12
+ mergeAttributes(contactId: number, partial: Record<string, unknown>, nowEpoch: number): Promise<ContactRow | null>;
13
+ all(): ContactRow[];
14
+ }
15
+ export declare class FakeChannelIdentitiesRepo {
16
+ private nextId;
17
+ private readonly rows;
18
+ find(channelId: number, externalUserId: string): Promise<ChannelIdentityRow | null>;
19
+ create(opts: {
20
+ contactId: number;
21
+ channelId: number;
22
+ externalUserId: string;
23
+ }): Promise<ChannelIdentityRow>;
24
+ all(): ChannelIdentityRow[];
25
+ }
26
+ export declare class FakeConversationsRepo {
27
+ readonly tenantId: number;
28
+ private nextId;
29
+ private readonly rows;
30
+ constructor(tenantId: number);
31
+ findByContactAndSource(contactId: number, source: string): Promise<ConversationRow | null>;
32
+ create(opts: {
33
+ contactId: number;
34
+ source: string;
35
+ mode?: "ai" | "queued" | "human";
36
+ nowEpoch: number;
37
+ }): Promise<ConversationRow>;
38
+ touchLastMessageAt(conversationId: number, nowEpoch: number): Promise<void>;
39
+ updateInboxMetadata(conversationId: number, opts: {
40
+ lastMessageText?: string;
41
+ lastMessageAt?: number;
42
+ incrementUnread?: boolean;
43
+ status?: "open" | "pending" | "resolved";
44
+ }): Promise<void>;
45
+ markAsRead(conversationId: number): Promise<void>;
46
+ setAssignee(conversationId: number, adminId: number | null): Promise<void>;
47
+ all(): ConversationRow[];
48
+ }
49
+ export declare class FakeMessagesRepo {
50
+ readonly tenantId: number;
51
+ private nextId;
52
+ private readonly rows;
53
+ constructor(tenantId: number);
54
+ insert(opts: {
55
+ conversationId: number;
56
+ role: "user" | "assistant" | "human" | "system";
57
+ text: string;
58
+ externalMessageId?: string;
59
+ metaJson?: string;
60
+ stage?: string;
61
+ nowEpoch: number;
62
+ }): Promise<MessageRow>;
63
+ findUserByExternalId(conversationId: number, externalMessageId: string): Promise<MessageRow | null>;
64
+ all(): MessageRow[];
65
+ }
66
+ export declare class FakeOutboundQueueRepo {
67
+ readonly tenantId: number;
68
+ private nextId;
69
+ private readonly rows;
70
+ constructor(tenantId: number);
71
+ enqueue(opts: {
72
+ channelId: number;
73
+ conversationId?: number | null;
74
+ envelope: {
75
+ idempotencyKey?: string;
76
+ };
77
+ scheduledAt?: number;
78
+ nowEpoch: number;
79
+ }): Promise<OutboundQueueRow>;
80
+ all(): OutboundQueueRow[];
81
+ }
82
+ //# sourceMappingURL=testkit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testkit.d.ts","sourceRoot":"","sources":["../src/testkit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Интерфейс для STT-транскрибации голосовых сообщений.
3
+ * Pipeline инжектит реализацию через ProcessInboundDeps.transcriber.
4
+ * Реализация живёт в apps/api (WhisperTranscriber).
5
+ */
6
+ export interface ITranscriber {
7
+ /**
8
+ * Транскрибирует аудиофайл в текст.
9
+ * @param audio Raw bytes аудиофайла (OGG, MP3, WAV, …)
10
+ * @param filename Имя файла с расширением — провайдер определяет по нему формат
11
+ * @returns Текст транскрипции, или null если транскрипция не удалась
12
+ */
13
+ transcribe(audio: Uint8Array, filename: string): Promise<string | null>;
14
+ }
15
+ //# sourceMappingURL=transcriber.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcriber.d.ts","sourceRoot":"","sources":["../src/transcriber.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACzE"}
@@ -0,0 +1,98 @@
1
+ import type { Inbound, OutboundEnvelope } from "@chatman-media/channel-core";
2
+ /**
3
+ * Минимальный snapshot tenant'а нужный для pipeline. Резолвится по
4
+ * channels.tenant_id один раз на inbound и прокидывается через context.
5
+ */
6
+ export interface TenantContext {
7
+ tenantId: number;
8
+ slug: string;
9
+ llmBillingMode: "byok" | "managed";
10
+ }
11
+ /**
12
+ * Канал-уровневый контекст. channelId стабильный — соответствует
13
+ * channels.id в БД, использовать его, не external_id.
14
+ */
15
+ export interface ChannelContext {
16
+ channelId: number;
17
+ kind: "telegram_bot" | "telegram_userbot" | "whatsapp" | "web";
18
+ /** External id канала у провайдера (bot username, phone). Для логов. */
19
+ externalId: string;
20
+ }
21
+ /**
22
+ * Решение pipeline по входящему: что было сделано и нужны ли downstream
23
+ * действия. Возвращается из processInbound() так чтобы apps/worker мог
24
+ * dispatch'ить telemetry / WebSocket events без дёрганья БД.
25
+ */
26
+ export interface ProcessInboundResult {
27
+ contactId: number;
28
+ conversationId: number;
29
+ /**
30
+ * Сообщение записано в messages? Может быть false если inbound полностью
31
+ * проигнорирован (например, callback_query без матча на функционал).
32
+ */
33
+ persisted: boolean;
34
+ /**
35
+ * Сколько OutboundEnvelope'ов было поставлено в outbound_queue в этом
36
+ * pipeline-проходе. 0 = бот ничего не отвечает (mediaOnly / handover-mode).
37
+ */
38
+ outboundEnqueued: number;
39
+ /**
40
+ * Текст user-сообщения (после inboundText() свёртки). Заполняется когда
41
+ * pipeline вызван с `deferReply: true` — `generateReplyAndEnqueue` потом
42
+ * использует это значение для reply.generate БЕЗ нового read'а из БД.
43
+ * Когда deferReply=false (default) — поле не выставляется.
44
+ */
45
+ userMessageText?: string;
46
+ /** True если только media без caption (бот не должен отвечать). */
47
+ mediaOnly?: boolean;
48
+ /**
49
+ * True если processInbound пропустил reply.generate (deferReply=true) —
50
+ * caller должен вызвать `generateReplyAndEnqueue` чтобы завершить
51
+ * pipeline. False (или undefined) = pipeline отработал полностью.
52
+ */
53
+ replyDeferred?: boolean;
54
+ /** Если конверсация переключилась в режим оператора — сюда попадает причина. */
55
+ escalatedReason?: string;
56
+ }
57
+ /**
58
+ * Дешёвый side-effect sink. Тонкая обёртка чтобы pipeline не зависел от
59
+ * конкретного логгера/EventBus'а. apps/worker подсоединяет реальный sink
60
+ * на boot. Все поля опциональные — testkit-fake может не реализовывать
61
+ * ничего и pipeline всё равно работает.
62
+ */
63
+ export interface PipelineSink {
64
+ log?: (level: "debug" | "info" | "warn" | "error", msg: string, meta?: Record<string, unknown>) => void;
65
+ emit?: (event: PipelineEvent) => void;
66
+ }
67
+ export type PipelineEvent = {
68
+ type: "inbound-received";
69
+ tenantId: number;
70
+ conversationId: number;
71
+ inbound: Inbound;
72
+ } | {
73
+ type: "message-persisted";
74
+ tenantId: number;
75
+ conversationId: number;
76
+ messageId: number;
77
+ role: "user" | "assistant";
78
+ } | {
79
+ type: "outbound-enqueued";
80
+ tenantId: number;
81
+ conversationId: number;
82
+ queueItemId: number;
83
+ envelope: OutboundEnvelope;
84
+ } | {
85
+ type: "conversation-escalated";
86
+ tenantId: number;
87
+ conversationId: number;
88
+ reason: string;
89
+ };
90
+ /**
91
+ * Стандартный clock — для тестирования (replace на fake clock в unit-тестах).
92
+ * `nowEpoch()` возвращает unix-seconds — формат, в котором БД хранит timestamps.
93
+ */
94
+ export interface Clock {
95
+ nowEpoch(): number;
96
+ }
97
+ export declare const systemClock: Clock;
98
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAE7E;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,cAAc,GAAG,kBAAkB,GAAG,UAAU,GAAG,KAAK,CAAC;IAC/D,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gFAAgF;IAChF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACxG,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GACxF;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;CAAE,GACtH;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAA;CAAE,GACxH;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjG;;;GAGG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,IAAI,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,EAAE,KAEzB,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { Db } from "./dal/types.ts";
2
+ /**
3
+ * Открывает транзакцию, выставляет `SET LOCAL app.tenant_id = <id>` (для
4
+ * Postgres RLS-policy `tenant_isolation`), потом вызывает `fn(tx)`.
5
+ *
6
+ * RLS-policies в миграции 0004 сравнивают `tenant_id` колонку с
7
+ * `current_setting('app.tenant_id', true)::int`. Без SET LOCAL var = NULL
8
+ * → policy всегда false → 0 видимых строк (fail-safe).
9
+ *
10
+ * Использование в apps/api / apps/worker:
11
+ * const result = await withTenant(db, tenantId, async (tx) => {
12
+ * const repo = new ContactsRepo({ db: tx, tenantId });
13
+ * return repo.byId(42);
14
+ * });
15
+ *
16
+ * `tx` имеет тот же тип что `Db` (Drizzle PostgresJsDatabase) — repo-методы
17
+ * принимают его без изменений. SET LOCAL scoped в транзакцию: после COMMIT
18
+ * setting сбрасывается, connection возвращается в pool clean.
19
+ *
20
+ * Idempotent при nested-вызовах: вложенный withTenant с тем же tenantId
21
+ * просто переоткроет SET LOCAL (no-op effectively); с разным — переопределит
22
+ * (caller responsibility — не делать так без чёткого понимания).
23
+ */
24
+ export declare function withTenant<T>(db: Db, tenantId: number, fn: (tx: Db) => Promise<T>): Promise<T>;
25
+ //# sourceMappingURL=with-tenant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-tenant.d.ts","sourceRoot":"","sources":["../src/with-tenant.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC,CAOZ"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@chatman-media/conversation-engine",
3
+ "version": "1.2.0",
4
+ "description": "Channel-agnostic pipeline обработки inbound сообщений: contact-resolve → conversation lookup → mode routing → AI-reply / queued / human → outbound. Сердце data plane.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "bun build ./src/index.ts --outdir ./dist --target node --format esm --external '@chatman-media/*' --external drizzle-orm && tsc -p tsconfig.build.json",
22
+ "typecheck": "tsc --noEmit",
23
+ "check": "biome check ./src",
24
+ "format": "biome format --write ./src",
25
+ "test": "bun test",
26
+ "prepublishOnly": "bun run build"
27
+ },
28
+ "keywords": [
29
+ "lead-engine",
30
+ "conversation",
31
+ "pipeline",
32
+ "multi-tenant"
33
+ ],
34
+ "author": "Alexander Kireev",
35
+ "license": "MIT",
36
+ "dependencies": {
37
+ "@chatman-media/channel-core": "1.0.0",
38
+ "@chatman-media/channel-telegram": "1.0.0",
39
+ "@chatman-media/llm-router": "1.0.0",
40
+ "@chatman-media/kb": "1.2.0",
41
+ "@chatman-media/storage": "1.3.0",
42
+ "@chatman-media/verticals": "1.1.0"
43
+ },
44
+ "peerDependencies": {
45
+ "drizzle-orm": ">=0.36.0"
46
+ },
47
+ "devDependencies": {
48
+ "@biomejs/biome": "^2.4.14",
49
+ "@types/bun": "1.3.14",
50
+ "drizzle-orm": "^0.36.0",
51
+ "postgres": "^3.4.5",
52
+ "typescript": "^6.0.3"
53
+ },
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/chatman-media/lead-engine.git",
57
+ "directory": "packages/conversation-engine"
58
+ },
59
+ "homepage": "https://github.com/chatman-media/lead-engine/tree/main/packages/conversation-engine#readme",
60
+ "bugs": {
61
+ "url": "https://github.com/chatman-media/lead-engine/issues"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ }
66
+ }