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,5 +1,5 @@
1
1
  {
2
- "_comment": "List node types to show in the Component Palette. Empty list or missing file = all nodes shown (default-on). Node type identifiers must match the 'type' class attribute on the plugin in server/nodes/<category>/<file>.py verbatim.",
2
+ "_comment": "Node visibility config. `enabled_nodes`: positive list shown in normal mode (empty = show all). `disabled_groups` / `disabled_nodes`: ABSOLUTE blocklist enforced in BOTH normal and dev mode — wins over enabled_nodes and over the show_all default. Use disabled_groups to hide every plugin in a backend group (e.g. 'android' hides all 16 Android service nodes + androidTool); use disabled_nodes for one-off type names like 'android_agent' that don't share the group label. Node type identifiers must match the 'type' class attribute on the plugin in server/nodes/<category>/<file>.py verbatim.",
3
3
  "enabled_nodes": [
4
4
  "start",
5
5
  "chatTrigger",
@@ -25,5 +25,20 @@
25
25
 
26
26
  "telegramSend",
27
27
  "telegramReceive"
28
+ ],
29
+ "disabled_groups": [
30
+ "android",
31
+ "email"
32
+ ],
33
+ "disabled_nodes": [
34
+ "android_agent",
35
+ "androidTool"
36
+ ],
37
+ "disabled_credential_categories": [
38
+ "android",
39
+ "email"
40
+ ],
41
+ "disabled_skill_folders": [
42
+ "android_agent"
28
43
  ]
29
44
  }
@@ -62,6 +62,14 @@
62
62
  "codestral-latest": {"input": 0.30, "output": 0.90},
63
63
  "open-mistral-nemo": {"input": 0.15, "output": 0.15},
64
64
  "_default": {"input": 0.20, "output": 0.60}
65
+ },
66
+ "ollama": {
67
+ "_description": "Local inference is free at the API layer — user-borne hardware costs are not tracked here.",
68
+ "_default": {"input": 0.00, "output": 0.00}
69
+ },
70
+ "lmstudio": {
71
+ "_description": "Local inference — same zero-cost convention as ollama.",
72
+ "_default": {"input": 0.00, "output": 0.00}
65
73
  }
66
74
  },
67
75
 
@@ -20,6 +20,10 @@ AI_CHAT_MODEL_TYPES: FrozenSet[str] = frozenset([
20
20
  'deepseekChatModel',
21
21
  'kimiChatModel',
22
22
  'mistralChatModel',
23
+ # Local-server providers (Phase 1 of the LiteLLM adoption — see
24
+ # plans/i-plan-to-implement-nested-orbit.md).
25
+ 'ollamaChatModel',
26
+ 'lmstudioChatModel',
23
27
  ])
24
28
 
