@omnizap-system/omnizap 2.6.0 → 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 (261) hide show
  1. package/.env.example +58 -13
  2. package/.github/workflows/ci.yml +5 -5
  3. package/.github/workflows/codeql.yml +1 -1
  4. package/.github/workflows/db-migration-check.yml +2 -2
  5. package/.github/workflows/dependency-review.yml +1 -1
  6. package/.github/workflows/deploy.yml +2 -2
  7. package/.github/workflows/release.yml +2 -2
  8. package/.github/workflows/security-attest-provenance.yml +2 -2
  9. package/.github/workflows/security-gitleaks.yml +13 -4
  10. package/.github/workflows/security-runner-hardening.yml +2 -2
  11. package/.github/workflows/security-scorecard.yml +1 -1
  12. package/.github/workflows/security-zap-baseline.yml +1 -1
  13. package/.github/workflows/security-zap-full-scan.yml +2 -1
  14. package/.github/workflows/security-zizmor.yml +1 -1
  15. package/.github/workflows/wiki-sync.yml +1 -1
  16. package/.gitleaksignore +9 -0
  17. package/CODE_OF_CONDUCT.md +2 -2
  18. package/GEMINI.md +64 -0
  19. package/README.md +52 -82
  20. package/SECURITY.md +1 -1
  21. package/app/config/index.js +2 -0
  22. package/app/configParts/adminIdentity.js +5 -5
  23. package/app/configParts/baileysConfig.js +230 -58
  24. package/app/configParts/groupUtils.js +5 -0
  25. package/app/configParts/messagePersistenceService.js +145 -4
  26. package/app/configParts/sessionConfig.js +157 -0
  27. package/app/connection/baileysCompatibility.test.js +1 -1
  28. package/app/connection/groupOwnerWriteStateResolver.js +109 -0
  29. package/app/connection/socketController.js +660 -158
  30. package/app/connection/socketController.multiSession.test.js +108 -0
  31. package/app/controllers/messageController.js +1 -1
  32. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  33. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  34. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  35. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
  36. package/app/controllers/messageProcessingPipeline.js +93 -13
  37. package/app/controllers/messageProcessingPipeline.test.js +200 -0
  38. package/app/modules/adminModule/AGENT.md +1 -1
  39. package/app/modules/adminModule/commandConfig.json +3318 -1347
  40. package/app/modules/adminModule/groupCommandHandlers.js +858 -15
  41. package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
  42. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  43. package/app/modules/aiModule/AGENT.md +47 -30
  44. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  45. package/app/modules/aiModule/catCommand.js +135 -27
  46. package/app/modules/aiModule/commandConfig.json +114 -28
  47. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  48. package/app/modules/gameModule/AGENT.md +1 -1
  49. package/app/modules/gameModule/commandConfig.json +29 -0
  50. package/app/modules/menuModule/AGENT.md +1 -1
  51. package/app/modules/menuModule/commandConfig.json +45 -10
  52. package/app/modules/menuModule/menuCatalogService.js +190 -0
  53. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  54. package/app/modules/menuModule/menuDynamicService.js +511 -0
  55. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  56. package/app/modules/menuModule/menus.js +36 -5
  57. package/app/modules/playModule/AGENT.md +10 -5
  58. package/app/modules/playModule/commandConfig.json +140 -12
  59. package/app/modules/playModule/playCommand.js +1 -1417
  60. package/app/modules/playModule/playCommandConstants.js +80 -0
  61. package/app/modules/playModule/playCommandCore.js +361 -0
  62. package/app/modules/playModule/playCommandHandlers.js +41 -0
  63. package/app/modules/playModule/playCommandMediaClient.js +1872 -0
  64. package/app/modules/playModule/playConfigRuntime.js +245 -4
  65. package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
  66. package/app/modules/quoteModule/AGENT.md +1 -1
  67. package/app/modules/quoteModule/commandConfig.json +29 -0
  68. package/app/modules/quoteModule/quoteCommand.js +3 -2
  69. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  70. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  71. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
  72. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
  73. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
  74. package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
  75. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
  76. package/app/modules/statsModule/AGENT.md +1 -1
  77. package/app/modules/statsModule/commandConfig.json +58 -0
  78. package/app/modules/statsModule/rankingCommon.js +5 -4
  79. package/app/modules/stickerModule/AGENT.md +1 -1
  80. package/app/modules/stickerModule/addStickerMetadata.js +4 -3
  81. package/app/modules/stickerModule/commandConfig.json +145 -0
  82. package/app/modules/stickerModule/stickerCommand.js +1 -1
  83. package/app/modules/stickerPackModule/AGENT.md +1 -1
  84. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  85. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  86. package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
  87. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
  88. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
  89. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
  90. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
  91. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
  92. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
  93. package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
  94. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
  95. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  96. package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
  97. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
  98. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  99. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  100. package/app/modules/systemMetricsModule/pingCommand.js +6 -5
  101. package/app/modules/tiktokModule/AGENT.md +1 -1
  102. package/app/modules/tiktokModule/commandConfig.json +29 -0
  103. package/app/modules/tiktokModule/tiktokCommand.js +2 -1
  104. package/app/modules/userModule/AGENT.md +1 -1
  105. package/app/modules/userModule/commandConfig.json +29 -0
  106. package/app/modules/userModule/userCommand.js +72 -23
  107. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  108. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  109. package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
  110. package/app/observability/metrics.js +136 -0
  111. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  112. package/app/services/ai/conversationRouterService.js +4 -3
  113. package/app/services/ai/geminiService.js +132 -7
  114. package/app/services/ai/geminiService.test.js +59 -2
  115. package/app/services/ai/globalModuleAiHelpService.js +3 -2
  116. package/app/services/ai/messageCommandExecutionService.js +2 -1
  117. package/app/services/ai/moduleAiHelpCoreService.js +45 -14
  118. package/app/services/ai/moduleToolExecutorService.js +3 -2
  119. package/app/services/ai/moduleToolRegistryService.js +2 -1
  120. package/app/services/ai/toolCandidateSelectorService.js +6 -5
  121. package/app/services/auth/googleWebLinkService.js +3 -2
  122. package/app/services/auth/whatsappLoginLinkService.js +3 -2
  123. package/app/services/external/pokeApiService.js +4 -3
  124. package/app/services/group/groupMetadataService.js +24 -1
  125. package/app/services/infra/dbWriteQueue.js +57 -26
  126. package/app/services/infra/featureFlagService.js +2 -1
  127. package/app/services/messaging/captchaService.js +3 -2
  128. package/app/services/messaging/newsBroadcastService.js +846 -29
  129. package/app/services/multiSession/assignmentBalancerService.js +457 -0
  130. package/app/services/multiSession/groupOwnershipRepository.js +381 -0
  131. package/app/services/multiSession/groupOwnershipService.js +890 -0
  132. package/app/services/multiSession/groupOwnershipService.test.js +309 -0
  133. package/app/services/multiSession/sessionRegistryService.js +293 -0
  134. package/app/services/sticker/stickerFocusService.js +11 -10
  135. package/app/store/aiPromptStore.js +36 -19
  136. package/app/store/conversationSessionStore.js +7 -6
  137. package/app/store/groupConfigStore.js +41 -5
  138. package/app/store/premiumUserStore.js +21 -7
  139. package/app/utils/antiLink/antiLinkModule.js +352 -16
  140. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  141. package/app/workers/aiLearningWorker.js +6 -5
  142. package/app/workers/commandConfigEnrichmentWorker.js +4 -3
  143. package/database/index.js +14 -8
  144. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  145. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  146. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  147. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  148. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  149. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  150. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  151. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  152. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  153. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  154. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  155. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  156. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  157. package/database/schema.sql +102 -1
  158. package/docker-compose.yml +4 -1
  159. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  160. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
  161. package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
  162. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  163. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
  164. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  165. package/docs/security/omnizap-static-security-headers.conf +25 -0
  166. package/docs/wiki/Home.md +1 -1
  167. package/ecosystem.prod.config.cjs +32 -12
  168. package/index.js +57 -23
  169. package/observability/alert-rules.yml +20 -0
  170. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  171. package/observability/mysql-setup.sql +4 -4
  172. package/observability/system-admin-observability.md +26 -0
  173. package/package.json +20 -6
  174. package/public/apple-touch-icon.png +0 -0
  175. package/public/comandos/commands-catalog.json +2853 -3326
  176. package/public/favicon-16x16.png +0 -0
  177. package/public/favicon-32x32.png +0 -0
  178. package/public/favicon.ico +0 -0
  179. package/public/js/apps/apiDocsApp.js +3 -2
  180. package/public/js/apps/commandsReactApp.js +280 -99
  181. package/public/js/apps/createPackApp.js +11 -10
  182. package/public/js/apps/homeReactApp.js +181 -130
  183. package/public/js/apps/loginReactApp.js +1 -1
  184. package/public/js/apps/stickersApp.js +263 -110
  185. package/public/js/apps/termsReactApp.js +73 -24
  186. package/public/js/apps/userApp.js +4 -3
  187. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  188. package/public/js/apps/userReactApp.js +355 -280
  189. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  190. package/public/pages/api-docs.html +1 -1
  191. package/public/pages/aup.html +2 -2
  192. package/public/pages/dpa.html +3 -3
  193. package/public/pages/licenca.html +4 -4
  194. package/public/pages/login.html +1 -1
  195. package/public/pages/notice-and-takedown.html +2 -2
  196. package/public/pages/politica-de-privacidade.html +6 -6
  197. package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
  198. package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
  199. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
  200. package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
  201. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
  202. package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
  203. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
  204. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
  205. package/public/pages/stickers-admin.html +1 -1
  206. package/public/pages/stickers-create.html +1 -1
  207. package/public/pages/stickers.html +6 -6
  208. package/public/pages/suboperadores.html +2 -2
  209. package/public/pages/termos-de-uso-texto-integral.html +6 -6
  210. package/public/pages/termos-de-uso.html +3 -3
  211. package/public/pages/user-password-reset.html +4 -5
  212. package/public/pages/user-systemadm.html +9 -463
  213. package/public/pages/user.html +2 -2
  214. package/scripts/clear-whatsapp-session.sh +123 -0
  215. package/scripts/core-ai-mode.mjs +163 -0
  216. package/scripts/deploy.sh +11 -1
  217. package/scripts/email-broadcast-terms-update.mjs +2 -1
  218. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  219. package/scripts/generate-commands-catalog.mjs +166 -2
  220. package/scripts/generate-module-agents.mjs +2 -1
  221. package/scripts/generate-seo-satellite-pages.mjs +5 -4
  222. package/scripts/github-deploy-notify.mjs +2 -1
  223. package/scripts/github-release-notify.mjs +25 -10
  224. package/scripts/new-whatsapp-session.sh +317 -0
  225. package/scripts/release.sh +2 -19
  226. package/scripts/security-smoketest.mjs +6 -5
  227. package/scripts/security-web-surface-check.mjs +218 -0
  228. package/scripts/sticker-catalog-loadtest.mjs +5 -4
  229. package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
  230. package/server/auth/jwt/webJwtService.js +1 -1
  231. package/server/auth/stickerCatalogAuthContext.js +2 -1
  232. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
  233. package/server/auth/userPassword/userPasswordAuthService.js +2 -1
  234. package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
  235. package/server/auth/webAccount/webAccountHandlers.js +9 -10
  236. package/server/controllers/admin/adminPanelHandlers.js +267 -16
  237. package/server/controllers/admin/systemAdminController.js +267 -0
  238. package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
  239. package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
  240. package/server/controllers/sticker/stickerCatalogController.js +23 -36
  241. package/server/controllers/system/contactController.js +9 -17
  242. package/server/controllers/system/githubController.js +3 -2
  243. package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
  244. package/server/controllers/system/systemController.js +254 -1
  245. package/server/controllers/system/systemMetricsController.js +2 -1
  246. package/server/controllers/userController.js +6 -0
  247. package/server/email/emailTemplateService.js +5 -3
  248. package/server/http/httpServer.js +11 -6
  249. package/server/middleware/rateLimit.js +2 -1
  250. package/server/middleware/securityHeaders.js +20 -1
  251. package/server/routes/admin/systemAdminRouter.js +6 -0
  252. package/server/routes/indexRouter.js +30 -6
  253. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  254. package/server/routes/static/staticPageRouter.js +27 -1
  255. package/server/utils/publicContact.js +31 -0
  256. package/utils/time/timeModule.js +135 -0
  257. package/utils/time/timeModule.test.js +65 -0
  258. package/utils/whatsapp/contactEnv.js +39 -0
  259. package/vite.config.mjs +7 -1
  260. package/public/assets/images/brand-icon-192.png +0 -0
  261. package/scripts/sync-readme-snapshot.mjs +0 -133
