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
@@ -1,7 +1,7 @@
1
1
  import React, { memo, useState, useEffect, useMemo } from 'react';
2
2
  import { nodePropsEqual } from './nodeMemoEquality';
3
3
  import { Handle, Position, NodeProps } from 'reactflow';
4
- import { NodeData } from '../types/NodeTypes';
4
+ import { NodeData, NodeStyle } from '../types/NodeTypes';
5
5
  import { useAppStore } from '../store/useAppStore';
6
6
  import AIAgentExecutionService from '../services/execution/aiAgentExecutionService';
7
7
  import { useAppTheme } from '../hooks/useAppTheme';
@@ -9,6 +9,7 @@ import { useNodeStatus } from '../contexts/WebSocketContext';
9
9
  import { dracula } from '../styles/theme';
10
10
  import { useNodeSpec } from '../lib/nodeSpec';
11
11
  import { NodeIcon } from '../assets/icons';
12
+ import { Badge } from '@/components/ui/badge';
12
13
 
13
14
  // LangGraph phase icons and labels. Colors reference the dracula token
14
15
  // constants so a future palette change in tokens.css propagates without
@@ -44,7 +45,7 @@ const REACT_POSITION: Record<SpecHandle['position'], Position> = {
44
45
 
45
46
  const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
46
47
  const theme = useAppTheme();
47
- const { setSelectedNode } = useAppStore();
48
+ const setSelectedNode = useAppStore((s) => s.setSelectedNode);
48
49
  const [_configValid, setConfigValid] = useState(true);
49
50
  const [_configErrors, setConfigErrors] = useState<string[]>([]);
50
51
 
@@ -72,6 +73,15 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
72
73
  const isExecuting = nodeStatus?.status === 'executing';
73
74
  const currentPhase = nodeStatus?.data?.phase as string | undefined;
74
75
  const phaseConfig = currentPhase ? PHASE_CONFIG[currentPhase] : null;
76
+ // Live LangGraph supervised-loop counter. Backed by the
77
+ // `agent_progress` CloudEvents broadcast (services/ai.py emits one
78
+ // per astream snapshot). `iteration` advances on each agent_node call;
79
+ // `max_iterations` mirrors LangGraph's recursion_limit
80
+ // (llm_defaults.json:agent.recursion_limit).
81
+ const iteration = nodeStatus?.data?.iteration as number | undefined;
82
+ const maxIterations = nodeStatus?.data?.max_iterations as number | undefined;
83
+ const showIterationBadge =
84
+ isExecuting && typeof iteration === 'number' && typeof maxIterations === 'number';
75
85
 
76
86
  // Validate configuration whenever data changes
77
87
  useEffect(() => {
@@ -91,60 +101,40 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
91
101
  setSelectedNode({ id, type, data, position: { x: 0, y: 0 } });
92
102
  };
93
103
 
94
- const getBorderColor = () => {
95
- if (isExecuting) {
96
- if (theme.isDarkMode && phaseConfig) return phaseConfig.color;
97
- return accentColor;
98
- }
99
- if (selected) return theme.colors.focus;
100
- return theme.colors.border;
101
- };
102
-
103
- const getBoxShadow = () => {
104
- if (isExecuting) {
105
- if (theme.isDarkMode && phaseConfig) {
106
- return `0 0 20px ${phaseConfig.color}80, 0 0 40px ${phaseConfig.color}40`;
107
- }
108
- return `0 0 0 3px ${accentColor}80, 0 4px 16px ${accentColor}60`;
109
- }
110
- if (selected) {
111
- return `0 4px 12px ${theme.colors.focusRing}, 0 0 0 1px ${theme.colors.focusRing}`;
112
- }
113
- return `0 2px 4px ${theme.colors.shadow}`;
114
- };
115
-
116
104
  const hasRightOutputs = rightOutputs.length > 0;
117
105
  const hasLeftLabels = hasMainInput || leftInputs.length > 0;
118
106
 
119
107
  return (
108
+ // `node` + `node-agent` + `selected` co-classes are the design-handoff
109
+ // structural hooks for per-theme decorations (Renaissance wax seal,
110
+ // Cyber neon underglow + corner LED blink, etc.).
120
111
  <div
112
+ className={`node node-agent ${selected ? 'selected' : ''}`}
113
+ data-executing={isExecuting ? '' : undefined}
121
114
  style={{
115
+ '--node-color': accentColor,
122
116
  position: 'relative',
123
117
  padding: theme.spacing.lg,
124
118
  paddingLeft: hasLeftLabels ? '72px' : theme.spacing.lg,
125
119
  paddingRight: hasRightOutputs ? '72px' : theme.spacing.lg,
126
- minWidth: `${width}px`,
120
+ // Width is fixed (not just min) so long titles like
121
+ // "PRODUCTIVITY AGENT" wrap inside the bordered card
122
+ // instead of growing the node and bleeding past the border.
123
+ width: `${width}px`,
127
124
  minHeight: `${height}px`,
128
- borderRadius: theme.borderRadius.lg,
129
- background: theme.isDarkMode
130
- ? `linear-gradient(135deg, ${accentColor}20 0%, ${theme.colors.backgroundAlt} 100%)`
131
- : `linear-gradient(145deg, #ffffff 0%, ${accentColor}08 100%)`,
132
- border: `2px solid ${getBorderColor()}`,
133
125
  color: theme.colors.text,
134
126
  fontSize: theme.fontSize.sm,
135
127
  fontWeight: theme.fontWeight.medium,
136
128
  textAlign: 'center',
137
129
  cursor: 'pointer',
138
130
  transition: 'all 0.3s ease',
139
- boxShadow: getBoxShadow(),
140
131
  overflow: 'visible',
141
132
  display: 'flex',
142
133
  flexDirection: 'column',
143
134
  alignItems: 'center',
144
135
  justifyContent: 'center',
145
136
  gap: theme.spacing.sm,
146
- animation: isExecuting ? 'pulse 1.5s ease-in-out infinite' : 'none',
147
- }}
137
+ } as NodeStyle}
148
138
  >
149
139
  {/* Main input (left, top area) — shown when the spec declares a main input */}
150
140
  {hasMainInput && (
@@ -159,28 +149,50 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
159
149
  type="target"
160
150
  position={Position.Left}
161
151
  isConnectable={isConnectable}
152
+ className="node-handle in"
162
153
  style={{
163
154
  position: 'absolute', left: '-6px', top: '30%', transform: 'translateY(-50%)',
164
155
  width: theme.nodeSize.handle, height: theme.nodeSize.handle,
165
- backgroundColor: theme.colors.background,
166
- border: `2px solid ${theme.colors.textSecondary}`, borderRadius: '50%',
156
+ borderRadius: '50%',
167
157
  }}
168
158
  title="Input"
169
159
  />
170
160
  </>
171
161
  )}
172
162
 
163
+ {/* Live LangGraph iteration counter (e.g. "12 / 500"). shadcn
164
+ Badge primitive with `outline` variant — picks up `--border`
165
+ and `--foreground` from whichever theme is active. The
166
+ per-node-type accent rides via the `--node-color` custom
167
+ property already set on the parent at line ~105 (one of the
168
+ two grandfathered inline-style channels for canvas nodes per
169
+ CLAUDE.md). `tabular-nums` keeps "1 / 500" -> "12 / 500" from
170
+ jittering as digits widen. */}
171
+ {showIterationBadge && (
172
+ <Badge
173
+ variant="outline"
174
+ title={`Iteration ${iteration} of ${maxIterations} (LangGraph recursion_limit)`}
175
+ className="absolute top-1 left-1 z-20 tabular-nums pointer-events-none"
176
+ style={{
177
+ color: 'var(--node-color)',
178
+ borderColor: 'var(--node-color)',
179
+ }}
180
+ >
181
+ {iteration} / {maxIterations}
182
+ </Badge>
183
+ )}
184
+
173
185
  {/* Parameters gear */}
174
186
  <button
175
187
  onClick={handleParametersClick}
188
+ className="node-gear"
176
189
  style={{
177
190
  position: 'absolute', top: theme.spacing.xs, right: theme.spacing.xs,
178
191
  width: theme.nodeSize.paramButton, height: theme.nodeSize.paramButton,
179
- borderRadius: theme.borderRadius.sm, backgroundColor: theme.colors.backgroundAlt,
180
- border: `1px solid ${theme.colors.border}`, cursor: 'pointer',
192
+ cursor: 'pointer',
181
193
  display: 'flex', alignItems: 'center', justifyContent: 'center',
182
- fontSize: theme.fontSize.xs, color: theme.colors.textSecondary,
183
- fontWeight: theme.fontWeight.normal, transition: theme.transitions.fast, zIndex: 20,
194
+ fontSize: theme.fontSize.xs,
195
+ fontWeight: theme.fontWeight.normal, zIndex: 20,
184
196
  }}
185
197
  title="Edit Parameters"
186
198
  >⚙️</button>
@@ -191,17 +203,31 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
191
203
  <NodeIcon icon={spec?.icon} className="h-7 w-7 text-3xl" />
192
204
  </div>
193
205
 
194
- {/* Title */}
195
- <div style={{
206
+ {/* Title — alignSelf:stretch + width:100% forces the flex child
207
+ to fill the parent's content area (parent is align-items:center
208
+ which would otherwise size the child to its content). With
209
+ overflowWrap + wordBreak, long display names like
210
+ "PRODUCTIVITY AGENT" wrap to multiple lines. */}
211
+ <div className="node-label" style={{
212
+ alignSelf: 'stretch',
213
+ width: '100%',
196
214
  fontSize: theme.fontSize.base, fontWeight: theme.fontWeight.semibold,
197
215
  color: theme.colors.text, lineHeight: '1.2', marginBottom: theme.spacing.xs,
216
+ overflowWrap: 'break-word', wordBreak: 'break-word', whiteSpace: 'normal',
217
+ textAlign: 'center',
198
218
  }}>{title}</div>
199
219
 
200
- {/* Subtitle */}
201
- <div style={{
220
+ {/* Subtitle. Phase color is JS-driven (PHASE_CONFIG[phase].color) so
221
+ we keep the inline color while still exposing `node-sub` so themes
222
+ can style the resting subtitle. */}
223
+ <div className="node-sub" style={{
224
+ alignSelf: 'stretch',
225
+ width: '100%',
202
226
  fontSize: theme.fontSize.xs, fontWeight: theme.fontWeight.normal,
203
227
  color: isExecuting && phaseConfig ? phaseConfig.color : theme.colors.focus,
204
228
  lineHeight: '1.2', marginBottom: theme.spacing.lg, transition: 'color 0.3s ease',
229
+ overflowWrap: 'break-word', wordBreak: 'break-word', whiteSpace: 'normal',
230
+ textAlign: 'center',
205
231
  }}>{isExecuting && phaseConfig ? phaseConfig.label : subtitle}</div>
206
232
 
207
233
  {/* Left inputs below the main one (Memory / Task / etc.) */}
@@ -218,11 +244,11 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
218
244
  type="target"
219
245
  position={REACT_POSITION[h.position]}
220
246
  isConnectable={isConnectable}
247
+ className="node-handle in"
221
248
  style={{
222
249
  position: 'absolute', left: '-6px', top: h.offset || '50%',
223
250
  width: theme.nodeSize.handle, height: theme.nodeSize.handle,
224
- backgroundColor: theme.colors.background,
225
- border: `2px solid ${theme.colors.textSecondary}`, borderRadius: '0',
251
+ borderRadius: '0',
226
252
  transform: 'translateY(-50%) rotate(45deg)',
227
253
  }}
228
254
  title={h.label || h.name}
@@ -247,11 +273,11 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
247
273
  type="target"
248
274
  position={Position.Bottom}
249
275
  isConnectable={isConnectable}
276
+ className="node-handle in"
250
277
  style={{
251
278
  position: 'absolute', bottom: '-6px', left: h.offset || '50%',
252
279
  width: theme.nodeSize.handle, height: theme.nodeSize.handle,
253
- backgroundColor: theme.colors.background,
254
- border: `2px solid ${theme.colors.textSecondary}`, borderRadius: '0',
280
+ borderRadius: '0',
255
281
  transform: 'translateX(-50%) rotate(45deg)',
256
282
  }}
257
283
  title={h.label || h.name}
@@ -265,11 +291,10 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
265
291
  type="source"
266
292
  position={Position.Top}
267
293
  isConnectable={isConnectable}
294
+ className="node-handle out"
268
295
  style={{
269
296
  position: 'absolute', top: '-6px', left: '50%', transform: 'translateX(-50%)',
270
297
  width: theme.nodeSize.handle, height: theme.nodeSize.handle,
271
- backgroundColor: accentColor,
272
- border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
273
298
  borderRadius: '50%', zIndex: 20,
274
299
  }}
275
300
  title={topOutput.label || topOutput.name}
@@ -294,12 +319,11 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
294
319
  type="source"
295
320
  position={Position.Right}
296
321
  isConnectable={isConnectable}
322
+ className="node-handle out"
297
323
  style={{
298
324
  position: 'absolute', right: '-6px', top: h.offset || '50%',
299
325
  transform: 'translateY(-50%)',
300
326
  width: theme.nodeSize.handle, height: theme.nodeSize.handle,
301
- backgroundColor: accentColor,
302
- border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
303
327
  borderRadius: '50%', zIndex: 20,
304
328
  }}
305
329
  title={h.label || h.name}
@@ -316,11 +340,11 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
316
340
  type="source"
317
341
  position={Position.Right}
318
342
  isConnectable={isConnectable}
343
+ className="node-handle out"
319
344
  style={{
320
345
  position: 'absolute', right: '-6px', top: '50%', transform: 'translateY(-50%)',
321
346
  width: theme.nodeSize.handle, height: theme.nodeSize.handle,
322
- backgroundColor: theme.colors.background,
323
- border: `2px solid ${theme.colors.textSecondary}`, borderRadius: '50%',
347
+ borderRadius: '50%',
324
348
  }}
325
349
  title="Main Output"
326
350
  />
@@ -4,23 +4,24 @@ import { Handle, Position, NodeProps } from 'reactflow';
4
4
  import { resolveNodeDescription } from '../lib/nodeSpec';
5
5
  import { NodeIcon } from '../assets/icons';
6
6
  import { useNodeSpec } from '../lib/nodeSpec';
7
- import { NodeData } from '../types/NodeTypes';
7
+ import { NodeData, NodeStyle } from '../types/NodeTypes';
8
8
  import { INodeInputDefinition, INodeOutputDefinition, NodeConnectionType } from '../types/INodeProperties';
9
9
  import { useAppStore } from '../store/useAppStore';
10
- import { useAppTheme } from '../hooks/useAppTheme';
11
10
  import { useNodeStatus } from '../contexts/WebSocketContext';
12
11
  import EditableNodeLabel from './ui/EditableNodeLabel';
13
12
 
14
13
  const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
15
- const theme = useAppTheme();
16
- const { setSelectedNode, setRenamingNodeId, updateNodeData } = useAppStore();
14
+ const setSelectedNode = useAppStore((s) => s.setSelectedNode);
15
+ const setRenamingNodeId = useAppStore((s) => s.setRenamingNodeId);
16
+ const updateNodeData = useAppStore((s) => s.updateNodeData);
17
17
  const isDisabled = data?.disabled === true;
18
18
 
19
- // Per-id slice subscription so an unrelated node's status update
20
- // does not re-render this generic node.
21
- const nodeStatus = useNodeStatus(id);
22
- const executionStatus = nodeStatus?.status || 'idle';
23
- const isExecuting = executionStatus === 'executing' || executionStatus === 'waiting';
19
+ // Per-id slice subscription so an unrelated node's status update does
20
+ // not re-render this generic node. Wave 21: visual execution state
21
+ // moved to per-theme CSS via the `.executing` class on the wrapper —
22
+ // we keep the subscription so future state-driven CSS toggles can
23
+ // read it without refactoring.
24
+ useNodeStatus(id);
24
25
 
25
26
  // Wave 6 Phase 3e: backend NodeSpec -> legacy fallback
26
27
  const definition = type ? resolveNodeDescription(type) : null;
@@ -97,34 +98,22 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
97
98
  const nodeInputs = getNodeInputs();
98
99
  const nodeOutputs = getNodeOutputs();
99
100
 
100
- // Helper functions for color management
101
+ // Node accent color — feeds the `--node-color` CSS custom property
102
+ // on the wrapper. Handle borders/backgrounds are now owned by
103
+ // `.node-handle` in base.css + per-theme overrides (Wave 26.C).
101
104
  const getNodeColor = () => definition.defaults.color || '#9E9E9E';
102
- const getBorderColor = () => {
103
- const color = getNodeColor();
104
- if (color.startsWith('#')) {
105
- const hex = color.substring(1);
106
- const r = Math.max(0, parseInt(hex.substring(0, 2), 16) - 40);
107
- const g = Math.max(0, parseInt(hex.substring(2, 4), 16) - 40);
108
- const b = Math.max(0, parseInt(hex.substring(4, 6), 16) - 40);
109
- return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
110
- }
111
- return color;
112
- };
113
105
 
114
106
  return (
107
+ // `node` + `selected` co-classes activate per-theme generic-node
108
+ // decorations (wax seal on Renaissance, neon LED on Cyber, etc.).
115
109
  <div
110
+ className={`node ${selected ? 'selected' : ''}`}
116
111
  style={{
112
+ '--node-color': getNodeColor(),
117
113
  position: 'relative',
118
114
  padding: '12px 32px 12px 16px',
119
115
  minWidth: '160px',
120
116
  minHeight: '60px',
121
- borderRadius: '12px',
122
- background: `linear-gradient(135deg, ${getNodeColor()} 0%, ${getBorderColor()} 100%)`,
123
- border: `2px solid ${isExecuting
124
- ? (theme.isDarkMode ? theme.dracula.cyan : '#2563eb')
125
- : selected
126
- ? '#3b82f6'
127
- : getBorderColor()}`,
128
117
  color: 'white',
129
118
  fontFamily: 'system-ui, -apple-system, sans-serif',
130
119
  fontSize: '14px',
@@ -132,19 +121,9 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
132
121
  textAlign: 'center',
133
122
  cursor: 'pointer',
134
123
  transition: 'all 0.2s ease',
135
- boxShadow: isExecuting
136
- ? theme.isDarkMode
137
- ? `0 4px 12px ${theme.dracula.cyan}66, 0 0 0 3px ${theme.dracula.cyan}4D`
138
- : `0 0 0 3px rgba(37, 99, 235, 0.5), 0 4px 16px rgba(37, 99, 235, 0.35)`
139
- : selected
140
- ? `0 8px 25px ${getNodeColor()}40, 0 0 0 2px ${theme.colors.focus}`
141
- : theme.isDarkMode
142
- ? `0 4px 12px ${getNodeColor()}40`
143
- : `0 2px 8px ${getNodeColor()}25, 0 4px 16px rgba(0, 0, 0, 0.08)`,
144
124
  overflow: 'visible',
145
125
  opacity: isDisabled ? 0.5 : 1,
146
- animation: isExecuting ? 'pulse 1.5s ease-in-out infinite' : 'none',
147
- }}
126
+ } as NodeStyle}
148
127
  >
149
128
  {/* Disabled Overlay */}
150
129
  {isDisabled && (
@@ -165,12 +144,14 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
165
144
  <span style={{ fontSize: '24px', opacity: 0.8 }}>||</span>
166
145
  </div>
167
146
  )}
168
- {/* Input Handles - Multiple handles based on node definition */}
147
+ {/* Input Handles - Multiple handles based on node definition.
148
+ Visual styling (background, border) owned by `.node-handle.in`
149
+ in base.css + per-theme overrides. Inline keeps layout only. */}
169
150
  {nodeInputs.map((input, index) => {
170
151
  const totalInputs = nodeInputs.length;
171
- const topPosition = totalInputs === 1 ? '50%' :
152
+ const topPosition = totalInputs === 1 ? '50%' :
172
153
  `${20 + (60 * index) / Math.max(totalInputs - 1, 1)}%`;
173
-
154
+
174
155
  return (
175
156
  <Handle
176
157
  key={`input-${input.name}-${index}`}
@@ -178,6 +159,7 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
178
159
  type="target"
179
160
  position={Position.Left}
180
161
  isConnectable={isConnectable}
162
+ className="node-handle in"
181
163
  style={{
182
164
  position: 'absolute',
183
165
  left: '-6px',
@@ -185,20 +167,20 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
185
167
  transform: 'translateY(-50%)',
186
168
  width: '12px',
187
169
  height: '12px',
188
- backgroundColor: 'rgba(255,255,255,0.9)',
189
- border: `2px solid ${getBorderColor()}`,
190
170
  borderRadius: '50%'
191
171
  }}
192
172
  title={`${input.displayName}: ${input.description}`}
193
173
  />
194
174
  );
195
175
  })}
196
-
197
176
 
198
- {/* Parameters Button */}
177
+
178
+ {/* Parameters Button — visual styles (background, border, hover)
179
+ owned by `.node-gear` in base.css + per-theme overrides.
180
+ Inline keeps positioning + label color only. */}
199
181
  <button
200
182
  onClick={handleParametersClick}
201
- className="absolute top-2 right-2 z-20 flex h-5 w-5 cursor-pointer items-center justify-center rounded-full border-0 bg-white/95 text-[10px] font-semibold shadow-sm transition-all duration-200 hover:scale-115 hover:bg-white hover:shadow-md"
183
+ className="node-gear absolute top-2 right-2 z-20 flex h-5 w-5 cursor-pointer items-center justify-center rounded-full text-[10px] font-semibold transition-all duration-200 hover:scale-115"
202
184
  style={{ color: getNodeColor() }}
203
185
  title="Edit Parameters"
204
186
  >
@@ -229,12 +211,13 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
229
211
  </div>
230
212
 
231
213
 
232
- {/* Output Handles - Multiple handles based on node definition */}
214
+ {/* Output Handles - Multiple handles based on node definition.
215
+ Visual styling owned by `.node-handle.out` in base.css. */}
233
216
  {nodeOutputs.map((output, index) => {
234
217
  const totalOutputs = nodeOutputs.length;
235
- const topPosition = totalOutputs === 1 ? '50%' :
218
+ const topPosition = totalOutputs === 1 ? '50%' :
236
219
  `${20 + (60 * index) / Math.max(totalOutputs - 1, 1)}%`;
237
-
220
+
238
221
  return (
239
222
  <Handle
240
223
  key={`output-${output.name}-${index}`}
@@ -242,6 +225,7 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
242
225
  type="source"
243
226
  position={Position.Right}
244
227
  isConnectable={isConnectable}
228
+ className="node-handle out"
245
229
  style={{
246
230
  position: 'absolute',
247
231
  right: '-6px',
@@ -249,8 +233,6 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
249
233
  transform: 'translateY(-50%)',
250
234
  width: '12px',
251
235
  height: '12px',
252
- backgroundColor: 'rgba(255,255,255,0.9)',
253
- border: `2px solid ${getBorderColor()}`,
254
236
  borderRadius: '50%'
255
237
  }}
256
238
  title={`${output.displayName}: ${output.description}`}