25
29
  AI_AGENT_TYPES: FrozenSet[str] = frozenset([
@@ -313,7 +317,7 @@ TRIGGER_TYPES: FrozenSet[str] = EVENT_TRIGGER_TYPES
313
317
  # unlike push-based triggers (WhatsApp, Webhook) that receive events externally.
314
318
  # In deployment mode, these get a dedicated polling loop instead of event_waiter.
315
319
  POLLING_TRIGGER_TYPES: FrozenSet[str] = frozenset([
316
- 'gmailReceive',
320
+ 'googleGmailReceive',
317
321
  'twitterReceive',
318
322
  'emailReceive',
319
323
  ])
@@ -336,7 +340,7 @@ WORKFLOW_TRIGGER_TYPES: FrozenSet[str] = frozenset([
336
340
  'chatTrigger',
337
341
  'taskTrigger',
338
342
  'twitterReceive',
339
- 'gmailReceive',
343
+ 'googleGmailReceive',
340
344
  'telegramReceive',
341
345
  'emailReceive',
342
346
  ])
@@ -348,31 +352,50 @@ WORKFLOW_TRIGGER_TYPES: FrozenSet[str] = frozenset([
348
352
  def detect_ai_provider(node_type: str, parameters: dict = None) -> str:
349
353
  """Detect AI provider from node type or parameters.
350
354
 
355
+ Substring match against the node type's lower-case form. Local-server
356
+ providers (ollama, lmstudio) MUST be checked here — without these
357
+ branches `lmstudioChatModel` falls through to the final ``return
358
+ 'openai'`` and ``execute_chat`` ends up calling api.openai.com with
359
+ the local-server placeholder key, which is the exact symptom users
360
+ see as "401 from OpenAI when I picked LM Studio".
361
+
351
362
  Args:
352
- node_type: The node type string
353
- parameters: Optional parameters dict (used for aiAgent/chatAgent)
363
+ node_type: The node type string (e.g. "ollamaChatModel").
364
+ parameters: Optional parameters dict (used for aiAgent/chatAgent
365
+ where the provider lives in a dropdown, not the type).
354
366
 
355
367
  Returns:
356
- Provider string: 'openai', 'anthropic', 'gemini', 'openrouter', 'groq', or 'cerebras'
368
+ Provider string matching a key in ``services.ai.PROVIDER_CONFIGS``.
357
369
  """
358
370
  # AI Agent types get provider from parameters
359
371
  if node_type in AI_AGENT_TYPES:
360
372
  return (parameters or {}).get('provider', 'openai')
361
- elif 'deepseek' in node_type.lower():
373
+ nt = node_type.lower()
374
+ # Order matters: more-specific tokens first, so e.g. `lm_studio` /
375
+ # `lmstudio` / `lm-studio` all classify before any future generic
376
+ # `openai`-prefixed match could shadow them.
377
+ if 'deepseek' in nt:
362
378
  return 'deepseek'
363
- elif 'kimi' in node_type.lower():
379
+ if 'kimi' in nt:
364
380
  return 'kimi'
365
- elif 'mistral' in node_type.lower():
381
+ if 'mistral' in nt:
366
382
  return 'mistral'
367
- elif 'cerebras' in node_type.lower():
383
+ if 'cerebras' in nt:
368
384
  return 'cerebras'
369
- elif 'groq' in node_type.lower():
385
+ if 'groq' in nt:
370
386
  return 'groq'
371
- elif 'openrouter' in node_type.lower():
387
+ if 'openrouter' in nt:
372
388
  return 'openrouter'
373
- elif 'anthropic' in node_type.lower():
389
+ if 'anthropic' in nt:
374
390
  return 'anthropic'
375
- elif 'gemini' in node_type.lower():
391
+ if 'gemini' in nt:
376
392
  return 'gemini'
377
- else:
378
- return 'openai'
393
+ # Local-server providers — match the LMStudioChatModelNode /
394
+ # OllamaChatModelNode plugin types so the runtime path reads the
395
+ # correct {provider}_proxy credential and the openai SDK is pointed
396
+ # at the user's local server, not api.openai.com.
397
+ if 'lmstudio' in nt or 'lm_studio' in nt or 'lm-studio' in nt:
398
+ return 'lmstudio'
399
+ if 'ollama' in nt:
400
+ return 'ollama'
401
+ return 'openai'
@@ -20,12 +20,12 @@ from core.credentials_database import CredentialsDatabase
20
20
  _clog("core imports done")
21
21
  from services.ai import AIService
22
22
  _clog("AIService imported")
23
- from services.maps import MapsService
23
+ from nodes.location._service import MapsService
24
24
  from services.workflow import WorkflowService
25
25
  _clog("WorkflowService imported")
26
26
  from services.auth import AuthService
27
27
  from services.text import TextService
28
- from services.android_service import AndroidService
28
+ from nodes.android._dispatcher import AndroidService
29
29
  from services.user_auth import UserAuthService
30
30
  from services.compaction import init_compaction_service
31
31
  _clog("all service imports done")
@@ -189,6 +189,7 @@ class CredentialsDatabase:
189
189
  api_key: str,
190
190
  models: Optional[List[str]] = None,
191
191
  session_id: str = "default",
192
+ model_params: Optional[Dict[str, Dict[str, Any]]] = None,
192
193
  ) -> None:
193
194
  """
194
195
  Save encrypted API key.
@@ -198,10 +199,23 @@ class CredentialsDatabase:
198
199
  api_key: Plaintext API key to encrypt and store
199
200
  models: List of available models for this key
200
201
  session_id: Session identifier (default: "default")
202
+ model_params: Optional per-model parameters keyed by model id, e.g.
203
+ ``{"qwen2.5-7b": {"context_length": 8192}}``. Used by local
204
+ providers (Ollama, LM Studio) where the context window depends
205
+ on what the user has loaded — the value is fetched from the
206
+ official SDK during validation. Stored alongside the model
207
+ list under the same JSON column (``model_params`` subkey) so
208
+ ``model_registry.get_context_length()`` can read the real
209
+ ctx instead of a JSON default. Cloud providers leave this
210
+ empty — their per-model params live in ``model_registry.json``
211
+ from OpenRouter.
201
212
  """
202
213
  key_id = f"{session_id}_{provider}"
203
214
  encrypted = self.encryption.encrypt(api_key)
204
215
  key_hash = hashlib.sha256(api_key.encode()).hexdigest()[:16]
216
+ models_blob: Dict[str, Any] = {"models": models or []}
217
+ if model_params:
218
+ models_blob["model_params"] = model_params
205
219
 
206
220
  async with self.get_session() as session:
207
221
  existing = await session.get(EncryptedAPIKey, key_id)
@@ -210,7 +224,7 @@ class CredentialsDatabase:
210
224
  if existing:
211
225
  existing.key_encrypted = encrypted
212
226
  existing.key_hash = key_hash
213
- existing.models = {"models": models or []}
227
+ existing.models = models_blob
214
228
  existing.is_valid = True
215
229
  existing.last_validated = now
216
230
  existing.updated_at = now
@@ -222,7 +236,7 @@ class CredentialsDatabase:
222
236
  session_id=session_id,
223
237
  key_encrypted=encrypted,
224
238
  key_hash=key_hash,
225
- models={"models": models or []},
239
+ models=models_blob,
226
240
  is_valid=True,
227
241
  last_validated=now,
228
242
  )
@@ -230,6 +244,25 @@ class CredentialsDatabase:
230
244
  await session.commit()
231
245
  logger.debug(f"Saved encrypted API key for provider: {provider}")
232
246
 
247
+ async def get_api_key_model_params(
248
+ self, provider: str, session_id: str = "default"
249
+ ) -> Dict[str, Dict[str, Any]]:
250
+ """Return the per-model param map stored alongside the model list.
251
+
252
+ Empty dict if the provider has no entry, no params were stored,
253
+ or the row predates the ``model_params`` column. The runtime
254
+ path (``model_registry.get_context_length`` etc.) consults this
255
+ before falling back to JSON defaults so local-LLM context comes
256
+ from the actual loaded model, not a guess.
257
+ """
258
+ key_id = f"{session_id}_{provider}"
259
+ async with self.get_session() as session:
260
+ row = await session.get(EncryptedAPIKey, key_id)
261
+ if not row or not row.models:
262
+ return {}
263
+ params = row.models.get("model_params") or {}
264
+ return params if isinstance(params, dict) else {}
265
+
233
266
  async def get_api_key(
234
267
  self, provider: str, session_id: str = "default"
235
268
  ) -> Optional[str]:
@@ -32,9 +32,10 @@ class WebSocketLogHandler(logging.Handler):
32
32
  self._source_map = {
33
33
  'services.workflow': 'workflow',
34
34
  'services.ai': 'ai',
35
- 'services.android': 'android',
36
- 'services.whatsapp_service': 'whatsapp',
37
- 'routers.android': 'android',
35
+ 'nodes.android._relay': 'android',
36
+ 'nodes.android._dispatcher': 'android',
37
+ 'nodes.android._router': 'android',
38
+ 'nodes.whatsapp._service': 'whatsapp',
38
39
  'routers.websocket': 'websocket',
39
40
  'routers.workflow': 'workflow',
40
41
  'services.execution': 'execution',
package/server/main.py CHANGED
@@ -40,7 +40,7 @@ from core.config import Settings
40
40
  from core.logging import configure_logging, get_logger, setup_websocket_logging, shutdown_websocket_logging
41
41
  from core.tracing import init_tracing
42
42
  _startup_log("Importing routers...")
43
- from routers import workflow, database, maps, nodejs_compat, android, websocket, webhook, auth, twitter, google, credentials, schemas
43
+ from routers import workflow, database, nodejs_compat, websocket, webhook, auth, credentials, schemas
44
44
  _startup_log("All imports complete")
45
45
 
46
46
  # Initialize settings, logging, and tracing.
@@ -75,18 +75,18 @@ async def lifespan(app: FastAPI):
75
75
  # Side-effect import; the package __init__ walks its submodules.
76
76
  import nodes # noqa: F401
77
77
 
78
+ # Side-effect import: services/cli_agent/__init__.py self-registers
79
+ # `cli_login` / `cli_auth_status` into `services.ws_handler_registry`.
80
+ import services.cli_agent # noqa: F401
81
+
78
82
  # Wire dependency injection
79
83
  container.wire(modules=[
80
84
  "routers.workflow",
81
85
  "routers.database",
82
- "routers.maps",
83
86
  "routers.nodejs_compat",
84
- "routers.android",
85
87
  "routers.websocket",
86
88
  "routers.webhook",
87
89
  "routers.auth",
88
- "routers.twitter",
89
- "routers.google",
90
90
  "middleware.auth"
91
91
  ])
92
92
 
@@ -293,6 +293,35 @@ async def lifespan(app: FastAPI):
293
293
  else:
294
294
  _startup_log("[Temporal] Disabled")
295
295
 
296
+ # Enter the CLI-agent MCP server's lifespan so its
297
+ # StreamableHTTPSessionManager task group is initialised (Starlette
298
+ # does NOT auto-propagate lifespans across `app.mount`). Stored on
299
+ # `app.state` so shutdown can exit the context cleanly.
300
+ cli_mcp_lifespan_ctx = None
301
+ try:
302
+ from services.cli_agent.mcp_server import get_mcp_app as _get_cli_mcp_app
303
+ _cli_mcp_app = _get_cli_mcp_app()
304
+ cli_mcp_lifespan_ctx = _cli_mcp_app.router.lifespan_context(_cli_mcp_app)
305
+ await cli_mcp_lifespan_ctx.__aenter__()
306
+ app.state.cli_mcp_lifespan_ctx = cli_mcp_lifespan_ctx
307
+ _startup_log("[CLI MCP] StreamableHTTP session manager initialised")
308
+ except Exception as exc:
309
+ logger.warning("[CLI MCP] lifespan init failed: %s", exc)
310
+
311
+ # One-time status-broadcaster refresh: populates the cache and runs
312
+ # the load-bearing auto-reconnects (Telegram bot via stored token,
313
+ # Android relay via stored pairing). Per-WS-client refresh used to
314
+ # do this on every connect, which produced an M-by-N storm under
315
+ # PartySocket's auto-reconnect on every page nav / network blip --
316
+ # the refresh now runs ONCE at startup, and state changes after
317
+ # that flow through the originating code path's event-driven
318
+ # broadcasts (whatsapp `_handle_event`, telegram `_broadcast_status`,
319
+ # OAuth callbacks, android relay broadcaster).
320
+ #
321
+ # Spawned as a background task so a slow upstream (Telegram getMe,
322
+ # Twitter token validation) doesn't block lifespan startup.
323
+ asyncio.create_task(get_status_broadcaster()._refresh_all_services())
324
+
296
325
  _startup_log("All services initialized")
297
326
  print("Application startup complete", flush=True)
298
327
  yield
@@ -334,11 +363,11 @@ async def lifespan(app: FastAPI):
334
363
  await _proxy_svc.shutdown()
335
364
 
336
365
  # Close Android relay client (prevents "Unclosed client session" warning)
337
- from services.android.manager import close_relay_client
366
+ from nodes.android._relay.manager import close_relay_client
338
367
  await close_relay_client(clear_stored_session=False)
339
368
 
340
369
  # Shut down agent-browser daemon (prevents orphaned processes and EBUSY file locks)
341
- from services.browser_service import shutdown_browser_service
370
+ from nodes.browser._service import shutdown_browser_service
342
371
  await shutdown_browser_service()
343
372
 
344
373
  # Kill all managed processes (process manager node)
@@ -361,6 +390,15 @@ async def lifespan(app: FastAPI):
361
390
  logger.info("Execution recovery sweeper stopped")
362
391
 
363
392
  shutdown_scheduler() # Stop APScheduler
393
+
394
+ # Exit the CLI-agent MCP server's lifespan
395
+ cli_mcp_lifespan_ctx = getattr(app.state, "cli_mcp_lifespan_ctx", None)
396
+ if cli_mcp_lifespan_ctx is not None:
397
+ try:
398
+ await cli_mcp_lifespan_ctx.__aexit__(None, None, None)
399
+ except Exception as exc:
400
+ logger.debug("[CLI MCP] lifespan shutdown: %s", exc)
401
+
364
402
  await container.cache().shutdown()
365
403
  await container.database().shutdown()
366
404
  logger.info("Services shutdown complete")
@@ -415,20 +453,68 @@ app.add_middleware(
415
453
  expose_headers=["*"],
416
454
  )
417
455
 
418
- # Include routers
456
+ # Include framework-level routers (everything that's NOT plugin-specific
457
+ # stays here). Plugin-owned routers self-register via
458
+ # ``services.ws_handler_registry.register_router`` from their plugin
459
+ # folder's ``__init__.py`` and are mounted via the loop below — main.py
460
+ # never imports a migrated plugin module by name.
419
461
  app.include_router(auth.router) # Auth routes (login, register, logout, status)
420
462
  app.include_router(nodejs_compat.router) # Node.js compatibility (includes root endpoints)
421
463
  app.include_router(workflow.router)
422
464
  app.include_router(database.router)
423
- app.include_router(maps.router)
424
- app.include_router(android.router)
425
465
  app.include_router(websocket.router)
426
- app.include_router(webhook.router)
427
- app.include_router(twitter.router) # Twitter/X OAuth routes
428
- app.include_router(google.router) # Google Workspace OAuth routes (Gmail, Calendar, Drive, Sheets, Tasks, Contacts)
429
466
  app.include_router(credentials.router) # Credentials panel - lazy per-tile icon endpoint (n8n pattern)
430
467
  app.include_router(schemas.router) # Per-node output schema endpoint (GET /api/schemas/nodes/{type}.json)
431
468
 
469
+ # Routers awaiting migration into their plugin folders. As each plugin
470
+ # moves to the self-contained pattern (nodes/<plugin>/_router.py +
471
+ # register_router from __init__.py), the corresponding line below is
472
+ # removed. Tracked in the plugin-extraction plan.
473
+ app.include_router(webhook.router)
474
+ # Twitter, Google, Android, and Maps routers moved (Maps deleted in
475
+ # Wave 11.I milestone N -- all four endpoints were dead, the validate-key
476
+ # path now flows through CREDENTIAL_REGISTRY's GoogleMapsCredential._probe).
477
+
478
+ # Plugin-registered routers — populated by `nodes/<plugin>/__init__.py`
479
+ # at import time via `register_router(...)`. Plugins are imported during
480
+ # the node-discovery walk on app startup; iterating here picks up
481
+ # anything that registered.
482
+ from services.ws_handler_registry import get_routers as _get_plugin_routers
483
+ for _plugin_router in _get_plugin_routers():
484
+ app.include_router(_plugin_router)
485
+
486
+
487
+ # ---------------------------------------------------------------------------
488
+ # CLI agent IDE MCP server (VSCode pattern)
489
+ # ---------------------------------------------------------------------------
490
+ #
491
+ # Spawned Claude Code / Codex CLI sessions auto-discover this server via
492
+ # a per-batch lockfile (~/.claude/ide/<pid>.lock) and call tools like
493
+ # mcp__machina__getSkill / getCredential / broadcastLog over MCP-over-HTTP.
494
+ # Bearer-token auth scoped per batch (see services/cli_agent/mcp_server.py).
495
+ try:
496
+ from services.cli_agent.mcp_server import get_mcp_app as _get_cli_mcp_app
497
+ _cli_mcp_app = _get_cli_mcp_app()
498
+ app.mount("/mcp/ide", _cli_mcp_app)
499
+ logger.info("[main] mounted CLI agent MCP server at /mcp/ide")
500
+ except Exception as exc: # pragma: no cover — defensive; MCP must not block startup
501
+ logger.warning("[main] failed to mount CLI agent MCP server: %s", exc)
502
+
503
+
504
+ # Stale lockfile sweep on startup — mirrors VSCode's behavior. PIDs in
505
+ # leftover lockfiles that are no longer alive get cleaned up.
506
+ @app.on_event("startup")
507
+ async def _sweep_cli_lockfiles_on_startup() -> None:
508
+ try:
509
+ from services.cli_agent.config import list_provider_names, get_provider_config
510
+ from services.cli_agent.lockfile import sweep_stale_lockfiles
511
+ for name in list_provider_names():
512
+ cfg = get_provider_config(name)
513
+ if cfg and cfg.ide_lockfile_dir:
514
+ sweep_stale_lockfiles(cfg.ide_lockfile_dir)
515
+ except Exception as exc:
516
+ logger.debug("[main] CLI lockfile sweep failed: %s", exc)
517
+
432
518
 
433
519
  @app.get("/health")
434
520
  async def health_check():
@@ -49,6 +49,7 @@ class NodeMetadata(TypedDict, total=False):
49
49
  handles: list[NodeHandle] # replaces AGENT_CONFIGS topology
50
50
  credentials: list[str] # provider keys
51
51
  hideOutputHandle: bool # replaces NO_OUTPUT_NODE_TYPES
52
+ hideInputHandle: bool # auto-derived for usable_as_tool=True nodes
52
53
  visibility: Literal["all", "normal", "dev"] # replaces SIMPLE_MODE_CATEGORIES
53
54
 
54
55
 
@@ -3,19 +3,21 @@
3
3
  "version": "1.0.0",
4
4
  "description": "Persistent Node.js server for JavaScript/TypeScript code execution",
5
5
  "type": "module",
6
- "main": "src/index.ts",
6
+ "main": "dist/index.js",
7
7
  "scripts": {
8
- "start": "tsx src/index.ts",
8
+ "build": "esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --packages=external --outfile=dist/index.js",
9
+ "start": "node dist/index.js",
9
10
  "dev": "tsx watch src/index.ts"
10
11
  },
11
12
  "dependencies": {
12
- "express": "^5.0.0"
13
+ "express": "^5.2.1"
13
14
  },
14
15
  "devDependencies": {
15
- "@types/express": "^5.0.0",
16
- "@types/node": "^22.0.0",
16
+ "@types/express": "^5.0.6",
17
+ "@types/node": "^22.19.18",
18
+ "esbuild": "^0.25.0",
17
19
  "tsx": "^4.21.0",
18
- "typescript": "^5.7.0"
20
+ "typescript": "^5.9.3"
19
21
  },
20
22
  "engines": {
21
23
  "node": ">=22.0.0"
@@ -5,7 +5,7 @@
5
5
 
6
6
  import express, { Request, Response, NextFunction } from 'express';
7
7
  import vm from 'node:vm';
8
- import { execSync } from 'node:child_process';
8
+ import { execFileSync } from 'node:child_process';
9
9
  import path from 'node:path';
10
10
  import { fileURLToPath } from 'node:url';
11
11
 
@@ -65,6 +65,14 @@ app.post('/execute', (req: Request, res: Response) => {
65
65
 
66
66
  try {
67
67
  const context = vm.createContext(sandbox);
68
+ // This service IS the sandboxed JS executor for the pythonExecutor /
69
+ // javascriptExecutor workflow nodes. Node's `vm` is not a security
70
+ // boundary (per https://nodejs.org/api/vm.html#vmcreatecontextcontextobject-options);
71
+ // the deployment context is: server binds to localhost only (line 16,
72
+ // default 'localhost') and is invoked exclusively by the same-machine
73
+ // Python backend via NodeJSClient. Public network exposure is the
74
+ // operator's responsibility.
75
+ // codeql[js/code-injection]
68
76
  vm.runInContext(code, context, { timeout, filename: 'user-code.js' });
69
77
 
70
78
  res.json({
@@ -83,7 +91,11 @@ app.post('/execute', (req: Request, res: Response) => {
83
91
  }
84
92
  });
85
93
 
86
- // Install packages - package list from request
94
+ // Install packages - package list from request.
95
+ // Localhost-only service (see server.listen at the bottom); same trust
96
+ // boundary as the /execute sandbox. No request-rate limiting because the
97
+ // only caller is the same-machine Python backend.
98
+ // codeql[js/missing-rate-limiting]
87
99
  app.post('/packages/install', (req: Request, res: Response) => {
88
100
  const { packages } = req.body as { packages: string[] };
89
101
 
@@ -100,17 +112,22 @@ app.post('/packages/install', (req: Request, res: Response) => {
100
112
  }
101
113
 
102
114
  try {
103
- execSync(`npm install ${packages.join(' ')}`, { cwd: USER_PACKAGES_DIR, timeout: 60000 });
115
+ // execFileSync (argv array, no shell) instead of execSync with template
116
+ // string. The regex above already validates names, but going through
117
+ // execFileSync removes the shell from the path entirely as
118
+ // defense-in-depth.
119
+ execFileSync('npm', ['install', ...packages], { cwd: USER_PACKAGES_DIR, timeout: 60000 });
104
120
  res.json({ success: true, message: `Installed: ${packages.join(', ')}` });
105
121
  } catch (error) {
106
122
  res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error) });
107
123
  }
108
124
  });
109
125
 
110
- // List packages
126
+ // List packages — same localhost-only trust boundary as above.
127
+ // codeql[js/missing-rate-limiting]
111
128
  app.get('/packages', (_req: Request, res: Response) => {
112
129
  try {
113
- const output = execSync('npm list --json --depth=0', { cwd: USER_PACKAGES_DIR, encoding: 'utf-8' });
130
+ const output = execFileSync('npm', ['list', '--json', '--depth=0'], { cwd: USER_PACKAGES_DIR, encoding: 'utf-8' });
114
131
  res.json({ success: true, packages: JSON.parse(output).dependencies || {} });
115
132
  } catch {
116
133
  res.json({ success: true, packages: {} });
@@ -234,7 +234,7 @@ server/nodes/telegram/
234
234
  └── telegram_receive.py # TriggerNode
235
235
  ```
236
236
 
237
- ### Five generic registries to plug into
237
+ ### Six generic registries to plug into
238
238
 
239
239
  Telegram's `__init__.py` is the canonical wiring example. Adding any
240
240
  of these concerns to your plugin is one `register_*` call from your
@@ -243,14 +243,26 @@ package's `__init__.py` — the consumer never imports your folder.
243
243
  | Concern | Where to register | What it does |
244
244
  |---|---|---|
245
245
  | Credentials-modal WebSocket commands | `services.ws_handler_registry.register_ws_handlers({"<type>": handler})` | Adds `<type>` to the central WS dispatcher (no router edits) |
246
+ | FastAPI router (Wave 11.I) | `services.ws_handler_registry.register_router(router, name="<name>")` | Plugin's HTTP router mounts via the plugin loop in `main.py`; sibling concern in the same file as `register_ws_handlers` |
246
247
  | Event-trigger filter | `services.event_waiter.register_filter_builder(node_type, fn)` | Plugs into `FILTER_BUILDERS` for `event_waiter.build_filter()` |
247
248
  | Trigger pre-execution check | `services.event_waiter.register_trigger_precheck(node_type, async_fn)` | Generic `triggers.py` handler runs `run_trigger_precheck` before entering the wait loop |
248
249
  | Service-status refresh on WS connect | `services.status_broadcaster.register_service_refresh(async_callback)` | Callback runs once per `_refresh_all_services` cycle |
249
250
  | Output schema | `services.node_output_schemas.register_output_schema(node_type, ModelClass)` | Avoids declaring a duplicate `Output` class in the central schema file |
250
251
 
251
- All five are idempotent (same callable / class for the same key is a
252
+ All six are idempotent (same callable / class for the same key is a
252
253
  no-op; conflicts raise `ValueError`).
253
254
 
255
+ ### Credential validation (Wave 11.I)
256
+
257
+ The credential-validator dispatch is a sibling concern, handled by the
258
+ existing `services/plugin/credential.py:Credential` base class. Your
259
+ `Credential` subclass overrides `_probe(api_key) -> ProbeResult` (or,
260
+ in rare cases like local-LLM 2-storage, the whole `validate(data)
261
+ -> dict` classmethod). Maps, Apify, all 9 cloud LLM providers, and
262
+ both local-LLM providers (Ollama / LM Studio) all dispatch through the
263
+ same scaffold — no `_SPECIAL_PROVIDER_VALIDATORS` dict in
264
+ `routers/websocket.py`.
265
+
254
266
  ### When NOT to use this shape
255
267
 
256
268
  Don't create `_service.py` / `_handlers.py` siblings unless the plugin
@@ -284,8 +296,11 @@ Full reference: [docs-internal/plugin_system.md → "Self-contained plugin folde
284
296
  - **Don't edit `server/nodes/__init__.py`** — it's a pure auto-discovery
285
297
  walker. Adding a new folder doesn't need edits either; `pkgutil` finds
286
298
  subpackages automatically.
287
- - **Don't instantiate services directly.** Use
288
- `from core.container import container; svc = container.X()`.
299
+ - **Don't instantiate services directly.** Use the canonical lazy
300
+ helpers in `services.plugin.deps`:
301
+ `from services.plugin.deps import get_auth_service, get_database, get_cache, get_ai_service, get_text_service, get_maps_service, get_android_service`.
302
+ These resolve the singleton from the DI container at call time
303
+ (test monkeypatching depends on call-time lookup — never memoise).
289
304
  - **Don't call `auth_service.get_api_key(...)` from plugins.** Declare
290
305
  a `Credential` subclass; the `Connection` facade / service layer
291
306
  resolves tokens.
@@ -314,6 +329,18 @@ Full reference: [docs-internal/plugin_system.md → "Self-contained plugin folde
314
329
  subclasses and auto-inject at execution time. Plugins that need the
315
330
  injected key read `ctx.raw["_raw_parameters"]["api_key"]` — it's
316
331
  stashed before Pydantic validation strips it.
332
+ - **`isConfigNode` is auto-derived — don't declare it.** Plugins
333
+ whose `group` tuple contains `"memory"` or `"tool"` automatically
334
+ export `uiHints.isConfigNode: True` (set by `_derive_auto_ui_hints`
335
+ in `services/plugin/base.py`). The flag tells the frontend that the
336
+ node's parameter panel inherits its parent's main inputs instead
337
+ of showing direct upstream connections. If you genuinely want to
338
+ opt out, declare `ui_hints = {"isConfigNode": False}` — explicit
339
+ always wins via `dict.update`. Adding a new auto-derivation rule
340
+ goes in `_derive_auto_ui_hints`, not in individual plugins; new
341
+ uiHint flags must also be added to `INodeUIHints` in
342
+ `client/src/types/INodeProperties.ts` and to the `known` set in
343
+ `server/tests/test_node_spec.py::test_ui_hints_only_carry_known_flags`.
317
344
 
318
345
  ---
319
346
 
@@ -152,3 +152,5 @@ async def prepare_agent_call(
152
152
  "context": context,
153
153
  "database": database,
154
154
  }
155
+
156
+
@@ -38,6 +38,9 @@ class SpecializedAgentParams(BaseModel):
38
38
  provider: Literal[
39
39
  "openai", "anthropic", "gemini", "openrouter",
40
40
  "groq", "cerebras", "deepseek", "kimi", "mistral",
41
+ # Local-server providers — see ai_agent.Params for the proxy_url
42
+ # rationale. Same fix; same reason.
43
+ "ollama", "lmstudio",
41
44
  ] = "openai"
42
45
  model: str = Field(
43
46
  default="", json_schema_extra={"placeholder": "Select a model..."},
@@ -107,12 +110,12 @@ class SpecializedAgentBase(ActionNode, abstract=True):
107
110
  pre-dispatch flow. Team-lead teammate injection happens inside
108
111
  ``prepare_agent_call`` based on ``self.type``.
109
112
  """
110
- from core.container import container
113
+ from services.plugin.deps import get_ai_service, get_database
111
114
 
112
115
  from ._inline import prepare_agent_call
113
116
 
114
- ai_service = container.ai_service()
115
- database = container.database()
117
+ ai_service = get_ai_service()
118
+ database = get_database()
116
119
  kwargs = await prepare_agent_call(
117
120
  node_id=ctx.node_id, node_type=self.type,
118
121
  parameters=params.model_dump(),