@omnizap-system/omnizap 2.5.12

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 (425) hide show
  1. package/.clusterfuzzlite/Dockerfile +10 -0
  2. package/.env.example +907 -0
  3. package/.github/codeql/codeql-config.yml +10 -0
  4. package/.github/dependabot.yml +35 -0
  5. package/.github/workflows/ci.yml +73 -0
  6. package/.github/workflows/codeql.yml +106 -0
  7. package/.github/workflows/db-migration-check.yml +98 -0
  8. package/.github/workflows/dependency-review.yml +22 -0
  9. package/.github/workflows/deploy.yml +95 -0
  10. package/.github/workflows/release.yml +106 -0
  11. package/.github/workflows/security-attest-provenance.yml +51 -0
  12. package/.github/workflows/security-gitleaks.yml +34 -0
  13. package/.github/workflows/security-runner-hardening.yml +31 -0
  14. package/.github/workflows/security-scorecard.yml +44 -0
  15. package/.github/workflows/security-zap-baseline.yml +44 -0
  16. package/.github/workflows/security-zap-full-scan.yml +43 -0
  17. package/.github/workflows/security-zizmor.yml +36 -0
  18. package/.github/workflows/wiki-sync.yml +44 -0
  19. package/.gitleaks.toml +15 -0
  20. package/.prettierrc +34 -0
  21. package/CODE_OF_CONDUCT.md +114 -0
  22. package/LICENSE +56 -0
  23. package/README.md +110 -0
  24. package/SECURITY.md +110 -0
  25. package/app/config/index.js +4 -0
  26. package/app/configParts/adminIdentity.js +92 -0
  27. package/app/configParts/baileysConfig.js +1818 -0
  28. package/app/configParts/groupUtils.js +692 -0
  29. package/app/configParts/loggerConfig.js +394 -0
  30. package/app/configParts/messagePersistenceService.js +305 -0
  31. package/app/connection/baileysCompatibility.test.js +40 -0
  32. package/app/connection/baileysDbAuthState.js +344 -0
  33. package/app/connection/socketController.js +2243 -0
  34. package/app/controllers/messageController.js +7 -0
  35. package/app/controllers/messagePipeline/commandMiddleware.js +146 -0
  36. package/app/controllers/messagePipeline/conversationMiddleware.js +183 -0
  37. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +522 -0
  38. package/app/controllers/messagePipeline/postProcessingMiddleware.js +41 -0
  39. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +166 -0
  40. package/app/controllers/messageProcessingPipeline.js +699 -0
  41. package/app/modules/adminModule/AGENT.md +4056 -0
  42. package/app/modules/adminModule/adminAiHelpService.js +56 -0
  43. package/app/modules/adminModule/adminConfigRuntime.js +177 -0
  44. package/app/modules/adminModule/commandConfig.json +7122 -0
  45. package/app/modules/adminModule/groupCommandHandlers.js +1823 -0
  46. package/app/modules/adminModule/groupCommandHandlers.test.js +350 -0
  47. package/app/modules/adminModule/groupEventHandlers.js +399 -0
  48. package/app/modules/aiModule/AGENT.md +547 -0
  49. package/app/modules/aiModule/aiAiHelpService.js +14 -0
  50. package/app/modules/aiModule/aiConfigRuntime.js +135 -0
  51. package/app/modules/aiModule/catCommand.js +967 -0
  52. package/app/modules/aiModule/commandConfig.json +981 -0
  53. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +83 -0
  54. package/app/modules/gameModule/AGENT.md +196 -0
  55. package/app/modules/gameModule/commandConfig.json +366 -0
  56. package/app/modules/gameModule/diceCommand.js +42 -0
  57. package/app/modules/gameModule/gameAiHelpService.js +14 -0
  58. package/app/modules/gameModule/gameConfigRuntime.js +68 -0
  59. package/app/modules/menuModule/AGENT.md +205 -0
  60. package/app/modules/menuModule/commandConfig.json +366 -0
  61. package/app/modules/menuModule/common.js +316 -0
  62. package/app/modules/menuModule/menuAiHelpService.js +14 -0
  63. package/app/modules/menuModule/menuConfigRuntime.js +68 -0
  64. package/app/modules/menuModule/menus.js +66 -0
  65. package/app/modules/playModule/AGENT.md +321 -0
  66. package/app/modules/playModule/commandConfig.json +584 -0
  67. package/app/modules/playModule/playAiHelpService.js +14 -0
  68. package/app/modules/playModule/playCommand.js +1417 -0
  69. package/app/modules/playModule/playConfigRuntime.js +68 -0
  70. package/app/modules/quoteModule/AGENT.md +199 -0
  71. package/app/modules/quoteModule/commandConfig.json +366 -0
  72. package/app/modules/quoteModule/quoteAiHelpService.js +14 -0
  73. package/app/modules/quoteModule/quoteCommand.js +842 -0
  74. package/app/modules/quoteModule/quoteConfigRuntime.js +68 -0
  75. package/app/modules/rpgPokemonModule/AGENT.md +229 -0
  76. package/app/modules/rpgPokemonModule/commandConfig.json +386 -0
  77. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +795 -0
  78. package/app/modules/rpgPokemonModule/rpgBattleService.js +2110 -0
  79. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +770 -0
  80. package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
  81. package/app/modules/rpgPokemonModule/rpgPokemonAiHelpService.js +14 -0
  82. package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +174 -0
  83. package/app/modules/rpgPokemonModule/rpgPokemonConfigRuntime.js +68 -0
  84. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
  85. package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
  86. package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
  87. package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
  88. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1847 -0
  89. package/app/modules/rpgPokemonModule/rpgPokemonService.js +6839 -0
  90. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
  91. package/app/modules/statsModule/AGENT.md +320 -0
  92. package/app/modules/statsModule/commandConfig.json +540 -0
  93. package/app/modules/statsModule/globalRankingCommand.js +64 -0
  94. package/app/modules/statsModule/rankingCommand.js +41 -0
  95. package/app/modules/statsModule/rankingCommon.js +1305 -0
  96. package/app/modules/statsModule/statsAiHelpService.js +14 -0
  97. package/app/modules/statsModule/statsConfigRuntime.js +68 -0
  98. package/app/modules/stickerModule/AGENT.md +692 -0
  99. package/app/modules/stickerModule/addStickerMetadata.js +239 -0
  100. package/app/modules/stickerModule/commandConfig.json +1216 -0
  101. package/app/modules/stickerModule/convertToWebp.js +367 -0
  102. package/app/modules/stickerModule/stickerAiHelpService.js +14 -0
  103. package/app/modules/stickerModule/stickerCommand.js +446 -0
  104. package/app/modules/stickerModule/stickerConfigRuntime.js +68 -0
  105. package/app/modules/stickerModule/stickerConvertCommand.js +159 -0
  106. package/app/modules/stickerModule/stickerTextCommand.js +653 -0
  107. package/app/modules/stickerPackModule/AGENT.md +215 -0
  108. package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
  109. package/app/modules/stickerPackModule/autoPackCollectorService.js +357 -0
  110. package/app/modules/stickerPackModule/commandConfig.json +387 -0
  111. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +227 -0
  112. package/app/modules/stickerPackModule/domainEvents.js +52 -0
  113. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +429 -0
  114. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +75 -0
  115. package/app/modules/stickerPackModule/semanticThemeClusterService.js +544 -0
  116. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +400 -0
  117. package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
  118. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +175 -0
  119. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +3702 -0
  120. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +559 -0
  121. package/app/modules/stickerPackModule/stickerClassificationService.js +557 -0
  122. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +249 -0
  123. package/app/modules/stickerPackModule/stickerDomainEventBus.js +65 -0
  124. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +208 -0
  125. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +99 -0
  126. package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
  127. package/app/modules/stickerPackModule/stickerPackAiHelpService.js +14 -0
  128. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1148 -0
  129. package/app/modules/stickerPackModule/stickerPackConfigRuntime.js +68 -0
  130. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +152 -0
  131. package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
  132. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +101 -0
  133. package/app/modules/stickerPackModule/stickerPackItemRepository.js +432 -0
  134. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +313 -0
  135. package/app/modules/stickerPackModule/stickerPackMessageService.js +268 -0
  136. package/app/modules/stickerPackModule/stickerPackRepository.js +450 -0
  137. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +179 -0
  138. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +271 -0
  139. package/app/modules/stickerPackModule/stickerPackService.js +733 -0
  140. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +32 -0
  141. package/app/modules/stickerPackModule/stickerPackUtils.js +107 -0
  142. package/app/modules/stickerPackModule/stickerStorageService.js +559 -0
  143. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +242 -0
  144. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +242 -0
  145. package/app/modules/systemMetricsModule/AGENT.md +193 -0
  146. package/app/modules/systemMetricsModule/commandConfig.json +344 -0
  147. package/app/modules/systemMetricsModule/pingCommand.js +399 -0
  148. package/app/modules/systemMetricsModule/systemMetricsAiHelpService.js +14 -0
  149. package/app/modules/systemMetricsModule/systemMetricsConfigRuntime.js +68 -0
  150. package/app/modules/tiktokModule/AGENT.md +196 -0
  151. package/app/modules/tiktokModule/commandConfig.json +366 -0
  152. package/app/modules/tiktokModule/tiktokAiHelpService.js +14 -0
  153. package/app/modules/tiktokModule/tiktokCommand.js +716 -0
  154. package/app/modules/tiktokModule/tiktokConfigRuntime.js +68 -0
  155. package/app/modules/userModule/AGENT.md +200 -0
  156. package/app/modules/userModule/commandConfig.json +386 -0
  157. package/app/modules/userModule/userAiHelpService.js +14 -0
  158. package/app/modules/userModule/userCommand.js +1155 -0
  159. package/app/modules/userModule/userConfigRuntime.js +68 -0
  160. package/app/modules/waifuPicsModule/AGENT.md +431 -0
  161. package/app/modules/waifuPicsModule/commandConfig.json +780 -0
  162. package/app/modules/waifuPicsModule/waifuPicsAiHelpService.js +14 -0
  163. package/app/modules/waifuPicsModule/waifuPicsCommand.js +586 -0
  164. package/app/modules/waifuPicsModule/waifuPicsConfigRuntime.js +68 -0
  165. package/app/observability/metrics.js +766 -0
  166. package/app/services/ai/aiHelpResponseCacheRepository.js +280 -0
  167. package/app/services/ai/aiLearningRepository.js +400 -0
  168. package/app/services/ai/commandConfigEnrichmentRepository.js +769 -0
  169. package/app/services/ai/commandConfigEnrichmentService.js +452 -0
  170. package/app/services/ai/commandConfigValidationService.js +443 -0
  171. package/app/services/ai/commandToolBuilderService.js +192 -0
  172. package/app/services/ai/conversationRouterService.js +516 -0
  173. package/app/services/ai/geminiService.js +115 -0
  174. package/app/services/ai/geminiService.test.js +87 -0
  175. package/app/services/ai/globalModuleAiHelpService.js +1412 -0
  176. package/app/services/ai/globalToolCallingService.js +203 -0
  177. package/app/services/ai/messageCommandExecutionService.js +391 -0
  178. package/app/services/ai/moduleAiHelpCoreService.js +1099 -0
  179. package/app/services/ai/moduleAiHelpWrapperFactory.js +65 -0
  180. package/app/services/ai/moduleCommandConfigRuntimeService.js +113 -0
  181. package/app/services/ai/moduleToolExecutorService.js +464 -0
  182. package/app/services/ai/moduleToolRegistryService.js +178 -0
  183. package/app/services/ai/toolCandidateSelectorService.js +781 -0
  184. package/app/services/auth/googleWebLinkService.js +80 -0
  185. package/app/services/auth/whatsappLoginLinkService.js +230 -0
  186. package/app/services/external/pokeApiService.js +398 -0
  187. package/app/services/group/groupMetadataService.js +311 -0
  188. package/app/services/infra/dbWriteQueue.js +874 -0
  189. package/app/services/infra/featureFlagService.js +131 -0
  190. package/app/services/infra/queueUtils.js +55 -0
  191. package/app/services/messaging/captchaService.js +491 -0
  192. package/app/services/messaging/messagePersistenceService.js +1 -0
  193. package/app/services/messaging/newsBroadcastService.js +347 -0
  194. package/app/services/sticker/stickerFocusService.js +347 -0
  195. package/app/services/sticker/stickerFocusService.test.js +43 -0
  196. package/app/store/aiPromptStore.js +38 -0
  197. package/app/store/conversationSessionStore.js +131 -0
  198. package/app/store/groupConfigStore.js +58 -0
  199. package/app/store/premiumUserStore.js +54 -0
  200. package/app/utils/antiLink/antiLinkModule.js +700 -0
  201. package/app/utils/http/getImageBufferModule.js +18 -0
  202. package/app/utils/json/jsonSanitizer.js +113 -0
  203. package/app/utils/json/jsonSanitizer.test.js +40 -0
  204. package/app/utils/systemMetrics/systemMetricsModule.js +88 -0
  205. package/app/workers/aiLearningWorker.js +605 -0
  206. package/app/workers/commandConfigEnrichmentWorker.js +242 -0
  207. package/database/index.js +2075 -0
  208. package/database/init.js +151 -0
  209. package/database/migrations/.gitkeep +0 -0
  210. package/database/migrations/20260307_d0_hardening_down.sql +64 -0
  211. package/database/migrations/20260307_d0_hardening_up.sql +79 -0
  212. package/database/migrations/20260307_d1_terms_acceptance_down.sql +11 -0
  213. package/database/migrations/20260307_d1_terms_acceptance_up.sql +37 -0
  214. package/database/migrations/20260307_d2_auth_hardening_down.sql +75 -0
  215. package/database/migrations/20260307_d2_auth_hardening_up.sql +100 -0
  216. package/database/migrations/20260314_d7_canonical_sender_down.sql +53 -0
  217. package/database/migrations/20260314_d7_canonical_sender_up.sql +114 -0
  218. package/database/migrations/20260406_d30_security_analytics_down.sql +95 -0
  219. package/database/migrations/20260406_d30_security_analytics_up.sql +292 -0
  220. package/database/migrations/20260407_d31_web_google_session_token_hardening_down.sql +2 -0
  221. package/database/migrations/20260407_d31_web_google_session_token_hardening_up.sql +17 -0
  222. package/database/migrations/20260408_d32_ai_help_response_cache_down.sql +1 -0
  223. package/database/migrations/20260408_d32_ai_help_response_cache_up.sql +22 -0
  224. package/database/migrations/20260409_d33_ai_learning_tables_down.sql +4 -0
  225. package/database/migrations/20260409_d33_ai_learning_tables_up.sql +52 -0
  226. package/database/migrations/20260410_d34_command_config_enrichment_down.sql +3 -0
  227. package/database/migrations/20260410_d34_command_config_enrichment_up.sql +48 -0
  228. package/database/schema.sql +1186 -0
  229. package/docker-compose.yml +104 -0
  230. package/docs/audits/stickerCatalogController-out-of-scope.md +103 -0
  231. package/docs/audits/stickerCatalogController-symbols.md +58 -0
  232. package/docs/compliance/acceptable-use-policy-2026-03-07.md +35 -0
  233. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +80 -0
  234. package/docs/compliance/monthly-compliance-checklist-2026-03-07.md +88 -0
  235. package/docs/compliance/notice-and-takedown-policy-2026-03-07.md +34 -0
  236. package/docs/compliance/privacy-policy-2026-03-07.md +75 -0
  237. package/docs/compliance/subprocessors-inventory-2026-03-07.md +16 -0
  238. package/docs/database/production-db-evolution-runbook-2026q1.md +365 -0
  239. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +86 -0
  240. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +77 -0
  241. package/docs/security/network-hardening-runbook-2026-03-07.md +137 -0
  242. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +238 -0
  243. package/docs/seo/satellite-page-template.md +116 -0
  244. package/docs/seo/satellite-pages-phase1.json +364 -0
  245. package/docs/wiki/Home.md +120 -0
  246. package/docs/wiki/pair-extraordinaire-2026-03-08.md +3 -0
  247. package/docs/wiki/recent-changes-2026-03-08.md +47 -0
  248. package/ecosystem.prod.config.cjs +135 -0
  249. package/eslint.config.js +89 -0
  250. package/index.js +488 -0
  251. package/ml/clip_classifier/Dockerfile +18 -0
  252. package/ml/clip_classifier/README.md +118 -0
  253. package/ml/clip_classifier/adaptive_scoring.py +40 -0
  254. package/ml/clip_classifier/classifier.py +654 -0
  255. package/ml/clip_classifier/embedding_store.py +481 -0
  256. package/ml/clip_classifier/env_loader.py +15 -0
  257. package/ml/clip_classifier/llm_label_expander.py +144 -0
  258. package/ml/clip_classifier/main.py +213 -0
  259. package/ml/clip_classifier/requirements.txt +10 -0
  260. package/ml/clip_classifier/similarity_engine.py +74 -0
  261. package/new-logo.png +0 -0
  262. package/observability/alert-rules.yml +60 -0
  263. package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
  264. package/observability/grafana/dashboards/omnizap-overview.json +170 -0
  265. package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
  266. package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
  267. package/observability/loki-config.yml +38 -0
  268. package/observability/mysql-setup.sql +46 -0
  269. package/observability/prometheus.yml +35 -0
  270. package/observability/promtail-config.yml +84 -0
  271. package/observability/sticker-catalog-slo.md +83 -0
  272. package/observability/sticker-scale-hardening-rollout.md +128 -0
  273. package/package.json +144 -0
  274. package/public/apple-touch-icon.png +0 -0
  275. package/public/assets/css/commands-react.input.css +71 -0
  276. package/public/assets/css/create-pack-react.input.css +31 -0
  277. package/public/assets/css/home-react.input.css +106 -0
  278. package/public/assets/css/login-react.input.css +58 -0
  279. package/public/assets/css/stickers-react.input.css +18 -0
  280. package/public/assets/css/terms-react.input.css +115 -0
  281. package/public/assets/css/user-react.input.css +57 -0
  282. package/public/assets/images/brand-icon-192.png +0 -0
  283. package/public/assets/images/brand-logo-128.webp +0 -0
  284. package/public/assets/images/hero-banner-1280.jpg +0 -0
  285. package/public/comandos/commands-catalog.json +4517 -0
  286. package/public/css/api-docs.css +161 -0
  287. package/public/css/stickers-admin.css +1288 -0
  288. package/public/css/styles.css +679 -0
  289. package/public/css/systemadm/admin.css +474 -0
  290. package/public/css/systemadm/base.css +73 -0
  291. package/public/css/systemadm/components.css +662 -0
  292. package/public/css/systemadm/layout.css +229 -0
  293. package/public/css/systemadm/tokens.css +56 -0
  294. package/public/favicon-16x16.png +0 -0
  295. package/public/favicon-32x32.png +0 -0
  296. package/public/favicon.ico +0 -0
  297. package/public/js/apps/apiDocsApp.js +235 -0
  298. package/public/js/apps/commandsReactApp.js +528 -0
  299. package/public/js/apps/createPackApp.js +1646 -0
  300. package/public/js/apps/homeReactApp.js +942 -0
  301. package/public/js/apps/loginReactApp.js +496 -0
  302. package/public/js/apps/stickersAdminApp.js +1753 -0
  303. package/public/js/apps/stickersApp.js +3797 -0
  304. package/public/js/apps/termsReactApp.js +528 -0
  305. package/public/js/apps/userApp.js +2540 -0
  306. package/public/js/apps/userProfile/actions.js +66 -0
  307. package/public/js/apps/userReactApp.js +547 -0
  308. package/public/js/catalog.js +950 -0
  309. package/public/pages/api-docs.html +40 -0
  310. package/public/pages/aup.html +158 -0
  311. package/public/pages/comandos.html +41 -0
  312. package/public/pages/dpa.html +227 -0
  313. package/public/pages/home.html +45 -0
  314. package/public/pages/licenca.html +182 -0
  315. package/public/pages/login.html +40 -0
  316. package/public/pages/notice-and-takedown.html +234 -0
  317. package/public/pages/politica-de-privacidade.html +251 -0
  318. package/public/pages/seo-bot-whatsapp-para-grupo.html +350 -0
  319. package/public/pages/seo-bot-whatsapp-sem-programar.html +350 -0
  320. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +350 -0
  321. package/public/pages/seo-como-criar-comandos-whatsapp.html +350 -0
  322. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +350 -0
  323. package/public/pages/seo-como-moderar-grupo-whatsapp.html +350 -0
  324. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +350 -0
  325. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +350 -0
  326. package/public/pages/stickers-admin.html +31 -0
  327. package/public/pages/stickers-create.html +41 -0
  328. package/public/pages/stickers.html +45 -0
  329. package/public/pages/suboperadores.html +237 -0
  330. package/public/pages/termos-de-uso-texto-integral.html +241 -0
  331. package/public/pages/termos-de-uso.html +41 -0
  332. package/public/pages/user-password-reset.html +32 -0
  333. package/public/pages/user-systemadm.html +508 -0
  334. package/public/pages/user.html +39 -0
  335. package/public/robots.txt +9 -0
  336. package/public/site.webmanifest +24 -0
  337. package/public/sitemap.xml +98 -0
  338. package/schemas/command-config.schema.json +582 -0
  339. package/scripts/baileys-compat-smoke.mjs +12 -0
  340. package/scripts/cache-bust.mjs +142 -0
  341. package/scripts/deploy.sh +916 -0
  342. package/scripts/email-broadcast-terms-update.mjs +170 -0
  343. package/scripts/enrich-command-discovery-fields.mjs +286 -0
  344. package/scripts/generate-command-config-schema.mjs +273 -0
  345. package/scripts/generate-commands-catalog.mjs +308 -0
  346. package/scripts/generate-module-agents.mjs +631 -0
  347. package/scripts/generate-seo-satellite-pages.mjs +400 -0
  348. package/scripts/github-deploy-notify.mjs +174 -0
  349. package/scripts/github-release-notify.mjs +219 -0
  350. package/scripts/release.sh +599 -0
  351. package/scripts/run-codeql-local.sh +116 -0
  352. package/scripts/run-prettier-all.mjs +25 -0
  353. package/scripts/security-smoketest.mjs +581 -0
  354. package/scripts/sticker-catalog-loadtest.mjs +210 -0
  355. package/scripts/sticker-worker-task.mjs +119 -0
  356. package/scripts/sync-readme-snapshot.mjs +133 -0
  357. package/scripts/validate-command-config-schema.mjs +130 -0
  358. package/scripts/validate-command-configs.mjs +15 -0
  359. package/scripts/wiki-sync.sh +191 -0
  360. package/server/auth/googleWebAuth/googleWebAuthRuntime.js +62 -0
  361. package/server/auth/googleWebAuth/googleWebAuthService.js +807 -0
  362. package/server/auth/jwt/webJwtService.js +147 -0
  363. package/server/auth/stickerCatalogAuthContext.js +165 -0
  364. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +189 -0
  365. package/server/auth/userPassword/index.js +14 -0
  366. package/server/auth/userPassword/userPasswordAuthService.js +422 -0
  367. package/server/auth/userPassword/userPasswordCrypto.js +199 -0
  368. package/server/auth/userPassword/userPasswordCrypto.test.js +76 -0
  369. package/server/auth/userPassword/userPasswordRecoveryService.js +728 -0
  370. package/server/auth/validation/authSchemas.js +236 -0
  371. package/server/auth/webAccount/webAccountHandlers.js +1434 -0
  372. package/server/controllers/admin/adminBanService.js +138 -0
  373. package/server/controllers/admin/adminPanelHandlers.js +2083 -0
  374. package/server/controllers/admin/stickerCatalogAdminContext.js +17 -0
  375. package/server/controllers/admin/systemAdminController.js +201 -0
  376. package/server/controllers/email/emailAutomationController.js +239 -0
  377. package/server/controllers/metricsController.js +21 -0
  378. package/server/controllers/seo/stickerCatalogSeoContext.js +514 -0
  379. package/server/controllers/sticker/nonCatalogHandlers.js +303 -0
  380. package/server/controllers/sticker/stickerCatalogController.js +4700 -0
  381. package/server/controllers/system/contactController.js +115 -0
  382. package/server/controllers/system/githubController.js +137 -0
  383. package/server/controllers/system/stickerCatalogSystemContext.js +758 -0
  384. package/server/controllers/system/storageController.js +154 -0
  385. package/server/controllers/system/systemController.js +135 -0
  386. package/server/controllers/system/systemMetricsController.js +156 -0
  387. package/server/controllers/system/visitController.js +90 -0
  388. package/server/controllers/userController.js +145 -0
  389. package/server/email/emailAutomationRuntime.js +225 -0
  390. package/server/email/emailAutomationService.js +125 -0
  391. package/server/email/emailOutboxRepository.js +282 -0
  392. package/server/email/emailTemplateService.js +480 -0
  393. package/server/email/emailTransportService.js +156 -0
  394. package/server/http/clientIp.js +95 -0
  395. package/server/http/httpRequestUtils.js +262 -0
  396. package/server/http/httpRequestUtils.test.js +80 -0
  397. package/server/http/httpServer.js +180 -0
  398. package/server/http/requestContext.js +20 -0
  399. package/server/http/siteRoutingUtils.js +87 -0
  400. package/server/index.js +1 -0
  401. package/server/middleware/cachePolicy.js +26 -0
  402. package/server/middleware/cachePolicyHelpers.js +1 -0
  403. package/server/middleware/endpointRateLimit.js +181 -0
  404. package/server/middleware/rateLimit.js +70 -0
  405. package/server/middleware/requireAdminAuth.js +48 -0
  406. package/server/middleware/securityHeaders.js +97 -0
  407. package/server/routes/admin/systemAdminRouter.js +64 -0
  408. package/server/routes/email/emailAutomationRouter.js +46 -0
  409. package/server/routes/health/healthRouter.js +41 -0
  410. package/server/routes/indexRouter.js +234 -0
  411. package/server/routes/metrics/metricsRouter.js +58 -0
  412. package/server/routes/static/staticPageRouter.js +134 -0
  413. package/server/routes/sticker/catalogHandlers/catalogAdminHttp.js +105 -0
  414. package/server/routes/sticker/catalogHandlers/catalogAuthHttp.js +77 -0
  415. package/server/routes/sticker/catalogHandlers/catalogPublicHttp.js +120 -0
  416. package/server/routes/sticker/catalogHandlers/catalogUploadHttp.js +83 -0
  417. package/server/routes/sticker/catalogRouter.js +77 -0
  418. package/server/routes/sticker/stickerApiRouter.js +84 -0
  419. package/server/routes/sticker/stickerDataRouter.js +145 -0
  420. package/server/routes/sticker/stickerSiteRouter.js +43 -0
  421. package/server/routes/user/userApiPaths.js +66 -0
  422. package/server/routes/user/userRouter.js +65 -0
  423. package/server/utils/safePath.js +26 -0
  424. package/utils/logger/loggerModule.js +35 -0
  425. package/vite.config.mjs +38 -0
