@agentunion/kite 1.3.2 → 1.4.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 +200 -0
- package/cli.js +76 -0
- package/extensions/agents/assistant/entry.py +111 -1
- package/extensions/agents/assistant/server.py +263 -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 +263 -215
- package/extensions/event_hub_bench/entry.py +107 -1
- package/extensions/services/backup/entry.py +299 -21
- package/extensions/services/backup/module.md +24 -22
- package/extensions/services/model_service/entry.py +145 -19
- package/extensions/services/model_service/module.md +21 -22
- package/extensions/services/watchdog/entry.py +188 -25
- package/extensions/services/watchdog/monitor.py +144 -34
- 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 +421 -98
- 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 +36 -33
- package/kernel/registry_store.py +48 -15
- package/kernel/rpc_router.py +120 -53
- package/kernel/server.py +219 -12
- 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/doctor.py +35 -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/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/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 +289 -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 +819 -158
- package/launcher/logging_setup.py +104 -0
- package/launcher/module.md +37 -37
- package/package.json +2 -1
- package/scripts/plan_manager.py +315 -0
- package/extensions/services/web/routes/routes_modules.py +0 -249
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
"""Routes for module management — scan, view, and edit module metadata + config."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
import os
|
|
7
|
-
import re
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
import yaml
|
|
12
|
-
from fastapi import APIRouter, HTTPException
|
|
13
|
-
|
|
14
|
-
from routes.schemas import ModuleConfigUpdate, ModuleMetadataUpdate
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
|
-
router = APIRouter(tags=["modules"])
|
|
19
|
-
|
|
20
|
-
# Fields that may NOT be changed via the metadata API
|
|
21
|
-
_READONLY_FIELDS = frozenset({"name", "type", "runtime", "entry"})
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# ---------------------------------------------------------------------------
|
|
25
|
-
# Helpers
|
|
26
|
-
# ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
def _get_project_root() -> Path:
|
|
29
|
-
"""Return the Kite project root directory."""
|
|
30
|
-
env = os.environ.get("KITE_PROJECT")
|
|
31
|
-
if env:
|
|
32
|
-
return Path(env)
|
|
33
|
-
# Fallback: __file__ is routes/routes_modules.py → up 5 levels to project root
|
|
34
|
-
# routes_modules.py → routes/ → web/ → services/ → extensions/ → Kite/
|
|
35
|
-
return Path(__file__).resolve().parents[4]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _scan_modules() -> list[dict[str, Any]]:
|
|
39
|
-
"""Discover all modules by scanning kernel/ (depth 0) and extensions/ (depth 2)."""
|
|
40
|
-
root = _get_project_root()
|
|
41
|
-
found: list[dict[str, Any]] = []
|
|
42
|
-
|
|
43
|
-
# 1. kernel/module.md
|
|
44
|
-
kernel_md = root / "kernel" / "module.md"
|
|
45
|
-
if kernel_md.is_file():
|
|
46
|
-
meta, _ = _parse_module_md(kernel_md)
|
|
47
|
-
if meta:
|
|
48
|
-
meta["_path"] = str(kernel_md)
|
|
49
|
-
meta["_dir"] = str(kernel_md.parent)
|
|
50
|
-
found.append(meta)
|
|
51
|
-
|
|
52
|
-
# 2. launcher/module.md
|
|
53
|
-
launcher_md = root / "launcher" / "module.md"
|
|
54
|
-
if launcher_md.is_file():
|
|
55
|
-
meta, _ = _parse_module_md(launcher_md)
|
|
56
|
-
if meta:
|
|
57
|
-
meta["_path"] = str(launcher_md)
|
|
58
|
-
meta["_dir"] = str(launcher_md.parent)
|
|
59
|
-
found.append(meta)
|
|
60
|
-
|
|
61
|
-
# 3. extensions/{category}/{name}/module.md (depth 2)
|
|
62
|
-
ext_dir = root / "extensions"
|
|
63
|
-
if ext_dir.is_dir():
|
|
64
|
-
for category in sorted(ext_dir.iterdir()):
|
|
65
|
-
if not category.is_dir() or category.name.startswith("."):
|
|
66
|
-
continue
|
|
67
|
-
for mod_dir in sorted(category.iterdir()):
|
|
68
|
-
if not mod_dir.is_dir() or mod_dir.name.startswith("."):
|
|
69
|
-
continue
|
|
70
|
-
md_path = mod_dir / "module.md"
|
|
71
|
-
if md_path.is_file():
|
|
72
|
-
meta, _ = _parse_module_md(md_path)
|
|
73
|
-
if meta:
|
|
74
|
-
meta["_path"] = str(md_path)
|
|
75
|
-
meta["_dir"] = str(mod_dir)
|
|
76
|
-
found.append(meta)
|
|
77
|
-
|
|
78
|
-
return found
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
_FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n?(.*)", re.DOTALL)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _parse_module_md(path: Path) -> tuple[dict[str, Any], str]:
|
|
85
|
-
"""Parse YAML frontmatter from a module.md file. Returns (frontmatter_dict, body_str)."""
|
|
86
|
-
try:
|
|
87
|
-
text = path.read_text(encoding="utf-8")
|
|
88
|
-
except Exception as exc:
|
|
89
|
-
logger.warning("Failed to read %s: %s", path, exc)
|
|
90
|
-
return {}, ""
|
|
91
|
-
|
|
92
|
-
m = _FRONTMATTER_RE.match(text)
|
|
93
|
-
if not m:
|
|
94
|
-
return {}, text
|
|
95
|
-
|
|
96
|
-
try:
|
|
97
|
-
fm = yaml.safe_load(m.group(1)) or {}
|
|
98
|
-
except yaml.YAMLError as exc:
|
|
99
|
-
logger.warning("Failed to parse frontmatter in %s: %s", path, exc)
|
|
100
|
-
return {}, text
|
|
101
|
-
|
|
102
|
-
return fm, m.group(2)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _write_module_md(path: Path, frontmatter: dict[str, Any], body: str) -> None:
|
|
106
|
-
"""Write frontmatter + body back to module.md."""
|
|
107
|
-
fm_str = yaml.dump(frontmatter, allow_unicode=True, sort_keys=False, default_flow_style=False).rstrip()
|
|
108
|
-
content = f"---\n{fm_str}\n---\n{body}"
|
|
109
|
-
path.write_text(content, encoding="utf-8")
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def _find_module(name: str) -> dict[str, Any] | None:
|
|
113
|
-
"""Find a single module by name."""
|
|
114
|
-
for m in _scan_modules():
|
|
115
|
-
if m.get("name") == name:
|
|
116
|
-
return m
|
|
117
|
-
return None
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def _deep_merge(base: dict, overlay: dict) -> dict:
|
|
121
|
-
"""Recursively merge overlay into base (mutates base)."""
|
|
122
|
-
for k, v in overlay.items():
|
|
123
|
-
if k in base and isinstance(base[k], dict) and isinstance(v, dict):
|
|
124
|
-
_deep_merge(base[k], v)
|
|
125
|
-
else:
|
|
126
|
-
base[k] = v
|
|
127
|
-
return base
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
# ---------------------------------------------------------------------------
|
|
131
|
-
# API endpoints
|
|
132
|
-
# ---------------------------------------------------------------------------
|
|
133
|
-
|
|
134
|
-
@router.get("/modules")
|
|
135
|
-
async def list_modules():
|
|
136
|
-
"""List all discovered modules."""
|
|
137
|
-
modules = _scan_modules()
|
|
138
|
-
result = []
|
|
139
|
-
for m in modules:
|
|
140
|
-
mod_dir = Path(m["_dir"])
|
|
141
|
-
has_config = (mod_dir / "config.yaml").is_file()
|
|
142
|
-
result.append({
|
|
143
|
-
"name": m.get("name", ""),
|
|
144
|
-
"display_name": m.get("display_name", ""),
|
|
145
|
-
"type": m.get("type", ""),
|
|
146
|
-
"state": m.get("state", "enabled"),
|
|
147
|
-
"version": m.get("version", ""),
|
|
148
|
-
"runtime": m.get("runtime", ""),
|
|
149
|
-
"preferred_port": m.get("preferred_port"),
|
|
150
|
-
"monitor": m.get("monitor"),
|
|
151
|
-
"has_config": has_config,
|
|
152
|
-
})
|
|
153
|
-
return result
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
@router.get("/modules/{name}")
|
|
157
|
-
async def get_module(name: str):
|
|
158
|
-
"""Get module details including config.yaml content (if any)."""
|
|
159
|
-
m = _find_module(name)
|
|
160
|
-
if not m:
|
|
161
|
-
raise HTTPException(404, f"Module not found: {name}")
|
|
162
|
-
|
|
163
|
-
mod_dir = Path(m["_dir"])
|
|
164
|
-
md_path = Path(m["_path"])
|
|
165
|
-
|
|
166
|
-
# Parse full frontmatter
|
|
167
|
-
frontmatter, _ = _parse_module_md(md_path)
|
|
168
|
-
|
|
169
|
-
# Read config.yaml if present
|
|
170
|
-
config = None
|
|
171
|
-
config_path = mod_dir / "config.yaml"
|
|
172
|
-
if config_path.is_file():
|
|
173
|
-
try:
|
|
174
|
-
config = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
175
|
-
except Exception as exc:
|
|
176
|
-
logger.warning("Failed to read config.yaml for %s: %s", name, exc)
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
"name": frontmatter.get("name", name),
|
|
180
|
-
"display_name": frontmatter.get("display_name", ""),
|
|
181
|
-
"type": frontmatter.get("type", ""),
|
|
182
|
-
"state": frontmatter.get("state", "enabled"),
|
|
183
|
-
"version": frontmatter.get("version", ""),
|
|
184
|
-
"runtime": frontmatter.get("runtime", ""),
|
|
185
|
-
"entry": frontmatter.get("entry", ""),
|
|
186
|
-
"preferred_port": frontmatter.get("preferred_port"),
|
|
187
|
-
"advertise_ip": frontmatter.get("advertise_ip"),
|
|
188
|
-
"monitor": frontmatter.get("monitor"),
|
|
189
|
-
"events": frontmatter.get("events"),
|
|
190
|
-
"subscriptions": frontmatter.get("subscriptions"),
|
|
191
|
-
"depends_on": frontmatter.get("depends_on"),
|
|
192
|
-
"has_config": config is not None,
|
|
193
|
-
"config": config,
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
@router.put("/modules/{name}/metadata")
|
|
198
|
-
async def update_module_metadata(name: str, update: ModuleMetadataUpdate):
|
|
199
|
-
"""Update module.md frontmatter (whitelisted fields only)."""
|
|
200
|
-
m = _find_module(name)
|
|
201
|
-
if not m:
|
|
202
|
-
raise HTTPException(404, f"Module not found: {name}")
|
|
203
|
-
|
|
204
|
-
md_path = Path(m["_path"])
|
|
205
|
-
frontmatter, body = _parse_module_md(md_path)
|
|
206
|
-
if not frontmatter:
|
|
207
|
-
raise HTTPException(500, "Failed to parse module.md frontmatter")
|
|
208
|
-
|
|
209
|
-
# Apply only non-None fields, excluding readonly
|
|
210
|
-
changes = update.model_dump(exclude_none=True)
|
|
211
|
-
for key in list(changes.keys()):
|
|
212
|
-
if key in _READONLY_FIELDS:
|
|
213
|
-
continue
|
|
214
|
-
frontmatter[key] = changes[key]
|
|
215
|
-
|
|
216
|
-
_write_module_md(md_path, frontmatter, body)
|
|
217
|
-
return {"ok": True, "name": name}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
@router.put("/modules/{name}/config")
|
|
221
|
-
async def update_module_config(name: str, update: ModuleConfigUpdate):
|
|
222
|
-
"""Deep-merge update into module's config.yaml."""
|
|
223
|
-
m = _find_module(name)
|
|
224
|
-
if not m:
|
|
225
|
-
raise HTTPException(404, f"Module not found: {name}")
|
|
226
|
-
|
|
227
|
-
mod_dir = Path(m["_dir"])
|
|
228
|
-
config_path = mod_dir / "config.yaml"
|
|
229
|
-
|
|
230
|
-
# Read existing config
|
|
231
|
-
existing: dict[str, Any] = {}
|
|
232
|
-
if config_path.is_file():
|
|
233
|
-
try:
|
|
234
|
-
existing = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
235
|
-
except Exception:
|
|
236
|
-
pass
|
|
237
|
-
|
|
238
|
-
# Deep merge
|
|
239
|
-
overlay = update.model_dump(exclude_none=True)
|
|
240
|
-
# Remove pydantic internal keys
|
|
241
|
-
overlay.pop("model_config", None)
|
|
242
|
-
_deep_merge(existing, overlay)
|
|
243
|
-
|
|
244
|
-
# Write back
|
|
245
|
-
config_path.write_text(
|
|
246
|
-
yaml.dump(existing, allow_unicode=True, sort_keys=False, default_flow_style=False),
|
|
247
|
-
encoding="utf-8",
|
|
248
|
-
)
|
|
249
|
-
return {"ok": True, "name": name, "config": existing}
|