machinaos 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.template +71 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/cli.js +159 -0
- package/client/.dockerignore +45 -0
- package/client/Dockerfile +68 -0
- package/client/eslint.config.js +29 -0
- package/client/index.html +13 -0
- package/client/nginx.conf +66 -0
- package/client/package.json +48 -0
- package/client/src/App.tsx +27 -0
- package/client/src/Dashboard.tsx +1173 -0
- package/client/src/ParameterPanel.tsx +301 -0
- package/client/src/components/AIAgentNode.tsx +321 -0
- package/client/src/components/APIKeyValidator.tsx +118 -0
- package/client/src/components/ClaudeChatModelNode.tsx +18 -0
- package/client/src/components/ConditionalEdge.tsx +189 -0
- package/client/src/components/CredentialsModal.tsx +306 -0
- package/client/src/components/EdgeConditionEditor.tsx +443 -0
- package/client/src/components/GeminiChatModelNode.tsx +18 -0
- package/client/src/components/GenericNode.tsx +357 -0
- package/client/src/components/LocationParameterPanel.tsx +154 -0
- package/client/src/components/ModelNode.tsx +286 -0
- package/client/src/components/OpenAIChatModelNode.tsx +18 -0
- package/client/src/components/OutputPanel.tsx +471 -0
- package/client/src/components/ParameterRenderer.tsx +1874 -0
- package/client/src/components/SkillEditorModal.tsx +417 -0
- package/client/src/components/SquareNode.tsx +797 -0
- package/client/src/components/StartNode.tsx +250 -0
- package/client/src/components/ToolkitNode.tsx +365 -0
- package/client/src/components/TriggerNode.tsx +463 -0
- package/client/src/components/auth/LoginPage.tsx +247 -0
- package/client/src/components/auth/ProtectedRoute.tsx +59 -0
- package/client/src/components/base/BaseChatModelNode.tsx +271 -0
- package/client/src/components/icons/AIProviderIcons.tsx +50 -0
- package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
- package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
- package/client/src/components/maps/index.ts +26 -0
- package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
- package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
- package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
- package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
- package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
- package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
- package/client/src/components/parameterPanel/index.ts +42 -0
- package/client/src/components/shared/DataPanel.tsx +142 -0
- package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
- package/client/src/components/ui/AIResultModal.tsx +204 -0
- package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
- package/client/src/components/ui/CodeEditor.tsx +81 -0
- package/client/src/components/ui/CollapsibleSection.tsx +88 -0
- package/client/src/components/ui/ComponentItem.tsx +154 -0
- package/client/src/components/ui/ComponentPalette.tsx +321 -0
- package/client/src/components/ui/ConsolePanel.tsx +1074 -0
- package/client/src/components/ui/ErrorBoundary.tsx +196 -0
- package/client/src/components/ui/InputNodesPanel.tsx +204 -0
- package/client/src/components/ui/MapSelector.tsx +314 -0
- package/client/src/components/ui/Modal.tsx +149 -0
- package/client/src/components/ui/NodeContextMenu.tsx +192 -0
- package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
- package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
- package/client/src/components/ui/SettingsPanel.tsx +243 -0
- package/client/src/components/ui/TopToolbar.tsx +736 -0
- package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
- package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
- package/client/src/config/antdTheme.ts +186 -0
- package/client/src/config/api.ts +54 -0
- package/client/src/contexts/AuthContext.tsx +221 -0
- package/client/src/contexts/ThemeContext.tsx +42 -0
- package/client/src/contexts/WebSocketContext.tsx +1971 -0
- package/client/src/factories/baseChatModelFactory.ts +256 -0
- package/client/src/hooks/useAndroidOperations.ts +164 -0
- package/client/src/hooks/useApiKeyValidation.ts +107 -0
- package/client/src/hooks/useApiKeys.ts +238 -0
- package/client/src/hooks/useAppTheme.ts +17 -0
- package/client/src/hooks/useComponentPalette.ts +51 -0
- package/client/src/hooks/useCopyPaste.ts +155 -0
- package/client/src/hooks/useDragAndDrop.ts +124 -0
- package/client/src/hooks/useDragVariable.ts +88 -0
- package/client/src/hooks/useExecution.ts +313 -0
- package/client/src/hooks/useParameterPanel.ts +176 -0
- package/client/src/hooks/useReactFlowNodes.ts +189 -0
- package/client/src/hooks/useToolSchema.ts +209 -0
- package/client/src/hooks/useWhatsApp.ts +196 -0
- package/client/src/hooks/useWorkflowManagement.ts +46 -0
- package/client/src/index.css +315 -0
- package/client/src/main.tsx +19 -0
- package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
- package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
- package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
- package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
- package/client/src/nodeDefinitions/chatNodes.ts +135 -0
- package/client/src/nodeDefinitions/codeNodes.ts +54 -0
- package/client/src/nodeDefinitions/documentNodes.ts +379 -0
- package/client/src/nodeDefinitions/index.ts +15 -0
- package/client/src/nodeDefinitions/locationNodes.ts +463 -0
- package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
- package/client/src/nodeDefinitions/skillNodes.ts +211 -0
- package/client/src/nodeDefinitions/toolNodes.ts +198 -0
- package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
- package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
- package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
- package/client/src/nodeDefinitions.ts +104 -0
- package/client/src/schemas/workflowSchema.ts +264 -0
- package/client/src/services/dynamicParameterService.ts +96 -0
- package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
- package/client/src/services/executionService.ts +232 -0
- package/client/src/services/workflowApi.ts +91 -0
- package/client/src/store/useAppStore.ts +582 -0
- package/client/src/styles/theme.ts +508 -0
- package/client/src/styles/zIndex.ts +17 -0
- package/client/src/types/ComponentTypes.ts +39 -0
- package/client/src/types/EdgeCondition.ts +231 -0
- package/client/src/types/INodeProperties.ts +288 -0
- package/client/src/types/NodeTypes.ts +28 -0
- package/client/src/utils/formatters.ts +33 -0
- package/client/src/utils/googleMapsLoader.ts +140 -0
- package/client/src/utils/locationUtils.ts +85 -0
- package/client/src/utils/nodeUtils.ts +31 -0
- package/client/src/utils/workflow.ts +30 -0
- package/client/src/utils/workflowExport.ts +120 -0
- package/client/src/vite-env.d.ts +12 -0
- package/client/tailwind.config.js +60 -0
- package/client/tsconfig.json +25 -0
- package/client/tsconfig.node.json +11 -0
- package/client/vite.config.js +35 -0
- package/docker-compose.prod.yml +107 -0
- package/docker-compose.yml +104 -0
- package/docs-MachinaOs/README.md +85 -0
- package/docs-MachinaOs/deployment/docker.mdx +228 -0
- package/docs-MachinaOs/deployment/production.mdx +345 -0
- package/docs-MachinaOs/docs.json +75 -0
- package/docs-MachinaOs/faq.mdx +309 -0
- package/docs-MachinaOs/favicon.svg +5 -0
- package/docs-MachinaOs/installation.mdx +160 -0
- package/docs-MachinaOs/introduction.mdx +114 -0
- package/docs-MachinaOs/logo/dark.svg +6 -0
- package/docs-MachinaOs/logo/light.svg +6 -0
- package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
- package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
- package/docs-MachinaOs/nodes/android.mdx +411 -0
- package/docs-MachinaOs/nodes/overview.mdx +181 -0
- package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
- package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
- package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
- package/docs-MachinaOs/quickstart.mdx +119 -0
- package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
- package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
- package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
- package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
- package/nul +0 -0
- package/package.json +70 -0
- package/scripts/build.js +158 -0
- package/scripts/check-ports.ps1 +33 -0
- package/scripts/clean.js +40 -0
- package/scripts/docker.js +93 -0
- package/scripts/kill-port.ps1 +154 -0
- package/scripts/start.js +210 -0
- package/scripts/stop.js +325 -0
- package/server/.dockerignore +44 -0
- package/server/Dockerfile +45 -0
- package/server/constants.py +249 -0
- package/server/core/__init__.py +1 -0
- package/server/core/cache.py +461 -0
- package/server/core/config.py +128 -0
- package/server/core/container.py +99 -0
- package/server/core/database.py +1211 -0
- package/server/core/logging.py +314 -0
- package/server/main.py +289 -0
- package/server/middleware/__init__.py +5 -0
- package/server/middleware/auth.py +89 -0
- package/server/models/__init__.py +1 -0
- package/server/models/auth.py +52 -0
- package/server/models/cache.py +24 -0
- package/server/models/database.py +211 -0
- package/server/models/nodes.py +455 -0
- package/server/package.json +9 -0
- package/server/pyproject.toml +72 -0
- package/server/requirements.txt +83 -0
- package/server/routers/__init__.py +1 -0
- package/server/routers/android.py +294 -0
- package/server/routers/auth.py +203 -0
- package/server/routers/database.py +151 -0
- package/server/routers/maps.py +142 -0
- package/server/routers/nodejs_compat.py +289 -0
- package/server/routers/webhook.py +90 -0
- package/server/routers/websocket.py +2127 -0
- package/server/routers/whatsapp.py +761 -0
- package/server/routers/workflow.py +200 -0
- package/server/services/__init__.py +1 -0
- package/server/services/ai.py +2415 -0
- package/server/services/android/__init__.py +27 -0
- package/server/services/android/broadcaster.py +114 -0
- package/server/services/android/client.py +608 -0
- package/server/services/android/manager.py +78 -0
- package/server/services/android/protocol.py +165 -0
- package/server/services/android_service.py +588 -0
- package/server/services/auth.py +131 -0
- package/server/services/chat_client.py +160 -0
- package/server/services/deployment/__init__.py +12 -0
- package/server/services/deployment/manager.py +706 -0
- package/server/services/deployment/state.py +47 -0
- package/server/services/deployment/triggers.py +275 -0
- package/server/services/event_waiter.py +785 -0
- package/server/services/execution/__init__.py +77 -0
- package/server/services/execution/cache.py +769 -0
- package/server/services/execution/conditions.py +373 -0
- package/server/services/execution/dlq.py +132 -0
- package/server/services/execution/executor.py +1351 -0
- package/server/services/execution/models.py +531 -0
- package/server/services/execution/recovery.py +235 -0
- package/server/services/handlers/__init__.py +126 -0
- package/server/services/handlers/ai.py +355 -0
- package/server/services/handlers/android.py +260 -0
- package/server/services/handlers/code.py +278 -0
- package/server/services/handlers/document.py +598 -0
- package/server/services/handlers/http.py +193 -0
- package/server/services/handlers/polyglot.py +105 -0
- package/server/services/handlers/tools.py +845 -0
- package/server/services/handlers/triggers.py +107 -0
- package/server/services/handlers/utility.py +822 -0
- package/server/services/handlers/whatsapp.py +476 -0
- package/server/services/maps.py +289 -0
- package/server/services/memory_store.py +103 -0
- package/server/services/node_executor.py +375 -0
- package/server/services/parameter_resolver.py +218 -0
- package/server/services/polyglot_client.py +169 -0
- package/server/services/scheduler.py +155 -0
- package/server/services/skill_loader.py +417 -0
- package/server/services/status_broadcaster.py +826 -0
- package/server/services/temporal/__init__.py +23 -0
- package/server/services/temporal/activities.py +344 -0
- package/server/services/temporal/client.py +76 -0
- package/server/services/temporal/executor.py +147 -0
- package/server/services/temporal/worker.py +251 -0
- package/server/services/temporal/workflow.py +355 -0
- package/server/services/temporal/ws_client.py +236 -0
- package/server/services/text.py +111 -0
- package/server/services/user_auth.py +172 -0
- package/server/services/websocket_client.py +29 -0
- package/server/services/workflow.py +597 -0
- package/server/skills/android-skill/SKILL.md +82 -0
- package/server/skills/assistant-personality/SKILL.md +45 -0
- package/server/skills/code-skill/SKILL.md +140 -0
- package/server/skills/http-skill/SKILL.md +161 -0
- package/server/skills/maps-skill/SKILL.md +170 -0
- package/server/skills/memory-skill/SKILL.md +154 -0
- package/server/skills/scheduler-skill/SKILL.md +84 -0
- package/server/skills/whatsapp-skill/SKILL.md +283 -0
- package/server/uv.lock +2916 -0
- package/server/whatsapp-rpc/.dockerignore +30 -0
- package/server/whatsapp-rpc/Dockerfile +44 -0
- package/server/whatsapp-rpc/Dockerfile.web +17 -0
- package/server/whatsapp-rpc/README.md +139 -0
- package/server/whatsapp-rpc/cli.js +95 -0
- package/server/whatsapp-rpc/configs/config.yaml +7 -0
- package/server/whatsapp-rpc/docker-compose.yml +35 -0
- package/server/whatsapp-rpc/docs/API.md +410 -0
- package/server/whatsapp-rpc/go.mod +67 -0
- package/server/whatsapp-rpc/go.sum +203 -0
- package/server/whatsapp-rpc/package.json +30 -0
- package/server/whatsapp-rpc/schema.json +1294 -0
- package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
- package/server/whatsapp-rpc/scripts/cli.js +162 -0
- package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
- package/server/whatsapp-rpc/src/go/config/config.go +49 -0
- package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
- package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
- package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
- package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
- package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
- package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
- package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
- package/server/whatsapp-rpc/web/app.py +609 -0
- package/server/whatsapp-rpc/web/requirements.txt +6 -0
- package/server/whatsapp-rpc/web/rpc_client.py +427 -0
- package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
- package/server/whatsapp-rpc/web/templates/base.html +150 -0
- package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
- package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
- package/server/whatsapp-rpc/web/templates/groups.html +328 -0
- package/server/whatsapp-rpc/web/templates/messages.html +465 -0
- package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
- package/server/whatsapp-rpc/web/templates/send.html +259 -0
- package/server/whatsapp-rpc/web/templates/settings.html +459 -0
|
@@ -0,0 +1,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)
|