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.
Files changed (288) hide show
  1. package/.env.template +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/bin/cli.js +159 -0
  5. package/client/.dockerignore +45 -0
  6. package/client/Dockerfile +68 -0
  7. package/client/eslint.config.js +29 -0
  8. package/client/index.html +13 -0
  9. package/client/nginx.conf +66 -0
  10. package/client/package.json +48 -0
  11. package/client/src/App.tsx +27 -0
  12. package/client/src/Dashboard.tsx +1173 -0
  13. package/client/src/ParameterPanel.tsx +301 -0
  14. package/client/src/components/AIAgentNode.tsx +321 -0
  15. package/client/src/components/APIKeyValidator.tsx +118 -0
  16. package/client/src/components/ClaudeChatModelNode.tsx +18 -0
  17. package/client/src/components/ConditionalEdge.tsx +189 -0
  18. package/client/src/components/CredentialsModal.tsx +306 -0
  19. package/client/src/components/EdgeConditionEditor.tsx +443 -0
  20. package/client/src/components/GeminiChatModelNode.tsx +18 -0
  21. package/client/src/components/GenericNode.tsx +357 -0
  22. package/client/src/components/LocationParameterPanel.tsx +154 -0
  23. package/client/src/components/ModelNode.tsx +286 -0
  24. package/client/src/components/OpenAIChatModelNode.tsx +18 -0
  25. package/client/src/components/OutputPanel.tsx +471 -0
  26. package/client/src/components/ParameterRenderer.tsx +1874 -0
  27. package/client/src/components/SkillEditorModal.tsx +417 -0
  28. package/client/src/components/SquareNode.tsx +797 -0
  29. package/client/src/components/StartNode.tsx +250 -0
  30. package/client/src/components/ToolkitNode.tsx +365 -0
  31. package/client/src/components/TriggerNode.tsx +463 -0
  32. package/client/src/components/auth/LoginPage.tsx +247 -0
  33. package/client/src/components/auth/ProtectedRoute.tsx +59 -0
  34. package/client/src/components/base/BaseChatModelNode.tsx +271 -0
  35. package/client/src/components/icons/AIProviderIcons.tsx +50 -0
  36. package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
  37. package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
  38. package/client/src/components/maps/index.ts +26 -0
  39. package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
  40. package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
  41. package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
  42. package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
  43. package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
  44. package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
  45. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
  46. package/client/src/components/parameterPanel/index.ts +42 -0
  47. package/client/src/components/shared/DataPanel.tsx +142 -0
  48. package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
  49. package/client/src/components/ui/AIResultModal.tsx +204 -0
  50. package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
  51. package/client/src/components/ui/CodeEditor.tsx +81 -0
  52. package/client/src/components/ui/CollapsibleSection.tsx +88 -0
  53. package/client/src/components/ui/ComponentItem.tsx +154 -0
  54. package/client/src/components/ui/ComponentPalette.tsx +321 -0
  55. package/client/src/components/ui/ConsolePanel.tsx +1074 -0
  56. package/client/src/components/ui/ErrorBoundary.tsx +196 -0
  57. package/client/src/components/ui/InputNodesPanel.tsx +204 -0
  58. package/client/src/components/ui/MapSelector.tsx +314 -0
  59. package/client/src/components/ui/Modal.tsx +149 -0
  60. package/client/src/components/ui/NodeContextMenu.tsx +192 -0
  61. package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
  62. package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
  63. package/client/src/components/ui/SettingsPanel.tsx +243 -0
  64. package/client/src/components/ui/TopToolbar.tsx +736 -0
  65. package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
  66. package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
  67. package/client/src/config/antdTheme.ts +186 -0
  68. package/client/src/config/api.ts +54 -0
  69. package/client/src/contexts/AuthContext.tsx +221 -0
  70. package/client/src/contexts/ThemeContext.tsx +42 -0
  71. package/client/src/contexts/WebSocketContext.tsx +1971 -0
  72. package/client/src/factories/baseChatModelFactory.ts +256 -0
  73. package/client/src/hooks/useAndroidOperations.ts +164 -0
  74. package/client/src/hooks/useApiKeyValidation.ts +107 -0
  75. package/client/src/hooks/useApiKeys.ts +238 -0
  76. package/client/src/hooks/useAppTheme.ts +17 -0
  77. package/client/src/hooks/useComponentPalette.ts +51 -0
  78. package/client/src/hooks/useCopyPaste.ts +155 -0
  79. package/client/src/hooks/useDragAndDrop.ts +124 -0
  80. package/client/src/hooks/useDragVariable.ts +88 -0
  81. package/client/src/hooks/useExecution.ts +313 -0
  82. package/client/src/hooks/useParameterPanel.ts +176 -0
  83. package/client/src/hooks/useReactFlowNodes.ts +189 -0
  84. package/client/src/hooks/useToolSchema.ts +209 -0
  85. package/client/src/hooks/useWhatsApp.ts +196 -0
  86. package/client/src/hooks/useWorkflowManagement.ts +46 -0
  87. package/client/src/index.css +315 -0
  88. package/client/src/main.tsx +19 -0
  89. package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
  90. package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
  91. package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
  92. package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
  93. package/client/src/nodeDefinitions/chatNodes.ts +135 -0
  94. package/client/src/nodeDefinitions/codeNodes.ts +54 -0
  95. package/client/src/nodeDefinitions/documentNodes.ts +379 -0
  96. package/client/src/nodeDefinitions/index.ts +15 -0
  97. package/client/src/nodeDefinitions/locationNodes.ts +463 -0
  98. package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
  99. package/client/src/nodeDefinitions/skillNodes.ts +211 -0
  100. package/client/src/nodeDefinitions/toolNodes.ts +198 -0
  101. package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
  102. package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
  103. package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
  104. package/client/src/nodeDefinitions.ts +104 -0
  105. package/client/src/schemas/workflowSchema.ts +264 -0
  106. package/client/src/services/dynamicParameterService.ts +96 -0
  107. package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
  108. package/client/src/services/executionService.ts +232 -0
  109. package/client/src/services/workflowApi.ts +91 -0
  110. package/client/src/store/useAppStore.ts +582 -0
  111. package/client/src/styles/theme.ts +508 -0
  112. package/client/src/styles/zIndex.ts +17 -0
  113. package/client/src/types/ComponentTypes.ts +39 -0
  114. package/client/src/types/EdgeCondition.ts +231 -0
  115. package/client/src/types/INodeProperties.ts +288 -0
  116. package/client/src/types/NodeTypes.ts +28 -0
  117. package/client/src/utils/formatters.ts +33 -0
  118. package/client/src/utils/googleMapsLoader.ts +140 -0
  119. package/client/src/utils/locationUtils.ts +85 -0
  120. package/client/src/utils/nodeUtils.ts +31 -0
  121. package/client/src/utils/workflow.ts +30 -0
  122. package/client/src/utils/workflowExport.ts +120 -0
  123. package/client/src/vite-env.d.ts +12 -0
  124. package/client/tailwind.config.js +60 -0
  125. package/client/tsconfig.json +25 -0
  126. package/client/tsconfig.node.json +11 -0
  127. package/client/vite.config.js +35 -0
  128. package/docker-compose.prod.yml +107 -0
  129. package/docker-compose.yml +104 -0
  130. package/docs-MachinaOs/README.md +85 -0
  131. package/docs-MachinaOs/deployment/docker.mdx +228 -0
  132. package/docs-MachinaOs/deployment/production.mdx +345 -0
  133. package/docs-MachinaOs/docs.json +75 -0
  134. package/docs-MachinaOs/faq.mdx +309 -0
  135. package/docs-MachinaOs/favicon.svg +5 -0
  136. package/docs-MachinaOs/installation.mdx +160 -0
  137. package/docs-MachinaOs/introduction.mdx +114 -0
  138. package/docs-MachinaOs/logo/dark.svg +6 -0
  139. package/docs-MachinaOs/logo/light.svg +6 -0
  140. package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
  141. package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
  142. package/docs-MachinaOs/nodes/android.mdx +411 -0
  143. package/docs-MachinaOs/nodes/overview.mdx +181 -0
  144. package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
  145. package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
  146. package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
  147. package/docs-MachinaOs/quickstart.mdx +119 -0
  148. package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
  149. package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
  150. package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
  151. package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
  152. package/nul +0 -0
  153. package/package.json +70 -0
  154. package/scripts/build.js +158 -0
  155. package/scripts/check-ports.ps1 +33 -0
  156. package/scripts/clean.js +40 -0
  157. package/scripts/docker.js +93 -0
  158. package/scripts/kill-port.ps1 +154 -0
  159. package/scripts/start.js +210 -0
  160. package/scripts/stop.js +325 -0
  161. package/server/.dockerignore +44 -0
  162. package/server/Dockerfile +45 -0
  163. package/server/constants.py +249 -0
  164. package/server/core/__init__.py +1 -0
  165. package/server/core/cache.py +461 -0
  166. package/server/core/config.py +128 -0
  167. package/server/core/container.py +99 -0
  168. package/server/core/database.py +1211 -0
  169. package/server/core/logging.py +314 -0
  170. package/server/main.py +289 -0
  171. package/server/middleware/__init__.py +5 -0
  172. package/server/middleware/auth.py +89 -0
  173. package/server/models/__init__.py +1 -0
  174. package/server/models/auth.py +52 -0
  175. package/server/models/cache.py +24 -0
  176. package/server/models/database.py +211 -0
  177. package/server/models/nodes.py +455 -0
  178. package/server/package.json +9 -0
  179. package/server/pyproject.toml +72 -0
  180. package/server/requirements.txt +83 -0
  181. package/server/routers/__init__.py +1 -0
  182. package/server/routers/android.py +294 -0
  183. package/server/routers/auth.py +203 -0
  184. package/server/routers/database.py +151 -0
  185. package/server/routers/maps.py +142 -0
  186. package/server/routers/nodejs_compat.py +289 -0
  187. package/server/routers/webhook.py +90 -0
  188. package/server/routers/websocket.py +2127 -0
  189. package/server/routers/whatsapp.py +761 -0
  190. package/server/routers/workflow.py +200 -0
  191. package/server/services/__init__.py +1 -0
  192. package/server/services/ai.py +2415 -0
  193. package/server/services/android/__init__.py +27 -0
  194. package/server/services/android/broadcaster.py +114 -0
  195. package/server/services/android/client.py +608 -0
  196. package/server/services/android/manager.py +78 -0
  197. package/server/services/android/protocol.py +165 -0
  198. package/server/services/android_service.py +588 -0
  199. package/server/services/auth.py +131 -0
  200. package/server/services/chat_client.py +160 -0
  201. package/server/services/deployment/__init__.py +12 -0
  202. package/server/services/deployment/manager.py +706 -0
  203. package/server/services/deployment/state.py +47 -0
  204. package/server/services/deployment/triggers.py +275 -0
  205. package/server/services/event_waiter.py +785 -0
  206. package/server/services/execution/__init__.py +77 -0
  207. package/server/services/execution/cache.py +769 -0
  208. package/server/services/execution/conditions.py +373 -0
  209. package/server/services/execution/dlq.py +132 -0
  210. package/server/services/execution/executor.py +1351 -0
  211. package/server/services/execution/models.py +531 -0
  212. package/server/services/execution/recovery.py +235 -0
  213. package/server/services/handlers/__init__.py +126 -0
  214. package/server/services/handlers/ai.py +355 -0
  215. package/server/services/handlers/android.py +260 -0
  216. package/server/services/handlers/code.py +278 -0
  217. package/server/services/handlers/document.py +598 -0
  218. package/server/services/handlers/http.py +193 -0
  219. package/server/services/handlers/polyglot.py +105 -0
  220. package/server/services/handlers/tools.py +845 -0
  221. package/server/services/handlers/triggers.py +107 -0
  222. package/server/services/handlers/utility.py +822 -0
  223. package/server/services/handlers/whatsapp.py +476 -0
  224. package/server/services/maps.py +289 -0
  225. package/server/services/memory_store.py +103 -0
  226. package/server/services/node_executor.py +375 -0
  227. package/server/services/parameter_resolver.py +218 -0
  228. package/server/services/polyglot_client.py +169 -0
  229. package/server/services/scheduler.py +155 -0
  230. package/server/services/skill_loader.py +417 -0
  231. package/server/services/status_broadcaster.py +826 -0
  232. package/server/services/temporal/__init__.py +23 -0
  233. package/server/services/temporal/activities.py +344 -0
  234. package/server/services/temporal/client.py +76 -0
  235. package/server/services/temporal/executor.py +147 -0
  236. package/server/services/temporal/worker.py +251 -0
  237. package/server/services/temporal/workflow.py +355 -0
  238. package/server/services/temporal/ws_client.py +236 -0
  239. package/server/services/text.py +111 -0
  240. package/server/services/user_auth.py +172 -0
  241. package/server/services/websocket_client.py +29 -0
  242. package/server/services/workflow.py +597 -0
  243. package/server/skills/android-skill/SKILL.md +82 -0
  244. package/server/skills/assistant-personality/SKILL.md +45 -0
  245. package/server/skills/code-skill/SKILL.md +140 -0
  246. package/server/skills/http-skill/SKILL.md +161 -0
  247. package/server/skills/maps-skill/SKILL.md +170 -0
  248. package/server/skills/memory-skill/SKILL.md +154 -0
  249. package/server/skills/scheduler-skill/SKILL.md +84 -0
  250. package/server/skills/whatsapp-skill/SKILL.md +283 -0
  251. package/server/uv.lock +2916 -0
  252. package/server/whatsapp-rpc/.dockerignore +30 -0
  253. package/server/whatsapp-rpc/Dockerfile +44 -0
  254. package/server/whatsapp-rpc/Dockerfile.web +17 -0
  255. package/server/whatsapp-rpc/README.md +139 -0
  256. package/server/whatsapp-rpc/cli.js +95 -0
  257. package/server/whatsapp-rpc/configs/config.yaml +7 -0
  258. package/server/whatsapp-rpc/docker-compose.yml +35 -0
  259. package/server/whatsapp-rpc/docs/API.md +410 -0
  260. package/server/whatsapp-rpc/go.mod +67 -0
  261. package/server/whatsapp-rpc/go.sum +203 -0
  262. package/server/whatsapp-rpc/package.json +30 -0
  263. package/server/whatsapp-rpc/schema.json +1294 -0
  264. package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
  265. package/server/whatsapp-rpc/scripts/cli.js +162 -0
  266. package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
  267. package/server/whatsapp-rpc/src/go/config/config.go +49 -0
  268. package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
  269. package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
  270. package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
  271. package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
  272. package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
  273. package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
  274. package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
  275. package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
  276. package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
  277. package/server/whatsapp-rpc/web/app.py +609 -0
  278. package/server/whatsapp-rpc/web/requirements.txt +6 -0
  279. package/server/whatsapp-rpc/web/rpc_client.py +427 -0
  280. package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
  281. package/server/whatsapp-rpc/web/templates/base.html +150 -0
  282. package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
  283. package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
  284. package/server/whatsapp-rpc/web/templates/groups.html +328 -0
  285. package/server/whatsapp-rpc/web/templates/messages.html +465 -0
  286. package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
  287. package/server/whatsapp-rpc/web/templates/send.html +259 -0
  288. package/server/whatsapp-rpc/web/templates/settings.html +459 -0