@@ -0,0 +1,512 @@
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
2
+ import logger from '#logger';
3
+ import { getAllToolRecords } from '../services/ai/moduleToolRegistryService.js';
4
+ import { applyCommandConfigEnrichmentSuggestion, saveCommandConfigEnrichmentSuggestion } from '../services/ai/commandConfigEnrichmentRepository.js';
5
+ import { generateCommandConfigEnrichmentSuggestion } from '../services/ai/commandConfigEnrichmentService.js';
6
+ import { upsertAiHelpCachedResponse } from '../services/ai/aiHelpResponseCacheRepository.js';
7
+ import { markToolCandidateCommandConfigCacheDirty } from '../services/ai/toolCandidateSelectorService.js';
8
+
9
+ const DEFAULT_INTERVAL_MS = 4 * 60 * 1000;
10
+ const DEFAULT_BATCH_SIZE = 12;
11
+ const DEFAULT_MIN_AUTO_APPLY_CONFIDENCE = 0.7;
12
+ const DEFAULT_MAX_HELP_QUESTIONS_PER_COMMAND = 3;
13
+ const DEFAULT_MAX_HELP_CALLS_PER_CYCLE = 42;
14
+
15
+ const parseEnvBool = (value, fallback) => {
16
+ if (value === undefined || value === null || value === '') return fallback;
17
+ const normalized = String(value).trim().toLowerCase();
18
+ if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
19
+ if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
20
+ return fallback;
21
+ };
22
+
23
+ const parseEnvInt = (value, fallback, min, max) => {
24
+ const parsed = Number.parseInt(String(value ?? ''), 10);
25
+ if (!Number.isFinite(parsed)) return fallback;
26
+ return Math.max(min, Math.min(max, parsed));
27
+ };
28
+
29
+ const parseEnvFloat = (value, fallback, min, max) => {
30
+ const parsed = Number.parseFloat(String(value ?? ''));
31
+ if (!Number.isFinite(parsed)) return fallback;
32
+ return Math.max(min, Math.min(max, parsed));
33
+ };
34
+
35
+ const AI_HELP_CONTINUOUS_LEARNING_ENABLED = parseEnvBool(process.env.AI_HELP_CONTINUOUS_LEARNING_ENABLED, true);
36
+ const AI_HELP_CONTINUOUS_LEARNING_INTERVAL_MS = parseEnvInt(process.env.AI_HELP_CONTINUOUS_LEARNING_INTERVAL_MS, DEFAULT_INTERVAL_MS, 45_000, 24 * 60 * 60 * 1000);
37
+ const AI_HELP_CONTINUOUS_LEARNING_BATCH_SIZE = parseEnvInt(process.env.AI_HELP_CONTINUOUS_LEARNING_BATCH_SIZE, DEFAULT_BATCH_SIZE, 1, 120);
38
+ const AI_HELP_CONTINUOUS_LEARNING_MIN_AUTO_APPLY_CONFIDENCE = parseEnvFloat(process.env.AI_HELP_CONTINUOUS_LEARNING_MIN_AUTO_APPLY_CONFIDENCE, DEFAULT_MIN_AUTO_APPLY_CONFIDENCE, 0.1, 0.99);
39
+ const AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_QUESTIONS_PER_COMMAND = parseEnvInt(process.env.AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_QUESTIONS_PER_COMMAND, DEFAULT_MAX_HELP_QUESTIONS_PER_COMMAND, 1, 12);
40
+ const AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_CALLS_PER_CYCLE = parseEnvInt(process.env.AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_CALLS_PER_CYCLE, DEFAULT_MAX_HELP_CALLS_PER_CYCLE, 1, 250);
41
+
42
+ let schedulerHandle = null;
43
+ let schedulerStarted = false;
44
+ let cycleInProgress = false;
45
+ let proactiveCursorIndex = 0;
46
+ let proactiveRound = 0;
47
+ let proactiveRegistrySignature = '';
48
+
49
+ const normalizeText = (value) =>
50
+ String(value || '')
51
+ .trim()
52
+ .toLowerCase()
53
+ .normalize('NFD')
54
+ .replace(/[\u0300-\u036f]/g, '')
55
+ .replace(/[^a-z0-9\s/_.-]/g, ' ')
56
+ .replace(/\s+/g, ' ')
57
+ .trim();
58
+
59
+ const normalizeDisplayText = (value) =>
60
+ String(value || '')
61
+ .trim()
62
+ .replace(/\s+/g, ' ');
63
+
64
+ const uniqueList = (items = [], limit = 8) => {
65
+ const output = [];
66
+ const seen = new Set();
67
+ for (const item of Array.isArray(items) ? items : []) {
68
+ const normalized = normalizeDisplayText(item);
69
+ if (!normalized) continue;
70
+ const dedupeKey = normalizeText(normalized);
71
+ if (!dedupeKey || seen.has(dedupeKey)) continue;
72
+ seen.add(dedupeKey);
73
+ output.push(normalized);
74
+ if (output.length >= limit) break;
75
+ }
76
+ return output;
77
+ };
78
+
79
+ const ensureArray = (value) => (Array.isArray(value) ? value : []);
80
+ const pickFirstText = (...values) => {
81
+ for (const value of values) {
82
+ const text = String(value ?? '').trim();
83
+ if (text) return text;
84
+ }
85
+ return '';
86
+ };
87
+
88
+ const readCommandUsage = (entry = {}) => {
89
+ const usageV2 = ensureArray(entry?.usage);
90
+ if (usageV2.length) return usageV2;
91
+ const docsUsage = ensureArray(entry?.docs?.usage_examples);
92
+ if (docsUsage.length) return docsUsage;
93
+ return ensureArray(entry?.metodos_de_uso);
94
+ };
95
+
96
+ const readCommandFaqPatterns = (entry = {}) => {
97
+ const discovery = entry?.discovery && typeof entry.discovery === 'object' && !Array.isArray(entry.discovery) ? entry.discovery : {};
98
+ const source = ensureArray(discovery?.faq_queries).length ? discovery.faq_queries : entry?.faq_patterns;
99
+ return ensureArray(source);
100
+ };
101
+
102
+ const readCommandUserPhrasings = (entry = {}) => {
103
+ const discovery = entry?.discovery && typeof entry.discovery === 'object' && !Array.isArray(entry.discovery) ? entry.discovery : {};
104
+ const source = ensureArray(discovery?.user_phrasings).length ? discovery.user_phrasings : entry?.user_phrasings;
105
+ return ensureArray(source);
106
+ };
107
+
108
+ const readCommandDescription = (entry = {}) => pickFirstText(entry?.description, entry?.docs?.summary, entry?.descricao);
109
+
110
+ const readCommandPermission = (entry = {}) => pickFirstText(entry?.permission, entry?.permissao_necessaria);
111
+
112
+ const readCommandContexts = (entry = {}) => {
113
+ const contextsV2 = ensureArray(entry?.contexts);
114
+ if (contextsV2.length) return contextsV2;
115
+ return ensureArray(entry?.local_de_uso);
116
+ };
117
+
118
+ const readCommandUsageLimit = (entry = {}) => pickFirstText(entry?.limits?.usage_description, entry?.limite_de_uso);
119
+
120
+ const renderUsage = (method, commandPrefix = '/') => String(method || '').replaceAll('<prefix>', commandPrefix);
121
+
122
+ const computeRegistrySignature = (records = []) => records.map((record) => `${record?.toolName || ''}:${record?.moduleKey || ''}:${record?.commandName || ''}`).join('|');
123
+
124
+ const buildDeterministicExplainAnswer = ({ record, commandPrefix = '/' }) => {
125
+ const entry = record?.commandEntry && typeof record.commandEntry === 'object' ? record.commandEntry : {};
126
+ const commandName = String(record?.commandName || '').trim();
127
+ const commandToken = `${commandPrefix}${commandName}`;
128
+ const usage = readCommandUsage(entry).map((line) => renderUsage(line, commandPrefix));
129
+ const description = readCommandDescription(entry) || 'Sem descricao cadastrada.';
130
+ const permission = readCommandPermission(entry) || 'nao definido';
131
+ const contexts = readCommandContexts(entry);
132
+ const whereLabel = contexts.length ? contexts.join(', ') : 'nao definido';
133
+ const usageLimit = readCommandUsageLimit(entry) || 'nao informado';
134
+
135
+ const lines = [`Comando: ${commandToken}`, `Resumo: ${description}`, '', `Quem pode usar: ${permission}`, `Onde pode usar: ${whereLabel}`, `Limite: ${usageLimit}`, '', 'Como usar:', ...(usage.length ? usage.map((line) => `- ${line}`) : [`- ${commandToken}`]), '', 'Resposta pre-carregada em background para acelerar o IA Helper.'];
136
+ return lines.join('\n');
137
+ };
138
+
139
+ const buildDeterministicQuestionAnswer = ({ question, record, commandPrefix = '/' }) => {
140
+ const entry = record?.commandEntry && typeof record.commandEntry === 'object' ? record.commandEntry : {};
141
+ const commandName = String(record?.commandName || '').trim();
142
+ const commandToken = `${commandPrefix}${commandName}`;
143
+ const usage = readCommandUsage(entry).map((line) => renderUsage(line, commandPrefix));
144
+ const description = readCommandDescription(entry) || 'Sem descricao cadastrada.';
145
+ const start = normalizeDisplayText(question);
146
+
147
+ return [start ? `Pergunta: ${start}` : `Comando: ${commandToken}`, '', `Resumo: ${description}`, usage.length ? `Exemplo: ${usage[0]}` : `Exemplo: ${commandToken}`, `Para detalhes completos, use: ${commandPrefix}help ${commandName}`].join('\n');
148
+ };
149
+
150
+ const buildSyntheticEvent = ({ record, round = 0 }) => {
151
+ const entry = record?.commandEntry && typeof record.commandEntry === 'object' ? record.commandEntry : {};
152
+ const commandName = String(record?.commandName || '').trim();
153
+ const usage = readCommandUsage(entry);
154
+ const faq = readCommandFaqPatterns(entry);
155
+ const phrasings = readCommandUserPhrasings(entry);
156
+
157
+ const candidates = uniqueList([...phrasings, ...faq, ...usage, `como usar ${commandName}`, `o que faz ${commandName}`, `quando devo usar ${commandName}`, `me explica o comando ${commandName}`], 18);
158
+
159
+ const picked = candidates.length ? candidates[round % candidates.length] : `como usar ${commandName}`;
160
+ return {
161
+ id: null,
162
+ user_question: picked,
163
+ normalized_question: normalizeText(picked),
164
+ tool_suggested: record?.toolName || commandName,
165
+ tool_executed: record?.toolName || commandName,
166
+ success: true,
167
+ confidence: 0.92,
168
+ };
169
+ };
170
+
171
+ const buildHelpWarmupQuestions = ({ record, syntheticEvent }) => {
172
+ const entry = record?.commandEntry && typeof record.commandEntry === 'object' ? record.commandEntry : {};
173
+ const commandName = String(record?.commandName || '').trim();
174
+ const usage = readCommandUsage(entry);
175
+ const faq = readCommandFaqPatterns(entry);
176
+ const phrasings = readCommandUserPhrasings(entry);
177
+
178
+ return uniqueList([syntheticEvent?.user_question || '', ...phrasings, ...faq, ...usage, `como usar /${commandName}`, `quero exemplo real de ${commandName}`, `o que eu recebo de resposta ao usar ${commandName}`], AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_QUESTIONS_PER_COMMAND);
179
+ };
180
+
181
+ const saveAiHelpSeedCacheEntries = async ({ record, syntheticEvent, maxEntries = 3 }) => {
182
+ const result = {
183
+ helpCalls: 0,
184
+ helpErrors: 0,
185
+ };
186
+
187
+ const commandName = String(record?.commandName || '').trim();
188
+ const moduleKey = String(record?.moduleKey || '').trim();
189
+ if (!commandName || !moduleKey) return result;
190
+
191
+ const explainQuestion = `explicar comando ${commandName}`;
192
+ const explainAnswer = buildDeterministicExplainAnswer({
193
+ record,
194
+ commandPrefix: '/',
195
+ });
196
+
197
+ try {
198
+ await upsertAiHelpCachedResponse({
199
+ moduleKey,
200
+ scope: 'command_explain',
201
+ question: explainQuestion,
202
+ normalizedQuestion: explainQuestion,
203
+ answer: explainAnswer,
204
+ source: 'continuous_seed',
205
+ commandName,
206
+ metadata: {
207
+ mode: 'continuous_learning',
208
+ reason: 'command_explain_seed',
209
+ },
210
+ });
211
+ result.helpCalls += 1;
212
+ } catch (error) {
213
+ result.helpErrors += 1;
214
+ logger.warn('Falha ao persistir seed de command_explain no IA Helper continuo.', {
215
+ action: 'ai_helper_continuous_learning_cache_seed_failed',
216
+ module: moduleKey,
217
+ command: commandName,
218
+ error: error?.message,
219
+ });
220
+ }
221
+
222
+ const questions = buildHelpWarmupQuestions({
223
+ record,
224
+ syntheticEvent,
225
+ }).slice(0, Math.max(0, maxEntries - result.helpCalls));
226
+
227
+ for (const question of questions) {
228
+ try {
229
+ await upsertAiHelpCachedResponse({
230
+ moduleKey,
231
+ scope: 'question',
232
+ question,
233
+ normalizedQuestion: question,
234
+ answer: buildDeterministicQuestionAnswer({
235
+ question,
236
+ record,
237
+ commandPrefix: '/',
238
+ }),
239
+ source: 'continuous_seed',
240
+ commandName,
241
+ metadata: {
242
+ mode: 'continuous_learning',
243
+ reason: 'question_seed',
244
+ },
245
+ });
246
+ result.helpCalls += 1;
247
+ } catch (error) {
248
+ result.helpErrors += 1;
249
+ logger.warn('Falha ao persistir seed de question no IA Helper continuo.', {
250
+ action: 'ai_helper_continuous_learning_cache_question_seed_failed',
251
+ module: moduleKey,
252
+ command: commandName,
253
+ question,
254
+ error: error?.message,
255
+ });
256
+ }
257
+ }
258
+
259
+ return result;
260
+ };
261
+
262
+ const selectProactiveBatch = () => {
263
+ const records = getAllToolRecords();
264
+ if (!records.length) {
265
+ proactiveCursorIndex = 0;
266
+ proactiveRound = 0;
267
+ proactiveRegistrySignature = '';
268
+ return {
269
+ records: [],
270
+ batch: [],
271
+ previousCursor: 0,
272
+ nextCursor: 0,
273
+ completedRound: false,
274
+ };
275
+ }
276
+
277
+ const signature = computeRegistrySignature(records);
278
+ if (signature !== proactiveRegistrySignature) {
279
+ proactiveRegistrySignature = signature;
280
+ proactiveCursorIndex = 0;
281
+ proactiveRound = 0;
282
+ }
283
+
284
+ const previousCursor = proactiveCursorIndex;
285
+ const safeBatchSize = Math.max(1, Math.min(AI_HELP_CONTINUOUS_LEARNING_BATCH_SIZE, records.length));
286
+ const batch = [];
287
+ for (let index = 0; index < safeBatchSize; index += 1) {
288
+ const cursor = (proactiveCursorIndex + index) % records.length;
289
+ batch.push(records[cursor]);
290
+ }
291
+
292
+ proactiveCursorIndex = (proactiveCursorIndex + safeBatchSize) % records.length;
293
+ const completedRound = records.length > 0 && proactiveCursorIndex === 0;
294
+ if (completedRound) proactiveRound += 1;
295
+
296
+ return {
297
+ records,
298
+ batch,
299
+ previousCursor,
300
+ nextCursor: proactiveCursorIndex,
301
+ completedRound,
302
+ };
303
+ };
304
+
305
+ const processProactiveCommand = async ({ record, round = 0, helpCallBudget = Infinity }) => {
306
+ const syntheticEvent = buildSyntheticEvent({
307
+ record,
308
+ round,
309
+ });
310
+
311
+ const result = {
312
+ suggestionGenerated: 0,
313
+ suggestionApplied: 0,
314
+ suggestionChanged: 0,
315
+ helpCalls: 0,
316
+ helpErrors: 0,
317
+ };
318
+
319
+ const suggestionOutput = await generateCommandConfigEnrichmentSuggestion({
320
+ learningEvent: syntheticEvent,
321
+ toolRecord: record,
322
+ });
323
+
324
+ if (suggestionOutput?.suggestion) {
325
+ const savedSuggestion = await saveCommandConfigEnrichmentSuggestion({
326
+ moduleKey: record.moduleKey,
327
+ commandName: record.commandName,
328
+ sourceTool: record.toolName,
329
+ sourceEventId: null,
330
+ question: syntheticEvent.user_question,
331
+ normalizedQuestion: syntheticEvent.normalized_question,
332
+ suggestion: suggestionOutput.suggestion,
333
+ confidence: suggestionOutput.confidence,
334
+ modelName: suggestionOutput.modelName,
335
+ source: suggestionOutput.source,
336
+ status: 'pending',
337
+ });
338
+
339
+ if (savedSuggestion?.id) {
340
+ result.suggestionGenerated += 1;
341
+ const sourceValue = String(savedSuggestion.source || '');
342
+ const shouldAutoApply = savedSuggestion.confidence >= AI_HELP_CONTINUOUS_LEARNING_MIN_AUTO_APPLY_CONFIDENCE && sourceValue.startsWith('llm');
343
+
344
+ if (shouldAutoApply) {
345
+ const applyResult = await applyCommandConfigEnrichmentSuggestion({
346
+ suggestionId: savedSuggestion.id,
347
+ reviewNotes: `auto_apply_proactive: confidence>=${AI_HELP_CONTINUOUS_LEARNING_MIN_AUTO_APPLY_CONFIDENCE}`,
348
+ });
349
+ if (applyResult?.applied) {
350
+ result.suggestionApplied += 1;
351
+ if (applyResult.changed) result.suggestionChanged += 1;
352
+ }
353
+ }
354
+ }
355
+ }
356
+
357
+ if (helpCallBudget <= 0) {
358
+ return result;
359
+ }
360
+
361
+ const cacheSeedResult = await saveAiHelpSeedCacheEntries({
362
+ record,
363
+ syntheticEvent,
364
+ maxEntries: Math.max(1, helpCallBudget),
365
+ });
366
+ result.helpCalls += cacheSeedResult.helpCalls;
367
+ result.helpErrors += cacheSeedResult.helpErrors;
368
+
369
+ return result;
370
+ };
371
+
372
+ const processContinuousLearningBatch = async ({ reason = 'scheduler' } = {}) => {
373
+ if (cycleInProgress) return;
374
+ if (!AI_HELP_CONTINUOUS_LEARNING_ENABLED) return;
375
+
376
+ cycleInProgress = true;
377
+ const startedAt = __timeNowMs();
378
+
379
+ try {
380
+ const selected = selectProactiveBatch();
381
+ if (!selected.batch.length) {
382
+ logger.info('Worker de aprendizado continuo IA sem comandos no registry.', {
383
+ action: 'ai_helper_continuous_learning_cycle_processed',
384
+ reason,
385
+ fetched_commands: 0,
386
+ generated_suggestions: 0,
387
+ applied_suggestions: 0,
388
+ changed_suggestions: 0,
389
+ help_calls: 0,
390
+ help_errors: 0,
391
+ duration_ms: __timeNowMs() - startedAt,
392
+ });
393
+ return;
394
+ }
395
+
396
+ let generatedSuggestions = 0;
397
+ let appliedSuggestions = 0;
398
+ let changedSuggestions = 0;
399
+ let helpCalls = 0;
400
+ let helpErrors = 0;
401
+ let processedCommands = 0;
402
+
403
+ for (const record of selected.batch) {
404
+ if (!record) continue;
405
+ const remainingHelpBudget = AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_CALLS_PER_CYCLE - helpCalls;
406
+ if (remainingHelpBudget <= 0 && processedCommands > 0) {
407
+ break;
408
+ }
409
+
410
+ try {
411
+ const commandResult = await processProactiveCommand({
412
+ record,
413
+ round: proactiveRound,
414
+ helpCallBudget: Math.max(0, remainingHelpBudget),
415
+ });
416
+ generatedSuggestions += commandResult.suggestionGenerated;
417
+ appliedSuggestions += commandResult.suggestionApplied;
418
+ changedSuggestions += commandResult.suggestionChanged;
419
+ helpCalls += commandResult.helpCalls;
420
+ helpErrors += commandResult.helpErrors;
421
+ processedCommands += 1;
422
+ } catch (error) {
423
+ logger.warn('Falha ao processar comando no worker de aprendizado continuo IA.', {
424
+ action: 'ai_helper_continuous_learning_command_failed',
425
+ module: record?.moduleKey || null,
426
+ command: record?.commandName || null,
427
+ error: error?.message,
428
+ });
429
+ }
430
+ }
431
+
432
+ if (changedSuggestions > 0) {
433
+ markToolCandidateCommandConfigCacheDirty();
434
+ }
435
+
436
+ logger.info('Ciclo do worker de aprendizado continuo IA concluido.', {
437
+ action: 'ai_helper_continuous_learning_cycle_processed',
438
+ reason,
439
+ previous_cursor: selected.previousCursor,
440
+ next_cursor: selected.nextCursor,
441
+ completed_round: selected.completedRound,
442
+ round: proactiveRound,
443
+ registry_size: selected.records.length,
444
+ fetched_commands: selected.batch.length,
445
+ processed_commands: processedCommands,
446
+ generated_suggestions: generatedSuggestions,
447
+ applied_suggestions: appliedSuggestions,
448
+ changed_suggestions: changedSuggestions,
449
+ help_calls: helpCalls,
450
+ help_errors: helpErrors,
451
+ max_help_calls_per_cycle: AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_CALLS_PER_CYCLE,
452
+ duration_ms: __timeNowMs() - startedAt,
453
+ });
454
+ } finally {
455
+ cycleInProgress = false;
456
+ }
457
+ };
458
+
459
+ export const startAiHelperContinuousLearningWorker = () => {
460
+ if (schedulerStarted) return;
461
+
462
+ if (!AI_HELP_CONTINUOUS_LEARNING_ENABLED) {
463
+ logger.info('Worker de aprendizado continuo IA desativado.', {
464
+ action: 'ai_helper_continuous_learning_worker_disabled',
465
+ enabled: AI_HELP_CONTINUOUS_LEARNING_ENABLED,
466
+ });
467
+ return;
468
+ }
469
+
470
+ schedulerStarted = true;
471
+ void processContinuousLearningBatch({ reason: 'startup' });
472
+
473
+ schedulerHandle = setInterval(() => {
474
+ void processContinuousLearningBatch({ reason: 'scheduler' });
475
+ }, AI_HELP_CONTINUOUS_LEARNING_INTERVAL_MS);
476
+ if (typeof schedulerHandle?.unref === 'function') {
477
+ schedulerHandle.unref();
478
+ }
479
+
480
+ logger.info('Scheduler do worker de aprendizado continuo IA iniciado.', {
481
+ action: 'ai_helper_continuous_learning_worker_scheduler_started',
482
+ interval_ms: AI_HELP_CONTINUOUS_LEARNING_INTERVAL_MS,
483
+ batch_size: AI_HELP_CONTINUOUS_LEARNING_BATCH_SIZE,
484
+ min_auto_apply_confidence: AI_HELP_CONTINUOUS_LEARNING_MIN_AUTO_APPLY_CONFIDENCE,
485
+ max_help_questions_per_command: AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_QUESTIONS_PER_COMMAND,
486
+ max_help_calls_per_cycle: AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_CALLS_PER_CYCLE,
487
+ });
488
+ };
489
+
490
+ export const stopAiHelperContinuousLearningWorker = () => {
491
+ if (schedulerHandle) {
492
+ clearInterval(schedulerHandle);
493
+ schedulerHandle = null;
494
+ }
495
+ schedulerStarted = false;
496
+ };
497
+
498
+ export const runAiHelperContinuousLearningWorkerOnce = async (reason = 'manual') => {
499
+ await processContinuousLearningBatch({
500
+ reason,
501
+ });
502
+ };
503
+
504
+ export const getAiHelperContinuousLearningWorkerConfig = () => ({
505
+ enabled: AI_HELP_CONTINUOUS_LEARNING_ENABLED,
506
+ intervalMs: AI_HELP_CONTINUOUS_LEARNING_INTERVAL_MS,
507
+ batchSize: AI_HELP_CONTINUOUS_LEARNING_BATCH_SIZE,
508
+ minAutoApplyConfidence: AI_HELP_CONTINUOUS_LEARNING_MIN_AUTO_APPLY_CONFIDENCE,
509
+ maxHelpQuestionsPerCommand: AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_QUESTIONS_PER_COMMAND,
510
+ maxHelpCallsPerCycle: AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_CALLS_PER_CYCLE,
511
+ startedAt: __timeNowIso(),
512
+ });
@@ -1,3 +1,4 @@
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
1
2
  import OpenAI from 'openai';
