machinaos 0.0.78 → 0.0.80

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 (743) hide show
  1. package/.env.template +74 -5
  2. package/{workflows/AI Assistant_workflow-1778504793388-ou1m1tz2x.json → .machina/workflows/AI Assistant_example_workflow-1779017037684-e2e5da7a.json } +164 -105
  3. package/{workflows/AI Employee_example_workflow-1777720598005-u4cm858dv.json → .machina/workflows/AI Employee_example_workflow-1779102911870-cbc76c82.json } +582 -328
  4. package/{workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json → .machina/workflows/Claude Assistant_example_workflow-1779095939967-2369cff4.json } +152 -83
  5. package/README.md +5 -2
  6. package/bin/cli.js +2 -2
  7. package/{machina → cli}/__main__.py +11 -7
  8. package/cli/_common.py +122 -0
  9. package/cli/buildenv.py +40 -0
  10. package/cli/cli.py +204 -0
  11. package/{machina → cli}/colors.py +10 -2
  12. package/cli/commands/__init__.py +1 -0
  13. package/cli/commands/_temporal_specs.py +59 -0
  14. package/{machina → cli}/commands/build.py +35 -45
  15. package/cli/commands/clean.py +141 -0
  16. package/cli/commands/daemon/__init__.py +47 -0
  17. package/cli/commands/daemon/_state.py +97 -0
  18. package/cli/commands/daemon/restart.py +14 -0
  19. package/cli/commands/daemon/start.py +49 -0
  20. package/cli/commands/daemon/status.py +20 -0
  21. package/cli/commands/daemon/stop.py +22 -0
  22. package/{machina → cli}/commands/dev.py +32 -42
  23. package/{machina → cli}/commands/docs.py +13 -11
  24. package/{machina → cli}/commands/start.py +69 -62
  25. package/{machina → cli}/commands/stop.py +7 -10
  26. package/{machina → cli}/commands/version.py +12 -6
  27. package/cli/config.py +170 -0
  28. package/cli/platform_.py +169 -0
  29. package/{machina → cli}/ports.py +42 -3
  30. package/{machina → cli}/run.py +29 -2
  31. package/{machina → cli}/supervisor.py +29 -12
  32. package/{machina → cli}/tcp.py +6 -2
  33. package/{machina → cli}/tree.py +38 -11
  34. package/client/dist/assets/{ActionBar-Du2MSFSz.js → ActionBar-Cjr3TF7g.js} +1 -1
  35. package/client/dist/assets/{ApiKeyInput-k2LBmBjb.js → ApiKeyInput-DIJE2PVA.js} +1 -1
  36. package/client/dist/assets/{ApiKeyPanel-C_bV9U0X.js → ApiKeyPanel-CPmye7uh.js} +1 -1
  37. package/client/dist/assets/{ApiUsageSection-CmVfwZzL.js → ApiUsageSection-TF_7gH2D.js} +1 -1
  38. package/client/dist/assets/{EmailPanel-CeKIMGu-.js → EmailPanel-Bs-xvbKR.js} +1 -1
  39. package/client/dist/assets/{OAuthPanel-KA3t3Q2K.js → OAuthPanel-BDtVJhAV.js} +1 -1
  40. package/client/dist/assets/{QrPairingPanel-NgNpJNuk.js → QrPairingPanel-BwJehTuZ.js} +1 -1
  41. package/client/dist/assets/{RateLimitSection-Du5YNVIA.js → RateLimitSection-CfNOoPIS.js} +1 -1
  42. package/client/dist/assets/{StatusCard-DNLyayXc.js → StatusCard-DkwIrgdP.js} +1 -1
  43. package/client/dist/assets/index-P2FzntoL.js +165 -0
  44. package/client/dist/index.html +1 -1
  45. package/client/package.json +1 -1
  46. package/client/src/Dashboard.tsx +128 -76
  47. package/client/src/adapters/nodeSpecToDescription.ts +7 -0
  48. package/client/src/assets/icons/index.test.ts +10 -0
  49. package/client/src/assets/icons/index.ts +16 -3
  50. package/client/src/components/AIAgentNode.tsx +8 -8
  51. package/client/src/components/ParameterRenderer.tsx +6 -3
  52. package/client/src/components/SkillEditorModal.tsx +1 -0
  53. package/client/src/components/credentials/panels/EmailPanel.tsx +2 -0
  54. package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +2 -0
  55. package/client/src/components/credentials/sections/RateLimitSection.tsx +1 -0
  56. package/client/src/components/icons/AIProviderIcons.tsx +1 -0
  57. package/client/src/components/maps/GoogleMapsPicker.tsx +1 -0
  58. package/client/src/components/parameterPanel/InputSection.tsx +1 -0
  59. package/client/src/components/parameterPanel/MasterSkillEditor.tsx +1 -0
  60. package/client/src/components/parameterPanel/OutputSection.tsx +1 -0
  61. package/client/src/components/ui/ComponentPalette.tsx +1 -0
  62. package/client/src/components/ui/MapSelector.tsx +1 -0
  63. package/client/src/components/ui/NodeContextMenu.tsx +3 -3
  64. package/client/src/components/ui/SettingsPanel.tsx +1 -0
  65. package/client/src/components/ui/action-button.tsx +1 -0
  66. package/client/src/components/ui/badge.tsx +1 -0
  67. package/client/src/components/ui/button.tsx +1 -0
  68. package/client/src/components/ui/form.tsx +1 -0
  69. package/client/src/components/ui/tabs.tsx +1 -0
  70. package/client/src/contexts/AuthContext.tsx +1 -0
  71. package/client/src/contexts/ThemeContext.tsx +1 -0
  72. package/client/src/contexts/WebSocketContext.tsx +104 -34
  73. package/client/src/hooks/__tests__/useApiKeys.test.ts +2 -2
  74. package/client/src/hooks/useReactFlowNodes.ts +1 -0
  75. package/client/src/hooks/useWorkflowValidation.ts +142 -0
  76. package/client/src/lib/nodeSpec.ts +1 -0
  77. package/client/src/test/providers.tsx +1 -0
  78. package/client/src/types/__tests__/cloudEvents.test.ts +5 -2
  79. package/client/src/types/cloudEvents.ts +19 -7
  80. package/client/src/utils/nodeUtils.ts +1 -1
  81. package/client/src/utils/workflow.ts +8 -2
  82. package/client/src/utils/workflowExport.ts +60 -3
  83. package/package.json +24 -23
  84. package/scripts/install.js +16 -27
  85. package/scripts/migrate_icons.py +3 -1
  86. package/scripts/migrate_skill_icons.py +6 -7
  87. package/scripts/postinstall.js +11 -9
  88. package/server/config/ai_cli_providers.json +2 -3
  89. package/server/config/credential_providers.json +15 -15
  90. package/server/config/llm_defaults.json +1 -1
  91. package/server/config/model_registry.json +416 -611
  92. package/server/constants.py +285 -223
  93. package/server/core/__init__.py +1 -1
  94. package/server/core/cache.py +9 -29
  95. package/server/core/cleanup.py +12 -24
  96. package/server/core/config.py +148 -24
  97. package/server/core/container.py +68 -59
  98. package/server/core/credential_backends.py +5 -13
  99. package/server/core/credentials_database.py +13 -43
  100. package/server/core/database.py +292 -353
  101. package/server/core/health.py +4 -5
  102. package/server/core/logging.py +241 -87
  103. package/server/core/paths.py +285 -0
  104. package/server/core/tracing.py +2 -8
  105. package/server/gunicorn.conf.py +1 -0
  106. package/server/main.py +150 -74
  107. package/server/middleware/auth.py +18 -24
  108. package/server/models/__init__.py +1 -1
  109. package/server/models/auth.py +5 -12
  110. package/server/models/database.py +36 -68
  111. package/server/models/node_metadata.py +25 -18
  112. package/server/nodejs/dist/index.js +107 -0
  113. package/server/nodes/README.md +11 -5
  114. package/server/nodes/__init__.py +1 -1
  115. package/server/nodes/_visuals.py +146 -14
  116. package/server/nodes/agent/_events.py +124 -0
  117. package/server/nodes/agent/_handles.py +15 -29
  118. package/server/nodes/agent/_inline.py +28 -25
  119. package/server/nodes/agent/_specialized.py +30 -15
  120. package/server/nodes/agent/{ai_agent.py → ai_agent/__init__.py} +33 -17
  121. package/server/nodes/agent/ai_agent/meta.json +3 -0
  122. package/server/nodes/agent/{ai_employee.py → ai_employee/__init__.py} +5 -2
  123. package/server/nodes/agent/ai_employee/meta.json +3 -0
  124. package/server/nodes/agent/{android_agent.py → android_agent/__init__.py} +1 -1
  125. package/server/nodes/agent/android_agent/meta.json +3 -0
  126. package/server/nodes/agent/{autonomous_agent.py → autonomous_agent/__init__.py} +2 -1
  127. package/server/nodes/agent/autonomous_agent/meta.json +3 -0
  128. package/server/nodes/agent/{chat_agent.py → chat_agent/__init__.py} +29 -12
  129. package/server/nodes/agent/chat_agent/meta.json +3 -0
  130. package/server/nodes/agent/{claude_code_agent.py → claude_code_agent/__init__.py} +192 -95
  131. package/server/nodes/agent/claude_code_agent/_handlers.py +169 -0
  132. package/server/{services/claude_oauth.py → nodes/agent/claude_code_agent/_oauth.py} +26 -13
  133. package/server/nodes/agent/claude_code_agent/_pool.py +1020 -0
  134. package/server/nodes/agent/claude_code_agent/_provider.py +513 -0
  135. package/server/nodes/agent/claude_code_agent/_skills.py +245 -0
  136. package/server/nodes/agent/claude_code_agent/meta.json +3 -0
  137. package/server/nodes/agent/{codex_agent.py → codex_agent/__init__.py} +26 -35
  138. package/server/nodes/agent/codex_agent/meta.json +3 -0
  139. package/server/nodes/agent/{coding_agent.py → coding_agent/__init__.py} +1 -1
  140. package/server/nodes/agent/coding_agent/meta.json +3 -0
  141. package/server/nodes/agent/{consumer_agent.py → consumer_agent/__init__.py} +1 -1
  142. package/server/nodes/agent/consumer_agent/meta.json +3 -0
  143. package/server/nodes/agent/{orchestrator_agent.py → orchestrator_agent/__init__.py} +5 -2
  144. package/server/nodes/agent/orchestrator_agent/meta.json +3 -0
  145. package/server/nodes/agent/{payments_agent.py → payments_agent/__init__.py} +1 -1
  146. package/server/nodes/agent/payments_agent/meta.json +3 -0
  147. package/server/nodes/agent/{productivity_agent.py → productivity_agent/__init__.py} +1 -1
  148. package/server/nodes/agent/productivity_agent/meta.json +3 -0
  149. package/server/nodes/agent/{rlm_agent.py → rlm_agent/__init__.py} +18 -17
  150. package/server/nodes/agent/rlm_agent/meta.json +3 -0
  151. package/server/nodes/agent/{social_agent.py → social_agent/__init__.py} +1 -1
  152. package/server/nodes/agent/social_agent/meta.json +3 -0
  153. package/server/nodes/agent/{task_agent.py → task_agent/__init__.py} +1 -1
  154. package/server/nodes/agent/task_agent/meta.json +3 -0
  155. package/server/nodes/agent/{tool_agent.py → tool_agent/__init__.py} +1 -1
  156. package/server/nodes/agent/tool_agent/meta.json +3 -0
  157. package/server/nodes/agent/{travel_agent.py → travel_agent/__init__.py} +1 -1
  158. package/server/nodes/agent/travel_agent/meta.json +3 -0
  159. package/server/nodes/agent/{web_agent.py → web_agent/__init__.py} +1 -1
  160. package/server/nodes/agent/web_agent/meta.json +3 -0
  161. package/server/nodes/android/__init__.py +24 -0
  162. package/server/nodes/android/_base.py +93 -76
  163. package/server/nodes/android/_dispatcher.py +140 -223
  164. package/server/nodes/android/_events.py +154 -0
  165. package/server/nodes/android/_handlers.py +13 -7
  166. package/server/nodes/android/_option_loaders.py +1 -4
  167. package/server/nodes/android/_refresh.py +27 -37
  168. package/server/nodes/android/_relay/broadcaster.py +25 -41
  169. package/server/nodes/android/_relay/client.py +23 -42
  170. package/server/nodes/android/_relay/manager.py +1 -0
  171. package/server/nodes/android/_relay/protocol.py +6 -0
  172. package/server/nodes/android/_router.py +48 -133
  173. package/server/nodes/android/{airplane_mode_control.py → airplane_mode_control/__init__.py} +2 -1
  174. package/server/nodes/android/airplane_mode_control/meta.json +3 -0
  175. package/server/nodes/android/{app_launcher.py → app_launcher/__init__.py} +2 -1
  176. package/server/nodes/android/app_launcher/meta.json +3 -0
  177. package/server/nodes/android/{app_list.py → app_list/__init__.py} +2 -1
  178. package/server/nodes/android/app_list/meta.json +3 -0
  179. package/server/nodes/android/{audio_automation.py → audio_automation/__init__.py} +2 -1
  180. package/server/nodes/android/audio_automation/meta.json +3 -0
  181. package/server/nodes/android/{battery_monitor.py → battery_monitor/__init__.py} +2 -1
  182. package/server/nodes/android/battery_monitor/meta.json +3 -0
  183. package/server/nodes/android/{bluetooth_automation.py → bluetooth_automation/__init__.py} +2 -1
  184. package/server/nodes/android/bluetooth_automation/meta.json +3 -0
  185. package/server/nodes/android/{camera_control.py → camera_control/__init__.py} +2 -1
  186. package/server/nodes/android/camera_control/meta.json +3 -0
  187. package/server/nodes/android/{device_state_automation.py → device_state_automation/__init__.py} +2 -1
  188. package/server/nodes/android/device_state_automation/meta.json +3 -0
  189. package/server/nodes/android/{environmental_sensors.py → environmental_sensors/__init__.py} +2 -1
  190. package/server/nodes/android/environmental_sensors/meta.json +3 -0
  191. package/server/nodes/android/{location.py → location/__init__.py} +2 -1
  192. package/server/nodes/android/location/meta.json +3 -0
  193. package/server/nodes/android/{media_control.py → media_control/__init__.py} +2 -1
  194. package/server/nodes/android/media_control/meta.json +3 -0
  195. package/server/nodes/android/{motion_detection.py → motion_detection/__init__.py} +2 -1
  196. package/server/nodes/android/motion_detection/meta.json +3 -0
  197. package/server/nodes/android/{network_monitor.py → network_monitor/__init__.py} +2 -1
  198. package/server/nodes/android/network_monitor/meta.json +3 -0
  199. package/server/nodes/android/{screen_control_automation.py → screen_control_automation/__init__.py} +2 -1
  200. package/server/nodes/android/screen_control_automation/meta.json +3 -0
  201. package/server/nodes/android/{system_info.py → system_info/__init__.py} +2 -1
  202. package/server/nodes/android/system_info/meta.json +3 -0
  203. package/server/nodes/android/{wifi_automation.py → wifi_automation/__init__.py} +2 -1
  204. package/server/nodes/android/wifi_automation/meta.json +3 -0
  205. package/server/nodes/browser/__init__.py +22 -1
  206. package/server/nodes/browser/_install.py +63 -0
  207. package/server/nodes/browser/_service.py +21 -25
  208. package/server/nodes/browser/{browser.py → browser/__init__.py} +58 -25
  209. package/server/nodes/browser/browser/meta.json +3 -0
  210. package/server/nodes/chat/{chat_history.py → chat_history/__init__.py} +2 -4
  211. package/server/nodes/chat/chat_history/meta.json +3 -0
  212. package/server/nodes/chat/{chat_send.py → chat_send/__init__.py} +2 -4
  213. package/server/nodes/chat/chat_send/icon.svg +1 -0
  214. package/server/nodes/chat/chat_send/meta.json +3 -0
  215. package/server/nodes/code/_base.py +1 -1
  216. package/server/nodes/code/{javascript_executor.py → javascript_executor/__init__.py} +5 -5
  217. package/server/nodes/code/javascript_executor/meta.json +3 -0
  218. package/server/nodes/code/{python_executor.py → python_executor/__init__.py} +32 -14
  219. package/server/nodes/code/python_executor/meta.json +3 -0
  220. package/server/nodes/code/{typescript_executor.py → typescript_executor/__init__.py} +5 -5
  221. package/server/nodes/code/typescript_executor/meta.json +3 -0
  222. package/server/nodes/document/{document_parser.py → document_parser/__init__.py} +26 -15
  223. package/server/nodes/document/document_parser/meta.json +3 -0
  224. package/server/nodes/document/{embedding_generator.py → embedding_generator/__init__.py} +16 -9
  225. package/server/nodes/document/embedding_generator/meta.json +3 -0
  226. package/server/nodes/document/{file_downloader.py → file_downloader/__init__.py} +30 -20
  227. package/server/nodes/document/file_downloader/meta.json +3 -0
  228. package/server/nodes/document/{http_scraper.py → http_scraper/__init__.py} +31 -21
  229. package/server/nodes/document/http_scraper/meta.json +3 -0
  230. package/server/nodes/document/{text_chunker.py → text_chunker/__init__.py} +17 -12
  231. package/server/nodes/document/text_chunker/meta.json +3 -0
  232. package/server/nodes/document/{vector_store.py → vector_store/__init__.py} +88 -72
  233. package/server/nodes/document/vector_store/meta.json +3 -0
  234. package/server/nodes/email/__init__.py +9 -2
  235. package/server/nodes/email/_events.py +54 -0
  236. package/server/nodes/email/_filters.py +3 -3
  237. package/server/nodes/email/_himalaya.py +95 -50
  238. package/server/nodes/email/_service.py +23 -13
  239. package/server/nodes/email/{email_read.py → email_read/__init__.py} +23 -11
  240. package/server/nodes/email/email_read/icon.svg +6 -0
  241. package/server/nodes/email/email_read/meta.json +3 -0
  242. package/server/nodes/email/{email_receive.py → email_receive/__init__.py} +45 -23
  243. package/server/nodes/email/email_receive/meta.json +3 -0
  244. package/server/nodes/email/{email_send.py → email_send/__init__.py} +13 -7
  245. package/server/nodes/email/email_send/meta.json +3 -0
  246. package/server/nodes/filesystem/_backend.py +1 -5
  247. package/server/nodes/filesystem/{file_modify.py → file_modify/__init__.py} +10 -5
  248. package/server/nodes/filesystem/file_modify/meta.json +3 -0
  249. package/server/nodes/filesystem/{file_read.py → file_read/__init__.py} +7 -3
  250. package/server/nodes/filesystem/file_read/meta.json +3 -0
  251. package/server/nodes/filesystem/{fs_search.py → fs_search/__init__.py} +11 -3
  252. package/server/nodes/filesystem/fs_search/meta.json +3 -0
  253. package/server/nodes/filesystem/{shell.py → shell/__init__.py} +12 -5
  254. package/server/nodes/filesystem/shell/meta.json +3 -0
  255. package/server/nodes/google/__init__.py +12 -0
  256. package/server/nodes/google/_auth_helper.py +7 -13
  257. package/server/nodes/google/_base.py +14 -11
  258. package/server/nodes/google/_credentials.py +2 -1
  259. package/server/nodes/google/_events.py +47 -0
  260. package/server/nodes/google/_filters.py +3 -3
  261. package/server/nodes/google/_gmail.py +70 -47
  262. package/server/nodes/google/_handlers.py +3 -1
  263. package/server/nodes/google/_oauth.py +25 -11
  264. package/server/nodes/google/_option_loaders.py +9 -30
  265. package/server/nodes/google/_refresh.py +8 -12
  266. package/server/nodes/google/_router.py +4 -5
  267. package/server/nodes/google/{calendar.py → calendar/__init__.py} +87 -64
  268. package/server/nodes/google/calendar/meta.json +3 -0
  269. package/server/nodes/google/{contacts.py → contacts/__init__.py} +84 -72
  270. package/server/nodes/google/contacts/meta.json +3 -0
  271. package/server/nodes/google/{drive.py → drive/__init__.py} +87 -72
  272. package/server/nodes/google/drive/meta.json +3 -0
  273. package/server/nodes/google/{gmail.py → gmail/__init__.py} +73 -39
  274. package/server/nodes/google/gmail/meta.json +3 -0
  275. package/server/nodes/google/{gmail_receive.py → gmail_receive/__init__.py} +31 -24
  276. package/server/nodes/google/gmail_receive/icon.svg +7 -0
  277. package/server/nodes/google/gmail_receive/meta.json +3 -0
  278. package/server/nodes/google/google.svg +7 -0
  279. package/server/nodes/google/{sheets.py → sheets/__init__.py} +54 -42
  280. package/server/nodes/google/sheets/meta.json +3 -0
  281. package/server/nodes/google/{tasks.py → tasks/__init__.py} +56 -43
  282. package/server/nodes/google/tasks/meta.json +3 -0
  283. package/server/nodes/groups.py +28 -28
  284. package/server/nodes/location/__init__.py +31 -1
  285. package/server/nodes/location/_credentials.py +1 -6
  286. package/server/nodes/location/_service.py +88 -107
  287. package/server/nodes/location/{gmaps_create.py → gmaps_create/__init__.py} +6 -6
  288. package/server/nodes/location/gmaps_create/meta.json +3 -0
  289. package/server/nodes/location/{gmaps_locations.py → gmaps_locations/__init__.py} +8 -6
  290. package/server/nodes/location/gmaps_locations/meta.json +3 -0
  291. package/server/nodes/location/{gmaps_nearby_places.py → gmaps_nearby_places/__init__.py} +8 -6
  292. package/server/nodes/location/gmaps_nearby_places/meta.json +3 -0
  293. package/server/nodes/model/_base.py +10 -7
  294. package/server/nodes/model/_credentials.py +10 -10
  295. package/server/nodes/model/_local_validator.py +28 -24
  296. package/server/nodes/model/{anthropic_chat_model.py → anthropic_chat_model/__init__.py} +5 -3
  297. package/server/nodes/model/anthropic_chat_model/meta.json +3 -0
  298. package/server/nodes/model/{cerebras_chat_model.py → cerebras_chat_model/__init__.py} +5 -3
  299. package/server/nodes/model/cerebras_chat_model/meta.json +3 -0
  300. package/server/nodes/model/{deepseek_chat_model.py → deepseek_chat_model/__init__.py} +8 -4
  301. package/server/nodes/model/deepseek_chat_model/meta.json +3 -0
  302. package/server/nodes/model/{gemini_chat_model.py → gemini_chat_model/__init__.py} +5 -3
  303. package/server/nodes/model/gemini_chat_model/meta.json +3 -0
  304. package/server/nodes/model/{groq_chat_model.py → groq_chat_model/__init__.py} +2 -2
  305. package/server/nodes/model/groq_chat_model/meta.json +3 -0
  306. package/server/nodes/model/{kimi_chat_model.py → kimi_chat_model/__init__.py} +2 -2
  307. package/server/nodes/model/kimi_chat_model/meta.json +3 -0
  308. package/server/nodes/model/{lmstudio_chat_model.py → lmstudio_chat_model/__init__.py} +2 -2
  309. package/server/nodes/model/lmstudio_chat_model/meta.json +3 -0
  310. package/server/nodes/model/{mistral_chat_model.py → mistral_chat_model/__init__.py} +2 -2
  311. package/server/nodes/model/mistral_chat_model/meta.json +3 -0
  312. package/server/nodes/model/{ollama_chat_model.py → ollama_chat_model/__init__.py} +2 -2
  313. package/server/nodes/model/ollama_chat_model/meta.json +3 -0
  314. package/server/nodes/model/{openai_chat_model.py → openai_chat_model/__init__.py} +8 -4
  315. package/server/nodes/model/openai_chat_model/meta.json +3 -0
  316. package/server/nodes/model/{openrouter_chat_model.py → openrouter_chat_model/__init__.py} +8 -4
  317. package/server/nodes/model/openrouter_chat_model/meta.json +3 -0
  318. package/server/nodes/proxy/_usage.py +14 -15
  319. package/server/nodes/proxy/{proxy_config.py → proxy_config/__init__.py} +39 -30
  320. package/server/nodes/proxy/proxy_config/meta.json +3 -0
  321. package/server/nodes/proxy/{proxy_request.py → proxy_request/__init__.py} +30 -16
  322. package/server/nodes/proxy/proxy_request/meta.json +3 -0
  323. package/server/nodes/proxy/{proxy_status.py → proxy_status/__init__.py} +2 -0
  324. package/server/nodes/proxy/proxy_status/meta.json +3 -0
  325. package/server/nodes/scheduler/{cron_scheduler.py → cron_scheduler/__init__.py} +96 -23
  326. package/server/nodes/scheduler/cron_scheduler/_workflow.py +155 -0
  327. package/server/nodes/scheduler/cron_scheduler/meta.json +3 -0
  328. package/server/nodes/scheduler/{timer.py → timer/__init__.py} +6 -5
  329. package/server/nodes/scheduler/timer/meta.json +3 -0
  330. package/server/nodes/scraper/_credentials.py +0 -1
  331. package/server/nodes/scraper/{apify_actor.py → apify_actor/__init__.py} +44 -35
  332. package/server/nodes/scraper/apify_actor/icon.svg +5 -0
  333. package/server/nodes/scraper/apify_actor/meta.json +3 -0
  334. package/server/nodes/scraper/{crawlee_scraper.py → crawlee_scraper/__init__.py} +96 -57
  335. package/server/nodes/scraper/crawlee_scraper/meta.json +3 -0
  336. package/server/nodes/search/{brave_search.py → brave_search/__init__.py} +6 -5
  337. package/server/nodes/search/brave_search/icon.svg +3 -0
  338. package/server/nodes/search/brave_search/meta.json +3 -0
  339. package/server/nodes/search/{duckduckgo_search.py → duckduckgo_search/__init__.py} +17 -6
  340. package/server/nodes/search/duckduckgo_search/meta.json +3 -0
  341. package/server/nodes/search/{perplexity_search.py → perplexity_search/__init__.py} +4 -5
  342. package/server/nodes/search/perplexity_search/icon.svg +3 -0
  343. package/server/nodes/search/perplexity_search/meta.json +3 -0
  344. package/server/nodes/search/{serper_search.py → serper_search/__init__.py} +32 -25
  345. package/server/nodes/search/serper_search/icon.svg +3 -0
  346. package/server/nodes/search/serper_search/meta.json +3 -0
  347. package/server/nodes/skill/__init__.py +21 -1
  348. package/server/nodes/skill/_expander.py +75 -0
  349. package/server/nodes/skill/{master_skill.py → master_skill/__init__.py} +2 -8
  350. package/server/nodes/skill/master_skill/_events.py +84 -0
  351. package/server/nodes/skill/master_skill/meta.json +3 -0
  352. package/server/nodes/skill/{simple_memory.py → simple_memory/__init__.py} +8 -16
  353. package/server/nodes/skill/simple_memory/meta.json +3 -0
  354. package/server/nodes/social/_base.py +223 -231
  355. package/server/nodes/social/{social_receive.py → social_receive/__init__.py} +38 -13
  356. package/server/nodes/social/social_receive/meta.json +3 -0
  357. package/server/nodes/social/{social_send.py → social_send/__init__.py} +71 -29
  358. package/server/nodes/social/social_send/icon.svg +1 -0
  359. package/server/nodes/social/social_send/meta.json +3 -0
  360. package/server/nodes/stripe/__init__.py +7 -3
  361. package/server/nodes/stripe/_credentials.py +0 -1
  362. package/server/nodes/stripe/_handlers.py +18 -7
  363. package/server/nodes/stripe/_install.py +14 -15
  364. package/server/nodes/stripe/_source.py +5 -5
  365. package/server/nodes/stripe/icon.svg +1 -0
  366. package/server/nodes/stripe/meta.json +3 -0
  367. package/server/nodes/stripe/stripe_action.py +4 -4
  368. package/server/nodes/stripe/stripe_receive.py +6 -9
  369. package/server/nodes/telegram/__init__.py +13 -0
  370. package/server/nodes/telegram/_credentials.py +2 -7
  371. package/server/nodes/telegram/_events.py +167 -0
  372. package/server/nodes/telegram/_filters.py +3 -11
  373. package/server/nodes/telegram/_handlers.py +17 -7
  374. package/server/nodes/telegram/_refresh.py +24 -34
  375. package/server/nodes/telegram/_service.py +29 -45
  376. package/server/nodes/telegram/meta.json +3 -0
  377. package/server/nodes/telegram/telegram.svg +3 -0
  378. package/server/nodes/telegram/telegram_receive.py +38 -18
  379. package/server/nodes/telegram/telegram_send.py +21 -19
  380. package/server/nodes/text/{file_handler.py → file_handler/__init__.py} +7 -1
  381. package/server/nodes/text/file_handler/meta.json +3 -0
  382. package/server/nodes/text/{text_generator.py → text_generator/__init__.py} +2 -1
  383. package/server/nodes/text/text_generator/meta.json +3 -0
  384. package/server/nodes/tool/{agent_builder.py → agent_builder/__init__.py} +105 -100
  385. package/server/nodes/tool/agent_builder/_events.py +91 -0
  386. package/server/nodes/tool/agent_builder/meta.json +3 -0
  387. package/server/nodes/tool/{calculator_tool.py → calculator_tool/__init__.py} +19 -7
  388. package/server/nodes/tool/calculator_tool/meta.json +3 -0
  389. package/server/nodes/tool/{current_time_tool.py → current_time_tool/__init__.py} +6 -4
  390. package/server/nodes/tool/current_time_tool/meta.json +3 -0
  391. package/server/nodes/tool/{task_manager.py → task_manager/__init__.py} +17 -18
  392. package/server/nodes/tool/task_manager/meta.json +3 -0
  393. package/server/nodes/tool/{write_todos.py → write_todos/__init__.py} +20 -6
  394. package/server/nodes/tool/write_todos/meta.json +3 -0
  395. package/server/nodes/trigger/{chat_trigger.py → chat_trigger/__init__.py} +11 -7
  396. package/server/nodes/trigger/chat_trigger/_events.py +53 -0
  397. package/server/nodes/trigger/chat_trigger/meta.json +3 -0
  398. package/server/nodes/trigger/{task_trigger.py → task_trigger/__init__.py} +10 -7
  399. package/server/nodes/trigger/task_trigger/meta.json +3 -0
  400. package/server/nodes/trigger/{webhook_trigger.py → webhook_trigger/__init__.py} +10 -7
  401. package/server/nodes/trigger/webhook_trigger/_events.py +54 -0
  402. package/server/nodes/trigger/webhook_trigger/meta.json +3 -0
  403. package/server/nodes/twitter/__init__.py +7 -1
  404. package/server/nodes/twitter/_base.py +86 -61
  405. package/server/nodes/twitter/_credentials.py +7 -5
  406. package/server/nodes/twitter/_events.py +101 -0
  407. package/server/nodes/twitter/_filters.py +9 -9
  408. package/server/nodes/twitter/_handlers.py +3 -1
  409. package/server/nodes/twitter/_oauth.py +1 -2
  410. package/server/nodes/twitter/_refresh.py +8 -12
  411. package/server/nodes/twitter/{twitter_receive.py → twitter_receive/__init__.py} +7 -7
  412. package/server/nodes/twitter/twitter_receive/icon.svg +1 -0
  413. package/server/nodes/twitter/twitter_receive/meta.json +3 -0
  414. package/server/nodes/twitter/{twitter_search.py → twitter_search/__init__.py} +16 -11
  415. package/server/nodes/twitter/twitter_search/icon.svg +1 -0
  416. package/server/nodes/twitter/twitter_search/meta.json +3 -0
  417. package/server/nodes/twitter/{twitter_send.py → twitter_send/__init__.py} +60 -27
  418. package/server/nodes/twitter/twitter_send/icon.svg +1 -0
  419. package/server/nodes/twitter/twitter_send/meta.json +3 -0
  420. package/server/nodes/twitter/{twitter_user.py → twitter_user/__init__.py} +34 -19
  421. package/server/nodes/twitter/twitter_user/icon.svg +1 -0
  422. package/server/nodes/twitter/twitter_user/meta.json +3 -0
  423. package/server/nodes/utility/{console.py → console/__init__.py} +17 -22
  424. package/server/nodes/utility/console/meta.json +3 -0
  425. package/server/nodes/utility/{http_request.py → http_request/__init__.py} +9 -6
  426. package/server/nodes/utility/http_request/meta.json +3 -0
  427. package/server/nodes/utility/{process_manager.py → process_manager/__init__.py} +10 -6
  428. package/server/nodes/utility/process_manager/meta.json +3 -0
  429. package/server/nodes/utility/team_monitor/meta.json +3 -0
  430. package/server/nodes/utility/{webhook_response.py → webhook_response/__init__.py} +12 -7
  431. package/server/nodes/utility/webhook_response/meta.json +3 -0
  432. package/server/nodes/visuals.json +69 -251
  433. package/server/nodes/whatsapp/__init__.py +24 -0
  434. package/server/nodes/whatsapp/_base.py +283 -338
  435. package/server/nodes/whatsapp/_credentials.py +44 -0
  436. package/server/nodes/whatsapp/_events.py +277 -0
  437. package/server/nodes/whatsapp/_filters.py +36 -37
  438. package/server/nodes/whatsapp/_handlers.py +2 -0
  439. package/server/nodes/whatsapp/_option_loaders.py +1 -3
  440. package/server/nodes/whatsapp/_refresh.py +13 -18
  441. package/server/nodes/whatsapp/_runtime.py +9 -6
  442. package/server/nodes/whatsapp/_service.py +89 -152
  443. package/server/nodes/whatsapp/meta.json +3 -0
  444. package/server/nodes/whatsapp/whatsapp_db.py +116 -54
  445. package/server/nodes/whatsapp/whatsapp_receive.py +30 -13
  446. package/server/nodes/whatsapp/whatsapp_send.py +60 -37
  447. package/server/nodes/workflow/{start.py → start/__init__.py} +1 -4
  448. package/server/nodes/workflow/start/meta.json +3 -0
  449. package/server/package-lock.json +3 -3
  450. package/server/package.json +3 -0
  451. package/server/pyproject.toml +39 -10
  452. package/server/requirements.txt +3 -5
  453. package/server/routers/__init__.py +1 -1
  454. package/server/routers/auth.py +16 -56
  455. package/server/routers/database.py +27 -50
  456. package/server/routers/nodejs_compat.py +25 -87
  457. package/server/routers/schemas.py +66 -2
  458. package/server/routers/webhook.py +12 -12
  459. package/server/routers/websocket.py +312 -1716
  460. package/server/routers/workflow.py +28 -53
  461. package/server/scripts/smoke_test_skills.py +178 -0
  462. package/server/services/__init__.py +1 -1
  463. package/server/services/_supervisor/process.py +9 -3
  464. package/server/services/_supervisor/registry.py +3 -3
  465. package/server/services/_supervisor/util.py +1 -1
  466. package/server/services/agent_team.py +15 -43
  467. package/server/services/agent_teams/__init__.py +17 -0
  468. package/server/services/agent_teams/handlers.py +195 -0
  469. package/server/services/ai.py +853 -1108
  470. package/server/services/auth.py +10 -34
  471. package/server/services/chat_client.py +5 -34
  472. package/server/services/circuit_breaker.py +2 -6
  473. package/server/services/cli_agent/__init__.py +28 -4
  474. package/server/services/cli_agent/_cli_auth.py +61 -0
  475. package/server/services/cli_agent/_handlers.py +24 -183
  476. package/server/services/cli_agent/config.py +5 -8
  477. package/server/services/cli_agent/factory.py +168 -22
  478. package/server/services/cli_agent/jsonl_watcher.py +380 -0
  479. package/server/services/cli_agent/lockfile.py +9 -2
  480. package/server/services/cli_agent/mcp_server.py +110 -34
  481. package/server/services/cli_agent/protocol.py +37 -19
  482. package/server/services/cli_agent/providers/__init__.py +8 -4
  483. package/server/services/cli_agent/providers/google_gemini.py +11 -5
  484. package/server/services/cli_agent/providers/openai_codex.py +34 -34
  485. package/server/services/cli_agent/service.py +245 -83
  486. package/server/services/cli_agent/session.py +409 -229
  487. package/server/services/cli_agent/transports/__init__.py +47 -0
  488. package/server/services/cli_agent/transports/base.py +111 -0
  489. package/server/services/cli_agent/transports/posix.py +196 -0
  490. package/server/services/cli_agent/transports/windows.py +189 -0
  491. package/server/services/cli_agent/types.py +45 -18
  492. package/server/services/cli_agent/workflow_tools.py +28 -15
  493. package/server/services/compaction.py +68 -52
  494. package/server/services/credential_registry.py +6 -20
  495. package/server/services/credentials/__init__.py +18 -0
  496. package/server/services/credentials/handlers.py +196 -0
  497. package/server/services/deployment/__init__.py +12 -1
  498. package/server/services/deployment/canary_registry.py +137 -0
  499. package/server/services/deployment/handlers.py +382 -0
  500. package/server/services/deployment/manager.py +653 -163
  501. package/server/services/deployment/poll_registry.py +2 -6
  502. package/server/services/deployment/state.py +2 -0
  503. package/server/services/deployment/triggers.py +87 -93
  504. package/server/services/event_waiter.py +47 -54
  505. package/server/services/events/__init__.py +11 -0
  506. package/server/services/events/admin_handlers.py +368 -0
  507. package/server/services/events/daemon.py +3 -1
  508. package/server/services/events/dispatch.py +188 -0
  509. package/server/services/events/envelope.py +264 -45
  510. package/server/services/events/oauth_lifecycle.py +98 -42
  511. package/server/services/events/triggers.py +3 -13
  512. package/server/services/events/verifiers/hmac_basic.py +1 -1
  513. package/server/services/events/verifiers/standard_webhooks.py +2 -4
  514. package/server/services/events/webhook.py +2 -3
  515. package/server/services/example_loader.py +73 -15
  516. package/server/services/execution/cache.py +36 -76
  517. package/server/services/execution/conditions.py +7 -20
  518. package/server/services/execution/dlq.py +20 -24
  519. package/server/services/execution/executor.py +234 -265
  520. package/server/services/execution/models.py +40 -46
  521. package/server/services/execution/recovery.py +23 -46
  522. package/server/services/handlers/__init__.py +12 -16
  523. package/server/services/handlers/todo.py +3 -6
  524. package/server/services/handlers/tools.py +143 -194
  525. package/server/services/handlers/triggers.py +24 -23
  526. package/server/services/llm/config.py +10 -1
  527. package/server/services/llm/factory.py +16 -4
  528. package/server/services/llm/messages.py +1 -5
  529. package/server/services/llm/protocol.py +9 -1
  530. package/server/services/llm/providers/anthropic.py +23 -12
  531. package/server/services/llm/providers/gemini.py +43 -22
  532. package/server/services/llm/providers/openai.py +14 -6
  533. package/server/services/llm/providers/openrouter.py +6 -1
  534. package/server/services/markdown_formatter.py +1 -2
  535. package/server/services/memory/__init__.py +2 -2
  536. package/server/services/memory/jsonl.py +6 -2
  537. package/server/services/memory/markdown.py +6 -6
  538. package/server/services/memory/state.py +6 -5
  539. package/server/services/memory_store.py +8 -12
  540. package/server/services/model_registry.py +22 -20
  541. package/server/services/node_executor.py +85 -80
  542. package/server/services/node_output_schemas.py +4 -7
  543. package/server/services/node_registry.py +40 -4
  544. package/server/services/node_spec.py +3 -7
  545. package/server/services/nodejs_client.py +4 -14
  546. package/server/services/oauth_utils.py +11 -7
  547. package/server/services/parameter_resolver.py +30 -36
  548. package/server/services/plugin/base.py +321 -38
  549. package/server/services/plugin/connection.py +12 -7
  550. package/server/services/plugin/credential.py +80 -22
  551. package/server/services/plugin/edge_walker.py +128 -105
  552. package/server/services/plugin/identifiers.py +48 -0
  553. package/server/services/plugin/interceptor.py +1 -1
  554. package/server/services/plugin/oauth.py +25 -21
  555. package/server/services/plugin/operation.py +1 -1
  556. package/server/services/plugin/polling.py +151 -26
  557. package/server/services/plugin/registry.py +52 -4
  558. package/server/services/plugin/routing.py +6 -9
  559. package/server/services/plugin/scaling.py +36 -18
  560. package/server/services/plugin/service_factories.py +95 -0
  561. package/server/services/plugin/shutdown_hooks.py +103 -0
  562. package/server/services/plugin/social_provider_registry.py +80 -0
  563. package/server/services/plugin/ws.py +2 -1
  564. package/server/services/pricing.py +26 -40
  565. package/server/services/pricing_handlers.py +90 -0
  566. package/server/services/process_service.py +33 -32
  567. package/server/services/proxy/models.py +15 -9
  568. package/server/services/proxy/service.py +26 -40
  569. package/server/services/rlm/adapters.py +43 -40
  570. package/server/services/rlm/constants.py +9 -9
  571. package/server/services/rlm/service.py +57 -45
  572. package/server/services/scheduler.py +8 -39
  573. package/server/services/settings/__init__.py +16 -0
  574. package/server/services/settings/handlers.py +275 -0
  575. package/server/services/skill_loader.py +53 -45
  576. package/server/services/skill_prompt.py +8 -6
  577. package/server/services/skills/__init__.py +23 -0
  578. package/server/services/skills/handlers.py +479 -0
  579. package/server/services/status_broadcaster.py +314 -291
  580. package/server/services/temporal/__init__.py +22 -1
  581. package/server/services/temporal/_handlers.py +65 -0
  582. package/server/services/temporal/_install.py +158 -0
  583. package/server/services/temporal/_refresh.py +57 -0
  584. package/server/services/temporal/_retry_policies.py +85 -0
  585. package/server/services/temporal/_runtime.py +181 -0
  586. package/server/services/temporal/_supervised_runtime.py +102 -0
  587. package/server/services/temporal/activities.py +168 -11
  588. package/server/services/temporal/agent_activities.py +683 -0
  589. package/server/services/temporal/agent_workflow.py +601 -0
  590. package/server/services/temporal/client.py +58 -13
  591. package/server/services/temporal/executor.py +2 -3
  592. package/server/services/temporal/plugin_activities.py +37 -2
  593. package/server/services/temporal/plugin_registry.py +82 -0
  594. package/server/services/temporal/polling_trigger_workflow.py +267 -0
  595. package/server/services/temporal/schedules.py +220 -0
  596. package/server/services/temporal/search_attributes.py +177 -0
  597. package/server/services/temporal/trigger_listener_workflow.py +378 -0
  598. package/server/services/temporal/worker.py +111 -18
  599. package/server/services/temporal/workflow.py +259 -40
  600. package/server/services/temporal/ws_client.py +22 -11
  601. package/server/services/text.py +14 -28
  602. package/server/services/tracked_http.py +29 -49
  603. package/server/services/user_auth.py +7 -21
  604. package/server/services/workflow.py +28 -20
  605. package/server/services/workflow_import.py +351 -0
  606. package/server/services/workflow_ops.py +4 -0
  607. package/server/services/workflow_storage/__init__.py +18 -0
  608. package/server/services/workflow_storage/handlers.py +132 -0
  609. package/server/services/workflow_validator.py +209 -0
  610. package/server/services/ws_handler_registry.py +80 -9
  611. package/server/skills/assistant/agent-builder-skill/SKILL.md +6 -6
  612. package/server/tests/conftest.py +54 -3
  613. package/server/tests/credentials/test_auth_service.py +9 -21
  614. package/server/tests/credentials/test_credential_broadcasts.py +116 -22
  615. package/server/tests/credentials/test_credentials_database.py +12 -38
  616. package/server/tests/credentials/test_encryption.py +3 -9
  617. package/server/tests/credentials/test_google_oauth.py +1 -3
  618. package/server/tests/credentials/test_oauth_utils.py +31 -38
  619. package/server/tests/credentials/test_twitter_oauth.py +1 -3
  620. package/server/tests/credentials/test_websocket_handlers.py +37 -72
  621. package/server/tests/fixtures/tool_names_snapshot.json +78 -0
  622. package/server/tests/llm/test_factory.py +12 -4
  623. package/server/tests/llm/test_providers.py +25 -32
  624. package/server/tests/llm/test_wiring.py +27 -22
  625. package/server/tests/nodes/_compat.py +4 -5
  626. package/server/tests/nodes/_harness.py +31 -24
  627. package/server/tests/nodes/_mocks.py +2 -6
  628. package/server/tests/nodes/test_agent_builder.py +43 -35
  629. package/server/tests/nodes/test_ai_agents.py +29 -24
  630. package/server/tests/nodes/test_ai_chat_models.py +3 -9
  631. package/server/tests/nodes/test_ai_tools.py +29 -24
  632. package/server/tests/nodes/test_android.py +34 -64
  633. package/server/tests/nodes/test_chat_utility.py +2 -2
  634. package/server/tests/nodes/test_code_fs_process.py +26 -84
  635. package/server/tests/nodes/test_document.py +23 -47
  636. package/server/tests/nodes/test_email.py +88 -51
  637. package/server/tests/nodes/test_google_workspace.py +26 -20
  638. package/server/tests/nodes/test_http_proxy.py +43 -89
  639. package/server/tests/nodes/test_search.py +3 -9
  640. package/server/tests/nodes/test_specialized_agents.py +58 -162
  641. package/server/tests/nodes/test_stripe_plugin.py +25 -5
  642. package/server/tests/nodes/test_telegram_social.py +33 -37
  643. package/server/tests/nodes/test_twitter.py +59 -150
  644. package/server/tests/nodes/test_web_automation.py +21 -51
  645. package/server/tests/nodes/test_whatsapp.py +13 -19
  646. package/server/tests/nodes/test_workflow_triggers.py +16 -45
  647. package/server/tests/services/cli_agent/test_claude_session_events.py +201 -0
  648. package/server/tests/services/cli_agent/test_jsonl_watcher.py +190 -0
  649. package/server/tests/services/cli_agent/test_mcp_server.py +67 -29
  650. package/server/tests/services/cli_agent/test_providers.py +236 -47
  651. package/server/tests/services/cli_agent/test_service.py +9 -7
  652. package/server/tests/services/memory/test_jsonl.py +30 -25
  653. package/server/tests/services/test_events.py +26 -7
  654. package/server/tests/services/test_identifiers.py +122 -0
  655. package/server/tests/services/test_process_lifecycle.py +129 -0
  656. package/server/tests/services/test_supervisor.py +0 -1
  657. package/server/tests/temporal/__init__.py +0 -0
  658. package/server/tests/temporal/test_agent_workflow.py +215 -0
  659. package/server/tests/temporal/test_dispatch.py +231 -0
  660. package/server/tests/test_admin_handlers.py +394 -0
  661. package/server/tests/test_auto_skill.py +4 -2
  662. package/server/tests/test_canary_registry.py +310 -0
  663. package/server/tests/test_chat_trigger_canary_producer.py +101 -0
  664. package/server/tests/test_cloudevents_node_parameters.py +129 -0
  665. package/server/tests/test_credential_icon.py +115 -0
  666. package/server/tests/test_cron_canary.py +511 -0
  667. package/server/tests/test_deployment_canary_listener.py +692 -0
  668. package/server/tests/test_event_framework_phase_a.py +537 -0
  669. package/server/tests/test_no_raw_prints.py +131 -0
  670. package/server/tests/test_node_spec.py +196 -103
  671. package/server/tests/test_parameter_resolver.py +20 -20
  672. package/server/tests/test_plugin_contract.py +76 -49
  673. package/server/tests/test_plugin_helpers.py +0 -1
  674. package/server/tests/test_plugin_self_containment.py +40 -47
  675. package/server/tests/test_polling_trigger_workflow.py +572 -0
  676. package/server/tests/test_retry_policies.py +146 -0
  677. package/server/tests/test_service_factories.py +168 -0
  678. package/server/tests/test_shutdown_hooks.py +199 -0
  679. package/server/tests/test_social_provider_registry.py +177 -0
  680. package/server/tests/test_status_broadcasts.py +214 -63
  681. package/server/tests/test_task_trigger_canary_producer.py +131 -0
  682. package/server/tests/test_telegram_trigger_canary_producer.py +113 -0
  683. package/server/tests/test_tool_registry.py +110 -0
  684. package/server/tests/test_trigger_listener_workflow.py +365 -0
  685. package/server/tests/test_whatsapp_trigger_canary_producer.py +164 -0
  686. package/server/tests/test_workflow_ops.py +1 -3
  687. package/server/tests/test_workflow_validator.py +791 -0
  688. package/server/uv.lock +3539 -0
  689. package/client/dist/assets/index-DQ0nwhec.js +0 -257
  690. package/client/src/assets/icons/apify/index.ts +0 -19
  691. package/client/src/assets/icons/browser/index.ts +0 -17
  692. package/client/src/assets/icons/email/index.ts +0 -22
  693. package/client/src/assets/icons/google/index.ts +0 -34
  694. package/client/src/assets/icons/llm/deepseek.svg +0 -1
  695. package/client/src/assets/icons/llm/index.ts +0 -18
  696. package/client/src/assets/icons/llm/kimi.svg +0 -1
  697. package/client/src/assets/icons/llm/mistral.svg +0 -1
  698. package/client/src/assets/icons/search/index.ts +0 -28
  699. package/client/src/assets/icons/telegram/index.ts +0 -19
  700. package/machina/buildenv.py +0 -44
  701. package/machina/cli.py +0 -55
  702. package/machina/commands/__init__.py +0 -1
  703. package/machina/commands/clean.py +0 -80
  704. package/machina/commands/daemon.py +0 -150
  705. package/machina/config.py +0 -93
  706. package/machina/platform_.py +0 -37
  707. package/machina/pyproject.toml +0 -33
  708. package/server/nodes/agent/deep_agent.py +0 -103
  709. package/server/services/agents/__init__.py +0 -9
  710. package/server/services/agents/adapters.py +0 -199
  711. package/server/services/agents/constants.py +0 -10
  712. package/server/services/agents/service.py +0 -297
  713. package/server/services/cli_agent/providers/anthropic_claude.py +0 -419
  714. /package/{machina → cli}/README.md +0 -0
  715. /package/{machina → cli}/__init__.py +0 -0
  716. /package/{client/src/assets/icons/apify → server/credentials/icons}/apify.svg +0 -0
  717. /package/{client/src/assets/icons/search/brave.svg → server/credentials/icons/brave_search.svg} +0 -0
  718. /package/{client/src/assets/icons/email/read.svg → server/credentials/icons/email_himalaya.svg} +0 -0
  719. /package/{client/src/assets/icons/search → server/credentials/icons}/perplexity.svg +0 -0
  720. /package/{client/src/assets/icons/search/google.svg → server/credentials/icons/serper.svg} +0 -0
  721. /package/{client/src/assets → server/credentials}/icons/stripe.svg +0 -0
  722. /package/{client/src/assets/icons/twitter/x.svg → server/credentials/icons/twitter.svg} +0 -0
  723. /package/{client/src/assets/icons/browser/chrome.svg → server/nodes/browser/browser/icon.svg} +0 -0
  724. /package/{client/src/assets/icons/chat/chat.svg → server/nodes/chat/chat_history/icon.svg} +0 -0
  725. /package/{client/src/assets/icons/code/javascript.svg → server/nodes/code/javascript_executor/icon.svg} +0 -0
  726. /package/{client/src/assets/icons/code/python.svg → server/nodes/code/python_executor/icon.svg} +0 -0
  727. /package/{client/src/assets/icons/code/typescript.svg → server/nodes/code/typescript_executor/icon.svg} +0 -0
  728. /package/{client/src/assets/icons/email/receive.svg → server/nodes/email/email_receive/icon.svg} +0 -0
  729. /package/{client/src/assets/icons/email/send.svg → server/nodes/email/email_send/icon.svg} +0 -0
  730. /package/{client/src/assets/icons/google/calendar.svg → server/nodes/google/calendar/icon.svg} +0 -0
  731. /package/{client/src/assets/icons/google/contacts.svg → server/nodes/google/contacts/icon.svg} +0 -0
  732. /package/{client/src/assets/icons/google/drive.svg → server/nodes/google/drive/icon.svg} +0 -0
  733. /package/{client/src/assets/icons/google/gmail.svg → server/nodes/google/gmail/icon.svg} +0 -0
  734. /package/{client/src/assets/icons/google/sheets.svg → server/nodes/google/sheets/icon.svg} +0 -0
  735. /package/{client/src/assets/icons/google/tasks.svg → server/nodes/google/tasks/icon.svg} +0 -0
  736. /package/{client/src/assets/icons/search/duckduckgo.svg → server/nodes/search/duckduckgo_search/icon.svg} +0 -0
  737. /package/{client/src/assets/icons/social/social.svg → server/nodes/social/social_receive/icon.svg} +0 -0
  738. /package/{client/src/assets/icons/telegram/telegram.svg → server/nodes/telegram/icon.svg} +0 -0
  739. /package/server/nodes/utility/{team_monitor.py → team_monitor/__init__.py} +0 -0
  740. /package/{client/src/assets/icons/whatsapp/whatsapp-db.svg → server/nodes/whatsapp/icon_whatsappDb.svg} +0 -0
  741. /package/{client/src/assets/icons/whatsapp/whatsapp-receive.svg → server/nodes/whatsapp/icon_whatsappReceive.svg} +0 -0
  742. /package/{client/src/assets/icons/whatsapp/whatsapp-send.svg → server/nodes/whatsapp/icon_whatsappSend.svg} +0 -0
  743. /package/{client/src/assets/icons → server/nodes}/whatsapp/whatsapp.svg +0 -0
