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,118 @@
|
|
|
1
|
+
"""Generic TriggerNode base classes backed by EventSources.
|
|
2
|
+
|
|
3
|
+
Plugins subclass :class:`WebhookTriggerNode` and declare:
|
|
4
|
+
|
|
5
|
+
type, display_name, group, ... (standard plugin metadata)
|
|
6
|
+
webhook_source: ClassVar[Type[WebhookSource]]
|
|
7
|
+
Params, Output (Pydantic models)
|
|
8
|
+
shape_output(event) (optional) reshape WorkflowEvent -> Output dict
|
|
9
|
+
_check_precondition() (optional) return error str if not ready
|
|
10
|
+
_extra_filter(params) (optional) additional filter on top of event_type
|
|
11
|
+
|
|
12
|
+
The base provides:
|
|
13
|
+
- event_type derived from webhook_source.type
|
|
14
|
+
- build_filter combining CloudEvents type-glob + _extra_filter
|
|
15
|
+
- execute() with precheck + shape passthrough
|
|
16
|
+
- the standard Operation("wait") stub
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import time
|
|
22
|
+
from typing import Any, Callable, ClassVar, Dict, Optional, Type
|
|
23
|
+
|
|
24
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
25
|
+
|
|
26
|
+
from services.plugin import NodeContext, Operation, TaskQueue, TriggerNode
|
|
27
|
+
|
|
28
|
+
from .envelope import WorkflowEvent
|
|
29
|
+
from .webhook import WebhookSource
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BaseTriggerParams(BaseModel):
|
|
33
|
+
"""Default Params for WebhookTriggerNode subclasses. Subclass and add
|
|
34
|
+
provider-specific filters."""
|
|
35
|
+
|
|
36
|
+
event_type_filter: str = Field(
|
|
37
|
+
default="all",
|
|
38
|
+
description=(
|
|
39
|
+
"Event type to match. 'all' for every event, exact name, "
|
|
40
|
+
"or wildcard prefix (e.g. 'foo.*')."
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
model_config = ConfigDict(extra="ignore")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class WebhookTriggerNode(TriggerNode):
|
|
48
|
+
"""TriggerNode whose events arrive through a :class:`WebhookSource`.
|
|
49
|
+
|
|
50
|
+
The framework owns the wait/dispatch flow; subclasses describe only
|
|
51
|
+
the differences from the generic shape.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
webhook_source: ClassVar[Optional[Type[WebhookSource]]] = None
|
|
55
|
+
# Auto-prepended to ``event_type_filter`` so users can type "charge.*"
|
|
56
|
+
# instead of "stripe.charge.*". Empty disables the convenience.
|
|
57
|
+
event_type_prefix: ClassVar[str] = ""
|
|
58
|
+
mode: ClassVar[str] = "event"
|
|
59
|
+
task_queue: ClassVar[str] = TaskQueue.TRIGGERS_EVENT
|
|
60
|
+
Params: ClassVar[Type[BaseModel]] = BaseTriggerParams
|
|
61
|
+
|
|
62
|
+
def __init_subclass__(cls, **kwargs):
|
|
63
|
+
super().__init_subclass__(**kwargs)
|
|
64
|
+
if cls.webhook_source is not None and not getattr(cls, "event_type", ""):
|
|
65
|
+
cls.event_type = cls.webhook_source.type
|
|
66
|
+
|
|
67
|
+
def build_filter(self, params: BaseModel) -> Callable[[Any], bool]:
|
|
68
|
+
type_filter = (getattr(params, "event_type_filter", "") or "all").strip()
|
|
69
|
+
if (
|
|
70
|
+
type_filter
|
|
71
|
+
and type_filter != "all"
|
|
72
|
+
and self.event_type_prefix
|
|
73
|
+
and not type_filter.startswith(self.event_type_prefix)
|
|
74
|
+
):
|
|
75
|
+
type_filter = self.event_type_prefix + type_filter
|
|
76
|
+
extras = self._extra_filter(params)
|
|
77
|
+
|
|
78
|
+
def matches(event: Any) -> bool:
|
|
79
|
+
ev = event if isinstance(event, WorkflowEvent) else WorkflowEvent(**event)
|
|
80
|
+
if not ev.matches_type(type_filter):
|
|
81
|
+
return False
|
|
82
|
+
return extras(ev) if extras else True
|
|
83
|
+
|
|
84
|
+
return matches
|
|
85
|
+
|
|
86
|
+
def _extra_filter(self, params: BaseModel) -> Optional[Callable[[WorkflowEvent], bool]]:
|
|
87
|
+
"""Override for filters beyond event-type matching (e.g. livemode)."""
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
async def _check_precondition(self) -> Optional[str]:
|
|
91
|
+
"""Override to short-circuit. Return error string or None."""
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
async def execute(
|
|
95
|
+
self,
|
|
96
|
+
node_id: str,
|
|
97
|
+
parameters: Dict[str, Any],
|
|
98
|
+
context,
|
|
99
|
+
) -> Dict[str, Any]:
|
|
100
|
+
err = await self._check_precondition()
|
|
101
|
+
if err:
|
|
102
|
+
return self._wrap_error(start_time=time.time(), error=err)
|
|
103
|
+
result = await super().execute(node_id, parameters, context)
|
|
104
|
+
if result.get("success"):
|
|
105
|
+
event = result.get("result") or {}
|
|
106
|
+
ev = event if isinstance(event, WorkflowEvent) else WorkflowEvent(**event)
|
|
107
|
+
result["result"] = self.shape_output(ev)
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
def shape_output(self, event: WorkflowEvent) -> Dict[str, Any]:
|
|
111
|
+
"""Default: dump the CloudEvent. Override for provider-shaped output."""
|
|
112
|
+
return event.model_dump(mode="json")
|
|
113
|
+
|
|
114
|
+
@Operation("wait")
|
|
115
|
+
async def wait(self, ctx: NodeContext, params: BaseModel):
|
|
116
|
+
raise NotImplementedError(
|
|
117
|
+
"Event triggers return via TriggerNode.execute, not the op body"
|
|
118
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Webhook signature verifiers — pluggable HMAC schemes per provider.
|
|
2
|
+
|
|
3
|
+
Each verifier is a stateless class with one method:
|
|
4
|
+
|
|
5
|
+
verify(headers, body, secret) -> None # raises ValueError on mismatch
|
|
6
|
+
|
|
7
|
+
Plugins reference verifier classes directly; there's no string-keyed
|
|
8
|
+
registry to avoid (yet another) lookup table.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from .base import WebhookVerifier
|
|
14
|
+
from .hmac_basic import HmacVerifier
|
|
15
|
+
from .stripe import StripeVerifier
|
|
16
|
+
from .standard_webhooks import StandardWebhooksVerifier
|
|
17
|
+
from .github import GitHubVerifier
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"WebhookVerifier",
|
|
21
|
+
"HmacVerifier",
|
|
22
|
+
"StripeVerifier",
|
|
23
|
+
"StandardWebhooksVerifier",
|
|
24
|
+
"GitHubVerifier",
|
|
25
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Webhook verifier base — single-method contract."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Mapping
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WebhookVerifier:
|
|
9
|
+
"""Abstract base. Each provider's signature scheme is one subclass."""
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def verify(cls, headers: Mapping[str, str], body: bytes, secret: str) -> None:
|
|
13
|
+
"""Raise ``ValueError`` if the request is not authentic.
|
|
14
|
+
|
|
15
|
+
``headers`` is case-insensitive at the caller's discretion;
|
|
16
|
+
verifiers handle the lookup themselves to stay defensive.
|
|
17
|
+
``body`` is the raw request bytes (signed payload).
|
|
18
|
+
``secret`` is the provider-issued signing secret.
|
|
19
|
+
"""
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def _header(headers: Mapping[str, str], name: str) -> str:
|
|
24
|
+
target = name.lower()
|
|
25
|
+
for k, v in headers.items():
|
|
26
|
+
if k.lower() == target:
|
|
27
|
+
return v
|
|
28
|
+
return ""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""GitHub webhook signature verifier.
|
|
2
|
+
|
|
3
|
+
Header: ``X-Hub-Signature-256: sha256=<hex_hmac>``
|
|
4
|
+
Algorithm: HMAC-SHA256 of the secret over the raw body, hex-encoded.
|
|
5
|
+
Reference: https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import hmac
|
|
12
|
+
from typing import Mapping
|
|
13
|
+
|
|
14
|
+
from .base import WebhookVerifier
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GitHubVerifier(WebhookVerifier):
|
|
18
|
+
@classmethod
|
|
19
|
+
def verify(cls, headers: Mapping[str, str], body: bytes, secret: str) -> None:
|
|
20
|
+
sig = cls._header(headers, "x-hub-signature-256")
|
|
21
|
+
if not sig.startswith("sha256="):
|
|
22
|
+
raise ValueError("X-Hub-Signature-256 missing or malformed")
|
|
23
|
+
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
|
|
24
|
+
if not hmac.compare_digest(expected, sig):
|
|
25
|
+
raise ValueError("X-Hub-Signature-256 mismatch")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Generic HMAC-SHA256 verifier — fallback for providers whose scheme
|
|
2
|
+
is "single header carrying hex HMAC of the raw body".
|
|
3
|
+
|
|
4
|
+
Subclasses customise :attr:`header_name` and (optionally)
|
|
5
|
+
:attr:`signature_prefix`. The default reads ``X-Signature-256`` and
|
|
6
|
+
expects a bare hex digest.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import hmac
|
|
13
|
+
from typing import ClassVar, Mapping
|
|
14
|
+
|
|
15
|
+
from .base import WebhookVerifier
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HmacVerifier(WebhookVerifier):
|
|
19
|
+
header_name: ClassVar[str] = "X-Signature-256"
|
|
20
|
+
signature_prefix: ClassVar[str] = "" # e.g. "sha256=" for some providers
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def verify(cls, headers: Mapping[str, str], body: bytes, secret: str) -> None:
|
|
24
|
+
sig = cls._header(headers, cls.header_name)
|
|
25
|
+
if not sig:
|
|
26
|
+
raise ValueError(f"{cls.header_name} header missing")
|
|
27
|
+
if cls.signature_prefix and not sig.startswith(cls.signature_prefix):
|
|
28
|
+
raise ValueError(f"{cls.header_name} missing prefix {cls.signature_prefix!r}")
|
|
29
|
+
provided = sig[len(cls.signature_prefix):]
|
|
30
|
+
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
|
|
31
|
+
if not hmac.compare_digest(expected, provided):
|
|
32
|
+
raise ValueError(f"{cls.header_name} mismatch")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Standard Webhooks (Svix) signature verifier.
|
|
2
|
+
|
|
3
|
+
Spec: https://www.standardwebhooks.com/
|
|
4
|
+
Headers: ``webhook-id``, ``webhook-timestamp``, ``webhook-signature``
|
|
5
|
+
Signed payload: ``f"{webhook-id}.{webhook-timestamp}.{raw_body}"``
|
|
6
|
+
Algorithm: HMAC-SHA256 of the secret (b64-decoded after the ``whsec_`` prefix
|
|
7
|
+
is stripped) over the signed payload, base64-encoded.
|
|
8
|
+
``webhook-signature`` may carry multiple space-separated ``v1,<sig>`` entries
|
|
9
|
+
to support secret rotation.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import base64
|
|
15
|
+
import hashlib
|
|
16
|
+
import hmac
|
|
17
|
+
from typing import Mapping
|
|
18
|
+
|
|
19
|
+
from .base import WebhookVerifier
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StandardWebhooksVerifier(WebhookVerifier):
|
|
23
|
+
@classmethod
|
|
24
|
+
def verify(cls, headers: Mapping[str, str], body: bytes, secret: str) -> None:
|
|
25
|
+
msg_id = cls._header(headers, "webhook-id")
|
|
26
|
+
timestamp = cls._header(headers, "webhook-timestamp")
|
|
27
|
+
sig_header = cls._header(headers, "webhook-signature")
|
|
28
|
+
if not (msg_id and timestamp and sig_header):
|
|
29
|
+
raise ValueError("Standard Webhooks headers missing")
|
|
30
|
+
|
|
31
|
+
if secret.startswith("whsec_"):
|
|
32
|
+
key = base64.b64decode(secret[len("whsec_"):])
|
|
33
|
+
else:
|
|
34
|
+
key = secret.encode()
|
|
35
|
+
|
|
36
|
+
signed = f"{msg_id}.{timestamp}.".encode() + body
|
|
37
|
+
expected = base64.b64encode(
|
|
38
|
+
hmac.new(key, signed, hashlib.sha256).digest()
|
|
39
|
+
).decode()
|
|
40
|
+
|
|
41
|
+
candidates: list[str] = []
|
|
42
|
+
for part in sig_header.split():
|
|
43
|
+
if "," in part:
|
|
44
|
+
_, value = part.split(",", 1)
|
|
45
|
+
candidates.append(value)
|
|
46
|
+
if not any(hmac.compare_digest(expected, c) for c in candidates):
|
|
47
|
+
raise ValueError("Standard Webhooks signature mismatch")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Stripe webhook signature verifier.
|
|
2
|
+
|
|
3
|
+
Header format: ``Stripe-Signature: t=<unix_ts>,v1=<hex_hmac>[,v1=<rotated>]``
|
|
4
|
+
Signed payload: ``f"{timestamp}.{raw_body}"`` (HMAC-SHA256, hex)
|
|
5
|
+
Reference: https://stripe.com/docs/webhooks/signatures
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import hmac
|
|
12
|
+
from typing import Mapping
|
|
13
|
+
|
|
14
|
+
from .base import WebhookVerifier
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StripeVerifier(WebhookVerifier):
|
|
18
|
+
@classmethod
|
|
19
|
+
def verify(cls, headers: Mapping[str, str], body: bytes, secret: str) -> None:
|
|
20
|
+
sig_header = cls._header(headers, "stripe-signature")
|
|
21
|
+
if not sig_header:
|
|
22
|
+
raise ValueError("Stripe-Signature header missing")
|
|
23
|
+
|
|
24
|
+
timestamp = ""
|
|
25
|
+
candidates: list[str] = []
|
|
26
|
+
for part in sig_header.split(","):
|
|
27
|
+
if "=" not in part:
|
|
28
|
+
continue
|
|
29
|
+
k, v = part.split("=", 1)
|
|
30
|
+
k = k.strip()
|
|
31
|
+
v = v.strip()
|
|
32
|
+
if k == "t":
|
|
33
|
+
timestamp = v
|
|
34
|
+
elif k == "v1":
|
|
35
|
+
candidates.append(v)
|
|
36
|
+
if not timestamp or not candidates:
|
|
37
|
+
raise ValueError("Stripe-Signature missing t= or v1= component")
|
|
38
|
+
|
|
39
|
+
signed = f"{timestamp}.".encode() + body
|
|
40
|
+
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
|
|
41
|
+
if not any(hmac.compare_digest(expected, c) for c in candidates):
|
|
42
|
+
raise ValueError("Stripe-Signature mismatch")
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""WebhookSource — push source backed by an HTTP POST to /webhook/{path}.
|
|
2
|
+
|
|
3
|
+
Plugins subclass and declare:
|
|
4
|
+
|
|
5
|
+
class MyWebhook(WebhookSource):
|
|
6
|
+
path = "myprovider"
|
|
7
|
+
verifier = MyVerifier # WebhookVerifier subclass (optional)
|
|
8
|
+
secret_field = "my_webhook_secret" # extra field on the credential
|
|
9
|
+
|
|
10
|
+
async def shape(self, request, body, payload) -> WorkflowEvent: ...
|
|
11
|
+
|
|
12
|
+
A single edit to ``routers/webhook.py`` consults :data:`WEBHOOK_SOURCES`
|
|
13
|
+
before the legacy generic dispatch path. No plugin name is hardcoded
|
|
14
|
+
in the router.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
from typing import ClassVar, Dict, Optional, Type
|
|
21
|
+
|
|
22
|
+
from fastapi import HTTPException, Request
|
|
23
|
+
|
|
24
|
+
from core.logging import get_logger
|
|
25
|
+
|
|
26
|
+
from .envelope import WorkflowEvent
|
|
27
|
+
from .push import PushEventSource
|
|
28
|
+
from .verifiers import WebhookVerifier
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
WEBHOOK_SOURCES: Dict[str, "WebhookSource"] = {}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def register_webhook_source(source: "WebhookSource") -> None:
|
|
37
|
+
"""Idempotent: same instance for the same path is a no-op; conflicts raise."""
|
|
38
|
+
existing = WEBHOOK_SOURCES.get(source.path)
|
|
39
|
+
if existing is not None and existing is not source:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
f"Webhook path {source.path!r} already registered to {type(existing).__name__}"
|
|
42
|
+
)
|
|
43
|
+
WEBHOOK_SOURCES[source.path] = source
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class WebhookSource(PushEventSource):
|
|
47
|
+
"""Base for HTTP-webhook event sources.
|
|
48
|
+
|
|
49
|
+
Subclass contract:
|
|
50
|
+
path: URL fragment under /webhook/
|
|
51
|
+
verifier: WebhookVerifier subclass (or None to skip verification)
|
|
52
|
+
secret_field: credential extra-field name holding the signing secret
|
|
53
|
+
shape(): turn the verified request into a WorkflowEvent
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
path: ClassVar[str] = ""
|
|
57
|
+
verifier: ClassVar[Optional[Type[WebhookVerifier]]] = None
|
|
58
|
+
secret_field: ClassVar[Optional[str]] = None
|
|
59
|
+
|
|
60
|
+
async def _resolve_secret(self) -> Optional[str]:
|
|
61
|
+
if self.secret_field is None or self.credential is None:
|
|
62
|
+
return None
|
|
63
|
+
try:
|
|
64
|
+
secrets = await self.credential.resolve()
|
|
65
|
+
except PermissionError:
|
|
66
|
+
return None
|
|
67
|
+
return secrets.get(self.secret_field)
|
|
68
|
+
|
|
69
|
+
async def shape(self, request: Request, body: bytes, payload: dict) -> WorkflowEvent:
|
|
70
|
+
"""Override to map the verified payload into a CloudEvent."""
|
|
71
|
+
return WorkflowEvent(
|
|
72
|
+
source=f"webhook://{self.path}",
|
|
73
|
+
type=f"webhook.{self.path}",
|
|
74
|
+
data=payload,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
async def handle(self, request: Request) -> WorkflowEvent:
|
|
78
|
+
"""Called by the webhook router. Verifies signature, parses the
|
|
79
|
+
body, shapes the event, dispatches into ``event_waiter``, and
|
|
80
|
+
returns the event for logging / response shaping."""
|
|
81
|
+
body = await request.body()
|
|
82
|
+
if self.verifier is not None:
|
|
83
|
+
secret = await self._resolve_secret()
|
|
84
|
+
if not secret:
|
|
85
|
+
logger.warning(
|
|
86
|
+
"[%s] no signing secret available; accepting unverified event",
|
|
87
|
+
self.type or self.path,
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
try:
|
|
91
|
+
self.verifier.verify(dict(request.headers), body, secret)
|
|
92
|
+
except ValueError as e:
|
|
93
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
payload = json.loads(body.decode() or "{}")
|
|
97
|
+
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
|
98
|
+
raise HTTPException(status_code=400, detail=f"invalid JSON body: {e}")
|
|
99
|
+
|
|
100
|
+
event = await self.shape(request, body, payload)
|
|
101
|
+
await self.receive(event)
|
|
102
|
+
|
|
103
|
+
from services import event_waiter
|
|
104
|
+
event_waiter.dispatch(self.type, event)
|
|
105
|
+
return event
|
|
@@ -145,8 +145,19 @@ async def _dispatch_tool(tool_name: str, tool_args: Dict[str, Any],
|
|
|
145
145
|
node_type = config.get('node_type', '')
|
|
146
146
|
node_params = config.get('parameters', {})
|
|
147
147
|
|
|
148
|
-
# Execution context
|
|
149
|
-
|
|
148
|
+
# Execution context for tool handlers. Includes canvas state
|
|
149
|
+
# (nodes/edges/workflow_id) that callers (chat_tool_executor in
|
|
150
|
+
# ai.py / rlm adapter / deepagents adapter) plumb in via `config`.
|
|
151
|
+
# Tools that need canvas awareness (e.g. agentBuilder.inspect_canvas)
|
|
152
|
+
# read these via NodeContext.nodes / .edges / .workflow_id; tools
|
|
153
|
+
# that don't simply ignore them.
|
|
154
|
+
context = {
|
|
155
|
+
'workspace_dir': config.get('workspace_dir', ''),
|
|
156
|
+
'nodes': config.get('nodes', []),
|
|
157
|
+
'edges': config.get('edges', []),
|
|
158
|
+
'workflow_id': config.get('workflow_id'),
|
|
159
|
+
'parent_node_id': config.get('parent_node_id'),
|
|
160
|
+
}
|
|
150
161
|
|
|
151
162
|
logger.info("[Tool] Executing '%s' (node_type=%s, workspace=%s)", tool_name, node_type, context['workspace_dir'])
|
|
152
163
|
|
|
@@ -201,10 +212,12 @@ async def _dispatch_tool(tool_name: str, tool_args: Dict[str, Any],
|
|
|
201
212
|
from nodes.android._base import execute_android_service_tool
|
|
202
213
|
return await execute_android_service_tool(tool_args, config)
|
|
203
214
|
|
|
204
|
-
# taskManager: dispatcher
|
|
215
|
+
# taskManager: dispatcher reads through to the agent-delegation
|
|
205
216
|
# tracking dicts (_delegated_tasks / _delegation_results) defined
|
|
206
|
-
# below in this module
|
|
217
|
+
# below in this module. The operation matrix moved to the plugin
|
|
218
|
+
# (Wave 11.I, milestone O).
|
|
207
219
|
if node_type == 'taskManager':
|
|
220
|
+
from nodes.tool.task_manager import _execute_task_manager
|
|
208
221
|
return await _execute_task_manager(tool_args, config)
|
|
209
222
|
|
|
210
223
|
# proxyConfig: 10-operation matrix lives on the plugin
|
|
@@ -242,34 +255,11 @@ async def _dispatch_tool(tool_name: str, tool_args: Dict[str, Any],
|
|
|
242
255
|
|
|
243
256
|
|
|
244
257
|
# _execute_duckduckgo_search: Wave 11.C moved logic to
|
|
245
|
-
# nodes/search/duckduckgo_search.py DuckDuckGoSearchNode.search()
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
|
|
250
|
-
args: Dict[str, Any],
|
|
251
|
-
config: Dict[str, Any] = None,
|
|
252
|
-
) -> Dict[str, Any]:
|
|
253
|
-
"""Back-compat wrapper around the plugin search path."""
|
|
254
|
-
config = config or {}
|
|
255
|
-
query = str(args.get("query", "")).strip()
|
|
256
|
-
if not query:
|
|
257
|
-
return {"error": "No search query provided"}
|
|
258
|
-
max_results = int(config.get("max_results", args.get("max_results", 5)))
|
|
259
|
-
provider = config.get("provider", "duckduckgo")
|
|
260
|
-
from ddgs import DDGS
|
|
261
|
-
raw = list(DDGS().text(query, max_results=max_results))
|
|
262
|
-
results = [
|
|
263
|
-
{
|
|
264
|
-
"title": item.get("title", ""),
|
|
265
|
-
"snippet": item.get("body", ""),
|
|
266
|
-
"url": item.get("href", ""),
|
|
267
|
-
}
|
|
268
|
-
for item in raw
|
|
269
|
-
]
|
|
270
|
-
return {"query": query, "provider": provider, "results": results}
|
|
271
|
-
|
|
272
|
-
|
|
258
|
+
# nodes/search/duckduckgo_search.py DuckDuckGoSearchNode.search(). Wave
|
|
259
|
+
# 11.I milestone O moved the flat (args, config) -> dict shim used by
|
|
260
|
+
# contract tests to tests/nodes/_compat.py -- production code never
|
|
261
|
+
# called it.
|
|
262
|
+
#
|
|
273
263
|
# Wave 11.D.9: _execute_whatsapp_{send,db} deleted. WhatsApp tool execution
|
|
274
264
|
# now routes through the plugin fast-path (nodes/whatsapp/*.py).
|
|
275
265
|
|
|
@@ -779,160 +769,12 @@ async def _execute_check_delegated_tasks(args: Dict[str, Any],
|
|
|
779
769
|
}
|
|
780
770
|
|
|
781
771
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
)
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
Dual-purpose: works as AI tool (LLM fills args) or workflow node (uses params).
|
|
789
|
-
|
|
790
|
-
Operations:
|
|
791
|
-
- list_tasks: List all active and completed delegated tasks
|
|
792
|
-
- get_task: Get status details for a specific task
|
|
793
|
-
- mark_done: Remove a task from active tracking
|
|
794
|
-
|
|
795
|
-
Args:
|
|
796
|
-
tool_args: Arguments from LLM tool call (operation, task_id, status_filter)
|
|
797
|
-
config: Tool configuration with node parameters
|
|
798
|
-
|
|
799
|
-
Returns:
|
|
800
|
-
Dict with operation results
|
|
801
|
-
"""
|
|
802
|
-
# Merge tool_args with node parameters (tool_args takes precedence)
|
|
803
|
-
params = config.get('parameters', {})
|
|
804
|
-
operation = tool_args.get('operation') or params.get('operation', 'list_tasks')
|
|
805
|
-
task_id = tool_args.get('task_id') or params.get('task_id')
|
|
806
|
-
status_filter = tool_args.get('status_filter') or params.get('status_filter')
|
|
807
|
-
database = config.get('database')
|
|
808
|
-
|
|
809
|
-
logger.debug(f"[TaskManager] Operation: {operation}, task_id: {task_id}, filter: {status_filter}")
|
|
810
|
-
|
|
811
|
-
if operation == 'list_tasks':
|
|
812
|
-
# Collect all tasks from _delegated_tasks and _delegation_results
|
|
813
|
-
tasks = []
|
|
814
|
-
|
|
815
|
-
# Active tasks from asyncio.Task tracking
|
|
816
|
-
for tid, task in _delegated_tasks.items():
|
|
817
|
-
if task.done():
|
|
818
|
-
try:
|
|
819
|
-
if task.cancelled():
|
|
820
|
-
status = 'cancelled'
|
|
821
|
-
elif task.exception():
|
|
822
|
-
status = 'error'
|
|
823
|
-
else:
|
|
824
|
-
status = 'completed'
|
|
825
|
-
except Exception:
|
|
826
|
-
status = 'completed'
|
|
827
|
-
else:
|
|
828
|
-
status = 'running'
|
|
829
|
-
|
|
830
|
-
tasks.append({
|
|
831
|
-
'task_id': tid,
|
|
832
|
-
'status': status,
|
|
833
|
-
'active': True
|
|
834
|
-
})
|
|
835
|
-
|
|
836
|
-
# Completed tasks from in-memory cache
|
|
837
|
-
for tid, result in _delegation_results.items():
|
|
838
|
-
if tid not in [t['task_id'] for t in tasks]:
|
|
839
|
-
tasks.append({
|
|
840
|
-
'task_id': tid,
|
|
841
|
-
'status': result.get('status', 'completed'),
|
|
842
|
-
'agent_name': result.get('agent_name'),
|
|
843
|
-
'result_summary': str(result.get('result', ''))[:200],
|
|
844
|
-
'active': False
|
|
845
|
-
})
|
|
846
|
-
|
|
847
|
-
# Apply status filter
|
|
848
|
-
if status_filter:
|
|
849
|
-
tasks = [t for t in tasks if t.get('status') == status_filter]
|
|
850
|
-
|
|
851
|
-
return {
|
|
852
|
-
'success': True,
|
|
853
|
-
'operation': 'list_tasks',
|
|
854
|
-
'tasks': tasks,
|
|
855
|
-
'count': len(tasks),
|
|
856
|
-
'running': sum(1 for t in tasks if t.get('status') == 'running'),
|
|
857
|
-
'completed': sum(1 for t in tasks if t.get('status') == 'completed'),
|
|
858
|
-
'errors': sum(1 for t in tasks if t.get('status') == 'error')
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
elif operation == 'get_task':
|
|
862
|
-
if not task_id:
|
|
863
|
-
return {'success': False, 'error': 'task_id is required for get_task operation'}
|
|
864
|
-
|
|
865
|
-
# Use existing get_delegated_task_status for detailed lookup
|
|
866
|
-
result = await get_delegated_task_status(task_ids=[task_id], database=database)
|
|
867
|
-
tasks = result.get('tasks', [])
|
|
868
|
-
|
|
869
|
-
if not tasks:
|
|
870
|
-
return {
|
|
871
|
-
'success': False,
|
|
872
|
-
'error': f'Task {task_id} not found',
|
|
873
|
-
'task_id': task_id
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
task_info = tasks[0]
|
|
877
|
-
return {
|
|
878
|
-
'success': True,
|
|
879
|
-
'operation': 'get_task',
|
|
880
|
-
'task_id': task_id,
|
|
881
|
-
'status': task_info.get('status'),
|
|
882
|
-
'agent_name': task_info.get('agent_name'),
|
|
883
|
-
'result': task_info.get('result'),
|
|
884
|
-
'error': task_info.get('error')
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
elif operation == 'mark_done':
|
|
888
|
-
if not task_id:
|
|
889
|
-
return {'success': False, 'error': 'task_id is required for mark_done operation'}
|
|
890
|
-
|
|
891
|
-
# Remove from active tracking
|
|
892
|
-
removed = False
|
|
893
|
-
if task_id in _delegated_tasks:
|
|
894
|
-
del _delegated_tasks[task_id]
|
|
895
|
-
removed = True
|
|
896
|
-
if task_id in _delegation_results:
|
|
897
|
-
del _delegation_results[task_id]
|
|
898
|
-
removed = True
|
|
899
|
-
|
|
900
|
-
return {
|
|
901
|
-
'success': True,
|
|
902
|
-
'operation': 'mark_done',
|
|
903
|
-
'task_id': task_id,
|
|
904
|
-
'removed': removed,
|
|
905
|
-
'message': f'Task {task_id} marked as done and removed from tracking' if removed else f'Task {task_id} was not in active tracking'
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
return {'success': False, 'error': f'Unknown operation: {operation}'}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
async def handle_task_manager(
|
|
912
|
-
node_id: str,
|
|
913
|
-
node_type: str,
|
|
914
|
-
parameters: Dict[str, Any],
|
|
915
|
-
context: Dict[str, Any]
|
|
916
|
-
) -> Dict[str, Any]:
|
|
917
|
-
"""Handle taskManager as workflow node.
|
|
918
|
-
|
|
919
|
-
Wrapper for _execute_task_manager that conforms to the standard
|
|
920
|
-
workflow node handler signature.
|
|
921
|
-
|
|
922
|
-
Args:
|
|
923
|
-
node_id: The node ID
|
|
924
|
-
node_type: Should be 'taskManager'
|
|
925
|
-
parameters: Node parameters (operation, task_id, status_filter)
|
|
926
|
-
context: Execution context
|
|
927
|
-
|
|
928
|
-
Returns:
|
|
929
|
-
Task manager operation results
|
|
930
|
-
"""
|
|
931
|
-
config = {
|
|
932
|
-
'parameters': parameters,
|
|
933
|
-
'database': context.get('database')
|
|
934
|
-
}
|
|
935
|
-
return await _execute_task_manager({}, config)
|
|
772
|
+
# _execute_task_manager + handle_task_manager: Wave 11.I milestone O moved
|
|
773
|
+
# the operation matrix to nodes/tool/task_manager.py. The plugin reads
|
|
774
|
+
# through to the delegation registry (``_delegated_tasks`` /
|
|
775
|
+
# ``_delegation_results`` / ``get_delegated_task_status``) which still
|
|
776
|
+
# lives in this module -- delegation lifecycle is genuine cross-cutting
|
|
777
|
+
# framework state. ``handle_task_manager`` had no callers and was deleted.
|
|
936
778
|
|
|
937
779
|
|
|
938
780
|
# =============================================================================
|