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.
- package/README.md +143 -107
- package/client/dist/assets/ActionBar-Du2MSFSz.js +1 -0
- package/client/dist/assets/ApiKeyInput-k2LBmBjb.js +1 -0
- package/client/dist/assets/ApiKeyPanel-C_bV9U0X.js +1 -0
- package/client/dist/assets/ApiUsageSection-CmVfwZzL.js +1 -0
- package/client/dist/assets/EmailPanel-CeKIMGu-.js +1 -0
- package/client/dist/assets/OAuthPanel-KA3t3Q2K.js +1 -0
- package/client/dist/assets/QrPairingPanel-NgNpJNuk.js +1 -0
- package/client/dist/assets/RateLimitSection-Du5YNVIA.js +1 -0
- package/client/dist/assets/StatusCard-DNLyayXc.js +1 -0
- package/client/dist/assets/index-DQ0nwhec.js +257 -0
- package/client/dist/assets/index-DxmbVskS.css +1 -0
- package/client/dist/assets/vendor-flow-CZmBvHRo.js +1 -0
- package/client/dist/assets/vendor-icons-CVrPjN2Q.js +22 -0
- package/client/dist/assets/vendor-markdown-CRou3yQ5.js +62 -0
- package/client/dist/assets/vendor-misc-C4VxKHs5.js +1 -0
- package/client/dist/assets/vendor-query-SzWcOU0G.js +1 -0
- package/client/dist/assets/vendor-radix-Dnos29jG.js +56 -0
- package/client/dist/assets/vendor-react-DvWIbVx0.js +1 -0
- package/client/dist/index.html +37 -3
- package/client/index.html +28 -1
- package/client/package.json +44 -40
- package/client/src/App.tsx +2 -0
- package/client/src/Dashboard.tsx +157 -45
- package/client/src/ParameterPanel.tsx +3 -5
- package/client/src/adapters/nodeSpecToDescription.ts +1 -0
- package/client/src/assets/icons/NodeIcon.tsx +32 -0
- package/client/src/assets/icons/index.ts +4 -0
- package/client/src/assets/icons/stripe.svg +1 -0
- package/client/src/assets/icons/themedGlyphs.ts +404 -0
- package/client/src/components/AIAgentNode.tsx +77 -53
- package/client/src/components/GenericNode.tsx +34 -52
- package/client/src/components/OutputPanel.tsx +64 -147
- package/client/src/components/ParameterRenderer.tsx +5 -3
- package/client/src/components/SkillEditorModal.tsx +9 -18
- package/client/src/components/SquareNode.tsx +97 -115
- package/client/src/components/StartNode.tsx +32 -42
- package/client/src/components/SvgFilterDefs.tsx +54 -0
- package/client/src/components/TeamMonitorNode.tsx +12 -14
- package/client/src/components/ToolkitNode.tsx +35 -60
- package/client/src/components/TriggerNode.tsx +43 -77
- package/client/src/components/__tests__/CredentialsModal.test.tsx +49 -45
- package/client/src/components/credentials/CredentialsModal.tsx +98 -30
- package/client/src/components/credentials/CredentialsPalette.tsx +73 -5
- package/client/src/components/credentials/catalogueAdapter.ts +17 -1
- package/client/src/components/credentials/panels/ApiKeyPanel.tsx +102 -37
- package/client/src/components/credentials/panels/EmailPanel.tsx +7 -19
- package/client/src/components/credentials/panels/OAuthPanel.tsx +5 -1
- package/client/src/components/credentials/panels/QrPairingPanel.tsx +1 -3
- package/client/src/components/credentials/primitives/ActionBar.tsx +7 -11
- package/client/src/components/credentials/primitives/OAuthConnect.tsx +19 -28
- package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +24 -3
- package/client/src/components/credentials/types.ts +12 -2
- package/client/src/components/credentials/useCredentialPanel.ts +43 -19
- package/client/src/components/icons/AIProviderIcons.tsx +16 -0
- package/client/src/components/onboarding/OnboardingWizard.tsx +23 -63
- package/client/src/components/onboarding/nodeRoleClasses.ts +23 -0
- package/client/src/components/onboarding/steps/CanvasStep.tsx +15 -21
- package/client/src/components/onboarding/steps/ConceptsStep.tsx +2 -11
- package/client/src/components/onboarding/steps/GetStartedStep.tsx +2 -10
- package/client/src/components/parameterPanel/InputSection.tsx +9 -7
- package/client/src/components/parameterPanel/MasterSkillEditor.tsx +84 -198
- package/client/src/components/parameterPanel/MiddleSection.tsx +57 -80
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +31 -25
- package/client/src/components/parameterPanel/__tests__/InputSection.test.tsx +7 -2
- package/client/src/components/ui/AIResultModal.tsx +1 -1
- package/client/src/components/ui/CollapsibleSection.tsx +9 -5
- package/client/src/components/ui/CommandPalette.tsx +147 -0
- package/client/src/components/ui/CommandPaletteHost.tsx +189 -0
- package/client/src/components/ui/ComponentItem.tsx +13 -7
- package/client/src/components/ui/ComponentPalette.tsx +24 -13
- package/client/src/components/ui/ConsolePanel.tsx +19 -11
- package/client/src/components/ui/DropCap.tsx +28 -0
- package/client/src/components/ui/EditableNodeLabel.tsx +10 -2
- package/client/src/components/ui/InputNodesPanel.tsx +1 -1
- package/client/src/components/ui/Modal.tsx +38 -6
- package/client/src/components/ui/OutputDisplayPanel.tsx +1 -1
- package/client/src/components/ui/SettingsPanel.tsx +42 -13
- package/client/src/components/ui/StatusBar.tsx +108 -0
- package/client/src/components/ui/ThemeSwitcher.tsx +109 -0
- package/client/src/components/ui/TopToolbar.tsx +42 -25
- package/client/src/components/ui/WorkflowSidebar.tsx +32 -16
- package/client/src/components/ui/action-button.tsx +40 -15
- package/client/src/components/ui/button.tsx +24 -1
- package/client/src/components/ui/dropdown-menu.tsx +24 -2
- package/client/src/components/ui/input.tsx +19 -2
- package/client/src/components/ui/select.tsx +15 -0
- package/client/src/components/ui/textarea.tsx +15 -2
- package/client/src/contexts/AuthContext.tsx +148 -109
- package/client/src/contexts/ThemeContext.tsx +93 -17
- package/client/src/contexts/WebSocketContext.tsx +373 -206
- package/client/src/contexts/__tests__/AuthContext.test.tsx +221 -0
- package/client/src/hooks/__tests__/useDragVariable.test.ts +7 -1
- package/client/src/hooks/__tests__/useWorkflowOpsListener.test.ts +142 -0
- package/client/src/hooks/useAppTheme.ts +209 -7
- package/client/src/hooks/useAutoSkillEdges.ts +7 -2
- package/client/src/hooks/useCatalogueQuery.ts +67 -1
- package/client/src/hooks/useDragVariable.ts +1 -1
- package/client/src/hooks/useNodeAllowlist.ts +115 -8
- package/client/src/hooks/useOnboarding.ts +20 -8
- package/client/src/hooks/useParameterPanel.ts +2 -1
- package/client/src/hooks/useReactFlowNodes.ts +2 -1
- package/client/src/hooks/useSound.ts +185 -0
- package/client/src/hooks/useWorkflowManagement.ts +6 -8
- package/client/src/hooks/useWorkflowOpsListener.ts +90 -0
- package/client/src/index.css +65 -3
- package/client/src/lib/__tests__/connectionConfig.test.ts +91 -0
- package/client/src/lib/aiModelProviders.ts +8 -0
- package/client/src/lib/connectionConfig.ts +107 -0
- package/client/src/lib/queryPersist.ts +13 -5
- package/client/src/lib/sound.ts +393 -0
- package/client/src/main.tsx +20 -0
- package/client/src/store/useAppStore.ts +26 -0
- package/client/src/styles/canvasAnimations.ts +37 -36
- package/client/src/styles/theme.ts +36 -20
- package/client/src/test/setup.ts +1 -0
- package/client/src/themes/atomic.css +253 -0
- package/client/src/themes/base.css +373 -0
- package/client/src/themes/cyber.css +890 -0
- package/client/src/themes/dark.css +70 -0
- package/client/src/themes/edo.css +246 -0
- package/client/src/themes/greek.css +293 -0
- package/client/src/themes/light.css +78 -0
- package/client/src/themes/plague.css +253 -0
- package/client/src/themes/renaissance.css +727 -0
- package/client/src/themes/rot.css +249 -0
- package/client/src/themes/steampunk.css +272 -0
- package/client/src/themes/surveillance.css +289 -0
- package/client/src/themes/wasteland.css +250 -0
- package/client/src/types/INodeProperties.ts +5 -0
- package/client/src/types/NodeTypes.ts +11 -1
- package/client/src/types/__tests__/cloudEvents.test.ts +99 -0
- package/client/src/types/cloudEvents.ts +78 -0
- package/client/src/vite-env.d.ts +7 -0
- package/client/tsconfig.json +1 -1
- package/client/vite.config.js +62 -2
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/machina/commands/build.py +51 -7
- package/machina/pyproject.toml +4 -0
- package/machina/supervisor.py +12 -2
- package/machina/tree.py +71 -21
- package/package.json +4 -4
- package/scripts/install.js +16 -1
- package/server/config/ai_cli_providers.json +54 -0
- package/server/config/credential_providers.json +109 -2
- package/server/config/llm_defaults.json +24 -0
- package/server/config/model_registry.json +338 -499
- package/server/config/node_allowlist.json +16 -1
- package/server/config/pricing.json +8 -0
- package/server/constants.py +38 -15
- package/server/core/container.py +2 -2
- package/server/core/credentials_database.py +35 -2
- package/server/core/logging.py +4 -3
- package/server/main.py +99 -13
- package/server/models/node_metadata.py +1 -0
- package/server/nodejs/package.json +8 -6
- package/server/nodejs/src/index.ts +22 -5
- package/server/nodes/README.md +31 -4
- package/server/nodes/agent/_inline.py +2 -0
- package/server/nodes/agent/_specialized.py +6 -3
- package/server/nodes/agent/ai_agent.py +13 -3
- package/server/nodes/agent/chat_agent.py +6 -3
- package/server/nodes/agent/claude_code_agent.py +287 -75
- package/server/nodes/agent/codex_agent.py +239 -0
- package/server/nodes/agent/deep_agent.py +3 -3
- package/server/nodes/agent/rlm_agent.py +3 -3
- package/server/nodes/android/__init__.py +31 -1
- package/server/nodes/android/_base.py +9 -5
- package/server/{services/android_service.py → nodes/android/_dispatcher.py} +2 -2
- package/server/nodes/android/_handlers.py +154 -0
- package/server/nodes/android/_option_loaders.py +44 -0
- package/server/nodes/android/_refresh.py +127 -0
- package/server/{services/android → nodes/android/_relay}/client.py +4 -4
- package/server/{routers/android.py → nodes/android/_router.py} +27 -8
- package/server/nodes/browser/browser.py +2 -2
- package/server/nodes/code/_base.py +6 -2
- package/server/nodes/code/_claude_code.py +134 -0
- package/server/nodes/document/embedding_generator.py +3 -3
- package/server/nodes/document/http_scraper.py +3 -3
- package/server/nodes/document/vector_store.py +5 -5
- package/server/nodes/email/__init__.py +11 -1
- package/server/nodes/email/_filters.py +21 -0
- package/server/{services/himalaya_service.py → nodes/email/_himalaya.py} +6 -10
- package/server/{services/email_service.py → nodes/email/_service.py} +9 -13
- package/server/nodes/email/email_read.py +1 -1
- package/server/nodes/email/email_receive.py +54 -5
- package/server/nodes/email/email_send.py +1 -1
- package/server/nodes/filesystem/shell.py +24 -1
- package/server/nodes/google/__init__.py +55 -1
- package/server/{services/handlers/google_auth.py → nodes/google/_auth_helper.py} +8 -5
- package/server/nodes/google/_base.py +2 -2
- package/server/nodes/google/_credentials.py +5 -5
- package/server/nodes/google/_filters.py +25 -0
- package/server/nodes/google/_handlers.py +57 -0
- package/server/{services/google_oauth.py → nodes/google/_oauth.py} +195 -162
- package/server/nodes/google/_option_loaders.py +107 -0
- package/server/nodes/google/_refresh.py +66 -0
- package/server/nodes/google/_router.py +131 -0
- package/server/nodes/google/gmail_receive.py +41 -4
- package/server/nodes/groups.py +1 -0
- package/server/nodes/location/_credentials.py +45 -1
- package/server/{services/maps.py → nodes/location/_service.py} +18 -3
- package/server/nodes/location/gmaps_create.py +4 -4
- package/server/nodes/location/gmaps_locations.py +4 -4
- package/server/nodes/location/gmaps_nearby_places.py +4 -4
- package/server/nodes/model/_base.py +8 -3
- package/server/nodes/model/_credentials.py +96 -8
- package/server/nodes/model/_local_validator.py +345 -0
- package/server/nodes/model/lmstudio_chat_model.py +23 -0
- package/server/nodes/model/ollama_chat_model.py +25 -0
- package/server/nodes/proxy/_usage.py +2 -2
- package/server/nodes/proxy/proxy_config.py +14 -14
- package/server/nodes/proxy/proxy_request.py +4 -4
- package/server/nodes/scraper/_credentials.py +29 -1
- package/server/nodes/scraper/apify_actor.py +9 -9
- package/server/nodes/scraper/crawlee_scraper.py +5 -5
- package/server/nodes/search/brave_search.py +4 -0
- package/server/nodes/search/perplexity_search.py +9 -0
- package/server/nodes/search/serper_search.py +3 -0
- package/server/nodes/skill/simple_memory.py +12 -0
- package/server/nodes/social/_base.py +2 -2
- package/server/nodes/stripe/__init__.py +46 -0
- package/server/nodes/stripe/_credentials.py +33 -0
- package/server/nodes/stripe/_handlers.py +270 -0
- package/server/nodes/stripe/_install.py +127 -0
- package/server/nodes/stripe/_source.py +174 -0
- package/server/nodes/stripe/stripe_action.py +81 -0
- package/server/nodes/stripe/stripe_receive.py +92 -0
- package/server/nodes/telegram/_credentials.py +52 -1
- package/server/nodes/telegram/_handlers.py +19 -18
- package/server/nodes/telegram/_service.py +134 -32
- package/server/nodes/telegram/telegram_send.py +5 -6
- package/server/nodes/text/file_handler.py +2 -2
- package/server/nodes/text/text_generator.py +2 -2
- package/server/nodes/tool/agent_builder.py +630 -0
- package/server/nodes/tool/task_manager.py +144 -2
- package/server/nodes/twitter/__init__.py +38 -1
- package/server/nodes/twitter/_base.py +7 -7
- package/server/nodes/twitter/_credentials.py +1 -1
- package/server/nodes/twitter/_filters.py +37 -0
- package/server/nodes/twitter/_handlers.py +77 -0
- package/server/nodes/twitter/_oauth.py +124 -0
- package/server/nodes/twitter/_refresh.py +78 -0
- package/server/nodes/twitter/_router.py +29 -0
- package/server/nodes/twitter/twitter_receive.py +4 -0
- package/server/nodes/visuals.json +64 -19
- package/server/nodes/whatsapp/__init__.py +45 -5
- package/server/nodes/whatsapp/_base.py +3 -3
- package/server/nodes/whatsapp/_filters.py +137 -0
- package/server/nodes/whatsapp/_handlers.py +167 -0
- package/server/nodes/whatsapp/_option_loaders.py +68 -0
- package/server/nodes/whatsapp/_refresh.py +62 -0
- package/server/nodes/whatsapp/_runtime.py +1 -1
- package/server/pyproject.toml +29 -7
- package/server/routers/schemas.py +2 -2
- package/server/routers/webhook.py +26 -9
- package/server/routers/websocket.py +149 -810
- package/server/services/ai.py +89 -8
- package/server/services/auth.py +220 -43
- package/server/services/claude_oauth.py +126 -100
- package/server/services/cli_agent/__init__.py +78 -0
- package/server/services/cli_agent/_handlers.py +237 -0
- package/server/services/cli_agent/config.py +112 -0
- package/server/services/cli_agent/factory.py +48 -0
- package/server/services/cli_agent/lockfile.py +141 -0
- package/server/services/cli_agent/mcp_server.py +482 -0
- package/server/services/cli_agent/protocol.py +173 -0
- package/server/services/cli_agent/providers/__init__.py +9 -0
- package/server/services/cli_agent/providers/anthropic_claude.py +419 -0
- package/server/services/cli_agent/providers/google_gemini.py +80 -0
- package/server/services/cli_agent/providers/openai_codex.py +310 -0
- package/server/services/cli_agent/service.py +607 -0
- package/server/services/cli_agent/session.py +618 -0
- package/server/services/cli_agent/types.py +227 -0
- package/server/services/cli_agent/workflow_tools.py +233 -0
- package/server/services/credential_registry.py +26 -1
- package/server/services/deployment/manager.py +26 -145
- package/server/services/deployment/poll_registry.py +59 -0
- package/server/services/event_waiter.py +76 -246
- package/server/services/events/__init__.py +54 -0
- package/server/services/events/cli.py +78 -0
- package/server/services/events/daemon.py +163 -0
- package/server/services/events/envelope.py +281 -0
- package/server/services/events/lifecycle.py +99 -0
- package/server/services/events/oauth_lifecycle.py +534 -0
- package/server/services/events/polling.py +60 -0
- package/server/services/events/push.py +36 -0
- package/server/services/events/source.py +63 -0
- package/server/services/events/triggers.py +118 -0
- package/server/services/events/verifiers/__init__.py +25 -0
- package/server/services/events/verifiers/base.py +28 -0
- package/server/services/events/verifiers/github.py +25 -0
- package/server/services/events/verifiers/hmac_basic.py +32 -0
- package/server/services/events/verifiers/standard_webhooks.py +47 -0
- package/server/services/events/verifiers/stripe.py +42 -0
- package/server/services/events/webhook.py +105 -0
- package/server/services/handlers/tools.py +28 -186
- package/server/services/llm/config.py +7 -0
- package/server/services/llm/factory.py +8 -2
- package/server/services/memory/__init__.py +52 -0
- package/server/services/memory/jsonl.py +80 -0
- package/server/services/memory/markdown.py +65 -0
- package/server/services/memory/state.py +112 -0
- package/server/services/memory/vector_store.py +40 -0
- package/server/services/model_registry.py +76 -0
- package/server/services/node_allowlist.py +71 -15
- package/server/services/node_executor.py +2 -2
- package/server/services/node_output_schemas.py +21 -10
- package/server/services/node_spec.py +1 -1
- package/server/services/oauth_utils.py +1 -1
- package/server/services/plugin/__init__.py +2 -0
- package/server/services/plugin/base.py +44 -2
- package/server/services/plugin/credential.py +288 -1
- package/server/services/plugin/deps.py +105 -0
- package/server/services/plugin/edge_walker.py +12 -4
- package/server/services/plugin/oauth.py +381 -0
- package/server/services/plugin/polling.py +247 -0
- package/server/services/plugin/registry.py +145 -0
- package/server/services/plugin/singleton.py +65 -0
- package/server/services/plugin/ws.py +81 -0
- package/server/services/process_service.py +31 -2
- package/server/services/status_broadcaster.py +155 -238
- package/server/services/temporal/workflow.py +7 -7
- package/server/services/workflow.py +21 -3
- package/server/services/ws_handler_registry.py +111 -28
- package/server/skills/GUIDE.md +16 -1
- package/server/skills/assistant/agent-builder-skill/SKILL.md +166 -0
- package/server/skills/payments_agent/stripe-skill/SKILL.md +306 -0
- package/server/tests/credentials/test_auth_service.py +16 -9
- package/server/tests/credentials/test_credential_broadcasts.py +219 -0
- package/server/tests/credentials/test_google_oauth.py +6 -6
- package/server/tests/credentials/test_oauth_utils.py +1 -1
- package/server/tests/credentials/test_twitter_oauth.py +2 -2
- package/server/tests/credentials/test_websocket_handlers.py +44 -20
- package/server/tests/llm/test_factory.py +1 -0
- package/server/tests/llm/test_wiring.py +5 -1
- package/server/tests/nodes/_compat.py +24 -24
- package/server/tests/nodes/test_agent_builder.py +439 -0
- package/server/tests/nodes/test_ai_tools.py +18 -14
- package/server/tests/nodes/test_code_fs_process.py +17 -8
- package/server/tests/nodes/test_email.py +10 -9
- package/server/tests/nodes/test_google_workspace.py +2 -2
- package/server/tests/nodes/test_specialized_agents.py +100 -53
- package/server/tests/nodes/test_stripe_plugin.py +293 -0
- package/server/tests/nodes/test_telegram_social.py +4 -4
- package/server/tests/nodes/test_twitter.py +1 -1
- package/server/tests/nodes/test_web_automation.py +2 -2
- package/server/tests/nodes/test_whatsapp.py +9 -9
- package/server/tests/services/cli_agent/__init__.py +0 -0
- package/server/tests/services/cli_agent/test_mcp_server.py +432 -0
- package/server/tests/services/cli_agent/test_providers.py +358 -0
- package/server/tests/services/cli_agent/test_service.py +298 -0
- package/server/tests/services/memory/__init__.py +0 -0
- package/server/tests/services/memory/test_jsonl.py +188 -0
- package/server/tests/services/test_events.py +333 -0
- package/server/tests/test_node_spec.py +56 -16
- package/server/tests/test_plugin_helpers.py +116 -0
- package/server/tests/test_plugin_self_containment.py +486 -0
- package/server/tests/test_status_broadcasts.py +425 -0
- package/workflows/{AI Assistant_workflow-1777421105154-0m4snkzjf.json → AI Assistant_workflow-1778504793388-ou1m1tz2x.json } +70 -266
- package/workflows/{AI Employee_workflow-1777720598005-u4cm858dv.json → AI Employee_example_workflow-1777720598005-u4cm858dv.json } +112 -112
- package/workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json +709 -0
- package/client/dist/assets/ActionBar-vzPpSR77.js +0 -1
- package/client/dist/assets/ApiKeyInput-Ds7AKFe8.js +0 -1
- package/client/dist/assets/ApiKeyPanel-gfblELep.js +0 -1
- package/client/dist/assets/ApiUsageSection-BMNWTe2r.js +0 -1
- package/client/dist/assets/EmailPanel-B1Om64p5.js +0 -1
- package/client/dist/assets/OAuthPanel-CXyQYGBz.js +0 -1
- package/client/dist/assets/QrPairingPanel-BgNuI1we.js +0 -1
- package/client/dist/assets/RateLimitSection-YYK8sx1T.js +0 -1
- package/client/dist/assets/StatusCard-DuYA5hJR.js +0 -1
- package/client/dist/assets/index-D9tZfgvi.js +0 -363
- package/client/dist/assets/index-al7snTkG.css +0 -1
- package/client/src/components/credentials/providers.tsx +0 -177
- package/server/routers/google.py +0 -277
- package/server/routers/maps.py +0 -142
- package/server/routers/twitter.py +0 -365
- package/server/services/claude_code_service.py +0 -106
- package/server/services/memory.py +0 -159
- package/server/services/node_option_loaders/__init__.py +0 -77
- package/server/services/node_option_loaders/android_loaders.py +0 -55
- package/server/services/node_option_loaders/google_loaders.py +0 -97
- package/server/services/node_option_loaders/whatsapp_loaders.py +0 -69
- package/server/services/twitter_oauth.py +0 -411
- package/server/services/websocket_client.py +0 -29
- /package/server/{services/android → nodes/android/_relay}/__init__.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/broadcaster.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/manager.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/protocol.py +0 -0
- /package/server/{services/browser_service.py → nodes/browser/_service.py} +0 -0
- /package/server/{services/whatsapp_service.py → nodes/whatsapp/_service.py} +0 -0
- /package/server/skills/{task_agent → assistant}/write-todos-skill/SKILL.md +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "
|
|
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
|
|
package/server/constants.py
CHANGED
|
@@ -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
|
-
'
|
|
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
|
-
'
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
379
|
+
if 'kimi' in nt:
|
|
364
380
|
return 'kimi'
|
|
365
|
-
|
|
381
|
+
if 'mistral' in nt:
|
|
366
382
|
return 'mistral'
|
|
367
|
-
|
|
383
|
+
if 'cerebras' in nt:
|
|
368
384
|
return 'cerebras'
|
|
369
|
-
|
|
385
|
+
if 'groq' in nt:
|
|
370
386
|
return 'groq'
|
|
371
|
-
|
|
387
|
+
if 'openrouter' in nt:
|
|
372
388
|
return 'openrouter'
|
|
373
|
-
|
|
389
|
+
if 'anthropic' in nt:
|
|
374
390
|
return 'anthropic'
|
|
375
|
-
|
|
391
|
+
if 'gemini' in nt:
|
|
376
392
|
return 'gemini'
|
|
377
|
-
|
|
378
|
-
|
|
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'
|
package/server/core/container.py
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
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=
|
|
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]:
|
package/server/core/logging.py
CHANGED
|
@@ -32,9 +32,10 @@ class WebSocketLogHandler(logging.Handler):
|
|
|
32
32
|
self._source_map = {
|
|
33
33
|
'services.workflow': 'workflow',
|
|
34
34
|
'services.ai': 'ai',
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
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,
|
|
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
|
|
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
|
|
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": "
|
|
6
|
+
"main": "dist/index.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"
|
|
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.
|
|
13
|
+
"express": "^5.2.1"
|
|
13
14
|
},
|
|
14
15
|
"devDependencies": {
|
|
15
|
-
"@types/express": "^5.0.
|
|
16
|
-
"@types/node": "^22.
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
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 =
|
|
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: {} });
|
package/server/nodes/README.md
CHANGED
|
@@ -234,7 +234,7 @@ server/nodes/telegram/
|
|
|
234
234
|
└── telegram_receive.py # TriggerNode
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
-
###
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
|
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 =
|
|
115
|
-
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(),
|