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
|
@@ -44,53 +44,22 @@ class StatusBroadcaster:
|
|
|
44
44
|
"connected_devices": [],
|
|
45
45
|
"connection_type": None,
|
|
46
46
|
"qr_data": None,
|
|
47
|
-
"session_token": None
|
|
48
|
-
},
|
|
49
|
-
"whatsapp": {
|
|
50
|
-
"connected": False,
|
|
51
|
-
"has_session": False,
|
|
52
|
-
"running": False,
|
|
53
|
-
"pairing": False,
|
|
54
|
-
"device_id": None,
|
|
55
|
-
"qr": None
|
|
56
|
-
},
|
|
57
|
-
"twitter": {
|
|
58
|
-
"connected": False,
|
|
59
|
-
"username": None,
|
|
60
|
-
"user_id": None,
|
|
61
|
-
"name": None,
|
|
62
|
-
"profile_image_url": None
|
|
47
|
+
"session_token": None,
|
|
63
48
|
},
|
|
49
|
+
"whatsapp": {"connected": False, "has_session": False, "running": False, "pairing": False, "device_id": None, "qr": None},
|
|
50
|
+
"twitter": {"connected": False, "username": None, "user_id": None, "name": None, "profile_image_url": None},
|
|
64
51
|
"google": {
|
|
65
52
|
"connected": False,
|
|
66
53
|
"email": None,
|
|
67
54
|
"name": None,
|
|
68
55
|
},
|
|
69
|
-
"telegram": {
|
|
70
|
-
"connected": False,
|
|
71
|
-
"bot_id": None,
|
|
72
|
-
"bot_username": None,
|
|
73
|
-
"bot_name": None
|
|
74
|
-
},
|
|
56
|
+
"telegram": {"connected": False, "bot_id": None, "bot_username": None, "bot_name": None},
|
|
75
57
|
"api_keys": {}, # provider -> validation status
|
|
76
58
|
"nodes": {}, # node_id -> node status
|
|
77
59
|
"variables": {}, # variable_name -> value
|
|
78
|
-
"workflow": {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
},
|
|
82
|
-
"workflow_lock": {
|
|
83
|
-
"locked": False,
|
|
84
|
-
"workflow_id": None,
|
|
85
|
-
"locked_at": None,
|
|
86
|
-
"reason": None
|
|
87
|
-
},
|
|
88
|
-
"deployment": {
|
|
89
|
-
"isRunning": False,
|
|
90
|
-
"activeRuns": 0,
|
|
91
|
-
"status": "idle",
|
|
92
|
-
"workflow_id": None
|
|
93
|
-
}
|
|
60
|
+
"workflow": {"executing": False, "current_node": None},
|
|
61
|
+
"workflow_lock": {"locked": False, "workflow_id": None, "locked_at": None, "reason": None},
|
|
62
|
+
"deployment": {"isRunning": False, "activeRuns": 0, "status": "idle", "workflow_id": None},
|
|
94
63
|
}
|
|
95
64
|
|
|
96
65
|
async def connect(self, websocket: WebSocket):
|
|
@@ -113,10 +82,7 @@ class StatusBroadcaster:
|
|
|
113
82
|
logger.info(f"[StatusBroadcaster] Client connected. Total: {len(self._connections)}")
|
|
114
83
|
|
|
115
84
|
try:
|
|
116
|
-
await websocket.send_json({
|
|
117
|
-
"type": "initial_status",
|
|
118
|
-
"data": self._status
|
|
119
|
-
})
|
|
85
|
+
await websocket.send_json({"type": "initial_status", "data": self._status})
|
|
120
86
|
except Exception as e:
|
|
121
87
|
logger.error(f"[StatusBroadcaster] Failed to send initial status: {e}")
|
|
122
88
|
|
|
@@ -155,10 +121,12 @@ class StatusBroadcaster:
|
|
|
155
121
|
return
|
|
156
122
|
|
|
157
123
|
event = WorkflowEvent.deployment_snapshot(running_ids)
|
|
158
|
-
await websocket.send_json(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
124
|
+
await websocket.send_json(
|
|
125
|
+
{
|
|
126
|
+
"type": "deployment_snapshot",
|
|
127
|
+
"data": event.model_dump(mode="json"),
|
|
128
|
+
}
|
|
129
|
+
)
|
|
162
130
|
|
|
163
131
|
async def disconnect(self, websocket: WebSocket):
|
|
164
132
|
"""Remove a WebSocket connection."""
|
|
@@ -244,12 +212,7 @@ class StatusBroadcaster:
|
|
|
244
212
|
# =========================================================================
|
|
245
213
|
|
|
246
214
|
async def update_api_key_status(
|
|
247
|
-
self,
|
|
248
|
-
provider: str,
|
|
249
|
-
valid: bool,
|
|
250
|
-
message: Optional[str] = None,
|
|
251
|
-
has_key: bool = True,
|
|
252
|
-
models: Optional[List[str]] = None
|
|
215
|
+
self, provider: str, valid: bool, message: Optional[str] = None, has_key: bool = True, models: Optional[List[str]] = None
|
|
253
216
|
):
|
|
254
217
|
"""Update API key validation status and broadcast."""
|
|
255
218
|
self._status["api_keys"][provider] = {
|
|
@@ -257,14 +220,10 @@ class StatusBroadcaster:
|
|
|
257
220
|
"hasKey": has_key,
|
|
258
221
|
"message": message,
|
|
259
222
|
"models": models or [],
|
|
260
|
-
"timestamp": asyncio.get_event_loop().time()
|
|
223
|
+
"timestamp": asyncio.get_event_loop().time(),
|
|
261
224
|
}
|
|
262
225
|
|
|
263
|
-
await self.broadcast({
|
|
264
|
-
"type": "api_key_status",
|
|
265
|
-
"provider": provider,
|
|
266
|
-
"data": self._status["api_keys"][provider]
|
|
267
|
-
})
|
|
226
|
+
await self.broadcast({"type": "api_key_status", "provider": provider, "data": self._status["api_keys"][provider]})
|
|
268
227
|
|
|
269
228
|
def get_api_key_status(self, provider: str) -> Optional[Dict[str, Any]]:
|
|
270
229
|
"""Get API key validation status for a provider."""
|
|
@@ -289,15 +248,23 @@ class StatusBroadcaster:
|
|
|
289
248
|
*,
|
|
290
249
|
provider: str,
|
|
291
250
|
customer_id: Optional[str] = None,
|
|
251
|
+
workflow_id: Optional[str] = None,
|
|
252
|
+
**data_extra: Any,
|
|
292
253
|
) -> None:
|
|
293
|
-
"""Emit a CloudEvents-typed credential
|
|
254
|
+
"""Emit a CloudEvents-typed credential broadcast.
|
|
294
255
|
|
|
295
256
|
Args:
|
|
296
257
|
event_type: CloudEvents `type` field. Convention:
|
|
297
|
-
``"credential.api_key.saved"`` / ``".deleted"
|
|
298
|
-
``"credential.oauth.
|
|
258
|
+
``"credential.api_key.saved"`` / ``".deleted"`` /
|
|
259
|
+
``".runtime_failed"``, ``"credential.oauth.connected"`` /
|
|
260
|
+
``".disconnected"`` / ``".runtime_failed"``.
|
|
299
261
|
provider: Provider id (e.g. ``"openai"``, ``"twitter"``).
|
|
300
262
|
customer_id: For multi-tenant OAuth flows. Default omitted.
|
|
263
|
+
workflow_id: Optional CloudEvents extension attribute scoping
|
|
264
|
+
runtime events to the workflow that triggered them.
|
|
265
|
+
**data_extra: Additional fields merged into the envelope's
|
|
266
|
+
``data`` block (e.g. ``reason``, ``node_id``, ``error``
|
|
267
|
+
for runtime failure events).
|
|
301
268
|
"""
|
|
302
269
|
# Local import keeps the broadcaster module independent of the
|
|
303
270
|
# event framework's load order during startup.
|
|
@@ -307,16 +274,92 @@ class StatusBroadcaster:
|
|
|
307
274
|
source="machinaos://services/credentials",
|
|
308
275
|
type=event_type,
|
|
309
276
|
subject=provider,
|
|
277
|
+
workflow_id=workflow_id,
|
|
310
278
|
data={
|
|
311
279
|
"provider": provider,
|
|
312
280
|
**({"customer_id": customer_id} if customer_id else {}),
|
|
281
|
+
**data_extra,
|
|
313
282
|
},
|
|
314
283
|
)
|
|
315
284
|
|
|
316
|
-
await self.broadcast(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
285
|
+
await self.broadcast(
|
|
286
|
+
{
|
|
287
|
+
"type": "credential_catalogue_updated",
|
|
288
|
+
"data": event.model_dump(mode="json"),
|
|
289
|
+
}
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
async def broadcast_workflow_lifecycle(
|
|
293
|
+
self,
|
|
294
|
+
stage: str,
|
|
295
|
+
*,
|
|
296
|
+
workflow_id: str,
|
|
297
|
+
**data_extra: Any,
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Emit a CloudEvents-typed workflow.{stage} broadcast.
|
|
300
|
+
|
|
301
|
+
Wraps :meth:`WorkflowEvent.workflow_lifecycle` so callers don't
|
|
302
|
+
hand-build envelopes. ``stage`` matches the factory's Literal
|
|
303
|
+
(``imported`` / ``deployment.started`` / etc.). Wire-format key
|
|
304
|
+
is ``workflow_lifecycle`` — the frontend's ``WebSocketContext``
|
|
305
|
+
routes on this key and invalidates the workflows query so the
|
|
306
|
+
sidebar refreshes across all connected clients.
|
|
307
|
+
"""
|
|
308
|
+
from services.events import WorkflowEvent
|
|
309
|
+
|
|
310
|
+
event = WorkflowEvent.workflow_lifecycle(
|
|
311
|
+
stage=stage, # type: ignore[arg-type]
|
|
312
|
+
workflow_id=workflow_id,
|
|
313
|
+
data=data_extra or None,
|
|
314
|
+
)
|
|
315
|
+
await self.broadcast(
|
|
316
|
+
{
|
|
317
|
+
"type": "workflow_lifecycle",
|
|
318
|
+
"data": event.model_dump(mode="json"),
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
async def broadcast_node_parameters_updated(
|
|
323
|
+
self,
|
|
324
|
+
node_id: str,
|
|
325
|
+
*,
|
|
326
|
+
parameters: Dict[str, Any],
|
|
327
|
+
workflow_id: Optional[str] = None,
|
|
328
|
+
version: int = 1,
|
|
329
|
+
source_hint: str = "user",
|
|
330
|
+
) -> None:
|
|
331
|
+
"""Emit a CloudEvents-typed ``node.parameters.updated`` event.
|
|
332
|
+
|
|
333
|
+
Replaces three legacy raw-dict broadcast sites with one typed
|
|
334
|
+
envelope (RFC §6.4 CloudEvents discipline). Wire-format key
|
|
335
|
+
``node_parameters_updated`` stays the same for FE back-compat;
|
|
336
|
+
only the inner payload becomes the typed envelope.
|
|
337
|
+
|
|
338
|
+
Callers:
|
|
339
|
+
- ``routers/websocket.py:handle_save_node_parameters`` (user
|
|
340
|
+
edited the parameter panel — ``source_hint="user"``).
|
|
341
|
+
- ``services/cli_agent/service.py:_persist_memory`` (Claude
|
|
342
|
+
Code CLI memory bridge appended a turn —
|
|
343
|
+
``source_hint="cli"``).
|
|
344
|
+
- ``services/temporal/agent_activities.py:persist_agent_turn``
|
|
345
|
+
(F4.B AgentWorkflow per-turn memory append —
|
|
346
|
+
``source_hint="agent"``).
|
|
347
|
+
"""
|
|
348
|
+
from services.events import WorkflowEvent
|
|
349
|
+
|
|
350
|
+
event = WorkflowEvent.node_parameters_updated(
|
|
351
|
+
node_id,
|
|
352
|
+
parameters=parameters,
|
|
353
|
+
workflow_id=workflow_id,
|
|
354
|
+
version=version,
|
|
355
|
+
source_hint=source_hint,
|
|
356
|
+
)
|
|
357
|
+
await self.broadcast(
|
|
358
|
+
{
|
|
359
|
+
"type": "node_parameters_updated",
|
|
360
|
+
"data": event.model_dump(mode="json"),
|
|
361
|
+
}
|
|
362
|
+
)
|
|
320
363
|
|
|
321
364
|
async def broadcast_agent_progress(
|
|
322
365
|
self,
|
|
@@ -346,42 +389,146 @@ class StatusBroadcaster:
|
|
|
346
389
|
phase=phase,
|
|
347
390
|
)
|
|
348
391
|
|
|
349
|
-
await self.broadcast(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
392
|
+
await self.broadcast(
|
|
393
|
+
{
|
|
394
|
+
"type": "agent_progress",
|
|
395
|
+
"data": event.model_dump(mode="json"),
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
async def broadcast_claude_session_spawned(
|
|
400
|
+
self,
|
|
401
|
+
memory_node_id: str,
|
|
402
|
+
*,
|
|
403
|
+
session_uuid: str,
|
|
404
|
+
pid: int,
|
|
405
|
+
workflow_id: Optional[str] = None,
|
|
406
|
+
) -> None:
|
|
407
|
+
"""Emit ``claude.session.spawned`` when a pooled claude is
|
|
408
|
+
cold-started. Single wire key for all four session-lifecycle
|
|
409
|
+
events; FE discriminates on envelope.type.
|
|
410
|
+
"""
|
|
411
|
+
from services.events import WorkflowEvent
|
|
412
|
+
|
|
413
|
+
event = WorkflowEvent.claude_session_spawned(
|
|
414
|
+
memory_node_id,
|
|
415
|
+
session_uuid=session_uuid,
|
|
416
|
+
pid=pid,
|
|
417
|
+
workflow_id=workflow_id,
|
|
418
|
+
)
|
|
419
|
+
await self.broadcast(
|
|
420
|
+
{
|
|
421
|
+
"type": "claude_session_event",
|
|
422
|
+
"data": event.model_dump(mode="json"),
|
|
423
|
+
}
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
async def broadcast_claude_session_cleared(
|
|
427
|
+
self,
|
|
428
|
+
memory_node_id: str,
|
|
429
|
+
*,
|
|
430
|
+
old_session_uuid: str,
|
|
431
|
+
new_session_uuid: str,
|
|
432
|
+
workflow_id: Optional[str] = None,
|
|
433
|
+
) -> None:
|
|
434
|
+
"""Emit ``claude.session.cleared`` after ``/clear`` minted a new
|
|
435
|
+
session UUID. Carries both old + new so the FE can update
|
|
436
|
+
any session-uuid display + warn if there are open references."""
|
|
437
|
+
from services.events import WorkflowEvent
|
|
438
|
+
|
|
439
|
+
event = WorkflowEvent.claude_session_cleared(
|
|
440
|
+
memory_node_id,
|
|
441
|
+
old_session_uuid=old_session_uuid,
|
|
442
|
+
new_session_uuid=new_session_uuid,
|
|
443
|
+
workflow_id=workflow_id,
|
|
444
|
+
)
|
|
445
|
+
await self.broadcast(
|
|
446
|
+
{
|
|
447
|
+
"type": "claude_session_event",
|
|
448
|
+
"data": event.model_dump(mode="json"),
|
|
449
|
+
}
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
async def broadcast_claude_session_terminated(
|
|
453
|
+
self,
|
|
454
|
+
memory_node_id: str,
|
|
455
|
+
*,
|
|
456
|
+
reason: str,
|
|
457
|
+
session_uuid: Optional[str] = None,
|
|
458
|
+
workflow_id: Optional[str] = None,
|
|
459
|
+
) -> None:
|
|
460
|
+
"""Emit ``claude.session.terminated`` when a pooled session
|
|
461
|
+
ends. ``reason`` is one of ``idle / crashed / evicted /
|
|
462
|
+
shutdown / explicit`` (typed enum in
|
|
463
|
+
:class:`WorkflowEvent.claude_session_terminated`)."""
|
|
464
|
+
from services.events import WorkflowEvent
|
|
465
|
+
|
|
466
|
+
event = WorkflowEvent.claude_session_terminated(
|
|
467
|
+
memory_node_id,
|
|
468
|
+
reason=reason, # type: ignore[arg-type]
|
|
469
|
+
session_uuid=session_uuid,
|
|
470
|
+
workflow_id=workflow_id,
|
|
471
|
+
)
|
|
472
|
+
await self.broadcast(
|
|
473
|
+
{
|
|
474
|
+
"type": "claude_session_event",
|
|
475
|
+
"data": event.model_dump(mode="json"),
|
|
476
|
+
}
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
async def broadcast_claude_session_usage(
|
|
480
|
+
self,
|
|
481
|
+
memory_node_id: str,
|
|
482
|
+
*,
|
|
483
|
+
session_uuid: str,
|
|
484
|
+
total_cost_usd: Optional[float] = None,
|
|
485
|
+
input_tokens: int = 0,
|
|
486
|
+
output_tokens: int = 0,
|
|
487
|
+
cache_read_input_tokens: int = 0,
|
|
488
|
+
cache_creation_input_tokens: int = 0,
|
|
489
|
+
duration_ms: Optional[int] = None,
|
|
490
|
+
num_turns: Optional[int] = None,
|
|
491
|
+
workflow_id: Optional[str] = None,
|
|
492
|
+
) -> None:
|
|
493
|
+
"""Emit ``claude.session.usage`` after each turn's ``result``
|
|
494
|
+
event. Replaces the (unparseable) ``/usage`` TUI scrape with
|
|
495
|
+
structured data straight from the JSONL ``result.usage`` block.
|
|
496
|
+
FE renders a usage panel on simpleMemory by subscribing here."""
|
|
497
|
+
from services.events import WorkflowEvent
|
|
498
|
+
|
|
499
|
+
event = WorkflowEvent.claude_session_usage(
|
|
500
|
+
memory_node_id,
|
|
501
|
+
session_uuid=session_uuid,
|
|
502
|
+
total_cost_usd=total_cost_usd,
|
|
503
|
+
input_tokens=input_tokens,
|
|
504
|
+
output_tokens=output_tokens,
|
|
505
|
+
cache_read_input_tokens=cache_read_input_tokens,
|
|
506
|
+
cache_creation_input_tokens=cache_creation_input_tokens,
|
|
507
|
+
duration_ms=duration_ms,
|
|
508
|
+
num_turns=num_turns,
|
|
509
|
+
workflow_id=workflow_id,
|
|
510
|
+
)
|
|
511
|
+
await self.broadcast(
|
|
512
|
+
{
|
|
513
|
+
"type": "claude_session_usage",
|
|
514
|
+
"data": event.model_dump(mode="json"),
|
|
515
|
+
}
|
|
516
|
+
)
|
|
353
517
|
|
|
354
518
|
# =========================================================================
|
|
355
519
|
# Android Status Updates
|
|
356
520
|
# =========================================================================
|
|
357
521
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
):
|
|
369
|
-
"""Update Android relay connection status and broadcast."""
|
|
370
|
-
self._status["android"] = {
|
|
371
|
-
"connected": connected,
|
|
372
|
-
"paired": paired,
|
|
373
|
-
"device_id": device_id,
|
|
374
|
-
"device_name": device_name,
|
|
375
|
-
"connected_devices": connected_devices or [],
|
|
376
|
-
"connection_type": connection_type,
|
|
377
|
-
"qr_data": qr_data,
|
|
378
|
-
"session_token": session_token
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
await self.broadcast({
|
|
382
|
-
"type": "android_status",
|
|
383
|
-
"data": self._status["android"]
|
|
384
|
-
})
|
|
522
|
+
# Wave 12 B1-B3: all three ``update_<plugin>_status`` methods MOVED
|
|
523
|
+
# to per-plugin ``_events.py:broadcast_<plugin>_status`` wrappers
|
|
524
|
+
# (android / whatsapp / telegram). Each plugin owns its broadcast
|
|
525
|
+
# shape + the dual-emit (legacy raw + typed CloudEvents sibling).
|
|
526
|
+
# The shared ``_emit_connection_typed`` helper retired with B3
|
|
527
|
+
# because no remaining caller uses it — plugin-specific typed
|
|
528
|
+
# factories (e.g. ``android_connection_status``) replace the
|
|
529
|
+
# cross-plugin parametrised helper per RFC §6.4. Status cache
|
|
530
|
+
# slots in ``self._status[<plugin>]`` stay so ``get_status()`` +
|
|
531
|
+
# WS-connect initial snapshot keep working.
|
|
385
532
|
|
|
386
533
|
# =========================================================================
|
|
387
534
|
# Status updates -- per-service refresh bodies live in their plugin
|
|
@@ -392,59 +539,18 @@ class StatusBroadcaster:
|
|
|
392
539
|
# zero per-plugin knowledge.
|
|
393
540
|
# =========================================================================
|
|
394
541
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
pairing: bool = False,
|
|
401
|
-
device_id: Optional[str] = None,
|
|
402
|
-
qr: Optional[str] = None
|
|
403
|
-
):
|
|
404
|
-
"""Update WhatsApp connection status and broadcast."""
|
|
405
|
-
import time
|
|
406
|
-
self._status["whatsapp"] = {
|
|
407
|
-
"connected": connected,
|
|
408
|
-
"has_session": has_session,
|
|
409
|
-
"running": running,
|
|
410
|
-
"pairing": pairing,
|
|
411
|
-
"device_id": device_id,
|
|
412
|
-
"qr": qr,
|
|
413
|
-
"timestamp": time.time()
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
await self.broadcast({
|
|
417
|
-
"type": "whatsapp_status",
|
|
418
|
-
"data": self._status["whatsapp"]
|
|
419
|
-
})
|
|
542
|
+
# Wave 12 B2: ``update_whatsapp_status`` MOVED to
|
|
543
|
+
# ``nodes/whatsapp/_events.py:broadcast_whatsapp_status``. Plus the
|
|
544
|
+
# 7 send_custom_event callsites (message_sent/received, 4 newsletter
|
|
545
|
+
# events, history_sync_complete) all moved to the plugin's
|
|
546
|
+
# ``_events.py`` typed wrappers. Status cache slot stays here.
|
|
420
547
|
|
|
421
548
|
# =========================================================================
|
|
422
549
|
# Telegram Status Updates
|
|
423
550
|
# =========================================================================
|
|
424
551
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
connected: bool,
|
|
428
|
-
bot_id: Optional[int] = None,
|
|
429
|
-
bot_username: Optional[str] = None,
|
|
430
|
-
bot_name: Optional[str] = None,
|
|
431
|
-
owner_chat_id: Optional[int] = None
|
|
432
|
-
):
|
|
433
|
-
"""Update Telegram bot connection status and broadcast."""
|
|
434
|
-
import time
|
|
435
|
-
self._status["telegram"] = {
|
|
436
|
-
"connected": connected,
|
|
437
|
-
"bot_id": bot_id,
|
|
438
|
-
"bot_username": bot_username,
|
|
439
|
-
"bot_name": bot_name,
|
|
440
|
-
"owner_chat_id": owner_chat_id,
|
|
441
|
-
"timestamp": time.time()
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
await self.broadcast({
|
|
445
|
-
"type": "telegram_status",
|
|
446
|
-
"data": self._status["telegram"]
|
|
447
|
-
})
|
|
552
|
+
# Wave 12 B3: ``update_telegram_status`` MOVED to
|
|
553
|
+
# ``nodes/telegram/_events.py:broadcast_telegram_status``.
|
|
448
554
|
|
|
449
555
|
# =========================================================================
|
|
450
556
|
# Node Status Updates
|
|
@@ -455,7 +561,7 @@ class StatusBroadcaster:
|
|
|
455
561
|
node_id: str,
|
|
456
562
|
status: str, # "idle", "executing", "waiting", "success", "error"
|
|
457
563
|
data: Optional[Dict[str, Any]] = None,
|
|
458
|
-
workflow_id: Optional[str] = None
|
|
564
|
+
workflow_id: Optional[str] = None,
|
|
459
565
|
):
|
|
460
566
|
"""Update a specific node's status and broadcast.
|
|
461
567
|
|
|
@@ -465,27 +571,21 @@ class StatusBroadcaster:
|
|
|
465
571
|
data: Optional status data
|
|
466
572
|
workflow_id: Optional workflow ID to scope the status update (n8n pattern)
|
|
467
573
|
"""
|
|
468
|
-
logger.debug(
|
|
574
|
+
logger.debug(
|
|
575
|
+
f"[BROADCAST] update_node_status: node={node_id}, status={status}, workflow={workflow_id}, connections={len(self._connections)}"
|
|
576
|
+
)
|
|
469
577
|
self._status["nodes"][node_id] = {
|
|
470
578
|
"status": status,
|
|
471
579
|
"data": data or {},
|
|
472
580
|
"timestamp": asyncio.get_event_loop().time(),
|
|
473
|
-
"workflow_id": workflow_id
|
|
581
|
+
"workflow_id": workflow_id,
|
|
474
582
|
}
|
|
475
583
|
|
|
476
|
-
await self.broadcast(
|
|
477
|
-
"type": "node_status",
|
|
478
|
-
|
|
479
|
-
"workflow_id": workflow_id,
|
|
480
|
-
"data": self._status["nodes"][node_id]
|
|
481
|
-
})
|
|
584
|
+
await self.broadcast(
|
|
585
|
+
{"type": "node_status", "node_id": node_id, "workflow_id": workflow_id, "data": self._status["nodes"][node_id]}
|
|
586
|
+
)
|
|
482
587
|
|
|
483
|
-
async def update_node_output(
|
|
484
|
-
self,
|
|
485
|
-
node_id: str,
|
|
486
|
-
output: Any,
|
|
487
|
-
workflow_id: Optional[str] = None
|
|
488
|
-
):
|
|
588
|
+
async def update_node_output(self, node_id: str, output: Any, workflow_id: Optional[str] = None):
|
|
489
589
|
"""Update a node's output data and broadcast."""
|
|
490
590
|
if node_id not in self._status["nodes"]:
|
|
491
591
|
self._status["nodes"][node_id] = {"status": "idle", "data": {}}
|
|
@@ -494,12 +594,7 @@ class StatusBroadcaster:
|
|
|
494
594
|
if workflow_id:
|
|
495
595
|
self._status["nodes"][node_id]["workflow_id"] = workflow_id
|
|
496
596
|
|
|
497
|
-
await self.broadcast({
|
|
498
|
-
"type": "node_output",
|
|
499
|
-
"node_id": node_id,
|
|
500
|
-
"workflow_id": workflow_id,
|
|
501
|
-
"output": output
|
|
502
|
-
})
|
|
597
|
+
await self.broadcast({"type": "node_output", "node_id": node_id, "workflow_id": workflow_id, "output": output})
|
|
503
598
|
|
|
504
599
|
# =========================================================================
|
|
505
600
|
# Variable Updates
|
|
@@ -509,20 +604,13 @@ class StatusBroadcaster:
|
|
|
509
604
|
"""Update a workflow variable and broadcast."""
|
|
510
605
|
self._status["variables"][name] = value
|
|
511
606
|
|
|
512
|
-
await self.broadcast({
|
|
513
|
-
"type": "variable_update",
|
|
514
|
-
"name": name,
|
|
515
|
-
"value": value
|
|
516
|
-
})
|
|
607
|
+
await self.broadcast({"type": "variable_update", "name": name, "value": value})
|
|
517
608
|
|
|
518
609
|
async def update_variables(self, variables: Dict[str, Any]):
|
|
519
610
|
"""Update multiple variables at once and broadcast."""
|
|
520
611
|
self._status["variables"].update(variables)
|
|
521
612
|
|
|
522
|
-
await self.broadcast({
|
|
523
|
-
"type": "variables_update",
|
|
524
|
-
"variables": variables
|
|
525
|
-
})
|
|
613
|
+
await self.broadcast({"type": "variables_update", "variables": variables})
|
|
526
614
|
|
|
527
615
|
# =========================================================================
|
|
528
616
|
# Workflow Status Updates
|
|
@@ -561,11 +649,13 @@ class StatusBroadcaster:
|
|
|
561
649
|
"progress": progress,
|
|
562
650
|
}
|
|
563
651
|
|
|
564
|
-
await self.broadcast(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
652
|
+
await self.broadcast(
|
|
653
|
+
{
|
|
654
|
+
"type": "workflow_status",
|
|
655
|
+
"workflow_id": workflow_id,
|
|
656
|
+
"data": payload,
|
|
657
|
+
}
|
|
658
|
+
)
|
|
569
659
|
|
|
570
660
|
async def workflow_run_started(self, workflow_id: Optional[str]) -> bool:
|
|
571
661
|
"""Mark a new active run for `workflow_id`.
|
|
@@ -583,7 +673,8 @@ class StatusBroadcaster:
|
|
|
583
673
|
went_active = prev == 0
|
|
584
674
|
if went_active:
|
|
585
675
|
await self.update_workflow_status(
|
|
586
|
-
executing=True,
|
|
676
|
+
executing=True,
|
|
677
|
+
workflow_id=workflow_id,
|
|
587
678
|
)
|
|
588
679
|
return went_active
|
|
589
680
|
|
|
@@ -620,7 +711,8 @@ class StatusBroadcaster:
|
|
|
620
711
|
went_idle = prev > 0 and new_count == 0
|
|
621
712
|
if went_idle:
|
|
622
713
|
await self.update_workflow_status(
|
|
623
|
-
executing=False,
|
|
714
|
+
executing=False,
|
|
715
|
+
workflow_id=workflow_id,
|
|
624
716
|
)
|
|
625
717
|
if clear_stuck_nodes:
|
|
626
718
|
# include_waiting=False (default) -- don't touch deployment
|
|
@@ -660,10 +752,9 @@ class StatusBroadcaster:
|
|
|
660
752
|
|
|
661
753
|
statuses = ("executing", "waiting") if include_waiting else ("executing",)
|
|
662
754
|
stuck = [
|
|
663
|
-
(nid, info)
|
|
664
|
-
|
|
665
|
-
and info.get("status") in statuses
|
|
666
|
-
and not is_node_in_active_delegation(nid)
|
|
755
|
+
(nid, info)
|
|
756
|
+
for nid, info in self._status["nodes"].items()
|
|
757
|
+
if info.get("workflow_id") == workflow_id and info.get("status") in statuses and not is_node_in_active_delegation(nid)
|
|
667
758
|
]
|
|
668
759
|
for node_id, _info in stuck:
|
|
669
760
|
try:
|
|
@@ -671,7 +762,8 @@ class StatusBroadcaster:
|
|
|
671
762
|
except Exception as e:
|
|
672
763
|
logger.warning(
|
|
673
764
|
"[StatusBroadcaster] Failed to clear stuck node %s: %s",
|
|
674
|
-
node_id,
|
|
765
|
+
node_id,
|
|
766
|
+
e,
|
|
675
767
|
)
|
|
676
768
|
return len(stuck)
|
|
677
769
|
|
|
@@ -694,7 +786,7 @@ class StatusBroadcaster:
|
|
|
694
786
|
active_runs: int = 0,
|
|
695
787
|
workflow_id: Optional[str] = None,
|
|
696
788
|
data: Optional[Dict[str, Any]] = None,
|
|
697
|
-
error: Optional[str] = None
|
|
789
|
+
error: Optional[str] = None,
|
|
698
790
|
):
|
|
699
791
|
"""Update deployment status and broadcast.
|
|
700
792
|
|
|
@@ -709,31 +801,16 @@ class StatusBroadcaster:
|
|
|
709
801
|
data: Optional additional data (e.g., run_id, trigger info)
|
|
710
802
|
error: Optional error message if status is 'error'
|
|
711
803
|
"""
|
|
712
|
-
self._status["deployment"] = {
|
|
713
|
-
"isRunning": is_running,
|
|
714
|
-
"activeRuns": active_runs,
|
|
715
|
-
"status": status,
|
|
716
|
-
"workflow_id": workflow_id
|
|
717
|
-
}
|
|
804
|
+
self._status["deployment"] = {"isRunning": is_running, "activeRuns": active_runs, "status": status, "workflow_id": workflow_id}
|
|
718
805
|
|
|
719
806
|
# Broadcast deployment_status message (matches frontend handler)
|
|
720
|
-
await self.broadcast({
|
|
721
|
-
"type": "deployment_status",
|
|
722
|
-
"status": status,
|
|
723
|
-
"workflow_id": workflow_id,
|
|
724
|
-
"data": data,
|
|
725
|
-
"error": error
|
|
726
|
-
})
|
|
807
|
+
await self.broadcast({"type": "deployment_status", "status": status, "workflow_id": workflow_id, "data": data, "error": error})
|
|
727
808
|
|
|
728
809
|
# =========================================================================
|
|
729
810
|
# Workflow Lock Management (Per-Workflow Locks - n8n pattern)
|
|
730
811
|
# =========================================================================
|
|
731
812
|
|
|
732
|
-
async def lock_workflow(
|
|
733
|
-
self,
|
|
734
|
-
workflow_id: str,
|
|
735
|
-
reason: str = "deployment"
|
|
736
|
-
) -> bool:
|
|
813
|
+
async def lock_workflow(self, workflow_id: str, reason: str = "deployment") -> bool:
|
|
737
814
|
"""Lock a specific workflow to prevent concurrent modifications.
|
|
738
815
|
|
|
739
816
|
Per-workflow locking (n8n pattern): Each workflow has its own independent lock.
|
|
@@ -756,29 +833,17 @@ class StatusBroadcaster:
|
|
|
756
833
|
if workflow_id in self._status["workflow_locks"]:
|
|
757
834
|
existing_lock = self._status["workflow_locks"][workflow_id]
|
|
758
835
|
if existing_lock.get("locked"):
|
|
759
|
-
logger.warning(
|
|
760
|
-
f"[WorkflowLock] Workflow {workflow_id} is already locked "
|
|
761
|
-
f"for {existing_lock.get('reason')}"
|
|
762
|
-
)
|
|
836
|
+
logger.warning(f"[WorkflowLock] Workflow {workflow_id} is already locked " f"for {existing_lock.get('reason')}")
|
|
763
837
|
return False
|
|
764
838
|
|
|
765
839
|
# Lock this specific workflow
|
|
766
|
-
lock_info = {
|
|
767
|
-
"locked": True,
|
|
768
|
-
"workflow_id": workflow_id,
|
|
769
|
-
"locked_at": time.time(),
|
|
770
|
-
"reason": reason
|
|
771
|
-
}
|
|
840
|
+
lock_info = {"locked": True, "workflow_id": workflow_id, "locked_at": time.time(), "reason": reason}
|
|
772
841
|
self._status["workflow_locks"][workflow_id] = lock_info
|
|
773
842
|
|
|
774
843
|
# Also update legacy single lock for backward compatibility
|
|
775
844
|
self._status["workflow_lock"] = lock_info.copy()
|
|
776
845
|
|
|
777
|
-
await self.broadcast({
|
|
778
|
-
"type": "workflow_lock",
|
|
779
|
-
"workflow_id": workflow_id,
|
|
780
|
-
"data": lock_info
|
|
781
|
-
})
|
|
846
|
+
await self.broadcast({"type": "workflow_lock", "workflow_id": workflow_id, "data": lock_info})
|
|
782
847
|
|
|
783
848
|
logger.info(f"[WorkflowLock] Locked workflow {workflow_id} for {reason}")
|
|
784
849
|
return True
|
|
@@ -811,23 +876,15 @@ class StatusBroadcaster:
|
|
|
811
876
|
|
|
812
877
|
# Update legacy single lock if it was for this workflow
|
|
813
878
|
if self._status["workflow_lock"].get("workflow_id") == workflow_id:
|
|
814
|
-
self._status["workflow_lock"] = {
|
|
815
|
-
"locked": False,
|
|
816
|
-
"workflow_id": None,
|
|
817
|
-
"locked_at": None,
|
|
818
|
-
"reason": None
|
|
819
|
-
}
|
|
879
|
+
self._status["workflow_lock"] = {"locked": False, "workflow_id": None, "locked_at": None, "reason": None}
|
|
820
880
|
|
|
821
|
-
await self.broadcast(
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
"data": {
|
|
825
|
-
"locked": False,
|
|
881
|
+
await self.broadcast(
|
|
882
|
+
{
|
|
883
|
+
"type": "workflow_lock",
|
|
826
884
|
"workflow_id": workflow_id,
|
|
827
|
-
"locked_at": None,
|
|
828
|
-
"reason": None
|
|
885
|
+
"data": {"locked": False, "workflow_id": workflow_id, "locked_at": None, "reason": None},
|
|
829
886
|
}
|
|
830
|
-
|
|
887
|
+
)
|
|
831
888
|
|
|
832
889
|
logger.info(f"[WorkflowLock] Unlocked workflow {workflow_id}")
|
|
833
890
|
return True
|
|
@@ -847,10 +904,7 @@ class StatusBroadcaster:
|
|
|
847
904
|
|
|
848
905
|
if workflow_id is None:
|
|
849
906
|
# Check if ANY workflow is locked
|
|
850
|
-
return any(
|
|
851
|
-
lock.get("locked", False)
|
|
852
|
-
for lock in self._status["workflow_locks"].values()
|
|
853
|
-
)
|
|
907
|
+
return any(lock.get("locked", False) for lock in self._status["workflow_locks"].values())
|
|
854
908
|
|
|
855
909
|
# Check specific workflow
|
|
856
910
|
lock = self._status["workflow_locks"].get(workflow_id, {})
|
|
@@ -875,7 +929,7 @@ class StatusBroadcaster:
|
|
|
875
929
|
"locked": lock.get("locked", False),
|
|
876
930
|
"workflow_id": workflow_id,
|
|
877
931
|
"locked_at": lock.get("locked_at"),
|
|
878
|
-
"reason": lock.get("reason")
|
|
932
|
+
"reason": lock.get("reason"),
|
|
879
933
|
}
|
|
880
934
|
|
|
881
935
|
# Return legacy single lock for backward compatibility
|
|
@@ -885,11 +939,7 @@ class StatusBroadcaster:
|
|
|
885
939
|
"""Get all active workflow locks."""
|
|
886
940
|
if "workflow_locks" not in self._status:
|
|
887
941
|
return {}
|
|
888
|
-
return {
|
|
889
|
-
wid: lock.copy()
|
|
890
|
-
for wid, lock in self._status["workflow_locks"].items()
|
|
891
|
-
if lock.get("locked")
|
|
892
|
-
}
|
|
942
|
+
return {wid: lock.copy() for wid, lock in self._status["workflow_locks"].items() if lock.get("locked")}
|
|
893
943
|
|
|
894
944
|
# =========================================================================
|
|
895
945
|
# Console Log Updates
|
|
@@ -922,16 +972,14 @@ class StatusBroadcaster:
|
|
|
922
972
|
# Save to database for persistence
|
|
923
973
|
try:
|
|
924
974
|
from core.container import container
|
|
975
|
+
|
|
925
976
|
database = container.database()
|
|
926
977
|
await database.add_console_log(log_data)
|
|
927
978
|
except Exception as e:
|
|
928
979
|
logger.warning(f"[StatusBroadcaster] Failed to persist console log: {e}")
|
|
929
980
|
|
|
930
981
|
# Broadcast to all clients
|
|
931
|
-
await self.broadcast({
|
|
932
|
-
"type": "console_log",
|
|
933
|
-
"data": log_data
|
|
934
|
-
})
|
|
982
|
+
await self.broadcast({"type": "console_log", "data": log_data})
|
|
935
983
|
|
|
936
984
|
logger.debug(f"[StatusBroadcaster] Console log broadcast: label={log_data.get('label')}")
|
|
937
985
|
|
|
@@ -941,10 +989,7 @@ class StatusBroadcaster:
|
|
|
941
989
|
return []
|
|
942
990
|
|
|
943
991
|
if workflow_id:
|
|
944
|
-
return [
|
|
945
|
-
log for log in self._status["console_logs"]
|
|
946
|
-
if log.get("workflow_id") == workflow_id
|
|
947
|
-
]
|
|
992
|
+
return [log for log in self._status["console_logs"] if log.get("workflow_id") == workflow_id]
|
|
948
993
|
return list(self._status["console_logs"])
|
|
949
994
|
|
|
950
995
|
async def clear_console_logs(self, workflow_id: Optional[str] = None):
|
|
@@ -954,17 +999,11 @@ class StatusBroadcaster:
|
|
|
954
999
|
return
|
|
955
1000
|
|
|
956
1001
|
if workflow_id:
|
|
957
|
-
self._status["console_logs"] = [
|
|
958
|
-
log for log in self._status["console_logs"]
|
|
959
|
-
if log.get("workflow_id") != workflow_id
|
|
960
|
-
]
|
|
1002
|
+
self._status["console_logs"] = [log for log in self._status["console_logs"] if log.get("workflow_id") != workflow_id]
|
|
961
1003
|
else:
|
|
962
1004
|
self._status["console_logs"] = []
|
|
963
1005
|
|
|
964
|
-
await self.broadcast({
|
|
965
|
-
"type": "console_logs_cleared",
|
|
966
|
-
"workflow_id": workflow_id
|
|
967
|
-
})
|
|
1006
|
+
await self.broadcast({"type": "console_logs_cleared", "workflow_id": workflow_id})
|
|
968
1007
|
|
|
969
1008
|
# =========================================================================
|
|
970
1009
|
# Terminal Log Updates
|
|
@@ -993,10 +1032,7 @@ class StatusBroadcaster:
|
|
|
993
1032
|
self._status["terminal_logs"] = self._status["terminal_logs"][-200:]
|
|
994
1033
|
|
|
995
1034
|
# Broadcast to all clients
|
|
996
|
-
await self.broadcast({
|
|
997
|
-
"type": "terminal_log",
|
|
998
|
-
"data": log_data
|
|
999
|
-
})
|
|
1035
|
+
await self.broadcast({"type": "terminal_log", "data": log_data})
|
|
1000
1036
|
|
|
1001
1037
|
def get_terminal_logs(self) -> List[Dict[str, Any]]:
|
|
1002
1038
|
"""Get terminal log history."""
|
|
@@ -1007,9 +1043,7 @@ class StatusBroadcaster:
|
|
|
1007
1043
|
async def clear_terminal_logs(self):
|
|
1008
1044
|
"""Clear terminal log history."""
|
|
1009
1045
|
self._status["terminal_logs"] = []
|
|
1010
|
-
await self.broadcast({
|
|
1011
|
-
"type": "terminal_logs_cleared"
|
|
1012
|
-
})
|
|
1046
|
+
await self.broadcast({"type": "terminal_logs_cleared"})
|
|
1013
1047
|
|
|
1014
1048
|
# =========================================================================
|
|
1015
1049
|
# Agent Team Updates
|
|
@@ -1023,12 +1057,7 @@ class StatusBroadcaster:
|
|
|
1023
1057
|
event_type: Event type (team_created, task_added, task_claimed, etc.)
|
|
1024
1058
|
data: Event data
|
|
1025
1059
|
"""
|
|
1026
|
-
await self.broadcast({
|
|
1027
|
-
"type": "team_event",
|
|
1028
|
-
"team_id": team_id,
|
|
1029
|
-
"event_type": event_type,
|
|
1030
|
-
"data": data
|
|
1031
|
-
})
|
|
1060
|
+
await self.broadcast({"type": "team_event", "team_id": team_id, "event_type": event_type, "data": data})
|
|
1032
1061
|
|
|
1033
1062
|
# =========================================================================
|
|
1034
1063
|
# Generic Updates
|
|
@@ -1042,15 +1071,13 @@ class StatusBroadcaster:
|
|
|
1042
1071
|
See DESIGN.md section "Cross-Thread Event Dispatch" for pattern details.
|
|
1043
1072
|
"""
|
|
1044
1073
|
# Broadcast to all WebSocket clients
|
|
1045
|
-
await self.broadcast({
|
|
1046
|
-
"type": event_type,
|
|
1047
|
-
"data": data
|
|
1048
|
-
})
|
|
1074
|
+
await self.broadcast({"type": event_type, "data": data})
|
|
1049
1075
|
|
|
1050
1076
|
# Dispatch to event waiters (for trigger nodes)
|
|
1051
1077
|
# Use dispatch_async directly - we're in async context
|
|
1052
1078
|
try:
|
|
1053
1079
|
from services import event_waiter
|
|
1080
|
+
|
|
1054
1081
|
event_data = data if isinstance(data, dict) else {"data": data}
|
|
1055
1082
|
resolved_count = await event_waiter.dispatch_async(event_type, event_data)
|
|
1056
1083
|
if resolved_count > 0:
|
|
@@ -1086,9 +1113,7 @@ class StatusBroadcaster:
|
|
|
1086
1113
|
clearing the visible state.
|
|
1087
1114
|
"""
|
|
1088
1115
|
had_status = node_id in self._status["nodes"]
|
|
1089
|
-
previous_workflow = (
|
|
1090
|
-
self._status["nodes"][node_id].get("workflow_id") if had_status else None
|
|
1091
|
-
)
|
|
1116
|
+
previous_workflow = self._status["nodes"][node_id].get("workflow_id") if had_status else None
|
|
1092
1117
|
self._status["nodes"][node_id] = {
|
|
1093
1118
|
"status": "idle",
|
|
1094
1119
|
"data": {},
|
|
@@ -1097,11 +1122,13 @@ class StatusBroadcaster:
|
|
|
1097
1122
|
"cleared": True,
|
|
1098
1123
|
}
|
|
1099
1124
|
logger.info(f"[StatusBroadcaster] Reset node status to idle: {node_id}")
|
|
1100
|
-
await self.broadcast(
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1125
|
+
await self.broadcast(
|
|
1126
|
+
{
|
|
1127
|
+
"type": "node_status_cleared",
|
|
1128
|
+
"node_id": node_id,
|
|
1129
|
+
"workflow_id": previous_workflow,
|
|
1130
|
+
}
|
|
1131
|
+
)
|
|
1105
1132
|
return had_status
|
|
1106
1133
|
|
|
1107
1134
|
def get_variable(self, name: str) -> Any:
|
|
@@ -1125,9 +1152,7 @@ class StatusBroadcaster:
|
|
|
1125
1152
|
|
|
1126
1153
|
import typing as _typing # local alias to avoid shadowing module-level name
|
|
1127
1154
|
|
|
1128
|
-
_ServiceRefreshCallback = _typing.Callable[
|
|
1129
|
-
["StatusBroadcaster"], _typing.Awaitable[None]
|
|
1130
|
-
]
|
|
1155
|
+
_ServiceRefreshCallback = _typing.Callable[["StatusBroadcaster"], _typing.Awaitable[None]]
|
|
1131
1156
|
_SERVICE_REFRESH_CALLBACKS: _typing.List[_ServiceRefreshCallback] = []
|
|
1132
1157
|
|
|
1133
1158
|
from services.plugin.registry import IdempotentList as _IdempotentList # noqa: E402
|
|
@@ -1135,9 +1160,7 @@ from services.plugin.registry import IdempotentList as _IdempotentList # noqa:
|
|
|
1135
1160
|
# Backed by the module-level _SERVICE_REFRESH_CALLBACKS list so the
|
|
1136
1161
|
# existing iterator at line 153 (``for callback in list(_SERVICE_REFRESH_CALLBACKS)``)
|
|
1137
1162
|
# keeps working unchanged.
|
|
1138
|
-
_SERVICE_REFRESH_FANOUT: _IdempotentList[_ServiceRefreshCallback] = _IdempotentList(
|
|
1139
|
-
"service_refresh", items=_SERVICE_REFRESH_CALLBACKS
|
|
1140
|
-
)
|
|
1163
|
+
_SERVICE_REFRESH_FANOUT: _IdempotentList[_ServiceRefreshCallback] = _IdempotentList("service_refresh", items=_SERVICE_REFRESH_CALLBACKS)
|
|
1141
1164
|
|
|
1142
1165
|
|
|
1143
1166
|
def register_service_refresh(callback: _ServiceRefreshCallback) -> None:
|