2
3
  import logger from '#logger';
3
4
  import { insertLearnedKeywords, insertLearnedPatterns, listPendingLearningEvents, markLearningEventsProcessed } from '../services/ai/aiLearningRepository.js';
@@ -305,7 +306,7 @@ const seedLearningFromCommandConfig = async ({ reason = 'scheduler' } = {}) => {
305
306
  };
306
307
  }
307
308
 
308
- const nowMs = Date.now();
309
+ const nowMs = __timeNowMs();
309
310
  const registryStats = getToolRegistryStats();
310
311
  const registrySignature = String(registryStats?.signature || '');
311
312
 
@@ -385,7 +386,7 @@ const processLearningBatch = async ({ reason = 'scheduler' } = {}) => {
385
386
 
386
387
  cycleInProgress = true;
387
388
  try {
388
- const startedAt = Date.now();
389
+ const startedAt = __timeNowMs();
389
390
  logger.info('Worker de aprendizado IA iniciado.', {
390
391
  action: 'ai_learning_worker_started',
391
392
  reason,
@@ -433,7 +434,7 @@ const processLearningBatch = async ({ reason = 'scheduler' } = {}) => {
433
434
  config_seed_executed: configSeedResult.executed,
434
435
  config_seed_skipped: configSeedResult.skipped,
435
436
  config_seed_tool_count: configSeedResult.toolCount,
436
- duration_ms: Date.now() - startedAt,
437
+ duration_ms: __timeNowMs() - startedAt,
437
438
  });
438
439
  return;
439
440
  }
@@ -457,7 +458,7 @@ const processLearningBatch = async ({ reason = 'scheduler' } = {}) => {
457
458
  config_seed_executed: configSeedResult.executed,
458
459
  config_seed_skipped: configSeedResult.skipped,
459
460
  config_seed_tool_count: configSeedResult.toolCount,
460
- duration_ms: Date.now() - startedAt,
461
+ duration_ms: __timeNowMs() - startedAt,
461
462
  });
462
463
  return;
463
464
  }
