kiku-agent 1.0.4 → 1.0.6
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 +25 -2
- package/package.json +12 -3
- package/server/agent/agentCore.js +164 -55
- package/server/agent/cli.js +1300 -695
- package/server/agent/commandRegistry.js +436 -74
- package/server/agent/commands.js +629 -63
- package/server/agent/doctor.js +7 -6
- package/server/agent/mcpClient.js +436 -364
- package/server/agent/queryDetails.js +182 -0
- package/server/agent/sanitizer.js +4 -4
- package/server/agent/subagent.js +1 -0
- package/server/agent/suggestions.js +346 -0
- package/server/agent/theme.js +12 -12
- package/server/agent/tools.js +118 -7
- package/server/agent/ui.js +433 -31
- package/server/agent/updateManager.js +438 -0
- package/server/agent/version.js +10 -0
- package/server/mcp/data/brands.js +9 -4
- package/server/mcp/server.js +2 -1
- package/src/data/brandsData.js +65 -0
package/README.md
CHANGED
|
@@ -48,7 +48,24 @@ The same package also installs the stdio MCP server command:
|
|
|
48
48
|
kiku-mcp
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
### Updating the CLI and MCP server
|
|
52
|
+
|
|
53
|
+
Kiku uses the published `kiku-agent` package on npm as its update source. Inside an interactive session:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
/update # Check the configured release channel
|
|
57
|
+
/update install # Install the stable `latest` release
|
|
58
|
+
/update install next # Install a preview release
|
|
59
|
+
/update install 1.2.3 # Install an exact published version
|
|
60
|
+
/update channel next # Persist the preferred channel
|
|
61
|
+
/update rollback # Restore the previously recorded version
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Automatic installation is available for global npm installs. Source checkouts, local dependencies, and `npx` executions receive a safe manual command instead of having their files replaced. Major updates require `--allow-major`; deprecated releases require `--allow-deprecated`. npm commands are executed without a shell and only allow known channel names or strict semantic versions.
|
|
65
|
+
|
|
66
|
+
After an update or rollback, restart `kiku-agent` and any application hosting `kiku-mcp`. Update settings and rollback history are stored in the normal Kiku user data directory as `update-state.json`; sessions, memory, `.env`, and YouTube cookies are not modified.
|
|
67
|
+
|
|
68
|
+
For a local checkout before the package is published, use `npm pack` followed by `npm install -g ./kiku-agent-1.0.6.tgz`.
|
|
52
69
|
|
|
53
70
|
### ✨ Features
|
|
54
71
|
- **LM Studio Integration**: Connects seamlessly to LM Studio's OpenAI-compatible server at `http://127.0.0.1:1234/v1` (default model `google/gemma-4-e4b`, with auto-detection of loaded models).
|
|
@@ -104,6 +121,7 @@ npm run kiku-agent -- --model google/gemma-4-e4b-Q4_K_M --url http://127.0.0.1:1
|
|
|
104
121
|
| Flag | Description |
|
|
105
122
|
|---|---|
|
|
106
123
|
| `-h`, `--help` | Show usage help |
|
|
124
|
+
| `-V`, `--version` | Print the installed package version and exit |
|
|
107
125
|
| `-P`, `--provider <name>` | Select active provider (`lmstudio`, `openai`, `gemini`, `groq`, `deepseek`, `openrouter`, `custom`) |
|
|
108
126
|
| `-k`, `--api-key <key>` | Specify API key for cloud provider |
|
|
109
127
|
| `-m`, `--model <id>` | Override model ID (e.g. `gpt-4o-mini`, `google/gemma-4-e4b-Q4_K_M`) |
|
|
@@ -131,8 +149,11 @@ Run `/help` inside a session for the full list. Commonly used:
|
|
|
131
149
|
- `/model use <model-id>` — Switch runtime model without restarting the CLI
|
|
132
150
|
- `/model reconnect` — Recheck server and leave catalog-only mode after a chat model is loaded
|
|
133
151
|
- `/doctor` — Check Node, writable app data, LM Studio, model availability, FFmpeg, speaker, and yt-dlp
|
|
152
|
+
- `/update [check|install|channel|rollback]` — Manage npm-backed CLI and MCP package updates
|
|
134
153
|
- `/tools` — View all tool names and parameter descriptions
|
|
135
|
-
- `/details [Qn]` —
|
|
154
|
+
- `/details [Qn]` — Open a compact diagnostic overview for the latest or selected question (F2 opens latest)
|
|
155
|
+
- `/details [Qn] answer|thinking|tools` — Inspect one diagnostic section without dumping every raw result
|
|
156
|
+
- `/details [Qn] tool <n>` — Inspect bounded raw arguments and result for one tool call
|
|
136
157
|
- `/stats` — Print summary counts of dramas, songs, brands, and awards
|
|
137
158
|
- `/thinking [show|hide]` — Toggle reasoning process visibility
|
|
138
159
|
- `/save` — Export session transcript to Markdown file
|
|
@@ -252,6 +273,8 @@ The production deployment exposes a stateless Streamable HTTP endpoint at `https
|
|
|
252
273
|
/mcp tools
|
|
253
274
|
```
|
|
254
275
|
|
|
276
|
+
`/mcp` shows the connection summary plus every available MCP tool command in a compact list. `/mcp tools` expands the same list with parameters and descriptions. Long command panels open at their first page in the classic TUI; use Page Down or End to continue through the result.
|
|
277
|
+
|
|
255
278
|
---
|
|
256
279
|
|
|
257
280
|
## ⚙️ Environment Variables
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiku-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Kiku AI terminal agent and MCP server for Ju Jingyi knowledge, media, and career data.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,16 @@
|
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=20.0.0"
|
|
25
25
|
},
|
|
26
|
+
"kikuUpdate": {
|
|
27
|
+
"channel": "stable",
|
|
28
|
+
"releaseSummary": [
|
|
29
|
+
"AI research suggestion engine and batch subagent execution",
|
|
30
|
+
"Enhanced web search fallbacks and missing catalog tool hints",
|
|
31
|
+
"Updated brand endorsements (Versace) and dual catalog sync",
|
|
32
|
+
"Secure npm-backed updates and terminal markdown renderer"
|
|
33
|
+
],
|
|
34
|
+
"releaseUrl": "https://www.npmjs.com/package/kiku-agent"
|
|
35
|
+
},
|
|
26
36
|
"keywords": [
|
|
27
37
|
"kiku",
|
|
28
38
|
"ju-jingyi",
|
|
@@ -51,8 +61,7 @@
|
|
|
51
61
|
"kiku-agent": "node server/agent/cli.js",
|
|
52
62
|
"kiku-mcp": "node server/mcp/stdio.js",
|
|
53
63
|
"kiku-ink": "npx tsx server/agent/ink/main.jsx",
|
|
54
|
-
"test": "node --test server/agent/tests/*.test.js server/mcp/tests/*.test.js"
|
|
55
|
-
"prepublishOnly": "npm test"
|
|
64
|
+
"test": "node --test server/agent/tests/*.test.js server/mcp/tests/*.test.js"
|
|
56
65
|
},
|
|
57
66
|
"dependencies": {
|
|
58
67
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
@@ -9,6 +9,7 @@ import { sanitizeQuery, isLikelySafe } from './sanitizer.js';
|
|
|
9
9
|
import { createAbortError, isAbortError } from './errors.js';
|
|
10
10
|
import { ResearchSubagent } from './subagent.js';
|
|
11
11
|
import { ResearchOrchestrator } from './research.js';
|
|
12
|
+
import { SuggestionEngine } from './suggestions.js';
|
|
12
13
|
import { McpClientManager } from './mcpClient.js';
|
|
13
14
|
import { DynamicToolRouter } from './toolRouter.js';
|
|
14
15
|
import { contextCompactor } from './contextCompactor.js';
|
|
@@ -62,6 +63,7 @@ export class KikuAgent {
|
|
|
62
63
|
this.mcpManager = options.mcpManager || new McpClientManager();
|
|
63
64
|
this.router = new DynamicToolRouter(this.getAllToolsSchema());
|
|
64
65
|
this.research = new ResearchOrchestrator({ client: this.client });
|
|
66
|
+
this.suggestions = new SuggestionEngine({ client: this.client, memory: this.memory });
|
|
65
67
|
this.systemPrompt = this.buildSystemPrompt();
|
|
66
68
|
this.initHistory();
|
|
67
69
|
this.restoredMessageCount = 0;
|
|
@@ -88,7 +90,7 @@ export class KikuAgent {
|
|
|
88
90
|
await this.mcpManager.loadPersistedServers();
|
|
89
91
|
this.router.setTools(this.getAllToolsSchema());
|
|
90
92
|
this.systemPrompt = this.buildSystemPrompt();
|
|
91
|
-
} catch {}
|
|
93
|
+
} catch { }
|
|
92
94
|
}
|
|
93
95
|
if (!this.conversationHistory || this.conversationHistory.length === 0) {
|
|
94
96
|
this.initHistory();
|
|
@@ -128,6 +130,20 @@ export class KikuAgent {
|
|
|
128
130
|
return this.research.cancel();
|
|
129
131
|
}
|
|
130
132
|
|
|
133
|
+
async generateSuggestions(options = {}) {
|
|
134
|
+
await this._ready;
|
|
135
|
+
return this.suggestions.generateSuggestions(options);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async runSuggestionBatch(targets, options = {}) {
|
|
139
|
+
await this._ready;
|
|
140
|
+
return this.suggestions.runBatchSubagents(targets, options);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getLastSuggestions() {
|
|
144
|
+
return this.suggestions.getLastSuggestions();
|
|
145
|
+
}
|
|
146
|
+
|
|
131
147
|
initHistory() {
|
|
132
148
|
this.conversationHistory = [
|
|
133
149
|
{
|
|
@@ -139,12 +155,12 @@ export class KikuAgent {
|
|
|
139
155
|
|
|
140
156
|
clearMemory() {
|
|
141
157
|
if (this.session.messages.length > 0) {
|
|
142
|
-
this.memory.archiveSession(this.session.toSnapshot()).catch(() => {});
|
|
143
|
-
this.memory.consolidateSession(this.session.toSnapshot()).catch(() => {});
|
|
158
|
+
this.memory.archiveSession(this.session.toSnapshot()).catch(() => { });
|
|
159
|
+
this.memory.consolidateSession(this.session.toSnapshot()).catch(() => { });
|
|
144
160
|
}
|
|
145
161
|
this.initHistory();
|
|
146
162
|
this.session.reset();
|
|
147
|
-
this.session.clearDisk().catch(() => {});
|
|
163
|
+
this.session.clearDisk().catch(() => { });
|
|
148
164
|
}
|
|
149
165
|
|
|
150
166
|
async loadSession() {
|
|
@@ -156,7 +172,7 @@ export class KikuAgent {
|
|
|
156
172
|
content: this.systemPrompt
|
|
157
173
|
}
|
|
158
174
|
];
|
|
159
|
-
|
|
175
|
+
|
|
160
176
|
for (const msg of this.session.messages) {
|
|
161
177
|
if (msg.role === 'user') {
|
|
162
178
|
const textPart = msg.parts.find(p => p.type === 'text');
|
|
@@ -224,6 +240,47 @@ export class KikuAgent {
|
|
|
224
240
|
return result;
|
|
225
241
|
}
|
|
226
242
|
|
|
243
|
+
async removeMcp(name) {
|
|
244
|
+
const result = await this.mcpManager.remove(name);
|
|
245
|
+
this.router.setTools(this.getAllToolsSchema());
|
|
246
|
+
this.systemPrompt = this.buildSystemPrompt();
|
|
247
|
+
if (this.conversationHistory && this.conversationHistory[0]?.role === 'system') {
|
|
248
|
+
this.conversationHistory[0].content = this.getEnrichedSystemPrompt();
|
|
249
|
+
}
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async reconnectMcp(name) {
|
|
254
|
+
const result = name && name !== 'all'
|
|
255
|
+
? await this.mcpManager.reconnect(name)
|
|
256
|
+
: await this.mcpManager.reconnectAll();
|
|
257
|
+
this.router.setTools(this.getAllToolsSchema());
|
|
258
|
+
this.systemPrompt = this.buildSystemPrompt();
|
|
259
|
+
if (this.conversationHistory && this.conversationHistory[0]?.role === 'system') {
|
|
260
|
+
this.conversationHistory[0].content = this.getEnrichedSystemPrompt();
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async saveMcpConfig(target = 'global') {
|
|
266
|
+
if (target === 'workspace' || target === 'local') {
|
|
267
|
+
return this.mcpManager.saveToWorkspace();
|
|
268
|
+
}
|
|
269
|
+
return this.mcpManager.savePersistedConfig();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async loadMcpConfig(customPath) {
|
|
273
|
+
const results = customPath
|
|
274
|
+
? await this.mcpManager.loadConfigFile(customPath)
|
|
275
|
+
: await this.mcpManager.loadPersistedServers();
|
|
276
|
+
this.router.setTools(this.getAllToolsSchema());
|
|
277
|
+
this.systemPrompt = this.buildSystemPrompt();
|
|
278
|
+
if (this.conversationHistory && this.conversationHistory[0]?.role === 'system') {
|
|
279
|
+
this.conversationHistory[0].content = this.getEnrichedSystemPrompt();
|
|
280
|
+
}
|
|
281
|
+
return results;
|
|
282
|
+
}
|
|
283
|
+
|
|
227
284
|
buildSystemPrompt() {
|
|
228
285
|
const allTools = this.getAllToolsSchema();
|
|
229
286
|
const toolsSummary = allTools.map(t => {
|
|
@@ -280,9 +337,9 @@ ${toolsSummary}
|
|
|
280
337
|
- Directory listing ➔ list_directory
|
|
281
338
|
- Screenshot ➔ screenshot_webpage
|
|
282
339
|
|
|
283
|
-
6. AGGRESSIVE WEB SEARCH FALLBACK:
|
|
284
|
-
- If local
|
|
285
|
-
- Combine local data WITH web search for complete, accurate answers.
|
|
340
|
+
6. AGGRESSIVE WEB SEARCH FALLBACK (CRITICAL):
|
|
341
|
+
- If a local catalog tool (e.g. search_works, search_discography, get_brand_endorsements, get_awards) returns not_found, empty, or 0 results, DO NOT give up or guess. You MUST IMMEDIATELY call web_search or web_extract in the next step to look up live external facts online.
|
|
342
|
+
- Combine local catalog data WITH live web search for complete, accurate, and up-to-date answers.
|
|
286
343
|
- Use web_extract for comprehensive research on any topic.
|
|
287
344
|
|
|
288
345
|
7. LEGAL & CAREER KNOWLEDGE:
|
|
@@ -367,18 +424,18 @@ ${toolsSummary}
|
|
|
367
424
|
for (const topic of topics) {
|
|
368
425
|
this.memory.trackTopicInterest(topic);
|
|
369
426
|
}
|
|
370
|
-
|
|
427
|
+
|
|
371
428
|
const {
|
|
372
|
-
onStepStart = () => {},
|
|
373
|
-
onReasoningStart = () => {},
|
|
374
|
-
onReasoningDelta = () => {},
|
|
375
|
-
onReasoningEnd = () => {},
|
|
376
|
-
onToolStart = () => {},
|
|
377
|
-
onToolResult = () => {},
|
|
378
|
-
onTokenStart = () => {},
|
|
379
|
-
onTokenDelta = () => {},
|
|
380
|
-
onFinalAnswer = () => {},
|
|
381
|
-
onError = () => {},
|
|
429
|
+
onStepStart = () => { },
|
|
430
|
+
onReasoningStart = () => { },
|
|
431
|
+
onReasoningDelta = () => { },
|
|
432
|
+
onReasoningEnd = () => { },
|
|
433
|
+
onToolStart = () => { },
|
|
434
|
+
onToolResult = () => { },
|
|
435
|
+
onTokenStart = () => { },
|
|
436
|
+
onTokenDelta = () => { },
|
|
437
|
+
onFinalAnswer = () => { },
|
|
438
|
+
onError = () => { },
|
|
382
439
|
signal = null
|
|
383
440
|
} = callbacks;
|
|
384
441
|
|
|
@@ -509,6 +566,15 @@ ${toolsSummary}
|
|
|
509
566
|
let toolCalls = assistantMessage.tool_calls || [];
|
|
510
567
|
if (toolCalls.length === 0 && assistantMessage.content) {
|
|
511
568
|
toolCalls = this.parseTextToolCalls(assistantMessage.content);
|
|
569
|
+
|
|
570
|
+
if (toolCalls.length > 0) {
|
|
571
|
+
let cleaned = assistantMessage.content;
|
|
572
|
+
|
|
573
|
+
cleaned = cleaned.replace(/```(?:json)?\s*\{[\s\S]*?\}\s*```/g, '');
|
|
574
|
+
|
|
575
|
+
cleaned = cleaned.replace(/Action:\s*[a-zA-Z0-9_]+\s*\nAction Input:\s*(?:\{[\s\S]*?\}|[^\n]+)/g, '');
|
|
576
|
+
assistantMessage.content = cleaned.trim();
|
|
577
|
+
}
|
|
512
578
|
}
|
|
513
579
|
|
|
514
580
|
this.conversationHistory.push(assistantMessage);
|
|
@@ -519,8 +585,8 @@ ${toolsSummary}
|
|
|
519
585
|
|
|
520
586
|
const finalAnswer = assistantMessage.content || (
|
|
521
587
|
dominantLang === 'zh' ? "抱歉,无法处理您的回答。" :
|
|
522
|
-
|
|
523
|
-
|
|
588
|
+
dominantLang === 'en' ? "Sorry, unable to process a response." :
|
|
589
|
+
"ขออภัยค่ะ ไม่สามารถประมวลผลคำตอบได้"
|
|
524
590
|
);
|
|
525
591
|
onFinalAnswer(finalAnswer, { durationMs: totalDurationMs, language: dominantLang });
|
|
526
592
|
this._extractFactsFromFinalAnswer(finalAnswer);
|
|
@@ -532,11 +598,8 @@ ${toolsSummary}
|
|
|
532
598
|
return finalAnswer;
|
|
533
599
|
}
|
|
534
600
|
|
|
535
|
-
|
|
536
|
-
if (signal?.aborted)
|
|
537
|
-
this.stateMachine.reset();
|
|
538
|
-
throw createAbortError();
|
|
539
|
-
}
|
|
601
|
+
const executionPromises = toolCalls.map(async (tc, toolCallIndex) => {
|
|
602
|
+
if (signal?.aborted) throw createAbortError();
|
|
540
603
|
|
|
541
604
|
const toolName = tc?.function?.name || '';
|
|
542
605
|
const toolCallId = tc?.id || `call_${queryId}_${currentIteration}_${toolCallIndex}`;
|
|
@@ -560,20 +623,21 @@ ${toolsSummary}
|
|
|
560
623
|
validationWarnings: prepared.warnings,
|
|
561
624
|
};
|
|
562
625
|
eventBus.emit(Events.AGENT_TOOL_ERROR, {
|
|
563
|
-
queryId, toolName, args: tc
|
|
626
|
+
queryId, toolName, args: tc?.function?.arguments,
|
|
564
627
|
error: prepared.error,
|
|
565
628
|
});
|
|
566
629
|
onToolStart(toolName, {});
|
|
567
630
|
onToolResult(toolName, rejectedResult, 0);
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
631
|
+
return {
|
|
632
|
+
toolMessage: {
|
|
633
|
+
role: 'tool',
|
|
634
|
+
tool_call_id: toolCallId,
|
|
635
|
+
name: toolName,
|
|
636
|
+
content: JSON.stringify(rejectedResult)
|
|
637
|
+
}
|
|
573
638
|
};
|
|
574
|
-
this.conversationHistory.push(toolMessage);
|
|
575
|
-
continue;
|
|
576
639
|
}
|
|
640
|
+
|
|
577
641
|
if (prepared.warnings.length > 0) {
|
|
578
642
|
eventBus.emit(Events.AGENT_TOOL_ERROR, {
|
|
579
643
|
queryId, toolName, warnings: prepared.warnings,
|
|
@@ -581,7 +645,6 @@ ${toolsSummary}
|
|
|
581
645
|
}
|
|
582
646
|
|
|
583
647
|
const finalArgs = prepared.args;
|
|
584
|
-
|
|
585
648
|
this.stateMachine.transition(AgentStates.FETCHING_TOOL, { toolName });
|
|
586
649
|
|
|
587
650
|
const toolPart = this.session.addToolPart(currentAssistantSessionMsg, toolName, finalArgs);
|
|
@@ -610,30 +673,40 @@ ${toolsSummary}
|
|
|
610
673
|
|
|
611
674
|
this._extractFactsFromToolResult(toolName, finalArgs, result);
|
|
612
675
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
:
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
676
|
+
|
|
677
|
+
const cappedResult = this._capToolResultForHistory(result);
|
|
678
|
+
|
|
679
|
+
return {
|
|
680
|
+
toolMessage: {
|
|
681
|
+
role: 'tool',
|
|
682
|
+
tool_call_id: toolCallId,
|
|
683
|
+
name: toolName,
|
|
684
|
+
content: JSON.stringify({
|
|
685
|
+
authoritative: true,
|
|
686
|
+
result: cappedResult,
|
|
687
|
+
instruction: result.status === 'not_found'
|
|
688
|
+
? 'No matching data was found. Do not invent records; say that the catalog has no match or use an appropriate fallback tool.'
|
|
689
|
+
: result.status === 'error'
|
|
690
|
+
? 'The tool failed. Do not claim that the requested facts were verified.'
|
|
691
|
+
: 'Use only facts present in this result when answering. Do not echo raw JSON data — summarize in natural language.'
|
|
692
|
+
})
|
|
693
|
+
}
|
|
626
694
|
};
|
|
695
|
+
});
|
|
627
696
|
|
|
628
|
-
|
|
697
|
+
const toolResults = await Promise.all(executionPromises);
|
|
698
|
+
for (const item of toolResults) {
|
|
699
|
+
if (item?.toolMessage) {
|
|
700
|
+
this.conversationHistory.push(item.toolMessage);
|
|
701
|
+
}
|
|
629
702
|
}
|
|
630
703
|
}
|
|
631
704
|
|
|
632
|
-
const langPrompt = dominantLang === 'zh'
|
|
705
|
+
const langPrompt = dominantLang === 'zh'
|
|
633
706
|
? "请根据上述工具查询结果,直接回答(不要开头问候语),用流畅优雅的中文,先1-2句概述,再列要点。包含具体的 YouTube 真实链接(如 Direct Watch: https://www.youtube.com/watch?v=... หรือ Search: https://www.youtube.com/results?search_query=...)及事实数据。"
|
|
634
707
|
: dominantLang === 'en'
|
|
635
|
-
|
|
636
|
-
|
|
708
|
+
? "Based on the tool results above, answer directly (no opening greeting or template phrase). Give a 1-2 sentence summary, then concise bullet points in polished English. Include verified YouTube direct and search links (https://www.youtube.com/results?search_query=...)."
|
|
709
|
+
: "สรุปคำตอบจากผลลัพธ์เครื่องมือข้างต้น โดยไม่ต้องทักทาย ตอบตรงประเด็น 1-2 ประโยค แล้วตามด้วย bullet points สั้นๆ เป็นภาษาไทย พร้อมแสดงลิงก์ YouTube จริง (Direct Watch Link และ https://www.youtube.com/results?search_query=...) และข้อมูลผลงานที่ถูกต้องแม่นยำ";
|
|
637
710
|
|
|
638
711
|
this.conversationHistory.push({ role: 'user', content: langPrompt });
|
|
639
712
|
|
|
@@ -677,19 +750,55 @@ ${toolsSummary}
|
|
|
677
750
|
const defaultDone = dominantLang === 'zh' ? "查询完毕" : dominantLang === 'en' ? "Search complete." : "เสร็จสิ้นการค้นหาข้อมูล";
|
|
678
751
|
const output = finalRes?.content || defaultDone;
|
|
679
752
|
onFinalAnswer(output, { durationMs: totalDurationMs, language: dominantLang });
|
|
680
|
-
|
|
753
|
+
|
|
681
754
|
this._extractFactsFromFinalAnswer(output);
|
|
682
755
|
|
|
683
756
|
this.stateMachine.transition(AgentStates.COMPLETE);
|
|
684
757
|
eventBus.emit(Events.AGENT_FINAL_ANSWER, { queryId, answer: output, durationMs: totalDurationMs });
|
|
685
758
|
eventBus.emit(Events.AGENT_QUERY_END, { queryId, durationMs: totalDurationMs });
|
|
686
759
|
this.stateMachine.reset();
|
|
687
|
-
|
|
760
|
+
|
|
688
761
|
this.saveSession().catch(err => console.error('Session save failed:', err));
|
|
689
|
-
|
|
762
|
+
|
|
690
763
|
return output;
|
|
691
764
|
}
|
|
692
765
|
|
|
766
|
+
_capToolResultForHistory(result) {
|
|
767
|
+
if (!result || typeof result !== 'object') return result;
|
|
768
|
+
|
|
769
|
+
const MAX_RESULT_BYTES = 4096;
|
|
770
|
+
const serialized = JSON.stringify(result);
|
|
771
|
+
if (serialized.length <= MAX_RESULT_BYTES) return result;
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
const truncate = (obj) => {
|
|
775
|
+
if (Array.isArray(obj)) {
|
|
776
|
+
if (obj.length > 8) {
|
|
777
|
+
const kept = obj.slice(0, 8).map(truncate);
|
|
778
|
+
kept.push({ _truncated: true, totalItems: obj.length, showing: 8 });
|
|
779
|
+
return kept;
|
|
780
|
+
}
|
|
781
|
+
return obj.map(truncate);
|
|
782
|
+
}
|
|
783
|
+
if (obj && typeof obj === 'object') {
|
|
784
|
+
const out = {};
|
|
785
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
786
|
+
out[key] = truncate(val);
|
|
787
|
+
}
|
|
788
|
+
return out;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (typeof obj === 'string' && obj.length > 500) {
|
|
792
|
+
return obj.slice(0, 500) + '…[truncated]';
|
|
793
|
+
}
|
|
794
|
+
return obj;
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
const capped = truncate(result);
|
|
798
|
+
capped._cappedForHistory = true;
|
|
799
|
+
return capped;
|
|
800
|
+
}
|
|
801
|
+
|
|
693
802
|
_extractFactsFromToolResult(toolName, toolArgs, result) {
|
|
694
803
|
if (!result || result.status === 'error' || result.status === 'not_found') return;
|
|
695
804
|
|