@agentunion/kite 1.3.2 → 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.
- package/CHANGELOG.md +302 -0
- package/cli.js +119 -4
- package/core/dependency_checker.py +250 -0
- package/core/env_checker.py +490 -0
- package/dependencies_lock.json +128 -0
- package/extensions/agents/assistant/entry.py +111 -1
- package/extensions/agents/assistant/server.py +279 -215
- package/extensions/channels/acp_channel/entry.py +111 -1
- package/extensions/channels/acp_channel/module.md +23 -22
- package/extensions/channels/acp_channel/server.py +279 -215
- package/extensions/event_hub_bench/entry.py +107 -1
- package/extensions/services/backup/entry.py +306 -21
- package/extensions/services/backup/module.md +24 -22
- package/extensions/services/evol/auth_manager.py +443 -0
- package/extensions/services/evol/config.yaml +149 -0
- package/extensions/services/evol/config_loader.py +117 -0
- package/extensions/services/evol/entry.py +406 -0
- package/extensions/services/evol/evol_api.py +173 -0
- package/extensions/services/evol/evol_config.json5 +29 -0
- package/extensions/services/evol/migrate_tokens.py +122 -0
- package/extensions/services/evol/module.md +32 -0
- package/extensions/services/evol/pairing.py +250 -0
- package/extensions/services/evol/pairing_codes.jsonl +1 -0
- package/extensions/services/evol/relay.py +682 -0
- package/extensions/services/evol/relay_config.json5 +67 -0
- package/extensions/services/evol/routes/__init__.py +1 -0
- package/extensions/services/evol/routes/routes_management_ws.py +127 -0
- package/extensions/services/evol/routes/routes_rpc.py +89 -0
- package/extensions/services/evol/routes/routes_test.py +61 -0
- package/extensions/services/evol/server.py +875 -0
- package/extensions/services/evol/static/css/style.css +1200 -0
- package/extensions/services/evol/static/index.html +781 -0
- package/extensions/services/evol/static/index_evol.html +14 -0
- package/extensions/services/evol/static/js/app.js +6304 -0
- package/extensions/services/evol/static/js/auth.js +326 -0
- package/extensions/services/evol/static/js/dialog.js +285 -0
- package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
- package/extensions/services/evol/static/js/evol-app.js +1949 -0
- package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
- package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
- package/extensions/services/evol/static/js/kernel-client.js +396 -0
- package/extensions/services/evol/static/js/main.js +141 -0
- package/extensions/services/evol/static/js/registry-tests.js +585 -0
- package/extensions/services/evol/static/js/stats.js +217 -0
- package/extensions/services/evol/static/js/token-manager.js +175 -0
- package/extensions/services/evol/static/pairing.html +248 -0
- package/extensions/services/evol/static/test_registry.html +262 -0
- package/extensions/services/evol/static/test_relay.html +462 -0
- package/extensions/services/evol/stats_manager.py +240 -0
- package/extensions/services/model_service/entry.py +167 -19
- package/extensions/services/model_service/module.md +21 -22
- package/extensions/services/proxy/.claude/settings.local.json +13 -0
- package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
- package/extensions/services/proxy/_fix_prints.py +133 -0
- package/extensions/services/proxy/_fix_prints2.py +87 -0
- package/extensions/services/proxy/agentcp/LICENCE +178 -0
- package/extensions/services/proxy/agentcp/README copy.md +85 -0
- package/extensions/services/proxy/agentcp/README.md +260 -0
- package/extensions/services/proxy/agentcp/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/agent.py +4 -0
- package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
- package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
- package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
- package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
- package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
- package/extensions/services/proxy/agentcp/base/client.py +112 -0
- package/extensions/services/proxy/agentcp/base/env.py +34 -0
- package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
- package/extensions/services/proxy/agentcp/base/log.py +98 -0
- package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
- package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
- package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
- package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/context/context.py +73 -0
- package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
- package/extensions/services/proxy/agentcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
- package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
- package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
- package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
- package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
- package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/hcp.py +299 -0
- package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
- package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
- package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
- package/extensions/services/proxy/agentcp/llm_server.py +172 -0
- package/extensions/services/proxy/agentcp/mermaid.py +210 -0
- package/extensions/services/proxy/agentcp/message.py +149 -0
- package/extensions/services/proxy/agentcp/metrics.py +256 -0
- package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
- package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
- package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
- package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
- package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
- package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
- package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
- package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
- package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
- package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
- package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
- package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
- package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
- package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
- package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/requirements.txt +7 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
- package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
- package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
- package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
- package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
- package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
- package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
- package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
- package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
- package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
- package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
- package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
- package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
- package/extensions/services/proxy/agentcp/workflow.py +203 -0
- package/extensions/services/proxy/console_auth.py +109 -0
- package/extensions/services/proxy/evol/__init__.py +1 -0
- package/extensions/services/proxy/evol/config.py +37 -0
- package/extensions/services/proxy/evol/http/__init__.py +1 -0
- package/extensions/services/proxy/evol/http/async_http.py +551 -0
- package/extensions/services/proxy/evol/log.py +28 -0
- package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
- package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
- package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
- package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
- package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
- package/extensions/services/proxy/evol/server/__init__.py +1 -0
- package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
- package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
- package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
- package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
- package/extensions/services/proxy/evol/version.py +24 -0
- package/extensions/services/proxy/logs/websocket.log +260 -0
- package/extensions/services/proxy/main.py +240 -0
- package/extensions/services/proxy/requirements.txt +13 -0
- package/extensions/services/proxy/server.py +271 -0
- package/extensions/services/watchdog/entry.py +215 -26
- package/extensions/services/watchdog/module.md +1 -0
- package/extensions/services/watchdog/monitor.py +178 -38
- package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
- package/extensions/services/web/config_example.py +35 -0
- package/extensions/services/web/config_loader.py +110 -0
- package/extensions/services/web/entry.py +114 -26
- package/extensions/services/web/module.md +35 -24
- package/extensions/services/web/pairing.py +250 -0
- package/extensions/services/web/pairing_codes.jsonl +16 -0
- package/extensions/services/web/relay.py +643 -0
- package/extensions/services/web/relay_config.json5 +67 -0
- package/extensions/services/web/routes/routes_management_ws.py +127 -0
- package/extensions/services/web/routes/routes_rpc.py +89 -0
- package/extensions/services/web/routes/routes_test.py +61 -0
- package/extensions/services/web/routes/schemas.py +0 -22
- package/extensions/services/web/server.py +434 -99
- package/extensions/services/web/static/css/style.css +67 -28
- package/extensions/services/web/static/index.html +234 -44
- package/extensions/services/web/static/js/app.js +1335 -48
- package/extensions/services/web/static/js/kernel-client-example.js +161 -0
- package/extensions/services/web/static/js/kernel-client.js +383 -0
- package/extensions/services/web/static/js/registry-tests.js +558 -0
- package/extensions/services/web/static/js/token-manager.js +175 -0
- package/extensions/services/web/static/pairing.html +248 -0
- package/extensions/services/web/static/test_registry.html +262 -0
- package/extensions/services/web/web_config.json5 +29 -0
- package/kernel/entry.py +120 -32
- package/kernel/event_hub.py +141 -16
- package/kernel/module.md +60 -33
- package/kernel/registry_store.py +45 -36
- package/kernel/rpc_router.py +152 -59
- package/kernel/server.py +322 -26
- package/kite_cli/__init__.py +3 -0
- package/kite_cli/__main__.py +5 -0
- package/kite_cli/commands/__init__.py +1 -0
- package/kite_cli/commands/clean.py +101 -0
- package/kite_cli/commands/deps_install.py +67 -0
- package/kite_cli/commands/doctor.py +35 -0
- package/kite_cli/commands/env_check.py +45 -0
- package/kite_cli/commands/history.py +111 -0
- package/kite_cli/commands/info.py +96 -0
- package/kite_cli/commands/install.py +313 -0
- package/kite_cli/commands/list.py +143 -0
- package/kite_cli/commands/log.py +81 -0
- package/kite_cli/commands/prepare.py +49 -0
- package/kite_cli/commands/rollback.py +88 -0
- package/kite_cli/commands/search.py +73 -0
- package/kite_cli/commands/uninstall.py +85 -0
- package/kite_cli/commands/update.py +118 -0
- package/kite_cli/commands/venv_setup.py +56 -0
- package/kite_cli/core/__init__.py +1 -0
- package/kite_cli/core/checker.py +142 -0
- package/kite_cli/core/dependency.py +229 -0
- package/kite_cli/core/downloader.py +209 -0
- package/kite_cli/core/install_info.py +40 -0
- package/kite_cli/core/tool_installer.py +397 -0
- package/kite_cli/core/validator.py +78 -0
- package/kite_cli/main.py +317 -0
- package/kite_cli/utils/__init__.py +1 -0
- package/kite_cli/utils/i18n.py +252 -0
- package/kite_cli/utils/interactive.py +63 -0
- package/kite_cli/utils/operation_log.py +77 -0
- package/kite_cli/utils/paths.py +34 -0
- package/kite_cli/utils/version.py +308 -0
- package/launcher/entry.py +1124 -178
- package/launcher/logging_setup.py +104 -0
- package/launcher/module.md +46 -37
- package/launcher/module_scanner.py +11 -1
- package/main.py +4 -1
- package/package.json +9 -1
- package/python_version.json +4 -0
- package/requirements.txt +38 -0
- package/scripts/env-manager.js +328 -0
- package/scripts/plan_manager.py +315 -0
- package/scripts/python-env.js +79 -0
- package/scripts/scan_dependencies.py +461 -0
- package/scripts/setup-python-env.js +191 -0
- package/extensions/services/web/routes/routes_modules.py +0 -249
|
@@ -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)
|