@@ -537,7 +538,7 @@ const processLearningBatch = async ({ reason = 'scheduler' } = {}) => {
537
538
  config_seed_executed: configSeedResult.executed,
538
539
  config_seed_skipped: configSeedResult.skipped,
539
540
  config_seed_tool_count: configSeedResult.toolCount,
540
- duration_ms: Date.now() - startedAt,
541
+ duration_ms: __timeNowMs() - startedAt,
541
542
  });
542
543
  } finally {
543
544
  cycleInProgress = false;
@@ -1,3 +1,4 @@
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
1
2
  import logger from '#logger';
2
3
  import { getToolRecord } from '../services/ai/moduleToolRegistryService.js';
3
4
  import { applyCommandConfigEnrichmentSuggestion, getCommandConfigEnrichmentCursor, listLearningEventsForCommandConfigEnrichment, saveCommandConfigEnrichmentSuggestion, updateCommandConfigEnrichmentCursor } from '../services/ai/commandConfigEnrichmentRepository.js';
@@ -53,7 +54,7 @@ const processEnrichmentBatch = async ({ reason = 'scheduler' } = {}) => {
53
54
  if (!isWorkerReady()) return;
54
55
 
55
56
  cycleInProgress = true;
56
- const startedAt = Date.now();
57
+ const startedAt = __timeNowMs();
57
58
 
58
59
  try {
59
60
  logger.info('Worker de enriquecimento de commandConfig iniciado.', {
@@ -78,7 +79,7 @@ const processEnrichmentBatch = async ({ reason = 'scheduler' } = {}) => {
78
79
  generated_suggestions: 0,
79
80
  auto_applied: 0,
80
81
  applied_changed: 0,
81
- duration_ms: Date.now() - startedAt,
82
+ duration_ms: __timeNowMs() - startedAt,
82
83
  });
83
84
  return;
84
85
  }
@@ -186,7 +187,7 @@ const processEnrichmentBatch = async ({ reason = 'scheduler' } = {}) => {
186
187
  auto_applied: autoApplied,
187
188
  applied_changed: appliedChanged,
188
189
  skipped_unknown_tool: skippedUnknownTool,
189
- duration_ms: Date.now() - startedAt,
190
+ duration_ms: __timeNowMs() - startedAt,
190
191
  });
191
192
  } finally {
192
193
  cycleInProgress = false;
package/database/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /* eslint-disable no-unused-vars */
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
2
2
  /* eslint-disable no-useless-escape */
3
3
 
4
4
  /**
@@ -102,9 +102,15 @@ export const TABLES = {
102
102
  MESSAGE_ANALYSIS_EVENT: 'message_analysis_event',
103
103
  BAILEYS_EVENT_JOURNAL: 'baileys_event_journal',
104
104
  BAILEYS_AUTH_STATE: 'baileys_auth_state',
105
+ WA_SESSION_REGISTRY: 'wa_session_registry',
106
+ GROUP_ASSIGNMENT: 'group_assignment',
107
+ GROUP_ASSIGNMENT_HISTORY: 'group_assignment_history',
105
108
  CHATS: 'chats',
106
109
  GROUPS_METADATA: 'groups_metadata',
107
110
  GROUP_CONFIGS: 'group_configs',
111
+ GROUP_USER_WARNINGS: 'group_user_warnings',
112
+ SYSTEM_PREMIUM_USERS: 'system_premium_users',
113
+ SYSTEM_AI_PROMPTS: 'system_ai_prompts',
108
114
  LID_MAP: 'lid_map',
109
115
  STICKER_PACK: 'sticker_pack',
110
116
  STICKER_ASSET: 'sticker_asset',
@@ -408,8 +414,8 @@ let dbInFlightMetric = 0;
408
414
  function createEmptyStats() {
409
415
  return {
410
416
  enabled: monitorConfig.enabled,
411
- startedAt: Date.now(),
412
- lastResetAt: Date.now(),
417
+ startedAt: __timeNowMs(),
418
+ lastResetAt: __timeNowMs(),
413
419
  counters: {
414
420
  total: 0,
415
421
  error: 0,
@@ -632,7 +638,7 @@ function createDbMonitorLogger({ enabled, logPath, rotateBytes, keep }) {
632
638
  } catch (error) {
633
639
  queue.push(
634
640
  JSON.stringify({
635
- ts: new Date().toISOString(),
641
+ ts: __timeNowIso(),
636
642
  event: 'logger_error',
637
643
  errorMessage: error.message,
638
644
  }),
@@ -678,7 +684,7 @@ export function resetDbStats() {
678
684
  * @returns {object}
679
685
  */
680
686
  export function getDbStats() {
681
- const now = Date.now();
687
+ const now = __timeNowMs();
682
688
  const sampleCount = dbStats.samples.length;
683
689
  const percentiles = calculatePercentiles();
684
690
  const histogram = {
@@ -1219,7 +1225,7 @@ function recordStats({ fingerprint, normalizedSql, type, table, durationMs, ok,
1219
1225
  entry.maxMs = Math.max(entry.maxMs, durationMs);
1220
1226
  entry.minMs = entry.minMs === null ? durationMs : Math.min(entry.minMs, durationMs);
1221
1227
  entry.lastMs = durationMs;
1222
- entry.lastSeenAt = Date.now();
1228
+ entry.lastSeenAt = __timeNowMs();
1223
1229
 
1224
1230
  if (rowCount !== undefined) entry.lastRowCount = rowCount;
1225
1231
  if (affectedRows !== undefined) entry.lastAffectedRows = affectedRows;
@@ -1232,7 +1238,7 @@ function recordStats({ fingerprint, normalizedSql, type, table, durationMs, ok,
1232
1238
  */
1233
1239
  function buildMonitorLogEntry({ event, durationMs, type, table, fingerprint, normalizedSql, sql, rowCount, affectedRows, traceId, error, params }) {
1234
1240
  const entry = {
1235
- ts: new Date().toISOString(),
1241
+ ts: __timeNowIso(),
1236
1242
  event,
1237
1243
  durationMs: durationMs !== undefined && durationMs !== null ? Number(durationMs.toFixed(2)) : null,
1238
1244
  type: type ?? null,
@@ -2058,7 +2064,7 @@ export async function upsert(tableName, data) {
2058
2064
  * @param {(connection: import('mysql2/promise').PoolConnection) => Promise<any>} callback
2059
2065
  * @returns {Promise<any>}
2060
2066
  */
2061
- async function withTransaction(callback) {
2067
+ export async function withTransaction(callback) {
2062
2068
  const connection = await pool.getConnection();
2063
2069
  try {
2064
2070
  await connection.beginTransaction();
@@ -58,7 +58,7 @@ UPDATE schema_change_log
58
58
  SET status = 'rolled_back',
59
59
  notes = 'D0 rollback executed',
60
60
  updated_at = CURRENT_TIMESTAMP
61
- WHERE migration_key = @migration_key;
61
+ WHERE migration_key = CONVERT(@migration_key USING utf8mb4) COLLATE utf8mb4_unicode_ci;
62
62
 
63
63
  DROP PROCEDURE IF EXISTS __ensure_index;
64
64
  DROP PROCEDURE IF EXISTS __drop_index_if_exists;
@@ -47,7 +47,7 @@ UPDATE schema_change_log
47
47
  SET status = 'rolled_back',
48
48
  notes = 'D+7 rollback executed',
49
49
  updated_at = CURRENT_TIMESTAMP
50
- WHERE migration_key = @migration_key;
50
+ WHERE migration_key = CONVERT(@migration_key USING utf8mb4) COLLATE utf8mb4_unicode_ci;
51
51
 
52
52
  DROP PROCEDURE IF EXISTS __drop_column_if_exists;
53
53
  DROP PROCEDURE IF EXISTS __drop_index_if_exists;