@@ -17,7 +17,7 @@ logger = get_logger(__name__)
17
17
 
18
18
  def _is_invite_link(value: str) -> bool:
19
19
  """Check if a channel identifier is an invite link URL."""
20
- return value.strip().startswith('http://') or value.strip().startswith('https://')
20
+ return value.strip().startswith("http://") or value.strip().startswith("https://")
21
21
 
22
22
 
23
23
  def _resolve_channel_identifier(value: str) -> Dict[str, str]:
@@ -30,8 +30,8 @@ def _resolve_channel_identifier(value: str) -> Dict[str, str]:
30
30
  return {}
31
31
  value = value.strip()
32
32
  if _is_invite_link(value):
33
- return {'invite': value}
34
- return {'jid': value}
33
+ return {"invite": value}
34
+ return {"jid": value}
35
35
 
36
36
 
37
37
  async def _resolve_to_jid(value: str, rpc_call) -> str:
@@ -48,44 +48,40 @@ async def _resolve_to_jid(value: str, rpc_call) -> str:
48
48
  if not _is_invite_link(value):
49
49
  return value
50
50
  # Resolve invite link to JID via newsletter_info
51
- data = await rpc_call('newsletter_info', {'invite': value})
51
+ data = await rpc_call("newsletter_info", {"invite": value})
52
52
  if isinstance(data, dict):
