@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.
Files changed (78) hide show
  1. package/CHANGELOG.md +200 -0
  2. package/cli.js +76 -0
  3. package/extensions/agents/assistant/entry.py +111 -1
  4. package/extensions/agents/assistant/server.py +263 -215
  5. package/extensions/channels/acp_channel/entry.py +111 -1
  6. package/extensions/channels/acp_channel/module.md +23 -22
  7. package/extensions/channels/acp_channel/server.py +263 -215
  8. package/extensions/event_hub_bench/entry.py +107 -1
  9. package/extensions/services/backup/entry.py +299 -21
  10. package/extensions/services/backup/module.md +24 -22
  11. package/extensions/services/model_service/entry.py +145 -19
  12. package/extensions/services/model_service/module.md +21 -22
  13. package/extensions/services/watchdog/entry.py +188 -25
  14. package/extensions/services/watchdog/monitor.py +144 -34
  15. package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
  16. package/extensions/services/web/config_example.py +35 -0
  17. package/extensions/services/web/config_loader.py +110 -0
  18. package/extensions/services/web/entry.py +114 -26
  19. package/extensions/services/web/module.md +35 -24
  20. package/extensions/services/web/pairing.py +250 -0
  21. package/extensions/services/web/pairing_codes.jsonl +16 -0
  22. package/extensions/services/web/relay.py +643 -0
  23. package/extensions/services/web/relay_config.json5 +67 -0
  24. package/extensions/services/web/routes/routes_management_ws.py +127 -0
  25. package/extensions/services/web/routes/routes_rpc.py +89 -0
  26. package/extensions/services/web/routes/routes_test.py +61 -0
  27. package/extensions/services/web/routes/schemas.py +0 -22
  28. package/extensions/services/web/server.py +421 -98
  29. package/extensions/services/web/static/css/style.css +67 -28
  30. package/extensions/services/web/static/index.html +234 -44
  31. package/extensions/services/web/static/js/app.js +1335 -48
  32. package/extensions/services/web/static/js/kernel-client-example.js +161 -0
  33. package/extensions/services/web/static/js/kernel-client.js +383 -0
  34. package/extensions/services/web/static/js/registry-tests.js +558 -0
  35. package/extensions/services/web/static/js/token-manager.js +175 -0
  36. package/extensions/services/web/static/pairing.html +248 -0
  37. package/extensions/services/web/static/test_registry.html +262 -0
  38. package/extensions/services/web/web_config.json5 +29 -0
  39. package/kernel/entry.py +120 -32
  40. package/kernel/event_hub.py +141 -16
  41. package/kernel/module.md +36 -33
  42. package/kernel/registry_store.py +48 -15
  43. package/kernel/rpc_router.py +120 -53
  44. package/kernel/server.py +219 -12
  45. package/kite_cli/__init__.py +3 -0
  46. package/kite_cli/__main__.py +5 -0
  47. package/kite_cli/commands/__init__.py +1 -0
  48. package/kite_cli/commands/clean.py +101 -0
  49. package/kite_cli/commands/doctor.py +35 -0
  50. package/kite_cli/commands/history.py +111 -0
  51. package/kite_cli/commands/info.py +96 -0
  52. package/kite_cli/commands/install.py +313 -0
  53. package/kite_cli/commands/list.py +143 -0
  54. package/kite_cli/commands/log.py +81 -0
  55. package/kite_cli/commands/rollback.py +88 -0
  56. package/kite_cli/commands/search.py +73 -0
  57. package/kite_cli/commands/uninstall.py +85 -0
  58. package/kite_cli/commands/update.py +118 -0
  59. package/kite_cli/core/__init__.py +1 -0
  60. package/kite_cli/core/checker.py +142 -0
  61. package/kite_cli/core/dependency.py +229 -0
  62. package/kite_cli/core/downloader.py +209 -0
  63. package/kite_cli/core/install_info.py +40 -0
  64. package/kite_cli/core/tool_installer.py +397 -0
  65. package/kite_cli/core/validator.py +78 -0
  66. package/kite_cli/main.py +289 -0
  67. package/kite_cli/utils/__init__.py +1 -0
  68. package/kite_cli/utils/i18n.py +252 -0
  69. package/kite_cli/utils/interactive.py +63 -0
  70. package/kite_cli/utils/operation_log.py +77 -0
  71. package/kite_cli/utils/paths.py +34 -0
  72. package/kite_cli/utils/version.py +308 -0
  73. package/launcher/entry.py +819 -158
  74. package/launcher/logging_setup.py +104 -0
  75. package/launcher/module.md +37 -37
  76. package/package.json +2 -1
  77. package/scripts/plan_manager.py +315 -0
  78. 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}