machinaos 0.0.10 → 0.0.13
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 +16 -0
- package/client/package.json +1 -1
- package/client/src/Dashboard.tsx +3 -3
- package/client/src/components/AIAgentNode.tsx +24 -12
- package/client/src/components/OutputPanel.tsx +3 -2
- package/client/src/components/parameterPanel/InputSection.tsx +16 -3
- package/client/src/nodeDefinitions/aiAgentNodes.ts +12 -0
- package/client/src/nodeDefinitions/specializedAgentNodes.ts +68 -320
- package/client/src/nodeDefinitions/toolNodes.ts +87 -1
- package/client/src/nodeDefinitions/workflowNodes.ts +55 -1
- package/package.json +12 -3
- package/scripts/daemon.js +427 -0
- package/scripts/start.js +7 -1
- package/scripts/sync-version.js +108 -0
- package/server/Dockerfile +6 -7
- package/server/constants.py +2 -0
- package/server/core/cleanup.py +123 -0
- package/server/core/config.py +16 -0
- package/server/core/database.py +92 -1
- package/server/core/health.py +121 -0
- package/server/examples/__init__.py +1 -0
- package/server/gunicorn.conf.py +46 -0
- package/server/main.py +38 -3
- package/server/models/database.py +1 -0
- package/server/models/nodes.py +18 -2
- package/server/requirements-docker.txt +86 -0
- package/server/routers/database.py +16 -0
- package/server/routers/websocket.py +6 -5
- package/server/services/ai.py +115 -14
- package/server/services/auth.py +6 -1
- package/server/services/deployment/manager.py +14 -0
- package/server/services/event_waiter.py +55 -0
- package/server/services/example_loader.py +60 -0
- package/server/services/execution/executor.py +2 -0
- package/server/services/execution/models.py +8 -0
- package/server/services/handlers/__init__.py +2 -0
- package/server/services/handlers/ai.py +164 -11
- package/server/services/handlers/document.py +13 -4
- package/server/services/handlers/tools.py +445 -14
- package/server/services/node_executor.py +3 -0
- package/server/services/temporal/activities.py +3 -0
- package/server/services/workflow.py +2 -0
- package/server/skills/android_agent/app-launcher-skill/SKILL.md +137 -0
- package/server/skills/android_agent/app-list-skill/SKILL.md +148 -0
- package/server/skills/android_agent/audio-skill/SKILL.md +169 -0
- package/server/skills/android_agent/battery-skill/SKILL.md +114 -0
- package/server/skills/android_agent/bluetooth-skill/SKILL.md +151 -0
- package/server/skills/android_agent/camera-skill/SKILL.md +148 -0
- package/server/skills/android_agent/environmental-skill/SKILL.md +140 -0
- package/server/skills/android_agent/location-skill/SKILL.md +163 -0
- package/server/skills/android_agent/motion-skill/SKILL.md +141 -0
- package/server/skills/android_agent/screen-control-skill/SKILL.md +164 -0
- package/server/skills/android_agent/wifi-skill/SKILL.md +182 -0
- package/server/skills/assistant/subagent-skill/SKILL.md +205 -0
- package/server/skills/coding_agent/javascript-skill/SKILL.md +196 -0
- package/server/skills/coding_agent/python-skill/SKILL.md +165 -0
- package/server/skills/social_agent/whatsapp-db-skill/SKILL.md +284 -0
- package/server/skills/social_agent/whatsapp-send-skill/SKILL.md +180 -0
- package/server/skills/task_agent/cron-scheduler-skill/SKILL.md +215 -0
- package/server/skills/task_agent/task-manager-skill/SKILL.md +251 -0
- package/server/skills/task_agent/timer-skill/SKILL.md +168 -0
- package/server/skills/travel_agent/geocoding-skill/SKILL.md +186 -0
- package/server/skills/travel_agent/nearby-places-skill/SKILL.md +234 -0
- package/server/skills/web_agent/http-request-skill/SKILL.md +211 -0
- package/server/skills/android/skill/SKILL.md +0 -84
- package/server/skills/assistant/code-skill/SKILL.md +0 -176
- package/server/skills/assistant/http-skill/SKILL.md +0 -163
- package/server/skills/assistant/maps-skill/SKILL.md +0 -172
- package/server/skills/assistant/scheduler-skill/SKILL.md +0 -86
- package/server/skills/assistant/whatsapp-skill/SKILL.md +0 -285
- /package/server/skills/{android → android_agent}/personality/SKILL.md +0 -0
- /package/server/skills/{assistant → web_agent}/web-search-skill/SKILL.md +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Dict, Any, List, Optional, Tuple, TYPE_CHECKING
|
|
4
4
|
from core.logging import get_logger
|
|
5
|
-
from constants import ANDROID_SERVICE_NODE_TYPES
|
|
5
|
+
from constants import ANDROID_SERVICE_NODE_TYPES, AI_AGENT_TYPES
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from services.ai import AIService
|
|
@@ -16,14 +16,15 @@ async def _collect_agent_connections(
|
|
|
16
16
|
context: Dict[str, Any],
|
|
17
17
|
database: "Database",
|
|
18
18
|
log_prefix: str = "[Agent]"
|
|
19
|
-
) -> Tuple[Optional[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]], Optional[Dict[str, Any]]]:
|
|
20
|
-
"""Shared logic for collecting memory, skill, tool, and
|
|
19
|
+
) -> Tuple[Optional[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]], Optional[Dict[str, Any]], Optional[Dict[str, Any]]]:
|
|
20
|
+
"""Shared logic for collecting memory, skill, tool, input, and task data from connected nodes.
|
|
21
21
|
|
|
22
22
|
Scans edges targeting the given node and collects:
|
|
23
23
|
- Memory data from input-memory handle (simpleMemory nodes)
|
|
24
24
|
- Skill data from input-skill handle (skill nodes)
|
|
25
25
|
- Tool data from input-tools handle (tool nodes, including androidTool Sub-Node pattern)
|
|
26
26
|
- Input data from input-main or input-chat handle (for auto-prompting fallback)
|
|
27
|
+
- Task data from input-task handle (taskTrigger nodes for delegated task results)
|
|
27
28
|
|
|
28
29
|
Args:
|
|
29
30
|
node_id: The agent node ID to collect connections for
|
|
@@ -32,7 +33,7 @@ async def _collect_agent_connections(
|
|
|
32
33
|
log_prefix: Prefix for log messages (e.g., "[AI Agent]" or "[Chat Agent]")
|
|
33
34
|
|
|
34
35
|
Returns:
|
|
35
|
-
Tuple of (memory_data, skill_data, tool_data, input_data)
|
|
36
|
+
Tuple of (memory_data, skill_data, tool_data, input_data, task_data)
|
|
36
37
|
"""
|
|
37
38
|
nodes = context.get('nodes')
|
|
38
39
|
edges = context.get('edges')
|
|
@@ -42,11 +43,12 @@ async def _collect_agent_connections(
|
|
|
42
43
|
skill_data: List[Dict[str, Any]] = []
|
|
43
44
|
tool_data: List[Dict[str, Any]] = []
|
|
44
45
|
input_data: Optional[Dict[str, Any]] = None
|
|
46
|
+
task_data: Optional[Dict[str, Any]] = None
|
|
45
47
|
|
|
46
48
|
logger.info(f"{log_prefix} Processing node {node_id}, edges={len(edges) if edges else 0}, nodes={len(nodes) if nodes else 0}, workflow_id={workflow_id}")
|
|
47
49
|
|
|
48
50
|
if not edges or not nodes:
|
|
49
|
-
return memory_data, skill_data, tool_data, input_data
|
|
51
|
+
return memory_data, skill_data, tool_data, input_data, task_data
|
|
50
52
|
|
|
51
53
|
# Log incoming edges for debugging
|
|
52
54
|
incoming_edges = [e for e in edges if e.get('target') == node_id]
|
|
@@ -201,6 +203,45 @@ async def _collect_agent_connections(
|
|
|
201
203
|
tool_entry['connected_services'] = connected_services
|
|
202
204
|
logger.debug(f"{log_prefix} Android toolkit has {len(connected_services)} connected services")
|
|
203
205
|
|
|
206
|
+
# Special handling for AI Agent nodes - discover their connected tools
|
|
207
|
+
# This allows parent agent to know child agent's capabilities
|
|
208
|
+
if tool_type in AI_AGENT_TYPES:
|
|
209
|
+
child_tools = []
|
|
210
|
+
|
|
211
|
+
# Count edges targeting this child agent
|
|
212
|
+
child_incoming_edges = [e for e in edges if e.get('target') == source_node_id]
|
|
213
|
+
child_tool_edges = [e for e in child_incoming_edges if e.get('targetHandle') == 'input-tools']
|
|
214
|
+
logger.debug(f"{log_prefix} Child agent {source_node_id}: {len(child_incoming_edges)} incoming edges, {len(child_tool_edges)} input-tools edges")
|
|
215
|
+
|
|
216
|
+
# Log all incoming edge handles for debugging
|
|
217
|
+
if child_incoming_edges:
|
|
218
|
+
handles = [e.get('targetHandle', 'None') for e in child_incoming_edges]
|
|
219
|
+
logger.debug(f"{log_prefix} Child agent {source_node_id} incoming handles: {handles}")
|
|
220
|
+
|
|
221
|
+
# Scan edges for tools connected to this child agent's input-tools handle
|
|
222
|
+
for child_edge in edges:
|
|
223
|
+
if child_edge.get('target') != source_node_id:
|
|
224
|
+
continue
|
|
225
|
+
if child_edge.get('targetHandle') != 'input-tools':
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
child_tool_id = child_edge.get('source')
|
|
229
|
+
child_tool_node = next((n for n in nodes if n.get('id') == child_tool_id), None)
|
|
230
|
+
|
|
231
|
+
logger.debug(f"{log_prefix} Child agent {source_node_id}: tool edge from {child_tool_id}, node found: {child_tool_node is not None}")
|
|
232
|
+
|
|
233
|
+
if child_tool_node:
|
|
234
|
+
child_tool_type = child_tool_node.get('type', '')
|
|
235
|
+
child_tool_label = child_tool_node.get('data', {}).get('label', child_tool_type)
|
|
236
|
+
child_tools.append({
|
|
237
|
+
'node_type': child_tool_type,
|
|
238
|
+
'label': child_tool_label
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
if child_tools:
|
|
242
|
+
tool_entry['child_tools'] = child_tools
|
|
243
|
+
logger.debug(f"{log_prefix} Child agent {source_node_id} has tools: {[t['label'] for t in child_tools]}")
|
|
244
|
+
|
|
204
245
|
tool_data.append(tool_entry)
|
|
205
246
|
logger.debug(f"{log_prefix} Connected tool: {tool_type}")
|
|
206
247
|
|
|
@@ -212,14 +253,90 @@ async def _collect_agent_connections(
|
|
|
212
253
|
input_data = source_output
|
|
213
254
|
logger.debug(f"{log_prefix} Input from {source_node.get('type')}: {list(source_output.keys()) if isinstance(source_output, dict) else type(source_output)}")
|
|
214
255
|
|
|
256
|
+
# Task data detection - taskTrigger nodes connected to input-task handle
|
|
257
|
+
# Used to receive results from delegated child agents
|
|
258
|
+
elif target_handle == 'input-task':
|
|
259
|
+
logger.info(f"{log_prefix} Found input-task edge from {source_node_id} (type={source_node.get('type')})")
|
|
260
|
+
|
|
261
|
+
# Try context.outputs first (parallel executor), then database via get_output_fn
|
|
262
|
+
source_output = context.get('outputs', {}).get(source_node_id)
|
|
263
|
+
logger.info(f"{log_prefix} Context outputs check for {source_node_id}: {source_output is not None}")
|
|
264
|
+
|
|
265
|
+
if not source_output:
|
|
266
|
+
# Database is source of truth - use get_output_fn to retrieve stored output
|
|
267
|
+
get_output_fn = context.get('get_output_fn')
|
|
268
|
+
session_id = context.get('session_id', 'default')
|
|
269
|
+
if get_output_fn:
|
|
270
|
+
try:
|
|
271
|
+
source_output = await get_output_fn(session_id, source_node_id, 'output_0')
|
|
272
|
+
logger.info(f"{log_prefix} DB lookup for {source_node_id}: {source_output is not None}")
|
|
273
|
+
except Exception as e:
|
|
274
|
+
logger.warning(f"{log_prefix} Failed to get output from DB: {e}")
|
|
275
|
+
else:
|
|
276
|
+
logger.warning(f"{log_prefix} No get_output_fn in context, cannot retrieve task output")
|
|
277
|
+
|
|
278
|
+
logger.info(f"{log_prefix} Source output for {source_node_id}: {source_output is not None}, type={type(source_output).__name__ if source_output else 'None'}")
|
|
279
|
+
if source_output:
|
|
280
|
+
# Handle nested result structure - taskTrigger may return {"result": {...}} or flat dict
|
|
281
|
+
if isinstance(source_output, dict) and 'result' in source_output and isinstance(source_output.get('result'), dict):
|
|
282
|
+
# Nested structure - extract inner result
|
|
283
|
+
task_data = source_output.get('result')
|
|
284
|
+
logger.info(f"{log_prefix} Extracted nested task_data from result key")
|
|
285
|
+
else:
|
|
286
|
+
task_data = source_output
|
|
287
|
+
logger.info(f"{log_prefix} Task completion data: task_id={task_data.get('task_id')}, status={task_data.get('status')}, agent_name={task_data.get('agent_name')}")
|
|
288
|
+
|
|
215
289
|
# Log collection results
|
|
216
|
-
logger.info(f"{log_prefix} Collected: {len(skill_data)} skills, {len(tool_data)} tools, memory={'yes' if memory_data else 'no'}, input={'yes' if input_data else 'no'}")
|
|
290
|
+
logger.info(f"{log_prefix} Collected: {len(skill_data)} skills, {len(tool_data)} tools, memory={'yes' if memory_data else 'no'}, input={'yes' if input_data else 'no'}, task={'yes' if task_data else 'no'}")
|
|
217
291
|
for sd in skill_data:
|
|
218
292
|
logger.debug(f"{log_prefix} Skill: type={sd.get('node_type')}, label={sd.get('label')}")
|
|
219
293
|
for td in tool_data:
|
|
220
294
|
logger.info(f"{log_prefix} Tool: type={td.get('node_type')}, node_id={td.get('node_id')}")
|
|
221
295
|
|
|
222
|
-
return memory_data, skill_data, tool_data, input_data
|
|
296
|
+
return memory_data, skill_data, tool_data, input_data, task_data
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _format_task_context(task_data: Dict[str, Any]) -> str:
|
|
300
|
+
"""Format task completion data as context for the agent.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
task_data: Task completion data from taskTrigger node
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Formatted string to prepend to agent prompt
|
|
307
|
+
"""
|
|
308
|
+
status = task_data.get('status', 'unknown')
|
|
309
|
+
agent_name = task_data.get('agent_name', 'Unknown Agent')
|
|
310
|
+
task_id = task_data.get('task_id', '')
|
|
311
|
+
|
|
312
|
+
if status == 'completed':
|
|
313
|
+
result = task_data.get('result', 'No result provided')
|
|
314
|
+
return f"""A delegated task has completed:
|
|
315
|
+
- Agent: {agent_name}
|
|
316
|
+
- Task ID: {task_id}
|
|
317
|
+
- Status: Completed Successfully
|
|
318
|
+
- Result: {result}
|
|
319
|
+
|
|
320
|
+
IMPORTANT: This task is COMPLETE. Do NOT delegate or call any agent tools.
|
|
321
|
+
Simply report this result to the user in a natural, conversational way."""
|
|
322
|
+
|
|
323
|
+
elif status == 'error':
|
|
324
|
+
error = task_data.get('error', 'Unknown error')
|
|
325
|
+
return f"""A delegated task has failed:
|
|
326
|
+
- Agent: {agent_name}
|
|
327
|
+
- Task ID: {task_id}
|
|
328
|
+
- Status: Error
|
|
329
|
+
- Error: {error}
|
|
330
|
+
|
|
331
|
+
IMPORTANT: This task has FAILED. Do NOT retry or delegate again.
|
|
332
|
+
Report this error to the user and suggest next steps if appropriate."""
|
|
333
|
+
|
|
334
|
+
else:
|
|
335
|
+
return f"""Task update received:
|
|
336
|
+
- Agent: {agent_name}
|
|
337
|
+
- Task ID: {task_id}
|
|
338
|
+
- Status: {status}
|
|
339
|
+
- Data: {task_data}"""
|
|
223
340
|
|
|
224
341
|
|
|
225
342
|
async def handle_ai_agent(
|
|
@@ -245,11 +362,29 @@ async def handle_ai_agent(
|
|
|
245
362
|
"""
|
|
246
363
|
workflow_id = context.get('workflow_id')
|
|
247
364
|
|
|
248
|
-
# Collect connected memory, skill, tool, and
|
|
249
|
-
memory_data, skill_data, tool_data, input_data = await _collect_agent_connections(
|
|
365
|
+
# Collect connected memory, skill, tool, input, and task nodes using shared base function
|
|
366
|
+
memory_data, skill_data, tool_data, input_data, task_data = await _collect_agent_connections(
|
|
250
367
|
node_id, context, database, log_prefix="[AI Agent]"
|
|
251
368
|
)
|
|
252
369
|
|
|
370
|
+
# If task data is present, format it as context for the agent
|
|
371
|
+
if task_data:
|
|
372
|
+
task_context = _format_task_context(task_data)
|
|
373
|
+
original_prompt = parameters.get('prompt', '')
|
|
374
|
+
parameters = {**parameters, 'prompt': f"{task_context}\n\n{original_prompt}"}
|
|
375
|
+
logger.info(f"[AI Agent] Task context injected for task_id={task_data.get('task_id')}")
|
|
376
|
+
|
|
377
|
+
# CRITICAL FIX: Strip ALL tools when handling task completion
|
|
378
|
+
# When reporting a delegated task result, the agent should NOT use any tools.
|
|
379
|
+
# Binding tools while instructing "do not use tools" confuses Gemini (returns empty []).
|
|
380
|
+
# The agent's only job is to report the result naturally.
|
|
381
|
+
task_status = task_data.get('status', '')
|
|
382
|
+
if task_status in ('completed', 'error') and tool_data:
|
|
383
|
+
original_tool_count = len(tool_data)
|
|
384
|
+
# Strip ALL tools - agent is just reporting result, not executing anything
|
|
385
|
+
tool_data = []
|
|
386
|
+
logger.info(f"[AI Agent] Stripped ALL {original_tool_count} tools for task completion handling")
|
|
387
|
+
|
|
253
388
|
# Auto-use input data if prompt is empty (fallback for trigger nodes)
|
|
254
389
|
if not parameters.get('prompt') and input_data:
|
|
255
390
|
prompt = (
|
|
@@ -306,11 +441,29 @@ async def handle_chat_agent(
|
|
|
306
441
|
"""
|
|
307
442
|
workflow_id = context.get('workflow_id')
|
|
308
443
|
|
|
309
|
-
# Collect connected memory, skill, tool, and
|
|
310
|
-
memory_data, skill_data, tool_data, input_data = await _collect_agent_connections(
|
|
444
|
+
# Collect connected memory, skill, tool, input, and task nodes using shared base function
|
|
445
|
+
memory_data, skill_data, tool_data, input_data, task_data = await _collect_agent_connections(
|
|
311
446
|
node_id, context, database, log_prefix="[Chat Agent]"
|
|
312
447
|
)
|
|
313
448
|
|
|
449
|
+
# If task data is present, format it as context for the agent
|
|
450
|
+
if task_data:
|
|
451
|
+
task_context = _format_task_context(task_data)
|
|
452
|
+
original_prompt = parameters.get('prompt', '')
|
|
453
|
+
parameters = {**parameters, 'prompt': f"{task_context}\n\n{original_prompt}"}
|
|
454
|
+
logger.info(f"[Chat Agent] Task context injected for task_id={task_data.get('task_id')}")
|
|
455
|
+
|
|
456
|
+
# CRITICAL FIX: Strip ALL tools when handling task completion
|
|
457
|
+
# When reporting a delegated task result, the agent should NOT use any tools.
|
|
458
|
+
# Binding tools while instructing "do not use tools" confuses Gemini (returns empty []).
|
|
459
|
+
# The agent's only job is to report the result naturally.
|
|
460
|
+
task_status = task_data.get('status', '')
|
|
461
|
+
if task_status in ('completed', 'error') and tool_data:
|
|
462
|
+
original_tool_count = len(tool_data)
|
|
463
|
+
# Strip ALL tools - agent is just reporting result, not executing anything
|
|
464
|
+
tool_data = []
|
|
465
|
+
logger.info(f"[Chat Agent] Stripped ALL {original_tool_count} tools for task completion handling")
|
|
466
|
+
|
|
314
467
|
# Auto-use input data if prompt is empty (fallback for trigger nodes)
|
|
315
468
|
if not parameters.get('prompt') and input_data:
|
|
316
469
|
prompt = (
|
|
@@ -370,7 +370,10 @@ async def handle_embedding_generator(
|
|
|
370
370
|
logger.info("[embeddingGenerator] Starting", node_id=node_id, texts=len(texts), provider=provider)
|
|
371
371
|
|
|
372
372
|
if provider == 'huggingface':
|
|
373
|
-
|
|
373
|
+
try:
|
|
374
|
+
from langchain_huggingface import HuggingFaceEmbeddings
|
|
375
|
+
except ImportError:
|
|
376
|
+
raise ImportError("HuggingFace embeddings not available. Install with: pip install langchain-huggingface sentence-transformers")
|
|
374
377
|
embedder = HuggingFaceEmbeddings(model_name=model)
|
|
375
378
|
elif provider == 'openai':
|
|
376
379
|
from langchain_openai import OpenAIEmbeddings
|
|
@@ -448,7 +451,10 @@ async def handle_vector_store(
|
|
|
448
451
|
|
|
449
452
|
async def _chroma_op(operation: str, params: Dict, collection: str) -> Dict:
|
|
450
453
|
"""ChromaDB operations."""
|
|
451
|
-
|
|
454
|
+
try:
|
|
455
|
+
import chromadb
|
|
456
|
+
except ImportError:
|
|
457
|
+
raise ImportError("ChromaDB not available. Install with: pip install chromadb")
|
|
452
458
|
import uuid
|
|
453
459
|
|
|
454
460
|
persist_dir = params.get('persistDir', './data/vectors')
|
|
@@ -500,8 +506,11 @@ async def _chroma_op(operation: str, params: Dict, collection: str) -> Dict:
|
|
|
500
506
|
|
|
501
507
|
async def _qdrant_op(operation: str, params: Dict, collection: str) -> Dict:
|
|
502
508
|
"""Qdrant operations."""
|
|
503
|
-
|
|
504
|
-
|
|
509
|
+
try:
|
|
510
|
+
from qdrant_client import QdrantClient
|
|
511
|
+
from qdrant_client.models import VectorParams, Distance, PointStruct
|
|
512
|
+
except ImportError:
|
|
513
|
+
raise ImportError("Qdrant client not available. Install with: pip install qdrant-client")
|
|
505
514
|
import uuid
|
|
506
515
|
|
|
507
516
|
url = params.get('qdrantUrl', 'http://localhost:6333')
|