machinaos 0.0.1
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 +71 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/cli.js +159 -0
- package/client/.dockerignore +45 -0
- package/client/Dockerfile +68 -0
- package/client/eslint.config.js +29 -0
- package/client/index.html +13 -0
- package/client/nginx.conf +66 -0
- package/client/package.json +48 -0
- package/client/src/App.tsx +27 -0
- package/client/src/Dashboard.tsx +1173 -0
- package/client/src/ParameterPanel.tsx +301 -0
- package/client/src/components/AIAgentNode.tsx +321 -0
- package/client/src/components/APIKeyValidator.tsx +118 -0
- package/client/src/components/ClaudeChatModelNode.tsx +18 -0
- package/client/src/components/ConditionalEdge.tsx +189 -0
- package/client/src/components/CredentialsModal.tsx +306 -0
- package/client/src/components/EdgeConditionEditor.tsx +443 -0
- package/client/src/components/GeminiChatModelNode.tsx +18 -0
- package/client/src/components/GenericNode.tsx +357 -0
- package/client/src/components/LocationParameterPanel.tsx +154 -0
- package/client/src/components/ModelNode.tsx +286 -0
- package/client/src/components/OpenAIChatModelNode.tsx +18 -0
- package/client/src/components/OutputPanel.tsx +471 -0
- package/client/src/components/ParameterRenderer.tsx +1874 -0
- package/client/src/components/SkillEditorModal.tsx +417 -0
- package/client/src/components/SquareNode.tsx +797 -0
- package/client/src/components/StartNode.tsx +250 -0
- package/client/src/components/ToolkitNode.tsx +365 -0
- package/client/src/components/TriggerNode.tsx +463 -0
- package/client/src/components/auth/LoginPage.tsx +247 -0
- package/client/src/components/auth/ProtectedRoute.tsx +59 -0
- package/client/src/components/base/BaseChatModelNode.tsx +271 -0
- package/client/src/components/icons/AIProviderIcons.tsx +50 -0
- package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
- package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
- package/client/src/components/maps/index.ts +26 -0
- package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
- package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
- package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
- package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
- package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
- package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
- package/client/src/components/parameterPanel/index.ts +42 -0
- package/client/src/components/shared/DataPanel.tsx +142 -0
- package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
- package/client/src/components/ui/AIResultModal.tsx +204 -0
- package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
- package/client/src/components/ui/CodeEditor.tsx +81 -0
- package/client/src/components/ui/CollapsibleSection.tsx +88 -0
- package/client/src/components/ui/ComponentItem.tsx +154 -0
- package/client/src/components/ui/ComponentPalette.tsx +321 -0
- package/client/src/components/ui/ConsolePanel.tsx +1074 -0
- package/client/src/components/ui/ErrorBoundary.tsx +196 -0
- package/client/src/components/ui/InputNodesPanel.tsx +204 -0
- package/client/src/components/ui/MapSelector.tsx +314 -0
- package/client/src/components/ui/Modal.tsx +149 -0
- package/client/src/components/ui/NodeContextMenu.tsx +192 -0
- package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
- package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
- package/client/src/components/ui/SettingsPanel.tsx +243 -0
- package/client/src/components/ui/TopToolbar.tsx +736 -0
- package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
- package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
- package/client/src/config/antdTheme.ts +186 -0
- package/client/src/config/api.ts +54 -0
- package/client/src/contexts/AuthContext.tsx +221 -0
- package/client/src/contexts/ThemeContext.tsx +42 -0
- package/client/src/contexts/WebSocketContext.tsx +1971 -0
- package/client/src/factories/baseChatModelFactory.ts +256 -0
- package/client/src/hooks/useAndroidOperations.ts +164 -0
- package/client/src/hooks/useApiKeyValidation.ts +107 -0
- package/client/src/hooks/useApiKeys.ts +238 -0
- package/client/src/hooks/useAppTheme.ts +17 -0
- package/client/src/hooks/useComponentPalette.ts +51 -0
- package/client/src/hooks/useCopyPaste.ts +155 -0
- package/client/src/hooks/useDragAndDrop.ts +124 -0
- package/client/src/hooks/useDragVariable.ts +88 -0
- package/client/src/hooks/useExecution.ts +313 -0
- package/client/src/hooks/useParameterPanel.ts +176 -0
- package/client/src/hooks/useReactFlowNodes.ts +189 -0
- package/client/src/hooks/useToolSchema.ts +209 -0
- package/client/src/hooks/useWhatsApp.ts +196 -0
- package/client/src/hooks/useWorkflowManagement.ts +46 -0
- package/client/src/index.css +315 -0
- package/client/src/main.tsx +19 -0
- package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
- package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
- package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
- package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
- package/client/src/nodeDefinitions/chatNodes.ts +135 -0
- package/client/src/nodeDefinitions/codeNodes.ts +54 -0
- package/client/src/nodeDefinitions/documentNodes.ts +379 -0
- package/client/src/nodeDefinitions/index.ts +15 -0
- package/client/src/nodeDefinitions/locationNodes.ts +463 -0
- package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
- package/client/src/nodeDefinitions/skillNodes.ts +211 -0
- package/client/src/nodeDefinitions/toolNodes.ts +198 -0
- package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
- package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
- package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
- package/client/src/nodeDefinitions.ts +104 -0
- package/client/src/schemas/workflowSchema.ts +264 -0
- package/client/src/services/dynamicParameterService.ts +96 -0
- package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
- package/client/src/services/executionService.ts +232 -0
- package/client/src/services/workflowApi.ts +91 -0
- package/client/src/store/useAppStore.ts +582 -0
- package/client/src/styles/theme.ts +508 -0
- package/client/src/styles/zIndex.ts +17 -0
- package/client/src/types/ComponentTypes.ts +39 -0
- package/client/src/types/EdgeCondition.ts +231 -0
- package/client/src/types/INodeProperties.ts +288 -0
- package/client/src/types/NodeTypes.ts +28 -0
- package/client/src/utils/formatters.ts +33 -0
- package/client/src/utils/googleMapsLoader.ts +140 -0
- package/client/src/utils/locationUtils.ts +85 -0
- package/client/src/utils/nodeUtils.ts +31 -0
- package/client/src/utils/workflow.ts +30 -0
- package/client/src/utils/workflowExport.ts +120 -0
- package/client/src/vite-env.d.ts +12 -0
- package/client/tailwind.config.js +60 -0
- package/client/tsconfig.json +25 -0
- package/client/tsconfig.node.json +11 -0
- package/client/vite.config.js +35 -0
- package/docker-compose.prod.yml +107 -0
- package/docker-compose.yml +104 -0
- package/docs-MachinaOs/README.md +85 -0
- package/docs-MachinaOs/deployment/docker.mdx +228 -0
- package/docs-MachinaOs/deployment/production.mdx +345 -0
- package/docs-MachinaOs/docs.json +75 -0
- package/docs-MachinaOs/faq.mdx +309 -0
- package/docs-MachinaOs/favicon.svg +5 -0
- package/docs-MachinaOs/installation.mdx +160 -0
- package/docs-MachinaOs/introduction.mdx +114 -0
- package/docs-MachinaOs/logo/dark.svg +6 -0
- package/docs-MachinaOs/logo/light.svg +6 -0
- package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
- package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
- package/docs-MachinaOs/nodes/android.mdx +411 -0
- package/docs-MachinaOs/nodes/overview.mdx +181 -0
- package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
- package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
- package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
- package/docs-MachinaOs/quickstart.mdx +119 -0
- package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
- package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
- package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
- package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
- package/nul +0 -0
- package/package.json +70 -0
- package/scripts/build.js +158 -0
- package/scripts/check-ports.ps1 +33 -0
- package/scripts/clean.js +40 -0
- package/scripts/docker.js +93 -0
- package/scripts/kill-port.ps1 +154 -0
- package/scripts/start.js +210 -0
- package/scripts/stop.js +325 -0
- package/server/.dockerignore +44 -0
- package/server/Dockerfile +45 -0
- package/server/constants.py +249 -0
- package/server/core/__init__.py +1 -0
- package/server/core/cache.py +461 -0
- package/server/core/config.py +128 -0
- package/server/core/container.py +99 -0
- package/server/core/database.py +1211 -0
- package/server/core/logging.py +314 -0
- package/server/main.py +289 -0
- package/server/middleware/__init__.py +5 -0
- package/server/middleware/auth.py +89 -0
- package/server/models/__init__.py +1 -0
- package/server/models/auth.py +52 -0
- package/server/models/cache.py +24 -0
- package/server/models/database.py +211 -0
- package/server/models/nodes.py +455 -0
- package/server/package.json +9 -0
- package/server/pyproject.toml +72 -0
- package/server/requirements.txt +83 -0
- package/server/routers/__init__.py +1 -0
- package/server/routers/android.py +294 -0
- package/server/routers/auth.py +203 -0
- package/server/routers/database.py +151 -0
- package/server/routers/maps.py +142 -0
- package/server/routers/nodejs_compat.py +289 -0
- package/server/routers/webhook.py +90 -0
- package/server/routers/websocket.py +2127 -0
- package/server/routers/whatsapp.py +761 -0
- package/server/routers/workflow.py +200 -0
- package/server/services/__init__.py +1 -0
- package/server/services/ai.py +2415 -0
- package/server/services/android/__init__.py +27 -0
- package/server/services/android/broadcaster.py +114 -0
- package/server/services/android/client.py +608 -0
- package/server/services/android/manager.py +78 -0
- package/server/services/android/protocol.py +165 -0
- package/server/services/android_service.py +588 -0
- package/server/services/auth.py +131 -0
- package/server/services/chat_client.py +160 -0
- package/server/services/deployment/__init__.py +12 -0
- package/server/services/deployment/manager.py +706 -0
- package/server/services/deployment/state.py +47 -0
- package/server/services/deployment/triggers.py +275 -0
- package/server/services/event_waiter.py +785 -0
- package/server/services/execution/__init__.py +77 -0
- package/server/services/execution/cache.py +769 -0
- package/server/services/execution/conditions.py +373 -0
- package/server/services/execution/dlq.py +132 -0
- package/server/services/execution/executor.py +1351 -0
- package/server/services/execution/models.py +531 -0
- package/server/services/execution/recovery.py +235 -0
- package/server/services/handlers/__init__.py +126 -0
- package/server/services/handlers/ai.py +355 -0
- package/server/services/handlers/android.py +260 -0
- package/server/services/handlers/code.py +278 -0
- package/server/services/handlers/document.py +598 -0
- package/server/services/handlers/http.py +193 -0
- package/server/services/handlers/polyglot.py +105 -0
- package/server/services/handlers/tools.py +845 -0
- package/server/services/handlers/triggers.py +107 -0
- package/server/services/handlers/utility.py +822 -0
- package/server/services/handlers/whatsapp.py +476 -0
- package/server/services/maps.py +289 -0
- package/server/services/memory_store.py +103 -0
- package/server/services/node_executor.py +375 -0
- package/server/services/parameter_resolver.py +218 -0
- package/server/services/polyglot_client.py +169 -0
- package/server/services/scheduler.py +155 -0
- package/server/services/skill_loader.py +417 -0
- package/server/services/status_broadcaster.py +826 -0
- package/server/services/temporal/__init__.py +23 -0
- package/server/services/temporal/activities.py +344 -0
- package/server/services/temporal/client.py +76 -0
- package/server/services/temporal/executor.py +147 -0
- package/server/services/temporal/worker.py +251 -0
- package/server/services/temporal/workflow.py +355 -0
- package/server/services/temporal/ws_client.py +236 -0
- package/server/services/text.py +111 -0
- package/server/services/user_auth.py +172 -0
- package/server/services/websocket_client.py +29 -0
- package/server/services/workflow.py +597 -0
- package/server/skills/android-skill/SKILL.md +82 -0
- package/server/skills/assistant-personality/SKILL.md +45 -0
- package/server/skills/code-skill/SKILL.md +140 -0
- package/server/skills/http-skill/SKILL.md +161 -0
- package/server/skills/maps-skill/SKILL.md +170 -0
- package/server/skills/memory-skill/SKILL.md +154 -0
- package/server/skills/scheduler-skill/SKILL.md +84 -0
- package/server/skills/whatsapp-skill/SKILL.md +283 -0
- package/server/uv.lock +2916 -0
- package/server/whatsapp-rpc/.dockerignore +30 -0
- package/server/whatsapp-rpc/Dockerfile +44 -0
- package/server/whatsapp-rpc/Dockerfile.web +17 -0
- package/server/whatsapp-rpc/README.md +139 -0
- package/server/whatsapp-rpc/cli.js +95 -0
- package/server/whatsapp-rpc/configs/config.yaml +7 -0
- package/server/whatsapp-rpc/docker-compose.yml +35 -0
- package/server/whatsapp-rpc/docs/API.md +410 -0
- package/server/whatsapp-rpc/go.mod +67 -0
- package/server/whatsapp-rpc/go.sum +203 -0
- package/server/whatsapp-rpc/package.json +30 -0
- package/server/whatsapp-rpc/schema.json +1294 -0
- package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
- package/server/whatsapp-rpc/scripts/cli.js +162 -0
- package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
- package/server/whatsapp-rpc/src/go/config/config.go +49 -0
- package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
- package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
- package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
- package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
- package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
- package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
- package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
- package/server/whatsapp-rpc/web/app.py +609 -0
- package/server/whatsapp-rpc/web/requirements.txt +6 -0
- package/server/whatsapp-rpc/web/rpc_client.py +427 -0
- package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
- package/server/whatsapp-rpc/web/templates/base.html +150 -0
- package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
- package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
- package/server/whatsapp-rpc/web/templates/groups.html +328 -0
- package/server/whatsapp-rpc/web/templates/messages.html +465 -0
- package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
- package/server/whatsapp-rpc/web/templates/send.html +259 -0
- package/server/whatsapp-rpc/web/templates/settings.html +459 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Deployment State - Immutable state snapshot for event-driven deployment."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Dict, Any, List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class DeploymentState:
|
|
10
|
+
"""Immutable deployment state."""
|
|
11
|
+
deployment_id: str
|
|
12
|
+
workflow_id: str
|
|
13
|
+
is_running: bool
|
|
14
|
+
nodes: List[Dict]
|
|
15
|
+
edges: List[Dict]
|
|
16
|
+
session_id: str
|
|
17
|
+
settings: Dict[str, Any] = field(default_factory=dict)
|
|
18
|
+
deployed_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
19
|
+
|
|
20
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
21
|
+
return {
|
|
22
|
+
"deployment_id": self.deployment_id,
|
|
23
|
+
"workflow_id": self.workflow_id,
|
|
24
|
+
"is_running": self.is_running,
|
|
25
|
+
"session_id": self.session_id,
|
|
26
|
+
"settings": self.settings,
|
|
27
|
+
"deployed_at": self.deployed_at,
|
|
28
|
+
"node_count": len(self.nodes),
|
|
29
|
+
"edge_count": len(self.edges),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class TriggerInfo:
|
|
35
|
+
"""Info about a registered trigger."""
|
|
36
|
+
node_id: str
|
|
37
|
+
node_type: str
|
|
38
|
+
job_id: Optional[str] = None # For cron triggers
|
|
39
|
+
fired: bool = False # For start triggers
|
|
40
|
+
|
|
41
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
42
|
+
d = {"type": self.node_type, "node_id": self.node_id}
|
|
43
|
+
if self.job_id:
|
|
44
|
+
d["job_id"] = self.job_id
|
|
45
|
+
if self.fired:
|
|
46
|
+
d["fired"] = True
|
|
47
|
+
return d
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Trigger Management - Setup and teardown of workflow triggers.
|
|
2
|
+
|
|
3
|
+
Handles cron scheduling, event-based triggers (webhook, whatsapp), and cleanup.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Dict, Any, Callable, Optional, Set
|
|
9
|
+
|
|
10
|
+
from core.logging import get_logger
|
|
11
|
+
from constants import WORKFLOW_TRIGGER_TYPES
|
|
12
|
+
from services import event_waiter
|
|
13
|
+
from services import scheduler as cron_scheduler
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TriggerManager:
|
|
19
|
+
"""Manages workflow trigger lifecycle."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, main_loop: Optional[asyncio.AbstractEventLoop] = None):
|
|
22
|
+
self._active_cron_jobs: Dict[str, str] = {} # node_id -> job_id
|
|
23
|
+
self._active_listeners: Dict[str, asyncio.Task] = {} # node_id -> task
|
|
24
|
+
self._main_loop = main_loop
|
|
25
|
+
self._is_running = False
|
|
26
|
+
|
|
27
|
+
def set_running(self, running: bool):
|
|
28
|
+
self._is_running = running
|
|
29
|
+
|
|
30
|
+
def set_main_loop(self, loop: asyncio.AbstractEventLoop):
|
|
31
|
+
self._main_loop = loop
|
|
32
|
+
|
|
33
|
+
# =========================================================================
|
|
34
|
+
# CRON TRIGGERS
|
|
35
|
+
# =========================================================================
|
|
36
|
+
|
|
37
|
+
def setup_cron(self, node_id: str, cron_expr: str, timezone: str,
|
|
38
|
+
on_tick: Callable[[], None]) -> str:
|
|
39
|
+
"""Setup a cron trigger that calls on_tick on schedule."""
|
|
40
|
+
job_id = f"cron_{node_id}"
|
|
41
|
+
|
|
42
|
+
def tick_callback():
|
|
43
|
+
if not self._is_running:
|
|
44
|
+
return
|
|
45
|
+
on_tick()
|
|
46
|
+
|
|
47
|
+
cron_scheduler.register_cron_job(
|
|
48
|
+
job_id=job_id,
|
|
49
|
+
cron_expression=cron_expr,
|
|
50
|
+
callback=tick_callback,
|
|
51
|
+
timezone=timezone
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self._active_cron_jobs[node_id] = job_id
|
|
55
|
+
logger.info("Cron trigger setup", job_id=job_id, expr=cron_expr)
|
|
56
|
+
return job_id
|
|
57
|
+
|
|
58
|
+
def teardown_cron(self, node_id: str) -> bool:
|
|
59
|
+
"""Remove a specific cron trigger."""
|
|
60
|
+
job_id = self._active_cron_jobs.pop(node_id, None)
|
|
61
|
+
if job_id:
|
|
62
|
+
cron_scheduler.remove_cron_job(job_id)
|
|
63
|
+
logger.debug("Cron trigger removed", job_id=job_id)
|
|
64
|
+
return True
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def get_cron_node_ids(self) -> list:
|
|
68
|
+
"""Get node IDs of active cron triggers."""
|
|
69
|
+
return list(self._active_cron_jobs.keys())
|
|
70
|
+
|
|
71
|
+
def teardown_all_crons(self) -> int:
|
|
72
|
+
"""Remove all cron triggers."""
|
|
73
|
+
count = 0
|
|
74
|
+
for node_id, job_id in list(self._active_cron_jobs.items()):
|
|
75
|
+
cron_scheduler.remove_cron_job(job_id)
|
|
76
|
+
count += 1
|
|
77
|
+
self._active_cron_jobs.clear()
|
|
78
|
+
logger.info("All cron triggers removed", count=count)
|
|
79
|
+
return count
|
|
80
|
+
|
|
81
|
+
# =========================================================================
|
|
82
|
+
# EVENT TRIGGERS (Webhook, WhatsApp, etc.)
|
|
83
|
+
# =========================================================================
|
|
84
|
+
|
|
85
|
+
async def setup_event_trigger(self, node_id: str, node_type: str,
|
|
86
|
+
parameters: Dict[str, Any],
|
|
87
|
+
on_event: Callable[[Dict], Any],
|
|
88
|
+
broadcaster: Any,
|
|
89
|
+
workflow_id: Optional[str] = None) -> None:
|
|
90
|
+
"""Setup an event-based trigger with queue-based sequential processing.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
node_id: The trigger node ID
|
|
94
|
+
node_type: Type of trigger (whatsappReceive, webhookTrigger, etc.)
|
|
95
|
+
parameters: Node parameters for filtering
|
|
96
|
+
on_event: Callback when event is received
|
|
97
|
+
broadcaster: Status broadcaster for real-time updates
|
|
98
|
+
workflow_id: Workflow ID for scoped status updates (n8n pattern)
|
|
99
|
+
"""
|
|
100
|
+
event_queue: asyncio.Queue = asyncio.Queue()
|
|
101
|
+
is_executing = False
|
|
102
|
+
|
|
103
|
+
async def collector():
|
|
104
|
+
"""Continuously collect events into queue."""
|
|
105
|
+
while self._is_running:
|
|
106
|
+
try:
|
|
107
|
+
waiter = await event_waiter.register(node_type, node_id, parameters)
|
|
108
|
+
config = event_waiter.get_trigger_config(node_type)
|
|
109
|
+
|
|
110
|
+
if config and not is_executing:
|
|
111
|
+
queue_size = event_queue.qsize()
|
|
112
|
+
msg = f"Waiting for {config.display_name}..."
|
|
113
|
+
if queue_size > 0:
|
|
114
|
+
msg = f"Waiting... ({queue_size} queued)"
|
|
115
|
+
await broadcaster.update_node_status(node_id, "waiting", {
|
|
116
|
+
"message": msg,
|
|
117
|
+
"event_type": config.event_type,
|
|
118
|
+
"waiter_id": waiter.id,
|
|
119
|
+
"queue_size": queue_size
|
|
120
|
+
}, workflow_id=workflow_id)
|
|
121
|
+
|
|
122
|
+
event_data = await event_waiter.wait_for_event(waiter)
|
|
123
|
+
if self._is_running:
|
|
124
|
+
await event_queue.put(event_data)
|
|
125
|
+
|
|
126
|
+
except asyncio.CancelledError:
|
|
127
|
+
break
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error("Trigger collector error", node_id=node_id, error=str(e))
|
|
130
|
+
if self._is_running:
|
|
131
|
+
await asyncio.sleep(1)
|
|
132
|
+
|
|
133
|
+
async def processor():
|
|
134
|
+
"""Process events from queue sequentially."""
|
|
135
|
+
nonlocal is_executing
|
|
136
|
+
while self._is_running:
|
|
137
|
+
try:
|
|
138
|
+
try:
|
|
139
|
+
event_data = await asyncio.wait_for(event_queue.get(), timeout=1.0)
|
|
140
|
+
except asyncio.TimeoutError:
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
is_executing = True
|
|
144
|
+
config = event_waiter.get_trigger_config(node_type)
|
|
145
|
+
|
|
146
|
+
# Clear waiting indicator during execution
|
|
147
|
+
await broadcaster.update_node_status(node_id, "idle", {
|
|
148
|
+
"message": "Graph executing...",
|
|
149
|
+
"is_processing": True
|
|
150
|
+
}, workflow_id=workflow_id)
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
await on_event(event_data)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.error("Trigger execution error", node_id=node_id, error=str(e))
|
|
156
|
+
|
|
157
|
+
is_executing = False
|
|
158
|
+
|
|
159
|
+
# Return to waiting state
|
|
160
|
+
queue_size = event_queue.qsize()
|
|
161
|
+
name = config.display_name if config else node_type
|
|
162
|
+
msg = f"Waiting for {name}..." if queue_size == 0 else f"Processing next... ({queue_size} queued)"
|
|
163
|
+
await broadcaster.update_node_status(node_id, "waiting", {
|
|
164
|
+
"message": msg,
|
|
165
|
+
"queue_size": queue_size,
|
|
166
|
+
"is_processing": False
|
|
167
|
+
}, workflow_id=workflow_id)
|
|
168
|
+
|
|
169
|
+
except asyncio.CancelledError:
|
|
170
|
+
break
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.error("Trigger processor error", node_id=node_id, error=str(e))
|
|
173
|
+
is_executing = False
|
|
174
|
+
|
|
175
|
+
async def combined():
|
|
176
|
+
collector_task = asyncio.create_task(collector())
|
|
177
|
+
processor_task = asyncio.create_task(processor())
|
|
178
|
+
try:
|
|
179
|
+
await asyncio.gather(collector_task, processor_task)
|
|
180
|
+
except asyncio.CancelledError:
|
|
181
|
+
collector_task.cancel()
|
|
182
|
+
processor_task.cancel()
|
|
183
|
+
await asyncio.gather(collector_task, processor_task, return_exceptions=True)
|
|
184
|
+
|
|
185
|
+
task = asyncio.create_task(combined())
|
|
186
|
+
self._active_listeners[node_id] = task
|
|
187
|
+
|
|
188
|
+
async def teardown_all_listeners(self) -> int:
|
|
189
|
+
"""Cancel all event listeners."""
|
|
190
|
+
count = 0
|
|
191
|
+
for node_id, task in list(self._active_listeners.items()):
|
|
192
|
+
if not task.done():
|
|
193
|
+
task.cancel()
|
|
194
|
+
count += 1
|
|
195
|
+
|
|
196
|
+
if self._active_listeners:
|
|
197
|
+
await asyncio.gather(*self._active_listeners.values(), return_exceptions=True)
|
|
198
|
+
|
|
199
|
+
self._active_listeners.clear()
|
|
200
|
+
return count
|
|
201
|
+
|
|
202
|
+
def get_listener_node_ids(self) -> list:
|
|
203
|
+
"""Get node IDs of active listeners."""
|
|
204
|
+
return list(self._active_listeners.keys())
|
|
205
|
+
|
|
206
|
+
# =========================================================================
|
|
207
|
+
# UTILITY METHODS
|
|
208
|
+
# =========================================================================
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def build_cron_expression(parameters: Dict[str, Any]) -> Optional[str]:
|
|
212
|
+
"""Build cron expression from user-friendly parameters."""
|
|
213
|
+
frequency = parameters.get('frequency', 'minutes')
|
|
214
|
+
|
|
215
|
+
second, minute, hour = '0', '*/5', '*'
|
|
216
|
+
day, month, weekday = '*', '*', '*'
|
|
217
|
+
|
|
218
|
+
if frequency == 'seconds':
|
|
219
|
+
interval = str(parameters.get('interval', 30))
|
|
220
|
+
second, minute = f'*/{interval}', '*'
|
|
221
|
+
|
|
222
|
+
elif frequency == 'minutes':
|
|
223
|
+
interval = str(parameters.get('intervalMinutes', 5))
|
|
224
|
+
minute = f'*/{interval}' if interval != '1' else '*'
|
|
225
|
+
|
|
226
|
+
elif frequency == 'hours':
|
|
227
|
+
interval = str(parameters.get('intervalHours', 1))
|
|
228
|
+
minute = '0'
|
|
229
|
+
hour = f'*/{interval}' if interval != '1' else '*'
|
|
230
|
+
|
|
231
|
+
elif frequency == 'days':
|
|
232
|
+
time_str = parameters.get('dailyTime', '09:00')
|
|
233
|
+
parts = time_str.split(':')
|
|
234
|
+
hour = parts[0] if parts else '9'
|
|
235
|
+
minute = parts[1] if len(parts) > 1 else '0'
|
|
236
|
+
|
|
237
|
+
elif frequency == 'weeks':
|
|
238
|
+
time_str = parameters.get('weeklyTime', '09:00')
|
|
239
|
+
parts = time_str.split(':')
|
|
240
|
+
hour = parts[0] if parts else '9'
|
|
241
|
+
minute = parts[1] if len(parts) > 1 else '0'
|
|
242
|
+
weekday = parameters.get('weekday', '1')
|
|
243
|
+
|
|
244
|
+
elif frequency == 'months':
|
|
245
|
+
time_str = parameters.get('monthlyTime', '09:00')
|
|
246
|
+
parts = time_str.split(':')
|
|
247
|
+
hour = parts[0] if parts else '9'
|
|
248
|
+
minute = parts[1] if len(parts) > 1 else '0'
|
|
249
|
+
day = parameters.get('monthDay', '1')
|
|
250
|
+
|
|
251
|
+
elif frequency == 'once':
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
return f'{second} {minute} {hour} {day} {month} {weekday}'
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
def find_trigger_nodes(nodes: list, edges: list) -> tuple:
|
|
258
|
+
"""Find trigger nodes, split into start nodes and event triggers."""
|
|
259
|
+
# Nodes with incoming edges
|
|
260
|
+
nodes_with_inputs = {e.get('target') for e in edges if e.get('target')}
|
|
261
|
+
|
|
262
|
+
trigger_types_no_cron = WORKFLOW_TRIGGER_TYPES - {'cronScheduler'}
|
|
263
|
+
triggers = [n for n in nodes
|
|
264
|
+
if n.get('type') in trigger_types_no_cron
|
|
265
|
+
and n.get('id') not in nodes_with_inputs]
|
|
266
|
+
|
|
267
|
+
start_nodes = [n for n in triggers if n.get('type') == 'start']
|
|
268
|
+
event_triggers = [n for n in triggers if n.get('type') != 'start']
|
|
269
|
+
|
|
270
|
+
return start_nodes, event_triggers
|
|
271
|
+
|
|
272
|
+
@staticmethod
|
|
273
|
+
def find_cron_nodes(nodes: list) -> list:
|
|
274
|
+
"""Find all cron scheduler nodes."""
|
|
275
|
+
return [n for n in nodes if n.get('type') == 'cronScheduler']
|