@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,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
server.py - 精简版 FastAPI 服务器
|
|
3
|
+
|
|
4
|
+
仅提供4个代理路由 + 健康检查:
|
|
5
|
+
- /claude-proxy/{path} - Claude代理(仅 v1/messages 路径且 model 包含 "claude")
|
|
6
|
+
- /codex-proxy/{path} - Codex代理(不做 model 过滤)
|
|
7
|
+
- /gemini-proxy/{path} - Gemini代理(不做 model 过滤)
|
|
8
|
+
- /openclaw-proxy/{path} - OpenClaw代理(OpenAI兼容接口)
|
|
9
|
+
- /health - 健康检查
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
import uuid
|
|
18
|
+
|
|
19
|
+
import uvicorn
|
|
20
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
21
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
22
|
+
from fastapi.responses import JSONResponse, Response
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ==================== 并发管理器 ====================
|
|
26
|
+
|
|
27
|
+
class ConcurrencyLimitManager:
|
|
28
|
+
"""管理代理接口的总并发数,带自动清理超时请求"""
|
|
29
|
+
|
|
30
|
+
_instance = None
|
|
31
|
+
_lock = threading.Lock()
|
|
32
|
+
|
|
33
|
+
def __new__(cls):
|
|
34
|
+
if cls._instance is None:
|
|
35
|
+
with cls._lock:
|
|
36
|
+
if cls._instance is None:
|
|
37
|
+
cls._instance = super().__new__(cls)
|
|
38
|
+
cls._instance._initialized = False
|
|
39
|
+
return cls._instance
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
if self._initialized:
|
|
43
|
+
return
|
|
44
|
+
self._initialized = True
|
|
45
|
+
self._active_requests = {}
|
|
46
|
+
self._count_lock = threading.Lock()
|
|
47
|
+
self._timeout_seconds = 300
|
|
48
|
+
self._limit = 5
|
|
49
|
+
print("[ConcurrencyLimitManager] initialized")
|
|
50
|
+
|
|
51
|
+
def acquire(self) -> tuple:
|
|
52
|
+
with self._count_lock:
|
|
53
|
+
self._cleanup_timeout_requests()
|
|
54
|
+
request_id = f"req_{int(time.time() * 1000)}_{id(threading.current_thread())}"
|
|
55
|
+
self._active_requests[request_id] = time.time()
|
|
56
|
+
return True, "", request_id
|
|
57
|
+
|
|
58
|
+
def release(self, request_id: str = None):
|
|
59
|
+
with self._count_lock:
|
|
60
|
+
if request_id and request_id in self._active_requests:
|
|
61
|
+
del self._active_requests[request_id]
|
|
62
|
+
elif not request_id and self._active_requests:
|
|
63
|
+
oldest_id = min(self._active_requests, key=self._active_requests.get)
|
|
64
|
+
del self._active_requests[oldest_id]
|
|
65
|
+
|
|
66
|
+
def _cleanup_timeout_requests(self):
|
|
67
|
+
current_time = time.time()
|
|
68
|
+
timeout_ids = [
|
|
69
|
+
rid for rid, t in self._active_requests.items()
|
|
70
|
+
if current_time - t > self._timeout_seconds
|
|
71
|
+
]
|
|
72
|
+
for rid in timeout_ids:
|
|
73
|
+
del self._active_requests[rid]
|
|
74
|
+
|
|
75
|
+
def get_current_count(self) -> int:
|
|
76
|
+
with self._count_lock:
|
|
77
|
+
return len(self._active_requests)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
concurrency_manager = ConcurrencyLimitManager()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ==================== FastAPI 应用 ====================
|
|
84
|
+
|
|
85
|
+
app = FastAPI(title="Evol Sample Backend", version="1.0.0")
|
|
86
|
+
|
|
87
|
+
app.add_middleware(
|
|
88
|
+
CORSMiddleware,
|
|
89
|
+
allow_origins=["*"],
|
|
90
|
+
allow_credentials=True,
|
|
91
|
+
allow_methods=["*"],
|
|
92
|
+
allow_headers=["*"],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ==================== 健康检查 ====================
|
|
97
|
+
|
|
98
|
+
@app.get("/health")
|
|
99
|
+
async def health_check():
|
|
100
|
+
from evol.server.claude_proxy_async import get_current_agent_id
|
|
101
|
+
agent_id = get_current_agent_id()
|
|
102
|
+
return {
|
|
103
|
+
"status": "ok",
|
|
104
|
+
"agent_id": agent_id.id if agent_id else None,
|
|
105
|
+
"is_online": agent_id.is_online_success if agent_id else False,
|
|
106
|
+
"active_requests": concurrency_manager.get_current_count()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ==================== Claude Proxy 路由 ====================
|
|
111
|
+
|
|
112
|
+
@app.api_route("/claude-proxy/{full_path:path}",
|
|
113
|
+
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
|
|
114
|
+
async def claude_proxy_route(request: Request, full_path: str):
|
|
115
|
+
"""Claude代理路由 - 仅放行 v1/messages 路径且 model 包含 'claude'"""
|
|
116
|
+
acquired, error_msg, request_id = concurrency_manager.acquire()
|
|
117
|
+
if not acquired:
|
|
118
|
+
return Response(content=error_msg.encode("utf-8"), status_code=429)
|
|
119
|
+
|
|
120
|
+
if "v1/messages" not in full_path:
|
|
121
|
+
concurrency_manager.release(request_id)
|
|
122
|
+
raise HTTPException(status_code=200, detail="OK")
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
request_body = await request.body()
|
|
126
|
+
bodyjson = await asyncio.to_thread(json.loads, request_body.decode('utf-8'))
|
|
127
|
+
model = bodyjson.get('model', '')
|
|
128
|
+
if "claude" not in model:
|
|
129
|
+
raise HTTPException(status_code=400, detail="Unsupported model")
|
|
130
|
+
|
|
131
|
+
from evol.server.claude_proxy_async import proxy_claude_request
|
|
132
|
+
response = await proxy_claude_request(request)
|
|
133
|
+
return response
|
|
134
|
+
|
|
135
|
+
except asyncio.CancelledError:
|
|
136
|
+
logging.getLogger("evol_server").warning("claude-proxy request cancelled (client disconnected?)")
|
|
137
|
+
return Response(content=b"Client Closed Request", status_code=499)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
if isinstance(e, HTTPException):
|
|
140
|
+
return Response(content=str(e.detail).encode("utf-8"), status_code=e.status_code)
|
|
141
|
+
return Response(content=str(e).encode("utf-8"), status_code=503)
|
|
142
|
+
finally:
|
|
143
|
+
concurrency_manager.release(request_id)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ==================== Codex Proxy 路由 ====================
|
|
147
|
+
|
|
148
|
+
@app.api_route("/codex-proxy/{full_path:path}",
|
|
149
|
+
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
|
|
150
|
+
async def codex_proxy_route(request: Request, full_path: str):
|
|
151
|
+
"""Codex代理路由 - 不做 model 过滤"""
|
|
152
|
+
acquired, error_msg, request_id = concurrency_manager.acquire()
|
|
153
|
+
if not acquired:
|
|
154
|
+
return Response(content=error_msg.encode("utf-8"), status_code=429)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
from evol.server.claude_proxy_async import proxy_claude_request
|
|
158
|
+
response = await proxy_claude_request(request)
|
|
159
|
+
return response
|
|
160
|
+
|
|
161
|
+
except asyncio.CancelledError:
|
|
162
|
+
logging.getLogger("evol_server").warning("codex-proxy request cancelled (client disconnected?)")
|
|
163
|
+
return Response(content=b"Client Closed Request", status_code=499)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
if isinstance(e, HTTPException):
|
|
166
|
+
return Response(content=str(e.detail).encode("utf-8"), status_code=e.status_code)
|
|
167
|
+
return Response(content=str(e).encode("utf-8"), status_code=503)
|
|
168
|
+
finally:
|
|
169
|
+
concurrency_manager.release(request_id)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# ==================== Gemini Proxy 路由 ====================
|
|
173
|
+
|
|
174
|
+
@app.api_route("/gemini-proxy/{full_path:path}",
|
|
175
|
+
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
|
|
176
|
+
async def gemini_proxy_route(request: Request, full_path: str):
|
|
177
|
+
"""Gemini代理路由 - 不做 model 过滤"""
|
|
178
|
+
acquired, error_msg, request_id = concurrency_manager.acquire()
|
|
179
|
+
if not acquired:
|
|
180
|
+
return Response(content=error_msg.encode("utf-8"), status_code=429)
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
from evol.server.claude_proxy_async import proxy_claude_request
|
|
184
|
+
response = await proxy_claude_request(request)
|
|
185
|
+
return response
|
|
186
|
+
|
|
187
|
+
except asyncio.CancelledError:
|
|
188
|
+
logging.getLogger("evol_server").warning("gemini-proxy request cancelled (client disconnected?)")
|
|
189
|
+
return Response(content=b"Client Closed Request", status_code=499)
|
|
190
|
+
except Exception as e:
|
|
191
|
+
if isinstance(e, HTTPException):
|
|
192
|
+
return Response(content=str(e.detail).encode("utf-8"), status_code=e.status_code)
|
|
193
|
+
return Response(content=str(e).encode("utf-8"), status_code=503)
|
|
194
|
+
finally:
|
|
195
|
+
concurrency_manager.release(request_id)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ==================== OpenClaw Proxy 路由 ====================
|
|
199
|
+
|
|
200
|
+
@app.api_route("/openclaw-proxy/{full_path:path}",
|
|
201
|
+
methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
|
|
202
|
+
async def openclaw_proxy_route(request: Request, full_path: str):
|
|
203
|
+
"""OpenClaw代理路由 - OpenAI兼容接口"""
|
|
204
|
+
acquired, error_msg, request_id = concurrency_manager.acquire()
|
|
205
|
+
if not acquired:
|
|
206
|
+
from evol.server.openclaw_proxy import openai_error_response
|
|
207
|
+
return openai_error_response(error_msg, error_type="rate_limit_exceeded", status_code=429)
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
if full_path == "v1/models":
|
|
211
|
+
from evol.server.openclaw_proxy import get_models_list
|
|
212
|
+
return JSONResponse(get_models_list())
|
|
213
|
+
elif full_path == "v1/chat/completions":
|
|
214
|
+
from evol.server.openclaw_proxy import proxy_openclaw_request
|
|
215
|
+
response = await proxy_openclaw_request(request)
|
|
216
|
+
return response
|
|
217
|
+
else:
|
|
218
|
+
from evol.server.openclaw_proxy import openai_error_response
|
|
219
|
+
return openai_error_response(
|
|
220
|
+
f"Path not found: /{full_path}. Supported: /v1/models, /v1/chat/completions.",
|
|
221
|
+
error_type="invalid_request_error",
|
|
222
|
+
status_code=404
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
except asyncio.CancelledError:
|
|
226
|
+
logging.getLogger("evol_server").warning("openclaw-proxy request cancelled (client disconnected?)")
|
|
227
|
+
return Response(content=b"Client Closed Request", status_code=499)
|
|
228
|
+
except Exception as e:
|
|
229
|
+
from evol.server.openclaw_proxy import openai_error_response
|
|
230
|
+
return openai_error_response(f"Internal server error: {str(e)}", error_type="api_error", status_code=500)
|
|
231
|
+
finally:
|
|
232
|
+
concurrency_manager.release(request_id)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ==================== 启动服务器 ====================
|
|
236
|
+
|
|
237
|
+
# 全局变量存储实际端口
|
|
238
|
+
_actual_port = None
|
|
239
|
+
|
|
240
|
+
def get_actual_port():
|
|
241
|
+
"""获取服务器实际使用的端口"""
|
|
242
|
+
return _actual_port
|
|
243
|
+
|
|
244
|
+
async def run_server(port: int = 0):
|
|
245
|
+
"""启动 FastAPI 服务器
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
port: 端口号,0 表示系统自动分配
|
|
249
|
+
"""
|
|
250
|
+
global _actual_port
|
|
251
|
+
|
|
252
|
+
# 如果端口为 0,先获取一个可用端口
|
|
253
|
+
if port == 0:
|
|
254
|
+
import socket
|
|
255
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
256
|
+
sock.bind(('', 0))
|
|
257
|
+
port = sock.getsockname()[1]
|
|
258
|
+
sock.close()
|
|
259
|
+
|
|
260
|
+
_actual_port = port
|
|
261
|
+
|
|
262
|
+
config = uvicorn.Config(
|
|
263
|
+
app,
|
|
264
|
+
host="0.0.0.0",
|
|
265
|
+
port=port,
|
|
266
|
+
log_level="info",
|
|
267
|
+
access_log=True,
|
|
268
|
+
)
|
|
269
|
+
server = uvicorn.Server(config)
|
|
270
|
+
server.install_signal_handlers = False # 禁止 uvicorn 安装信号处理器,防止外部信号导致服务器关闭
|
|
271
|
+
await server.serve()
|
|
@@ -504,7 +504,7 @@ async def _ws_connect(token: str, kernel_port: int, _t0: float):
|
|
|
504
504
|
ws_url = f"ws://127.0.0.1:{kernel_port}/ws?token={token}&id=watchdog"
|
|
505
505
|
print(f"[watchdog] Connecting to Kernel: {ws_url}")
|
|
506
506
|
|
|
507
|
-
async with websockets.connect(ws_url, open_timeout=5, ping_interval=
|
|
507
|
+
async with websockets.connect(ws_url, open_timeout=5, ping_interval=None, close_timeout=10) as ws:
|
|
508
508
|
_ws_global = ws
|
|
509
509
|
print(f"[watchdog] Connected to Kernel ({_fmt_elapsed(_t0)})")
|
|
510
510
|
|
|
@@ -564,12 +564,14 @@ async def _ws_connect(token: str, kernel_port: int, _t0: float):
|
|
|
564
564
|
|
|
565
565
|
# Publish module.ready (every reconnect)
|
|
566
566
|
if not _shutting_down:
|
|
567
|
+
startup_time = time.monotonic() - _t0
|
|
567
568
|
await _rpc_call(ws, "event.publish", {
|
|
568
569
|
"event_id": str(uuid.uuid4()),
|
|
569
570
|
"event": "module.ready",
|
|
570
571
|
"data": {
|
|
571
572
|
"module_id": "watchdog",
|
|
572
573
|
"graceful_shutdown": True,
|
|
574
|
+
"startup_time": startup_time,
|
|
573
575
|
},
|
|
574
576
|
})
|
|
575
577
|
print(f"[watchdog] module.ready published ({_fmt_elapsed(_t0)})")
|
|
@@ -578,9 +580,6 @@ async def _ws_connect(token: str, kernel_port: int, _t0: float):
|
|
|
578
580
|
if _monitor_task is None or _monitor_task.done():
|
|
579
581
|
_monitor_task = asyncio.create_task(_monitor.run())
|
|
580
582
|
|
|
581
|
-
# Start heartbeat loop
|
|
582
|
-
heartbeat_task = asyncio.create_task(_heartbeat_loop(ws))
|
|
583
|
-
|
|
584
583
|
# Message loop: handle incoming RPC + events
|
|
585
584
|
# CRITICAL: RPC 死锁防范
|
|
586
585
|
# - 入站 RPC 请求必须用 create_task() 异步执行,不可 await
|
|
@@ -622,19 +621,16 @@ async def _rpc_call(ws, method: str, params: dict = None):
|
|
|
622
621
|
await ws.send(json.dumps(msg))
|
|
623
622
|
|
|
624
623
|
|
|
625
|
-
async def
|
|
626
|
-
"""
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
except Exception as e:
|
|
633
|
-
print(f"[watchdog] Heartbeat error: {e}")
|
|
634
|
-
break
|
|
624
|
+
async def _publish_event(ws, event: dict):
|
|
625
|
+
"""Publish an event via RPC event.publish."""
|
|
626
|
+
await _rpc_call(ws, "event.publish", {
|
|
627
|
+
"event_id": str(uuid.uuid4()),
|
|
628
|
+
"event": event.get("event", ""),
|
|
629
|
+
"data": event.get("data", {}),
|
|
630
|
+
})
|
|
635
631
|
|
|
636
632
|
|
|
637
|
-
async def _rpc_call_with_response(ws, method: str, params: dict = None, timeout: float = 5)
|
|
633
|
+
async def _rpc_call_with_response(ws, method: str, params: dict = None, timeout: float = 5):
|
|
638
634
|
"""Send a JSON-RPC 2.0 request and await the response."""
|
|
639
635
|
rpc_id = str(uuid.uuid4())
|
|
640
636
|
msg = {"jsonrpc": "2.0", "id": rpc_id, "method": method}
|
|
@@ -665,12 +661,32 @@ async def _publish_event(ws, event: dict):
|
|
|
665
661
|
})
|
|
666
662
|
|
|
667
663
|
|
|
664
|
+
async def _handle_ping_event(data: dict):
|
|
665
|
+
"""Handle system.ping event and reply with system.pong."""
|
|
666
|
+
t1 = data.get("ping_time")
|
|
667
|
+
t2 = time.time()
|
|
668
|
+
|
|
669
|
+
await _publish_event(_ws_global, {
|
|
670
|
+
"event": "system.pong",
|
|
671
|
+
"data": {
|
|
672
|
+
"module_id": MODULE_NAME,
|
|
673
|
+
"ping_time": t1,
|
|
674
|
+
"pong_time": t2,
|
|
675
|
+
},
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
|
|
668
679
|
async def _handle_event_notification(msg: dict, monitor: HealthMonitor):
|
|
669
680
|
"""Handle an event notification (JSON-RPC 2.0 Notification with method='event')."""
|
|
670
681
|
params = msg.get("params", {})
|
|
671
682
|
event_type = params.get("event", "")
|
|
672
683
|
data = params.get("data", {})
|
|
673
684
|
|
|
685
|
+
# Handle system.ping event
|
|
686
|
+
if event_type == "system.ping":
|
|
687
|
+
await _handle_ping_event(data)
|
|
688
|
+
return
|
|
689
|
+
|
|
674
690
|
# Debug: log all shutdown events
|
|
675
691
|
if event_type == "module.shutdown":
|
|
676
692
|
target = data.get("module_id", "")
|
|
@@ -713,11 +729,21 @@ async def _handle_rpc_request(ws, msg: dict, monitor: HealthMonitor):
|
|
|
713
729
|
|
|
714
730
|
async def _rpc_health(monitor: HealthMonitor) -> dict:
|
|
715
731
|
"""RPC handler for watchdog.health."""
|
|
732
|
+
# 统计不健康的模块数量
|
|
733
|
+
unhealthy_count = sum(1 for s in monitor.modules.values() if s.state == "unhealthy")
|
|
734
|
+
# 统计资源严重不足的模块数量
|
|
735
|
+
critical_resources = sum(1 for s in monitor.modules.values() if s.resource_state == "critical")
|
|
736
|
+
# 统计总重启次数
|
|
737
|
+
total_restarts = sum(s.restarted_count for s in monitor.modules.values())
|
|
738
|
+
|
|
716
739
|
return {
|
|
717
740
|
"status": "healthy",
|
|
741
|
+
"uptime_seconds": round(time.time() - _start_ts),
|
|
718
742
|
"details": {
|
|
719
743
|
"monitored_modules": len(monitor.modules),
|
|
720
|
-
"
|
|
744
|
+
"unhealthy_modules": unhealthy_count,
|
|
745
|
+
"critical_resources": critical_resources,
|
|
746
|
+
"total_restarts": total_restarts,
|
|
721
747
|
},
|
|
722
748
|
}
|
|
723
749
|
|
|
@@ -55,6 +55,8 @@ class ModuleStatus:
|
|
|
55
55
|
self.memory_samples: list[float] = [] # last 5 memory_rss samples
|
|
56
56
|
self.recovery_since: float = 0 # when recovery observation started
|
|
57
57
|
self.last_metrics: dict = {}
|
|
58
|
+
# Startup metrics
|
|
59
|
+
self.startup_time: float = 0 # module startup time in seconds (from module.ready event)
|
|
58
60
|
|
|
59
61
|
|
|
60
62
|
class HealthMonitor:
|
|
@@ -409,6 +411,10 @@ class HealthMonitor:
|
|
|
409
411
|
return
|
|
410
412
|
|
|
411
413
|
if not module_id or module_id == "watchdog":
|
|
414
|
+
# Handle registry.updated (no module_id)
|
|
415
|
+
if event_type == "registry.updated":
|
|
416
|
+
# Registry changed, re-discover modules
|
|
417
|
+
asyncio.create_task(self.discover_modules())
|
|
412
418
|
return
|
|
413
419
|
|
|
414
420
|
if event_type == "module.started":
|
|
@@ -421,6 +427,8 @@ class HealthMonitor:
|
|
|
421
427
|
elif event_type == "module.stopped":
|
|
422
428
|
print(f"[watchdog] Received module.stopped: {module_id}")
|
|
423
429
|
self.modules.pop(module_id, None)
|
|
430
|
+
# Re-discover to update module list
|
|
431
|
+
asyncio.create_task(self.discover_modules())
|
|
424
432
|
await self._handle_module_stopped(module_id, data)
|
|
425
433
|
|
|
426
434
|
elif event_type == "module.exiting":
|
|
@@ -441,8 +449,12 @@ class HealthMonitor:
|
|
|
441
449
|
|
|
442
450
|
elif event_type == "module.ready":
|
|
443
451
|
graceful = bool(data.get("graceful_shutdown"))
|
|
444
|
-
|
|
452
|
+
startup_time = data.get("startup_time", 0)
|
|
453
|
+
print(f"[watchdog] Received module.ready: {module_id}, graceful_shutdown={graceful}, startup_time={startup_time:.3f}s")
|
|
445
454
|
self._graceful_modules[module_id] = graceful
|
|
455
|
+
# Save startup time to module status
|
|
456
|
+
if module_id in self.modules:
|
|
457
|
+
self.modules[module_id].startup_time = startup_time
|
|
446
458
|
# Reset launcher loss tracking when launcher reconnects
|
|
447
459
|
if module_id == "launcher":
|
|
448
460
|
self._launcher_offline = False
|
|
@@ -694,10 +706,15 @@ class HealthMonitor:
|
|
|
694
706
|
return
|
|
695
707
|
print("[watchdog] system.ready received, starting health checks")
|
|
696
708
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
709
|
+
# Initial discovery (first time)
|
|
710
|
+
await self.discover_modules()
|
|
711
|
+
|
|
712
|
+
# Track discovery count and last discovery time
|
|
713
|
+
discovery_count = 1
|
|
714
|
+
last_discovery = asyncio.get_event_loop().time()
|
|
715
|
+
discovery_interval = 300.0 # 5 minutes after first 2 discoveries
|
|
700
716
|
|
|
717
|
+
while self._running:
|
|
701
718
|
if self.modules:
|
|
702
719
|
tasks = []
|
|
703
720
|
for s in self.modules.values():
|
|
@@ -709,6 +726,18 @@ class HealthMonitor:
|
|
|
709
726
|
interval = self._min_interval()
|
|
710
727
|
await asyncio.sleep(interval)
|
|
711
728
|
|
|
729
|
+
# Periodic re-discovery
|
|
730
|
+
now = asyncio.get_event_loop().time()
|
|
731
|
+
if discovery_count < 2:
|
|
732
|
+
# First 2 times: discover every cycle
|
|
733
|
+
await self.discover_modules()
|
|
734
|
+
discovery_count += 1
|
|
735
|
+
last_discovery = now
|
|
736
|
+
elif now - last_discovery >= discovery_interval:
|
|
737
|
+
# After that: discover every 5 minutes
|
|
738
|
+
await self.discover_modules()
|
|
739
|
+
last_discovery = now
|
|
740
|
+
|
|
712
741
|
def _min_interval(self) -> float:
|
|
713
742
|
"""Return the shortest check interval needed across all modules."""
|
|
714
743
|
if not self.modules:
|
|
@@ -732,6 +761,7 @@ class HealthMonitor:
|
|
|
732
761
|
"last_error": s.last_error,
|
|
733
762
|
"resource_state": s.resource_state,
|
|
734
763
|
"metrics": s.last_metrics,
|
|
764
|
+
"startup_time": s.startup_time,
|
|
735
765
|
}
|
|
736
766
|
for mid, s in self.modules.items()
|
|
737
767
|
}
|
|
@@ -305,7 +305,7 @@ class WebServer:
|
|
|
305
305
|
url = f"ws://127.0.0.1:{self.kernel_port}/ws?token={self.token}&id=web"
|
|
306
306
|
print(f"[web] WS connecting to Kernel")
|
|
307
307
|
try:
|
|
308
|
-
async with websockets.connect(url, open_timeout=5, ping_interval=
|
|
308
|
+
async with websockets.connect(url, open_timeout=5, ping_interval=None, close_timeout=10) as ws:
|
|
309
309
|
self._ws = ws
|
|
310
310
|
elapsed = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
|
|
311
311
|
elapsed_str = f" ({elapsed:.1f}s)" if elapsed else ""
|
|
@@ -364,17 +364,18 @@ class WebServer:
|
|
|
364
364
|
|
|
365
365
|
# Send module.ready (every reconnect, not just first time)
|
|
366
366
|
if not self._shutting_down:
|
|
367
|
+
startup_time = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
|
|
367
368
|
await self._rpc_call(ws, "event.publish", {
|
|
368
369
|
"event_id": str(uuid.uuid4()),
|
|
369
370
|
"event": "module.ready",
|
|
370
371
|
"data": {
|
|
371
372
|
"module_id": "web",
|
|
372
373
|
"graceful_shutdown": True,
|
|
374
|
+
"startup_time": startup_time,
|
|
373
375
|
},
|
|
374
376
|
})
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
print(f"[web] module.ready sent{elapsed_str}")
|
|
377
|
+
elapsed_str = self._fmt_elapsed(self.boot_t0)
|
|
378
|
+
print(f"[web] module.ready published ({elapsed_str})")
|
|
378
379
|
|
|
379
380
|
# Publish web.started event with access URL
|
|
380
381
|
display_host = "localhost" if self.host == "0.0.0.0" else self.host
|
|
@@ -397,9 +398,6 @@ class WebServer:
|
|
|
397
398
|
# - 事件通知和 RPC 响应可以同步处理(它们不会反向调用 rpc_call)
|
|
398
399
|
print(f"[web] Entering receive loop")
|
|
399
400
|
|
|
400
|
-
# Start heartbeat loop
|
|
401
|
-
heartbeat_task = asyncio.create_task(self._heartbeat_loop(ws))
|
|
402
|
-
|
|
403
401
|
try:
|
|
404
402
|
async for raw in ws:
|
|
405
403
|
try:
|
|
@@ -443,16 +441,20 @@ class WebServer:
|
|
|
443
441
|
msg["params"] = params
|
|
444
442
|
await ws.send(json.dumps(msg))
|
|
445
443
|
|
|
446
|
-
async def
|
|
447
|
-
"""
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
444
|
+
async def _handle_ping_event(self, data: dict):
|
|
445
|
+
"""Handle system.ping event and reply with system.pong."""
|
|
446
|
+
import time
|
|
447
|
+
t1 = data.get("ping_time")
|
|
448
|
+
t2 = time.time()
|
|
449
|
+
|
|
450
|
+
await self._publish_event({
|
|
451
|
+
"event": "system.pong",
|
|
452
|
+
"data": {
|
|
453
|
+
"module_id": "web",
|
|
454
|
+
"ping_time": t1,
|
|
455
|
+
"pong_time": t2,
|
|
456
|
+
},
|
|
457
|
+
})
|
|
456
458
|
|
|
457
459
|
async def _handle_event_notification(self, msg: dict):
|
|
458
460
|
"""Handle an event notification (JSON-RPC 2.0 Notification with method='event')."""
|
|
@@ -460,6 +462,11 @@ class WebServer:
|
|
|
460
462
|
event_type = params.get("event", "")
|
|
461
463
|
data = params.get("data", {})
|
|
462
464
|
|
|
465
|
+
# Handle system.ping event
|
|
466
|
+
if event_type == "system.ping":
|
|
467
|
+
await self._handle_ping_event(data)
|
|
468
|
+
return
|
|
469
|
+
|
|
463
470
|
# Log all events for debugging
|
|
464
471
|
print(f"[web] Event received: {event_type}, data: {data}")
|
|
465
472
|
|
|
@@ -532,10 +539,15 @@ class WebServer:
|
|
|
532
539
|
|
|
533
540
|
async def _rpc_health(self) -> dict:
|
|
534
541
|
"""RPC handler for web.health."""
|
|
542
|
+
# 获取 WebSocket 连接数
|
|
543
|
+
from routes.routes_management_ws import _management_clients
|
|
544
|
+
active_connections = len(_management_clients)
|
|
545
|
+
|
|
535
546
|
return {
|
|
536
547
|
"status": "healthy",
|
|
548
|
+
"uptime_seconds": round(time.time() - self._start_time),
|
|
537
549
|
"details": {
|
|
538
|
-
"
|
|
550
|
+
"active_ws_connections": active_connections,
|
|
539
551
|
},
|
|
540
552
|
}
|
|
541
553
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// Load tokens on page load
|
|
7
7
|
async function loadTokens() {
|
|
8
|
-
console.log('[
|
|
8
|
+
console.log('[web] loadTokens() called');
|
|
9
9
|
|
|
10
10
|
// 立即显示加载中
|
|
11
11
|
const tbody = document.getElementById('tokens-tbody');
|
|
@@ -18,16 +18,16 @@ async function loadTokens() {
|
|
|
18
18
|
try {
|
|
19
19
|
// Use kernel client to call RPC
|
|
20
20
|
if (!kernelClient || !kernelClient.connected) {
|
|
21
|
-
console.error('[
|
|
21
|
+
console.error('[web] kernelClient not ready:', { kernelClient, connected: kernelClient?.connected });
|
|
22
22
|
throw new Error('WebSocket 未连接');
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
console.log('[
|
|
25
|
+
console.log('[web] Calling web.list_tokens...');
|
|
26
26
|
const result = await kernelClient.call('web.list_tokens', {});
|
|
27
|
-
console.log('[
|
|
27
|
+
console.log('[web] Got result:', result);
|
|
28
28
|
renderTokens(result.tokens || []);
|
|
29
29
|
} catch (err) {
|
|
30
|
-
console.error('[
|
|
30
|
+
console.error('[web] Failed to load tokens:', err);
|
|
31
31
|
|
|
32
32
|
// 根据错误类型显示不同的提示
|
|
33
33
|
let errorMsg = err.message;
|
|
@@ -126,7 +126,7 @@ async function revokeToken(token) {
|
|
|
126
126
|
await kernelClient.call('web.revoke_token', { token });
|
|
127
127
|
|
|
128
128
|
} catch (err) {
|
|
129
|
-
console.error('[
|
|
129
|
+
console.error('[web] Failed to revoke token:', err);
|
|
130
130
|
|
|
131
131
|
// 失败后恢复该行
|
|
132
132
|
const row = event.target.closest('tr');
|
|
@@ -140,7 +140,7 @@ async function revokeToken(token) {
|
|
|
140
140
|
? '权限不足:无法吊销 Token'
|
|
141
141
|
: `吊销失败: ${err.message}`;
|
|
142
142
|
|
|
143
|
-
console.error('[
|
|
143
|
+
console.error('[web]', errorMsg);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
@@ -157,7 +157,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
157
157
|
modulesNav.addEventListener('click', () => {
|
|
158
158
|
// Wait a bit for page switch, then load tokens
|
|
159
159
|
setTimeout(() => {
|
|
160
|
-
console.log('[
|
|
160
|
+
console.log('[web] Modules page clicked, loading tokens...');
|
|
161
161
|
loadTokens();
|
|
162
162
|
}, 100);
|
|
163
163
|
});
|
|
@@ -165,10 +165,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
165
165
|
|
|
166
166
|
// Wait for kernelClient to be ready before loading tokens
|
|
167
167
|
window.addEventListener('kernelClientReady', () => {
|
|
168
|
-
console.log('[
|
|
168
|
+
console.log('[web] kernelClient ready');
|
|
169
169
|
// Load tokens if modules page is active on load
|
|
170
170
|
if (document.getElementById('page-modules').classList.contains('active')) {
|
|
171
|
-
console.log('[
|
|
171
|
+
console.log('[web] Modules page is active, loading tokens...');
|
|
172
172
|
loadTokens();
|
|
173
173
|
}
|
|
174
174
|
});
|
package/kernel/entry.py
CHANGED
|
@@ -460,7 +460,7 @@ def main():
|
|
|
460
460
|
launcher_token = secrets.token_urlsafe(32)
|
|
461
461
|
|
|
462
462
|
# Step 3: Create KernelServer with launcher_token
|
|
463
|
-
server = KernelServer(launcher_token=launcher_token, advertise_ip=advertise_ip, module_id=MODULE_NAME)
|
|
463
|
+
server = KernelServer(launcher_token=launcher_token, advertise_ip=advertise_ip, module_id=MODULE_NAME, boot_t0=_t0)
|
|
464
464
|
|
|
465
465
|
# Step 4: Bind port
|
|
466
466
|
bind_host = advertise_ip
|