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.
Files changed (72) hide show
  1. package/.env.template +16 -0
  2. package/client/package.json +1 -1
  3. package/client/src/Dashboard.tsx +3 -3
  4. package/client/src/components/AIAgentNode.tsx +24 -12
  5. package/client/src/components/OutputPanel.tsx +3 -2
  6. package/client/src/components/parameterPanel/InputSection.tsx +16 -3
  7. package/client/src/nodeDefinitions/aiAgentNodes.ts +12 -0
  8. package/client/src/nodeDefinitions/specializedAgentNodes.ts +68 -320
  9. package/client/src/nodeDefinitions/toolNodes.ts +87 -1
  10. package/client/src/nodeDefinitions/workflowNodes.ts +55 -1
  11. package/package.json +12 -3
  12. package/scripts/daemon.js +427 -0
  13. package/scripts/start.js +7 -1
  14. package/scripts/sync-version.js +108 -0
  15. package/server/Dockerfile +6 -7
  16. package/server/constants.py +2 -0
  17. package/server/core/cleanup.py +123 -0
  18. package/server/core/config.py +16 -0
  19. package/server/core/database.py +92 -1
  20. package/server/core/health.py +121 -0
  21. package/server/examples/__init__.py +1 -0
  22. package/server/gunicorn.conf.py +46 -0
  23. package/server/main.py +38 -3
  24. package/server/models/database.py +1 -0
  25. package/server/models/nodes.py +18 -2
  26. package/server/requirements-docker.txt +86 -0
  27. package/server/routers/database.py +16 -0
  28. package/server/routers/websocket.py +6 -5
  29. package/server/services/ai.py +115 -14
  30. package/server/services/auth.py +6 -1
  31. package/server/services/deployment/manager.py +14 -0
  32. package/server/services/event_waiter.py +55 -0
  33. package/server/services/example_loader.py +60 -0
  34. package/server/services/execution/executor.py +2 -0
  35. package/server/services/execution/models.py +8 -0
  36. package/server/services/handlers/__init__.py +2 -0
  37. package/server/services/handlers/ai.py +164 -11
  38. package/server/services/handlers/document.py +13 -4
  39. package/server/services/handlers/tools.py +445 -14
  40. package/server/services/node_executor.py +3 -0
  41. package/server/services/temporal/activities.py +3 -0
  42. package/server/services/workflow.py +2 -0
  43. package/server/skills/android_agent/app-launcher-skill/SKILL.md +137 -0
  44. package/server/skills/android_agent/app-list-skill/SKILL.md +148 -0
  45. package/server/skills/android_agent/audio-skill/SKILL.md +169 -0
  46. package/server/skills/android_agent/battery-skill/SKILL.md +114 -0
  47. package/server/skills/android_agent/bluetooth-skill/SKILL.md +151 -0
  48. package/server/skills/android_agent/camera-skill/SKILL.md +148 -0
  49. package/server/skills/android_agent/environmental-skill/SKILL.md +140 -0
  50. package/server/skills/android_agent/location-skill/SKILL.md +163 -0
  51. package/server/skills/android_agent/motion-skill/SKILL.md +141 -0
  52. package/server/skills/android_agent/screen-control-skill/SKILL.md +164 -0
  53. package/server/skills/android_agent/wifi-skill/SKILL.md +182 -0
  54. package/server/skills/assistant/subagent-skill/SKILL.md +205 -0
  55. package/server/skills/coding_agent/javascript-skill/SKILL.md +196 -0
  56. package/server/skills/coding_agent/python-skill/SKILL.md +165 -0
  57. package/server/skills/social_agent/whatsapp-db-skill/SKILL.md +284 -0
  58. package/server/skills/social_agent/whatsapp-send-skill/SKILL.md +180 -0
  59. package/server/skills/task_agent/cron-scheduler-skill/SKILL.md +215 -0
  60. package/server/skills/task_agent/task-manager-skill/SKILL.md +251 -0
  61. package/server/skills/task_agent/timer-skill/SKILL.md +168 -0
  62. package/server/skills/travel_agent/geocoding-skill/SKILL.md +186 -0
  63. package/server/skills/travel_agent/nearby-places-skill/SKILL.md +234 -0
  64. package/server/skills/web_agent/http-request-skill/SKILL.md +211 -0
  65. package/server/skills/android/skill/SKILL.md +0 -84
  66. package/server/skills/assistant/code-skill/SKILL.md +0 -176
  67. package/server/skills/assistant/http-skill/SKILL.md +0 -163
  68. package/server/skills/assistant/maps-skill/SKILL.md +0 -172
  69. package/server/skills/assistant/scheduler-skill/SKILL.md +0 -86
  70. package/server/skills/assistant/whatsapp-skill/SKILL.md +0 -285
  71. /package/server/skills/{android → android_agent}/personality/SKILL.md +0 -0
  72. /package/server/skills/{assistant → web_agent}/web-search-skill/SKILL.md +0 -0
