kiku-agent 1.3.0 → 1.3.2
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 +7 -7
- package/package.json +4 -7
- package/server/agent/agentCore.js +2 -2
- package/server/agent/catalogEntityResolver.js +8 -0
- package/server/agent/cli.js +4 -0
- package/server/agent/eval/AGENTS.md +228 -0
- package/server/agent/eval/analyze.py +684 -0
- package/server/agent/eval/gates.json +15 -0
- package/server/agent/eval/modules/analysis/compare.js +198 -0
- package/server/agent/eval/modules/analysis/gates.js +72 -0
- package/server/agent/eval/modules/analysis/judge.js +70 -0
- package/server/agent/eval/modules/core/cli.js +453 -0
- package/server/agent/eval/modules/core/config.js +60 -0
- package/server/agent/eval/modules/core/store.js +236 -0
- package/server/agent/eval/modules/evaluation/cases.js +689 -0
- package/server/agent/eval/modules/evaluation/runner.js +298 -0
- package/server/agent/eval/modules/evaluation/scoring.js +351 -0
- package/server/agent/eval/modules/evaluation/worker.js +122 -0
- package/server/agent/eval/modules/reporting/benchmark.js +265 -0
- package/server/agent/eval/modules/reporting/benchmarkPng.js +487 -0
- package/server/agent/eval/modules/reporting/dashboard.js +1406 -0
- package/server/agent/eval/modules/server/evalApi.js +1099 -0
- package/server/agent/eval/modules/ui/evalUi.js +4796 -0
- package/server/agent/eval/modules/utils/smokes.js +119 -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/memory/contextAllocator.js +214 -0
- package/server/agent/memory/hierarchicalStore.js +523 -0
- package/server/agent/memory/reflectionWorker.js +110 -0
- package/server/agent/prompts.js +1 -1
- 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/tests/agentBubbleSystem.test.js +65 -0
- package/server/agent/tests/agentFollowUps.test.js +127 -0
- package/server/agent/tests/apiRouter.test.js +108 -0
- package/server/agent/tests/architectureFixes.test.js +81 -0
- package/server/agent/tests/architectureRefactoring.test.js +194 -0
- package/server/agent/tests/autoResearch.test.js +64 -0
- package/server/agent/tests/bilibiliEmbed.test.js +251 -0
- package/server/agent/tests/catalogSync.test.js +130 -0
- package/server/agent/tests/citations.test.js +180 -0
- package/server/agent/tests/clarificationAnalytics.test.js +219 -0
- package/server/agent/tests/clarifyingQuestions.test.js +594 -0
- package/server/agent/tests/cliAutomation.test.js +121 -0
- package/server/agent/tests/cloudProviders.test.js +234 -0
- package/server/agent/tests/commandInput.test.js +24 -0
- package/server/agent/tests/commands.test.js +417 -0
- package/server/agent/tests/contextIntelligence.test.js +162 -0
- package/server/agent/tests/conversationSafety.test.js +53 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +135 -0
- package/server/agent/tests/dramaAnswerQuality.test.js +73 -0
- package/server/agent/tests/dynamicCatalogIngestion.test.js +285 -0
- package/server/agent/tests/dynamicRoutingAndSearch.test.js +92 -0
- package/server/agent/tests/edgeCasesScenarios.test.js +49 -0
- package/server/agent/tests/entertainmentMetrics.test.js +126 -0
- package/server/agent/tests/envBootstrap.test.js +87 -0
- package/server/agent/tests/envCommand.test.js +215 -0
- package/server/agent/tests/errorHandling.test.js +80 -0
- package/server/agent/tests/errors.test.js +13 -0
- package/server/agent/tests/evalAnalyze.test.js +54 -0
- package/server/agent/tests/evalCommand.test.js +54 -0
- package/server/agent/tests/evalCorpus.test.js +126 -0
- package/server/agent/tests/evalHarness.test.js +62 -0
- package/server/agent/tests/evalJudgeContext.test.js +38 -0
- package/server/agent/tests/evalMetrics.test.js +312 -0
- package/server/agent/tests/evalStore.test.js +190 -0
- package/server/agent/tests/evalStudio.test.js +179 -0
- package/server/agent/tests/feedbackLearning.test.js +186 -0
- package/server/agent/tests/firstRun.test.js +44 -0
- package/server/agent/tests/gemma4eOptimization.test.js +95 -0
- package/server/agent/tests/hardwareTiers.test.js +206 -0
- package/server/agent/tests/hybridWebSearch.test.js +192 -0
- package/server/agent/tests/judgeService.test.js +251 -0
- package/server/agent/tests/lanDiscovery.test.js +319 -0
- package/server/agent/tests/mcpClient.test.js +201 -0
- package/server/agent/tests/modelManager.test.js +109 -0
- package/server/agent/tests/multilingualScenarios.test.js +69 -0
- package/server/agent/tests/newCommands.test.js +347 -0
- package/server/agent/tests/newCommandsTui.test.js +141 -0
- package/server/agent/tests/newTools.test.js +113 -0
- package/server/agent/tests/newsIntelligence.test.js +178 -0
- package/server/agent/tests/nextGenArchitecture.test.js +99 -0
- package/server/agent/tests/offlineInput.test.js +217 -0
- package/server/agent/tests/packagingGuard.test.js +57 -0
- package/server/agent/tests/presentation.test.js +61 -0
- package/server/agent/tests/providerPersistence.test.js +39 -0
- package/server/agent/tests/queryBenchmarks.test.js +83 -0
- package/server/agent/tests/queryDetails.test.js +126 -0
- package/server/agent/tests/realWorldValidation.test.js +391 -0
- package/server/agent/tests/resilienceAndConcurrency.test.js +117 -0
- package/server/agent/tests/runtimeWatchdog.test.js +32 -0
- package/server/agent/tests/sanitizer.test.js +37 -0
- package/server/agent/tests/scenariosData.js +378 -0
- package/server/agent/tests/schemaRouting.test.js +54 -0
- package/server/agent/tests/securityAuditRemediation.test.js +214 -0
- package/server/agent/tests/securityHardening.test.js +146 -0
- package/server/agent/tests/securitySanitizer.test.js +38 -0
- package/server/agent/tests/sessionAndCopy.test.js +232 -0
- package/server/agent/tests/sidebarControls.test.js +101 -0
- package/server/agent/tests/statefulMemory.test.js +270 -0
- package/server/agent/tests/subagent.test.js +216 -0
- package/server/agent/tests/subagentProgress.test.js +109 -0
- package/server/agent/tests/subagentTextFallback.test.js +115 -0
- package/server/agent/tests/suggestions.test.js +199 -0
- package/server/agent/tests/suggestionsTui.test.js +251 -0
- package/server/agent/tests/telemetryAndProfiles.test.js +179 -0
- package/server/agent/tests/terminalImage.test.js +80 -0
- package/server/agent/tests/terminalMarkdown.test.js +166 -0
- package/server/agent/tests/terminalText.test.js +133 -0
- package/server/agent/tests/textProtocolLoop.test.js +126 -0
- package/server/agent/tests/textToolParsing.test.js +139 -0
- package/server/agent/tests/theme.test.js +26 -0
- package/server/agent/tests/toolRegistryParity.test.js +53 -0
- package/server/agent/tests/toolRoutingRegressions.test.js +119 -0
- package/server/agent/tests/tools.test.js +259 -0
- package/server/agent/tests/unifiedEndpoints.test.js +152 -0
- package/server/agent/tests/v110Features.test.js +262 -0
- package/server/agent/tests/versionBump.test.js +69 -0
- package/server/agent/tests/vramEstimator.test.js +520 -0
- package/server/agent/tests/webChatInspection.test.js +300 -0
- package/server/agent/tests/webChatMemory.test.js +112 -0
- package/server/agent/tests/webChatSecurity.test.js +169 -0
- package/server/agent/tests/webExtract.test.js +261 -0
- package/server/agent/tests/webSources.test.js +247 -0
- 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/apiRouter.js +7 -11
- package/server/eval/judgeRubrics.js +110 -0
- package/server/eval/judgeService.js +290 -0
- package/server/eval/runBenchmark.js +182 -0
- package/server/mcp/examples/librechat.example.yaml +16 -0
- package/server/mcp/tests/connection.test.js +156 -0
- package/server/routes/agent.js +55 -0
- package/server/supabase_schema.sql +63 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { REPO_ROOT, DEFAULTS } from '../core/config.js';
|
|
5
|
+
import { estimateTextTokens } from '../../../contextCompactor.js';
|
|
6
|
+
|
|
7
|
+
export async function runWebSmoke({ out = DEFAULTS.webSmokeOut, timeoutMs = 60000 } = {}) {
|
|
8
|
+
const outPath = path.isAbsolute(out) ? out : path.resolve(REPO_ROOT, out);
|
|
9
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
10
|
+
process.env.KIKU_DATA_DIR = await fs.mkdtemp(path.join(os.tmpdir(), 'kiku-web-eval-'));
|
|
11
|
+
const { executeTool } = await import('../../../tools.js');
|
|
12
|
+
const checks = [
|
|
13
|
+
['web_search', { query: 'Ju Jingyi Legend of Yunxi' }],
|
|
14
|
+
['search_chinese_web', { query: '鞠婧祎 芸汐传' }],
|
|
15
|
+
['fetch_webpage', { url: 'https://example.com' }],
|
|
16
|
+
['web_extract', { query: 'Ju Jingyi Legend of Yunxi', maxResults: 2 }],
|
|
17
|
+
];
|
|
18
|
+
const rows = [];
|
|
19
|
+
try {
|
|
20
|
+
for (const [tool, parameters] of checks) {
|
|
21
|
+
const start = performance.now();
|
|
22
|
+
let row;
|
|
23
|
+
try {
|
|
24
|
+
const result = await executeTool(tool, parameters, { signal: AbortSignal.timeout(timeoutMs) });
|
|
25
|
+
row = { tool, parameters, result, durationMs: Math.round(performance.now() - start) };
|
|
26
|
+
} catch (error) {
|
|
27
|
+
row = { tool, parameters, error: error.message, durationMs: Math.round(performance.now() - start) };
|
|
28
|
+
}
|
|
29
|
+
rows.push(row);
|
|
30
|
+
await fs.writeFile(outPath, JSON.stringify({ checkedAt: new Date().toISOString(), scope: 'Real external tool availability, not answer-quality scoring. Success status does not establish result relevance.', rows }, null, 2));
|
|
31
|
+
console.log(`${tool}: ${row.result?.status || row.error}; ${row.durationMs}ms`);
|
|
32
|
+
}
|
|
33
|
+
} finally {
|
|
34
|
+
await fs.rm(process.env.KIKU_DATA_DIR, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
return { out: outPath, rows };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runResearchSmoke({ out = DEFAULTS.researchSmokeOut, model = DEFAULTS.model, baseUrl = DEFAULTS.baseUrl, timeoutMs = 180000 } = {}) {
|
|
40
|
+
const outPath = path.isAbsolute(out) ? out : path.resolve(REPO_ROOT, out);
|
|
41
|
+
const fsSync = await import('node:fs');
|
|
42
|
+
const dir = fsSync.mkdtempSync(path.join(os.tmpdir(), 'kiku-research-eval-'));
|
|
43
|
+
process.env.KIKU_DATA_DIR = dir;
|
|
44
|
+
const [{ LLMClient }, { ResearchOrchestrator }, { scoreResearchComplexity, shouldAutoResearch }] = await Promise.all([
|
|
45
|
+
import('../../../llmClient.js'), import('../../../research.js'), import('../../../agentCore.js'),
|
|
46
|
+
]);
|
|
47
|
+
const question = 'Compare In Blossom and Sword and Fairy 4 using the Kiku catalog and public sources. Verify the release years, the characters played by Ju Jingyi, and episode counts. Explain any source disagreement, and clearly distinguish unverified information. Provide a concise evidence report with sources.';
|
|
48
|
+
const client = new LLMClient({ provider: 'lmstudio', model, apiKey: 'lm-studio', baseUrl });
|
|
49
|
+
const started = Date.now();
|
|
50
|
+
const report = {
|
|
51
|
+
startedAt: new Date().toISOString(), model: client.model, question, timeoutMs,
|
|
52
|
+
complexity: scoreResearchComplexity(question), autoEligible: shouldAutoResearch(question, { mode: 'auto' }),
|
|
53
|
+
scope: 'Direct real ResearchOrchestrator run, default 3 workers and 3 iterations. Auto eligibility is checked separately; this does not test the main agent decision to invoke research.',
|
|
54
|
+
state: 'running', maxConcurrentRequests: 0, requests: [], progress: [],
|
|
55
|
+
};
|
|
56
|
+
fsSync.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
57
|
+
const save = () => { report.elapsedMs = Date.now() - started; fsSync.writeFileSync(outPath, JSON.stringify(report, null, 2)); };
|
|
58
|
+
let active = 0;
|
|
59
|
+
const chat = client.chat.bind(client);
|
|
60
|
+
client.chat = async options => {
|
|
61
|
+
const row = { startedMs: Date.now() - started };
|
|
62
|
+
report.requests.push(row);
|
|
63
|
+
report.maxConcurrentRequests = Math.max(report.maxConcurrentRequests, ++active);
|
|
64
|
+
save();
|
|
65
|
+
try { const result = await chat(options); row.usage = result?.usage; return result; }
|
|
66
|
+
catch (error) { row.error = error.message; throw error; }
|
|
67
|
+
finally { active--; row.endedMs = Date.now() - started; save(); }
|
|
68
|
+
};
|
|
69
|
+
const research = new ResearchOrchestrator({ client });
|
|
70
|
+
const hardStop = setTimeout(() => {
|
|
71
|
+
report.state = 'timeout'; report.error = 'Hard timeout after cancellation grace period'; save(); process.exit(1);
|
|
72
|
+
}, timeoutMs + 15000);
|
|
73
|
+
try {
|
|
74
|
+
report.result = await research.run(question, {
|
|
75
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
76
|
+
onProgress: event => { report.progress.push({ elapsedMs: Date.now() - started, ...event }); save(); console.log(JSON.stringify(event)); },
|
|
77
|
+
});
|
|
78
|
+
report.state = 'completed';
|
|
79
|
+
} catch (error) {
|
|
80
|
+
report.state = 'error'; report.error = error.message; process.exitCode = 1;
|
|
81
|
+
} finally {
|
|
82
|
+
clearTimeout(hardStop); research.cancel(); save(); fsSync.rmSync(dir, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
console.log(JSON.stringify({ state: report.state, elapsedMs: report.elapsedMs, requests: report.requests.length, maxConcurrentRequests: report.maxConcurrentRequests }));
|
|
85
|
+
return report;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function runTokenCalibration({ out = DEFAULTS.tokenCalibrationOut, model = DEFAULTS.model, baseUrl = DEFAULTS.baseUrl } = {}) {
|
|
89
|
+
const outPath = path.isAbsolute(out) ? out : path.resolve(REPO_ROOT, out);
|
|
90
|
+
const samples = {
|
|
91
|
+
th: 'จวีจิ้งอีเป็นนักร้องและนักแสดงชาวจีน แฟนคลับต้องการตรวจสอบข้อมูลผลงานจากแหล่งอ้างอิง หากไม่มีข้อมูลเพียงพอควรบอกตรงไปตรงมาและไม่แต่งรายละเอียดเพิ่มเติม ',
|
|
92
|
+
en: 'The assistant should verify the title, character and release year against the catalog. If evidence is missing, explain the uncertainty instead of inventing details. ',
|
|
93
|
+
zh: '请根据资料库核实电视剧名称、角色和发行年份。如果没有足够证据,请明确说明不确定性,不要编造细节。',
|
|
94
|
+
mixed: 'ข้อมูล作品 In Blossom (花间令) ปี 2024: {"role":"Yang Caiwei","episodes":32,"verified":true}. ',
|
|
95
|
+
};
|
|
96
|
+
const rows = [];
|
|
97
|
+
for (const [language, sample] of Object.entries(samples)) {
|
|
98
|
+
const measurements = [];
|
|
99
|
+
for (const repeats of [3, 6]) {
|
|
100
|
+
const content = sample.repeat(repeats).trim();
|
|
101
|
+
const started = Date.now();
|
|
102
|
+
const response = await fetch(`${baseUrl.replace(/\/$/, '')}/chat/completions`, {
|
|
103
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, signal: AbortSignal.timeout(90000),
|
|
104
|
+
body: JSON.stringify({ model, messages: [{ role: 'user', content }], max_tokens: 1, temperature: 0, stream: false }),
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) throw new Error(`Calibration HTTP ${response.status}`);
|
|
107
|
+
const result = await response.json();
|
|
108
|
+
if (!Number.isFinite(result.usage?.prompt_tokens)) throw new Error('Provider did not report prompt token usage');
|
|
109
|
+
measurements.push({ repeats, characters: content.length, estimate: estimateTextTokens(content), promptTokens: result.usage.prompt_tokens, durationMs: Date.now() - started });
|
|
110
|
+
}
|
|
111
|
+
const estimatedDelta = measurements[1].estimate - measurements[0].estimate;
|
|
112
|
+
const measuredDelta = measurements[1].promptTokens - measurements[0].promptTokens;
|
|
113
|
+
rows.push({ language, sample, measurements, estimatedDelta, measuredDelta, ratio: measuredDelta > 0 ? estimatedDelta / measuredDelta : null });
|
|
114
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
115
|
+
await fs.writeFile(outPath, JSON.stringify({ model, checkedAt: new Date().toISOString(), method: 'Real prompt usage delta between 3 and 6 repeats; chat-template overhead cancels. Four samples only, no universal tokenizer bound or o200k claim.', rows }, null, 2));
|
|
116
|
+
console.log(`${language}: estimated delta ${estimatedDelta}, measured ${measuredDelta}, ratio ${rows.at(-1).ratio}`);
|
|
117
|
+
}
|
|
118
|
+
return { out: outPath, rows };
|
|
119
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { Box, Text, Static, useInput, useApp, useStdin } from 'ink';
|
|
3
|
+
import { PromptInput } from './PromptInput.jsx';
|
|
4
|
+
import { ThinkingPanel } from './ThinkingPanel.jsx';
|
|
5
|
+
import { ToolCompleted, ToolSummary, ToolRunning } from './ToolResult.jsx';
|
|
6
|
+
import { TaskPlanPanel } from './TaskPlanPanel.jsx';
|
|
7
|
+
import { Panel, Separator } from './Panel.jsx';
|
|
8
|
+
import { useAgentState, useStreamingText, useThinking, useToolResults, useTaskPlan, useEvent } from './useEventBus.js';
|
|
9
|
+
import { eventBus, Events } from '../eventBus.js';
|
|
10
|
+
import { getInkPalette } from '../theme.js';
|
|
11
|
+
import { PACKAGE_VERSION } from '../version.js';
|
|
12
|
+
|
|
13
|
+
const FAN_NAMES = ['Mìjú / 蜜橘'];
|
|
14
|
+
function randomFanName() {
|
|
15
|
+
return FAN_NAMES[Math.floor(Math.random() * FAN_NAMES.length)];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function Header() {
|
|
19
|
+
const theme = getInkPalette();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Box flexDirection="column">
|
|
23
|
+
<Text color={theme.primary}>
|
|
24
|
+
{' ██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗'}
|
|
25
|
+
</Text>
|
|
26
|
+
<Text color={theme.primary}>
|
|
27
|
+
{' ██║ ██╔╝██║██║ ██╔╝██║ ██║ ██╔══██╗██║'}
|
|
28
|
+
</Text>
|
|
29
|
+
<Text color={theme.secondary}>
|
|
30
|
+
{' █████═╝ ██║█████═╝ ██║ ██║ ███████║██║'}
|
|
31
|
+
</Text>
|
|
32
|
+
<Text color={theme.secondary}>
|
|
33
|
+
{' ██╔═██╗ ██║██╔═██╗ ██║ ██║ ██╔══██║██║'}
|
|
34
|
+
</Text>
|
|
35
|
+
<Text color={theme.primary}>
|
|
36
|
+
{' ██║ ╚██╗██║██║ ╚██╗╚██████╔╝ ██║ ██║██║'}
|
|
37
|
+
</Text>
|
|
38
|
+
<Text color={theme.primary}>
|
|
39
|
+
{' ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝'}
|
|
40
|
+
</Text>
|
|
41
|
+
<Text />
|
|
42
|
+
<Box>
|
|
43
|
+
<Text color={theme.muted}>Architecture: </Text>
|
|
44
|
+
<Text bold color={theme.highlight}>Next-Gen v{PACKAGE_VERSION} </Text>
|
|
45
|
+
<Text color={theme.muted}>• SSOT Catalog • Dynamic Tool Router • Semantic Memory</Text>
|
|
46
|
+
</Box>
|
|
47
|
+
<Text />
|
|
48
|
+
<Text bold color={theme.secondary}>Example Questions & Capabilities:</Text>
|
|
49
|
+
<Text color={theme.text}> <Text color={theme.primary}>[Dramas 2026]</Text> <Text>"What are Ju Jingyi's upcoming 2026 drama series (Veil of Shadows, Eternal Fragrance)?"</Text></Text>
|
|
50
|
+
<Text color={theme.text}> <Text color={theme.primary}>[Characters ]</Text> <Text>"Tell me about Yang Caiwei in In Blossom and Han Yunxi in Legend of Yunxi."</Text></Text>
|
|
51
|
+
<Text color={theme.text}> <Text color={theme.info}>[Music & OST]</Text> <Text>"What OST songs are from The Blooms at Ruyi Pavilion? /play Rain of Ginkgo"</Text></Text>
|
|
52
|
+
<Text color={theme.text}> <Text color={theme.info}>[Global Brand]</Text> <Text>"Tell me about her Global Fragrance Ambassador role for Versace and luxury brands."</Text></Text>
|
|
53
|
+
<Text color={theme.text}> <Text color={theme.success}>[Fact-Check ]</Text> <Text>"/verify Check Star48 contract status or /research 2026 broadcast licenses"</Text></Text>
|
|
54
|
+
<Text color={theme.text}> <Text color={theme.success}>[Persona/Mem]</Text> <Text>"/skill learn Always answer politely as a passionate Miju fan club member"</Text></Text>
|
|
55
|
+
<Text color={theme.text}> <Text color={theme.tertiary}>[Multi-Agent]</Text> <Text>"/subagent Investigate her upcoming drama production timeline and Weibo updates"</Text></Text>
|
|
56
|
+
<Text color={theme.text}> <Text color={theme.tertiary}>[System Stats]</Text> <Text>"/stats or /doctor to inspect database records, models, and audio engine"</Text></Text>
|
|
57
|
+
<Text />
|
|
58
|
+
<Text>
|
|
59
|
+
<Text color={theme.tertiary}>Quick Commands: </Text>
|
|
60
|
+
<Text color={theme.info}>/help</Text>
|
|
61
|
+
<Text dimColor> • </Text>
|
|
62
|
+
<Text color={theme.success}>/subagent</Text>
|
|
63
|
+
<Text dimColor> • </Text>
|
|
64
|
+
<Text color={theme.success}>/research</Text>
|
|
65
|
+
<Text dimColor> • </Text>
|
|
66
|
+
<Text color={theme.success}>/verify</Text>
|
|
67
|
+
<Text dimColor> • </Text>
|
|
68
|
+
<Text color={theme.success}>/skill</Text>
|
|
69
|
+
<Text dimColor> • </Text>
|
|
70
|
+
<Text color={theme.success}>/exit</Text>
|
|
71
|
+
<Text dimColor> (Tab or ↑/↓ to navigate)</Text>
|
|
72
|
+
</Text>
|
|
73
|
+
<Text />
|
|
74
|
+
</Box>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function UserMessage({ text }) {
|
|
79
|
+
const theme = getInkPalette();
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Box flexDirection="column">
|
|
83
|
+
<Text>
|
|
84
|
+
<Text color={theme.secondary} bold>{randomFanName()}: </Text>
|
|
85
|
+
<Text color={theme.text}>{text}</Text>
|
|
86
|
+
</Text>
|
|
87
|
+
</Box>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function StreamingResponse({ text, isStreaming }) {
|
|
92
|
+
if (!text && !isStreaming) return null;
|
|
93
|
+
const theme = getInkPalette();
|
|
94
|
+
|
|
95
|
+
const lines = text.split('\n');
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Box flexDirection="column">
|
|
99
|
+
{lines.map((line, i) => (
|
|
100
|
+
<Box key={i}>
|
|
101
|
+
<Text> </Text>
|
|
102
|
+
<Text color={theme.text}>{line || ' '}</Text>
|
|
103
|
+
</Box>
|
|
104
|
+
))}
|
|
105
|
+
{isStreaming && (
|
|
106
|
+
<Box>
|
|
107
|
+
<Text color={theme.muted} dimColor> ▌</Text>
|
|
108
|
+
</Box>
|
|
109
|
+
)}
|
|
110
|
+
</Box>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function CompletedResponse({ text }) {
|
|
115
|
+
if (!text) return null;
|
|
116
|
+
const theme = getInkPalette();
|
|
117
|
+
|
|
118
|
+
const lines = text.split('\n');
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Box flexDirection="column">
|
|
122
|
+
{lines.map((line, i) => (
|
|
123
|
+
<Box key={i}>
|
|
124
|
+
<Text> </Text>
|
|
125
|
+
<Text color={theme.text}>{line || ' '}</Text>
|
|
126
|
+
</Box>
|
|
127
|
+
))}
|
|
128
|
+
</Box>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function MusicStatusBar({ musicStatus }) {
|
|
133
|
+
if (!musicStatus) return null;
|
|
134
|
+
const theme = getInkPalette();
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<Box>
|
|
138
|
+
<Text color={theme.info}>{musicStatus} </Text>
|
|
139
|
+
</Box>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function App({ agent, client, baseUrl, printHelp, printAd }) {
|
|
144
|
+
const { exit } = useApp();
|
|
145
|
+
const theme = getInkPalette();
|
|
146
|
+
const [outputLines, setOutputLines] = useState([]);
|
|
147
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
148
|
+
const [lastQuery, setLastQuery] = useState(null);
|
|
149
|
+
const [finalAnswer, setFinalAnswer] = useState('');
|
|
150
|
+
const [, setThemeRevision] = useState(0);
|
|
151
|
+
|
|
152
|
+
const agentState = useAgentState();
|
|
153
|
+
const { text: streamingText, isStreaming, reset: resetStreaming } = useStreamingText();
|
|
154
|
+
const thinking = useThinking();
|
|
155
|
+
const { tools, clearTools } = useToolResults();
|
|
156
|
+
const { plan: taskPlan, clearPlan } = useTaskPlan();
|
|
157
|
+
const musicStatus = useEvent('music:state:change', '');
|
|
158
|
+
|
|
159
|
+
const agentStateLabel = isProcessing ? agentState.label : '';
|
|
160
|
+
const showStreaming = isStreaming && streamingText.length > 0;
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
const unsub = eventBus.on(Events.AGENT_FINAL_ANSWER, (data) => {
|
|
164
|
+
setFinalAnswer(data?.answer || '');
|
|
165
|
+
setIsProcessing(false);
|
|
166
|
+
clearTools();
|
|
167
|
+
});
|
|
168
|
+
return unsub;
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
const unsub = eventBus.on(Events.AGENT_QUERY_ERROR, (data) => {
|
|
173
|
+
setOutputLines(prev => [...prev, {
|
|
174
|
+
type: 'error',
|
|
175
|
+
text: data?.error || 'Unknown error',
|
|
176
|
+
id: Date.now(),
|
|
177
|
+
}]);
|
|
178
|
+
setIsProcessing(false);
|
|
179
|
+
});
|
|
180
|
+
return unsub;
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
const handleSubmit = useCallback(async (input) => {
|
|
184
|
+
const trimmed = input.trim();
|
|
185
|
+
if (!trimmed) return;
|
|
186
|
+
|
|
187
|
+
setOutputLines(prev => [...prev, {
|
|
188
|
+
type: 'user',
|
|
189
|
+
text: trimmed,
|
|
190
|
+
id: Date.now(),
|
|
191
|
+
}]);
|
|
192
|
+
|
|
193
|
+
if (trimmed.startsWith('/')) {
|
|
194
|
+
const { routeCommand } = await import('../commands.js');
|
|
195
|
+
const cmdCtx = {
|
|
196
|
+
agent,
|
|
197
|
+
client,
|
|
198
|
+
baseUrl,
|
|
199
|
+
sub: trimmed.split(/\s+/)[1] || '',
|
|
200
|
+
extra: trimmed.split(/\s+/).slice(2).join(' '),
|
|
201
|
+
parts: trimmed.split(/\s+/),
|
|
202
|
+
lastQuery,
|
|
203
|
+
printHelp,
|
|
204
|
+
printWelcome: () => {},
|
|
205
|
+
printAd,
|
|
206
|
+
enqueueQuery: () => {},
|
|
207
|
+
clearQueryQueue: () => {},
|
|
208
|
+
getQueryQueueLength: () => 0,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const result = await routeCommand(trimmed, cmdCtx);
|
|
212
|
+
const commandName = trimmed.split(/\s+/, 1)[0].toLowerCase();
|
|
213
|
+
if (commandName === '/theme' || commandName === '/themes') {
|
|
214
|
+
setThemeRevision(revision => revision + 1);
|
|
215
|
+
}
|
|
216
|
+
if (result === 'EXIT') {
|
|
217
|
+
exit();
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
setLastQuery(trimmed);
|
|
223
|
+
setIsProcessing(true);
|
|
224
|
+
setFinalAnswer('');
|
|
225
|
+
resetStreaming();
|
|
226
|
+
clearTools();
|
|
227
|
+
clearPlan();
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const { sanitizeQuery } = await import('../sanitizer.js');
|
|
231
|
+
const { sanitized } = sanitizeQuery(trimmed);
|
|
232
|
+
const promptForLLM = sanitized || trimmed;
|
|
233
|
+
|
|
234
|
+
await agent.ask(promptForLLM, {
|
|
235
|
+
onStepStart: () => {},
|
|
236
|
+
onReasoningStart: () => {},
|
|
237
|
+
onReasoningDelta: () => {},
|
|
238
|
+
onReasoningEnd: () => {},
|
|
239
|
+
onToolStart: () => {},
|
|
240
|
+
onToolResult: () => {},
|
|
241
|
+
onTokenStart: () => {},
|
|
242
|
+
onTokenDelta: () => {},
|
|
243
|
+
onFinalAnswer: (answer) => {
|
|
244
|
+
setOutputLines(prev => [...prev, {
|
|
245
|
+
type: 'assistant',
|
|
246
|
+
text: answer,
|
|
247
|
+
tools: [...tools],
|
|
248
|
+
thinking: thinking.thinking,
|
|
249
|
+
thinkingDuration: thinking.duration,
|
|
250
|
+
id: Date.now(),
|
|
251
|
+
}]);
|
|
252
|
+
setIsProcessing(false);
|
|
253
|
+
setFinalAnswer('');
|
|
254
|
+
resetStreaming();
|
|
255
|
+
clearTools();
|
|
256
|
+
},
|
|
257
|
+
onError: (err) => {
|
|
258
|
+
setOutputLines(prev => [...prev, {
|
|
259
|
+
type: 'error',
|
|
260
|
+
text: err.message,
|
|
261
|
+
id: Date.now(),
|
|
262
|
+
}]);
|
|
263
|
+
setIsProcessing(false);
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
} catch (err) {
|
|
267
|
+
setOutputLines(prev => [...prev, {
|
|
268
|
+
type: 'error',
|
|
269
|
+
text: err.message,
|
|
270
|
+
id: Date.now(),
|
|
271
|
+
}]);
|
|
272
|
+
setIsProcessing(false);
|
|
273
|
+
}
|
|
274
|
+
}, [agent, client, baseUrl, lastQuery, tools, thinking]);
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<Box flexDirection="column" width="100%">
|
|
278
|
+
<Static items={[0]}>
|
|
279
|
+
{() => <Header key="header" />}
|
|
280
|
+
</Static>
|
|
281
|
+
|
|
282
|
+
<Box flexDirection="column">
|
|
283
|
+
{outputLines.map((line) => {
|
|
284
|
+
if (line.type === 'user') {
|
|
285
|
+
return <UserMessage key={line.id} text={line.text} />;
|
|
286
|
+
}
|
|
287
|
+
if (line.type === 'assistant') {
|
|
288
|
+
return (
|
|
289
|
+
<Box key={line.id} flexDirection="column">
|
|
290
|
+
{line.thinking && line.thinking.length > 0 && (
|
|
291
|
+
<ThinkingPanel
|
|
292
|
+
text={line.thinking}
|
|
293
|
+
isActive={false}
|
|
294
|
+
duration={line.thinkingDuration}
|
|
295
|
+
/>
|
|
296
|
+
)}
|
|
297
|
+
{line.tools && line.tools.length > 0 && (
|
|
298
|
+
<ToolSummary tools={line.tools} />
|
|
299
|
+
)}
|
|
300
|
+
<CompletedResponse text={line.text} />
|
|
301
|
+
</Box>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (line.type === 'error') {
|
|
305
|
+
return (
|
|
306
|
+
<Box key={line.id}>
|
|
307
|
+
<Text> </Text>
|
|
308
|
+
<Text color={theme.error}>[ERROR] {line.text}</Text>
|
|
309
|
+
</Box>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
})}
|
|
314
|
+
</Box>
|
|
315
|
+
|
|
316
|
+
{isProcessing && (
|
|
317
|
+
<Box flexDirection="column">
|
|
318
|
+
{taskPlan && <TaskPlanPanel plan={taskPlan} />}
|
|
319
|
+
|
|
320
|
+
{thinking.isActive && (
|
|
321
|
+
<ThinkingPanel
|
|
322
|
+
text={thinking.thinking}
|
|
323
|
+
isActive={true}
|
|
324
|
+
duration={0}
|
|
325
|
+
/>
|
|
326
|
+
)}
|
|
327
|
+
|
|
328
|
+
{tools.map((t, idx) => (
|
|
329
|
+
t.status === 'running'
|
|
330
|
+
? <ToolRunning key={t.id || idx} name={t.name} args={t.args} />
|
|
331
|
+
: <ToolCompleted key={t.id || idx} name={t.name} result={t.result} duration={t.duration} />
|
|
332
|
+
))}
|
|
333
|
+
|
|
334
|
+
{showStreaming && (
|
|
335
|
+
<StreamingResponse text={streamingText} isStreaming={true} />
|
|
336
|
+
)}
|
|
337
|
+
</Box>
|
|
338
|
+
)}
|
|
339
|
+
|
|
340
|
+
<PromptInput
|
|
341
|
+
onSubmit={handleSubmit}
|
|
342
|
+
musicStatus={musicStatus}
|
|
343
|
+
isProcessing={isProcessing}
|
|
344
|
+
agentStateLabel={agentStateLabel}
|
|
345
|
+
/>
|
|
346
|
+
</Box>
|
|
347
|
+
);
|
|
348
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { getInkPalette } from '../theme.js';
|
|
4
|
+
|
|
5
|
+
export function Panel({ title, lines = [], borderColor = 'sakura', width }) {
|
|
6
|
+
const theme = getInkPalette();
|
|
7
|
+
const colorMap = {
|
|
8
|
+
sakura: theme.primary, gold: theme.highlight, cyan: theme.info,
|
|
9
|
+
emerald: theme.success, slate: theme.muted, roseGlow: theme.secondary,
|
|
10
|
+
lavender: theme.tertiary, amber: theme.warning,
|
|
11
|
+
};
|
|
12
|
+
const color = colorMap[borderColor] || borderColor;
|
|
13
|
+
const backgroundColor = theme.panel;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Box flexDirection="column" width={width || '100%'}>
|
|
17
|
+
{title && (
|
|
18
|
+
<Box>
|
|
19
|
+
<Text backgroundColor={backgroundColor}> </Text>
|
|
20
|
+
<Text backgroundColor={backgroundColor} bold color={color}>{title} </Text>
|
|
21
|
+
</Box>
|
|
22
|
+
)}
|
|
23
|
+
{lines.map((line, i) => (
|
|
24
|
+
<Box key={i}>
|
|
25
|
+
<Text backgroundColor={backgroundColor}> </Text>
|
|
26
|
+
{typeof line === 'string' ? (
|
|
27
|
+
<Text backgroundColor={backgroundColor} color={theme.text}>{line} </Text>
|
|
28
|
+
) : (
|
|
29
|
+
line
|
|
30
|
+
)}
|
|
31
|
+
</Box>
|
|
32
|
+
))}
|
|
33
|
+
</Box>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function InlinePanel({ icon, text, color = 'white' }) {
|
|
38
|
+
return (
|
|
39
|
+
<Box>
|
|
40
|
+
<Text> </Text>
|
|
41
|
+
<Text>{icon} </Text>
|
|
42
|
+
<Text color={color}>{text}</Text>
|
|
43
|
+
</Box>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function Separator({ color }) {
|
|
48
|
+
const theme = getInkPalette();
|
|
49
|
+
return (
|
|
50
|
+
<Box>
|
|
51
|
+
<Text color={color || theme.subdued}>{'─'.repeat(70)}</Text>
|
|
52
|
+
</Box>
|
|
53
|
+
);
|
|
54
|
+
}
|