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,543 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 會話清理管理器
4
+ ==============
5
+
6
+ 統一管理 Web 會話的清理策略、統計和性能監控。
7
+ 與內存監控系統深度集成,提供智能清理決策。
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import threading
13
+ import time
14
+ from collections.abc import Callable
15
+ from dataclasses import dataclass
16
+ from datetime import datetime
17
+ from enum import Enum
18
+ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
19
+
20
+ from ...debug import web_debug_log as debug_log
21
+ from ...log_writer import get_logger as _get_struct_logger
22
+ from ...utils.error_handler import ErrorHandler, ErrorType
23
+ from ..models.feedback_session import CleanupReason, SessionStatus
24
+
25
+ if TYPE_CHECKING:
26
+ from ..models.feedback_session import WebFeedbackSession
27
+
28
+
29
+ @runtime_checkable
30
+ class SessionStore(Protocol):
31
+ """会话存储接口 — SessionCleanupManager 仅通过此接口操作会话。
32
+
33
+ Phase 0 引入,为 Phase 4(WebUIManager 拆分)解耦做准备。
34
+ """
35
+
36
+ def session_items(self) -> list[tuple[str, WebFeedbackSession]]:
37
+ """返回 [(session_id, session), ...] 快照"""
38
+ ...
39
+
40
+ def session_get(self, session_id: str) -> WebFeedbackSession | None:
41
+ """按 session_id 获取会话"""
42
+ ...
43
+
44
+ def session_remove(self, session_id: str) -> None:
45
+ """移除会话(同时清理 active_sessions 和 desktop_app_instances)"""
46
+ ...
47
+
48
+ def session_count(self) -> int:
49
+ """当前会话总数"""
50
+ ...
51
+
52
+ @property
53
+ def current_session_id(self) -> str | None:
54
+ """当前最新活跃会话的 ID(用于保护不被清理)"""
55
+ ...
56
+
57
+ def session_clear_all(self) -> int:
58
+ """清空全部会话,返回清理数量"""
59
+ ...
60
+
61
+
62
+ @dataclass
63
+ class CleanupPolicy:
64
+ """清理策略配置"""
65
+
66
+ max_idle_time: int = 1800 # 最大空閒時間(秒)
67
+ max_session_age: int = 7200 # 最大會話年齡(秒)
68
+ max_sessions: int = 10 # 最大會話數量
69
+ cleanup_interval: int = 300 # 清理間隔(秒)
70
+ memory_pressure_threshold: float = 0.8 # 內存壓力閾值
71
+ enable_auto_cleanup: bool = True # 啟用自動清理
72
+ preserve_active_session: bool = True # 保護活躍會話
73
+
74
+
75
+ @dataclass
76
+ class CleanupStats:
77
+ """清理統計數據"""
78
+
79
+ total_cleanups: int = 0
80
+ expired_cleanups: int = 0
81
+ memory_pressure_cleanups: int = 0
82
+ manual_cleanups: int = 0
83
+ auto_cleanups: int = 0
84
+ total_sessions_cleaned: int = 0
85
+ total_cleanup_time: float = 0.0
86
+ average_cleanup_time: float = 0.0
87
+ last_cleanup_time: datetime | None = None
88
+ cleanup_efficiency: float = 0.0 # 清理效率(清理的會話數/總會話數)
89
+
90
+
91
+ class CleanupTrigger(Enum):
92
+ """清理觸發器類型"""
93
+
94
+ AUTO = "auto" # 自動清理
95
+ MEMORY_PRESSURE = "memory_pressure" # 內存壓力
96
+ MANUAL = "manual" # 手動清理
97
+ EXPIRED = "expired" # 過期清理
98
+ CAPACITY = "capacity" # 容量限制
99
+
100
+
101
+ class SessionCleanupManager:
102
+ """會話清理管理器"""
103
+
104
+ def __init__(self, web_ui_manager, policy: CleanupPolicy | None = None):
105
+ """
106
+ 初始化會話清理管理器
107
+
108
+ Args:
109
+ web_ui_manager: WebUIManager 實例(须实现 SessionStore 协议)
110
+ policy: 清理策略配置
111
+ """
112
+ self.web_ui_manager = web_ui_manager
113
+ self._store: SessionStore = web_ui_manager
114
+ self.policy = policy or CleanupPolicy()
115
+ self.stats = CleanupStats()
116
+
117
+ # 清理狀態
118
+ self.is_running = False
119
+ self.cleanup_thread: threading.Thread | None = None
120
+ self._stop_event = threading.Event()
121
+
122
+ # 回調函數
123
+ self.cleanup_callbacks: list[Callable] = []
124
+ self.stats_callbacks: list[Callable] = []
125
+
126
+ # 清理歷史記錄
127
+ self.cleanup_history: list[dict[str, Any]] = []
128
+ self.max_history = 100
129
+
130
+ debug_log("SessionCleanupManager 初始化完成")
131
+
132
+ def start_auto_cleanup(self) -> bool:
133
+ """啟動自動清理"""
134
+ if not self.policy.enable_auto_cleanup:
135
+ debug_log("自動清理已禁用")
136
+ return False
137
+
138
+ if self.is_running:
139
+ debug_log("自動清理已在運行")
140
+ return True
141
+
142
+ try:
143
+ self.is_running = True
144
+ self._stop_event.clear()
145
+
146
+ self.cleanup_thread = threading.Thread(
147
+ target=self._auto_cleanup_loop,
148
+ name="SessionCleanupManager",
149
+ daemon=True,
150
+ )
151
+ self.cleanup_thread.start()
152
+
153
+ debug_log(f"自動清理已啟動,間隔 {self.policy.cleanup_interval} 秒")
154
+ return True
155
+
156
+ except Exception as e:
157
+ self.is_running = False
158
+ error_id = ErrorHandler.log_error_with_context(
159
+ e, context={"operation": "啟動自動清理"}, error_type=ErrorType.SYSTEM
160
+ )
161
+ debug_log(f"啟動自動清理失敗 [錯誤ID: {error_id}]: {e}")
162
+ return False
163
+
164
+ def stop_auto_cleanup(self) -> bool:
165
+ """停止自動清理"""
166
+ if not self.is_running:
167
+ debug_log("自動清理未在運行")
168
+ return True
169
+
170
+ try:
171
+ self.is_running = False
172
+ self._stop_event.set()
173
+
174
+ if self.cleanup_thread and self.cleanup_thread.is_alive():
175
+ self.cleanup_thread.join(timeout=5)
176
+
177
+ debug_log("自動清理已停止")
178
+ return True
179
+
180
+ except Exception as e:
181
+ error_id = ErrorHandler.log_error_with_context(
182
+ e, context={"operation": "停止自動清理"}, error_type=ErrorType.SYSTEM
183
+ )
184
+ debug_log(f"停止自動清理失敗 [錯誤ID: {error_id}]: {e}")
185
+ return False
186
+
187
+ def _auto_cleanup_loop(self):
188
+ """自動清理主循環"""
189
+ debug_log("自動清理循環開始")
190
+
191
+ while not self._stop_event.is_set():
192
+ try:
193
+ # 執行清理檢查
194
+ self._perform_auto_cleanup()
195
+
196
+ # 等待下次清理
197
+ if self._stop_event.wait(self.policy.cleanup_interval):
198
+ break
199
+
200
+ except Exception as e:
201
+ error_id = ErrorHandler.log_error_with_context(
202
+ e,
203
+ context={"operation": "自動清理循環"},
204
+ error_type=ErrorType.SYSTEM,
205
+ )
206
+ debug_log(f"自動清理循環錯誤 [錯誤ID: {error_id}]: {e}")
207
+
208
+ # 發生錯誤時等待較短時間後重試
209
+ if self._stop_event.wait(30):
210
+ break
211
+
212
+ debug_log("自動清理循環結束")
213
+
214
+ def _perform_auto_cleanup(self):
215
+ """執行自動清理"""
216
+ cleanup_start_time = time.time()
217
+ cleaned_sessions = 0
218
+
219
+ try:
220
+ total = self._store.session_count()
221
+ _get_struct_logger("workbench").info(
222
+ f"total_sessions={total}",
223
+ "[CLEANUP_SCAN] 开始扫描过期会话",
224
+ )
225
+ if total > self.policy.max_sessions:
226
+ cleaned = self._cleanup_by_capacity()
227
+ cleaned_sessions += cleaned
228
+ debug_log(f"容量限制清理了 {cleaned} 個會話")
229
+
230
+ # 2. 清理過期會話
231
+ cleaned = self._cleanup_expired_sessions()
232
+ cleaned_sessions += cleaned
233
+
234
+ # 3. 清理空閒會話
235
+ cleaned = self._cleanup_idle_sessions()
236
+ cleaned_sessions += cleaned
237
+
238
+ # 4. 更新統計
239
+ cleanup_duration = time.time() - cleanup_start_time
240
+ self._update_cleanup_stats(
241
+ CleanupTrigger.AUTO, cleaned_sessions, cleanup_duration
242
+ )
243
+
244
+ if cleaned_sessions > 0:
245
+ debug_log(
246
+ f"自動清理完成,清理了 {cleaned_sessions} 個會話,耗時: {cleanup_duration:.2f}秒"
247
+ )
248
+
249
+ except Exception as e:
250
+ error_id = ErrorHandler.log_error_with_context(
251
+ e, context={"operation": "執行自動清理"}, error_type=ErrorType.SYSTEM
252
+ )
253
+ debug_log(f"執行自動清理失敗 [錯誤ID: {error_id}]: {e}")
254
+
255
+ def trigger_cleanup(self, trigger: CleanupTrigger, force: bool = False) -> int:
256
+ """觸發清理操作"""
257
+ cleanup_start_time = time.time()
258
+ cleaned_sessions = 0
259
+
260
+ try:
261
+ debug_log(f"觸發清理操作,觸發器: {trigger.value},強制: {force}")
262
+
263
+ if trigger == CleanupTrigger.MEMORY_PRESSURE:
264
+ cleaned_sessions = (
265
+ self.web_ui_manager.cleanup_sessions_by_memory_pressure(force) # type: ignore[attr-defined]
266
+ )
267
+ elif trigger == CleanupTrigger.EXPIRED:
268
+ cleaned_sessions = self._cleanup_expired_sessions()
269
+ elif trigger == CleanupTrigger.CAPACITY:
270
+ cleaned_sessions = self._cleanup_by_capacity()
271
+ elif trigger == CleanupTrigger.MANUAL:
272
+ cleaned_sessions += self._cleanup_expired_sessions()
273
+ if force:
274
+ cleaned_sessions += (
275
+ self.web_ui_manager.cleanup_sessions_by_memory_pressure(force) # type: ignore[attr-defined]
276
+ )
277
+ else:
278
+ # 自動清理
279
+ self._perform_auto_cleanup()
280
+ return 0 # 統計已在 _perform_auto_cleanup 中更新
281
+
282
+ # 更新統計
283
+ cleanup_duration = time.time() - cleanup_start_time
284
+ self._update_cleanup_stats(trigger, cleaned_sessions, cleanup_duration)
285
+
286
+ debug_log(
287
+ f"清理操作完成,清理了 {cleaned_sessions} 個會話,耗時: {cleanup_duration:.2f}秒"
288
+ )
289
+ return cleaned_sessions
290
+
291
+ except Exception as e:
292
+ error_id = ErrorHandler.log_error_with_context(
293
+ e,
294
+ context={
295
+ "operation": "觸發清理",
296
+ "trigger": trigger.value,
297
+ "force": force,
298
+ },
299
+ error_type=ErrorType.SYSTEM,
300
+ )
301
+ debug_log(f"觸發清理操作失敗 [錯誤ID: {error_id}]: {e}")
302
+ return 0
303
+
304
+ def _cleanup_by_capacity(self) -> int:
305
+ """根據容量限制清理會話"""
306
+ count = self._store.session_count()
307
+ if count <= self.policy.max_sessions:
308
+ return 0
309
+
310
+ excess_count = count - self.policy.max_sessions
311
+ current_id = self._store.current_session_id
312
+
313
+ session_priorities: list[tuple[str, Any, float]] = []
314
+ for session_id, session in self._store.session_items():
315
+ if (
316
+ self.policy.preserve_active_session
317
+ and current_id
318
+ and session.session_id == current_id
319
+ ):
320
+ continue
321
+
322
+ priority_score = 0.0
323
+
324
+ if session.status in [
325
+ SessionStatus.COMPLETED,
326
+ SessionStatus.ERROR,
327
+ SessionStatus.TIMEOUT,
328
+ ]:
329
+ priority_score += 100
330
+ elif session.status == SessionStatus.FEEDBACK_SUBMITTED:
331
+ priority_score += 50
332
+
333
+ priority_score += session.get_age() / 60
334
+ priority_score += session.get_idle_time() / 30
335
+
336
+ session_priorities.append((session_id, session, priority_score))
337
+
338
+ session_priorities.sort(key=lambda x: x[2], reverse=True)
339
+ cleaned_count = 0
340
+
341
+ for i in range(min(excess_count, len(session_priorities))):
342
+ session_id, session, _ = session_priorities[i]
343
+ try:
344
+ session._cleanup_sync_enhanced(CleanupReason.MANUAL)
345
+ self._store.session_remove(session_id)
346
+ cleaned_count += 1
347
+ except Exception as e:
348
+ debug_log(f"容量清理會話 {session_id} 失敗: {e}")
349
+
350
+ return cleaned_count
351
+
352
+ def _cleanup_expired_sessions(self) -> int:
353
+ """清理過期會話"""
354
+ expired_ids: list[str] = []
355
+
356
+ for session_id, session in self._store.session_items():
357
+ if session.is_expired() or session.get_age() > self.policy.max_session_age:
358
+ expired_ids.append(session_id)
359
+
360
+ cleaned_count = 0
361
+ for session_id in expired_ids:
362
+ try:
363
+ session = self._store.session_get(session_id)
364
+ if session:
365
+ _age = session.get_age()
366
+ _get_struct_logger("workbench").info(
367
+ f"session={session_id} age_seconds={_age:.0f} reason=expired",
368
+ "[CLEANUP_SESSION] 清理过期会话",
369
+ )
370
+ session._cleanup_sync_enhanced(CleanupReason.EXPIRED)
371
+ self._store.session_remove(session_id)
372
+ cleaned_count += 1
373
+ except Exception as e:
374
+ debug_log(f"清理過期會話 {session_id} 失敗: {e}")
375
+
376
+ return cleaned_count
377
+
378
+ def _cleanup_idle_sessions(self) -> int:
379
+ """清理空閒會話"""
380
+ current_id = self._store.current_session_id
381
+ idle_ids: list[str] = []
382
+
383
+ for session_id, session in self._store.session_items():
384
+ if (
385
+ self.policy.preserve_active_session
386
+ and current_id
387
+ and session.session_id == current_id
388
+ ):
389
+ continue
390
+
391
+ if session.get_idle_time() > self.policy.max_idle_time:
392
+ idle_ids.append(session_id)
393
+
394
+ cleaned_count = 0
395
+ for session_id in idle_ids:
396
+ try:
397
+ session = self._store.session_get(session_id)
398
+ if session:
399
+ session._cleanup_sync_enhanced(CleanupReason.EXPIRED)
400
+ self._store.session_remove(session_id)
401
+ cleaned_count += 1
402
+ except Exception as e:
403
+ debug_log(f"清理空閒會話 {session_id} 失敗: {e}")
404
+
405
+ return cleaned_count
406
+
407
+ def _update_cleanup_stats(
408
+ self, trigger: CleanupTrigger, cleaned_count: int, duration: float
409
+ ):
410
+ """更新清理統計"""
411
+ self.stats.total_cleanups += 1
412
+ self.stats.total_sessions_cleaned += cleaned_count
413
+ self.stats.total_cleanup_time += duration
414
+ self.stats.last_cleanup_time = datetime.now()
415
+
416
+ # 更新平均清理時間
417
+ if self.stats.total_cleanups > 0:
418
+ self.stats.average_cleanup_time = (
419
+ self.stats.total_cleanup_time / self.stats.total_cleanups
420
+ )
421
+
422
+ total_sessions = self._store.session_count() + cleaned_count
423
+ if total_sessions > 0:
424
+ self.stats.cleanup_efficiency = cleaned_count / total_sessions
425
+
426
+ # 根據觸發器類型更新統計
427
+ if trigger == CleanupTrigger.AUTO:
428
+ self.stats.auto_cleanups += 1
429
+ elif trigger == CleanupTrigger.MEMORY_PRESSURE:
430
+ self.stats.memory_pressure_cleanups += 1
431
+ elif trigger == CleanupTrigger.EXPIRED:
432
+ self.stats.expired_cleanups += 1
433
+ elif trigger == CleanupTrigger.MANUAL:
434
+ self.stats.manual_cleanups += 1
435
+
436
+ # 記錄清理歷史
437
+ cleanup_record = {
438
+ "timestamp": datetime.now().isoformat(),
439
+ "trigger": trigger.value,
440
+ "cleaned_count": cleaned_count,
441
+ "duration": duration,
442
+ "total_sessions_before": total_sessions,
443
+ "total_sessions_after": self._store.session_count(),
444
+ }
445
+
446
+ self.cleanup_history.append(cleanup_record)
447
+
448
+ # 限制歷史記錄數量
449
+ if len(self.cleanup_history) > self.max_history:
450
+ self.cleanup_history = self.cleanup_history[-self.max_history :]
451
+
452
+ # 調用統計回調
453
+ for callback in self.stats_callbacks:
454
+ try:
455
+ callback(self.stats, cleanup_record)
456
+ except Exception as e:
457
+ debug_log(f"統計回調執行失敗: {e}")
458
+
459
+ def get_cleanup_statistics(self) -> dict[str, Any]:
460
+ """獲取清理統計數據"""
461
+ stats_dict = {
462
+ "total_cleanups": self.stats.total_cleanups,
463
+ "expired_cleanups": self.stats.expired_cleanups,
464
+ "memory_pressure_cleanups": self.stats.memory_pressure_cleanups,
465
+ "manual_cleanups": self.stats.manual_cleanups,
466
+ "auto_cleanups": self.stats.auto_cleanups,
467
+ "total_sessions_cleaned": self.stats.total_sessions_cleaned,
468
+ "total_cleanup_time": round(self.stats.total_cleanup_time, 2),
469
+ "average_cleanup_time": round(self.stats.average_cleanup_time, 2),
470
+ "cleanup_efficiency": round(self.stats.cleanup_efficiency, 3),
471
+ "last_cleanup_time": self.stats.last_cleanup_time.isoformat()
472
+ if self.stats.last_cleanup_time
473
+ else None,
474
+ "is_auto_cleanup_running": self.is_running,
475
+ "current_sessions": self._store.session_count(),
476
+ "policy": {
477
+ "max_idle_time": self.policy.max_idle_time,
478
+ "max_session_age": self.policy.max_session_age,
479
+ "max_sessions": self.policy.max_sessions,
480
+ "cleanup_interval": self.policy.cleanup_interval,
481
+ "enable_auto_cleanup": self.policy.enable_auto_cleanup,
482
+ "preserve_active_session": self.policy.preserve_active_session,
483
+ },
484
+ }
485
+
486
+ return stats_dict
487
+
488
+ def get_cleanup_history(self, limit: int = 20) -> list[dict[str, Any]]:
489
+ """獲取清理歷史記錄"""
490
+ return self.cleanup_history[-limit:] if self.cleanup_history else []
491
+
492
+ def add_cleanup_callback(self, callback: Callable):
493
+ """添加清理回調函數"""
494
+ if callback not in self.cleanup_callbacks:
495
+ self.cleanup_callbacks.append(callback)
496
+ debug_log("添加清理回調函數")
497
+
498
+ def add_stats_callback(self, callback: Callable):
499
+ """添加統計回調函數"""
500
+ if callback not in self.stats_callbacks:
501
+ self.stats_callbacks.append(callback)
502
+ debug_log("添加統計回調函數")
503
+
504
+ def update_policy(self, **kwargs):
505
+ """更新清理策略"""
506
+ for key, value in kwargs.items():
507
+ if hasattr(self.policy, key):
508
+ setattr(self.policy, key, value)
509
+ debug_log(f"更新清理策略 {key} = {value}")
510
+ else:
511
+ debug_log(f"未知的策略參數: {key}")
512
+
513
+ def reset_stats(self):
514
+ """重置統計數據"""
515
+ self.stats = CleanupStats()
516
+ self.cleanup_history.clear()
517
+ debug_log("清理統計數據已重置")
518
+
519
+ def force_cleanup_all(self, exclude_current: bool = True) -> int:
520
+ """強制清理所有會話"""
521
+ current_id = self._store.current_session_id if exclude_current else None
522
+ ids_to_clean: list[str] = []
523
+
524
+ for session_id, session in self._store.session_items():
525
+ if current_id and session.session_id == current_id:
526
+ continue
527
+ ids_to_clean.append(session_id)
528
+
529
+ cleaned_count = 0
530
+ for session_id in ids_to_clean:
531
+ try:
532
+ session = self._store.session_get(session_id)
533
+ if session:
534
+ session._cleanup_sync_enhanced(CleanupReason.MANUAL)
535
+ self._store.session_remove(session_id)
536
+ cleaned_count += 1
537
+ except Exception as e:
538
+ debug_log(f"強制清理會話 {session_id} 失敗: {e}")
539
+
540
+ self._update_cleanup_stats(CleanupTrigger.MANUAL, cleaned_count, 0.0)
541
+
542
+ debug_log(f"強制清理完成,清理了 {cleaned_count} 個會話")
543
+ return cleaned_count