machinaos 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/.env.template +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/bin/cli.js +159 -0
  5. package/client/.dockerignore +45 -0
  6. package/client/Dockerfile +68 -0
  7. package/client/eslint.config.js +29 -0
  8. package/client/index.html +13 -0
  9. package/client/nginx.conf +66 -0
  10. package/client/package.json +48 -0
  11. package/client/src/App.tsx +27 -0
  12. package/client/src/Dashboard.tsx +1173 -0
  13. package/client/src/ParameterPanel.tsx +301 -0
  14. package/client/src/components/AIAgentNode.tsx +321 -0
  15. package/client/src/components/APIKeyValidator.tsx +118 -0
  16. package/client/src/components/ClaudeChatModelNode.tsx +18 -0
  17. package/client/src/components/ConditionalEdge.tsx +189 -0
  18. package/client/src/components/CredentialsModal.tsx +306 -0
  19. package/client/src/components/EdgeConditionEditor.tsx +443 -0
  20. package/client/src/components/GeminiChatModelNode.tsx +18 -0
  21. package/client/src/components/GenericNode.tsx +357 -0
  22. package/client/src/components/LocationParameterPanel.tsx +154 -0
  23. package/client/src/components/ModelNode.tsx +286 -0
  24. package/client/src/components/OpenAIChatModelNode.tsx +18 -0
  25. package/client/src/components/OutputPanel.tsx +471 -0
  26. package/client/src/components/ParameterRenderer.tsx +1874 -0
  27. package/client/src/components/SkillEditorModal.tsx +417 -0
  28. package/client/src/components/SquareNode.tsx +797 -0
  29. package/client/src/components/StartNode.tsx +250 -0
  30. package/client/src/components/ToolkitNode.tsx +365 -0
  31. package/client/src/components/TriggerNode.tsx +463 -0
  32. package/client/src/components/auth/LoginPage.tsx +247 -0
  33. package/client/src/components/auth/ProtectedRoute.tsx +59 -0
  34. package/client/src/components/base/BaseChatModelNode.tsx +271 -0
  35. package/client/src/components/icons/AIProviderIcons.tsx +50 -0
  36. package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
  37. package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
  38. package/client/src/components/maps/index.ts +26 -0
  39. package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
  40. package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
  41. package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
  42. package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
  43. package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
  44. package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
  45. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
  46. package/client/src/components/parameterPanel/index.ts +42 -0
  47. package/client/src/components/shared/DataPanel.tsx +142 -0
  48. package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
  49. package/client/src/components/ui/AIResultModal.tsx +204 -0
  50. package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
  51. package/client/src/components/ui/CodeEditor.tsx +81 -0
  52. package/client/src/components/ui/CollapsibleSection.tsx +88 -0
  53. package/client/src/components/ui/ComponentItem.tsx +154 -0
  54. package/client/src/components/ui/ComponentPalette.tsx +321 -0
  55. package/client/src/components/ui/ConsolePanel.tsx +1074 -0
  56. package/client/src/components/ui/ErrorBoundary.tsx +196 -0
  57. package/client/src/components/ui/InputNodesPanel.tsx +204 -0
  58. package/client/src/components/ui/MapSelector.tsx +314 -0
  59. package/client/src/components/ui/Modal.tsx +149 -0
  60. package/client/src/components/ui/NodeContextMenu.tsx +192 -0
  61. package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
  62. package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
  63. package/client/src/components/ui/SettingsPanel.tsx +243 -0
  64. package/client/src/components/ui/TopToolbar.tsx +736 -0
  65. package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
  66. package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
  67. package/client/src/config/antdTheme.ts +186 -0
  68. package/client/src/config/api.ts +54 -0
  69. package/client/src/contexts/AuthContext.tsx +221 -0
  70. package/client/src/contexts/ThemeContext.tsx +42 -0
  71. package/client/src/contexts/WebSocketContext.tsx +1971 -0
  72. package/client/src/factories/baseChatModelFactory.ts +256 -0
  73. package/client/src/hooks/useAndroidOperations.ts +164 -0
  74. package/client/src/hooks/useApiKeyValidation.ts +107 -0
  75. package/client/src/hooks/useApiKeys.ts +238 -0
  76. package/client/src/hooks/useAppTheme.ts +17 -0
  77. package/client/src/hooks/useComponentPalette.ts +51 -0
  78. package/client/src/hooks/useCopyPaste.ts +155 -0
  79. package/client/src/hooks/useDragAndDrop.ts +124 -0
  80. package/client/src/hooks/useDragVariable.ts +88 -0
  81. package/client/src/hooks/useExecution.ts +313 -0
  82. package/client/src/hooks/useParameterPanel.ts +176 -0
  83. package/client/src/hooks/useReactFlowNodes.ts +189 -0
  84. package/client/src/hooks/useToolSchema.ts +209 -0
  85. package/client/src/hooks/useWhatsApp.ts +196 -0
  86. package/client/src/hooks/useWorkflowManagement.ts +46 -0
  87. package/client/src/index.css +315 -0
  88. package/client/src/main.tsx +19 -0
  89. package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
  90. package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
  91. package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
  92. package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
  93. package/client/src/nodeDefinitions/chatNodes.ts +135 -0
  94. package/client/src/nodeDefinitions/codeNodes.ts +54 -0
  95. package/client/src/nodeDefinitions/documentNodes.ts +379 -0
  96. package/client/src/nodeDefinitions/index.ts +15 -0
  97. package/client/src/nodeDefinitions/locationNodes.ts +463 -0
  98. package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
  99. package/client/src/nodeDefinitions/skillNodes.ts +211 -0
  100. package/client/src/nodeDefinitions/toolNodes.ts +198 -0
  101. package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
  102. package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
  103. package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
  104. package/client/src/nodeDefinitions.ts +104 -0
  105. package/client/src/schemas/workflowSchema.ts +264 -0
  106. package/client/src/services/dynamicParameterService.ts +96 -0
  107. package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
  108. package/client/src/services/executionService.ts +232 -0
  109. package/client/src/services/workflowApi.ts +91 -0
  110. package/client/src/store/useAppStore.ts +582 -0
  111. package/client/src/styles/theme.ts +508 -0
  112. package/client/src/styles/zIndex.ts +17 -0
  113. package/client/src/types/ComponentTypes.ts +39 -0
  114. package/client/src/types/EdgeCondition.ts +231 -0
  115. package/client/src/types/INodeProperties.ts +288 -0
  116. package/client/src/types/NodeTypes.ts +28 -0
  117. package/client/src/utils/formatters.ts +33 -0
  118. package/client/src/utils/googleMapsLoader.ts +140 -0
  119. package/client/src/utils/locationUtils.ts +85 -0
  120. package/client/src/utils/nodeUtils.ts +31 -0
  121. package/client/src/utils/workflow.ts +30 -0
  122. package/client/src/utils/workflowExport.ts +120 -0
  123. package/client/src/vite-env.d.ts +12 -0
  124. package/client/tailwind.config.js +60 -0
  125. package/client/tsconfig.json +25 -0
  126. package/client/tsconfig.node.json +11 -0
  127. package/client/vite.config.js +35 -0
  128. package/docker-compose.prod.yml +107 -0
  129. package/docker-compose.yml +104 -0
  130. package/docs-MachinaOs/README.md +85 -0
  131. package/docs-MachinaOs/deployment/docker.mdx +228 -0
  132. package/docs-MachinaOs/deployment/production.mdx +345 -0
  133. package/docs-MachinaOs/docs.json +75 -0
  134. package/docs-MachinaOs/faq.mdx +309 -0
  135. package/docs-MachinaOs/favicon.svg +5 -0
  136. package/docs-MachinaOs/installation.mdx +160 -0
  137. package/docs-MachinaOs/introduction.mdx +114 -0
  138. package/docs-MachinaOs/logo/dark.svg +6 -0
  139. package/docs-MachinaOs/logo/light.svg +6 -0
  140. package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
  141. package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
  142. package/docs-MachinaOs/nodes/android.mdx +411 -0
  143. package/docs-MachinaOs/nodes/overview.mdx +181 -0
  144. package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
  145. package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
  146. package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
  147. package/docs-MachinaOs/quickstart.mdx +119 -0
  148. package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
  149. package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
  150. package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
  151. package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
  152. package/nul +0 -0
  153. package/package.json +70 -0
  154. package/scripts/build.js +158 -0
  155. package/scripts/check-ports.ps1 +33 -0
  156. package/scripts/clean.js +40 -0
  157. package/scripts/docker.js +93 -0
  158. package/scripts/kill-port.ps1 +154 -0
  159. package/scripts/start.js +210 -0
  160. package/scripts/stop.js +325 -0
  161. package/server/.dockerignore +44 -0
  162. package/server/Dockerfile +45 -0
  163. package/server/constants.py +249 -0
  164. package/server/core/__init__.py +1 -0
  165. package/server/core/cache.py +461 -0
  166. package/server/core/config.py +128 -0
  167. package/server/core/container.py +99 -0
  168. package/server/core/database.py +1211 -0
  169. package/server/core/logging.py +314 -0
  170. package/server/main.py +289 -0
  171. package/server/middleware/__init__.py +5 -0
  172. package/server/middleware/auth.py +89 -0
  173. package/server/models/__init__.py +1 -0
  174. package/server/models/auth.py +52 -0
  175. package/server/models/cache.py +24 -0
  176. package/server/models/database.py +211 -0
  177. package/server/models/nodes.py +455 -0
  178. package/server/package.json +9 -0
  179. package/server/pyproject.toml +72 -0
  180. package/server/requirements.txt +83 -0
  181. package/server/routers/__init__.py +1 -0
  182. package/server/routers/android.py +294 -0
  183. package/server/routers/auth.py +203 -0
  184. package/server/routers/database.py +151 -0
  185. package/server/routers/maps.py +142 -0
  186. package/server/routers/nodejs_compat.py +289 -0
  187. package/server/routers/webhook.py +90 -0
  188. package/server/routers/websocket.py +2127 -0
  189. package/server/routers/whatsapp.py +761 -0
  190. package/server/routers/workflow.py +200 -0
  191. package/server/services/__init__.py +1 -0
  192. package/server/services/ai.py +2415 -0
  193. package/server/services/android/__init__.py +27 -0
  194. package/server/services/android/broadcaster.py +114 -0
  195. package/server/services/android/client.py +608 -0
  196. package/server/services/android/manager.py +78 -0
  197. package/server/services/android/protocol.py +165 -0
  198. package/server/services/android_service.py +588 -0
  199. package/server/services/auth.py +131 -0
  200. package/server/services/chat_client.py +160 -0
  201. package/server/services/deployment/__init__.py +12 -0
  202. package/server/services/deployment/manager.py +706 -0
  203. package/server/services/deployment/state.py +47 -0
  204. package/server/services/deployment/triggers.py +275 -0
  205. package/server/services/event_waiter.py +785 -0
  206. package/server/services/execution/__init__.py +77 -0
  207. package/server/services/execution/cache.py +769 -0
  208. package/server/services/execution/conditions.py +373 -0
  209. package/server/services/execution/dlq.py +132 -0
  210. package/server/services/execution/executor.py +1351 -0
  211. package/server/services/execution/models.py +531 -0
  212. package/server/services/execution/recovery.py +235 -0
  213. package/server/services/handlers/__init__.py +126 -0
  214. package/server/services/handlers/ai.py +355 -0
  215. package/server/services/handlers/android.py +260 -0
  216. package/server/services/handlers/code.py +278 -0
  217. package/server/services/handlers/document.py +598 -0
  218. package/server/services/handlers/http.py +193 -0
  219. package/server/services/handlers/polyglot.py +105 -0
  220. package/server/services/handlers/tools.py +845 -0
  221. package/server/services/handlers/triggers.py +107 -0
  222. package/server/services/handlers/utility.py +822 -0
  223. package/server/services/handlers/whatsapp.py +476 -0
  224. package/server/services/maps.py +289 -0
  225. package/server/services/memory_store.py +103 -0
  226. package/server/services/node_executor.py +375 -0
  227. package/server/services/parameter_resolver.py +218 -0
  228. package/server/services/polyglot_client.py +169 -0
  229. package/server/services/scheduler.py +155 -0
  230. package/server/services/skill_loader.py +417 -0
  231. package/server/services/status_broadcaster.py +826 -0
  232. package/server/services/temporal/__init__.py +23 -0
  233. package/server/services/temporal/activities.py +344 -0
  234. package/server/services/temporal/client.py +76 -0
  235. package/server/services/temporal/executor.py +147 -0
  236. package/server/services/temporal/worker.py +251 -0
  237. package/server/services/temporal/workflow.py +355 -0
  238. package/server/services/temporal/ws_client.py +236 -0
  239. package/server/services/text.py +111 -0
  240. package/server/services/user_auth.py +172 -0
  241. package/server/services/websocket_client.py +29 -0
  242. package/server/services/workflow.py +597 -0
  243. package/server/skills/android-skill/SKILL.md +82 -0
  244. package/server/skills/assistant-personality/SKILL.md +45 -0
  245. package/server/skills/code-skill/SKILL.md +140 -0
  246. package/server/skills/http-skill/SKILL.md +161 -0
  247. package/server/skills/maps-skill/SKILL.md +170 -0
  248. package/server/skills/memory-skill/SKILL.md +154 -0
  249. package/server/skills/scheduler-skill/SKILL.md +84 -0
  250. package/server/skills/whatsapp-skill/SKILL.md +283 -0
  251. package/server/uv.lock +2916 -0
  252. package/server/whatsapp-rpc/.dockerignore +30 -0
  253. package/server/whatsapp-rpc/Dockerfile +44 -0
  254. package/server/whatsapp-rpc/Dockerfile.web +17 -0
  255. package/server/whatsapp-rpc/README.md +139 -0
  256. package/server/whatsapp-rpc/cli.js +95 -0
  257. package/server/whatsapp-rpc/configs/config.yaml +7 -0
  258. package/server/whatsapp-rpc/docker-compose.yml +35 -0
  259. package/server/whatsapp-rpc/docs/API.md +410 -0
  260. package/server/whatsapp-rpc/go.mod +67 -0
  261. package/server/whatsapp-rpc/go.sum +203 -0
  262. package/server/whatsapp-rpc/package.json +30 -0
  263. package/server/whatsapp-rpc/schema.json +1294 -0
  264. package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
  265. package/server/whatsapp-rpc/scripts/cli.js +162 -0
  266. package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
  267. package/server/whatsapp-rpc/src/go/config/config.go +49 -0
  268. package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
  269. package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
  270. package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
  271. package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
  272. package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
  273. package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
  274. package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
  275. package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
  276. package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
  277. package/server/whatsapp-rpc/web/app.py +609 -0
  278. package/server/whatsapp-rpc/web/requirements.txt +6 -0
  279. package/server/whatsapp-rpc/web/rpc_client.py +427 -0
  280. package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
  281. package/server/whatsapp-rpc/web/templates/base.html +150 -0
  282. package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
  283. package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
  284. package/server/whatsapp-rpc/web/templates/groups.html +328 -0
  285. package/server/whatsapp-rpc/web/templates/messages.html +465 -0
  286. package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
  287. package/server/whatsapp-rpc/web/templates/send.html +259 -0
  288. package/server/whatsapp-rpc/web/templates/settings.html +459 -0
