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
@@ -109,6 +109,13 @@ def detect_provider_from_model(model: str) -> str:
109
109
 
110
110
 
111
111
  def is_model_valid_for_provider(model: str, provider: str) -> bool:
112
+ # Open-world providers — OpenRouter is a multi-vendor proxy, ollama
113
+ # and lmstudio serve user-installed local models whose names don't
114
+ # match any "lmstudio"/"ollama" substring. Treat as always-valid;
115
+ # the upstream API will 404 a genuinely missing model. See the
116
+ # mirror in services/ai.py for the full rationale.
117
+ if provider in ('openrouter', 'ollama', 'lmstudio'):
118
+ return True
112
119
  cfg = PROVIDER_CONFIGS.get(provider)
113
120
  if not cfg:
114
121
  return True
@@ -12,8 +12,14 @@ logger = get_logger(__name__)
12
12
  # Providers with dedicated SDK implementations
13
13
  _DEDICATED_PROVIDERS = frozenset({"anthropic", "openai", "gemini", "openrouter"})
14
14
 
15
- # All providers supported by native SDK path (dedicated + OpenAI-compatible via base_url)
16
- NATIVE_PROVIDERS = _DEDICATED_PROVIDERS | frozenset({"xai", "deepseek", "kimi", "mistral"})
15
+ # All providers supported by native SDK path (dedicated + OpenAI-compatible via base_url).
16
+ # Local servers (ollama, lmstudio) expose OpenAI-compatible /v1 endpoints, so they fall
17
+ # through to OpenAIProvider with base_url from llm_defaults.json — same path as deepseek/
18
+ # kimi/mistral. The user's custom URL rides via the existing {provider}_proxy credential.
19
+ NATIVE_PROVIDERS = _DEDICATED_PROVIDERS | frozenset({
20
+ "xai", "deepseek", "kimi", "mistral",
21
+ "ollama", "lmstudio",
22
+ })
17
23
 
18
24
 
