@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
package/kernel/server.py
CHANGED
|
@@ -33,9 +33,13 @@ class KernelServer:
|
|
|
33
33
|
- Event notifications (delivered to subscribers)
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
|
-
def __init__(self, launcher_token: str = None, advertise_ip: str = "127.0.0.1"):
|
|
36
|
+
def __init__(self, launcher_token: str = None, advertise_ip: str = "127.0.0.1", module_id: str = None, boot_t0: float = 0):
|
|
37
|
+
if module_id is None:
|
|
38
|
+
raise ValueError("module_id is required")
|
|
39
|
+
self.module_id = module_id
|
|
37
40
|
self.advertise_ip = advertise_ip
|
|
38
41
|
self.port: int = 0 # set by entry.py before uvicorn.run
|
|
42
|
+
self.boot_t0 = boot_t0 # Startup time for ready event
|
|
39
43
|
|
|
40
44
|
# Core components
|
|
41
45
|
self.registry = RegistryStore(launcher_token) # Can be None
|
|
@@ -54,7 +58,6 @@ class KernelServer:
|
|
|
54
58
|
)
|
|
55
59
|
|
|
56
60
|
# Background tasks
|
|
57
|
-
self._ttl_task: asyncio.Task | None = None
|
|
58
61
|
self._dedup_task: asyncio.Task | None = None
|
|
59
62
|
self._uvicorn_server = None # set by entry.py for graceful shutdown
|
|
60
63
|
self._shutting_down = False
|
|
@@ -64,11 +67,25 @@ class KernelServer:
|
|
|
64
67
|
self._launcher_subscribed = False
|
|
65
68
|
self._ready_published = False
|
|
66
69
|
|
|
70
|
+
# Subscribe to events that Kernel needs to handle
|
|
71
|
+
# Kernel 通过订阅机制接收事件(与其他模块一致)
|
|
72
|
+
self.event_hub.handle_subscribe(self.module_id, ["module.shutdown", "system.pong"])
|
|
73
|
+
|
|
74
|
+
# Register internal event callback for Kernel
|
|
75
|
+
# Kernel 自身没有 WebSocket 连接,通过回调机制接收订阅的事件
|
|
76
|
+
self.event_hub.register_internal_callback(self.module_id, self._handle_internal_event)
|
|
77
|
+
|
|
67
78
|
# Debounce timers for disconnected modules (module_id -> asyncio.Task)
|
|
68
79
|
self._debounce_tasks: dict[str, asyncio.Task] = {}
|
|
69
80
|
# Launcher loss timer (35s after launcher offline)
|
|
70
81
|
self._launcher_loss_task: asyncio.Task | None = None
|
|
71
82
|
|
|
83
|
+
# Ping/Pong tracking
|
|
84
|
+
self._ping_sent_times: dict[str, float] = {} # module_id -> last ping sent time (t1)
|
|
85
|
+
self._pong_latencies: dict[str, dict] = {} # module_id -> {"outbound": ms, "inbound": ms, "last_update": timestamp}
|
|
86
|
+
self._pong_status: dict[str, str] = {} # module_id -> "ok" | "timeout" | "never"
|
|
87
|
+
self._ping_task: asyncio.Task | None = None # Global ping broadcast task
|
|
88
|
+
|
|
72
89
|
# Build FastAPI app
|
|
73
90
|
self.app = self._create_app()
|
|
74
91
|
|
|
@@ -80,15 +97,16 @@ class KernelServer:
|
|
|
80
97
|
|
|
81
98
|
@app.on_event("startup")
|
|
82
99
|
async def _startup():
|
|
83
|
-
server.
|
|
100
|
+
server.event_hub.start_internal_senders()
|
|
84
101
|
server._dedup_task = asyncio.create_task(server._dedup_loop())
|
|
102
|
+
server._ping_task = asyncio.create_task(server._ping_broadcast_loop())
|
|
85
103
|
|
|
86
104
|
@app.on_event("shutdown")
|
|
87
105
|
async def _shutdown():
|
|
88
|
-
if server._ttl_task:
|
|
89
|
-
server._ttl_task.cancel()
|
|
90
106
|
if server._dedup_task:
|
|
91
107
|
server._dedup_task.cancel()
|
|
108
|
+
if server._ping_task:
|
|
109
|
+
server._ping_task.cancel()
|
|
92
110
|
|
|
93
111
|
# ── WebSocket endpoint ──
|
|
94
112
|
|
|
@@ -138,9 +156,9 @@ class KernelServer:
|
|
|
138
156
|
print(f"[kernel] launcher reconnected, cancelled loss timer")
|
|
139
157
|
print(f"[kernel] launcher connected")
|
|
140
158
|
|
|
141
|
-
#
|
|
142
|
-
if module_id in server.
|
|
143
|
-
server.
|
|
159
|
+
# Initialize ping status for new connection
|
|
160
|
+
if module_id not in server._pong_status:
|
|
161
|
+
server._pong_status[module_id] = "never"
|
|
144
162
|
|
|
145
163
|
try:
|
|
146
164
|
while True:
|
|
@@ -231,17 +249,54 @@ class KernelServer:
|
|
|
231
249
|
|
|
232
250
|
# ── Background loops ──
|
|
233
251
|
|
|
234
|
-
async def
|
|
235
|
-
"""
|
|
252
|
+
async def _ping_broadcast_loop(self):
|
|
253
|
+
"""Broadcast system.ping event every 20s to all connected modules."""
|
|
254
|
+
import time
|
|
255
|
+
import uuid
|
|
236
256
|
while True:
|
|
237
|
-
await asyncio.sleep(10)
|
|
238
257
|
try:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
258
|
+
await asyncio.sleep(20)
|
|
259
|
+
|
|
260
|
+
# Record ping send time for all connected modules
|
|
261
|
+
t1 = time.time()
|
|
262
|
+
for module_id in list(self.connections.keys()):
|
|
263
|
+
self._ping_sent_times[module_id] = t1
|
|
264
|
+
# Mark as timeout if previous ping didn't get pong
|
|
265
|
+
if module_id in self._pong_status:
|
|
266
|
+
if self._pong_status[module_id] == "ok":
|
|
267
|
+
# Previous was ok, now waiting for new pong
|
|
268
|
+
pass
|
|
269
|
+
elif self._pong_status[module_id] == "never":
|
|
270
|
+
# Still never received pong
|
|
271
|
+
pass
|
|
272
|
+
# If was timeout, keep it as timeout until pong arrives
|
|
273
|
+
|
|
274
|
+
# Broadcast system.ping event
|
|
275
|
+
self.event_hub.publish_internal(
|
|
276
|
+
"system.ping",
|
|
277
|
+
{"ping_time": t1},
|
|
278
|
+
source=self.module_id
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Check for timeouts after 20s
|
|
282
|
+
await asyncio.sleep(20)
|
|
283
|
+
t_check = time.time()
|
|
284
|
+
for module_id in list(self.connections.keys()):
|
|
285
|
+
if module_id in self._ping_sent_times:
|
|
286
|
+
# If no pong received within 20s, mark as timeout
|
|
287
|
+
if module_id not in self._pong_latencies or \
|
|
288
|
+
self._pong_latencies[module_id].get("last_update", 0) < self._ping_sent_times[module_id]:
|
|
289
|
+
if self._pong_status.get(module_id) == "never":
|
|
290
|
+
# Keep as "never" if never received
|
|
291
|
+
pass
|
|
292
|
+
else:
|
|
293
|
+
self._pong_status[module_id] = "timeout"
|
|
294
|
+
print(f"[kernel] {module_id} ping timeout (no pong in 20s)")
|
|
295
|
+
|
|
296
|
+
except asyncio.CancelledError:
|
|
297
|
+
break
|
|
243
298
|
except Exception as e:
|
|
244
|
-
print(f"[kernel]
|
|
299
|
+
print(f"[kernel] Ping broadcast loop error: {e}")
|
|
245
300
|
|
|
246
301
|
async def _dedup_loop(self):
|
|
247
302
|
"""Clean up dedup table every 30s."""
|
|
@@ -265,7 +320,7 @@ class KernelServer:
|
|
|
265
320
|
# 5s elapsed, module did not reconnect — mark offline
|
|
266
321
|
self._debounce_tasks.pop(module_id, None)
|
|
267
322
|
self.registry.set_offline(module_id)
|
|
268
|
-
self.event_hub.publish_internal("module.offline", {"module_id": module_id})
|
|
323
|
+
self.event_hub.publish_internal("module.offline", {"module_id": module_id}, source=self.module_id)
|
|
269
324
|
print(f"[kernel] {module_id} offline (5s debounce expired)")
|
|
270
325
|
|
|
271
326
|
# If launcher went offline, start 35s launcher loss timer
|
|
@@ -289,7 +344,7 @@ class KernelServer:
|
|
|
289
344
|
# Publish module.shutdown with reason launcher_lost to all modules
|
|
290
345
|
self.event_hub.publish_internal("module.shutdown", {
|
|
291
346
|
"reason": "launcher_lost",
|
|
292
|
-
})
|
|
347
|
+
}, source=self.module_id)
|
|
293
348
|
|
|
294
349
|
# Wait for modules to clean up (up to 10s)
|
|
295
350
|
await asyncio.sleep(10)
|
|
@@ -302,7 +357,7 @@ class KernelServer:
|
|
|
302
357
|
def self_register(self):
|
|
303
358
|
"""Register Kernel itself in the registry (in-memory, no RPC needed)."""
|
|
304
359
|
self.registry.register_module({
|
|
305
|
-
"module_id":
|
|
360
|
+
"module_id": self.module_id,
|
|
306
361
|
"module_type": "infrastructure",
|
|
307
362
|
"api_endpoint": f"http://{self.advertise_ip}:{self.port}",
|
|
308
363
|
"health_endpoint": "/health",
|
|
@@ -313,25 +368,266 @@ class KernelServer:
|
|
|
313
368
|
|
|
314
369
|
def publish_ready(self):
|
|
315
370
|
"""Publish module.ready event for Kernel (internal, no WS needed)."""
|
|
371
|
+
import time
|
|
372
|
+
startup_time = time.monotonic() - self.boot_t0 if self.boot_t0 else 0
|
|
316
373
|
self.event_hub.publish_internal("module.ready", {
|
|
317
|
-
"module_id":
|
|
374
|
+
"module_id": self.module_id,
|
|
318
375
|
"ws_endpoint": f"ws://{self.advertise_ip}:{self.port}/ws",
|
|
319
376
|
"graceful_shutdown": True,
|
|
320
|
-
|
|
377
|
+
"startup_time": startup_time,
|
|
378
|
+
}, source=self.module_id)
|
|
379
|
+
|
|
380
|
+
async def _handle_internal_event(self, event_type: str, data: dict):
|
|
381
|
+
"""内部事件处理器(通过 EventHub 回调机制调用)
|
|
382
|
+
|
|
383
|
+
Kernel 自身没有 WebSocket 连接,通过此回调接收发送给自己的事件。
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
event_type: 事件类型(如 "module.shutdown")
|
|
387
|
+
data: 事件数据
|
|
388
|
+
|
|
389
|
+
Note:
|
|
390
|
+
此方法由 EventHub 的 _invoke_callback_safe 包装调用,异常会被捕获并记录。
|
|
391
|
+
"""
|
|
392
|
+
try:
|
|
393
|
+
# 处理 Kernel 订阅的事件
|
|
394
|
+
if event_type == "module.shutdown":
|
|
395
|
+
await self.handle_shutdown_event(data)
|
|
396
|
+
elif event_type == "system.pong":
|
|
397
|
+
await self._handle_pong_event(data)
|
|
398
|
+
# 忽略系统广播事件(Kernel 没有订阅但会收到广播)
|
|
399
|
+
elif event_type in ("module.ready", "module.registered", "module.started",
|
|
400
|
+
"module.stopped", "module.crashed", "module.exiting",
|
|
401
|
+
"module.offline", "system.ready", "registry.updated"):
|
|
402
|
+
# 这些是系统事件,会广播给所有模块,Kernel 收到是正常的
|
|
403
|
+
pass
|
|
404
|
+
else:
|
|
405
|
+
# 收到未知事件,可能是订阅了但忘记实现处理器
|
|
406
|
+
print(f"[kernel] Warning: Received unhandled event: {event_type}")
|
|
407
|
+
except Exception as e:
|
|
408
|
+
# 双重保险:即使 EventHub 的包装器失败,这里也捕获
|
|
409
|
+
print(f"[kernel] Error handling internal event {event_type}: {e}")
|
|
410
|
+
import traceback
|
|
411
|
+
traceback.print_exc()
|
|
412
|
+
raise # 重新抛出,让 EventHub 的包装器记录
|
|
413
|
+
|
|
414
|
+
async def _handle_pong_event(self, data: dict):
|
|
415
|
+
"""处理 system.pong 事件,计算往返延迟
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
data: {
|
|
419
|
+
"module_id": str,
|
|
420
|
+
"ping_time": float, # t1 - Kernel 发送 ping 的时间
|
|
421
|
+
"pong_time": float, # t2 - 模块收到 ping 并发送 pong 的时间
|
|
422
|
+
}
|
|
423
|
+
"""
|
|
424
|
+
import time
|
|
425
|
+
module_id = data.get("module_id")
|
|
426
|
+
t1 = data.get("ping_time") # Kernel 发送 ping 的时间
|
|
427
|
+
t2 = data.get("pong_time") # 模块收到 ping 并发送 pong 的时间
|
|
428
|
+
t3 = time.time() # Kernel 收到 pong 的时间
|
|
429
|
+
|
|
430
|
+
if not module_id or t1 is None or t2 is None:
|
|
431
|
+
print(f"[kernel] Invalid pong event data: {data}")
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
# 验证 ping_time 是否匹配
|
|
435
|
+
if module_id not in self._ping_sent_times:
|
|
436
|
+
print(f"[kernel] Received pong from {module_id} but no ping record")
|
|
437
|
+
return
|
|
438
|
+
|
|
439
|
+
expected_t1 = self._ping_sent_times[module_id]
|
|
440
|
+
if abs(t1 - expected_t1) > 0.1: # 允许 100ms 误差
|
|
441
|
+
print(f"[kernel] Pong from {module_id} has mismatched ping_time (expected {expected_t1}, got {t1})")
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
# 计算两个延迟
|
|
445
|
+
outbound_ms = (t2 - t1) * 1000 # 去程:Kernel → 模块
|
|
446
|
+
inbound_ms = (t3 - t2) * 1000 # 回程:模块 → Kernel
|
|
447
|
+
|
|
448
|
+
self._pong_latencies[module_id] = {
|
|
449
|
+
"outbound": round(outbound_ms, 2),
|
|
450
|
+
"inbound": round(inbound_ms, 2),
|
|
451
|
+
"last_update": t3,
|
|
452
|
+
}
|
|
453
|
+
self._pong_status[module_id] = "ok"
|
|
454
|
+
|
|
455
|
+
async def handle_shutdown_event(self, event_data):
|
|
456
|
+
"""处理 module.shutdown 事件(标准优雅退出流程)
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
event_data: 事件数据,应包含 module_id 字段
|
|
460
|
+
|
|
461
|
+
Note:
|
|
462
|
+
仅响应针对 Kernel 的 shutdown 事件(module_id == "kernel")。
|
|
463
|
+
防御性检查:验证数据完整性,防止重复处理。
|
|
464
|
+
"""
|
|
465
|
+
# 防御性检查:验证数据类型
|
|
466
|
+
if not isinstance(event_data, dict):
|
|
467
|
+
print(f"[kernel] Warning: Invalid shutdown event data type: {type(event_data)}")
|
|
468
|
+
return
|
|
469
|
+
|
|
470
|
+
# 防御性检查:验证 module_id
|
|
471
|
+
target_module = event_data.get("module_id")
|
|
472
|
+
if target_module != self.module_id:
|
|
473
|
+
print(f"[kernel] Warning: Received shutdown for wrong module: {target_module}")
|
|
474
|
+
return
|
|
475
|
+
|
|
476
|
+
# 防御性检查:防止重复处理
|
|
477
|
+
if self._shutting_down:
|
|
478
|
+
print("[kernel] Warning: Shutdown already in progress, ignoring duplicate event")
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
reason = event_data.get("reason", "unknown")
|
|
482
|
+
print(f"[kernel] 收到 shutdown 事件 (reason={reason}),开始优雅退出")
|
|
483
|
+
|
|
484
|
+
try:
|
|
485
|
+
# Step 1: 立即发送 ack
|
|
486
|
+
self.event_hub.publish_internal("module.shutdown.ack", {
|
|
487
|
+
"module_id": self.module_id
|
|
488
|
+
}, source=self.module_id)
|
|
489
|
+
print("[kernel] 已发送 shutdown.ack")
|
|
490
|
+
|
|
491
|
+
# Step 2: 发送 exiting
|
|
492
|
+
self.event_hub.publish_internal("module.exiting", {
|
|
493
|
+
"module_id": self.module_id,
|
|
494
|
+
"type": "passive",
|
|
495
|
+
"reason": f"响应 shutdown ({reason})",
|
|
496
|
+
"restart": "auto",
|
|
497
|
+
"action": "none",
|
|
498
|
+
"timeout": 5.0
|
|
499
|
+
}, source=self.module_id)
|
|
500
|
+
print("[kernel] 已发送 exiting")
|
|
501
|
+
|
|
502
|
+
# Step 3: 执行清理
|
|
503
|
+
await self._do_cleanup()
|
|
504
|
+
|
|
505
|
+
# Step 4: 发送 ready
|
|
506
|
+
self.event_hub.publish_internal("module.shutdown.ready", {
|
|
507
|
+
"module_id": self.module_id
|
|
508
|
+
}, source=self.module_id)
|
|
509
|
+
print("[kernel] 已发送 shutdown.ready")
|
|
510
|
+
|
|
511
|
+
# Step 5: 等待一小段时间确保事件发送完成
|
|
512
|
+
await asyncio.sleep(0.2)
|
|
513
|
+
|
|
514
|
+
# Step 6: 关闭所有 WebSocket 连接(包括 Launcher)
|
|
515
|
+
close_tasks = []
|
|
516
|
+
for mid, ws in list(self.connections.items()):
|
|
517
|
+
try:
|
|
518
|
+
close_tasks.append(ws.close(code=1000, reason="Graceful shutdown complete"))
|
|
519
|
+
except Exception as e:
|
|
520
|
+
print(f"[kernel] 关闭 {mid} 连接失败: {e}")
|
|
521
|
+
|
|
522
|
+
if close_tasks:
|
|
523
|
+
await asyncio.gather(*close_tasks, return_exceptions=True)
|
|
524
|
+
print(f"[kernel] 已关闭 {len(close_tasks)} 个 WebSocket 连接")
|
|
525
|
+
|
|
526
|
+
# Step 7: 触发 uvicorn 关闭
|
|
527
|
+
if self._uvicorn_server:
|
|
528
|
+
print("[kernel] 触发 uvicorn 关闭")
|
|
529
|
+
self._uvicorn_server.should_exit = True
|
|
530
|
+
else:
|
|
531
|
+
print("[kernel] uvicorn 引用未设置,直接退出")
|
|
532
|
+
import sys
|
|
533
|
+
sys.exit(0)
|
|
534
|
+
|
|
535
|
+
except Exception as e:
|
|
536
|
+
# 如果优雅退出失败,强制退出
|
|
537
|
+
print(f"[kernel] 优雅退出过程中发生错误: {e}")
|
|
538
|
+
import traceback
|
|
539
|
+
traceback.print_exc()
|
|
540
|
+
print("[kernel] 强制退出")
|
|
541
|
+
import sys
|
|
542
|
+
sys.exit(1)
|
|
543
|
+
|
|
544
|
+
async def _do_cleanup(self):
|
|
545
|
+
"""执行清理工作(从原 shutdown() 方法提取)"""
|
|
546
|
+
if self._shutting_down:
|
|
547
|
+
return
|
|
548
|
+
|
|
549
|
+
self._shutting_down = True
|
|
550
|
+
print("[kernel] 执行清理工作...")
|
|
551
|
+
|
|
552
|
+
# 取消所有 debounce 任务
|
|
553
|
+
if self._debounce_tasks:
|
|
554
|
+
print(f"[kernel] 取消 {len(self._debounce_tasks)} 个 debounce 任务")
|
|
555
|
+
for task in self._debounce_tasks.values():
|
|
556
|
+
if not task.done():
|
|
557
|
+
task.cancel()
|
|
558
|
+
self._debounce_tasks.clear()
|
|
559
|
+
|
|
560
|
+
# 取消 launcher loss 计时器
|
|
561
|
+
if self._launcher_loss_task and not self._launcher_loss_task.done():
|
|
562
|
+
print("[kernel] 取消 launcher loss 计时器")
|
|
563
|
+
self._launcher_loss_task.cancel()
|
|
564
|
+
self._launcher_loss_task = None
|
|
565
|
+
|
|
566
|
+
# 关闭其他模块的 WebSocket 连接(保留 Launcher 连接)
|
|
567
|
+
other_connections = {mid: ws for mid, ws in self.connections.items() if mid != "launcher"}
|
|
568
|
+
if other_connections:
|
|
569
|
+
print(f"[kernel] 关闭 {len(other_connections)} 个其他模块的 WebSocket 连接")
|
|
570
|
+
for module_id, ws in other_connections.items():
|
|
571
|
+
try:
|
|
572
|
+
await ws.close(code=1001, reason="Server shutting down")
|
|
573
|
+
except Exception as e:
|
|
574
|
+
print(f"[kernel] 关闭连接失败 {module_id}: {e}")
|
|
575
|
+
|
|
576
|
+
# 清空 RPC 转发队列
|
|
577
|
+
pending_count = len(self.rpc_router._pending)
|
|
578
|
+
if pending_count > 0:
|
|
579
|
+
print(f"[kernel] 清空 {pending_count} 个待处理的 RPC 转发")
|
|
580
|
+
self.rpc_router._pending.clear()
|
|
581
|
+
|
|
582
|
+
print("[kernel] 清理完成")
|
|
321
583
|
|
|
322
584
|
async def shutdown(self):
|
|
323
|
-
"""Shutdown Kernel gracefully
|
|
585
|
+
"""Shutdown Kernel gracefully (legacy method for launcher_lost scenario).
|
|
586
|
+
|
|
587
|
+
This method is kept for the launcher_lost timeout scenario where Kernel
|
|
588
|
+
must shut down autonomously without receiving a shutdown event.
|
|
589
|
+
"""
|
|
324
590
|
if self._shutting_down:
|
|
325
591
|
return
|
|
326
592
|
|
|
327
593
|
self._shutting_down = True
|
|
328
|
-
print("[kernel] Shutting down...")
|
|
594
|
+
print("[kernel] Shutting down (launcher_lost)...")
|
|
595
|
+
|
|
596
|
+
# Cancel all debounce tasks
|
|
597
|
+
print(f"[kernel] Cancelling {len(self._debounce_tasks)} debounce tasks...")
|
|
598
|
+
for task in self._debounce_tasks.values():
|
|
599
|
+
if not task.done():
|
|
600
|
+
task.cancel()
|
|
601
|
+
self._debounce_tasks.clear()
|
|
602
|
+
|
|
603
|
+
# Cancel launcher loss timer
|
|
604
|
+
if self._launcher_loss_task and not self._launcher_loss_task.done():
|
|
605
|
+
print("[kernel] Cancelling launcher loss timer...")
|
|
606
|
+
self._launcher_loss_task.cancel()
|
|
607
|
+
self._launcher_loss_task = None
|
|
608
|
+
|
|
609
|
+
# Close all WebSocket connections
|
|
610
|
+
print(f"[kernel] Closing {len(self.connections)} WebSocket connections...")
|
|
611
|
+
for module_id, ws in list(self.connections.items()):
|
|
612
|
+
try:
|
|
613
|
+
await ws.close(code=1001, reason="Server shutting down")
|
|
614
|
+
except Exception as e:
|
|
615
|
+
print(f"[kernel] Failed to close connection for {module_id}: {e}")
|
|
616
|
+
self.connections.clear()
|
|
329
617
|
|
|
330
|
-
#
|
|
331
|
-
|
|
618
|
+
# Clear pending RPC forwards
|
|
619
|
+
pending_count = len(self.rpc_router._pending)
|
|
620
|
+
if pending_count > 0:
|
|
621
|
+
print(f"[kernel] Clearing {pending_count} pending RPC forwards...")
|
|
622
|
+
self.rpc_router._pending.clear()
|
|
332
623
|
|
|
333
624
|
# Trigger uvicorn shutdown
|
|
334
625
|
if self._uvicorn_server:
|
|
626
|
+
print("[kernel] Triggering uvicorn graceful shutdown...")
|
|
335
627
|
self._uvicorn_server.should_exit = True
|
|
336
628
|
else:
|
|
337
|
-
print("[kernel] Warning: uvicorn server reference not set")
|
|
629
|
+
print("[kernel] Warning: uvicorn server reference not set, forcing exit")
|
|
630
|
+
import sys
|
|
631
|
+
sys.exit(0)
|
|
632
|
+
|
|
633
|
+
print("[kernel] Shutdown complete")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""命令模块"""
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""clean 命令实现 - 清理临时文件和缓存"""
|
|
2
|
+
import shutil
|
|
3
|
+
import tempfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from kite_cli.utils.interactive import confirm_action
|
|
6
|
+
from kite_cli.utils.operation_log import log_operation
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_clean(args):
|
|
10
|
+
"""执行清理命令"""
|
|
11
|
+
skip_confirm = args.yes if hasattr(args, 'yes') else False
|
|
12
|
+
clean_all = args.all if hasattr(args, 'all') else False
|
|
13
|
+
|
|
14
|
+
cleaned_items = []
|
|
15
|
+
|
|
16
|
+
# 1. 清理系统临时目录中的 kite-install-* 目录
|
|
17
|
+
temp_dir = Path(tempfile.gettempdir())
|
|
18
|
+
kite_temp_dirs = list(temp_dir.glob("kite-install-*"))
|
|
19
|
+
|
|
20
|
+
if kite_temp_dirs:
|
|
21
|
+
print(f"[Info] 发现 {len(kite_temp_dirs)} 个临时安装目录")
|
|
22
|
+
if not skip_confirm:
|
|
23
|
+
if not confirm_action("是否清理临时安装目录?"):
|
|
24
|
+
print("[Cancelled] 已取消")
|
|
25
|
+
return 1
|
|
26
|
+
|
|
27
|
+
for temp_path in kite_temp_dirs:
|
|
28
|
+
try:
|
|
29
|
+
shutil.rmtree(temp_path, ignore_errors=True)
|
|
30
|
+
cleaned_items.append({"type": "temp_dir", "path": str(temp_path)})
|
|
31
|
+
print(f" [Done] 已清理: {temp_path.name}")
|
|
32
|
+
except Exception as e:
|
|
33
|
+
print(f" [Warning] 清理失败: {temp_path.name} - {e}")
|
|
34
|
+
|
|
35
|
+
# 2. 如果指定 --all,清理所有模块的 .venv 和 node_modules
|
|
36
|
+
if clean_all:
|
|
37
|
+
print("\n[Warning] --all 选项会清理所有模块的依赖环境")
|
|
38
|
+
if not skip_confirm:
|
|
39
|
+
if not confirm_action("确认清理所有模块依赖?"):
|
|
40
|
+
print("[Cancelled] 已取消清理依赖")
|
|
41
|
+
else:
|
|
42
|
+
cleaned_items.extend(clean_module_deps())
|
|
43
|
+
else:
|
|
44
|
+
cleaned_items.extend(clean_module_deps())
|
|
45
|
+
|
|
46
|
+
# 记录操作日志
|
|
47
|
+
if cleaned_items:
|
|
48
|
+
log_operation("clean", {
|
|
49
|
+
"cleaned": cleaned_items
|
|
50
|
+
}, success=True)
|
|
51
|
+
|
|
52
|
+
print(f"\n[Done] 清理完成!共清理 {len(cleaned_items)} 项")
|
|
53
|
+
else:
|
|
54
|
+
print("[Info] 没有需要清理的内容")
|
|
55
|
+
|
|
56
|
+
return 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def clean_module_deps() -> list:
|
|
60
|
+
"""清理所有模块的依赖环境"""
|
|
61
|
+
import os
|
|
62
|
+
from kite_cli.utils.paths import get_install_path
|
|
63
|
+
|
|
64
|
+
cleaned = []
|
|
65
|
+
|
|
66
|
+
# 遍历所有安装位置
|
|
67
|
+
for location in ["dev", "local", "global"]:
|
|
68
|
+
try:
|
|
69
|
+
base_path = get_install_path(location, "")
|
|
70
|
+
if not base_path.exists():
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
# 查找所有模块目录
|
|
74
|
+
for module_dir in base_path.iterdir():
|
|
75
|
+
if not module_dir.is_dir():
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
# 清理 .venv
|
|
79
|
+
venv_dir = module_dir / ".venv"
|
|
80
|
+
if venv_dir.exists():
|
|
81
|
+
try:
|
|
82
|
+
shutil.rmtree(venv_dir)
|
|
83
|
+
cleaned.append({"type": "venv", "path": str(venv_dir)})
|
|
84
|
+
print(f" [Done] 已清理: {module_dir.name}/.venv")
|
|
85
|
+
except Exception as e:
|
|
86
|
+
print(f" [Warning] 清理失败: {module_dir.name}/.venv - {e}")
|
|
87
|
+
|
|
88
|
+
# 清理 node_modules
|
|
89
|
+
node_modules = module_dir / "node_modules"
|
|
90
|
+
if node_modules.exists():
|
|
91
|
+
try:
|
|
92
|
+
shutil.rmtree(node_modules)
|
|
93
|
+
cleaned.append({"type": "node_modules", "path": str(node_modules)})
|
|
94
|
+
print(f" [Done] 已清理: {module_dir.name}/node_modules")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(f" [Warning] 清理失败: {module_dir.name}/node_modules - {e}")
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f" [Warning] 扫描 {location} 失败: {e}")
|
|
100
|
+
|
|
101
|
+
return cleaned
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
kite deps-install 命令
|
|
3
|
+
|
|
4
|
+
只负责安装依赖库
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_deps_install(args):
|
|
12
|
+
"""安装依赖库"""
|
|
13
|
+
project_root = Path(__file__).parent.parent.parent
|
|
14
|
+
sys.path.insert(0, str(project_root))
|
|
15
|
+
|
|
16
|
+
from core.env_checker import (
|
|
17
|
+
check_dependencies_installed,
|
|
18
|
+
install_dependencies,
|
|
19
|
+
load_dependencies_lock
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
print("=" * 60)
|
|
23
|
+
print(" Kite 依赖库安装")
|
|
24
|
+
print("=" * 60)
|
|
25
|
+
print()
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
# 1. 加载依赖锁定
|
|
29
|
+
lock_data = load_dependencies_lock()
|
|
30
|
+
if not lock_data:
|
|
31
|
+
print("✓ 无依赖锁定文件,跳过")
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
dependencies = lock_data.get("dependencies", {})
|
|
35
|
+
if not dependencies:
|
|
36
|
+
print("✓ 无依赖要求,跳过")
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
print(f"依赖锁定文件: {len(dependencies)} 个依赖")
|
|
40
|
+
|
|
41
|
+
# 2. 检查依赖
|
|
42
|
+
deps_ok, deps_msg, deps_details = check_dependencies_installed()
|
|
43
|
+
if deps_ok:
|
|
44
|
+
print(f"✓ 依赖库已安装且版本一致")
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
# 3. 安装依赖
|
|
48
|
+
print(f"✗ {deps_msg}")
|
|
49
|
+
if deps_details:
|
|
50
|
+
if deps_details.get("missing"):
|
|
51
|
+
print(f" 缺失: {len(deps_details['missing'])} 个")
|
|
52
|
+
if deps_details.get("version_mismatch"):
|
|
53
|
+
print(f" 版本不匹配: {len(deps_details['version_mismatch'])} 个")
|
|
54
|
+
|
|
55
|
+
print(f"\n正在安装依赖...")
|
|
56
|
+
if not install_dependencies():
|
|
57
|
+
print("✗ 依赖安装失败")
|
|
58
|
+
return 1
|
|
59
|
+
|
|
60
|
+
print(f"✓ 依赖安装成功")
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(f"\n✗ 依赖安装失败: {e}")
|
|
65
|
+
import traceback
|
|
66
|
+
traceback.print_exc()
|
|
67
|
+
return 1
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""doctor 命令实现 - 检查下载工具状态"""
|
|
2
|
+
from kite_cli.core.tool_installer import ToolInstaller
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def run_doctor(args):
|
|
6
|
+
"""执行环境检查命令"""
|
|
7
|
+
print("[Doctor] 检查下载工具状态...\n")
|
|
8
|
+
|
|
9
|
+
tools = [
|
|
10
|
+
("Python", "python"),
|
|
11
|
+
("pip", "pip"),
|
|
12
|
+
("Node.js", "node"),
|
|
13
|
+
("npm", "npm"),
|
|
14
|
+
("Git", "git")
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
all_ok = True
|
|
18
|
+
|
|
19
|
+
for name, cmd in tools:
|
|
20
|
+
status = ToolInstaller.check_tool(cmd)
|
|
21
|
+
if status:
|
|
22
|
+
print(f" [OK] {name:10} 已安装")
|
|
23
|
+
else:
|
|
24
|
+
print(f" [Missing] {name:10} 未安装")
|
|
25
|
+
all_ok = False
|
|
26
|
+
|
|
27
|
+
print()
|
|
28
|
+
|
|
29
|
+
if all_ok:
|
|
30
|
+
print("[Done] 所有工具已就绪")
|
|
31
|
+
return 0
|
|
32
|
+
else:
|
|
33
|
+
print("[Warning] 部分工具未安装")
|
|
34
|
+
print("[Info] 使用 'kite install' 时会自动尝试安装缺失的工具")
|
|
35
|
+
return 1
|