@@ -244,6 +244,7 @@ async def handle_execute_node(data: Dict[str, Any], websocket: WebSocket) -> Dic
244
244
  nodes=data.get("nodes", []), edges=data.get("edges", []),
245
245
  session_id=data.get("session_id", "default"),
246
246
  workflow_id=workflow_id,
247
+ outputs=data.get("outputs", {}), # Upstream node outputs for data flow
247
248
  )
248
249
 
249
250
  if result.get("success"):
@@ -628,20 +629,20 @@ async def handle_deploy_workflow(data: Dict[str, Any], websocket: WebSocket) ->
628
629
  session_id = data.get("session_id", "default")
629
630
 
630
631
  # DEBUG: Log received edges to trace tool connection issues
631
- logger.info(f"[Deploy] Received {len(edges)} edges for workflow {workflow_id}")
632
+ logger.debug(f"[Deploy] Received {len(edges)} edges for workflow {workflow_id}")
632
633
  for e in edges:
633
634
  target_handle = e.get('targetHandle')
634
635
  if target_handle and target_handle.startswith('input-') and target_handle != 'input-main':
635
- logger.info(f"[Deploy] Config edge: {e.get('source')} -> {e.get('target')} (handle={target_handle})")
636
+ logger.debug(f"[Deploy] Config edge: {e.get('source')} -> {e.get('target')} (handle={target_handle})")
636
637
 
637
638
  # Check for tool connections to AI Agent
638
639
  tool_edges = [e for e in edges if e.get('targetHandle') == 'input-tools']
639
640
  if tool_edges:
640
- logger.info(f"[Deploy] Tool edges found: {len(tool_edges)}")
641
+ logger.debug(f"[Deploy] Tool edges found: {len(tool_edges)}")
641
642
  for te in tool_edges:
642
- logger.info(f"[Deploy] Tool edge: source={te.get('source')} -> target={te.get('target')}")
643
+ logger.debug(f"[Deploy] Tool edge: source={te.get('source')} -> target={te.get('target')}")
643
644
  else:
644
- logger.info(f"[Deploy] No input-tools edges found")
645
+ logger.debug(f"[Deploy] No input-tools edges found")
645
646
 
646
647
  if not nodes:
647
648
  return {"success": False, "error": "No nodes provided"}
@@ -1402,6 +1402,20 @@ class AIService:
1402
1402
 
1403
1403
  logger.debug(f"[LangGraph] Built {len(tools)} tools")
1404
1404
 
1405
+ # Auto-inject check_delegated_tasks tool when delegation tools present
1406
+ if any(name.startswith('delegate_to_') for name in tool_configs):
1407
+ check_info = {
1408
+ 'node_type': '_builtin_check_delegated_tasks',
1409
+ 'node_id': f'{node_id}_check_tasks',
1410
+ 'parameters': {},
1411
+ 'label': 'Check Delegated Tasks',
1412
+ }
1413
+ check_tool, check_config = await self._build_tool_from_node(check_info)
1414
+ if check_tool:
1415
+ tools.append(check_tool)
1416
+ tool_configs[check_tool.name] = check_config
1417
+ logger.debug(f"[LangGraph] Auto-injected check_delegated_tasks tool")
1418
+
1405
1419
  # Create tool executor callback
