@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.
- package/CHANGELOG.md +102 -0
- package/cli.js +44 -5
- package/core/dependency_checker.py +250 -0
- package/core/env_checker.py +490 -0
- package/dependencies_lock.json +128 -0
- package/extensions/agents/assistant/server.py +33 -17
- package/extensions/channels/acp_channel/server.py +33 -17
- package/extensions/services/backup/entry.py +23 -16
- 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 +23 -1
- 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 +42 -16
- package/extensions/services/watchdog/module.md +1 -0
- package/extensions/services/watchdog/monitor.py +34 -4
- package/extensions/services/web/module.md +1 -1
- package/extensions/services/web/server.py +30 -18
- package/extensions/services/web/static/js/token-manager.js +10 -10
- package/kernel/entry.py +1 -1
- package/kernel/module.md +25 -1
- package/kernel/registry_store.py +2 -26
- package/kernel/rpc_router.py +36 -10
- package/kernel/server.py +106 -17
- package/kite_cli/commands/deps_install.py +67 -0
- package/kite_cli/commands/env_check.py +45 -0
- package/kite_cli/commands/prepare.py +49 -0
- package/kite_cli/commands/venv_setup.py +56 -0
- package/kite_cli/main.py +29 -1
- package/launcher/entry.py +306 -21
- package/launcher/module.md +9 -0
- package/launcher/module_scanner.py +11 -1
- package/main.py +4 -1
- package/package.json +8 -1
- package/python_version.json +4 -0
- package/requirements.txt +38 -0
- package/scripts/env-manager.js +328 -0
- package/scripts/python-env.js +79 -0
- package/scripts/scan_dependencies.py +461 -0
- package/scripts/setup-python-env.js +191 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright 2025 AgentUnion Inc.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import requests
|
|
16
|
+
import datetime
|
|
17
|
+
import socket
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
from typing import Optional
|
|
21
|
+
from agentcp.base.log import log_debug, log_error, log_exception, log_info, log_warning
|
|
22
|
+
from agentcp.base.auth_client import AuthClient
|
|
23
|
+
|
|
24
|
+
from agentcp.msg.message_serialize import *
|
|
25
|
+
from ..context import ErrorContext, exceptions
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HeartbeatClient:
|
|
29
|
+
# 重连相关常量
|
|
30
|
+
MAX_SEND_FAILURES = 3 # 发送失败触发重连的阈值
|
|
31
|
+
MAX_RECV_FAILURES = 3 # 接收失败触发重连的阈值
|
|
32
|
+
MAX_MISSED_HEARTBEATS = 3 # 心跳响应超时阈值(错过次数)
|
|
33
|
+
RECONNECT_BACKOFF_MAX = 30 # 重连退避上限(秒)
|
|
34
|
+
SOCKET_TIMEOUT = 1.0 # socket 超时时间(秒)
|
|
35
|
+
|
|
36
|
+
def __init__(self, agent_id: str, server_url: str, aid_path: str, seed_password: str):
|
|
37
|
+
self.agent_id = agent_id
|
|
38
|
+
self.server_url = server_url
|
|
39
|
+
self.seed_password = seed_password
|
|
40
|
+
self.port = 0 # server_port
|
|
41
|
+
self.sign_cookie = 0
|
|
42
|
+
self.udp_socket = None
|
|
43
|
+
self.local_ip = "0.0.0.0"
|
|
44
|
+
self.local_port = 0
|
|
45
|
+
self.server_ip = "127.0.0.1"
|
|
46
|
+
self.heartbeat_interval = 5000
|
|
47
|
+
self.is_running = False
|
|
48
|
+
self.is_sending_heartbeat = False
|
|
49
|
+
self.send_thread: Optional[threading.Thread] = None
|
|
50
|
+
self.receive_thread: Optional[threading.Thread] = None
|
|
51
|
+
self.msg_seq = 0
|
|
52
|
+
self.last_hb = 0
|
|
53
|
+
self.message_listener = None
|
|
54
|
+
self.auth_client = AuthClient(agent_id, server_url, aid_path, seed_password)
|
|
55
|
+
self.on_recv_invite = None
|
|
56
|
+
|
|
57
|
+
# 新增:用于自动恢复的状态
|
|
58
|
+
self._socket_lock = threading.Lock() # 保护 socket 操作
|
|
59
|
+
self._reconnect_lock = threading.Lock() # 防止并发重连
|
|
60
|
+
self._last_reconnect_ts = 0 # 上次重连时间戳
|
|
61
|
+
self._last_hb_recv = 0 # 上次收到心跳响应的时间戳
|
|
62
|
+
self._send_failures = 0 # 连续发送失败次数
|
|
63
|
+
self._recv_failures = 0 # 连续接收失败次数
|
|
64
|
+
|
|
65
|
+
def initialize(self):
|
|
66
|
+
self.sign_in()
|
|
67
|
+
|
|
68
|
+
def sign_in(self) -> bool:
|
|
69
|
+
data = self.auth_client.sign_in()
|
|
70
|
+
if data is None:
|
|
71
|
+
log_error("sign_in failed: data is None")
|
|
72
|
+
return False
|
|
73
|
+
self.server_ip = data.get("server_ip")
|
|
74
|
+
self.port = int(data.get("port", 0))
|
|
75
|
+
self.sign_cookie = data.get("sign_cookie")
|
|
76
|
+
log_info(f'signin {self.server_ip} {self.port} {self.sign_cookie}')
|
|
77
|
+
|
|
78
|
+
return self.server_ip is not None and self.port != 0 and self.sign_cookie is not None
|
|
79
|
+
|
|
80
|
+
def sign_out(self):
|
|
81
|
+
self.auth_client.sign_out()
|
|
82
|
+
|
|
83
|
+
def set_on_recv_invite(self, listener):
|
|
84
|
+
"""设置消息监听器"""
|
|
85
|
+
self.on_recv_invite = listener
|
|
86
|
+
|
|
87
|
+
# ========== 新增:Socket 生命周期管理 ==========
|
|
88
|
+
|
|
89
|
+
def _create_socket(self):
|
|
90
|
+
"""创建并绑定 UDP socket,设置超时"""
|
|
91
|
+
with self._socket_lock:
|
|
92
|
+
self._close_socket_internal()
|
|
93
|
+
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
94
|
+
self.udp_socket.settimeout(self.SOCKET_TIMEOUT)
|
|
95
|
+
self.udp_socket.bind((self.local_ip, 0)) # 使用新端口
|
|
96
|
+
self.local_ip, self.local_port = self.udp_socket.getsockname()
|
|
97
|
+
log_info(f"UDP socket created and bound to {self.local_ip}:{self.local_port}")
|
|
98
|
+
|
|
99
|
+
def _close_socket_internal(self):
|
|
100
|
+
"""内部方法:关闭 socket(不加锁,由调用方保证线程安全)"""
|
|
101
|
+
if self.udp_socket is not None:
|
|
102
|
+
try:
|
|
103
|
+
self.udp_socket.close()
|
|
104
|
+
except Exception as e:
|
|
105
|
+
log_warning(f"Close socket error: {e}")
|
|
106
|
+
self.udp_socket = None
|
|
107
|
+
|
|
108
|
+
def _close_socket(self):
|
|
109
|
+
"""安全关闭 socket"""
|
|
110
|
+
with self._socket_lock:
|
|
111
|
+
self._close_socket_internal()
|
|
112
|
+
|
|
113
|
+
def _reconnect(self, reason: str):
|
|
114
|
+
"""重连:限流/退避后执行 sign_in() + _create_socket()"""
|
|
115
|
+
if not self.is_running:
|
|
116
|
+
log_debug(f"Reconnect skipped (client offline): {reason}")
|
|
117
|
+
return False
|
|
118
|
+
if not self._reconnect_lock.acquire(blocking=False):
|
|
119
|
+
log_debug(f"Reconnect already in progress, skip: {reason}")
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
now = time.time()
|
|
124
|
+
# 限流:距离上次重连至少间隔 5 秒
|
|
125
|
+
elapsed = now - self._last_reconnect_ts
|
|
126
|
+
if elapsed < 5:
|
|
127
|
+
backoff = min(5 - elapsed, self.RECONNECT_BACKOFF_MAX)
|
|
128
|
+
log_info(f"Reconnect backoff: waiting {backoff:.1f}s")
|
|
129
|
+
time.sleep(backoff)
|
|
130
|
+
|
|
131
|
+
log_info(f"Reconnecting due to: {reason}")
|
|
132
|
+
self._last_reconnect_ts = time.time()
|
|
133
|
+
|
|
134
|
+
# 重新登录
|
|
135
|
+
if not self.sign_in():
|
|
136
|
+
log_error("Reconnect failed: sign_in returned False")
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
# 重建 socket
|
|
140
|
+
self._create_socket()
|
|
141
|
+
|
|
142
|
+
# 重置失败计数
|
|
143
|
+
self._send_failures = 0
|
|
144
|
+
self._recv_failures = 0
|
|
145
|
+
self._last_hb_recv = int(time.time() * 1000)
|
|
146
|
+
|
|
147
|
+
log_info("Reconnect successful")
|
|
148
|
+
return True
|
|
149
|
+
except Exception as e:
|
|
150
|
+
log_error(f"Reconnect exception: {e}")
|
|
151
|
+
return False
|
|
152
|
+
finally:
|
|
153
|
+
self._reconnect_lock.release()
|
|
154
|
+
|
|
155
|
+
# ========== 发送心跳(带异常恢复和超时检测) ==========
|
|
156
|
+
|
|
157
|
+
def __send_heartbeat(self):
|
|
158
|
+
backoff = 1 # 初始退避时间(秒)
|
|
159
|
+
|
|
160
|
+
while self.is_sending_heartbeat and self.is_running:
|
|
161
|
+
try:
|
|
162
|
+
current_time_ms = int(datetime.datetime.now().timestamp() * 1000)
|
|
163
|
+
|
|
164
|
+
# 检查心跳响应超时
|
|
165
|
+
if self._last_hb_recv > 0:
|
|
166
|
+
timeout_threshold = self.MAX_MISSED_HEARTBEATS * self.heartbeat_interval
|
|
167
|
+
if current_time_ms - self._last_hb_recv > timeout_threshold:
|
|
168
|
+
log_warning(f"Heartbeat response timeout: {current_time_ms - self._last_hb_recv}ms > {timeout_threshold}ms")
|
|
169
|
+
self._reconnect("heartbeat_response_timeout")
|
|
170
|
+
backoff = 1
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
# 发送心跳
|
|
174
|
+
if current_time_ms > (self.last_hb + self.heartbeat_interval):
|
|
175
|
+
log_debug(f'send heartbeat message to {self.server_ip}:{self.port}')
|
|
176
|
+
self.last_hb = current_time_ms
|
|
177
|
+
self.msg_seq = self.msg_seq + 1
|
|
178
|
+
req = HeartbeatMessageReq()
|
|
179
|
+
req.header.MessageMask = 0
|
|
180
|
+
req.header.MessageSeq = self.msg_seq
|
|
181
|
+
req.header.MessageType = 513
|
|
182
|
+
req.header.PayloadSize = 100
|
|
183
|
+
req.AgentId = self.agent_id
|
|
184
|
+
req.SignCookie = self.sign_cookie
|
|
185
|
+
buf = io.BytesIO()
|
|
186
|
+
req.serialize(buf)
|
|
187
|
+
data = buf.getvalue()
|
|
188
|
+
|
|
189
|
+
with self._socket_lock:
|
|
190
|
+
if self.udp_socket is not None:
|
|
191
|
+
self.udp_socket.sendto(data, (self.server_ip, self.port))
|
|
192
|
+
else:
|
|
193
|
+
raise Exception("UDP socket is None")
|
|
194
|
+
|
|
195
|
+
# 发送成功,重置失败计数和退避
|
|
196
|
+
self._send_failures = 0
|
|
197
|
+
backoff = 1
|
|
198
|
+
|
|
199
|
+
time.sleep(1)
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
self._send_failures += 1
|
|
203
|
+
log_error(f"Heartbeat send error (failures={self._send_failures}): {e}")
|
|
204
|
+
ErrorContext.publish(exceptions.SDKError(f"Heartbeat send error: {e}"))
|
|
205
|
+
|
|
206
|
+
# 达到阈值,触发重连
|
|
207
|
+
if self._send_failures >= self.MAX_SEND_FAILURES:
|
|
208
|
+
log_warning(f"Send failures reached threshold ({self.MAX_SEND_FAILURES}), triggering reconnect")
|
|
209
|
+
self._reconnect("send_failures_threshold")
|
|
210
|
+
backoff = 1
|
|
211
|
+
else:
|
|
212
|
+
# 指数退避
|
|
213
|
+
time.sleep(backoff)
|
|
214
|
+
backoff = min(backoff * 2, self.RECONNECT_BACKOFF_MAX)
|
|
215
|
+
|
|
216
|
+
# ========== 接收消息(可中断、可恢复) ==========
|
|
217
|
+
|
|
218
|
+
def _receive_messages(self):
|
|
219
|
+
while self.is_running:
|
|
220
|
+
try:
|
|
221
|
+
# 使用 socket 超时,确保能定期检查 is_running
|
|
222
|
+
with self._socket_lock:
|
|
223
|
+
if self.udp_socket is None:
|
|
224
|
+
time.sleep(0.5)
|
|
225
|
+
continue
|
|
226
|
+
sock = self.udp_socket
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
data, addr = sock.recvfrom(1536)
|
|
230
|
+
except socket.timeout:
|
|
231
|
+
# 超时是正常的,继续循环检查 is_running
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
# 接收成功,重置失败计数
|
|
235
|
+
self._recv_failures = 0
|
|
236
|
+
|
|
237
|
+
udp_header, offset = UdpMessageHeader.deserialize(data, 0)
|
|
238
|
+
|
|
239
|
+
if udp_header.MessageType == 258:
|
|
240
|
+
hb_resp, offset = HeartbeatMessageResp.deserialize(data, 0)
|
|
241
|
+
self.heartbeat_interval = hb_resp.NextBeat
|
|
242
|
+
|
|
243
|
+
# 更新最后收到心跳响应的时间
|
|
244
|
+
self._last_hb_recv = int(datetime.datetime.now().timestamp() * 1000)
|
|
245
|
+
|
|
246
|
+
# 服务器端身份验证失败(比如服务器发生了异常重启),需要重新登录
|
|
247
|
+
if hb_resp.NextBeat == 401:
|
|
248
|
+
log_warning(f"Heartbeat failed: {hb_resp.NextBeat}, triggering reconnect")
|
|
249
|
+
ErrorContext.publish(exceptions.SDKError(f"401,心跳", code=0))
|
|
250
|
+
self._reconnect("401_auth_failed")
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
if self.heartbeat_interval <= 5000:
|
|
254
|
+
self.heartbeat_interval = 5000
|
|
255
|
+
|
|
256
|
+
elif udp_header.MessageType == 259:
|
|
257
|
+
invite_req, offset = InviteMessageReq.deserialize(data, 0)
|
|
258
|
+
if self.on_recv_invite is not None:
|
|
259
|
+
ErrorContext.publish(exceptions.SDKError(f"收到邀请,加入session: {invite_req}", code=0))
|
|
260
|
+
self.on_recv_invite(invite_req)
|
|
261
|
+
|
|
262
|
+
resp = InviteMessageResp()
|
|
263
|
+
self.msg_seq = self.msg_seq + 1
|
|
264
|
+
resp.header.MessageMask = 0
|
|
265
|
+
resp.header.MessageSeq = self.msg_seq
|
|
266
|
+
resp.header.MessageType = 516
|
|
267
|
+
resp.AgentId = self.agent_id
|
|
268
|
+
resp.InviterAgentId = invite_req.InviterAgentId
|
|
269
|
+
resp.SignCookie = self.sign_cookie
|
|
270
|
+
buf = io.BytesIO()
|
|
271
|
+
resp.serialize(buf)
|
|
272
|
+
resp_data = buf.getvalue()
|
|
273
|
+
|
|
274
|
+
with self._socket_lock:
|
|
275
|
+
if self.udp_socket is not None:
|
|
276
|
+
self.udp_socket.sendto(resp_data, (self.server_ip, self.port))
|
|
277
|
+
|
|
278
|
+
except socket.timeout:
|
|
279
|
+
# 超时是正常的,继续循环
|
|
280
|
+
continue
|
|
281
|
+
except Exception as e:
|
|
282
|
+
if not self.is_running:
|
|
283
|
+
break
|
|
284
|
+
|
|
285
|
+
self._recv_failures += 1
|
|
286
|
+
log_error(f"Receive message exception (failures={self._recv_failures}): {e}")
|
|
287
|
+
ErrorContext.publish(exceptions.SDKError(f"Receive message exception: {e}"))
|
|
288
|
+
|
|
289
|
+
# 达到阈值,触发重连
|
|
290
|
+
if self._recv_failures >= self.MAX_RECV_FAILURES:
|
|
291
|
+
log_warning(f"Recv failures reached threshold ({self.MAX_RECV_FAILURES}), triggering reconnect")
|
|
292
|
+
self._reconnect("recv_failures_threshold")
|
|
293
|
+
else:
|
|
294
|
+
time.sleep(1.5)
|
|
295
|
+
|
|
296
|
+
def online(self):
|
|
297
|
+
"""开始心跳"""
|
|
298
|
+
if self.is_running:
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
# 使用统一的 socket 创建方法
|
|
302
|
+
self._create_socket()
|
|
303
|
+
|
|
304
|
+
# 初始化心跳响应时间
|
|
305
|
+
self._last_hb_recv = int(time.time() * 1000)
|
|
306
|
+
|
|
307
|
+
self.is_running = True
|
|
308
|
+
self.is_sending_heartbeat = True
|
|
309
|
+
|
|
310
|
+
self.send_thread = threading.Thread(target=self.__send_heartbeat, daemon=True)
|
|
311
|
+
self.receive_thread = threading.Thread(target=self._receive_messages, daemon=True)
|
|
312
|
+
|
|
313
|
+
self.send_thread.start()
|
|
314
|
+
self.receive_thread.start()
|
|
315
|
+
log_info("Successfully went online")
|
|
316
|
+
|
|
317
|
+
def offline(self):
|
|
318
|
+
"""停止心跳"""
|
|
319
|
+
log_info("Going offline...")
|
|
320
|
+
|
|
321
|
+
# 1. 先设置标志位,通知线程退出
|
|
322
|
+
self.is_running = False
|
|
323
|
+
self.is_sending_heartbeat = False
|
|
324
|
+
|
|
325
|
+
# 2. 关闭 socket(会使阻塞的 recvfrom 抛出异常)
|
|
326
|
+
self._close_socket()
|
|
327
|
+
|
|
328
|
+
# 3. 等待线程退出
|
|
329
|
+
if self.send_thread is not None and self.send_thread.is_alive():
|
|
330
|
+
self.send_thread.join(timeout=3)
|
|
331
|
+
if self.send_thread.is_alive():
|
|
332
|
+
log_warning("Send thread did not exit in time")
|
|
333
|
+
|
|
334
|
+
if self.receive_thread is not None and self.receive_thread.is_alive():
|
|
335
|
+
self.receive_thread.join(timeout=3)
|
|
336
|
+
if self.receive_thread.is_alive():
|
|
337
|
+
log_warning("Receive thread did not exit in time")
|
|
338
|
+
|
|
339
|
+
self.send_thread = None
|
|
340
|
+
self.receive_thread = None
|
|
341
|
+
log_info("Successfully went offline")
|
|
342
|
+
|
|
343
|
+
def get_online_status(self, aids):
|
|
344
|
+
try:
|
|
345
|
+
ep_url = self.server_url + "/query_online_state"
|
|
346
|
+
data = {
|
|
347
|
+
"agent_id": f"{self.agent_id}",
|
|
348
|
+
"signature": self.auth_client.signature,
|
|
349
|
+
"agents": aids
|
|
350
|
+
}
|
|
351
|
+
response = requests.post(ep_url, json=data, verify=False, proxies={}, timeout=(3, 10))
|
|
352
|
+
if response.status_code == 200:
|
|
353
|
+
log_info(f"get_online_status ok:{response.json()}")
|
|
354
|
+
return response.json()["data"]
|
|
355
|
+
else:
|
|
356
|
+
log_error(f"get_online_status failed:{response.json()}")
|
|
357
|
+
return []
|
|
358
|
+
except Exception as e:
|
|
359
|
+
log_exception(f"get_online_status exception: {e}")
|
|
360
|
+
return []
|