quantum-framework 0.18.0__py3-none-any.whl

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 (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,360 @@
1
+ """
2
+ WebSocket Manager for Quantum Admin
3
+ Handles real-time log streaming for deployments
4
+ """
5
+ import asyncio
6
+ import logging
7
+ from datetime import datetime
8
+ from typing import Dict, List, Optional, Set
9
+ from dataclasses import dataclass, field
10
+
11
+ from fastapi import WebSocket, WebSocketDisconnect
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class LogMessage:
18
+ """A log message with metadata"""
19
+ timestamp: datetime
20
+ level: str # info, warning, error, success
21
+ message: str
22
+ step: Optional[str] = None
23
+
24
+ def to_dict(self):
25
+ return {
26
+ "timestamp": self.timestamp.isoformat(),
27
+ "level": self.level,
28
+ "message": self.message,
29
+ "step": self.step
30
+ }
31
+
32
+
33
+ @dataclass
34
+ class DeploymentChannel:
35
+ """A channel for a specific deployment"""
36
+ deploy_id: str
37
+ connections: Set[WebSocket] = field(default_factory=set)
38
+ logs: List[LogMessage] = field(default_factory=list)
39
+ status: str = "pending"
40
+ current_step: Optional[str] = None
41
+
42
+
43
+ class WebSocketManager:
44
+ """
45
+ Manages WebSocket connections for real-time deployment logs
46
+
47
+ Features:
48
+ - Multiple connections per deployment
49
+ - Log buffering for late joiners
50
+ - Status updates and step progress
51
+ """
52
+
53
+ def __init__(self, max_log_buffer: int = 1000):
54
+ """
55
+ Initialize WebSocket manager
56
+
57
+ Args:
58
+ max_log_buffer: Maximum number of logs to buffer per deployment
59
+ """
60
+ self.channels: Dict[str, DeploymentChannel] = {}
61
+ self.max_log_buffer = max_log_buffer
62
+ self._lock = asyncio.Lock()
63
+
64
+ async def connect(self, websocket: WebSocket, deploy_id: str) -> bool:
65
+ """
66
+ Connect a WebSocket to a deployment channel
67
+
68
+ Args:
69
+ websocket: The WebSocket connection
70
+ deploy_id: Deployment ID to subscribe to
71
+
72
+ Returns:
73
+ True if connected successfully
74
+ """
75
+ try:
76
+ await websocket.accept()
77
+
78
+ async with self._lock:
79
+ if deploy_id not in self.channels:
80
+ self.channels[deploy_id] = DeploymentChannel(deploy_id=deploy_id)
81
+
82
+ channel = self.channels[deploy_id]
83
+ channel.connections.add(websocket)
84
+
85
+ # Send buffered logs to new connection
86
+ for log in channel.logs:
87
+ try:
88
+ await websocket.send_json({
89
+ "type": "log",
90
+ "data": log.to_dict()
91
+ })
92
+ except Exception:
93
+ break
94
+
95
+ # Send current status
96
+ await websocket.send_json({
97
+ "type": "status",
98
+ "data": {
99
+ "status": channel.status,
100
+ "step": channel.current_step
101
+ }
102
+ })
103
+
104
+ logger.info(f"WebSocket connected to deployment {deploy_id}")
105
+ return True
106
+
107
+ except Exception as e:
108
+ logger.error(f"Failed to connect WebSocket: {e}")
109
+ return False
110
+
111
+ async def disconnect(self, websocket: WebSocket, deploy_id: str):
112
+ """
113
+ Disconnect a WebSocket from a deployment channel
114
+
115
+ Args:
116
+ websocket: The WebSocket connection
117
+ deploy_id: Deployment ID to unsubscribe from
118
+ """
119
+ async with self._lock:
120
+ if deploy_id in self.channels:
121
+ channel = self.channels[deploy_id]
122
+ channel.connections.discard(websocket)
123
+
124
+ # Clean up empty channels for completed deployments
125
+ if not channel.connections and channel.status in ("completed", "failed", "cancelled"):
126
+ del self.channels[deploy_id]
127
+ logger.info(f"Cleaned up channel for deployment {deploy_id}")
128
+
129
+ logger.info(f"WebSocket disconnected from deployment {deploy_id}")
130
+
131
+ async def broadcast_log(
132
+ self,
133
+ deploy_id: str,
134
+ message: str,
135
+ level: str = "info",
136
+ step: Optional[str] = None
137
+ ):
138
+ """
139
+ Broadcast a log message to all connected clients
140
+
141
+ Args:
142
+ deploy_id: Deployment ID
143
+ message: Log message
144
+ level: Log level (info, warning, error, success)
145
+ step: Current pipeline step (optional)
146
+ """
147
+ log = LogMessage(
148
+ timestamp=datetime.now(),
149
+ level=level,
150
+ message=message,
151
+ step=step
152
+ )
153
+
154
+ async with self._lock:
155
+ if deploy_id not in self.channels:
156
+ self.channels[deploy_id] = DeploymentChannel(deploy_id=deploy_id)
157
+
158
+ channel = self.channels[deploy_id]
159
+
160
+ # Buffer the log
161
+ channel.logs.append(log)
162
+ if len(channel.logs) > self.max_log_buffer:
163
+ channel.logs = channel.logs[-self.max_log_buffer:]
164
+
165
+ # Broadcast to all connections
166
+ disconnected = set()
167
+ for ws in channel.connections:
168
+ try:
169
+ await ws.send_json({
170
+ "type": "log",
171
+ "data": log.to_dict()
172
+ })
173
+ except Exception:
174
+ disconnected.add(ws)
175
+
176
+ # Remove disconnected clients
177
+ channel.connections -= disconnected
178
+
179
+ async def broadcast_status(
180
+ self,
181
+ deploy_id: str,
182
+ status: str,
183
+ step: Optional[str] = None,
184
+ progress: Optional[int] = None
185
+ ):
186
+ """
187
+ Broadcast a status update to all connected clients
188
+
189
+ Args:
190
+ deploy_id: Deployment ID
191
+ status: Deployment status (pending, running, completed, failed, cancelled)
192
+ step: Current pipeline step
193
+ progress: Progress percentage (0-100)
194
+ """
195
+ async with self._lock:
196
+ if deploy_id not in self.channels:
197
+ self.channels[deploy_id] = DeploymentChannel(deploy_id=deploy_id)
198
+
199
+ channel = self.channels[deploy_id]
200
+ channel.status = status
201
+ channel.current_step = step
202
+
203
+ data = {
204
+ "status": status,
205
+ "step": step
206
+ }
207
+ if progress is not None:
208
+ data["progress"] = progress
209
+
210
+ # Broadcast to all connections
211
+ disconnected = set()
212
+ for ws in channel.connections:
213
+ try:
214
+ await ws.send_json({
215
+ "type": "status",
216
+ "data": data
217
+ })
218
+ except Exception:
219
+ disconnected.add(ws)
220
+
221
+ # Remove disconnected clients
222
+ channel.connections -= disconnected
223
+
224
+ async def broadcast_step_complete(
225
+ self,
226
+ deploy_id: str,
227
+ step: str,
228
+ success: bool,
229
+ duration_seconds: float,
230
+ message: Optional[str] = None
231
+ ):
232
+ """
233
+ Broadcast that a pipeline step has completed
234
+
235
+ Args:
236
+ deploy_id: Deployment ID
237
+ step: Step name that completed
238
+ success: Whether the step succeeded
239
+ duration_seconds: How long the step took
240
+ message: Optional completion message
241
+ """
242
+ async with self._lock:
243
+ if deploy_id not in self.channels:
244
+ return
245
+
246
+ channel = self.channels[deploy_id]
247
+
248
+ data = {
249
+ "step": step,
250
+ "success": success,
251
+ "duration": duration_seconds,
252
+ "message": message
253
+ }
254
+
255
+ disconnected = set()
256
+ for ws in channel.connections:
257
+ try:
258
+ await ws.send_json({
259
+ "type": "step_complete",
260
+ "data": data
261
+ })
262
+ except Exception:
263
+ disconnected.add(ws)
264
+
265
+ channel.connections -= disconnected
266
+
267
+ async def broadcast_complete(
268
+ self,
269
+ deploy_id: str,
270
+ success: bool,
271
+ message: str,
272
+ version: Optional[str] = None
273
+ ):
274
+ """
275
+ Broadcast that the deployment has completed
276
+
277
+ Args:
278
+ deploy_id: Deployment ID
279
+ success: Whether the deployment succeeded
280
+ message: Completion message
281
+ version: Deployed version (git commit or docker tag)
282
+ """
283
+ status = "completed" if success else "failed"
284
+
285
+ async with self._lock:
286
+ if deploy_id not in self.channels:
287
+ self.channels[deploy_id] = DeploymentChannel(deploy_id=deploy_id)
288
+
289
+ channel = self.channels[deploy_id]
290
+ channel.status = status
291
+
292
+ data = {
293
+ "success": success,
294
+ "message": message,
295
+ "version": version
296
+ }
297
+
298
+ disconnected = set()
299
+ for ws in channel.connections:
300
+ try:
301
+ await ws.send_json({
302
+ "type": "complete",
303
+ "data": data
304
+ })
305
+ except Exception:
306
+ disconnected.add(ws)
307
+
308
+ channel.connections -= disconnected
309
+
310
+ def get_channel_info(self, deploy_id: str) -> Optional[Dict]:
311
+ """
312
+ Get information about a deployment channel
313
+
314
+ Args:
315
+ deploy_id: Deployment ID
316
+
317
+ Returns:
318
+ Channel info dict or None
319
+ """
320
+ channel = self.channels.get(deploy_id)
321
+ if not channel:
322
+ return None
323
+
324
+ return {
325
+ "deploy_id": channel.deploy_id,
326
+ "connections": len(channel.connections),
327
+ "log_count": len(channel.logs),
328
+ "status": channel.status,
329
+ "current_step": channel.current_step
330
+ }
331
+
332
+ def get_logs(self, deploy_id: str, limit: int = 100) -> List[Dict]:
333
+ """
334
+ Get buffered logs for a deployment
335
+
336
+ Args:
337
+ deploy_id: Deployment ID
338
+ limit: Maximum number of logs to return
339
+
340
+ Returns:
341
+ List of log dicts
342
+ """
343
+ channel = self.channels.get(deploy_id)
344
+ if not channel:
345
+ return []
346
+
347
+ logs = channel.logs[-limit:] if limit else channel.logs
348
+ return [log.to_dict() for log in logs]
349
+
350
+
351
+ # Singleton instance
352
+ _ws_manager = None
353
+
354
+
355
+ def get_websocket_manager() -> WebSocketManager:
356
+ """Get singleton instance of WebSocketManager"""
357
+ global _ws_manager
358
+ if _ws_manager is None:
359
+ _ws_manager = WebSocketManager()
360
+ return _ws_manager
@@ -0,0 +1,121 @@
1
+ <q:component name="AdminShell" require_auth="true" require_role="admin" login_url="/admin/login">
2
+ <!--
3
+ Moldura das telas do admin: barra lateral, cabeçalho e mensagens.
4
+ Uso numa tela:
5
+ <q:import component="AdminShell" from="admin/_layout" />
6
+ <AdminShell title="Applications" active="applications" flash="{flash}" flashType="{flashType}">
7
+ ...conteúdo da tela...
8
+ </AdminShell>
9
+ O conteúdo é renderizado no escopo da tela (COMP-3). Antes, as 16 telas
10
+ repetiam esta barra lateral inteira, cada uma com a sua cópia.
11
+ -->
12
+ <q:param name="title" type="string" required="true" />
13
+ <q:param name="active" type="string" default="" />
14
+ <q:param name="flash" type="string" default="" />
15
+ <q:param name="flashType" type="string" default="success" />
16
+
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="UTF-8" />
20
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
21
+ <title>{title} - Quantum Admin</title>
22
+ <link rel="stylesheet" href="/static/quantum-admin.css" />
23
+ </head>
24
+ <body>
25
+ <div class="qa-layout">
26
+ <aside class="qa-sidebar">
27
+ <div class="qa-sidebar-header">
28
+ <a href="/admin/applications" class="qa-sidebar-logo">
29
+ <div class="qa-sidebar-logo-icon">Q</div>
30
+ <span class="qa-sidebar-logo-text">Quantum<span class="qa-sidebar-version">Admin</span></span>
31
+ </a>
32
+ </div>
33
+
34
+ <nav class="qa-sidebar-nav">
35
+ <div class="qa-sidebar-section">
36
+ <div class="qa-sidebar-section-title">Overview</div>
37
+ <a href="/admin/dashboard" class="{'qa-sidebar-link active' if active == 'dashboard' else 'qa-sidebar-link'}">
38
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
39
+ Dashboard
40
+ </a>
41
+ <a href="/admin/applications" class="{'qa-sidebar-link active' if active == 'applications' else 'qa-sidebar-link'}">
42
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
43
+ Applications
44
+ </a>
45
+ </div>
46
+
47
+ <div class="qa-sidebar-section">
48
+ <div class="qa-sidebar-section-title">Framework</div>
49
+ <a href="/admin/components" class="{'qa-sidebar-link active' if active == 'components' else 'qa-sidebar-link'}">
50
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
51
+ Components
52
+ </a>
53
+ <a href="/admin/features" class="{'qa-sidebar-link active' if active == 'features' else 'qa-sidebar-link'}">
54
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
55
+ Features
56
+ </a>
57
+ <a href="/admin/tests" class="{'qa-sidebar-link active' if active == 'tests' else 'qa-sidebar-link'}">
58
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
59
+ Tests
60
+ </a>
61
+ </div>
62
+
63
+ <div class="qa-sidebar-section">
64
+ <div class="qa-sidebar-section-title">Runtime</div>
65
+ <a href="/admin/connectors" class="{'qa-sidebar-link active' if active == 'connectors' else 'qa-sidebar-link'}">
66
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8h1a4 4 0 0 1 0 8h-1"/><path d="M6 8H5a4 4 0 0 0 0 8h1"/><line x1="6" y1="12" x2="18" y2="12"/></svg>
67
+ Connectors
68
+ </a>
69
+ <a href="/admin/database" class="{'qa-sidebar-link active' if active == 'database' else 'qa-sidebar-link'}">
70
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
71
+ Database
72
+ </a>
73
+ <a href="/admin/agents" class="{'qa-sidebar-link active' if active == 'agents' else 'qa-sidebar-link'}">
74
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
75
+ Agents
76
+ </a>
77
+ <a href="/admin/jobs" class="{'qa-sidebar-link active' if active == 'jobs' else 'qa-sidebar-link'}">
78
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
79
+ Jobs
80
+ </a>
81
+ </div>
82
+
83
+ <div class="qa-sidebar-section">
84
+ <div class="qa-sidebar-section-title">System</div>
85
+ <a href="/admin/settings" class="{'qa-sidebar-link active' if active == 'settings' else 'qa-sidebar-link'}">
86
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
87
+ Settings
88
+ </a>
89
+ </div>
90
+ </nav>
91
+
92
+ <div class="qa-sidebar-footer">
93
+ <div class="qa-sidebar-user">
94
+ <div class="qa-sidebar-user-avatar">{upper(first(session.userName)) if session.userName else 'Q'}</div>
95
+ <div class="qa-sidebar-user-info">
96
+ <div class="qa-sidebar-user-name">{session.userName}</div>
97
+ <form method="POST" action="/admin/logout" style="margin: 0;">
98
+ <button type="submit" class="qa-btn qa-btn-ghost qa-btn-sm">Sign out</button>
99
+ </form>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </aside>
104
+
105
+ <main class="qa-main">
106
+ <header class="qa-header">
107
+ <h1 class="qa-header-title">{title}</h1>
108
+ </header>
109
+ <div class="qa-content">
110
+ <q:if condition="flash">
111
+ <div class="{'qa-flash qa-flash-error' if flashType == 'error' else 'qa-flash qa-flash-success'}" role="status">
112
+ <span class="qa-text-sm qa-font-medium">{flash}</span>
113
+ </div>
114
+ </q:if>
115
+ <q:slot />
116
+ </div>
117
+ </main>
118
+ </div>
119
+ </body>
120
+ </html>
121
+ </q:component>
@@ -0,0 +1,107 @@
1
+ <q:component name="AdminAgents" require_auth="true" require_role="admin" login_url="/admin/login">
2
+ <!--
3
+ q:agent e q:team declarados em components/, examples/ e projects/
4
+ (admin.agents.list). A contagem de providers é a dos que as declarações
5
+ usam — antes somava ollama/openai/anthropic mesmo sem nenhum agente.
6
+ -->
7
+ <q:import component="AdminShell" from="admin/_layout" />
8
+
9
+ <q:invoke name="ia" service="admin.agents.list" />
10
+
11
+ <AdminShell title="Agents" active="agents" flash="{flash}" flashType="{flashType}">
12
+
13
+ <div class="qa-grid qa-grid-4 qa-mb-6">
14
+ <div class="qa-stat-card">
15
+ <div class="qa-stat-label">Agent Definitions</div>
16
+ <div class="qa-stat-value">{len(ia.agents)}</div>
17
+ <div class="qa-stat-change"><span class="qa-text-muted">q:agent tags found</span></div>
18
+ </div>
19
+ <div class="qa-stat-card">
20
+ <div class="qa-stat-label">Team Definitions</div>
21
+ <div class="qa-stat-value">{len(ia.teams)}</div>
22
+ <div class="qa-stat-change"><span class="qa-text-muted">q:team tags found</span></div>
23
+ </div>
24
+ <div class="qa-stat-card">
25
+ <div class="qa-stat-label">Source Files</div>
26
+ <div class="qa-stat-value">{len(ia.sources)}</div>
27
+ <div class="qa-stat-change"><span class="qa-text-muted">files with agents or teams</span></div>
28
+ </div>
29
+ <div class="qa-stat-card">
30
+ <div class="qa-stat-label">Providers</div>
31
+ <div class="qa-stat-value">{len(ia.providers)}</div>
32
+ <div class="qa-stat-change"><span class="qa-text-muted">{join(ia.providers, ', ') if ia.providers else 'none declared'}</span></div>
33
+ </div>
34
+ </div>
35
+
36
+ <q:if condition="len(ia.agents) &gt; 0">
37
+ <div class="qa-card qa-mb-4">
38
+ <div class="qa-card-header">
39
+ <div class="qa-card-title">Agent Definitions</div>
40
+ </div>
41
+ <div class="qa-card-body qa-p-0" style="overflow-x: auto;">
42
+ <table class="qa-table">
43
+ <thead><tr><th>Name</th><th>Model</th><th>Provider</th><th>Source File</th></tr></thead>
44
+ <tbody>
45
+ <q:loop type="array" var="agente" items="{ia.agents}">
46
+ <tr>
47
+ <td><span class="qa-font-mono">{agente.name or '-'}</span></td>
48
+ <td><span class="qa-badge qa-badge-info">{agente.model or '-'}</span></td>
49
+ <td><span class="qa-badge qa-badge-primary">{agente.provider or '-'}</span></td>
50
+ <td><a href="/admin/source?file={agente.source}" class="qa-font-mono qa-text-sm">{agente.source}</a></td>
51
+ </tr>
52
+ </q:loop>
53
+ </tbody>
54
+ </table>
55
+ </div>
56
+ </div>
57
+ </q:if>
58
+
59
+ <q:if condition="len(ia.teams) &gt; 0">
60
+ <div class="qa-card qa-mb-4">
61
+ <div class="qa-card-header">
62
+ <div class="qa-card-title">Team Definitions</div>
63
+ </div>
64
+ <div class="qa-card-body qa-p-0" style="overflow-x: auto;">
65
+ <table class="qa-table">
66
+ <thead><tr><th>Team</th><th>Supervisor</th><th>Source File</th></tr></thead>
67
+ <tbody>
68
+ <q:loop type="array" var="time" items="{ia.teams}">
69
+ <tr>
70
+ <td><span class="qa-font-mono">{time.name or '-'}</span></td>
71
+ <td><span class="qa-badge qa-badge-warning">{time.supervisor or '-'}</span></td>
72
+ <td><a href="/admin/source?file={time.source}" class="qa-font-mono qa-text-sm">{time.source}</a></td>
73
+ </tr>
74
+ </q:loop>
75
+ </tbody>
76
+ </table>
77
+ </div>
78
+ </div>
79
+ </q:if>
80
+
81
+ <q:if condition="ia.llm">
82
+ <div class="qa-card qa-mb-4">
83
+ <div class="qa-card-header">
84
+ <div>
85
+ <div class="qa-card-title">LLM Configuration</div>
86
+ <div class="qa-card-subtitle">llm section of quantum.config.yaml</div>
87
+ </div>
88
+ </div>
89
+ <div class="qa-card-body">
90
+ <div class="qa-flex qa-items-center qa-justify-between"><span class="qa-text-sm qa-text-muted">Base URL</span><span class="qa-font-mono">{ia.llm.base_url or '-'}</span></div>
91
+ <div class="qa-flex qa-items-center qa-justify-between"><span class="qa-text-sm qa-text-muted">Default Model</span><span class="qa-font-mono">{ia.llm.default_model or '-'}</span></div>
92
+ <div class="qa-flex qa-items-center qa-justify-between"><span class="qa-text-sm qa-text-muted">Timeout</span><span class="qa-font-mono">{ia.llm.timeout}s</span></div>
93
+ </div>
94
+ </div>
95
+ </q:if>
96
+
97
+ <q:if condition="len(ia.agents) == 0 and len(ia.teams) == 0">
98
+ <div class="qa-card">
99
+ <div class="qa-empty">
100
+ <div class="qa-empty-title">No Agents Found</div>
101
+ <div class="qa-empty-text">No q:agent or q:team was found in components/, examples/ or projects/.</div>
102
+ </div>
103
+ </div>
104
+ </q:if>
105
+
106
+ </AdminShell>
107
+ </q:component>