1406
1420
  async def tool_executor(tool_name: str, tool_args: Dict) -> Any:
1407
1421
  """Execute a tool by name."""
@@ -1438,6 +1452,7 @@ class AIService:
1438
1452
  # These allow child agents to execute with their own tools
1439
1453
  config['ai_service'] = self
1440
1454
  config['database'] = self.database
1455
+ config['parent_node_id'] = node_id # For delegation result tracking
1441
1456
  if context:
1442
1457
  config['nodes'] = context.get('nodes', [])
1443
1458
  config['edges'] = context.get('edges', [])
@@ -1709,6 +1724,20 @@ class AIService:
1709
1724
 
1710
1725
  logger.debug(f"[ChatAgent] Built {len(all_tools)} tools from tool_data")
1711
1726
 
1727
+ # Auto-inject check_delegated_tasks tool when delegation tools present
1728
+ if any(name.startswith('delegate_to_') for name in tool_node_configs):
1729
+ check_info = {
1730
+ 'node_type': '_builtin_check_delegated_tasks',
1731
+ 'node_id': f'{node_id}_check_tasks',
1732
+ 'parameters': {},
1733
+ 'label': 'Check Delegated Tasks',
1734
+ }
1735
+ check_tool, check_config = await self._build_tool_from_node(check_info)
1736
+ if check_tool:
1737
+ all_tools.append(check_tool)
1738
+ tool_node_configs[check_tool.name] = check_config
1739
+ logger.debug(f"[ChatAgent] Auto-injected check_delegated_tasks tool")
1740
+
1712
1741
  logger.debug(f"[ChatAgent] Total tools available: {len(all_tools)}")
1713
1742
  # Debug: log all tool schemas to verify they're correct
1714
1743
  for t in all_tools:
@@ -1861,6 +1890,7 @@ class AIService:
1861
1890
  # These allow child agents to execute with their own tools
1862
1891
  config['ai_service'] = self
1863
1892
  config['database'] = self.database
1893
+ config['parent_node_id'] = node_id # For delegation result tracking
1864
1894
  if context:
1865
1895
  config['nodes'] = context.get('nodes', [])
1866
1896
  config['edges'] = context.get('edges', [])
@@ -2062,8 +2092,12 @@ class AIService:
2062
2092
  'whatsappDb': 'whatsapp_db',
2063
2093
  'gmaps_locations': 'geocode',
2064
2094
  'gmaps_nearby_places': 'nearby_places',
2095
+ 'taskManager': 'task_manager',
2065
2096
  'timer': 'timer',
2066
2097
  'cronScheduler': 'cron_scheduler',
2098
+ # Built-in check tool for delegation results
2099
+ '_builtin_check_delegated_tasks': 'check_delegated_tasks',
2100
+ # Agent delegation tools
2067
2101
  'aiAgent': 'delegate_to_ai_agent',
2068
2102
  'chatAgent': 'delegate_to_chat_agent',
2069
2103
  'android_agent': 'delegate_to_android_agent',
@@ -2105,20 +2139,24 @@ class AIService:
2105
2139
  'whatsappDb': 'Query WhatsApp database - list contacts, search groups, get contact/group info, retrieve chat history.',
2106
2140
  'gmaps_locations': 'Geocode addresses to coordinates or reverse geocode coordinates to addresses using Google Maps.',
2107
2141
  'gmaps_nearby_places': 'Search for nearby places (restaurants, hospitals, banks, etc.) using Google Maps Places API.',