@@ -0,0 +1,781 @@
1
+ import natural from 'natural';
2
+ import winkBm25TextSearch from 'wink-bm25-text-search';
3
+ import logger from '#logger';
4
+ import { getAllToolRecords, getToolRegistryStats } from './moduleToolRegistryService.js';
5
+ import { getLearnedKnowledgeVersion, listLearnedKeywords, listLearnedPatterns } from './aiLearningRepository.js';
6
+ import { getCommandConfigEnrichmentVersion, listAppliedCommandConfigEnrichmentStates } from './commandConfigEnrichmentRepository.js';
7
+
8
+ const DEFAULT_TOOL_SELECTION_MAX_CANDIDATES = 8;
9
+ const DEFAULT_TOOL_SELECTION_MIN_SCORE = 0.2;
10
+ const DEFAULT_TOOL_SELECTION_FALLBACK_POPULAR_LIMIT = 5;
11
+ const DEFAULT_TOOL_SELECTION_CACHE_TTL_MS = 10 * 60 * 1000;
12
+ const DEFAULT_TOOL_SELECTION_CACHE_MAX_ENTRIES = 800;
13
+ const DEFAULT_TOOL_SELECTION_LEARNED_REFRESH_MS = 60_000;
14
+ const DEFAULT_TOOL_SELECTION_LEARNED_MAX_ROWS = 10_000;
15
+ const DEFAULT_TOOL_SELECTION_ENRICHMENT_REFRESH_MS = 120_000;
16
+ const DEFAULT_TOOL_SELECTION_ENRICHMENT_MAX_ROWS = 10_000;
17
+ const MIN_BM25_DOCS = 3;
18
+
19
+ const BM25_WEIGHT = 0.35;
20
+ const KEYWORD_MATCH_WEIGHT = 0.25;
21
+ const LEARNED_PATTERNS_WEIGHT = 0.2;
22
+ const FUZZY_MATCH_WEIGHT = 0.1;
23
+ const TOOL_POPULARITY_WEIGHT = 0.1;
24
+
25
+ const BM25_FIELD_WEIGHTS = {
26
+ descricao: 3,
27
+ capability_keywords: 2,
28
+ faq_patterns: 2,
29
+ user_phrasings: 3,
30
+ };
31
+
32
+ const tokenizer = new natural.WordTokenizer();
33
+ const queryCache = new Map();
34
+ const STOPWORDS = new Set(['a', 'as', 'o', 'os', 'ao', 'aos', 'de', 'do', 'da', 'dos', 'das', 'em', 'no', 'na', 'nos', 'nas', 'para', 'por', 'com', 'sem', 'e', 'ou', 'um', 'uma', 'uns', 'umas', 'que', 'como', 'me', 'te', 'se', 'eu', 'voce', 'vc', 'bot', 'ajuda', 'help']);
35
+
36
+ let cachedIndexSnapshot = null;
37
+ let cachedIndexSignature = '';
38
+ let learnedRefreshPromise = null;
39
+ let commandConfigRefreshPromise = null;
40
+
41
+ let learnedKnowledgeCache = {
42
+ loaded: false,
43
+ version: '0:0',
44
+ lastLoadedAt: 0,
45
+ nextRefreshAt: 0,
46
+ keywordsByTool: new Map(),
47
+ patternsByTool: new Map(),
48
+ };
49
+
50
+ let commandConfigEnrichmentCache = {
51
+ loaded: false,
52
+ version: '0:0:0',
53
+ lastLoadedAt: 0,
54
+ nextRefreshAt: 0,
55
+ overlayByModuleCommand: new Map(),
56
+ };
57
+
58
+ const parseEnvInt = (value, fallback, min, max) => {
59
+ const parsed = Number.parseInt(String(value ?? ''), 10);
60
+ if (!Number.isFinite(parsed)) return fallback;
61
+ return Math.max(min, Math.min(max, parsed));
62
+ };
63
+
64
+ const parseEnvFloat = (value, fallback, min, max) => {
65
+ const parsed = Number.parseFloat(String(value ?? ''));
66
+ if (!Number.isFinite(parsed)) return fallback;
67
+ return Math.max(min, Math.min(max, parsed));
68
+ };
69
+
70
+ const TOOL_SELECTION_MAX_CANDIDATES = parseEnvInt(process.env.TOOL_SELECTION_MAX_CANDIDATES, DEFAULT_TOOL_SELECTION_MAX_CANDIDATES, 1, 32);
71
+ const TOOL_SELECTION_MIN_SCORE = parseEnvFloat(process.env.TOOL_SELECTION_MIN_SCORE, DEFAULT_TOOL_SELECTION_MIN_SCORE, 0, 1);
72
+ const TOOL_SELECTION_FALLBACK_POPULAR_LIMIT = parseEnvInt(process.env.TOOL_SELECTION_FALLBACK_POPULAR_LIMIT, DEFAULT_TOOL_SELECTION_FALLBACK_POPULAR_LIMIT, 1, 20);
73
+ const TOOL_SELECTION_CACHE_TTL_MS = parseEnvInt(process.env.TOOL_SELECTION_CACHE_TTL_MS, DEFAULT_TOOL_SELECTION_CACHE_TTL_MS, 5_000, 24 * 60 * 60 * 1000);
74
+ const TOOL_SELECTION_CACHE_MAX_ENTRIES = parseEnvInt(process.env.TOOL_SELECTION_CACHE_MAX_ENTRIES, DEFAULT_TOOL_SELECTION_CACHE_MAX_ENTRIES, 50, 10_000);
75
+ const TOOL_SELECTION_LEARNED_REFRESH_MS = parseEnvInt(process.env.TOOL_SELECTION_LEARNED_REFRESH_MS, DEFAULT_TOOL_SELECTION_LEARNED_REFRESH_MS, 5_000, 30 * 60 * 1000);
76
+ const TOOL_SELECTION_LEARNED_MAX_ROWS = parseEnvInt(process.env.TOOL_SELECTION_LEARNED_MAX_ROWS, DEFAULT_TOOL_SELECTION_LEARNED_MAX_ROWS, 100, 50_000);
77
+ const TOOL_SELECTION_ENRICHMENT_REFRESH_MS = parseEnvInt(process.env.TOOL_SELECTION_ENRICHMENT_REFRESH_MS, DEFAULT_TOOL_SELECTION_ENRICHMENT_REFRESH_MS, 5_000, 30 * 60 * 1000);
78
+ const TOOL_SELECTION_ENRICHMENT_MAX_ROWS = parseEnvInt(process.env.TOOL_SELECTION_ENRICHMENT_MAX_ROWS, DEFAULT_TOOL_SELECTION_ENRICHMENT_MAX_ROWS, 100, 50_000);
79
+
80
+ const clamp01 = (value) => Math.max(0, Math.min(1, Number(value) || 0));
81
+
82
+ const normalizeText = (value) =>
83
+ String(value || '')
84
+ .trim()
85
+ .toLowerCase()
86
+ .normalize('NFD')
87
+ .replace(/[\u0300-\u036f]/g, '')
88
+ .replace(/[^a-z0-9\s/_.-]/g, ' ')
89
+ .replace(/\s+/g, ' ')
90
+ .trim();
91
+
92
+ const unique = (items = []) => Array.from(new Set((Array.isArray(items) ? items : []).filter(Boolean)));
93
+
94
+ const tokenizeText = (value) => {
95
+ const normalized = normalizeText(value);
96
+ if (!normalized) return [];
97
+
98
+ try {
99
+ const tokens = tokenizer.tokenize(normalized);
100
+ if (Array.isArray(tokens) && tokens.length > 0) {
101
+ return tokens.map((token) => normalizeText(token)).filter((token) => token.length >= 2 && !STOPWORDS.has(token));
102
+ }
103
+ } catch {
104
+ // Fallback simples quando tokenizacao da lib falha.
105
+ }
106
+
107
+ return normalized.split(/\s+/).filter((token) => token.length >= 2 && !STOPWORDS.has(token));
108
+ };
109
+
110
+ const roundScore = (value) => Number((Number(value) || 0).toFixed(4));
111
+
112
+ const computeTokenOverlapScore = (queryTokens = [], docTokenSet = new Set()) => {
113
+ const normalizedTokens = unique(queryTokens);
114
+ if (!normalizedTokens.length || !docTokenSet.size) return 0;
115
+
116
+ let hits = 0;
117
+ for (const token of normalizedTokens) {
118
+ if (docTokenSet.has(token)) hits += 1;
119
+ }
120
+
121
+ return clamp01(hits / normalizedTokens.length);
122
+ };
123
+
124
+ const computeFuzzyScore = (queryText, phrases = []) => {
125
+ const normalizedQuery = normalizeText(queryText);
126
+ if (!normalizedQuery) return 0;
127
+
128
+ let best = 0;
129
+ for (const phrase of phrases) {
130
+ const normalizedPhrase = normalizeText(phrase);
131
+ if (!normalizedPhrase) continue;
132
+ const score = Number(natural.JaroWinklerDistance(normalizedQuery, normalizedPhrase)) || 0;
133
+ if (score > best) best = score;
134
+ }
135
+ return clamp01(best);
136
+ };
137
+
138
+ const toSafeNumber = (value, fallback = 0) => {
139
+ const parsed = Number(value);
140
+ if (!Number.isFinite(parsed)) return fallback;
141
+ return parsed;
142
+ };
143
+
144
+ const resolveLearningSignals = (commandEntry = {}) => {
145
+ const usageCount = Math.max(0, toSafeNumber(commandEntry.tool_usage_count, 0));
146
+ const successRate = clamp01(toSafeNumber(commandEntry.tool_success_rate, 0.5));
147
+ const suggestionPriority = clamp01(toSafeNumber(commandEntry.suggestion_priority, 0) / 100);
148
+ return {
149
+ usageCount,
150
+ successRate,
151
+ suggestionPriority,
152
+ };
153
+ };
154
+
155
+ const computePopularityScore = (learningSignals = {}) => {
156
+ const usageScore = clamp01(Math.log1p(Math.max(0, learningSignals.usageCount || 0)) / 8);
157
+ const successScore = clamp01(learningSignals.successRate);
158
+ const priorityScore = clamp01(learningSignals.suggestionPriority);
159
+ return clamp01(usageScore * 0.5 + successScore * 0.35 + priorityScore * 0.15);
160
+ };
161
+
162
+ const buildDocumentFields = (record) => {
163
+ const commandEntry = record?.commandEntry || {};
164
+ const descricao = normalizeText(commandEntry?.descricao || '');
165
+ const capabilityKeywords = unique(commandEntry?.capability_keywords || []).join(' ');
166
+ const faqPatterns = unique(commandEntry?.faq_patterns || []).join(' ');
167
+ const userPhrasings = unique(commandEntry?.user_phrasings || []).join(' ');
168
+
169
+ const aliases = unique(record?.aliases || []);
170
+ const commandTerms = unique([record?.commandName, ...aliases]).join(' ');
171
+ const staticKeywordText = normalizeText([capabilityKeywords, commandTerms, faqPatterns].join(' '));
172
+ const staticKeywordTokens = new Set(tokenizeText(staticKeywordText));
173
+ const matchPhrases = unique([record?.commandName, ...aliases, commandEntry?.descricao, ...(Array.isArray(commandEntry?.user_phrasings) ? commandEntry.user_phrasings : []), ...(Array.isArray(commandEntry?.faq_patterns) ? commandEntry.faq_patterns : [])]);
174
+
175
+ return {
176
+ descricao,
177
+ capability_keywords: normalizeText(capabilityKeywords),
178
+ faq_patterns: normalizeText(faqPatterns),
179
+ user_phrasings: normalizeText(userPhrasings),
180
+ staticKeywordTokens,
181
+ matchPhrases,
182
+ };
183
+ };
184
+
185
+ const ensureCacheSize = () => {
186
+ if (queryCache.size <= TOOL_SELECTION_CACHE_MAX_ENTRIES) return;
187
+ const overflow = queryCache.size - TOOL_SELECTION_CACHE_MAX_ENTRIES;
188
+ const iterator = queryCache.keys();
189
+ for (let i = 0; i < overflow; i += 1) {
190
+ const next = iterator.next();
191
+ if (next.done) break;
192
+ queryCache.delete(next.value);
193
+ }
194
+ };
195
+
196
+ const pruneCache = (nowMs = Date.now()) => {
197
+ for (const [cacheKey, cacheEntry] of queryCache.entries()) {
198
+ if (!cacheEntry || cacheEntry.expiresAt <= nowMs) {
199
+ queryCache.delete(cacheKey);
200
+ }
201
+ }
202
+ };
203
+
204
+ const buildCacheKey = ({ message, limit, minScore, signature, learnedVersion, commandConfigVersion }) => `${signature}|${learnedVersion}|${commandConfigVersion}|${limit}|${roundScore(minScore)}|${normalizeText(message)}`;
205
+
206
+ const extractBm25Scores = (bm25Engine, queryText) => {
207
+ const rawResults = bm25Engine?.search?.(queryText);
208
+ const results = Array.isArray(rawResults) ? rawResults : [];
209
+ if (!results.length) return new Map();
210
+
211
+ let maxScore = 0;
212
+ for (const [, score] of results) {
213
+ const numeric = Math.max(0, toSafeNumber(score, 0));
214
+ if (numeric > maxScore) maxScore = numeric;
215
+ }
216
+ if (!maxScore) return new Map();
217
+
218
+ const normalizedScores = new Map();
219
+ for (const [toolName, score] of results) {
220
+ const normalized = clamp01(toSafeNumber(score, 0) / maxScore);
221
+ normalizedScores.set(
222
+ String(toolName || '')
223
+ .trim()
224
+ .toLowerCase(),
225
+ normalized,
226
+ );
227
+ }
228
+ return normalizedScores;
229
+ };
230
+
231
+ const buildOrGetIndexSnapshot = () => {
232
+ const registryStats = getToolRegistryStats();
233
+ const signature = String(registryStats?.signature || '');
234
+
235
+ if (cachedIndexSnapshot && cachedIndexSignature === signature) {
236
+ return cachedIndexSnapshot;
237
+ }
238
+
239
+ const records = getAllToolRecords();
240
+ const bm25Engine = winkBm25TextSearch();
241
+ bm25Engine.defineConfig({
242
+ fldWeights: BM25_FIELD_WEIGHTS,
243
+ bm25Params: { k1: 1.2, b: 0.75 },
244
+ ovFldNames: ['descricao', 'capability_keywords', 'faq_patterns', 'user_phrasings'],
245
+ });
246
+ bm25Engine.definePrepTasks([tokenizeText]);
247
+
248
+ const entries = [];
249
+ for (const record of records) {
250
+ const fields = buildDocumentFields(record);
251
+ const learningSignals = resolveLearningSignals(record.commandEntry || {});
252
+ const popularityScore = computePopularityScore(learningSignals);
253
+ entries.push({
254
+ ...record,
255
+ ...fields,
256
+ learningSignals,
257
+ popularityScore,
258
+ });
259
+ bm25Engine.addDoc(
260
+ {
261
+ descricao: fields.descricao,
262
+ capability_keywords: fields.capability_keywords,
263
+ faq_patterns: fields.faq_patterns,
264
+ user_phrasings: fields.user_phrasings,
265
+ },
266
+ record.toolName,
267
+ );
268
+ }
269
+
270
+ let bm25Ready = false;
271
+ if (entries.length >= MIN_BM25_DOCS) {
272
+ try {
273
+ bm25Engine.consolidate();
274
+ bm25Ready = true;
275
+ } catch (error) {
276
+ logger.warn('Falha ao consolidar indice BM25 de tools. Fallback lexical sera usado.', {
277
+ action: 'tool_candidate_index_bm25_consolidate_failed',
278
+ error: error?.message,
279
+ tool_count: entries.length,
280
+ });
281
+ }
282
+ }
283
+
284
+ const popularEntries = [...entries].sort((left, right) => {
285
+ if (right.popularityScore !== left.popularityScore) {
286
+ return right.popularityScore - left.popularityScore;
287
+ }
288
+ return left.toolName.localeCompare(right.toolName);
289
+ });
290
+
291
+ cachedIndexSnapshot = {
292
+ signature,
293
+ builtAt: new Date().toISOString(),
294
+ bm25Ready,
295
+ bm25Engine,
296
+ entries,
297
+ popularEntries,
298
+ };
299
+ cachedIndexSignature = signature;
300
+ pruneCache();
301
+
302
+ logger.info('Indice de selecao dinamica de tools atualizado.', {
303
+ action: 'tool_candidate_index_ready',
304
+ tool_count: entries.length,
305
+ bm25_ready: bm25Ready,
306
+ registry_signature: signature || null,
307
+ });
308
+
309
+ return cachedIndexSnapshot;
310
+ };
311
+
312
+ const computeKeywordMatchScore = ({ queryTokens = [], staticKeywordTokens = new Set(), learnedKeywordMap = new Map() }) => {
313
+ const staticOverlap = computeTokenOverlapScore(queryTokens, staticKeywordTokens);
314
+
315
+ let learnedScore = 0;
316
+ if (learnedKeywordMap.size > 0) {
317
+ let totalWeight = 0;
318
+ let matchedWeight = 0;
319
+ for (const [keyword, weight] of learnedKeywordMap.entries()) {
320
+ const safeWeight = Math.max(0, Number(weight) || 0);
321
+ totalWeight += safeWeight;
322
+ if (queryTokens.includes(keyword)) {
323
+ matchedWeight += safeWeight;
324
+ }
325
+ }
326
+ learnedScore = totalWeight > 0 ? clamp01(matchedWeight / totalWeight) : 0;
327
+ }
328
+
329
+ return clamp01(staticOverlap * 0.65 + learnedScore * 0.35);
330
+ };
331
+
332
+ const computeLearnedPatternsScore = ({ queryText, queryTokens = [], learnedPatterns = [] }) => {
333
+ if (!Array.isArray(learnedPatterns) || learnedPatterns.length === 0) return 0;
334
+
335
+ let best = 0;
336
+ for (const item of learnedPatterns) {
337
+ const pattern = normalizeText(item?.pattern || '');
338
+ if (!pattern) continue;
339
+
340
+ const confidence = clamp01(item?.confidence);
341
+ const patternTokens = item?.tokens instanceof Set ? item.tokens : new Set(tokenizeText(pattern));
342
+ const fuzzy = Number(natural.JaroWinklerDistance(normalizeText(queryText), pattern)) || 0;
343
+ const overlap = computeTokenOverlapScore(queryTokens, patternTokens);
344
+ const raw = clamp01(fuzzy * 0.7 + overlap * 0.3);
345
+ const weighted = clamp01(raw * (0.5 + confidence * 0.5));
346
+ if (weighted > best) best = weighted;
347
+ }
348
+
349
+ return best;
350
+ };
351
+
352
+ const buildLearnedKnowledgeMaps = ({ patterns = [], keywords = [] }) => {
353
+ const keywordsByTool = new Map();
354
+ const keywordDedup = new Map();
355
+
356
+ for (const row of keywords) {
357
+ const tool = normalizeText(row?.tool);
358
+ const keyword = normalizeText(row?.keyword);
359
+ if (!tool || !keyword) continue;
360
+
361
+ const dedupKey = `${tool}::${keyword}`;
362
+ const weight = clamp01(row?.weight) || 0.5;
363
+ const current = keywordDedup.get(dedupKey) || 0;
364
+ if (weight > current) {
365
+ keywordDedup.set(dedupKey, weight);
366
+ }
367
+ }
368
+
369
+ for (const [dedupKey, weight] of keywordDedup.entries()) {
370
+ const [tool, keyword] = dedupKey.split('::');
371
+ if (!tool || !keyword) continue;
372
+ const toolMap = keywordsByTool.get(tool) || new Map();
373
+ toolMap.set(keyword, Math.max(weight, toolMap.get(keyword) || 0));
374
+ keywordsByTool.set(tool, toolMap);
375
+ }
376
+
377
+ const patternsByTool = new Map();
378
+ const patternDedup = new Map();
379
+
380
+ for (const row of patterns) {
381
+ const tool = normalizeText(row?.tool);
382
+ const pattern = String(row?.pattern || '').trim();
383
+ if (!tool || !pattern) continue;
384
+
385
+ const dedupKey = `${tool}::${normalizeText(pattern)}`;
386
+ const confidence = clamp01(row?.confidence) || 0.5;
387
+ const current = patternDedup.get(dedupKey);
388
+ if (!current || confidence > current.confidence) {
389
+ patternDedup.set(dedupKey, {
390
+ pattern,
391
+ confidence,
392
+ tokens: new Set(tokenizeText(pattern)),
393
+ });
394
+ }
395
+ }
396
+
397
+ for (const [dedupKey, value] of patternDedup.entries()) {
398
+ const [tool] = dedupKey.split('::');
399
+ if (!tool) continue;
400
+ const list = patternsByTool.get(tool) || [];
401
+ list.push(value);
402
+ patternsByTool.set(tool, list);
403
+ }
404
+
405
+ return {
406
+ keywordsByTool,
407
+ patternsByTool,
408
+ };
409
+ };
410
+
411
+ const buildModuleCommandKey = (moduleKey, commandName) => `${normalizeText(moduleKey)}:${normalizeText(commandName)}`;
412
+
413
+ const buildCommandConfigOverlayMaps = (states = []) => {
414
+ const overlayByModuleCommand = new Map();
415
+ for (const state of states) {
416
+ const key = buildModuleCommandKey(state?.module_key, state?.command_name);
417
+ if (!key || key === ':') continue;
418
+
419
+ const overlay = state?.overlay && typeof state.overlay === 'object' ? state.overlay : {};
420
+ const capabilityKeywords = unique(overlay?.capability_keywords || []);
421
+ const faqPatterns = unique(overlay?.faq_patterns || []);
422
+ const userPhrasings = unique(overlay?.user_phrasings || []);
423
+ const methodHints = unique(overlay?.metodos_de_uso_sugeridos || []);
424
+ const descricaoSugerida = String(overlay?.descricao_sugerida || '').trim();
425
+
426
+ const overlayKeywordTokens = new Set(tokenizeText([capabilityKeywords.join(' '), faqPatterns.join(' '), userPhrasings.join(' ')].join(' ')));
427
+ const overlayMatchPhrases = unique([descricaoSugerida, ...capabilityKeywords, ...faqPatterns, ...userPhrasings, ...methodHints]);
428
+
429
+ overlayByModuleCommand.set(key, {
430
+ capabilityKeywords,
431
+ faqPatterns,
432
+ userPhrasings,
433
+ methodHints,
434
+ descricaoSugerida,
435
+ overlayKeywordTokens,
436
+ overlayMatchPhrases,
437
+ version: Number(state?.version || 0),
438
+ confidence: clamp01(state?.confidence),
439
+ });
440
+ }
441
+
442
+ return {
443
+ overlayByModuleCommand,
444
+ };
445
+ };
446
+
447
+ const refreshLearnedKnowledgeCache = async ({ force = false } = {}) => {
448
+ const nowMs = Date.now();
449
+ if (!force && learnedKnowledgeCache.loaded && learnedKnowledgeCache.nextRefreshAt > nowMs && learnedKnowledgeCache.version) {
450
+ return learnedKnowledgeCache;
451
+ }
452
+
453
+ if (learnedRefreshPromise) {
454
+ return learnedRefreshPromise;
455
+ }
456
+
457
+ learnedRefreshPromise = (async () => {
458
+ const versionInfo = await getLearnedKnowledgeVersion();
459
+ const nextVersion = String(versionInfo?.version || '0:0');
460
+
461
+ if (!force && learnedKnowledgeCache.loaded && learnedKnowledgeCache.version === nextVersion) {
462
+ learnedKnowledgeCache = {
463
+ ...learnedKnowledgeCache,
464
+ nextRefreshAt: nowMs + TOOL_SELECTION_LEARNED_REFRESH_MS,
465
+ };
466
+ return learnedKnowledgeCache;
467
+ }
468
+
469
+ const [patterns, keywords] = await Promise.all([listLearnedPatterns({ limit: TOOL_SELECTION_LEARNED_MAX_ROWS }), listLearnedKeywords({ limit: TOOL_SELECTION_LEARNED_MAX_ROWS })]);
470
+
471
+ const maps = buildLearnedKnowledgeMaps({ patterns, keywords });
472
+ learnedKnowledgeCache = {
473
+ loaded: true,
474
+ version: nextVersion,
475
+ lastLoadedAt: nowMs,
476
+ nextRefreshAt: nowMs + TOOL_SELECTION_LEARNED_REFRESH_MS,
477
+ keywordsByTool: maps.keywordsByTool,
478
+ patternsByTool: maps.patternsByTool,
479
+ };
480
+
481
+ logger.info('Cache de conhecimento aprendido para tool selector atualizado.', {
482
+ action: 'tool_candidate_learning_cache_refreshed',
483
+ version: nextVersion,
484
+ pattern_count: patterns.length,
485
+ keyword_count: keywords.length,
486
+ tools_with_patterns: maps.patternsByTool.size,
487
+ tools_with_keywords: maps.keywordsByTool.size,
488
+ });
489
+
490
+ return learnedKnowledgeCache;
491
+ })();
492
+
493
+ try {
494
+ return await learnedRefreshPromise;
495
+ } finally {
496
+ learnedRefreshPromise = null;
497
+ }
498
+ };
499
+
500
+ const refreshCommandConfigEnrichmentCache = async ({ force = false } = {}) => {
501
+ const nowMs = Date.now();
502
+ if (!force && commandConfigEnrichmentCache.loaded && commandConfigEnrichmentCache.nextRefreshAt > nowMs && commandConfigEnrichmentCache.version) {
503
+ return commandConfigEnrichmentCache;
504
+ }
505
+
506
+ if (commandConfigRefreshPromise) {
507
+ return commandConfigRefreshPromise;
508
+ }
509
+
510
+ commandConfigRefreshPromise = (async () => {
511
+ const versionInfo = await getCommandConfigEnrichmentVersion();
512
+ const nextVersion = String(versionInfo?.version || '0:0:0');
513
+
514
+ if (!force && commandConfigEnrichmentCache.loaded && commandConfigEnrichmentCache.version === nextVersion) {
515
+ commandConfigEnrichmentCache = {
516
+ ...commandConfigEnrichmentCache,
517
+ nextRefreshAt: nowMs + TOOL_SELECTION_ENRICHMENT_REFRESH_MS,
518
+ };
519
+ return commandConfigEnrichmentCache;
520
+ }
521
+
522
+ const states = await listAppliedCommandConfigEnrichmentStates({
523
+ limit: TOOL_SELECTION_ENRICHMENT_MAX_ROWS,
524
+ });
525
+ const maps = buildCommandConfigOverlayMaps(states);
526
+
527
+ commandConfigEnrichmentCache = {
528
+ loaded: true,
529
+ version: nextVersion,
530
+ lastLoadedAt: nowMs,
531
+ nextRefreshAt: nowMs + TOOL_SELECTION_ENRICHMENT_REFRESH_MS,
532
+ overlayByModuleCommand: maps.overlayByModuleCommand,
533
+ };
534
+
535
+ logger.info('Cache de enriquecimento aplicado de commandConfig atualizado.', {
536
+ action: 'tool_candidate_command_config_cache_refreshed',
537
+ version: nextVersion,
538
+ state_count: states.length,
539
+ enriched_commands: maps.overlayByModuleCommand.size,
540
+ });
541
+
542
+ return commandConfigEnrichmentCache;
543
+ })();
544
+
545
+ try {
546
+ return await commandConfigRefreshPromise;
547
+ } finally {
548
+ commandConfigRefreshPromise = null;
549
+ }
550
+ };
551
+
552
+ const buildSelectionFromEntries = ({ rankedEntries = [], fallbackUsed = false, selectionTimeMs = 0, cacheHit = false, limit = TOOL_SELECTION_MAX_CANDIDATES }) => {
553
+ const limited = rankedEntries.slice(0, Math.max(1, limit));
554
+ return {
555
+ tools: limited.map((entry) => entry.tool),
556
+ fallbackUsed,
557
+ cacheHit,
558
+ selectionTimeMs,
559
+ selectedCount: limited.length,
560
+ candidateTools: limited.map((entry) => ({
561
+ toolName: entry.toolName,
562
+ commandName: entry.commandName,
563
+ moduleKey: entry.moduleKey,
564
+ score: roundScore(entry.finalScore),
565
+ bm25Score: roundScore(entry.bm25Score),
566
+ keywordMatchScore: roundScore(entry.keywordMatchScore),
567
+ learnedPatternsScore: roundScore(entry.learnedPatternsScore),
568
+ fuzzyScore: roundScore(entry.fuzzyScore),
569
+ popularityScore: roundScore(entry.popularityScore),
570
+ commandConfigOverlayVersion: Number(entry.commandConfigOverlayVersion || 0),
571
+ usageCount: Number(entry.learningSignals?.usageCount || 0),
572
+ successRate: roundScore(entry.learningSignals?.successRate),
573
+ })),
574
+ };
575
+ };
576
+
577
+ export const selectCandidateTools = async (userMessage, limit = TOOL_SELECTION_MAX_CANDIDATES) => {
578
+ const startNs = process.hrtime.bigint();
579
+ const safeLimit = Math.max(1, Math.min(32, Number(limit) || TOOL_SELECTION_MAX_CANDIDATES));
580
+ const minScore = TOOL_SELECTION_MIN_SCORE;
581
+ const snapshot = buildOrGetIndexSnapshot();
582
+ const [learnedKnowledge, commandConfigKnowledge] = await Promise.all([refreshLearnedKnowledgeCache(), refreshCommandConfigEnrichmentCache()]);
583
+
584
+ if (!snapshot.entries.length) {
585
+ return {
586
+ tools: [],
587
+ fallbackUsed: false,
588
+ cacheHit: false,
589
+ selectionTimeMs: 0,
590
+ selectedCount: 0,
591
+ candidateTools: [],
592
+ };
593
+ }
594
+
595
+ const normalizedMessage = normalizeText(userMessage);
596
+ const nowMs = Date.now();
597
+ pruneCache(nowMs);
598
+ const cacheKey = buildCacheKey({
599
+ message: normalizedMessage,
600
+ limit: safeLimit,
601
+ minScore,
602
+ signature: snapshot.signature,
603
+ learnedVersion: learnedKnowledge.version,
604
+ commandConfigVersion: commandConfigKnowledge.version,
605
+ });
606
+
607
+ const cached = queryCache.get(cacheKey);
608
+ if (cached && cached.expiresAt > nowMs) {
609
+ const cacheResult = {
610
+ ...cached.result,
611
+ cacheHit: true,
612
+ };
613
+ logger.info('Selecao dinamica de tools em cache.', {
614
+ action: 'tool_candidate_selection',
615
+ selection_time_ms: cacheResult.selectionTimeMs,
616
+ candidate_tools: cacheResult.candidateTools,
617
+ fallback_used: cacheResult.fallbackUsed,
618
+ cache_hit: true,
619
+ limit: safeLimit,
620
+ min_score: minScore,
621
+ });
622
+ return cacheResult;
623
+ }
624
+
625
+ const queryTokens = tokenizeText(normalizedMessage);
626
+ const bm25Scores = snapshot.bm25Ready && normalizedMessage ? extractBm25Scores(snapshot.bm25Engine, normalizedMessage) : new Map();
627
+
628
+ const ranked = snapshot.entries
629
+ .map((entry) => {
630
+ const bm25Score = bm25Scores.get(entry.toolName) || 0;
631
+ const learnedKeywordMap = learnedKnowledge.keywordsByTool.get(entry.toolName) || new Map();
632
+ const learnedPatterns = learnedKnowledge.patternsByTool.get(entry.toolName) || [];
633
+ const moduleCommandKey = buildModuleCommandKey(entry.moduleKey, entry.commandName);
634
+ const commandConfigOverlay = commandConfigKnowledge.overlayByModuleCommand.get(moduleCommandKey) || null;
635
+
636
+ const effectiveStaticKeywordTokens = commandConfigOverlay ? new Set([...entry.staticKeywordTokens, ...commandConfigOverlay.overlayKeywordTokens]) : entry.staticKeywordTokens;
637
+ const effectiveMatchPhrases = commandConfigOverlay ? unique([...entry.matchPhrases, ...commandConfigOverlay.overlayMatchPhrases]) : entry.matchPhrases;
638
+
639
+ const keywordMatchScore = computeKeywordMatchScore({
640
+ queryTokens,
641
+ staticKeywordTokens: effectiveStaticKeywordTokens,
642
+ learnedKeywordMap,
643
+ });
644
+ const learnedPatternsScore = computeLearnedPatternsScore({
645
+ queryText: normalizedMessage,
646
+ queryTokens,
647
+ learnedPatterns,
648
+ });
649
+ const fuzzyScore = computeFuzzyScore(normalizedMessage, effectiveMatchPhrases);
650
+ const finalScore = clamp01(bm25Score * BM25_WEIGHT + keywordMatchScore * KEYWORD_MATCH_WEIGHT + learnedPatternsScore * LEARNED_PATTERNS_WEIGHT + fuzzyScore * FUZZY_MATCH_WEIGHT + entry.popularityScore * TOOL_POPULARITY_WEIGHT);
651
+ return {
652
+ ...entry,
653
+ bm25Score,
654
+ keywordMatchScore,
655
+ learnedPatternsScore,
656
+ fuzzyScore,
657
+ commandConfigOverlayVersion: Number(commandConfigOverlay?.version || 0),
658
+ finalScore,
659
+ };
660
+ })
661
+ .sort((left, right) => {
662
+ if (right.finalScore !== left.finalScore) return right.finalScore - left.finalScore;
663
+ if (right.popularityScore !== left.popularityScore) {
664
+ return right.popularityScore - left.popularityScore;
665
+ }
666
+ return left.toolName.localeCompare(right.toolName);
667
+ });
668
+
669
+ const aboveThreshold = ranked.filter((entry) => entry.finalScore >= minScore);
670
+ const fallbackUsed = aboveThreshold.length === 0;
671
+ const selectedEntries = fallbackUsed
672
+ ? snapshot.popularEntries.slice(0, Math.min(safeLimit, TOOL_SELECTION_FALLBACK_POPULAR_LIMIT)).map((entry) => ({
673
+ ...entry,
674
+ bm25Score: 0,
675
+ keywordMatchScore: 0,
676
+ learnedPatternsScore: 0,
677
+ fuzzyScore: 0,
678
+ finalScore: entry.popularityScore,
679
+ }))
680
+ : aboveThreshold.slice(0, safeLimit);
681
+
682
+ const elapsedMs = Number(process.hrtime.bigint() - startNs) / 1e6;
683
+ const selection = buildSelectionFromEntries({
684
+ rankedEntries: selectedEntries,
685
+ fallbackUsed,
686
+ selectionTimeMs: Number(elapsedMs.toFixed(3)),
687
+ cacheHit: false,
688
+ limit: safeLimit,
689
+ });
690
+
691
+ queryCache.set(cacheKey, {
692
+ expiresAt: nowMs + TOOL_SELECTION_CACHE_TTL_MS,
693
+ result: selection,
694
+ });
695
+ ensureCacheSize();
696
+
697
+ logger.info('Selecao dinamica de tools concluida.', {
698
+ action: 'tool_candidate_selection',
699
+ selection_time_ms: selection.selectionTimeMs,
700
+ candidate_tools: selection.candidateTools,
701
+ selected_count: selection.selectedCount,
702
+ fallback_used: selection.fallbackUsed,
703
+ cache_hit: false,
704
+ limit: safeLimit,
705
+ min_score: minScore,
706
+ });
707
+
708
+ return selection;
709
+ };
710
+
711
+ export const getCandidateTools = async (userMessage, limit = TOOL_SELECTION_MAX_CANDIDATES) => {
712
+ const selection = await selectCandidateTools(userMessage, limit);
713
+ return selection.tools;
714
+ };
715
+
716
+ export const getToolCandidateSelectorConfig = () => ({
717
+ maxCandidates: TOOL_SELECTION_MAX_CANDIDATES,
718
+ minScore: TOOL_SELECTION_MIN_SCORE,
719
+ fallbackPopularLimit: TOOL_SELECTION_FALLBACK_POPULAR_LIMIT,
720
+ cacheTtlMs: TOOL_SELECTION_CACHE_TTL_MS,
721
+ cacheMaxEntries: TOOL_SELECTION_CACHE_MAX_ENTRIES,
722
+ learnedRefreshMs: TOOL_SELECTION_LEARNED_REFRESH_MS,
723
+ learnedMaxRows: TOOL_SELECTION_LEARNED_MAX_ROWS,
724
+ commandConfigRefreshMs: TOOL_SELECTION_ENRICHMENT_REFRESH_MS,
725
+ commandConfigMaxRows: TOOL_SELECTION_ENRICHMENT_MAX_ROWS,
726
+ scoreWeights: {
727
+ bm25: BM25_WEIGHT,
728
+ keywordMatch: KEYWORD_MATCH_WEIGHT,
729
+ learnedPatterns: LEARNED_PATTERNS_WEIGHT,
730
+ fuzzy: FUZZY_MATCH_WEIGHT,
731
+ popularity: TOOL_POPULARITY_WEIGHT,
732
+ },
733
+ });
734
+
735
+ export const warmupToolCandidateSelector = async () => {
736
+ buildOrGetIndexSnapshot();
737
+ await Promise.all([refreshLearnedKnowledgeCache({ force: true }), refreshCommandConfigEnrichmentCache({ force: true })]);
738
+ };
739
+
740
+ export const markToolCandidateLearningCacheDirty = () => {
741
+ learnedKnowledgeCache = {
742
+ ...learnedKnowledgeCache,
743
+ nextRefreshAt: 0,
744
+ };
745
+ commandConfigEnrichmentCache = {
746
+ ...commandConfigEnrichmentCache,
747
+ nextRefreshAt: 0,
748
+ };
749
+ queryCache.clear();
750
+ };
751
+
752
+ export const markToolCandidateCommandConfigCacheDirty = () => {
753
+ commandConfigEnrichmentCache = {
754
+ ...commandConfigEnrichmentCache,
755
+ nextRefreshAt: 0,
756
+ };
757
+ queryCache.clear();
758
+ };
759
+
760
+ export const resetToolCandidateSelectorCacheForTests = () => {
761
+ queryCache.clear();
762
+ cachedIndexSnapshot = null;
763
+ cachedIndexSignature = '';
764
+ learnedKnowledgeCache = {
765
+ loaded: false,
766
+ version: '0:0',
767
+ lastLoadedAt: 0,
768
+ nextRefreshAt: 0,
769
+ keywordsByTool: new Map(),
770
+ patternsByTool: new Map(),
771
+ };
772
+ commandConfigEnrichmentCache = {
773
+ loaded: false,
774
+ version: '0:0:0',
775
+ lastLoadedAt: 0,
776
+ nextRefreshAt: 0,
777
+ overlayByModuleCommand: new Map(),
778
+ };
779
+ learnedRefreshPromise = null;
780
+ commandConfigRefreshPromise = null;
781
+ };