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,355 @@
1
+ """AI node handlers - AI Agent, Chat Agent, AI Chat Model, Simple Memory."""
2
+
3
+ from typing import Dict, Any, List, Optional, TYPE_CHECKING
4
+ from core.logging import get_logger
5
+ from constants import ANDROID_SERVICE_NODE_TYPES
6
+
7
+ if TYPE_CHECKING:
8
+ from services.ai import AIService
9
+ from core.database import Database
10
+
11
+ logger = get_logger(__name__)
12
+
13
+
14
+ async def handle_ai_agent(
15
+ node_id: str,
16
+ node_type: str,
17
+ parameters: Dict[str, Any],
18
+ context: Dict[str, Any],
19
+ ai_service: "AIService",
20
+ database: "Database"
21
+ ) -> Dict[str, Any]:
22
+ """Handle AI agent node execution with memory and tool support.
23
+
24
+ Args:
25
+ node_id: The node ID
26
+ node_type: The node type (aiAgent)
27
+ parameters: Resolved parameters
28
+ context: Execution context with nodes, edges, session_id, start_time, execution_id
29
+ ai_service: The AI service instance
30
+ database: The database instance
31
+
32
+ Returns:
33
+ Execution result dict
34
+ """
35
+ nodes = context.get('nodes')
36
+ edges = context.get('edges')
37
+ execution_id = context.get('execution_id', 'unknown')
38
+ workflow_id = context.get('workflow_id') # Extract for status broadcasts
39
+ memory_data = None
40
+ tool_data: List[Dict[str, Any]] = [] # Collect connected tool nodes
41
+
42
+ logger.info(f"[AI Agent] Processing node {node_id}, edges={len(edges) if edges else 0}, nodes={len(nodes) if nodes else 0}, workflow_id={workflow_id}")
43
+
44
+ if edges and nodes:
45
+ # Log all edges targeting this AI Agent for debugging
46
+ incoming_edges = [e for e in edges if e.get('target') == node_id]
47
+ logger.info(f"[AI Agent] Incoming edges to {node_id}: {len(incoming_edges)}")
48
+ for e in incoming_edges:
49
+ logger.info(f"[AI Agent] Edge: source={e.get('source')}, targetHandle={e.get('targetHandle')}")
50
+
51
+ # Check for tool edges specifically
52
+ tool_incoming = [e for e in incoming_edges if e.get('targetHandle') == 'input-tools']
53
+ logger.info(f"[AI Agent] Tool edges (input-tools handle): {len(tool_incoming)}")
54
+
55
+ for edge in edges:
56
+ if edge.get('target') != node_id:
57
+ continue
58
+
59
+ target_handle = edge.get('targetHandle')
60
+ source_node_id = edge.get('source')
61
+ source_node = next((n for n in nodes if n.get('id') == source_node_id), None)
62
+
63
+ if not source_node:
64
+ continue
65
+
66
+ # Memory detection - load markdown content for editing
67
+ if target_handle == 'input-memory':
68
+ if source_node.get('type') == 'simpleMemory':
69
+ memory_params = await database.get_node_parameters(source_node_id) or {}
70
+ memory_session_id = memory_params.get('sessionId', 'default')
71
+ window_size = int(memory_params.get('windowSize', 10))
72
+ memory_content = memory_params.get('memoryContent', '# Conversation History\n\n*No messages yet.*\n')
73
+ long_term_enabled = memory_params.get('longTermEnabled', False)
74
+ retrieval_count = int(memory_params.get('retrievalCount', 3))
75
+
76
+ memory_data = {
77
+ 'node_id': source_node_id, # For saving updated content
78
+ 'session_id': memory_session_id,
79
+ 'window_size': window_size,
80
+ 'memory_content': memory_content,
81
+ 'long_term_enabled': long_term_enabled,
82
+ 'retrieval_count': retrieval_count
83
+ }
84
+ logger.debug("AI Agent connected memory node", memory_session=memory_session_id, content_length=len(memory_content))
85
+
86
+ # Tool detection (new) - any node connected to input-tools becomes a tool
87
+ elif target_handle == 'input-tools':
88
+ tool_type = source_node.get('type')
89
+ logger.info(f"[AI Agent] Found tool connected via input-tools: type={tool_type}, node_id={source_node_id}")
90
+ tool_params = await database.get_node_parameters(source_node_id) or {}
91
+
92
+ # Build base tool entry
93
+ tool_entry = {
94
+ 'node_id': source_node_id,
95
+ 'node_type': tool_type,
96
+ 'parameters': tool_params,
97
+ 'label': source_node.get('data', {}).get('label', tool_type)
98
+ }
99
+
100
+ # Special handling for androidTool - discover connected Android services
101
+ # Follows n8n Sub-Node pattern
102
+ if tool_type == 'androidTool':
103
+ connected_services = []
104
+
105
+ # Scan edges for Android nodes connected to this toolkit
106
+ for service_edge in edges:
107
+ # Skip if not targeting this androidTool node
108
+ if service_edge.get('target') != source_node_id:
109
+ continue
110
+
111
+ target_handle = service_edge.get('targetHandle')
112
+ # Accept input-main or no handle (ReactFlow may omit handle for single-input nodes)
113
+ if target_handle is not None and target_handle != 'input-main':
114
+ logger.debug(f"[Android Toolkit] Skipping edge with targetHandle: {target_handle}")
115
+ continue
116
+
117
+ android_node_id = service_edge.get('source')
118
+ android_node = next((n for n in nodes if n.get('id') == android_node_id), None)
119
+
120
+ if android_node and android_node.get('type') in ANDROID_SERVICE_NODE_TYPES:
121
+ android_params = await database.get_node_parameters(android_node_id) or {}
122
+ connected_services.append({
123
+ 'node_id': android_node_id,
124
+ 'node_type': android_node.get('type'),
125
+ 'service_id': android_params.get('service_id'),
126
+ 'action': android_params.get('action'), # Default action
127
+ 'parameters': android_params,
128
+ 'label': android_node.get('data', {}).get('label', android_node.get('type'))
129
+ })
130
+ logger.debug(f"Android toolkit connected service: {android_params.get('service_id')}")
131
+
132
+ tool_entry['connected_services'] = connected_services
133
+ logger.debug(f"Android toolkit has {len(connected_services)} connected services")
134
+
135
+ tool_data.append(tool_entry)
136
+ logger.debug(f"AI Agent connected tool: {tool_type}")
137
+
138
+ # Log tool data collection results
139
+ logger.info(f"[AI Agent Handler] Collected tools: count={len(tool_data)}, workflow_id={workflow_id}")
140
+ for td in tool_data:
141
+ logger.info(f"[AI Agent Handler] Tool: type={td.get('node_type')}, node_id={td.get('node_id')}")
142
+
143
+ # Get broadcaster for real-time status updates
144
+ from services.status_broadcaster import get_status_broadcaster
145
+ broadcaster = get_status_broadcaster()
146
+
147
+ return await ai_service.execute_agent(
148
+ node_id,
149
+ parameters,
150
+ memory_data=memory_data,
151
+ tool_data=tool_data if tool_data else None,
152
+ broadcaster=broadcaster,
153
+ workflow_id=workflow_id
154
+ )
155
+
156
+
157
+ async def handle_chat_agent(
158
+ node_id: str,
159
+ node_type: str,
160
+ parameters: Dict[str, Any],
161
+ context: Dict[str, Any],
162
+ ai_service: "AIService",
163
+ database: "Database"
164
+ ) -> Dict[str, Any]:
165
+ """Handle Chat Agent node execution with skill-based tool calling.
166
+
167
+ Chat Agent supports:
168
+ - Memory (input-memory): SimpleMemory node for conversation history
169
+ - Skills (input-skill): Provide context/instructions via SKILL.md
170
+ - Tools (input-tools): Tool nodes (httpRequest, etc.) for LangGraph tool calling
171
+
172
+ Args:
173
+ node_id: The node ID
174
+ node_type: The node type (chatAgent)
175
+ parameters: Resolved parameters
176
+ context: Execution context with nodes, edges, session_id, start_time, execution_id
177
+ ai_service: The AI service instance
178
+ database: The database instance
179
+
180
+ Returns:
181
+ Execution result dict
182
+ """
183
+ nodes = context.get('nodes')
184
+ edges = context.get('edges')
185
+ workflow_id = context.get('workflow_id')
186
+ memory_data = None
187
+ skill_data: List[Dict[str, Any]] = []
188
+ tool_data: List[Dict[str, Any]] = [] # Tools for LangGraph
189
+ input_data: Optional[Dict[str, Any]] = None
190
+
191
+ logger.info(f"[Chat Agent] Processing node {node_id}, workflow_id={workflow_id}")
192
+
193
+ if edges and nodes:
194
+ incoming_edges = [e for e in edges if e.get('target') == node_id]
195
+ logger.debug(f"[Chat Agent] Incoming edges: {len(incoming_edges)}")
196
+
197
+ for edge in edges:
198
+ if edge.get('target') != node_id:
199
+ continue
200
+
201
+ target_handle = edge.get('targetHandle')
202
+ source_node_id = edge.get('source')
203
+ source_node = next((n for n in nodes if n.get('id') == source_node_id), None)
204
+
205
+ if not source_node:
206
+ continue
207
+
208
+ # Memory detection - load markdown content (same as AI Agent)
209
+ if target_handle == 'input-memory':
210
+ if source_node.get('type') == 'simpleMemory':
211
+ memory_params = await database.get_node_parameters(source_node_id) or {}
212
+ memory_session_id = memory_params.get('sessionId', 'default')
213
+ window_size = int(memory_params.get('windowSize', 10))
214
+ memory_content = memory_params.get('memoryContent', '# Conversation History\n\n*No messages yet.*\n')
215
+ long_term_enabled = memory_params.get('longTermEnabled', False)
216
+ retrieval_count = int(memory_params.get('retrievalCount', 3))
217
+
218
+ memory_data = {
219
+ 'node_id': source_node_id,
220
+ 'session_id': memory_session_id,
221
+ 'window_size': window_size,
222
+ 'memory_content': memory_content,
223
+ 'long_term_enabled': long_term_enabled,
224
+ 'retrieval_count': retrieval_count
225
+ }
226
+ logger.info(f"[Chat Agent] Connected memory node: session={memory_session_id}, content_length={len(memory_content)}")
227
+
228
+ # Skill detection - nodes connected to input-skill handle
229
+ elif target_handle == 'input-skill':
230
+ skill_type = source_node.get('type')
231
+ skill_params = await database.get_node_parameters(source_node_id) or {}
232
+ skill_entry = {
233
+ 'node_id': source_node_id,
234
+ 'node_type': skill_type,
235
+ 'skill_name': skill_params.get('skillName', skill_type),
236
+ 'parameters': skill_params,
237
+ 'label': source_node.get('data', {}).get('label', skill_type)
238
+ }
239
+ skill_data.append(skill_entry)
240
+ logger.debug(f"[Chat Agent] Connected skill: {skill_type}")
241
+
242
+ # Tool detection - nodes connected to input-tools handle (for LangGraph)
243
+ elif target_handle == 'input-tools':
244
+ tool_type = source_node.get('type')
245
+ tool_params = await database.get_node_parameters(source_node_id) or {}
246
+ tool_entry = {
247
+ 'node_id': source_node_id,
248
+ 'node_type': tool_type,
249
+ 'parameters': tool_params,
250
+ 'label': source_node.get('data', {}).get('label', tool_type)
251
+ }
252
+ tool_data.append(tool_entry)
253
+ logger.info(f"[Chat Agent] Connected tool: {tool_type} ({tool_entry['label']})")
254
+
255
+ # Input data detection - nodes connected to input-main
256
+ elif target_handle == 'input-main' or target_handle is None:
257
+ source_output = context.get('outputs', {}).get(source_node_id)
258
+ if source_output:
259
+ input_data = source_output
260
+ logger.debug(f"[Chat Agent] Input from {source_node.get('type')}: {list(source_output.keys())}")
261
+
262
+ # Log discovered connections
263
+ logger.info(f"[Chat Agent] Discovered: memory={'yes' if memory_data else 'no'}, {len(skill_data)} skills, {len(tool_data)} tools")
264
+ for td in tool_data:
265
+ logger.debug(f"[Chat Agent] Tool: type={td.get('node_type')}, label={td.get('label')}")
266
+
267
+ # Auto-use input data if prompt is empty (fallback for trigger nodes)
268
+ if not parameters.get('prompt') and input_data:
269
+ prompt = (
270
+ input_data.get('message') or
271
+ input_data.get('text') or
272
+ input_data.get('content') or
273
+ str(input_data)
274
+ )
275
+ parameters = {**parameters, 'prompt': prompt}
276
+ logger.info(f"[Chat Agent] Auto-using input as prompt: {prompt[:100] if len(str(prompt)) > 100 else prompt}...")
277
+
278
+ # Get broadcaster for real-time status updates
279
+ from services.status_broadcaster import get_status_broadcaster
280
+ broadcaster = get_status_broadcaster()
281
+
282
+ # Execute Chat Agent with memory, skills and tools
283
+ return await ai_service.execute_chat_agent(
284
+ node_id,
285
+ parameters,
286
+ memory_data=memory_data,
287
+ skill_data=skill_data if skill_data else None,
288
+ tool_data=tool_data if tool_data else None,
289
+ broadcaster=broadcaster,
290
+ workflow_id=workflow_id
291
+ )
292
+
293
+
294
+ async def handle_ai_chat_model(
295
+ node_id: str,
296
+ node_type: str,
297
+ parameters: Dict[str, Any],
298
+ context: Dict[str, Any],
299
+ ai_service: "AIService"
300
+ ) -> Dict[str, Any]:
301
+ """Handle AI chat model node execution.
302
+
303
+ Args:
304
+ node_id: The node ID
305
+ node_type: The node type (openaiChatModel, anthropicChatModel, etc.)
306
+ parameters: Resolved parameters
307
+ context: Execution context
308
+ ai_service: The AI service instance
309
+
310
+ Returns:
311
+ Execution result dict
312
+ """
313
+ return await ai_service.execute_chat(node_id, node_type, parameters)
314
+
315
+
316
+ async def handle_simple_memory(
317
+ node_id: str,
318
+ node_type: str,
319
+ parameters: Dict[str, Any],
320
+ context: Dict[str, Any]
321
+ ) -> Dict[str, Any]:
322
+ """Handle simple memory node execution.
323
+
324
+ Args:
325
+ node_id: The node ID
326
+ node_type: The node type (simpleMemory)
327
+ parameters: Resolved parameters
328
+ context: Execution context
329
+
330
+ Returns:
331
+ Execution result dict with session info and messages
332
+ """
333
+ from services.memory_store import get_messages, clear_session
334
+
335
+ session_id = parameters.get('sessionId', 'default')
336
+ memory_type = parameters.get('memoryType', 'buffer')
337
+ window_size = int(parameters.get('windowSize', 10)) if memory_type == 'window' else None
338
+ clear_on_run = parameters.get('clearOnRun', False)
339
+
340
+ if clear_on_run:
341
+ cleared = clear_session(session_id)
342
+ logger.info(f"[Memory] Cleared {cleared} messages from session '{session_id}'")
343
+
344
+ messages = get_messages(session_id, window_size)
345
+
346
+ return {
347
+ "success": True,
348
+ "result": {
349
+ "session_id": session_id,
350
+ "messages": messages,
351
+ "message_count": len(messages),
352
+ "memory_type": memory_type,
353
+ "window_size": window_size
354
+ }
355
+ }
@@ -0,0 +1,260 @@
1
+ """Android node handlers - Device Setup and Android Services."""
2
+
3
+ import json
4
+ import time
5
+ from datetime import datetime
6
+ from typing import Dict, Any, TYPE_CHECKING
7
+ from core.logging import get_logger
8
+ from constants import ANDROID_SERVICE_NODE_TYPES
9
+
10
+ if TYPE_CHECKING:
11
+ from services.android_service import AndroidService
12
+ from core.config import Settings
13
+
14
+ logger = get_logger(__name__)
15
+
16
+
17
+ async def handle_android_device_setup(
18
+ node_id: str,
19
+ node_type: str,
20
+ parameters: Dict[str, Any],
21
+ context: Dict[str, Any],
22
+ settings: "Settings"
23
+ ) -> Dict[str, Any]:
24
+ """Handle Android device setup node execution.
25
+
26
+ Supports two connection types:
27
+ - local: ADB connection with port forwarding
28
+ - remote: WebSocket connection to remote proxy
29
+
30
+ Args:
31
+ node_id: The node ID
32
+ node_type: The node type (androidDeviceSetup)
33
+ parameters: Resolved parameters
34
+ context: Execution context with start_time
35
+ settings: Application settings
36
+
37
+ Returns:
38
+ Execution result dict with connection info
39
+ """
40
+ start_time = context.get('start_time', time.time())
41
+ connection_type = parameters.get('connection_type', 'local')
42
+ device_id = parameters.get('device_id', '')
43
+ websocket_url = parameters.get('websocket_url', settings.websocket_url)
44
+ port = parameters.get('port', 8888)
45
+ auto_forward = parameters.get('auto_forward', True)
46
+
47
+ logger.info("[Android Device Setup] Executing", node_id=node_id, connection_type=connection_type,
48
+ device_id=device_id, websocket_url=websocket_url, port=port, auto_forward=auto_forward)
49
+
50
+ if connection_type == 'local' and auto_forward and device_id:
51
+ # Local ADB connection with port forwarding
52
+ import subprocess
53
+ try:
54
+ cmd = ["adb", "-s", device_id, "forward", f"tcp:{port}", f"tcp:{port}"]
55
+ subprocess_result = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5)
56
+
57
+ if subprocess_result.returncode == 0:
58
+ connection_info = {
59
+ "connection_type": "local",
60
+ "device_id": device_id,
61
+ "port": port,
62
+ "port_forwarding": "active",
63
+ "local_endpoint": f"http://localhost:{port}",
64
+ "message": f"Port forwarding setup: localhost:{port} -> device:{port}",
65
+ "timestamp": datetime.now().isoformat()
66
+ }
67
+ return {
68
+ "success": True,
69
+ "node_id": node_id,
70
+ "node_type": node_type,
71
+ "result": connection_info,
72
+ "execution_time": time.time() - start_time,
73
+ "timestamp": datetime.now().isoformat()
74
+ }
75
+ else:
76
+ error_msg = subprocess_result.stderr.strip() or subprocess_result.stdout.strip()
77
+ return {
78
+ "success": False,
79
+ "node_id": node_id,
80
+ "node_type": node_type,
81
+ "error": f"Port forwarding failed: {error_msg}",
82
+ "execution_time": time.time() - start_time,
83
+ "timestamp": datetime.now().isoformat()
84
+ }
85
+ except Exception as e:
86
+ return {
87
+ "success": False,
88
+ "node_id": node_id,
89
+ "node_type": node_type,
90
+ "error": f"Port forwarding error: {str(e)}",
91
+ "execution_time": time.time() - start_time,
92
+ "timestamp": datetime.now().isoformat()
93
+ }
94
+
95
+ elif connection_type == 'remote' and websocket_url:
96
+ # Remote WebSocket connection via Android Services Relay
97
+ try:
98
+ from services.android import get_relay_client
99
+
100
+ api_key = settings.websocket_api_key
101
+ if not api_key:
102
+ raise ValueError("WEBSOCKET_API_KEY not configured in environment")
103
+
104
+ logger.info("[Android Device Setup] Connecting to Relay",
105
+ url=websocket_url, api_key=api_key[:8] + "...")
106
+
107
+ relay_client = await get_relay_client(websocket_url, api_key)
108
+
109
+ if relay_client and relay_client.is_connected():
110
+ # Wait for Android device to pair via QR code
111
+ if not relay_client.is_paired():
112
+ logger.info("[Android Device Setup] Waiting for Android device to pair...")
113
+ paired = await relay_client.wait_for_pairing(timeout=5.0)
114
+ if not paired:
115
+ # Return QR data for pairing
116
+ connection_info = {
117
+ "connection_type": "remote",
118
+ "websocket_url": websocket_url,
119
+ "port": port,
120
+ "status": "waiting_for_pairing",
121
+ "qr_data": relay_client.qr_data,
122
+ "session_token": relay_client.session_token,
123
+ "message": "Scan QR code with Android app to pair",
124
+ "timestamp": datetime.now().isoformat()
125
+ }
126
+ return {
127
+ "success": True,
128
+ "node_id": node_id,
129
+ "node_type": node_type,
130
+ "result": connection_info,
131
+ "execution_time": time.time() - start_time,
132
+ "timestamp": datetime.now().isoformat()
133
+ }
134
+
135
+ # Device is paired
136
+ device_id = relay_client.paired_device_id
137
+ device_name = relay_client.paired_device_name
138
+
139
+ connection_info = {
140
+ "connection_type": "remote",
141
+ "websocket_url": websocket_url,
142
+ "port": port,
143
+ "status": "paired",
144
+ "paired": True,
145
+ "device_id": device_id,
146
+ "device_name": device_name,
147
+ "session_token": relay_client.session_token,
148
+ "message": f"Paired with Android device: {device_name or device_id}",
149
+ "timestamp": datetime.now().isoformat()
150
+ }
151
+
152
+ logger.info("[Android Device Setup] Relay connected and paired",
153
+ device_id=device_id, device_name=device_name)
154
+
155
+ return {
156
+ "success": True,
157
+ "node_id": node_id,
158
+ "node_type": node_type,
159
+ "result": connection_info,
160
+ "execution_time": time.time() - start_time,
161
+ "timestamp": datetime.now().isoformat()
162
+ }
163
+ else:
164
+ logger.error("[Android Device Setup] Relay connection failed")
165
+ return {
166
+ "success": False,
167
+ "node_id": node_id,
168
+ "node_type": node_type,
169
+ "error": "Failed to connect to relay server",
170
+ "execution_time": time.time() - start_time,
171
+ "timestamp": datetime.now().isoformat()
172
+ }
173
+ except Exception as e:
174
+ logger.error("[Android Device Setup] Relay error", error=str(e))
175
+ return {
176
+ "success": False,
177
+ "node_id": node_id,
178
+ "node_type": node_type,
179
+ "error": f"Relay connection error: {str(e)}",
180
+ "execution_time": time.time() - start_time,
181
+ "timestamp": datetime.now().isoformat()
182
+ }
183
+ else:
184
+ # Configuration saved without active setup
185
+ connection_info = {
186
+ "connection_type": connection_type,
187
+ "device_id": device_id if connection_type == 'local' else None,
188
+ "websocket_url": websocket_url if connection_type == 'remote' else None,
189
+ "port": port,
190
+ "port_forwarding": "not_setup",
191
+ "message": "Device configuration saved (auto_forward disabled or missing device info)",
192
+ "timestamp": datetime.now().isoformat()
193
+ }
194
+
195
+ return {
196
+ "success": True,
197
+ "node_id": node_id,
198
+ "node_type": node_type,
199
+ "result": connection_info,
200
+ "execution_time": time.time() - start_time,
201
+ "timestamp": datetime.now().isoformat()
202
+ }
203
+
204
+
205
+ async def handle_android_service(
206
+ node_id: str,
207
+ node_type: str,
208
+ parameters: Dict[str, Any],
209
+ context: Dict[str, Any],
210
+ android_service: "AndroidService"
211
+ ) -> Dict[str, Any]:
212
+ """Handle Android service node execution.
213
+
214
+ Executes Android service actions like battery status, network info, etc.
215
+
216
+ Args:
217
+ node_id: The node ID
218
+ node_type: The node type (batteryMonitor, networkMonitor, etc.)
219
+ parameters: Resolved parameters
220
+ context: Execution context
221
+ android_service: The Android service instance
222
+
223
+ Returns:
224
+ Execution result dict
225
+ """
226
+ logger.debug(f"[ANDROID DEBUG] Matched! node_type={node_type}")
227
+
228
+ service_id = parameters.get('service_id', 'battery')
229
+ action = parameters.get('action', 'status')
230
+ service_params = parameters.get('parameters', {})
231
+ android_host = parameters.get('android_host', 'localhost')
232
+ android_port = parameters.get('android_port', 8888)
233
+
234
+ # Parse parameters if it's a JSON string
235
+ if isinstance(service_params, str):
236
+ try:
237
+ service_params = json.loads(service_params)
238
+ except json.JSONDecodeError:
239
+ service_params = {}
240
+
241
+ # Extract additional parameters that are at root level (from additionalProperties in node definitions)
242
+ # These include: package_name (appLauncher), and any future custom parameters
243
+ additional_param_keys = ['package_name']
244
+ for key in additional_param_keys:
245
+ if key in parameters and parameters[key]:
246
+ service_params[key] = parameters[key]
247
+
248
+ logger.debug(f"[ANDROID DEBUG] Extracted params: service_id={service_id}, action={action}, host={android_host}, port={android_port}, service_params={service_params}")
249
+ logger.debug(f"[ANDROID DEBUG] About to call android_service.execute_service")
250
+
251
+ result = await android_service.execute_service(
252
+ node_id=node_id,
253
+ service_id=service_id,
254
+ action=action,
255
+ parameters=service_params,
256
+ android_host=android_host,
257
+ android_port=android_port
258
+ )
259
+ logger.debug(f"[ANDROID DEBUG] Got result from android_service")
260
+ return result