53
- result = data.get('result', data)
54
- jid = result.get('jid') or result.get('id')
53
+ result = data.get("result", data)
54
+ jid = result.get("jid") or result.get("id")
55
55
  if jid:
56
56
  return jid
57
57
  raise ValueError(f"Could not resolve invite link to channel JID: {value}")
58
58
 
59
59
 
60
60
  # Media types that can be downloaded via the media RPC
61
- MEDIA_MESSAGE_TYPES = frozenset({'image', 'video', 'audio', 'document', 'sticker'})
61
+ MEDIA_MESSAGE_TYPES = frozenset({"image", "video", "audio", "document", "sticker"})
62
62
 
63
63
 
64
64
  async def _download_single_media(message: Dict[str, Any], rpc_call) -> None:
65
65
  """Download media for a single message in-place. Mutates the message dict."""
66
- message_id = message.get('message_id') or message.get('id')
66
+ message_id = message.get("message_id") or message.get("id")
67
67
  if not message_id:
68
68
  return
69
69
  try:
70
- data = await rpc_call('media', {'message_id': message_id})
70
+ data = await rpc_call("media", {"message_id": message_id})
71
71
  if isinstance(data, dict):
72
- result = data.get('result', data)
73
- if result.get('data'):
74
- message['media_data'] = result['data']
75
- message['media_mime_type'] = result.get('mime_type', '')
72
+ result = data.get("result", data)
73
+ if result.get("data"):
74
+ message["media_data"] = result["data"]
75
+ message["media_mime_type"] = result.get("mime_type", "")
76
76
  else:
77
- message['media_error'] = result.get('error', 'No media data returned')
77
+ message["media_error"] = result.get("error", "No media data returned")
78
78
  else:
79
- message['media_error'] = 'Unexpected response format'
79
+ message["media_error"] = "Unexpected response format"
80
80
  except Exception as e:
81
- message['media_error'] = str(e)
81
+ message["media_error"] = str(e)
82
82
 
83
83
 
84
- async def _enrich_messages_with_media(
85
- messages: List[Dict[str, Any]],
86
- rpc_call,
87
- max_concurrent: int = 5
88
- ) -> List[Dict[str, Any]]:
84
+ async def _enrich_messages_with_media(messages: List[Dict[str, Any]], rpc_call, max_concurrent: int = 5) -> List[Dict[str, Any]]:
89
85
  """Download media for messages that contain media types.
90
86
 
91
87
  Uses asyncio.Semaphore for concurrency control and asyncio.gather
@@ -106,30 +102,19 @@ async def _enrich_messages_with_media(
106
102
  await _download_single_media(msg, rpc_call)
107
103
 
108
104
  # Filter to only media messages
109
- media_messages = [
110
- msg for msg in messages
111
- if msg.get('message_type', msg.get('type', '')) in MEDIA_MESSAGE_TYPES
112
- ]
105
+ media_messages = [msg for msg in messages if msg.get("message_type", msg.get("type", "")) in MEDIA_MESSAGE_TYPES]
113
106
 
114
107
  if not media_messages:
115
108
  return messages
116
109
 
117
110
  logger.info(f"Downloading media for {len(media_messages)} messages (max concurrent: {max_concurrent})")
118
111
 
119
- await asyncio.gather(
120
- *(download_with_semaphore(msg) for msg in media_messages),
121
- return_exceptions=True
122
- )
112
+ await asyncio.gather(*(download_with_semaphore(msg) for msg in media_messages), return_exceptions=True)
123
113
 
124
114
  return messages
125
115
 
126
116
 
127
- async def handle_whatsapp_send(
128
- node_id: str,
129
- node_type: str,
130
- parameters: Dict[str, Any],
131
- context: Dict[str, Any]
132
- ) -> Dict[str, Any]:
117
+ async def handle_whatsapp_send(node_id: str, node_type: str, parameters: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
133
118
  """Handle WhatsApp send message node via Go RPC service.
