@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
|
@@ -19,11 +19,126 @@ import uuid
|
|
|
19
19
|
import websockets
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
# System broadcast events (received by all modules, may not need handling)
|
|
23
|
+
SYSTEM_BROADCAST_EVENTS = {
|
|
24
|
+
"module.ready", "module.registered", "module.started", "module.stopped",
|
|
25
|
+
"module.crashed", "module.exiting", "module.offline",
|
|
26
|
+
"module.shutdown.ack", "module.shutdown.ready",
|
|
27
|
+
"system.ready", "registry.updated",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
22
31
|
# ── Safe stdout/stderr: ignore BrokenPipeError after Launcher closes stdio ──
|
|
23
32
|
|
|
24
33
|
|
|
25
34
|
# ── Module configuration ──
|
|
26
|
-
|
|
35
|
+
|
|
36
|
+
def _load_module_config() -> dict:
|
|
37
|
+
"""Load module configuration from module.md frontmatter.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dict with keys: name, preferred_port, advertise_ip
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
SystemExit: If module.md is invalid or name is non-compliant
|
|
44
|
+
"""
|
|
45
|
+
_this_dir = os.path.dirname(os.path.abspath(__file__))
|
|
46
|
+
module_md = os.path.join(_this_dir, "module.md")
|
|
47
|
+
|
|
48
|
+
# Calculate relative path for error messages
|
|
49
|
+
project_root = os.environ.get("KITE_PROJECT", "")
|
|
50
|
+
if project_root and _this_dir.startswith(project_root):
|
|
51
|
+
rel_path = os.path.relpath(_this_dir, project_root)
|
|
52
|
+
else:
|
|
53
|
+
rel_path = _this_dir
|
|
54
|
+
|
|
55
|
+
# Default values (will be overridden if valid config exists)
|
|
56
|
+
result = {
|
|
57
|
+
"name": "",
|
|
58
|
+
"preferred_port": 0,
|
|
59
|
+
"advertise_ip": "0.0.0.0"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Check if module.md exists
|
|
63
|
+
if not os.path.exists(module_md):
|
|
64
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
65
|
+
print(f" Path: {rel_path}/module.md")
|
|
66
|
+
print(f" Reason: File not found")
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
with open(module_md, encoding="utf-8") as f:
|
|
71
|
+
text = f.read()
|
|
72
|
+
|
|
73
|
+
# Extract YAML frontmatter (between --- markers)
|
|
74
|
+
import re
|
|
75
|
+
m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
|
|
76
|
+
if not m:
|
|
77
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
78
|
+
print(f" Path: {rel_path}/module.md")
|
|
79
|
+
print(f" Reason: Missing YAML frontmatter")
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
|
|
82
|
+
# Parse YAML frontmatter
|
|
83
|
+
try:
|
|
84
|
+
import yaml
|
|
85
|
+
fm = yaml.safe_load(m.group(1)) or {}
|
|
86
|
+
except ImportError:
|
|
87
|
+
print(f"[{rel_path}] ERROR: PyYAML not installed, cannot parse module.md")
|
|
88
|
+
sys.exit(1)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
91
|
+
print(f" Path: {rel_path}/module.md")
|
|
92
|
+
print(f" Reason: YAML parse error: {e}")
|
|
93
|
+
sys.exit(1)
|
|
94
|
+
|
|
95
|
+
# Validate 'name' field (required)
|
|
96
|
+
if "name" not in fm:
|
|
97
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
98
|
+
print(f" Path: {rel_path}/module.md")
|
|
99
|
+
print(f" Reason: Missing 'name' field")
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
raw_name = str(fm["name"]).strip()
|
|
103
|
+
|
|
104
|
+
if not raw_name:
|
|
105
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
106
|
+
print(f" Path: {rel_path}/module.md")
|
|
107
|
+
print(f" Reason: Empty module name")
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
|
|
110
|
+
# Validate name characters
|
|
111
|
+
sanitized = re.sub(r'[^a-zA-Z0-9_\-]', '', raw_name)
|
|
112
|
+
|
|
113
|
+
if sanitized != raw_name:
|
|
114
|
+
invalid_chars = ''.join(sorted(set(c for c in raw_name if c not in sanitized)))
|
|
115
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
116
|
+
print(f" Path: {rel_path}/module.md")
|
|
117
|
+
print(f" Reason: Invalid characters in name '{raw_name}': {repr(invalid_chars)}")
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
result["name"] = sanitized
|
|
121
|
+
|
|
122
|
+
# Extract optional fields
|
|
123
|
+
if "preferred_port" in fm:
|
|
124
|
+
try:
|
|
125
|
+
result["preferred_port"] = int(fm["preferred_port"])
|
|
126
|
+
except (ValueError, TypeError):
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
if "advertise_ip" in fm:
|
|
130
|
+
result["advertise_ip"] = str(fm["advertise_ip"])
|
|
131
|
+
|
|
132
|
+
except SystemExit:
|
|
133
|
+
raise # Re-raise exit to prevent catching by outer except
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print(f"[{rel_path}] ERROR: Failed to read module.md: {e}")
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
_module_config = _load_module_config()
|
|
141
|
+
MODULE_NAME = _module_config["name"]
|
|
27
142
|
|
|
28
143
|
|
|
29
144
|
class _SafeWriter:
|
|
@@ -264,6 +379,7 @@ def _read_stdin_kite_message(expected_type: str, timeout: float = 10) -> dict |
|
|
|
264
379
|
# Global WS reference for publish_event callback
|
|
265
380
|
_ws_global = None
|
|
266
381
|
_shutting_down = False
|
|
382
|
+
_exit_code = 0 # Exit code for main() to use
|
|
267
383
|
|
|
268
384
|
|
|
269
385
|
def _is_auth_failure(e: Exception) -> bool:
|
|
@@ -333,7 +449,7 @@ async def main():
|
|
|
333
449
|
|
|
334
450
|
async def _ws_loop(token: str, kernel_port: int, _t0: float):
|
|
335
451
|
"""Connect to Kernel with exponential backoff reconnection."""
|
|
336
|
-
global _shutting_down
|
|
452
|
+
global _shutting_down, _exit_code
|
|
337
453
|
retry_delay = 0.3
|
|
338
454
|
max_delay = 5.0
|
|
339
455
|
max_retries = 10
|
|
@@ -349,10 +465,14 @@ async def _ws_loop(token: str, kernel_port: int, _t0: float):
|
|
|
349
465
|
attempt += 1
|
|
350
466
|
if _is_auth_failure(e):
|
|
351
467
|
print(f"[backup] Kernel 认证失败,退出")
|
|
352
|
-
|
|
468
|
+
_exit_code = 1
|
|
469
|
+
_shutting_down = True
|
|
470
|
+
return
|
|
353
471
|
if attempt >= max_retries:
|
|
354
472
|
print(f"[backup] 重连失败 {max_retries} 次,退出")
|
|
355
|
-
|
|
473
|
+
_exit_code = 1
|
|
474
|
+
_shutting_down = True
|
|
475
|
+
return
|
|
356
476
|
_write_crash(type(e), e, e.__traceback__, severity="error", handled=True)
|
|
357
477
|
print(f"[backup] 连接错误: {e}, {retry_delay:.1f}s 后重试 ({attempt}/{max_retries})")
|
|
358
478
|
_ws_global_clear()
|
|
@@ -374,7 +494,7 @@ async def _ws_connect(token: str, kernel_port: int, _t0: float):
|
|
|
374
494
|
ws_url = f"ws://127.0.0.1:{kernel_port}/ws?token={token}&id=backup"
|
|
375
495
|
print(f"[backup] Connecting to Kernel: {ws_url}")
|
|
376
496
|
|
|
377
|
-
async with websockets.connect(ws_url, open_timeout=5, ping_interval=None,
|
|
497
|
+
async with websockets.connect(ws_url, open_timeout=5, ping_interval=None, close_timeout=10) as ws:
|
|
378
498
|
_ws_global = ws
|
|
379
499
|
print(f"[backup] Connected to Kernel ({_fmt_elapsed(_t0)})")
|
|
380
500
|
|
|
@@ -392,8 +512,24 @@ async def _ws_connect(token: str, kernel_port: int, _t0: float):
|
|
|
392
512
|
await _rpc_call(ws, "registry.register", {
|
|
393
513
|
"module_id": "backup",
|
|
394
514
|
"module_type": "service",
|
|
515
|
+
"launcher_id": "launcher", # 声明归属的 Launcher
|
|
516
|
+
"tools": {
|
|
517
|
+
"rpc": {
|
|
518
|
+
"module": {
|
|
519
|
+
"health": {"method": "health", "description": "健康检查"},
|
|
520
|
+
"status": {"method": "status", "description": "状态查询"},
|
|
521
|
+
"config": {
|
|
522
|
+
"get": {"method": "get_settings", "description": "获取配置"},
|
|
523
|
+
"update": {"method": "update_settings", "description": "更新配置"},
|
|
524
|
+
"reset": {"method": "reset_settings", "description": "恢复默认配置"},
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
},
|
|
395
529
|
"events_publish": {
|
|
396
|
-
"backup
|
|
530
|
+
"backup": {
|
|
531
|
+
"test": {"description": "Test event from backup module"},
|
|
532
|
+
}
|
|
397
533
|
},
|
|
398
534
|
"events_subscribe": [
|
|
399
535
|
"module.started",
|
|
@@ -405,12 +541,14 @@ async def _ws_connect(token: str, kernel_port: int, _t0: float):
|
|
|
405
541
|
|
|
406
542
|
# Publish module.ready (every reconnect)
|
|
407
543
|
if not _shutting_down:
|
|
544
|
+
startup_time = time.monotonic() - _t0
|
|
408
545
|
await _rpc_call(ws, "event.publish", {
|
|
409
546
|
"event_id": str(uuid.uuid4()),
|
|
410
547
|
"event": "module.ready",
|
|
411
548
|
"data": {
|
|
412
549
|
"module_id": "backup",
|
|
413
550
|
"graceful_shutdown": True,
|
|
551
|
+
"startup_time": startup_time,
|
|
414
552
|
},
|
|
415
553
|
})
|
|
416
554
|
print(f"[backup] module.ready published ({_fmt_elapsed(_t0)})")
|
|
@@ -419,6 +557,11 @@ async def _ws_connect(token: str, kernel_port: int, _t0: float):
|
|
|
419
557
|
test_task = asyncio.create_task(_test_event_loop(ws))
|
|
420
558
|
|
|
421
559
|
# Message loop: handle incoming RPC + events
|
|
560
|
+
# CRITICAL: RPC 死锁防范
|
|
561
|
+
# - 入站 RPC 请求必须用 create_task() 异步执行,不可 await
|
|
562
|
+
# - 原因:如果 handler 内部调用 rpc_call() 发出站请求,出站响应需要本接收循环来分发
|
|
563
|
+
# - 如果接收循环被 await handler 阻塞,出站响应永远收不到 → 超时死锁
|
|
564
|
+
# - 事件通知和 RPC 响应可以同步处理(它们不会反向调用 rpc_call)
|
|
422
565
|
async for raw in ws:
|
|
423
566
|
try:
|
|
424
567
|
msg = json.loads(raw)
|
|
@@ -433,8 +576,8 @@ async def _ws_connect(token: str, kernel_port: int, _t0: float):
|
|
|
433
576
|
# Event Notification
|
|
434
577
|
await _handle_event_notification(msg)
|
|
435
578
|
elif has_method and has_id:
|
|
436
|
-
# Incoming RPC request
|
|
437
|
-
|
|
579
|
+
# Incoming RPC request — run in background to prevent deadlock
|
|
580
|
+
asyncio.create_task(_handle_rpc_request(ws, msg))
|
|
438
581
|
# Ignore RPC responses (we don't await them in this simple impl)
|
|
439
582
|
except Exception as e:
|
|
440
583
|
print(f"[backup] 消息处理异常(已忽略): {e}")
|
|
@@ -457,12 +600,32 @@ async def _publish_event(ws, event: dict):
|
|
|
457
600
|
})
|
|
458
601
|
|
|
459
602
|
|
|
603
|
+
async def _handle_ping_event(data: dict):
|
|
604
|
+
"""Handle system.ping event and reply with system.pong."""
|
|
605
|
+
t1 = data.get("ping_time")
|
|
606
|
+
t2 = time.time()
|
|
607
|
+
|
|
608
|
+
await _publish_event(_ws_global, {
|
|
609
|
+
"event": "system.pong",
|
|
610
|
+
"data": {
|
|
611
|
+
"module_id": MODULE_NAME,
|
|
612
|
+
"ping_time": t1,
|
|
613
|
+
"pong_time": t2,
|
|
614
|
+
},
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
|
|
460
618
|
async def _handle_event_notification(msg: dict):
|
|
461
619
|
"""Handle an event notification (JSON-RPC 2.0 Notification with method='event')."""
|
|
462
620
|
params = msg.get("params", {})
|
|
463
621
|
event_type = params.get("event", "")
|
|
464
622
|
data = params.get("data", {})
|
|
465
623
|
|
|
624
|
+
# Handle system.ping event
|
|
625
|
+
if event_type == "system.ping":
|
|
626
|
+
await _handle_ping_event(data)
|
|
627
|
+
return
|
|
628
|
+
|
|
466
629
|
# Special handling for module.shutdown
|
|
467
630
|
if event_type == "module.shutdown":
|
|
468
631
|
target = data.get("module_id", "")
|
|
@@ -472,8 +635,13 @@ async def _handle_event_notification(msg: dict):
|
|
|
472
635
|
await _handle_shutdown()
|
|
473
636
|
return
|
|
474
637
|
|
|
475
|
-
#
|
|
476
|
-
|
|
638
|
+
# Layer 2: 忽略系统广播事件
|
|
639
|
+
if event_type in SYSTEM_BROADCAST_EVENTS:
|
|
640
|
+
return
|
|
641
|
+
|
|
642
|
+
# Layer 3: 警告未知事件(仅开发环境)
|
|
643
|
+
if os.environ.get("KITE_ENV") == "development":
|
|
644
|
+
print(f"[backup] Debug: Unhandled event: {event_type}")
|
|
477
645
|
|
|
478
646
|
|
|
479
647
|
async def _handle_rpc_request(ws, msg: dict):
|
|
@@ -485,6 +653,9 @@ async def _handle_rpc_request(ws, msg: dict):
|
|
|
485
653
|
handlers = {
|
|
486
654
|
"health": lambda p: _rpc_health(),
|
|
487
655
|
"status": lambda p: _rpc_status(),
|
|
656
|
+
"get_settings": lambda p: _rpc_get_settings(p),
|
|
657
|
+
"update_settings": lambda p: _rpc_update_settings(p),
|
|
658
|
+
"reset_settings": lambda p: _rpc_reset_settings(p),
|
|
488
659
|
}
|
|
489
660
|
handler = handlers.get(method)
|
|
490
661
|
if handler:
|
|
@@ -522,30 +693,144 @@ async def _rpc_status() -> dict:
|
|
|
522
693
|
}
|
|
523
694
|
|
|
524
695
|
|
|
696
|
+
# ── Configuration management helpers ──
|
|
697
|
+
|
|
698
|
+
def _read_module_md() -> tuple[dict, str]:
|
|
699
|
+
"""读取 module.md,返回 (frontmatter, body)"""
|
|
700
|
+
from pathlib import Path
|
|
701
|
+
md_path = Path(__file__).parent / "module.md"
|
|
702
|
+
text = md_path.read_text(encoding="utf-8")
|
|
703
|
+
|
|
704
|
+
# 提取 YAML frontmatter (--- ... ---)
|
|
705
|
+
m = re.match(r'^---\s*\n(.*?)\n---\s*\n?(.*)', text, re.DOTALL)
|
|
706
|
+
if not m:
|
|
707
|
+
return {}, text
|
|
708
|
+
|
|
709
|
+
import yaml
|
|
710
|
+
frontmatter = yaml.safe_load(m.group(1)) or {}
|
|
711
|
+
body = m.group(2)
|
|
712
|
+
return frontmatter, body
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def _write_module_md(frontmatter: dict, body: str):
|
|
716
|
+
"""写入 module.md"""
|
|
717
|
+
from pathlib import Path
|
|
718
|
+
import yaml
|
|
719
|
+
md_path = Path(__file__).parent / "module.md"
|
|
720
|
+
fm_str = yaml.dump(frontmatter, allow_unicode=True, sort_keys=False, default_flow_style=False).rstrip()
|
|
721
|
+
content = f"---\n{fm_str}\n---\n{body}"
|
|
722
|
+
md_path.write_text(content, encoding="utf-8")
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
# ── RPC handlers for configuration management ──
|
|
726
|
+
|
|
727
|
+
async def _rpc_get_settings(params: dict) -> dict:
|
|
728
|
+
"""RPC handler for backup.get_settings — 获取模块的所有设置"""
|
|
729
|
+
frontmatter, _ = _read_module_md()
|
|
730
|
+
return {
|
|
731
|
+
"name": frontmatter.get("name", "backup"),
|
|
732
|
+
"display_name": frontmatter.get("display_name", ""),
|
|
733
|
+
"type": frontmatter.get("type", ""),
|
|
734
|
+
"state": frontmatter.get("state", "enabled"),
|
|
735
|
+
"version": frontmatter.get("version", ""),
|
|
736
|
+
"runtime": frontmatter.get("runtime", ""),
|
|
737
|
+
"entry": frontmatter.get("entry", ""),
|
|
738
|
+
"preferred_port": frontmatter.get("preferred_port"),
|
|
739
|
+
"advertise_ip": frontmatter.get("advertise_ip"),
|
|
740
|
+
"monitor": frontmatter.get("monitor"),
|
|
741
|
+
"events": frontmatter.get("events"),
|
|
742
|
+
"subscriptions": frontmatter.get("subscriptions"),
|
|
743
|
+
"depends_on": frontmatter.get("depends_on"),
|
|
744
|
+
"has_config": False, # backup 模块暂无 config.yaml
|
|
745
|
+
"config": None,
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
async def _rpc_update_settings(params: dict) -> dict:
|
|
750
|
+
"""RPC handler for backup.update_settings — 更新模块设置"""
|
|
751
|
+
metadata = params.get("metadata", {})
|
|
752
|
+
config = params.get("config", {})
|
|
753
|
+
|
|
754
|
+
# 更新 module.md frontmatter
|
|
755
|
+
if metadata:
|
|
756
|
+
frontmatter, body = _read_module_md()
|
|
757
|
+
for key, value in metadata.items():
|
|
758
|
+
frontmatter[key] = value
|
|
759
|
+
_write_module_md(frontmatter, body)
|
|
760
|
+
|
|
761
|
+
# 更新 config.yaml(如果需要)
|
|
762
|
+
if config:
|
|
763
|
+
# backup 模块暂无 config.yaml,此处预留接口
|
|
764
|
+
pass
|
|
765
|
+
|
|
766
|
+
# 返回更新后的完整设置
|
|
767
|
+
return await _rpc_get_settings({})
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
async def _rpc_reset_settings(params: dict) -> dict:
|
|
771
|
+
"""RPC handler for backup.reset_settings — 恢复默认值"""
|
|
772
|
+
fields = params.get("fields", [])
|
|
773
|
+
reset_all = params.get("all", False)
|
|
774
|
+
|
|
775
|
+
# 默认值定义
|
|
776
|
+
defaults = {
|
|
777
|
+
"state": "enabled",
|
|
778
|
+
"preferred_port": 20000,
|
|
779
|
+
"advertise_ip": "127.0.0.1",
|
|
780
|
+
"monitor": True,
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
frontmatter, body = _read_module_md()
|
|
784
|
+
|
|
785
|
+
if reset_all:
|
|
786
|
+
# 恢复所有字段
|
|
787
|
+
for key, value in defaults.items():
|
|
788
|
+
frontmatter[key] = value
|
|
789
|
+
else:
|
|
790
|
+
# 恢复指定字段
|
|
791
|
+
for field in fields:
|
|
792
|
+
if field in defaults:
|
|
793
|
+
frontmatter[field] = defaults[field]
|
|
794
|
+
|
|
795
|
+
_write_module_md(frontmatter, body)
|
|
796
|
+
|
|
797
|
+
# 返回恢复后的设置
|
|
798
|
+
return await _rpc_get_settings({})
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
|
|
525
802
|
async def _handle_shutdown():
|
|
526
|
-
"""Handle module.shutdown event —
|
|
803
|
+
"""Handle module.shutdown event — ack → exiting → cleanup → ready → exit."""
|
|
527
804
|
global _shutting_down
|
|
528
805
|
print("[backup] Received shutdown request")
|
|
529
806
|
_shutting_down = True
|
|
530
|
-
# Step
|
|
807
|
+
# Step 1: Send ack (立即确认收到)
|
|
531
808
|
await _publish_event(_ws_global, {
|
|
532
|
-
"event": "module.
|
|
533
|
-
"data": {"module_id": "backup"
|
|
809
|
+
"event": "module.shutdown.ack",
|
|
810
|
+
"data": {"module_id": "backup"},
|
|
534
811
|
})
|
|
535
|
-
# Step
|
|
812
|
+
# Step 2: Send module.exiting (开始清理)
|
|
536
813
|
await _publish_event(_ws_global, {
|
|
537
|
-
"event": "module.
|
|
538
|
-
"data": {
|
|
814
|
+
"event": "module.exiting",
|
|
815
|
+
"data": {
|
|
816
|
+
"module_id": "backup",
|
|
817
|
+
"type": "passive",
|
|
818
|
+
"reason": "shutdown_requested",
|
|
819
|
+
"restart": "auto",
|
|
820
|
+
"action": "none",
|
|
821
|
+
"timeout": 2.0,
|
|
822
|
+
"restart_delay": 0.0,
|
|
823
|
+
},
|
|
539
824
|
})
|
|
540
|
-
# Step
|
|
541
|
-
# Step
|
|
825
|
+
# Step 3: Cleanup (nothing to clean up for backup)
|
|
826
|
+
# Step 4: Send ready (清理完成)
|
|
542
827
|
await _publish_event(_ws_global, {
|
|
543
828
|
"event": "module.shutdown.ready",
|
|
544
829
|
"data": {"module_id": "backup"},
|
|
545
830
|
})
|
|
546
831
|
print("[backup] Shutdown ready, exiting")
|
|
547
|
-
# Step
|
|
548
|
-
sys.exit(
|
|
832
|
+
# Step 5: Exit
|
|
833
|
+
sys.exit(_exit_code)
|
|
549
834
|
|
|
550
835
|
|
|
551
836
|
async def _test_event_loop(ws):
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: backup
|
|
3
|
-
display_name: Backup
|
|
4
|
-
version:
|
|
5
|
-
type: service
|
|
6
|
-
state: enabled
|
|
7
|
-
runtime: python
|
|
8
|
-
entry: entry.py
|
|
9
|
-
events:
|
|
10
|
-
|
|
11
|
-
subscriptions:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
---
|
|
2
|
+
name: backup
|
|
3
|
+
display_name: Backup
|
|
4
|
+
version: '1.0'
|
|
5
|
+
type: service
|
|
6
|
+
state: enabled
|
|
7
|
+
runtime: python
|
|
8
|
+
entry: entry.py
|
|
9
|
+
events:
|
|
10
|
+
- backup.test
|
|
11
|
+
subscriptions:
|
|
12
|
+
- module.started
|
|
13
|
+
- module.stopped
|
|
14
|
+
- module.shutdown
|
|
15
|
+
preferred_port: 20000
|
|
16
|
+
advertise_ip: 127.0.0.1
|
|
17
|
+
monitor: true
|
|
18
|
+
---
|
|
19
|
+
# Backup(备份模块)
|
|
20
|
+
|
|
21
|
+
自动备份工程代码和数据的扩展模块。
|
|
22
|
+
|
|
23
|
+
- 定时备份 — 按配置周期自动备份指定目录
|
|
24
|
+
- 事件通知 — 通过 Event Hub 发布备份状态事件
|