kiku-agent 1.2.0 → 1.3.1

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 (131) hide show
  1. package/README.md +459 -407
  2. package/package.json +32 -3
  3. package/server/agent/agentCore.js +635 -267
  4. package/server/agent/appPaths.js +2 -0
  5. package/server/agent/catalogEntityResolver.js +637 -0
  6. package/server/agent/citations.js +0 -21
  7. package/server/agent/clarificationAnalytics.js +1 -1
  8. package/server/agent/clarifyingQuestions.js +286 -216
  9. package/server/agent/cli.js +532 -73
  10. package/server/agent/commandFeatures.js +123 -24
  11. package/server/agent/commandRegistry.js +128 -62
  12. package/server/agent/commands.js +1410 -95
  13. package/server/agent/config.js +253 -163
  14. package/server/agent/contextCompactor.js +239 -25
  15. package/server/agent/dynamicCatalog.js +192 -0
  16. package/server/agent/envBootstrap.js +46 -0
  17. package/server/agent/envManager.js +317 -0
  18. package/server/agent/eventBus.js +3 -3
  19. package/server/agent/feedbackLearning.js +0 -8
  20. package/server/agent/gpuMetrics.js +188 -55
  21. package/server/agent/ingestionWorker.js +339 -0
  22. package/server/agent/ink/App.jsx +348 -0
  23. package/server/agent/ink/Panel.jsx +54 -0
  24. package/server/agent/ink/PromptInput.jsx +321 -0
  25. package/server/agent/ink/TaskPlanPanel.jsx +88 -0
  26. package/server/agent/ink/ThinkingPanel.jsx +68 -0
  27. package/server/agent/ink/ToolResult.jsx +133 -0
  28. package/server/agent/ink/main.jsx +195 -0
  29. package/server/agent/ink/useEventBus.js +218 -0
  30. package/server/agent/lanDiscovery.js +369 -0
  31. package/server/agent/llmClient.js +111 -39
  32. package/server/agent/lmStudioWakeup.js +144 -0
  33. package/server/agent/mcpClient.js +2 -2
  34. package/server/agent/memory/contextAllocator.js +214 -0
  35. package/server/agent/memory/hierarchicalStore.js +523 -0
  36. package/server/agent/memory/reflectionWorker.js +110 -0
  37. package/server/agent/memory.js +635 -494
  38. package/server/agent/modelConfig.js +487 -0
  39. package/server/agent/newsIntelligence.js +219 -0
  40. package/server/agent/newsWatch.js +85 -0
  41. package/server/agent/prompts.js +146 -0
  42. package/server/agent/publicTools.js +7 -0
  43. package/server/agent/queryDetails.js +62 -1
  44. package/server/agent/research.js +314 -35
  45. package/server/agent/routingInterceptor.js +77 -0
  46. package/server/agent/rumoredProjects.js +15 -0
  47. package/server/agent/sanitizer.js +117 -17
  48. package/server/agent/subagent.js +52 -9
  49. package/server/agent/suggestionTui.js +33 -13
  50. package/server/agent/suggestions.js +29 -13
  51. package/server/agent/telemetry/hardwareTiers.js +332 -0
  52. package/server/agent/telemetry/runtimeWatchdog.js +66 -0
  53. package/server/agent/telemetry/techPowerUpClient.js +682 -0
  54. package/server/agent/telemetry/techpowerup-mcp.json +22 -0
  55. package/server/agent/telemetry.js +0 -11
  56. package/server/agent/terminalImage.js +0 -1
  57. package/server/agent/textToolProtocol.js +190 -0
  58. package/server/agent/tokenTracker.js +220 -0
  59. package/server/agent/toolRouter.js +362 -216
  60. package/server/agent/tools.js +1155 -194
  61. package/server/agent/ui.js +144 -41
  62. package/server/agent/vramService.js +572 -0
  63. package/server/agent/webSearch.js +2279 -273
  64. package/server/agent/websearch/adapters/duckduckgo.js +97 -0
  65. package/server/agent/websearch/adapters/jina.js +77 -0
  66. package/server/agent/websearch/adapters/searxng.js +57 -0
  67. package/server/agent/websearch/core/canonical.js +89 -0
  68. package/server/agent/websearch/core/fusion.js +81 -0
  69. package/server/agent/websearch/core/types.js +29 -0
  70. package/server/agent/websearch/extract.js +605 -0
  71. package/server/agent/websearch/index.js +43 -0
  72. package/server/agent/websearch/orchestrator.js +266 -0
  73. package/server/agent/websearch/resilience/breaker.js +97 -0
  74. package/server/agent/websearch/resilience/cache.js +59 -0
  75. package/server/agent/websearch/resilience/limiter.js +93 -0
  76. package/server/agent/websearch/security/filter.js +36 -0
  77. package/server/agent/websearch/security/ssrf.js +289 -0
  78. package/server/agent/websearch/telemetry.js +71 -0
  79. package/server/agent/youtubeAuth.js +82 -73
  80. package/server/agent/youtubeTranscript.js +209 -0
  81. package/server/apiRouter.js +390 -190
  82. package/server/mcp/data/brands.js +5 -5
  83. package/server/mcp/data/discography.js +56 -45
  84. package/server/mcp/data/gallery.js +9 -0
  85. package/server/mcp/data/index.js +1 -0
  86. package/server/mcp/data/industryIndex.js +251 -0
  87. package/server/mcp/data/profile.js +5 -5
  88. package/server/mcp/data/timeline.js +2 -2
  89. package/server/mcp/data/works.js +2 -2
  90. package/server/mcp/handler.js +0 -2
  91. package/server/mcp/prompts.js +42 -2
  92. package/server/mcp/resources.js +5 -1
  93. package/server/mcp/security.js +25 -14
  94. package/server/mcp/server.js +92 -3
  95. package/server/mcp/tools.js +20 -11
  96. package/server/routes/agent.js +55 -0
  97. package/server/web-chat/core/connection.js +218 -194
  98. package/server/web-chat/core/conversationContext.js +1 -1
  99. package/server/web-chat/core/directCatalogEngine.js +190 -184
  100. package/server/web-chat/core/followUpEngine.js +88 -11
  101. package/server/web-chat/core/security.js +7 -4
  102. package/server/web-chat/router.js +54 -17
  103. package/server/web-chat/routes/bilibiliRoute.js +34 -0
  104. package/server/web-chat/routes/chatRoute.js +488 -397
  105. package/server/web-chat/routes/clarifyConfigRoute.js +1 -2
  106. package/server/web-chat/routes/exportRoute.js +2 -1
  107. package/server/web-chat/routes/lanDiscoveryRoute.js +90 -0
  108. package/server/web-chat/routes/newsRoute.js +36 -6
  109. package/server/web-chat/routes/providerRoute.js +87 -0
  110. package/server/web-chat/routes/staticRoute.js +61 -13
  111. package/server/web-chat/routes/statusRoute.js +23 -2
  112. package/server/web-chat/routes/vramRoute.js +22 -0
  113. package/server/web-chat/server.js +135 -106
  114. package/server/web-chat/ui/htmlBuilder.js +8 -8
  115. package/server/web-chat/ui/previews/modelSettings.preview.html +41 -0
  116. package/server/web-chat/ui/scripts/agentBubbleSystem.js +0 -5
  117. package/server/web-chat/ui/scripts/client.js +10526 -8397
  118. package/server/web-chat/ui/scripts/webchatI18n.js +190 -138
  119. package/server/web-chat/ui/styles/main.css +7471 -4246
  120. package/server/web-chat/ui/templates/body.html +305 -179
  121. package/src/data/brandsData.js +29 -6
  122. package/src/data/discographyData.js +918 -900
  123. package/src/data/galleryData.js +66 -0
  124. package/src/data/industryIndexData.js +292 -0
  125. package/src/data/musicVideoData.js +48 -5
  126. package/src/data/profileData.js +15 -1
  127. package/src/data/timelineData.js +4 -3
  128. package/src/data/worksData.js +536 -535
  129. package/src/lib/vram/estimator.js +464 -0
  130. package/src/lib/vram/modelRegistry.js +387 -0
  131. package/tokens.css +33 -2
