@omnizap-system/omnizap 2.6.1 → 2.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/.env.example +54 -9
  2. package/.github/workflows/ci.yml +3 -3
  3. package/.github/workflows/security-runner-hardening.yml +1 -1
  4. package/.github/workflows/security-zap-full-scan.yml +1 -0
  5. package/app/config/index.js +2 -0
  6. package/app/configParts/adminIdentity.js +5 -5
  7. package/app/configParts/baileysConfig.js +226 -55
  8. package/app/configParts/groupUtils.js +5 -0
  9. package/app/configParts/messagePersistenceService.js +143 -3
  10. package/app/configParts/sessionConfig.js +157 -0
  11. package/app/connection/baileysCompatibility.test.js +1 -1
  12. package/app/connection/groupOwnerWriteStateResolver.js +109 -0
  13. package/app/connection/socketController.js +625 -124
  14. package/app/connection/socketController.multiSession.test.js +108 -0
  15. package/app/controllers/messageController.js +1 -1
  16. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  17. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  18. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  19. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
  20. package/app/controllers/messageProcessingPipeline.js +88 -9
  21. package/app/controllers/messageProcessingPipeline.test.js +200 -0
  22. package/app/modules/adminModule/AGENT.md +1 -1
  23. package/app/modules/adminModule/commandConfig.json +3318 -1347
  24. package/app/modules/adminModule/groupCommandHandlers.js +856 -14
  25. package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
  26. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  27. package/app/modules/aiModule/AGENT.md +47 -30
  28. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  29. package/app/modules/aiModule/catCommand.js +132 -25
  30. package/app/modules/aiModule/commandConfig.json +114 -28
  31. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  32. package/app/modules/gameModule/AGENT.md +1 -1
  33. package/app/modules/gameModule/commandConfig.json +29 -0
  34. package/app/modules/menuModule/AGENT.md +1 -1
  35. package/app/modules/menuModule/commandConfig.json +45 -10
  36. package/app/modules/menuModule/menuCatalogService.js +190 -0
  37. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  38. package/app/modules/menuModule/menuDynamicService.js +511 -0
  39. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  40. package/app/modules/menuModule/menus.js +36 -5
  41. package/app/modules/playModule/AGENT.md +10 -5
  42. package/app/modules/playModule/commandConfig.json +74 -16
  43. package/app/modules/playModule/playCommandConstants.js +13 -7
  44. package/app/modules/playModule/playCommandCore.js +4 -6
  45. package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
  46. package/app/modules/playModule/playConfigRuntime.js +5 -6
  47. package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
  48. package/app/modules/quoteModule/AGENT.md +1 -1
  49. package/app/modules/quoteModule/commandConfig.json +29 -0
  50. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  51. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  52. package/app/modules/statsModule/AGENT.md +1 -1
  53. package/app/modules/statsModule/commandConfig.json +58 -0
  54. package/app/modules/stickerModule/AGENT.md +1 -1
  55. package/app/modules/stickerModule/commandConfig.json +145 -0
  56. package/app/modules/stickerPackModule/AGENT.md +1 -1
  57. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  58. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  59. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
  60. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
  61. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  62. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  63. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  64. package/app/modules/tiktokModule/AGENT.md +1 -1
  65. package/app/modules/tiktokModule/commandConfig.json +29 -0
  66. package/app/modules/userModule/AGENT.md +1 -1
  67. package/app/modules/userModule/commandConfig.json +29 -0
  68. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  69. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  70. package/app/observability/metrics.js +136 -0
  71. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  72. package/app/services/ai/geminiService.js +131 -7
  73. package/app/services/ai/geminiService.test.js +59 -2
  74. package/app/services/ai/moduleAiHelpCoreService.js +33 -4
  75. package/app/services/group/groupMetadataService.js +24 -1
  76. package/app/services/infra/dbWriteQueue.js +51 -21
  77. package/app/services/messaging/newsBroadcastService.js +843 -27
  78. package/app/services/multiSession/assignmentBalancerService.js +457 -0
  79. package/app/services/multiSession/groupOwnershipRepository.js +381 -0
  80. package/app/services/multiSession/groupOwnershipService.js +890 -0
  81. package/app/services/multiSession/groupOwnershipService.test.js +309 -0
  82. package/app/services/multiSession/sessionRegistryService.js +293 -0
  83. package/app/store/aiPromptStore.js +36 -19
  84. package/app/store/groupConfigStore.js +41 -5
  85. package/app/store/premiumUserStore.js +21 -7
  86. package/app/utils/antiLink/antiLinkModule.js +352 -16
  87. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  88. package/database/index.js +6 -0
  89. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  90. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  91. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  92. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  93. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  94. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  95. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  96. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  97. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  98. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  99. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  100. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  101. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  102. package/database/schema.sql +102 -1
  103. package/docker-compose.yml +4 -1
  104. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  105. package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
  106. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  107. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  108. package/docs/security/omnizap-static-security-headers.conf +25 -0
  109. package/ecosystem.prod.config.cjs +31 -11
  110. package/index.js +52 -18
  111. package/observability/alert-rules.yml +20 -0
  112. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  113. package/observability/mysql-setup.sql +4 -4
  114. package/observability/system-admin-observability.md +26 -0
  115. package/package.json +12 -5
  116. package/public/comandos/commands-catalog.json +2253 -78
  117. package/public/js/apps/commandsReactApp.js +267 -87
  118. package/public/js/apps/createPackApp.js +3 -3
  119. package/public/js/apps/stickersApp.js +255 -103
  120. package/public/js/apps/termsReactApp.js +57 -8
  121. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  122. package/public/js/apps/userReactApp.js +96 -47
  123. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  124. package/public/pages/politica-de-privacidade.html +1 -1
  125. package/public/pages/stickers.html +5 -5
  126. package/public/pages/termos-de-uso-texto-integral.html +1 -1
  127. package/public/pages/termos-de-uso.html +1 -1
  128. package/public/pages/user-password-reset.html +3 -4
  129. package/public/pages/user-systemadm.html +8 -462
  130. package/public/pages/user.html +1 -1
  131. package/scripts/clear-whatsapp-session.sh +123 -0
  132. package/scripts/core-ai-mode.mjs +163 -0
  133. package/scripts/deploy.sh +10 -0
  134. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  135. package/scripts/generate-commands-catalog.mjs +155 -0
  136. package/scripts/new-whatsapp-session.sh +317 -0
  137. package/scripts/security-web-surface-check.mjs +218 -0
  138. package/server/controllers/admin/adminPanelHandlers.js +253 -3
  139. package/server/controllers/admin/systemAdminController.js +267 -0
  140. package/server/controllers/sticker/stickerCatalogController.js +9 -23
  141. package/server/controllers/system/contactController.js +9 -17
  142. package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
  143. package/server/controllers/system/systemController.js +254 -1
  144. package/server/controllers/userController.js +6 -0
  145. package/server/email/emailTemplateService.js +3 -2
  146. package/server/http/httpServer.js +8 -4
  147. package/server/middleware/securityHeaders.js +20 -1
  148. package/server/routes/admin/systemAdminRouter.js +6 -0
  149. package/server/routes/indexRouter.js +30 -6
  150. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  151. package/server/routes/static/staticPageRouter.js +27 -1
  152. package/server/utils/publicContact.js +31 -0
  153. package/utils/whatsapp/contactEnv.js +39 -0
  154. package/vite.config.mjs +2 -1
  155. package/app/modules/playModule/local/installYtDlp.js +0 -25
  156. package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
