cloneloop 0.1.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 (118) hide show
  1. cloneloop-0.1.0.dist-info/METADATA +392 -0
  2. cloneloop-0.1.0.dist-info/RECORD +118 -0
  3. cloneloop-0.1.0.dist-info/WHEEL +4 -0
  4. cloneloop-0.1.0.dist-info/entry_points.txt +5 -0
  5. mcp_ai_supervisor/__init__.py +52 -0
  6. mcp_ai_supervisor/__main__.py +551 -0
  7. mcp_ai_supervisor/debug.py +15 -0
  8. mcp_ai_supervisor/desktop_app/__init__.py +29 -0
  9. mcp_ai_supervisor/desktop_app/desktop_app.py +390 -0
  10. mcp_ai_supervisor/helpers/__init__.py +4 -0
  11. mcp_ai_supervisor/helpers/feedback_helpers.py +140 -0
  12. mcp_ai_supervisor/helpers/image_helpers.py +73 -0
  13. mcp_ai_supervisor/i18n.py +14 -0
  14. mcp_ai_supervisor/log_writer.py +16 -0
  15. mcp_ai_supervisor/logging/__init__.py +8 -0
  16. mcp_ai_supervisor/logging/log_parser.py +293 -0
  17. mcp_ai_supervisor/logging/log_tailer.py +367 -0
  18. mcp_ai_supervisor/py.typed +0 -0
  19. mcp_ai_supervisor/server.py +654 -0
  20. mcp_ai_supervisor/utils/__init__.py +28 -0
  21. mcp_ai_supervisor/utils/error_handler.py +10 -0
  22. mcp_ai_supervisor/utils/memory_monitor.py +11 -0
  23. mcp_ai_supervisor/utils/paths.py +7 -0
  24. mcp_ai_supervisor/utils/resource_manager.py +15 -0
  25. mcp_ai_supervisor/utils/structure_checker.py +291 -0
  26. mcp_ai_supervisor/web/__init__.py +26 -0
  27. mcp_ai_supervisor/web/constants/__init__.py +8 -0
  28. mcp_ai_supervisor/web/constants/message_codes.py +173 -0
  29. mcp_ai_supervisor/web/core/__init__.py +30 -0
  30. mcp_ai_supervisor/web/core/agent_bridge.py +957 -0
  31. mcp_ai_supervisor/web/core/auto_responder.py +332 -0
  32. mcp_ai_supervisor/web/core/context_collector.py +124 -0
  33. mcp_ai_supervisor/web/core/conversation_recorder.py +751 -0
  34. mcp_ai_supervisor/web/core/delegate_manager.py +1193 -0
  35. mcp_ai_supervisor/web/core/feedback_mediator.py +259 -0
  36. mcp_ai_supervisor/web/core/message_channel.py +551 -0
  37. mcp_ai_supervisor/web/core/response_builder.py +333 -0
  38. mcp_ai_supervisor/web/core/scene_detector.py +184 -0
  39. mcp_ai_supervisor/web/core/task_state.py +194 -0
  40. mcp_ai_supervisor/web/locales/en/translation.json +643 -0
  41. mcp_ai_supervisor/web/locales/zh-CN/translation.json +629 -0
  42. mcp_ai_supervisor/web/locales/zh-TW/translation.json +648 -0
  43. mcp_ai_supervisor/web/main.py +747 -0
  44. mcp_ai_supervisor/web/managers/__init__.py +8 -0
  45. mcp_ai_supervisor/web/managers/desktop_manager.py +228 -0
  46. mcp_ai_supervisor/web/managers/server_manager.py +170 -0
  47. mcp_ai_supervisor/web/managers/session_manager.py +515 -0
  48. mcp_ai_supervisor/web/managers/workbench_connector.py +186 -0
  49. mcp_ai_supervisor/web/models/__init__.py +13 -0
  50. mcp_ai_supervisor/web/models/feedback_result.py +16 -0
  51. mcp_ai_supervisor/web/models/feedback_session.py +938 -0
  52. mcp_ai_supervisor/web/models/session_cleaner.py +245 -0
  53. mcp_ai_supervisor/web/models/session_commands.py +213 -0
  54. mcp_ai_supervisor/web/models/session_resolver.py +158 -0
  55. mcp_ai_supervisor/web/models/session_timer.py +242 -0
  56. mcp_ai_supervisor/web/routes/__init__.py +12 -0
  57. mcp_ai_supervisor/web/routes/main_routes.py +965 -0
  58. mcp_ai_supervisor/web/routes/settings_routes.py +195 -0
  59. mcp_ai_supervisor/web/routes/ws_handlers.py +270 -0
  60. mcp_ai_supervisor/web/static/css/audio-management.css +545 -0
  61. mcp_ai_supervisor/web/static/css/notification-settings.css +152 -0
  62. mcp_ai_supervisor/web/static/css/prompt-management.css +566 -0
  63. mcp_ai_supervisor/web/static/css/session-management.css +1428 -0
  64. mcp_ai_supervisor/web/static/css/styles.css +2267 -0
  65. mcp_ai_supervisor/web/static/favicon.ico +0 -0
  66. mcp_ai_supervisor/web/static/icon-192.png +0 -0
  67. mcp_ai_supervisor/web/static/icon.svg +11 -0
  68. mcp_ai_supervisor/web/static/index.html +37 -0
  69. mcp_ai_supervisor/web/static/js/app.js +1721 -0
  70. mcp_ai_supervisor/web/static/js/i18n.js +376 -0
  71. mcp_ai_supervisor/web/static/js/modules/audio/audio-manager.js +610 -0
  72. mcp_ai_supervisor/web/static/js/modules/audio/audio-settings-ui.js +732 -0
  73. mcp_ai_supervisor/web/static/js/modules/connection-monitor.js +435 -0
  74. mcp_ai_supervisor/web/static/js/modules/constants/message-codes.js +168 -0
  75. mcp_ai_supervisor/web/static/js/modules/countdown-manager.js +273 -0
  76. mcp_ai_supervisor/web/static/js/modules/drag-drop-handler.js +677 -0
  77. mcp_ai_supervisor/web/static/js/modules/file-upload-manager.js +555 -0
  78. mcp_ai_supervisor/web/static/js/modules/image-handler.js +199 -0
  79. mcp_ai_supervisor/web/static/js/modules/logger.js +404 -0
  80. mcp_ai_supervisor/web/static/js/modules/notification/notification-manager.js +360 -0
  81. mcp_ai_supervisor/web/static/js/modules/notification/notification-settings.js +344 -0
  82. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-input-buttons.js +427 -0
  83. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-manager.js +414 -0
  84. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-modal.js +458 -0
  85. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-settings-ui.js +524 -0
  86. mcp_ai_supervisor/web/static/js/modules/session/session-data-manager.js +1042 -0
  87. mcp_ai_supervisor/web/static/js/modules/session/session-details-modal.js +594 -0
  88. mcp_ai_supervisor/web/static/js/modules/session/session-ui-renderer.js +836 -0
  89. mcp_ai_supervisor/web/static/js/modules/session-manager.js +1059 -0
  90. mcp_ai_supervisor/web/static/js/modules/settings-manager.js +1002 -0
  91. mcp_ai_supervisor/web/static/js/modules/tab-manager.js +235 -0
  92. mcp_ai_supervisor/web/static/js/modules/textarea-height-manager.js +267 -0
  93. mcp_ai_supervisor/web/static/js/modules/ui-manager.js +578 -0
  94. mcp_ai_supervisor/web/static/js/modules/utils/dom-utils.js +392 -0
  95. mcp_ai_supervisor/web/static/js/modules/utils/status-utils.js +403 -0
  96. mcp_ai_supervisor/web/static/js/modules/utils/time-utils.js +440 -0
  97. mcp_ai_supervisor/web/static/js/modules/utils.js +557 -0
  98. mcp_ai_supervisor/web/static/js/modules/websocket-manager.js +875 -0
  99. mcp_ai_supervisor/web/static/js/modules/workbench-notification.js +103 -0
  100. mcp_ai_supervisor/web/static/js/vendor/marked.min.js +6 -0
  101. mcp_ai_supervisor/web/static/js/vendor/purify.min.js +3 -0
  102. mcp_ai_supervisor/web/templates/components/image-upload.html +43 -0
  103. mcp_ai_supervisor/web/templates/components/settings-card.html +58 -0
  104. mcp_ai_supervisor/web/templates/components/status-indicator.html +31 -0
  105. mcp_ai_supervisor/web/templates/components/toggle-switch.html +19 -0
  106. mcp_ai_supervisor/web/templates/feedback.html +1198 -0
  107. mcp_ai_supervisor/web/templates/index.html +378 -0
  108. mcp_ai_supervisor/web/utils/__init__.py +12 -0
  109. mcp_ai_supervisor/web/utils/browser.py +35 -0
  110. mcp_ai_supervisor/web/utils/compression_config.py +195 -0
  111. mcp_ai_supervisor/web/utils/compression_monitor.py +314 -0
  112. mcp_ai_supervisor/web/utils/ide_opener.py +286 -0
  113. mcp_ai_supervisor/web/utils/network.py +66 -0
  114. mcp_ai_supervisor/web/utils/port_manager.py +340 -0
  115. mcp_ai_supervisor/web/utils/session_cleanup_manager.py +543 -0
  116. mcp_ai_supervisor/web/utils/workbench_client.py +899 -0
  117. mcp_ai_supervisor/workbench/__init__.py +5 -0
  118. mcp_ai_supervisor/workbench/__main__.py +5 -0
