@agentunion/kite 1.4.0 → 1.5.0

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 (235) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/cli.js +44 -5
  3. package/core/dependency_checker.py +250 -0
  4. package/core/env_checker.py +490 -0
  5. package/dependencies_lock.json +128 -0
  6. package/extensions/agents/assistant/server.py +33 -17
  7. package/extensions/channels/acp_channel/server.py +33 -17
  8. package/extensions/services/backup/entry.py +23 -16
  9. package/extensions/services/evol/auth_manager.py +443 -0
  10. package/extensions/services/evol/config.yaml +149 -0
  11. package/extensions/services/evol/config_loader.py +117 -0
  12. package/extensions/services/evol/entry.py +406 -0
  13. package/extensions/services/evol/evol_api.py +173 -0
  14. package/extensions/services/evol/evol_config.json5 +29 -0
  15. package/extensions/services/evol/migrate_tokens.py +122 -0
  16. package/extensions/services/evol/module.md +32 -0
  17. package/extensions/services/evol/pairing.py +250 -0
  18. package/extensions/services/evol/pairing_codes.jsonl +1 -0
  19. package/extensions/services/evol/relay.py +682 -0
  20. package/extensions/services/evol/relay_config.json5 +67 -0
  21. package/extensions/services/evol/routes/__init__.py +1 -0
  22. package/extensions/services/evol/routes/routes_management_ws.py +127 -0
  23. package/extensions/services/evol/routes/routes_rpc.py +89 -0
  24. package/extensions/services/evol/routes/routes_test.py +61 -0
  25. package/extensions/services/evol/server.py +875 -0
  26. package/extensions/services/evol/static/css/style.css +1200 -0
  27. package/extensions/services/evol/static/index.html +781 -0
  28. package/extensions/services/evol/static/index_evol.html +14 -0
  29. package/extensions/services/evol/static/js/app.js +6304 -0
  30. package/extensions/services/evol/static/js/auth.js +326 -0
  31. package/extensions/services/evol/static/js/dialog.js +285 -0
  32. package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
  33. package/extensions/services/evol/static/js/evol-app.js +1949 -0
  34. package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
  35. package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
  36. package/extensions/services/evol/static/js/kernel-client.js +396 -0
  37. package/extensions/services/evol/static/js/main.js +141 -0
  38. package/extensions/services/evol/static/js/registry-tests.js +585 -0
  39. package/extensions/services/evol/static/js/stats.js +217 -0
  40. package/extensions/services/evol/static/js/token-manager.js +175 -0
  41. package/extensions/services/evol/static/pairing.html +248 -0
  42. package/extensions/services/evol/static/test_registry.html +262 -0
  43. package/extensions/services/evol/static/test_relay.html +462 -0
  44. package/extensions/services/evol/stats_manager.py +240 -0
  45. package/extensions/services/model_service/entry.py +23 -1
  46. package/extensions/services/proxy/.claude/settings.local.json +13 -0
  47. package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
  48. package/extensions/services/proxy/_fix_prints.py +133 -0
  49. package/extensions/services/proxy/_fix_prints2.py +87 -0
  50. package/extensions/services/proxy/agentcp/LICENCE +178 -0
  51. package/extensions/services/proxy/agentcp/README copy.md +85 -0
  52. package/extensions/services/proxy/agentcp/README.md +260 -0
  53. package/extensions/services/proxy/agentcp/__init__.py +16 -0
  54. package/extensions/services/proxy/agentcp/agent.py +4 -0
  55. package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
  56. package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
  57. package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
  58. package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
  59. package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
  60. package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
  61. package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
  62. package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
  63. package/extensions/services/proxy/agentcp/base/client.py +112 -0
  64. package/extensions/services/proxy/agentcp/base/env.py +34 -0
  65. package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
  66. package/extensions/services/proxy/agentcp/base/log.py +98 -0
  67. package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
  68. package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
  69. package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
  70. package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
  71. package/extensions/services/proxy/agentcp/context/context.py +73 -0
  72. package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
  73. package/extensions/services/proxy/agentcp/create_profile.py +125 -0
  74. package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
  75. package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
  76. package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
  77. package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
  78. package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
  79. package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
  80. package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
  81. package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
  82. package/extensions/services/proxy/agentcp/hcp.py +299 -0
  83. package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
  84. package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
  85. package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
  86. package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
  87. package/extensions/services/proxy/agentcp/llm_server.py +172 -0
  88. package/extensions/services/proxy/agentcp/mermaid.py +210 -0
  89. package/extensions/services/proxy/agentcp/message.py +149 -0
  90. package/extensions/services/proxy/agentcp/metrics.py +256 -0
  91. package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
  92. package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
  93. package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
  94. package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
  95. package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
  96. package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
  97. package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
  98. package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
  99. package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
  100. package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
  101. package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
  102. package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
  103. package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
  104. package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
  105. package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
  106. package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
  107. package/extensions/services/proxy/agentcp/requirements.txt +7 -0
  108. package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
  109. package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
  110. package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
  111. package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
  112. package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
  113. package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
  114. package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
  115. package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
  116. package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
  117. package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
  118. package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
  119. package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
  120. package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
  121. package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
  122. package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
  123. package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
  124. package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
  125. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
  126. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
  127. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
  128. package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
  129. package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
  130. package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
  131. package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
  132. package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
  133. package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
  134. package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
  135. package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
  136. package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
  137. package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
  138. package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
  139. package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
  140. package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
  141. package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
  142. package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
  143. package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
  144. package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
  145. package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
  146. package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
  147. package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
  148. package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
  149. package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
  150. package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
  151. package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
  152. package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
  153. package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
  154. package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
  155. package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
  156. package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
  157. package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
  158. package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
  159. package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
  160. package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
  161. package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
  162. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
  163. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
  164. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
  165. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
  166. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
  167. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
  168. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
  169. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
  170. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
  171. package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
  172. package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
  173. package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
  174. package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
  175. package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
  176. package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
  177. package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
  178. package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
  179. package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
  180. package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
  181. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
  182. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
  183. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
  184. package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
  185. package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
  186. package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
  187. package/extensions/services/proxy/agentcp/workflow.py +203 -0
  188. package/extensions/services/proxy/console_auth.py +109 -0
  189. package/extensions/services/proxy/evol/__init__.py +1 -0
  190. package/extensions/services/proxy/evol/config.py +37 -0
  191. package/extensions/services/proxy/evol/http/__init__.py +1 -0
  192. package/extensions/services/proxy/evol/http/async_http.py +551 -0
  193. package/extensions/services/proxy/evol/log.py +28 -0
  194. package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
  195. package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
  196. package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
  197. package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
  198. package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
  199. package/extensions/services/proxy/evol/server/__init__.py +1 -0
  200. package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
  201. package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
  202. package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
  203. package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
  204. package/extensions/services/proxy/evol/version.py +24 -0
  205. package/extensions/services/proxy/logs/websocket.log +260 -0
  206. package/extensions/services/proxy/main.py +240 -0
  207. package/extensions/services/proxy/requirements.txt +13 -0
  208. package/extensions/services/proxy/server.py +271 -0
  209. package/extensions/services/watchdog/entry.py +42 -16
  210. package/extensions/services/watchdog/module.md +1 -0
  211. package/extensions/services/watchdog/monitor.py +34 -4
  212. package/extensions/services/web/module.md +1 -1
  213. package/extensions/services/web/server.py +30 -18
  214. package/extensions/services/web/static/js/token-manager.js +10 -10
  215. package/kernel/entry.py +1 -1
  216. package/kernel/module.md +25 -1
  217. package/kernel/registry_store.py +2 -26
  218. package/kernel/rpc_router.py +36 -10
  219. package/kernel/server.py +106 -17
  220. package/kite_cli/commands/deps_install.py +67 -0
  221. package/kite_cli/commands/env_check.py +45 -0
  222. package/kite_cli/commands/prepare.py +49 -0
  223. package/kite_cli/commands/venv_setup.py +56 -0
  224. package/kite_cli/main.py +29 -1
  225. package/launcher/entry.py +306 -21
  226. package/launcher/module.md +9 -0
  227. package/launcher/module_scanner.py +11 -1
  228. package/main.py +4 -1
  229. package/package.json +8 -1
  230. package/python_version.json +4 -0
  231. package/requirements.txt +38 -0
  232. package/scripts/env-manager.js +328 -0
  233. package/scripts/python-env.js +79 -0
  234. package/scripts/scan_dependencies.py +461 -0
  235. package/scripts/setup-python-env.js +191 -0