@@ -0,0 +1,108 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { createGroupOwnerWriteStateResolver } from './groupOwnerWriteStateResolver.js';
5
+
6
+ const createCache = () => {
7
+ const map = new Map();
8
+ return {
9
+ get: (key) => map.get(key),
10
+ set: (key, value) => {
11
+ map.set(key, value);
12
+ return true;
13
+ },
14
+ del: (key) => map.delete(key),
15
+ keys: () => Array.from(map.keys()),
16
+ };
17
+ };
18
+
19
+ const buildCacheKey = (groupJid, sessionId) => `${sessionId}:${groupJid}`;
20
+
21
+ const normalizeSessionId = (value) => String(value || '').trim() || 'default';
22
+ const isGroupJid = (jid) => String(jid || '').endsWith('@g.us');
23
+
24
+ test('socketController multi-session: fencing token por assignment_version invalida writer stale', async () => {
25
+ let ownerState = {
26
+ ownerSessionId: 'session-a',
27
+ assignmentVersion: 1,
28
+ };
29
+
30
+ const resolver = createGroupOwnerWriteStateResolver({
31
+ buildCacheKeyImpl: buildCacheKey,
32
+ getOwnerImpl: async () => ownerState,
33
+ tryAcquireImpl: async () => ({ acquired: false, reason: 'claim_disabled' }),
34
+ cacheImpl: createCache(),
35
+ isGroupJidImpl: isGroupJid,
36
+ normalizeSessionIdImpl: normalizeSessionId,
37
+ loggerImpl: { warn: () => {} },
38
+ });
39
+
40
+ const first = await resolver('120363555555555555@g.us', 'session-a', {
41
+ allowClaim: false,
42
+ source: 'test_first',
43
+ });
44
+ assert.equal(first.allowed, true);
45
+ assert.equal(first.assignmentVersion, 1);
46
+
47
+ ownerState = {
48
+ ownerSessionId: 'session-a',
49
+ assignmentVersion: 2,
50
+ };
51
+
52
+ const stale = await resolver('120363555555555555@g.us', 'session-a', {
53
+ allowClaim: false,
54
+ source: 'test_stale',
55
+ expectedAssignmentVersion: 1,
56
+ enforceFence: true,
57
+ });
58
+ assert.equal(stale.allowed, false);
59
+ assert.equal(stale.reason, 'fence_token_mismatch');
60
+ assert.equal(stale.assignmentVersion, 2);
61
+ });
62
+
63
+ test('socketController multi-session: sessão antiga perde escrita após failover de owner', async () => {
64
+ let ownerState = {
65
+ ownerSessionId: 'session-a',
66
+ assignmentVersion: 7,
67
+ };
68
+
69
+ const resolver = createGroupOwnerWriteStateResolver({
70
+ buildCacheKeyImpl: buildCacheKey,
71
+ getOwnerImpl: async () => ownerState,
72
+ tryAcquireImpl: async () => ({ acquired: false, reason: 'claim_disabled' }),
73
+ cacheImpl: createCache(),
74
+ isGroupJidImpl: isGroupJid,
75
+ normalizeSessionIdImpl: normalizeSessionId,
76
+ loggerImpl: { warn: () => {} },
77
+ });
78
+
79
+ const beforeFailover = await resolver('120363666666666666@g.us', 'session-a', {
80
+ allowClaim: false,
81
+ source: 'before_failover',
82
+ });
83
+ assert.equal(beforeFailover.allowed, true);
84
+ assert.equal(beforeFailover.assignmentVersion, 7);
85
+
86
+ ownerState = {
87
+ ownerSessionId: 'session-b',
88
+ assignmentVersion: 8,
89
+ };
90
+
91
+ const staleOwner = await resolver('120363666666666666@g.us', 'session-a', {
92
+ allowClaim: false,
93
+ source: 'after_failover_old_owner',
94
+ expectedAssignmentVersion: 7,
95
+ enforceFence: true,
96
+ });
97
+ assert.equal(staleOwner.allowed, false);
98
+ assert.equal(staleOwner.reason, 'owned_by_other');
99
+
100
+ const newOwner = await resolver('120363666666666666@g.us', 'session-b', {
101
+ allowClaim: false,
102
+ source: 'after_failover_new_owner',
103
+ expectedAssignmentVersion: 8,
104
+ enforceFence: true,
105
+ });
106
+ assert.equal(newOwner.allowed, true);
107
+ assert.equal(newOwner.assignmentVersion, 8);
108
+ });
@@ -4,4 +4,4 @@ import { handleMessagesThroughPipeline } from './messageProcessingPipeline.js';
4
4
  * Facade do controller de mensagens.
