machinaos 0.0.76 → 0.0.78

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 (393) hide show
  1. package/README.md +143 -107
  2. package/client/dist/assets/ActionBar-Du2MSFSz.js +1 -0
  3. package/client/dist/assets/ApiKeyInput-k2LBmBjb.js +1 -0
  4. package/client/dist/assets/ApiKeyPanel-C_bV9U0X.js +1 -0
  5. package/client/dist/assets/ApiUsageSection-CmVfwZzL.js +1 -0
  6. package/client/dist/assets/EmailPanel-CeKIMGu-.js +1 -0
  7. package/client/dist/assets/OAuthPanel-KA3t3Q2K.js +1 -0
  8. package/client/dist/assets/QrPairingPanel-NgNpJNuk.js +1 -0
  9. package/client/dist/assets/RateLimitSection-Du5YNVIA.js +1 -0
  10. package/client/dist/assets/StatusCard-DNLyayXc.js +1 -0
  11. package/client/dist/assets/index-DQ0nwhec.js +257 -0
  12. package/client/dist/assets/index-DxmbVskS.css +1 -0
  13. package/client/dist/assets/vendor-flow-CZmBvHRo.js +1 -0
  14. package/client/dist/assets/vendor-icons-CVrPjN2Q.js +22 -0
  15. package/client/dist/assets/vendor-markdown-CRou3yQ5.js +62 -0
  16. package/client/dist/assets/vendor-misc-C4VxKHs5.js +1 -0
  17. package/client/dist/assets/vendor-query-SzWcOU0G.js +1 -0
  18. package/client/dist/assets/vendor-radix-Dnos29jG.js +56 -0
  19. package/client/dist/assets/vendor-react-DvWIbVx0.js +1 -0
  20. package/client/dist/index.html +37 -3
  21. package/client/index.html +28 -1
  22. package/client/package.json +44 -40
  23. package/client/src/App.tsx +2 -0
  24. package/client/src/Dashboard.tsx +157 -45
  25. package/client/src/ParameterPanel.tsx +3 -5
  26. package/client/src/adapters/nodeSpecToDescription.ts +1 -0
  27. package/client/src/assets/icons/NodeIcon.tsx +32 -0
  28. package/client/src/assets/icons/index.ts +4 -0
  29. package/client/src/assets/icons/stripe.svg +1 -0
  30. package/client/src/assets/icons/themedGlyphs.ts +404 -0
  31. package/client/src/components/AIAgentNode.tsx +77 -53
  32. package/client/src/components/GenericNode.tsx +34 -52
  33. package/client/src/components/OutputPanel.tsx +64 -147
  34. package/client/src/components/ParameterRenderer.tsx +5 -3
  35. package/client/src/components/SkillEditorModal.tsx +9 -18
  36. package/client/src/components/SquareNode.tsx +97 -115
  37. package/client/src/components/StartNode.tsx +32 -42
  38. package/client/src/components/SvgFilterDefs.tsx +54 -0
  39. package/client/src/components/TeamMonitorNode.tsx +12 -14
  40. package/client/src/components/ToolkitNode.tsx +35 -60
  41. package/client/src/components/TriggerNode.tsx +43 -77
  42. package/client/src/components/__tests__/CredentialsModal.test.tsx +49 -45
  43. package/client/src/components/credentials/CredentialsModal.tsx +98 -30
  44. package/client/src/components/credentials/CredentialsPalette.tsx +73 -5
  45. package/client/src/components/credentials/catalogueAdapter.ts +17 -1
  46. package/client/src/components/credentials/panels/ApiKeyPanel.tsx +102 -37
  47. package/client/src/components/credentials/panels/EmailPanel.tsx +7 -19
  48. package/client/src/components/credentials/panels/OAuthPanel.tsx +5 -1
  49. package/client/src/components/credentials/panels/QrPairingPanel.tsx +1 -3
  50. package/client/src/components/credentials/primitives/ActionBar.tsx +7 -11
  51. package/client/src/components/credentials/primitives/OAuthConnect.tsx +19 -28
  52. package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +24 -3
  53. package/client/src/components/credentials/types.ts +12 -2
  54. package/client/src/components/credentials/useCredentialPanel.ts +43 -19
  55. package/client/src/components/icons/AIProviderIcons.tsx +16 -0
  56. package/client/src/components/onboarding/OnboardingWizard.tsx +23 -63
  57. package/client/src/components/onboarding/nodeRoleClasses.ts +23 -0
  58. package/client/src/components/onboarding/steps/CanvasStep.tsx +15 -21
  59. package/client/src/components/onboarding/steps/ConceptsStep.tsx +2 -11
  60. package/client/src/components/onboarding/steps/GetStartedStep.tsx +2 -10
  61. package/client/src/components/parameterPanel/InputSection.tsx +9 -7
  62. package/client/src/components/parameterPanel/MasterSkillEditor.tsx +84 -198
  63. package/client/src/components/parameterPanel/MiddleSection.tsx +57 -80
  64. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +31 -25
  65. package/client/src/components/parameterPanel/__tests__/InputSection.test.tsx +7 -2
  66. package/client/src/components/ui/AIResultModal.tsx +1 -1
  67. package/client/src/components/ui/CollapsibleSection.tsx +9 -5
  68. package/client/src/components/ui/CommandPalette.tsx +147 -0
  69. package/client/src/components/ui/CommandPaletteHost.tsx +189 -0
  70. package/client/src/components/ui/ComponentItem.tsx +13 -7
  71. package/client/src/components/ui/ComponentPalette.tsx +24 -13
  72. package/client/src/components/ui/ConsolePanel.tsx +19 -11
  73. package/client/src/components/ui/DropCap.tsx +28 -0
  74. package/client/src/components/ui/EditableNodeLabel.tsx +10 -2
  75. package/client/src/components/ui/InputNodesPanel.tsx +1 -1
  76. package/client/src/components/ui/Modal.tsx +38 -6
  77. package/client/src/components/ui/OutputDisplayPanel.tsx +1 -1
  78. package/client/src/components/ui/SettingsPanel.tsx +42 -13
  79. package/client/src/components/ui/StatusBar.tsx +108 -0
  80. package/client/src/components/ui/ThemeSwitcher.tsx +109 -0
  81. package/client/src/components/ui/TopToolbar.tsx +42 -25
  82. package/client/src/components/ui/WorkflowSidebar.tsx +32 -16
  83. package/client/src/components/ui/action-button.tsx +40 -15
  84. package/client/src/components/ui/button.tsx +24 -1
  85. package/client/src/components/ui/dropdown-menu.tsx +24 -2
  86. package/client/src/components/ui/input.tsx +19 -2
  87. package/client/src/components/ui/select.tsx +15 -0
  88. package/client/src/components/ui/textarea.tsx +15 -2
  89. package/client/src/contexts/AuthContext.tsx +148 -109
  90. package/client/src/contexts/ThemeContext.tsx +93 -17
  91. package/client/src/contexts/WebSocketContext.tsx +373 -206
  92. package/client/src/contexts/__tests__/AuthContext.test.tsx +221 -0
  93. package/client/src/hooks/__tests__/useDragVariable.test.ts +7 -1
  94. package/client/src/hooks/__tests__/useWorkflowOpsListener.test.ts +142 -0
  95. package/client/src/hooks/useAppTheme.ts +209 -7
  96. package/client/src/hooks/useAutoSkillEdges.ts +7 -2
  97. package/client/src/hooks/useCatalogueQuery.ts +67 -1
  98. package/client/src/hooks/useDragVariable.ts +1 -1
  99. package/client/src/hooks/useNodeAllowlist.ts +115 -8
  100. package/client/src/hooks/useOnboarding.ts +20 -8
  101. package/client/src/hooks/useParameterPanel.ts +2 -1
  102. package/client/src/hooks/useReactFlowNodes.ts +2 -1
  103. package/client/src/hooks/useSound.ts +185 -0
  104. package/client/src/hooks/useWorkflowManagement.ts +6 -8
  105. package/client/src/hooks/useWorkflowOpsListener.ts +90 -0
  106. package/client/src/index.css +65 -3
  107. package/client/src/lib/__tests__/connectionConfig.test.ts +91 -0
  108. package/client/src/lib/aiModelProviders.ts +8 -0
  109. package/client/src/lib/connectionConfig.ts +107 -0
  110. package/client/src/lib/queryPersist.ts +13 -5
  111. package/client/src/lib/sound.ts +393 -0
  112. package/client/src/main.tsx +20 -0
  113. package/client/src/store/useAppStore.ts +26 -0
  114. package/client/src/styles/canvasAnimations.ts +37 -36
  115. package/client/src/styles/theme.ts +36 -20
  116. package/client/src/test/setup.ts +1 -0
  117. package/client/src/themes/atomic.css +253 -0
  118. package/client/src/themes/base.css +373 -0
  119. package/client/src/themes/cyber.css +890 -0
  120. package/client/src/themes/dark.css +70 -0
  121. package/client/src/themes/edo.css +246 -0
  122. package/client/src/themes/greek.css +293 -0
  123. package/client/src/themes/light.css +78 -0
  124. package/client/src/themes/plague.css +253 -0
  125. package/client/src/themes/renaissance.css +727 -0
  126. package/client/src/themes/rot.css +249 -0
  127. package/client/src/themes/steampunk.css +272 -0
  128. package/client/src/themes/surveillance.css +289 -0
  129. package/client/src/themes/wasteland.css +250 -0
  130. package/client/src/types/INodeProperties.ts +5 -0
  131. package/client/src/types/NodeTypes.ts +11 -1
  132. package/client/src/types/__tests__/cloudEvents.test.ts +99 -0
  133. package/client/src/types/cloudEvents.ts +78 -0
  134. package/client/src/vite-env.d.ts +7 -0
  135. package/client/tsconfig.json +1 -1
  136. package/client/vite.config.js +62 -2
  137. package/install.ps1 +1 -1
  138. package/install.sh +1 -1
  139. package/machina/commands/build.py +51 -7
  140. package/machina/pyproject.toml +4 -0
  141. package/machina/supervisor.py +12 -2
  142. package/machina/tree.py +71 -21
  143. package/package.json +4 -4
  144. package/scripts/install.js +16 -1
  145. package/server/config/ai_cli_providers.json +54 -0
  146. package/server/config/credential_providers.json +109 -2
  147. package/server/config/llm_defaults.json +24 -0
  148. package/server/config/model_registry.json +338 -499
  149. package/server/config/node_allowlist.json +16 -1
  150. package/server/config/pricing.json +8 -0
  151. package/server/constants.py +38 -15
  152. package/server/core/container.py +2 -2
  153. package/server/core/credentials_database.py +35 -2
  154. package/server/core/logging.py +4 -3
  155. package/server/main.py +99 -13
  156. package/server/models/node_metadata.py +1 -0
  157. package/server/nodejs/package.json +8 -6
  158. package/server/nodejs/src/index.ts +22 -5
  159. package/server/nodes/README.md +31 -4
  160. package/server/nodes/agent/_inline.py +2 -0
  161. package/server/nodes/agent/_specialized.py +6 -3
  162. package/server/nodes/agent/ai_agent.py +13 -3
  163. package/server/nodes/agent/chat_agent.py +6 -3
  164. package/server/nodes/agent/claude_code_agent.py +287 -75
  165. package/server/nodes/agent/codex_agent.py +239 -0
  166. package/server/nodes/agent/deep_agent.py +3 -3
  167. package/server/nodes/agent/rlm_agent.py +3 -3
  168. package/server/nodes/android/__init__.py +31 -1
  169. package/server/nodes/android/_base.py +9 -5
  170. package/server/{services/android_service.py → nodes/android/_dispatcher.py} +2 -2
  171. package/server/nodes/android/_handlers.py +154 -0
  172. package/server/nodes/android/_option_loaders.py +44 -0
  173. package/server/nodes/android/_refresh.py +127 -0
  174. package/server/{services/android → nodes/android/_relay}/client.py +4 -4
  175. package/server/{routers/android.py → nodes/android/_router.py} +27 -8
  176. package/server/nodes/browser/browser.py +2 -2
  177. package/server/nodes/code/_base.py +6 -2
  178. package/server/nodes/code/_claude_code.py +134 -0
  179. package/server/nodes/document/embedding_generator.py +3 -3
  180. package/server/nodes/document/http_scraper.py +3 -3
  181. package/server/nodes/document/vector_store.py +5 -5
  182. package/server/nodes/email/__init__.py +11 -1
  183. package/server/nodes/email/_filters.py +21 -0
  184. package/server/{services/himalaya_service.py → nodes/email/_himalaya.py} +6 -10
  185. package/server/{services/email_service.py → nodes/email/_service.py} +9 -13
  186. package/server/nodes/email/email_read.py +1 -1
  187. package/server/nodes/email/email_receive.py +54 -5
  188. package/server/nodes/email/email_send.py +1 -1
  189. package/server/nodes/filesystem/shell.py +24 -1
  190. package/server/nodes/google/__init__.py +55 -1
  191. package/server/{services/handlers/google_auth.py → nodes/google/_auth_helper.py} +8 -5
  192. package/server/nodes/google/_base.py +2 -2
  193. package/server/nodes/google/_credentials.py +5 -5
  194. package/server/nodes/google/_filters.py +25 -0
  195. package/server/nodes/google/_handlers.py +57 -0
  196. package/server/{services/google_oauth.py → nodes/google/_oauth.py} +195 -162
  197. package/server/nodes/google/_option_loaders.py +107 -0
  198. package/server/nodes/google/_refresh.py +66 -0
  199. package/server/nodes/google/_router.py +131 -0
  200. package/server/nodes/google/gmail_receive.py +41 -4
  201. package/server/nodes/groups.py +1 -0
  202. package/server/nodes/location/_credentials.py +45 -1
  203. package/server/{services/maps.py → nodes/location/_service.py} +18 -3
  204. package/server/nodes/location/gmaps_create.py +4 -4
  205. package/server/nodes/location/gmaps_locations.py +4 -4
  206. package/server/nodes/location/gmaps_nearby_places.py +4 -4
  207. package/server/nodes/model/_base.py +8 -3
  208. package/server/nodes/model/_credentials.py +96 -8
  209. package/server/nodes/model/_local_validator.py +345 -0
  210. package/server/nodes/model/lmstudio_chat_model.py +23 -0
  211. package/server/nodes/model/ollama_chat_model.py +25 -0
  212. package/server/nodes/proxy/_usage.py +2 -2
  213. package/server/nodes/proxy/proxy_config.py +14 -14
  214. package/server/nodes/proxy/proxy_request.py +4 -4
  215. package/server/nodes/scraper/_credentials.py +29 -1
  216. package/server/nodes/scraper/apify_actor.py +9 -9
  217. package/server/nodes/scraper/crawlee_scraper.py +5 -5
  218. package/server/nodes/search/brave_search.py +4 -0
  219. package/server/nodes/search/perplexity_search.py +9 -0
  220. package/server/nodes/search/serper_search.py +3 -0
  221. package/server/nodes/skill/simple_memory.py +12 -0
  222. package/server/nodes/social/_base.py +2 -2
  223. package/server/nodes/stripe/__init__.py +46 -0
  224. package/server/nodes/stripe/_credentials.py +33 -0
  225. package/server/nodes/stripe/_handlers.py +270 -0
  226. package/server/nodes/stripe/_install.py +127 -0
  227. package/server/nodes/stripe/_source.py +174 -0
  228. package/server/nodes/stripe/stripe_action.py +81 -0
  229. package/server/nodes/stripe/stripe_receive.py +92 -0
  230. package/server/nodes/telegram/_credentials.py +52 -1
  231. package/server/nodes/telegram/_handlers.py +19 -18
  232. package/server/nodes/telegram/_service.py +134 -32
  233. package/server/nodes/telegram/telegram_send.py +5 -6
  234. package/server/nodes/text/file_handler.py +2 -2
  235. package/server/nodes/text/text_generator.py +2 -2
  236. package/server/nodes/tool/agent_builder.py +630 -0
  237. package/server/nodes/tool/task_manager.py +144 -2
  238. package/server/nodes/twitter/__init__.py +38 -1
  239. package/server/nodes/twitter/_base.py +7 -7
  240. package/server/nodes/twitter/_credentials.py +1 -1
  241. package/server/nodes/twitter/_filters.py +37 -0
  242. package/server/nodes/twitter/_handlers.py +77 -0
  243. package/server/nodes/twitter/_oauth.py +124 -0
  244. package/server/nodes/twitter/_refresh.py +78 -0
  245. package/server/nodes/twitter/_router.py +29 -0
  246. package/server/nodes/twitter/twitter_receive.py +4 -0
  247. package/server/nodes/visuals.json +64 -19
  248. package/server/nodes/whatsapp/__init__.py +45 -5
  249. package/server/nodes/whatsapp/_base.py +3 -3
  250. package/server/nodes/whatsapp/_filters.py +137 -0
  251. package/server/nodes/whatsapp/_handlers.py +167 -0
  252. package/server/nodes/whatsapp/_option_loaders.py +68 -0
  253. package/server/nodes/whatsapp/_refresh.py +62 -0
  254. package/server/nodes/whatsapp/_runtime.py +1 -1
  255. package/server/pyproject.toml +29 -7
  256. package/server/routers/schemas.py +2 -2
  257. package/server/routers/webhook.py +26 -9
  258. package/server/routers/websocket.py +149 -810
  259. package/server/services/ai.py +89 -8
  260. package/server/services/auth.py +220 -43
  261. package/server/services/claude_oauth.py +126 -100
  262. package/server/services/cli_agent/__init__.py +78 -0
  263. package/server/services/cli_agent/_handlers.py +237 -0
  264. package/server/services/cli_agent/config.py +112 -0
  265. package/server/services/cli_agent/factory.py +48 -0
  266. package/server/services/cli_agent/lockfile.py +141 -0
  267. package/server/services/cli_agent/mcp_server.py +482 -0
  268. package/server/services/cli_agent/protocol.py +173 -0
  269. package/server/services/cli_agent/providers/__init__.py +9 -0
  270. package/server/services/cli_agent/providers/anthropic_claude.py +419 -0
  271. package/server/services/cli_agent/providers/google_gemini.py +80 -0
  272. package/server/services/cli_agent/providers/openai_codex.py +310 -0
  273. package/server/services/cli_agent/service.py +607 -0
  274. package/server/services/cli_agent/session.py +618 -0
  275. package/server/services/cli_agent/types.py +227 -0
  276. package/server/services/cli_agent/workflow_tools.py +233 -0
  277. package/server/services/credential_registry.py +26 -1
  278. package/server/services/deployment/manager.py +26 -145
  279. package/server/services/deployment/poll_registry.py +59 -0
  280. package/server/services/event_waiter.py +76 -246
  281. package/server/services/events/__init__.py +54 -0
  282. package/server/services/events/cli.py +78 -0
  283. package/server/services/events/daemon.py +163 -0
  284. package/server/services/events/envelope.py +281 -0
  285. package/server/services/events/lifecycle.py +99 -0
  286. package/server/services/events/oauth_lifecycle.py +534 -0
  287. package/server/services/events/polling.py +60 -0
  288. package/server/services/events/push.py +36 -0
  289. package/server/services/events/source.py +63 -0
  290. package/server/services/events/triggers.py +118 -0
  291. package/server/services/events/verifiers/__init__.py +25 -0
  292. package/server/services/events/verifiers/base.py +28 -0
  293. package/server/services/events/verifiers/github.py +25 -0
  294. package/server/services/events/verifiers/hmac_basic.py +32 -0
  295. package/server/services/events/verifiers/standard_webhooks.py +47 -0
  296. package/server/services/events/verifiers/stripe.py +42 -0
  297. package/server/services/events/webhook.py +105 -0
  298. package/server/services/handlers/tools.py +28 -186
  299. package/server/services/llm/config.py +7 -0
  300. package/server/services/llm/factory.py +8 -2
  301. package/server/services/memory/__init__.py +52 -0
  302. package/server/services/memory/jsonl.py +80 -0
  303. package/server/services/memory/markdown.py +65 -0
  304. package/server/services/memory/state.py +112 -0
  305. package/server/services/memory/vector_store.py +40 -0
  306. package/server/services/model_registry.py +76 -0
  307. package/server/services/node_allowlist.py +71 -15
  308. package/server/services/node_executor.py +2 -2
  309. package/server/services/node_output_schemas.py +21 -10
  310. package/server/services/node_spec.py +1 -1
  311. package/server/services/oauth_utils.py +1 -1
  312. package/server/services/plugin/__init__.py +2 -0
  313. package/server/services/plugin/base.py +44 -2
  314. package/server/services/plugin/credential.py +288 -1
  315. package/server/services/plugin/deps.py +105 -0
  316. package/server/services/plugin/edge_walker.py +12 -4
  317. package/server/services/plugin/oauth.py +381 -0
  318. package/server/services/plugin/polling.py +247 -0
  319. package/server/services/plugin/registry.py +145 -0
  320. package/server/services/plugin/singleton.py +65 -0
  321. package/server/services/plugin/ws.py +81 -0
  322. package/server/services/process_service.py +31 -2
  323. package/server/services/status_broadcaster.py +155 -238
  324. package/server/services/temporal/workflow.py +7 -7
  325. package/server/services/workflow.py +21 -3
  326. package/server/services/ws_handler_registry.py +111 -28
  327. package/server/skills/GUIDE.md +16 -1
  328. package/server/skills/assistant/agent-builder-skill/SKILL.md +166 -0
  329. package/server/skills/payments_agent/stripe-skill/SKILL.md +306 -0
  330. package/server/tests/credentials/test_auth_service.py +16 -9
  331. package/server/tests/credentials/test_credential_broadcasts.py +219 -0
  332. package/server/tests/credentials/test_google_oauth.py +6 -6
  333. package/server/tests/credentials/test_oauth_utils.py +1 -1
  334. package/server/tests/credentials/test_twitter_oauth.py +2 -2
  335. package/server/tests/credentials/test_websocket_handlers.py +44 -20
  336. package/server/tests/llm/test_factory.py +1 -0
  337. package/server/tests/llm/test_wiring.py +5 -1
  338. package/server/tests/nodes/_compat.py +24 -24
  339. package/server/tests/nodes/test_agent_builder.py +439 -0
  340. package/server/tests/nodes/test_ai_tools.py +18 -14
  341. package/server/tests/nodes/test_code_fs_process.py +17 -8
  342. package/server/tests/nodes/test_email.py +10 -9
  343. package/server/tests/nodes/test_google_workspace.py +2 -2
  344. package/server/tests/nodes/test_specialized_agents.py +100 -53
  345. package/server/tests/nodes/test_stripe_plugin.py +293 -0
  346. package/server/tests/nodes/test_telegram_social.py +4 -4
  347. package/server/tests/nodes/test_twitter.py +1 -1
  348. package/server/tests/nodes/test_web_automation.py +2 -2
  349. package/server/tests/nodes/test_whatsapp.py +9 -9
  350. package/server/tests/services/cli_agent/__init__.py +0 -0
  351. package/server/tests/services/cli_agent/test_mcp_server.py +432 -0
  352. package/server/tests/services/cli_agent/test_providers.py +358 -0
  353. package/server/tests/services/cli_agent/test_service.py +298 -0
  354. package/server/tests/services/memory/__init__.py +0 -0
  355. package/server/tests/services/memory/test_jsonl.py +188 -0
  356. package/server/tests/services/test_events.py +333 -0
  357. package/server/tests/test_node_spec.py +56 -16
  358. package/server/tests/test_plugin_helpers.py +116 -0
  359. package/server/tests/test_plugin_self_containment.py +486 -0
  360. package/server/tests/test_status_broadcasts.py +425 -0
  361. package/workflows/{AI Assistant_workflow-1777421105154-0m4snkzjf.json → AI Assistant_workflow-1778504793388-ou1m1tz2x.json } +70 -266
  362. package/workflows/{AI Employee_workflow-1777720598005-u4cm858dv.json → AI Employee_example_workflow-1777720598005-u4cm858dv.json } +112 -112
  363. package/workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json +709 -0
  364. package/client/dist/assets/ActionBar-vzPpSR77.js +0 -1
  365. package/client/dist/assets/ApiKeyInput-Ds7AKFe8.js +0 -1
  366. package/client/dist/assets/ApiKeyPanel-gfblELep.js +0 -1
  367. package/client/dist/assets/ApiUsageSection-BMNWTe2r.js +0 -1
  368. package/client/dist/assets/EmailPanel-B1Om64p5.js +0 -1
  369. package/client/dist/assets/OAuthPanel-CXyQYGBz.js +0 -1
  370. package/client/dist/assets/QrPairingPanel-BgNuI1we.js +0 -1
  371. package/client/dist/assets/RateLimitSection-YYK8sx1T.js +0 -1
  372. package/client/dist/assets/StatusCard-DuYA5hJR.js +0 -1
  373. package/client/dist/assets/index-D9tZfgvi.js +0 -363
  374. package/client/dist/assets/index-al7snTkG.css +0 -1
  375. package/client/src/components/credentials/providers.tsx +0 -177
  376. package/server/routers/google.py +0 -277
  377. package/server/routers/maps.py +0 -142
  378. package/server/routers/twitter.py +0 -365
  379. package/server/services/claude_code_service.py +0 -106
  380. package/server/services/memory.py +0 -159
  381. package/server/services/node_option_loaders/__init__.py +0 -77
  382. package/server/services/node_option_loaders/android_loaders.py +0 -55
  383. package/server/services/node_option_loaders/google_loaders.py +0 -97
  384. package/server/services/node_option_loaders/whatsapp_loaders.py +0 -69
  385. package/server/services/twitter_oauth.py +0 -411
  386. package/server/services/websocket_client.py +0 -29
  387. /package/server/{services/android → nodes/android/_relay}/__init__.py +0 -0
  388. /package/server/{services/android → nodes/android/_relay}/broadcaster.py +0 -0
  389. /package/server/{services/android → nodes/android/_relay}/manager.py +0 -0
  390. /package/server/{services/android → nodes/android/_relay}/protocol.py +0 -0
  391. /package/server/{services/browser_service.py → nodes/browser/_service.py} +0 -0
  392. /package/server/{services/whatsapp_service.py → nodes/whatsapp/_service.py} +0 -0
  393. /package/server/skills/{task_agent → assistant}/write-todos-skill/SKILL.md +0 -0
