kiku-agent 1.3.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 +7 -7
- package/package.json +6 -1
- package/server/agent/agentCore.js +2 -2
- package/server/agent/catalogEntityResolver.js +8 -0
- package/server/agent/cli.js +4 -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/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/routes/agent.js +55 -0
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ kiku-agent
|
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
> [!NOTE]
|
|
55
|
-
> The global package deliberately excludes native binary build dependencies (Python, `node-gyp`, Visual Studio C++ build tools are not required). For a local checkout before publishing, pack and install via `npm install -g ./kiku-agent-1.3.
|
|
55
|
+
> The global package deliberately excludes native binary build dependencies (Python, `node-gyp`, Visual Studio C++ build tools are not required). For a local checkout before publishing, pack and install via `npm install -g ./kiku-agent-1.3.1.tgz`.
|
|
56
56
|
|
|
57
57
|
### Zero-Install One-Shot Query
|
|
58
58
|
|
|
@@ -96,7 +96,7 @@ npm run kiku-agent -- --provider openrouter --model openrouter/auto
|
|
|
96
96
|
|
|
97
97
|
## Features
|
|
98
98
|
|
|
99
|
-
- **Autonomous ReAct Agent**: Equipped with **
|
|
99
|
+
- **Autonomous ReAct Agent**: Equipped with **60 validated tools** covering catalog knowledge, live web search, news/encyclopedia discovery, YouTube audio playback, filesystem inspection, stateful memory checkpoints, entertainment analytics, and research workers.
|
|
100
100
|
- **Stateful & Self-Reflective Memory Engine**: Hierarchical workspace (`kiku/runs/{run_id}/...`), pointer-over-data rule (>1,000 chars referenced to prevent context bloat), real-time step checkpoints (`checkpoint_step`, `record_failure`, `load_active_run`), post-run compaction into global reflections (`kiku/knowledge/reflections.md`), and 4-tier token budget allocation.
|
|
101
101
|
- **Fault-Tolerant Hybrid Meta-Search**: Zero-key search combining DuckDuckGo, SearXNG, and Jina with token bucket rate limiting, circuit breakers, SSRF DNS pinning, multilingual query fan-out, and Reciprocal Rank Fusion (RRF).
|
|
102
102
|
- **Predictive VRAM & Telemetry Engine**: Real-time GPU memory footprint estimation, context scaling ladder, multi-stage memory bus width disambiguation, sliding-window attention (SWA) KV cache modeling, and offline database of 100+ accelerators (`/vram`, `npm run vram:live`).
|
|
@@ -214,15 +214,15 @@ Type `/help` inside any active session for the interactive command palette. Comm
|
|
|
214
214
|
|
|
215
215
|
---
|
|
216
216
|
|
|
217
|
-
## Available Agent Tools (
|
|
217
|
+
## Available Agent Tools (60)
|
|
218
218
|
|
|
219
|
-
Kiku Agent includes **
|
|
219
|
+
Kiku Agent includes **60 validated tools** across 6 categories that the agent autonomously invokes during conversations:
|
|
220
220
|
|
|
221
221
|
- **Catalog (14)**: Profile, fandom facts, quotes, filmography (2013–2026), character roles, discography, music videos, brands, career timeline, awards, Weibo/Douyin feeds, 4K gallery, and global fuzzy search.
|
|
222
222
|
- **Web & Network (6)**: Live DuckDuckGo search, hybrid meta-search orchestrator (SearXNG, Jina), article extraction, raw webpage fetch, JSON parser, page screenshots, and REST API querying.
|
|
223
223
|
- **Discovery & Analytics (11)**: Live news/RSS feeds, encyclopedia lookup, Chinese web portals, Chinese web image search (`search_web_images` for on-set stills, red carpet & press photos), Bilibili video search, music-platform search, YouTube transcripts, datetime utility, and entertainment/industry metrics.
|
|
224
224
|
- **Music Player (15)**: YouTube streaming via `yt-dlp` & `ffmpeg`, playlist queue management, track playback controls, and volume mute/unmute.
|
|
225
|
-
- **Filesystem & Stateful Memory (
|
|
225
|
+
- **Filesystem & Stateful Memory (7)**: Directory listing, safe local file reader, archive statistics, plus **Hierarchical Memory Checkpoints**: `checkpoint_step` (record execution step and state), `record_failure` (capture error code and recovery path), `load_active_run` (hydrate run context and memory pointers), and `record_dynamic_event` (log dynamic career & media updates).
|
|
226
226
|
- **Research, Skills & VIP (7)**: Autonomous research subagents, deep research coordinator, claim verification, topic suggestion, persistent skill learning, trivia quiz challenge, and Developer VIP key management.
|
|
227
227
|
|
|
228
228
|
> [!TIP]
|
|
@@ -269,7 +269,7 @@ The package ships an official Model Context Protocol server exposing the Ju Jing
|
|
|
269
269
|
kiku-mcp
|
|
270
270
|
|
|
271
271
|
# Zero-install via npx:
|
|
272
|
-
npx -y kiku-agent kiku-mcp
|
|
272
|
+
npx -y -p kiku-agent kiku-mcp
|
|
273
273
|
```
|
|
274
274
|
|
|
275
275
|
#### Claude Desktop & Cursor Integration
|
|
@@ -281,7 +281,7 @@ Add the following snippet to your `claude_desktop_config.json` or Cursor MCP con
|
|
|
281
281
|
"mcpServers": {
|
|
282
282
|
"kiku": {
|
|
283
283
|
"command": "npx",
|
|
284
|
-
"args": ["-y", "kiku-agent", "kiku-mcp"]
|
|
284
|
+
"args": ["-y", "-p", "kiku-agent", "kiku-mcp"]
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiku-agent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Kiku AI terminal agent and MCP server for Ju Jingyi knowledge, media, and career data.",
|
|
5
5
|
"license": "GPL-3.0",
|
|
6
6
|
"type": "module",
|
|
@@ -15,8 +15,13 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"server/*.js",
|
|
18
|
+
"server/routes/**/*",
|
|
18
19
|
"server/web-chat/**/*",
|
|
19
20
|
"server/agent/*.js",
|
|
21
|
+
"server/agent/websearch/**/*",
|
|
22
|
+
"server/agent/memory/**/*",
|
|
23
|
+
"server/agent/telemetry/**/*",
|
|
24
|
+
"server/agent/ink/**/*",
|
|
20
25
|
"server/mcp/*.js",
|
|
21
26
|
"server/mcp/data/*.js",
|
|
22
27
|
"server/mcp/**/*.json",
|
|
@@ -997,10 +997,10 @@ export class KikuAgent {
|
|
|
997
997
|
}
|
|
998
998
|
|
|
999
999
|
const mentionsArtist = /(ju\s?jing\s?yi|jujingyi|จวีจิ้งอี|จิ้งอี|จวี|鞠婧祎|小鞠|kikuuu?|miju|蜜橘)/i.test(promptForLLM);
|
|
1000
|
-
if (
|
|
1000
|
+
if (queryResolution.matchedEntities.length === 0 && !mentionsArtist && !autoResearchTriggered) {
|
|
1001
1001
|
turnDirectives.push({
|
|
1002
1002
|
role: 'system',
|
|
1003
|
-
content: "[SYSTEM NOTE: This query does not appear to be about Ju Jingyi or her catalog. Answer directly from your general knowledge in the user's language. Do NOT call catalog tools (profile, works, discography, brands, awards, gallery, quotes, timeline) for this query
|
|
1003
|
+
content: "[SYSTEM NOTE: This query does not appear to be about Ju Jingyi or her catalog. Answer directly from your general knowledge in the user's language. Do NOT call catalog tools (profile, works, discography, brands, awards, gallery, quotes, timeline) for this query. When using web_search, search_news, or search_chinese_web, search for the specific entity or topic the user asked about — do NOT prepend 'Ju Jingyi' or '鞠婧祎' to the search query.]"
|
|
1004
1004
|
});
|
|
1005
1005
|
}
|
|
1006
1006
|
|
|
@@ -586,6 +586,14 @@ export function getContextualizedSearchQuery(keyword = '', platform = 'bilibili'
|
|
|
586
586
|
|
|
587
587
|
const resolution = resolveQueryEntities(raw);
|
|
588
588
|
|
|
589
|
+
const ARTIST_RE = /(ju\s?jing\s?yi|jujingyi|จวีจิ้งอี|จิ้งอี|鞠婧祎|小鞠|kikuuu?|蜜橘)/i;
|
|
590
|
+
const mentionsArtist = ARTIST_RE.test(raw);
|
|
591
|
+
const hasCatalogMatch = resolution.matchedEntities.length > 0;
|
|
592
|
+
|
|
593
|
+
if (!mentionsArtist && !hasCatalogMatch) {
|
|
594
|
+
return raw;
|
|
595
|
+
}
|
|
596
|
+
|
|
589
597
|
if (platform === 'bilibili' || platform === 'chinese_web') {
|
|
590
598
|
const parts = ['鞠婧祎'];
|
|
591
599
|
|
package/server/agent/cli.js
CHANGED
|
@@ -2202,6 +2202,10 @@ async function runWebChatStandalone(opts) {
|
|
|
2202
2202
|
}
|
|
2203
2203
|
|
|
2204
2204
|
async function main() {
|
|
2205
|
+
if (process.argv[2] === 'kiku-mcp' || process.argv[2] === 'mcp-server') {
|
|
2206
|
+
await import('../mcp/stdio.js');
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2205
2209
|
const opts = parseCliArgs();
|
|
2206
2210
|
|
|
2207
2211
|
if (!opts.query && !process.stdin.isTTY) {
|
|
@@ -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
|
+
}
|