@adatechnology/meta-whatsapp-module 0.2.0-rc.8 → 0.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.
package/dist/index.d.cts CHANGED
@@ -1,8 +1,10 @@
1
1
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
2
2
  import { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
3
3
  import * as _adatechnology_meta_whatsapp_contracts from '@adatechnology/meta-whatsapp-contracts';
4
- import { SessionState, SessionMode, ConversationSummary, FlowGraphData, FlowGraphSummary, LiveFlowPosition, WhatsAppSettings, MessageDirection, MessageSender, MessageStatus, RealtimeNotifierInterface, ChannelAdapterInterface, ObjectStorageInterface, FlowActionKind, FlowActionHandler, ConversationSession, MetaWhatsAppHooks, SubjectResolverInterface, CatalogPort } from '@adatechnology/meta-whatsapp-contracts';
4
+ import { TranscriptionMode, SessionState, SessionMode, ConversationSummary, MessageDirection, MessageSender, MessageStatus, CacheInterface, FlowGraphData, FlowGraphSummary, LiveFlowPosition, WhatsAppSettings, RealtimeNotifierInterface, ChannelAdapterInterface, ObjectStorageInterface, FlowActionKind, FlowActionHandler, ConversationSession, WhatsAppMessage, WhatsAppStatus, MetaWhatsAppHooks, SubjectResolverInterface, CatalogPort, SendChannelMediaParams } from '@adatechnology/meta-whatsapp-contracts';
5
+ export { PREVIEW_MEDIA_ID_PREFIX, TranscriptionMode, resolvePreviewUploadId, toPreviewMediaId } from '@adatechnology/meta-whatsapp-contracts';
5
6
  import { WhatsAppMessageProvider } from '@adatechnology/meta-whatsapp-provider';
7
+ export { WEBHOOK_CLAIM_TTL_SECONDS, WEBHOOK_NONCE_TTL_SECONDS } from '@adatechnology/meta-graph-core';
6
8
 
7
9
  type MetaWhatsAppDatabase = PgDatabase<PgQueryResultHKT, any, any>;
8
10
  type DrizzleMigrateFunction = (db: never, config: {
@@ -10,6 +12,72 @@ type DrizzleMigrateFunction = (db: never, config: {
10
12
  migrationsTable?: string;
11
13
  }) => Promise<void>;
12
14
 
15
+ /**
16
+ * Vocabulário de transcrição de áudio do módulo.
17
+ *
18
+ * Fica em arquivo próprio porque o schema e os dois use-cases (ingestão automática e sob demanda)
19
+ * precisam dos mesmos tipos, e pendurá-los em qualquer um dos três faria os outros dois importarem
20
+ * de dentro de uma camada que não é a deles.
21
+ */
22
+
23
+ declare const TRANSCRIPTION_STATUS: {
24
+ /** Falhou de forma retriável (cota, rede, 5xx) — vai sair quando alguém tentar de novo. */
25
+ readonly PENDING: "pending";
26
+ /** Processado. Texto vazio aqui é áudio em silêncio, e NÃO deve ser reprocessado. */
27
+ readonly DONE: "done";
28
+ /** Falha definitiva do engine (credencial, áudio corrompido, arquivo grande demais). */
29
+ readonly FAILED: "failed";
30
+ /** Nenhum engine da cadeia aceita o formato. Retentar não conserta codec. */
31
+ readonly UNSUPPORTED: "unsupported";
32
+ };
33
+ type TranscriptionStatus = (typeof TRANSCRIPTION_STATUS)[keyof typeof TRANSCRIPTION_STATUS];
34
+ /**
35
+ * Quando transcrever.
36
+ *
37
+ * `auto` transcreve durante a ingestão da mídia, onde o buffer do áudio JÁ está em memória — não
38
+ * custa um segundo download do storage. `onDemand` só transcreve quando o atendente pede, o que
39
+ * troca latência na interface por não gastar cota com áudio que ninguém vai ler.
40
+ *
41
+ * O tipo vem do contrato (o painel escolhe, a API transporta, o módulo obedece) e `satisfies`
42
+ * garante em tempo de compilação que estes valores continuam sendo exatamente os de lá — sem isso,
43
+ * um modo novo no contrato passaria despercebido aqui.
44
+ */
45
+ declare const TRANSCRIPTION_MODE: {
46
+ readonly AUTO: "auto";
47
+ readonly ON_DEMAND: "onDemand";
48
+ };
49
+
50
+ /**
51
+ * O contrato mínimo de `@adatechnology/audio-transcription-provider`, declarado aqui em vez de
52
+ * importado — mesma decisão do `MessageModerator` em `LogMessage.use-case.ts`.
53
+ *
54
+ * Assim o módulo não ganha dependência de pacote por um recurso opcional, e transcrição não fica
55
+ * amarrada a WhatsApp: o que atravessa esta fronteira é um buffer de áudio e um mime.
56
+ */
57
+ type AudioTranscriber = {
58
+ readonly name: string;
59
+ transcribe: (input: {
60
+ buffer: Buffer;
61
+ mimeType: string;
62
+ languageHint?: string;
63
+ }) => Promise<{
64
+ text: string;
65
+ language?: string;
66
+ durationSeconds?: number;
67
+ engine: string;
68
+ }>;
69
+ };
70
+ /**
71
+ * Recorte do erro do provider que o módulo precisa ler para escolher entre `'pending'` e
72
+ * `'failed'`. Estrutural, não `instanceof`: o provider é opcional e pode nem estar instalado, e um
73
+ * `instanceof` contra classe ausente não compila.
74
+ */
75
+ declare function isRetriableTranscriptionError(error: unknown): boolean;
76
+ declare function isUnsupportedTranscriptionError(error: unknown): boolean;
77
+ declare function transcriptionRetryAfterSeconds(error: unknown): number | undefined;
78
+ /** Mime de áudio? Só áudio é transcrito — vídeo, imagem e documento passam sem tocar no engine. */
79
+ declare function isAudioMimeType(mimeType: string | undefined | null): boolean;
80
+
13
81
  declare const metaWhatsAppSchema: drizzle_orm_pg_core.PgSchema<"meta_whatsapp">;
14
82
  declare const sessions: drizzle_orm_pg_core.PgTableWithColumns<{
15
83
  name: "sessions";
@@ -524,6 +592,117 @@ declare const messages: drizzle_orm_pg_core.PgTableWithColumns<{
524
592
  identity: undefined;
525
593
  generated: undefined;
526
594
  }, {}, {}>;
595
+ moderationFlagged: drizzle_orm_pg_core.PgColumn<{
596
+ name: "moderation_flagged";
597
+ tableName: "messages";
598
+ dataType: "boolean";
599
+ columnType: "PgBoolean";
600
+ data: boolean;
601
+ driverParam: boolean;
602
+ notNull: false;
603
+ hasDefault: false;
604
+ isPrimaryKey: false;
605
+ isAutoincrement: false;
606
+ hasRuntimeDefault: false;
607
+ enumValues: undefined;
608
+ baseColumn: never;
609
+ identity: undefined;
610
+ generated: undefined;
611
+ }, {}, {}>;
612
+ moderationTerms: drizzle_orm_pg_core.PgColumn<{
613
+ name: "moderation_terms";
614
+ tableName: "messages";
615
+ dataType: "json";
616
+ columnType: "PgJsonb";
617
+ data: string[];
618
+ driverParam: unknown;
619
+ notNull: false;
620
+ hasDefault: false;
621
+ isPrimaryKey: false;
622
+ isAutoincrement: false;
623
+ hasRuntimeDefault: false;
624
+ enumValues: undefined;
625
+ baseColumn: never;
626
+ identity: undefined;
627
+ generated: undefined;
628
+ }, {}, {
629
+ $type: string[];
630
+ }>;
631
+ transcriptionStatus: drizzle_orm_pg_core.PgColumn<{
632
+ name: "transcription_status";
633
+ tableName: "messages";
634
+ dataType: "string";
635
+ columnType: "PgVarchar";
636
+ data: TranscriptionStatus;
637
+ driverParam: string;
638
+ notNull: false;
639
+ hasDefault: false;
640
+ isPrimaryKey: false;
641
+ isAutoincrement: false;
642
+ hasRuntimeDefault: false;
643
+ enumValues: [string, ...string[]];
644
+ baseColumn: never;
645
+ identity: undefined;
646
+ generated: undefined;
647
+ }, {}, {
648
+ length: 16;
649
+ $type: TranscriptionStatus;
650
+ }>;
651
+ transcriptionText: drizzle_orm_pg_core.PgColumn<{
652
+ name: "transcription_text";
653
+ tableName: "messages";
654
+ dataType: "string";
655
+ columnType: "PgText";
656
+ data: string;
657
+ driverParam: string;
658
+ notNull: false;
659
+ hasDefault: false;
660
+ isPrimaryKey: false;
661
+ isAutoincrement: false;
662
+ hasRuntimeDefault: false;
663
+ enumValues: [string, ...string[]];
664
+ baseColumn: never;
665
+ identity: undefined;
666
+ generated: undefined;
667
+ }, {}, {}>;
668
+ transcriptionLanguage: drizzle_orm_pg_core.PgColumn<{
669
+ name: "transcription_language";
670
+ tableName: "messages";
671
+ dataType: "string";
672
+ columnType: "PgVarchar";
673
+ data: string;
674
+ driverParam: string;
675
+ notNull: false;
676
+ hasDefault: false;
677
+ isPrimaryKey: false;
678
+ isAutoincrement: false;
679
+ hasRuntimeDefault: false;
680
+ enumValues: [string, ...string[]];
681
+ baseColumn: never;
682
+ identity: undefined;
683
+ generated: undefined;
684
+ }, {}, {
685
+ length: 32;
686
+ }>;
687
+ transcriptionEngine: drizzle_orm_pg_core.PgColumn<{
688
+ name: "transcription_engine";
689
+ tableName: "messages";
690
+ dataType: "string";
691
+ columnType: "PgVarchar";
692
+ data: string;
693
+ driverParam: string;
694
+ notNull: false;
695
+ hasDefault: false;
696
+ isPrimaryKey: false;
697
+ isAutoincrement: false;
698
+ hasRuntimeDefault: false;
699
+ enumValues: [string, ...string[]];
700
+ baseColumn: never;
701
+ identity: undefined;
702
+ generated: undefined;
703
+ }, {}, {
704
+ length: 32;
705
+ }>;
527
706
  createdAt: drizzle_orm_pg_core.PgColumn<{
528
707
  name: "created_at";
529
708
  tableName: "messages";
@@ -544,13 +723,25 @@ declare const messages: drizzle_orm_pg_core.PgTableWithColumns<{
544
723
  };
545
724
  dialect: "pg";
546
725
  }>;
547
- declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
548
- name: "flow_graphs";
726
+ /**
727
+ * Biblioteca de arquivos da conversa.
728
+ *
729
+ * Tabela própria, e não derivada de `messages.payload`: o painel precisa de `source` e `linkedAt`,
730
+ * que não existem no payload; busca por nome de arquivo quer índice, não varredura de jsonb; e
731
+ * `messageId` anulável é o que permite o atendente anexar documento à conversa sem que exista uma
732
+ * mensagem correspondente.
733
+ *
734
+ * `sessionId` em cascata apaga a LINHA junto com a conversa, mas não o binário no storage — quem
735
+ * apaga objeto é passo de aplicação (listar `uploadId` → apagar no storage → apagar a sessão).
736
+ * Confiar só na FK deixaria objeto órfão sendo cobrado para sempre.
737
+ */
738
+ declare const documents: drizzle_orm_pg_core.PgTableWithColumns<{
739
+ name: "documents";
549
740
  schema: "meta_whatsapp";
550
741
  columns: {
551
742
  id: drizzle_orm_pg_core.PgColumn<{
552
743
  name: "id";
553
- tableName: "flow_graphs";
744
+ tableName: "documents";
554
745
  dataType: "string";
555
746
  columnType: "PgUUID";
556
747
  data: string;
@@ -567,7 +758,7 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
567
758
  }, {}, {}>;
568
759
  companyId: drizzle_orm_pg_core.PgColumn<{
569
760
  name: "company_id";
570
- tableName: "flow_graphs";
761
+ tableName: "documents";
571
762
  dataType: "string";
572
763
  columnType: "PgUUID";
573
764
  data: string;
@@ -582,11 +773,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
582
773
  identity: undefined;
583
774
  generated: undefined;
584
775
  }, {}, {}>;
585
- key: drizzle_orm_pg_core.PgColumn<{
586
- name: "key";
587
- tableName: "flow_graphs";
776
+ sessionId: drizzle_orm_pg_core.PgColumn<{
777
+ name: "session_id";
778
+ tableName: "documents";
588
779
  dataType: "string";
589
- columnType: "PgVarchar";
780
+ columnType: "PgUUID";
590
781
  data: string;
591
782
  driverParam: string;
592
783
  notNull: true;
@@ -594,16 +785,31 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
594
785
  isPrimaryKey: false;
595
786
  isAutoincrement: false;
596
787
  hasRuntimeDefault: false;
597
- enumValues: [string, ...string[]];
788
+ enumValues: undefined;
598
789
  baseColumn: never;
599
790
  identity: undefined;
600
791
  generated: undefined;
601
- }, {}, {
602
- length: 64;
603
- }>;
604
- label: drizzle_orm_pg_core.PgColumn<{
605
- name: "label";
606
- tableName: "flow_graphs";
792
+ }, {}, {}>;
793
+ messageId: drizzle_orm_pg_core.PgColumn<{
794
+ name: "message_id";
795
+ tableName: "documents";
796
+ dataType: "string";
797
+ columnType: "PgUUID";
798
+ data: string;
799
+ driverParam: string;
800
+ notNull: false;
801
+ hasDefault: false;
802
+ isPrimaryKey: false;
803
+ isAutoincrement: false;
804
+ hasRuntimeDefault: false;
805
+ enumValues: undefined;
806
+ baseColumn: never;
807
+ identity: undefined;
808
+ generated: undefined;
809
+ }, {}, {}>;
810
+ uploadId: drizzle_orm_pg_core.PgColumn<{
811
+ name: "upload_id";
812
+ tableName: "documents";
607
813
  dataType: "string";
608
814
  columnType: "PgVarchar";
609
815
  data: string;
@@ -618,11 +824,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
618
824
  identity: undefined;
619
825
  generated: undefined;
620
826
  }, {}, {
621
- length: 120;
827
+ length: 256;
622
828
  }>;
623
- startNodeId: drizzle_orm_pg_core.PgColumn<{
624
- name: "start_node_id";
625
- tableName: "flow_graphs";
829
+ filename: drizzle_orm_pg_core.PgColumn<{
830
+ name: "filename";
831
+ tableName: "documents";
626
832
  dataType: "string";
627
833
  columnType: "PgVarchar";
628
834
  data: string;
@@ -637,36 +843,36 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
637
843
  identity: undefined;
638
844
  generated: undefined;
639
845
  }, {}, {
640
- length: 64;
846
+ length: 512;
641
847
  }>;
642
- nodes: drizzle_orm_pg_core.PgColumn<{
643
- name: "nodes";
644
- tableName: "flow_graphs";
645
- dataType: "json";
646
- columnType: "PgJsonb";
647
- data: Record<string, unknown>;
648
- driverParam: unknown;
848
+ mimeType: drizzle_orm_pg_core.PgColumn<{
849
+ name: "mime_type";
850
+ tableName: "documents";
851
+ dataType: "string";
852
+ columnType: "PgVarchar";
853
+ data: string;
854
+ driverParam: string;
649
855
  notNull: true;
650
- hasDefault: true;
856
+ hasDefault: false;
651
857
  isPrimaryKey: false;
652
858
  isAutoincrement: false;
653
859
  hasRuntimeDefault: false;
654
- enumValues: undefined;
860
+ enumValues: [string, ...string[]];
655
861
  baseColumn: never;
656
862
  identity: undefined;
657
863
  generated: undefined;
658
864
  }, {}, {
659
- $type: Record<string, unknown>;
865
+ length: 128;
660
866
  }>;
661
- version: drizzle_orm_pg_core.PgColumn<{
662
- name: "version";
663
- tableName: "flow_graphs";
867
+ sizeBytes: drizzle_orm_pg_core.PgColumn<{
868
+ name: "size_bytes";
869
+ tableName: "documents";
664
870
  dataType: "number";
665
871
  columnType: "PgInteger";
666
872
  data: number;
667
873
  driverParam: string | number;
668
874
  notNull: true;
669
- hasDefault: true;
875
+ hasDefault: false;
670
876
  isPrimaryKey: false;
671
877
  isAutoincrement: false;
672
878
  hasRuntimeDefault: false;
@@ -675,31 +881,33 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
675
881
  identity: undefined;
676
882
  generated: undefined;
677
883
  }, {}, {}>;
678
- showInMenu: drizzle_orm_pg_core.PgColumn<{
679
- name: "show_in_menu";
680
- tableName: "flow_graphs";
681
- dataType: "boolean";
682
- columnType: "PgBoolean";
683
- data: boolean;
684
- driverParam: boolean;
685
- notNull: true;
686
- hasDefault: true;
884
+ sha256: drizzle_orm_pg_core.PgColumn<{
885
+ name: "sha256";
886
+ tableName: "documents";
887
+ dataType: "string";
888
+ columnType: "PgVarchar";
889
+ data: string;
890
+ driverParam: string;
891
+ notNull: false;
892
+ hasDefault: false;
687
893
  isPrimaryKey: false;
688
894
  isAutoincrement: false;
689
895
  hasRuntimeDefault: false;
690
- enumValues: undefined;
896
+ enumValues: [string, ...string[]];
691
897
  baseColumn: never;
692
898
  identity: undefined;
693
899
  generated: undefined;
694
- }, {}, {}>;
695
- menuOptionLabel: drizzle_orm_pg_core.PgColumn<{
696
- name: "menu_option_label";
697
- tableName: "flow_graphs";
900
+ }, {}, {
901
+ length: 64;
902
+ }>;
903
+ source: drizzle_orm_pg_core.PgColumn<{
904
+ name: "source";
905
+ tableName: "documents";
698
906
  dataType: "string";
699
907
  columnType: "PgVarchar";
700
908
  data: string;
701
909
  driverParam: string;
702
- notNull: false;
910
+ notNull: true;
703
911
  hasDefault: false;
704
912
  isPrimaryKey: false;
705
913
  isAutoincrement: false;
@@ -709,11 +917,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
709
917
  identity: undefined;
710
918
  generated: undefined;
711
919
  }, {}, {
712
- length: 64;
920
+ length: 12;
713
921
  }>;
714
- createdAt: drizzle_orm_pg_core.PgColumn<{
715
- name: "created_at";
716
- tableName: "flow_graphs";
922
+ linkedAt: drizzle_orm_pg_core.PgColumn<{
923
+ name: "linked_at";
924
+ tableName: "documents";
717
925
  dataType: "date";
718
926
  columnType: "PgTimestamp";
719
927
  data: Date;
@@ -728,16 +936,23 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
728
936
  identity: undefined;
729
937
  generated: undefined;
730
938
  }, {}, {}>;
731
- updatedAt: drizzle_orm_pg_core.PgColumn<{
732
- name: "updated_at";
939
+ };
940
+ dialect: "pg";
941
+ }>;
942
+ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
943
+ name: "flow_graphs";
944
+ schema: "meta_whatsapp";
945
+ columns: {
946
+ id: drizzle_orm_pg_core.PgColumn<{
947
+ name: "id";
733
948
  tableName: "flow_graphs";
734
- dataType: "date";
735
- columnType: "PgTimestamp";
736
- data: Date;
949
+ dataType: "string";
950
+ columnType: "PgUUID";
951
+ data: string;
737
952
  driverParam: string;
738
953
  notNull: true;
739
954
  hasDefault: true;
740
- isPrimaryKey: false;
955
+ isPrimaryKey: true;
741
956
  isAutoincrement: false;
742
957
  hasRuntimeDefault: false;
743
958
  enumValues: undefined;
@@ -745,23 +960,16 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
745
960
  identity: undefined;
746
961
  generated: undefined;
747
962
  }, {}, {}>;
748
- };
749
- dialect: "pg";
750
- }>;
751
- declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
752
- name: "settings";
753
- schema: "meta_whatsapp";
754
- columns: {
755
963
  companyId: drizzle_orm_pg_core.PgColumn<{
756
964
  name: "company_id";
757
- tableName: "settings";
965
+ tableName: "flow_graphs";
758
966
  dataType: "string";
759
967
  columnType: "PgUUID";
760
968
  data: string;
761
969
  driverParam: string;
762
970
  notNull: true;
763
971
  hasDefault: false;
764
- isPrimaryKey: true;
972
+ isPrimaryKey: false;
765
973
  isAutoincrement: false;
766
974
  hasRuntimeDefault: false;
767
975
  enumValues: undefined;
@@ -769,14 +977,14 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
769
977
  identity: undefined;
770
978
  generated: undefined;
771
979
  }, {}, {}>;
772
- templateName: drizzle_orm_pg_core.PgColumn<{
773
- name: "template_name";
774
- tableName: "settings";
980
+ key: drizzle_orm_pg_core.PgColumn<{
981
+ name: "key";
982
+ tableName: "flow_graphs";
775
983
  dataType: "string";
776
984
  columnType: "PgVarchar";
777
985
  data: string;
778
986
  driverParam: string;
779
- notNull: false;
987
+ notNull: true;
780
988
  hasDefault: false;
781
989
  isPrimaryKey: false;
782
990
  isAutoincrement: false;
@@ -786,17 +994,17 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
786
994
  identity: undefined;
787
995
  generated: undefined;
788
996
  }, {}, {
789
- length: 128;
997
+ length: 64;
790
998
  }>;
791
- templateLanguage: drizzle_orm_pg_core.PgColumn<{
792
- name: "template_language";
793
- tableName: "settings";
999
+ label: drizzle_orm_pg_core.PgColumn<{
1000
+ name: "label";
1001
+ tableName: "flow_graphs";
794
1002
  dataType: "string";
795
1003
  columnType: "PgVarchar";
796
1004
  data: string;
797
1005
  driverParam: string;
798
1006
  notNull: true;
799
- hasDefault: true;
1007
+ hasDefault: false;
800
1008
  isPrimaryKey: false;
801
1009
  isAutoincrement: false;
802
1010
  hasRuntimeDefault: false;
@@ -805,16 +1013,473 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
805
1013
  identity: undefined;
806
1014
  generated: undefined;
807
1015
  }, {}, {
808
- length: 16;
1016
+ length: 120;
809
1017
  }>;
810
- templateVariables: drizzle_orm_pg_core.PgColumn<{
811
- name: "template_variables";
812
- tableName: "settings";
813
- dataType: "json";
814
- columnType: "PgJsonb";
815
- data: string[];
816
- driverParam: unknown;
817
- notNull: true;
1018
+ startNodeId: drizzle_orm_pg_core.PgColumn<{
1019
+ name: "start_node_id";
1020
+ tableName: "flow_graphs";
1021
+ dataType: "string";
1022
+ columnType: "PgVarchar";
1023
+ data: string;
1024
+ driverParam: string;
1025
+ notNull: true;
1026
+ hasDefault: false;
1027
+ isPrimaryKey: false;
1028
+ isAutoincrement: false;
1029
+ hasRuntimeDefault: false;
1030
+ enumValues: [string, ...string[]];
1031
+ baseColumn: never;
1032
+ identity: undefined;
1033
+ generated: undefined;
1034
+ }, {}, {
1035
+ length: 64;
1036
+ }>;
1037
+ nodes: drizzle_orm_pg_core.PgColumn<{
1038
+ name: "nodes";
1039
+ tableName: "flow_graphs";
1040
+ dataType: "json";
1041
+ columnType: "PgJsonb";
1042
+ data: Record<string, unknown>;
1043
+ driverParam: unknown;
1044
+ notNull: true;
1045
+ hasDefault: true;
1046
+ isPrimaryKey: false;
1047
+ isAutoincrement: false;
1048
+ hasRuntimeDefault: false;
1049
+ enumValues: undefined;
1050
+ baseColumn: never;
1051
+ identity: undefined;
1052
+ generated: undefined;
1053
+ }, {}, {
1054
+ $type: Record<string, unknown>;
1055
+ }>;
1056
+ version: drizzle_orm_pg_core.PgColumn<{
1057
+ name: "version";
1058
+ tableName: "flow_graphs";
1059
+ dataType: "number";
1060
+ columnType: "PgInteger";
1061
+ data: number;
1062
+ driverParam: string | number;
1063
+ notNull: true;
1064
+ hasDefault: true;
1065
+ isPrimaryKey: false;
1066
+ isAutoincrement: false;
1067
+ hasRuntimeDefault: false;
1068
+ enumValues: undefined;
1069
+ baseColumn: never;
1070
+ identity: undefined;
1071
+ generated: undefined;
1072
+ }, {}, {}>;
1073
+ showInMenu: drizzle_orm_pg_core.PgColumn<{
1074
+ name: "show_in_menu";
1075
+ tableName: "flow_graphs";
1076
+ dataType: "boolean";
1077
+ columnType: "PgBoolean";
1078
+ data: boolean;
1079
+ driverParam: boolean;
1080
+ notNull: true;
1081
+ hasDefault: true;
1082
+ isPrimaryKey: false;
1083
+ isAutoincrement: false;
1084
+ hasRuntimeDefault: false;
1085
+ enumValues: undefined;
1086
+ baseColumn: never;
1087
+ identity: undefined;
1088
+ generated: undefined;
1089
+ }, {}, {}>;
1090
+ menuOptionLabel: drizzle_orm_pg_core.PgColumn<{
1091
+ name: "menu_option_label";
1092
+ tableName: "flow_graphs";
1093
+ dataType: "string";
1094
+ columnType: "PgVarchar";
1095
+ data: string;
1096
+ driverParam: string;
1097
+ notNull: false;
1098
+ hasDefault: false;
1099
+ isPrimaryKey: false;
1100
+ isAutoincrement: false;
1101
+ hasRuntimeDefault: false;
1102
+ enumValues: [string, ...string[]];
1103
+ baseColumn: never;
1104
+ identity: undefined;
1105
+ generated: undefined;
1106
+ }, {}, {
1107
+ length: 64;
1108
+ }>;
1109
+ createdAt: drizzle_orm_pg_core.PgColumn<{
1110
+ name: "created_at";
1111
+ tableName: "flow_graphs";
1112
+ dataType: "date";
1113
+ columnType: "PgTimestamp";
1114
+ data: Date;
1115
+ driverParam: string;
1116
+ notNull: true;
1117
+ hasDefault: true;
1118
+ isPrimaryKey: false;
1119
+ isAutoincrement: false;
1120
+ hasRuntimeDefault: false;
1121
+ enumValues: undefined;
1122
+ baseColumn: never;
1123
+ identity: undefined;
1124
+ generated: undefined;
1125
+ }, {}, {}>;
1126
+ updatedAt: drizzle_orm_pg_core.PgColumn<{
1127
+ name: "updated_at";
1128
+ tableName: "flow_graphs";
1129
+ dataType: "date";
1130
+ columnType: "PgTimestamp";
1131
+ data: Date;
1132
+ driverParam: string;
1133
+ notNull: true;
1134
+ hasDefault: true;
1135
+ isPrimaryKey: false;
1136
+ isAutoincrement: false;
1137
+ hasRuntimeDefault: false;
1138
+ enumValues: undefined;
1139
+ baseColumn: never;
1140
+ identity: undefined;
1141
+ generated: undefined;
1142
+ }, {}, {}>;
1143
+ };
1144
+ dialect: "pg";
1145
+ }>;
1146
+ /**
1147
+ * Biblioteca de mídia do bot: arquivos que um nó `action` de `send_media` dispara ao chegar nele.
1148
+ *
1149
+ * Tabela separada de `documents` de propósito, e não por simetria: `documents` é o acervo DE UMA
1150
+ * CONVERSA (tem `sessionId` obrigatório) e tem índice único em (companyId, uploadId), porque lá o
1151
+ * mesmo binário chegando duas vezes é reentrega de job. Aqui é o oposto — o mesmo arquivo é
1152
+ * enviado para todo cliente que passar pelo nó, então aquele único bloquearia o segundo envio.
1153
+ *
1154
+ * Sem FK para `flow_graphs.id`: o vínculo natural é (companyId, flowKey), que é justamente o
1155
+ * índice único de lá. `nodeId` não tem como ser FK — nós vivem dentro do jsonb `nodes` — então
1156
+ * apagar um nó no editor deixa a linha órfã; quem lista sempre parte de um nó existente, e a
1157
+ * limpeza é passo de aplicação ao salvar o grafo.
1158
+ */
1159
+ declare const flowMedia: drizzle_orm_pg_core.PgTableWithColumns<{
1160
+ name: "flow_media";
1161
+ schema: "meta_whatsapp";
1162
+ columns: {
1163
+ id: drizzle_orm_pg_core.PgColumn<{
1164
+ name: "id";
1165
+ tableName: "flow_media";
1166
+ dataType: "string";
1167
+ columnType: "PgUUID";
1168
+ data: string;
1169
+ driverParam: string;
1170
+ notNull: true;
1171
+ hasDefault: true;
1172
+ isPrimaryKey: true;
1173
+ isAutoincrement: false;
1174
+ hasRuntimeDefault: false;
1175
+ enumValues: undefined;
1176
+ baseColumn: never;
1177
+ identity: undefined;
1178
+ generated: undefined;
1179
+ }, {}, {}>;
1180
+ companyId: drizzle_orm_pg_core.PgColumn<{
1181
+ name: "company_id";
1182
+ tableName: "flow_media";
1183
+ dataType: "string";
1184
+ columnType: "PgUUID";
1185
+ data: string;
1186
+ driverParam: string;
1187
+ notNull: true;
1188
+ hasDefault: false;
1189
+ isPrimaryKey: false;
1190
+ isAutoincrement: false;
1191
+ hasRuntimeDefault: false;
1192
+ enumValues: undefined;
1193
+ baseColumn: never;
1194
+ identity: undefined;
1195
+ generated: undefined;
1196
+ }, {}, {}>;
1197
+ flowKey: drizzle_orm_pg_core.PgColumn<{
1198
+ name: "flow_key";
1199
+ tableName: "flow_media";
1200
+ dataType: "string";
1201
+ columnType: "PgVarchar";
1202
+ data: string;
1203
+ driverParam: string;
1204
+ notNull: true;
1205
+ hasDefault: false;
1206
+ isPrimaryKey: false;
1207
+ isAutoincrement: false;
1208
+ hasRuntimeDefault: false;
1209
+ enumValues: [string, ...string[]];
1210
+ baseColumn: never;
1211
+ identity: undefined;
1212
+ generated: undefined;
1213
+ }, {}, {
1214
+ length: 64;
1215
+ }>;
1216
+ nodeId: drizzle_orm_pg_core.PgColumn<{
1217
+ name: "node_id";
1218
+ tableName: "flow_media";
1219
+ dataType: "string";
1220
+ columnType: "PgVarchar";
1221
+ data: string;
1222
+ driverParam: string;
1223
+ notNull: true;
1224
+ hasDefault: false;
1225
+ isPrimaryKey: false;
1226
+ isAutoincrement: false;
1227
+ hasRuntimeDefault: false;
1228
+ enumValues: [string, ...string[]];
1229
+ baseColumn: never;
1230
+ identity: undefined;
1231
+ generated: undefined;
1232
+ }, {}, {
1233
+ length: 64;
1234
+ }>;
1235
+ uploadId: drizzle_orm_pg_core.PgColumn<{
1236
+ name: "upload_id";
1237
+ tableName: "flow_media";
1238
+ dataType: "string";
1239
+ columnType: "PgVarchar";
1240
+ data: string;
1241
+ driverParam: string;
1242
+ notNull: true;
1243
+ hasDefault: false;
1244
+ isPrimaryKey: false;
1245
+ isAutoincrement: false;
1246
+ hasRuntimeDefault: false;
1247
+ enumValues: [string, ...string[]];
1248
+ baseColumn: never;
1249
+ identity: undefined;
1250
+ generated: undefined;
1251
+ }, {}, {
1252
+ length: 256;
1253
+ }>;
1254
+ filename: drizzle_orm_pg_core.PgColumn<{
1255
+ name: "filename";
1256
+ tableName: "flow_media";
1257
+ dataType: "string";
1258
+ columnType: "PgVarchar";
1259
+ data: string;
1260
+ driverParam: string;
1261
+ notNull: true;
1262
+ hasDefault: false;
1263
+ isPrimaryKey: false;
1264
+ isAutoincrement: false;
1265
+ hasRuntimeDefault: false;
1266
+ enumValues: [string, ...string[]];
1267
+ baseColumn: never;
1268
+ identity: undefined;
1269
+ generated: undefined;
1270
+ }, {}, {
1271
+ length: 512;
1272
+ }>;
1273
+ mimeType: drizzle_orm_pg_core.PgColumn<{
1274
+ name: "mime_type";
1275
+ tableName: "flow_media";
1276
+ dataType: "string";
1277
+ columnType: "PgVarchar";
1278
+ data: string;
1279
+ driverParam: string;
1280
+ notNull: true;
1281
+ hasDefault: false;
1282
+ isPrimaryKey: false;
1283
+ isAutoincrement: false;
1284
+ hasRuntimeDefault: false;
1285
+ enumValues: [string, ...string[]];
1286
+ baseColumn: never;
1287
+ identity: undefined;
1288
+ generated: undefined;
1289
+ }, {}, {
1290
+ length: 128;
1291
+ }>;
1292
+ sizeBytes: drizzle_orm_pg_core.PgColumn<{
1293
+ name: "size_bytes";
1294
+ tableName: "flow_media";
1295
+ dataType: "number";
1296
+ columnType: "PgInteger";
1297
+ data: number;
1298
+ driverParam: string | number;
1299
+ notNull: true;
1300
+ hasDefault: false;
1301
+ isPrimaryKey: false;
1302
+ isAutoincrement: false;
1303
+ hasRuntimeDefault: false;
1304
+ enumValues: undefined;
1305
+ baseColumn: never;
1306
+ identity: undefined;
1307
+ generated: undefined;
1308
+ }, {}, {}>;
1309
+ caption: drizzle_orm_pg_core.PgColumn<{
1310
+ name: "caption";
1311
+ tableName: "flow_media";
1312
+ dataType: "string";
1313
+ columnType: "PgText";
1314
+ data: string;
1315
+ driverParam: string;
1316
+ notNull: false;
1317
+ hasDefault: false;
1318
+ isPrimaryKey: false;
1319
+ isAutoincrement: false;
1320
+ hasRuntimeDefault: false;
1321
+ enumValues: [string, ...string[]];
1322
+ baseColumn: never;
1323
+ identity: undefined;
1324
+ generated: undefined;
1325
+ }, {}, {}>;
1326
+ sortOrder: drizzle_orm_pg_core.PgColumn<{
1327
+ name: "sort_order";
1328
+ tableName: "flow_media";
1329
+ dataType: "number";
1330
+ columnType: "PgInteger";
1331
+ data: number;
1332
+ driverParam: string | number;
1333
+ notNull: true;
1334
+ hasDefault: true;
1335
+ isPrimaryKey: false;
1336
+ isAutoincrement: false;
1337
+ hasRuntimeDefault: false;
1338
+ enumValues: undefined;
1339
+ baseColumn: never;
1340
+ identity: undefined;
1341
+ generated: undefined;
1342
+ }, {}, {}>;
1343
+ active: drizzle_orm_pg_core.PgColumn<{
1344
+ name: "active";
1345
+ tableName: "flow_media";
1346
+ dataType: "boolean";
1347
+ columnType: "PgBoolean";
1348
+ data: boolean;
1349
+ driverParam: boolean;
1350
+ notNull: true;
1351
+ hasDefault: true;
1352
+ isPrimaryKey: false;
1353
+ isAutoincrement: false;
1354
+ hasRuntimeDefault: false;
1355
+ enumValues: undefined;
1356
+ baseColumn: never;
1357
+ identity: undefined;
1358
+ generated: undefined;
1359
+ }, {}, {}>;
1360
+ metaMediaIds: drizzle_orm_pg_core.PgColumn<{
1361
+ name: "meta_media_ids";
1362
+ tableName: "flow_media";
1363
+ dataType: "json";
1364
+ columnType: "PgJsonb";
1365
+ data: Record<string, string>;
1366
+ driverParam: unknown;
1367
+ notNull: true;
1368
+ hasDefault: true;
1369
+ isPrimaryKey: false;
1370
+ isAutoincrement: false;
1371
+ hasRuntimeDefault: false;
1372
+ enumValues: undefined;
1373
+ baseColumn: never;
1374
+ identity: undefined;
1375
+ generated: undefined;
1376
+ }, {}, {
1377
+ $type: Record<string, string>;
1378
+ }>;
1379
+ createdAt: drizzle_orm_pg_core.PgColumn<{
1380
+ name: "created_at";
1381
+ tableName: "flow_media";
1382
+ dataType: "date";
1383
+ columnType: "PgTimestamp";
1384
+ data: Date;
1385
+ driverParam: string;
1386
+ notNull: true;
1387
+ hasDefault: true;
1388
+ isPrimaryKey: false;
1389
+ isAutoincrement: false;
1390
+ hasRuntimeDefault: false;
1391
+ enumValues: undefined;
1392
+ baseColumn: never;
1393
+ identity: undefined;
1394
+ generated: undefined;
1395
+ }, {}, {}>;
1396
+ updatedAt: drizzle_orm_pg_core.PgColumn<{
1397
+ name: "updated_at";
1398
+ tableName: "flow_media";
1399
+ dataType: "date";
1400
+ columnType: "PgTimestamp";
1401
+ data: Date;
1402
+ driverParam: string;
1403
+ notNull: true;
1404
+ hasDefault: true;
1405
+ isPrimaryKey: false;
1406
+ isAutoincrement: false;
1407
+ hasRuntimeDefault: false;
1408
+ enumValues: undefined;
1409
+ baseColumn: never;
1410
+ identity: undefined;
1411
+ generated: undefined;
1412
+ }, {}, {}>;
1413
+ };
1414
+ dialect: "pg";
1415
+ }>;
1416
+ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
1417
+ name: "settings";
1418
+ schema: "meta_whatsapp";
1419
+ columns: {
1420
+ companyId: drizzle_orm_pg_core.PgColumn<{
1421
+ name: "company_id";
1422
+ tableName: "settings";
1423
+ dataType: "string";
1424
+ columnType: "PgUUID";
1425
+ data: string;
1426
+ driverParam: string;
1427
+ notNull: true;
1428
+ hasDefault: false;
1429
+ isPrimaryKey: true;
1430
+ isAutoincrement: false;
1431
+ hasRuntimeDefault: false;
1432
+ enumValues: undefined;
1433
+ baseColumn: never;
1434
+ identity: undefined;
1435
+ generated: undefined;
1436
+ }, {}, {}>;
1437
+ templateName: drizzle_orm_pg_core.PgColumn<{
1438
+ name: "template_name";
1439
+ tableName: "settings";
1440
+ dataType: "string";
1441
+ columnType: "PgVarchar";
1442
+ data: string;
1443
+ driverParam: string;
1444
+ notNull: false;
1445
+ hasDefault: false;
1446
+ isPrimaryKey: false;
1447
+ isAutoincrement: false;
1448
+ hasRuntimeDefault: false;
1449
+ enumValues: [string, ...string[]];
1450
+ baseColumn: never;
1451
+ identity: undefined;
1452
+ generated: undefined;
1453
+ }, {}, {
1454
+ length: 128;
1455
+ }>;
1456
+ templateLanguage: drizzle_orm_pg_core.PgColumn<{
1457
+ name: "template_language";
1458
+ tableName: "settings";
1459
+ dataType: "string";
1460
+ columnType: "PgVarchar";
1461
+ data: string;
1462
+ driverParam: string;
1463
+ notNull: true;
1464
+ hasDefault: true;
1465
+ isPrimaryKey: false;
1466
+ isAutoincrement: false;
1467
+ hasRuntimeDefault: false;
1468
+ enumValues: [string, ...string[]];
1469
+ baseColumn: never;
1470
+ identity: undefined;
1471
+ generated: undefined;
1472
+ }, {}, {
1473
+ length: 16;
1474
+ }>;
1475
+ templateVariables: drizzle_orm_pg_core.PgColumn<{
1476
+ name: "template_variables";
1477
+ tableName: "settings";
1478
+ dataType: "json";
1479
+ columnType: "PgJsonb";
1480
+ data: string[];
1481
+ driverParam: unknown;
1482
+ notNull: true;
818
1483
  hasDefault: true;
819
1484
  isPrimaryKey: false;
820
1485
  isAutoincrement: false;
@@ -838,17 +1503,51 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
838
1503
  isPrimaryKey: false;
839
1504
  isAutoincrement: false;
840
1505
  hasRuntimeDefault: false;
841
- enumValues: [string, ...string[]];
1506
+ enumValues: [string, ...string[]];
1507
+ baseColumn: never;
1508
+ identity: undefined;
1509
+ generated: undefined;
1510
+ }, {}, {}>;
1511
+ farewellMessage: drizzle_orm_pg_core.PgColumn<{
1512
+ name: "farewell_message";
1513
+ tableName: "settings";
1514
+ dataType: "string";
1515
+ columnType: "PgText";
1516
+ data: string;
1517
+ driverParam: string;
1518
+ notNull: false;
1519
+ hasDefault: false;
1520
+ isPrimaryKey: false;
1521
+ isAutoincrement: false;
1522
+ hasRuntimeDefault: false;
1523
+ enumValues: [string, ...string[]];
1524
+ baseColumn: never;
1525
+ identity: undefined;
1526
+ generated: undefined;
1527
+ }, {}, {}>;
1528
+ transcriptionEnabled: drizzle_orm_pg_core.PgColumn<{
1529
+ name: "transcription_enabled";
1530
+ tableName: "settings";
1531
+ dataType: "boolean";
1532
+ columnType: "PgBoolean";
1533
+ data: boolean;
1534
+ driverParam: boolean;
1535
+ notNull: false;
1536
+ hasDefault: false;
1537
+ isPrimaryKey: false;
1538
+ isAutoincrement: false;
1539
+ hasRuntimeDefault: false;
1540
+ enumValues: undefined;
842
1541
  baseColumn: never;
843
1542
  identity: undefined;
844
1543
  generated: undefined;
845
1544
  }, {}, {}>;
846
- farewellMessage: drizzle_orm_pg_core.PgColumn<{
847
- name: "farewell_message";
1545
+ transcriptionMode: drizzle_orm_pg_core.PgColumn<{
1546
+ name: "transcription_mode";
848
1547
  tableName: "settings";
849
1548
  dataType: "string";
850
- columnType: "PgText";
851
- data: string;
1549
+ columnType: "PgVarchar";
1550
+ data: TranscriptionMode;
852
1551
  driverParam: string;
853
1552
  notNull: false;
854
1553
  hasDefault: false;
@@ -859,7 +1558,10 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
859
1558
  baseColumn: never;
860
1559
  identity: undefined;
861
1560
  generated: undefined;
862
- }, {}, {}>;
1561
+ }, {}, {
1562
+ length: 16;
1563
+ $type: TranscriptionMode;
1564
+ }>;
863
1565
  createdAt: drizzle_orm_pg_core.PgColumn<{
864
1566
  name: "created_at";
865
1567
  tableName: "settings";
@@ -905,6 +1607,10 @@ type MessageRow = typeof messages.$inferSelect;
905
1607
  type NewMessageRow = typeof messages.$inferInsert;
906
1608
  type FlowGraphRow = typeof flowGraphs.$inferSelect;
907
1609
  type NewFlowGraphRow = typeof flowGraphs.$inferInsert;
1610
+ type DocumentRow = typeof documents.$inferSelect;
1611
+ type NewDocumentRow = typeof documents.$inferInsert;
1612
+ type FlowMediaRow = typeof flowMedia.$inferSelect;
1613
+ type NewFlowMediaRow = typeof flowMedia.$inferInsert;
908
1614
 
909
1615
  interface ListConversationsFilters {
910
1616
  page?: number;
@@ -917,13 +1623,23 @@ declare class SessionRepository {
917
1623
  constructor(db: MetaWhatsAppDatabase);
918
1624
  getContext(companyId: string, whatsappNumber: string): Promise<SessionRow | undefined>;
919
1625
  getOrCreate(companyId: string, whatsappNumber: string, startState: SessionState): Promise<SessionRow>;
920
- setState(companyId: string, whatsappNumber: string, state: SessionState, context?: Record<string, unknown>): Promise<void>;
1626
+ setState<TSessionContext extends Record<string, unknown> = Record<string, unknown>>(companyId: string, whatsappNumber: string, state: SessionState, context?: TSessionContext): Promise<void>;
1627
+ patchContext<TSessionContext extends Record<string, unknown> = Record<string, unknown>>(companyId: string, whatsappNumber: string, patch: Partial<TSessionContext>): Promise<void>;
1628
+ readContext<TSessionContext extends Record<string, unknown> = Record<string, unknown>>(companyId: string, whatsappNumber: string): Promise<TSessionContext | undefined>;
921
1629
  setFlowPosition(companyId: string, whatsappNumber: string, flowKey: string | null, currentNodeId: string | null): Promise<void>;
922
1630
  touchInbound(companyId: string, whatsappNumber: string): Promise<void>;
923
1631
  hoursSinceLastInbound(companyId: string, whatsappNumber: string): Promise<number | undefined>;
924
1632
  setMode(companyId: string, whatsappNumber: string, mode: SessionMode, assignedUserId?: string | null): Promise<void>;
925
1633
  takeover(companyId: string, whatsappNumber: string, agentUserId: string): Promise<void>;
926
1634
  release(companyId: string, whatsappNumber: string): Promise<void>;
1635
+ /**
1636
+ * Apaga a sessão; a cascata das FKs leva mensagens e documentos.
1637
+ *
1638
+ * Não apaga o binário no storage — isso é passo de aplicação, e é por isso que este método é
1639
+ * chamado por `DeleteConversationUseCase` e não diretamente pelo host. Chamar daqui sem apagar os
1640
+ * objetos antes deixa mídia órfã sendo cobrada para sempre.
1641
+ */
1642
+ deleteByNumber(companyId: string, whatsappNumber: string): Promise<void>;
927
1643
  requestHuman(companyId: string, whatsappNumber: string): Promise<void>;
928
1644
  markRead(companyId: string, whatsappNumber: string): Promise<void>;
929
1645
  markAllRead(companyId: string, userId: string): Promise<number>;
@@ -960,11 +1676,108 @@ declare class SessionRepository {
960
1676
  waMessageId: string | null;
961
1677
  status: string | null;
962
1678
  readAt: Date | null;
1679
+ moderationFlagged: boolean | null;
1680
+ moderationTerms: string[] | null;
1681
+ transcriptionStatus: TranscriptionStatus | null;
1682
+ transcriptionText: string | null;
1683
+ transcriptionLanguage: string | null;
1684
+ transcriptionEngine: string | null;
963
1685
  createdAt: Date;
964
1686
  }[];
965
1687
  } | null>;
966
1688
  }
967
1689
 
1690
+ interface InsertMessageParams {
1691
+ companyId: string;
1692
+ sessionId: string;
1693
+ whatsappNumber: string;
1694
+ direction: MessageDirection;
1695
+ sender: MessageSender;
1696
+ agentUserId?: string | null;
1697
+ type: string;
1698
+ content?: string | null;
1699
+ payload?: Record<string, unknown> | null;
1700
+ waMessageId?: string | null;
1701
+ status?: MessageStatus | null;
1702
+ /** `undefined` deixa a coluna nula: não avaliado, distinto de avaliado e limpo. */
1703
+ moderationFlagged?: boolean | null;
1704
+ moderationTerms?: string[] | null;
1705
+ }
1706
+ interface ListMessagesParams$1 {
1707
+ companyId: string;
1708
+ sessionId: string;
1709
+ limit?: number;
1710
+ before?: string;
1711
+ }
1712
+ interface SaveTranscriptionByWaMessageIdParams extends Omit<SaveTranscriptionParams, 'messageId'> {
1713
+ /**
1714
+ * Id da mensagem na Meta. É o único que quem processa o webhook conhece — o id do módulo só
1715
+ * existe depois da gravação, e obrigar o host a descobri-lo faria cada um escrever a própria
1716
+ * consulta por `wa_message_id`.
1717
+ */
1718
+ waMessageId: string;
1719
+ }
1720
+ interface SaveTranscriptionParams {
1721
+ companyId: string;
1722
+ messageId: string;
1723
+ status: TranscriptionStatus;
1724
+ /** Ausente em `pending`/`failed`/`unsupported`; vazio em `done` é silêncio já processado. */
1725
+ text?: string | null;
1726
+ language?: string | null;
1727
+ engine?: string | null;
1728
+ }
1729
+ declare class MessageRepository {
1730
+ private readonly db;
1731
+ constructor(db: MetaWhatsAppDatabase);
1732
+ insertMessage(params: InsertMessageParams): Promise<MessageRow | undefined>;
1733
+ updateMessageStatus(companyId: string, waMessageId: string, status: MessageStatus): Promise<MessageRow | undefined>;
1734
+ /**
1735
+ * Grava a transcrição endereçando pelo id da Meta, para quem só tem esse.
1736
+ *
1737
+ * Serve ao caso em que a transcrição acontece no próprio webhook — o grafo precisa do texto para
1738
+ * responder ao cliente, e jogar fora o que ele já pagou para transcrever significaria transcrever
1739
+ * o mesmo áudio uma segunda vez só para o painel ver.
1740
+ *
1741
+ * Devolve `undefined` quando não achou a mensagem: entrega duplicada e mensagem apagada são
1742
+ * corridas normais, não erro.
1743
+ */
1744
+ saveTranscriptionByWaMessageId(params: SaveTranscriptionByWaMessageIdParams): Promise<MessageRow | undefined>;
1745
+ findById(companyId: string, messageId: string): Promise<MessageRow | undefined>;
1746
+ /**
1747
+ * Grava o resultado da transcrição. Devolve `undefined` quando a mensagem não existe (apagada
1748
+ * entre o enfileiramento e a execução do job) — não é erro, é corrida normal.
1749
+ *
1750
+ * `text`/`language`/`engine` só são tocados quando informados: uma retentativa que volta a falhar
1751
+ * atualiza o status sem apagar a transcrição parcial de uma tentativa anterior que tenha vindo de
1752
+ * outro engine da cadeia.
1753
+ */
1754
+ saveTranscription(params: SaveTranscriptionParams): Promise<MessageRow | undefined>;
1755
+ listByConversation(params: ListMessagesParams$1): Promise<MessageRow[]>;
1756
+ }
1757
+
1758
+ declare const DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS = 300;
1759
+ /**
1760
+ * Cache de leitura dos grafos de fluxo, com invalidação na publicação.
1761
+ *
1762
+ * TTL **e** invalidação explícita, e não um dos dois: só TTL deixaria o cliente andando no grafo
1763
+ * antigo até a chave expirar, logo depois de alguém corrigir o fluxo no editor; só invalidação
1764
+ * deixaria cache envenenado para sempre se um `delete` se perdesse (Redis reiniciando, deploy no
1765
+ * meio da escrita).
1766
+ *
1767
+ * Toda operação é tolerante a falha por decisão de projeto: cache é aceleração, não dependência.
1768
+ * Redis fora do ar tem que degradar para leitura no banco — que é exatamente o comportamento de
1769
+ * quem não configura cache nenhum — em vez de derrubar a conversa do cliente.
1770
+ */
1771
+ declare class FlowGraphCache {
1772
+ private readonly provider;
1773
+ private readonly ttlSeconds;
1774
+ constructor(provider: CacheInterface, ttlSeconds?: number);
1775
+ private keyFor;
1776
+ read(companyId: string, flowKey: string): Promise<FlowGraphData | undefined>;
1777
+ write(companyId: string, graph: FlowGraphData): Promise<void>;
1778
+ invalidate(companyId: string, flowKey: string): Promise<void>;
1779
+ }
1780
+
968
1781
  declare class InvalidFlowGraphError extends Error {
969
1782
  readonly validationMessage: string;
970
1783
  constructor(key: string, validationMessage: string);
@@ -974,7 +1787,8 @@ declare class OptimisticLockError extends Error {
974
1787
  }
975
1788
  declare class FlowGraphRepository {
976
1789
  private readonly db;
977
- constructor(db: MetaWhatsAppDatabase);
1790
+ private readonly cache?;
1791
+ constructor(db: MetaWhatsAppDatabase, cache?: FlowGraphCache | undefined);
978
1792
  get(companyId: string, key: string): Promise<FlowGraphData | undefined>;
979
1793
  list(companyId: string): Promise<FlowGraphSummary[]>;
980
1794
  private assertValidNodes;
@@ -995,42 +1809,124 @@ declare class SettingsRepository {
995
1809
  resolveTemplateVariables(companyId: string, context: Record<string, unknown>): Promise<string[]>;
996
1810
  }
997
1811
 
998
- interface InsertMessageParams {
1812
+ type LogMessageParams = Omit<InsertMessageParams, 'sessionId'> & {
1813
+ startState: SessionState;
1814
+ };
1815
+ /**
1816
+ * O contrato mínimo de `@adatechnology/text-moderation`, declarado aqui em vez de importado.
1817
+ *
1818
+ * Assim o módulo não ganha dependência de pacote para um recurso opcional, e — mais importante —
1819
+ * moderação não fica amarrada a WhatsApp: o que atravessa esta fronteira é texto, e qualquer canal
1820
+ * que passe a escrever no transcript por este use case herda a marcação sem tocar nada aqui.
1821
+ */
1822
+ type MessageModerator = {
1823
+ inspect: (text: string) => {
1824
+ isOffensive: boolean;
1825
+ matchedTerms: readonly string[];
1826
+ };
1827
+ };
1828
+ declare class LogMessageUseCase {
1829
+ private readonly sessionRepository;
1830
+ private readonly messageRepository;
1831
+ private readonly realtime?;
1832
+ private readonly moderator?;
1833
+ constructor(sessionRepository: SessionRepository, messageRepository: MessageRepository, realtime?: RealtimeNotifierInterface | undefined, moderator?: MessageModerator | undefined);
1834
+ execute(params: LogMessageParams): Promise<MessageRow | undefined>;
1835
+ private moderationOf;
1836
+ }
1837
+
1838
+ interface LinkDocumentParams {
999
1839
  companyId: string;
1000
1840
  sessionId: string;
1001
- whatsappNumber: string;
1002
- direction: MessageDirection;
1003
- sender: MessageSender;
1004
- agentUserId?: string | null;
1005
- type: string;
1006
- content?: string | null;
1007
- payload?: Record<string, unknown> | null;
1008
- waMessageId?: string | null;
1009
- status?: MessageStatus | null;
1841
+ messageId?: string | null;
1842
+ uploadId: string;
1843
+ filename: string;
1844
+ mimeType: string;
1845
+ sizeBytes: number;
1846
+ sha256?: string | null;
1847
+ source: string;
1010
1848
  }
1011
- interface ListMessagesParams$1 {
1849
+ interface ListDocumentsParams {
1012
1850
  companyId: string;
1013
1851
  sessionId: string;
1852
+ search?: string;
1853
+ /**
1854
+ * Origens aceitas, como lista explícita (`['agent', 'bot']`) e não como apelido de UI.
1855
+ *
1856
+ * Agrupamento tipo "Equipe" é vocabulário de tela e muda por produto; traduzir aqui obrigaria o
1857
+ * módulo a conhecer o rótulo de cada host. Quem recebe o apelido na borda é a rota.
1858
+ */
1859
+ sources?: readonly string[];
1860
+ sortDirection?: 'asc' | 'desc';
1861
+ page?: number;
1014
1862
  limit?: number;
1015
- before?: string;
1016
1863
  }
1017
- declare class MessageRepository {
1864
+ interface ListCompanyDocumentsParams$1 {
1865
+ companyId: string;
1866
+ search?: string;
1867
+ sources?: readonly string[];
1868
+ sortDirection?: 'asc' | 'desc';
1869
+ page?: number;
1870
+ limit?: number;
1871
+ }
1872
+ interface CompanyDocumentRow {
1873
+ id: string;
1874
+ uploadId: string;
1875
+ filename: string;
1876
+ mimeType: string;
1877
+ sizeBytes: number;
1878
+ source: string;
1879
+ linkedAt: Date;
1880
+ whatsappNumber: string;
1881
+ }
1882
+ interface ListCompanyDocumentsResult {
1883
+ rows: CompanyDocumentRow[];
1884
+ total: number;
1885
+ }
1886
+ interface ListDocumentsResult {
1887
+ rows: DocumentRow[];
1888
+ /** Total no servidor, ANTES do corte de página — é o que permite calcular a última página. */
1889
+ total: number;
1890
+ }
1891
+ declare class DocumentRepository {
1018
1892
  private readonly db;
1019
1893
  constructor(db: MetaWhatsAppDatabase);
1020
- insertMessage(params: InsertMessageParams): Promise<MessageRow | undefined>;
1021
- updateMessageStatus(companyId: string, waMessageId: string, status: MessageStatus): Promise<MessageRow | undefined>;
1022
- listByConversation(params: ListMessagesParams$1): Promise<MessageRow[]>;
1023
- }
1024
-
1025
- type LogMessageParams = Omit<InsertMessageParams, 'sessionId'> & {
1026
- startState: SessionState;
1027
- };
1028
- declare class LogMessageUseCase {
1029
- private readonly sessionRepository;
1030
- private readonly messageRepository;
1031
- private readonly realtime?;
1032
- constructor(sessionRepository: SessionRepository, messageRepository: MessageRepository, realtime?: RealtimeNotifierInterface | undefined);
1033
- execute(params: LogMessageParams): Promise<MessageRow | undefined>;
1894
+ /**
1895
+ * Idempotente por (companyId, uploadId), garantido pelo índice único e não por SELECT prévio: o
1896
+ * job de ingestão é reentregue por retry e duas tentativas concorrentes passariam as duas por uma
1897
+ * checagem, duplicando a linha no painel.
1898
+ *
1899
+ * Devolve `undefined` quando o documento já estava linkado.
1900
+ */
1901
+ link(params: LinkDocumentParams): Promise<DocumentRow | undefined>;
1902
+ listByConversation(params: ListDocumentsParams): Promise<ListDocumentsResult>;
1903
+ /**
1904
+ * A biblioteca da EMPRESA inteira, não de uma conversa.
1905
+ *
1906
+ * A busca casa nome do arquivo OU telefone da conversa — ver `companyDocumentSearch`.
1907
+ *
1908
+ * Faz join com `sessions` para carregar de qual conversa cada arquivo veio — numa lista global,
1909
+ * arquivo sem essa referência é inútil: o atendente vê "comprovante.pdf" e não sabe de quem.
1910
+ *
1911
+ * Ordena por `linkedAt` apoiada no índice `idx_documents_company_linked`, que já existia para a
1912
+ * varredura de retenção.
1913
+ */
1914
+ listByCompany(params: ListCompanyDocumentsParams$1): Promise<ListCompanyDocumentsResult>;
1915
+ /**
1916
+ * Um documento pela key do objeto. Serve para recuperar o nome original na hora de assinar o
1917
+ * download: a key é caminho no bucket e salvaria o arquivo com o id da Meta.
1918
+ */
1919
+ findByUploadId(companyId: string, uploadId: string): Promise<DocumentRow | undefined>;
1920
+ /**
1921
+ * Os objetos a apagar no storage antes de a linha sumir.
1922
+ *
1923
+ * Existe porque a cascata da FK apaga a linha e deixa o binário órfão: quem for apagar a conversa
1924
+ * precisa desta lista primeiro, senão paga armazenamento para sempre por arquivo inalcançável.
1925
+ */
1926
+ listUploadIdsBySession(companyId: string, sessionId: string): Promise<string[]>;
1927
+ /** Varredura de retenção por idade — o par é o mesmo cuidado com o objeto no storage. */
1928
+ listExpired(companyId: string, olderThan: Date, limit?: number): Promise<DocumentRow[]>;
1929
+ deleteById(companyId: string, id: string): Promise<void>;
1034
1930
  }
1035
1931
 
1036
1932
  type SendTextParams = {
@@ -1067,7 +1963,8 @@ declare class SendMessageUseCase {
1067
1963
  private readonly sessionRepository;
1068
1964
  private readonly logMessage;
1069
1965
  private readonly objectStorage?;
1070
- constructor(channel: ChannelAdapterInterface, sessionRepository: SessionRepository, logMessage: LogMessageUseCase, objectStorage?: ObjectStorageInterface | undefined);
1966
+ private readonly documentRepository?;
1967
+ constructor(channel: ChannelAdapterInterface, sessionRepository: SessionRepository, logMessage: LogMessageUseCase, objectStorage?: ObjectStorageInterface | undefined, documentRepository?: DocumentRepository | undefined);
1071
1968
  private assertWithinWindow;
1072
1969
  sendText(params: SendTextParams): Promise<MessageRow | undefined>;
1073
1970
  sendMedia(params: SendMediaParams): Promise<MessageRow | undefined>;
@@ -1120,6 +2017,143 @@ declare class ListMessagesUseCase {
1120
2017
  execute(params: ListMessagesParams): Promise<MessageRow[]>;
1121
2018
  }
1122
2019
 
2020
+ type ListConversationDocumentsParams = {
2021
+ companyId: string;
2022
+ whatsappNumber: string;
2023
+ search?: string;
2024
+ /** Origens explícitas. O apelido de UI ("Equipe") é traduzido na borda HTTP, não aqui. */
2025
+ sources?: readonly string[];
2026
+ sortDirection?: 'asc' | 'desc';
2027
+ page?: number;
2028
+ limit?: number;
2029
+ };
2030
+ /**
2031
+ * O que o `ConversationDocumentsPanel` do conversations-ui consome. O shape é o do pacote de UI
2032
+ * (`ConversationDocument`), com `linkedAt` já em ISO para não obrigar cada host a serializar.
2033
+ */
2034
+ type ConversationDocumentView = {
2035
+ /**
2036
+ * O `uploadId` (key no storage), e NÃO o id da linha.
2037
+ *
2038
+ * É este valor que o consumidor devolve para pedir a URL assinada ou montar o zip, então expor o
2039
+ * UUID da tabela aqui fazia o download apontar para um objeto inexistente — falha que só aparece
2040
+ * no clique, porque a assinatura é gerada sem consultar o bucket.
2041
+ */
2042
+ id: string;
2043
+ filename: string;
2044
+ mimeType: string;
2045
+ sizeBytes: number;
2046
+ source: string;
2047
+ linkedAt: string;
2048
+ };
2049
+ /** Espelha o `ConversationDocumentPage` do conversations-ui: lista da página + total no servidor. */
2050
+ type ConversationDocumentsPage = {
2051
+ documents: ConversationDocumentView[];
2052
+ total: number;
2053
+ };
2054
+ declare class ListConversationDocumentsUseCase {
2055
+ private readonly sessionRepository;
2056
+ private readonly documentRepository;
2057
+ constructor(sessionRepository: SessionRepository, documentRepository: DocumentRepository);
2058
+ execute(params: ListConversationDocumentsParams): Promise<ConversationDocumentsPage>;
2059
+ }
2060
+
2061
+ type ListCompanyDocumentsParams = {
2062
+ companyId: string;
2063
+ search?: string;
2064
+ /** Origens explícitas. O apelido de UI ("Equipe") é traduzido na borda HTTP, não aqui. */
2065
+ sources?: readonly string[];
2066
+ sortDirection?: 'asc' | 'desc';
2067
+ page?: number;
2068
+ limit?: number;
2069
+ };
2070
+ /**
2071
+ * Um arquivo na biblioteca da empresa. Igual ao da conversa, mais `conversationId` — sem saber de
2072
+ * quem veio, uma lista global de anexos não responde nenhuma pergunta útil.
2073
+ */
2074
+ type CompanyDocumentView = {
2075
+ /** O `uploadId` (key no storage), pelo mesmo motivo do `ConversationDocumentView`. */
2076
+ id: string;
2077
+ conversationId: string;
2078
+ filename: string;
2079
+ mimeType: string;
2080
+ sizeBytes: number;
2081
+ source: string;
2082
+ linkedAt: string;
2083
+ };
2084
+ type CompanyDocumentsPage = {
2085
+ documents: CompanyDocumentView[];
2086
+ total: number;
2087
+ };
2088
+ /**
2089
+ * Biblioteca de arquivos de todas as conversas da empresa.
2090
+ *
2091
+ * Existe separada de `ListConversationDocumentsUseCase` porque a pergunta é outra: aquela parte de
2092
+ * uma conversa conhecida, esta varre a empresa e por isso precisa dizer de qual conversa cada
2093
+ * arquivo veio. Reaproveitar a primeira exigiria um `sessionId` opcional que muda o significado do
2094
+ * retorno — dois nomes claros custam menos que um parâmetro que dobra o comportamento.
2095
+ */
2096
+ declare class ListCompanyDocumentsUseCase {
2097
+ private readonly documentRepository;
2098
+ constructor(documentRepository: DocumentRepository);
2099
+ execute(params: ListCompanyDocumentsParams): Promise<CompanyDocumentsPage>;
2100
+ }
2101
+
2102
+ type DeleteConversationParams = {
2103
+ companyId: string;
2104
+ whatsappNumber: string;
2105
+ };
2106
+ type DeleteConversationResult = {
2107
+ /** Objetos efetivamente apagados no storage. */
2108
+ deletedObjects: number;
2109
+ /** Objetos que o storage recusou. A conversa NÃO é apagada quando isto é maior que zero. */
2110
+ failedObjects: readonly string[];
2111
+ };
2112
+ /**
2113
+ * Apaga a conversa e a mídia dela.
2114
+ *
2115
+ * A ordem é o ponto: **storage primeiro, banco depois**. A FK de `documents.session_id` é
2116
+ * `on delete cascade`, então apagar a sessão primeiro derrubaria as linhas e levaria embora a única
2117
+ * lista de `uploadId` existente — os binários ficariam órfãos, cobrados para sempre e inalcançáveis.
2118
+ *
2119
+ * Se algum objeto falhar, a conversa é preservada e o chamador recebe a lista. Apagar as linhas
2120
+ * "mesmo assim" transformaria uma falha visível e reexecutável em lixo silencioso no storage.
2121
+ */
2122
+ declare class DeleteConversationUseCase {
2123
+ private readonly sessionRepository;
2124
+ private readonly documentRepository;
2125
+ private readonly objectStorage?;
2126
+ constructor(sessionRepository: SessionRepository, documentRepository: DocumentRepository, objectStorage?: ObjectStorageInterface | undefined);
2127
+ execute(params: DeleteConversationParams): Promise<DeleteConversationResult>;
2128
+ }
2129
+
2130
+ type PurgeExpiredDocumentsParams = {
2131
+ companyId: string;
2132
+ /** Dias de retenção. O produto configura; o módulo não escolhe política de dado pessoal. */
2133
+ retentionDays: number;
2134
+ /** Teto por execução, para o job não segurar conexão nem storage por tempo indefinido. */
2135
+ batchSize?: number;
2136
+ /** Instante de referência — injetado para o teste não depender do relógio. */
2137
+ now?: Date;
2138
+ };
2139
+ type PurgeExpiredDocumentsResult = {
2140
+ purged: number;
2141
+ failed: readonly string[];
2142
+ };
2143
+ /**
2144
+ * Apaga documento vencido: objeto no storage primeiro, linha depois — a mesma ordem do
2145
+ * `DeleteConversationUseCase`, e pelo mesmo motivo.
2146
+ *
2147
+ * A linha só cai quando o objeto caiu. Contar como purgado sem ter apagado o binário deixaria lixo
2148
+ * pago no storage sem nenhum registro que o alcance depois: a linha era o único ponteiro.
2149
+ */
2150
+ declare class PurgeExpiredDocumentsUseCase {
2151
+ private readonly documentRepository;
2152
+ private readonly objectStorage?;
2153
+ constructor(documentRepository: DocumentRepository, objectStorage?: ObjectStorageInterface | undefined);
2154
+ execute(params: PurgeExpiredDocumentsParams): Promise<PurgeExpiredDocumentsResult>;
2155
+ }
2156
+
1123
2157
  type ExportConversationParams = {
1124
2158
  companyId: string;
1125
2159
  whatsappNumber: string;
@@ -1241,10 +2275,77 @@ declare class FlowInterpreter {
1241
2275
  }): Promise<FlowRunResult>;
1242
2276
  }
1243
2277
 
2278
+ type FlowMediaLocation = {
2279
+ companyId: string;
2280
+ flowKey: string;
2281
+ nodeId: string;
2282
+ };
2283
+ type AttachFlowMediaParams = FlowMediaLocation & {
2284
+ uploadId: string;
2285
+ filename: string;
2286
+ mimeType: string;
2287
+ sizeBytes: number;
2288
+ caption?: string;
2289
+ sortOrder?: number;
2290
+ };
2291
+ type UpdateFlowMediaParams = {
2292
+ companyId: string;
2293
+ id: string;
2294
+ caption?: string | null;
2295
+ sortOrder?: number;
2296
+ active?: boolean;
2297
+ };
2298
+ declare class FlowMediaRepository {
2299
+ private readonly db;
2300
+ constructor(db: MetaWhatsAppDatabase);
2301
+ private locationFilter;
2302
+ listActive(location: FlowMediaLocation): Promise<FlowMediaRow[]>;
2303
+ listAll(location: FlowMediaLocation): Promise<FlowMediaRow[]>;
2304
+ /**
2305
+ * Anexa um arquivo já existente no storage ao nó.
2306
+ *
2307
+ * `onConflictDoUpdate` em vez de deixar estourar: reanexar o mesmo arquivo é clique repetido no
2308
+ * editor, e o esperado ali é atualizar a legenda/ordem — não um erro de índice único na cara de
2309
+ * quem está montando o fluxo.
2310
+ */
2311
+ attach(params: AttachFlowMediaParams): Promise<FlowMediaRow>;
2312
+ update(params: UpdateFlowMediaParams): Promise<FlowMediaRow | undefined>;
2313
+ /**
2314
+ * Desanexa do nó. Não toca no storage de propósito: o mesmo `uploadId` pode estar anexado a
2315
+ * outro nó ou a outro fluxo, e apagar o binário aqui quebraria os demais. Quem apaga objeto é o
2316
+ * host, que é dono da biblioteca de arquivos.
2317
+ */
2318
+ detach(params: {
2319
+ companyId: string;
2320
+ id: string;
2321
+ }): Promise<void>;
2322
+ detachRemovedNodes(params: {
2323
+ companyId: string;
2324
+ flowKey: string;
2325
+ existingNodeIds: string[];
2326
+ }): Promise<void>;
2327
+ }
2328
+
2329
+ /**
2330
+ * Copyright (c) 2026 Ada Technology. MIT License.
2331
+ *
2332
+ * O algoritmo mora em `meta-graph-core`, compartilhado com os outros objetos que a Meta assina.
2333
+ * Aqui fica só a tradução para o vocabulário deste domínio: o erro lançado e o namespace do nonce.
2334
+ */
2335
+
1244
2336
  interface NonceStoreInterface {
1245
2337
  setIfAbsent(key: string, ttlSeconds: number): Promise<boolean>;
2338
+ /**
2339
+ * Estende a chave já reivindicada para a janela cheia. Um SET simples, sem NX.
2340
+ *
2341
+ * Opcional só por compatibilidade com hosts que ainda não o implementam: sem ele o claim curto
2342
+ * expira, e a Meta pode reentregar uma entrega já processada — trabalho repetido, que a dedupe
2343
+ * por `waMessageId` ainda segura antes de virar efeito visível ao cliente. Implementar é o
2344
+ * caminho correto.
2345
+ */
2346
+ confirm?(key: string, ttlSeconds: number): Promise<void>;
1246
2347
  }
1247
- declare const WEBHOOK_NONCE_TTL_SECONDS = 300;
2348
+
1248
2349
  declare function verifyWebhookChallenge(params: {
1249
2350
  mode: string | null;
1250
2351
  token: string | null;
@@ -1256,11 +2357,101 @@ declare function verifyWebhookSignature(params: {
1256
2357
  signatureHeader: string | null | undefined;
1257
2358
  appSecret: string;
1258
2359
  }): void;
2360
+ /**
2361
+ * Reivindica a entrega por pouco tempo. O par obrigatório é `confirmWebhookDelivery` ao fim do
2362
+ * processamento — ver `WEBHOOK_CLAIM_TTL_SECONDS` para o porquê dos dois tempos.
2363
+ */
1259
2364
  declare function claimWebhookDelivery(params: {
1260
2365
  nonceStore: NonceStoreInterface;
1261
2366
  signatureHeader: string;
1262
2367
  ttlSeconds?: number;
1263
2368
  }): Promise<boolean>;
2369
+ /** Só depois da entrega processada por inteiro: é isto que fecha a janela anti-replay. */
2370
+ declare function confirmWebhookDelivery(params: {
2371
+ nonceStore: NonceStoreInterface;
2372
+ signatureHeader: string;
2373
+ ttlSeconds?: number;
2374
+ }): Promise<void>;
2375
+
2376
+ /**
2377
+ * Copyright (c) 2026 Ada Technology. MIT License.
2378
+ *
2379
+ * Separa a entrega do webhook em duas metades: o que precisa acontecer antes de responder à Meta
2380
+ * (persistir a mensagem, que é escrita local e rápida) e o que pode esperar alguns milissegundos
2381
+ * (os hooks do host, que costumam falar com rede — motor de fluxo, IA, integrações).
2382
+ *
2383
+ * A segunda metade rodava dentro da requisição do webhook. Um deploy no meio de uma conversa
2384
+ * matava a chamada em voo, e o cliente ficava sem resposta sem que nada registrasse a perda. Com
2385
+ * uma fila no host, derrubar o processo vira atraso: o job espera e é retomado.
2386
+ */
2387
+
2388
+ type InboundMediaDescriptor = {
2389
+ sourceMediaId: string;
2390
+ mimeType: string;
2391
+ filename?: string;
2392
+ };
2393
+ type InboundMessageEffectsJob = {
2394
+ kind: 'message';
2395
+ companyId: string;
2396
+ message: WhatsAppMessage;
2397
+ savedMessageId: string;
2398
+ media?: InboundMediaDescriptor;
2399
+ receivedAt: number;
2400
+ };
2401
+ type InboundStatusEffectsJob = {
2402
+ kind: 'status';
2403
+ companyId: string;
2404
+ status: WhatsAppStatus;
2405
+ whatsappNumber: string;
2406
+ receivedAt: number;
2407
+ };
2408
+ type InboundDispatchJob = InboundMessageEffectsJob | InboundStatusEffectsJob;
2409
+ /**
2410
+ * Porta de fila. O módulo não escolhe a tecnologia — BullMQ, SQS, o que o host já tiver — mas
2411
+ * exige dela duas garantias, e sem as duas o padrão não entrega o que promete:
2412
+ *
2413
+ * 1. **Durabilidade**: o job sobrevive à morte do processo que o enfileirou. Uma fila em memória
2414
+ * reintroduz exatamente a perda que este desenho existe para evitar.
2415
+ * 2. **Retentativa com backoff**: o destino dos hooks (n8n, IA, API de terceiro) também cai em
2416
+ * deploy. Sem retry, o job falha uma vez e a conversa morre do mesmo jeito.
2417
+ *
2418
+ * `jobId` é estável e derivado da mensagem: reentrega da Meta e re-enfileiramento produzem o mesmo
2419
+ * id, e a fila descarta o segundo em vez de rodar o efeito duas vezes.
2420
+ */
2421
+ interface InboundDispatchQueueInterface {
2422
+ enqueue(job: InboundDispatchJob, options: {
2423
+ jobId: string;
2424
+ }): Promise<void>;
2425
+ }
2426
+ declare function buildInboundJobId(job: InboundDispatchJob): string;
2427
+ declare function toSessionContract(row: SessionRow): ConversationSession;
2428
+ /**
2429
+ * Os efeitos de host de uma entrega já persistida. Vive fora do `ReceiveWebhookUseCase` porque
2430
+ * roda nos dois lados: inline, quando o host não configurou fila, e dentro do worker, quando
2431
+ * configurou. Um só corpo de regra para os dois caminhos — duas cópias divergiriam.
2432
+ */
2433
+ declare class InboundEffectsDispatcher {
2434
+ private readonly params;
2435
+ constructor(params: {
2436
+ sessionRepository: SessionRepository;
2437
+ hooks?: MetaWhatsAppHooks;
2438
+ realtime?: RealtimeNotifierInterface;
2439
+ });
2440
+ run(job: InboundDispatchJob): Promise<void>;
2441
+ runMessageEffects(job: InboundMessageEffectsJob): Promise<void>;
2442
+ runStatusEffects(job: InboundStatusEffectsJob): Promise<void>;
2443
+ }
2444
+ /**
2445
+ * Ponto de entrada do worker do host: recebe o job que a fila devolveu e roda os efeitos.
2446
+ *
2447
+ * Deixe a exceção propagar. É ela que faz a fila contar a tentativa e reagendar com backoff;
2448
+ * capturar aqui para logar transforma falha recuperável em mensagem perdida em silêncio.
2449
+ */
2450
+ declare class ProcessInboundDispatchUseCase {
2451
+ private readonly dispatcher;
2452
+ constructor(dispatcher: InboundEffectsDispatcher);
2453
+ execute(job: InboundDispatchJob): Promise<void>;
2454
+ }
1264
2455
 
1265
2456
  type ReceiveWebhookParams = {
1266
2457
  companyId: string;
@@ -1271,11 +2462,15 @@ type ReceiveWebhookResult = {
1271
2462
  duplicate: boolean;
1272
2463
  messagesProcessed: number;
1273
2464
  statusesProcessed: number;
2465
+ ignoredForeignNumber: number;
2466
+ accountEventsProcessed: number;
2467
+ unhandledEvents: number;
1274
2468
  };
1275
2469
  declare class ReceiveWebhookUseCase {
1276
2470
  private readonly params;
1277
2471
  constructor(params: {
1278
2472
  appSecret: string;
2473
+ phoneNumberId: string;
1279
2474
  nonceStore: NonceStoreInterface;
1280
2475
  sessionRepository: SessionRepository;
1281
2476
  messageRepository: MessageRepository;
@@ -1283,12 +2478,53 @@ declare class ReceiveWebhookUseCase {
1283
2478
  startState: SessionState;
1284
2479
  hooks?: MetaWhatsAppHooks;
1285
2480
  realtime?: RealtimeNotifierInterface;
2481
+ /**
2482
+ * Sem fila o módulo se comporta como sempre: os hooks rodam dentro da requisição do webhook.
2483
+ * Configurá-la é o que faz a conversa sobreviver a um deploy no meio do atendimento.
2484
+ */
2485
+ inboundQueue?: InboundDispatchQueueInterface;
1286
2486
  });
2487
+ private readonly dispatcher;
1287
2488
  execute(input: ReceiveWebhookParams): Promise<ReceiveWebhookResult>;
2489
+ private handleAccountEvent;
1288
2490
  private handleMessage;
1289
2491
  private handleStatus;
2492
+ private dispatch;
1290
2493
  }
1291
2494
 
2495
+ /**
2496
+ * Política efetiva de transcrição de uma empresa.
2497
+ *
2498
+ * A separação que este arquivo existe para manter: **ambiente decide se é POSSÍVEL, settings decide
2499
+ * se é para FAZER.** A capacidade (engine, chave, storage que sabe reler) é injetada pelo host e é
2500
+ * por deploy — chave de API não vai para tabela de configuração de tenant. Já "transcrever ou não" e
2501
+ * "automático ou sob demanda" são decisão de operação de cada empresa, e pedir deploy para mudar
2502
+ * isso é o que transforma um interruptor em ticket.
2503
+ */
2504
+ type TranscriptionPolicy = {
2505
+ readonly isEnabled: boolean;
2506
+ readonly mode: TranscriptionMode;
2507
+ };
2508
+ type TranscriptionPolicyDefaults = {
2509
+ /** O que vale quando o painel não decidiu. Tipicamente vem do ambiente do host. */
2510
+ readonly isEnabled: boolean;
2511
+ readonly mode: TranscriptionMode;
2512
+ };
2513
+ type ResolveTranscriptionPolicyDependencies = {
2514
+ readonly settingsRepository: SettingsRepository;
2515
+ readonly defaults: TranscriptionPolicyDefaults;
2516
+ };
2517
+ /**
2518
+ * Resolve a política por empresa, com o padrão do host como base.
2519
+ *
2520
+ * Uma consulta a `settings` por áudio transcrito. Não é cacheado de propósito: é uma leitura por
2521
+ * chave primária, acontece uma vez por nota de voz (não por mensagem), e cachear introduziria a
2522
+ * pergunta "por quanto tempo o operador continua vendo o interruptor antigo depois de mexer nele" —
2523
+ * custo real, para economizar um índice único.
2524
+ */
2525
+ declare function createTranscriptionPolicyResolver(dependencies: ResolveTranscriptionPolicyDependencies): (companyId: string) => Promise<TranscriptionPolicy>;
2526
+ type TranscriptionPolicyResolver = ReturnType<typeof createTranscriptionPolicyResolver>;
2527
+
1292
2528
  type IngestInboundMediaParams = {
1293
2529
  companyId: string;
1294
2530
  messageId: string;
@@ -1299,13 +2535,52 @@ type IngestInboundMediaParams = {
1299
2535
  type IngestInboundMediaResult = {
1300
2536
  uploadId: string;
1301
2537
  alreadyIngested: boolean;
2538
+ /**
2539
+ * Só presente quando a transcrição automática rodou nesta execução. Ausente é o normal: mídia que
2540
+ * não é áudio, transcrição desligada, modo sob demanda, ou mídia já ingerida antes.
2541
+ */
2542
+ transcription?: {
2543
+ status: TranscriptionStatus;
2544
+ };
2545
+ };
2546
+ /**
2547
+ * Transcrição durante a ingestão — o modo `auto`.
2548
+ *
2549
+ * Entra aqui, e não em use-case separado, por um motivo só: neste ponto o buffer do áudio ACABOU de
2550
+ * ser baixado e está em memória. Transcrever fora daqui custaria um segundo download do storage por
2551
+ * áudio, e o `TranscribeAudioUseCase` existe justamente para esse caso (sob demanda e retomada).
2552
+ */
2553
+ type IngestTranscriptionOptions = {
2554
+ transcriber: AudioTranscriber;
2555
+ messageRepository: MessageRepository;
2556
+ /**
2557
+ * Política POR EMPRESA, resolvida a cada áudio. Não é um `mode` fixo porque o interruptor mora nas
2558
+ * configurações da empresa: um valor capturado na construção do use-case congelaria a escolha até
2559
+ * o próximo deploy, e o worker é um processo longo — o operador mexeria no painel e nada mudaria.
2560
+ */
2561
+ resolvePolicy: TranscriptionPolicyResolver;
2562
+ languageHint?: string;
2563
+ hooks?: Pick<MetaWhatsAppHooks, 'onTranscriptionDeferred'>;
1302
2564
  };
1303
2565
  declare class IngestInboundMediaUseCase {
1304
2566
  private readonly db;
1305
2567
  private readonly channel;
1306
2568
  private readonly objectStorage;
1307
- constructor(db: MetaWhatsAppDatabase, channel: ChannelAdapterInterface, objectStorage: ObjectStorageInterface);
2569
+ private readonly documentRepository?;
2570
+ private readonly transcription?;
2571
+ constructor(db: MetaWhatsAppDatabase, channel: ChannelAdapterInterface, objectStorage: ObjectStorageInterface, documentRepository?: DocumentRepository | undefined, transcription?: IngestTranscriptionOptions | undefined);
1308
2572
  execute(params: IngestInboundMediaParams): Promise<IngestInboundMediaResult>;
2573
+ /**
2574
+ * Transcreve o áudio recém-baixado, quando o modo é `auto`.
2575
+ *
2576
+ * **Nunca propaga erro.** Neste ponto o binário já está no storage e já entrou na biblioteca da
2577
+ * conversa: deixar uma falha de transcrição subir marcaria a ingestão inteira como falha, e o
2578
+ * retry do host baixaria de novo da Meta um arquivo que está salvo — gastando banda para reproduzir
2579
+ * um efeito que já aconteceu. O status fica gravado na mensagem e o `onTranscriptionDeferred`
2580
+ * avisa quem sabe reenfileirar.
2581
+ */
2582
+ private transcribeIfAuto;
2583
+ private recordTranscriptionFailure;
1309
2584
  }
1310
2585
  declare function extractMediaDescriptor(message: MessageRow): {
1311
2586
  sourceMediaId: string;
@@ -1313,6 +2588,105 @@ declare function extractMediaDescriptor(message: MessageRow): {
1313
2588
  filename?: string;
1314
2589
  } | undefined;
1315
2590
 
2591
+ /**
2592
+ * Storage com leitura garantida. `getObject` é opcional no contrato, então quem monta este use-case
2593
+ * precisa provar que o método existe — sem os bytes não há o que transcrever, e um use-case que
2594
+ * sempre falha é pior do que a ausência ser visível no tipo.
2595
+ */
2596
+ type ReadableObjectStorage = ObjectStorageInterface & {
2597
+ getObject: NonNullable<ObjectStorageInterface['getObject']>;
2598
+ };
2599
+ type TranscribeAudioParams = {
2600
+ companyId: string;
2601
+ messageId: string;
2602
+ /**
2603
+ * Refaz mesmo com transcrição já salva. Serve ao "transcrever de novo" depois de trocar de engine
2604
+ * — sem isto, um resultado ruim do engine antigo ficaria congelado para sempre.
2605
+ */
2606
+ force?: boolean;
2607
+ };
2608
+ type TranscribeAudioResult = {
2609
+ status: TranscriptionStatus;
2610
+ text: string | null;
2611
+ language: string | null;
2612
+ engine: string | null;
2613
+ /** `true` quando devolveu o que já estava salvo, sem gastar cota. */
2614
+ alreadyTranscribed: boolean;
2615
+ };
2616
+ type TranscribeAudioDependencies = {
2617
+ messageRepository: MessageRepository;
2618
+ objectStorage: ReadableObjectStorage;
2619
+ transcriber: AudioTranscriber;
2620
+ /**
2621
+ * Política por empresa. Ausente, a transcrição sob demanda não consulta configuração nenhuma e
2622
+ * atende sempre — que é o comportamento de quem controla o liga/desliga só por ambiente.
2623
+ */
2624
+ resolvePolicy?: TranscriptionPolicyResolver;
2625
+ /** ISO 639-1 do produto. Informar corta a detecção do Whisper e evita pt-BR curto virar espanhol. */
2626
+ languageHint?: string;
2627
+ hooks?: Pick<MetaWhatsAppHooks, 'onTranscriptionDeferred'>;
2628
+ };
2629
+ /**
2630
+ * Transcreve o áudio de UMA mensagem já persistida e ingerida.
2631
+ *
2632
+ * Serve aos dois modos: é o que o painel chama no botão "transcrever" (`onDemand`) e é o que o host
2633
+ * chama ao retomar um `pending` reenfileirado. O modo `auto` não passa por aqui — ele transcreve
2634
+ * dentro da ingestão, onde o buffer já está em memória e não custa um segundo download.
2635
+ *
2636
+ * Idempotente por `transcription_status`: chamar de novo num `'done'` devolve o que está salvo em
2637
+ * vez de gastar cota transcrevendo o mesmo áudio.
2638
+ */
2639
+ declare class TranscribeAudioUseCase {
2640
+ private readonly dependencies;
2641
+ constructor(dependencies: TranscribeAudioDependencies);
2642
+ execute(params: TranscribeAudioParams): Promise<TranscribeAudioResult>;
2643
+ private transcribeBuffer;
2644
+ /**
2645
+ * Carimba o motivo antes de propagar. O status é o que impede os dois desperdícios simétricos:
2646
+ * reprocessar para sempre um codec impossível, e desistir de um áudio que só esbarrou na cota.
2647
+ */
2648
+ private persistFailure;
2649
+ }
2650
+ declare function resolveFailureStatus(error: unknown): TranscriptionStatus;
2651
+
2652
+ /**
2653
+ * Guarda um arquivo gravado no simulador e devolve o id que o webhook vai referenciar.
2654
+ *
2655
+ * Existe como use-case para o host só precisar da ROTA: receber o corpo, chamar isto, devolver o
2656
+ * `mediaId`. A parte que erra — onde gravar, com que chave, como marcar o id para o adaptador
2657
+ * reconhecer depois — fica aqui, num lugar só, e não em cada produto.
2658
+ *
2659
+ * O SDK para exatamente na porta do HTTP: registrar endpoint é do host, e um pacote que abrisse rota
2660
+ * no servidor de quem o instala decidiria caminho, autenticação e versionamento no lugar dele.
2661
+ */
2662
+ type StorePreviewMediaParams = {
2663
+ companyId: string;
2664
+ /** Bytes do arquivo. Quem converte de base64 é a rota — o use-case não conhece transporte. */
2665
+ buffer: Buffer;
2666
+ mimeType: string;
2667
+ filename?: string;
2668
+ };
2669
+ type StorePreviewMediaResult = {
2670
+ /** Já com o prefixo: é isto que o simulador manda no webhook. */
2671
+ mediaId: string;
2672
+ uploadId: string;
2673
+ };
2674
+ declare class StorePreviewMediaUseCase {
2675
+ private readonly objectStorage;
2676
+ /**
2677
+ * Fonte do sufixo único da chave. Injetada porque o módulo não escolhe gerador de id — e porque
2678
+ * um teste precisa de chave previsível.
2679
+ */
2680
+ private readonly generateKeySuffix;
2681
+ constructor(objectStorage: ObjectStorageInterface,
2682
+ /**
2683
+ * Fonte do sufixo único da chave. Injetada porque o módulo não escolhe gerador de id — e porque
2684
+ * um teste precisa de chave previsível.
2685
+ */
2686
+ generateKeySuffix: () => string);
2687
+ execute(params: StorePreviewMediaParams): Promise<StorePreviewMediaResult>;
2688
+ }
2689
+
1316
2690
  interface MetaWhatsAppModuleConfig {
1317
2691
  phoneNumberId: string;
1318
2692
  accessToken: string;
@@ -1321,15 +2695,66 @@ interface MetaWhatsAppModuleConfig {
1321
2695
  wabaId?: string;
1322
2696
  apiVersion?: string;
1323
2697
  baseUrl?: string;
2698
+ /**
2699
+ * Catálogo do Meta Commerce que a vitrine no chat oferece. Sem ele — ou sem `providers.catalog`
2700
+ * — a action `send_product_list` não é registrada: nó que o editor oferece e que em silêncio não
2701
+ * faz nada é pior do que nó que não existe.
2702
+ */
2703
+ catalogId?: string;
1324
2704
  }
1325
2705
  interface MetaWhatsAppModuleFeatures {
1326
2706
  flowEngine?: boolean;
2707
+ flowGraphCache?: boolean | {
2708
+ ttlSeconds?: number;
2709
+ };
2710
+ /**
2711
+ * Aceita mídia do simulador de conversa — o que faz o microfone aparecer no preview do cliente.
2712
+ *
2713
+ * **Desligado por omissão, e a decisão é consciente.** Ligado, o canal passa a aceitar id que não
2714
+ * veio da Meta: `preview-upload:<chave>` faz o servidor ler aquele objeto do storage. Em ambiente
2715
+ * de simulação isso é o recurso; em produção é leitura arbitrária do bucket por webhook forjado.
2716
+ *
2717
+ * Exige `providers.objectStorage` com `getObject` — sem os bytes não há o que devolver, e o flag é
2718
+ * ignorado em vez de produzir um canal que falha na primeira nota de voz.
2719
+ */
2720
+ previewMedia?: boolean;
2721
+ }
2722
+ /**
2723
+ * Transcrição de áudio. Ausente = desligada, e as colunas ficam nulas ("não avaliado").
2724
+ *
2725
+ * `mode` é a decisão de produto que este objeto existe para carregar: `auto` transcreve toda nota de
2726
+ * voz recebida, na ingestão, onde o buffer já está em memória; `onDemand` só transcreve quando o
2727
+ * atendente pede, gastando cota apenas com áudio que alguém vai ler de fato.
2728
+ */
2729
+ interface MetaWhatsAppTranscriptionConfig {
2730
+ transcriber: AudioTranscriber;
2731
+ /**
2732
+ * PADRÃO, não decisão final: vale para as empresas que não mexeram no interruptor do painel. As
2733
+ * configurações por empresa (`settings.transcriptionMode`) têm precedência.
2734
+ *
2735
+ * Padrão `onDemand` — o modo que não gasta cota sem alguém pedir.
2736
+ */
2737
+ mode?: TranscriptionMode;
2738
+ /**
2739
+ * PADRÃO de ligado/desligado para empresas sem escolha registrada. `true` — injetar o transcritor
2740
+ * já é a declaração de que o host quer o recurso; quem controla por empresa usa o painel.
2741
+ */
2742
+ isEnabledByDefault?: boolean;
2743
+ /** ISO 639-1 do produto (ex.: `'pt'`). Corta a detecção de idioma do engine. */
2744
+ languageHint?: string;
1327
2745
  }
1328
2746
  interface MetaWhatsAppModuleProviders {
1329
2747
  objectStorage?: ObjectStorageInterface;
2748
+ cache?: CacheInterface;
1330
2749
  realtime?: RealtimeNotifierInterface;
1331
2750
  subjectResolver?: SubjectResolverInterface;
1332
2751
  catalog?: CatalogPort;
2752
+ moderator?: MessageModerator;
2753
+ /**
2754
+ * Converte nota de voz em texto. Exige `objectStorage` com `getObject` para o modo sob demanda —
2755
+ * transcrever um áudio já salvo significa ler os bytes de volta.
2756
+ */
2757
+ transcription?: MetaWhatsAppTranscriptionConfig;
1333
2758
  }
1334
2759
  interface CreateMetaWhatsAppModuleParams {
1335
2760
  db: MetaWhatsAppDatabase;
@@ -1350,10 +2775,31 @@ declare function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams
1350
2775
  release: ReleaseConversationUseCase;
1351
2776
  list: ListConversationsUseCase;
1352
2777
  listMessages: ListMessagesUseCase;
2778
+ listDocuments: ListConversationDocumentsUseCase;
2779
+ listCompanyDocuments: ListCompanyDocumentsUseCase;
2780
+ delete: DeleteConversationUseCase;
2781
+ purgeExpiredDocuments: PurgeExpiredDocumentsUseCase;
1353
2782
  export: ExportConversationUseCase;
2783
+ transcribeAudio: TranscribeAudioUseCase | undefined;
1354
2784
  repository: SessionRepository;
2785
+ messageRepository: MessageRepository;
2786
+ documentRepository: DocumentRepository;
1355
2787
  };
2788
+ /**
2789
+ * `undefined` = o host não injetou transcritor, e nenhuma configuração de empresa muda isso: a
2790
+ * capacidade não existe. Presente, `resolvePolicy` responde o que vale para uma empresa —
2791
+ * é o que a rota de configurações usa para dizer ao painel se desenha o interruptor.
2792
+ */
2793
+ transcription: {
2794
+ defaultMode: TranscriptionMode;
2795
+ resolvePolicy: (companyId: string) => Promise<TranscriptionPolicy>;
2796
+ } | undefined;
1356
2797
  settings: SettingsRepository;
2798
+ /**
2799
+ * `undefined` quando o recurso não está ligado (ou falta storage legível). O host consulta a
2800
+ * ausência para não registrar a rota de upload — e o preview, sem a rota, esconde o microfone.
2801
+ */
2802
+ previewMedia: StorePreviewMediaUseCase | undefined;
1357
2803
  webhook: {
1358
2804
  receive: ReceiveWebhookUseCase;
1359
2805
  verifyChallenge: (query: {
@@ -1372,26 +2818,49 @@ declare function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams
1372
2818
  delete: DeleteFlowGraphUseCase;
1373
2819
  livePositions: GetLiveFlowPositionsUseCase;
1374
2820
  repository: FlowGraphRepository;
2821
+ mediaRepository: FlowMediaRepository;
1375
2822
  } | undefined;
1376
2823
  catalog: CatalogPort | undefined;
1377
2824
  };
1378
2825
  type MetaWhatsAppModule = ReturnType<typeof createMetaWhatsAppModule>;
1379
2826
 
2827
+ /**
2828
+ * Leitura de mídia do simulador de conversa.
2829
+ *
2830
+ * **Atrás de flag de propósito, e não ligado por omissão.** Aceitar id que não veio da Meta é
2831
+ * exatamente o que um webhook forjado exploraria: bastaria mandar `preview-upload:<chave>` para
2832
+ * fazer o servidor ler um objeto arbitrário do storage e devolvê-lo. Num ambiente de simulação isso
2833
+ * é o recurso; em produção é leitura arbitrária. Quem liga assume, e a decisão fica visível no
2834
+ * lugar onde o módulo é montado.
2835
+ */
2836
+ type PreviewMediaSupport = {
2837
+ readonly isEnabled: boolean;
2838
+ readonly objectStorage: ObjectStorageInterface & {
2839
+ getObject: NonNullable<ObjectStorageInterface['getObject']>;
2840
+ };
2841
+ /** Mime a devolver, já que o storage guarda bytes e não o tipo. Padrão `audio/ogg`. */
2842
+ readonly defaultMimeType?: string;
2843
+ };
1380
2844
  declare class WhatsAppChannelAdapter implements ChannelAdapterInterface {
1381
2845
  private readonly messages;
1382
- constructor(messages: WhatsAppMessageProvider);
2846
+ /**
2847
+ * Ausente, o adaptador se comporta como sempre: todo id vai para a Graph API. É o que garante
2848
+ * que atualizar o pacote não abre nada em quem não pediu.
2849
+ */
2850
+ private readonly previewMedia?;
2851
+ constructor(messages: WhatsAppMessageProvider,
2852
+ /**
2853
+ * Ausente, o adaptador se comporta como sempre: todo id vai para a Graph API. É o que garante
2854
+ * que atualizar o pacote não abre nada em quem não pediu.
2855
+ */
2856
+ previewMedia?: PreviewMediaSupport | undefined);
1383
2857
  private translateErrors;
1384
2858
  sendText(to: string, body: string): Promise<{
1385
2859
  externalMessageId: string | null;
1386
2860
  }>;
1387
- sendMedia(params: {
1388
- to: string;
1389
- buffer: Buffer;
1390
- mimeType: string;
1391
- filename: string;
1392
- caption?: string;
1393
- }): Promise<{
2861
+ sendMedia(params: SendChannelMediaParams): Promise<{
1394
2862
  externalMessageId: string | null;
2863
+ mediaId?: string | undefined;
1395
2864
  }>;
1396
2865
  sendTemplate(params: {
1397
2866
  to: string;
@@ -1412,6 +2881,34 @@ declare class WhatsAppChannelAdapter implements ChannelAdapterInterface {
1412
2881
  }): Promise<{
1413
2882
  externalMessageId: string | null;
1414
2883
  }>;
2884
+ sendInteractiveButtons(params: {
2885
+ to: string;
2886
+ body: string;
2887
+ buttons: {
2888
+ id: string;
2889
+ title: string;
2890
+ }[];
2891
+ }): Promise<{
2892
+ externalMessageId: string | null;
2893
+ }>;
2894
+ sendProductList(params: {
2895
+ to: string;
2896
+ headerText: string;
2897
+ body: string;
2898
+ footerText?: string;
2899
+ sections: {
2900
+ title: string;
2901
+ retailerIds: string[];
2902
+ }[];
2903
+ }): Promise<{
2904
+ externalMessageId: string | null;
2905
+ }>;
2906
+ /**
2907
+ * Busca o binário da mídia — da Meta, ou do storage quando o id é do simulador.
2908
+ *
2909
+ * O desvio acontece ANTES de qualquer chamada de rede: id do simulador não existe na Meta, e
2910
+ * tentar buscá-lo lá renderia um 404 confuso em vez do áudio que o operador acabou de gravar.
2911
+ */
1415
2912
  fetchMediaAsBase64(mediaId: string): Promise<{
1416
2913
  data: string;
1417
2914
  mimeType: string;
@@ -1455,4 +2952,132 @@ declare function redeemSseTicket(store: TicketStoreInterface, ticket: string): P
1455
2952
  whatsappNumber: string;
1456
2953
  } | null>;
1457
2954
 
1458
- export { type CreateFlowGraphParams, CreateFlowGraphUseCase, type CreateMetaWhatsAppModuleParams, DeleteFlowGraphUseCase, type DrizzleMigrateFunction, type ExportConversationParams, type ExportConversationResult, ExportConversationUseCase, FlowGraphRepository, type FlowGraphRow, FlowInterpreter, type FlowRunResult, type FlowStepInput, type FlowStepResult, GetFlowGraphUseCase, GetLiveFlowPositionsUseCase, type IngestInboundMediaParams, type IngestInboundMediaResult, IngestInboundMediaUseCase, type InsertMessageParams, InvalidFlowGraphError, type ListConversationsFilters, type ListConversationsParams, ListConversationsUseCase, ListFlowGraphsUseCase, type ListMessagesParams, ListMessagesUseCase, type LogMessageParams, LogMessageUseCase, META_WHATSAPP_MIGRATIONS_TABLE, MessageRepository, type MessageRow, type MetaWhatsAppDatabase, type MetaWhatsAppModule, type MetaWhatsAppModuleConfig, type MetaWhatsAppModuleFeatures, type MetaWhatsAppModuleProviders, type NewFlowGraphRow, type NewMessageRow, type NewSessionRow, type NewSettingsRow, type NonceStoreInterface, OptimisticLockError, type RealtimeRelay, type ReceiveWebhookParams, type ReceiveWebhookResult, ReceiveWebhookUseCase, type ReleaseConversationParams, ReleaseConversationUseCase, type ListMessagesParams$1 as RepositoryListMessagesParams, type RunMetaWhatsAppMigrationsParams, type SaveFlowGraphParams, SaveFlowGraphUseCase, type SendMediaParams, SendMessageUseCase, type SendTemplateParams, type SendTextParams, SessionRepository, type SessionRow, SettingsRepository, type SettingsRow, SseHub, type SseListener, type TakeoverConversationParams, TakeoverConversationUseCase, type TicketStoreInterface, WEBHOOK_NONCE_TTL_SECONDS, WhatsAppChannelAdapter, claimWebhookDelivery, createMetaWhatsAppModule, extractMediaDescriptor, flowGraphs, issueSseTicket, messages, metaWhatsAppMigrationsFolder, metaWhatsAppSchema, redeemSseTicket, runMetaWhatsAppMigrations, sessions, settings, verifyWebhookChallenge, verifyWebhookSignature };
2955
+ /**
2956
+ * Porta de escrita no transcript, e não a classe `LogMessageUseCase`.
2957
+ *
2958
+ * Exigir a classe amarraria a action a quem já guarda as mensagens nas tabelas do módulo — um host
2959
+ * em migração, com o transcript ainda no schema dele, não conseguiria usar a única action built-in
2960
+ * sem gravar o envio numa tabela que o painel dele não lê. O que a action precisa é só de um lugar
2961
+ * para registrar o que saiu.
2962
+ */
2963
+ /**
2964
+ * Onde guardar o `mediaId` que a Meta devolveu para cada arquivo da biblioteca.
2965
+ *
2966
+ * Porta, e não tabela: o módulo não sabe se o host guarda isso numa coluna, no Redis ou em memória.
2967
+ * O que ele precisa é reaproveitar o id em vez de ressubir o mesmo binário para cada cliente que
2968
+ * passa pelo nó — a Meta aceita reusar por 30 dias.
2969
+ */
2970
+ type FlowMediaIdStore = {
2971
+ get(params: {
2972
+ flowMediaId: string;
2973
+ senderKey: string;
2974
+ }): Promise<string | undefined>;
2975
+ set(params: {
2976
+ flowMediaId: string;
2977
+ senderKey: string;
2978
+ mediaId: string;
2979
+ }): Promise<void>;
2980
+ clear(params: {
2981
+ flowMediaId: string;
2982
+ senderKey: string;
2983
+ }): Promise<void>;
2984
+ };
2985
+ /**
2986
+ * O `senderKey` vem junto do store, e não solto, para não existir a forma inválida.
2987
+ *
2988
+ * O `mediaId` é escopado ao número remetente (`phone_number_id`) na Meta: cachear sem separar por
2989
+ * número faz o id de um número ser mandado pelo outro, e o envio falha. Exigi-lo dentro do mesmo
2990
+ * objeto torna "liguei o cache e esqueci o número" impossível de escrever.
2991
+ */
2992
+ type FlowMediaIdCache = {
2993
+ readonly store: FlowMediaIdStore;
2994
+ readonly senderKey: string;
2995
+ };
2996
+ type FlowMediaTranscriptLogger = {
2997
+ execute(params: LogMessageParams): Promise<unknown>;
2998
+ };
2999
+ type CreateSendMediaActionParams = {
3000
+ flowMediaRepository: FlowMediaRepository;
3001
+ objectStorage: ObjectStorageInterface & {
3002
+ getObject: NonNullable<ObjectStorageInterface['getObject']>;
3003
+ };
3004
+ logMessage: FlowMediaTranscriptLogger;
3005
+ /**
3006
+ * Reaproveitamento do arquivo já subido. Capacidade por ausência: sem o cache, cada envio ressobe
3007
+ * o binário — exatamente o comportamento anterior, sem flag para desligar.
3008
+ */
3009
+ mediaIdCache?: FlowMediaIdCache;
3010
+ startState: SessionState;
3011
+ onError?: (error: unknown, details: {
3012
+ flowKey: string;
3013
+ nodeId: string;
3014
+ uploadId: string;
3015
+ }) => void;
3016
+ };
3017
+ declare function createSendMediaAction(params: CreateSendMediaActionParams): FlowActionHandler;
3018
+
3019
+ /** Tetos da Meta para `interactive.product_list`. Acima disso a mensagem inteira é recusada. */
3020
+ declare const PRODUCT_LIST_LIMIT: {
3021
+ readonly ITEMS: 30;
3022
+ readonly SECTIONS: 10;
3023
+ };
3024
+ type CreateSendProductListActionParams = {
3025
+ catalog: CatalogPort;
3026
+ catalogId: string;
3027
+ logMessage: FlowMediaTranscriptLogger;
3028
+ startState: SessionState;
3029
+ /** Falha de UMA vitrine não pode travar a conversa; o host observa por aqui. */
3030
+ onError?: (error: unknown, details: {
3031
+ flowKey: string;
3032
+ nodeId: string;
3033
+ }) => void;
3034
+ };
3035
+ /**
3036
+ * Action `send_product_list`: ao passar pelo nó, envia a vitrine do catálogo publicado na Meta.
3037
+ *
3038
+ * O nó guarda **critério**, não a lista de produtos. Congelar `retailerId` em `actionParams` faria
3039
+ * o fluxo continuar oferecendo o item esgotado — ou o item excluído, que a Meta recusa junto com a
3040
+ * mensagem inteira. O que o operador edita é o texto e o filtro; o estoque manda no resto.
3041
+ *
3042
+ * Só produto em estoque entra. Vitrine que mostra o que não tem produz a pior conversa possível:
3043
+ * o cliente escolhe, responde, e recebe "acabou".
3044
+ */
3045
+ declare function createSendProductListAction(params: CreateSendProductListActionParams): FlowActionHandler;
3046
+
3047
+ /**
3048
+ * Store padrão do cache de `mediaId`, na própria linha da biblioteca de mídia.
3049
+ *
3050
+ * A porta `FlowMediaIdStore` existe para o host poder escolher Redis ou memória. Esta é a
3051
+ * implementação que o módulo já traz pronta, para o caso comum não exigir nada de quem instala: a
3052
+ * tabela é do módulo, a coluna é do módulo, e sem isso todo host reescreveria o mesmo repositório.
3053
+ *
3054
+ * Um mapa por `phone_number_id` na mesma linha, e não uma tabela à parte: o id não existe sem o
3055
+ * arquivo, morre com ele, e a linha é lida de qualquer forma no envio.
3056
+ */
3057
+ declare class FlowMediaIdRepository implements FlowMediaIdStore {
3058
+ private readonly db;
3059
+ constructor(db: MetaWhatsAppDatabase);
3060
+ get(params: {
3061
+ flowMediaId: string;
3062
+ senderKey: string;
3063
+ }): Promise<string | undefined>;
3064
+ /**
3065
+ * Grava só a chave deste número.
3066
+ *
3067
+ * `jsonb_set` no banco, e não ler-alterar-escrever na aplicação: dois clientes passando pelo nó
3068
+ * ao mesmo tempo com números diferentes leriam o mesmo mapa e o último gravaria por cima,
3069
+ * apagando o id do outro. A escrita atômica não tem essa janela.
3070
+ */
3071
+ set(params: {
3072
+ flowMediaId: string;
3073
+ senderKey: string;
3074
+ mediaId: string;
3075
+ }): Promise<void>;
3076
+ /** Remove só a chave deste número — o id do outro número continua válido. */
3077
+ clear(params: {
3078
+ flowMediaId: string;
3079
+ senderKey: string;
3080
+ }): Promise<void>;
3081
+ }
3082
+
3083
+ export { type AttachFlowMediaParams, type AudioTranscriber, type CompanyDocumentView, type CompanyDocumentsPage, type ConversationDocumentView, type ConversationDocumentsPage, type CreateFlowGraphParams, CreateFlowGraphUseCase, type CreateMetaWhatsAppModuleParams, type CreateSendMediaActionParams, type CreateSendProductListActionParams, DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS, type DeleteConversationParams, type DeleteConversationResult, DeleteConversationUseCase, DeleteFlowGraphUseCase, DocumentRepository, type DocumentRow, type DrizzleMigrateFunction, type ExportConversationParams, type ExportConversationResult, ExportConversationUseCase, FlowGraphCache, FlowGraphRepository, type FlowGraphRow, FlowInterpreter, type FlowMediaIdCache, FlowMediaIdRepository, type FlowMediaIdStore, type FlowMediaLocation, FlowMediaRepository, type FlowMediaRow, type FlowMediaTranscriptLogger, type FlowRunResult, type FlowStepInput, type FlowStepResult, GetFlowGraphUseCase, GetLiveFlowPositionsUseCase, type InboundDispatchJob, type InboundDispatchQueueInterface, InboundEffectsDispatcher, type InboundMediaDescriptor, type InboundMessageEffectsJob, type InboundStatusEffectsJob, type IngestInboundMediaParams, type IngestInboundMediaResult, IngestInboundMediaUseCase, type IngestTranscriptionOptions, type InsertMessageParams, InvalidFlowGraphError, type LinkDocumentParams, type ListCompanyDocumentsParams, ListCompanyDocumentsUseCase, type ListConversationDocumentsParams, ListConversationDocumentsUseCase, type ListConversationsFilters, type ListConversationsParams, ListConversationsUseCase, type ListDocumentsParams, type ListDocumentsResult, ListFlowGraphsUseCase, type ListMessagesParams, ListMessagesUseCase, type LogMessageParams, LogMessageUseCase, META_WHATSAPP_MIGRATIONS_TABLE, type MessageModerator, MessageRepository, type MessageRow, type MetaWhatsAppDatabase, type MetaWhatsAppModule, type MetaWhatsAppModuleConfig, type MetaWhatsAppModuleFeatures, type MetaWhatsAppModuleProviders, type MetaWhatsAppTranscriptionConfig, type NewDocumentRow, type NewFlowGraphRow, type NewFlowMediaRow, type NewMessageRow, type NewSessionRow, type NewSettingsRow, type NonceStoreInterface, OptimisticLockError, PRODUCT_LIST_LIMIT, type PreviewMediaSupport, ProcessInboundDispatchUseCase, type PurgeExpiredDocumentsParams, type PurgeExpiredDocumentsResult, PurgeExpiredDocumentsUseCase, type RealtimeRelay, type ReceiveWebhookParams, type ReceiveWebhookResult, ReceiveWebhookUseCase, type ReleaseConversationParams, ReleaseConversationUseCase, type ListMessagesParams$1 as RepositoryListMessagesParams, type ResolveTranscriptionPolicyDependencies, type RunMetaWhatsAppMigrationsParams, type SaveFlowGraphParams, SaveFlowGraphUseCase, type SaveTranscriptionByWaMessageIdParams, type SaveTranscriptionParams, type SendMediaParams, SendMessageUseCase, type SendTemplateParams, type SendTextParams, SessionRepository, type SessionRow, SettingsRepository, type SettingsRow, SseHub, type SseListener, type StorePreviewMediaParams, type StorePreviewMediaResult, StorePreviewMediaUseCase, TRANSCRIPTION_MODE, TRANSCRIPTION_STATUS, type TakeoverConversationParams, TakeoverConversationUseCase, type TicketStoreInterface, type TranscribeAudioDependencies, type TranscribeAudioParams, type TranscribeAudioResult, TranscribeAudioUseCase, type TranscriptionPolicy, type TranscriptionPolicyDefaults, type TranscriptionPolicyResolver, type TranscriptionStatus, type UpdateFlowMediaParams, WhatsAppChannelAdapter, buildInboundJobId, claimWebhookDelivery, confirmWebhookDelivery, createMetaWhatsAppModule, createSendMediaAction, createSendProductListAction, createTranscriptionPolicyResolver, documents, extractMediaDescriptor, flowGraphs, flowMedia, isAudioMimeType, isRetriableTranscriptionError, isUnsupportedTranscriptionError, issueSseTicket, messages, metaWhatsAppMigrationsFolder, metaWhatsAppSchema, redeemSseTicket, resolveFailureStatus, runMetaWhatsAppMigrations, sessions, settings, toSessionContract, transcriptionRetryAfterSeconds, verifyWebhookChallenge, verifyWebhookSignature };