2142
+ 'taskManager': 'Track delegated sub-agent tasks. Operations: list_tasks (see all tasks), get_task (check specific task status/result), mark_done (cleanup completed tasks).',
2108
2143
  'timer': 'Wait/sleep for a specified duration. Specify duration (1-3600) and unit (seconds, minutes, or hours). Returns timestamp and elapsed time after waiting.',
2109
2144
  'cronScheduler': 'Schedule a delayed or recurring execution. Supports seconds, minutes, hours, daily, weekly, monthly frequencies with timezone. Use frequency to set schedule type, then set the relevant interval/time parameters.',
2110
- 'aiAgent': 'Delegate a task to another AI Agent. The agent works independently without blocking.',
2111
- 'chatAgent': 'Delegate a task to a Chat Agent. The agent works independently without blocking.',
2112
- 'android_agent': 'Delegate a task to an Android Control Agent for device automation. Works independently.',
2113
- 'coding_agent': 'Delegate a task to a Coding Agent for code execution. Works independently.',
2114
- 'web_agent': 'Delegate a task to a Web Control Agent for web automation. Works independently.',
2115
- 'task_agent': 'Delegate a task to a Task Management Agent for task automation. Works independently.',
2116
- 'social_agent': 'Delegate a task to a Social Media Agent for social messaging. Works independently.',
2117
- 'travel_agent': 'Delegate a task to a Travel Agent for travel planning and itinerary building. Works independently.',
2118
- 'tool_agent': 'Delegate a task to a Tool Agent for multi-tool orchestration and complex task execution. Works independently.',
2119
- 'productivity_agent': 'Delegate a task to a Productivity Agent for scheduling, reminders, and workflow automation. Works independently.',
2120
- 'payments_agent': 'Delegate a task to a Payments Agent for payment processing, invoice generation, and financial operations. Works independently.',
2121
- 'consumer_agent': 'Delegate a task to a Consumer Agent for customer support, product recommendations, and order management. Works independently.',
2145
+ # Built-in check tool for delegation results
2146
+ '_builtin_check_delegated_tasks': 'Check status and retrieve results of previously delegated tasks.',
2147
+ # Agent delegation tools - ONE-SHOT fire-and-forget pattern
2148
+ 'aiAgent': 'ONE-SHOT delegation to AI Agent. Call ONCE per task, returns task_id immediately. Agent works in background - do NOT re-call.',
2149
+ 'chatAgent': 'ONE-SHOT delegation to Chat Agent. Call ONCE per task, returns task_id immediately. Agent works in background - do NOT re-call.',
2150
+ 'android_agent': 'ONE-SHOT delegation to Android Agent. Call ONCE per task, returns task_id. Agent works in background - do NOT re-call.',
2151
+ 'coding_agent': 'ONE-SHOT delegation to Coding Agent. Call ONCE per task, returns task_id. Agent works in background - do NOT re-call.',
2152
+ 'web_agent': 'ONE-SHOT delegation to Web Agent. Call ONCE per task, returns task_id. Agent works in background - do NOT re-call.',
2153
+ 'task_agent': 'ONE-SHOT delegation to Task Agent. Call ONCE per task, returns task_id. Agent works in background - do NOT re-call.',
2154
+ 'social_agent': 'ONE-SHOT delegation to Social Agent. Call ONCE per task, returns task_id. Agent works in background - do NOT re-call.',
2155
+ 'travel_agent': 'ONE-SHOT delegation to Travel Agent. Call ONCE per task, returns task_id. Agent works in background - do NOT re-call.',
2156
+ 'tool_agent': 'ONE-SHOT delegation to Tool Agent. Call ONCE per task, returns task_id. Agent works in background - do NOT re-call.',
2157
+ 'productivity_agent': 'ONE-SHOT delegation to Productivity Agent. Call ONCE per task, returns task_id. Agent works in background - do NOT re-call.',
2158
+ 'payments_agent': 'ONE-SHOT delegation to Payments Agent. Call ONCE per task, returns task_id. Agent works in background - do NOT re-call.',
2159
+ 'consumer_agent': 'ONE-SHOT delegation to Consumer Agent. Call ONCE per task, returns task_id. Agent works in background - do NOT re-call.',
2122
2160
  # Android service nodes (direct tool usage)
