@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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Web 服务配置
|
|
3
|
+
server: {
|
|
4
|
+
host: "0.0.0.0",
|
|
5
|
+
port: 18766,
|
|
6
|
+
ssl: false,
|
|
7
|
+
// SSL 证书路径(如果启用 SSL)
|
|
8
|
+
ssl_cert: null,
|
|
9
|
+
ssl_key: null
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
// 静态文件配置
|
|
13
|
+
static: {
|
|
14
|
+
directory: "static",
|
|
15
|
+
cache_max_age: 3600 // 秒
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// CORS 配置
|
|
19
|
+
cors: {
|
|
20
|
+
enabled: false,
|
|
21
|
+
origins: ["*"]
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// 日志配置
|
|
25
|
+
logging: {
|
|
26
|
+
level: "INFO",
|
|
27
|
+
format: "%(asctime)s [%(name)s] %(levelname)s: %(message)s"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/kernel/entry.py
CHANGED
|
@@ -26,7 +26,113 @@ import uvicorn
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
# ── Module configuration ──
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
def _load_module_config() -> dict:
|
|
31
|
+
"""Load module configuration from module.md frontmatter.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Dict with keys: name, preferred_port, advertise_ip
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
SystemExit: If module.md is invalid or name is non-compliant
|
|
38
|
+
"""
|
|
39
|
+
_this_dir = os.path.dirname(os.path.abspath(__file__))
|
|
40
|
+
module_md = os.path.join(_this_dir, "module.md")
|
|
41
|
+
|
|
42
|
+
# Calculate relative path for error messages
|
|
43
|
+
project_root = os.environ.get("KITE_PROJECT", "")
|
|
44
|
+
if project_root and _this_dir.startswith(project_root):
|
|
45
|
+
rel_path = os.path.relpath(_this_dir, project_root)
|
|
46
|
+
else:
|
|
47
|
+
rel_path = _this_dir
|
|
48
|
+
|
|
49
|
+
# Default values (will be overridden if valid config exists)
|
|
50
|
+
result = {
|
|
51
|
+
"name": "",
|
|
52
|
+
"preferred_port": 0,
|
|
53
|
+
"advertise_ip": "127.0.0.1"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Check if module.md exists
|
|
57
|
+
if not os.path.exists(module_md):
|
|
58
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
59
|
+
print(f" Path: {rel_path}/module.md")
|
|
60
|
+
print(f" Reason: File not found")
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
with open(module_md, encoding="utf-8") as f:
|
|
65
|
+
text = f.read()
|
|
66
|
+
|
|
67
|
+
# Extract YAML frontmatter (between --- markers)
|
|
68
|
+
import re
|
|
69
|
+
m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
|
|
70
|
+
if not m:
|
|
71
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
72
|
+
print(f" Path: {rel_path}/module.md")
|
|
73
|
+
print(f" Reason: Missing YAML frontmatter")
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
|
|
76
|
+
# Parse YAML frontmatter
|
|
77
|
+
try:
|
|
78
|
+
import yaml
|
|
79
|
+
fm = yaml.safe_load(m.group(1)) or {}
|
|
80
|
+
except ImportError:
|
|
81
|
+
print(f"[{rel_path}] ERROR: PyYAML not installed, cannot parse module.md")
|
|
82
|
+
sys.exit(1)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
85
|
+
print(f" Path: {rel_path}/module.md")
|
|
86
|
+
print(f" Reason: YAML parse error: {e}")
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
# Validate 'name' field (required)
|
|
90
|
+
if "name" not in fm:
|
|
91
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
92
|
+
print(f" Path: {rel_path}/module.md")
|
|
93
|
+
print(f" Reason: Missing 'name' field")
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
raw_name = str(fm["name"]).strip()
|
|
97
|
+
|
|
98
|
+
if not raw_name:
|
|
99
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
100
|
+
print(f" Path: {rel_path}/module.md")
|
|
101
|
+
print(f" Reason: Empty module name")
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
# Validate name characters
|
|
105
|
+
sanitized = re.sub(r'[^a-zA-Z0-9_\-]', '', raw_name)
|
|
106
|
+
|
|
107
|
+
if sanitized != raw_name:
|
|
108
|
+
invalid_chars = ''.join(sorted(set(c for c in raw_name if c not in sanitized)))
|
|
109
|
+
print(f"[{rel_path}] ERROR: Invalid module configuration in module.md")
|
|
110
|
+
print(f" Path: {rel_path}/module.md")
|
|
111
|
+
print(f" Reason: Invalid characters in name '{raw_name}': {repr(invalid_chars)}")
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
|
|
114
|
+
result["name"] = sanitized
|
|
115
|
+
|
|
116
|
+
# Extract optional fields
|
|
117
|
+
if "preferred_port" in fm:
|
|
118
|
+
try:
|
|
119
|
+
result["preferred_port"] = int(fm["preferred_port"])
|
|
120
|
+
except (ValueError, TypeError):
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
if "advertise_ip" in fm:
|
|
124
|
+
result["advertise_ip"] = str(fm["advertise_ip"])
|
|
125
|
+
|
|
126
|
+
except SystemExit:
|
|
127
|
+
raise # Re-raise exit to prevent catching by outer except
|
|
128
|
+
except Exception as e:
|
|
129
|
+
print(f"[{rel_path}] ERROR: Failed to read module.md: {e}")
|
|
130
|
+
sys.exit(1)
|
|
131
|
+
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
_module_config = _load_module_config()
|
|
135
|
+
MODULE_NAME = _module_config["name"]
|
|
30
136
|
|
|
31
137
|
|
|
32
138
|
def _fmt_elapsed(t0: float) -> str:
|
|
@@ -256,27 +362,6 @@ if _project_root not in sys.path:
|
|
|
256
362
|
from kernel.server import KernelServer
|
|
257
363
|
|
|
258
364
|
|
|
259
|
-
def _read_module_md() -> dict:
|
|
260
|
-
"""Read preferred_port and advertise_ip from own module.md."""
|
|
261
|
-
md_path = os.path.join(_this_dir, "module.md")
|
|
262
|
-
result = {"preferred_port": 0, "advertise_ip": "127.0.0.1"}
|
|
263
|
-
try:
|
|
264
|
-
with open(md_path, "r", encoding="utf-8") as f:
|
|
265
|
-
text = f.read()
|
|
266
|
-
m = re.match(r'^---\s*\n(.*?)\n---', text, re.DOTALL)
|
|
267
|
-
if m:
|
|
268
|
-
try:
|
|
269
|
-
import yaml
|
|
270
|
-
fm = yaml.safe_load(m.group(1)) or {}
|
|
271
|
-
except ImportError:
|
|
272
|
-
fm = {}
|
|
273
|
-
result["preferred_port"] = int(fm.get("preferred_port", 0))
|
|
274
|
-
result["advertise_ip"] = fm.get("advertise_ip", "127.0.0.1")
|
|
275
|
-
except Exception:
|
|
276
|
-
pass
|
|
277
|
-
return result
|
|
278
|
-
|
|
279
|
-
|
|
280
365
|
def _bind_port(preferred: int, host: str) -> int:
|
|
281
366
|
"""Try preferred port first, fall back to OS-assigned."""
|
|
282
367
|
if preferred:
|
|
@@ -284,11 +369,15 @@ def _bind_port(preferred: int, host: str) -> int:
|
|
|
284
369
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
285
370
|
s.bind((host, preferred))
|
|
286
371
|
return preferred
|
|
287
|
-
except OSError:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
372
|
+
except OSError as e:
|
|
373
|
+
print(f"\033[31m[kernel] ✖ 指定端口 {preferred} 绑定失败: {e},将使用系统随机端口\033[0m")
|
|
374
|
+
try:
|
|
375
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
376
|
+
s.bind((host, 0))
|
|
377
|
+
return s.getsockname()[1]
|
|
378
|
+
except OSError as e:
|
|
379
|
+
print(f"\033[31m[kernel] ✖ 端口绑定完全失败 ({host}): {e}\033[0m")
|
|
380
|
+
raise
|
|
292
381
|
|
|
293
382
|
|
|
294
383
|
def _read_stdin_kite_messages(expected: set[str], timeout: float = 10) -> dict[str, dict]:
|
|
@@ -362,17 +451,16 @@ def main():
|
|
|
362
451
|
if is_debug:
|
|
363
452
|
print("[kernel] 调试模式已启用 (KITE_DEBUG=1),接受所有令牌")
|
|
364
453
|
|
|
365
|
-
# Step 1:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
preferred_port = md_config["preferred_port"]
|
|
454
|
+
# Step 1: Use cached module config (already loaded at module level)
|
|
455
|
+
advertise_ip = _module_config["advertise_ip"]
|
|
456
|
+
preferred_port = _module_config["preferred_port"]
|
|
369
457
|
|
|
370
458
|
# Step 2: Generate launcher token
|
|
371
459
|
import secrets
|
|
372
460
|
launcher_token = secrets.token_urlsafe(32)
|
|
373
461
|
|
|
374
462
|
# Step 3: Create KernelServer with launcher_token
|
|
375
|
-
server = KernelServer(launcher_token=launcher_token, advertise_ip=advertise_ip)
|
|
463
|
+
server = KernelServer(launcher_token=launcher_token, advertise_ip=advertise_ip, module_id=MODULE_NAME, boot_t0=_t0)
|
|
376
464
|
|
|
377
465
|
# Step 4: Bind port
|
|
378
466
|
bind_host = advertise_ip
|
package/kernel/event_hub.py
CHANGED
|
@@ -43,6 +43,9 @@ QUEUE_MAXSIZE = 10000
|
|
|
43
43
|
# System events that are auto-broadcast to ALL connected modules (no subscription needed)
|
|
44
44
|
SYSTEM_EVENTS = {"module.offline", "module.ready", "module.shutdown"}
|
|
45
45
|
|
|
46
|
+
# Callback execution pool size (max concurrent callback tasks)
|
|
47
|
+
CALLBACK_POOL_SIZE = 100
|
|
48
|
+
|
|
46
49
|
|
|
47
50
|
class EventHub:
|
|
48
51
|
|
|
@@ -66,6 +69,13 @@ class EventHub:
|
|
|
66
69
|
self._cnt_errors = 0
|
|
67
70
|
self._start_time = time.time()
|
|
68
71
|
|
|
72
|
+
# Internal event callbacks (module_id -> callback)
|
|
73
|
+
# 用于没有 WebSocket 连接的模块(如 Kernel)通过回调接收事件
|
|
74
|
+
self._internal_callbacks: dict[str, callable] = {}
|
|
75
|
+
|
|
76
|
+
# Queue overflow tracking (per module)
|
|
77
|
+
self._queue_overflow: dict[str, int] = {} # module_id -> overflow count
|
|
78
|
+
|
|
69
79
|
# ── Connection lifecycle ──
|
|
70
80
|
|
|
71
81
|
def add_connection(self, module_id: str, ws: WebSocket):
|
|
@@ -119,13 +129,80 @@ class EventHub:
|
|
|
119
129
|
await ws.send_text(raw)
|
|
120
130
|
self._cnt_routed += 1
|
|
121
131
|
except Exception:
|
|
122
|
-
|
|
132
|
+
# Connection closed (normal during shutdown) — exit silently
|
|
123
133
|
break
|
|
124
134
|
except asyncio.CancelledError:
|
|
125
135
|
pass
|
|
126
136
|
|
|
137
|
+
async def _callback_sender_loop(self, mid: str, callback: callable, queue: asyncio.Queue):
|
|
138
|
+
"""回调专用的发送循环(类似 _sender_loop,但调用回调而不是发送 WS)
|
|
139
|
+
|
|
140
|
+
从队列取出 JSON-RPC 2.0 Notification,解析后调用回调函数。
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
while True:
|
|
144
|
+
raw = await queue.get()
|
|
145
|
+
try:
|
|
146
|
+
msg = _loads(raw)
|
|
147
|
+
params = msg.get("params", {})
|
|
148
|
+
event_type = params.get("event", "")
|
|
149
|
+
data = params.get("data", {})
|
|
150
|
+
await callback(event_type, data)
|
|
151
|
+
self._cnt_routed += 1
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print(f"[kernel] Internal callback error for {mid} on {event_type}: {e}")
|
|
154
|
+
import traceback
|
|
155
|
+
traceback.print_exc()
|
|
156
|
+
except asyncio.CancelledError:
|
|
157
|
+
pass
|
|
158
|
+
|
|
127
159
|
# ── Subscribe / Unsubscribe ──
|
|
128
160
|
|
|
161
|
+
def register_internal_callback(self, module_id: str, callback: callable):
|
|
162
|
+
"""注册内部事件回调(用于 Kernel 自身接收事件)
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
module_id: 模块 ID(通常是 "kernel")
|
|
166
|
+
callback: 异步回调函数 async def callback(event_type: str, data: dict)
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
TypeError: 如果 callback 不是 callable
|
|
170
|
+
|
|
171
|
+
Note:
|
|
172
|
+
只注册回调和创建队列,不创建 task。
|
|
173
|
+
需要在事件循环启动后调用 start_internal_senders() 来创建 sender tasks。
|
|
174
|
+
"""
|
|
175
|
+
if not callable(callback):
|
|
176
|
+
raise TypeError(f"callback must be callable, got {type(callback)}")
|
|
177
|
+
|
|
178
|
+
# 存储回调函数引用
|
|
179
|
+
self._internal_callbacks[module_id] = callback
|
|
180
|
+
|
|
181
|
+
# 创建队列(如果还没有)
|
|
182
|
+
if module_id not in self._queues:
|
|
183
|
+
q = asyncio.Queue(maxsize=QUEUE_MAXSIZE)
|
|
184
|
+
self._queues[module_id] = q
|
|
185
|
+
|
|
186
|
+
print(f"[kernel] Registered internal callback for {module_id}")
|
|
187
|
+
|
|
188
|
+
def start_internal_senders(self):
|
|
189
|
+
"""启动所有已注册的内部回调的 sender tasks(必须在事件循环中调用)"""
|
|
190
|
+
for module_id, callback in self._internal_callbacks.items():
|
|
191
|
+
if module_id not in self._senders:
|
|
192
|
+
q = self._queues[module_id]
|
|
193
|
+
self._senders[module_id] = asyncio.create_task(
|
|
194
|
+
self._callback_sender_loop(module_id, callback, q)
|
|
195
|
+
)
|
|
196
|
+
print(f"[kernel] Started internal sender for {module_id}")
|
|
197
|
+
|
|
198
|
+
def unregister_internal_callback(self, module_id: str):
|
|
199
|
+
"""注销内部事件回调"""
|
|
200
|
+
self._internal_callbacks.pop(module_id, None)
|
|
201
|
+
self._queues.pop(module_id, None)
|
|
202
|
+
task = self._senders.pop(module_id, None)
|
|
203
|
+
if task:
|
|
204
|
+
task.cancel()
|
|
205
|
+
|
|
129
206
|
def handle_subscribe(self, module_id: str, events: list[str]):
|
|
130
207
|
if module_id not in self.subscriptions:
|
|
131
208
|
self.subscriptions[module_id] = set()
|
|
@@ -134,28 +211,32 @@ class EventHub:
|
|
|
134
211
|
)
|
|
135
212
|
print(f"[kernel] {module_id} subscribed: {events}")
|
|
136
213
|
|
|
137
|
-
def handle_unsubscribe(self, module_id: str, events: list[str]):
|
|
214
|
+
def handle_unsubscribe(self, module_id: str, events: list[str]) -> dict:
|
|
215
|
+
"""Unsubscribe from events. Returns dict with unsubscribed events and count."""
|
|
138
216
|
subs = self.subscriptions.get(module_id)
|
|
217
|
+
unsubscribed = []
|
|
139
218
|
if subs:
|
|
140
219
|
to_remove = {item for item in subs if item[0] in events}
|
|
220
|
+
unsubscribed = [item[0] for item in to_remove]
|
|
141
221
|
subs.difference_update(to_remove)
|
|
142
222
|
print(f"[kernel] {module_id} unsubscribed: {events}")
|
|
223
|
+
return {"unsubscribed": unsubscribed, "count": len(unsubscribed)}
|
|
143
224
|
|
|
144
225
|
# ── Event publishing (called by RpcRouter) ──
|
|
145
226
|
|
|
146
227
|
def publish_event(self, sender_id: str, event_id: str, event_type: str,
|
|
147
228
|
data: dict = None, echo: bool = False) -> dict:
|
|
148
229
|
"""Publish an event from a module. Called by RpcRouter for event.publish RPC.
|
|
149
|
-
Returns
|
|
230
|
+
Returns empty dict on success, raises exception on validation failure."""
|
|
150
231
|
self._cnt_received += 1
|
|
151
232
|
|
|
152
233
|
if not event_id or not event_type:
|
|
153
234
|
self._cnt_errors += 1
|
|
154
|
-
|
|
235
|
+
raise ValueError("Missing required field: event_id or event")
|
|
155
236
|
|
|
156
237
|
if self.dedup.is_duplicate(event_id):
|
|
157
238
|
self._cnt_dedup += 1
|
|
158
|
-
return {
|
|
239
|
+
return {} # silently accept duplicates
|
|
159
240
|
|
|
160
241
|
msg = {
|
|
161
242
|
"jsonrpc": "2.0",
|
|
@@ -170,11 +251,17 @@ class EventHub:
|
|
|
170
251
|
}
|
|
171
252
|
|
|
172
253
|
self._route_event(sender_id, msg, event_type, echo)
|
|
173
|
-
return {
|
|
254
|
+
return {}
|
|
174
255
|
|
|
175
|
-
def publish_internal(self, event_type: str, data: dict):
|
|
256
|
+
def publish_internal(self, event_type: str, data: dict, source: str):
|
|
176
257
|
"""Publish a Kernel-originated event (e.g. module.offline, module.registered).
|
|
177
|
-
No dedup check — internal events are unique by nature.
|
|
258
|
+
No dedup check — internal events are unique by nature.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
event_type: 事件类型(如 "module.ready")
|
|
262
|
+
data: 事件数据
|
|
263
|
+
source: 事件来源模块 ID(必填,通常是调用者的 module_id)
|
|
264
|
+
"""
|
|
178
265
|
import uuid
|
|
179
266
|
event_id = str(uuid.uuid4())
|
|
180
267
|
self._cnt_received += 1
|
|
@@ -185,25 +272,53 @@ class EventHub:
|
|
|
185
272
|
"params": {
|
|
186
273
|
"event_id": event_id,
|
|
187
274
|
"event": event_type,
|
|
188
|
-
"source":
|
|
275
|
+
"source": source,
|
|
189
276
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
190
277
|
"data": data,
|
|
191
278
|
},
|
|
192
279
|
}
|
|
193
280
|
|
|
194
|
-
self._route_event(
|
|
281
|
+
self._route_event(source, msg, event_type, echo=False)
|
|
195
282
|
|
|
196
283
|
# ── Routing ──
|
|
197
284
|
|
|
198
285
|
def _route_event(self, sender_id: str, msg: dict, event_type: str, echo: bool):
|
|
199
286
|
"""Enqueue event to all matching subscribers' delivery queues.
|
|
200
287
|
System events (module.offline, module.ready, module.shutdown) are auto-broadcast
|
|
201
|
-
to ALL connected modules, regardless of subscription.
|
|
288
|
+
to ALL connected modules, regardless of subscription.
|
|
289
|
+
|
|
290
|
+
Exception: module.shutdown with a target module_id is sent ONLY to that module.
|
|
291
|
+
"""
|
|
202
292
|
e_parts = tuple(event_type.split("."))
|
|
203
293
|
raw = None # lazy serialization
|
|
294
|
+
params = msg.get("params", {})
|
|
295
|
+
data = params.get("data", {})
|
|
204
296
|
|
|
205
|
-
# System events → broadcast to all connected modules
|
|
297
|
+
# System events → broadcast to all connected modules (unless targeted)
|
|
206
298
|
if event_type in SYSTEM_EVENTS:
|
|
299
|
+
# Check if this is a targeted shutdown (has module_id in data)
|
|
300
|
+
if event_type == "module.shutdown":
|
|
301
|
+
target_module = data.get("module_id", "")
|
|
302
|
+
|
|
303
|
+
# Targeted shutdown → send only to target module
|
|
304
|
+
if target_module:
|
|
305
|
+
queue = self._queues.get(target_module)
|
|
306
|
+
if queue:
|
|
307
|
+
if raw is None:
|
|
308
|
+
raw = _dumps(msg)
|
|
309
|
+
try:
|
|
310
|
+
queue.put_nowait(raw)
|
|
311
|
+
self._cnt_queued += 1
|
|
312
|
+
except asyncio.QueueFull:
|
|
313
|
+
# Queue 满了,记录溢出
|
|
314
|
+
self._queue_overflow[target_module] = self._queue_overflow.get(target_module, 0) + 1
|
|
315
|
+
overflow_count = self._queue_overflow[target_module]
|
|
316
|
+
# 每 100 次溢出打印一次警告
|
|
317
|
+
if overflow_count % 100 == 1:
|
|
318
|
+
print(f"[kernel] Warning: Queue full for {target_module}, dropped {overflow_count} events")
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
# Broadcast system events (no target or non-shutdown events)
|
|
207
322
|
for mid, queue in self._queues.items():
|
|
208
323
|
if mid == sender_id and not echo:
|
|
209
324
|
continue
|
|
@@ -211,9 +326,14 @@ class EventHub:
|
|
|
211
326
|
raw = _dumps(msg)
|
|
212
327
|
try:
|
|
213
328
|
queue.put_nowait(raw)
|
|
329
|
+
self._cnt_queued += 1
|
|
214
330
|
except asyncio.QueueFull:
|
|
215
|
-
|
|
216
|
-
|
|
331
|
+
# Queue 满了,记录溢出
|
|
332
|
+
self._queue_overflow[mid] = self._queue_overflow.get(mid, 0) + 1
|
|
333
|
+
overflow_count = self._queue_overflow[mid]
|
|
334
|
+
if overflow_count % 100 == 1:
|
|
335
|
+
print(f"[kernel] Warning: Queue full for {mid}, dropped {overflow_count} events")
|
|
336
|
+
|
|
217
337
|
return
|
|
218
338
|
|
|
219
339
|
# Normal events → subscription-based routing
|
|
@@ -228,9 +348,14 @@ class EventHub:
|
|
|
228
348
|
raw = _dumps(msg)
|
|
229
349
|
try:
|
|
230
350
|
queue.put_nowait(raw)
|
|
351
|
+
self._cnt_queued += 1
|
|
231
352
|
except asyncio.QueueFull:
|
|
232
|
-
|
|
233
|
-
|
|
353
|
+
# Queue 满了,记录溢出
|
|
354
|
+
self._queue_overflow[mid] = self._queue_overflow.get(mid, 0) + 1
|
|
355
|
+
overflow_count = self._queue_overflow[mid]
|
|
356
|
+
# 每 100 次溢出打印一次警告
|
|
357
|
+
if overflow_count % 100 == 1:
|
|
358
|
+
print(f"[kernel] Warning: Queue full for {mid}, dropped {overflow_count} events")
|
|
234
359
|
break
|
|
235
360
|
|
|
236
361
|
# ── Stats ──
|
package/kernel/module.md
CHANGED
|
@@ -1,33 +1,60 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: kernel
|
|
3
|
-
display_name:
|
|
4
|
-
type: infrastructure
|
|
5
|
-
state: enabled
|
|
6
|
-
runtime: python
|
|
7
|
-
entry: entry.py
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- `
|
|
33
|
-
- `
|
|
1
|
+
---
|
|
2
|
+
name: kernel
|
|
3
|
+
display_name: Kite内核
|
|
4
|
+
type: infrastructure
|
|
5
|
+
state: enabled
|
|
6
|
+
runtime: python
|
|
7
|
+
entry: entry.py
|
|
8
|
+
version: 1.0.1
|
|
9
|
+
advertise_ip: 127.0.0.1
|
|
10
|
+
monitor: true
|
|
11
|
+
preferred_port: 0
|
|
12
|
+
display_order: 100
|
|
13
|
+
---
|
|
14
|
+
# Kernel
|
|
15
|
+
|
|
16
|
+
Unified infrastructure module that merges Registry (service discovery) and Event Hub (event routing) into a single process with a WebSocket JSON-RPC 2.0 interface.
|
|
17
|
+
|
|
18
|
+
## Responsibilities
|
|
19
|
+
|
|
20
|
+
- **Service Registry**: Module registration, glob lookup, dot-path queries
|
|
21
|
+
- **Event Routing**: NATS-style wildcard subscriptions, per-subscriber queues, 1h dedup
|
|
22
|
+
- **RPC Routing**: JSON-RPC 2.0 dispatch for builtin methods + cross-module forwarding
|
|
23
|
+
- **Token Verification**: In-memory token→module_id resolution (no cross-process HTTP)
|
|
24
|
+
- **Ping/Pong Monitoring**: Active ping to all connected modules, track response latencies
|
|
25
|
+
|
|
26
|
+
## Protocol
|
|
27
|
+
|
|
28
|
+
Single WebSocket endpoint: `ws://127.0.0.1:{port}/ws?token={TOKEN}&id={MODULE_ID}`
|
|
29
|
+
|
|
30
|
+
Three frame types on the wire:
|
|
31
|
+
- **RPC Request**: `{jsonrpc:"2.0", id, method, params}` — client→Kernel or Kernel→client (forwarded)
|
|
32
|
+
- **RPC Response**: `{jsonrpc:"2.0", id, result}` or `{jsonrpc:"2.0", id, error}` — response to request
|
|
33
|
+
- **Event Notification**: `{jsonrpc:"2.0", method:"event", params:{event_id, event, source, timestamp, data}}` — no id, no response
|
|
34
|
+
|
|
35
|
+
## HTTP Endpoints (debug only)
|
|
36
|
+
|
|
37
|
+
- `GET /health` — combined registry + event hub health
|
|
38
|
+
- `GET /stats` — connections, subscriptions, counters
|
|
39
|
+
|
|
40
|
+
## RPC Methods
|
|
41
|
+
|
|
42
|
+
### Registry Methods
|
|
43
|
+
- `registry.register` — Register a module
|
|
44
|
+
- `registry.deregister` — Deregister a module
|
|
45
|
+
- `registry.lookup` — Lookup modules by field/value
|
|
46
|
+
- `registry.get` — Get module info by dot-path
|
|
47
|
+
- `registry.verify` — Verify a token
|
|
48
|
+
|
|
49
|
+
### Event Methods
|
|
50
|
+
- `event.publish` — Publish an event
|
|
51
|
+
- `event.subscribe` — Subscribe to events
|
|
52
|
+
- `event.unsubscribe` — Unsubscribe from events
|
|
53
|
+
|
|
54
|
+
### Kernel Methods
|
|
55
|
+
- `kernel.ping` — Ping (used for latency measurement)
|
|
56
|
+
- `kernel.stats` — Get kernel statistics
|
|
57
|
+
- `kernel.health` — Get kernel health status
|
|
58
|
+
- `kernel.latencies` — Get ping/pong latencies for all modules
|
|
59
|
+
- `kernel.generate_tokens` — Generate tokens for modules (Launcher only)
|
|
60
|
+
- `kernel.register_tokens` — Register token mapping (Launcher only)
|