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
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Idempotent plugin registries.
|
|
2
|
+
|
|
3
|
+
Single generic base for all the ``register_*`` plumbing that plugin
|
|
4
|
+
``__init__.py`` files call into. Replaces five hand-rolled
|
|
5
|
+
implementations with the same shape.
|
|
6
|
+
|
|
7
|
+
Two flavours:
|
|
8
|
+
|
|
9
|
+
- :class:`IdempotentRegistry` -- key->value mapping with collision
|
|
10
|
+
detection. Used for WS handlers, FastAPI routers, filter builders,
|
|
11
|
+
trigger prechecks, output schemas. Re-registering the same value for
|
|
12
|
+
a key is a no-op (re-import safe); a different value raises
|
|
13
|
+
``ValueError`` so plugin namespace clashes fail at import time.
|
|
14
|
+
|
|
15
|
+
- :class:`IdempotentList` -- fanout list with identity dedup. Used for
|
|
16
|
+
per-cycle callbacks like ``register_service_refresh`` where every
|
|
17
|
+
registered callable runs once per cycle. Re-registering the same
|
|
18
|
+
callable is a no-op; different callables coexist (fanout semantics).
|
|
19
|
+
|
|
20
|
+
Each module that exposes a ``register_*`` public API constructs one of
|
|
21
|
+
these and provides a thin wrapper. Module-level dicts that other code
|
|
22
|
+
reads directly (e.g. ``FILTER_BUILDERS``, ``NODE_OUTPUT_SCHEMAS``) can
|
|
23
|
+
be passed in as the backing store via the ``items`` kwarg, so existing
|
|
24
|
+
readers keep working.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from types import MappingProxyType
|
|
30
|
+
from typing import (
|
|
31
|
+
Callable,
|
|
32
|
+
Dict,
|
|
33
|
+
Generic,
|
|
34
|
+
Iterator,
|
|
35
|
+
List,
|
|
36
|
+
Mapping,
|
|
37
|
+
Optional,
|
|
38
|
+
TypeVar,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
K = TypeVar("K")
|
|
42
|
+
V = TypeVar("V")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _qual(value: object) -> str:
|
|
46
|
+
"""Best-effort qualified name for callable / class -- used in error messages."""
|
|
47
|
+
module = getattr(value, "__module__", None)
|
|
48
|
+
qualname = getattr(value, "__qualname__", None)
|
|
49
|
+
if module and qualname:
|
|
50
|
+
return f"{module}.{qualname}"
|
|
51
|
+
return repr(value)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class IdempotentRegistry(Generic[K, V]):
|
|
55
|
+
"""Key->value registry with idempotent register + collision raise.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name: Human-readable label used in error messages
|
|
59
|
+
(``f"{name}: '{key}' already registered..."``).
|
|
60
|
+
items: Optional pre-existing dict to use as the backing store.
|
|
61
|
+
Lets a module expose its registry-backed dict as a
|
|
62
|
+
module-level constant for readers that grew up reading the
|
|
63
|
+
dict directly.
|
|
64
|
+
on_register: Optional callback fired after a successful
|
|
65
|
+
register. Used e.g. by ``register_output_schema`` to bust a
|
|
66
|
+
JSON-schema cache.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
name: str,
|
|
72
|
+
*,
|
|
73
|
+
items: Optional[Dict[K, V]] = None,
|
|
74
|
+
on_register: Optional[Callable[[K, V], None]] = None,
|
|
75
|
+
) -> None:
|
|
76
|
+
self._name = name
|
|
77
|
+
self._items: Dict[K, V] = items if items is not None else {}
|
|
78
|
+
self._on_register = on_register
|
|
79
|
+
|
|
80
|
+
def register(self, key: K, value: V) -> None:
|
|
81
|
+
"""Add ``key -> value``. Idempotent on identity; raises on conflict."""
|
|
82
|
+
existing = self._items.get(key)
|
|
83
|
+
if existing is not None and existing is not value:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"{self._name}: {key!r} is already registered by "
|
|
86
|
+
f"{_qual(existing)}; refusing to overwrite with {_qual(value)}"
|
|
87
|
+
)
|
|
88
|
+
self._items[key] = value
|
|
89
|
+
if self._on_register is not None:
|
|
90
|
+
self._on_register(key, value)
|
|
91
|
+
|
|
92
|
+
def get(self, key: K) -> Optional[V]:
|
|
93
|
+
return self._items.get(key)
|
|
94
|
+
|
|
95
|
+
def items(self) -> Mapping[K, V]:
|
|
96
|
+
"""Read-only view. Use ``dict(reg.items())`` for a mutable copy."""
|
|
97
|
+
return MappingProxyType(self._items)
|
|
98
|
+
|
|
99
|
+
def keys(self) -> List[K]:
|
|
100
|
+
return list(self._items.keys())
|
|
101
|
+
|
|
102
|
+
def values(self) -> List[V]:
|
|
103
|
+
return list(self._items.values())
|
|
104
|
+
|
|
105
|
+
def __contains__(self, key: object) -> bool:
|
|
106
|
+
return key in self._items
|
|
107
|
+
|
|
108
|
+
def __len__(self) -> int:
|
|
109
|
+
return len(self._items)
|
|
110
|
+
|
|
111
|
+
def __iter__(self) -> Iterator[K]:
|
|
112
|
+
return iter(self._items)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class IdempotentList(Generic[V]):
|
|
116
|
+
"""Fanout list with identity dedup.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
name: Human-readable label (used in repr / future logging).
|
|
120
|
+
items: Optional pre-existing list to use as the backing store.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
name: str,
|
|
126
|
+
*,
|
|
127
|
+
items: Optional[List[V]] = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
self._name = name
|
|
130
|
+
self._items: List[V] = items if items is not None else []
|
|
131
|
+
|
|
132
|
+
def register(self, value: V) -> None:
|
|
133
|
+
"""Append if not already present (identity dedup)."""
|
|
134
|
+
if value in self._items:
|
|
135
|
+
return
|
|
136
|
+
self._items.append(value)
|
|
137
|
+
|
|
138
|
+
def __iter__(self) -> Iterator[V]:
|
|
139
|
+
return iter(self._items)
|
|
140
|
+
|
|
141
|
+
def __len__(self) -> int:
|
|
142
|
+
return len(self._items)
|
|
143
|
+
|
|
144
|
+
def __contains__(self, value: object) -> bool:
|
|
145
|
+
return value in self._items
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Service-singleton mixin for plugin-owned long-lived services.
|
|
2
|
+
|
|
3
|
+
Replaces the per-plugin ``_instance`` + ``get_instance()`` boilerplate
|
|
4
|
+
that each ``nodes/<plugin>/_service.py`` currently re-implements.
|
|
5
|
+
Pre-T inventory (Wave 11.I plan):
|
|
6
|
+
|
|
7
|
+
- ``nodes/telegram/_service.py:TelegramService`` -- ``_instance`` + sync
|
|
8
|
+
``get_instance`` + async ``reset_instance`` (custom — disconnects).
|
|
9
|
+
- ``nodes/email/_service.py:EmailService`` -- ``_instance`` + sync
|
|
10
|
+
``get_instance``, no reset.
|
|
11
|
+
- ``nodes/browser/_service.py`` -- module-level ``_instance`` global,
|
|
12
|
+
no class-level state (different shape, kept bespoke).
|
|
13
|
+
- ``nodes/whatsapp/_service.py`` -- supervisor-aware singleton via
|
|
14
|
+
``BaseProcessSupervisor``, kept bespoke (different lifecycle).
|
|
15
|
+
|
|
16
|
+
Subclasses get :meth:`instance` for free. They override
|
|
17
|
+
:meth:`reset_instance` only if there's a side effect on reset
|
|
18
|
+
(Telegram disconnects the bot poll loop; that stays per-plugin).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import ClassVar, Optional, TypeVar
|
|
24
|
+
|
|
25
|
+
T = TypeVar("T", bound="ServiceSingleton")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ServiceSingleton:
|
|
29
|
+
"""Mixin: per-class lazy singleton.
|
|
30
|
+
|
|
31
|
+
Each subclass that opts in gets its own ``_instance`` slot lazily
|
|
32
|
+
populated on first :meth:`instance` call. The Python attribute
|
|
33
|
+
rules guarantee per-class isolation (read climbs MRO, write goes
|
|
34
|
+
to the immediate class), so ``A.instance()`` and ``B.instance()``
|
|
35
|
+
return distinct singletons even though both inherit the same
|
|
36
|
+
declaration here.
|
|
37
|
+
|
|
38
|
+
Usage::
|
|
39
|
+
|
|
40
|
+
class TelegramService(ServiceSingleton):
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
# module-level helper consumed by handler files:
|
|
44
|
+
def get_telegram_service() -> TelegramService:
|
|
45
|
+
return TelegramService.instance()
|
|
46
|
+
|
|
47
|
+
Subclasses may also override :meth:`reset_instance` to add side
|
|
48
|
+
effects (e.g. tear down a bot polling loop) before clearing.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
_instance: ClassVar[Optional["ServiceSingleton"]] = None
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def instance(cls: type[T]) -> T:
|
|
55
|
+
"""Return the per-class singleton, constructing it on first call."""
|
|
56
|
+
if cls.__dict__.get("_instance") is None:
|
|
57
|
+
cls._instance = cls()
|
|
58
|
+
return cls._instance # type: ignore[return-value]
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def reset_instance(cls) -> None:
|
|
62
|
+
"""Drop the cached singleton. Override for async / cleanup
|
|
63
|
+
flows; the default is a sync no-side-effect clear."""
|
|
64
|
+
if cls.__dict__.get("_instance") is not None:
|
|
65
|
+
cls._instance = None
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""WebSocket handler helpers (Wave 11.I, milestone T-residual).
|
|
2
|
+
|
|
3
|
+
Single helper today: :func:`ws_response`, an opt-in decorator that
|
|
4
|
+
collapses the duplicated ``try / except / return {success: False,
|
|
5
|
+
error: str(e)}`` block at the bottom of every plugin WS handler.
|
|
6
|
+
|
|
7
|
+
Audited 79 instances of this exact pattern across ``nodes/<plugin>/``
|
|
8
|
+
in the pre-T-residual codebase. The decorator is **opt-in** because
|
|
9
|
+
not every handler returns the ``{success: bool}`` envelope -- OAuth
|
|
10
|
+
lifecycle handlers built by
|
|
11
|
+
:mod:`services.events.oauth_lifecycle` return ``{connected: bool}``,
|
|
12
|
+
auto-wrapping would clobber that. Plugin authors decorate per-handler.
|
|
13
|
+
|
|
14
|
+
Honors the :class:`services.plugin.base.NodeUserError` contract: when
|
|
15
|
+
a handler raises ``NodeUserError`` (user-correctable failure -- bad
|
|
16
|
+
input, unknown enum value, missing required field), the wrapper logs
|
|
17
|
+
at WARN without a traceback. Genuinely unexpected exceptions log at
|
|
18
|
+
ERROR with full traceback via ``logger.exception``.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from functools import wraps
|
|
24
|
+
from typing import Any, Awaitable, Callable, Dict
|
|
25
|
+
|
|
26
|
+
from fastapi import WebSocket
|
|
27
|
+
|
|
28
|
+
from core.logging import get_logger
|
|
29
|
+
from services.plugin.base import NodeUserError
|
|
30
|
+
|
|
31
|
+
logger = get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
WSHandlerFn = Callable[[Dict[str, Any], WebSocket], Awaitable[Dict[str, Any]]]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def ws_response(handler: WSHandlerFn) -> WSHandlerFn:
|
|
37
|
+
"""Wrap a WS handler to convert exceptions into a ``{success: False,
|
|
38
|
+
error: ...}`` envelope.
|
|
39
|
+
|
|
40
|
+
Successful return values pass through untouched -- the wrapper
|
|
41
|
+
only intervenes on exceptions. Extra fields the handler adds to
|
|
42
|
+
the success envelope (``connected``, ``message``, ``result``, ...)
|
|
43
|
+
are preserved.
|
|
44
|
+
|
|
45
|
+
Two log levels:
|
|
46
|
+
|
|
47
|
+
* :class:`NodeUserError` -> WARN, no traceback. Reason: these are
|
|
48
|
+
expected and user-correctable; a stack trace just clutters the
|
|
49
|
+
operator log without adding signal.
|
|
50
|
+
* Anything else -> ERROR via ``logger.exception`` (full traceback).
|
|
51
|
+
Reason: it's a server bug, the trace is the diagnostic.
|
|
52
|
+
|
|
53
|
+
Usage::
|
|
54
|
+
|
|
55
|
+
from services.plugin.ws import ws_response
|
|
56
|
+
|
|
57
|
+
@ws_response
|
|
58
|
+
async def handle_thing(data, websocket):
|
|
59
|
+
...
|
|
60
|
+
return {"success": True, "result": ...}
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
handler_name = getattr(handler, "__name__", repr(handler))
|
|
64
|
+
|
|
65
|
+
@wraps(handler)
|
|
66
|
+
async def wrapper(
|
|
67
|
+
data: Dict[str, Any], websocket: WebSocket,
|
|
68
|
+
) -> Dict[str, Any]:
|
|
69
|
+
try:
|
|
70
|
+
return await handler(data, websocket)
|
|
71
|
+
except NodeUserError as exc:
|
|
72
|
+
logger.warning(f"[{handler_name}] {type(exc).__name__}: {exc}")
|
|
73
|
+
return {"success": False, "error": str(exc)}
|
|
74
|
+
except Exception as exc: # noqa: BLE001 -- surface as envelope
|
|
75
|
+
logger.exception(f"[{handler_name}] unexpected error")
|
|
76
|
+
return {"success": False, "error": str(exc)}
|
|
77
|
+
|
|
78
|
+
return wrapper
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
__all__ = ["ws_response"]
|
|
@@ -16,7 +16,7 @@ import shutil
|
|
|
16
16
|
from dataclasses import dataclass, field
|
|
17
17
|
from datetime import datetime
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Any, Dict, List, Optional
|
|
19
|
+
from typing import Any, Awaitable, Callable, Dict, List, Optional
|
|
20
20
|
|
|
21
21
|
from core.logging import get_logger
|
|
22
22
|
from services._supervisor.util import kill_tree
|
|
@@ -26,6 +26,10 @@ logger = get_logger(__name__)
|
|
|
26
26
|
MAX_PROCESSES = 10 # Default limit, configurable via Settings panel
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
# (stream_name, line) -> awaitable. Invoked once per decoded stdout/stderr line.
|
|
30
|
+
LineHandler = Callable[[str, str], Awaitable[None]]
|
|
31
|
+
|
|
32
|
+
|
|
29
33
|
@dataclass
|
|
30
34
|
class ManagedProcess:
|
|
31
35
|
name: str
|
|
@@ -43,6 +47,11 @@ class ManagedProcess:
|
|
|
43
47
|
exit_code: Optional[int] = None
|
|
44
48
|
stdout_lines: int = 0
|
|
45
49
|
stderr_lines: int = 0
|
|
50
|
+
# Optional per-line callback: framework-level subscribers (e.g. the
|
|
51
|
+
# generalised event source `DaemonEventSource`) install this to ingest
|
|
52
|
+
# the daemon's stdout/stderr without re-tailing the log files we already
|
|
53
|
+
# write for the Terminal tab.
|
|
54
|
+
line_handler: Optional[LineHandler] = None
|
|
46
55
|
|
|
47
56
|
|
|
48
57
|
class ProcessService:
|
|
@@ -70,8 +79,17 @@ class ProcessService:
|
|
|
70
79
|
command: str,
|
|
71
80
|
workflow_id: str = "default",
|
|
72
81
|
working_directory: str = "",
|
|
82
|
+
*,
|
|
83
|
+
line_handler: Optional[LineHandler] = None,
|
|
73
84
|
) -> Dict[str, Any]:
|
|
74
|
-
"""Start a long-running process.
|
|
85
|
+
"""Start a long-running process.
|
|
86
|
+
|
|
87
|
+
``line_handler``: optional ``async (stream_name, line) -> None``
|
|
88
|
+
callback invoked for every decoded stdout/stderr line, AFTER the
|
|
89
|
+
line has been written to the log file and broadcast to the
|
|
90
|
+
Terminal tab. Used by ``DaemonEventSource`` to parse subprocess
|
|
91
|
+
output into typed events without re-tailing the log files.
|
|
92
|
+
"""
|
|
75
93
|
if not command:
|
|
76
94
|
return {"success": False, "error": "command is required"}
|
|
77
95
|
|
|
@@ -170,6 +188,7 @@ class ProcessService:
|
|
|
170
188
|
working_directory=cwd,
|
|
171
189
|
process=proc,
|
|
172
190
|
log_dir=log_dir,
|
|
191
|
+
line_handler=line_handler,
|
|
173
192
|
)
|
|
174
193
|
|
|
175
194
|
managed.stdout_task = asyncio.create_task(
|
|
@@ -392,6 +411,16 @@ class ProcessService:
|
|
|
392
411
|
"message": text,
|
|
393
412
|
"source": source,
|
|
394
413
|
})
|
|
414
|
+
|
|
415
|
+
# Optional framework-level subscriber.
|
|
416
|
+
if managed.line_handler is not None:
|
|
417
|
+
try:
|
|
418
|
+
await managed.line_handler(stream_name, text)
|
|
419
|
+
except Exception as cb_err:
|
|
420
|
+
logger.debug(
|
|
421
|
+
"[Process] line_handler %s/%s raised: %s",
|
|
422
|
+
managed.name, stream_name, cb_err,
|
|
423
|
+
)
|
|
395
424
|
except asyncio.CancelledError:
|
|
396
425
|
pass
|
|
397
426
|
except Exception as e:
|