5
5
  * Mantem assinatura/compatibilidade enquanto delega ao pipeline modular.
6
6
  */
7
- export const handleMessages = async (update, sock) => handleMessagesThroughPipeline(update, sock);
7
+ export const handleMessages = async (update, sock, options = {}) => handleMessagesThroughPipeline(update, sock, options);
@@ -42,17 +42,19 @@ export const createCommandMiddleware = ({ isAdminCommand, isKnownNonAdminCommand
42
42
  }
43
43
  }
44
44
 
45
- if (COMMAND_REACT_EMOJI) {
46
- try {
47
- await sendAndStore(ctx.sock, ctx.remoteJid, {
48
- react: {
49
- text: COMMAND_REACT_EMOJI,
50
- key: ctx.key,
51
- },
45
+ if (COMMAND_REACT_EMOJI?.trim() && ctx?.key) {
46
+ sendAndStore(ctx.sock, ctx.remoteJid, {
47
+ react: {
48
+ text: COMMAND_REACT_EMOJI,
49
+ key: ctx.key,
50
+ },
51
+ }).catch((error) => {
52
+ logger.warn('Falha ao enviar reação de comando', {
53
+ error: error?.message,
54
+ jid: ctx.remoteJid,
55
+ messageId: ctx.key?.id,
52
56
  });
53
- } catch (error) {
54
- logger.warn('Falha ao enviar reação de comando:', error?.message);
55
- }
57
+ });
56
58
  }
57
59
 
58
60
  const execution = await executeMessageCommandRoute({
@@ -1,4 +1,4 @@
1
- export const createConversationMiddleware = ({ logger, resolveSenderAdminForContext, resolveSenderOwnerForContext, resolveHasGoogleLoginForContext, isUserAdmin, isAdminSenderAsync, resolveCanonicalSenderJidForContext, isWhatsAppUserLinkedToGoogleWebAccount, WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN, ensureUserHasGoogleWebLoginForCommand, executeMessageCommandRoute, isAdminCommand, runCommand, sendReply, routeConversationMessage, stopMessagePipeline }) => {
1
+ export const createConversationMiddleware = ({ logger, resolveSenderAdminForContext, resolveSenderOwnerForContext, resolveHasGoogleLoginForContext, isUserAdmin, isAdminSenderAsync, resolveCanonicalSenderJidForContext, isWhatsAppUserLinkedToGoogleWebAccount, WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN, ensureUserHasGoogleWebLoginForCommand, executeMessageCommandRoute, isAdminCommand, runCommand, sendReply, routeConversationMessage, stopMessagePipeline, conversationAutoReplyEnabled = true }) => {
2
2
  const resolveToolSecurityContextForConversation = async (ctx) => {
3
3
  if (ctx.memo.toolSecurityContext) return ctx.memo.toolSecurityContext;
4
4
 
@@ -133,6 +133,7 @@ export const createConversationMiddleware = ({ logger, resolveSenderAdminForCont
133
133
  };
134
134
 
135
135
  return async (ctx) => {
136
+ if (!conversationAutoReplyEnabled) return null;
136
137
  if (ctx.isCommandMessage || ctx.isMessageFromBot || !ctx.isNotifyUpsert) return null;
137
138
 
138
139
  try {
@@ -26,6 +26,8 @@ const createBaseContext = (overrides = {}) => ({
26
26
  mediaEntries: [],
27
27
  upsertType: 'notify',
28
28
  isNotifyUpsert: true,
29
+ sessionId: 'session-default',
30
+ ownerSessionId: null,
29
31
  isCommandMessage: false,
30
32
  hasCommandPrefix: false,
31
33
  analysisPayload: {
@@ -166,6 +168,108 @@ test('pre-processing trata trigger de iniciar login', async () => {
166
168
  assert.equal(stopSpy.calls[0].metadataPatch.flow, 'whatsapp_google_login');
167
169
  });
168
170
 
171
+ test('pre-processing owner gate bloqueia sessao nao-owner em modo enforce', async () => {
172
+ const stopSpy = createStopSpy();
173
+
174
+ const middlewares = createPreProcessingMiddlewares({
175
+ executeQuery: async () => [],
176
+ TABLES: { RPG_PLAYER: 'rpg_player' },
177
+ isStatusJid: () => false,
178
+ stopMessagePipeline: stopSpy.stopMessagePipeline,
179
+ handleAntiLink: async () => false,
180
+ ensureCommandPrefixForContext: async () => '/',
181
+ resolveCaptchaByMessage: async () => {},
182
+ maybeHandleStartLoginMessage: async () => false,
183
+ mergeAnalysisMetadata: (analysisPayload, patch) => {
184
+ analysisPayload.metadata = {
185
+ ...(analysisPayload.metadata || {}),
186
+ ...(patch || {}),
187
+ };
188
+ },
189
+ ensureGroupConfigForContext: async () => ({}),
190
+ resolveStickerFocusState: () => ({ enabled: false }),
191
+ resolveStickerFocusMessageClassification: () => ({ isThrottleCandidate: false }),
192
+ resolveGroupOwnerForContext: async (ctx) => {
193
+ ctx.ownerSessionId = 'session-owner';
194
+ return { ownerSessionId: 'session-owner' };
195
+ },
196
+ ownerEnforcementMode: 'enforce',
197
+ primarySessionId: 'session-primary',
198
+ isUserAdmin: async () => false,
199
+ canSendMessageInStickerFocus: () => ({ allowed: true, remainingMs: 0 }),
200
+ registerMessageUsageInStickerFocus: () => {},
201
+ shouldSendStickerFocusWarning: () => false,
202
+ sendReply: async () => {},
203
+ formatStickerFocusRuleLabel: () => '',
204
+ formatRemainingMinutesLabel: () => 1,
205
+ logger: { warn: () => {}, info: () => {} },
206
+ });
207
+
208
+ const ctx = createBaseContext({
209
+ sessionId: 'session-worker',
210
+ isGroupMessage: true,
211
+ remoteJid: '120363111111111111@g.us',
212
+ });
213
+ const result = await middlewares.enforceGroupOwnerMiddleware(ctx);
214
+
215
+ assert.deepEqual(result, { stop: true });
216
+ assert.equal(stopSpy.calls.length, 1);
217
+ assert.equal(stopSpy.calls[0].processingResult, 'blocked_group_owner_enforcement');
218
+ assert.equal(ctx.analysisPayload.metadata.owner_enforcement_result, 'blocked_non_owner');
219
+ assert.equal(ctx.analysisPayload.metadata.owner_session_id, 'session-owner');
220
+ });
221
+
222
+ test('pre-processing owner gate apenas loga em modo shadow para sessao nao-owner', async () => {
223
+ const stopSpy = createStopSpy();
224
+ const infoLogs = [];
225
+
226
+ const middlewares = createPreProcessingMiddlewares({
227
+ executeQuery: async () => [],
228
+ TABLES: { RPG_PLAYER: 'rpg_player' },
229
+ isStatusJid: () => false,
230
+ stopMessagePipeline: stopSpy.stopMessagePipeline,
231
+ handleAntiLink: async () => false,
232
+ ensureCommandPrefixForContext: async () => '/',
233
+ resolveCaptchaByMessage: async () => {},
234
+ maybeHandleStartLoginMessage: async () => false,
235
+ mergeAnalysisMetadata: (analysisPayload, patch) => {
236
+ analysisPayload.metadata = {
237
+ ...(analysisPayload.metadata || {}),
238
+ ...(patch || {}),
239
+ };
240
+ },
241
+ ensureGroupConfigForContext: async () => ({}),
242
+ resolveStickerFocusState: () => ({ enabled: false }),
243
+ resolveStickerFocusMessageClassification: () => ({ isThrottleCandidate: false }),
244
+ resolveGroupOwnerForContext: async (ctx) => {
245
+ ctx.ownerSessionId = 'session-owner';
246
+ return { ownerSessionId: 'session-owner' };
247
+ },
248
+ ownerEnforcementMode: 'shadow',
249
+ primarySessionId: 'session-primary',
250
+ isUserAdmin: async () => false,
251
+ canSendMessageInStickerFocus: () => ({ allowed: true, remainingMs: 0 }),
252
+ registerMessageUsageInStickerFocus: () => {},
253
+ shouldSendStickerFocusWarning: () => false,
254
+ sendReply: async () => {},
255
+ formatStickerFocusRuleLabel: () => '',
256
+ formatRemainingMinutesLabel: () => 1,
257
+ logger: { warn: () => {}, info: (msg, payload) => infoLogs.push({ msg, payload }) },
258
+ });
259
+
260
+ const ctx = createBaseContext({
261
+ sessionId: 'session-worker',
262
+ isGroupMessage: true,
263
+ });
264
+ const result = await middlewares.enforceGroupOwnerMiddleware(ctx);
265
+
266
+ assert.equal(result, null);
267
+ assert.equal(stopSpy.calls.length, 0);
268
+ assert.equal(ctx.pipelineStopped, false);
269
+ assert.equal(ctx.analysisPayload.metadata.owner_enforcement_result, 'shadow_non_owner');
270
+ assert.equal(infoLogs.length, 1);
271
+ });
272
+
169
273
  test('conversation middleware responde e interrompe pipeline', async () => {
170
274
  const stopSpy = createStopSpy();
171
275
  const replies = [];
@@ -1,4 +1,9 @@
1
- export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJid, stopMessagePipeline, handleAntiLink, ensureCommandPrefixForContext, resolveCaptchaByMessage, maybeHandleStartLoginMessage, mergeAnalysisMetadata, ensureGroupConfigForContext, resolveStickerFocusState, resolveStickerFocusMessageClassification, resolveSenderAdminForContext, isUserAdmin, canSendMessageInStickerFocus, registerMessageUsageInStickerFocus, shouldSendStickerFocusWarning, sendReply, formatStickerFocusRuleLabel, formatRemainingMinutesLabel, logger }) => {
1
+ export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJid, stopMessagePipeline, handleAntiLink, ensureCommandPrefixForContext, resolveCaptchaByMessage, maybeHandleStartLoginMessage, mergeAnalysisMetadata, ensureGroupConfigForContext, resolveStickerFocusState, resolveStickerFocusMessageClassification, resolveGroupOwnerForContext, ownerEnforcementMode = 'off', primarySessionId = 'default', resolveSenderAdminForContext, isUserAdmin, canSendMessageInStickerFocus, registerMessageUsageInStickerFocus, shouldSendStickerFocusWarning, sendReply, formatStickerFocusRuleLabel, formatRemainingMinutesLabel, logger }) => {
2
+ const normalizedOwnerEnforcementMode = String(ownerEnforcementMode || 'off')
3
+ .trim()
4
+ .toLowerCase();
5
+ const effectiveOwnerEnforcementMode = normalizedOwnerEnforcementMode === 'enforce' || normalizedOwnerEnforcementMode === 'shadow' ? normalizedOwnerEnforcementMode : 'off';
6
+
2
7
  const touchSenderLastSeenMiddleware = async (ctx) => {
3
8
  if (!ctx.senderJid || isStatusJid(ctx.remoteJid)) return;
4
9
 
@@ -21,6 +26,78 @@ export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJ
21
26
  });
22
27
  };
23
28
 
29
+ const enforceGroupOwnerMiddleware = async (ctx) => {
30
+ if (!ctx.isGroupMessage) return null;
31
+
32
+ mergeAnalysisMetadata(ctx.analysisPayload, {
33
+ owner_enforcement_mode: effectiveOwnerEnforcementMode,
34
+ processing_session_id: ctx.sessionId,
35
+ });
36
+
37
+ if (effectiveOwnerEnforcementMode === 'off') return null;
38
+
39
+ if (typeof resolveGroupOwnerForContext !== 'function') {
40
+ logger.warn('Middleware de owner enforcement sem resolver de owner configurado.', {
41
+ action: 'group_owner_enforcement_missing_resolver',
42
+ sessionId: ctx.sessionId,
43
+ groupId: ctx.remoteJid,
44
+ });
45
+ return null;
46
+ }
47
+
48
+ const ownerState = await resolveGroupOwnerForContext(ctx);
49
+ const ownerSessionId = String(ctx.ownerSessionId || ownerState?.ownerSessionId || '').trim() || null;
50
+ ctx.ownerSessionId = ownerSessionId;
51
+
52
+ mergeAnalysisMetadata(ctx.analysisPayload, {
53
+ owner_session_id: ownerSessionId,
54
+ });
55
+
56
+ if (!ownerSessionId) {
57
+ mergeAnalysisMetadata(ctx.analysisPayload, {
58
+ owner_enforcement_result: 'owner_not_found',
59
+ });
60
+ return null;
61
+ }
62
+
63
+ const currentSessionId = String(ctx.sessionId || '').trim() || primarySessionId;
64
+ const isOwnerSession = ownerSessionId === currentSessionId;
65
+ if (isOwnerSession) {
66
+ mergeAnalysisMetadata(ctx.analysisPayload, {
67
+ owner_enforcement_result: 'owner_match',
68
+ });
69
+ return null;
70
+ }
71
+
72
+ if (effectiveOwnerEnforcementMode === 'shadow') {
73
+ mergeAnalysisMetadata(ctx.analysisPayload, {
74
+ owner_enforcement_result: 'shadow_non_owner',
75
+ });
76
+ logger.info('Owner enforcement (shadow): sessao nao-owner detectada no grupo.', {
77
+ action: 'group_owner_enforcement_shadow_non_owner',
78
+ groupId: ctx.remoteJid,
79
+ sessionId: currentSessionId,
80
+ ownerSessionId,
81
+ messageId: ctx.key?.id || null,
82
+ });
83
+ return null;
84
+ }
85
+
86
+ mergeAnalysisMetadata(ctx.analysisPayload, {
87
+ owner_enforcement_result: 'blocked_non_owner',
88
+ blocked_by: 'group_owner_enforcement',
89
+ });
90
+ logger.info('Owner enforcement: mensagem bloqueada em sessao nao-owner.', {
91
+ action: 'group_owner_enforcement_blocked_non_owner',
92
+ groupId: ctx.remoteJid,
93
+ sessionId: currentSessionId,
94
+ ownerSessionId,
95
+ messageId: ctx.key?.id || null,
96
+ isCommand: ctx.isCommandMessage,
97
+ });
98
+ return stopMessagePipeline(ctx, 'blocked_group_owner_enforcement');
99
+ };
100
+
24
101
  const applyGroupPolicyMiddleware = async (ctx) => {
25
102
  if (!ctx.isGroupMessage) return null;
26
103
 
@@ -129,7 +206,7 @@ export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJ
129
206
  if (shouldSendStickerFocusWarning({ groupId: ctx.remoteJid, senderJid: ctx.senderJid })) {
130
207
  try {
131
208
  await sendReply(ctx.sock, ctx.remoteJid, ctx.messageInfo, ctx.expirationMessage, {
132
- text: '🖼️ Este chat está com *foco em sticker* ativo.\n' + 'Siga o padrão: envie apenas *imagens* ou *vídeos* para criação automática, ou compartilhe seus *stickers*.\n' + `Mensagens como texto e áudio seguem uma janela de tempo: *${formatStickerFocusRuleLabel(stickerFocusState)}*.\n` + `Tente novamente em ~${formatRemainingMinutesLabel(messageGate.remainingMs)} min ou peça para um admin abrir a janela com *${ctx.commandPrefix}chatwindow on*.`,
209
+ text: '🖼️ *Modo Sticker ativo!*\n\n' + 'Este chat está focado em *stickers automáticos*.\n' + '👉 Envie apenas *imagens* ou *vídeos* para gerar stickers,\n' + '👉 Ou compartilhe *stickers* normalmente.\n\n' + '⏳ *Texto e áudio estão temporariamente limitados*.\n' + `Janela atual: *${formatStickerFocusRuleLabel(stickerFocusState)}*\n` + `Tente novamente em ~${formatRemainingMinutesLabel(messageGate.remainingMs)}.\n\n` + `💡 Um admin pode liberar com: *${ctx.commandPrefix}chatwindow on*`,
133
210
  });
134
211
  } catch (error) {
135
212
  logger.warn('Falha ao enviar aviso de sticker focus.', {
@@ -157,6 +234,7 @@ export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJ
157
234
  return {
158
235
  touchSenderLastSeenMiddleware,
159
236
  ignoreUnprocessableMessageMiddleware,
237
+ enforceGroupOwnerMiddleware,
160
238
  applyGroupPolicyMiddleware,
161
239
  resolveCaptchaMiddleware,
162
240
  handleStartLoginTriggerMiddleware,
@@ -4,7 +4,7 @@ import 'dotenv/config';
4
4
  import { isAdminCommand } from '../modules/adminModule/groupCommandHandlers.js';
5
5
  import { explicarComandoGlobal, registerGlobalHelpCommandExecution } from '../services/ai/globalModuleAiHelpService.js';
6
6
  import { extractSupportedStickerMediaDetails, processSticker } from '../modules/stickerModule/stickerCommand.js';
7
- import { detectAllMediaTypes, extractMessageContent, getExpiration, getJidServer, isGroupJid, isStatusJid, isSameJidUser, normalizeJid, normalizeWAPresence, resolveBotJid, extractSenderInfoFromMessage, resolveUserId, resolveAddressingModeFromMessageKey, resolveCanonicalWhatsAppJid, parseEnvBool, parseEnvInt } from '../config/index.js';
7
+ import { detectAllMediaTypes, extractMessageContent, getExpiration, getJidServer, isGroupJid, isStatusJid, isSameJidUser, normalizeJid, normalizeWAPresence, resolveBotJid, extractSenderInfoFromMessage, resolveUserId, resolveAddressingModeFromMessageKey, resolveCanonicalWhatsAppJid, parseEnvBool, parseEnvInt, getMultiSessionRuntimeConfig } from '../config/index.js';
8
8
  import { isUserAdmin } from '../config/index.js';
9
9
  import { isAdminSenderAsync } from '../config/index.js';
10
10
  import { executeQuery, TABLES } from '../../database/index.js';
@@ -20,6 +20,7 @@ import { createMessageAnalysisEvent } from '../modules/analyticsModule/messageAn
20
20
  import { routeConversationMessage } from '../services/ai/conversationRouterService.js';
21
21
  import { executeMessageCommandRoute, isKnownNonAdminCommand } from '../services/ai/messageCommandExecutionService.js';
22
22
  import { canSendMessageInStickerFocus, registerMessageUsageInStickerFocus, resolveStickerFocusMessageClassification, resolveStickerFocusState, shouldSendStickerFocusWarning } from '../services/sticker/stickerFocusService.js';
23
+ import { getOwner as getGroupOwner } from '../services/multiSession/groupOwnershipService.js';
23
24
  import { createPreProcessingMiddlewares } from './messagePipeline/preProcessingMiddlewares.js';
24
25
  import { createConversationMiddleware } from './messagePipeline/conversationMiddleware.js';
25
26
  import { createCommandMiddleware } from './messagePipeline/commandMiddleware.js';
@@ -41,6 +42,7 @@ const MESSAGE_REPLY_PRESENCE_BEFORE = normalizeWAPresence(process.env.MESSAGE_RE
41
42
  const MESSAGE_REPLY_PRESENCE_AFTER = normalizeWAPresence(process.env.MESSAGE_REPLY_PRESENCE_AFTER, 'paused');
42
43
  const MESSAGE_REPLY_PRESENCE_DELAY_MS = parseEnvInt(process.env.MESSAGE_REPLY_PRESENCE_DELAY_MS, 280, 0, 3_000);
43
44
  const MESSAGE_REPLY_PRESENCE_SUBSCRIBE = parseEnvBool(process.env.MESSAGE_REPLY_PRESENCE_SUBSCRIBE, true);
45
+ const CONVERSATIONAL_AUTO_REPLY_ENABLED = parseEnvBool(process.env.CONVERSATIONAL_AUTO_REPLY_ENABLED, false);
44
46
  const WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN = parseEnvBool(process.env.WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN, true);
45
47
  const SITE_ORIGIN =
46
48
  String(process.env.SITE_ORIGIN || process.env.WHATSAPP_LOGIN_BASE_URL || 'https://omnizap.shop')
@@ -48,6 +50,19 @@ const SITE_ORIGIN =
48
50
  .replace(/\/+$/, '') || 'https://omnizap.shop';
49
51
  const SITE_LOGIN_URL = `${SITE_ORIGIN}/login/`;
50
52
  const SITE_GROUP_LOGIN_URL = `${SITE_ORIGIN}/login`;
53
+ const MULTI_SESSION_RUNTIME_CONFIG = getMultiSessionRuntimeConfig();
54
+ const PRIMARY_SESSION_ID = String(MULTI_SESSION_RUNTIME_CONFIG?.primarySessionId || 'default').trim() || 'default';
55
+ const VALID_SESSION_IDS = new Set(Array.isArray(MULTI_SESSION_RUNTIME_CONFIG?.sessionIds) ? MULTI_SESSION_RUNTIME_CONFIG.sessionIds : [PRIMARY_SESSION_ID]);
56
+ const GROUP_OWNER_ENFORCEMENT_MODE = String(MULTI_SESSION_RUNTIME_CONFIG?.ownerEnforcementMode || 'off')
57
+ .trim()
58
+ .toLowerCase();
59
+
60
+ const normalizeSessionId = (sessionId) => {
61
+ const normalized = String(sessionId || '').trim();
62
+ if (!normalized) return PRIMARY_SESSION_ID;
63
+ if (VALID_SESSION_IDS.has(normalized)) return normalized;
64
+ return PRIMARY_SESSION_ID;
65
+ };
51
66
 
52
67
  let messageAnalyticsTableMissingLogged = false;
53
68
  const recentCommandExecutions = new Map();
@@ -170,7 +185,7 @@ const maybeHandleStartLoginMessage = async ({ sock, messageInfo, extractedText,
170
185
 
171
186
  if (isGroupMessage) {
172
187
  await sendReply(sock, remoteJid, messageInfo, expirationMessage, {
173
- text: 'Por seguranca, envie *iniciar* no privado do bot para receber seu link de login.',
188
+ text: '🔐 *Segurança dos seus dados*\n\n' + 'Para proteger sua conta, o acesso é feito apenas no *privado*.\n' + 'Envie *iniciar* no chat privado do bot para receber seu link de login seguro.\n\n' + '⚠️ Por segurança, não enviamos links de acesso em grupos.',
174
189
  });
175
190
  return true;
176
191
  }
@@ -199,7 +214,7 @@ const maybeHandleStartLoginMessage = async ({ sock, messageInfo, extractedText,
199
214
  const safeName = String(senderName || '').trim();
200
215
  const greeting = safeName ? `Oi, *${safeName}*!` : 'Oi!';
201
216
  await sendReply(sock, remoteJid, messageInfo, expirationMessage, {
202
- text: `${greeting}\n\n` + 'Para continuar no OmniZap, faca login com Google neste link:\n' + `${loginUrl}\n\n` + 'Seu numero do WhatsApp sera vinculado automaticamente a conta logada.',
217
+ text: `${greeting}\n\n` + '🚀 *Bem-vindo ao OmniZap!*\n\n' + 'Para continuar, faça login com sua conta *Google* no link abaixo:\n\n' + `${loginUrl}\n\n` + '🔐 Seu número do WhatsApp será vinculado automaticamente à conta após o login.\n' + '⚠️ Use apenas este link e não compartilhe com outras pessoas.',
203
218
  });
204
219
 
205
220
  return true;
@@ -374,7 +389,7 @@ const ensureUserHasGoogleWebLoginForCommand = async ({ sock, messageInfo, sender
374
389
  }
375
390
 
376
391
  const loginUrl = isGroupMessage ? SITE_GROUP_LOGIN_URL : buildSiteLoginUrlForUser(resolvedCanonicalUserId || senderJid);
377
- const loginMessage = isGroupMessage ? `Para usar os comandos do bot, você precisa estar logado no site com sua conta Google.\n\nAcesse:\n${loginUrl}` : `Para usar os comandos do bot, você precisa estar logado no site com sua conta Google.\n\nCadastre-se / faça login em:\n${loginUrl}\n\nDepois volte aqui e envie o comando novamente (ex.: ${commandPrefix}menu).`;
392
+ const loginMessage = isGroupMessage ? '🔐 *Login necessário*\n\n' + 'Para usar os comandos do bot, você precisa estar logado com sua conta *Google*.\n\n' + 'Acesse o link abaixo para entrar:\n' + `${loginUrl}\n\n` + '⚠️ Por segurança, recomendamos abrir o link no privado.' : '🔐 *Login necessário*\n\n' + 'Para usar os comandos do bot, faça login com sua conta *Google* no link abaixo:\n\n' + `${loginUrl}\n\n` + '✅ Após entrar, volte aqui e envie o comando novamente\n' + `(ex.: *${commandPrefix}menu*).`;
378
393
 
379
394
  await sendReply(sock, remoteJid, messageInfo, expirationMessage, {
380
395
  text: loginMessage,
@@ -459,6 +474,43 @@ const resolveSenderOwnerForContext = async (ctx) => {
459
474
  return ctx.memo.senderOwnerPromise;
460
475
  };
461
476
 
477
+ const resolveGroupOwnerForContext = async (ctx, { bypassCache = false } = {}) => {
478
+ if (!ctx.isGroupMessage) return null;
479
+
480
+ if (!bypassCache && ctx.memo.groupOwnerPromise) {
481
+ return ctx.memo.groupOwnerPromise;
482
+ }
483
+
484
+ const fetchOwnerPromise = (async () => {
485
+ try {
486
+ const ownerState = await getGroupOwner(ctx.remoteJid, { bypassCache });
487
+ const ownerSessionId = String(ownerState?.ownerSessionId || '').trim() || null;
488
+ ctx.ownerSessionId = ownerSessionId;
489
+ mergeAnalysisMetadata(ctx.analysisPayload, {
490
+ owner_session_id: ownerSessionId,
491
+ });
492
+ return ownerState;
493
+ } catch (error) {
494
+ logger.warn('Falha ao resolver owner de grupo para roteamento de sessão.', {
495
+ action: 'group_owner_resolution_failed',
496
+ sessionId: ctx.sessionId,
497
+ groupId: ctx.remoteJid,
498
+ error: error?.message,
499
+ });
500
+ ctx.ownerSessionId = null;
501
+ mergeAnalysisMetadata(ctx.analysisPayload, {
502
+ owner_resolution_error: String(error?.code || error?.name || 'lookup_failed')
503
+ .trim()
504
+ .toLowerCase(),
505
+ });
506
+ return null;
507
+ }
508
+ })();
509
+
510
+ ctx.memo.groupOwnerPromise = fetchOwnerPromise;
511
+ return fetchOwnerPromise;
512
+ };
513
+
462
514
  const resolveHasGoogleLoginForContext = async (ctx) => {
463
515
  if (ctx.isMessageFromBot || !WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN) {
464
516
  return undefined;
@@ -476,10 +528,11 @@ const resolveHasGoogleLoginForContext = async (ctx) => {
476
528
  return ctx.memo.hasGoogleLoginPromise;
477
529
  };
478
530
 
479
- const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyUpsert, sock }) => {
531
+ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyUpsert, sock, sessionId }) => {
480
532
  const key = messageInfo?.key || {};
481
533
  const remoteJid = key?.remoteJid;
482
534
  if (!remoteJid) return null;
535
+ const normalizedSessionId = normalizeSessionId(sessionId);
483
536
 
484
537
  const isGroupMessage = isGroupJid(remoteJid);
485
538
  const extractedText = extractMessageContent(messageInfo);
@@ -505,6 +558,7 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
505
558
  .slice(0, 10);
506
559
 
507
560
  const analysisPayload = {
561
+ sessionId: normalizedSessionId,
508
562
  messageId: key?.id || null,
509
563
  chatId: remoteJid || null,
510
564
  senderId: senderJid || null,
@@ -530,6 +584,8 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
530
584
  upsert_type: upsertType,
531
585
  is_notify_upsert: isNotifyUpsert,
532
586
  is_history_append: upsertType === 'append',
587
+ session_id: normalizedSessionId,
588
+ owner_session_id: null,
533
589
  addressing_mode: addressingMode || null,
534
590
  participant_alt: key?.participantAlt || null,
535
591
  remote_jid_alt: key?.remoteJidAlt || null,
@@ -550,6 +606,8 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
550
606
  botJidCandidates,
551
607
  botJid,
552
608
  isMessageFromBot,
609
+ sessionId: normalizedSessionId,
610
+ ownerSessionId: null,
553
611
  commandPrefix: DEFAULT_COMMAND_PREFIX,
554
612
  groupConfig: null,
555
613
  groupConfigLoaded: false,
@@ -564,7 +622,7 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
564
622
  };
565
623
  };
566
624
 
567
- const { touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware } = createPreProcessingMiddlewares({
625
+ const { touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, enforceGroupOwnerMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware } = createPreProcessingMiddlewares({
568
626
  executeQuery,
569
627
  TABLES,
570
628
  isStatusJid,
@@ -577,6 +635,9 @@ const { touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, app
577
635
  ensureGroupConfigForContext,
578
636
  resolveStickerFocusState,
579
637
  resolveStickerFocusMessageClassification,
638
+ resolveGroupOwnerForContext,
639
+ ownerEnforcementMode: GROUP_OWNER_ENFORCEMENT_MODE,
640
+ primarySessionId: PRIMARY_SESSION_ID,
580
641
  resolveSenderAdminForContext,
581
642
  isUserAdmin,
582
643
  canSendMessageInStickerFocus,
@@ -605,6 +666,7 @@ const routeConversationMiddleware = createConversationMiddleware({
605
666
  sendReply,
606
667
  routeConversationMessage,
607
668
  stopMessagePipeline,
669
+ conversationAutoReplyEnabled: CONVERSATIONAL_AUTO_REPLY_ENABLED,
608
670
  });
609
671
 
610
672
  const executeCommandMiddleware = createCommandMiddleware({
@@ -642,7 +704,7 @@ const runPostProcessingMiddleware = createPostProcessingMiddleware({
642
704
  normalizeAnalysisErrorCode,
643
705
  });
644
706
 
645
- const MESSAGE_PIPELINE_MIDDLEWARES = [touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware, routeConversationMiddleware, executeCommandMiddleware, runPostProcessingMiddleware];
707
+ const MESSAGE_PIPELINE_MIDDLEWARES = [touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, enforceGroupOwnerMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware, routeConversationMiddleware, executeCommandMiddleware, runPostProcessingMiddleware];
646
708
 
647
709
  const runMessagePipeline = async (ctx) => {
648
710
  for (const middleware of MESSAGE_PIPELINE_MIDDLEWARES) {
@@ -657,7 +719,8 @@ const runMessagePipeline = async (ctx) => {
657
719
  *
658
720
  * @param {Object} update - Objeto contendo a atualização do WhatsApp.
659
721
  */
660
- export const handleMessagesThroughPipeline = async (update, sock) => {
722
+ export const handleMessagesThroughPipeline = async (update, sock, options = {}) => {
723
+ const sessionId = normalizeSessionId(options?.sessionId);
661
724
  if (update.messages && Array.isArray(update.messages)) {
662
725
  try {
663
726
  const upsertType = update?.type || null;
@@ -669,6 +732,7 @@ export const handleMessagesThroughPipeline = async (update, sock) => {
669
732
  upsertType,
670
733
  isNotifyUpsert,
671
734
  sock,
735
+ sessionId,
672
736
  });
673
737
  if (!context) continue;
674
738
 
@@ -678,19 +742,34 @@ export const handleMessagesThroughPipeline = async (update, sock) => {
678
742
  context.analysisPayload.processingResult = 'error';
679
743
  context.analysisPayload.errorCode = normalizeAnalysisErrorCode(messageError);
680
744
  logger.error('Erro ao processar mensagem individual:', {
745
+ sessionId,
746
+ ownerSessionId: context.ownerSessionId || null,
681
747
  error: messageError?.message,
682
748
  messageId: context.key?.id || null,
683
749
  remoteJid: context.remoteJid,
684
750
  });
685
751
  } finally {
752
+ logger.debug('Mensagem processada pelo pipeline.', {
753
+ action: 'message_pipeline_processed',
754
+ sessionId: context.sessionId,
755
+ ownerSessionId: context.ownerSessionId || null,
756
+ messageId: context.key?.id || null,
757
+ remoteJid: context.remoteJid,
758
+ result: context.analysisPayload.processingResult,
759
+ isCommand: context.analysisPayload.isCommand,
760
+ });
686
761
  persistMessageAnalysisEvent(context.analysisPayload);
687
762
  }
688
763
  }
689
764
  } catch (error) {
690
- logger.error('Erro ao processar mensagens:', error?.message);
765
+ logger.error('Erro ao processar mensagens:', {
766
+ sessionId,
767
+ error: error?.message,
768
+ });
691
769
  }
692
770
  } else {
693
771
  logger.info('🔄 Processando evento recebido:', {
772
+ sessionId,
694
773
  eventType: update?.type || 'unknown',
695
774
  eventData: update,
696
775
  });