machinaos 0.0.78 → 0.0.80
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/.env.template +74 -5
- package/{workflows/AI Assistant_workflow-1778504793388-ou1m1tz2x.json → .machina/workflows/AI Assistant_example_workflow-1779017037684-e2e5da7a.json } +164 -105
- package/{workflows/AI Employee_example_workflow-1777720598005-u4cm858dv.json → .machina/workflows/AI Employee_example_workflow-1779102911870-cbc76c82.json } +582 -328
- package/{workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json → .machina/workflows/Claude Assistant_example_workflow-1779095939967-2369cff4.json } +152 -83
- package/README.md +5 -2
- package/bin/cli.js +2 -2
- package/{machina → cli}/__main__.py +11 -7
- package/cli/_common.py +122 -0
- package/cli/buildenv.py +40 -0
- package/cli/cli.py +204 -0
- package/{machina → cli}/colors.py +10 -2
- package/cli/commands/__init__.py +1 -0
- package/cli/commands/_temporal_specs.py +59 -0
- package/{machina → cli}/commands/build.py +35 -45
- package/cli/commands/clean.py +141 -0
- package/cli/commands/daemon/__init__.py +47 -0
- package/cli/commands/daemon/_state.py +97 -0
- package/cli/commands/daemon/restart.py +14 -0
- package/cli/commands/daemon/start.py +49 -0
- package/cli/commands/daemon/status.py +20 -0
- package/cli/commands/daemon/stop.py +22 -0
- package/{machina → cli}/commands/dev.py +32 -42
- package/{machina → cli}/commands/docs.py +13 -11
- package/{machina → cli}/commands/start.py +69 -62
- package/{machina → cli}/commands/stop.py +7 -10
- package/{machina → cli}/commands/version.py +12 -6
- package/cli/config.py +170 -0
- package/cli/platform_.py +169 -0
- package/{machina → cli}/ports.py +42 -3
- package/{machina → cli}/run.py +29 -2
- package/{machina → cli}/supervisor.py +29 -12
- package/{machina → cli}/tcp.py +6 -2
- package/{machina → cli}/tree.py +38 -11
- package/client/dist/assets/{ActionBar-Du2MSFSz.js → ActionBar-Cjr3TF7g.js} +1 -1
- package/client/dist/assets/{ApiKeyInput-k2LBmBjb.js → ApiKeyInput-DIJE2PVA.js} +1 -1
- package/client/dist/assets/{ApiKeyPanel-C_bV9U0X.js → ApiKeyPanel-CPmye7uh.js} +1 -1
- package/client/dist/assets/{ApiUsageSection-CmVfwZzL.js → ApiUsageSection-TF_7gH2D.js} +1 -1
- package/client/dist/assets/{EmailPanel-CeKIMGu-.js → EmailPanel-Bs-xvbKR.js} +1 -1
- package/client/dist/assets/{OAuthPanel-KA3t3Q2K.js → OAuthPanel-BDtVJhAV.js} +1 -1
- package/client/dist/assets/{QrPairingPanel-NgNpJNuk.js → QrPairingPanel-BwJehTuZ.js} +1 -1
- package/client/dist/assets/{RateLimitSection-Du5YNVIA.js → RateLimitSection-CfNOoPIS.js} +1 -1
- package/client/dist/assets/{StatusCard-DNLyayXc.js → StatusCard-DkwIrgdP.js} +1 -1
- package/client/dist/assets/index-P2FzntoL.js +165 -0
- package/client/dist/index.html +1 -1
- package/client/package.json +1 -1
- package/client/src/Dashboard.tsx +128 -76
- package/client/src/adapters/nodeSpecToDescription.ts +7 -0
- package/client/src/assets/icons/index.test.ts +10 -0
- package/client/src/assets/icons/index.ts +16 -3
- package/client/src/components/AIAgentNode.tsx +8 -8
- package/client/src/components/ParameterRenderer.tsx +6 -3
- package/client/src/components/SkillEditorModal.tsx +1 -0
- package/client/src/components/credentials/panels/EmailPanel.tsx +2 -0
- package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +2 -0
- package/client/src/components/credentials/sections/RateLimitSection.tsx +1 -0
- package/client/src/components/icons/AIProviderIcons.tsx +1 -0
- package/client/src/components/maps/GoogleMapsPicker.tsx +1 -0
- package/client/src/components/parameterPanel/InputSection.tsx +1 -0
- package/client/src/components/parameterPanel/MasterSkillEditor.tsx +1 -0
- package/client/src/components/parameterPanel/OutputSection.tsx +1 -0
- package/client/src/components/ui/ComponentPalette.tsx +1 -0
- package/client/src/components/ui/MapSelector.tsx +1 -0
- package/client/src/components/ui/NodeContextMenu.tsx +3 -3
- package/client/src/components/ui/SettingsPanel.tsx +1 -0
- package/client/src/components/ui/action-button.tsx +1 -0
- package/client/src/components/ui/badge.tsx +1 -0
- package/client/src/components/ui/button.tsx +1 -0
- package/client/src/components/ui/form.tsx +1 -0
- package/client/src/components/ui/tabs.tsx +1 -0
- package/client/src/contexts/AuthContext.tsx +1 -0
- package/client/src/contexts/ThemeContext.tsx +1 -0
- package/client/src/contexts/WebSocketContext.tsx +104 -34
- package/client/src/hooks/__tests__/useApiKeys.test.ts +2 -2
- package/client/src/hooks/useReactFlowNodes.ts +1 -0
- package/client/src/hooks/useWorkflowValidation.ts +142 -0
- package/client/src/lib/nodeSpec.ts +1 -0
- package/client/src/test/providers.tsx +1 -0
- package/client/src/types/__tests__/cloudEvents.test.ts +5 -2
- package/client/src/types/cloudEvents.ts +19 -7
- package/client/src/utils/nodeUtils.ts +1 -1
- package/client/src/utils/workflow.ts +8 -2
- package/client/src/utils/workflowExport.ts +60 -3
- package/package.json +24 -23
- package/scripts/install.js +16 -27
- package/scripts/migrate_icons.py +3 -1
- package/scripts/migrate_skill_icons.py +6 -7
- package/scripts/postinstall.js +11 -9
- package/server/config/ai_cli_providers.json +2 -3
- package/server/config/credential_providers.json +15 -15
- package/server/config/llm_defaults.json +1 -1
- package/server/config/model_registry.json +416 -611
- package/server/constants.py +285 -223
- package/server/core/__init__.py +1 -1
- package/server/core/cache.py +9 -29
- package/server/core/cleanup.py +12 -24
- package/server/core/config.py +148 -24
- package/server/core/container.py +68 -59
- package/server/core/credential_backends.py +5 -13
- package/server/core/credentials_database.py +13 -43
- package/server/core/database.py +292 -353
- package/server/core/health.py +4 -5
- package/server/core/logging.py +241 -87
- package/server/core/paths.py +285 -0
- package/server/core/tracing.py +2 -8
- package/server/gunicorn.conf.py +1 -0
- package/server/main.py +150 -74
- package/server/middleware/auth.py +18 -24
- package/server/models/__init__.py +1 -1
- package/server/models/auth.py +5 -12
- package/server/models/database.py +36 -68
- package/server/models/node_metadata.py +25 -18
- package/server/nodejs/dist/index.js +107 -0
- package/server/nodes/README.md +11 -5
- package/server/nodes/__init__.py +1 -1
- package/server/nodes/_visuals.py +146 -14
- package/server/nodes/agent/_events.py +124 -0
- package/server/nodes/agent/_handles.py +15 -29
- package/server/nodes/agent/_inline.py +28 -25
- package/server/nodes/agent/_specialized.py +30 -15
- package/server/nodes/agent/{ai_agent.py → ai_agent/__init__.py} +33 -17
- package/server/nodes/agent/ai_agent/meta.json +3 -0
- package/server/nodes/agent/{ai_employee.py → ai_employee/__init__.py} +5 -2
- package/server/nodes/agent/ai_employee/meta.json +3 -0
- package/server/nodes/agent/{android_agent.py → android_agent/__init__.py} +1 -1
- package/server/nodes/agent/android_agent/meta.json +3 -0
- package/server/nodes/agent/{autonomous_agent.py → autonomous_agent/__init__.py} +2 -1
- package/server/nodes/agent/autonomous_agent/meta.json +3 -0
- package/server/nodes/agent/{chat_agent.py → chat_agent/__init__.py} +29 -12
- package/server/nodes/agent/chat_agent/meta.json +3 -0
- package/server/nodes/agent/{claude_code_agent.py → claude_code_agent/__init__.py} +192 -95
- package/server/nodes/agent/claude_code_agent/_handlers.py +169 -0
- package/server/{services/claude_oauth.py → nodes/agent/claude_code_agent/_oauth.py} +26 -13
- package/server/nodes/agent/claude_code_agent/_pool.py +1020 -0
- package/server/nodes/agent/claude_code_agent/_provider.py +513 -0
- package/server/nodes/agent/claude_code_agent/_skills.py +245 -0
- package/server/nodes/agent/claude_code_agent/meta.json +3 -0
- package/server/nodes/agent/{codex_agent.py → codex_agent/__init__.py} +26 -35
- package/server/nodes/agent/codex_agent/meta.json +3 -0
- package/server/nodes/agent/{coding_agent.py → coding_agent/__init__.py} +1 -1
- package/server/nodes/agent/coding_agent/meta.json +3 -0
- package/server/nodes/agent/{consumer_agent.py → consumer_agent/__init__.py} +1 -1
- package/server/nodes/agent/consumer_agent/meta.json +3 -0
- package/server/nodes/agent/{orchestrator_agent.py → orchestrator_agent/__init__.py} +5 -2
- package/server/nodes/agent/orchestrator_agent/meta.json +3 -0
- package/server/nodes/agent/{payments_agent.py → payments_agent/__init__.py} +1 -1
- package/server/nodes/agent/payments_agent/meta.json +3 -0
- package/server/nodes/agent/{productivity_agent.py → productivity_agent/__init__.py} +1 -1
- package/server/nodes/agent/productivity_agent/meta.json +3 -0
- package/server/nodes/agent/{rlm_agent.py → rlm_agent/__init__.py} +18 -17
- package/server/nodes/agent/rlm_agent/meta.json +3 -0
- package/server/nodes/agent/{social_agent.py → social_agent/__init__.py} +1 -1
- package/server/nodes/agent/social_agent/meta.json +3 -0
- package/server/nodes/agent/{task_agent.py → task_agent/__init__.py} +1 -1
- package/server/nodes/agent/task_agent/meta.json +3 -0
- package/server/nodes/agent/{tool_agent.py → tool_agent/__init__.py} +1 -1
- package/server/nodes/agent/tool_agent/meta.json +3 -0
- package/server/nodes/agent/{travel_agent.py → travel_agent/__init__.py} +1 -1
- package/server/nodes/agent/travel_agent/meta.json +3 -0
- package/server/nodes/agent/{web_agent.py → web_agent/__init__.py} +1 -1
- package/server/nodes/agent/web_agent/meta.json +3 -0
- package/server/nodes/android/__init__.py +24 -0
- package/server/nodes/android/_base.py +93 -76
- package/server/nodes/android/_dispatcher.py +140 -223
- package/server/nodes/android/_events.py +154 -0
- package/server/nodes/android/_handlers.py +13 -7
- package/server/nodes/android/_option_loaders.py +1 -4
- package/server/nodes/android/_refresh.py +27 -37
- package/server/nodes/android/_relay/broadcaster.py +25 -41
- package/server/nodes/android/_relay/client.py +23 -42
- package/server/nodes/android/_relay/manager.py +1 -0
- package/server/nodes/android/_relay/protocol.py +6 -0
- package/server/nodes/android/_router.py +48 -133
- package/server/nodes/android/{airplane_mode_control.py → airplane_mode_control/__init__.py} +2 -1
- package/server/nodes/android/airplane_mode_control/meta.json +3 -0
- package/server/nodes/android/{app_launcher.py → app_launcher/__init__.py} +2 -1
- package/server/nodes/android/app_launcher/meta.json +3 -0
- package/server/nodes/android/{app_list.py → app_list/__init__.py} +2 -1
- package/server/nodes/android/app_list/meta.json +3 -0
- package/server/nodes/android/{audio_automation.py → audio_automation/__init__.py} +2 -1
- package/server/nodes/android/audio_automation/meta.json +3 -0
- package/server/nodes/android/{battery_monitor.py → battery_monitor/__init__.py} +2 -1
- package/server/nodes/android/battery_monitor/meta.json +3 -0
- package/server/nodes/android/{bluetooth_automation.py → bluetooth_automation/__init__.py} +2 -1
- package/server/nodes/android/bluetooth_automation/meta.json +3 -0
- package/server/nodes/android/{camera_control.py → camera_control/__init__.py} +2 -1
- package/server/nodes/android/camera_control/meta.json +3 -0
- package/server/nodes/android/{device_state_automation.py → device_state_automation/__init__.py} +2 -1
- package/server/nodes/android/device_state_automation/meta.json +3 -0
- package/server/nodes/android/{environmental_sensors.py → environmental_sensors/__init__.py} +2 -1
- package/server/nodes/android/environmental_sensors/meta.json +3 -0
- package/server/nodes/android/{location.py → location/__init__.py} +2 -1
- package/server/nodes/android/location/meta.json +3 -0
- package/server/nodes/android/{media_control.py → media_control/__init__.py} +2 -1
- package/server/nodes/android/media_control/meta.json +3 -0
- package/server/nodes/android/{motion_detection.py → motion_detection/__init__.py} +2 -1
- package/server/nodes/android/motion_detection/meta.json +3 -0
- package/server/nodes/android/{network_monitor.py → network_monitor/__init__.py} +2 -1
- package/server/nodes/android/network_monitor/meta.json +3 -0
- package/server/nodes/android/{screen_control_automation.py → screen_control_automation/__init__.py} +2 -1
- package/server/nodes/android/screen_control_automation/meta.json +3 -0
- package/server/nodes/android/{system_info.py → system_info/__init__.py} +2 -1
- package/server/nodes/android/system_info/meta.json +3 -0
- package/server/nodes/android/{wifi_automation.py → wifi_automation/__init__.py} +2 -1
- package/server/nodes/android/wifi_automation/meta.json +3 -0
- package/server/nodes/browser/__init__.py +22 -1
- package/server/nodes/browser/_install.py +63 -0
- package/server/nodes/browser/_service.py +21 -25
- package/server/nodes/browser/{browser.py → browser/__init__.py} +58 -25
- package/server/nodes/browser/browser/meta.json +3 -0
- package/server/nodes/chat/{chat_history.py → chat_history/__init__.py} +2 -4
- package/server/nodes/chat/chat_history/meta.json +3 -0
- package/server/nodes/chat/{chat_send.py → chat_send/__init__.py} +2 -4
- package/server/nodes/chat/chat_send/icon.svg +1 -0
- package/server/nodes/chat/chat_send/meta.json +3 -0
- package/server/nodes/code/_base.py +1 -1
- package/server/nodes/code/{javascript_executor.py → javascript_executor/__init__.py} +5 -5
- package/server/nodes/code/javascript_executor/meta.json +3 -0
- package/server/nodes/code/{python_executor.py → python_executor/__init__.py} +32 -14
- package/server/nodes/code/python_executor/meta.json +3 -0
- package/server/nodes/code/{typescript_executor.py → typescript_executor/__init__.py} +5 -5
- package/server/nodes/code/typescript_executor/meta.json +3 -0
- package/server/nodes/document/{document_parser.py → document_parser/__init__.py} +26 -15
- package/server/nodes/document/document_parser/meta.json +3 -0
- package/server/nodes/document/{embedding_generator.py → embedding_generator/__init__.py} +16 -9
- package/server/nodes/document/embedding_generator/meta.json +3 -0
- package/server/nodes/document/{file_downloader.py → file_downloader/__init__.py} +30 -20
- package/server/nodes/document/file_downloader/meta.json +3 -0
- package/server/nodes/document/{http_scraper.py → http_scraper/__init__.py} +31 -21
- package/server/nodes/document/http_scraper/meta.json +3 -0
- package/server/nodes/document/{text_chunker.py → text_chunker/__init__.py} +17 -12
- package/server/nodes/document/text_chunker/meta.json +3 -0
- package/server/nodes/document/{vector_store.py → vector_store/__init__.py} +88 -72
- package/server/nodes/document/vector_store/meta.json +3 -0
- package/server/nodes/email/__init__.py +9 -2
- package/server/nodes/email/_events.py +54 -0
- package/server/nodes/email/_filters.py +3 -3
- package/server/nodes/email/_himalaya.py +95 -50
- package/server/nodes/email/_service.py +23 -13
- package/server/nodes/email/{email_read.py → email_read/__init__.py} +23 -11
- package/server/nodes/email/email_read/icon.svg +6 -0
- package/server/nodes/email/email_read/meta.json +3 -0
- package/server/nodes/email/{email_receive.py → email_receive/__init__.py} +45 -23
- package/server/nodes/email/email_receive/meta.json +3 -0
- package/server/nodes/email/{email_send.py → email_send/__init__.py} +13 -7
- package/server/nodes/email/email_send/meta.json +3 -0
- package/server/nodes/filesystem/_backend.py +1 -5
- package/server/nodes/filesystem/{file_modify.py → file_modify/__init__.py} +10 -5
- package/server/nodes/filesystem/file_modify/meta.json +3 -0
- package/server/nodes/filesystem/{file_read.py → file_read/__init__.py} +7 -3
- package/server/nodes/filesystem/file_read/meta.json +3 -0
- package/server/nodes/filesystem/{fs_search.py → fs_search/__init__.py} +11 -3
- package/server/nodes/filesystem/fs_search/meta.json +3 -0
- package/server/nodes/filesystem/{shell.py → shell/__init__.py} +12 -5
- package/server/nodes/filesystem/shell/meta.json +3 -0
- package/server/nodes/google/__init__.py +12 -0
- package/server/nodes/google/_auth_helper.py +7 -13
- package/server/nodes/google/_base.py +14 -11
- package/server/nodes/google/_credentials.py +2 -1
- package/server/nodes/google/_events.py +47 -0
- package/server/nodes/google/_filters.py +3 -3
- package/server/nodes/google/_gmail.py +70 -47
- package/server/nodes/google/_handlers.py +3 -1
- package/server/nodes/google/_oauth.py +25 -11
- package/server/nodes/google/_option_loaders.py +9 -30
- package/server/nodes/google/_refresh.py +8 -12
- package/server/nodes/google/_router.py +4 -5
- package/server/nodes/google/{calendar.py → calendar/__init__.py} +87 -64
- package/server/nodes/google/calendar/meta.json +3 -0
- package/server/nodes/google/{contacts.py → contacts/__init__.py} +84 -72
- package/server/nodes/google/contacts/meta.json +3 -0
- package/server/nodes/google/{drive.py → drive/__init__.py} +87 -72
- package/server/nodes/google/drive/meta.json +3 -0
- package/server/nodes/google/{gmail.py → gmail/__init__.py} +73 -39
- package/server/nodes/google/gmail/meta.json +3 -0
- package/server/nodes/google/{gmail_receive.py → gmail_receive/__init__.py} +31 -24
- package/server/nodes/google/gmail_receive/icon.svg +7 -0
- package/server/nodes/google/gmail_receive/meta.json +3 -0
- package/server/nodes/google/google.svg +7 -0
- package/server/nodes/google/{sheets.py → sheets/__init__.py} +54 -42
- package/server/nodes/google/sheets/meta.json +3 -0
- package/server/nodes/google/{tasks.py → tasks/__init__.py} +56 -43
- package/server/nodes/google/tasks/meta.json +3 -0
- package/server/nodes/groups.py +28 -28
- package/server/nodes/location/__init__.py +31 -1
- package/server/nodes/location/_credentials.py +1 -6
- package/server/nodes/location/_service.py +88 -107
- package/server/nodes/location/{gmaps_create.py → gmaps_create/__init__.py} +6 -6
- package/server/nodes/location/gmaps_create/meta.json +3 -0
- package/server/nodes/location/{gmaps_locations.py → gmaps_locations/__init__.py} +8 -6
- package/server/nodes/location/gmaps_locations/meta.json +3 -0
- package/server/nodes/location/{gmaps_nearby_places.py → gmaps_nearby_places/__init__.py} +8 -6
- package/server/nodes/location/gmaps_nearby_places/meta.json +3 -0
- package/server/nodes/model/_base.py +10 -7
- package/server/nodes/model/_credentials.py +10 -10
- package/server/nodes/model/_local_validator.py +28 -24
- package/server/nodes/model/{anthropic_chat_model.py → anthropic_chat_model/__init__.py} +5 -3
- package/server/nodes/model/anthropic_chat_model/meta.json +3 -0
- package/server/nodes/model/{cerebras_chat_model.py → cerebras_chat_model/__init__.py} +5 -3
- package/server/nodes/model/cerebras_chat_model/meta.json +3 -0
- package/server/nodes/model/{deepseek_chat_model.py → deepseek_chat_model/__init__.py} +8 -4
- package/server/nodes/model/deepseek_chat_model/meta.json +3 -0
- package/server/nodes/model/{gemini_chat_model.py → gemini_chat_model/__init__.py} +5 -3
- package/server/nodes/model/gemini_chat_model/meta.json +3 -0
- package/server/nodes/model/{groq_chat_model.py → groq_chat_model/__init__.py} +2 -2
- package/server/nodes/model/groq_chat_model/meta.json +3 -0
- package/server/nodes/model/{kimi_chat_model.py → kimi_chat_model/__init__.py} +2 -2
- package/server/nodes/model/kimi_chat_model/meta.json +3 -0
- package/server/nodes/model/{lmstudio_chat_model.py → lmstudio_chat_model/__init__.py} +2 -2
- package/server/nodes/model/lmstudio_chat_model/meta.json +3 -0
- package/server/nodes/model/{mistral_chat_model.py → mistral_chat_model/__init__.py} +2 -2
- package/server/nodes/model/mistral_chat_model/meta.json +3 -0
- package/server/nodes/model/{ollama_chat_model.py → ollama_chat_model/__init__.py} +2 -2
- package/server/nodes/model/ollama_chat_model/meta.json +3 -0
- package/server/nodes/model/{openai_chat_model.py → openai_chat_model/__init__.py} +8 -4
- package/server/nodes/model/openai_chat_model/meta.json +3 -0
- package/server/nodes/model/{openrouter_chat_model.py → openrouter_chat_model/__init__.py} +8 -4
- package/server/nodes/model/openrouter_chat_model/meta.json +3 -0
- package/server/nodes/proxy/_usage.py +14 -15
- package/server/nodes/proxy/{proxy_config.py → proxy_config/__init__.py} +39 -30
- package/server/nodes/proxy/proxy_config/meta.json +3 -0
- package/server/nodes/proxy/{proxy_request.py → proxy_request/__init__.py} +30 -16
- package/server/nodes/proxy/proxy_request/meta.json +3 -0
- package/server/nodes/proxy/{proxy_status.py → proxy_status/__init__.py} +2 -0
- package/server/nodes/proxy/proxy_status/meta.json +3 -0
- package/server/nodes/scheduler/{cron_scheduler.py → cron_scheduler/__init__.py} +96 -23
- package/server/nodes/scheduler/cron_scheduler/_workflow.py +155 -0
- package/server/nodes/scheduler/cron_scheduler/meta.json +3 -0
- package/server/nodes/scheduler/{timer.py → timer/__init__.py} +6 -5
- package/server/nodes/scheduler/timer/meta.json +3 -0
- package/server/nodes/scraper/_credentials.py +0 -1
- package/server/nodes/scraper/{apify_actor.py → apify_actor/__init__.py} +44 -35
- package/server/nodes/scraper/apify_actor/icon.svg +5 -0
- package/server/nodes/scraper/apify_actor/meta.json +3 -0
- package/server/nodes/scraper/{crawlee_scraper.py → crawlee_scraper/__init__.py} +96 -57
- package/server/nodes/scraper/crawlee_scraper/meta.json +3 -0
- package/server/nodes/search/{brave_search.py → brave_search/__init__.py} +6 -5
- package/server/nodes/search/brave_search/icon.svg +3 -0
- package/server/nodes/search/brave_search/meta.json +3 -0
- package/server/nodes/search/{duckduckgo_search.py → duckduckgo_search/__init__.py} +17 -6
- package/server/nodes/search/duckduckgo_search/meta.json +3 -0
- package/server/nodes/search/{perplexity_search.py → perplexity_search/__init__.py} +4 -5
- package/server/nodes/search/perplexity_search/icon.svg +3 -0
- package/server/nodes/search/perplexity_search/meta.json +3 -0
- package/server/nodes/search/{serper_search.py → serper_search/__init__.py} +32 -25
- package/server/nodes/search/serper_search/icon.svg +3 -0
- package/server/nodes/search/serper_search/meta.json +3 -0
- package/server/nodes/skill/__init__.py +21 -1
- package/server/nodes/skill/_expander.py +75 -0
- package/server/nodes/skill/{master_skill.py → master_skill/__init__.py} +2 -8
- package/server/nodes/skill/master_skill/_events.py +84 -0
- package/server/nodes/skill/master_skill/meta.json +3 -0
- package/server/nodes/skill/{simple_memory.py → simple_memory/__init__.py} +8 -16
- package/server/nodes/skill/simple_memory/meta.json +3 -0
- package/server/nodes/social/_base.py +223 -231
- package/server/nodes/social/{social_receive.py → social_receive/__init__.py} +38 -13
- package/server/nodes/social/social_receive/meta.json +3 -0
- package/server/nodes/social/{social_send.py → social_send/__init__.py} +71 -29
- package/server/nodes/social/social_send/icon.svg +1 -0
- package/server/nodes/social/social_send/meta.json +3 -0
- package/server/nodes/stripe/__init__.py +7 -3
- package/server/nodes/stripe/_credentials.py +0 -1
- package/server/nodes/stripe/_handlers.py +18 -7
- package/server/nodes/stripe/_install.py +14 -15
- package/server/nodes/stripe/_source.py +5 -5
- package/server/nodes/stripe/icon.svg +1 -0
- package/server/nodes/stripe/meta.json +3 -0
- package/server/nodes/stripe/stripe_action.py +4 -4
- package/server/nodes/stripe/stripe_receive.py +6 -9
- package/server/nodes/telegram/__init__.py +13 -0
- package/server/nodes/telegram/_credentials.py +2 -7
- package/server/nodes/telegram/_events.py +167 -0
- package/server/nodes/telegram/_filters.py +3 -11
- package/server/nodes/telegram/_handlers.py +17 -7
- package/server/nodes/telegram/_refresh.py +24 -34
- package/server/nodes/telegram/_service.py +29 -45
- package/server/nodes/telegram/meta.json +3 -0
- package/server/nodes/telegram/telegram.svg +3 -0
- package/server/nodes/telegram/telegram_receive.py +38 -18
- package/server/nodes/telegram/telegram_send.py +21 -19
- package/server/nodes/text/{file_handler.py → file_handler/__init__.py} +7 -1
- package/server/nodes/text/file_handler/meta.json +3 -0
- package/server/nodes/text/{text_generator.py → text_generator/__init__.py} +2 -1
- package/server/nodes/text/text_generator/meta.json +3 -0
- package/server/nodes/tool/{agent_builder.py → agent_builder/__init__.py} +105 -100
- package/server/nodes/tool/agent_builder/_events.py +91 -0
- package/server/nodes/tool/agent_builder/meta.json +3 -0
- package/server/nodes/tool/{calculator_tool.py → calculator_tool/__init__.py} +19 -7
- package/server/nodes/tool/calculator_tool/meta.json +3 -0
- package/server/nodes/tool/{current_time_tool.py → current_time_tool/__init__.py} +6 -4
- package/server/nodes/tool/current_time_tool/meta.json +3 -0
- package/server/nodes/tool/{task_manager.py → task_manager/__init__.py} +17 -18
- package/server/nodes/tool/task_manager/meta.json +3 -0
- package/server/nodes/tool/{write_todos.py → write_todos/__init__.py} +20 -6
- package/server/nodes/tool/write_todos/meta.json +3 -0
- package/server/nodes/trigger/{chat_trigger.py → chat_trigger/__init__.py} +11 -7
- package/server/nodes/trigger/chat_trigger/_events.py +53 -0
- package/server/nodes/trigger/chat_trigger/meta.json +3 -0
- package/server/nodes/trigger/{task_trigger.py → task_trigger/__init__.py} +10 -7
- package/server/nodes/trigger/task_trigger/meta.json +3 -0
- package/server/nodes/trigger/{webhook_trigger.py → webhook_trigger/__init__.py} +10 -7
- package/server/nodes/trigger/webhook_trigger/_events.py +54 -0
- package/server/nodes/trigger/webhook_trigger/meta.json +3 -0
- package/server/nodes/twitter/__init__.py +7 -1
- package/server/nodes/twitter/_base.py +86 -61
- package/server/nodes/twitter/_credentials.py +7 -5
- package/server/nodes/twitter/_events.py +101 -0
- package/server/nodes/twitter/_filters.py +9 -9
- package/server/nodes/twitter/_handlers.py +3 -1
- package/server/nodes/twitter/_oauth.py +1 -2
- package/server/nodes/twitter/_refresh.py +8 -12
- package/server/nodes/twitter/{twitter_receive.py → twitter_receive/__init__.py} +7 -7
- package/server/nodes/twitter/twitter_receive/icon.svg +1 -0
- package/server/nodes/twitter/twitter_receive/meta.json +3 -0
- package/server/nodes/twitter/{twitter_search.py → twitter_search/__init__.py} +16 -11
- package/server/nodes/twitter/twitter_search/icon.svg +1 -0
- package/server/nodes/twitter/twitter_search/meta.json +3 -0
- package/server/nodes/twitter/{twitter_send.py → twitter_send/__init__.py} +60 -27
- package/server/nodes/twitter/twitter_send/icon.svg +1 -0
- package/server/nodes/twitter/twitter_send/meta.json +3 -0
- package/server/nodes/twitter/{twitter_user.py → twitter_user/__init__.py} +34 -19
- package/server/nodes/twitter/twitter_user/icon.svg +1 -0
- package/server/nodes/twitter/twitter_user/meta.json +3 -0
- package/server/nodes/utility/{console.py → console/__init__.py} +17 -22
- package/server/nodes/utility/console/meta.json +3 -0
- package/server/nodes/utility/{http_request.py → http_request/__init__.py} +9 -6
- package/server/nodes/utility/http_request/meta.json +3 -0
- package/server/nodes/utility/{process_manager.py → process_manager/__init__.py} +10 -6
- package/server/nodes/utility/process_manager/meta.json +3 -0
- package/server/nodes/utility/team_monitor/meta.json +3 -0
- package/server/nodes/utility/{webhook_response.py → webhook_response/__init__.py} +12 -7
- package/server/nodes/utility/webhook_response/meta.json +3 -0
- package/server/nodes/visuals.json +69 -251
- package/server/nodes/whatsapp/__init__.py +24 -0
- package/server/nodes/whatsapp/_base.py +283 -338
- package/server/nodes/whatsapp/_credentials.py +44 -0
- package/server/nodes/whatsapp/_events.py +277 -0
- package/server/nodes/whatsapp/_filters.py +36 -37
- package/server/nodes/whatsapp/_handlers.py +2 -0
- package/server/nodes/whatsapp/_option_loaders.py +1 -3
- package/server/nodes/whatsapp/_refresh.py +13 -18
- package/server/nodes/whatsapp/_runtime.py +9 -6
- package/server/nodes/whatsapp/_service.py +89 -152
- package/server/nodes/whatsapp/meta.json +3 -0
- package/server/nodes/whatsapp/whatsapp_db.py +116 -54
- package/server/nodes/whatsapp/whatsapp_receive.py +30 -13
- package/server/nodes/whatsapp/whatsapp_send.py +60 -37
- package/server/nodes/workflow/{start.py → start/__init__.py} +1 -4
- package/server/nodes/workflow/start/meta.json +3 -0
- package/server/package-lock.json +3 -3
- package/server/package.json +3 -0
- package/server/pyproject.toml +39 -10
- package/server/requirements.txt +3 -5
- package/server/routers/__init__.py +1 -1
- package/server/routers/auth.py +16 -56
- package/server/routers/database.py +27 -50
- package/server/routers/nodejs_compat.py +25 -87
- package/server/routers/schemas.py +66 -2
- package/server/routers/webhook.py +12 -12
- package/server/routers/websocket.py +312 -1716
- package/server/routers/workflow.py +28 -53
- package/server/scripts/smoke_test_skills.py +178 -0
- package/server/services/__init__.py +1 -1
- package/server/services/_supervisor/process.py +9 -3
- package/server/services/_supervisor/registry.py +3 -3
- package/server/services/_supervisor/util.py +1 -1
- package/server/services/agent_team.py +15 -43
- package/server/services/agent_teams/__init__.py +17 -0
- package/server/services/agent_teams/handlers.py +195 -0
- package/server/services/ai.py +853 -1108
- package/server/services/auth.py +10 -34
- package/server/services/chat_client.py +5 -34
- package/server/services/circuit_breaker.py +2 -6
- package/server/services/cli_agent/__init__.py +28 -4
- package/server/services/cli_agent/_cli_auth.py +61 -0
- package/server/services/cli_agent/_handlers.py +24 -183
- package/server/services/cli_agent/config.py +5 -8
- package/server/services/cli_agent/factory.py +168 -22
- package/server/services/cli_agent/jsonl_watcher.py +380 -0
- package/server/services/cli_agent/lockfile.py +9 -2
- package/server/services/cli_agent/mcp_server.py +110 -34
- package/server/services/cli_agent/protocol.py +37 -19
- package/server/services/cli_agent/providers/__init__.py +8 -4
- package/server/services/cli_agent/providers/google_gemini.py +11 -5
- package/server/services/cli_agent/providers/openai_codex.py +34 -34
- package/server/services/cli_agent/service.py +245 -83
- package/server/services/cli_agent/session.py +409 -229
- package/server/services/cli_agent/transports/__init__.py +47 -0
- package/server/services/cli_agent/transports/base.py +111 -0
- package/server/services/cli_agent/transports/posix.py +196 -0
- package/server/services/cli_agent/transports/windows.py +189 -0
- package/server/services/cli_agent/types.py +45 -18
- package/server/services/cli_agent/workflow_tools.py +28 -15
- package/server/services/compaction.py +68 -52
- package/server/services/credential_registry.py +6 -20
- package/server/services/credentials/__init__.py +18 -0
- package/server/services/credentials/handlers.py +196 -0
- package/server/services/deployment/__init__.py +12 -1
- package/server/services/deployment/canary_registry.py +137 -0
- package/server/services/deployment/handlers.py +382 -0
- package/server/services/deployment/manager.py +653 -163
- package/server/services/deployment/poll_registry.py +2 -6
- package/server/services/deployment/state.py +2 -0
- package/server/services/deployment/triggers.py +87 -93
- package/server/services/event_waiter.py +47 -54
- package/server/services/events/__init__.py +11 -0
- package/server/services/events/admin_handlers.py +368 -0
- package/server/services/events/daemon.py +3 -1
- package/server/services/events/dispatch.py +188 -0
- package/server/services/events/envelope.py +264 -45
- package/server/services/events/oauth_lifecycle.py +98 -42
- package/server/services/events/triggers.py +3 -13
- package/server/services/events/verifiers/hmac_basic.py +1 -1
- package/server/services/events/verifiers/standard_webhooks.py +2 -4
- package/server/services/events/webhook.py +2 -3
- package/server/services/example_loader.py +73 -15
- package/server/services/execution/cache.py +36 -76
- package/server/services/execution/conditions.py +7 -20
- package/server/services/execution/dlq.py +20 -24
- package/server/services/execution/executor.py +234 -265
- package/server/services/execution/models.py +40 -46
- package/server/services/execution/recovery.py +23 -46
- package/server/services/handlers/__init__.py +12 -16
- package/server/services/handlers/todo.py +3 -6
- package/server/services/handlers/tools.py +143 -194
- package/server/services/handlers/triggers.py +24 -23
- package/server/services/llm/config.py +10 -1
- package/server/services/llm/factory.py +16 -4
- package/server/services/llm/messages.py +1 -5
- package/server/services/llm/protocol.py +9 -1
- package/server/services/llm/providers/anthropic.py +23 -12
- package/server/services/llm/providers/gemini.py +43 -22
- package/server/services/llm/providers/openai.py +14 -6
- package/server/services/llm/providers/openrouter.py +6 -1
- package/server/services/markdown_formatter.py +1 -2
- package/server/services/memory/__init__.py +2 -2
- package/server/services/memory/jsonl.py +6 -2
- package/server/services/memory/markdown.py +6 -6
- package/server/services/memory/state.py +6 -5
- package/server/services/memory_store.py +8 -12
- package/server/services/model_registry.py +22 -20
- package/server/services/node_executor.py +85 -80
- package/server/services/node_output_schemas.py +4 -7
- package/server/services/node_registry.py +40 -4
- package/server/services/node_spec.py +3 -7
- package/server/services/nodejs_client.py +4 -14
- package/server/services/oauth_utils.py +11 -7
- package/server/services/parameter_resolver.py +30 -36
- package/server/services/plugin/base.py +321 -38
- package/server/services/plugin/connection.py +12 -7
- package/server/services/plugin/credential.py +80 -22
- package/server/services/plugin/edge_walker.py +128 -105
- package/server/services/plugin/identifiers.py +48 -0
- package/server/services/plugin/interceptor.py +1 -1
- package/server/services/plugin/oauth.py +25 -21
- package/server/services/plugin/operation.py +1 -1
- package/server/services/plugin/polling.py +151 -26
- package/server/services/plugin/registry.py +52 -4
- package/server/services/plugin/routing.py +6 -9
- package/server/services/plugin/scaling.py +36 -18
- package/server/services/plugin/service_factories.py +95 -0
- package/server/services/plugin/shutdown_hooks.py +103 -0
- package/server/services/plugin/social_provider_registry.py +80 -0
- package/server/services/plugin/ws.py +2 -1
- package/server/services/pricing.py +26 -40
- package/server/services/pricing_handlers.py +90 -0
- package/server/services/process_service.py +33 -32
- package/server/services/proxy/models.py +15 -9
- package/server/services/proxy/service.py +26 -40
- package/server/services/rlm/adapters.py +43 -40
- package/server/services/rlm/constants.py +9 -9
- package/server/services/rlm/service.py +57 -45
- package/server/services/scheduler.py +8 -39
- package/server/services/settings/__init__.py +16 -0
- package/server/services/settings/handlers.py +275 -0
- package/server/services/skill_loader.py +53 -45
- package/server/services/skill_prompt.py +8 -6
- package/server/services/skills/__init__.py +23 -0
- package/server/services/skills/handlers.py +479 -0
- package/server/services/status_broadcaster.py +314 -291
- package/server/services/temporal/__init__.py +22 -1
- package/server/services/temporal/_handlers.py +65 -0
- package/server/services/temporal/_install.py +158 -0
- package/server/services/temporal/_refresh.py +57 -0
- package/server/services/temporal/_retry_policies.py +85 -0
- package/server/services/temporal/_runtime.py +181 -0
- package/server/services/temporal/_supervised_runtime.py +102 -0
- package/server/services/temporal/activities.py +168 -11
- package/server/services/temporal/agent_activities.py +683 -0
- package/server/services/temporal/agent_workflow.py +601 -0
- package/server/services/temporal/client.py +58 -13
- package/server/services/temporal/executor.py +2 -3
- package/server/services/temporal/plugin_activities.py +37 -2
- package/server/services/temporal/plugin_registry.py +82 -0
- package/server/services/temporal/polling_trigger_workflow.py +267 -0
- package/server/services/temporal/schedules.py +220 -0
- package/server/services/temporal/search_attributes.py +177 -0
- package/server/services/temporal/trigger_listener_workflow.py +378 -0
- package/server/services/temporal/worker.py +111 -18
- package/server/services/temporal/workflow.py +259 -40
- package/server/services/temporal/ws_client.py +22 -11
- package/server/services/text.py +14 -28
- package/server/services/tracked_http.py +29 -49
- package/server/services/user_auth.py +7 -21
- package/server/services/workflow.py +28 -20
- package/server/services/workflow_import.py +351 -0
- package/server/services/workflow_ops.py +4 -0
- package/server/services/workflow_storage/__init__.py +18 -0
- package/server/services/workflow_storage/handlers.py +132 -0
- package/server/services/workflow_validator.py +209 -0
- package/server/services/ws_handler_registry.py +80 -9
- package/server/skills/assistant/agent-builder-skill/SKILL.md +6 -6
- package/server/tests/conftest.py +54 -3
- package/server/tests/credentials/test_auth_service.py +9 -21
- package/server/tests/credentials/test_credential_broadcasts.py +116 -22
- package/server/tests/credentials/test_credentials_database.py +12 -38
- package/server/tests/credentials/test_encryption.py +3 -9
- package/server/tests/credentials/test_google_oauth.py +1 -3
- package/server/tests/credentials/test_oauth_utils.py +31 -38
- package/server/tests/credentials/test_twitter_oauth.py +1 -3
- package/server/tests/credentials/test_websocket_handlers.py +37 -72
- package/server/tests/fixtures/tool_names_snapshot.json +78 -0
- package/server/tests/llm/test_factory.py +12 -4
- package/server/tests/llm/test_providers.py +25 -32
- package/server/tests/llm/test_wiring.py +27 -22
- package/server/tests/nodes/_compat.py +4 -5
- package/server/tests/nodes/_harness.py +31 -24
- package/server/tests/nodes/_mocks.py +2 -6
- package/server/tests/nodes/test_agent_builder.py +43 -35
- package/server/tests/nodes/test_ai_agents.py +29 -24
- package/server/tests/nodes/test_ai_chat_models.py +3 -9
- package/server/tests/nodes/test_ai_tools.py +29 -24
- package/server/tests/nodes/test_android.py +34 -64
- package/server/tests/nodes/test_chat_utility.py +2 -2
- package/server/tests/nodes/test_code_fs_process.py +26 -84
- package/server/tests/nodes/test_document.py +23 -47
- package/server/tests/nodes/test_email.py +88 -51
- package/server/tests/nodes/test_google_workspace.py +26 -20
- package/server/tests/nodes/test_http_proxy.py +43 -89
- package/server/tests/nodes/test_search.py +3 -9
- package/server/tests/nodes/test_specialized_agents.py +58 -162
- package/server/tests/nodes/test_stripe_plugin.py +25 -5
- package/server/tests/nodes/test_telegram_social.py +33 -37
- package/server/tests/nodes/test_twitter.py +59 -150
- package/server/tests/nodes/test_web_automation.py +21 -51
- package/server/tests/nodes/test_whatsapp.py +13 -19
- package/server/tests/nodes/test_workflow_triggers.py +16 -45
- package/server/tests/services/cli_agent/test_claude_session_events.py +201 -0
- package/server/tests/services/cli_agent/test_jsonl_watcher.py +190 -0
- package/server/tests/services/cli_agent/test_mcp_server.py +67 -29
- package/server/tests/services/cli_agent/test_providers.py +236 -47
- package/server/tests/services/cli_agent/test_service.py +9 -7
- package/server/tests/services/memory/test_jsonl.py +30 -25
- package/server/tests/services/test_events.py +26 -7
- package/server/tests/services/test_identifiers.py +122 -0
- package/server/tests/services/test_process_lifecycle.py +129 -0
- package/server/tests/services/test_supervisor.py +0 -1
- package/server/tests/temporal/__init__.py +0 -0
- package/server/tests/temporal/test_agent_workflow.py +215 -0
- package/server/tests/temporal/test_dispatch.py +231 -0
- package/server/tests/test_admin_handlers.py +394 -0
- package/server/tests/test_auto_skill.py +4 -2
- package/server/tests/test_canary_registry.py +310 -0
- package/server/tests/test_chat_trigger_canary_producer.py +101 -0
- package/server/tests/test_cloudevents_node_parameters.py +129 -0
- package/server/tests/test_credential_icon.py +115 -0
- package/server/tests/test_cron_canary.py +511 -0
- package/server/tests/test_deployment_canary_listener.py +692 -0
- package/server/tests/test_event_framework_phase_a.py +537 -0
- package/server/tests/test_no_raw_prints.py +131 -0
- package/server/tests/test_node_spec.py +196 -103
- package/server/tests/test_parameter_resolver.py +20 -20
- package/server/tests/test_plugin_contract.py +76 -49
- package/server/tests/test_plugin_helpers.py +0 -1
- package/server/tests/test_plugin_self_containment.py +40 -47
- package/server/tests/test_polling_trigger_workflow.py +572 -0
- package/server/tests/test_retry_policies.py +146 -0
- package/server/tests/test_service_factories.py +168 -0
- package/server/tests/test_shutdown_hooks.py +199 -0
- package/server/tests/test_social_provider_registry.py +177 -0
- package/server/tests/test_status_broadcasts.py +214 -63
- package/server/tests/test_task_trigger_canary_producer.py +131 -0
- package/server/tests/test_telegram_trigger_canary_producer.py +113 -0
- package/server/tests/test_tool_registry.py +110 -0
- package/server/tests/test_trigger_listener_workflow.py +365 -0
- package/server/tests/test_whatsapp_trigger_canary_producer.py +164 -0
- package/server/tests/test_workflow_ops.py +1 -3
- package/server/tests/test_workflow_validator.py +791 -0
- package/server/uv.lock +3539 -0
- package/client/dist/assets/index-DQ0nwhec.js +0 -257
- package/client/src/assets/icons/apify/index.ts +0 -19
- package/client/src/assets/icons/browser/index.ts +0 -17
- package/client/src/assets/icons/email/index.ts +0 -22
- package/client/src/assets/icons/google/index.ts +0 -34
- package/client/src/assets/icons/llm/deepseek.svg +0 -1
- package/client/src/assets/icons/llm/index.ts +0 -18
- package/client/src/assets/icons/llm/kimi.svg +0 -1
- package/client/src/assets/icons/llm/mistral.svg +0 -1
- package/client/src/assets/icons/search/index.ts +0 -28
- package/client/src/assets/icons/telegram/index.ts +0 -19
- package/machina/buildenv.py +0 -44
- package/machina/cli.py +0 -55
- package/machina/commands/__init__.py +0 -1
- package/machina/commands/clean.py +0 -80
- package/machina/commands/daemon.py +0 -150
- package/machina/config.py +0 -93
- package/machina/platform_.py +0 -37
- package/machina/pyproject.toml +0 -33
- package/server/nodes/agent/deep_agent.py +0 -103
- package/server/services/agents/__init__.py +0 -9
- package/server/services/agents/adapters.py +0 -199
- package/server/services/agents/constants.py +0 -10
- package/server/services/agents/service.py +0 -297
- package/server/services/cli_agent/providers/anthropic_claude.py +0 -419
- /package/{machina → cli}/README.md +0 -0
- /package/{machina → cli}/__init__.py +0 -0
- /package/{client/src/assets/icons/apify → server/credentials/icons}/apify.svg +0 -0
- /package/{client/src/assets/icons/search/brave.svg → server/credentials/icons/brave_search.svg} +0 -0
- /package/{client/src/assets/icons/email/read.svg → server/credentials/icons/email_himalaya.svg} +0 -0
- /package/{client/src/assets/icons/search → server/credentials/icons}/perplexity.svg +0 -0
- /package/{client/src/assets/icons/search/google.svg → server/credentials/icons/serper.svg} +0 -0
- /package/{client/src/assets → server/credentials}/icons/stripe.svg +0 -0
- /package/{client/src/assets/icons/twitter/x.svg → server/credentials/icons/twitter.svg} +0 -0
- /package/{client/src/assets/icons/browser/chrome.svg → server/nodes/browser/browser/icon.svg} +0 -0
- /package/{client/src/assets/icons/chat/chat.svg → server/nodes/chat/chat_history/icon.svg} +0 -0
- /package/{client/src/assets/icons/code/javascript.svg → server/nodes/code/javascript_executor/icon.svg} +0 -0
- /package/{client/src/assets/icons/code/python.svg → server/nodes/code/python_executor/icon.svg} +0 -0
- /package/{client/src/assets/icons/code/typescript.svg → server/nodes/code/typescript_executor/icon.svg} +0 -0
- /package/{client/src/assets/icons/email/receive.svg → server/nodes/email/email_receive/icon.svg} +0 -0
- /package/{client/src/assets/icons/email/send.svg → server/nodes/email/email_send/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/calendar.svg → server/nodes/google/calendar/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/contacts.svg → server/nodes/google/contacts/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/drive.svg → server/nodes/google/drive/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/gmail.svg → server/nodes/google/gmail/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/sheets.svg → server/nodes/google/sheets/icon.svg} +0 -0
- /package/{client/src/assets/icons/google/tasks.svg → server/nodes/google/tasks/icon.svg} +0 -0
- /package/{client/src/assets/icons/search/duckduckgo.svg → server/nodes/search/duckduckgo_search/icon.svg} +0 -0
- /package/{client/src/assets/icons/social/social.svg → server/nodes/social/social_receive/icon.svg} +0 -0
- /package/{client/src/assets/icons/telegram/telegram.svg → server/nodes/telegram/icon.svg} +0 -0
- /package/server/nodes/utility/{team_monitor.py → team_monitor/__init__.py} +0 -0
- /package/{client/src/assets/icons/whatsapp/whatsapp-db.svg → server/nodes/whatsapp/icon_whatsappDb.svg} +0 -0
- /package/{client/src/assets/icons/whatsapp/whatsapp-receive.svg → server/nodes/whatsapp/icon_whatsappReceive.svg} +0 -0
- /package/{client/src/assets/icons/whatsapp/whatsapp-send.svg → server/nodes/whatsapp/icon_whatsappSend.svg} +0 -0
- /package/{client/src/assets/icons → server/nodes}/whatsapp/whatsapp.svg +0 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""Wave 12 D3: Visibility admin WS handlers.
|
|
2
|
+
|
|
3
|
+
Thin wrappers around Temporal's Visibility API so the MachinaOs admin
|
|
4
|
+
UI can inspect canary listener / schedule state without leaving the
|
|
5
|
+
app. No new DB tables, no custom persistence — Temporal Event History
|
|
6
|
+
+ Visibility queries ARE the registry (see
|
|
7
|
+
``docs-internal/event_framework.md`` § "Failure inspection").
|
|
8
|
+
|
|
9
|
+
Three WS message types:
|
|
10
|
+
|
|
11
|
+
- ``list_canary_listeners`` →
|
|
12
|
+
``client.list_workflows(query="EventWorkflowId='X' AND WorkflowType IN (...)")``
|
|
13
|
+
Returns running TriggerListenerWorkflow + PollingTriggerWorkflow
|
|
14
|
+
instances for a deployment.
|
|
15
|
+
|
|
16
|
+
- ``list_canary_schedules`` →
|
|
17
|
+
``client.list_schedules(query="EventWorkflowId='X' AND EventTriggerKind='cron'")``
|
|
18
|
+
Returns Temporal Schedules created by the deployment's cron
|
|
19
|
+
triggers (Wave 12 C3).
|
|
20
|
+
|
|
21
|
+
- ``get_workflow_failure_history`` →
|
|
22
|
+
``client.get_workflow_handle(...).fetch_history()`` filtered to
|
|
23
|
+
ActivityTaskFailed events for ops inspection of failed runs.
|
|
24
|
+
|
|
25
|
+
All three are read-only — no replay, no mutation. Cancel + delete are
|
|
26
|
+
already covered by ``DeploymentManager.cancel()`` (the production
|
|
27
|
+
cancel path). These handlers exist purely for visibility.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from typing import Any, Dict, List, Optional
|
|
33
|
+
|
|
34
|
+
from fastapi import WebSocket
|
|
35
|
+
|
|
36
|
+
from core.logging import get_logger
|
|
37
|
+
from services.plugin.ws import ws_response
|
|
38
|
+
|
|
39
|
+
logger = get_logger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Mirrors the workflow-type names declared in
|
|
43
|
+
# services.deployment.manager._LISTENER_WORKFLOW_TYPES. Re-declared
|
|
44
|
+
# here to keep this module's dependency graph minimal — admin code
|
|
45
|
+
# shouldn't pull in DeploymentManager.
|
|
46
|
+
_LISTENER_WORKFLOW_TYPES: tuple[str, ...] = (
|
|
47
|
+
"TriggerListenerWorkflow",
|
|
48
|
+
"PollingTriggerWorkflow",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@ws_response
|
|
53
|
+
async def handle_list_canary_listeners(
|
|
54
|
+
data: Dict[str, Any],
|
|
55
|
+
websocket: WebSocket,
|
|
56
|
+
) -> Dict[str, Any]:
|
|
57
|
+
"""List Temporal-durable canary listener workflows.
|
|
58
|
+
|
|
59
|
+
Request shape::
|
|
60
|
+
|
|
61
|
+
{"type": "list_canary_listeners", "workflow_id": "<deployment-id>"}
|
|
62
|
+
|
|
63
|
+
Response::
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
"listeners": [
|
|
67
|
+
{"id": "trigger-listener-<wf>-<node>",
|
|
68
|
+
"type": "TriggerListenerWorkflow",
|
|
69
|
+
"status": "RUNNING",
|
|
70
|
+
"start_time": "<ISO>",
|
|
71
|
+
"trigger_node_id": "<node>",
|
|
72
|
+
"event_type": "<com.machinaos. ... >"},
|
|
73
|
+
...
|
|
74
|
+
],
|
|
75
|
+
"count": <int>,
|
|
76
|
+
}
|
|
77
|
+
"""
|
|
78
|
+
from core.container import container
|
|
79
|
+
|
|
80
|
+
workflow_id = data.get("workflow_id")
|
|
81
|
+
if not workflow_id:
|
|
82
|
+
return {"success": False, "error": "workflow_id is required"}
|
|
83
|
+
|
|
84
|
+
wrapper = container.temporal_client()
|
|
85
|
+
if wrapper is None or wrapper.client is None:
|
|
86
|
+
return {"success": True, "listeners": [], "count": 0, "note": "Temporal not connected"}
|
|
87
|
+
|
|
88
|
+
wf_types_in = ", ".join(f"'{t}'" for t in _LISTENER_WORKFLOW_TYPES)
|
|
89
|
+
query = f"EventWorkflowId='{workflow_id}' " f"AND WorkflowType IN ({wf_types_in}) " f"AND ExecutionStatus='Running'"
|
|
90
|
+
|
|
91
|
+
listeners: List[Dict[str, Any]] = []
|
|
92
|
+
try:
|
|
93
|
+
async for wf in wrapper.client.list_workflows(query=query):
|
|
94
|
+
listeners.append(_describe_workflow(wf))
|
|
95
|
+
except Exception as exc: # noqa: BLE001
|
|
96
|
+
logger.warning(
|
|
97
|
+
f"list_canary_listeners Visibility query failed: {exc} " f"(query={query!r})",
|
|
98
|
+
workflow_id=workflow_id,
|
|
99
|
+
)
|
|
100
|
+
return {"success": False, "error": str(exc), "query": query}
|
|
101
|
+
|
|
102
|
+
return {"success": True, "listeners": listeners, "count": len(listeners)}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@ws_response
|
|
106
|
+
async def handle_list_canary_schedules(
|
|
107
|
+
data: Dict[str, Any],
|
|
108
|
+
websocket: WebSocket,
|
|
109
|
+
) -> Dict[str, Any]:
|
|
110
|
+
"""List Temporal Schedules created by this deployment's cron triggers.
|
|
111
|
+
|
|
112
|
+
Request shape::
|
|
113
|
+
|
|
114
|
+
{"type": "list_canary_schedules", "workflow_id": "<deployment-id>"}
|
|
115
|
+
|
|
116
|
+
Response::
|
|
117
|
+
|
|
118
|
+
{
|
|
119
|
+
"schedules": [
|
|
120
|
+
{"id": "cron-schedule-<wf>-<node>",
|
|
121
|
+
"trigger_node_id": "<node>"},
|
|
122
|
+
...
|
|
123
|
+
],
|
|
124
|
+
"count": <int>,
|
|
125
|
+
}
|
|
126
|
+
"""
|
|
127
|
+
from core.container import container
|
|
128
|
+
|
|
129
|
+
workflow_id = data.get("workflow_id")
|
|
130
|
+
if not workflow_id:
|
|
131
|
+
return {"success": False, "error": "workflow_id is required"}
|
|
132
|
+
|
|
133
|
+
wrapper = container.temporal_client()
|
|
134
|
+
if wrapper is None or wrapper.client is None:
|
|
135
|
+
return {"success": True, "schedules": [], "count": 0, "note": "Temporal not connected"}
|
|
136
|
+
|
|
137
|
+
query = f"EventWorkflowId='{workflow_id}' " f"AND EventTriggerKind='cron'"
|
|
138
|
+
|
|
139
|
+
schedules: List[Dict[str, Any]] = []
|
|
140
|
+
try:
|
|
141
|
+
# ``Client.list_schedules`` is ``async def`` — must await it
|
|
142
|
+
# before iterating. Distinct from ``Client.list_workflows``
|
|
143
|
+
# which returns the iterator directly. See
|
|
144
|
+
# https://python.temporal.io/temporalio.client.Client.html#list_schedules
|
|
145
|
+
iterator = await wrapper.client.list_schedules(query=query)
|
|
146
|
+
async for desc in iterator:
|
|
147
|
+
schedules.append(_describe_schedule(desc))
|
|
148
|
+
except Exception as exc: # noqa: BLE001
|
|
149
|
+
logger.warning(
|
|
150
|
+
f"list_canary_schedules Visibility query failed: {exc} " f"(query={query!r})",
|
|
151
|
+
workflow_id=workflow_id,
|
|
152
|
+
)
|
|
153
|
+
return {"success": False, "error": str(exc), "query": query}
|
|
154
|
+
|
|
155
|
+
return {"success": True, "schedules": schedules, "count": len(schedules)}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@ws_response
|
|
159
|
+
async def handle_get_workflow_failure_history(
|
|
160
|
+
data: Dict[str, Any],
|
|
161
|
+
websocket: WebSocket,
|
|
162
|
+
) -> Dict[str, Any]:
|
|
163
|
+
"""Return ActivityTaskFailed events from a workflow's Event History.
|
|
164
|
+
|
|
165
|
+
Request shape::
|
|
166
|
+
|
|
167
|
+
{"type": "get_workflow_failure_history",
|
|
168
|
+
"workflow_id": "<temporal-workflow-id>",
|
|
169
|
+
"run_id": "<optional-run-id>"}
|
|
170
|
+
|
|
171
|
+
Response::
|
|
172
|
+
|
|
173
|
+
{
|
|
174
|
+
"workflow_id": "<id>",
|
|
175
|
+
"failures": [
|
|
176
|
+
{"event_id": <int>,
|
|
177
|
+
"event_type": "ActivityTaskFailed",
|
|
178
|
+
"event_time": "<ISO>",
|
|
179
|
+
"activity_id": "<id>",
|
|
180
|
+
"activity_type": "<type>",
|
|
181
|
+
"message": "<failure detail>"},
|
|
182
|
+
...
|
|
183
|
+
],
|
|
184
|
+
"count": <int>,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
Ops-only — no replay action available here. For more detail use
|
|
188
|
+
Temporal Web UI at http://localhost:8233 or the temporal CLI:
|
|
189
|
+
|
|
190
|
+
temporal workflow show --workflow-id <id> --output json
|
|
191
|
+
"""
|
|
192
|
+
from core.container import container
|
|
193
|
+
|
|
194
|
+
target_workflow_id = data.get("workflow_id")
|
|
195
|
+
if not target_workflow_id:
|
|
196
|
+
return {"success": False, "error": "workflow_id is required"}
|
|
197
|
+
|
|
198
|
+
wrapper = container.temporal_client()
|
|
199
|
+
if wrapper is None or wrapper.client is None:
|
|
200
|
+
return {"success": False, "error": "Temporal not connected"}
|
|
201
|
+
|
|
202
|
+
run_id: Optional[str] = data.get("run_id")
|
|
203
|
+
try:
|
|
204
|
+
handle = wrapper.client.get_workflow_handle(
|
|
205
|
+
target_workflow_id,
|
|
206
|
+
run_id=run_id,
|
|
207
|
+
)
|
|
208
|
+
except Exception as exc: # noqa: BLE001
|
|
209
|
+
return {"success": False, "error": f"workflow lookup failed: {exc}"}
|
|
210
|
+
|
|
211
|
+
failures: List[Dict[str, Any]] = []
|
|
212
|
+
try:
|
|
213
|
+
async for event in handle.fetch_history_events():
|
|
214
|
+
failure = _maybe_describe_failure(event)
|
|
215
|
+
if failure is not None:
|
|
216
|
+
failures.append(failure)
|
|
217
|
+
except Exception as exc: # noqa: BLE001
|
|
218
|
+
logger.warning(
|
|
219
|
+
f"get_workflow_failure_history history fetch failed: {exc}",
|
|
220
|
+
workflow_id=target_workflow_id,
|
|
221
|
+
)
|
|
222
|
+
return {"success": False, "error": str(exc)}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
"success": True,
|
|
226
|
+
"workflow_id": target_workflow_id,
|
|
227
|
+
"failures": failures,
|
|
228
|
+
"count": len(failures),
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# ---------------------------------------------------------------------------
|
|
233
|
+
# Internal helpers
|
|
234
|
+
# ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _describe_workflow(wf_execution: Any) -> Dict[str, Any]:
|
|
238
|
+
"""Best-effort dict view of a WorkflowExecution from the Visibility list.
|
|
239
|
+
|
|
240
|
+
Search-attribute access on ``WorkflowExecutionDescription`` varies
|
|
241
|
+
by SDK version (some expose ``.search_attributes`` as a plain dict,
|
|
242
|
+
others as TypedSearchAttributes). The helper tries the typed form
|
|
243
|
+
first + falls back to plain dict lookup.
|
|
244
|
+
"""
|
|
245
|
+
sa_view = _search_attributes_dict(getattr(wf_execution, "search_attributes", None))
|
|
246
|
+
return {
|
|
247
|
+
"id": getattr(wf_execution, "id", None),
|
|
248
|
+
"run_id": getattr(wf_execution, "run_id", None),
|
|
249
|
+
"type": _value_or_name(getattr(wf_execution, "workflow_type", None)),
|
|
250
|
+
"status": _value_or_name(getattr(wf_execution, "status", None)),
|
|
251
|
+
"start_time": _iso(getattr(wf_execution, "start_time", None)),
|
|
252
|
+
"close_time": _iso(getattr(wf_execution, "close_time", None)),
|
|
253
|
+
"trigger_node_id": sa_view.get("TriggerNodeId"),
|
|
254
|
+
"event_type": sa_view.get("EventType"),
|
|
255
|
+
"event_trigger_kind": sa_view.get("EventTriggerKind"),
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _describe_schedule(schedule_desc: Any) -> Dict[str, Any]:
|
|
260
|
+
"""Best-effort dict view of a ScheduleListDescription."""
|
|
261
|
+
sa_view = _search_attributes_dict(getattr(schedule_desc, "search_attributes", None))
|
|
262
|
+
return {
|
|
263
|
+
"id": getattr(schedule_desc, "id", None),
|
|
264
|
+
"trigger_node_id": sa_view.get("TriggerNodeId"),
|
|
265
|
+
"event_trigger_kind": sa_view.get("EventTriggerKind"),
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _maybe_describe_failure(event: Any) -> Optional[Dict[str, Any]]:
|
|
270
|
+
"""Return the ActivityTaskFailed shape, or None when ``event`` isn't a failure.
|
|
271
|
+
|
|
272
|
+
Temporal's HistoryEvent is a protobuf message; the failure detail
|
|
273
|
+
lives at ``event.activity_task_failed_event_attributes.failure``.
|
|
274
|
+
The protobuf API can change between SDK versions so we guard every
|
|
275
|
+
attribute access.
|
|
276
|
+
"""
|
|
277
|
+
event_type_attr = getattr(event, "event_type", None)
|
|
278
|
+
event_type_name = _value_or_name(event_type_attr) or ""
|
|
279
|
+
if "ActivityTaskFailed" not in str(event_type_name):
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
attrs = getattr(event, "activity_task_failed_event_attributes", None)
|
|
283
|
+
if attrs is None:
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
failure = getattr(attrs, "failure", None)
|
|
287
|
+
message = getattr(failure, "message", None) if failure is not None else None
|
|
288
|
+
|
|
289
|
+
activity_id = None
|
|
290
|
+
activity_type = None
|
|
291
|
+
# Activity identification lives on the scheduled-event lookup but
|
|
292
|
+
# the protobuf surface keeps it on the failure event too.
|
|
293
|
+
activity_id = getattr(attrs, "activity_id", None)
|
|
294
|
+
activity_type = getattr(attrs, "activity_type", None)
|
|
295
|
+
if activity_type is not None:
|
|
296
|
+
activity_type = getattr(activity_type, "name", str(activity_type))
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
"event_id": getattr(event, "event_id", None),
|
|
300
|
+
"event_type": "ActivityTaskFailed",
|
|
301
|
+
"event_time": _iso(getattr(event, "event_time", None)),
|
|
302
|
+
"activity_id": activity_id,
|
|
303
|
+
"activity_type": activity_type,
|
|
304
|
+
"message": message,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _search_attributes_dict(raw: Any) -> Dict[str, Any]:
|
|
309
|
+
"""Coerce TypedSearchAttributes / plain-dict / None into a flat dict view."""
|
|
310
|
+
if raw is None:
|
|
311
|
+
return {}
|
|
312
|
+
# TypedSearchAttributes is iterable over SearchAttributePair-like
|
|
313
|
+
# objects with ``key.name`` + ``value`` fields.
|
|
314
|
+
if hasattr(raw, "__iter__") and not isinstance(raw, dict):
|
|
315
|
+
result: Dict[str, Any] = {}
|
|
316
|
+
for pair in raw:
|
|
317
|
+
key = getattr(getattr(pair, "key", None), "name", None)
|
|
318
|
+
if key is None:
|
|
319
|
+
continue
|
|
320
|
+
result[key] = getattr(pair, "value", None)
|
|
321
|
+
if result:
|
|
322
|
+
return result
|
|
323
|
+
if isinstance(raw, dict):
|
|
324
|
+
# Untyped search-attribute map from older SDK: name -> [value, ...]
|
|
325
|
+
return {k: (v[0] if isinstance(v, list) and v else v) for k, v in raw.items()}
|
|
326
|
+
return {}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _value_or_name(obj: Any) -> Any:
|
|
330
|
+
"""Return ``.name`` for enum-like objects, otherwise the value itself."""
|
|
331
|
+
if obj is None:
|
|
332
|
+
return None
|
|
333
|
+
name = getattr(obj, "name", None)
|
|
334
|
+
if name is not None:
|
|
335
|
+
return name
|
|
336
|
+
return obj
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _iso(ts: Any) -> Optional[str]:
|
|
340
|
+
"""Format a datetime/Timestamp protobuf as ISO-8601."""
|
|
341
|
+
if ts is None:
|
|
342
|
+
return None
|
|
343
|
+
# datetime objects: have .isoformat()
|
|
344
|
+
isofmt = getattr(ts, "isoformat", None)
|
|
345
|
+
if callable(isofmt):
|
|
346
|
+
try:
|
|
347
|
+
return isofmt()
|
|
348
|
+
except Exception: # noqa: BLE001
|
|
349
|
+
pass
|
|
350
|
+
# Fallback: stringify.
|
|
351
|
+
return str(ts)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# Wire-key → handler dispatch table the WS router picks up via
|
|
355
|
+
# services.ws_handler_registry.register_ws_handlers.
|
|
356
|
+
WS_HANDLERS: Dict[str, Any] = {
|
|
357
|
+
"list_canary_listeners": handle_list_canary_listeners,
|
|
358
|
+
"list_canary_schedules": handle_list_canary_schedules,
|
|
359
|
+
"get_workflow_failure_history": handle_get_workflow_failure_history,
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
__all__ = [
|
|
364
|
+
"WS_HANDLERS",
|
|
365
|
+
"handle_list_canary_listeners",
|
|
366
|
+
"handle_list_canary_schedules",
|
|
367
|
+
"handle_get_workflow_failure_history",
|
|
368
|
+
]
|
|
@@ -71,6 +71,7 @@ class DaemonEventSource(EventSource):
|
|
|
71
71
|
|
|
72
72
|
def workdir(self) -> Path:
|
|
73
73
|
from core.config import Settings
|
|
74
|
+
|
|
74
75
|
cwd = Path(Settings().workspace_base_resolved).resolve() / self.workflow_namespace
|
|
75
76
|
cwd.mkdir(parents=True, exist_ok=True)
|
|
76
77
|
return cwd
|
|
@@ -140,7 +141,8 @@ class DaemonEventSource(EventSource):
|
|
|
140
141
|
async with self._lock:
|
|
141
142
|
self._stopped = True
|
|
142
143
|
await get_process_service().stop(
|
|
143
|
-
name=self.process_name,
|
|
144
|
+
name=self.process_name,
|
|
145
|
+
workflow_id=self.workflow_namespace,
|
|
144
146
|
)
|
|
145
147
|
self._started = False
|
|
146
148
|
self._pid = None
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Wave 12 A6: Temporal-native event dispatch.
|
|
2
|
+
|
|
3
|
+
One public function — :func:`emit` — takes a :class:`WorkflowEvent` and
|
|
4
|
+
routes it to:
|
|
5
|
+
|
|
6
|
+
1. **Running consumer workflows** via Temporal Visibility query +
|
|
7
|
+
``Client.signal_workflow``. Workflows tag themselves with the
|
|
8
|
+
``EventType`` Search Attribute when they're started; this dispatch
|
|
9
|
+
finds them via ``ListWorkflows(query="EventType='X' AND
|
|
10
|
+
ExecutionStatus='Running'")`` and signals each.
|
|
11
|
+
|
|
12
|
+
2. **In-process FastAPI WebSocket clients** via direct
|
|
13
|
+
``status_broadcaster.broadcast()`` call. Worker + WS pool share
|
|
14
|
+
memory + event loop, so no IPC hop is needed (audit confirmed —
|
|
15
|
+
``main.py:211-292``).
|
|
16
|
+
|
|
17
|
+
No EventDispatchWorkflow, no Redis Streams, no DLQ table. Temporal's
|
|
18
|
+
Visibility + Signal API + Event History provide the durability primitives
|
|
19
|
+
this function depends on; everything else is a thin pass-through.
|
|
20
|
+
|
|
21
|
+
Behind ``Settings.event_framework_enabled`` (default off in Phase A).
|
|
22
|
+
When disabled, :func:`emit` is a no-op pass-through that returns the
|
|
23
|
+
envelope unchanged.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import asyncio
|
|
29
|
+
from typing import Optional
|
|
30
|
+
|
|
31
|
+
from core.config import Settings
|
|
32
|
+
from core.logging import get_logger
|
|
33
|
+
from services.events.envelope import WorkflowEvent
|
|
34
|
+
|
|
35
|
+
logger = get_logger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Wire-routing key the FastAPI WebSocket layer switches on. Plugin emits
|
|
39
|
+
# its own wire key today (``credential_catalogue_updated`` /
|
|
40
|
+
# ``agent_progress`` / ``plugin_connection_status`` / etc.). This
|
|
41
|
+
# default is the generic CloudEvents-envelope channel used by events
|
|
42
|
+
# that don't carry a plugin-specific wire key.
|
|
43
|
+
_DEFAULT_WIRE_ROUTING_KEY = "cloudevent"
|
|
44
|
+
|
|
45
|
+
# Visibility query template — produces a SQL-like List Filter string.
|
|
46
|
+
# ``ExecutionStatus`` enum value 'Running' matches Temporal's status
|
|
47
|
+
# encoding; see https://docs.temporal.io/list-filter.
|
|
48
|
+
_RUNNING_CONSUMERS_QUERY = "EventType='{event_type}' AND ExecutionStatus='Running'"
|
|
49
|
+
|
|
50
|
+
# Signal handler name on consumer workflows. Plain identifier rather
|
|
51
|
+
# than CloudEvents type because Temporal Signal names are Python
|
|
52
|
+
# identifiers (cannot contain dots).
|
|
53
|
+
_SIGNAL_NAME = "on_event"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def emit(
|
|
57
|
+
event: WorkflowEvent,
|
|
58
|
+
*,
|
|
59
|
+
wire_routing_key: Optional[str] = None,
|
|
60
|
+
) -> WorkflowEvent:
|
|
61
|
+
"""Route ``event`` to running consumer workflows + in-process WS clients.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
event: The :class:`WorkflowEvent` to dispatch. CloudEvents
|
|
65
|
+
envelope with a populated ``type`` (the Visibility filter
|
|
66
|
+
keys off this).
|
|
67
|
+
wire_routing_key: Outer WebSocket routing key (the ``type``
|
|
68
|
+
field on the WS frame). Defaults to the generic
|
|
69
|
+
``cloudevent`` channel; plugin emitters override per their
|
|
70
|
+
existing wire key (e.g. ``"telegram_message_received"``).
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The envelope unchanged — callers may chain.
|
|
74
|
+
|
|
75
|
+
Behaviour when ``Settings.event_framework_enabled=False`` (Phase A
|
|
76
|
+
default): pass-through no-op. Logged at DEBUG so opt-in dogfooding
|
|
77
|
+
is observable without flipping the flag globally.
|
|
78
|
+
"""
|
|
79
|
+
if not Settings().event_framework_enabled:
|
|
80
|
+
logger.debug(
|
|
81
|
+
f"event-framework disabled — emit no-op for event.id={event.id} " f"type={event.type}",
|
|
82
|
+
)
|
|
83
|
+
return event
|
|
84
|
+
|
|
85
|
+
# Concurrent fan-out: Visibility query + Signal each consumer, AND
|
|
86
|
+
# broadcast to in-process WS clients. asyncio.gather lets the
|
|
87
|
+
# broadcast happen even if the Temporal Visibility query fails (and
|
|
88
|
+
# vice versa).
|
|
89
|
+
await asyncio.gather(
|
|
90
|
+
_signal_running_consumers(event),
|
|
91
|
+
_broadcast_in_process(event, wire_routing_key or _DEFAULT_WIRE_ROUTING_KEY),
|
|
92
|
+
return_exceptions=False,
|
|
93
|
+
)
|
|
94
|
+
return event
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def _signal_running_consumers(event: WorkflowEvent) -> None:
|
|
98
|
+
"""Find running workflows tagged with ``EventType=event.type`` and
|
|
99
|
+
signal each with ``on_event``.
|
|
100
|
+
|
|
101
|
+
Fail-soft: if the Temporal client is unavailable or the Visibility
|
|
102
|
+
query errors, log a warning and continue. The in-process broadcast
|
|
103
|
+
path is independent and still delivers to WS clients.
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
from core.container import container
|
|
107
|
+
|
|
108
|
+
wrapper = container.temporal_client()
|
|
109
|
+
except Exception as exc: # noqa: BLE001
|
|
110
|
+
logger.warning(f"emit: container.temporal_client unavailable: {exc}")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
if wrapper is None or wrapper.client is None:
|
|
114
|
+
logger.debug(
|
|
115
|
+
f"emit: Temporal not connected — skipping consumer fan-out for " f"event.id={event.id}",
|
|
116
|
+
)
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
client = wrapper.client
|
|
120
|
+
query = _RUNNING_CONSUMERS_QUERY.format(event_type=event.type)
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
consumers = []
|
|
124
|
+
async for wf in client.list_workflows(query=query):
|
|
125
|
+
consumers.append(wf)
|
|
126
|
+
except Exception as exc: # noqa: BLE001
|
|
127
|
+
logger.warning(f"emit: Visibility query failed (query={query!r}): {exc}")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
if not consumers:
|
|
131
|
+
logger.debug(
|
|
132
|
+
f"emit: 0 consumers for event.type={event.type!r} " f"(event.id={event.id})",
|
|
133
|
+
)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
# Signal each consumer concurrently. Per-target failures don't
|
|
137
|
+
# block other consumers (return_exceptions=True surfaces failures
|
|
138
|
+
# as exceptions in the result list).
|
|
139
|
+
signal_results = await asyncio.gather(
|
|
140
|
+
*[_signal_one(client, wf.id, event) for wf in consumers],
|
|
141
|
+
return_exceptions=True,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
delivered = sum(1 for r in signal_results if not isinstance(r, Exception))
|
|
145
|
+
failed = len(signal_results) - delivered
|
|
146
|
+
logger.info(
|
|
147
|
+
f"emit: signalled {delivered}/{len(consumers)} consumers for " f"event.type={event.type!r} (failed={failed}, event.id={event.id})",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
async def _signal_one(client, workflow_id: str, event: WorkflowEvent) -> None:
|
|
152
|
+
"""Send the ``on_event`` signal to one workflow.
|
|
153
|
+
|
|
154
|
+
Kept as its own coroutine so :func:`_signal_running_consumers` can
|
|
155
|
+
``asyncio.gather`` independently per-consumer.
|
|
156
|
+
"""
|
|
157
|
+
handle = client.get_workflow_handle(workflow_id)
|
|
158
|
+
await handle.signal(_SIGNAL_NAME, event.model_dump(mode="json"))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def _broadcast_in_process(event: WorkflowEvent, wire_routing_key: str) -> None:
|
|
162
|
+
"""Direct in-process WS fan-out via the status broadcaster.
|
|
163
|
+
|
|
164
|
+
Same asyncio event loop as the FastAPI handlers — see ``main.py:
|
|
165
|
+
211-292`` (TemporalWorkerManager starts as ``asyncio.create_task``).
|
|
166
|
+
Activity → broadcaster is a direct method call against in-memory
|
|
167
|
+
``Set[WebSocket]``; no IPC.
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
from services.status_broadcaster import get_status_broadcaster
|
|
171
|
+
|
|
172
|
+
broadcaster = get_status_broadcaster()
|
|
173
|
+
except Exception as exc: # noqa: BLE001
|
|
174
|
+
logger.warning(f"emit: status_broadcaster unavailable: {exc}")
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
await broadcaster.broadcast(
|
|
179
|
+
{
|
|
180
|
+
"type": wire_routing_key,
|
|
181
|
+
"data": event.model_dump(mode="json"),
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
except Exception as exc: # noqa: BLE001
|
|
185
|
+
logger.warning(f"emit: WS broadcast failed (wire_key={wire_routing_key!r}): {exc}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
__all__ = ["emit"]
|