@@ -0,0 +1,597 @@
1
+ """Workflow Service - Facade for workflow execution and deployment.
2
+
3
+ This is a thin facade that delegates to specialized modules:
4
+ - NodeExecutor: Single node execution
5
+ - ParameterResolver: Template variable resolution
6
+ - DeploymentManager: Event-driven deployment lifecycle
7
+ - WorkflowExecutor: Parallel/sequential orchestration
8
+ - TemporalExecutor: Durable workflow execution (optional)
9
+
10
+ Following n8n/Conductor patterns for clean separation of concerns.
11
+ """
12
+
13
+ import time
14
+ from datetime import datetime
15
+ from typing import Dict, Any, List, Optional, TYPE_CHECKING
16
+
17
+ from core.logging import get_logger
18
+ from constants import WORKFLOW_TRIGGER_TYPES
19
+ from services.node_executor import NodeExecutor
20
+ from services.parameter_resolver import ParameterResolver
21
+ from services.deployment import DeploymentManager
22
+ from services.execution import WorkflowExecutor, ExecutionCache
23
+
24
+ if TYPE_CHECKING:
25
+ from core.config import Settings
26
+ from core.database import Database
27
+ from core.cache import CacheService
28
+ from services.ai import AIService
29
+ from services.maps import MapsService
30
+ from services.text import TextService
31
+ from services.android_service import AndroidService
32
+ from services.temporal import TemporalExecutor
33
+
34
+ logger = get_logger(__name__)
35
+
36
+
37
+ class WorkflowService:
38
+ """Workflow execution and deployment service.
39
+
40
+ Thin facade delegating to specialized modules for:
41
+ - Node execution (NodeExecutor)
42
+ - Parameter resolution (ParameterResolver)
43
+ - Deployment lifecycle (DeploymentManager)
44
+ - Workflow orchestration (WorkflowExecutor or TemporalExecutor)
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ database: "Database",
50
+ ai_service: "AIService",
51
+ maps_service: "MapsService",
52
+ text_service: "TextService",
53
+ android_service: "AndroidService",
54
+ cache: "CacheService",
55
+ settings: "Settings",
56
+ ):
57
+ self.database = database
58
+ self.settings = settings
59
+
60
+ # In-memory output storage (fast access during execution)
61
+ self._outputs: Dict[str, Dict[str, Any]] = {}
62
+
63
+ # Initialize NodeExecutor
64
+ self._node_executor = NodeExecutor(
65
+ database=database,
66
+ ai_service=ai_service,
67
+ maps_service=maps_service,
68
+ text_service=text_service,
69
+ android_service=android_service,
70
+ settings=settings,
71
+ output_store=self.store_node_output,
72
+ )
73
+
74
+ # Initialize ParameterResolver
75
+ self._param_resolver = ParameterResolver(
76
+ database=database,
77
+ get_output_fn=self.get_node_output,
78
+ )
79
+
80
+ # Initialize Execution Cache
81
+ self._execution_cache = ExecutionCache(cache)
82
+ self._workflow_executor: Optional[WorkflowExecutor] = None
83
+
84
+ # Temporal executor (set via set_temporal_executor when enabled)
85
+ self._temporal_executor: Optional["TemporalExecutor"] = None
86
+
87
+ # Initialize DeploymentManager (lazy - needs broadcaster)
88
+ self._deployment_manager: Optional[DeploymentManager] = None
89
+ self._broadcaster = None
90
+
91
+ # Deployment settings
92
+ self._settings = {
93
+ "stop_on_error": False,
94
+ "max_concurrent_runs": 100,
95
+ "use_parallel_executor": True,
96
+ }
97
+
98
+ def set_temporal_executor(self, executor: "TemporalExecutor") -> None:
99
+ """Set the Temporal executor for durable workflow execution.
100
+
101
+ Args:
102
+ executor: Configured TemporalExecutor instance
103
+ """
104
+ self._temporal_executor = executor
105
+ logger.info("Temporal executor configured for workflow execution")
106
+
107
+ def _get_deployment_manager(self) -> DeploymentManager:
108
+ """Get or create DeploymentManager."""
109
+ if self._deployment_manager is None:
110
+ from services.status_broadcaster import get_status_broadcaster
111
+ self._broadcaster = get_status_broadcaster()
112
+ self._deployment_manager = DeploymentManager(
113
+ database=self.database,
114
+ execute_workflow_fn=self.execute_workflow,
115
+ store_output_fn=self.store_node_output,
116
+ broadcaster=self._broadcaster,
117
+ )
118
+ return self._deployment_manager
119
+
120
+ def _get_workflow_executor(self, status_callback=None) -> WorkflowExecutor:
121
+ """Get or create WorkflowExecutor."""
122
+ if self._workflow_executor is None or status_callback:
123
+ self._workflow_executor = WorkflowExecutor(
124
+ cache=self._execution_cache,
125
+ node_executor=self._execute_node_adapter,
126
+ status_callback=status_callback,
127
+ dlq_enabled=self.settings.dlq_enabled,
128
+ )
129
+ return self._workflow_executor
130
+
131
+ # =========================================================================
132
+ # NODE EXECUTION
133
+ # =========================================================================
134
+
135
+ async def execute_node(
136
+ self,
137
+ node_id: str,
138
+ node_type: str,
139
+ parameters: Dict[str, Any],
140
+ nodes: List[Dict] = None,
141
+ edges: List[Dict] = None,
142
+ session_id: str = "default",
143
+ execution_id: str = None,
144
+ workflow_id: str = None,
145
+ ) -> Dict[str, Any]:
146
+ """Execute a single workflow node."""
147
+ context = {
148
+ "nodes": nodes,
149
+ "edges": edges,
150
+ "session_id": session_id,
151
+ "execution_id": execution_id,
152
+ "workflow_id": workflow_id, # For per-workflow status scoping (n8n pattern)
153
+ "get_output_fn": self.get_node_output,
154
+ }
155
+ return await self._node_executor.execute(
156
+ node_id=node_id,
157
+ node_type=node_type,
158
+ parameters=parameters,
159
+ context=context,
160
+ resolve_params_fn=self._param_resolver.resolve,
161
+ )
162
+
163
+ async def _execute_node_adapter(
164
+ self,
165
+ node_id: str,
166
+ node_type: str,
167
+ parameters: Dict[str, Any],
168
+ context: Dict[str, Any],
169
+ ) -> Dict[str, Any]:
170
+ """Adapter for WorkflowExecutor to call NodeExecutor."""
171
+ return await self.execute_node(
172
+ node_id=node_id,
173
+ node_type=node_type,
174
+ parameters=parameters,
175
+ nodes=context.get("nodes"),
176
+ edges=context.get("edges"),
177
+ session_id=context.get("session_id", "default"),
178
+ execution_id=context.get("execution_id"),
179
+ workflow_id=context.get("workflow_id"), # Pass workflow_id for status scoping
180
+ )
181
+
182
+ # =========================================================================
183
+ # WORKFLOW EXECUTION
184
+ # =========================================================================
185
+
186
+ async def execute_workflow(
187
+ self,
188
+ nodes: List[Dict],
189
+ edges: List[Dict],
190
+ session_id: str = "default",
191
+ status_callback=None,
192
+ use_parallel: bool = None,
193
+ skip_clear_outputs: bool = False,
194
+ workflow_id: Optional[str] = None,
195
+ use_temporal: bool = None,
196
+ ) -> Dict[str, Any]:
197
+ """Execute entire workflow.
198
+
199
+ Args:
200
+ nodes: Workflow nodes
201
+ edges: Workflow edges
202
+ session_id: Session identifier
203
+ status_callback: Status update callback
204
+ use_parallel: Force parallel/sequential execution
205
+ skip_clear_outputs: Skip clearing outputs (for deployment runs)
206
+ workflow_id: Workflow ID for per-workflow status scoping (n8n pattern)
207
+ use_temporal: Force Temporal execution (None = use settings default)
208
+ """
209
+ start_time = time.time()
210
+
211
+ # Clear outputs unless skipped
212
+ if not skip_clear_outputs:
213
+ await self.clear_all_outputs(session_id)
214
+
215
+ if not nodes:
216
+ return self._error_result("No nodes in workflow", start_time)
217
+
218
+ # Find start node
219
+ start_node = self._find_start_node(nodes)
220
+ if not start_node:
221
+ return self._error_result("No start node found", start_time)
222
+
223
+ # Determine execution mode
224
+ if use_parallel is None:
225
+ use_parallel = self._settings.get("use_parallel_executor", True)
226
+
227
+ # Check if Temporal execution is requested
228
+ if use_temporal is None:
229
+ use_temporal = self.settings.temporal_enabled
230
+
231
+ # Use Temporal if enabled and executor is configured
232
+ if use_temporal and self._temporal_executor is not None:
233
+ return await self._execute_temporal(nodes, edges, session_id, status_callback, start_time, workflow_id)
234
+
235
+ # Log warning if Temporal was requested but not available
236
+ if use_temporal and self._temporal_executor is None:
237
+ logger.warning(
238
+ "Temporal execution requested but executor not configured. "
239
+ "Falling back to parallel/sequential execution. "
240
+ "Check TEMPORAL_ENABLED and Temporal server connection."
241
+ )
242
+
243
+ # Use parallel executor if enabled and Redis available
244
+ if use_parallel and self.settings.redis_enabled:
245
+ return await self._execute_parallel(nodes, edges, session_id, status_callback, start_time, workflow_id)
246
+
247
+ # Fall back to sequential
248
+ return await self._execute_sequential(nodes, edges, session_id, status_callback, start_time, workflow_id)
249
+
250
+ async def _execute_temporal(self, nodes, edges, session_id, status_callback, start_time, workflow_id: Optional[str] = None) -> Dict:
251
+ """Execute with Temporal for durable workflow orchestration."""
252
+ # Use passed workflow_id (from deployment) or generate new one
253
+ if not workflow_id:
254
+ workflow_id = f"temporal_{session_id}_{int(time.time() * 1000)}"
255
+
256
+ logger.info(
257
+ "Executing workflow via Temporal",
258
+ workflow_id=workflow_id,
259
+ node_count=len(nodes),
260
+ )
261
+
262
+ result = await self._temporal_executor.execute_workflow(
263
+ workflow_id=workflow_id,
264
+ nodes=nodes,
265
+ edges=edges,
266
+ session_id=session_id,
267
+ enable_caching=True,
268
+ )
269
+
270
+ # Notify status callback for completed nodes if provided
271
+ if status_callback and result.get("success"):
272
+ for node_id in result.get("nodes_executed", []):
273
+ try:
274
+ await status_callback(
275
+ node_id,
276
+ "completed",
277
+ result.get("outputs", {}).get(node_id, {}),
278
+ )
279
+ except Exception:
280
+ pass
281
+
282
+ return {
283
+ "success": result.get("success", False),
284
+ "execution_id": result.get("execution_id"),
285
+ "nodes_executed": result.get("nodes_executed", []),
286
+ "outputs": result.get("outputs", {}),
287
+ "errors": result.get("errors", []),
288
+ "execution_time": result.get("execution_time", time.time() - start_time),
289
+ "temporal_execution": True,
290
+ "timestamp": datetime.now().isoformat(),
291
+ }
292
+
293
+ async def _execute_parallel(self, nodes, edges, session_id, status_callback, start_time, workflow_id: Optional[str] = None) -> Dict:
294
+ """Execute with parallel orchestration engine."""
295
+ # Use passed workflow_id (from deployment) or generate new one
296
+ if not workflow_id:
297
+ workflow_id = f"workflow_{session_id}_{int(time.time() * 1000)}"
298
+ executor = self._get_workflow_executor(status_callback)
299
+
300
+ result = await executor.execute_workflow(
301
+ workflow_id=workflow_id,
302
+ nodes=nodes,
303
+ edges=edges,
304
+ session_id=session_id,
305
+ enable_caching=True,
306
+ )
307
+
308
+ return {
309
+ "success": result.get("success", False),
310
+ "execution_id": result.get("execution_id"),
311
+ "nodes_executed": result.get("nodes_executed", []),
312
+ "outputs": result.get("outputs", {}),
313
+ "errors": result.get("errors", []),
314
+ "execution_time": result.get("execution_time", time.time() - start_time),
315
+ "parallel_execution": True,
316
+ "timestamp": datetime.now().isoformat(),
317
+ }
318
+
319
+ async def _execute_sequential(self, nodes, edges, session_id, status_callback, start_time, workflow_id: Optional[str] = None) -> Dict:
320
+ """Execute nodes sequentially (fallback mode)."""
321
+ start_node = self._find_start_node(nodes)
322
+ execution_order = self._build_execution_order(start_node, nodes, edges)
323
+
324
+ results = {}
325
+ executed = []
326
+
327
+ for node in execution_order:
328
+ node_id = node['id']
329
+ node_type = node.get('type', 'unknown')
330
+
331
+ # Skip pre-executed trigger nodes
332
+ if node.get('_pre_executed'):
333
+ executed.append(node_id)
334
+ continue
335
+
336
+ # Skip disabled nodes (n8n-style disable)
337
+ if node.get('data', {}).get('disabled'):
338
+ logger.debug(f"Skipping disabled node: {node_id}")
339
+ executed.append(node_id)
340
+ if status_callback:
341
+ try:
342
+ await status_callback(node_id, "skipped", {"disabled": True})
343
+ except Exception:
344
+ pass
345
+ continue
346
+
347
+ # Notify executing
348
+ if status_callback:
349
+ try:
350
+ await status_callback(node_id, "executing", {})
351
+ except Exception:
352
+ pass
353
+
354
+ # Execute with workflow_id for per-workflow status scoping (n8n pattern)
355
+ result = await self.execute_node(
356
+ node_id=node_id,
357
+ node_type=node_type,
358
+ parameters={},
359
+ nodes=nodes,
360
+ edges=edges,
361
+ session_id=session_id,
362
+ workflow_id=workflow_id,
363
+ )
364
+
365
+ results[node_id] = result
366
+ executed.append(node_id)
367
+
368
+ # Notify completed
369
+ if status_callback:
370
+ status = "completed" if result.get("success") else "error"
371
+ try:
372
+ await status_callback(node_id, status, result)
373
+ except Exception:
374
+ pass
375
+
376
+ if not result.get("success") and self._settings.get("stop_on_error"):
377
+ break
378
+
379
+ return {
380
+ "success": all(r.get("success", False) for r in results.values()),
381
+ "nodes_executed": executed,
382
+ "node_results": results,
383
+ "execution_time": time.time() - start_time,
384
+ "parallel_execution": False,
385
+ "timestamp": datetime.now().isoformat(),
386
+ }
387
+
388
+ # =========================================================================
389
+ # DEPLOYMENT
390
+ # =========================================================================
391
+
392
+ async def deploy_workflow(
393
+ self,
394
+ nodes: List[Dict],
395
+ edges: List[Dict],
396
+ session_id: str = "default",
397
+ status_callback=None,
398
+ workflow_id: Optional[str] = None,
399
+ ) -> Dict[str, Any]:
400
+ """Deploy workflow in event-driven mode.
401
+
402
+ Args:
403
+ nodes: Workflow nodes
404
+ edges: Workflow edges
405
+ session_id: Session identifier
406
+ status_callback: Status update callback
407
+ workflow_id: Workflow ID for per-workflow deployment tracking
408
+ """
409
+ manager = self._get_deployment_manager()
410
+ return await manager.deploy(nodes, edges, session_id, status_callback, workflow_id)
411
+
412
+ async def cancel_deployment(self, workflow_id: Optional[str] = None) -> Dict[str, Any]:
413
+ """Cancel active deployment.
414
+
415
+ Args:
416
+ workflow_id: Specific workflow to cancel. If None, cancels first running deployment.
417
+ """
418
+ manager = self._get_deployment_manager()
419
+ return await manager.cancel(workflow_id)
420
+
421
+ def get_deployment_status(self, workflow_id: Optional[str] = None) -> Dict[str, Any]:
422
+ """Get deployment status.
423
+
424
+ Args:
425
+ workflow_id: Get status for specific workflow. If None, returns global status.
426
+ """
427
+ manager = self._get_deployment_manager()
428
+ return manager.get_status(workflow_id)
429
+
430
+ def is_deployment_running(self, workflow_id: Optional[str] = None) -> bool:
431
+ """Check if deployment is running.
432
+
433
+ Args:
434
+ workflow_id: Check specific workflow. If None, checks if ANY deployment is running.
435
+ """
436
+ manager = self._get_deployment_manager()
437
+ if workflow_id:
438
+ return manager.is_workflow_deployed(workflow_id)
439
+ return manager.is_running
440
+
441
+ def is_workflow_deployed(self, workflow_id: str) -> bool:
442
+ """Check if a specific workflow is deployed."""
443
+ return self._get_deployment_manager().is_workflow_deployed(workflow_id)
444
+
445
+ def get_deployed_workflows(self) -> List[str]:
446
+ """Get list of deployed workflow IDs."""
447
+ return self._get_deployment_manager().get_deployed_workflows()
448
+
449
+ # =========================================================================
450
+ # OUTPUT STORAGE
451
+ # =========================================================================
452
+
453
+ async def store_node_output(
454
+ self,
455
+ session_id: str,
456
+ node_id: str,
457
+ output_name: str,
458
+ data: Dict[str, Any],
459
+ ) -> None:
460
+ """Store node execution output."""
461
+ key = f"{session_id}_{node_id}"
462
+ if key not in self._outputs:
463
+ self._outputs[key] = {}
464
+ self._outputs[key][output_name] = data
465
+ await self.database.save_node_output(node_id, session_id, output_name, data)
466
+
467
+ async def get_node_output(
468
+ self,
469
+ session_id: str,
470
+ node_id: str,
471
+ output_name: str,
472
+ ) -> Optional[Dict[str, Any]]:
473
+ """Get stored node output."""
474
+ key = f"{session_id}_{node_id}"
475
+ output = self._outputs.get(key, {}).get(output_name)
476
+
477
+ if output is None:
478
+ output = await self.database.get_node_output(node_id, session_id, output_name)
479
+ if output:
480
+ if key not in self._outputs:
481
+ self._outputs[key] = {}
482
+ self._outputs[key][output_name] = output
483
+
484
+ # Special handling for start nodes
485
+ if output is None and node_id.startswith('start-'):
486
+ import json
487
+ params = await self.database.get_node_parameters(node_id)
488
+ if params and 'initialData' in params:
489
+ try:
490
+ output = json.loads(params.get('initialData', '{}'))
491
+ except Exception:
492
+ output = {}
493
+
494
+ return output
495
+
496
+ async def get_workflow_node_output(
497
+ self,
498
+ node_id: str,
499
+ output_name: str = "output_0",
500
+ session_id: str = "default",
501
+ ) -> Dict[str, Any]:
502
+ """Get stored output data for a node."""
503
+ output = await self.get_node_output(session_id, node_id, output_name)
504
+ if output:
505
+ return {"success": True, "node_id": node_id, "data": output}
506
+ return {"success": False, "node_id": node_id, "error": "No output found"}
507
+
508
+ async def clear_all_outputs(self, session_id: str = "default") -> None:
509
+ """Clear all outputs for a session."""
510
+ keys = [k for k in self._outputs if k.startswith(f"{session_id}_")]
511
+ for k in keys:
512
+ del self._outputs[k]
513
+ await self.database.clear_session_outputs(session_id)
514
+
515
+ # =========================================================================
516
+ # HELPERS
517
+ # =========================================================================
518
+
519
+ def _find_start_node(self, nodes: List[Dict]) -> Optional[Dict]:
520
+ """Find workflow entry point."""
521
+ # Priority: start > cronScheduler > other triggers
522
+ for node in nodes:
523
+ if node.get('type') == 'start':
524
+ return node
525
+ for node in nodes:
526
+ if node.get('type') == 'cronScheduler':
527
+ return node
528
+ for node in nodes:
529
+ if node.get('type') in WORKFLOW_TRIGGER_TYPES:
530
+ return node
531
+ return None
532
+
533
+ def _build_execution_order(self, start: Dict, nodes: List[Dict], edges: List[Dict]) -> List[Dict]:
534
+ """Build BFS execution order from start node."""
535
+ visited = set()
536
+ order = []
537
+ queue = [start['id']]
538
+
539
+ # Build adjacency map
540
+ adj = {}
541
+ for e in edges:
542
+ src = e.get('source')
543
+ if src:
544
+ adj.setdefault(src, []).append(e.get('target'))
545
+
546
+ node_map = {n['id']: n for n in nodes}
547
+
548
+ while queue:
549
+ nid = queue.pop(0)
550
+ if nid in visited:
551
+ continue
552
+ visited.add(nid)
553
+ node = node_map.get(nid)
554
+ if node:
555
+ order.append(node)
556
+ queue.extend(t for t in adj.get(nid, []) if t not in visited)
557
+
558
+ return order
559
+
560
+ def _error_result(self, error: str, start_time: float) -> Dict:
561
+ """Build error result."""
562
+ return {
563
+ "success": False,
564
+ "error": error,
565
+ "nodes_executed": [],
566
+ "execution_time": time.time() - start_time,
567
+ "timestamp": datetime.now().isoformat(),
568
+ }
569
+
570
+ # =========================================================================
571
+ # SETTINGS
572
+ # =========================================================================
573
+
574
+ async def load_deployment_settings(self) -> Dict[str, Any]:
575
+ """Load deployment settings from database."""
576
+ try:
577
+ db = await self.database.get_deployment_settings()
578
+ if db:
579
+ self._settings.update(db)
580
+ except Exception:
581
+ pass
582
+ return self._settings.copy()
583
+
584
+ async def update_deployment_settings(self, settings: Dict[str, Any]) -> Dict[str, Any]:
585
+ """Update deployment settings."""
586
+ self._settings.update(settings)
587
+ await self.database.save_deployment_settings(self._settings)
588
+ return self._settings.copy()
589
+
590
+ def get_deployment_settings(self) -> Dict[str, Any]:
591
+ """Get current deployment settings."""
592
+ return self._settings.copy()
593
+
594
+ @property
595
+ def node_outputs(self) -> Dict[str, Dict[str, Any]]:
596
+ """Backward compatibility: expose outputs as node_outputs."""
597
+ return self._outputs
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: android-skill
3
+ description: Control Android devices - battery, wifi, bluetooth, apps, location, camera, and sensors. Use when user wants to interact with their Android phone or tablet.
4
+ metadata:
5
+ author: machina
6
+ version: "2.0"
7
+ category: device
8
+ ---
9
+
10
+ # Android Device Control
11
+
12
+ This skill provides context for monitoring and controlling Android devices.
13
+
14
+ ## How It Works
15
+
16
+ This skill provides instructions and context. To execute Android actions, connect the **Android Toolkit** node to the Chat Agent's `input-tools` handle, then connect Android service nodes to the toolkit:
17
+
18
+ ```
19
+ [Battery Monitor] --+
20
+ |
21
+ [WiFi Automation] --+--> [Android Toolkit] --> [Chat Agent]
22
+ |
23
+ [Location] ---+
24
+ ```
25
+
26
+ ## Available Android Service Nodes
27
+
28
+ Connect these to the Android Toolkit:
29
+
30
+ | Node | Capabilities |
31
+ |------|-------------|
32
+ | **Battery Monitor** | Battery level, charging status, health, temperature |
33
+ | **WiFi Automation** | Enable/disable WiFi, scan networks, get status |
34
+ | **Bluetooth Automation** | Enable/disable Bluetooth, paired devices |
35
+ | **App Launcher** | Launch applications by package name |
36
+ | **App List** | List installed applications |
37
+ | **Location** | Get current GPS coordinates |
38
+ | **Audio Automation** | Volume control, mute/unmute |
39
+ | **Screen Control** | Brightness, wake/sleep screen |
40
+ | **Camera Control** | Camera info, capture photos |
41
+ | **Motion Detection** | Accelerometer, gyroscope data |
42
+ | **Environmental Sensors** | Temperature, humidity, pressure |
43
+
44
+ ## Example Interactions
45
+
46
+ **User**: "What's my phone's battery level?"
47
+ - Use the Battery Monitor service with action "status"
48
+
49
+ **User**: "Turn off WiFi"
50
+ - Use the WiFi Automation service with action "disable"
51
+
52
+ **User**: "What apps are installed?"
53
+ - Use the App List service with action "list"
54
+
55
+ **User**: "Open Chrome"
56
+ - Use the App Launcher service with action "launch", package_name "com.android.chrome"
57
+
58
+ **User**: "Where is my phone?"
59
+ - Use the Location service with action "current"
60
+
61
+ **User**: "Set volume to 50%"
62
+ - Use the Audio Automation service with action "set_volume", volume 50
63
+
64
+ ## Device Connection
65
+
66
+ Before using Android tools, ensure:
67
+ 1. Device is connected via ADB (USB) or remote WebSocket
68
+ 2. Android Device Setup node shows connected status (green indicator)
69
+ 3. Required permissions are granted on the device
70
+
71
+ ## Error Handling
72
+
73
+ - If device is not connected, inform user to check connection
74
+ - If permission is denied, suggest enabling it in device settings
75
+ - If action fails, provide the error message and suggest alternatives
76
+
77
+ ## Setup Requirements
78
+
79
+ 1. Connect this skill to Chat Agent's `input-skill` handle
80
+ 2. Add **Android Toolkit** node and connect to Chat Agent's `input-tools` handle
81
+ 3. Connect desired Android service nodes to the Android Toolkit
82
+ 4. Ensure Android device is paired (green status indicator)