@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
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Kernel Client 使用示例
3
+ *
4
+ * 在主页面(index.html)中集成 KernelClient
5
+ */
6
+
7
+ // 全局 Kernel 客户端实例
8
+ let kernelClient = null;
9
+
10
+ /**
11
+ * 初始化 Kernel 客户端
12
+ */
13
+ async function initKernelClient() {
14
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
15
+ kernelClient = new KernelClient(`${proto}//${location.host}/ws/relay`);
16
+
17
+ // 检查是否已配对
18
+ const token = localStorage.getItem('frontend_token');
19
+
20
+ if (!token) {
21
+ // 未配对,跳转到配对页面
22
+ window.location.href = '/pairing.html';
23
+ return;
24
+ }
25
+
26
+ try {
27
+ // 连接
28
+ const result = await kernelClient.connect();
29
+ console.log('[Kernel] Connected:', result);
30
+
31
+ // 订阅事件
32
+ await kernelClient.subscribe([
33
+ 'module.starting',
34
+ 'module.started',
35
+ 'module.ready',
36
+ 'module.stopped',
37
+ 'module.exiting',
38
+ 'module.shutdown',
39
+ 'module.crashed',
40
+ 'system.ready',
41
+ 'registry.updated',
42
+ 'module.registered',
43
+ 'module.unregistered'
44
+ ]);
45
+
46
+ // 监听模块状态变化
47
+ kernelClient.on('module.started', (data) => {
48
+ console.log('[Kernel] Module started:', data.module_id);
49
+ updateModuleStatus(data.module_id, 'running');
50
+ });
51
+
52
+ kernelClient.on('module.stopped', (data) => {
53
+ console.log('[Kernel] Module stopped:', data.module_id);
54
+ updateModuleStatus(data.module_id, 'stopped');
55
+ });
56
+
57
+ kernelClient.on('module.crashed', (data) => {
58
+ console.log('[Kernel] Module crashed:', data.module_id);
59
+ updateModuleStatus(data.module_id, 'crashed');
60
+ });
61
+
62
+ kernelClient.on('registry.updated', (data) => {
63
+ console.log('[Kernel] Registry updated:', data);
64
+ // 触发缓存失效事件
65
+ window.dispatchEvent(new CustomEvent('registryCacheInvalidate', {
66
+ detail: data
67
+ }));
68
+ });
69
+
70
+ // 更新连接状态指示器
71
+ updateConnectionStatus(true);
72
+
73
+ // 触发连接成功事件
74
+ window.dispatchEvent(new CustomEvent('kernelClientReady'));
75
+
76
+ } catch (err) {
77
+ console.error('[Kernel] Connection failed:', err);
78
+
79
+ // 如果是 token 无效,跳转到配对页面
80
+ if (err.message.includes('Invalid') || err.message.includes('expired')) {
81
+ window.location.href = '/pairing.html';
82
+ } else {
83
+ updateConnectionStatus(false);
84
+ }
85
+ }
86
+ }
87
+
88
+ /**
89
+ * 更新连接状态指示器
90
+ */
91
+ function updateConnectionStatus(connected) {
92
+ const indicators = [
93
+ document.getElementById('ws-indicator'),
94
+ document.getElementById('ws-indicator-modules')
95
+ ];
96
+
97
+ indicators.forEach(indicator => {
98
+ if (indicator) {
99
+ if (connected) {
100
+ indicator.textContent = '● 已连线';
101
+ indicator.style.color = 'var(--success)';
102
+ } else {
103
+ indicator.textContent = '○ 未连线';
104
+ indicator.style.color = 'var(--gray-400)';
105
+ }
106
+ }
107
+ });
108
+ }
109
+
110
+ /**
111
+ * 更新模块状态
112
+ */
113
+ function updateModuleStatus(moduleName, status) {
114
+ // 这里调用现有的模块状态更新逻辑
115
+ if (typeof _onModuleStatusChange === 'function') {
116
+ _onModuleStatusChange(moduleName, status);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * 调用 RPC 示例
122
+ */
123
+ async function exampleRpcCalls() {
124
+ if (!kernelClient) {
125
+ console.error('[Kernel] Client not initialized');
126
+ return;
127
+ }
128
+
129
+ try {
130
+ // 获取模块列表
131
+ const modules = await kernelClient.call('launcher.list_modules');
132
+ console.log('[Kernel] Modules:', modules);
133
+
134
+ // 启动模块
135
+ await kernelClient.call('launcher.start_module', { name: 'watchdog' });
136
+ console.log('[Kernel] Module started');
137
+
138
+ // 停止模块
139
+ await kernelClient.call('launcher.stop_module', { name: 'watchdog' });
140
+ console.log('[Kernel] Module stopped');
141
+
142
+ } catch (err) {
143
+ console.error('[Kernel] RPC error:', err);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * 页面加载时初始化
149
+ */
150
+ document.addEventListener('DOMContentLoaded', () => {
151
+ initKernelClient();
152
+ });
153
+
154
+ /**
155
+ * 页面关闭时断开连接
156
+ */
157
+ window.addEventListener('beforeunload', () => {
158
+ if (kernelClient) {
159
+ kernelClient.disconnect();
160
+ }
161
+ });
@@ -0,0 +1,383 @@
1
+ /**
2
+ * Kernel WebSocket 客户端
3
+ *
4
+ * 功能:
5
+ * - 配对/认证
6
+ * - 自动重连
7
+ * - RPC 调用
8
+ * - 事件订阅
9
+ * - Token 续期
10
+ */
11
+
12
+ class KernelClient {
13
+ constructor(url) {
14
+ this.url = url;
15
+ this.ws = null;
16
+ this.moduleId = null;
17
+ this.role = null;
18
+ this.connected = false;
19
+ this.authenticated = false;
20
+
21
+ // RPC 请求管理
22
+ this.rpcId = 0;
23
+ this.pendingRpcs = new Map(); // id -> {resolve, reject, timeout}
24
+
25
+ // 事件监听器
26
+ this.eventListeners = new Map(); // event -> [callbacks]
27
+
28
+ // 重连配置
29
+ this.reconnectDelay = 1000; // 1秒
30
+ this.maxReconnectDelay = 10000; // 10秒
31
+ this.reconnectAttempts = 0;
32
+ this.reconnectTimer = null;
33
+
34
+ // 心跳配置
35
+ this.heartbeatInterval = 20000; // 20秒
36
+ this.heartbeatTimer = null;
37
+
38
+ // Token 管理
39
+ this.token = localStorage.getItem('frontend_token');
40
+ this.sessionToken = sessionStorage.getItem('session_token');
41
+
42
+ // 生成 session_token(如果没有)
43
+ if (!this.sessionToken) {
44
+ this.sessionToken = 'sess_' + this._randomString(6);
45
+ sessionStorage.setItem('session_token', this.sessionToken);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * 配对(首次使用)
51
+ */
52
+ async pair(code) {
53
+ return new Promise((resolve, reject) => {
54
+ this._connect((ws) => {
55
+ // 发送配对消息
56
+ ws.send(JSON.stringify({
57
+ type: 'pair',
58
+ code: code
59
+ }));
60
+
61
+ // 等待响应
62
+ const handler = (event) => {
63
+ const msg = JSON.parse(event.data);
64
+
65
+ if (msg.type === 'paired') {
66
+ // 配对成功
67
+ this.token = msg.token;
68
+ this.sessionToken = msg.session_token;
69
+ this.moduleId = msg.module_id;
70
+ this.role = msg.role;
71
+
72
+ // 保存 token
73
+ localStorage.setItem('frontend_token', this.token);
74
+ sessionStorage.setItem('session_token', this.sessionToken);
75
+
76
+ this.authenticated = true;
77
+ ws.removeEventListener('message', handler);
78
+ resolve({
79
+ moduleId: this.moduleId,
80
+ role: this.role
81
+ });
82
+ } else if (msg.type === 'error') {
83
+ // 配对失败
84
+ ws.removeEventListener('message', handler);
85
+ reject(new Error(msg.message));
86
+ }
87
+ };
88
+
89
+ ws.addEventListener('message', handler);
90
+ }, reject);
91
+ });
92
+ }
93
+
94
+ /**
95
+ * 连接(已有 token)
96
+ */
97
+ async connect() {
98
+ if (!this.token) {
99
+ throw new Error('No token found, please pair first');
100
+ }
101
+
102
+ return new Promise((resolve, reject) => {
103
+ this._connect((ws) => {
104
+ // 发送认证消息
105
+ ws.send(JSON.stringify({
106
+ type: 'auth',
107
+ token: this.token,
108
+ session_token: this.sessionToken
109
+ }));
110
+
111
+ // 等待响应
112
+ const handler = (event) => {
113
+ const msg = JSON.parse(event.data);
114
+
115
+ if (msg.type === 'authenticated' || msg.type === 'reconnected') {
116
+ // 认证成功
117
+ this.moduleId = msg.module_id;
118
+ this.role = msg.role;
119
+ this.authenticated = true;
120
+
121
+ ws.removeEventListener('message', handler);
122
+ resolve({
123
+ moduleId: this.moduleId,
124
+ role: this.role,
125
+ reconnected: msg.type === 'reconnected'
126
+ });
127
+ } else if (msg.type === 'error') {
128
+ // 认证失败
129
+ ws.removeEventListener('message', handler);
130
+
131
+ // 如果是 token 无效,清除本地 token
132
+ if (msg.message.includes('Invalid') || msg.message.includes('expired')) {
133
+ localStorage.removeItem('frontend_token');
134
+ this.token = null;
135
+ }
136
+
137
+ reject(new Error(msg.message));
138
+ }
139
+ };
140
+
141
+ ws.addEventListener('message', handler);
142
+ }, reject);
143
+ });
144
+ }
145
+
146
+ /**
147
+ * 调用 RPC
148
+ */
149
+ async call(method, params = {}, timeout = 30000) {
150
+ if (!this.authenticated) {
151
+ throw new Error('Not authenticated');
152
+ }
153
+
154
+ return new Promise((resolve, reject) => {
155
+ const id = `rpc-${++this.rpcId}`;
156
+
157
+ // 发送 RPC 请求
158
+ this.ws.send(JSON.stringify({
159
+ jsonrpc: '2.0',
160
+ id: id,
161
+ method: method,
162
+ params: params
163
+ }));
164
+
165
+ // 设置超时
166
+ const timer = setTimeout(() => {
167
+ this.pendingRpcs.delete(id);
168
+ reject(new Error(`RPC timeout: ${method}`));
169
+ }, timeout);
170
+
171
+ // 保存 pending RPC
172
+ this.pendingRpcs.set(id, { resolve, reject, timeout: timer });
173
+ });
174
+ }
175
+
176
+ /**
177
+ * 订阅事件
178
+ */
179
+ async subscribe(events) {
180
+ return this.call('event.subscribe', { events });
181
+ }
182
+
183
+ /**
184
+ * 监听事件
185
+ */
186
+ on(event, callback) {
187
+ if (!this.eventListeners.has(event)) {
188
+ this.eventListeners.set(event, []);
189
+ }
190
+ this.eventListeners.get(event).push(callback);
191
+ }
192
+
193
+ /**
194
+ * 取消监听事件
195
+ */
196
+ off(event, callback) {
197
+ if (!this.eventListeners.has(event)) return;
198
+
199
+ const listeners = this.eventListeners.get(event);
200
+ const index = listeners.indexOf(callback);
201
+ if (index > -1) {
202
+ listeners.splice(index, 1);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * 断开连接
208
+ */
209
+ disconnect() {
210
+ if (this.reconnectTimer) {
211
+ clearTimeout(this.reconnectTimer);
212
+ this.reconnectTimer = null;
213
+ }
214
+
215
+ this._stopHeartbeat();
216
+
217
+ if (this.ws) {
218
+ // 发送 disconnect 消息
219
+ try {
220
+ this.ws.send(JSON.stringify({ type: 'disconnect' }));
221
+ } catch (e) {
222
+ // Ignore
223
+ }
224
+
225
+ this.ws.close();
226
+ this.ws = null;
227
+ }
228
+
229
+ this.connected = false;
230
+ this.authenticated = false;
231
+ }
232
+
233
+ /**
234
+ * 启动心跳
235
+ */
236
+ _startHeartbeat() {
237
+ this._stopHeartbeat();
238
+ this.heartbeatTimer = setInterval(() => {
239
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
240
+ try {
241
+ // 发送 ping 消息
242
+ this.ws.send(JSON.stringify({ type: 'ping' }));
243
+ } catch (e) {
244
+ console.error('[KernelClient] Heartbeat failed:', e);
245
+ }
246
+ }
247
+ }, this.heartbeatInterval);
248
+ }
249
+
250
+ /**
251
+ * 停止心跳
252
+ */
253
+ _stopHeartbeat() {
254
+ if (this.heartbeatTimer) {
255
+ clearInterval(this.heartbeatTimer);
256
+ this.heartbeatTimer = null;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * 内部:建立 WebSocket 连接
262
+ */
263
+ _connect(onOpen, onError) {
264
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
265
+ onOpen(this.ws);
266
+ return;
267
+ }
268
+
269
+ const ws = new WebSocket(this.url);
270
+
271
+ ws.onopen = () => {
272
+ console.log('[KernelClient] WebSocket connected');
273
+ this.ws = ws;
274
+ this.connected = true;
275
+ this.reconnectAttempts = 0;
276
+ this.reconnectDelay = 1000;
277
+ this._startHeartbeat();
278
+ onOpen(ws);
279
+ };
280
+
281
+ ws.onerror = (error) => {
282
+ console.error('[KernelClient] WebSocket error:', error);
283
+ if (onError) onError(error);
284
+ };
285
+
286
+ ws.onclose = () => {
287
+ console.log('[KernelClient] WebSocket closed');
288
+ this.connected = false;
289
+ this.authenticated = false;
290
+ this.ws = null;
291
+ this._stopHeartbeat();
292
+
293
+ // 自动重连(如果有 token)
294
+ if (this.token) {
295
+ this._scheduleReconnect();
296
+ }
297
+ };
298
+
299
+ ws.onmessage = (event) => {
300
+ this._handleMessage(event.data);
301
+ };
302
+ }
303
+
304
+ /**
305
+ * 内部:处理消息
306
+ */
307
+ _handleMessage(raw) {
308
+ try {
309
+ const msg = JSON.parse(raw);
310
+
311
+ // RPC 响应
312
+ if (msg.id && this.pendingRpcs.has(msg.id)) {
313
+ const { resolve, reject, timeout } = this.pendingRpcs.get(msg.id);
314
+ clearTimeout(timeout);
315
+ this.pendingRpcs.delete(msg.id);
316
+
317
+ if (msg.error) {
318
+ reject(new Error(msg.error.message || 'RPC error'));
319
+ } else {
320
+ resolve(msg.result);
321
+ }
322
+ return;
323
+ }
324
+
325
+ // 事件通知
326
+ if (msg.method === 'event' || !msg.id) {
327
+ const params = msg.params || {};
328
+ const event = params.event;
329
+ const data = params.data || {};
330
+
331
+ if (event && this.eventListeners.has(event)) {
332
+ const listeners = this.eventListeners.get(event);
333
+ listeners.forEach(callback => {
334
+ try {
335
+ callback(data);
336
+ } catch (e) {
337
+ console.error('[KernelClient] Event listener error:', e);
338
+ }
339
+ });
340
+ }
341
+ }
342
+ } catch (e) {
343
+ console.error('[KernelClient] Message parse error:', e);
344
+ }
345
+ }
346
+
347
+ /**
348
+ * 内部:安排重连
349
+ */
350
+ _scheduleReconnect() {
351
+ if (this.reconnectTimer) return;
352
+
353
+ this.reconnectAttempts++;
354
+ const delay = Math.min(
355
+ this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1),
356
+ this.maxReconnectDelay
357
+ );
358
+
359
+ console.log(`[KernelClient] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
360
+
361
+ this.reconnectTimer = setTimeout(() => {
362
+ this.reconnectTimer = null;
363
+ this.connect().catch(err => {
364
+ console.error('[KernelClient] Reconnect failed:', err);
365
+ });
366
+ }, delay);
367
+ }
368
+
369
+ /**
370
+ * 内部:生成随机字符串
371
+ */
372
+ _randomString(length) {
373
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
374
+ let result = '';
375
+ for (let i = 0; i < length; i++) {
376
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
377
+ }
378
+ return result;
379
+ }
380
+ }
381
+
382
+ // 导出
383
+ window.KernelClient = KernelClient;