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
@@ -0,0 +1,692 @@
1
+ """Wave 12 C1 canary: DeploymentManager Temporal-listener integration tests.
2
+
3
+ These tests exercise the canary plumbing inside :class:`DeploymentManager`
4
+ WITHOUT a live Temporal cluster — the ``container.temporal_client()``
5
+ call is monkeypatched to return a stub whose ``client.start_workflow``
6
+ and ``client.list_workflows`` calls are captured and asserted.
7
+
8
+ The canonical pattern under test:
9
+
10
+ 1. **Deterministic listener id** = ``trigger-listener-{workflow_id}-{node_id}``.
11
+ Re-deploy of the same MachinaOs workflow targets the same Temporal id.
12
+ 2. **WorkflowIDConflictPolicy.USE_EXISTING** — Temporal returns the existing
13
+ handle instead of erroring on a duplicate-id start. Idempotent re-deploy.
14
+ 3. **TypedSearchAttributes set at start** — ``EventType``, ``TriggerNodeId``,
15
+ ``EventWorkflowId``, ``EventTriggerKind``. These ARE the registry the
16
+ cancel path queries.
17
+ 4. **Cancel via ``list_workflows(query=...) + handle.cancel()``** — no
18
+ instance-state dict, no handle caching. The Temporal server's
19
+ Visibility store is the source of truth.
20
+
21
+ Cross-confirmed across three official sources (Temporal docs, samples-python,
22
+ Inngest/Prefect/n8n source analysis). Locking the implementation against
23
+ these invariants here prevents future drift back to tribal in-memory tracking.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import sys
29
+ import types
30
+ from typing import Any, Dict, List
31
+ from unittest.mock import AsyncMock, MagicMock
32
+
33
+ import pytest
34
+
35
+ # Stub `machina` namespace — same pattern as other event-framework tests.
36
+ if "machina" not in sys.modules:
37
+ _machina = types.ModuleType("cli")
38
+ _machina.__path__ = []
39
+ sys.modules["cli"] = _machina
40
+ _machina_tcp = types.ModuleType("cli.tcp")
41
+ _machina_tcp.probe_tcp_port = MagicMock(return_value=False)
42
+ sys.modules["cli.tcp"] = _machina_tcp
43
+
44
+
45
+ @pytest.fixture
46
+ def fresh_canary_registry(monkeypatch):
47
+ """Reset the canary-registry backing store to a known empty state.
48
+
49
+ The production registry accumulates as plugin modules import — that's
50
+ correct for runtime. Tests that assert canary scope must isolate
51
+ from accumulating module state so a future plugin opt-in doesn't
52
+ silently flip a test outcome. This fixture monkeypatches the
53
+ underlying ``_REGISTERED`` set; ``monkeypatch`` restores it after
54
+ the test.
55
+ """
56
+ from services.deployment import canary_registry
57
+
58
+ monkeypatch.setattr(canary_registry, "_REGISTERED", {})
59
+ return canary_registry
60
+
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # Helpers — build a DeploymentManager with the minimum scaffolding to drive
64
+ # the canary code paths. Database / status broadcaster are pure mocks.
65
+ # ---------------------------------------------------------------------------
66
+
67
+
68
+ def _build_manager_with_state(workflow_id: str, nodes, edges, session_id="sess"):
69
+ """Build a DeploymentManager populated with one in-flight deployment.
70
+
71
+ The canary methods read ``self._deployments[workflow_id]`` for nodes/edges
72
+ snapshots, so we register that state explicitly.
73
+ """
74
+ from services.deployment.manager import DeploymentManager
75
+ from services.deployment.state import DeploymentState
76
+
77
+ database = MagicMock()
78
+ database.get_node_parameters = AsyncMock(return_value={})
79
+ broadcaster = MagicMock()
80
+ broadcaster.update_node_status = AsyncMock()
81
+
82
+ mgr = DeploymentManager(
83
+ database=database,
84
+ execute_workflow_fn=AsyncMock(return_value={"success": True}),
85
+ store_output_fn=AsyncMock(),
86
+ broadcaster=broadcaster,
87
+ )
88
+ mgr._deployments[workflow_id] = DeploymentState(
89
+ deployment_id=f"deploy_{workflow_id}",
90
+ workflow_id=workflow_id,
91
+ is_running=True,
92
+ nodes=nodes,
93
+ edges=edges,
94
+ session_id=session_id,
95
+ )
96
+ return mgr, broadcaster
97
+
98
+
99
+ def _node(node_id: str, node_type: str) -> Dict[str, Any]:
100
+ return {"id": node_id, "type": node_type, "data": {}}
101
+
102
+
103
+ # ---------------------------------------------------------------------------
104
+ # C1d.1 — deterministic listener id
105
+ # ---------------------------------------------------------------------------
106
+
107
+
108
+ class TestDeterministicListenerId:
109
+ """Re-deploy targets the same Temporal id; downstream USE_EXISTING
110
+ policy handles the duplicate gracefully."""
111
+
112
+ def test_id_format_is_stable(self):
113
+ from services.deployment.manager import DeploymentManager
114
+
115
+ first = DeploymentManager._listener_workflow_id("wf-1", "wh-1")
116
+ second = DeploymentManager._listener_workflow_id("wf-1", "wh-1")
117
+ assert first == second == "trigger-listener-wf-1-wh-1"
118
+
119
+ def test_id_differs_per_node_in_same_workflow(self):
120
+ from services.deployment.manager import DeploymentManager
121
+
122
+ a = DeploymentManager._listener_workflow_id("wf-1", "wh-1")
123
+ b = DeploymentManager._listener_workflow_id("wf-1", "wh-2")
124
+ assert a != b
125
+
126
+
127
+ # ---------------------------------------------------------------------------
128
+ # C1d.2 — canary scope: flag + type
129
+ # ---------------------------------------------------------------------------
130
+
131
+
132
+ class TestCanaryScope:
133
+ """Canary only activates for trigger types opted in via
134
+ :func:`register_canary_trigger_type` AND when the feature flag is on.
135
+ Uses ``fresh_canary_registry`` so each test starts with an empty
136
+ registry — no cross-test pollution from accumulated plugin imports."""
137
+
138
+ @pytest.mark.asyncio
139
+ async def test_disabled_when_flag_off(self, monkeypatch, fresh_canary_registry):
140
+ from services.deployment.manager import DeploymentManager
141
+
142
+ fresh_canary_registry.register_canary_trigger_type("webhookTrigger", "com.machinaos.webhook.received")
143
+
144
+ class _Off:
145
+ event_framework_enabled = False
146
+
147
+ import core.config
148
+
149
+ monkeypatch.setattr(core.config, "Settings", lambda: _Off())
150
+
151
+ result = await DeploymentManager._canary_listener_enabled_for("webhookTrigger")
152
+ assert result is False
153
+
154
+ @pytest.mark.asyncio
155
+ async def test_disabled_for_unregistered_type(self, monkeypatch, fresh_canary_registry):
156
+ """Plugin must opt in via register_canary_trigger_type; types
157
+ not in the registry stay on the legacy path even with the flag on."""
158
+ from services.deployment.manager import DeploymentManager
159
+
160
+ # Registry is empty (fresh fixture); whatsappReceive not registered.
161
+ class _On:
162
+ event_framework_enabled = True
163
+
164
+ import core.config
165
+
166
+ monkeypatch.setattr(core.config, "Settings", lambda: _On())
167
+
168
+ result = await DeploymentManager._canary_listener_enabled_for("whatsappReceive")
169
+ assert result is False
170
+
171
+ @pytest.mark.asyncio
172
+ async def test_enabled_when_registered_and_flag_on(self, monkeypatch, fresh_canary_registry):
173
+ from services.deployment.manager import DeploymentManager
174
+
175
+ fresh_canary_registry.register_canary_trigger_type("webhookTrigger", "com.machinaos.webhook.received")
176
+ fresh_canary_registry.register_canary_trigger_type("chatTrigger", "com.machinaos.chat.message.received")
177
+
178
+ class _On:
179
+ event_framework_enabled = True
180
+
181
+ import core.config
182
+
183
+ monkeypatch.setattr(core.config, "Settings", lambda: _On())
184
+
185
+ assert await DeploymentManager._canary_listener_enabled_for("webhookTrigger") is True
186
+ assert await DeploymentManager._canary_listener_enabled_for("chatTrigger") is True
187
+ # Sanity: still respects the registry boundary.
188
+ assert await DeploymentManager._canary_listener_enabled_for("randomThing") is False
189
+
190
+
191
+ class TestTriggerKindDerivation:
192
+ """``EventTriggerKind`` Search Attribute is derived from node_type
193
+ by stripping the ``Trigger`` / ``Receive`` suffix. Locks the mapping
194
+ so future trigger types (e.g. ``slackReceive``) get a sensible
195
+ default kind without per-type code changes in DeploymentManager."""
196
+
197
+ def test_strips_trigger_suffix(self):
198
+ from services.deployment.manager import DeploymentManager
199
+
200
+ assert DeploymentManager._trigger_kind_for("webhookTrigger") == "webhook"
201
+ assert DeploymentManager._trigger_kind_for("chatTrigger") == "chat"
202
+ assert DeploymentManager._trigger_kind_for("taskTrigger") == "task"
203
+
204
+ def test_strips_receive_suffix(self):
205
+ from services.deployment.manager import DeploymentManager
206
+
207
+ assert DeploymentManager._trigger_kind_for("telegramReceive") == "telegram"
208
+ assert DeploymentManager._trigger_kind_for("whatsappReceive") == "whatsapp"
209
+ assert DeploymentManager._trigger_kind_for("emailReceive") == "email"
210
+
211
+ def test_unknown_suffix_returns_node_type_verbatim(self):
212
+ from services.deployment.manager import DeploymentManager
213
+
214
+ # Defensive fallback — never crashes, just emits whatever the
215
+ # node_type was. Ops dashboards see the raw value and can
216
+ # update the chip-mapping when needed.
217
+ assert DeploymentManager._trigger_kind_for("customListener") == "customListener"
218
+
219
+
220
+ # ---------------------------------------------------------------------------
221
+ # C1d.3 — _start_canary_listener: deterministic id + USE_EXISTING + search attrs
222
+ # ---------------------------------------------------------------------------
223
+
224
+
225
+ class TestStartCanaryListener:
226
+ """The canonical start-workflow shape: deterministic id +
227
+ WorkflowIDConflictPolicy.USE_EXISTING + TypedSearchAttributes."""
228
+
229
+ @pytest.mark.asyncio
230
+ async def test_starts_with_deterministic_id_and_use_existing_policy(self, monkeypatch):
231
+ from temporalio.common import WorkflowIDConflictPolicy
232
+
233
+ mgr, _ = _build_manager_with_state(
234
+ "wf-abc",
235
+ nodes=[_node("wh-1", "webhookTrigger"), _node("agent-1", "aiAgent")],
236
+ edges=[{"source": "wh-1", "target": "agent-1", "targetHandle": "input-main"}],
237
+ )
238
+
239
+ recorded_start: List[Dict[str, Any]] = []
240
+
241
+ async def fake_start_workflow(workflow_name, **kwargs):
242
+ recorded_start.append({"name": workflow_name, **kwargs})
243
+ return MagicMock()
244
+
245
+ client = MagicMock()
246
+ client.start_workflow = fake_start_workflow
247
+
248
+ wrapper = MagicMock()
249
+ wrapper.client = client
250
+
251
+ from core import container as container_mod
252
+
253
+ monkeypatch.setattr(container_mod.container, "temporal_client", lambda: wrapper)
254
+
255
+ node = _node("wh-1", "webhookTrigger")
256
+ listener_id = await mgr._start_canary_listener(node, "wf-abc", params={"path": "hook"})
257
+
258
+ assert listener_id == "trigger-listener-wf-abc-wh-1"
259
+ assert len(recorded_start) == 1
260
+ call = recorded_start[0]
261
+ assert call["name"] == "TriggerListenerWorkflow"
262
+ assert call["id"] == "trigger-listener-wf-abc-wh-1"
263
+ assert call["id_conflict_policy"] == WorkflowIDConflictPolicy.USE_EXISTING
264
+ assert call["task_queue"] == "machina-tasks"
265
+
266
+ @pytest.mark.asyncio
267
+ async def test_search_attributes_include_event_workflow_id(self, monkeypatch):
268
+ """Cancel path queries by EventWorkflowId — start must set it."""
269
+ from temporalio.common import (
270
+ TypedSearchAttributes,
271
+ )
272
+
273
+ mgr, _ = _build_manager_with_state(
274
+ "wf-xyz",
275
+ nodes=[_node("wh-1", "webhookTrigger")],
276
+ edges=[],
277
+ )
278
+
279
+ recorded: List[Dict[str, Any]] = []
280
+
281
+ async def fake_start_workflow(workflow_name, **kwargs):
282
+ recorded.append(kwargs)
283
+ return MagicMock()
284
+
285
+ client = MagicMock()
286
+ client.start_workflow = fake_start_workflow
287
+ wrapper = MagicMock()
288
+ wrapper.client = client
289
+
290
+ from core import container as container_mod
291
+
292
+ monkeypatch.setattr(container_mod.container, "temporal_client", lambda: wrapper)
293
+
294
+ await mgr._start_canary_listener(_node("wh-1", "webhookTrigger"), "wf-xyz", params={})
295
+
296
+ sa = recorded[0]["search_attributes"]
297
+ assert isinstance(sa, TypedSearchAttributes)
298
+
299
+ # Extract by key — TypedSearchAttributes is iterable over Pair objects.
300
+ attrs_by_name = {pair.key.name: pair.value for pair in sa}
301
+ assert attrs_by_name["EventWorkflowId"] == "wf-xyz"
302
+ assert attrs_by_name["TriggerNodeId"] == "wh-1"
303
+ # EventTriggerKind derived via _trigger_kind_for (strips "Trigger" /
304
+ # "Receive" suffix) — NOT hardcoded "webhook".
305
+ assert attrs_by_name["EventTriggerKind"] == "webhook"
306
+ # EventType MUST be the CloudEvents reverse-DNS string the
307
+ # producer puts on outgoing envelopes (registered via
308
+ # canary_registry). dispatch.emit's Visibility query substitutes
309
+ # event.type into the EventType filter — if the SA carries the
310
+ # legacy snake_case event_waiter string instead, the query
311
+ # never matches and no signal reaches the listener.
312
+ assert attrs_by_name["EventType"] == "com.machinaos.webhook.received"
313
+
314
+ @pytest.mark.asyncio
315
+ async def test_chat_trigger_uses_chat_kind_in_search_attrs(self, monkeypatch):
316
+ """C1 rollout #1: starting a chatTrigger listener picks the
317
+ right EventTriggerKind ('chat', not 'webhook')."""
318
+
319
+ mgr, _ = _build_manager_with_state(
320
+ "wf-chat",
321
+ nodes=[_node("ct-1", "chatTrigger"), _node("agent-1", "aiAgent")],
322
+ edges=[{"source": "ct-1", "target": "agent-1", "targetHandle": "input-main"}],
323
+ )
324
+
325
+ recorded: List[Dict[str, Any]] = []
326
+
327
+ async def fake_start_workflow(workflow_name, **kwargs):
328
+ recorded.append(kwargs)
329
+ return MagicMock()
330
+
331
+ client = MagicMock()
332
+ client.start_workflow = fake_start_workflow
333
+ wrapper = MagicMock()
334
+ wrapper.client = client
335
+
336
+ from core import container as container_mod
337
+
338
+ monkeypatch.setattr(container_mod.container, "temporal_client", lambda: wrapper)
339
+
340
+ listener_id = await mgr._start_canary_listener(_node("ct-1", "chatTrigger"), "wf-chat", params={"session_id": "default"})
341
+ assert listener_id == "trigger-listener-wf-chat-ct-1"
342
+
343
+ sa = recorded[0]["search_attributes"]
344
+ attrs_by_name = {pair.key.name: pair.value for pair in sa}
345
+ assert attrs_by_name["EventTriggerKind"] == "chat"
346
+ # CloudEvents reverse-DNS — see test_search_attributes_include_event_workflow_id
347
+ # for the full rationale.
348
+ assert attrs_by_name["EventType"] == "com.machinaos.chat.message.received"
349
+
350
+ @pytest.mark.asyncio
351
+ async def test_returns_none_when_temporal_not_connected(self, monkeypatch):
352
+ """Falls through to legacy path; doesn't raise."""
353
+
354
+ mgr, _ = _build_manager_with_state("wf-1", nodes=[_node("wh-1", "webhookTrigger")], edges=[])
355
+
356
+ wrapper = MagicMock()
357
+ wrapper.client = None
358
+
359
+ from core import container as container_mod
360
+
361
+ monkeypatch.setattr(container_mod.container, "temporal_client", lambda: wrapper)
362
+
363
+ result = await mgr._start_canary_listener(_node("wh-1", "webhookTrigger"), "wf-1", params={})
364
+ assert result is None
365
+
366
+
367
+ # ---------------------------------------------------------------------------
368
+ # C1d.4 — _cancel_canary_listeners: Visibility query + handle.cancel()
369
+ # ---------------------------------------------------------------------------
370
+
371
+
372
+ class TestCancelCanaryListeners:
373
+ """Cancel uses Visibility query — NO local dict — and graceful cancel()."""
374
+
375
+ @pytest.mark.asyncio
376
+ async def test_query_filters_by_workflow_id_and_listener_type(self, monkeypatch):
377
+ mgr, _ = _build_manager_with_state("wf-1", nodes=[], edges=[])
378
+
379
+ recorded_queries: List[str] = []
380
+ cancelled_ids: List[str] = []
381
+
382
+ async def fake_list_workflows(query):
383
+ recorded_queries.append(query)
384
+ for wf_id in ["trigger-listener-wf-1-wh-1", "trigger-listener-wf-1-wh-2"]:
385
+ yield MagicMock(id=wf_id)
386
+
387
+ handles_by_id: Dict[str, MagicMock] = {}
388
+
389
+ def fake_get_handle(wf_id):
390
+ handle = MagicMock()
391
+
392
+ async def fake_cancel():
393
+ cancelled_ids.append(wf_id)
394
+
395
+ handle.cancel = fake_cancel
396
+ handles_by_id[wf_id] = handle
397
+ return handle
398
+
399
+ client = MagicMock()
400
+ client.list_workflows = fake_list_workflows
401
+ client.get_workflow_handle = fake_get_handle
402
+
403
+ wrapper = MagicMock()
404
+ wrapper.client = client
405
+
406
+ from core import container as container_mod
407
+
408
+ monkeypatch.setattr(container_mod.container, "temporal_client", lambda: wrapper)
409
+
410
+ cancelled = await mgr._cancel_canary_listeners("wf-1")
411
+
412
+ # Query shape: EventWorkflowId + WorkflowType IN (...) +
413
+ # ExecutionStatus. The IN clause covers both push
414
+ # (TriggerListenerWorkflow) and polling (PollingTriggerWorkflow)
415
+ # listener workflow types since Wave 12 C2 — deployment cancel
416
+ # drains both in one sweep.
417
+ assert len(recorded_queries) == 1
418
+ q = recorded_queries[0]
419
+ assert "EventWorkflowId='wf-1'" in q
420
+ assert "'TriggerListenerWorkflow'" in q
421
+ assert "'PollingTriggerWorkflow'" in q
422
+ assert "WorkflowType IN" in q
423
+ assert "ExecutionStatus='Running'" in q
424
+
425
+ assert cancelled == 2
426
+ assert sorted(cancelled_ids) == [
427
+ "trigger-listener-wf-1-wh-1",
428
+ "trigger-listener-wf-1-wh-2",
429
+ ]
430
+
431
+ @pytest.mark.asyncio
432
+ async def test_zero_listeners_returns_zero(self, monkeypatch):
433
+ """Visibility query with no results is the steady-state — must not raise."""
434
+
435
+ mgr, _ = _build_manager_with_state("wf-1", nodes=[], edges=[])
436
+
437
+ async def empty_list(query):
438
+ if False:
439
+ yield None # make this an async generator
440
+
441
+ client = MagicMock()
442
+ client.list_workflows = empty_list
443
+ wrapper = MagicMock()
444
+ wrapper.client = client
445
+
446
+ from core import container as container_mod
447
+
448
+ monkeypatch.setattr(container_mod.container, "temporal_client", lambda: wrapper)
449
+
450
+ cancelled = await mgr._cancel_canary_listeners("wf-1")
451
+ assert cancelled == 0
452
+
453
+ @pytest.mark.asyncio
454
+ async def test_per_listener_failure_doesnt_block_sweep(self, monkeypatch):
455
+ """One listener failing to cancel shouldn't strand the others —
456
+ each handle.cancel() is wrapped in its own try/except."""
457
+
458
+ mgr, _ = _build_manager_with_state("wf-1", nodes=[], edges=[])
459
+
460
+ cancelled_ids: List[str] = []
461
+
462
+ async def list_two(query):
463
+ for wf_id in ["trigger-listener-wf-1-wh-1", "trigger-listener-wf-1-wh-2"]:
464
+ yield MagicMock(id=wf_id)
465
+
466
+ def get_handle(wf_id):
467
+ handle = MagicMock()
468
+ if wf_id == "trigger-listener-wf-1-wh-1":
469
+
470
+ async def boom():
471
+ raise RuntimeError("simulated handle.cancel failure")
472
+
473
+ handle.cancel = boom
474
+ else:
475
+
476
+ async def ok():
477
+ cancelled_ids.append(wf_id)
478
+
479
+ handle.cancel = ok
480
+ return handle
481
+
482
+ client = MagicMock()
483
+ client.list_workflows = list_two
484
+ client.get_workflow_handle = get_handle
485
+ wrapper = MagicMock()
486
+ wrapper.client = client
487
+
488
+ from core import container as container_mod
489
+
490
+ monkeypatch.setattr(container_mod.container, "temporal_client", lambda: wrapper)
491
+
492
+ cancelled = await mgr._cancel_canary_listeners("wf-1")
493
+ # Only the second one succeeded.
494
+ assert cancelled == 1
495
+ assert cancelled_ids == ["trigger-listener-wf-1-wh-2"]
496
+
497
+ @pytest.mark.asyncio
498
+ async def test_returns_zero_when_temporal_disconnected(self, monkeypatch):
499
+ mgr, _ = _build_manager_with_state("wf-1", nodes=[], edges=[])
500
+
501
+ wrapper = MagicMock()
502
+ wrapper.client = None
503
+
504
+ from core import container as container_mod
505
+
506
+ monkeypatch.setattr(container_mod.container, "temporal_client", lambda: wrapper)
507
+
508
+ cancelled = await mgr._cancel_canary_listeners("wf-1")
509
+ assert cancelled == 0
510
+
511
+
512
+ # ---------------------------------------------------------------------------
513
+ # C1d.5 — invariant: DeploymentManager has NO instance state for canary listeners
514
+ # ---------------------------------------------------------------------------
515
+
516
+
517
+ class TestCancelSweepsStuckNodeStatuses:
518
+ """Regression: ``DeploymentManager.cancel`` must broadcast a full
519
+ status cleanup so the FE doesn't leave downstream nodes glowing
520
+ forever after the deployment goes down.
521
+
522
+ Pre-fix the cancel only reset trigger nodes (cron + listener) to
523
+ "idle". Downstream agents/tools/actions that were mid-execute when
524
+ cancel hit stayed in "executing" status on every connected client
525
+ because no sweep was issued. Also the toolbar Start/Stop indicator
526
+ stuck at ``executing=True`` because no terminal
527
+ ``workflow_run_ended`` / ``update_workflow_status(executing=False)``
528
+ was emitted.
529
+ """
530
+
531
+ def test_cancel_source_calls_stuck_node_sweep_with_include_waiting(self):
532
+ """Source-introspection: ``cancel`` must call
533
+ ``_clear_stuck_node_statuses(..., include_waiting=True)`` so
534
+ every node currently broadcast as ``executing`` OR ``waiting``
535
+ for this deployment goes back to idle on the FE. Without
536
+ ``include_waiting=True`` non-firing trigger siblings (and any
537
+ other ``waiting`` indicators outside the cron/listener buckets
538
+ the manager owns directly) stay glowing."""
539
+ import inspect
540
+
541
+ from services.deployment.manager import DeploymentManager
542
+
543
+ src = inspect.getsource(DeploymentManager.cancel)
544
+ assert "_clear_stuck_node_statuses" in src, (
545
+ "DeploymentManager.cancel no longer sweeps stuck node "
546
+ "statuses. Downstream nodes mid-execute at cancel-time "
547
+ "will stay glowing on FE forever. Restore the "
548
+ "``_broadcaster._clear_stuck_node_statuses(workflow_id, "
549
+ "include_waiting=True)`` call after the trigger resets."
550
+ )
551
+ assert "include_waiting=True" in src, (
552
+ "DeploymentManager.cancel must pass include_waiting=True to "
553
+ "the stuck-node sweep — explicit user-cancel is the "
554
+ "'every indicator goes quiet' signal. Without it, sibling "
555
+ "trigger nodes (or other waiting indicators) outlive the "
556
+ "deployment."
557
+ )
558
+
559
+ def test_cancel_source_broadcasts_terminal_workflow_status(self):
560
+ """Source-introspection: ``cancel`` must emit a final
561
+ ``update_workflow_status(executing=False, workflow_id=...)`` so
562
+ the toolbar Start/Stop indicator reflects the cancel. The
563
+ run-counter eviction in ``workflow_run_ended`` can race against
564
+ in-flight child runs that already incremented the counter."""
565
+ import inspect
566
+
567
+ from services.deployment.manager import DeploymentManager
568
+
569
+ src = inspect.getsource(DeploymentManager.cancel)
570
+ assert "update_workflow_status" in src, (
571
+ "DeploymentManager.cancel must broadcast "
572
+ "``update_workflow_status(executing=False, workflow_id=...)`` "
573
+ "so the FE toolbar Start/Stop indicator goes quiet after a "
574
+ "deployment is cancelled."
575
+ )
576
+ assert "executing=False" in src, (
577
+ "DeploymentManager.cancel must pass executing=False to the "
578
+ "terminal workflow_status broadcast. Anything else leaves "
579
+ "the toolbar showing the deployment as still active."
580
+ )
581
+
582
+ @pytest.mark.asyncio
583
+ async def test_cancel_runtime_calls_sweep_and_terminal_broadcast(self):
584
+ """Runtime smoke: stub the broadcaster + state + trigger_manager
585
+ and assert cancel actually invokes the sweep + terminal status
586
+ broadcast with the right args."""
587
+ from services.deployment.manager import DeploymentManager
588
+ from services.deployment.state import DeploymentState
589
+
590
+ sweep_calls: List[Dict[str, Any]] = []
591
+ status_calls: List[Dict[str, Any]] = []
592
+
593
+ broadcaster = MagicMock()
594
+ broadcaster.update_node_status = AsyncMock()
595
+ broadcaster.update_workflow_status = AsyncMock(
596
+ side_effect=lambda **kw: status_calls.append(kw),
597
+ )
598
+ broadcaster._clear_stuck_node_statuses = AsyncMock(
599
+ side_effect=lambda workflow_id, include_waiting=False: sweep_calls.append(
600
+ {
601
+ "workflow_id": workflow_id,
602
+ "include_waiting": include_waiting,
603
+ }
604
+ )
605
+ or 0,
606
+ )
607
+
608
+ database = MagicMock()
609
+ mgr = DeploymentManager(
610
+ database=database,
611
+ execute_workflow_fn=AsyncMock(return_value={"success": True}),
612
+ store_output_fn=AsyncMock(),
613
+ broadcaster=broadcaster,
614
+ )
615
+ # Seed deployment state — the cancel path bails early if
616
+ # the workflow isn't in self._deployments.
617
+ mgr._deployments["wf-1"] = DeploymentState(
618
+ deployment_id="deploy_wf-1",
619
+ workflow_id="wf-1",
620
+ is_running=True,
621
+ nodes=[{"id": "n1", "type": "aiAgent"}],
622
+ edges=[],
623
+ session_id="sess",
624
+ )
625
+
626
+ # Patch _cancel_canary_listeners / _cancel_canary_cron_schedules
627
+ # to be no-ops so the test focuses on the sweep + terminal
628
+ # broadcast contract.
629
+ mgr._cancel_canary_listeners = AsyncMock(return_value=0)
630
+ mgr._cancel_canary_cron_schedules = AsyncMock(return_value=0)
631
+
632
+ result = await mgr.cancel("wf-1")
633
+ assert result["success"] is True
634
+
635
+ # Sweep ran once with include_waiting=True (every indicator
636
+ # goes quiet on explicit user cancel).
637
+ assert len(sweep_calls) == 1, f"Expected one stuck-node sweep on cancel, got " f"{len(sweep_calls)}: {sweep_calls!r}"
638
+ assert sweep_calls[0] == {
639
+ "workflow_id": "wf-1",
640
+ "include_waiting": True,
641
+ }
642
+
643
+ # Terminal executing=False broadcast for the toolbar.
644
+ assert {"executing": False, "workflow_id": "wf-1"} in status_calls, (
645
+ f"Expected update_workflow_status(executing=False, " f"workflow_id='wf-1') on cancel; got {status_calls!r}"
646
+ )
647
+
648
+
649
+ class TestNoInstanceStateForCanaryListeners:
650
+ """Lock the architectural decision: DeploymentManager does NOT keep
651
+ a Python dict of canary listener handles or ids. Temporal's Visibility
652
+ store IS the registry. Adding such state would defeat the entire
653
+ durability guarantee (the dict dies on FastAPI restart, same gap the
654
+ canary is meant to close)."""
655
+
656
+ def test_no_canary_listeners_attribute(self):
657
+ from services.deployment.manager import DeploymentManager
658
+
659
+ database = MagicMock()
660
+ broadcaster = MagicMock()
661
+
662
+ mgr = DeploymentManager(
663
+ database=database,
664
+ execute_workflow_fn=AsyncMock(),
665
+ store_output_fn=AsyncMock(),
666
+ broadcaster=broadcaster,
667
+ )
668
+
669
+ forbidden_attrs = {
670
+ "_canary_listeners",
671
+ "_listener_handles",
672
+ "_temporal_handles",
673
+ "_active_listener_workflows",
674
+ }
675
+ present = {a for a in forbidden_attrs if hasattr(mgr, a)}
676
+ assert not present, (
677
+ f"DeploymentManager has tribal listener-tracking state: {present}. "
678
+ "Use Visibility API queries instead — Temporal's server IS the registry. "
679
+ "Otherwise FastAPI restart drops the dict and the canary's durability "
680
+ "guarantee evaporates."
681
+ )
682
+
683
+ def test_helper_methods_are_static_or_stateless(self):
684
+ """``_listener_workflow_id`` and ``_canary_listener_enabled_for``
685
+ are intentionally @staticmethod — they don't read instance state.
686
+ Helper code that needs to know the listener id reconstructs it
687
+ from (workflow_id, node_id) deterministically.
688
+ """
689
+ from services.deployment.manager import DeploymentManager
690
+
691
+ # Both helpers can be called on the class without an instance.
692
+ assert DeploymentManager._listener_workflow_id("wf", "node") == "trigger-listener-wf-node"