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,209 @@
1
+ import { spawn } from 'child_process';
2
+ import { mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { ensureYouTubeCookies, isCookieFileValid } from './youtubeAuth.js';
6
+
7
+ const YTDLP_CMD = process.env.KIKU_YTDLP || 'yt-dlp';
8
+
9
+ const BASE_SUB_LANGS = ['th', 'en', 'zh-Hans', 'zh', 'zh-Hant', 'zh-CN', 'zh-TW', 'en-US'];
10
+ const LANG_PREFERENCES = {
11
+ th: ['th', 'th-TH'],
12
+ en: ['en', 'en-US', 'en-GB'],
13
+ zh: ['zh-Hans', 'zh-CN', 'zh', 'zh-Hant', 'zh-TW'],
14
+ };
15
+
16
+ export function extractYouTubeVideoId(input) {
17
+ if (!input || typeof input !== 'string') return null;
18
+ const trimmed = input.trim();
19
+ if (/^[\w-]{11}$/.test(trimmed)) return trimmed;
20
+ const patterns = [
21
+ /youtube\.com\/watch\?(?:[^#]*&)?v=([\w-]{11})/i,
22
+ /youtu\.be\/([\w-]{11})/i,
23
+ /youtube\.com\/shorts\/([\w-]{11})/i,
24
+ /youtube\.com\/embed\/([\w-]{11})/i,
25
+ /youtube\.com\/live\/([\w-]{11})/i,
26
+ ];
27
+ for (const re of patterns) {
28
+ const match = trimmed.match(re);
29
+ if (match) return match[1];
30
+ }
31
+ return null;
32
+ }
33
+
34
+
35
+ export function parseSubtitleToText(content) {
36
+ if (!content || typeof content !== 'string') return '';
37
+ const text = content.startsWith('\uFEFF') ? content.slice(1) : content;
38
+ const lines = text.split(/\r?\n/);
39
+
40
+ const out = [];
41
+ const seen = new Set();
42
+ for (let i = 0; i < lines.length; i++) {
43
+ let l = lines[i].trim();
44
+ if (!l) continue;
45
+ if (/^WEBVTT/i.test(l) || /^(NOTE|STYLE|REGION)\b/i.test(l)) continue;
46
+ if (l.includes('-->')) continue;
47
+
48
+ if (/^\d+$/.test(l)) {
49
+ let j = i + 1;
50
+ while (j < lines.length && !lines[j].trim()) j++;
51
+ if (j < lines.length && lines[j].includes('-->')) continue;
52
+ }
53
+ l = l.replace(/<[^>]+>/g, '');
54
+ l = l
55
+ .replace(/&amp;/g, '&')
56
+ .replace(/&#39;/g, "'")
57
+ .replace(/&apos;/g, "'")
58
+ .replace(/&quot;/g, '"')
59
+ .replace(/&gt;/g, '>')
60
+ .replace(/&lt;/g, '<')
61
+ .replace(/&nbsp;/g, ' ');
62
+ if (!l) continue;
63
+ const key = l.toLowerCase();
64
+ if (seen.has(key)) continue;
65
+ seen.add(key);
66
+ out.push(l);
67
+ }
68
+ return out.join(' ');
69
+ }
70
+
71
+ async function resolveCookieArgs() {
72
+ if (process.env.KIKU_YT_COOKIES && isCookieFileValid(process.env.KIKU_YT_COOKIES)) {
73
+ return ['--cookies', process.env.KIKU_YT_COOKIES];
74
+ }
75
+ if (process.env.KIKU_YT_BROWSER) {
76
+ return ['--cookies-from-browser', process.env.KIKU_YT_BROWSER];
77
+ }
78
+ try {
79
+ const status = await ensureYouTubeCookies({ silent: true });
80
+ if (status?.ok) {
81
+ if (status.cookiesPath) return ['--cookies', status.cookiesPath];
82
+ if (status.browser) return ['--cookies-from-browser', status.browser];
83
+ }
84
+ } catch { }
85
+ return [];
86
+ }
87
+
88
+ function runYtDlp(args, timeoutMs) {
89
+ return new Promise((resolve, reject) => {
90
+ const proc = spawn(YTDLP_CMD, args, { windowsHide: true });
91
+ let stdout = '';
92
+ let stderr = '';
93
+ const timer = setTimeout(() => {
94
+ try { proc.kill('SIGKILL'); } catch { }
95
+ reject(new Error(`yt-dlp timed out after ${timeoutMs}ms`));
96
+ }, timeoutMs);
97
+ proc.stdout.on('data', d => { stdout += d.toString(); });
98
+ proc.stderr.on('data', d => { stderr += d.toString(); });
99
+ proc.on('error', err => { clearTimeout(timer); reject(err); });
100
+ proc.on('close', code => { clearTimeout(timer); resolve({ code, stdout, stderr }); });
101
+ });
102
+ }
103
+
104
+ function listSubtitleFiles(dir) {
105
+ try {
106
+ return readdirSync(dir)
107
+ .map(name => {
108
+ const match = name.match(/^transcript\.(.+)\.(vtt|srt)$/i);
109
+ return match ? { file: join(dir, name), lang: match[1] } : null;
110
+ })
111
+ .filter(Boolean);
112
+ } catch {
113
+ return [];
114
+ }
115
+ }
116
+
117
+ function pickBestSubtitleFile(files, prefs) {
118
+ const score = (entry) => {
119
+ const lang = entry.lang.toLowerCase();
120
+ let best = prefs.length > 0 ? 100 : 0;
121
+ prefs.forEach((p, i) => {
122
+ const pLower = p.toLowerCase();
123
+ if (lang === pLower) best = Math.min(best, i);
124
+ else if (lang.startsWith(pLower)) best = Math.min(best, i + 0.5);
125
+ });
126
+ if (/(^|\.)orig$/i.test(lang) || /-orig$/i.test(lang)) best += 10;
127
+ return best;
128
+ };
129
+ return [...files].sort((a, b) => score(a) - score(b))[0];
130
+ }
131
+
132
+ export async function fetchYoutubeTranscript({ url, videoId, lang, maxChars } = {}) {
133
+ const id = videoId ? String(videoId).trim() : extractYouTubeVideoId(url);
134
+ if (!id || !/^[\w-]{11}$/.test(id)) {
135
+ return {
136
+ status: 'error',
137
+ message: 'Provide a valid YouTube URL (watch/youtu.be/shorts/embed/live) or an 11-character videoId.',
138
+ };
139
+ }
140
+
141
+ const dir = mkdtempSync(join(tmpdir(), 'kiku-transcript-'));
142
+ try {
143
+ const prefs = LANG_PREFERENCES[String(lang || '').toLowerCase()] || [];
144
+ const subLangs = [...new Set([...prefs, ...BASE_SUB_LANGS])].join(',');
145
+ const baseArgs = [
146
+ '--skip-download', '--no-warnings', '--quiet',
147
+ '--write-subs', '--write-auto-subs',
148
+ '--sub-format', 'vtt/srt/best',
149
+ '--sub-langs', subLangs,
150
+ '-o', join(dir, 'transcript.%(ext)s'),
151
+ ];
152
+ const watchUrl = `https://www.youtube.com/watch?v=${id}`;
153
+
154
+ const cookieArgs = await resolveCookieArgs();
155
+ let attempt = { code: 1, stdout: '', stderr: '' };
156
+ try {
157
+ attempt = await runYtDlp([...baseArgs, ...cookieArgs, watchUrl], 60_000);
158
+ } catch (err) {
159
+ attempt = { code: 1, stdout: '', stderr: err.message };
160
+ }
161
+
162
+ let files = listSubtitleFiles(dir);
163
+ if (files.length === 0 && cookieArgs.length > 0) {
164
+ try {
165
+ attempt = await runYtDlp([...baseArgs, watchUrl], 60_000);
166
+ } catch (err) {
167
+ attempt = { code: 1, stdout: '', stderr: err.message };
168
+ }
169
+ files = listSubtitleFiles(dir);
170
+ }
171
+
172
+ if (files.length === 0) {
173
+ const blocked = /sign in|confirm you|cookies|age/i.test(attempt.stderr || '');
174
+ return {
175
+ status: 'not_found',
176
+ videoId: id,
177
+ url: watchUrl,
178
+ message: blocked
179
+ ? `YouTube refused the caption request for ${id} (sign-in/cookies or age gate). Set KIKU_YT_COOKIES or KIKU_YT_BROWSER to enable authenticated caption downloads.`
180
+ : `No captions are available for this video (${id}), or yt-dlp could not reach it. Verify the video exists and has captions enabled.`,
181
+ };
182
+ }
183
+
184
+ const best = pickBestSubtitleFile(files, prefs);
185
+ const text = parseSubtitleToText(readFileSync(best.file, 'utf8'));
186
+ if (!text) {
187
+ return {
188
+ status: 'not_found',
189
+ videoId: id,
190
+ url: watchUrl,
191
+ message: `Captions were downloaded for ${id} but contained no readable text.`,
192
+ };
193
+ }
194
+
195
+ const max = Math.max(500, Math.min(Number(maxChars) || 6000, 20000));
196
+ const truncated = text.length > max;
197
+ return {
198
+ status: 'success',
199
+ videoId: id,
200
+ url: watchUrl,
201
+ lang: best.lang,
202
+ charCount: text.length,
203
+ truncated,
204
+ text: truncated ? `${text.slice(0, max)}…[truncated]` : text,
205
+ };
206
+ } finally {
207
+ try { rmSync(dir, { recursive: true, force: true }); } catch { }
208
+ }
209
+ }