@@ -96,15 +96,22 @@ class StatusBroadcaster:
96
96
  async def connect(self, websocket: WebSocket):
97
97
  """Accept a new WebSocket connection.
98
98
 
99
- Sends cached status immediately so the client is unblocked,
100
- then refreshes service statuses in a background task.
99
+ Sends the cached status snapshot immediately. Status changes
100
+ flow in via the originating code path (WhatsApp Go-service
101
+ events, Telegram bot connect/disconnect, OAuth callbacks,
102
+ Android relay events) -- the cache is kept fresh by those
103
+ event-driven broadcasts, so the connecting client doesn't need
104
+ a per-connect refresh-all storm.
105
+
106
+ Initial cache population (and load-bearing auto-reconnects for
107
+ Telegram + Android relay) happens in a one-time lifespan-startup
108
+ invocation of :meth:`_refresh_all_services` from ``main.py``.
101
109
  """
102
110
  await websocket.accept()
103
111
  async with self._lock:
104
112
  self._connections.add(websocket)
105
113
  logger.info(f"[StatusBroadcaster] Client connected. Total: {len(self._connections)}")
106
114
 
107
- # Send cached status immediately -- don't block on service refreshes
108
115
  try:
109
116
  await websocket.send_json({
110
117
  "type": "initial_status",
@@ -113,8 +120,45 @@ class StatusBroadcaster:
113
120
  except Exception as e:
114
121
  logger.error(f"[StatusBroadcaster] Failed to send initial status: {e}")
115
122
 
116
- # Refresh service statuses in background -- updates broadcast when ready
117
- asyncio.create_task(self._refresh_all_services())
123
+ # Reconcile snapshot every fresh client gets the
124
+ # currently-running-deployments truth so a stale FE
125
+ # `deploymentStatus.isRunning=true` (carried forward through a
126
+ # backend restart that wiped DeploymentManager._deployments)
127
+ # gets cleared. CloudEvents-shaped envelope; empty list is
128
+ # meaningful and triggers FE-side reset.
129
+ try:
130
+ await self._send_deployment_snapshot(websocket)
131
+ except Exception as e:
132
+ logger.error(f"[StatusBroadcaster] Failed to send deployment snapshot: {e}")
133
+
134
+ async def _send_deployment_snapshot(self, websocket: WebSocket) -> None:
135
+ """Build + send a CloudEvents deployment_snapshot to one client.
136
+
137
+ Lazy container resolution because the broadcaster is constructed
138
+ before WorkflowService and we do not want a circular dependency
139
+ at module-load time.
140
+ """
141
+ try:
142
+ from core.container import container
143
+ from services.events import WorkflowEvent
144
+
145
+ workflow_service = container.workflow_service()
146
+ dm = workflow_service._get_deployment_manager()
147
+ running_ids = dm.get_deployed_workflows()
148
+ except Exception as e:
149
+ # Backend startup race or DI not wired yet -- skip the snapshot
150
+ # rather than fail the connect entirely. The empty-list path
151
+ # would still be safer than crashing here.
152
+ logger.debug(
153
+ f"[StatusBroadcaster] DeploymentManager unavailable for snapshot: {e}",
154
+ )
155
+ return
156
+
157
+ event = WorkflowEvent.deployment_snapshot(running_ids)
158
+ await websocket.send_json({
159
+ "type": "deployment_snapshot",
160
+ "data": event.model_dump(mode="json"),
161
+ })
118
162
 
119
163
  async def disconnect(self, websocket: WebSocket):
120
164
  """Remove a WebSocket connection."""
@@ -123,20 +167,21 @@ class StatusBroadcaster:
123
167
  logger.info(f"[StatusBroadcaster] Client disconnected. Total: {len(self._connections)}")
124
168
 
125
169
  async def _refresh_all_services(self):
126
- """Spawn per-service refresh tasks; each broadcasts as soon as
127
- its own status is ready.
170
+ """Fan out plugin-registered refresh callbacks.
128
171
 
129
172
  Uses ``asyncio.TaskGroup`` (Python 3.11+) for structured
130
173
  concurrency: every refresh runs as an independent task and
131
174
  publishes its own ``<service>_status`` message the moment its
132
175
  cache slot is populated. The slowest service no longer gates
133
- the others the credentials/status UI hydrates incrementally.
176
+ the others -- the credentials/status UI hydrates incrementally.
134
177
 
135
- Plugin packages register their own refresh callbacks via
136
- :func:`register_service_refresh` (see
137
- ``nodes/<group>/__init__.py``). The hardcoded core refreshes
138
- below predate the registry; they'll move into their own plugin
139
- folders in subsequent waves.
178
+ Wave 11.I, milestone J: every refresher now lives in its
179
+ plugin folder (``nodes/<plugin>/_refresh.py``) and registers
180
+ via :func:`register_service_refresh`. The broadcaster has zero
181
+ per-plugin knowledge in this dispatch loop. Wave 11.I, V: this
182
+ method runs ONCE at FastAPI lifespan startup (no longer per
183
+ WS-client connect). State changes after that flow through the
184
+ originating code path's event-driven broadcasts.
140
185
 
141
186
  Each callback swallows its own exceptions, so TaskGroup never
142
187
  sees one. Kept inside try/except* defensively in case a future
@@ -145,11 +190,6 @@ class StatusBroadcaster:
145
190
  with tracer.start_as_current_span("broadcaster.refresh_all_services"):
146
191
  try:
147
192
  async with asyncio.TaskGroup() as tg:
148
- tg.create_task(self._refresh_whatsapp_status())
149
- tg.create_task(self._refresh_twitter_status())
150
- tg.create_task(self._refresh_google_status())
151
- tg.create_task(self._auto_reconnect_android_relay())
152
- # Plugin-registered refreshes (telegram, future ones)
153
193
  for callback in list(_SERVICE_REFRESH_CALLBACKS):
154
194
  tg.create_task(callback(self))
155
195
  except* Exception as eg:
@@ -230,6 +270,87 @@ class StatusBroadcaster:
230
270
  """Get API key validation status for a provider."""
231
271
  return self._status["api_keys"].get(provider)
232
272
 
273
+ # =========================================================================
274
+ # Credential Mutation Broadcasts (CloudEvents v1.0)
275
+ # =========================================================================
276
+ #
277
+ # Every backend handler that mutates credential state (store / remove API
278
+ # keys, OAuth login / logout) MUST emit a credential event via this helper
279
+ # so the frontend `useCatalogueQuery` cache stays coherent across all
280
+ # connected clients. Wire-format key stays `credential_catalogue_updated`
281
+ # for FE back-compat; the body is the CloudEvents-shaped `WorkflowEvent`
282
+ # so future EventBridge / Knative interop is a JSON-schema swap.
283
+ #
284
+ # Pytest invariant `test_credential_broadcasts.py` locks the contract.
285
+
286
+ async def broadcast_credential_event(
287
+ self,
288
+ event_type: str,
289
+ *,
290
+ provider: str,
291
+ customer_id: Optional[str] = None,
292
+ ) -> None:
293
+ """Emit a CloudEvents-typed credential-mutation broadcast.
294
+
295
+ Args:
296
+ event_type: CloudEvents `type` field. Convention:
297
+ ``"credential.api_key.saved"`` / ``".deleted"``,
298
+ ``"credential.oauth.disconnected"``.
299
+ provider: Provider id (e.g. ``"openai"``, ``"twitter"``).
300
+ customer_id: For multi-tenant OAuth flows. Default omitted.
301
+ """
302
+ # Local import keeps the broadcaster module independent of the
303
+ # event framework's load order during startup.
304
+ from services.events import WorkflowEvent
305
+
306
+ event = WorkflowEvent(
307
+ source="machinaos://services/credentials",
308
+ type=event_type,
309
+ subject=provider,
310
+ data={
311
+ "provider": provider,
312
+ **({"customer_id": customer_id} if customer_id else {}),
313
+ },
314
+ )
315
+
316
+ await self.broadcast({
317
+ "type": "credential_catalogue_updated",
318
+ "data": event.model_dump(mode="json"),
319
+ })
320
+
321
+ async def broadcast_agent_progress(
322
+ self,
323
+ node_id: str,
324
+ *,
325
+ workflow_id: Optional[str],
326
+ iteration: int,
327
+ max_iterations: int,
328
+ phase: Optional[str] = None,
329
+ ) -> None:
330
+ """Emit a CloudEvents-typed agent-progress event.
331
+
332
+ Wire key ``agent_progress`` is a parallel channel to
333
+ ``node_status``: same per-node scope, but the inner payload is a
334
+ full CloudEvents envelope instead of a raw dict. The FE routes
335
+ envelope.data into ``nodeStatusStore`` so the existing
336
+ ``useNodeStatus`` consumers see ``iteration`` /
337
+ ``max_iterations`` without a separate store.
338
+ """
339
+ from services.events import WorkflowEvent
340
+
341
+ event = WorkflowEvent.agent_progress(
342
+ node_id,
343
+ workflow_id=workflow_id,
344
+ iteration=iteration,
345
+ max_iterations=max_iterations,
346
+ phase=phase,
347
+ )
348
+
349
+ await self.broadcast({
350
+ "type": "agent_progress",
351
+ "data": event.model_dump(mode="json"),
352
+ })
353
+
233
354
  # =========================================================================
234
355
  # Android Status Updates
235
356
  # =========================================================================
@@ -263,218 +384,14 @@ class StatusBroadcaster:
263
384
  })
