@agentunion/kite 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +302 -0
- package/cli.js +119 -4
- package/core/dependency_checker.py +250 -0
- package/core/env_checker.py +490 -0
- package/dependencies_lock.json +128 -0
- package/extensions/agents/assistant/entry.py +111 -1
- package/extensions/agents/assistant/server.py +279 -215
- package/extensions/channels/acp_channel/entry.py +111 -1
- package/extensions/channels/acp_channel/module.md +23 -22
- package/extensions/channels/acp_channel/server.py +279 -215
- package/extensions/event_hub_bench/entry.py +107 -1
- package/extensions/services/backup/entry.py +306 -21
- package/extensions/services/backup/module.md +24 -22
- package/extensions/services/evol/auth_manager.py +443 -0
- package/extensions/services/evol/config.yaml +149 -0
- package/extensions/services/evol/config_loader.py +117 -0
- package/extensions/services/evol/entry.py +406 -0
- package/extensions/services/evol/evol_api.py +173 -0
- package/extensions/services/evol/evol_config.json5 +29 -0
- package/extensions/services/evol/migrate_tokens.py +122 -0
- package/extensions/services/evol/module.md +32 -0
- package/extensions/services/evol/pairing.py +250 -0
- package/extensions/services/evol/pairing_codes.jsonl +1 -0
- package/extensions/services/evol/relay.py +682 -0
- package/extensions/services/evol/relay_config.json5 +67 -0
- package/extensions/services/evol/routes/__init__.py +1 -0
- package/extensions/services/evol/routes/routes_management_ws.py +127 -0
- package/extensions/services/evol/routes/routes_rpc.py +89 -0
- package/extensions/services/evol/routes/routes_test.py +61 -0
- package/extensions/services/evol/server.py +875 -0
- package/extensions/services/evol/static/css/style.css +1200 -0
- package/extensions/services/evol/static/index.html +781 -0
- package/extensions/services/evol/static/index_evol.html +14 -0
- package/extensions/services/evol/static/js/app.js +6304 -0
- package/extensions/services/evol/static/js/auth.js +326 -0
- package/extensions/services/evol/static/js/dialog.js +285 -0
- package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
- package/extensions/services/evol/static/js/evol-app.js +1949 -0
- package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
- package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
- package/extensions/services/evol/static/js/kernel-client.js +396 -0
- package/extensions/services/evol/static/js/main.js +141 -0
- package/extensions/services/evol/static/js/registry-tests.js +585 -0
- package/extensions/services/evol/static/js/stats.js +217 -0
- package/extensions/services/evol/static/js/token-manager.js +175 -0
- package/extensions/services/evol/static/pairing.html +248 -0
- package/extensions/services/evol/static/test_registry.html +262 -0
- package/extensions/services/evol/static/test_relay.html +462 -0
- package/extensions/services/evol/stats_manager.py +240 -0
- package/extensions/services/model_service/entry.py +167 -19
- package/extensions/services/model_service/module.md +21 -22
- package/extensions/services/proxy/.claude/settings.local.json +13 -0
- package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
- package/extensions/services/proxy/_fix_prints.py +133 -0
- package/extensions/services/proxy/_fix_prints2.py +87 -0
- package/extensions/services/proxy/agentcp/LICENCE +178 -0
- package/extensions/services/proxy/agentcp/README copy.md +85 -0
- package/extensions/services/proxy/agentcp/README.md +260 -0
- package/extensions/services/proxy/agentcp/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/agent.py +4 -0
- package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
- package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
- package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
- package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
- package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
- package/extensions/services/proxy/agentcp/base/client.py +112 -0
- package/extensions/services/proxy/agentcp/base/env.py +34 -0
- package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
- package/extensions/services/proxy/agentcp/base/log.py +98 -0
- package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
- package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
- package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
- package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/context/context.py +73 -0
- package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
- package/extensions/services/proxy/agentcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
- package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
- package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
- package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
- package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
- package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/hcp.py +299 -0
- package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
- package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
- package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
- package/extensions/services/proxy/agentcp/llm_server.py +172 -0
- package/extensions/services/proxy/agentcp/mermaid.py +210 -0
- package/extensions/services/proxy/agentcp/message.py +149 -0
- package/extensions/services/proxy/agentcp/metrics.py +256 -0
- package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
- package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
- package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
- package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
- package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
- package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
- package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
- package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
- package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
- package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
- package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
- package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
- package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
- package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
- package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/requirements.txt +7 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
- package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
- package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
- package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
- package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
- package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
- package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
- package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
- package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
- package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
- package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
- package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
- package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
- package/extensions/services/proxy/agentcp/workflow.py +203 -0
- package/extensions/services/proxy/console_auth.py +109 -0
- package/extensions/services/proxy/evol/__init__.py +1 -0
- package/extensions/services/proxy/evol/config.py +37 -0
- package/extensions/services/proxy/evol/http/__init__.py +1 -0
- package/extensions/services/proxy/evol/http/async_http.py +551 -0
- package/extensions/services/proxy/evol/log.py +28 -0
- package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
- package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
- package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
- package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
- package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
- package/extensions/services/proxy/evol/server/__init__.py +1 -0
- package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
- package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
- package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
- package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
- package/extensions/services/proxy/evol/version.py +24 -0
- package/extensions/services/proxy/logs/websocket.log +260 -0
- package/extensions/services/proxy/main.py +240 -0
- package/extensions/services/proxy/requirements.txt +13 -0
- package/extensions/services/proxy/server.py +271 -0
- package/extensions/services/watchdog/entry.py +215 -26
- package/extensions/services/watchdog/module.md +1 -0
- package/extensions/services/watchdog/monitor.py +178 -38
- package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
- package/extensions/services/web/config_example.py +35 -0
- package/extensions/services/web/config_loader.py +110 -0
- package/extensions/services/web/entry.py +114 -26
- package/extensions/services/web/module.md +35 -24
- package/extensions/services/web/pairing.py +250 -0
- package/extensions/services/web/pairing_codes.jsonl +16 -0
- package/extensions/services/web/relay.py +643 -0
- package/extensions/services/web/relay_config.json5 +67 -0
- package/extensions/services/web/routes/routes_management_ws.py +127 -0
- package/extensions/services/web/routes/routes_rpc.py +89 -0
- package/extensions/services/web/routes/routes_test.py +61 -0
- package/extensions/services/web/routes/schemas.py +0 -22
- package/extensions/services/web/server.py +434 -99
- package/extensions/services/web/static/css/style.css +67 -28
- package/extensions/services/web/static/index.html +234 -44
- package/extensions/services/web/static/js/app.js +1335 -48
- package/extensions/services/web/static/js/kernel-client-example.js +161 -0
- package/extensions/services/web/static/js/kernel-client.js +383 -0
- package/extensions/services/web/static/js/registry-tests.js +558 -0
- package/extensions/services/web/static/js/token-manager.js +175 -0
- package/extensions/services/web/static/pairing.html +248 -0
- package/extensions/services/web/static/test_registry.html +262 -0
- package/extensions/services/web/web_config.json5 +29 -0
- package/kernel/entry.py +120 -32
- package/kernel/event_hub.py +141 -16
- package/kernel/module.md +60 -33
- package/kernel/registry_store.py +45 -36
- package/kernel/rpc_router.py +152 -59
- package/kernel/server.py +322 -26
- package/kite_cli/__init__.py +3 -0
- package/kite_cli/__main__.py +5 -0
- package/kite_cli/commands/__init__.py +1 -0
- package/kite_cli/commands/clean.py +101 -0
- package/kite_cli/commands/deps_install.py +67 -0
- package/kite_cli/commands/doctor.py +35 -0
- package/kite_cli/commands/env_check.py +45 -0
- package/kite_cli/commands/history.py +111 -0
- package/kite_cli/commands/info.py +96 -0
- package/kite_cli/commands/install.py +313 -0
- package/kite_cli/commands/list.py +143 -0
- package/kite_cli/commands/log.py +81 -0
- package/kite_cli/commands/prepare.py +49 -0
- package/kite_cli/commands/rollback.py +88 -0
- package/kite_cli/commands/search.py +73 -0
- package/kite_cli/commands/uninstall.py +85 -0
- package/kite_cli/commands/update.py +118 -0
- package/kite_cli/commands/venv_setup.py +56 -0
- package/kite_cli/core/__init__.py +1 -0
- package/kite_cli/core/checker.py +142 -0
- package/kite_cli/core/dependency.py +229 -0
- package/kite_cli/core/downloader.py +209 -0
- package/kite_cli/core/install_info.py +40 -0
- package/kite_cli/core/tool_installer.py +397 -0
- package/kite_cli/core/validator.py +78 -0
- package/kite_cli/main.py +317 -0
- package/kite_cli/utils/__init__.py +1 -0
- package/kite_cli/utils/i18n.py +252 -0
- package/kite_cli/utils/interactive.py +63 -0
- package/kite_cli/utils/operation_log.py +77 -0
- package/kite_cli/utils/paths.py +34 -0
- package/kite_cli/utils/version.py +308 -0
- package/launcher/entry.py +1124 -178
- package/launcher/logging_setup.py +104 -0
- package/launcher/module.md +46 -37
- package/launcher/module_scanner.py +11 -1
- package/main.py +4 -1
- package/package.json +9 -1
- package/python_version.json +4 -0
- package/requirements.txt +38 -0
- package/scripts/env-manager.js +328 -0
- package/scripts/plan_manager.py +315 -0
- package/scripts/python-env.js +79 -0
- package/scripts/scan_dependencies.py +461 -0
- package/scripts/setup-python-env.js +191 -0
- package/extensions/services/web/routes/routes_modules.py +0 -249
package/kernel/registry_store.py
CHANGED
|
@@ -20,10 +20,8 @@ class RegistryStore:
|
|
|
20
20
|
self.launcher_token = launcher_token
|
|
21
21
|
self.token_map: dict[str, str] = {} # module_id -> token
|
|
22
22
|
self.modules: dict[str, dict] = {} # module_id -> registration payload
|
|
23
|
-
self.heartbeats: dict[str, float] = {} # module_id -> last heartbeat timestamp
|
|
24
|
-
self.ttl = 60 # seconds before marking offline
|
|
25
|
-
self.heartbeat_interval = 30
|
|
26
23
|
self.is_debug = os.environ.get("KITE_DEBUG") == "1"
|
|
24
|
+
self.last_update_time = time.time() # Global registry update timestamp
|
|
27
25
|
|
|
28
26
|
# ── Token verification ──
|
|
29
27
|
|
|
@@ -57,45 +55,72 @@ class RegistryStore:
|
|
|
57
55
|
_REQUIRED_FIELDS = ("module_id", "module_type") # api_endpoint now optional
|
|
58
56
|
|
|
59
57
|
def register_module(self, data: dict) -> dict:
|
|
60
|
-
"""Register or update a module. Idempotent — same module_id overwrites.
|
|
58
|
+
"""Register or update a module. Idempotent — same module_id overwrites.
|
|
59
|
+
Returns dict with changed flag on success, raises exception on failure."""
|
|
61
60
|
# Validate required fields
|
|
62
61
|
missing = [f for f in self._REQUIRED_FIELDS if not data.get(f)]
|
|
63
62
|
if missing:
|
|
64
|
-
|
|
63
|
+
raise ValueError(f"Missing required fields: {', '.join(missing)}")
|
|
65
64
|
|
|
66
65
|
mid = data["module_id"]
|
|
67
66
|
|
|
68
|
-
#
|
|
67
|
+
# Validate nested dict structure for lookup-able fields
|
|
68
|
+
# Only check known wrapper fields (tools, hooks, events_publish)
|
|
69
|
+
# RPC methods are registered directly at top level with their actual names
|
|
70
|
+
# events_subscribe is a list, not a dict
|
|
71
|
+
for field_name in ["tools", "hooks", "events_publish"]:
|
|
72
|
+
field_value = data.get(field_name)
|
|
73
|
+
if field_value is not None:
|
|
74
|
+
if not isinstance(field_value, dict):
|
|
75
|
+
raise TypeError(f"Field '{field_name}' must be a dict (nested structure), got {type(field_value).__name__}")
|
|
76
|
+
# Check if it's a flat dict with dot-notation keys (common mistake)
|
|
77
|
+
if any("." in str(k) for k in field_value.keys()):
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"Field '{field_name}' contains dot-notation keys (e.g., 'module.config.get'). "
|
|
80
|
+
f"Use nested dict structure instead: {{'module': {{'config': {{'get': ...}}}}}}. "
|
|
81
|
+
f"See docs/模块开发指南.md section 4.3 for correct format."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Warn on tool name conflicts (skip 'rpc' — it's a standard tool every module registers)
|
|
69
85
|
new_tools = data.get("tools", {})
|
|
70
86
|
if isinstance(new_tools, dict):
|
|
71
87
|
for tool_name in new_tools:
|
|
88
|
+
if tool_name == "rpc":
|
|
89
|
+
continue
|
|
72
90
|
for other_mid, other_data in self.modules.items():
|
|
73
91
|
if other_mid == mid:
|
|
74
92
|
continue
|
|
75
93
|
if tool_name in other_data.get("tools", {}):
|
|
76
94
|
print(f"[kernel] WARNING: tool '{tool_name}' registered by both '{other_mid}' and '{mid}'")
|
|
77
95
|
|
|
96
|
+
# Check if content changed (compare with existing registration)
|
|
97
|
+
changed = True
|
|
98
|
+
old_record = self.modules.get(mid)
|
|
99
|
+
if old_record:
|
|
100
|
+
# Strip action field from new data
|
|
101
|
+
new_record = {k: v for k, v in data.items() if k != "action"}
|
|
102
|
+
# Compare without status and registered_at fields
|
|
103
|
+
old_data = {k: v for k, v in old_record.items() if k not in ("status", "registered_at")}
|
|
104
|
+
changed = (new_record != old_data)
|
|
105
|
+
|
|
78
106
|
# Strip action field — it's a request verb, not part of the registration payload
|
|
79
107
|
record = {k: v for k, v in data.items() if k != "action"}
|
|
80
108
|
record["status"] = "registered" # State machine: connected → registered (via register RPC)
|
|
81
109
|
record["registered_at"] = time.time()
|
|
82
110
|
self.modules[mid] = record
|
|
83
|
-
|
|
84
|
-
|
|
111
|
+
|
|
112
|
+
# Update global timestamp if content changed
|
|
113
|
+
if changed:
|
|
114
|
+
self.last_update_time = time.time()
|
|
115
|
+
|
|
116
|
+
return {"changed": changed}
|
|
85
117
|
|
|
86
118
|
def deregister_module(self, module_id: str) -> dict:
|
|
87
|
-
"""Remove a module record immediately."""
|
|
119
|
+
"""Remove a module record immediately. Returns empty dict."""
|
|
88
120
|
self.modules.pop(module_id, None)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def heartbeat(self, module_id: str) -> dict:
|
|
93
|
-
"""Renew heartbeat for a module."""
|
|
94
|
-
if module_id not in self.modules:
|
|
95
|
-
return {"ok": False, "error": "module not registered"}
|
|
96
|
-
self.heartbeats[module_id] = time.time()
|
|
97
|
-
# Don't change status — heartbeat just keeps alive, doesn't upgrade state
|
|
98
|
-
return {"ok": True}
|
|
121
|
+
# Update global timestamp
|
|
122
|
+
self.last_update_time = time.time()
|
|
123
|
+
return {}
|
|
99
124
|
|
|
100
125
|
def set_connected(self, module_id: str):
|
|
101
126
|
"""Mark a module as connected (WS established, not yet registered).
|
|
@@ -119,17 +144,6 @@ class RegistryStore:
|
|
|
119
144
|
mod = self.modules.get(module_id)
|
|
120
145
|
return mod is not None and mod.get("status") == "ready"
|
|
121
146
|
|
|
122
|
-
def check_ttl(self) -> list[str]:
|
|
123
|
-
"""Mark modules as offline if heartbeat expired. Returns list of newly-offline module_ids."""
|
|
124
|
-
now = time.time()
|
|
125
|
-
expired = []
|
|
126
|
-
for mid, last in list(self.heartbeats.items()):
|
|
127
|
-
if mid in self.modules and now - last > self.ttl:
|
|
128
|
-
if self.modules[mid].get("status") not in ("offline",):
|
|
129
|
-
self.modules[mid]["status"] = "offline"
|
|
130
|
-
expired.append(mid)
|
|
131
|
-
return expired
|
|
132
|
-
|
|
133
147
|
# ── Get by dot-path ──
|
|
134
148
|
|
|
135
149
|
def get_by_path(self, path: str) -> Any | None:
|
|
@@ -177,7 +191,7 @@ class RegistryStore:
|
|
|
177
191
|
def lookup(self, field: str = None, module: str = None, value: str = None) -> list[dict]:
|
|
178
192
|
"""
|
|
179
193
|
Search across all online modules. All three params support glob patterns.
|
|
180
|
-
Returns list of {field, module,
|
|
194
|
+
Returns list of {field, module, value}.
|
|
181
195
|
"""
|
|
182
196
|
results = []
|
|
183
197
|
for mid, data in self.modules.items():
|
|
@@ -186,8 +200,6 @@ class RegistryStore:
|
|
|
186
200
|
if module and not fnmatch.fnmatch(mid, module):
|
|
187
201
|
continue
|
|
188
202
|
|
|
189
|
-
api_ep = data.get("api_endpoint", "")
|
|
190
|
-
|
|
191
203
|
if field:
|
|
192
204
|
matches = self._match_fields(data, field)
|
|
193
205
|
for fpath, fval in matches:
|
|
@@ -196,7 +208,6 @@ class RegistryStore:
|
|
|
196
208
|
results.append({
|
|
197
209
|
"field": fpath,
|
|
198
210
|
"module": mid,
|
|
199
|
-
"api_endpoint": api_ep,
|
|
200
211
|
"value": fval,
|
|
201
212
|
})
|
|
202
213
|
elif value:
|
|
@@ -205,14 +216,12 @@ class RegistryStore:
|
|
|
205
216
|
results.append({
|
|
206
217
|
"field": k,
|
|
207
218
|
"module": mid,
|
|
208
|
-
"api_endpoint": api_ep,
|
|
209
219
|
"value": v,
|
|
210
220
|
})
|
|
211
221
|
else:
|
|
212
222
|
results.append({
|
|
213
223
|
"field": "module_id",
|
|
214
224
|
"module": mid,
|
|
215
|
-
"api_endpoint": api_ep,
|
|
216
225
|
"value": mid,
|
|
217
226
|
})
|
|
218
227
|
|
package/kernel/rpc_router.py
CHANGED
|
@@ -73,11 +73,16 @@ class RpcRouter:
|
|
|
73
73
|
self.connections = connections
|
|
74
74
|
self.kernel_server = kernel_server # Direct reference to KernelServer
|
|
75
75
|
|
|
76
|
+
# RPC 统计计数器
|
|
77
|
+
self._rpc_total = 0 # 总 RPC 调用次数
|
|
78
|
+
self._rpc_builtin = 0 # 内置方法调用次数
|
|
79
|
+
self._rpc_forwarded = 0 # 转发调用次数
|
|
80
|
+
self._rpc_errors = 0 # 错误次数
|
|
81
|
+
|
|
76
82
|
# Builtin method dispatch table
|
|
77
83
|
self.methods: dict[str, callable] = {
|
|
78
84
|
"registry.register": self._registry_register,
|
|
79
85
|
"registry.deregister": self._registry_deregister,
|
|
80
|
-
"registry.heartbeat": self._registry_heartbeat,
|
|
81
86
|
"registry.lookup": self._registry_lookup,
|
|
82
87
|
"registry.get": self._registry_get,
|
|
83
88
|
"registry.verify": self._registry_verify,
|
|
@@ -87,9 +92,9 @@ class RpcRouter:
|
|
|
87
92
|
"kernel.ping": self._kernel_ping,
|
|
88
93
|
"kernel.stats": self._kernel_stats,
|
|
89
94
|
"kernel.health": self._kernel_health,
|
|
95
|
+
"kernel.latencies": self._kernel_latencies,
|
|
90
96
|
"kernel.generate_tokens": self._kernel_generate_tokens,
|
|
91
97
|
"kernel.register_tokens": self._kernel_register_tokens,
|
|
92
|
-
"kernel.shutdown": self._kernel_shutdown,
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
# Pending cross-module forwards: internal_id -> PendingForward
|
|
@@ -102,6 +107,9 @@ class RpcRouter:
|
|
|
102
107
|
method = msg.get("method", "")
|
|
103
108
|
msg_id = msg.get("id")
|
|
104
109
|
|
|
110
|
+
# 统计:总调用次数
|
|
111
|
+
self._rpc_total += 1
|
|
112
|
+
|
|
105
113
|
# JSON-RPC Notification (no id) — currently not handled from clients
|
|
106
114
|
if msg_id is None:
|
|
107
115
|
return
|
|
@@ -111,12 +119,27 @@ class RpcRouter:
|
|
|
111
119
|
# Builtin method
|
|
112
120
|
handler = self.methods.get(method)
|
|
113
121
|
if handler:
|
|
122
|
+
# 统计:内置方法调用
|
|
123
|
+
self._rpc_builtin += 1
|
|
114
124
|
try:
|
|
115
125
|
result = await handler(caller_id, params)
|
|
116
|
-
await ws.send_text(_result_msg(msg_id, result))
|
|
117
126
|
except Exception as e:
|
|
127
|
+
# 统计:错误次数
|
|
128
|
+
self._rpc_errors += 1
|
|
118
129
|
print(f"[kernel] RPC handler error ({method}): {e}")
|
|
119
|
-
|
|
130
|
+
try:
|
|
131
|
+
await ws.send_text(_error_msg(msg_id, INTERNAL_ERROR, str(e)))
|
|
132
|
+
except Exception:
|
|
133
|
+
# Can't send error response, connection closed
|
|
134
|
+
pass
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
# Send result (may fail if connection closed during shutdown)
|
|
138
|
+
try:
|
|
139
|
+
await ws.send_text(_result_msg(msg_id, result))
|
|
140
|
+
except Exception as e:
|
|
141
|
+
# Connection closed during shutdown — this is normal, exit silently
|
|
142
|
+
pass
|
|
120
143
|
return
|
|
121
144
|
|
|
122
145
|
# Cross-module forward: method prefix is target module_id
|
|
@@ -126,19 +149,27 @@ class RpcRouter:
|
|
|
126
149
|
if target in self.connections:
|
|
127
150
|
# Check if target module is ready (state machine protection)
|
|
128
151
|
if not self.registry.is_ready(target):
|
|
152
|
+
# 统计:错误次数
|
|
153
|
+
self._rpc_errors += 1
|
|
129
154
|
await ws.send_text(_error_msg(
|
|
130
155
|
msg_id, MODULE_OFFLINE,
|
|
131
156
|
f"Module not ready: {target} (status: {self.registry.modules.get(target, {}).get('status', 'unknown')})"))
|
|
132
157
|
return
|
|
158
|
+
# 统计:转发调用
|
|
159
|
+
self._rpc_forwarded += 1
|
|
133
160
|
await self._forward(caller_id, ws, msg_id, target, method, params)
|
|
134
161
|
return
|
|
135
162
|
# Target not connected — check if registered but offline
|
|
136
163
|
if target in self.registry.modules:
|
|
164
|
+
# 统计:错误次数
|
|
165
|
+
self._rpc_errors += 1
|
|
137
166
|
await ws.send_text(_error_msg(
|
|
138
167
|
msg_id, MODULE_OFFLINE, f"Module offline: {target}"))
|
|
139
168
|
return
|
|
140
169
|
|
|
141
170
|
# Method not found
|
|
171
|
+
# 统计:错误次数
|
|
172
|
+
self._rpc_errors += 1
|
|
142
173
|
await ws.send_text(_error_msg(msg_id, METHOD_NOT_FOUND, f"Method not found: {method}"))
|
|
143
174
|
|
|
144
175
|
async def handle_response(self, module_id: str, msg: dict):
|
|
@@ -187,6 +218,11 @@ class RpcRouter:
|
|
|
187
218
|
# Strip target prefix from method for the forwarded request
|
|
188
219
|
actual_method = method[len(target) + 1:] # e.g. "watchdog.get_status" -> "get_status"
|
|
189
220
|
|
|
221
|
+
# Inject caller_id into params for permission checking
|
|
222
|
+
if not isinstance(params, dict):
|
|
223
|
+
params = {}
|
|
224
|
+
params["_caller_id"] = caller_id
|
|
225
|
+
|
|
190
226
|
# Record pending forward
|
|
191
227
|
loop = asyncio.get_event_loop()
|
|
192
228
|
pending = PendingForward(
|
|
@@ -243,62 +279,93 @@ class RpcRouter:
|
|
|
243
279
|
async def _registry_register(self, caller_id: str, params: dict) -> dict:
|
|
244
280
|
mid = params.get("module_id")
|
|
245
281
|
if not mid:
|
|
246
|
-
|
|
282
|
+
raise ValueError("module_id required")
|
|
247
283
|
# Permission: only Launcher or the module itself
|
|
248
284
|
if caller_id != "launcher" and caller_id != mid:
|
|
249
|
-
|
|
285
|
+
raise PermissionError(f"Module '{caller_id}' cannot register as '{mid}'")
|
|
286
|
+
|
|
287
|
+
print(f"[kernel] registry.register called by {caller_id} for module {mid}")
|
|
288
|
+
print(f"[kernel] rpc_methods: {params.get('rpc_methods')}")
|
|
289
|
+
|
|
250
290
|
result = self.registry.register_module(params)
|
|
251
|
-
|
|
252
|
-
|
|
291
|
+
self.event_hub.publish_internal("module.registered", {"module_id": mid}, source=self.kernel_server.module_id)
|
|
292
|
+
|
|
293
|
+
# Only publish registry.updated if content actually changed
|
|
294
|
+
if result.get("changed", True):
|
|
295
|
+
from datetime import datetime, timezone
|
|
296
|
+
self.event_hub.publish_internal("registry.updated", {
|
|
297
|
+
"module_id": mid,
|
|
298
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
299
|
+
"action": "register",
|
|
300
|
+
}, source=self.kernel_server.module_id)
|
|
301
|
+
print(f"[kernel] registry.updated published for {mid}")
|
|
302
|
+
else:
|
|
303
|
+
print(f"[kernel] {mid} re-registered with no changes, skipping registry.updated")
|
|
304
|
+
|
|
305
|
+
# When Launcher registers, Kernel publishes its own module.ready
|
|
306
|
+
if mid == "launcher" and self.kernel_server:
|
|
307
|
+
self.kernel_server.publish_ready()
|
|
308
|
+
print(f"[kernel] launcher registered → kernel module.ready published")
|
|
253
309
|
|
|
254
|
-
# When Launcher registers, Kernel publishes its own module.ready
|
|
255
|
-
if mid == "launcher" and self.kernel_server:
|
|
256
|
-
self.kernel_server.publish_ready()
|
|
257
|
-
print(f"[kernel] launcher registered → kernel module.ready published")
|
|
258
310
|
return result
|
|
259
311
|
|
|
260
312
|
async def _registry_deregister(self, caller_id: str, params: dict) -> dict:
|
|
261
313
|
mid = params.get("module_id")
|
|
262
314
|
if not mid:
|
|
263
|
-
|
|
315
|
+
raise ValueError("module_id required")
|
|
264
316
|
if caller_id != "launcher" and caller_id != mid:
|
|
265
|
-
|
|
266
|
-
result = self.registry.deregister_module(mid)
|
|
267
|
-
if result.get("ok"):
|
|
268
|
-
self.event_hub.publish_internal("module.unregistered", {"module_id": mid})
|
|
269
|
-
return result
|
|
317
|
+
raise PermissionError(f"Module '{caller_id}' cannot deregister '{mid}'")
|
|
270
318
|
|
|
271
|
-
|
|
272
|
-
mid =
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
319
|
+
self.registry.deregister_module(mid)
|
|
320
|
+
self.event_hub.publish_internal("module.unregistered", {"module_id": mid}, source=self.kernel_server.module_id)
|
|
321
|
+
|
|
322
|
+
# Publish registry.updated event for cache invalidation
|
|
323
|
+
from datetime import datetime, timezone
|
|
324
|
+
self.event_hub.publish_internal("registry.updated", {
|
|
325
|
+
"module_id": mid,
|
|
326
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
327
|
+
"action": "deregister",
|
|
328
|
+
}, source=self.kernel_server.module_id)
|
|
329
|
+
|
|
330
|
+
return {}
|
|
278
331
|
|
|
279
332
|
async def _registry_lookup(self, caller_id: str, params: dict) -> dict:
|
|
333
|
+
field = params.get("field")
|
|
334
|
+
module = params.get("module")
|
|
335
|
+
value = params.get("value")
|
|
336
|
+
|
|
337
|
+
print(f"[kernel] registry.lookup called by {caller_id}: field={field}, module={module}, value={value}")
|
|
338
|
+
|
|
280
339
|
results = self.registry.lookup(
|
|
281
|
-
field=
|
|
282
|
-
module=
|
|
283
|
-
value=
|
|
340
|
+
field=field,
|
|
341
|
+
module=module,
|
|
342
|
+
value=value,
|
|
284
343
|
)
|
|
285
|
-
|
|
344
|
+
|
|
345
|
+
print(f"[kernel] registry.lookup results: {len(results)} matches")
|
|
346
|
+
for r in results:
|
|
347
|
+
print(f"[kernel] - {r['module']}.{r['field']} = {r['value']}")
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
"results": results,
|
|
351
|
+
"last_update_time": self.registry.last_update_time
|
|
352
|
+
}
|
|
286
353
|
|
|
287
354
|
async def _registry_get(self, caller_id: str, params: dict) -> dict:
|
|
288
355
|
path = params.get("path", "")
|
|
289
356
|
if not path:
|
|
290
|
-
|
|
357
|
+
raise ValueError("path required")
|
|
291
358
|
val, found = self.registry.get_by_path(path)
|
|
292
359
|
if not found:
|
|
293
|
-
|
|
294
|
-
return {"
|
|
360
|
+
raise KeyError(f"Path not found: {path}")
|
|
361
|
+
return {"value": val}
|
|
295
362
|
|
|
296
363
|
async def _registry_verify(self, caller_id: str, params: dict) -> dict:
|
|
297
364
|
token = params.get("token", "")
|
|
298
365
|
module_id = self.registry.verify_token(token)
|
|
299
|
-
if module_id:
|
|
300
|
-
|
|
301
|
-
return {"
|
|
366
|
+
if not module_id:
|
|
367
|
+
raise PermissionError("Invalid token")
|
|
368
|
+
return {"module_id": module_id}
|
|
302
369
|
|
|
303
370
|
# ── Builtin handlers: event.* ──
|
|
304
371
|
|
|
@@ -311,23 +378,24 @@ class RpcRouter:
|
|
|
311
378
|
# When a module publishes module.ready, update its status in registry
|
|
312
379
|
if event_type == "module.ready":
|
|
313
380
|
mid = (data or {}).get("module_id", caller_id)
|
|
381
|
+
print(f"[kernel] DEBUG: 收到 module.ready 事件,module_id={mid}, caller_id={caller_id}")
|
|
314
382
|
self.registry.set_ready(mid)
|
|
383
|
+
print(f"[kernel] DEBUG: 已调用 set_ready({mid})")
|
|
315
384
|
|
|
316
385
|
return self.event_hub.publish_event(caller_id, event_id, event_type, data, echo)
|
|
317
386
|
|
|
318
387
|
async def _event_subscribe(self, caller_id: str, params: dict) -> dict:
|
|
319
388
|
events = params.get("events", [])
|
|
320
389
|
if not isinstance(events, list) or not events:
|
|
321
|
-
|
|
390
|
+
raise ValueError("events must be a non-empty list")
|
|
322
391
|
self.event_hub.handle_subscribe(caller_id, events)
|
|
323
|
-
return {
|
|
392
|
+
return {}
|
|
324
393
|
|
|
325
394
|
async def _event_unsubscribe(self, caller_id: str, params: dict) -> dict:
|
|
326
395
|
events = params.get("events", [])
|
|
327
396
|
if not isinstance(events, list) or not events:
|
|
328
|
-
|
|
329
|
-
self.event_hub.handle_unsubscribe(caller_id, events)
|
|
330
|
-
return {"ok": True}
|
|
397
|
+
raise ValueError("events must be a non-empty list")
|
|
398
|
+
return self.event_hub.handle_unsubscribe(caller_id, events)
|
|
331
399
|
|
|
332
400
|
# ── Builtin handlers: kernel.* ──
|
|
333
401
|
|
|
@@ -335,7 +403,16 @@ class RpcRouter:
|
|
|
335
403
|
return {"pong": True, "timestamp": datetime.now(timezone.utc).isoformat()}
|
|
336
404
|
|
|
337
405
|
async def _kernel_stats(self, caller_id: str, params: dict) -> dict:
|
|
338
|
-
|
|
406
|
+
event_stats = self.event_hub.get_stats()
|
|
407
|
+
return {
|
|
408
|
+
**event_stats,
|
|
409
|
+
"rpc": {
|
|
410
|
+
"total": self._rpc_total,
|
|
411
|
+
"builtin": self._rpc_builtin,
|
|
412
|
+
"forwarded": self._rpc_forwarded,
|
|
413
|
+
"errors": self._rpc_errors
|
|
414
|
+
}
|
|
415
|
+
}
|
|
339
416
|
|
|
340
417
|
async def _kernel_health(self, caller_id: str, params: dict) -> dict:
|
|
341
418
|
eh_health = self.event_hub.get_health()
|
|
@@ -349,6 +426,34 @@ class RpcRouter:
|
|
|
349
426
|
"event_stats": eh_health.get("details", {}),
|
|
350
427
|
}
|
|
351
428
|
|
|
429
|
+
async def _kernel_latencies(self, caller_id: str, params: dict) -> dict:
|
|
430
|
+
"""Get ping/pong latencies for all connected modules.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
{
|
|
434
|
+
"latencies": {
|
|
435
|
+
"module1": {
|
|
436
|
+
"outbound": 12.34, # ms, Kernel → module
|
|
437
|
+
"inbound": 23.45, # ms, module → Kernel
|
|
438
|
+
"status": "ok" | "timeout" | "never"
|
|
439
|
+
},
|
|
440
|
+
...
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
"""
|
|
444
|
+
result = {}
|
|
445
|
+
for module_id in self.kernel_server.connections.keys():
|
|
446
|
+
latency_data = self.kernel_server._pong_latencies.get(module_id, {})
|
|
447
|
+
status = self.kernel_server._pong_status.get(module_id, "never")
|
|
448
|
+
|
|
449
|
+
result[module_id] = {
|
|
450
|
+
"outbound": latency_data.get("outbound"),
|
|
451
|
+
"inbound": latency_data.get("inbound"),
|
|
452
|
+
"status": status,
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return {"latencies": result}
|
|
456
|
+
|
|
352
457
|
async def _kernel_generate_tokens(self, caller_id: str, params: dict) -> dict:
|
|
353
458
|
"""Generate tokens for a list of module names.
|
|
354
459
|
|
|
@@ -356,15 +461,15 @@ class RpcRouter:
|
|
|
356
461
|
params: {"modules": ["mod1", "mod2", ...]}
|
|
357
462
|
|
|
358
463
|
Returns:
|
|
359
|
-
{"
|
|
464
|
+
{"tokens": {"mod1": "token1", "mod2": "token2", ...}}
|
|
360
465
|
"""
|
|
361
466
|
# Only Launcher may request token generation
|
|
362
467
|
if caller_id != "launcher":
|
|
363
|
-
|
|
468
|
+
raise PermissionError("Only Launcher may generate tokens")
|
|
364
469
|
|
|
365
470
|
modules = params.get("modules", [])
|
|
366
471
|
if not isinstance(modules, list):
|
|
367
|
-
|
|
472
|
+
raise ValueError("modules must be a list")
|
|
368
473
|
|
|
369
474
|
import secrets
|
|
370
475
|
tokens = {}
|
|
@@ -374,25 +479,13 @@ class RpcRouter:
|
|
|
374
479
|
# Register tokens in registry
|
|
375
480
|
self.registry.register_tokens(tokens)
|
|
376
481
|
|
|
377
|
-
return {"
|
|
482
|
+
return {"tokens": tokens}
|
|
378
483
|
|
|
379
484
|
async def _kernel_register_tokens(self, caller_id: str, params: dict) -> dict:
|
|
380
485
|
# Only Launcher may register tokens
|
|
381
486
|
if caller_id != "launcher":
|
|
382
|
-
|
|
487
|
+
raise PermissionError("Only Launcher may register tokens")
|
|
383
488
|
self.registry.register_tokens(params)
|
|
384
|
-
return {
|
|
385
|
-
|
|
386
|
-
async def _kernel_shutdown(self, caller_id: str, params: dict) -> dict:
|
|
387
|
-
"""Shutdown Kernel. Only Launcher may call this."""
|
|
388
|
-
if caller_id != "launcher":
|
|
389
|
-
return {"ok": False, "error": "Only Launcher may shutdown Kernel"}
|
|
390
|
-
|
|
391
|
-
print("[kernel] Received shutdown request from Launcher")
|
|
392
|
-
|
|
393
|
-
# Schedule shutdown (don't block RPC response)
|
|
394
|
-
if self.kernel_server:
|
|
395
|
-
asyncio.create_task(self.kernel_server.shutdown())
|
|
489
|
+
return {}
|
|
396
490
|
|
|
397
|
-
return {"ok": True}
|
|
398
491
|
|