2123
2161
  'batteryMonitor': 'Monitor Android battery status, level, charging state, temperature, and health.',
2124
2162
  'networkMonitor': 'Monitor Android network connectivity, type, and internet availability.',
@@ -2174,6 +2212,29 @@ class AIService:
2174
2212
  service_names = [s.get('label') or s.get('service_id', 'unknown') for s in connected_services]
2175
2213
  tool_description = f"{tool_description} Connected: {', '.join(service_names)}"
2176
2214
 
2215
+ # For AI Agent nodes, enhance description with child agent's tool capabilities
2216
+ # This allows parent agent to know what the child agent can do
2217
+ from constants import AI_AGENT_TYPES
2218
+ if node_type in AI_AGENT_TYPES:
2219
+ child_tools = tool_info.get('child_tools', [])
2220
+ if child_tools:
2221
+ # Build capability description from child's connected tools
2222
+ capability_descriptions = []
2223
+ for child_tool in child_tools:
2224
+ child_type = child_tool.get('node_type', '')
2225
+ child_label = child_tool.get('label', child_type)
2226
+ # Get the tool description from DEFAULT_TOOL_DESCRIPTIONS
2227
+ child_desc = DEFAULT_TOOL_DESCRIPTIONS.get(child_type, f"Use {child_label}")
2228
+ capability_descriptions.append(f"- {child_label}: {child_desc}")
2229
+
2230
+ capabilities_text = "\n".join(capability_descriptions)
2231
+ tool_description = (
2232
+ f"Delegate tasks to '{node_label}' agent. "
2233
+ f"This agent has the following capabilities:\n{capabilities_text}\n"
2234
+ f"Call ONCE per task, returns task_id. Agent works in background."
2235
+ )
2236
+ logger.info(f"[LangGraph] Enhanced tool description for {node_type} with {len(child_tools)} child tools")
2237
+
2177
2238
  # Clean tool name (LangChain requires alphanumeric + underscores)
2178
2239
  import re
2179
2240
  tool_name = re.sub(r'[^a-zA-Z0-9_]', '_', tool_name)
@@ -2615,6 +2676,45 @@ class AIService:
2615
2676
 
2616
2677
  return NearbyPlacesSchema
2617
2678
 
2679
+ # Task Manager schema (dual-purpose: AI tool + workflow node)
2680
+ if node_type == 'taskManager':
2681
+ class TaskManagerSchema(BaseModel):
2682
+ """Manage delegated tasks - list, check status, mark done.
2683
+
2684
+ Use this to track sub-agent tasks you've delegated.
2685
+ - list_tasks: See all active and completed tasks
2686
+ - get_task: Get details on a specific task
2687
+ - mark_done: Clean up a completed task from tracking
2688
+ """
2689
+ operation: str = Field(
2690
+ description="Operation: 'list_tasks', 'get_task', or 'mark_done'"
2691
+ )
2692
+ task_id: Optional[str] = Field(
2693
+ default=None,
2694
+ description="Task ID (required for get_task/mark_done)"
2695
+ )
2696
+ status_filter: Optional[str] = Field(
2697
+ default=None,
2698
+ description="Filter by status: 'running', 'completed', or 'error' (for list_tasks)"
2699
+ )
2700
+
2701
+ return TaskManagerSchema
2702
+
2703
+ # Check delegated tasks schema (built-in tool for result retrieval)
2704
+ if node_type == '_builtin_check_delegated_tasks':
2705
+ class CheckDelegatedTasksSchema(BaseModel):
2706
+ """Check on previously delegated tasks and retrieve their results.
2707
+
2708
+ Call this to see if delegated agents have completed their work.
2709
+ Returns status and results for each task.
2710
+ """
2711
+ task_ids: Optional[List[str]] = Field(
2712
+ default=None,
2713
+ description="Specific task IDs to check. Omit to get ALL delegated tasks."
2714
+ )
2715
+
2716
+ return CheckDelegatedTasksSchema
2717
+
2618
2718
  # AI Agent delegation schema (fire-and-forget async delegation)
