@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
package/kite_cli/main.py
CHANGED
|
@@ -6,7 +6,7 @@ from kite_cli import __version__
|
|
|
6
6
|
from kite_cli.utils.i18n import t
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
KNOWN_COMMANDS = ["install", "uninstall", "update", "list", "search", "info", "log", "rollback", "clean", "doctor", "history"]
|
|
9
|
+
KNOWN_COMMANDS = ["install", "uninstall", "update", "list", "search", "info", "log", "rollback", "clean", "doctor", "history", "prepare", "env-check", "venv-setup", "deps-install"]
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class SilentArgumentParser(argparse.ArgumentParser):
|
|
@@ -46,6 +46,10 @@ Kite CLI v{__version__} — 模块安装管理工具
|
|
|
46
46
|
rollback 回滚最后一次操作
|
|
47
47
|
clean 清理临时文件和缓存
|
|
48
48
|
doctor 检查下载工具状态
|
|
49
|
+
prepare 打包前准备(扫描依赖,生成锁定文件)
|
|
50
|
+
env-check 环境检查(虚拟环境和依赖库)
|
|
51
|
+
venv-setup 创建虚拟环境
|
|
52
|
+
deps-install 安装依赖库
|
|
49
53
|
|
|
50
54
|
全局选项:
|
|
51
55
|
-h, -H, --help 显示此帮助信息
|
|
@@ -218,6 +222,18 @@ def main():
|
|
|
218
222
|
# doctor 命令
|
|
219
223
|
doctor_parser = subparsers.add_parser("doctor", help="检查下载工具状态", add_help=True)
|
|
220
224
|
|
|
225
|
+
# prepare 命令
|
|
226
|
+
prepare_parser = subparsers.add_parser("prepare", help="打包前准备(扫描依赖)", add_help=True)
|
|
227
|
+
|
|
228
|
+
# env-check 命令
|
|
229
|
+
env_check_parser = subparsers.add_parser("env-check", help="环境检查(虚拟环境和依赖库)", add_help=True)
|
|
230
|
+
|
|
231
|
+
# venv-setup 命令
|
|
232
|
+
venv_setup_parser = subparsers.add_parser("venv-setup", help="创建虚拟环境", add_help=True)
|
|
233
|
+
|
|
234
|
+
# deps-install 命令
|
|
235
|
+
deps_install_parser = subparsers.add_parser("deps-install", help="安装依赖库", add_help=True)
|
|
236
|
+
|
|
221
237
|
args, unknown = parser.parse_known_args()
|
|
222
238
|
|
|
223
239
|
# 处理全局 --help / --version
|
|
@@ -280,6 +296,18 @@ def main():
|
|
|
280
296
|
elif args.command == "doctor":
|
|
281
297
|
from kite_cli.commands.doctor import run_doctor
|
|
282
298
|
return run_doctor(args)
|
|
299
|
+
elif args.command == "prepare":
|
|
300
|
+
from kite_cli.commands.prepare import run_prepare
|
|
301
|
+
return run_prepare(args)
|
|
302
|
+
elif args.command == "env-check":
|
|
303
|
+
from kite_cli.commands.env_check import run_env_check
|
|
304
|
+
return run_env_check(args)
|
|
305
|
+
elif args.command == "venv-setup":
|
|
306
|
+
from kite_cli.commands.venv_setup import run_venv_setup
|
|
307
|
+
return run_venv_setup(args)
|
|
308
|
+
elif args.command == "deps-install":
|
|
309
|
+
from kite_cli.commands.deps_install import run_deps_install
|
|
310
|
+
return run_deps_install(args)
|
|
283
311
|
else:
|
|
284
312
|
print(f"未知命令: {args.command}")
|
|
285
313
|
return 1
|
package/launcher/entry.py
CHANGED
|
@@ -73,16 +73,25 @@ class Launcher:
|
|
|
73
73
|
discovery=self._load_discovery(),
|
|
74
74
|
)
|
|
75
75
|
|
|
76
|
+
# Load relay configuration
|
|
77
|
+
relay_config = self._load_relay_config()
|
|
78
|
+
self._relay_modules = relay_config.get("modules", [])
|
|
79
|
+
self._relay_token_limits = relay_config.get("token_limits", {})
|
|
80
|
+
|
|
76
81
|
self.kernel_port: int = 0
|
|
77
82
|
self.modules: dict[str, ModuleInfo] = {}
|
|
78
83
|
self._shutdown_event = asyncio.Event()
|
|
79
84
|
self._thread_shutdown = threading.Event()
|
|
80
85
|
self._shutdown_complete = threading.Event() # Set when normal shutdown finishes
|
|
81
86
|
self._module_tokens: dict[str, str] = {} # module_name -> per-module token
|
|
87
|
+
self._client_tokens: dict[str, str] = {} # virtual module_id -> kernel_token (for relay modules)
|
|
82
88
|
|
|
83
89
|
# Three-layer state model: desired_state per module
|
|
84
90
|
self._desired_states: dict[str, str] = {} # module_name -> "running" | "stopped"
|
|
85
91
|
|
|
92
|
+
# Relay module configuration (already loaded above, don't reinitialize)
|
|
93
|
+
# self._relay_modules and self._relay_token_limits are set in lines 78-79
|
|
94
|
+
|
|
86
95
|
# Kernel WebSocket client
|
|
87
96
|
self._ws: object | None = None
|
|
88
97
|
self._ws_task: asyncio.Task | None = None
|
|
@@ -526,10 +535,14 @@ class Launcher:
|
|
|
526
535
|
ready = await self._wait_event("module.ready", "kernel", timeout=15)
|
|
527
536
|
if ready:
|
|
528
537
|
self._graceful_modules["kernel"] = bool(ready.get("graceful_shutdown"))
|
|
529
|
-
|
|
538
|
+
# Use startup_time from module.ready event
|
|
539
|
+
startup_time = ready.get("startup_time", time.monotonic() - t_ws)
|
|
540
|
+
self._ready_times["kernel"] = startup_time
|
|
541
|
+
startup_str = f"{startup_time:.3f}s" if startup_time < 10 else f"{startup_time:.2f}s"
|
|
542
|
+
print(f"[launcher] Kernel 已就绪 ({startup_str})")
|
|
530
543
|
else:
|
|
531
544
|
print("\033[91m[launcher] 警告: Kernel 在 15s 内未发送 module.ready\033[0m")
|
|
532
|
-
|
|
545
|
+
self._ready_times["kernel"] = time.monotonic() - t_ws
|
|
533
546
|
|
|
534
547
|
await asyncio.gather(
|
|
535
548
|
_scan_and_generate_tokens(),
|
|
@@ -630,7 +643,7 @@ class Launcher:
|
|
|
630
643
|
launcher_token = self._module_tokens.get("launcher", "")
|
|
631
644
|
ws_url = f"ws://127.0.0.1:{self.kernel_port}/ws?token={launcher_token}&id=launcher"
|
|
632
645
|
t_ws_connect = time.monotonic()
|
|
633
|
-
async with websockets.connect(ws_url, open_timeout=3, ping_interval=
|
|
646
|
+
async with websockets.connect(ws_url, open_timeout=3, ping_interval=None, close_timeout=10) as ws:
|
|
634
647
|
self._ws = ws
|
|
635
648
|
_ws_s = time.monotonic() - t_ws_connect
|
|
636
649
|
print(f"[launcher] 已连接到 Kernel ({self._fmt_elapsed(_ws_s)})")
|
|
@@ -664,6 +677,7 @@ class Launcher:
|
|
|
664
677
|
"restart_launcher": {"method": "restart_launcher", "description": "重启 Launcher"},
|
|
665
678
|
"rescan": {"method": "rescan", "description": "重新扫描模块"},
|
|
666
679
|
"shutdown": {"method": "shutdown", "description": "关闭系统"},
|
|
680
|
+
"request_client_token": {"method": "request_client_token", "description": "为 Web 客户端申请 Kernel Token"},
|
|
667
681
|
},
|
|
668
682
|
"module": {
|
|
669
683
|
"config": {
|
|
@@ -692,10 +706,14 @@ class Launcher:
|
|
|
692
706
|
print("[launcher] 已注册到 Kernel")
|
|
693
707
|
|
|
694
708
|
# Publish module.ready for Launcher itself (every reconnect)
|
|
709
|
+
startup_time = time.monotonic() - self._t_start
|
|
695
710
|
await self._publish_event("module.ready", {
|
|
696
711
|
"module_id": "launcher",
|
|
697
712
|
"graceful_shutdown": True,
|
|
713
|
+
"startup_time": startup_time,
|
|
698
714
|
})
|
|
715
|
+
# Record launcher's own startup time
|
|
716
|
+
self._ready_times["launcher"] = startup_time
|
|
699
717
|
|
|
700
718
|
# Signal that connection is ready (after subscription and registration)
|
|
701
719
|
if self._ws_connected:
|
|
@@ -775,6 +793,17 @@ class Launcher:
|
|
|
775
793
|
self._rpc_results[rpc_id] = msg
|
|
776
794
|
waiter.set()
|
|
777
795
|
|
|
796
|
+
async def _handle_ping_event(self, data: dict):
|
|
797
|
+
"""Handle system.ping event and reply with system.pong."""
|
|
798
|
+
t1 = data.get("ping_time")
|
|
799
|
+
t2 = time.time()
|
|
800
|
+
|
|
801
|
+
await self._publish_event("system.pong", {
|
|
802
|
+
"module_id": "launcher",
|
|
803
|
+
"ping_time": t1,
|
|
804
|
+
"pong_time": t2,
|
|
805
|
+
})
|
|
806
|
+
|
|
778
807
|
async def _handle_event_notification(self, msg: dict):
|
|
779
808
|
"""Handle an event notification (JSON-RPC 2.0 Notification with method='event')."""
|
|
780
809
|
params = msg.get("params", {})
|
|
@@ -783,6 +812,11 @@ class Launcher:
|
|
|
783
812
|
data = params.get("data") if isinstance(params.get("data"), dict) else {}
|
|
784
813
|
ts = params.get("timestamp", "")
|
|
785
814
|
|
|
815
|
+
# Handle system.ping event
|
|
816
|
+
if event == "system.ping":
|
|
817
|
+
await self._handle_ping_event(data)
|
|
818
|
+
return
|
|
819
|
+
|
|
786
820
|
# Trigger event waiters
|
|
787
821
|
module_id = data.get("module_id", "")
|
|
788
822
|
waiter_key = f"{event}:{module_id}"
|
|
@@ -794,6 +828,14 @@ class Launcher:
|
|
|
794
828
|
|
|
795
829
|
# module.exiting also wakes module.ready waiter
|
|
796
830
|
if event == "module.exiting" and module_id:
|
|
831
|
+
# 处理 token 释放(如果是虚拟模块且标记了 token_revoked)
|
|
832
|
+
token_revoked = data.get("token_revoked", False)
|
|
833
|
+
if token_revoked and module_id in self._client_tokens:
|
|
834
|
+
del self._client_tokens[module_id]
|
|
835
|
+
print(f"[launcher] Token revoked for {module_id}")
|
|
836
|
+
# 记录审计日志
|
|
837
|
+
self._log_token_request("system", module_id, "revoke", True)
|
|
838
|
+
|
|
797
839
|
ready_key = f"module.ready:{module_id}"
|
|
798
840
|
ready_waiter = self._event_waiters.get(ready_key)
|
|
799
841
|
if ready_waiter:
|
|
@@ -863,9 +905,11 @@ class Launcher:
|
|
|
863
905
|
if step == "code_generated":
|
|
864
906
|
code = data.get("code", "")
|
|
865
907
|
expires_in = data.get("expires_in", 300)
|
|
908
|
+
module_id = data.get("module_id", "unknown")
|
|
866
909
|
if code:
|
|
867
910
|
print(f"[launcher] {GREEN}配对码: {code}{RESET}")
|
|
868
911
|
print(f"[launcher] {GREEN}有效期: {expires_in} 秒{RESET}")
|
|
912
|
+
print(f"[launcher] {GREEN}来源模块: {module_id}{RESET}")
|
|
869
913
|
print(f"[launcher] {GREEN}访问 Web 界面时使用此配对码进行配对{RESET}")
|
|
870
914
|
|
|
871
915
|
elif step == "pairing":
|
|
@@ -922,6 +966,8 @@ class Launcher:
|
|
|
922
966
|
"get_module_config": self._rpc_get_module_config,
|
|
923
967
|
"update_module_config": self._rpc_update_module_config,
|
|
924
968
|
"reset_module_config": self._rpc_reset_module_config,
|
|
969
|
+
"request_client_token": self._rpc_request_client_token,
|
|
970
|
+
"release_client_token": self._rpc_release_client_token,
|
|
925
971
|
}
|
|
926
972
|
handler = handlers.get(method)
|
|
927
973
|
if handler:
|
|
@@ -943,10 +989,30 @@ class Launcher:
|
|
|
943
989
|
|
|
944
990
|
async def _rpc_list_modules(self, params: dict) -> dict:
|
|
945
991
|
"""List all modules and their current status."""
|
|
992
|
+
# Get ping/pong latencies from Kernel
|
|
993
|
+
latencies = {}
|
|
994
|
+
try:
|
|
995
|
+
latencies_resp = await self._rpc_call(self._ws, "kernel.latencies", {}, timeout=2)
|
|
996
|
+
if "result" in latencies_resp:
|
|
997
|
+
latencies = latencies_resp["result"].get("latencies", {})
|
|
998
|
+
except Exception as e:
|
|
999
|
+
print(f"[launcher] Failed to get latencies: {e}")
|
|
1000
|
+
|
|
1001
|
+
current_time = time.time()
|
|
946
1002
|
result = []
|
|
947
1003
|
for name, info in self.modules.items():
|
|
948
1004
|
running = self.process_manager.is_running(name)
|
|
949
1005
|
rec = self.process_manager.get_record(name)
|
|
1006
|
+
|
|
1007
|
+
# Get latency info for this module
|
|
1008
|
+
latency_info = latencies.get(name, {})
|
|
1009
|
+
ping_status = latency_info.get("status", "never")
|
|
1010
|
+
|
|
1011
|
+
# Calculate uptime (running time in seconds)
|
|
1012
|
+
uptime_seconds = None
|
|
1013
|
+
if running and rec and rec.started_at:
|
|
1014
|
+
uptime_seconds = current_time - rec.started_at
|
|
1015
|
+
|
|
950
1016
|
result.append({
|
|
951
1017
|
"name": name,
|
|
952
1018
|
"display_name": info.display_name,
|
|
@@ -956,10 +1022,20 @@ class Launcher:
|
|
|
956
1022
|
"runtime": info.runtime,
|
|
957
1023
|
"preferred_port": info.preferred_port,
|
|
958
1024
|
"monitor": info.monitor,
|
|
1025
|
+
"display_order": info.display_order,
|
|
959
1026
|
"desired_state": self._desired_states.get(name, "stopped"),
|
|
960
1027
|
"actual_state": f"running({rec.pid})" if running and rec else "stopped",
|
|
961
1028
|
"pid": rec.pid if running and rec else None,
|
|
1029
|
+
"startup_time": self._ready_times.get(name), # Module startup time in seconds
|
|
1030
|
+
"uptime_seconds": uptime_seconds, # Running time in seconds
|
|
1031
|
+
"ping_status": ping_status,
|
|
1032
|
+
"ping_outbound_ms": latency_info.get("outbound"),
|
|
1033
|
+
"ping_inbound_ms": latency_info.get("inbound"),
|
|
962
1034
|
})
|
|
1035
|
+
|
|
1036
|
+
# Sort by display_order (descending), then by name (ascending)
|
|
1037
|
+
result.sort(key=lambda m: (-m["display_order"], m["name"]))
|
|
1038
|
+
|
|
963
1039
|
return {"modules": result}
|
|
964
1040
|
|
|
965
1041
|
async def _rpc_start_module(self, params: dict) -> dict:
|
|
@@ -1864,8 +1940,11 @@ class Launcher:
|
|
|
1864
1940
|
print(f"[launcher] 模块 '{info.name}' 主动退出: {reason} ({elapsed:.2f}s)")
|
|
1865
1941
|
elif ready:
|
|
1866
1942
|
self._graceful_modules[info.name] = bool(ready.get("graceful_shutdown"))
|
|
1867
|
-
|
|
1868
|
-
|
|
1943
|
+
# Use startup_time from module.ready event (module's self-reported startup time)
|
|
1944
|
+
startup_time = ready.get("startup_time", elapsed)
|
|
1945
|
+
self._ready_times[info.name] = startup_time
|
|
1946
|
+
startup_str = f"{startup_time:.3f}s" if startup_time < 10 else f"{startup_time:.2f}s"
|
|
1947
|
+
print(f"[launcher] 模块 '{info.name}' 已就绪 ({startup_str})")
|
|
1869
1948
|
else:
|
|
1870
1949
|
print(f"\033[91m[launcher] 警告: '{info.name}' 在 {timeout}s 内未发送 module.ready\033[0m")
|
|
1871
1950
|
|
|
@@ -2150,9 +2229,6 @@ class Launcher:
|
|
|
2150
2229
|
started_at=self._start_unix,
|
|
2151
2230
|
)
|
|
2152
2231
|
running.append(("launcher", launcher_info, launcher_rec))
|
|
2153
|
-
# Launcher is ready immediately (ready_time = 0)
|
|
2154
|
-
if "launcher" not in self._ready_times:
|
|
2155
|
-
self._ready_times["launcher"] = 0.0
|
|
2156
2232
|
|
|
2157
2233
|
for name, info in self.modules.items():
|
|
2158
2234
|
rec = self.process_manager.get_record(name)
|
|
@@ -2223,7 +2299,16 @@ class Launcher:
|
|
|
2223
2299
|
for name, info, rec in running_sorted:
|
|
2224
2300
|
label = info.display_name or name
|
|
2225
2301
|
ready_t = self._ready_times.get(name)
|
|
2226
|
-
|
|
2302
|
+
# Format startup time with ms/s auto-switch
|
|
2303
|
+
if ready_t is not None:
|
|
2304
|
+
if ready_t < 1:
|
|
2305
|
+
time_str = f"{ready_t * 1000:.0f}ms"
|
|
2306
|
+
elif ready_t < 10:
|
|
2307
|
+
time_str = f"{ready_t:.2f}s"
|
|
2308
|
+
else:
|
|
2309
|
+
time_str = f"{ready_t:.1f}s"
|
|
2310
|
+
else:
|
|
2311
|
+
time_str = "—"
|
|
2227
2312
|
|
|
2228
2313
|
# Calculate elapsed from start
|
|
2229
2314
|
if ready_t is not None and hasattr(self, '_start_unix'):
|
|
@@ -2295,18 +2380,53 @@ class Launcher:
|
|
|
2295
2380
|
|
|
2296
2381
|
lines.append(f"{G} Kernel WS: ws://127.0.0.1:{self.kernel_port}/ws 实例: {self.instance_id}{R}")
|
|
2297
2382
|
|
|
2298
|
-
#
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2383
|
+
# Check if web or evol modules are running and display their URLs
|
|
2384
|
+
RED = "\033[91m"
|
|
2385
|
+
running_names = {name for name, _, _ in running}
|
|
2386
|
+
|
|
2387
|
+
# Check web module
|
|
2388
|
+
if "web" in running_names:
|
|
2389
|
+
web_url = ""
|
|
2390
|
+
web_error = ""
|
|
2391
|
+
if self._ws:
|
|
2392
|
+
try:
|
|
2393
|
+
resp = await self._rpc_call(self._ws, "registry.get", {"path": "web.api_endpoint"}, timeout=3)
|
|
2394
|
+
val = resp.get("result", {}).get("value")
|
|
2395
|
+
if val and isinstance(val, str):
|
|
2396
|
+
web_url = val.replace("://127.0.0.1:", "://localhost:")
|
|
2397
|
+
else:
|
|
2398
|
+
web_error = "未注册到 Kernel Registry"
|
|
2399
|
+
except Exception as e:
|
|
2400
|
+
web_error = f"查询失败: {str(e)}"
|
|
2401
|
+
else:
|
|
2402
|
+
web_error = "Kernel 连接不可用"
|
|
2403
|
+
|
|
2404
|
+
if web_url:
|
|
2405
|
+
lines.append(f"{B} Web 管理后台: {web_url}{R}")
|
|
2406
|
+
else:
|
|
2407
|
+
lines.append(f"{RED} Web 管理后台: {web_error}{R}")
|
|
2408
|
+
|
|
2409
|
+
# Check evol module
|
|
2410
|
+
if "evol" in running_names:
|
|
2411
|
+
evol_url = ""
|
|
2412
|
+
evol_error = ""
|
|
2413
|
+
if self._ws:
|
|
2414
|
+
try:
|
|
2415
|
+
resp = await self._rpc_call(self._ws, "registry.get", {"path": "evol.api_endpoint"}, timeout=3)
|
|
2416
|
+
val = resp.get("result", {}).get("value")
|
|
2417
|
+
if val and isinstance(val, str):
|
|
2418
|
+
evol_url = val.replace("://127.0.0.1:", "://localhost:")
|
|
2419
|
+
else:
|
|
2420
|
+
evol_error = "未注册到 Kernel Registry"
|
|
2421
|
+
except Exception as e:
|
|
2422
|
+
evol_error = f"查询失败: {str(e)}"
|
|
2423
|
+
else:
|
|
2424
|
+
evol_error = "Kernel 连接不可用"
|
|
2425
|
+
|
|
2426
|
+
if evol_url:
|
|
2427
|
+
lines.append(f"{B} Evol: {evol_url}{R}")
|
|
2428
|
+
else:
|
|
2429
|
+
lines.append(f"{RED} Evol: {evol_error}{R}")
|
|
2310
2430
|
|
|
2311
2431
|
# Instance info
|
|
2312
2432
|
instances = self.process_manager.get_alive_instances()
|
|
@@ -2370,6 +2490,155 @@ class Launcher:
|
|
|
2370
2490
|
|
|
2371
2491
|
print("\n".join(lines))
|
|
2372
2492
|
|
|
2493
|
+
async def _rpc_request_client_token(self, params: dict) -> dict:
|
|
2494
|
+
"""为 relay 模块申请虚拟客户端 Token.
|
|
2495
|
+
|
|
2496
|
+
Args:
|
|
2497
|
+
params: {
|
|
2498
|
+
"module_id": str - 虚拟模块 ID (如 "web-client-a3f9e2")
|
|
2499
|
+
"_caller_id": str - 调用者模块 ID (由 Kernel 注入)
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
Returns:
|
|
2503
|
+
{
|
|
2504
|
+
"token": str - Kernel Token (64 字符 hex)
|
|
2505
|
+
"module_id": str - 虚拟模块 ID
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
Raises:
|
|
2509
|
+
PermissionError: 调用者不在白名单中
|
|
2510
|
+
ValueError: module_id 格式不合法或已存在
|
|
2511
|
+
RuntimeError: Token 限额已满
|
|
2512
|
+
"""
|
|
2513
|
+
import re
|
|
2514
|
+
|
|
2515
|
+
# 1. 获取调用者 ID
|
|
2516
|
+
caller_id = params.get("_caller_id")
|
|
2517
|
+
if not caller_id:
|
|
2518
|
+
raise PermissionError("Missing _caller_id in params")
|
|
2519
|
+
|
|
2520
|
+
# 2. 权限检查
|
|
2521
|
+
if caller_id not in self._relay_modules:
|
|
2522
|
+
print(f"[launcher] DEBUG: 权限检查失败 - caller_id={caller_id}, relay_modules={self._relay_modules}")
|
|
2523
|
+
raise PermissionError(f"Permission denied: {caller_id} not in relay_modules whitelist")
|
|
2524
|
+
|
|
2525
|
+
# 3. 参数验证
|
|
2526
|
+
module_id = params.get("module_id")
|
|
2527
|
+
if not module_id:
|
|
2528
|
+
raise ValueError("module_id is required")
|
|
2529
|
+
|
|
2530
|
+
# 4. 命名规范验证
|
|
2531
|
+
expected_prefix = f"{caller_id}-client-"
|
|
2532
|
+
if not module_id.startswith(expected_prefix):
|
|
2533
|
+
raise ValueError(f"module_id must start with {expected_prefix}")
|
|
2534
|
+
|
|
2535
|
+
suffix = module_id[len(expected_prefix):]
|
|
2536
|
+
if not re.match(r'^[a-zA-Z0-9_-]+$', suffix):
|
|
2537
|
+
raise ValueError("Invalid module_id suffix")
|
|
2538
|
+
|
|
2539
|
+
# 5. 检查是否已存在(幂等)
|
|
2540
|
+
if module_id in self._client_tokens:
|
|
2541
|
+
print(f"[launcher] Token already exists for {module_id}, returning existing token")
|
|
2542
|
+
return {
|
|
2543
|
+
"token": self._client_tokens[module_id],
|
|
2544
|
+
"module_id": module_id
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
# 6. 检查限额
|
|
2548
|
+
limit = self._relay_token_limits.get(caller_id, 100)
|
|
2549
|
+
current_count = sum(1 for mid in self._client_tokens if mid.startswith(expected_prefix))
|
|
2550
|
+
if current_count >= limit:
|
|
2551
|
+
raise RuntimeError(f"Token limit reached: {current_count}/{limit}")
|
|
2552
|
+
|
|
2553
|
+
# 7. 生成 token
|
|
2554
|
+
token = secrets.token_hex(32)
|
|
2555
|
+
|
|
2556
|
+
# 8. 注册到 Kernel
|
|
2557
|
+
try:
|
|
2558
|
+
result = await self._rpc_call(self._ws, "kernel.register_tokens", {module_id: token})
|
|
2559
|
+
if "error" in result:
|
|
2560
|
+
raise RuntimeError(f"Failed to register token: {result['error'].get('message', '')}")
|
|
2561
|
+
except Exception as e:
|
|
2562
|
+
raise RuntimeError(f"Failed to register token to Kernel: {e}")
|
|
2563
|
+
|
|
2564
|
+
# 9. 保存到本地映射
|
|
2565
|
+
self._client_tokens[module_id] = token
|
|
2566
|
+
|
|
2567
|
+
# 10. 记录审计日志
|
|
2568
|
+
self._log_token_request(caller_id, module_id, "request", True)
|
|
2569
|
+
|
|
2570
|
+
print(f"[launcher] Token generated for {module_id} (caller: {caller_id})")
|
|
2571
|
+
|
|
2572
|
+
return {
|
|
2573
|
+
"token": token,
|
|
2574
|
+
"module_id": module_id
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
async def _rpc_release_client_token(self, params: dict) -> dict:
|
|
2578
|
+
"""释放虚拟客户端 Token (可选).
|
|
2579
|
+
|
|
2580
|
+
Args:
|
|
2581
|
+
params: {
|
|
2582
|
+
"module_id": str - 虚拟模块 ID
|
|
2583
|
+
"_caller_id": str - 调用者模块 ID (由 Kernel 注入)
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
Returns:
|
|
2587
|
+
{}
|
|
2588
|
+
|
|
2589
|
+
Raises:
|
|
2590
|
+
PermissionError: 调用者不在白名单中或 module_id 不属于调用者
|
|
2591
|
+
ValueError: module_id 格式不合法
|
|
2592
|
+
"""
|
|
2593
|
+
# 1. 获取调用者 ID
|
|
2594
|
+
caller_id = params.get("_caller_id")
|
|
2595
|
+
if not caller_id:
|
|
2596
|
+
raise PermissionError("Missing _caller_id in params")
|
|
2597
|
+
|
|
2598
|
+
# 2. 权限检查
|
|
2599
|
+
if caller_id not in self._relay_modules:
|
|
2600
|
+
print(f"[launcher] DEBUG: 权限检查失败 - caller_id={caller_id}, relay_modules={self._relay_modules}")
|
|
2601
|
+
raise PermissionError(f"Permission denied: {caller_id} not in relay_modules whitelist")
|
|
2602
|
+
|
|
2603
|
+
# 3. 参数验证
|
|
2604
|
+
module_id = params.get("module_id")
|
|
2605
|
+
if not module_id:
|
|
2606
|
+
raise ValueError("module_id is required")
|
|
2607
|
+
|
|
2608
|
+
# 4. 验证所有权
|
|
2609
|
+
expected_prefix = f"{caller_id}-client-"
|
|
2610
|
+
if not module_id.startswith(expected_prefix):
|
|
2611
|
+
raise ValueError(f"module_id does not belong to {caller_id}")
|
|
2612
|
+
|
|
2613
|
+
# 5. 删除 token
|
|
2614
|
+
if module_id in self._client_tokens:
|
|
2615
|
+
del self._client_tokens[module_id]
|
|
2616
|
+
print(f"[launcher] Token released for {module_id} (caller: {caller_id})")
|
|
2617
|
+
|
|
2618
|
+
# 6. 记录审计日志
|
|
2619
|
+
self._log_token_request(caller_id, module_id, "release", True)
|
|
2620
|
+
|
|
2621
|
+
return {}
|
|
2622
|
+
|
|
2623
|
+
def _log_token_request(self, caller_id: str, module_id: str, action: str, success: bool):
|
|
2624
|
+
"""记录 Token 申请/释放审计日志."""
|
|
2625
|
+
from datetime import datetime, timezone
|
|
2626
|
+
record = {
|
|
2627
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
2628
|
+
"caller_id": caller_id,
|
|
2629
|
+
"module_id": module_id,
|
|
2630
|
+
"action": action,
|
|
2631
|
+
"success": success
|
|
2632
|
+
}
|
|
2633
|
+
try:
|
|
2634
|
+
log_dir = os.path.join(os.environ.get("KITE_MODULE_DATA", ""), "log")
|
|
2635
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
2636
|
+
log_file = os.path.join(log_dir, "token_requests.jsonl")
|
|
2637
|
+
with open(log_file, "a", encoding="utf-8") as f:
|
|
2638
|
+
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
|
2639
|
+
except Exception as e:
|
|
2640
|
+
print(f"[launcher] 警告: 写入 token 审计日志失败: {e}")
|
|
2641
|
+
|
|
2373
2642
|
# ── Utilities ──
|
|
2374
2643
|
|
|
2375
2644
|
def _load_discovery(self) -> dict | None:
|
|
@@ -2385,6 +2654,22 @@ class Launcher:
|
|
|
2385
2654
|
print(f"[launcher] 警告: 读取发现配置失败: {e}")
|
|
2386
2655
|
return None
|
|
2387
2656
|
|
|
2657
|
+
def _load_relay_config(self) -> dict:
|
|
2658
|
+
"""Read relay config from launcher's own module.md."""
|
|
2659
|
+
md_path = os.path.join(os.environ["KITE_PROJECT"], "launcher", "module.md")
|
|
2660
|
+
try:
|
|
2661
|
+
with open(md_path, "r", encoding="utf-8") as f:
|
|
2662
|
+
fm = _parse_frontmatter(f.read())
|
|
2663
|
+
relay = fm.get("relay")
|
|
2664
|
+
if isinstance(relay, dict) and relay:
|
|
2665
|
+
print(f"[launcher] Relay 配置已加载: modules={relay.get('modules')}, token_limits={relay.get('token_limits')}")
|
|
2666
|
+
return relay
|
|
2667
|
+
else:
|
|
2668
|
+
print(f"[launcher] 警告: relay 配置为空或格式错误")
|
|
2669
|
+
except Exception as e:
|
|
2670
|
+
print(f"[launcher] 警告: 读取 relay 配置失败: {e}")
|
|
2671
|
+
return {}
|
|
2672
|
+
|
|
2388
2673
|
def _log_lifecycle(self, event: str, module: str, **extra):
|
|
2389
2674
|
"""Append one JSONL line to lifecycle.jsonl."""
|
|
2390
2675
|
from datetime import datetime, timezone
|
package/launcher/module.md
CHANGED
|
@@ -31,6 +31,7 @@ class ModuleInfo:
|
|
|
31
31
|
preferred_port: int = 0 # 0 = OS assigns; non-zero = try this port first
|
|
32
32
|
depends_on: list = field(default_factory=list) # module names this module depends on
|
|
33
33
|
monitor: bool = True # whether Watchdog should monitor this module
|
|
34
|
+
display_order: int = 0 # 0-100, controls display order in module list (0=default, 100=highest)
|
|
34
35
|
module_dir: str = "" # absolute path to the module directory
|
|
35
36
|
launch: LaunchConfig = field(default_factory=LaunchConfig)
|
|
36
37
|
|
|
@@ -105,7 +106,8 @@ class ModuleScanner:
|
|
|
105
106
|
# Note: launcher is not scanned (it's the scanner itself)
|
|
106
107
|
kernel_dir = os.path.join(project_root, "kernel")
|
|
107
108
|
if os.path.isdir(kernel_dir):
|
|
108
|
-
|
|
109
|
+
# Kernel is at project root, check it directly
|
|
110
|
+
self._add_module(kernel_dir, modules)
|
|
109
111
|
self._scan_dir(os.path.join(project_root, "extensions"), 2, modules)
|
|
110
112
|
|
|
111
113
|
# Extra sources from discovery config
|
|
@@ -216,6 +218,13 @@ class ModuleScanner:
|
|
|
216
218
|
monitor_val = fm.get("monitor", "true")
|
|
217
219
|
monitor = str(monitor_val).lower() not in ("false", "0", "no")
|
|
218
220
|
|
|
221
|
+
# Parse display_order (0-100, default 0)
|
|
222
|
+
try:
|
|
223
|
+
display_order = int(fm.get("display_order", 0))
|
|
224
|
+
display_order = max(0, min(100, display_order)) # Clamp to [0, 100]
|
|
225
|
+
except (ValueError, TypeError):
|
|
226
|
+
display_order = 0
|
|
227
|
+
|
|
219
228
|
# Parse optional launch config
|
|
220
229
|
launch_raw = fm.get("launch", {})
|
|
221
230
|
if not isinstance(launch_raw, dict):
|
|
@@ -254,6 +263,7 @@ class ModuleScanner:
|
|
|
254
263
|
preferred_port=preferred_port,
|
|
255
264
|
depends_on=depends_on,
|
|
256
265
|
monitor=monitor,
|
|
266
|
+
display_order=display_order,
|
|
257
267
|
module_dir=mod_dir,
|
|
258
268
|
launch=launch,
|
|
259
269
|
)
|
package/main.py
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
Kite development entry point.
|
|
3
3
|
1. Run code stats
|
|
4
4
|
2. Start launcher
|
|
5
|
+
|
|
6
|
+
注意:环境检查已在 Node.js 层(cli.js)完成
|
|
5
7
|
"""
|
|
6
8
|
import sys
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
|
|
9
11
|
# Add project root to sys.path
|
|
10
|
-
|
|
12
|
+
project_root = Path(__file__).parent
|
|
13
|
+
sys.path.insert(0, str(project_root))
|
|
11
14
|
|
|
12
15
|
# 1. Run code stats
|
|
13
16
|
from launcher.count_lines import run_stats
|
package/package.json
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentunion/kite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Kite framework launcher — start Kite from anywhere",
|
|
5
5
|
"bin": {
|
|
6
6
|
"kite": "./cli.js"
|
|
7
7
|
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node scripts/setup-python-env.js"
|
|
10
|
+
},
|
|
8
11
|
"files": [
|
|
9
12
|
"cli.js",
|
|
10
13
|
"main.py",
|
|
14
|
+
"requirements.txt",
|
|
15
|
+
"python_version.json",
|
|
16
|
+
"dependencies_lock.json",
|
|
11
17
|
"launcher/**",
|
|
12
18
|
"kernel/**",
|
|
13
19
|
"extensions/**",
|
|
14
20
|
"kite_cli/**",
|
|
15
21
|
"scripts/**",
|
|
22
|
+
"core/**",
|
|
16
23
|
"CHANGELOG.md",
|
|
17
24
|
"!**/__pycache__",
|
|
18
25
|
"!**/__pycache__/**",
|
package/requirements.txt
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Kite 框架核心依赖
|
|
2
|
+
# 安装方法:pip install -r requirements.txt
|
|
3
|
+
|
|
4
|
+
# Web 框架
|
|
5
|
+
fastapi>=0.115.0
|
|
6
|
+
uvicorn>=0.32.0
|
|
7
|
+
starlette>=0.41.0
|
|
8
|
+
|
|
9
|
+
# WebSocket
|
|
10
|
+
websockets>=13.0
|
|
11
|
+
|
|
12
|
+
# HTTP 客户端
|
|
13
|
+
httpx>=0.27.0
|
|
14
|
+
aiohttp>=3.10.0
|
|
15
|
+
requests>=2.32.0
|
|
16
|
+
|
|
17
|
+
# 配置解析
|
|
18
|
+
json5>=0.9.25
|
|
19
|
+
pyyaml>=6.0.2
|
|
20
|
+
python-dotenv>=1.0.0
|
|
21
|
+
|
|
22
|
+
# 数据验证
|
|
23
|
+
pydantic>=2.9.0
|
|
24
|
+
|
|
25
|
+
# 异步 IO
|
|
26
|
+
aiofiles>=24.1.0
|
|
27
|
+
|
|
28
|
+
# 进程管理
|
|
29
|
+
psutil>=6.1.0
|
|
30
|
+
|
|
31
|
+
# 加密
|
|
32
|
+
cryptography>=43.0.0
|
|
33
|
+
|
|
34
|
+
# 进度条
|
|
35
|
+
tqdm>=4.66.0
|
|
36
|
+
|
|
37
|
+
# 类型扩展
|
|
38
|
+
typing-extensions>=4.12.0
|