@@ -0,0 +1,144 @@
1
+ import { spawn } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import fs from 'node:fs';
5
+
6
+ export async function isLmStudioResponding(baseUrl = 'http://127.0.0.1:1234/v1', timeoutMs = 2500) {
7
+ try {
8
+ const root = baseUrl.replace(/\/v1\/?$/, '').replace(/\/+$/, '');
9
+ const res = await fetch(`${root}/v1/models`, { signal: AbortSignal.timeout(timeoutMs) });
10
+ return res.ok;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ export async function getLoadedModels(baseUrl = 'http://127.0.0.1:1234/v1', timeoutMs = 2500) {
17
+ try {
18
+ const root = baseUrl.replace(/\/v1\/?$/, '').replace(/\/+$/, '');
19
+ const res = await fetch(`${root}/v1/models`, { signal: AbortSignal.timeout(timeoutMs) });
20
+ if (!res.ok) return [];
21
+ const data = await res.json();
22
+ return Array.isArray(data.data) ? data.data.map(m => m.id) : [];
23
+ } catch {
24
+ return [];
25
+ }
26
+ }
27
+
28
+ export function resolveLmsCli() {
29
+ const userHome = os.homedir();
30
+ const isWin = process.platform === 'win32';
31
+ const candidates = [
32
+ path.join(userHome, '.lmstudio', 'bin', isWin ? 'lms.exe' : 'lms'),
33
+ isWin ? 'lms.exe' : 'lms'
34
+ ];
35
+ return candidates.find(c => {
36
+ try { return fs.existsSync(c); } catch { return false; }
37
+ }) || (isWin ? 'lms.exe' : 'lms');
38
+ }
39
+
40
+ export async function loadLmStudioModel(model, options = {}) {
41
+ const lmsCmd = resolveLmsCli();
42
+ return new Promise((resolve) => {
43
+ const args = ['load', model, '-y', '--identifier', model];
44
+ const child = spawn(lmsCmd, args, {
45
+ windowsHide: true,
46
+ stdio: 'pipe',
47
+ shell: process.platform === 'win32'
48
+ });
49
+ let output = '';
50
+ child.stdout?.on('data', d => { output += d.toString(); });
51
+ child.stderr?.on('data', d => { output += d.toString(); });
52
+ child.on('close', code => {
53
+ resolve({ ok: code === 0, output });
54
+ });
55
+ child.on('error', err => {
56
+ resolve({ ok: false, error: err.message });
57
+ });
58
+ });
59
+ }
60
+
61
+ export async function wakeUpLmStudio(options = {}) {
62
+ const {
63
+ baseUrl = 'http://127.0.0.1:1234/v1',
64
+ model = 'google/gemma-4-e4b',
65
+ maxWaitMs = 35000
66
+ } = options;
67
+
68
+ const isLocal = baseUrl.includes('127.0.0.1') || baseUrl.includes('localhost');
69
+ if (!isLocal) {
70
+ return { ok: false, reason: 'Non-local endpoint, automatic wakeup skipped' };
71
+ }
72
+
73
+
74
+ let serverUp = await isLmStudioResponding(baseUrl, 2000);
75
+ if (!serverUp) {
76
+ const userHome = os.homedir();
77
+ const possiblePaths = [
78
+ path.join(userHome, 'AppData', 'Local', 'Programs', 'LM Studio', 'LM Studio.exe'),
79
+ path.join(process.env.LOCALAPPDATA || '', 'Programs', 'LM Studio', 'LM Studio.exe'),
80
+ 'C:\\Program Files\\LM Studio\\LM Studio.exe'
81
+ ];
82
+
83
+ const appExe = possiblePaths.find(p => p && fs.existsSync(p));
84
+ if (appExe) {
85
+ try {
86
+ const child = spawn(appExe, [], {
87
+ detached: true,
88
+ stdio: 'ignore',
89
+ windowsHide: false
90
+ });
91
+ child.unref();
92
+ } catch {}
93
+ }
94
+
95
+ try {
96
+ const lmsCmd = resolveLmsCli();
97
+ const lmsChild = spawn(lmsCmd, ['server', 'start'], {
98
+ detached: true,
99
+ stdio: 'ignore',
100
+ windowsHide: true,
101
+ shell: process.platform === 'win32'
102
+ });
103
+ lmsChild.unref();
104
+ } catch {}
105
+
106
+ const start = Date.now();
107
+ while (Date.now() - start < maxWaitMs) {
108
+ await new Promise(r => setTimeout(r, 1500));
109
+ if (await isLmStudioResponding(baseUrl, 2000)) {
110
+ serverUp = true;
111
+ break;
112
+ }
113
+ }
114
+ }
115
+
116
+ if (!serverUp) {
117
+ return { ok: false, reason: 'Timed out waiting for LM Studio server' };
118
+ }
119
+
120
+
121
+ if (model) {
122
+ const loaded = await getLoadedModels(baseUrl);
123
+ if (!loaded.includes(model)) {
124
+ try {
125
+ process.stdout.write(`\n🌸 [Kiku Auto-Wakeup] LM Studio server active. Auto-loading model "${model}" into VRAM...\n`);
126
+ } catch {}
127
+ await loadLmStudioModel(model);
128
+
129
+ const start = Date.now();
130
+ while (Date.now() - start < maxWaitMs) {
131
+ await new Promise(r => setTimeout(r, 1500));
132
+ const currentModels = await getLoadedModels(baseUrl);
133
+ if (currentModels.includes(model)) {
134
+ try {
135
+ process.stdout.write(`🌸 [Kiku Auto-Wakeup] Model "${model}" loaded and ready!\n\n`);
136
+ } catch {}
137
+ return { ok: true, awakened: true, modelLoaded: true };
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ return { ok: true, alreadyRunning: true };
144
+ }
@@ -185,7 +185,6 @@ export class McpClientManager {
185
185
  let resources = serverConfig.resources || [];
186
186
  let prompts = serverConfig.prompts || [];
187
187
 
188
- // If tools not pre-provided, fetch once upon connection
189
188
  if (!tools || tools.length === 0) {
190
189
  try {
191
190
  if (!this._isRateLimited(name)) {
@@ -570,7 +569,6 @@ export class McpClientManager {
570
569
  if (cfg.enabled === false || cfg.disabled === true) continue;
571
570
  if (this.servers.has(name)) continue;
572
571
 
573
- // If tools are cached or it's a known Kiku remote MCP server, register lazily without network call on startup
574
572
  let tools = cfg.tools;
575
573
  if (!tools || tools.length === 0) {
576
574
  if (name === 'kiku-mcp' || /jujingyi-kikuuu|vercel\.app/i.test(cfg.url || '')) {
@@ -641,3 +639,5 @@ export class McpClientManager {
641
639
  return results;
642
640
  }
643
641
  }
642
+
643
+ export const mcpClientManager = new McpClientManager();
@@ -0,0 +1,214 @@
1
+ export function extractWordTokens(text) {
2
+ if (!text || typeof text !== 'string') return [];
3
+ return text
4
+ .toLowerCase()
5
+ .replace(/[^\p{L}\p{N}\s]+/gu, ' ')
6
+ .split(/\s+/)
7
+ .filter(t => t.length >= 2);
8
+ }
9
+
10
+ export function buildTermFrequencyVector(tokens) {
11
+ const vec = {};
12
+ for (const t of tokens) {
13
+ vec[t] = (vec[t] || 0) + 1;
14
+ }
15
+ return vec;
16
+ }
17
+
18
+ export function computeCosineSimilarity(vecA, vecB) {
19
+ let dotProduct = 0;
20
+ let normA = 0;
21
+ let normB = 0;
22
+
23
+ for (const [key, val] of Object.entries(vecA)) {
24
+ normA += val * val;
25
+ if (vecB[key]) {
26
+ dotProduct += val * vecB[key];
27
+ }
28
+ }
29
+
30
+ for (const val of Object.values(vecB)) {
31
+ normB += val * val;
32
+ }
33
+
34
+ if (normA === 0 || normB === 0) return 0;
35
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
36
+ }
37
+
38
+ export class ContextAllocator {
39
+ constructor(options = {}) {
40
+ this.store = options.store;
41
+ this.defaultMaxTokens = options.maxTokens || 2000;
42
+ }
43
+
44
+ estimateTokens(text) {
45
+ if (!text) return 0;
46
+
47
+ return Math.ceil(text.length / 3.5);
48
+ }
49
+
50
+ truncateToTokenLimit(text, tokenLimit) {
51
+ if (!text) return '';
52
+ const charLimit = Math.floor(tokenLimit * 3.5);
53
+ if (text.length <= charLimit) return text;
54
+ return text.slice(0, charLimit - 15) + '\n...[truncated]';
55
+ }
56
+
57
+
58
+ async allocateContext({ currentQuery = '', activeRunId = null, memory = null, maxTokens = null } = {}) {
59
+ const budgetTokens = maxTokens || this.defaultMaxTokens;
60
+ const quota = {
61
+ user: Math.floor(budgetTokens * 0.20),
62
+ run: Math.floor(budgetTokens * 0.40),
63
+ reflections: Math.floor(budgetTokens * 0.20),
64
+ general: Math.floor(budgetTokens * 0.20),
65
+ };
66
+
67
+ const sections = [];
68
+
69
+
70
+ const userParts = [];
71
+ if (this.store) {
72
+ const guidelineNodes = await this.store.listNodes('kiku/guidelines/');
73
+ for (const node of guidelineNodes) {
74
+ const domain = node.domain || 'general';
75
+ const content = typeof node.content === 'string' ? node.content : JSON.stringify(node.content);
76
+ userParts.push(`[Guideline: ${domain}]\n${content}`);
77
+ }
78
+ }
79
+
80
+ if (memory?.preferences?.language) {
81
+ userParts.push(`User preferred language: ${memory.preferences.language}`);
82
+ }
83
+
84
+ const userSkillContext = memory?.buildUserSkillContext ? memory.buildUserSkillContext() : '';
85
+ if (userSkillContext) {
86
+ userParts.push(`User-taught skills:\n${userSkillContext}`);
87
+ }
88
+
89
+ const feedbackStyleContext = memory?.buildFeedbackStyleContext ? memory.buildFeedbackStyleContext() : '';
90
+ if (feedbackStyleContext) {
91
+ userParts.push(`Learned answer-style preferences:\n${feedbackStyleContext}`);
92
+ }
93
+
94
+ if (userParts.length > 0) {
95
+ const userText = this.truncateToTokenLimit(userParts.join('\n\n'), quota.user);
96
+ sections.push(`--- USER GUIDELINES & PREFERENCES (20% Quota) ---\n${userText}`);
97
+ }
98
+
99
+
100
+ if (activeRunId && this.store) {
101
+ const runData = await this.store.loadActiveRun(activeRunId);
102
+ if (runData.exists) {
103
+ const runParts = [];
104
+ runParts.push(`Active Run ID: ${activeRunId}`);
105
+
106
+ if (runData.state) {
107
+ runParts.push(`Status: ${runData.state.status || 'IN_PROGRESS'}`);
108
+ if (runData.state.currentStep) {
109
+ runParts.push(`Current Step: ${runData.state.currentStep}`);
110
+ }
111
+ if (runData.state.lastSummary) {
112
+ runParts.push(`Latest Step Summary: ${runData.state.lastSummary}`);
113
+ }
114
+ if (runData.state.lastFailure) {
115
+ runParts.push(`Recent Failure Alert: ${runData.state.lastFailure.errorTrace} -> Recovery: ${runData.state.lastFailure.recoveryAction}`);
116
+ }
117
+ }
118
+
119
+ if (runData.plan && Array.isArray(runData.plan.subtasks) && runData.plan.subtasks.length > 0) {
120
+ const checklist = runData.plan.subtasks.map(t => {
121
+ const marker = t.status === 'completed' ? '[x]' : (t.status === 'in_progress' ? '[>]' : (t.status === 'failed' ? '[!]' : '[ ]'));
122
+ const summaryText = t.summary ? ` - ${t.summary}` : '';
123
+ return `${marker} ${t.name} (${t.status})${summaryText}`;
124
+ }).join('\n');
125
+ runParts.push(`Execution Plan Checklist:\n${checklist}`);
126
+ }
127
+
128
+ if (runData.artifacts && Array.isArray(runData.artifacts.items) && runData.artifacts.items.length > 0) {
129
+ const artifactList = runData.artifacts.items.map(a => `- ${a.name}: ${a.uri}`).join('\n');
130
+ runParts.push(`Artifact Pointers:\n${artifactList}`);
131
+ }
132
+
133
+ const runText = this.truncateToTokenLimit(runParts.join('\n\n'), quota.run);
134
+ sections.push(`--- ACTIVE TASK RUN STATE (40% Quota) ---\n${runText}`);
135
+ }
136
+ }
137
+
138
+
139
+ if (this.store) {
140
+ const reflectionNodes = await this.store.listNodes('kiku/reflections/');
141
+ if (reflectionNodes.length > 0) {
142
+ const queryTokens = extractWordTokens(currentQuery);
143
+ const queryVector = buildTermFrequencyVector(queryTokens);
144
+
145
+ const scored = reflectionNodes.map(node => {
146
+ const content = node.content || {};
147
+ const textToMatch = [
148
+ node.domain || '',
149
+ content.issuePattern || '',
150
+ content.errorTrace || '',
151
+ content.recoveryAction || '',
152
+ content.lesson || '',
153
+ content.summary || ''
154
+ ].join(' ');
155
+
156
+ const nodeTokens = extractWordTokens(textToMatch);
157
+ const nodeVector = buildTermFrequencyVector(nodeTokens);
158
+ const similarity = computeCosineSimilarity(queryVector, nodeVector);
159
+
160
+ return {
161
+ node,
162
+ similarity,
163
+ confidence: Number(content.confidence) || 1.0,
164
+ score: similarity * (Number(content.confidence) || 1.0)
165
+ };
166
+ });
167
+
168
+ scored.sort((a, b) => b.score - a.score);
169
+
170
+
171
+ const positiveScored = scored.filter(s => s.score > 0.05).slice(0, 4);
172
+ const topReflections = positiveScored.length > 0 ? positiveScored : scored.slice(0, 2);
173
+
174
+ const refLines = topReflections.map(item => {
175
+ const c = item.node.content || {};
176
+ const title = item.node.domain ? `[Reflection: ${item.node.domain}]` : '[Reflection]';
177
+ const issue = c.issuePattern ? `Problem: ${c.issuePattern}` : (c.errorTrace ? `Error: ${c.errorTrace}` : '');
178
+ const solution = c.recoveryAction ? `Solution: ${c.recoveryAction}` : (c.lesson ? `Lesson: ${c.lesson}` : (c.summary || ''));
179
+ return `${title}\n${issue}\n${solution}`.trim();
180
+ }).filter(Boolean);
181
+
182
+ if (refLines.length > 0) {
183
+ const refText = this.truncateToTokenLimit(refLines.join('\n\n'), quota.reflections);
184
+ sections.push(`--- EPISODIC REFLECTIONS & GOTCHAS (20% Quota) ---\n${refText}`);
185
+ }
186
+ }
187
+ }
188
+
189
+
190
+ const generalParts = [];
191
+ if (memory?.getRelevantFacts) {
192
+ const facts = currentQuery ? memory.getRelevantFacts(currentQuery, 6) : (memory.facts || []).slice(-6);
193
+ if (facts.length > 0) {
194
+ generalParts.push('Known facts:\n' + facts.map(f => `- ${f.content}`).join('\n'));
195
+ }
196
+ }
197
+
198
+ if (memory?.getTopTopics) {
199
+ const topTopics = memory.getTopTopics(3);
200
+ if (topTopics.length > 0) {
201
+ generalParts.push('Frequent topics: ' + topTopics.map(t => `${t.topic} (${t.count}x)`).join(', '));
202
+ }
203
+ }
204
+
205
+ if (generalParts.length > 0) {
206
+ const genText = this.truncateToTokenLimit(generalParts.join('\n\n'), quota.general);
207
+ sections.push(`--- GENERAL KNOWLEDGE & CONTEXT (20% Quota) ---\n${genText}`);
208
+ }
209
+
210
+ if (sections.length === 0) return '';
211
+
212
+ return `\n\n=== STATEFUL & SELF-REFLECTIVE MEMORY CONTEXT ===\n${sections.join('\n\n')}\n=== END MEMORY CONTEXT ===`;
213
+ }
214
+ }