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.
- package/README.md +459 -407
- package/package.json +32 -3
- package/server/agent/agentCore.js +635 -267
- package/server/agent/appPaths.js +2 -0
- package/server/agent/catalogEntityResolver.js +637 -0
- package/server/agent/citations.js +0 -21
- package/server/agent/clarificationAnalytics.js +1 -1
- package/server/agent/clarifyingQuestions.js +286 -216
- package/server/agent/cli.js +532 -73
- package/server/agent/commandFeatures.js +123 -24
- package/server/agent/commandRegistry.js +128 -62
- package/server/agent/commands.js +1410 -95
- package/server/agent/config.js +253 -163
- package/server/agent/contextCompactor.js +239 -25
- package/server/agent/dynamicCatalog.js +192 -0
- package/server/agent/envBootstrap.js +46 -0
- package/server/agent/envManager.js +317 -0
- package/server/agent/eventBus.js +3 -3
- package/server/agent/feedbackLearning.js +0 -8
- package/server/agent/gpuMetrics.js +188 -55
- package/server/agent/ingestionWorker.js +339 -0
- package/server/agent/ink/App.jsx +348 -0
- package/server/agent/ink/Panel.jsx +54 -0
- package/server/agent/ink/PromptInput.jsx +321 -0
- package/server/agent/ink/TaskPlanPanel.jsx +88 -0
- package/server/agent/ink/ThinkingPanel.jsx +68 -0
- package/server/agent/ink/ToolResult.jsx +133 -0
- package/server/agent/ink/main.jsx +195 -0
- package/server/agent/ink/useEventBus.js +218 -0
- package/server/agent/lanDiscovery.js +369 -0
- package/server/agent/llmClient.js +111 -39
- package/server/agent/lmStudioWakeup.js +144 -0
- package/server/agent/mcpClient.js +2 -2
- package/server/agent/memory/contextAllocator.js +214 -0
- package/server/agent/memory/hierarchicalStore.js +523 -0
- package/server/agent/memory/reflectionWorker.js +110 -0
- package/server/agent/memory.js +635 -494
- package/server/agent/modelConfig.js +487 -0
- package/server/agent/newsIntelligence.js +219 -0
- package/server/agent/newsWatch.js +85 -0
- package/server/agent/prompts.js +146 -0
- package/server/agent/publicTools.js +7 -0
- package/server/agent/queryDetails.js +62 -1
- package/server/agent/research.js +314 -35
- package/server/agent/routingInterceptor.js +77 -0
- package/server/agent/rumoredProjects.js +15 -0
- package/server/agent/sanitizer.js +117 -17
- package/server/agent/subagent.js +52 -9
- package/server/agent/suggestionTui.js +33 -13
- package/server/agent/suggestions.js +29 -13
- package/server/agent/telemetry/hardwareTiers.js +332 -0
- package/server/agent/telemetry/runtimeWatchdog.js +66 -0
- package/server/agent/telemetry/techPowerUpClient.js +682 -0
- package/server/agent/telemetry/techpowerup-mcp.json +22 -0
- package/server/agent/telemetry.js +0 -11
- package/server/agent/terminalImage.js +0 -1
- package/server/agent/textToolProtocol.js +190 -0
- package/server/agent/tokenTracker.js +220 -0
- package/server/agent/toolRouter.js +362 -216
- package/server/agent/tools.js +1155 -194
- package/server/agent/ui.js +144 -41
- package/server/agent/vramService.js +572 -0
- package/server/agent/webSearch.js +2279 -273
- package/server/agent/websearch/adapters/duckduckgo.js +97 -0
- package/server/agent/websearch/adapters/jina.js +77 -0
- package/server/agent/websearch/adapters/searxng.js +57 -0
- package/server/agent/websearch/core/canonical.js +89 -0
- package/server/agent/websearch/core/fusion.js +81 -0
- package/server/agent/websearch/core/types.js +29 -0
- package/server/agent/websearch/extract.js +605 -0
- package/server/agent/websearch/index.js +43 -0
- package/server/agent/websearch/orchestrator.js +266 -0
- package/server/agent/websearch/resilience/breaker.js +97 -0
- package/server/agent/websearch/resilience/cache.js +59 -0
- package/server/agent/websearch/resilience/limiter.js +93 -0
- package/server/agent/websearch/security/filter.js +36 -0
- package/server/agent/websearch/security/ssrf.js +289 -0
- package/server/agent/websearch/telemetry.js +71 -0
- package/server/agent/youtubeAuth.js +82 -73
- package/server/agent/youtubeTranscript.js +209 -0
- package/server/apiRouter.js +390 -190
- package/server/mcp/data/brands.js +5 -5
- package/server/mcp/data/discography.js +56 -45
- package/server/mcp/data/gallery.js +9 -0
- package/server/mcp/data/index.js +1 -0
- package/server/mcp/data/industryIndex.js +251 -0
- package/server/mcp/data/profile.js +5 -5
- package/server/mcp/data/timeline.js +2 -2
- package/server/mcp/data/works.js +2 -2
- package/server/mcp/handler.js +0 -2
- package/server/mcp/prompts.js +42 -2
- package/server/mcp/resources.js +5 -1
- package/server/mcp/security.js +25 -14
- package/server/mcp/server.js +92 -3
- package/server/mcp/tools.js +20 -11
- package/server/routes/agent.js +55 -0
- package/server/web-chat/core/connection.js +218 -194
- package/server/web-chat/core/conversationContext.js +1 -1
- package/server/web-chat/core/directCatalogEngine.js +190 -184
- package/server/web-chat/core/followUpEngine.js +88 -11
- package/server/web-chat/core/security.js +7 -4
- package/server/web-chat/router.js +54 -17
- package/server/web-chat/routes/bilibiliRoute.js +34 -0
- package/server/web-chat/routes/chatRoute.js +488 -397
- package/server/web-chat/routes/clarifyConfigRoute.js +1 -2
- package/server/web-chat/routes/exportRoute.js +2 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +90 -0
- package/server/web-chat/routes/newsRoute.js +36 -6
- package/server/web-chat/routes/providerRoute.js +87 -0
- package/server/web-chat/routes/staticRoute.js +61 -13
- package/server/web-chat/routes/statusRoute.js +23 -2
- package/server/web-chat/routes/vramRoute.js +22 -0
- package/server/web-chat/server.js +135 -106
- package/server/web-chat/ui/htmlBuilder.js +8 -8
- package/server/web-chat/ui/previews/modelSettings.preview.html +41 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +0 -5
- package/server/web-chat/ui/scripts/client.js +10526 -8397
- package/server/web-chat/ui/scripts/webchatI18n.js +190 -138
- package/server/web-chat/ui/styles/main.css +7471 -4246
- package/server/web-chat/ui/templates/body.html +305 -179
- package/src/data/brandsData.js +29 -6
- package/src/data/discographyData.js +918 -900
- package/src/data/galleryData.js +66 -0
- package/src/data/industryIndexData.js +292 -0
- package/src/data/musicVideoData.js +48 -5
- package/src/data/profileData.js +15 -1
- package/src/data/timelineData.js +4 -3
- package/src/data/worksData.js +536 -535
- package/src/lib/vram/estimator.js +464 -0
- package/src/lib/vram/modelRegistry.js +387 -0
- 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(/&/g, '&')
|
|
56
|
+
.replace(/'/g, "'")
|
|
57
|
+
.replace(/'/g, "'")
|
|
58
|
+
.replace(/"/g, '"')
|
|
59
|
+
.replace(/>/g, '>')
|
|
60
|
+
.replace(/</g, '<')
|
|
61
|
+
.replace(/ /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
|
+
}
|