19
25
  def create_provider(
@@ -0,0 +1,52 @@
1
+ """Conversation-memory utilities — package entrypoint.
2
+
3
+ Reorganised from the single-file ``services/memory.py`` so each
4
+ storage format / concern owns its own module:
5
+
6
+ - :mod:`services.memory.markdown` — markdown parse/append/trim
7
+ (used by aiAgent / chatAgent / deep_agent / rlm_agent)
8
+ - :mod:`services.memory.jsonl` — Anthropic Messages JSONL
9
+ parse/append/trim (used by claude_code_agent's session bridge)
10
+ - :mod:`services.memory.vector_store` — per-session
11
+ ``InMemoryVectorStore`` for long-term archival
12
+ - :mod:`services.memory.state` — orchestration: clear every store
13
+ keyed by an agent's conversational scope
14
+
15
+ Public surface is re-exported here so existing
16
+ ``from services.memory import …`` imports keep working — the file
17
+ became a package in-place.
18
+ """
19
+
20
+ from services.memory.markdown import (
21
+ append_to_memory_markdown,
22
+ parse_memory_markdown,
23
+ trim_markdown_window,
24
+ )
25
+ from services.memory.vector_store import (
26
+ _memory_vector_stores,
27
+ get_memory_vector_store,
28
+ )
29
+ from services.memory.state import clear_agent_session_state
30
+ from services.memory.jsonl import (
31
+ append_message,
32
+ parse_jsonl,
33
+ trim_window,
34
+ )
35
+
36
+ __all__ = [
37
+ # Markdown helpers — used by every agent bridge (aiAgent /
38
+ # chatAgent / deep_agent / rlm_agent / claude_code_agent).
39
+ "parse_memory_markdown",
40
+ "append_to_memory_markdown",
41
+ "trim_markdown_window",
42
+ # Vector-store helpers.
43
+ "_memory_vector_stores",
44
+ "get_memory_vector_store",
45
+ # Orchestration.
46
+ "clear_agent_session_state",
47
+ # JSONL helpers — standalone primitive, not currently used by any
48
+ # agent bridge but tested + kept for future SDK migration.
49
+ "parse_jsonl",
50
+ "append_message",
51
+ "trim_window",
52
+ ]
@@ -0,0 +1,80 @@
1
+ """Standard-JSONL conversation-memory helpers.
2
+
3
+ Lines are Anthropic Messages API objects: ``{"role": "user"|"assistant",
4
+ "content": str | List[ContentBlock], ...}``. Extra metadata
5
+ (``timestamp``, ``session_id``, ``model``, ...) rides alongside
6
+ ``role`` / ``content`` and is preserved on round-trip; standard parsers
7
+ (Anthropic SDK, LangChain converters) ignore unknown keys.
8
+
9
+ Used by :mod:`nodes.agent.claude_code_agent` (session memory) via
10
+ :func:`services.cli_agent.service.AICliService.run_batch`.
11
+ """
12
+
13
+ import json
14
+ from typing import Any, List, Tuple
15
+
16
+ from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
17
+
18
+
19
+ def parse_jsonl(text: str) -> List[BaseMessage]:
20
+ """Standard JSONL -> LangChain ``BaseMessage`` list.
21
+
22
+ Tool-call content blocks collapse to text; rows with unknown roles
23
+ or unparseable JSON are skipped (forward compatibility).
24
+ """
25
+ if not text:
26
+ return []
27
+ out: List[BaseMessage] = []
28
+ for line in text.splitlines():
29
+ line = line.strip()
30
+ if not line:
31
+ continue
32
+ try:
33
+ obj = json.loads(line)
34
+ except json.JSONDecodeError:
35
+ continue
36
+ role = obj.get("role")
37
+ content = obj.get("content")
38
+ if isinstance(content, list):
39
+ content = " ".join(
40
+ blk.get("text", "") if isinstance(blk, dict) else str(blk)
41
+ for blk in content
42
+ if isinstance(blk, dict) and blk.get("type") == "text"
43
+ )
44
+ if not isinstance(content, str):
45
+ continue
46
+ if role == "user":
47
+ out.append(HumanMessage(content=content))
48
+ elif role == "assistant":
49
+ out.append(AIMessage(content=content))
50
+ return out
51
+
52
+
53
+ def append_message(
54
+ text: str, role: str, content: str, **metadata: Any,
55
+ ) -> str:
56
+ """Append one Anthropic Messages-format line to a JSONL string.
57
+
58
+ Metadata fields ride alongside ``role`` / ``content``. Always emits
59
+ a trailing newline so successive appends concatenate cleanly.
60
+ """
61
+ line = json.dumps(
62
+ {"role": role, "content": content, **metadata}, ensure_ascii=False,
63
+ )
64
+ if text and not text.endswith("\n"):
65
+ text = text + "\n"
66
+ return (text or "") + line + "\n"
67
+
68
+
69
+ def trim_window(text: str, window_size: int) -> Tuple[str, List[str]]:
70
+ """Keep the last ``window_size * 2`` lines (~ N user/assistant
71
+ pairs). Returns ``(trimmed, removed)``. Removed lines are returned
72
+ verbatim so callers can hand them to the long-term vector store.
73
+ """
74
+ lines = [ln for ln in (text or "").splitlines() if ln.strip()]
75
+ keep = window_size * 2
76
+ if len(lines) <= keep:
77
+ return text, []
78
+ removed = lines[:-keep]
79
+ trimmed = "\n".join(lines[-keep:]) + "\n"
80
+ return trimmed, removed
@@ -0,0 +1,65 @@
1
+ """Markdown-based conversation-memory helpers.
2
+
3
+ Parse, append, and trim conversation history stored in the bespoke
4
+ ``### **Human/Assistant** (timestamp)`` markdown format. Used by
5
+ aiAgent, chatAgent, deep_agent, and rlm_agent for persistent memory
6
+ across turns. The claude_code_agent bridge uses the JSONL helpers in
7
+ :mod:`services.memory.jsonl` instead.
8
+ """
9
+
10
+ import re
11
+ from datetime import datetime
12
+ from typing import List
13
+
14
+ from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
15
+
16
+
17
+ def parse_memory_markdown(content: str) -> List[BaseMessage]:
18
+ """Parse markdown memory content into LangChain messages.
19
+
20
+ Markdown format:
21
+ ### **Human** (timestamp)
22
+ message content
23
+
24
+ ### **Assistant** (timestamp)
25
+ response content
26
+ """
27
+ messages: List[BaseMessage] = []
28
+ pattern = r'### \*\*(Human|Assistant)\*\*[^\n]*\n(.*?)(?=\n### \*\*|$)'
29
+ for role, text in re.findall(pattern, content, re.DOTALL):
30
+ text = text.strip()
31
+ if text:
32
+ msg_class = HumanMessage if role == "Human" else AIMessage
33
+ messages.append(msg_class(content=text))
34
+ return messages
35
+
36
+
37
+ def append_to_memory_markdown(content: str, role: str, message: str) -> str:
38
+ """Append a message to markdown memory content."""
39
+ ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
40
+ label = "Human" if role == "human" else "Assistant"
41
+ entry = f"\n### **{label}** ({ts})\n{message}\n"
42
+ # Remove the empty-state placeholder if present.
43
+ return content.replace("*No messages yet.*\n", "") + entry
44
+
45
+
46
+ def trim_markdown_window(content: str, window_size: int) -> tuple:
47
+ """Keep the last ``window_size`` message PAIRS; return
48
+ ``(trimmed_content, removed_texts)``. Removed texts are returned
49
+ so callers can hand them to the long-term vector store."""
50
+ pattern = r'(### \*\*(Human|Assistant)\*\*[^\n]*\n.*?)(?=\n### \*\*|$)'
51
+ blocks = [m[0] for m in re.findall(pattern, content, re.DOTALL)]
52
+
53
+ if len(blocks) <= window_size * 2:
54
+ return content, []
55
+
56
+ keep = blocks[-(window_size * 2):]
57
+ removed = blocks[:-(window_size * 2)]
58
+
59
+ removed_texts: List[str] = []
60
+ for block in removed:
61
+ match = re.search(r"\n(.*)$", block, re.DOTALL)
62
+ if match:
63
+ removed_texts.append(match.group(1).strip())
64
+
65
+ return "# Conversation History\n" + "\n".join(keep), removed_texts
@@ -0,0 +1,112 @@
1
+ """Cross-store agent-session state-clear orchestration.
2
+
3
+ "Memory" from the user's perspective is not just the markdown
4
+ transcript — it's every piece of state an agent reuses across iterations
5
+ of a conversation. ``simpleMemory.memory_content`` is the visible part;
6
+ the long-term vector store and ``TodoService`` plan-work-update lists
7
+ are the invisible parts that quietly bloat subsequent runs (notably
8
+ ``task_agent``, whose default skill bundle instructs the LLM to read
9
+ accumulated todos every run).
10
+
11
+ When a ``memory_node_id`` is provided, the simpleMemory node's own
12
+ fields (``memory_content``, ``memory_jsonl``, ``last_session_id``) are
13
+ also reset to defaults via ``database.save_node_parameters`` so the
14
+ claude_code_agent JSONL bridge surface clears alongside.
15
+ """
16
+
17
+ from typing import Any, Dict, List, Optional
18
+
19
+ from core.logging import get_logger
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ DEFAULT_MEMORY_CONTENT = "# Conversation History\n\n*No messages yet.*\n"
25
+
26
+
27
+ async def clear_agent_session_state(
28
+ session_id: str,
29
+ workflow_id: str = None,
30
+ clear_long_term: bool = False,
31
+ memory_node_id: Optional[str] = None,
32
+ ) -> Dict[str, Any]:
33
+ """Clear every store keyed by an agent's conversational scope.
34
+
35
+ ``TodoService`` is keyed by ``ctx.workflow_id or ctx.node_id or
36
+ "default"`` (see ``server/nodes/tool/write_todos.py``). We clear all
37
+ three candidate keys to match whichever fallback the agent actually
38
+ used at write time.
39
+
40
+ Args:
41
+ session_id: ``simpleMemory`` node's ``session_id`` parameter.
42
+ workflow_id: Active workflow id (passed by the frontend so we
43
+ can clear ``TodoService`` entries written under it).
44
+ clear_long_term: When ``True``, drop the per-session vector
45
+ store too.
46
+ memory_node_id: When provided, reset the simpleMemory node's
47
+ ``memory_content`` to the default placeholder and wipe
48
+ ``memory_jsonl`` + ``last_session_id``. Without this the
49
+ JSONL bridge fields stay populated and ``--resume`` retries
50
+ stale UUIDs. Frontend-driven legacy clears omit this and
51
+ handle markdown reset client-side.
52
+
53
+ Returns:
54
+ Dict with ``cleared_vector_store`` (bool), ``cleared_todo_keys``
55
+ (list[str]), and ``cleared_memory_node`` (bool) for
56
+ caller-visible diagnostics.
57
+ """
58
+ # The live vector-store cache lives in ``services.ai`` (the dict in
59
+ # ``services.memory.vector_store`` is dormant — future cleanup will
60
+ # consolidate). Lazy import keeps ``services.ai``'s heavy LangChain
61
+ # deps off the hot path.
62
+ from services.ai import _memory_vector_stores as _live_vector_stores
63
+ from services.todo_service import get_todo_service
64
+
65
+ cleared_vector_store = False
66
+ if clear_long_term and session_id in _live_vector_stores:
67
+ del _live_vector_stores[session_id]
68
+ cleared_vector_store = True
69
+ logger.info(f"[Memory] Cleared vector store for session '{session_id}'")
70
+
71
+ todo_service = get_todo_service()
72
+ cleared_todo_keys: List[str] = []
73
+ seen = set()
74
+ for key in (workflow_id, session_id, "default"):
75
+ if key and key not in seen:
76
+ seen.add(key)
77
+ todo_service.clear(key)
78
+ cleared_todo_keys.append(key)
79
+
80
+ cleared_memory_node = False
81
+ if memory_node_id:
82
+ from services.plugin.deps import get_database
83
+
84
+ db = get_database()
85
+ params = await db.get_node_parameters(memory_node_id) or {}
86
+ params["memory_content"] = DEFAULT_MEMORY_CONTENT
87
+ # Wipe `last_session_id` so claude_code_agent starts a fresh
88
+ # session on next spawn instead of `--resume`-ing into the now-
89
+ # cleared transcript. Drop any orphan JSONL field from the
90
+ # earlier bridge revision.
91
+ params["last_session_id"] = None
92
+ params.pop("memory_jsonl", None)
93
+ await db.save_node_parameters(memory_node_id, params)
94
+ cleared_memory_node = True
95
+ logger.info(
96
+ "[Memory] Cleared simpleMemory node fields memory_node=%s "
97
+ "(memory_content reset + last_session_id wiped)",
98
+ memory_node_id,
99
+ )
100
+
101
+ logger.info(
102
+ "[Memory] Cleared agent session state session=%s workflow_id=%s "
103
+ "vector_store=%s todo_keys=%s memory_node=%s",
104
+ session_id, workflow_id, cleared_vector_store, cleared_todo_keys,
105
+ cleared_memory_node,
106
+ )
107
+
108
+ return {
109
+ "cleared_vector_store": cleared_vector_store,
110
+ "cleared_todo_keys": cleared_todo_keys,
111
+ "cleared_memory_node": cleared_memory_node,
112
+ }
@@ -0,0 +1,40 @@
1
+ """Per-session ``InMemoryVectorStore`` for long-term memory archival.
2
+
3
+ Note: a duplicate dict ``_memory_vector_stores`` lives in
4
+ :mod:`services.ai`. ``services.memory.state.clear_agent_session_state``
5
+ clears the live (``services.ai``) one — this module's dict is an
6
+ intentional second copy that future cleanup will consolidate. Keep both
7
+ for now; the documented duplication is preserved verbatim.
8
+ """
9
+
10
+ from typing import Any, Dict
11
+
12
+ from core.logging import get_logger
13
+
14
+ logger = get_logger(__name__)
15
+
16
+
17
+ # Per-``session_id`` cache. The live cache used by aiAgent etc. lives
18
+ # in ``services.ai``; this dict is the package-private counterpart for
19
+ # JSONL-bridge consumers and is referenced by state-clear orchestration.
20
+ _memory_vector_stores: Dict[str, Any] = {}
21
+
22
+
23
+ def get_memory_vector_store(session_id: str):
24
+ """Get or create an ``InMemoryVectorStore`` for a session.
25
+
26
+ Returns ``None`` (with a warning) when the optional LangChain
27
+ HuggingFace dep is missing — long-term archival is best-effort.
28
+ """
29
+ if session_id not in _memory_vector_stores:
30
+ try:
31
+ from langchain_core.vectorstores import InMemoryVectorStore
32
+ from langchain_huggingface import HuggingFaceEmbeddings
33
+
34
+ embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-en-v1.5")
35
+ _memory_vector_stores[session_id] = InMemoryVectorStore(embeddings)
36
+ logger.debug(f"[Memory] Created vector store for session '{session_id}'")
37
+ except ImportError as exc:
38
+ logger.warning(f"[Memory] Vector store not available: {exc}")
39
+ return None
40
+ return _memory_vector_stores[session_id]
@@ -235,6 +235,82 @@ class ModelRegistryService:
235
235
 
236
236
  return None
237
237
 
238
+ def register_local_model(
239
+ self,
240
+ provider: str,
241
+ model_id: str,
242
+ params: Dict[str, Any],
243
+ ) -> None:
244
+ """Register an Ollama / LM Studio model with its actual params.
245
+
246
+ Called from ``validate_local_llm`` after the official SDK probe
247
+ (``ollama.AsyncClient.ps()`` / ``lmstudio.AsyncClient.llm.list_loaded()``)
248
+ returns each currently-loaded model with its typed params:
249
+ ``context_length`` (live n_ctx the server enforces),
250
+ ``vision`` / ``supports_tools`` capability flags, and metadata
251
+ (``architecture``, ``param_size``, ``quantization``). The sync
252
+ ``get_context_length`` / ``get_max_output_tokens`` lookups find
253
+ this entry first, so chat / agent execution honour the real
254
+ n_ctx the server is serving — no JSON guess, no string parsing.
255
+
256
+ Stored under the same ``{provider}/{local_id}`` key shape as
257
+ cloud models so ``get_model_info``'s waterfall matches without
258
+ any per-provider branching. ``provider`` is canonical
259
+ (``ollama`` / ``lmstudio``).
260
+
261
+ Idempotent: a re-validation overwrites the prior entry.
262
+
263
+ Persisted to the same ``model_registry.json`` cache the
264
+ OpenRouter refresh writes — the entry survives a server
265
+ restart, so the user doesn't have to re-click "Fetch" every
266
+ time the process bounces just to keep the n_ctx context
267
+ registered for their local model.
268
+ """
269
+ ctx = int(params.get("context_length") or 0)
270
+ max_out = int(params.get("max_output_tokens") or 0)
271
+ # Default max_output to 25% of context (OpenRouter convention for
272
+ # local models), capped at 4096 — local backends rarely benefit
273
+ # from larger budgets and overcommitting eats into the prompt.
274
+ if not max_out and ctx:
275
+ max_out = min(4096, max(512, ctx // 4))
276
+
277
+ # Capability flags + metadata from the SDK probe go into
278
+ # `supported_parameters` so they survive the JSON roundtrip via
279
+ # `ModelInfo.to_dict` / `from_dict`. Cloud models populate this
280
+ # from OpenRouter's listing; for locals we use it as a typed
281
+ # capability bag so downstream code (tool-binding, vision UI
282
+ # gating) can read the same fields regardless of provider.
283
+ supported: List[str] = []
284
+ if params.get("supports_tools"):
285
+ supported.append("tools")
286
+ if params.get("vision"):
287
+ supported.append("vision")
288
+
289
+ # Match the user-friendly temperature default for OpenAI-compat
290
+ # local servers (0..2 range — same as the JSON default).
291
+ info = ModelInfo(
292
+ id=f"{provider}/{model_id}",
293
+ name=model_id,
294
+ provider=provider,
295
+ local_id=model_id,
296
+ context_length=ctx,
297
+ max_output_tokens=max_out,
298
+ temperature_range=(0.0, 2.0),
299
+ supported_parameters=supported,
300
+ )
301
+ self._models[f"{provider}/{model_id}"] = info
302
+ logger.info(
303
+ "[%s] registered model %s (ctx=%s, max_out=%s, tools=%s, vision=%s)",
304
+ provider, model_id, ctx, max_out,
305
+ params.get("supports_tools", False), params.get("vision", False),
306
+ )
307
+ # Persist so the entry survives process restart.
308
+ try:
309
+ self._save_cache()
310
+ except Exception as e: # noqa: BLE001 — best-effort persistence
311
+ logger.warning("Failed to persist local model entry %s/%s: %s",
312
+ provider, model_id, e)
313
+
238
314
  def get_max_output_tokens(self, model: str, provider: str) -> int:
239
315
  """Get max output tokens: registry -> llm_defaults -> 4096."""
240
316
  info = self.get_model_info(model, provider)
@@ -29,32 +29,88 @@ class NodeAllowlistService:
29
29
 
30
30
  Response shape:
31
31
  show_all: bool
32
- true -> do not filter the palette; every node is visible.
32
+ true -> do not filter the palette; every node is visible
33
+ (still subject to disabled_groups + disabled_nodes).
33
34
  false -> show only node types listed in enabled_nodes.
34
35
  enabled_nodes: list[str]
35
36
  Only meaningful when show_all is false.
37
+ disabled_groups: list[str]
38
+ Absolute blocklist. A node whose first group matches any
39
+ entry here is hidden in BOTH normal and dev mode, even
40
+ if listed in enabled_nodes. Use to disable an entire
41
+ backend group (e.g. 'android' hides all 16 Android
42
+ service nodes + androidTool).
43
+ disabled_nodes: list[str]
44
+ Absolute blocklist by exact node-type identifier. Same
45
+ mode-independent enforcement as disabled_groups; use
46
+ for one-off types whose group label doesn't match
47
+ (e.g. 'android_agent' belongs to the 'agent' group).
48
+ disabled_credential_categories: list[str]
49
+ Absolute blocklist for the Credentials Modal — every
50
+ provider whose `category` matches an entry here is
51
+ hidden from the modal AND its category header is
52
+ stripped. Use to disable an entire credential category
53
+ (e.g. 'android' hides the Android relay panel + the
54
+ Android category header). Mirrors disabled_groups
55
+ semantically — the same conceptual entity (android
56
+ feature surface) but the credential catalogue uses its
57
+ own category taxonomy independent of node groups.
58
+ disabled_skill_folders: list[str]
59
+ Absolute blocklist for the Master Skill folder
60
+ dropdown — every entry hides the matching subfolder
61
+ under server/skills/. Use when disabling a feature
62
+ that also ships its own skill folder (e.g.
63
+ 'android_agent' so users can't see the 12 android-
64
+ tied skills when android nodes are disabled).
65
+ Email has no dedicated skill folder (email-tied skills
66
+ live under productivity_agent mixed with Google
67
+ Workspace) so no email entry is needed.
36
68
  """
69
+ defaults = {
70
+ "show_all": True,
71
+ "enabled_nodes": [],
72
+ "disabled_groups": [],
73
+ "disabled_nodes": [],
74
+ "disabled_credential_categories": [],
75
+ "disabled_skill_folders": [],
76
+ }
77
+
37
78
  if not self._config_path.exists():
38
- return {"show_all": True, "enabled_nodes": []}
79
+ return defaults
39
80
 
40
81
  try:
41
82
  with self._config_path.open("r", encoding="utf-8") as f:
42
83
  raw = json.load(f)
43
84
  except Exception as e:
44
85
  logger.warning("Failed to parse node_allowlist.json, falling back to show_all: %s", e)
45
- return {"show_all": True, "enabled_nodes": []}
46
-
47
- enabled_nodes = raw.get("enabled_nodes", [])
48
- if not isinstance(enabled_nodes, list):
49
- logger.warning("node_allowlist.json 'enabled_nodes' is not a list, falling back to show_all")
50
- return {"show_all": True, "enabled_nodes": []}
51
-
52
- enabled_nodes = [n for n in enabled_nodes if isinstance(n, str)]
53
-
54
- if len(enabled_nodes) == 0:
55
- return {"show_all": True, "enabled_nodes": []}
56
-
57
- return {"show_all": False, "enabled_nodes": enabled_nodes}
86
+ return defaults
87
+
88
+ def _str_list(key: str) -> List[str]:
89
+ value = raw.get(key, [])
90
+ if not isinstance(value, list):
91
+ logger.warning(
92
+ "node_allowlist.json '%s' is not a list, treating as empty",
93
+ key,
94
+ )
95
+ return []
96
+ return [n for n in value if isinstance(n, str)]
97
+
98
+ enabled_nodes = _str_list("enabled_nodes")
99
+ disabled_groups = _str_list("disabled_groups")
100
+ disabled_nodes = _str_list("disabled_nodes")
101
+ disabled_credential_categories = _str_list("disabled_credential_categories")
102
+ disabled_skill_folders = _str_list("disabled_skill_folders")
103
+
104
+ show_all = len(enabled_nodes) == 0
105
+
106
+ return {
107
+ "show_all": show_all,
108
+ "enabled_nodes": enabled_nodes,
109
+ "disabled_groups": disabled_groups,
110
+ "disabled_nodes": disabled_nodes,
111
+ "disabled_credential_categories": disabled_credential_categories,
112
+ "disabled_skill_folders": disabled_skill_folders,
113
+ }
58
114
 
59
115
 
60
116
  _instance: NodeAllowlistService | None = None
@@ -33,9 +33,9 @@ if TYPE_CHECKING:
33
33
  from core.config import Settings
34
34
  from core.database import Database
35
35
  from services.ai import AIService
36
- from services.maps import MapsService
36
+ from nodes.location._service import MapsService
37
37
  from services.text import TextService
38
- from services.android_service import AndroidService
38
+ from nodes.android._dispatcher import AndroidService
39
39
 
40
40
  logger = get_logger(__name__)
41
41
 
@@ -866,6 +866,26 @@ def list_node_types_with_schema() -> list[str]:
866
866
  return sorted(NODE_OUTPUT_SCHEMAS.keys())
867
867
 
868
868
 
869
+ from services.plugin.registry import IdempotentRegistry as _IdempotentRegistry # noqa: E402
870
+
871
+
872
+ def _bust_schema_cache(node_type: str, _model_class: type[BaseModel]) -> None:
873
+ """on_register hook: drop the cached JSON schema for the re-registered type."""
874
+ _schema_cache.pop(node_type, None)
875
+
876
+
877
+ # Backed by the module-level NODE_OUTPUT_SCHEMAS dict so existing
878
+ # readers (e.g. get_output_schema, list_node_types_with_schema, tests)
879
+ # keep working.
880
+ _OUTPUT_SCHEMA_REGISTRY: _IdempotentRegistry[str, type[BaseModel]] = (
881
+ _IdempotentRegistry(
882
+ "output_schema",
883
+ items=NODE_OUTPUT_SCHEMAS,
884
+ on_register=_bust_schema_cache,
885
+ )
886
+ )
887
+
888
+
869
889
  def register_output_schema(node_type: str, model_class: type[BaseModel]) -> None:
870
890
  """Publish an output schema for a node type from a plugin package.
871
891
 
@@ -877,13 +897,4 @@ def register_output_schema(node_type: str, model_class: type[BaseModel]) -> None
877
897
 
878
898
  See e.g. ``nodes/telegram/__init__.py``.
879
899
  """
880
- existing = NODE_OUTPUT_SCHEMAS.get(node_type)
881
- if existing is not None and existing is not model_class:
882
- raise ValueError(
883
- f"Output schema for '{node_type}' is already registered as "
884
- f"{existing.__module__}.{existing.__qualname__}; refusing to "
885
- f"overwrite with {model_class.__module__}.{model_class.__qualname__}"
886
- )
887
- NODE_OUTPUT_SCHEMAS[node_type] = model_class
888
- # Bust the JSON-schema cache so the next call re-serialises.
889
- _schema_cache.pop(node_type, None)
900
+ _OUTPUT_SCHEMA_REGISTRY.register(node_type, model_class)
@@ -85,7 +85,7 @@ def get_node_spec(node_type: str) -> Optional[dict[str, Any]]:
85
85
  # Wave 10.A — full visual contract. Only emit fields when seeded so the
86
86
  # wire format stays compact and unseeded types keep the pre-10 shape.
87
87
  for key in ("color", "componentKind", "handles", "credentials",
88
- "hideOutputHandle", "visibility"):
88
+ "hideOutputHandle", "hideInputHandle", "visibility"):
89
89
  if key in meta:
90
90
  spec[key] = meta[key]
91
91
 
@@ -11,7 +11,7 @@ base URL (scheme + host + port) comes from the connection itself.
11
11
 
12
12
  from urllib.parse import urlparse
13
13
 
14
- from services.google_oauth import get_callback_paths
14
+ from nodes.google._oauth import get_callback_paths
15
15
 
16
16
 
17
17
  def get_base_url(connection) -> str:
@@ -28,6 +28,7 @@ from __future__ import annotations
28
28
  from services.plugin.base import BaseNode, NodeUserError
29
29
  from services.plugin.action import ActionNode
30
30
  from services.plugin.trigger import TriggerNode
31
+ from services.plugin.polling import PollingTriggerNode
31
32
  from services.plugin.tool import ToolNode
32
33
  from services.plugin.operation import Operation, OperationSpec
33
34
  from services.plugin.routing import Routing, RoutingRequest, RoutingOutput, execute_routing
@@ -52,6 +53,7 @@ __all__ = [
52
53
  "NodeUserError",
53
54
  "ActionNode",
54
55
  "TriggerNode",
56
+ "PollingTriggerNode",
55
57
  "ToolNode",
56
58
  "Operation",
57
59
  "OperationSpec",