kiku-agent 1.0.7 → 1.0.9
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/LICENSE +621 -0
- package/README.md +296 -14
- package/package.json +95 -92
- package/server/agent/agentCore.js +58 -31
- package/server/agent/cli.js +79 -43
- package/server/agent/commandFeatures.js +517 -0
- package/server/agent/commandInput.js +113 -0
- package/server/agent/commandRegistry.js +122 -8
- package/server/agent/commands.js +403 -28
- package/server/agent/jobManager.js +96 -0
- package/server/agent/mcpClient.js +68 -11
- package/server/agent/memory.js +87 -14
- package/server/agent/quizData.js +384 -0
- package/server/agent/terminalImage.js +248 -0
- package/server/agent/tools.js +153 -43
- package/server/agent/ui.js +67 -21
- package/server/agent/updateManager.js +21 -3
- package/src/assets/images/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/src/assets/images/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
|
@@ -11,11 +11,53 @@ export class McpClientManager {
|
|
|
11
11
|
constructor(options = {}) {
|
|
12
12
|
this.clientInfo = options.clientInfo || { name: 'kiku-agent', version: PACKAGE_VERSION };
|
|
13
13
|
this.autoLoad = options.autoLoad !== false;
|
|
14
|
+
this.globalConfigPath = options.globalConfigPath || path.join(KIKU_DATA_DIR, 'mcp_servers.json');
|
|
15
|
+
this.configPaths = options.configPaths || [
|
|
16
|
+
this.globalConfigPath,
|
|
17
|
+
path.join(process.cwd(), 'opencode.json'),
|
|
18
|
+
path.join(process.cwd(), '.mcp.json'),
|
|
19
|
+
path.join(process.cwd(), 'mcp.json'),
|
|
20
|
+
];
|
|
14
21
|
this.servers = new Map();
|
|
15
22
|
this.persistedConfigs = new Map();
|
|
23
|
+
this.configSources = new Map();
|
|
16
24
|
this.toolRegistry = new Map();
|
|
17
25
|
}
|
|
18
26
|
|
|
27
|
+
_trackConfigSource(serverName, filePath) {
|
|
28
|
+
const name = String(serverName || '').trim();
|
|
29
|
+
if (!name || !filePath) return;
|
|
30
|
+
const resolvedPath = path.resolve(filePath);
|
|
31
|
+
const sources = this.configSources.get(name) || new Set();
|
|
32
|
+
sources.add(resolvedPath);
|
|
33
|
+
this.configSources.set(name, sources);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async _removeFromConfigFile(filePath, serverName) {
|
|
37
|
+
let content;
|
|
38
|
+
try {
|
|
39
|
+
content = await fs.readFile(filePath, 'utf8');
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error?.code === 'ENOENT') return false;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const parsed = JSON.parse(content);
|
|
46
|
+
let changed = false;
|
|
47
|
+
for (const key of ['mcpServers', 'mcp']) {
|
|
48
|
+
const servers = parsed[key];
|
|
49
|
+
if (servers && typeof servers === 'object' && Object.hasOwn(servers, serverName)) {
|
|
50
|
+
delete servers[serverName];
|
|
51
|
+
changed = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (changed) {
|
|
56
|
+
await fs.writeFile(filePath, `${JSON.stringify(parsed, null, 2)}\n`, 'utf8');
|
|
57
|
+
}
|
|
58
|
+
return changed;
|
|
59
|
+
}
|
|
60
|
+
|
|
19
61
|
async connect(serverName, serverConfig = {}, { persist = true } = {}) {
|
|
20
62
|
const name = String(serverName || '').trim();
|
|
21
63
|
if (!name) throw new Error('Server name is required to connect to MCP.');
|
|
@@ -107,7 +149,8 @@ export class McpClientManager {
|
|
|
107
149
|
this._rebuildToolRegistry();
|
|
108
150
|
|
|
109
151
|
if (persist) {
|
|
110
|
-
await this.savePersistedConfig().catch(() =>
|
|
152
|
+
const saveResult = await this.savePersistedConfig().catch(() => null);
|
|
153
|
+
if (saveResult?.success) this._trackConfigSource(name, saveResult.filePath);
|
|
111
154
|
}
|
|
112
155
|
|
|
113
156
|
return {
|
|
@@ -153,12 +196,25 @@ export class McpClientManager {
|
|
|
153
196
|
}
|
|
154
197
|
|
|
155
198
|
async remove(serverName, { persist = true } = {}) {
|
|
199
|
+
const wasConnected = this.servers.has(serverName);
|
|
156
200
|
await this.disconnect(serverName, { persist: false });
|
|
157
201
|
const hadPersisted = this.persistedConfigs.delete(serverName);
|
|
202
|
+
const sources = [...(this.configSources.get(serverName) || [])];
|
|
203
|
+
let removedFromSource = false;
|
|
204
|
+
|
|
158
205
|
if (persist) {
|
|
159
|
-
|
|
206
|
+
for (const sourcePath of sources) {
|
|
207
|
+
const removed = await this._removeFromConfigFile(sourcePath, serverName);
|
|
208
|
+
removedFromSource = removedFromSource || removed;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const saveResult = await this.savePersistedConfig();
|
|
212
|
+
if (!saveResult.success) {
|
|
213
|
+
throw new Error(`Failed to update global MCP configuration: ${saveResult.error}`);
|
|
214
|
+
}
|
|
160
215
|
}
|
|
161
|
-
|
|
216
|
+
this.configSources.delete(serverName);
|
|
217
|
+
return wasConnected || hadPersisted || removedFromSource;
|
|
162
218
|
}
|
|
163
219
|
|
|
164
220
|
async disconnectAll() {
|
|
@@ -325,6 +381,7 @@ export class McpClientManager {
|
|
|
325
381
|
resourcesCount: server.resources.length,
|
|
326
382
|
promptsCount: server.prompts.length,
|
|
327
383
|
connectedAt: server.connectedAt,
|
|
384
|
+
sources: [...(this.configSources.get(name) || [])],
|
|
328
385
|
config: server.config,
|
|
329
386
|
});
|
|
330
387
|
}
|
|
@@ -339,6 +396,7 @@ export class McpClientManager {
|
|
|
339
396
|
resourcesCount: 0,
|
|
340
397
|
promptsCount: 0,
|
|
341
398
|
error: cfg.lastError || null,
|
|
399
|
+
sources: [...(this.configSources.get(name) || [])],
|
|
342
400
|
config: cfg,
|
|
343
401
|
});
|
|
344
402
|
}
|
|
@@ -353,7 +411,7 @@ export class McpClientManager {
|
|
|
353
411
|
|
|
354
412
|
async savePersistedConfig(customPath) {
|
|
355
413
|
try {
|
|
356
|
-
const filePath = customPath ||
|
|
414
|
+
const filePath = customPath || this.globalConfigPath;
|
|
357
415
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
358
416
|
|
|
359
417
|
const serversObj = {};
|
|
@@ -369,6 +427,9 @@ export class McpClientManager {
|
|
|
369
427
|
}
|
|
370
428
|
|
|
371
429
|
await fs.writeFile(filePath, JSON.stringify({ mcpServers: serversObj }, null, 2), 'utf8');
|
|
430
|
+
for (const name of Object.keys(serversObj)) {
|
|
431
|
+
this._trackConfigSource(name, filePath);
|
|
432
|
+
}
|
|
372
433
|
return { success: true, filePath, count: Object.keys(serversObj).length };
|
|
373
434
|
} catch (err) {
|
|
374
435
|
return { success: false, error: err.message };
|
|
@@ -383,20 +444,15 @@ export class McpClientManager {
|
|
|
383
444
|
async loadPersistedServers() {
|
|
384
445
|
if (this.autoLoad === false) return [];
|
|
385
446
|
const results = [];
|
|
386
|
-
const candidates = [
|
|
387
|
-
path.join(KIKU_DATA_DIR, 'mcp_servers.json'),
|
|
388
|
-
path.join(process.cwd(), 'opencode.json'),
|
|
389
|
-
path.join(process.cwd(), '.mcp.json'),
|
|
390
|
-
path.join(process.cwd(), 'mcp.json'),
|
|
391
|
-
];
|
|
392
447
|
|
|
393
|
-
for (const filePath of
|
|
448
|
+
for (const filePath of this.configPaths) {
|
|
394
449
|
try {
|
|
395
450
|
const content = await fs.readFile(filePath, 'utf8');
|
|
396
451
|
const parsed = JSON.parse(content);
|
|
397
452
|
const serversObj = parsed.mcpServers || parsed.mcp || {};
|
|
398
453
|
for (const [name, cfg] of Object.entries(serversObj)) {
|
|
399
454
|
this.persistedConfigs.set(name, cfg);
|
|
455
|
+
this._trackConfigSource(name, filePath);
|
|
400
456
|
if (cfg.enabled === false || cfg.disabled === true) continue;
|
|
401
457
|
if (this.servers.has(name)) continue;
|
|
402
458
|
try {
|
|
@@ -422,6 +478,7 @@ export class McpClientManager {
|
|
|
422
478
|
|
|
423
479
|
for (const [name, cfg] of Object.entries(serversObj)) {
|
|
424
480
|
this.persistedConfigs.set(name, cfg);
|
|
481
|
+
this._trackConfigSource(name, resolvedPath);
|
|
425
482
|
if (cfg.enabled === false || cfg.disabled === true) continue;
|
|
426
483
|
try {
|
|
427
484
|
const res = await this.connect(name, cfg);
|
package/server/agent/memory.js
CHANGED
|
@@ -13,9 +13,10 @@ export class AgentMemory {
|
|
|
13
13
|
this.preferences = {
|
|
14
14
|
language: null,
|
|
15
15
|
topicsOfInterest: [],
|
|
16
|
-
responseStyle: 'default',
|
|
17
|
-
userSkills: [],
|
|
18
|
-
skillsEnabled: true,
|
|
16
|
+
responseStyle: 'default',
|
|
17
|
+
userSkills: [],
|
|
18
|
+
skillsEnabled: true,
|
|
19
|
+
favorites: [],
|
|
19
20
|
};
|
|
20
21
|
this.sessions = [];
|
|
21
22
|
this.summaries = [];
|
|
@@ -68,9 +69,10 @@ export class AgentMemory {
|
|
|
68
69
|
const loadedPrefs = parsed.preferences || {};
|
|
69
70
|
this.preferences = {
|
|
70
71
|
...this.preferences,
|
|
71
|
-
...loadedPrefs,
|
|
72
|
-
userSkills: Array.isArray(loadedPrefs.userSkills) ? loadedPrefs.userSkills : [],
|
|
73
|
-
|
|
72
|
+
...loadedPrefs,
|
|
73
|
+
userSkills: Array.isArray(loadedPrefs.userSkills) ? loadedPrefs.userSkills : [],
|
|
74
|
+
favorites: Array.isArray(loadedPrefs.favorites) ? loadedPrefs.favorites : [],
|
|
75
|
+
topicsOfInterest: (loadedPrefs.topicsOfInterest || []).map(t => ({
|
|
74
76
|
...t,
|
|
75
77
|
lastSeen: t.lastSeen ? new Date(t.lastSeen) : new Date()
|
|
76
78
|
}))
|
|
@@ -238,10 +240,78 @@ export class AgentMemory {
|
|
|
238
240
|
return grouped;
|
|
239
241
|
}
|
|
240
242
|
|
|
241
|
-
setPreference(key, value) {
|
|
242
|
-
this.preferences[key] = value;
|
|
243
|
-
this.save().catch(() => {});
|
|
244
|
-
}
|
|
243
|
+
setPreference(key, value) {
|
|
244
|
+
this.preferences[key] = value;
|
|
245
|
+
this.save().catch(() => {});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
addFavorite(entity = {}) {
|
|
249
|
+
const allowedTypes = ['work', 'song', 'role', 'brand', 'award', 'photo', 'stage'];
|
|
250
|
+
const type = String(entity.type || '').toLowerCase();
|
|
251
|
+
const title = String(entity.title || '').replace(/[\u0000-\u001f\u007f]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
252
|
+
const entityId = String(entity.entityId || title).replace(/[\u0000-\u001f\u007f]/g, '').trim().slice(0, 200);
|
|
253
|
+
if (!allowedTypes.includes(type)) throw new Error(`Favorite type must be one of: ${allowedTypes.join(', ')}.`);
|
|
254
|
+
if (!title || !entityId) throw new Error('Favorite title and entity ID are required.');
|
|
255
|
+
if (!Array.isArray(this.preferences.favorites)) this.preferences.favorites = [];
|
|
256
|
+
const duplicate = this.preferences.favorites.find(item => item.type === type && item.entityId.toLowerCase() === entityId.toLowerCase());
|
|
257
|
+
if (duplicate) return { favorite: duplicate, created: false };
|
|
258
|
+
const favorite = {
|
|
259
|
+
id: `fav_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
|
|
260
|
+
type,
|
|
261
|
+
entityId,
|
|
262
|
+
title,
|
|
263
|
+
year: Number.isInteger(Number(entity.year)) ? Number(entity.year) : null,
|
|
264
|
+
detail: String(entity.detail || '').replace(/[\u0000-\u001f\u007f]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 300),
|
|
265
|
+
url: /^https?:\/\//i.test(String(entity.url || '')) ? String(entity.url).slice(0, 1000) : null,
|
|
266
|
+
createdAt: new Date().toISOString(),
|
|
267
|
+
};
|
|
268
|
+
this.preferences.favorites.unshift(favorite);
|
|
269
|
+
this.preferences.favorites = this.preferences.favorites.slice(0, 200);
|
|
270
|
+
this.save().catch(() => {});
|
|
271
|
+
return { favorite, created: true };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
getFavorites(type) {
|
|
275
|
+
const favorites = Array.isArray(this.preferences.favorites) ? this.preferences.favorites : [];
|
|
276
|
+
if (!type || type === 'all') return [...favorites];
|
|
277
|
+
return favorites.filter(item => item.type === String(type).toLowerCase());
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
removeFavorite(id) {
|
|
281
|
+
if (!/^fav_[a-zA-Z0-9_]+$/.test(String(id || ''))) return false;
|
|
282
|
+
const favorites = Array.isArray(this.preferences.favorites) ? this.preferences.favorites : [];
|
|
283
|
+
const before = favorites.length;
|
|
284
|
+
this.preferences.favorites = favorites.filter(item => item.id !== id);
|
|
285
|
+
if (this.preferences.favorites.length === before) return false;
|
|
286
|
+
this.save().catch(() => {});
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async exportFavorites(format = 'json') {
|
|
291
|
+
const normalized = String(format || 'json').toLowerCase();
|
|
292
|
+
if (!['json', 'markdown'].includes(normalized)) throw new Error('Favorite export format must be json or markdown.');
|
|
293
|
+
await fs.mkdir(EXPORT_DIR, { recursive: true });
|
|
294
|
+
const now = new Date();
|
|
295
|
+
const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
296
|
+
const extension = normalized === 'markdown' ? 'md' : 'json';
|
|
297
|
+
const filename = `kiku-favorites-${timestamp}.${extension}`;
|
|
298
|
+
const filePath = path.join(EXPORT_DIR, filename);
|
|
299
|
+
const favorites = this.getFavorites();
|
|
300
|
+
const content = normalized === 'json'
|
|
301
|
+
? JSON.stringify({ exportedAt: now.toISOString(), count: favorites.length, favorites }, null, 2)
|
|
302
|
+
: [
|
|
303
|
+
'# Kiku Agent Favorites',
|
|
304
|
+
'',
|
|
305
|
+
`Exported: ${now.toISOString()}`,
|
|
306
|
+
'',
|
|
307
|
+
'| ID | Type | Title | Year | Detail |',
|
|
308
|
+
'|---|---|---|---:|---|',
|
|
309
|
+
...favorites.map(item => `| ${item.id} | ${item.type} | ${String(item.title).replace(/\|/g, '\\|')} | ${item.year || ''} | ${String(item.detail || '').replace(/\|/g, '\\|')} |`),
|
|
310
|
+
'',
|
|
311
|
+
].join('\n');
|
|
312
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
313
|
+
return { filePath, filename, count: favorites.length, size: (await fs.stat(filePath)).size };
|
|
314
|
+
}
|
|
245
315
|
|
|
246
316
|
addUserSkill(instruction) {
|
|
247
317
|
const value = String(instruction || '').replace(/\s+/g, ' ').trim();
|
|
@@ -633,10 +703,13 @@ export class AgentMemory {
|
|
|
633
703
|
|
|
634
704
|
async clearAll() {
|
|
635
705
|
this.facts = [];
|
|
636
|
-
this.preferences = {
|
|
637
|
-
language: null,
|
|
638
|
-
topicsOfInterest: [],
|
|
639
|
-
responseStyle: 'default'
|
|
706
|
+
this.preferences = {
|
|
707
|
+
language: null,
|
|
708
|
+
topicsOfInterest: [],
|
|
709
|
+
responseStyle: 'default',
|
|
710
|
+
userSkills: [],
|
|
711
|
+
skillsEnabled: true,
|
|
712
|
+
favorites: [],
|
|
640
713
|
};
|
|
641
714
|
this.sessions = [];
|
|
642
715
|
this.summaries = [];
|