@@ -0,0 +1,169 @@
1
+ """Polyglot Server Client - HTTP client for polyglot-server plugin registry."""
2
+
3
+ import aiohttp
4
+ from typing import Dict, Any, List, Optional
5
+
6
+ from core.logging import get_logger
7
+
8
+ logger = get_logger(__name__)
9
+
10
+
11
+ class PolyglotClient:
12
+ """Client for polyglot-server plugin registry.
13
+
14
+ Provides async HTTP methods to interact with the polyglot-server
15
+ for plugin discovery, schema retrieval, and execution.
16
+ """
17
+
18
+ def __init__(self, base_url: str = "http://localhost:8080"):
19
+ self.base_url = base_url.rstrip("/")
20
+ self._session: Optional[aiohttp.ClientSession] = None
21
+
22
+ async def initialize(self) -> None:
23
+ """Initialize the HTTP session."""
24
+ if self._session is None or self._session.closed:
25
+ timeout = aiohttp.ClientTimeout(total=30)
26
+ self._session = aiohttp.ClientSession(timeout=timeout)
27
+ logger.info("PolyglotClient initialized", base_url=self.base_url)
28
+
29
+ async def close(self) -> None:
30
+ """Close the HTTP session."""
31
+ if self._session and not self._session.closed:
32
+ await self._session.close()
33
+ self._session = None
34
+ logger.info("PolyglotClient closed")
35
+
36
+ async def _ensure_session(self) -> aiohttp.ClientSession:
37
+ """Ensure session is initialized and return it."""
38
+ if self._session is None or self._session.closed:
39
+ await self.initialize()
40
+ return self._session
41
+
42
+ async def health_check(self) -> Dict[str, Any]:
43
+ """Check polyglot-server health status."""
44
+ session = await self._ensure_session()
45
+ try:
46
+ async with session.get(f"{self.base_url}/api/health") as resp:
47
+ if resp.status == 200:
48
+ return await resp.json()
49
+ return {"status": "error", "code": resp.status}
50
+ except aiohttp.ClientError as e:
51
+ logger.warning("Polyglot health check failed", error=str(e))
52
+ return {"status": "unreachable", "error": str(e)}
53
+
54
+ async def list_plugins(self) -> List[Dict[str, Any]]:
55
+ """List all available plugins from polyglot-server."""
56
+ session = await self._ensure_session()
57
+ try:
58
+ async with session.get(f"{self.base_url}/api/plugins") as resp:
59
+ if resp.status == 200:
60
+ data = await resp.json()
61
+ return data.get("plugins", [])
62
+ logger.warning("Failed to list plugins", status=resp.status)
63
+ return []
64
+ except aiohttp.ClientError as e:
65
+ logger.error("Failed to list plugins", error=str(e))
66
+ return []
67
+
68
+ async def get_plugin(self, name: str) -> Optional[Dict[str, Any]]:
69
+ """Get details for a specific plugin."""
70
+ session = await self._ensure_session()
71
+ try:
72
+ async with session.get(f"{self.base_url}/api/plugins/{name}") as resp:
73
+ if resp.status == 200:
74
+ return await resp.json()
75
+ return None
76
+ except aiohttp.ClientError as e:
77
+ logger.error("Failed to get plugin", plugin=name, error=str(e))
78
+ return None
79
+
80
+ async def get_schema(self, plugin_name: str) -> Optional[Dict[str, Any]]:
81
+ """Get plugin input/output schema for workflow node integration."""
82
+ session = await self._ensure_session()
83
+ try:
84
+ async with session.get(
85
+ f"{self.base_url}/api/plugins/{plugin_name}/schema"
86
+ ) as resp:
87
+ if resp.status == 200:
88
+ return await resp.json()
89
+ return None
90
+ except aiohttp.ClientError as e:
91
+ logger.error("Failed to get schema", plugin=plugin_name, error=str(e))
92
+ return None
93
+
94
+ async def execute(
95
+ self,
96
+ plugin_name: str,
97
+ action: str = "default",
98
+ params: Optional[Dict[str, Any]] = None,
99
+ ) -> Dict[str, Any]:
100
+ """Execute a plugin action.
101
+
102
+ Args:
103
+ plugin_name: Name of the plugin to execute
104
+ action: Action to perform (default: "default")
105
+ params: Optional parameters for the action
106
+
107
+ Returns:
108
+ Execution result with success status and result/error
109
+ """
110
+ session = await self._ensure_session()
111
+ payload = {
112
+ "action": action,
113
+ "params": params or {},
114
+ }
115
+
116
+ try:
117
+ async with session.post(
118
+ f"{self.base_url}/api/plugins/{plugin_name}/execute",
119
+ json=payload,
120
+ ) as resp:
121
+ result = await resp.json()
122
+ if resp.status == 200:
123
+ return result
124
+ return {
125
+ "success": False,
126
+ "error": result.get("error", f"HTTP {resp.status}"),
127
+ }
128
+ except aiohttp.ClientError as e:
129
+ logger.error(
130
+ "Plugin execution failed",
131
+ plugin=plugin_name,
132
+ action=action,
133
+ error=str(e),
134
+ )
135
+ return {"success": False, "error": str(e)}
136
+
137
+ async def list_categories(self) -> List[str]:
138
+ """List available plugin categories."""
139
+ session = await self._ensure_session()
140
+ try:
141
+ async with session.get(f"{self.base_url}/api/categories") as resp:
142
+ if resp.status == 200:
143
+ data = await resp.json()
144
+ return data.get("categories", [])
145
+ return []
146
+ except aiohttp.ClientError as e:
147
+ logger.error("Failed to list categories", error=str(e))
148
+ return []
149
+
150
+
151
+ # Global client instance for simple usage
152
+ _client: Optional[PolyglotClient] = None
153
+
154
+
155
+ async def get_polyglot_client(base_url: str = "http://localhost:8080") -> PolyglotClient:
156
+ """Get or create the global polyglot client instance."""
157
+ global _client
158
+ if _client is None:
159
+ _client = PolyglotClient(base_url)
160
+ await _client.initialize()
161
+ return _client
162
+
163
+
164
+ async def close_polyglot_client() -> None:
165
+ """Close the global polyglot client instance."""
166
+ global _client
167
+ if _client is not None:
168
+ await _client.close()
169
+ _client = None
@@ -0,0 +1,155 @@
1
+ """
2
+ Cron Scheduler Service using APScheduler.
3
+ Manages scheduled jobs for workflow automation.
4
+ """
5
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
6
+ from apscheduler.triggers.cron import CronTrigger
7
+ from apscheduler.jobstores.base import JobLookupError
8
+ from typing import Callable, Dict, Optional
9
+ import logging
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ _scheduler: Optional[AsyncIOScheduler] = None
14
+
15
+
16
+ def get_scheduler() -> AsyncIOScheduler:
17
+ """Get or create the singleton scheduler instance."""
18
+ global _scheduler
19
+ if _scheduler is None:
20
+ _scheduler = AsyncIOScheduler(timezone="UTC")
21
+ return _scheduler
22
+
23
+
24
+ def start_scheduler():
25
+ """Start the scheduler if not already running."""
26
+ scheduler = get_scheduler()
27
+ if not scheduler.running:
28
+ scheduler.start()
29
+ logger.info("[Scheduler] Started")
30
+
31
+
32
+ def shutdown_scheduler():
33
+ """Shutdown the scheduler gracefully."""
34
+ scheduler = get_scheduler()
35
+ if scheduler.running:
36
+ scheduler.shutdown(wait=False)
37
+ logger.info("[Scheduler] Shutdown")
38
+
39
+
40
+ def register_cron_job(
41
+ job_id: str,
42
+ cron_expression: str,
43
+ callback: Callable,
44
+ timezone: str = "UTC",
45
+ **kwargs
46
+ ) -> str:
47
+ """
48
+ Register a cron job with the scheduler.
49
+
50
+ Args:
51
+ job_id: Unique identifier for the job
52
+ cron_expression: 6-field cron expression (second minute hour day month weekday)
53
+ or 5-field (minute hour day month weekday)
54
+ callback: Async function to call when job fires
55
+ timezone: Timezone for schedule (default: UTC)
56
+ **kwargs: Additional arguments passed to the callback
57
+
58
+ Returns:
59
+ The job_id
60
+ """
61
+ scheduler = get_scheduler()
62
+
63
+ # Parse cron expression - support both 5-field and 6-field formats
64
+ parts = cron_expression.split()
65
+
66
+ if len(parts) >= 6:
67
+ # 6-field format: second minute hour day month weekday
68
+ trigger = CronTrigger(
69
+ second=parts[0],
70
+ minute=parts[1],
71
+ hour=parts[2],
72
+ day=parts[3],
73
+ month=parts[4],
74
+ day_of_week=parts[5],
75
+ timezone=timezone
76
+ )
77
+ else:
78
+ # 5-field format: minute hour day month weekday (default second=0)
79
+ if len(parts) < 5:
80
+ parts.extend(['*'] * (5 - len(parts)))
81
+ trigger = CronTrigger(
82
+ second='0',
83
+ minute=parts[0],
84
+ hour=parts[1],
85
+ day=parts[2],
86
+ month=parts[3],
87
+ day_of_week=parts[4],
88
+ timezone=timezone
89
+ )
90
+
91
+ scheduler.add_job(
92
+ callback,
93
+ trigger=trigger,
94
+ id=job_id,
95
+ replace_existing=True,
96
+ kwargs=kwargs
97
+ )
98
+
99
+ logger.info(f"[Scheduler] Registered cron job: {job_id} with expression: {cron_expression}")
100
+ return job_id
101
+
102
+
103
+ def remove_cron_job(job_id: str) -> bool:
104
+ """
105
+ Remove a cron job from the scheduler.
106
+
107
+ Args:
108
+ job_id: The job identifier to remove
109
+
110
+ Returns:
111
+ True if job was removed, False if not found
112
+ """
113
+ scheduler = get_scheduler()
114
+ try:
115
+ scheduler.remove_job(job_id)
116
+ logger.info(f"[Scheduler] Removed cron job: {job_id}")
117
+ return True
118
+ except JobLookupError:
119
+ logger.warning(f"[Scheduler] Job not found: {job_id}")
120
+ return False
121
+
122
+
123
+ def get_job_info(job_id: str) -> Optional[Dict]:
124
+ """
125
+ Get information about a scheduled job.
126
+
127
+ Args:
128
+ job_id: The job identifier
129
+
130
+ Returns:
131
+ Dict with job info or None if not found
132
+ """
133
+ scheduler = get_scheduler()
134
+ job = scheduler.get_job(job_id)
135
+ if job:
136
+ return {
137
+ "id": job.id,
138
+ "next_run_time": job.next_run_time.isoformat() if job.next_run_time else None,
139
+ "trigger": str(job.trigger)
140
+ }
141
+ return None
142
+
143
+
144
+ def get_all_jobs() -> list:
145
+ """Get list of all scheduled jobs."""
146
+ scheduler = get_scheduler()
147
+ jobs = scheduler.get_jobs()
148
+ return [
149
+ {
150
+ "id": job.id,
151
+ "next_run_time": job.next_run_time.isoformat() if job.next_run_time else None,
152
+ "trigger": str(job.trigger)
153
+ }
154
+ for job in jobs
155
+ ]