2619
2719
  if node_type in ('aiAgent', 'chatAgent', 'android_agent', 'coding_agent', 'web_agent', 'task_agent', 'social_agent', 'travel_agent', 'tool_agent', 'productivity_agent', 'payments_agent', 'consumer_agent'):
2620
2720
  agent_label = params.get('label', node_type)
@@ -2622,8 +2722,9 @@ class AIService:
2622
2722
  class DelegateToAgentSchema(BaseModel):
2623
2723
  """Delegate a task to another AI Agent (non-blocking).
2624
2724
 
2625
- The child agent will work independently without blocking the parent.
2626
- Use this to assign subtasks to specialized agents.
2725
+ The child agent works independently in the background.
2726
+ Returns a task_id immediately. Use 'check_delegated_tasks'
2727
+ tool to check status and retrieve results when ready.
2627
2728
  """
2628
2729
  task: str = Field(
2629
2730
  description=f"The task/instruction to delegate to '{agent_label}'. Be specific about what you want done."
@@ -86,7 +86,12 @@ class AuthService:
86
86
  api_key_record = await self.database.get_api_key_by_provider(provider, session_id)
87
87
  if api_key_record and api_key_record.is_valid:
88
88
  # Check if not expired (30 days)
89
- if datetime.now(timezone.utc) - api_key_record.last_validated < timedelta(days=30):
89
+ # Ensure both datetimes are timezone-aware for comparison
90
+ # SQLite may return offset-naive datetimes
91
+ last_validated = api_key_record.last_validated
92
+ if last_validated.tzinfo is None:
93
+ last_validated = last_validated.replace(tzinfo=timezone.utc)
94
+ if datetime.now(timezone.utc) - last_validated < timedelta(days=30):
90
95
  return self.decrypt_api_key(api_key_record.key_encrypted)
91
96
 
92
97
  return None
@@ -629,6 +629,20 @@ class DeploymentManager:
629
629
  downstream_ids.add(source)
630
630
  logger.debug(f"[Deployment] Including sub-node {source} connected to toolkit {target}")
631
631
 
632
+ # Include tool nodes connected to AI Agent nodes (for capability discovery)
633
+ # When a child agent is included, we need its connected tools so the parent
634
+ # can discover what capabilities the child has
635
+ from constants import AI_AGENT_TYPES
636
+ agent_node_ids = {n['id'] for n in nodes if n.get('type') in AI_AGENT_TYPES and n['id'] in downstream_ids}
637
+ for edge in edges:
638
+ target = edge.get('target')
639
+ source = edge.get('source')
640
+ target_handle = edge.get('targetHandle', '')
641
+ # Include tool nodes connected to agent's input-tools handle
642
+ if target in agent_node_ids and target_handle == 'input-tools' and source not in downstream_ids:
643
+ downstream_ids.add(source)
644
+ logger.debug(f"[Deployment] Including tool node {source} connected to agent {target}")
645
+
632
646
  return [n for n in nodes if n['id'] in downstream_ids]
633
647
 
634
648
  # =========================================================================
@@ -105,6 +105,11 @@ TRIGGER_REGISTRY: Dict[str, TriggerConfig] = {
105
105
  event_type='chat_message_received',
106
106
  display_name='Chat Message'
107
107
  ),
108
+ 'taskTrigger': TriggerConfig(
109
+ node_type='taskTrigger',
110
+ event_type='task_completed',
111
+ display_name='Task Completed'
112
+ ),
108
113
  # Future triggers - just add to registry:
109
114
  # 'emailTrigger': TriggerConfig('emailTrigger', 'email_received', 'Email'),
110
115
  # 'mqttTrigger': TriggerConfig('mqttTrigger', 'mqtt_message', 'MQTT Message'),
@@ -282,11 +287,61 @@ def build_chat_filter(params: Dict) -> Callable[[Dict], bool]:
282
287
  return matches
283
288
 
284
289
 
290
+ def build_task_completed_filter(params: Dict) -> Callable[[Dict], bool]:
291
+ """Build filter function for task completed events.
292
+
293
+ Filters by:
294
+ - task_id: Specific task ID to watch (optional)
295
+ - agent_name: Filter by child agent name (optional)
296
+ - status_filter: 'all', 'completed', 'error' (default: 'all')
297
+ - parent_node_id: Filter by parent node (optional, for scoping)
298
+
299
+ Args:
300
+ params: Node parameters
301
+
302
+ Returns:
303
+ Filter function that checks if event matches criteria
304
+ """
305
+ task_id_filter = params.get('task_id', '')
306
+ agent_name_filter = params.get('agent_name', '')
307
+ status_filter = params.get('status_filter', 'all') # all, completed, error
308
+ parent_node_id = params.get('parent_node_id', '')
309
+
310
+ def matches(data: Dict) -> bool:
311
+ # Task ID filter (exact match if specified)
312
+ if task_id_filter:
313
+ if data.get('task_id') != task_id_filter:
314
+ return False
315
+
316
+ # Agent name filter (contains match)
317
+ if agent_name_filter:
318
+ event_agent = data.get('agent_name', '')
319
+ if agent_name_filter.lower() not in event_agent.lower():
320
+ return False
321
+
322
+ # Status filter
323
+ event_status = data.get('status', '')
324
+ if status_filter == 'completed' and event_status != 'completed':
325
+ return False
326
+ if status_filter == 'error' and event_status != 'error':
327
+ return False
328
+
329
+ # Parent node filter (for scoping to specific parent agent)
330
+ if parent_node_id:
331
+ if data.get('parent_node_id') != parent_node_id:
332
+ return False
333
+
334
+ return True
335
+
336
+ return matches
337
+
338
+
285
339
  # Registry of filter builders per trigger type
286
340
  FILTER_BUILDERS: Dict[str, Callable[[Dict], Callable[[Dict], bool]]] = {
287
341
  'whatsappReceive': build_whatsapp_filter,
288
342
  'webhookTrigger': build_webhook_filter,
289
343
  'chatTrigger': build_chat_filter,
344
+ 'taskTrigger': build_task_completed_filter,
290
345
  }
291
346
 
292
347
 
@@ -0,0 +1,60 @@
1
+ """Example workflow loader - reuses existing database.save_workflow()"""
2
+ import json
3
+ import logging
4
+ from pathlib import Path
5
+ from typing import List, Dict, Any
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ # Workflows folder at project root (parent of server/)
10
+ EXAMPLES_DIR = Path(__file__).parent.parent.parent / "workflows"
11
+
12
+
13
+ def get_example_workflows() -> List[Dict[str, Any]]:
14
+ """Load all example workflow JSON files from disk."""
15
+ examples = []
16
+ if not EXAMPLES_DIR.exists():
17
+ logger.warning(f"Examples directory not found: {EXAMPLES_DIR}")
18
+ return examples
19
+
20
+ for file in sorted(EXAMPLES_DIR.glob("*.json")):
21
+ try:
22
+ with open(file, encoding="utf-8") as f:
23
+ workflow = json.load(f)
24
+ workflow["_filename"] = file.name # Track source
25
+ examples.append(workflow)
26
+ logger.debug(f"Loaded example: {file.name}")
27
+ except (json.JSONDecodeError, IOError) as e:
28
+ logger.error(f"Failed to load {file}: {e}")
29
+
30
+ return examples
31
+
32
+
33
+ async def import_examples_for_user(database) -> int:
34
+ """Import all examples using existing database.save_workflow().
35
+
36
+ Returns count of workflows imported.
37
+ """
38
+ examples = get_example_workflows()
39
+ imported = 0
40
+
41
+ for example in examples:
42
+ # Use ID from JSON, prefixed with 'example_' for clarity
43
+ workflow_id = f"example_{example.get('id', 'unknown')}"
44
+
45
+ # Reuse existing save_workflow method
46
+ success = await database.save_workflow(
47
+ workflow_id=workflow_id,
48
+ name=example.get("name", "Example Workflow"),
49
+ description=example.get("description"),
50
+ data={
51
+ "nodes": example.get("nodes", []),
52
+ "edges": example.get("edges", [])
53
+ }
54
+ )
55
+
56
+ if success:
57
+ imported += 1
58
+ logger.info(f"Imported example: {example.get('name')}")
59
+
60
+ return imported
@@ -804,6 +804,7 @@ class WorkflowExecutor:
804
804
 
805
805
  # Build execution context for node handler
806
806
  # workflow_id is included for per-workflow status scoping (n8n pattern)
807
+ logger.info(f"[Executor] Building context for {node.node_id}, ctx.outputs keys: {list(ctx.outputs.keys())}")
807
808
  exec_context = {
808
809
  "nodes": ctx.nodes,
809
810
  "edges": ctx.edges,
@@ -813,6 +814,7 @@ class WorkflowExecutor:
813
814
  "start_time": node.started_at,
814
815
  "outputs": ctx.outputs, # Previous node outputs
815
816
  }
817
+ logger.info(f"[Executor] exec_context['outputs'] keys: {list(exec_context['outputs'].keys())}")
816
818
 
817
819
  # Call the actual node executor
818
820
  result = await self.node_executor(
@@ -13,6 +13,10 @@ from datetime import datetime
13
13
  from enum import Enum
14
14
  from typing import Dict, Any, List, Optional
15
15
 
16
+ from core.logging import get_logger
17
+
18
+ logger = get_logger(__name__)
19
+
16
20
 
17
21
  class TaskStatus(str, Enum):
18
22
  """Task execution states (Conductor-style lifecycle).
@@ -361,6 +365,8 @@ class ExecutionContext:
361
365
  if node.get("_pre_executed"):
362
366
  # Mark as COMPLETED with trigger output
363
367
  trigger_output = node.get("_trigger_output", {})
368
+ logger.info(f"[ExecutionContext] Pre-executed node found: {node_id} (type={node_type})")
369
+ logger.info(f"[ExecutionContext] Trigger output keys: {list(trigger_output.keys()) if trigger_output else 'empty'}")
364
370
  node_exec = NodeExecution(
365
371
  node_id=node_id,
366
372
  node_type=node_type,
@@ -369,6 +375,7 @@ class ExecutionContext:
369
375
  completed_at=time.time(),
370
376
  )
371
377
  ctx.outputs[node_id] = trigger_output
378
+ logger.info(f"[ExecutionContext] Set ctx.outputs[{node_id}] = trigger_output")
372
379
  ctx.checkpoints.append(node_id)
373
380
  else:
374
381
  node_exec = NodeExecution(
@@ -378,6 +385,7 @@ class ExecutionContext:
378
385
 
379
386
  ctx.node_executions[node_id] = node_exec
380
387
 
388
+ logger.info(f"[ExecutionContext] Created context with outputs: {list(ctx.outputs.keys())}")
381
389
  return ctx
382
390
 
383
391
  def get_node_status(self, node_id: str) -> Optional[TaskStatus]:
@@ -84,6 +84,7 @@ from .document import (
84
84
  # Tool execution handlers (for AI Agent tool calling)
85
85
  from .tools import (
86
86
  execute_tool,
87
+ handle_task_manager,
87
88
  )
88
89
 
89
90
  __all__ = [
@@ -129,4 +130,5 @@ __all__ = [
129
130
  'handle_vector_store',
130
131
  # Tools
131
132
  'execute_tool',
133
+ 'handle_task_manager',
132
134
  ]