@@ -0,0 +1,1031 @@
1
+ """
2
+ agentIdPresenter.py - AgentID 上线统一管理模块
3
+
4
+ 【重要说明】
5
+ 本模块是 AgentID 上线后的统一入口点。
6
+ 无论在代码的哪个位置调用 agentId.online(),都必须在上线成功后调用本模块的 evol_agentId_online() 方法。
7
+
8
+ 【调用位置清单】(需要在以下位置的 agentId.online() 之后调用 evol_agentId_online)
9
+ 1. python_backend/evol/presenter/userPresenter.py:1726 - login() 用户登录
10
+ 2. python_backend/evol/presenter/userPresenter.py:1786 - load_aid() 加载AID
11
+ 3. python_backend/evol/presenter/userPresenter.py:1887 - quick_login() 快速登录
12
+ 4. python_backend/evol/presenter/agentPresenter.py:47 - set_aid() 设置AgentID
13
+ 5. python_backend/evol/server/claude_proxy_async.py:155 - full_rebuild_agentcp() 同步重建
14
+ 6. python_backend/evol/server/claude_proxy_async.py:217 - full_rebuild_agentcp() 新AID上线
15
+ 7. python_backend/evol/server/claude_proxy_async.py:333 - force_rebuild_agentcp() 异步重建
16
+ 8. python_backend/evol/server/claude_proxy_async.py:2089 - async_generate_handler() Session重试
17
+
18
+ 【使用示例】
19
+ from evol.presenter.agentIdPresenter import agentIdPresenter
20
+
21
+ # 在 agentId.online() 成功后调用
22
+ agentId.online()
23
+ if agentId.is_online_success:
24
+ agentIdPresenter.evol_agentId_online(agentId)
25
+
26
+ 【消息协议格式】
27
+
28
+ AgentCP 原始消息格式(message.message 字段解析后):
29
+ [
30
+ {
31
+ "type": "content", # 固定为 "content"
32
+ "content": "{\"action\":\"rpc_call\",\"trace_id\":\"xxx\",...}" # JSON 字符串
33
+ }
34
+ ]
35
+
36
+ content 字段解析后的 RPC 请求格式:
37
+ {
38
+ "action": "rpc_call", # 动作类型,固定为 rpc_call
39
+ "trace_id": "uuid-xxx", # 追踪ID,用于响应匹配
40
+ "presenter": "claudeCodePresenter", # 目标 Presenter 名称
41
+ "method": "scan_logs", # 要调用的方法名
42
+ "params": { # 方法参数(可选)
43
+ "use_cache": true,
44
+ "force_refresh": false
45
+ }
46
+ }
47
+
48
+ 响应消息格式(发送时自动包装为 content 类型):
49
+ {
50
+ "action": "rpc_response", # 响应类型
51
+ "trace_id": "uuid-xxx", # 对应请求的追踪ID
52
+ "status": "success" | "error", # 执行状态
53
+ "result": {...}, # 成功时的返回结果
54
+ "error": "错误信息" # 失败时的错误信息
55
+ }
56
+ """
57
+
58
+ import asyncio
59
+ import inspect
60
+ import json
61
+ import traceback
62
+ import threading
63
+ import uuid
64
+ from typing import Optional, Callable, Awaitable, List, Dict, Any, Type, Iterator
65
+ from agentcp import AgentID
66
+
67
+
68
+ # ==================== 流管理器 ====================
69
+
70
+ class StreamManager:
71
+ """
72
+ ACP 流管理器
73
+
74
+ 负责创建和管理 ACP 流,用于将本地迭代器的数据推送到远程。
75
+
76
+ 使用流程:
77
+ 1. 调用 create_stream_for_iterator() 创建流
78
+ 2. 返回 pull_url 给调用方
79
+ 3. 后台任务将迭代器数据推送到 push_url
80
+ """
81
+
82
+ # 活跃的流任务: {stream_id: task_info}
83
+ _active_streams: Dict[str, Dict[str, Any]] = {}
84
+ _lock = threading.Lock()
85
+
86
+ @classmethod
87
+ async def create_stream_for_iterator(
88
+ cls,
89
+ agentId: 'AgentID',
90
+ session_id: str,
91
+ to_aid_list: list,
92
+ iterator: Iterator[dict],
93
+ content_type: str = "text/event-stream",
94
+ ref_msg_id: str = ""
95
+ ) -> Dict[str, Any]:
96
+ """
97
+ 为迭代器创建 ACP 流
98
+
99
+ Args:
100
+ agentId: AgentID 实例
101
+ session_id: 会话 ID
102
+ to_aid_list: 目标 AID 列表
103
+ iterator: 数据迭代器
104
+ content_type: 内容类型
105
+ ref_msg_id: 引用消息 ID
106
+
107
+ Returns:
108
+ {
109
+ 'success': True/False,
110
+ 'pull_url': '拉取流的 URL',
111
+ 'stream_id': '流 ID',
112
+ 'error': '错误信息(如果失败)'
113
+ }
114
+ """
115
+ try:
116
+ # 1. 创建 ACP 流
117
+ stream_result = await agentId.create_stream(
118
+ session_id, to_aid_list, content_type, ref_msg_id
119
+ )
120
+ push_url, pull_url = stream_result
121
+
122
+ if push_url is None:
123
+ return {
124
+ 'success': False,
125
+ 'error': f"创建流失败: {pull_url}"
126
+ }
127
+
128
+ # 2. 生成流 ID
129
+ stream_id = str(uuid.uuid4())
130
+
131
+ # 3. 启动后台任务推送数据
132
+ task = asyncio.create_task(
133
+ cls._push_iterator_to_stream(
134
+ agentId, session_id, push_url, iterator, stream_id, content_type
135
+ )
136
+ )
137
+
138
+ # 4. 记录活跃流
139
+ with cls._lock:
140
+ cls._active_streams[stream_id] = {
141
+ 'task': task,
142
+ 'push_url': push_url,
143
+ 'pull_url': pull_url,
144
+ 'session_id': session_id,
145
+ 'status': 'running'
146
+ }
147
+
148
+ print(f"✅ [StreamManager] 流创建成功: stream_id={stream_id}")
149
+ print(f" pull_url: {pull_url}")
150
+
151
+ return {
152
+ 'success': True,
153
+ 'pull_url': pull_url,
154
+ 'stream_id': stream_id
155
+ }
156
+
157
+ except Exception as e:
158
+ print(f"❌ [StreamManager] 创建流失败: {e}")
159
+ traceback.print_exc()
160
+ return {
161
+ 'success': False,
162
+ 'error': str(e)
163
+ }
164
+
165
+ @classmethod
166
+ async def _push_iterator_to_stream(
167
+ cls,
168
+ agentId: 'AgentID',
169
+ session_id: str,
170
+ push_url: str,
171
+ iterator: Iterator[dict],
172
+ stream_id: str,
173
+ content_type: str
174
+ ):
175
+ """
176
+ 后台任务:将迭代器数据推送到流
177
+
178
+ Args:
179
+ agentId: AgentID 实例
180
+ session_id: 会话 ID
181
+ push_url: 推送 URL
182
+ iterator: 数据迭代器
183
+ stream_id: 流 ID
184
+ content_type: 内容类型
185
+ """
186
+ try:
187
+ print(f"🚀 [StreamManager] 开始推送数据: stream_id={stream_id}")
188
+
189
+ chunk_count = 0
190
+
191
+ # 使用线程池处理同步迭代器,避免阻塞事件循环
192
+ def process_iterator():
193
+ nonlocal chunk_count
194
+ for chunk in iterator:
195
+ # 将 chunk 转换为 JSON 字符串
196
+ if isinstance(chunk, dict):
197
+ chunk_str = json.dumps(chunk, ensure_ascii=False)
198
+ elif isinstance(chunk, str):
199
+ chunk_str = chunk
200
+ else:
201
+ chunk_str = str(chunk)
202
+
203
+ # 推送到流
204
+ agentId.send_chunk_to_stream(session_id, push_url, chunk_str, type=content_type)
205
+ chunk_count += 1
206
+
207
+ # 在线程池中执行同步迭代器
208
+ await asyncio.to_thread(process_iterator)
209
+
210
+ print(f"✅ [StreamManager] 数据推送完成: stream_id={stream_id}, chunks={chunk_count}")
211
+
212
+ except Exception as e:
213
+ print(f"❌ [StreamManager] 推送数据失败: stream_id={stream_id}, error={e}")
214
+ traceback.print_exc()
215
+
216
+ # 发送错误事件
217
+ try:
218
+ error_chunk = json.dumps({
219
+ 'type': 'error',
220
+ 'error': str(e)
221
+ }, ensure_ascii=False)
222
+ agentId.send_chunk_to_stream(session_id, push_url, error_chunk, type=content_type)
223
+ except:
224
+ pass
225
+
226
+ finally:
227
+ # 关闭流
228
+ try:
229
+ agentId.close_stream(session_id, push_url)
230
+ print(f"🔚 [StreamManager] 流已关闭: stream_id={stream_id}")
231
+ except Exception as e:
232
+ print(f"⚠️ [StreamManager] 关闭流失败: {e}")
233
+
234
+ # 更新状态
235
+ with cls._lock:
236
+ if stream_id in cls._active_streams:
237
+ cls._active_streams[stream_id]['status'] = 'completed'
238
+
239
+ @classmethod
240
+ def get_stream_status(cls, stream_id: str) -> Optional[Dict[str, Any]]:
241
+ """获取流状态"""
242
+ with cls._lock:
243
+ return cls._active_streams.get(stream_id)
244
+
245
+ @classmethod
246
+ def cancel_stream(cls, stream_id: str) -> bool:
247
+ """取消流"""
248
+ with cls._lock:
249
+ stream_info = cls._active_streams.get(stream_id)
250
+ if stream_info and stream_info.get('task'):
251
+ stream_info['task'].cancel()
252
+ stream_info['status'] = 'cancelled'
253
+ return True
254
+ return False
255
+
256
+ @classmethod
257
+ def cleanup_completed_streams(cls):
258
+ """清理已完成的流"""
259
+ with cls._lock:
260
+ completed = [
261
+ sid for sid, info in cls._active_streams.items()
262
+ if info.get('status') in ('completed', 'cancelled')
263
+ ]
264
+ for sid in completed:
265
+ del cls._active_streams[sid]
266
+ if completed:
267
+ print(f"🧹 [StreamManager] 清理了 {len(completed)} 个已完成的流")
268
+
269
+
270
+ # ==================== Presenter 注册表 ====================
271
+
272
+ class PresenterRegistry:
273
+ """
274
+ Presenter 注册表
275
+
276
+ 管理所有可被远程调用的 Presenter 类。
277
+ 支持动态注册和方法白名单控制。
278
+ """
279
+
280
+ # 已注册的 Presenter 映射: {presenter_name: presenter_class}
281
+ _presenters: Dict[str, Type] = {}
282
+
283
+ # 方法白名单: {presenter_name: [allowed_methods]} 或 {presenter_name: "*"} 表示全部允许
284
+ _method_whitelist: Dict[str, Any] = {}
285
+
286
+ @classmethod
287
+ def register(cls, presenter_class: Type, name: str = None, allowed_methods: List[str] = None):
288
+ """
289
+ 注册一个 Presenter
290
+
291
+ Args:
292
+ presenter_class: Presenter 类
293
+ name: 注册名称(可选,默认使用类名)
294
+ allowed_methods: 允许调用的方法列表(可选,默认全部允许)
295
+
296
+ Usage:
297
+ # 注册并允许所有方法
298
+ PresenterRegistry.register(claudeCodePresenter)
299
+
300
+ # 注册并只允许特定方法
301
+ PresenterRegistry.register(claudeCodePresenter, allowed_methods=['scan_logs', 'get_project_detail'])
302
+
303
+ # 使用自定义名称
304
+ PresenterRegistry.register(MyPresenter, name='customName')
305
+ """
306
+ presenter_name = name or presenter_class.__name__
307
+ cls._presenters[presenter_name] = presenter_class
308
+
309
+ if allowed_methods is None:
310
+ cls._method_whitelist[presenter_name] = "*" # 允许所有方法
311
+ else:
312
+ cls._method_whitelist[presenter_name] = allowed_methods
313
+
314
+ print(f"📝 [PresenterRegistry] 注册 Presenter: {presenter_name}")
315
+
316
+ @classmethod
317
+ def unregister(cls, name: str):
318
+ """注销一个 Presenter"""
319
+ if name in cls._presenters:
320
+ del cls._presenters[name]
321
+ del cls._method_whitelist[name]
322
+ print(f"🗑️ [PresenterRegistry] 注销 Presenter: {name}")
323
+
324
+ @classmethod
325
+ def get_presenter(cls, name: str) -> Optional[Type]:
326
+ """获取 Presenter 类"""
327
+ return cls._presenters.get(name)
328
+
329
+ @classmethod
330
+ def is_method_allowed(cls, presenter_name: str, method_name: str) -> bool:
331
+ """检查方法是否在白名单中"""
332
+ whitelist = cls._method_whitelist.get(presenter_name)
333
+ if whitelist is None:
334
+ return False
335
+ if whitelist == "*":
336
+ return True
337
+ return method_name in whitelist
338
+
339
+ @classmethod
340
+ def list_presenters(cls) -> Dict[str, List[str]]:
341
+ """列出所有注册的 Presenter 及其方法"""
342
+ result = {}
343
+ for name, presenter_class in cls._presenters.items():
344
+ methods = []
345
+ whitelist = cls._method_whitelist.get(name, [])
346
+
347
+ for attr_name in dir(presenter_class):
348
+ if attr_name.startswith('_'):
349
+ continue
350
+ attr = getattr(presenter_class, attr_name, None)
351
+ if callable(attr):
352
+ if whitelist == "*" or attr_name in whitelist:
353
+ methods.append(attr_name)
354
+
355
+ result[name] = methods
356
+ return result
357
+
358
+
359
+ # ==================== 初始化注册 Presenter ====================
360
+
361
+ def _init_presenter_registry():
362
+ """初始化 Presenter 注册表,注册所有可用的 Presenter"""
363
+
364
+ # 注册本项目中可用的 Presenter
365
+ try:
366
+ from evol.presenter.configPresenter import configPresenter
367
+ PresenterRegistry.register(configPresenter)
368
+ except ImportError as e:
369
+ print(f"⚠️ [PresenterRegistry] 导入 configPresenter 失败: {e}")
370
+
371
+ print(f"✅ [PresenterRegistry] 初始化完成,已注册 {len(PresenterRegistry._presenters)} 个 Presenter")
372
+
373
+
374
+ # ==================== RPC 调用执行器 ====================
375
+
376
+ class RPCExecutor:
377
+ """
378
+ RPC 调用执行器
379
+
380
+ 负责解析消息、调用 Presenter 方法、处理同步/异步调用
381
+ """
382
+
383
+ @staticmethod
384
+ def _check_remote_access_permission(rpc_context: Dict[str, Any]) -> Dict[str, Any]:
385
+ """
386
+ 检查远程访问权限
387
+
388
+ Args:
389
+ rpc_context: RPC 上下文信息
390
+
391
+ Returns:
392
+ None 表示通过验证,否则返回错误响应字典
393
+ """
394
+ try:
395
+ from evol.presenter.configPresenter import configPresenter
396
+
397
+ # 检查总开关
398
+ if not configPresenter.get_remote_access_enabled():
399
+ return {
400
+ "status": "error",
401
+ "error": "Evol客户端禁止Web访问,请手动开启",
402
+ "error_code": "REMOTE_ACCESS_DISABLED"
403
+ }
404
+
405
+ return None
406
+
407
+ except Exception as e:
408
+ print(f"⚠️ [RPCExecutor] 权限检查失败: {e}")
409
+ # 权限检查失败时,默认拒绝访问
410
+ return {
411
+ "status": "error",
412
+ "error": f"权限验证失败: {str(e)}",
413
+ "error_code": "PERMISSION_CHECK_FAILED"
414
+ }
415
+
416
+ @staticmethod
417
+ async def execute(
418
+ presenter_name: str,
419
+ method_name: str,
420
+ params: Dict[str, Any] = None,
421
+ rpc_context: Dict[str, Any] = None
422
+ ) -> Dict[str, Any]:
423
+ """
424
+ 执行 Presenter 方法调用
425
+
426
+ Args:
427
+ presenter_name: Presenter 名称
428
+ method_name: 方法名
429
+ params: 参数字典
430
+ rpc_context: RPC 上下文信息(包含 sender_aid, session_id, raw_data 等)
431
+
432
+ Returns:
433
+ 执行结果字典
434
+ """
435
+ params = params or {}
436
+ rpc_context = rpc_context or {}
437
+
438
+ # 0. 远程访问权限检查(所有 RPC 调用都需要验证)
439
+ permission_error = RPCExecutor._check_remote_access_permission(rpc_context)
440
+ if permission_error is not None:
441
+ print(f"🚫 [RPCExecutor] 远程访问被拒绝: {presenter_name}.{method_name}, 原因: {permission_error.get('error')}")
442
+ return permission_error
443
+
444
+ # 1. 获取 Presenter
445
+ presenter_class = PresenterRegistry.get_presenter(presenter_name)
446
+ if presenter_class is None:
447
+ return {
448
+ "status": "error",
449
+ "error": f"Presenter '{presenter_name}' 未注册",
450
+ "available_presenters": list(PresenterRegistry._presenters.keys())
451
+ }
452
+
453
+ # 2. 检查方法白名单
454
+ if not PresenterRegistry.is_method_allowed(presenter_name, method_name):
455
+ return {
456
+ "status": "error",
457
+ "error": f"方法 '{method_name}' 不在 '{presenter_name}' 的允许列表中"
458
+ }
459
+
460
+ # 3. 获取方法
461
+ method = getattr(presenter_class, method_name, None)
462
+ if method is None:
463
+ return {
464
+ "status": "error",
465
+ "error": f"方法 '{method_name}' 在 '{presenter_name}' 中不存在"
466
+ }
467
+
468
+ if not callable(method):
469
+ return {
470
+ "status": "error",
471
+ "error": f"'{method_name}' 不是可调用的方法"
472
+ }
473
+
474
+ # 4. 验证参数
475
+ try:
476
+ sig = inspect.signature(method)
477
+ # 过滤掉 self/cls 参数
478
+ valid_params = {}
479
+ for param_name, param in sig.parameters.items():
480
+ if param_name in ('self', 'cls'):
481
+ continue
482
+ # 特殊参数 _rpc_context:自动注入 RPC 上下文
483
+ if param_name == '_rpc_context':
484
+ valid_params['_rpc_context'] = rpc_context
485
+ continue
486
+ if param_name in params:
487
+ valid_params[param_name] = params[param_name]
488
+ elif param.default is inspect.Parameter.empty:
489
+ # 必需参数缺失
490
+ return {
491
+ "status": "error",
492
+ "error": f"缺少必需参数: '{param_name}'"
493
+ }
494
+ except Exception as e:
495
+ # 无法获取签名,直接传入所有参数
496
+ print(f"⚠️ [RPCExecutor] 无法获取方法签名: {e}")
497
+ valid_params = params
498
+
499
+ # 5. 执行方法
500
+ try:
501
+ # 打印更详细的调用信息
502
+ params_preview = str(valid_params)[:500] + "..." if len(str(valid_params)) > 500 else str(valid_params)
503
+ print(f"🔧 [RPCExecutor] 调用 {presenter_name}.{method_name}")
504
+ print(f" 参数: {params_preview}")
505
+ if rpc_context:
506
+ print(f" RPC上下文: sender={rpc_context.get('sender_aid')}, session={rpc_context.get('session_id')}")
507
+
508
+ if asyncio.iscoroutinefunction(method):
509
+ # 异步方法
510
+ result = await method(**valid_params)
511
+ else:
512
+ # 同步方法,在线程池中执行避免阻塞
513
+ result = await asyncio.to_thread(method, **valid_params)
514
+
515
+ print(f"✅ [RPCExecutor] {presenter_name}.{method_name} 执行成功")
516
+
517
+ return {
518
+ "status": "success",
519
+ "result": result
520
+ }
521
+
522
+ except Exception as e:
523
+ error_traceback = traceback.format_exc()
524
+ print(f"❌ [RPCExecutor] {presenter_name}.{method_name} 执行失败!")
525
+ print(f" 错误类型: {type(e).__name__}")
526
+ print(f" 错误信息: {e}")
527
+ print(f" 完整堆栈:\n{error_traceback}")
528
+
529
+ return {
530
+ "status": "error",
531
+ "error": str(e),
532
+ "traceback": error_traceback
533
+ }
534
+
535
+
536
+ # ==================== AgentID Presenter 主类 ====================
537
+
538
+ class agentIdPresenter:
539
+ """
540
+ AgentID 上线统一管理器
541
+
542
+ 职责:
543
+ 1. 统一管理 AgentID 上线后的初始化工作
544
+ 2. 注册消息监听器
545
+ 3. 实现 Presenter 消息分发(RPC 调用)
546
+ 4. 管理监听器的生命周期
547
+
548
+ 【重要】当前用户的 agentId 无论在哪里 online,都需要调用 evol_agentId_online() 方法
549
+ """
550
+
551
+ # 当前已上线的 AgentID
552
+ _current_agentId: Optional[AgentID] = None
553
+
554
+ # 已注册的消息处理器列表(用于追踪和清理)
555
+ _registered_handlers: List[Callable] = []
556
+
557
+ # 是否已初始化监听器
558
+ _listeners_initialized: bool = False
559
+
560
+ # Presenter 注册表是否已初始化
561
+ _registry_initialized: bool = False
562
+
563
+ @classmethod
564
+ def evol_agentId_online(cls, agentId: AgentID) -> bool:
565
+ """
566
+ AgentID 上线后的统一入口方法
567
+
568
+ 【重要】当前用户的 agentId 无论在代码的哪个位置调用 online(),
569
+ 都必须在上线成功后调用此方法。
570
+
571
+ 此方法会:
572
+ 1. 设置当前活跃的 AgentID
573
+ 2. 初始化 Presenter 注册表
574
+ 3. 初始化/重新初始化消息监听器
575
+ 4. 执行其他上线后的初始化工作
576
+
577
+ Args:
578
+ agentId: 已成功上线的 AgentID 实例
579
+
580
+ Returns:
581
+ bool: 初始化是否成功
582
+
583
+ Usage:
584
+ # 在任何位置的 agentId.online() 之后调用
585
+ agentId.online()
586
+ if agentId.is_online_success:
587
+ from evol.presenter.agentIdPresenter import agentIdPresenter
588
+ agentIdPresenter.evol_agentId_online(agentId)
589
+ """
590
+ if agentId is None:
591
+ print("❌ [agentIdPresenter] agentId 为空,无法初始化")
592
+ return False
593
+
594
+ if not agentId.is_online_success:
595
+ print(f"❌ [agentIdPresenter] agentId 未成功上线: {agentId.id}")
596
+ return False
597
+
598
+ try:
599
+ print(f"🔌 [agentIdPresenter] AgentID 上线成功,开始初始化: {agentId.id}")
600
+
601
+ # 初始化 Presenter 注册表(只执行一次)
602
+ if not cls._registry_initialized:
603
+ _init_presenter_registry()
604
+ cls._registry_initialized = True
605
+
606
+ # 如果之前有其他 AgentID,先清理
607
+ if cls._current_agentId is not None and cls._current_agentId.id != agentId.id:
608
+ print(f"🔄 [agentIdPresenter] 切换 AgentID: {cls._current_agentId.id} -> {agentId.id}")
609
+ cls._cleanup_listeners()
610
+
611
+ # 设置当前 AgentID
612
+ cls._current_agentId = agentId
613
+
614
+ # 初始化监听器
615
+ cls._init_message_listeners(agentId)
616
+
617
+ print(f"✅ [agentIdPresenter] AgentID 初始化完成: {agentId.id}")
618
+ return True
619
+
620
+ except Exception as e:
621
+ print(f"❌ [agentIdPresenter] 初始化失败: {str(e)}")
622
+ print(traceback.format_exc())
623
+ return False
624
+
625
+ @classmethod
626
+ def get_current_agentId(cls) -> Optional[AgentID]:
627
+ """
628
+ 获取当前已上线的 AgentID
629
+
630
+ Returns:
631
+ AgentID 或 None
632
+ """
633
+ return cls._current_agentId
634
+
635
+ @classmethod
636
+ def is_online(cls) -> bool:
637
+ """
638
+ 检查当前 AgentID 是否在线
639
+
640
+ Returns:
641
+ bool: True 表示在线
642
+ """
643
+ return (cls._current_agentId is not None and
644
+ cls._current_agentId.is_online_success)
645
+
646
+ @classmethod
647
+ def _init_message_listeners(cls, agentId: AgentID):
648
+ """
649
+ 初始化消息监听器
650
+
651
+ 在此方法中注册 RPC 消息处理器
652
+
653
+ Args:
654
+ agentId: AgentID 实例
655
+ """
656
+ if cls._listeners_initialized and cls._current_agentId == agentId:
657
+ print(f"ℹ️ [agentIdPresenter] 监听器已初始化,跳过重复初始化")
658
+ return
659
+
660
+ print(f"📡 [agentIdPresenter] 初始化消息监听器: {agentId.id}")
661
+
662
+ # 清理旧的监听器
663
+ cls._cleanup_listeners()
664
+
665
+ # 注册 RPC 消息处理器
666
+ async def rpc_message_handler(data: dict):
667
+ """
668
+ RPC 消息处理器
669
+
670
+ 处理所有消息,从 type="content" 的消息中解析 RPC 调用
671
+
672
+ 健壮性保证:
673
+ - 所有操作都在多层 try-except 中,确保不会因为任何异常导致程序崩溃
674
+ - 即使消息格式异常、agentId离线、响应序列化失败,也只记录日志不崩溃
675
+ """
676
+ sender_aid = None
677
+ session_id = None
678
+ trace_id = None
679
+ try:
680
+ if data is None:
681
+ return
682
+
683
+ if not isinstance(data, dict):
684
+ print(f"⚠️ [agentIdPresenter] 收到非dict消息,类型: {type(data)}")
685
+ return
686
+
687
+ # 安全获取发送者信息
688
+ try:
689
+ sender_aid = agentId.get_sender_from_message(data)
690
+ except Exception as e:
691
+ print(f"⚠️ [agentIdPresenter] get_sender_from_message 异常: {e}")
692
+ return
693
+
694
+ try:
695
+ session_id = agentId.get_session_id_from_message(data)
696
+ except Exception as e:
697
+ print(f"⚠️ [agentIdPresenter] get_session_id_from_message 异常: {e}")
698
+ return
699
+
700
+ if not sender_aid or not session_id:
701
+ print(f"⚠️ [agentIdPresenter] 无法获取发送者信息或会话ID,跳过消息")
702
+ return
703
+
704
+ # 安全获取消息内容数组
705
+ try:
706
+ messages = agentId.get_content_array_from_message(data)
707
+ except Exception as e:
708
+ print(f"⚠️ [agentIdPresenter] get_content_array_from_message 异常: {e}")
709
+ return
710
+
711
+ if not messages or len(messages) == 0:
712
+ return
713
+
714
+ # 遍历消息数组,查找 type="content" 的消息
715
+ for msg_item in messages:
716
+ try:
717
+ if not isinstance(msg_item, dict):
718
+ continue
719
+
720
+ msg_type = msg_item.get("type", "")
721
+
722
+ # 只处理 type="content" 的消息
723
+ if msg_type != "content":
724
+ continue
725
+
726
+ # 获取 content 字段
727
+ content_str = msg_item.get("content", "")
728
+ if not content_str:
729
+ continue
730
+
731
+ # 解析 content 为 JSON
732
+ try:
733
+ if isinstance(content_str, str):
734
+ content_data = json.loads(content_str)
735
+ elif isinstance(content_str, dict):
736
+ content_data = content_str
737
+ else:
738
+ continue
739
+ except json.JSONDecodeError:
740
+ # 不是 JSON 格式,跳过
741
+ continue
742
+ except Exception as json_err:
743
+ print(f"⚠️ [agentIdPresenter] JSON 解析异常: {json_err}")
744
+ continue
745
+
746
+ if not isinstance(content_data, dict):
747
+ continue
748
+
749
+ # 检查是否为 RPC 调用
750
+ action = content_data.get("action", "")
751
+ if action != "rpc_call":
752
+ # 不是 RPC 调用,跳过
753
+ continue
754
+
755
+ # 解析 RPC 调用参数
756
+ trace_id = content_data.get("trace_id", "")
757
+ presenter_name = content_data.get("presenter", "")
758
+ method_name = content_data.get("method", "")
759
+ params = content_data.get("params", {})
760
+
761
+ # 确保 params 是 dict
762
+ if not isinstance(params, dict):
763
+ params = {}
764
+
765
+ print(f"📨 [agentIdPresenter] 收到 RPC 调用: {presenter_name}.{method_name}, trace_id: {trace_id}")
766
+
767
+ # 参数验证
768
+ if not presenter_name:
769
+ await cls._send_rpc_response(
770
+ agentId, sender_aid, session_id, trace_id,
771
+ status="error",
772
+ error="缺少 presenter 参数"
773
+ )
774
+ continue
775
+
776
+ if not method_name:
777
+ await cls._send_rpc_response(
778
+ agentId, sender_aid, session_id, trace_id,
779
+ status="error",
780
+ error="缺少 method 参数"
781
+ )
782
+ continue
783
+
784
+ # 构建 RPC 上下文
785
+ rpc_context = {
786
+ 'sender_aid': sender_aid,
787
+ 'session_id': session_id,
788
+ 'raw_data': data,
789
+ 'trace_id': trace_id
790
+ }
791
+
792
+ # 执行 RPC 调用
793
+ try:
794
+ result = await RPCExecutor.execute(presenter_name, method_name, params, rpc_context)
795
+ except Exception as exec_err:
796
+ print(f"❌ [agentIdPresenter] RPCExecutor.execute 异常: {exec_err}")
797
+ traceback.print_exc()
798
+ result = {
799
+ "status": "error",
800
+ "error": f"RPC执行异常: {str(exec_err)}"
801
+ }
802
+
803
+ # 发送响应
804
+ await cls._send_rpc_response(
805
+ agentId, sender_aid, session_id, trace_id,
806
+ status=result.get("status", "error"),
807
+ result=result.get("result"),
808
+ error=result.get("error")
809
+ )
810
+
811
+ except Exception as item_err:
812
+ # 单条消息处理失败,不影响其他消息
813
+ print(f"❌ [agentIdPresenter] 处理单条消息异常: {item_err}")
814
+ traceback.print_exc()
815
+ # 尝试发送错误响应
816
+ if trace_id and sender_aid and session_id:
817
+ try:
818
+ await cls._send_rpc_response(
819
+ agentId, sender_aid, session_id, trace_id,
820
+ status="error",
821
+ error=f"消息处理异常: {str(item_err)}"
822
+ )
823
+ except Exception:
824
+ pass
825
+ continue
826
+
827
+ except Exception as e:
828
+ # 最外层兜底,确保不崩溃
829
+ try:
830
+ print(f"❌ [agentIdPresenter] RPC 消息处理失败: {str(e)}")
831
+ traceback.print_exc()
832
+
833
+ # 尝试发送错误响应
834
+ if sender_aid and session_id:
835
+ await cls._send_rpc_response(
836
+ agentId, sender_aid, session_id, trace_id or "",
837
+ status="error",
838
+ error=f"消息处理异常: {str(e)}"
839
+ )
840
+ except Exception as final_err:
841
+ # 即使异常处理也失败了,只打印日志
842
+ print(f"❌❌ [agentIdPresenter] 严重错误 - 异常处理失败: {final_err}")
843
+
844
+ # 注册监听器
845
+ agentId.add_message_handler(rpc_message_handler)
846
+ cls._registered_handlers.append(rpc_message_handler)
847
+
848
+ cls._listeners_initialized = True
849
+ print(f"✅ [agentIdPresenter] RPC 消息监听器注册成功")
850
+
851
+ @classmethod
852
+ async def _send_rpc_response(
853
+ cls,
854
+ agentId: AgentID,
855
+ sender_aid: str,
856
+ session_id: str,
857
+ trace_id: str,
858
+ status: str,
859
+ result: Any = None,
860
+ error: str = None
861
+ ):
862
+ """
863
+ 发送 RPC 响应消息
864
+
865
+ 健壮性保证:
866
+ - JSON 序列化失败时使用降级方案
867
+ - 发送消息失败时只记录日志不崩溃
868
+ - 所有操作都在 try-except 中
869
+
870
+ 响应消息格式(包装为 content 类型):
871
+ {
872
+ "type": "content",
873
+ "content": "{\"action\":\"rpc_response\",\"trace_id\":\"xxx\",...}"
874
+ }
875
+ """
876
+ try:
877
+ # 安全检查参数
878
+ if not sender_aid or not session_id:
879
+ print(f"⚠️ [agentIdPresenter] _send_rpc_response: sender_aid 或 session_id 为空,跳过发送")
880
+ return
881
+
882
+ if agentId is None or not agentId.is_online_success:
883
+ print(f"⚠️ [agentIdPresenter] _send_rpc_response: agentId 不在线,跳过发送")
884
+ return
885
+
886
+ # 构建响应数据
887
+ response_data = {
888
+ "action": "rpc_response",
889
+ "trace_id": trace_id or "",
890
+ "status": status or "error"
891
+ }
892
+
893
+ if status == "success" and result is not None:
894
+ # 尝试安全地序列化 result
895
+ try:
896
+ # 先测试 result 是否可序列化
897
+ json.dumps(result, ensure_ascii=False)
898
+ response_data["result"] = result
899
+ except (TypeError, ValueError) as json_err:
900
+ # result 不可序列化,尝试转换
901
+ print(f"⚠️ [agentIdPresenter] result 不可序列化: {json_err},尝试转换")
902
+ try:
903
+ # 尝试转为字符串
904
+ response_data["result"] = str(result)
905
+ response_data["_result_serialization_fallback"] = True
906
+ except Exception:
907
+ response_data["result"] = "[无法序列化的结果]"
908
+ response_data["_result_serialization_failed"] = True
909
+
910
+ if status == "error" and error:
911
+ response_data["error"] = str(error) if error else "未知错误"
912
+
913
+ # 安全序列化响应
914
+ try:
915
+ response_json = json.dumps(response_data, ensure_ascii=False)
916
+ except (TypeError, ValueError) as json_err:
917
+ print(f"⚠️ [agentIdPresenter] 响应序列化失败: {json_err},使用降级方案")
918
+ # 降级方案:只发送基本信息
919
+ fallback_data = {
920
+ "action": "rpc_response",
921
+ "trace_id": trace_id or "",
922
+ "status": "error",
923
+ "error": f"响应序列化失败: {str(json_err)}"
924
+ }
925
+ response_json = json.dumps(fallback_data, ensure_ascii=False)
926
+
927
+ # 包装为 content 类型的消息
928
+ response_msg = {
929
+ "type": "content",
930
+ "content": response_json
931
+ }
932
+
933
+ print(f"📤 [agentIdPresenter] 发送 RPC 响应: trace_id={trace_id}, status={status}")
934
+
935
+ # 发送消息
936
+ try:
937
+ agentId.send_message(session_id, [sender_aid], response_msg)
938
+ print(f"✅ [agentIdPresenter] RPC 响应已发送")
939
+ except Exception as send_err:
940
+ print(f"❌ [agentIdPresenter] send_message 失败: {send_err}")
941
+ # 发送失败,可能是 agentId 已离线,只记录日志
942
+
943
+ except Exception as e:
944
+ # 最外层兜底
945
+ print(f"❌ [agentIdPresenter] 发送 RPC 响应失败: {str(e)}")
946
+ try:
947
+ traceback.print_exc()
948
+ except:
949
+ pass
950
+
951
+ @classmethod
952
+ def _cleanup_listeners(cls):
953
+ """
954
+ 清理已注册的监听器
955
+
956
+ 在切换 AgentID 或关闭时调用
957
+ """
958
+ if cls._current_agentId is not None and cls._registered_handlers:
959
+ print(f"🧹 [agentIdPresenter] 清理监听器: {len(cls._registered_handlers)} 个")
960
+ for handler in cls._registered_handlers:
961
+ try:
962
+ cls._current_agentId.remove_message_handler(handler, session_id="")
963
+ except Exception as e:
964
+ print(f"⚠️ [agentIdPresenter] 清理监听器失败: {str(e)}")
965
+
966
+ cls._registered_handlers.clear()
967
+ cls._listeners_initialized = False
968
+
969
+ @classmethod
970
+ def offline(cls):
971
+ """
972
+ AgentID 下线处理
973
+
974
+ 清理资源和监听器
975
+ """
976
+ print(f"🔴 [agentIdPresenter] AgentID 下线")
977
+ cls._cleanup_listeners()
978
+ cls._current_agentId = None
979
+
980
+ # ==================== 对外暴露的工具方法 ====================
981
+
982
+ @classmethod
983
+ def list_available_presenters(cls) -> Dict[str, List[str]]:
984
+ """
985
+ 列出所有可用的 Presenter 及其方法
986
+
987
+ Returns:
988
+ Dict: {presenter_name: [method_names]}
989
+ """
990
+ if not cls._registry_initialized:
991
+ _init_presenter_registry()
992
+ cls._registry_initialized = True
993
+
994
+ return PresenterRegistry.list_presenters()
995
+
996
+ @classmethod
997
+ def register_presenter(cls, presenter_class: Type, name: str = None, allowed_methods: List[str] = None):
998
+ """
999
+ 动态注册一个 Presenter
1000
+
1001
+ Args:
1002
+ presenter_class: Presenter 类
1003
+ name: 注册名称(可选)
1004
+ allowed_methods: 允许的方法列表(可选)
1005
+ """
1006
+ PresenterRegistry.register(presenter_class, name, allowed_methods)
1007
+
1008
+
1009
+ # ==================== 便捷函数 ====================
1010
+
1011
+ def evol_agentId_online(agentId: AgentID) -> bool:
1012
+ """
1013
+ AgentID 上线后的便捷调用函数
1014
+
1015
+ 【重要】当前用户的 agentId 无论在代码的哪个位置调用 online(),
1016
+ 都必须在上线成功后调用此函数。
1017
+
1018
+ Args:
1019
+ agentId: 已成功上线的 AgentID 实例
1020
+
1021
+ Returns:
1022
+ bool: 初始化是否成功
1023
+
1024
+ Usage:
1025
+ from evol.presenter.agentIdPresenter import evol_agentId_online
1026
+
1027
+ agentId.online()
1028
+ if agentId.is_online_success:
1029
+ evol_agentId_online(agentId)
1030
+ """
1031
+ return agentIdPresenter.evol_agentId_online(agentId)