264
385
 
265
386
  # =========================================================================
266
- # WhatsApp Status Updates
387
+ # Status updates -- per-service refresh bodies live in their plugin
388
+ # folders (Wave 11.I, milestone J): nodes/<plugin>/_refresh.py.
389
+ # The plugin's __init__.py self-registers via
390
+ # services.status_broadcaster.register_service_refresh; the central
391
+ # _refresh_all_services fans out to every registered callback with
392
+ # zero per-plugin knowledge.
267
393
  # =========================================================================
268
394
 
269
- async def _refresh_whatsapp_status(self):
270
- """Fetch fresh WhatsApp status from Go service and update cache.
271
-
272
- Called on client connect to ensure initial_status has accurate data.
273
- Silently fails if WhatsApp service is unavailable. Span emitted
274
- via OTel for cold-start benchmarking.
275
- """
276
- with tracer.start_as_current_span("broadcaster.refresh_whatsapp") as span:
277
- try:
278
- from services.whatsapp_service import get_client
279
- import time
280
-
281
- client = await get_client()
282
- status_data = await client.call("status")
283
-
284
- self._status["whatsapp"] = {
285
- "connected": status_data.get("connected", False),
286
- "has_session": status_data.get("has_session", False),
287
- "running": status_data.get("running", False),
288
- "pairing": status_data.get("pairing", False),
289
- "device_id": status_data.get("device_id"),
290
- "qr": None,
291
- "timestamp": time.time()
292
- }
293
- logger.debug(f"[StatusBroadcaster] Refreshed WhatsApp status: connected={status_data.get('connected')}")
294
- await self.broadcast({
295
- "type": "whatsapp_status",
296
- "data": self._status["whatsapp"],
297
- })
298
- span.set_attribute("connected", bool(status_data.get("connected", False)))
299
- except Exception as e:
300
- span.record_exception(e)
301
- # Don't fail client connection if WhatsApp service is down
302
- logger.debug(f"[StatusBroadcaster] Could not refresh WhatsApp status: {e}")
303
-
304
- async def _refresh_twitter_status(self):
305
- """Fetch Twitter status from stored OAuth tokens in database.
306
-
307
- Called on client connect to check if user is authenticated with Twitter.
308
- Uses OAuth token system (matches REST endpoint and node handlers).
309
- Span emitted via OTel for cold-start benchmarking.
310
- """
311
- with tracer.start_as_current_span("broadcaster.refresh_twitter") as span:
312
- try:
313
- from core.container import container
314
- auth_service = container.auth_service()
315
-
316
- # Get tokens from OAuth system (NOT api_key system)
317
- tokens = await auth_service.get_oauth_tokens("twitter", customer_id="owner")
318
- if not tokens or not tokens.get("access_token"):
319
- self._status["twitter"] = {
320
- "connected": False,
321
- "username": None,
322
- "user_id": None,
323
- "name": None,
324
- "profile_image_url": None
325
- }
326
- else:
327
- # User info is stored in the OAuth token record
328
- email = tokens.get("email", "") # Stored as "@username"
329
- name = tokens.get("name", "")
330
- username = email.lstrip("@") if email.startswith("@") else email
331
-
332
- self._status["twitter"] = {
333
- "connected": True,
334
- "username": username or None,
335
- "user_id": None,
336
- "name": name or None,
337
- "profile_image_url": None
338
- }
339
- logger.debug(f"[StatusBroadcaster] Twitter status: connected as @{username}")
340
-
341
- await self.broadcast({
342
- "type": "twitter_status",
343
- "data": self._status["twitter"],
344
- })
345
- span.set_attribute("connected", bool(self._status["twitter"]["connected"]))
346
- except Exception as e:
347
- span.record_exception(e)
348
- logger.debug(f"[StatusBroadcaster] Could not refresh Twitter status: {e}")
349
-
350
- async def _refresh_google_status(self):
351
- """Fetch Google Workspace status from stored OAuth tokens.
352
-
353
- Called on client connect to check if user is authenticated with Google.
354
- Span emitted via OTel for cold-start benchmarking.
355
- """
356
- with tracer.start_as_current_span("broadcaster.refresh_google") as span:
357
- try:
358
- from core.container import container
359
- auth_service = container.auth_service()
360
-
361
- tokens = await auth_service.get_oauth_tokens("google", customer_id="owner")
362
- if not tokens or not tokens.get("access_token"):
363
- self._status["google"] = {
364
- "connected": False,
365
- "email": None,
366
- "name": None,
367
- }
368
- else:
369
- self._status["google"] = {
370
- "connected": True,
371
- "email": tokens.get("email"),
372
- "name": tokens.get("name"),
373
- }
374
- logger.debug(f"[StatusBroadcaster] Google status: connected as {tokens.get('email')}")
375
-
376
- await self.broadcast({
377
- "type": "google_status",
378
- "data": self._status["google"],
379
- })
380
- span.set_attribute("connected", bool(self._status["google"]["connected"]))
381
- except Exception as e:
382
- span.record_exception(e)
383
- logger.debug(f"[StatusBroadcaster] Could not refresh Google status: {e}")
384
-
385
- async def _auto_reconnect_android_relay(self):
386
- """Auto-reconnect to Android relay if there's a stored pairing session.
387
-
388
- Called on client connect to re-establish relay connection after server restart.
389
- The stored session contains relay URL, API key, and paired device info.
390
- Span emitted via OTel for cold-start benchmarking.
391
- """
392
- with tracer.start_as_current_span("broadcaster.refresh_android") as span:
393
- await self._auto_reconnect_android_relay_body(span)
394
-
395
- async def _auto_reconnect_android_relay_body(self, span):
396
- try:
397
- # Check if already connected
398
- from services.android.manager import get_current_relay_client
399
- existing = get_current_relay_client()
400
- if existing and existing.is_connected():
401
- # Already connected, just refresh status
402
- self._status["android"] = {
403
- "connected": True,
404
- "paired": existing.is_paired(),
405
- "device_id": existing.paired_device_id,
406
- "device_name": existing.paired_device_name,
407
- "connected_devices": list(existing.get_connected_devices()),
408
- "connection_type": "relay",
409
- "qr_data": existing.qr_data,
410
- "session_token": existing.session_token
411
- }
412
- logger.debug("[StatusBroadcaster] Android relay already connected")
413
- await self.broadcast({
414
- "type": "android_status",
415
- "data": self._status["android"],
416
- })
417
- span.set_attribute("path", "already_connected")
418
- return
419
-
420
- # Check for stored session
421
- from core.container import container
422
- database = container.database()
423
-
424
- session = await database.get_android_relay_session()
425
- if not session:
426
- span.set_attribute("path", "no_session")
427
- logger.debug("[StatusBroadcaster] No stored Android relay session")
428
- return
429
-
430
- relay_url = session.get("relay_url")
431
- api_key = session.get("api_key")
432
- device_id = session.get("device_id")
433
- session.get("device_name")
434
-
435
- if not relay_url or not api_key:
436
- span.set_attribute("path", "session_missing_creds")
437
- logger.debug("[StatusBroadcaster] Stored session missing relay URL or API key")
438
- return
439
-
440
- span.set_attribute("path", "auto_reconnect")
441
- logger.info("[StatusBroadcaster] Auto-reconnecting to Android relay...",
442
- relay_url=relay_url, device_id=device_id)
443
-
444
- # Attempt to reconnect
445
- from services.android.manager import get_relay_client
446
- client, error = await get_relay_client(relay_url, api_key)
447
-
448
- if client and client.is_connected():
449
- logger.info("[StatusBroadcaster] Android relay reconnected successfully")
450
- # Update status - connected to relay but need to check if still paired
451
- # The relay server creates a new session on each connect, so pairing is lost
452
- # Update the cached status to reflect the current state
453
- self._status["android"] = {
454
- "connected": True,
455
- "paired": client.is_paired(),
456
- "device_id": client.paired_device_id,
457
- "device_name": client.paired_device_name,
458
- "connected_devices": list(client.get_connected_devices()),
459
- "connection_type": "relay",
460
- "qr_data": client.qr_data,
461
- "session_token": client.session_token
462
- }
463
- await self.broadcast({
464
- "type": "android_status",
465
- "data": self._status["android"],
466
- })
467
- span.set_attribute("reconnect_ok", True)
468
- else:
469
- span.set_attribute("reconnect_ok", False)
470
- logger.warning(f"[StatusBroadcaster] Failed to reconnect Android relay: {error}")
471
- # Clear the stored session since reconnect failed
472
- await database.clear_android_relay_session()
473
-
474
- except Exception as e:
475
- span.record_exception(e)
476
- logger.debug(f"[StatusBroadcaster] Could not auto-reconnect Android relay: {e}")
477
-
478
395
  async def update_whatsapp_status(
479
396
  self,
480
397
  connected: bool,
@@ -501,10 +418,6 @@ class StatusBroadcaster:
501
418
  "data": self._status["whatsapp"]
502
419
  })