134
119
 
135
120
  Supports all message types: text, image, video, audio, document, sticker, location, contact
@@ -146,49 +131,51 @@ async def handle_whatsapp_send(
146
131
  Execution result dict
147
132
  """
148
133
  from nodes.whatsapp._service import handle_whatsapp_send as whatsapp_send_handler
134
+
149
135
  start_time = time.time()
150
136
 
151
137
  try:
152
138
  # Determine recipient (snake_case parameters)
153
- recipient_type = parameters.get('recipient_type', 'self')
154
- message_type = parameters.get('message_type', 'text')
139
+ recipient_type = parameters.get("recipient_type", "self")
140
+ message_type = parameters.get("message_type", "text")
155
141
 
156
142
  # Determine recipient based on type
157
- if recipient_type == 'self':
143
+ if recipient_type == "self":
158
144
  # Self will be resolved by the router using connected phone
159
- recipient = 'self'
160
- elif recipient_type == 'channel':
161
- recipient = parameters.get('channel_jid')
145
+ recipient = "self"
146
+ elif recipient_type == "channel":
147
+ recipient = parameters.get("channel_jid")
162
148
  if not recipient:
163
149
  raise ValueError("Channel JID is required")
164
150
  # Validate channel-supported message types
165
- channel_types = {'text', 'image', 'video', 'audio', 'document'}
151
+ channel_types = {"text", "image", "video", "audio", "document"}
166
152
  if message_type not in channel_types:
167
153
  raise ValueError(f"Channels only support: {', '.join(sorted(channel_types))}. Got: {message_type}")
168
- elif recipient_type == 'group':
169
- recipient = parameters.get('group_id')
154
+ elif recipient_type == "group":
155
+ recipient = parameters.get("group_id")
170
156
  if not recipient:
171
157
  raise ValueError("Group ID is required")
172
158
  else: # phone
173
- recipient = parameters.get('phone')
159
+ recipient = parameters.get("phone")
174
160
  if not recipient:
175
161
  raise ValueError("Phone number is required")
176
162
 
177
163
  # For text messages, validate message content
178
- if message_type == 'text' and not parameters.get('message'):
164
+ if message_type == "text" and not parameters.get("message"):
179
165
  raise ValueError("Message content is required for text messages")
180
166
 
181
167
  # Convert GFM markdown to WhatsApp-native formatting if enabled
182
- if message_type == 'text' and parameters.get('format_markdown', False):
168
+ if message_type == "text" and parameters.get("format_markdown", False):
183
169
  from services.markdown_formatter import to_whatsapp
184
- parameters['message'] = to_whatsapp(parameters['message'])
170
+
171
+ parameters["message"] = to_whatsapp(parameters["message"])
185
172
 
186
173
  # Call WhatsApp Go RPC service via handler - pass full params
187
174
  data = await whatsapp_send_handler(parameters)
188
175
 
189
- success = data.get('success', False)
176
+ success = data.get("success", False)
190
177
  if not success:
191
- raise Exception(data.get('error', 'Send failed'))
178
+ raise Exception(data.get("error", "Send failed"))
192
179
 
193
180
  # Build informative result based on message type (snake_case output)
194
181
  result = {
@@ -196,37 +183,37 @@ async def handle_whatsapp_send(
196
183
  "recipient": recipient,
197
184
  "recipient_type": recipient_type,
198
185
  "message_type": message_type,
199
- "timestamp": datetime.now().isoformat()
186
+ "timestamp": datetime.now().isoformat(),
200
187
  }
201
188
 
202
189
  # Add type-specific details using match statement
203
190
  match message_type:
204
- case 'text':
205
- msg_content = parameters.get('message', '')
191
+ case "text":
192
+ msg_content = parameters.get("message", "")
206
193
  result["preview"] = msg_content[:100] + "..." if len(msg_content) > 100 else msg_content
207
- case 'image' | 'video' | 'audio' | 'document' | 'sticker':
208
- media_source = parameters.get('media_source', 'base64')
194
+ case "image" | "video" | "audio" | "document" | "sticker":
195
+ media_source = parameters.get("media_source", "base64")
209
196
  result["media_source"] = media_source
210
- if parameters.get('caption'):
211
- result["caption"] = parameters.get('caption')
212
- if parameters.get('filename'):
213
- result["filename"] = parameters.get('filename')
214
- if parameters.get('mime_type'):
215
- result["mime_type"] = parameters.get('mime_type')
197
+ if parameters.get("caption"):
198
+ result["caption"] = parameters.get("caption")
199
+ if parameters.get("filename"):
200
+ result["filename"] = parameters.get("filename")
201
+ if parameters.get("mime_type"):
202
+ result["mime_type"] = parameters.get("mime_type")
216
203
  # For file uploads, include the uploaded filename
217
- file_param = parameters.get('file_path')
218
- if isinstance(file_param, dict) and file_param.get('type') == 'upload':
219
- result["uploaded_file"] = file_param.get('filename')
220
- result["mime_type"] = file_param.get('mimeType')
221
- case 'location':
204
+ file_param = parameters.get("file_path")
205
+ if isinstance(file_param, dict) and file_param.get("type") == "upload":
206
+ result["uploaded_file"] = file_param.get("filename")
207
+ result["mime_type"] = file_param.get("mimeType")
208
+ case "location":
222
209
  result["location"] = {
223
- "latitude": parameters.get('latitude'),
224
- "longitude": parameters.get('longitude'),
225
- "name": parameters.get('location_name'),
226
- "address": parameters.get('address')
210
+ "latitude": parameters.get("latitude"),
211
+ "longitude": parameters.get("longitude"),
212
+ "name": parameters.get("location_name"),
213
+ "address": parameters.get("address"),
227
214
  }
228
- case 'contact':
229
- result["contact_name"] = parameters.get('contact_name')
215
+ case "contact":
216
+ result["contact_name"] = parameters.get("contact_name")
230
217
 
231
218
  return {
232
219
  "success": success,
@@ -234,7 +221,7 @@ async def handle_whatsapp_send(
234
221
  "node_type": "whatsappSend",
235
222
  "result": result,
236
223
  "execution_time": time.time() - start_time,
237
- "timestamp": datetime.now().isoformat()
224
+ "timestamp": datetime.now().isoformat(),
238
225
  }
239
226
 
240
227
  except Exception as e:
@@ -245,16 +232,11 @@ async def handle_whatsapp_send(
245
232
  "node_type": "whatsappSend",
246
233
  "error": str(e),
247
234
  "execution_time": time.time() - start_time,
248
- "timestamp": datetime.now().isoformat()
235
+ "timestamp": datetime.now().isoformat(),
249
236
  }
250
237
 
251
238
 
252
- async def handle_whatsapp_db(
253
- node_id: str,
254
- node_type: str,
255
- parameters: Dict[str, Any],
256
- context: Dict[str, Any]
257
- ) -> Dict[str, Any]:
239
+ async def handle_whatsapp_db(node_id: str, node_type: str, parameters: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
258
240
  """Handle WhatsApp DB node - query contacts, groups, messages.
259
241
 
260
242
  Operations:
@@ -289,105 +271,103 @@ async def handle_whatsapp_db(
289
271
  Returns:
290
272
  Execution result dict
291
273
  """
292
- from nodes.whatsapp._service import (
293
- handle_whatsapp_chat_history as whatsapp_chat_history_handler,
294
- whatsapp_rpc_call
295
- )
274
+ from nodes.whatsapp._service import handle_whatsapp_chat_history as whatsapp_chat_history_handler, whatsapp_rpc_call
275
+
296
276
  start_time = time.time()
297
277
 
298
278
  try:
299
- operation = parameters.get('operation', 'chat_history')
279
+ operation = parameters.get("operation", "chat_history")
300
280
 
301
- if operation == 'chat_history':
281
+ if operation == "chat_history":
302
282
  return await _handle_chat_history(node_id, parameters, start_time, whatsapp_chat_history_handler, whatsapp_rpc_call)
303
- elif operation == 'search_groups':
283
+ elif operation == "search_groups":
304
284
  return await _handle_search_groups(node_id, parameters, start_time, whatsapp_rpc_call)
305
- elif operation == 'get_group_info':
285
+ elif operation == "get_group_info":
306
286
  return await _handle_get_group_info(node_id, parameters, start_time, whatsapp_rpc_call)
307
- elif operation == 'get_contact_info':
287
+ elif operation == "get_contact_info":
308
288
  return await _handle_get_contact_info(node_id, parameters, start_time, whatsapp_rpc_call)
309
- elif operation == 'list_contacts':
289
+ elif operation == "list_contacts":
310
290
  return await _handle_list_contacts(node_id, parameters, start_time, whatsapp_rpc_call)
311
- elif operation == 'check_contacts':
291
+ elif operation == "check_contacts":
312
292
  return await _handle_check_contacts(node_id, parameters, start_time, whatsapp_rpc_call)
313
- elif operation == 'list_channels':
293
+ elif operation == "list_channels":
314
294
  return await _handle_list_channels(node_id, parameters, start_time, whatsapp_rpc_call)
315
- elif operation == 'get_channel_info':
295
+ elif operation == "get_channel_info":
316
296
  return await _handle_get_channel_info(node_id, parameters, start_time, whatsapp_rpc_call)
317
- elif operation == 'channel_messages':
297
+ elif operation == "channel_messages":
318
298
  return await _handle_channel_messages(node_id, parameters, start_time, whatsapp_rpc_call)
319
- elif operation == 'channel_stats':
299
+ elif operation == "channel_stats":
320
300
  return await _handle_channel_stats(node_id, parameters, start_time, whatsapp_rpc_call)
321
- elif operation == 'channel_follow':
301
+ elif operation == "channel_follow":
322
302
  return await _handle_channel_follow(node_id, parameters, start_time, whatsapp_rpc_call)
323
- elif operation == 'channel_unfollow':
303
+ elif operation == "channel_unfollow":
324
304
  return await _handle_channel_unfollow(node_id, parameters, start_time, whatsapp_rpc_call)
325
- elif operation == 'channel_create':
305
+ elif operation == "channel_create":
326
306
  return await _handle_channel_create(node_id, parameters, start_time, whatsapp_rpc_call)
327
- elif operation == 'channel_mute':
307
+ elif operation == "channel_mute":
328
308
  return await _handle_channel_mute(node_id, parameters, start_time, whatsapp_rpc_call)
329
- elif operation == 'channel_mark_viewed':
309
+ elif operation == "channel_mark_viewed":
330
310
  return await _handle_channel_mark_viewed(node_id, parameters, start_time, whatsapp_rpc_call)
331
- elif operation == 'newsletter_react':
311
+ elif operation == "newsletter_react":
332
312
  return await _handle_newsletter_react(node_id, parameters, start_time, whatsapp_rpc_call)
333
- elif operation == 'newsletter_live_updates':
313
+ elif operation == "newsletter_live_updates":
334
314
  return await _handle_newsletter_live_updates(node_id, parameters, start_time, whatsapp_rpc_call)
335
- elif operation == 'contact_profile_pic':
315
+ elif operation == "contact_profile_pic":
336
316
  return await _handle_contact_profile_pic(node_id, parameters, start_time, whatsapp_rpc_call)
337
317
  else:
338
318
  raise ValueError(f"Unknown operation: {operation}")
339
319
 
340
320
  except Exception as e:
341
- logger.error("WhatsApp DB failed", node_id=node_id, operation=parameters.get('operation'), error=str(e))
321
+ logger.error("WhatsApp DB failed", node_id=node_id, operation=parameters.get("operation"), error=str(e))
342
322
  return {
343
323
  "success": False,
344
324
  "node_id": node_id,
345
325
  "node_type": "whatsappDb",
346
326
  "error": str(e),
347
327
  "execution_time": time.time() - start_time,
348
- "timestamp": datetime.now().isoformat()
328
+ "timestamp": datetime.now().isoformat(),
349
329
  }
350
330
 
351
331
 
352
332
  async def _handle_chat_history(node_id: str, parameters: Dict[str, Any], start_time: float, handler, rpc_call=None) -> Dict[str, Any]:
353
333
  """Handle chat_history operation."""
354
- chat_type = parameters.get('chat_type', 'individual')
334
+ chat_type = parameters.get("chat_type", "individual")
355
335
  rpc_params: Dict[str, Any] = {}
356
336
 
357
- if chat_type == 'individual':
358
- phone = parameters.get('phone')
337
+ if chat_type == "individual":
338
+ phone = parameters.get("phone")
359
339
  if not phone:
360
340
  raise ValueError("Phone number is required for individual chats")
361
- rpc_params['phone'] = phone
341
+ rpc_params["phone"] = phone
362
342
  else:
363
- group_id = parameters.get('group_id')
343
+ group_id = parameters.get("group_id")
364
344
  if not group_id:
365
345
  raise ValueError("Group ID is required for group chats")
366
- rpc_params['group_id'] = group_id
346
+ rpc_params["group_id"] = group_id
367
347
 
368
- group_filter = parameters.get('group_filter', 'all')
369
- if group_filter == 'contact':
370
- sender_phone = parameters.get('sender_phone')
348
+ group_filter = parameters.get("group_filter", "all")
349
+ if group_filter == "contact":
350
+ sender_phone = parameters.get("sender_phone")
371
351
  if sender_phone:
372
- rpc_params['sender_phone'] = sender_phone
352
+ rpc_params["sender_phone"] = sender_phone
373
353
 
374
- message_filter = parameters.get('message_filter', 'all')
375
- rpc_params['text_only'] = message_filter == 'text_only'
376
- rpc_params['limit'] = parameters.get('limit', 50)
377
- rpc_params['offset'] = parameters.get('offset', 0)
354
+ message_filter = parameters.get("message_filter", "all")
355
+ rpc_params["text_only"] = message_filter == "text_only"
356
+ rpc_params["limit"] = parameters.get("limit", 50)
357
+ rpc_params["offset"] = parameters.get("offset", 0)
378
358
 
379
359
  data = await handler(rpc_params)
380
360
 
381
- if not data.get('success', False):
382
- raise Exception(data.get('error', 'Failed to retrieve chat history'))
361
+ if not data.get("success", False):
362
+ raise Exception(data.get("error", "Failed to retrieve chat history"))
383
363
 
384
- messages = data.get('messages', [])
385
- base_offset = rpc_params.get('offset', 0)
364
+ messages = data.get("messages", [])
365
+ base_offset = rpc_params.get("offset", 0)
386
366
  for i, msg in enumerate(messages):
387
- msg['index'] = base_offset + i + 1
367
+ msg["index"] = base_offset + i + 1
388
368
 
389
369
  # Enrich with media data if requested
390
- if parameters.get('include_media_data') and rpc_call and messages:
370
+ if parameters.get("include_media_data") and rpc_call and messages:
391
371
  await _enrich_messages_with_media(messages, rpc_call)
392
372
 
393
373
  return {
@@ -397,41 +377,38 @@ async def _handle_chat_history(node_id: str, parameters: Dict[str, Any], start_t
397
377
  "result": {
398
378
  "operation": "chat_history",
399
379
  "messages": messages,
400
- "total": data.get('total', 0),
401
- "has_more": data.get('has_more', False),
380
+ "total": data.get("total", 0),
381
+ "has_more": data.get("has_more", False),
402
382
  "count": len(messages),
403
383
  "chat_type": chat_type,
404
- "timestamp": datetime.now().isoformat()
384
+ "timestamp": datetime.now().isoformat(),
405
385
  },
406
386
  "execution_time": time.time() - start_time,
407
- "timestamp": datetime.now().isoformat()
387
+ "timestamp": datetime.now().isoformat(),
408
388
  }
409
389
 
410
390
 
411
391
  async def _handle_search_groups(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
412
392
  """Handle search_groups operation."""
413
- query = parameters.get('query', '')
414
- limit = parameters.get('limit', 20) # Default limit to prevent context overflow
415
- data = await rpc_call('groups', {})
393
+ query = parameters.get("query", "")
394
+ limit = parameters.get("limit", 20) # Default limit to prevent context overflow
395
+ data = await rpc_call("groups", {})
416
396
 
417
- if not data.get('success', True):
418
- raise Exception(data.get('error', 'Failed to get groups'))
397
+ if not data.get("success", True):
398
+ raise Exception(data.get("error", "Failed to get groups"))
419
399
 
420
- groups = data if isinstance(data, list) else data.get('result', [])
400
+ groups = data if isinstance(data, list) else data.get("result", [])
421
401
 
422
402
  # Filter by query if provided
423
403
  if query:
424
404
  query_lower = query.lower()
425
- groups = [g for g in groups if query_lower in g.get('name', '').lower()]
405
+ groups = [g for g in groups if query_lower in g.get("name", "").lower()]
426
406
 
427
407
  total_found = len(groups)
428
408
 
429
409
  # Apply limit to prevent context overflow (51 groups * ~4KB = 200KB+ tokens)
430
410
  # Only return essential fields: jid and name
431
- groups_limited = [
432
- {"jid": g.get("jid", ""), "name": g.get("name", "")}
433
- for g in groups[:limit]
434
- ]
411
+ groups_limited = [{"jid": g.get("jid", ""), "name": g.get("name", "")} for g in groups[:limit]]
435
412
 
436
413
  return {
437
414
  "success": True,
@@ -444,31 +421,33 @@ async def _handle_search_groups(node_id: str, parameters: Dict[str, Any], start_
444
421
  "returned": len(groups_limited),
445
422
  "has_more": total_found > limit,
446
423
  "query": query,
447
- "hint": f"Showing {len(groups_limited)} of {total_found} groups. Use a more specific query or get_group_info for details." if total_found > limit else None,
448
- "timestamp": datetime.now().isoformat()
424
+ "hint": f"Showing {len(groups_limited)} of {total_found} groups. Use a more specific query or get_group_info for details."
425
+ if total_found > limit
426
+ else None,
427
+ "timestamp": datetime.now().isoformat(),
449
428
  },
450
429
  "execution_time": time.time() - start_time,
451
- "timestamp": datetime.now().isoformat()
430
+ "timestamp": datetime.now().isoformat(),
452
431
  }
453
432
 
454
433
 
455
434
  async def _handle_get_group_info(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
456
435
  """Handle get_group_info operation."""
457
- group_id = parameters.get('group_id_for_info') or parameters.get('group_id')
436
+ group_id = parameters.get("group_id_for_info") or parameters.get("group_id")
458
437
  if not group_id:
459
438
  raise ValueError("Group ID is required")
460
439
 
461
- participant_limit = parameters.get('participant_limit', 50) # Limit participants to prevent overflow
440
+ participant_limit = parameters.get("participant_limit", 50) # Limit participants to prevent overflow
462
441
 
463
- data = await rpc_call('group_info', {'group_id': group_id})
442
+ data = await rpc_call("group_info", {"group_id": group_id})
464
443
 
465
- if not data.get('success', True) if isinstance(data, dict) else True:
466
- raise Exception(data.get('error', 'Failed to get group info') if isinstance(data, dict) else 'Failed')
444
+ if not data.get("success", True) if isinstance(data, dict) else True:
445
+ raise Exception(data.get("error", "Failed to get group info") if isinstance(data, dict) else "Failed")
467
446
 
468
- result = data if not isinstance(data, dict) or 'result' not in data else data.get('result', data)
447
+ result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
469
448
 
470
449
  # Limit participants and return only essential fields
471
- participants = result.get('participants', [])
450
+ participants = result.get("participants", [])
472
451
  total_participants = len(participants)
473
452
  participants_limited = [
474
453
  {"phone": p.get("phone", ""), "name": p.get("name", ""), "is_admin": p.get("is_admin", False)}
@@ -491,63 +470,52 @@ async def _handle_get_group_info(node_id: str, parameters: Dict[str, Any], start
491
470
  "success": True,
492
471
  "node_id": node_id,
493
472
  "node_type": "whatsappDb",
494
- "result": {
495
- "operation": "get_group_info",
496
- **limited_result,
497
- "timestamp": datetime.now().isoformat()
498
- },
473
+ "result": {"operation": "get_group_info", **limited_result, "timestamp": datetime.now().isoformat()},
499
474
  "execution_time": time.time() - start_time,
500
- "timestamp": datetime.now().isoformat()
475
+ "timestamp": datetime.now().isoformat(),
501
476
  }
502
477
 
503
478
 
504
479
  async def _handle_get_contact_info(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
505
480
  """Handle get_contact_info operation."""
506
- phone = parameters.get('contact_phone') or parameters.get('phone')
481
+ phone = parameters.get("contact_phone") or parameters.get("phone")
507
482
  if not phone:
508
483
  raise ValueError("Phone number is required")
509
484
 
510
- data = await rpc_call('contact_info', {'phone': phone})
485
+ data = await rpc_call("contact_info", {"phone": phone})
511
486
 
512
- if isinstance(data, dict) and not data.get('success', True):
513
- raise Exception(data.get('error', 'Failed to get contact info'))
487
+ if isinstance(data, dict) and not data.get("success", True):
488
+ raise Exception(data.get("error", "Failed to get contact info"))
514
489
 
515
- result = data if not isinstance(data, dict) or 'result' not in data else data.get('result', data)
490
+ result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
516
491
 
517
492
  return {
518
493
  "success": True,
519
494
  "node_id": node_id,
520
495
  "node_type": "whatsappDb",
521
- "result": {
522
- "operation": "get_contact_info",
523
- **result,
524
- "timestamp": datetime.now().isoformat()
525
- },
496
+ "result": {"operation": "get_contact_info", **result, "timestamp": datetime.now().isoformat()},
526
497
  "execution_time": time.time() - start_time,
527
- "timestamp": datetime.now().isoformat()
498
+ "timestamp": datetime.now().isoformat(),
528
499
  }
529
500
 
530
501
 
531
502
  async def _handle_list_contacts(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
532
503
  """Handle list_contacts operation."""
533
- query = parameters.get('query', '')
534
- limit = parameters.get('limit', 50) # Default limit to prevent context overflow
504
+ query = parameters.get("query", "")
505
+ limit = parameters.get("limit", 50) # Default limit to prevent context overflow
535
506
 
536
- data = await rpc_call('contacts', {'query': query})
507
+ data = await rpc_call("contacts", {"query": query})
537
508
 
538
- if isinstance(data, dict) and not data.get('success', True):
539
- raise Exception(data.get('error', 'Failed to list contacts'))
509
+ if isinstance(data, dict) and not data.get("success", True):
510
+ raise Exception(data.get("error", "Failed to list contacts"))
540
511
 
541
- result = data if not isinstance(data, dict) or 'result' not in data else data.get('result', data)
542
- contacts = result.get('contacts', []) if isinstance(result, dict) else result
512
+ result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
513
+ contacts = result.get("contacts", []) if isinstance(result, dict) else result
543
514
 
544
515
  total_found = len(contacts)
545
516
 
546
517
  # Apply limit and return only essential fields: phone, name, jid
547
- contacts_limited = [
548
- {"phone": c.get("phone", ""), "name": c.get("name", ""), "jid": c.get("jid", "")}
549
- for c in contacts[:limit]
550
- ]
518
+ contacts_limited = [{"phone": c.get("phone", ""), "name": c.get("name", ""), "jid": c.get("jid", "")} for c in contacts[:limit]]
551
519
 
552
520
  return {
553
521
  "success": True,
@@ -560,64 +528,61 @@ async def _handle_list_contacts(node_id: str, parameters: Dict[str, Any], start_
560
528
  "returned": len(contacts_limited),
561
529
  "has_more": total_found > limit,
562
530
  "query": query,
563
- "hint": f"Showing {len(contacts_limited)} of {total_found} contacts. Use a more specific query to narrow results." if total_found > limit else None,
564
- "timestamp": datetime.now().isoformat()
531
+ "hint": f"Showing {len(contacts_limited)} of {total_found} contacts. Use a more specific query to narrow results."
532
+ if total_found > limit
533
+ else None,
534
+ "timestamp": datetime.now().isoformat(),
565
535
  },
566
536
  "execution_time": time.time() - start_time,
567
- "timestamp": datetime.now().isoformat()
537
+ "timestamp": datetime.now().isoformat(),
568
538
  }
569
539
 
570
540
 
571
541
  async def _handle_check_contacts(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
572
542
  """Handle check_contacts operation."""
573
- phones_str = parameters.get('phones', '')
543
+ phones_str = parameters.get("phones", "")
574
544
  if not phones_str:
575
545
  raise ValueError("Phone numbers are required")
576
546
 
577
547
  # Parse comma-separated phones
578
- phones = [p.strip() for p in phones_str.split(',') if p.strip()]
548
+ phones = [p.strip() for p in phones_str.split(",") if p.strip()]
579
549
  if not phones:
580
550
  raise ValueError("At least one phone number is required")
581
551
 
582
- data = await rpc_call('contact_check', {'phones': phones})
552
+ data = await rpc_call("contact_check", {"phones": phones})
583
553
 
584
- if isinstance(data, dict) and not data.get('success', True):
585
- raise Exception(data.get('error', 'Failed to check contacts'))
554
+ if isinstance(data, dict) and not data.get("success", True):
555
+ raise Exception(data.get("error", "Failed to check contacts"))
586
556
 
587
- results = data if isinstance(data, list) else data.get('result', [])
557
+ results = data if isinstance(data, list) else data.get("result", [])
588
558
 
589
559
  return {
590
560
  "success": True,
591
561
  "node_id": node_id,
592
562
  "node_type": "whatsappDb",
593
- "result": {
594
- "operation": "check_contacts",
595
- "results": results,
596
- "total": len(results),
597
- "timestamp": datetime.now().isoformat()
598
- },
563
+ "result": {"operation": "check_contacts", "results": results, "total": len(results), "timestamp": datetime.now().isoformat()},
599
564
  "execution_time": time.time() - start_time,
600
- "timestamp": datetime.now().isoformat()
565
+ "timestamp": datetime.now().isoformat(),
601
566
  }
602
567
 
603
568
 
604
569
  async def _handle_contact_profile_pic(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
605
570
  """Handle contact_profile_pic operation - get profile picture for a contact/group."""
606
- jid = parameters.get('profile_pic_jid') or parameters.get('phone')
571
+ jid = parameters.get("profile_pic_jid") or parameters.get("phone")
607
572
  if not jid:
608
573
  raise ValueError("JID or phone number is required")
609
574
 
610
- rpc_params: Dict[str, Any] = {'jid': jid}
611
- preview = parameters.get('preview', False)
575
+ rpc_params: Dict[str, Any] = {"jid": jid}
576
+ preview = parameters.get("preview", False)
612
577
  if preview:
613
- rpc_params['preview'] = True
578
+ rpc_params["preview"] = True
614
579
 
615
- data = await rpc_call('contact_profile_pic', rpc_params)
580
+ data = await rpc_call("contact_profile_pic", rpc_params)
616
581
 
617
- if isinstance(data, dict) and not data.get('success', True):
618
- raise Exception(data.get('error', 'Failed to get profile picture'))
582
+ if isinstance(data, dict) and not data.get("success", True):
583
+ raise Exception(data.get("error", "Failed to get profile picture"))
619
584
 
620
- result = data if not isinstance(data, dict) or 'result' not in data else data.get('result', data)
585
+ result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
621
586
 
622
587
  return {
623
588
  "success": True,
@@ -626,34 +591,33 @@ async def _handle_contact_profile_pic(node_id: str, parameters: Dict[str, Any],
626
591
  "result": {
627
592
  "operation": "contact_profile_pic",
628
593
  **(result if isinstance(result, dict) else {"url": result}),
629
- "timestamp": datetime.now().isoformat()
594
+ "timestamp": datetime.now().isoformat(),
630
595
  },
631
596
  "execution_time": time.time() - start_time,
632
- "timestamp": datetime.now().isoformat()
597
+ "timestamp": datetime.now().isoformat(),
633
598
  }
634
599
 
635
600
 
636
601
  async def _handle_list_channels(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
637
602
  """Handle list_channels operation - list subscribed newsletter channels."""
638
- refresh = parameters.get('refresh', False)
639
- limit = parameters.get('limit', 20)
603
+ refresh = parameters.get("refresh", False)
604
+ limit = parameters.get("limit", 20)
640
605
 
641
606
  rpc_params: Dict[str, Any] = {}
642
607
  if refresh:
643
- rpc_params['refresh'] = True
608
+ rpc_params["refresh"] = True
644
609
 
645
- data = await rpc_call('newsletters', rpc_params)
610
+ data = await rpc_call("newsletters", rpc_params)
646
611
 
647
- if isinstance(data, dict) and not data.get('success', True):
648
- raise Exception(data.get('error', 'Failed to list channels'))
612
+ if isinstance(data, dict) and not data.get("success", True):
613
+ raise Exception(data.get("error", "Failed to list channels"))
649
614
 
650
- channels = data if isinstance(data, list) else data.get('result', [])
615
+ channels = data if isinstance(data, list) else data.get("result", [])
651
616
  total_found = len(channels)
652
617
 
653
618
  # Return essential fields only
654
619
  channels_limited = [
655
- {"jid": c.get("jid", ""), "name": c.get("name", ""), "subscriber_count": c.get("subscriber_count", 0)}
656
- for c in channels[:limit]
620
+ {"jid": c.get("jid", ""), "name": c.get("name", ""), "subscriber_count": c.get("subscriber_count", 0)} for c in channels[:limit]
657
621
  ]
658
622
 
659
623
  return {
@@ -667,41 +631,37 @@ async def _handle_list_channels(node_id: str, parameters: Dict[str, Any], start_
667
631
  "returned": len(channels_limited),
668
632
  "has_more": total_found > limit,
669
633
  "hint": f"Showing {len(channels_limited)} of {total_found} channels." if total_found > limit else None,
670
- "timestamp": datetime.now().isoformat()
634
+ "timestamp": datetime.now().isoformat(),
671
635
  },
672
636
  "execution_time": time.time() - start_time,
673
- "timestamp": datetime.now().isoformat()
637
+ "timestamp": datetime.now().isoformat(),
674
638
  }
675
639
 
676
640
 
677
641
  async def _handle_get_channel_info(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
678
642
  """Handle get_channel_info operation - get channel details."""
679
- channel_jid = parameters.get('channel_jid')
643
+ channel_jid = parameters.get("channel_jid")
680
644
  if not channel_jid:
681
645
  raise ValueError("Channel JID is required")
682
646
 
683
647
  rpc_params: Dict[str, Any] = _resolve_channel_identifier(channel_jid)
684
- if parameters.get('refresh'):
685
- rpc_params['refresh'] = True
648
+ if parameters.get("refresh"):
649
+ rpc_params["refresh"] = True
686
650
 
687
- data = await rpc_call('newsletter_info', rpc_params)
651
+ data = await rpc_call("newsletter_info", rpc_params)
688
652
 
689
- if isinstance(data, dict) and not data.get('success', True):
690
- raise Exception(data.get('error', 'Failed to get channel info'))
653
+ if isinstance(data, dict) and not data.get("success", True):
654
+ raise Exception(data.get("error", "Failed to get channel info"))
691
655
 
692
- result = data if not isinstance(data, dict) or 'result' not in data else data.get('result', data)
656
+ result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
693
657
 
694
658
  return {
695
659
  "success": True,
696
660
  "node_id": node_id,
697
661
  "node_type": "whatsappDb",
698
- "result": {
699
- "operation": "get_channel_info",
700
- **result,
701
- "timestamp": datetime.now().isoformat()
702
- },
662
+ "result": {"operation": "get_channel_info", **result, "timestamp": datetime.now().isoformat()},
703
663
  "execution_time": time.time() - start_time,
704
- "timestamp": datetime.now().isoformat()
664
+ "timestamp": datetime.now().isoformat(),
705
665
  }
706
666
 
707
667
 
@@ -711,53 +671,53 @@ async def _handle_channel_messages(node_id: str, parameters: Dict[str, Any], sta
711
671
  Schema params (newsletter_messages RPC):
712
672
  jid (required), count, offset, before, since, until, media_type, search, refresh
713
673
  """
714
- channel_jid = parameters.get('channel_jid')
674
+ channel_jid = parameters.get("channel_jid")
715
675
  jid = await _resolve_to_jid(channel_jid, rpc_call)
716
676
 
717
- count = parameters.get('channel_count', 20)
718
- rpc_params: Dict[str, Any] = {'jid': jid, 'count': count}
677
+ count = parameters.get("channel_count", 20)
678
+ rpc_params: Dict[str, Any] = {"jid": jid, "count": count}
719
679
 
720
- before_server_id = parameters.get('before_server_id')
680
+ before_server_id = parameters.get("before_server_id")
721
681
  if before_server_id:
722
- rpc_params['before'] = int(before_server_id)
682
+ rpc_params["before"] = int(before_server_id)
723
683
 
724
684
  # Pagination offset
725
- msg_offset = parameters.get('message_offset')
685
+ msg_offset = parameters.get("message_offset")
726
686
  if msg_offset:
727
- rpc_params['offset'] = int(msg_offset)
687
+ rpc_params["offset"] = int(msg_offset)
728
688
 
729
689
  # Date range filters (unix timestamps as strings)
730
- since = parameters.get('since')
690
+ since = parameters.get("since")
731
691
  if since:
732
- rpc_params['since'] = str(since)
692
+ rpc_params["since"] = str(since)
733
693
 
734
- until = parameters.get('until')
694
+ until = parameters.get("until")
735
695
  if until:
736
- rpc_params['until'] = str(until)
696
+ rpc_params["until"] = str(until)
737
697
 
738
698
  # Media type filter
739
- media_type = parameters.get('media_type')
740
- if media_type and media_type != 'all':
741
- rpc_params['media_type'] = media_type
699
+ media_type = parameters.get("media_type")
700
+ if media_type and media_type != "all":
701
+ rpc_params["media_type"] = media_type
742
702
 
743
703
  # Text search
744
- search = parameters.get('search')
704
+ search = parameters.get("search")
745
705
  if search:
746
- rpc_params['search'] = search
706
+ rpc_params["search"] = search
747
707
 
748
708
  # Force refresh bypassing cache
749
- if parameters.get('refresh'):
750
- rpc_params['refresh'] = True
709
+ if parameters.get("refresh"):
710
+ rpc_params["refresh"] = True
751
711
 
752
- data = await rpc_call('newsletter_messages', rpc_params)
712
+ data = await rpc_call("newsletter_messages", rpc_params)
753
713
 
754
- if isinstance(data, dict) and not data.get('success', True):
755
- raise Exception(data.get('error', 'Failed to get channel messages'))
714
+ if isinstance(data, dict) and not data.get("success", True):
715
+ raise Exception(data.get("error", "Failed to get channel messages"))
756
716
 
757
- messages = data if isinstance(data, list) else data.get('result', [])
717
+ messages = data if isinstance(data, list) else data.get("result", [])
758
718
 
759
719
  # Enrich with media data if requested
760
- if parameters.get('include_media_data') and messages:
720
+ if parameters.get("include_media_data") and messages:
761
721
  await _enrich_messages_with_media(messages, rpc_call)
762
722
 
763
723
  return {
@@ -769,53 +729,48 @@ async def _handle_channel_messages(node_id: str, parameters: Dict[str, Any], sta
769
729
  "messages": messages,
770
730
  "count": len(messages),
771
731
  "channel_jid": channel_jid,
772
- "timestamp": datetime.now().isoformat()
732
+ "timestamp": datetime.now().isoformat(),
773
733
  },
774
734
  "execution_time": time.time() - start_time,
775
- "timestamp": datetime.now().isoformat()
735
+ "timestamp": datetime.now().isoformat(),
776
736
  }
777
737
 
778
738
 
779
739
  async def _handle_channel_stats(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
780
740
  """Handle channel_stats operation - get channel subscriber/view stats."""
781
- channel_jid = parameters.get('channel_jid')
741
+ channel_jid = parameters.get("channel_jid")
782
742
  if not channel_jid:
783
743
  raise ValueError("Channel JID is required")
784
744
 
785
- count = parameters.get('channel_count', 10)
786
- rpc_params: Dict[str, Any] = {**_resolve_channel_identifier(channel_jid), 'count': count}
745
+ count = parameters.get("channel_count", 10)
746
+ rpc_params: Dict[str, Any] = {**_resolve_channel_identifier(channel_jid), "count": count}
787
747
 
788
- data = await rpc_call('newsletter_stats', rpc_params)
748
+ data = await rpc_call("newsletter_stats", rpc_params)
789
749
 
790
- if isinstance(data, dict) and not data.get('success', True):
791
- raise Exception(data.get('error', 'Failed to get channel stats'))
750
+ if isinstance(data, dict) and not data.get("success", True):
751
+ raise Exception(data.get("error", "Failed to get channel stats"))
792
752
 
793
- result = data if not isinstance(data, dict) or 'result' not in data else data.get('result', data)
753
+ result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
794
754
 
795
755
  return {
796
756
  "success": True,
797
757
  "node_id": node_id,
798
758
  "node_type": "whatsappDb",
799
- "result": {
800
- "operation": "channel_stats",
801
- **result,
802
- "channel_jid": channel_jid,
803
- "timestamp": datetime.now().isoformat()
804
- },
759
+ "result": {"operation": "channel_stats", **result, "channel_jid": channel_jid, "timestamp": datetime.now().isoformat()},
805
760
  "execution_time": time.time() - start_time,
806
- "timestamp": datetime.now().isoformat()
761
+ "timestamp": datetime.now().isoformat(),
807
762
  }
808
763
 
809
764
 
810
765
  async def _handle_channel_follow(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
811
766
  """Handle channel_follow operation - follow/subscribe to a channel."""
812
- channel_jid = parameters.get('channel_jid')
767
+ channel_jid = parameters.get("channel_jid")
813
768
  jid = await _resolve_to_jid(channel_jid, rpc_call)
814
769
 
815
- data = await rpc_call('newsletter_follow', {'jid': jid})
770
+ data = await rpc_call("newsletter_follow", {"jid": jid})
816
771
 
817
- if isinstance(data, dict) and not data.get('success', True):
818
- raise Exception(data.get('error', 'Failed to follow channel'))
772
+ if isinstance(data, dict) and not data.get("success", True):
773
+ raise Exception(data.get("error", "Failed to follow channel"))
819
774
 
820
775
  return {
821
776
  "success": True,
@@ -825,22 +780,22 @@ async def _handle_channel_follow(node_id: str, parameters: Dict[str, Any], start
825
780
  "operation": "channel_follow",
826
781
  "channel_jid": channel_jid,
827
782
  "status": "followed",
828
- "timestamp": datetime.now().isoformat()
783
+ "timestamp": datetime.now().isoformat(),
829
784
  },
830
785
  "execution_time": time.time() - start_time,
831
- "timestamp": datetime.now().isoformat()
786
+ "timestamp": datetime.now().isoformat(),
832
787
  }
833
788
 
834
789
 
835
790
  async def _handle_channel_unfollow(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
836
791
  """Handle channel_unfollow operation - unfollow/unsubscribe from a channel."""
837
- channel_jid = parameters.get('channel_jid')
792
+ channel_jid = parameters.get("channel_jid")
838
793
  jid = await _resolve_to_jid(channel_jid, rpc_call)
839
794
 
840
- data = await rpc_call('newsletter_unfollow', {'jid': jid})
795
+ data = await rpc_call("newsletter_unfollow", {"jid": jid})
841
796
 
842
- if isinstance(data, dict) and not data.get('success', True):
843
- raise Exception(data.get('error', 'Failed to unfollow channel'))
797
+ if isinstance(data, dict) and not data.get("success", True):
798
+ raise Exception(data.get("error", "Failed to unfollow channel"))
844
799
 
845
800
  return {
846
801
  "success": True,
@@ -850,60 +805,56 @@ async def _handle_channel_unfollow(node_id: str, parameters: Dict[str, Any], sta
850
805
  "operation": "channel_unfollow",
851
806
  "channel_jid": channel_jid,
852
807
  "status": "unfollowed",
853
- "timestamp": datetime.now().isoformat()
808
+ "timestamp": datetime.now().isoformat(),
854
809
  },
855
810
  "execution_time": time.time() - start_time,
856
- "timestamp": datetime.now().isoformat()
811
+ "timestamp": datetime.now().isoformat(),
857
812
  }
858
813
 
859
814
 
860
815
  async def _handle_channel_create(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
861
816
  """Handle channel_create operation - create a new newsletter channel."""
862
- channel_name = parameters.get('channel_name')
817
+ channel_name = parameters.get("channel_name")
863
818
  if not channel_name:
864
819
  raise ValueError("Channel name is required")
865
820
 
866
- rpc_params: Dict[str, Any] = {'name': channel_name}
867
- description = parameters.get('channel_description')
821
+ rpc_params: Dict[str, Any] = {"name": channel_name}
822
+ description = parameters.get("channel_description")
868
823
  if description:
869
- rpc_params['description'] = description
870
- picture = parameters.get('picture')
824
+ rpc_params["description"] = description
825
+ picture = parameters.get("picture")
871
826
  if picture:
872
- rpc_params['picture'] = picture
827
+ rpc_params["picture"] = picture
873
828
 
874
- data = await rpc_call('newsletter_create', rpc_params)
829
+ data = await rpc_call("newsletter_create", rpc_params)
875
830
 
876
- if isinstance(data, dict) and not data.get('success', True):
877
- raise Exception(data.get('error', 'Failed to create channel'))
831
+ if isinstance(data, dict) and not data.get("success", True):
832
+ raise Exception(data.get("error", "Failed to create channel"))
878
833
 
879
- result = data if not isinstance(data, dict) or 'result' not in data else data.get('result', data)
834
+ result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
880
835
 
881
836
  return {
882
837
  "success": True,
883
838
  "node_id": node_id,
884
839
  "node_type": "whatsappDb",
885
- "result": {
886
- "operation": "channel_create",
887
- **result,
888
- "timestamp": datetime.now().isoformat()
889
- },
840
+ "result": {"operation": "channel_create", **result, "timestamp": datetime.now().isoformat()},
890
841
  "execution_time": time.time() - start_time,
891
- "timestamp": datetime.now().isoformat()
842
+ "timestamp": datetime.now().isoformat(),
892
843
  }
893
844
 
894
845
 
895
846
  async def _handle_channel_mute(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
896
847
  """Handle channel_mute operation - mute/unmute a newsletter channel."""
897
- channel_jid = parameters.get('channel_jid')
848
+ channel_jid = parameters.get("channel_jid")
898
849
  jid = await _resolve_to_jid(channel_jid, rpc_call)
899
850
 
900
- mute = parameters.get('mute', True)
901
- rpc_params = {'jid': jid, 'mute': bool(mute)}
851
+ mute = parameters.get("mute", True)
852
+ rpc_params = {"jid": jid, "mute": bool(mute)}
902
853
 
903
- data = await rpc_call('newsletter_mute', rpc_params)
854
+ data = await rpc_call("newsletter_mute", rpc_params)
904
855
 
905
- if isinstance(data, dict) and not data.get('success', True):
906
- raise Exception(data.get('error', 'Failed to mute/unmute channel'))
856
+ if isinstance(data, dict) and not data.get("success", True):
857
+ raise Exception(data.get("error", "Failed to mute/unmute channel"))
907
858
 
908
859
  return {
909
860
  "success": True,
@@ -914,32 +865,32 @@ async def _handle_channel_mute(node_id: str, parameters: Dict[str, Any], start_t
914
865
  "channel_jid": channel_jid,
915
866
  "muted": mute,
916
867
  "status": "muted" if mute else "unmuted",
917
- "timestamp": datetime.now().isoformat()
868
+ "timestamp": datetime.now().isoformat(),
918
869
  },
919
870
  "execution_time": time.time() - start_time,
920
- "timestamp": datetime.now().isoformat()
871
+ "timestamp": datetime.now().isoformat(),
921
872
  }
922
873
 
923
874
 
924
875
  async def _handle_channel_mark_viewed(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
925
876
  """Handle channel_mark_viewed operation - mark channel messages as viewed."""
926
- channel_jid = parameters.get('channel_jid')
877
+ channel_jid = parameters.get("channel_jid")
927
878
  jid = await _resolve_to_jid(channel_jid, rpc_call)
928
879
 
929
- server_ids_raw = parameters.get('server_ids', '')
880
+ server_ids_raw = parameters.get("server_ids", "")
930
881
  if not server_ids_raw:
931
882
  raise ValueError("server_ids is required (comma-separated message IDs)")
932
883
 
933
- server_ids = [int(sid.strip()) for sid in str(server_ids_raw).split(',') if sid.strip()]
884
+ server_ids = [int(sid.strip()) for sid in str(server_ids_raw).split(",") if sid.strip()]
934
885
  if not server_ids:
935
886
  raise ValueError("At least one server_id is required")
936
887
 
937
- rpc_params = {'jid': jid, 'server_ids': server_ids}
888
+ rpc_params = {"jid": jid, "server_ids": server_ids}
938
889
 
939
- data = await rpc_call('newsletter_mark_viewed', rpc_params)
890
+ data = await rpc_call("newsletter_mark_viewed", rpc_params)
940
891
 
941
- if isinstance(data, dict) and not data.get('success', True):
942
- raise Exception(data.get('error', 'Failed to mark channel as viewed'))
892
+ if isinstance(data, dict) and not data.get("success", True):
893
+ raise Exception(data.get("error", "Failed to mark channel as viewed"))
943
894
 
944
895
  return {
945
896
  "success": True,
@@ -948,36 +899,32 @@ async def _handle_channel_mark_viewed(node_id: str, parameters: Dict[str, Any],
948
899
  "result": {
949
900
  "operation": "channel_mark_viewed",
950
901
  "channel_jid": channel_jid,
951
- "server_ids": ','.join(str(s) for s in server_ids),
902
+ "server_ids": ",".join(str(s) for s in server_ids),
952
903
  "status": "marked_viewed",
953
- "timestamp": datetime.now().isoformat()
904
+ "timestamp": datetime.now().isoformat(),
954
905
  },
955
906
  "execution_time": time.time() - start_time,
956
- "timestamp": datetime.now().isoformat()
907
+ "timestamp": datetime.now().isoformat(),
957
908
  }
958
909
 
959
910
 
960
911
  async def _handle_newsletter_react(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
961
912
  """Handle newsletter_react operation - react to a channel message."""
962
- channel_jid = parameters.get('channel_jid')
913
+ channel_jid = parameters.get("channel_jid")
963
914
  jid = await _resolve_to_jid(channel_jid, rpc_call)
964
915
 
965
- server_id = parameters.get('react_server_id')
916
+ server_id = parameters.get("react_server_id")
966
917
  if not server_id:
967
918
  raise ValueError("Message server ID is required")
968
919
 
969
- reaction = parameters.get('reaction', '')
920
+ reaction = parameters.get("reaction", "")
970
921
 
971
- rpc_params: Dict[str, Any] = {
972
- 'jid': jid,
973
- 'server_id': int(server_id),
974
- 'reaction': reaction
975
- }
922
+ rpc_params: Dict[str, Any] = {"jid": jid, "server_id": int(server_id), "reaction": reaction}
976
923
 
977
- data = await rpc_call('newsletter_react', rpc_params)
924
+ data = await rpc_call("newsletter_react", rpc_params)
978
925
 
979
- if isinstance(data, dict) and not data.get('success', True):
980
- raise Exception(data.get('error', 'Failed to react to channel message'))
926
+ if isinstance(data, dict) and not data.get("success", True):
927
+ raise Exception(data.get("error", "Failed to react to channel message"))
981
928
 
982
929
  return {
983
930
  "success": True,
@@ -988,34 +935,34 @@ async def _handle_newsletter_react(node_id: str, parameters: Dict[str, Any], sta
988
935
  "channel_jid": channel_jid,
989
936
  "server_id": int(server_id),
990
937
  "reaction": reaction,
991
- "timestamp": datetime.now().isoformat()
938
+ "timestamp": datetime.now().isoformat(),
992
939
  },
993
940
  "execution_time": time.time() - start_time,
994
- "timestamp": datetime.now().isoformat()
941
+ "timestamp": datetime.now().isoformat(),
995
942
  }
996
943
 
997
944
 
998
945
  async def _handle_newsletter_live_updates(node_id: str, parameters: Dict[str, Any], start_time: float, rpc_call) -> Dict[str, Any]:
999
946
  """Handle newsletter_live_updates operation - subscribe to live view/reaction counts."""
1000
- channel_jid = parameters.get('channel_jid')
947
+ channel_jid = parameters.get("channel_jid")
1001
948
  jid = await _resolve_to_jid(channel_jid, rpc_call)
1002
949
 
1003
- server_ids_raw = parameters.get('server_ids', '')
950
+ server_ids_raw = parameters.get("server_ids", "")
1004
951
  if not server_ids_raw:
1005
952
  raise ValueError("server_ids is required (comma-separated message IDs)")
1006
953
 
1007
- server_ids = [int(sid.strip()) for sid in str(server_ids_raw).split(',') if sid.strip()]
954
+ server_ids = [int(sid.strip()) for sid in str(server_ids_raw).split(",") if sid.strip()]
1008
955
  if not server_ids:
1009
956
  raise ValueError("At least one server_id is required")
1010
957
 
1011
- rpc_params = {'jid': jid, 'server_ids': server_ids}
958
+ rpc_params = {"jid": jid, "server_ids": server_ids}
1012
959
 
1013
- data = await rpc_call('newsletter_live_updates', rpc_params)
960
+ data = await rpc_call("newsletter_live_updates", rpc_params)
1014
961
 
1015
- if isinstance(data, dict) and not data.get('success', True):
1016
- raise Exception(data.get('error', 'Failed to subscribe to live updates'))
962
+ if isinstance(data, dict) and not data.get("success", True):
963
+ raise Exception(data.get("error", "Failed to subscribe to live updates"))
1017
964
 
1018
- result = data if not isinstance(data, dict) or 'result' not in data else data.get('result', data)
965
+ result = data if not isinstance(data, dict) or "result" not in data else data.get("result", data)
1019
966
 
1020
967
  return {
1021
968
  "success": True,
@@ -1025,10 +972,8 @@ async def _handle_newsletter_live_updates(node_id: str, parameters: Dict[str, An
1025
972
  "operation": "newsletter_live_updates",
1026
973
  "channel_jid": channel_jid,
1027
974
  **(result if isinstance(result, dict) else {}),
1028
- "timestamp": datetime.now().isoformat()
975
+ "timestamp": datetime.now().isoformat(),
1029
976
  },
1030
977
  "execution_time": time.time() - start_time,
1031
- "timestamp": datetime.now().isoformat()
978
+ "timestamp": datetime.now().isoformat(),
1032
979
  }
1033
-
1034
-