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,4 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
+
"agentBuilder": {
|
|
3
|
+
"icon": "🛠️",
|
|
4
|
+
"color": "#ffb86c",
|
|
5
|
+
"skill": "agent-builder-skill"
|
|
6
|
+
},
|
|
2
7
|
"aiAgent": {
|
|
3
8
|
"icon": "🤖",
|
|
4
9
|
"color": "#bd93f9"
|
|
@@ -8,7 +13,8 @@
|
|
|
8
13
|
"color": "#bd93f9"
|
|
9
14
|
},
|
|
10
15
|
"airplaneModeControl": {
|
|
11
|
-
"icon": "✈️"
|
|
16
|
+
"icon": "✈️",
|
|
17
|
+
"color": "#50fa7b"
|
|
12
18
|
},
|
|
13
19
|
"android_agent": {
|
|
14
20
|
"icon": "📱",
|
|
@@ -25,15 +31,18 @@
|
|
|
25
31
|
},
|
|
26
32
|
"appLauncher": {
|
|
27
33
|
"icon": "🚀",
|
|
28
|
-
"skill": "app-launcher-skill"
|
|
34
|
+
"skill": "app-launcher-skill",
|
|
35
|
+
"color": "#50fa7b"
|
|
29
36
|
},
|
|
30
37
|
"appList": {
|
|
31
38
|
"icon": "📋",
|
|
32
|
-
"skill": "app-list-skill"
|
|
39
|
+
"skill": "app-list-skill",
|
|
40
|
+
"color": "#50fa7b"
|
|
33
41
|
},
|
|
34
42
|
"audioAutomation": {
|
|
35
43
|
"icon": "🔊",
|
|
36
|
-
"skill": "audio-skill"
|
|
44
|
+
"skill": "audio-skill",
|
|
45
|
+
"color": "#50fa7b"
|
|
37
46
|
},
|
|
38
47
|
"autonomous_agent": {
|
|
39
48
|
"icon": "🎯",
|
|
@@ -41,11 +50,13 @@
|
|
|
41
50
|
},
|
|
42
51
|
"batteryMonitor": {
|
|
43
52
|
"icon": "🔋",
|
|
44
|
-
"skill": "battery-skill"
|
|
53
|
+
"skill": "battery-skill",
|
|
54
|
+
"color": "#50fa7b"
|
|
45
55
|
},
|
|
46
56
|
"bluetoothAutomation": {
|
|
47
57
|
"icon": "🔵",
|
|
48
|
-
"skill": "bluetooth-skill"
|
|
58
|
+
"skill": "bluetooth-skill",
|
|
59
|
+
"color": "#50fa7b"
|
|
49
60
|
},
|
|
50
61
|
"braveSearch": {
|
|
51
62
|
"icon": "asset:brave",
|
|
@@ -63,7 +74,8 @@
|
|
|
63
74
|
},
|
|
64
75
|
"cameraControl": {
|
|
65
76
|
"icon": "📷",
|
|
66
|
-
"skill": "camera-skill"
|
|
77
|
+
"skill": "camera-skill",
|
|
78
|
+
"color": "#50fa7b"
|
|
67
79
|
},
|
|
68
80
|
"cerebrasChatModel": {
|
|
69
81
|
"icon": "lobehub:cerebras",
|
|
@@ -89,6 +101,10 @@
|
|
|
89
101
|
"icon": "lobehub:Claude",
|
|
90
102
|
"color": "#8be9fd"
|
|
91
103
|
},
|
|
104
|
+
"codex_agent": {
|
|
105
|
+
"icon": "lobehub:OpenAI",
|
|
106
|
+
"color": "#ffb86c"
|
|
107
|
+
},
|
|
92
108
|
"coding_agent": {
|
|
93
109
|
"icon": "💻",
|
|
94
110
|
"color": "#8be9fd"
|
|
@@ -124,7 +140,8 @@
|
|
|
124
140
|
"color": "#8be9fd"
|
|
125
141
|
},
|
|
126
142
|
"deviceStateAutomation": {
|
|
127
|
-
"icon": "⚙️"
|
|
143
|
+
"icon": "⚙️",
|
|
144
|
+
"color": "#50fa7b"
|
|
128
145
|
},
|
|
129
146
|
"documentParser": {
|
|
130
147
|
"icon": "📄",
|
|
@@ -153,7 +170,8 @@
|
|
|
153
170
|
},
|
|
154
171
|
"environmentalSensors": {
|
|
155
172
|
"icon": "🌡️",
|
|
156
|
-
"skill": "environmental-skill"
|
|
173
|
+
"skill": "environmental-skill",
|
|
174
|
+
"color": "#50fa7b"
|
|
157
175
|
},
|
|
158
176
|
"fileDownloader": {
|
|
159
177
|
"icon": "⬇️",
|
|
@@ -245,22 +263,29 @@
|
|
|
245
263
|
},
|
|
246
264
|
"javascriptExecutor": {
|
|
247
265
|
"icon": "asset:javascript",
|
|
248
|
-
"skill": "javascript-skill"
|
|
266
|
+
"skill": "javascript-skill",
|
|
267
|
+
"color": "#ffb86c"
|
|
249
268
|
},
|
|
250
269
|
"kimiChatModel": {
|
|
251
270
|
"icon": "lobehub:kimi",
|
|
252
271
|
"color": "#bd93f9"
|
|
253
272
|
},
|
|
273
|
+
"lmstudioChatModel": {
|
|
274
|
+
"icon": "lobehub:lmstudio",
|
|
275
|
+
"color": "#4F8EFF"
|
|
276
|
+
},
|
|
254
277
|
"location": {
|
|
255
278
|
"icon": "📍",
|
|
256
|
-
"skill": "location-skill"
|
|
279
|
+
"skill": "location-skill",
|
|
280
|
+
"color": "#50fa7b"
|
|
257
281
|
},
|
|
258
282
|
"masterSkill": {
|
|
259
283
|
"icon": "🎯",
|
|
260
284
|
"color": "#f1fa8c"
|
|
261
285
|
},
|
|
262
286
|
"mediaControl": {
|
|
263
|
-
"icon": "🎵"
|
|
287
|
+
"icon": "🎵",
|
|
288
|
+
"color": "#50fa7b"
|
|
264
289
|
},
|
|
265
290
|
"mistralChatModel": {
|
|
266
291
|
"icon": "lobehub:mistral",
|
|
@@ -268,10 +293,16 @@
|
|
|
268
293
|
},
|
|
269
294
|
"motionDetection": {
|
|
270
295
|
"icon": "📳",
|
|
271
|
-
"skill": "motion-skill"
|
|
296
|
+
"skill": "motion-skill",
|
|
297
|
+
"color": "#50fa7b"
|
|
272
298
|
},
|
|
273
299
|
"networkMonitor": {
|
|
274
|
-
"icon": "📡"
|
|
300
|
+
"icon": "📡",
|
|
301
|
+
"color": "#50fa7b"
|
|
302
|
+
},
|
|
303
|
+
"ollamaChatModel": {
|
|
304
|
+
"icon": "lobehub:ollama",
|
|
305
|
+
"color": "#000000"
|
|
275
306
|
},
|
|
276
307
|
"openaiChatModel": {
|
|
277
308
|
"icon": "lobehub:openai",
|
|
@@ -318,7 +349,8 @@
|
|
|
318
349
|
},
|
|
319
350
|
"pythonExecutor": {
|
|
320
351
|
"icon": "asset:python",
|
|
321
|
-
"skill": "python-skill"
|
|
352
|
+
"skill": "python-skill",
|
|
353
|
+
"color": "#ffb86c"
|
|
322
354
|
},
|
|
323
355
|
"rlm_agent": {
|
|
324
356
|
"icon": "🧠",
|
|
@@ -326,7 +358,8 @@
|
|
|
326
358
|
},
|
|
327
359
|
"screenControlAutomation": {
|
|
328
360
|
"icon": "💡",
|
|
329
|
-
"skill": "screen-control-skill"
|
|
361
|
+
"skill": "screen-control-skill",
|
|
362
|
+
"color": "#50fa7b"
|
|
330
363
|
},
|
|
331
364
|
"serperSearch": {
|
|
332
365
|
"icon": "asset:google",
|
|
@@ -359,7 +392,8 @@
|
|
|
359
392
|
"color": "#bd93f9"
|
|
360
393
|
},
|
|
361
394
|
"systemInfo": {
|
|
362
|
-
"icon": "📱"
|
|
395
|
+
"icon": "📱",
|
|
396
|
+
"color": "#50fa7b"
|
|
363
397
|
},
|
|
364
398
|
"taskManager": {
|
|
365
399
|
"icon": "📋",
|
|
@@ -427,7 +461,8 @@
|
|
|
427
461
|
"skill": "twitter-user-skill"
|
|
428
462
|
},
|
|
429
463
|
"typescriptExecutor": {
|
|
430
|
-
"icon": "asset:typescript"
|
|
464
|
+
"icon": "asset:typescript",
|
|
465
|
+
"color": "#ffb86c"
|
|
431
466
|
},
|
|
432
467
|
"vectorStore": {
|
|
433
468
|
"icon": "🗄️",
|
|
@@ -461,11 +496,21 @@
|
|
|
461
496
|
},
|
|
462
497
|
"wifiAutomation": {
|
|
463
498
|
"icon": "📶",
|
|
464
|
-
"skill": "wifi-skill"
|
|
499
|
+
"skill": "wifi-skill",
|
|
500
|
+
"color": "#50fa7b"
|
|
465
501
|
},
|
|
466
502
|
"writeTodos": {
|
|
467
503
|
"icon": "📝",
|
|
468
504
|
"color": "#bd93f9",
|
|
469
505
|
"skill": "write-todos-skill"
|
|
506
|
+
},
|
|
507
|
+
"stripeAction": {
|
|
508
|
+
"icon": "asset:stripe",
|
|
509
|
+
"color": "#635BFF",
|
|
510
|
+
"skill": "stripe-skill"
|
|
511
|
+
},
|
|
512
|
+
"stripeReceive": {
|
|
513
|
+
"icon": "asset:stripe",
|
|
514
|
+
"color": "#635BFF"
|
|
470
515
|
}
|
|
471
516
|
}
|
|
@@ -4,20 +4,60 @@ Public surface:
|
|
|
4
4
|
WhatsAppRuntime - edgymeow Go binary supervisor
|
|
5
5
|
get_whatsapp_runtime() - singleton accessor (lazy-init via classmethod)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
Two self-registrations happen on package import:
|
|
8
|
+
|
|
9
|
+
1. The Go-binary supervisor (``WhatsAppRuntime``) is registered with
|
|
10
|
+
:mod:`services._supervisor`, so the FastAPI lifespan teardown
|
|
11
|
+
reaches it via ``shutdown_all_supervisors()``.
|
|
12
|
+
|
|
13
|
+
2. The WebSocket handler dispatch table (``WS_HANDLERS`` in
|
|
14
|
+
:mod:`._handlers`) is registered with
|
|
15
|
+
:mod:`services.ws_handler_registry`, so the central WS router in
|
|
16
|
+
``routers/websocket.py`` picks up every ``whatsapp_*`` message type
|
|
17
|
+
without per-plugin imports. Same pattern as the telegram reference
|
|
18
|
+
plugin.
|
|
11
19
|
"""
|
|
12
20
|
|
|
13
21
|
from services._supervisor import register_supervisor
|
|
22
|
+
from services.event_waiter import register_filter_builder
|
|
23
|
+
from services.status_broadcaster import register_service_refresh
|
|
24
|
+
from services.ws_handler_registry import (
|
|
25
|
+
register_option_loader,
|
|
26
|
+
register_ws_handlers,
|
|
27
|
+
)
|
|
14
28
|
|
|
29
|
+
from ._filters import build_filter as build_whatsapp_filter
|
|
30
|
+
from ._handlers import WS_HANDLERS
|
|
31
|
+
from ._option_loaders import load_channels, load_group_members, load_groups
|
|
32
|
+
from ._refresh import refresh_whatsapp_status
|
|
15
33
|
from ._runtime import WhatsAppRuntime, get_whatsapp_runtime
|
|
16
34
|
|
|
17
|
-
#
|
|
35
|
+
# Supervisor: ensures shutdown_all_supervisors() reaches us.
|
|
18
36
|
# get_instance() constructs the singleton once (lazy in spawn, not here).
|
|
19
37
|
register_supervisor(WhatsAppRuntime.get_instance())
|
|
20
38
|
|
|
39
|
+
# WebSocket handlers: 19 message types (status / qr / send / restart /
|
|
40
|
+
# groups / newsletters / chat_history / rate_limit_* / mark_read /
|
|
41
|
+
# typing / presence / diagnostics / stop). Idempotent on re-import.
|
|
42
|
+
register_ws_handlers(WS_HANDLERS)
|
|
43
|
+
|
|
44
|
+
# Service-status refresh callback (Wave 11.I, milestone J) -- the
|
|
45
|
+
# broadcaster fans out to this on lifespan startup instead of
|
|
46
|
+
# hardcoding the call.
|
|
47
|
+
register_service_refresh(refresh_whatsapp_status)
|
|
48
|
+
|
|
49
|
+
# Trigger-event filter builder (Wave 11.I, milestone K) -- moved out
|
|
50
|
+
# of services/event_waiter.py so the central FILTER_BUILDERS table
|
|
51
|
+
# carries no plugin-specific code.
|
|
52
|
+
register_filter_builder("whatsappReceive", build_whatsapp_filter)
|
|
53
|
+
|
|
54
|
+
# loadOptionsMethod loaders (Wave 11.I, milestone M.1) -- self-register
|
|
55
|
+
# into services.ws_handler_registry's option-loader registry, sibling to
|
|
56
|
+
# the WS-handler / router registries.
|
|
57
|
+
register_option_loader("whatsappGroups", load_groups)
|
|
58
|
+
register_option_loader("whatsappChannels", load_channels)
|
|
59
|
+
register_option_loader("whatsappGroupMembers", load_group_members)
|
|
60
|
+
|
|
21
61
|
__all__ = [
|
|
22
62
|
"WhatsAppRuntime",
|
|
23
63
|
"get_whatsapp_runtime",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""WhatsApp send + DB business logic (Wave 11.D.9 inlined).
|
|
2
2
|
|
|
3
3
|
Imported by nodes.whatsapp.whatsapp_send and nodes.whatsapp.whatsapp_db.
|
|
4
|
-
RPC dispatch still flows through
|
|
4
|
+
RPC dispatch still flows through nodes.whatsapp._service (HTTP to the Go
|
|
5
5
|
bridge) — extracting that is a separate refactor, not required for the
|
|
6
6
|
plugin migration.
|
|
7
7
|
"""
|
|
@@ -145,7 +145,7 @@ async def handle_whatsapp_send(
|
|
|
145
145
|
Returns:
|
|
146
146
|
Execution result dict
|
|
147
147
|
"""
|
|
148
|
-
from
|
|
148
|
+
from nodes.whatsapp._service import handle_whatsapp_send as whatsapp_send_handler
|
|
149
149
|
start_time = time.time()
|
|
150
150
|
|
|
151
151
|
try:
|
|
@@ -289,7 +289,7 @@ async def handle_whatsapp_db(
|
|
|
289
289
|
Returns:
|
|
290
290
|
Execution result dict
|
|
291
291
|
"""
|
|
292
|
-
from
|
|
292
|
+
from nodes.whatsapp._service import (
|
|
293
293
|
handle_whatsapp_chat_history as whatsapp_chat_history_handler,
|
|
294
294
|
whatsapp_rpc_call
|
|
295
295
|
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""WhatsApp event-trigger filter builder (Wave 11.I, milestone K).
|
|
2
|
+
|
|
3
|
+
Moved verbatim from ``services/event_waiter.build_whatsapp_filter``.
|
|
4
|
+
The plugin's ``__init__.py`` self-registers via
|
|
5
|
+
``event_waiter.register_filter_builder("whatsappReceive", build_filter)``;
|
|
6
|
+
the central event_waiter dispatch table no longer hardcodes this
|
|
7
|
+
plugin's filter.
|
|
8
|
+
|
|
9
|
+
The filter shape mirrors the event payload shipped by the Go RPC's
|
|
10
|
+
``handleIncomingMessage()`` (see ``service.go``). Snake_case
|
|
11
|
+
parameters throughout — matches the plugin Params with no camelCase
|
|
12
|
+
aliases.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from typing import Callable, Dict
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_filter(params: Dict) -> Callable[[Dict], bool]:
|
|
24
|
+
"""Build filter function for WhatsApp messages.
|
|
25
|
+
|
|
26
|
+
Based on Go RPC ``handleIncomingMessage()`` event fields:
|
|
27
|
+
|
|
28
|
+
- ``message_id`` -- unique message ID
|
|
29
|
+
- ``sender`` -- Sender JID (may be LID for groups)
|
|
30
|
+
- ``sender_phone`` -- RESOLVED phone number (LID already resolved)
|
|
31
|
+
- ``chat_id`` -- Chat JID (same as sender for DMs, group JID for groups)
|
|
32
|
+
- ``timestamp`` -- message timestamp
|
|
33
|
+
- ``is_from_me`` -- true if sent by connected account
|
|
34
|
+
- ``is_group`` -- true if message is in a group chat
|
|
35
|
+
- ``message_type`` -- text / image / video / audio / document /
|
|
36
|
+
sticker / location / contact / contacts
|
|
37
|
+
- ``text`` -- text content (for text messages)
|
|
38
|
+
- ``is_forwarded`` -- true if message is forwarded
|
|
39
|
+
- ``forwarding_score`` -- forwarding count
|
|
40
|
+
- ``group_info`` -- present for group messages, with ``group_jid``
|
|
41
|
+
/ ``sender_jid`` / ``sender_phone`` / ``sender_name``
|
|
42
|
+
"""
|
|
43
|
+
msg_type = params.get('message_type_filter', 'all')
|
|
44
|
+
sender_filter = params.get('filter', 'all')
|
|
45
|
+
contact_phone = params.get('phone_number', '')
|
|
46
|
+
group_id = params.get('group_id', '')
|
|
47
|
+
sender_number = params.get('sender_number', '')
|
|
48
|
+
keywords = [
|
|
49
|
+
k.strip().lower()
|
|
50
|
+
for k in params.get('keywords', '').split(',')
|
|
51
|
+
if k.strip()
|
|
52
|
+
]
|
|
53
|
+
ignore_own = params.get('ignore_own_messages', True)
|
|
54
|
+
forwarded_filter = params.get('forwarded_filter', 'all')
|
|
55
|
+
|
|
56
|
+
logger.debug(
|
|
57
|
+
"[WhatsAppFilter] Built: type=%s, filter=%s, group_id=%r, forwarded=%s",
|
|
58
|
+
msg_type, sender_filter, group_id, forwarded_filter,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def matches(m: Dict) -> bool:
|
|
62
|
+
msg_chat_id = m.get('chat_id', '')
|
|
63
|
+
is_group = m.get('is_group', False)
|
|
64
|
+
group_info = m.get('group_info', {})
|
|
65
|
+
|
|
66
|
+
# Use sender_phone directly -- Go RPC already resolves LIDs.
|
|
67
|
+
# For group messages, prefer group_info.sender_phone, fall back
|
|
68
|
+
# to root sender_phone.
|
|
69
|
+
if is_group:
|
|
70
|
+
sender_phone = group_info.get('sender_phone', '') or m.get('sender_phone', '')
|
|
71
|
+
else:
|
|
72
|
+
sender_phone = m.get('sender_phone', '')
|
|
73
|
+
|
|
74
|
+
# Fallback: extract phone from sender JID if sender_phone absent.
|
|
75
|
+
if not sender_phone:
|
|
76
|
+
sender = m.get('sender', '')
|
|
77
|
+
sender_phone = sender.split('@')[0] if '@' in sender else sender
|
|
78
|
+
|
|
79
|
+
# Message type filter (schema field: message_type).
|
|
80
|
+
if msg_type != 'all' and m.get('message_type') != msg_type:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
# Sender filter -- for ``contact`` filter, use actual phone.
|
|
84
|
+
if sender_filter == 'self':
|
|
85
|
+
# Self-chat (notes-to-self) only -- must be from me AND in
|
|
86
|
+
# a chat with myself.
|
|
87
|
+
if not m.get('is_from_me'):
|
|
88
|
+
return False
|
|
89
|
+
chat_id = m.get('chat_id', '')
|
|
90
|
+
sender = m.get('sender', '')
|
|
91
|
+
if chat_id != sender:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
if sender_filter == 'any_contact':
|
|
95
|
+
if is_group:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
if sender_filter == 'contact':
|
|
99
|
+
if contact_phone not in sender_phone:
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
if sender_filter == 'group':
|
|
103
|
+
if not is_group:
|
|
104
|
+
return False
|
|
105
|
+
if msg_chat_id != group_id:
|
|
106
|
+
return False
|
|
107
|
+
# Optional: filter by specific sender within group.
|
|
108
|
+
if sender_number:
|
|
109
|
+
if sender_number not in sender_phone:
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
if sender_filter == 'channel':
|
|
113
|
+
if not msg_chat_id.endswith('@newsletter'):
|
|
114
|
+
return False
|
|
115
|
+
channel_jid = params.get('channel_jid', '')
|
|
116
|
+
if channel_jid and msg_chat_id != channel_jid:
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
if sender_filter == 'keywords':
|
|
120
|
+
text = (m.get('text') or '').lower()
|
|
121
|
+
if not any(kw in text for kw in keywords):
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
# Ignore own messages -- but not when filtering for ``self``.
|
|
125
|
+
if ignore_own and sender_filter != 'self' and m.get('is_from_me'):
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
is_forwarded = m.get('is_forwarded', False)
|
|
129
|
+
if forwarded_filter == 'only_forwarded' and not is_forwarded:
|
|
130
|
+
return False
|
|
131
|
+
if forwarded_filter == 'ignore_forwarded' and is_forwarded:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
logger.debug("[WhatsAppFilter] Matched message from %s", sender_phone)
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
return matches
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""WhatsApp WebSocket handlers — plugin-owned dispatch table.
|
|
2
|
+
|
|
3
|
+
Self-registered into the central WS dispatcher via
|
|
4
|
+
``register_ws_handlers(WS_HANDLERS)`` from this package's
|
|
5
|
+
``__init__.py``. ``routers/websocket.py`` knows nothing about
|
|
6
|
+
WhatsApp; the message-type strings are wired here so renames /
|
|
7
|
+
additions stay local to the plugin.
|
|
8
|
+
|
|
9
|
+
Each handler is a thin shim over the underlying RPC client in
|
|
10
|
+
:mod:`._service`. Logic lives in the service module; this module is
|
|
11
|
+
the WS surface.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any, Dict
|
|
17
|
+
|
|
18
|
+
from fastapi import WebSocket
|
|
19
|
+
|
|
20
|
+
from ._service import (
|
|
21
|
+
handle_whatsapp_chat_history as _wa_chat_history,
|
|
22
|
+
handle_whatsapp_connected_phone as _wa_connected_phone,
|
|
23
|
+
handle_whatsapp_group_info as _wa_group_info,
|
|
24
|
+
handle_whatsapp_groups as _wa_groups,
|
|
25
|
+
handle_whatsapp_newsletters as _wa_newsletters,
|
|
26
|
+
handle_whatsapp_qr as _wa_qr,
|
|
27
|
+
handle_whatsapp_restart as _wa_restart,
|
|
28
|
+
handle_whatsapp_send as _wa_send,
|
|
29
|
+
handle_whatsapp_start as _wa_start,
|
|
30
|
+
handle_whatsapp_status as _wa_status,
|
|
31
|
+
whatsapp_rpc_call as _wa_rpc_call,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def handle_whatsapp_status(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
36
|
+
return await _wa_status()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def handle_whatsapp_connected_phone(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
40
|
+
"""Get the connected WhatsApp phone number."""
|
|
41
|
+
return await _wa_connected_phone()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def handle_whatsapp_qr(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
45
|
+
return await _wa_qr()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def handle_whatsapp_send(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
49
|
+
"""Forward all send params to WhatsApp handler — supports all message types."""
|
|
50
|
+
return await _wa_send(data)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def handle_whatsapp_start(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
54
|
+
return await _wa_start()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def handle_whatsapp_restart(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
58
|
+
return await _wa_restart()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def handle_whatsapp_groups(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
62
|
+
return await _wa_groups()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def handle_whatsapp_newsletters(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
66
|
+
"""Get list of subscribed newsletter channels."""
|
|
67
|
+
return await _wa_newsletters()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def handle_whatsapp_group_info(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
71
|
+
"""Get group participants with resolved phone numbers."""
|
|
72
|
+
group_id = data.get("group_id", "")
|
|
73
|
+
return await _wa_group_info(group_id)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def handle_whatsapp_chat_history(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
77
|
+
"""Get chat history from WhatsApp history store."""
|
|
78
|
+
return await _wa_chat_history(data)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---- Rate-limit RPC passthroughs ----
|
|
82
|
+
|
|
83
|
+
async def handle_whatsapp_rate_limit_get(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
84
|
+
"""Get rate-limit config and current stats."""
|
|
85
|
+
return await _wa_rpc_call("rate_limit_get", {})
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def handle_whatsapp_rate_limit_set(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
89
|
+
"""Update rate-limit configuration."""
|
|
90
|
+
return await _wa_rpc_call("rate_limit_set", data.get("config", {}))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def handle_whatsapp_rate_limit_stats(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
94
|
+
"""Get current rate-limit statistics."""
|
|
95
|
+
return await _wa_rpc_call("rate_limit_stats", {})
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def handle_whatsapp_rate_limit_unpause(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
99
|
+
"""Resume rate limiting after an automatic pause."""
|
|
100
|
+
return await _wa_rpc_call("rate_limit_unpause", {})
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ---- Lightweight RPC passthroughs ----
|
|
104
|
+
|
|
105
|
+
async def handle_whatsapp_mark_read(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
106
|
+
"""Mark messages as read. Schema: mark_read({message_ids, chat_jid, sender_jid?})."""
|
|
107
|
+
message_ids = data.get("message_ids", [])
|
|
108
|
+
chat_jid = data.get("chat_jid", "")
|
|
109
|
+
if not message_ids or not chat_jid:
|
|
110
|
+
return {"success": False, "error": "message_ids (array) and chat_jid are required"}
|
|
111
|
+
params: Dict[str, Any] = {"message_ids": message_ids, "chat_jid": chat_jid}
|
|
112
|
+
if sender_jid := data.get("sender_jid"):
|
|
113
|
+
params["sender_jid"] = sender_jid
|
|
114
|
+
return await _wa_rpc_call("mark_read", params)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def handle_whatsapp_typing(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
118
|
+
"""Send typing indicator. Schema: typing({jid, state: 'composing'|'paused', media?})."""
|
|
119
|
+
jid = data.get("jid", "")
|
|
120
|
+
if not jid:
|
|
121
|
+
return {"success": False, "error": "jid is required"}
|
|
122
|
+
params: Dict[str, Any] = {"jid": jid, "state": data.get("state", "composing")}
|
|
123
|
+
if media := data.get("media"):
|
|
124
|
+
params["media"] = media
|
|
125
|
+
return await _wa_rpc_call("typing", params)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def handle_whatsapp_presence(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
129
|
+
"""Set online/offline presence. Schema: presence({status: 'available'|'unavailable'})."""
|
|
130
|
+
return await _wa_rpc_call("presence", {"status": data.get("status", "available")})
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def handle_whatsapp_stop(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
134
|
+
"""Graceful WhatsApp shutdown."""
|
|
135
|
+
return await _wa_rpc_call("stop", {})
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
async def handle_whatsapp_diagnostics(data: Dict[str, Any], websocket: WebSocket) -> Dict[str, Any]:
|
|
139
|
+
"""Get WhatsApp diagnostics / debug info."""
|
|
140
|
+
return await _wa_rpc_call("diagnostics", {})
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# Dispatch table consumed by ``services.ws_handler_registry`` —
|
|
144
|
+
# ``__init__.py`` registers this dict on package import. Ordering
|
|
145
|
+
# preserved from the legacy ``MESSAGE_HANDLERS`` block in
|
|
146
|
+
# ``routers/websocket.py`` so the wire surface is byte-identical.
|
|
147
|
+
WS_HANDLERS = {
|
|
148
|
+
"whatsapp_status": handle_whatsapp_status,
|
|
149
|
+
"whatsapp_connected_phone": handle_whatsapp_connected_phone,
|
|
150
|
+
"whatsapp_qr": handle_whatsapp_qr,
|
|
151
|
+
"whatsapp_send": handle_whatsapp_send,
|
|
152
|
+
"whatsapp_start": handle_whatsapp_start,
|
|
153
|
+
"whatsapp_restart": handle_whatsapp_restart,
|
|
154
|
+
"whatsapp_groups": handle_whatsapp_groups,
|
|
155
|
+
"whatsapp_group_info": handle_whatsapp_group_info,
|
|
156
|
+
"whatsapp_chat_history": handle_whatsapp_chat_history,
|
|
157
|
+
"whatsapp_newsletters": handle_whatsapp_newsletters,
|
|
158
|
+
"whatsapp_rate_limit_get": handle_whatsapp_rate_limit_get,
|
|
159
|
+
"whatsapp_rate_limit_set": handle_whatsapp_rate_limit_set,
|
|
160
|
+
"whatsapp_rate_limit_stats": handle_whatsapp_rate_limit_stats,
|
|
161
|
+
"whatsapp_rate_limit_unpause": handle_whatsapp_rate_limit_unpause,
|
|
162
|
+
"whatsapp_mark_read": handle_whatsapp_mark_read,
|
|
163
|
+
"whatsapp_typing": handle_whatsapp_typing,
|
|
164
|
+
"whatsapp_presence": handle_whatsapp_presence,
|
|
165
|
+
"whatsapp_stop": handle_whatsapp_stop,
|
|
166
|
+
"whatsapp_diagnostics": handle_whatsapp_diagnostics,
|
|
167
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""WhatsApp ``loadOptionsMethod`` loaders.
|
|
2
|
+
|
|
3
|
+
Wave 11.I, milestone M.1. Each function is registered with
|
|
4
|
+
``services.ws_handler_registry.register_option_loader`` from
|
|
5
|
+
``__init__.py`` so the central ``dispatch_load_options`` picks them
|
|
6
|
+
up without a plugin-specific import.
|
|
7
|
+
|
|
8
|
+
Adapter shape: turn the existing WS-handler responses
|
|
9
|
+
(``handle_whatsapp_groups`` / ``handle_whatsapp_newsletters`` /
|
|
10
|
+
``handle_whatsapp_group_info``) into the unified ``[{value, label}]``
|
|
11
|
+
list the frontend ``useLoadOptionsQuery`` consumes. The legacy WS
|
|
12
|
+
message types stay live for back-compat.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any, Dict, List
|
|
18
|
+
|
|
19
|
+
from ._service import (
|
|
20
|
+
handle_whatsapp_group_info,
|
|
21
|
+
handle_whatsapp_groups,
|
|
22
|
+
handle_whatsapp_newsletters,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def load_groups(params: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
27
|
+
"""List WhatsApp groups for the ``recipient_type='group'`` selector."""
|
|
28
|
+
response = await handle_whatsapp_groups()
|
|
29
|
+
groups = response.get("groups", []) if isinstance(response, dict) else []
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
"value": g.get("group_jid") or g.get("id") or "",
|
|
33
|
+
"label": g.get("name") or g.get("subject") or g.get("group_jid", ""),
|
|
34
|
+
}
|
|
35
|
+
for g in groups
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def load_channels(params: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
40
|
+
"""List subscribed newsletter channels for the ``channel_jid`` selector."""
|
|
41
|
+
response = await handle_whatsapp_newsletters()
|
|
42
|
+
channels = response.get("newsletters", []) if isinstance(response, dict) else []
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
"value": c.get("channel_jid") or c.get("id") or "",
|
|
46
|
+
"label": c.get("name") or c.get("channel_jid", ""),
|
|
47
|
+
}
|
|
48
|
+
for c in channels
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def load_group_members(params: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
53
|
+
"""List members of a specific WhatsApp group for the ``senderNumber``
|
|
54
|
+
selector. Depends on ``params['group_id']``."""
|
|
55
|
+
group_id = params.get("group_id") or ""
|
|
56
|
+
if not group_id:
|
|
57
|
+
return []
|
|
58
|
+
response = await handle_whatsapp_group_info(group_id)
|
|
59
|
+
participants = (
|
|
60
|
+
response.get("participants", []) if isinstance(response, dict) else []
|
|
61
|
+
)
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
"value": p.get("phone") or p.get("jid") or "",
|
|
65
|
+
"label": p.get("name") or p.get("phone") or p.get("jid", ""),
|
|
66
|
+
}
|
|
67
|
+
for p in participants
|
|
68
|
+
]
|