503
420
 
504
- def get_whatsapp_status(self) -> Dict[str, Any]:
505
- """Get WhatsApp connection status."""
506
- return self._status["whatsapp"].copy()
507
-
508
421
  # =========================================================================
509
422
  # Telegram Status Updates
510
423
  # =========================================================================
@@ -533,10 +446,6 @@ class StatusBroadcaster:
533
446
  "data": self._status["telegram"]
534
447
  })
535
448
 
536
- def get_telegram_status(self) -> Dict[str, Any]:
537
- """Get Telegram bot connection status."""
538
- return self._status["telegram"].copy()
539
-
540
449
  # =========================================================================
541
450
  # Node Status Updates
542
451
  # =========================================================================
@@ -1221,6 +1130,15 @@ _ServiceRefreshCallback = _typing.Callable[
1221
1130
  ]
1222
1131
  _SERVICE_REFRESH_CALLBACKS: _typing.List[_ServiceRefreshCallback] = []
1223
1132
 
1133
+ from services.plugin.registry import IdempotentList as _IdempotentList # noqa: E402
1134
+
1135
+ # Backed by the module-level _SERVICE_REFRESH_CALLBACKS list so the
1136
+ # existing iterator at line 153 (``for callback in list(_SERVICE_REFRESH_CALLBACKS)``)
1137
+ # keeps working unchanged.
1138
+ _SERVICE_REFRESH_FANOUT: _IdempotentList[_ServiceRefreshCallback] = _IdempotentList(
1139
+ "service_refresh", items=_SERVICE_REFRESH_CALLBACKS
1140
+ )
1141
+
1224
1142
 