@@ -0,0 +1,390 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 桌面應用程式主要模組
4
+
5
+ 此模組提供桌面應用程式的核心功能,包括:
6
+ - 桌面模式檢測
7
+ - Tauri 應用程式啟動
8
+ - 與現有 Web UI 的整合
9
+ """
10
+
11
+ import asyncio
12
+ import os
13
+ import sys
14
+ import time
15
+
16
+
17
+ # 導入現有的 MCP AI Supervisor 模組
18
+ try:
19
+ from mcp_ai_supervisor.debug import server_debug_log as debug_log
20
+ from mcp_ai_supervisor.log_writer import get_logger as _get_struct_logger
21
+ from mcp_ai_supervisor.web.main import WebUIManager, get_web_ui_manager
22
+ except ImportError as e:
23
+ # 在這裡無法使用 debug_log,因為導入失敗
24
+ sys.stderr.write(f"無法導入 MCP AI Supervisor 模組: {e}\n")
25
+ sys.exit(1)
26
+
27
+ # 桌面应用结构化日志记录器
28
+ _tauri_logger = _get_struct_logger("tauri")
29
+
30
+
31
+ class DesktopApp:
32
+ """桌面應用程式管理器"""
33
+
34
+ def __init__(self):
35
+ self.web_manager: WebUIManager | None = None
36
+ self.desktop_mode = False
37
+ self.app_handle = None
38
+
39
+ def set_desktop_mode(self, enabled: bool = True):
40
+ """設置桌面模式"""
41
+ self.desktop_mode = enabled
42
+ if enabled:
43
+ # 設置環境變數,防止開啟瀏覽器
44
+ os.environ["MCP_DESKTOP_MODE"] = "true"
45
+ debug_log("桌面模式已啟用,將禁止開啟瀏覽器")
46
+ else:
47
+ os.environ.pop("MCP_DESKTOP_MODE", None)
48
+ debug_log("桌面模式已禁用")
49
+
50
+ def is_desktop_mode(self) -> bool:
51
+ """檢查是否為桌面模式"""
52
+ return (
53
+ self.desktop_mode
54
+ or os.environ.get("MCP_DESKTOP_MODE", "").lower() == "true"
55
+ )
56
+
57
+ async def start_web_backend(self) -> str:
58
+ """啟動 Web 後端服務"""
59
+ debug_log("啟動 Web 後端服務...")
60
+
61
+ # 獲取 Web UI 管理器
62
+ self.web_manager = get_web_ui_manager()
63
+
64
+ # 設置桌面模式,禁止自動開啟瀏覽器
65
+ self.set_desktop_mode(True)
66
+
67
+ # 啟動服務器
68
+ if (
69
+ self.web_manager.server_thread is None
70
+ or not self.web_manager.server_thread.is_alive()
71
+ ):
72
+ self.web_manager.start_server()
73
+
74
+ # 等待服務器啟動
75
+ max_wait = 10.0 # 最多等待 10 秒
76
+ wait_count = 0.0
77
+ while wait_count < max_wait:
78
+ if (
79
+ self.web_manager.server_thread
80
+ and self.web_manager.server_thread.is_alive()
81
+ ):
82
+ break
83
+ await asyncio.sleep(0.5)
84
+ wait_count += 0.5
85
+
86
+ if not (
87
+ self.web_manager.server_thread and self.web_manager.server_thread.is_alive()
88
+ ):
89
+ raise RuntimeError("Web 服務器啟動失敗")
90
+
91
+ server_url = self.web_manager.get_server_url()
92
+ debug_log(f"Web 後端服務已啟動: {server_url}")
93
+ return server_url
94
+
95
+ def create_test_session(self):
96
+ """創建測試會話"""
97
+ if not self.web_manager:
98
+ raise RuntimeError("Web 管理器未初始化")
99
+
100
+ import tempfile
101
+
102
+ with tempfile.TemporaryDirectory() as temp_dir:
103
+ session_id, _ = self.web_manager.create_session(
104
+ temp_dir, "桌面應用程式測試 - 驗證 Tauri 整合功能"
105
+ )
106
+ debug_log(f"測試會話已創建: {session_id}")
107
+ return session_id
108
+
109
+ async def launch_tauri_app(self, server_url: str):
110
+ """啟動 Tauri 桌面應用程式"""
111
+ debug_log("正在啟動 Tauri 桌面視窗...")
112
+
113
+ import os
114
+ import subprocess
115
+ from pathlib import Path
116
+
117
+ import platform
118
+
119
+ system = platform.system().lower()
120
+ machine = platform.machine().lower()
121
+
122
+ def _get_binary_name() -> str:
123
+ if system == "windows":
124
+ return "mcp-ai-supervisor-desktop.exe"
125
+ elif system == "darwin":
126
+ if machine in ["arm64", "aarch64"]:
127
+ return "mcp-ai-supervisor-desktop-macos-arm64"
128
+ return "mcp-ai-supervisor-desktop-macos-intel"
129
+ elif system == "linux":
130
+ return "mcp-ai-supervisor-desktop-linux"
131
+ return "mcp-ai-supervisor-desktop"
132
+
133
+ def _find_project_root() -> Path:
134
+ """向上查找包含 pyproject.toml 和 packages/ 的项目根目录"""
135
+ p = Path(__file__).resolve().parent
136
+ for _ in range(10):
137
+ if (p / "pyproject.toml").exists() and (p / "packages").is_dir():
138
+ return p
139
+ p = p.parent
140
+ return Path(__file__).resolve().parent.parent.parent.parent
141
+
142
+ binary_name = _get_binary_name()
143
+ tauri_exe = None
144
+ project_root = _find_project_root()
145
+
146
+ search_dirs = []
147
+
148
+ # 1. 从 Python 包内的 desktop_release 查找(pip 安装场景)
149
+ try:
150
+ from mcp_ai_supervisor.desktop_release import __file__ as desktop_init
151
+ search_dirs.append(Path(desktop_init).parent)
152
+ except ImportError:
153
+ pass
154
+
155
+ # 2. artifacts/desktop/(monorepo 构建产物标准位置)
156
+ search_dirs.append(project_root / "artifacts" / "desktop")
157
+
158
+ for search_dir in search_dirs:
159
+ candidate = search_dir / binary_name
160
+ if candidate.exists():
161
+ tauri_exe = candidate
162
+ debug_log(f"找到 Tauri 可執行檔案: {tauri_exe}")
163
+ break
164
+ # 尝试该目录下的所有可执行文件作为 fallback
165
+ for name in [
166
+ "mcp-ai-supervisor-desktop.exe",
167
+ "mcp-ai-supervisor-desktop-macos-arm64",
168
+ "mcp-ai-supervisor-desktop-macos-intel",
169
+ "mcp-ai-supervisor-desktop-linux",
170
+ "mcp-ai-supervisor-desktop",
171
+ ]:
172
+ candidate = search_dir / name
173
+ if candidate.exists():
174
+ tauri_exe = candidate
175
+ debug_log(f"使用回退的可執行檔案: {tauri_exe}")
176
+ break
177
+ if tauri_exe:
178
+ break
179
+
180
+ if not tauri_exe:
181
+ # 最後嘗試 Tauri 開發構建路徑
182
+ debug_log("未找到預編譯二進制,嘗試開發環境路徑...")
183
+ tauri_exe = (
184
+ project_root
185
+ / "packages"
186
+ / "desktop"
187
+ / "target"
188
+ / "debug"
189
+ / binary_name
190
+ )
191
+
192
+ if not tauri_exe.exists():
193
+ # 嘗試 release 版本
194
+ tauri_exe = (
195
+ project_root
196
+ / "src-tauri"
197
+ / "target"
198
+ / "release"
199
+ / "mcp-ai-supervisor-desktop.exe"
200
+ )
201
+ if not tauri_exe.exists():
202
+ tauri_exe = (
203
+ project_root
204
+ / "src-tauri"
205
+ / "target"
206
+ / "release"
207
+ / "mcp-ai-supervisor-desktop"
208
+ )
209
+
210
+ if not tauri_exe.exists():
211
+ raise FileNotFoundError(
212
+ "找不到 Tauri 可執行檔案,已嘗試的路徑包括開發和發布目錄"
213
+ ) from None
214
+
215
+ debug_log(f"找到 Tauri 可執行檔案: {tauri_exe}")
216
+
217
+ env = os.environ.copy()
218
+ env["MCP_DESKTOP_MODE"] = "true"
219
+ session_id = os.environ.get("MCP_SESSION_ID", "")
220
+ project_dir = os.environ.get("MCP_PROJECT_DIR", "")
221
+
222
+ import urllib.parse
223
+ web_url = server_url
224
+ params = []
225
+ if project_dir:
226
+ params.append(f"project={urllib.parse.quote(project_dir, safe='')}")
227
+ env["MCP_PROJECT_DIR"] = project_dir
228
+ if session_id:
229
+ params.append(f"session_id={session_id}")
230
+ env["MCP_SESSION_ID"] = session_id
231
+ if params:
232
+ web_url = f"{server_url}?{'&'.join(params)}"
233
+ env["MCP_WEB_URL"] = web_url
234
+
235
+ debug_log(
236
+ f"[Tauri-Process] ━━━ 准备启动 Tauri 进程 ━━━\n"
237
+ f" MCP PID={os.getpid()}\n"
238
+ f" MCP_WEB_URL={web_url}\n"
239
+ f" MCP_SESSION_ID={session_id or '(未设置)'}\n"
240
+ f" MCP_PROJECT_DIR={project_dir or '(未设置)'}\n"
241
+ f" 已有 app_handle={self.app_handle is not None}"
242
+ )
243
+
244
+ # 啟動 Tauri 應用程式
245
+ try:
246
+ # Windows 下隱藏控制台視窗
247
+ creation_flags = 0
248
+ if os.name == "nt":
249
+ # CREATE_NO_WINDOW 只在 Windows 上存在
250
+ creation_flags = getattr(subprocess, "CREATE_NO_WINDOW", 0x08000000)
251
+
252
+ self.app_handle = subprocess.Popen(
253
+ [str(tauri_exe)],
254
+ env=env,
255
+ stdout=subprocess.PIPE,
256
+ stderr=subprocess.PIPE,
257
+ creationflags=creation_flags,
258
+ )
259
+ # [TAURI_LAUNCH] 弹窗启动
260
+ _tauri_logger.info(
261
+ f"pid={self.app_handle.pid} url={web_url} project={project_dir or '(未设置)'}",
262
+ "[TAURI_LAUNCH] Tauri 桌面应用已启动",
263
+ )
264
+ debug_log(
265
+ f"[Tauri-Process] Tauri 桌面应用已启动\n"
266
+ f" Tauri PID={self.app_handle.pid}\n"
267
+ f" MCP PID={os.getpid()}"
268
+ )
269
+
270
+ # 等待一下確保應用程式啟動
271
+ await asyncio.sleep(2)
272
+
273
+ except Exception as e:
274
+ # [TAURI_LAUNCH_FAIL] 弹窗启动失败
275
+ _tauri_logger.error(
276
+ f"project={project_dir or '(未设置)'} error={type(e).__name__}: {e}",
277
+ "[TAURI_LAUNCH_FAIL] Tauri 桌面应用启动失败",
278
+ exc_info=True,
279
+ )
280
+ debug_log(f"啟動 Tauri 應用程式失敗: {e}")
281
+ raise
282
+
283
+ def stop(self):
284
+ """停止桌面應用程式"""
285
+ tauri_pid = self.app_handle.pid if self.app_handle else "(无)"
286
+ debug_log(
287
+ f"[Tauri-Process] ━━━ 停止桌面应用 ━━━\n"
288
+ f" Tauri PID={tauri_pid}\n"
289
+ f" MCP PID={os.getpid()}"
290
+ )
291
+
292
+ # 停止 Tauri 應用程式
293
+ if self.app_handle:
294
+ try:
295
+ if self.app_handle.poll() is None:
296
+ self.app_handle.terminate()
297
+ self.app_handle.wait(timeout=3)
298
+ debug_log("Tauri 應用程式已停止")
299
+ else:
300
+ debug_log("Tauri 應用程式已不在運行")
301
+ except Exception as e:
302
+ debug_log(f"停止 Tauri 應用程式時發生錯誤: {e}")
303
+ try:
304
+ self.app_handle.kill()
305
+ self.app_handle.wait(timeout=2)
306
+ except Exception:
307
+ pass
308
+ finally:
309
+ self.app_handle = None
310
+
311
+ if self.web_manager:
312
+ # 注意:不停止 Web 服務器,保持持久性
313
+ debug_log("Web 服務器保持運行狀態")
314
+
315
+ # 注意:不清除桌面模式設置,保持 MCP_DESKTOP_MODE 環境變數
316
+ # 這樣下次 MCP 調用時仍然會啟動桌面應用程式
317
+ # self.set_desktop_mode(False) # 註釋掉這行
318
+ # [TAURI_CLOSE] 弹窗关闭
319
+ _tauri_logger.info(
320
+ f"project={(os.environ.get('MCP_PROJECT_DIR') or '(未设置)')} reason=stop",
321
+ "[TAURI_CLOSE] Tauri 桌面应用已停止",
322
+ )
323
+ debug_log("桌面應用程式已停止")
324
+
325
+
326
+ async def launch_desktop_app(test_mode: bool = False) -> DesktopApp:
327
+ """啟動桌面應用程式
328
+
329
+ Args:
330
+ test_mode: 是否為測試模式,測試模式下會創建測試會話
331
+ """
332
+ debug_log("正在啟動桌面應用程式...")
333
+
334
+ app = DesktopApp()
335
+
336
+ try:
337
+ # 啟動 Web 後端
338
+ server_url = await app.start_web_backend()
339
+
340
+ if test_mode:
341
+ # 測試模式:創建測試會話
342
+ debug_log("測試模式:創建測試會話")
343
+ app.create_test_session()
344
+ else:
345
+ # MCP 調用模式:使用現有會話
346
+ debug_log("MCP 調用模式:使用現有 MCP 會話,不創建新的測試會話")
347
+
348
+ # 啟動 Tauri 桌面應用程式
349
+ await app.launch_tauri_app(server_url)
350
+
351
+ debug_log(f"桌面應用程式已啟動,後端服務: {server_url}")
352
+ return app
353
+
354
+ except Exception as e:
355
+ debug_log(f"桌面應用程式啟動失敗: {e}")
356
+ app.stop()
357
+ raise
358
+
359
+
360
+ def run_desktop_app():
361
+ """同步方式運行桌面應用程式"""
362
+ try:
363
+ # 設置事件循環策略(Windows)
364
+ if sys.platform == "win32":
365
+ asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
366
+
367
+ # 運行應用程式
368
+ loop = asyncio.new_event_loop()
369
+ asyncio.set_event_loop(loop)
370
+
371
+ app = loop.run_until_complete(launch_desktop_app())
372
+
373
+ # 保持應用程式運行
374
+ debug_log("桌面應用程式正在運行,按 Ctrl+C 停止...")
375
+ try:
376
+ while True:
377
+ time.sleep(1)
378
+ except KeyboardInterrupt:
379
+ debug_log("收到停止信號...")
380
+ finally:
381
+ app.stop()
382
+ loop.close()
383
+
384
+ except Exception as e:
385
+ sys.stderr.write(f"桌面應用程式運行失敗: {e}\n")
386
+ sys.exit(1)
387
+
388
+
389
+ if __name__ == "__main__":
390
+ run_desktop_app()
@@ -0,0 +1,4 @@
1
+ """辅助函数模块
2
+
3
+ 从 server.py 提取的公共函数,解决循环 import 问题。
4
+ """
@@ -0,0 +1,140 @@
1
+ """反馈处理辅助函数
2
+
3
+ 从 server.py 提取,供 server.py 和 delegate_manager.py 共同使用,
4
+ 避免 server → web.main → delegate_manager → server 的循环 import。
5
+ """
6
+
7
+ import base64
8
+ import json
9
+ import os
10
+
11
+ from ..debug import server_debug_log as debug_log
12
+ from ..utils.resource_manager import create_temp_file
13
+
14
+
15
+ def save_feedback_to_file(feedback_data: dict, file_path: str | None = None) -> str:
16
+ """
17
+ 將回饋資料儲存到 JSON 文件
18
+
19
+ Args:
20
+ feedback_data: 回饋資料字典
21
+ file_path: 儲存路徑,若為 None 則自動產生臨時文件
22
+
23
+ Returns:
24
+ str: 儲存的文件路徑
25
+ """
26
+ if file_path is None:
27
+ file_path = create_temp_file(suffix=".json", prefix="feedback_")
28
+
29
+ directory = os.path.dirname(file_path)
30
+ if directory and not os.path.exists(directory):
31
+ os.makedirs(directory, exist_ok=True)
32
+
33
+ json_data = feedback_data.copy()
34
+
35
+ if "images" in json_data and isinstance(json_data["images"], list):
36
+ processed_images = []
37
+ for img in json_data["images"]:
38
+ if isinstance(img, dict) and "data" in img:
39
+ processed_img = img.copy()
40
+ if isinstance(img["data"], bytes):
41
+ processed_img["data"] = base64.b64encode(img["data"]).decode(
42
+ "utf-8"
43
+ )
44
+ processed_img["data_type"] = "base64"
45
+ processed_images.append(processed_img)
46
+ else:
47
+ processed_images.append(img)
48
+ json_data["images"] = processed_images
49
+
50
+ with open(file_path, "w", encoding="utf-8") as f:
51
+ json.dump(json_data, f, ensure_ascii=False, indent=2)
52
+
53
+ debug_log(f"反馈数据已保存至: {file_path}")
54
+ return file_path
55
+
56
+
57
+ def create_feedback_text(feedback_data: dict) -> str:
58
+ """
59
+ 建立格式化的回饋文字
60
+
61
+ Args:
62
+ feedback_data: 回饋資料字典
63
+
64
+ Returns:
65
+ str: 格式化後的回饋文字
66
+ """
67
+ text_parts = []
68
+
69
+ if feedback_data.get("interactive_feedback"):
70
+ text_parts.append(feedback_data["interactive_feedback"])
71
+
72
+ if feedback_data.get("command_logs"):
73
+ text_parts.append(f"=== 命令執行日誌 ===\n{feedback_data['command_logs']}")
74
+
75
+ if feedback_data.get("images"):
76
+ images = feedback_data["images"]
77
+ text_parts.append(f"=== 圖片附件概要 ===\n用戶提供了 {len(images)} 張圖片:")
78
+
79
+ for i, img in enumerate(images, 1):
80
+ size = img.get("size", 0)
81
+ name = img.get("name", "unknown")
82
+
83
+ if size < 1024:
84
+ size_str = f"{size} B"
85
+ elif size < 1024 * 1024:
86
+ size_kb = size / 1024
87
+ size_str = f"{size_kb:.1f} KB"
88
+ else:
89
+ size_mb = size / (1024 * 1024)
90
+ size_str = f"{size_mb:.1f} MB"
91
+
92
+ img_info = f" {i}. {name} ({size_str})"
93
+
94
+ if img.get("data"):
95
+ try:
96
+ if isinstance(img["data"], bytes):
97
+ img_base64 = base64.b64encode(img["data"]).decode("utf-8")
98
+ elif isinstance(img["data"], str):
99
+ img_base64 = img["data"]
100
+ else:
101
+ img_base64 = None
102
+
103
+ if img_base64:
104
+ preview = (
105
+ img_base64[:50] + "..."
106
+ if len(img_base64) > 50
107
+ else img_base64
108
+ )
109
+ img_info += f"\n Base64 預覽: {preview}"
110
+ img_info += f"\n 完整 Base64 長度: {len(img_base64)} 字符"
111
+
112
+ debug_log(f"图片 {i} Base64 已准备,长度: {len(img_base64)}")
113
+
114
+ include_full_base64 = feedback_data.get("settings", {}).get(
115
+ "enable_base64_detail", False
116
+ )
117
+
118
+ if include_full_base64:
119
+ file_name = img.get("name", "image.png")
120
+ if file_name.lower().endswith((".jpg", ".jpeg")):
121
+ mime_type = "image/jpeg"
122
+ elif file_name.lower().endswith(".gif"):
123
+ mime_type = "image/gif"
124
+ elif file_name.lower().endswith(".webp"):
125
+ mime_type = "image/webp"
126
+ else:
127
+ mime_type = "image/png"
128
+
129
+ img_info += f"\n 完整 Base64: data:{mime_type};base64,{img_base64}"
130
+
131
+ except Exception as e:
132
+ debug_log(f"图片 {i} Base64 处理失败: {e}")
133
+
134
+ text_parts.append(img_info)
135
+
136
+ text_parts.append(
137
+ "\n💡 注意:如果 AI 助手無法顯示圖片,圖片數據已包含在上述 Base64 信息中。"
138
+ )
139
+
140
+ return "\n\n".join(text_parts) if text_parts else "用戶未提供任何回饋內容。"
@@ -0,0 +1,73 @@
1
+ """图片处理辅助函数
2
+
3
+ 从 server.py 提取,供 server.py 和 delegate_manager.py 共同使用,
4
+ 避免 server → web.main → delegate_manager → server 的循环 import。
5
+ """
6
+
7
+ import base64
8
+
9
+ from mcp.types import ImageContent
10
+
11
+ from ..debug import server_debug_log as debug_log
12
+ from ..utils.error_handler import ErrorHandler, ErrorType
13
+
14
+
15
+ def process_images(images_data: list[dict]) -> list[ImageContent]:
16
+ """
17
+ 處理圖片資料,轉換為 MCP ImageContent 對象
18
+
19
+ Args:
20
+ images_data: 圖片資料列表
21
+
22
+ Returns:
23
+ List[ImageContent]: MCP 圖片內容對象列表
24
+ """
25
+ mcp_images = []
26
+
27
+ for i, img in enumerate(images_data, 1):
28
+ try:
29
+ if not img.get("data"):
30
+ debug_log(f"图片 {i} 没有数据,跳过")
31
+ continue
32
+
33
+ if isinstance(img["data"], bytes):
34
+ base64_data = base64.b64encode(img["data"]).decode("utf-8")
35
+ debug_log(
36
+ f"图片 {i} 从原始 bytes 编码为 base64,原始大小: {len(img['data'])} bytes"
37
+ )
38
+ elif isinstance(img["data"], str):
39
+ base64_data = img["data"]
40
+ debug_log(f"图片 {i} 使用现有 base64 数据")
41
+ else:
42
+ debug_log(f"图片 {i} 数据类型不支持: {type(img['data'])}")
43
+ continue
44
+
45
+ if len(base64_data) == 0:
46
+ debug_log(f"图片 {i} 数据为空,跳过")
47
+ continue
48
+
49
+ file_name = img.get("name", "image.png")
50
+ if file_name.lower().endswith((".jpg", ".jpeg")):
51
+ mime_type = "image/jpeg"
52
+ elif file_name.lower().endswith(".gif"):
53
+ mime_type = "image/gif"
54
+ elif file_name.lower().endswith(".webp"):
55
+ mime_type = "image/webp"
56
+ else:
57
+ mime_type = "image/png"
58
+
59
+ mcp_image = ImageContent(type="image", data=base64_data, mimeType=mime_type)
60
+ mcp_images.append(mcp_image)
61
+
62
+ debug_log(f"图片 {i} ({file_name}) 处理成功,格式: {mime_type}")
63
+
64
+ except Exception as e:
65
+ error_id = ErrorHandler.log_error_with_context(
66
+ e,
67
+ context={"operation": "圖片處理", "image_index": i},
68
+ error_type=ErrorType.FILE_IO,
69
+ )
70
+ debug_log(f"图片 {i} 处理失败 [错误ID: {error_id}]: {e}")
71
+
72
+ debug_log(f"共处理 {len(mcp_images)} 张图片")
73
+ return mcp_images
@@ -0,0 +1,14 @@
1
+ """代理模块:重定向到 mcp_supervisor_core.i18n
2
+
3
+ 重构过渡期保持向后兼容。
4
+ Phase 3 删除 src/ 时此文件一并删除。
5
+ """
6
+ from mcp_supervisor_core.i18n import * # noqa: F401,F403
7
+ from mcp_supervisor_core.i18n import ( # noqa: F401
8
+ I18nManager,
9
+ get_current_language,
10
+ get_i18n_manager,
11
+ reload_translations,
12
+ set_language,
13
+ t,
14
+ )
@@ -0,0 +1,16 @@
1
+ """代理模块:重定向到 mcp_supervisor_core.log_writer
2
+
3
+ 重构过渡期保持向后兼容。
4
+ Phase 3 删除 src/ 时此文件一并删除。
5
+ """
6
+ from mcp_supervisor_core.log_writer import * # noqa: F401,F403
7
+ from mcp_supervisor_core.log_writer import ( # noqa: F401
8
+ SupervisorLogger,
9
+ _cleanup_old_logs,
10
+ _normalize_path,
11
+ _project_hash,
12
+ _update_projects_json,
13
+ get_logger,
14
+ redirect_stdlib_logging,
15
+ reset_logger_cache,
16
+ )
@@ -0,0 +1,8 @@
1
+ """
2
+ 日志系统包
3
+ ==========
4
+
5
+ 提供结构化日志的解析、实时跟踪等功能。
6
+ LogWriter 目前在上层模块 (mcp_ai_supervisor.log_writer),
7
+ 后续版本可迁移到此包内。
8
+ """