1225
1143
  def register_service_refresh(callback: _ServiceRefreshCallback) -> None:
1226
1144
  """Register a per-service refresh callback.
@@ -1229,8 +1147,7 @@ def register_service_refresh(callback: _ServiceRefreshCallback) -> None:
1229
1147
  callback runs once per ``_refresh_all_services()`` cycle (i.e. on
1230
1148
  every WebSocket client connect).
1231
1149
  """
1232
- if callback not in _SERVICE_REFRESH_CALLBACKS:
1233
- _SERVICE_REFRESH_CALLBACKS.append(callback)
1150
+ _SERVICE_REFRESH_FANOUT.register(callback)
1234
1151
 
1235
1152
 
1236
1153
  # Global singleton instance
@@ -25,13 +25,13 @@ CONFIG_HANDLES = {"input-tools", "input-memory", "input-model", "input-skill", "
25
25
  # Trigger node types — event listeners that should never be scheduled
26
26
  # as blocking activities. Imported from constants to avoid drift (was
27
27
  # previously redefined here with a "keep in sync" comment — Wave 11.E.2).
28
- from constants import WORKFLOW_TRIGGER_TYPES as TRIGGER_NODE_TYPES
29
-
30
- # Android service types (connect to androidTool, not executed directly)
31
- ANDROID_SERVICE_TYPES = {
32
- "batteryMonitor", "locationService", "deviceState",
33
- "systemInfo", "appList", "appLauncher",
34
- }
28
+ # Android service types follow the same pattern: imported from constants
29
+ # so the canonical 16-entry list (Wave 11.I, milestone P -- the local
30
+ # 6-entry copy that lived here was a stale subset).
31
+ from constants import (
32
+ ANDROID_SERVICE_NODE_TYPES as ANDROID_SERVICE_TYPES,
33
+ WORKFLOW_TRIGGER_TYPES as TRIGGER_NODE_TYPES,
34
+ )
35
35
 
36
36
  # Skill node types (connect to Zeenie's input-skill, not executed directly)
37
37
  SKILL_NODE_TYPES = {
@@ -27,9 +27,9 @@ if TYPE_CHECKING:
27
27
  from core.database import Database
28
28
  from core.cache import CacheService
29
29
  from services.ai import AIService
30
- from services.maps import MapsService
30
+ from nodes.location._service import MapsService
31
31
  from services.text import TextService
32
- from services.android_service import AndroidService
32
+ from nodes.android._dispatcher import AndroidService
33
33
  from services.temporal import TemporalExecutor
34
34
 
35
35
  logger = get_logger(__name__)
@@ -461,7 +461,25 @@ class WorkflowService:
461
461
  workflow_id: Specific workflow to cancel. If None, cancels first running deployment.
462
462
  """
463
463
  manager = self._get_deployment_manager()
464
- return await manager.cancel(workflow_id)
464
+ result = await manager.cancel(workflow_id)
465
+
466
+ # Also cancel any in-flight CLI agent batches (claude_code_agent /
467
+ # codex_agent) for this workflow. Best-effort — failure to cancel
468
+ # CLI sessions must not block the deployment cancel.
469
+ if workflow_id:
470
+ try:
471
+ from services.cli_agent.service import get_ai_cli_service
472
+ cli_svc = get_ai_cli_service()
473
+ cancelled = await cli_svc.cancel_workflow(workflow_id)
474
+ if cancelled:
475
+ logger.info(
476
+ "[workflow] cancelled %d CLI agent session(s) for workflow %s",
477
+ cancelled, workflow_id,
478
+ )
479
+ except Exception as exc:
480
+ logger.debug("[workflow] CLI agent cancel: %s", exc)
481
+
482
+ return result
465
483
 
466
484
  def get_deployment_status(self, workflow_id: Optional[str] = None) -> Dict[str, Any]:
467
485
  """Get deployment status.