kiku-agent 1.3.1 → 1.3.3
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 +69 -12
- package/package.json +5 -12
- package/server/agent/agentCore.js +319 -46
- package/server/agent/appPaths.js +31 -0
- package/server/agent/audioPlayer.js +339 -12
- package/server/agent/autoContext.js +193 -0
- package/server/agent/catalogEntityResolver.js +170 -28
- package/server/agent/clarifyingQuestions.js +137 -35
- package/server/agent/cli.js +177 -48
- package/server/agent/cloudAdapters.js +116 -0
- package/server/agent/commandRegistry.js +409 -80
- package/server/agent/commands.js +1056 -125
- package/server/agent/config.js +1088 -690
- package/server/agent/contextCompactor.js +213 -21
- package/server/agent/cpuMetrics.js +199 -0
- package/server/agent/engineService.js +591 -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 +288 -0
- package/server/agent/eval/modules/evaluation/scoring.js +363 -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 +4799 -0
- package/server/agent/eval/modules/utils/smokes.js +119 -0
- package/server/agent/eventBus.js +1 -0
- package/server/agent/ink/main.jsx +196 -191
- package/server/agent/jobManager.js +11 -1
- package/server/agent/lanDiscovery.js +721 -23
- package/server/agent/llmClient.js +317 -71
- package/server/agent/lmStudioWakeup.js +517 -65
- package/server/agent/locationService.js +797 -0
- package/server/agent/mcpQuizData.js +509 -0
- package/server/agent/memory/contextAllocator.js +19 -7
- package/server/agent/memory.js +3 -1
- package/server/agent/modelCompatibility.js +352 -0
- package/server/agent/modelDefaults.js +1 -0
- package/server/agent/modelManager.js +46 -2
- package/server/agent/newsWatch.js +9 -3
- package/server/agent/processManager.js +229 -0
- package/server/agent/prompts.js +174 -8
- package/server/agent/routingInterceptor.js +9 -1
- package/server/agent/sanitizer.js +41 -6
- package/server/agent/searchQueryPlanner.js +202 -0
- package/server/agent/session.js +59 -4
- package/server/agent/tests/agentBubbleSystem.test.js +65 -0
- package/server/agent/tests/agentFollowUps.test.js +253 -0
- package/server/agent/tests/agentMusicControl.test.js +245 -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/autoContext.test.js +176 -0
- package/server/agent/tests/autoResearch.test.js +64 -0
- package/server/agent/tests/bilibiliEmbed.test.js +296 -0
- package/server/agent/tests/catalogSync.test.js +130 -0
- package/server/agent/tests/chatHardening.test.js +121 -0
- package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -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 +627 -0
- package/server/agent/tests/cliAutomation.test.js +158 -0
- package/server/agent/tests/cloudProviders.test.js +322 -0
- package/server/agent/tests/commandInput.test.js +24 -0
- package/server/agent/tests/commands.test.js +506 -0
- package/server/agent/tests/contextIntelligence.test.js +209 -0
- package/server/agent/tests/conversationSafety.test.js +53 -0
- package/server/agent/tests/cpuMetrics.test.js +101 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +167 -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 +131 -0
- package/server/agent/tests/engineService.test.js +436 -0
- package/server/agent/tests/engineUnload.test.js +94 -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 +284 -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 +320 -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/fetchApi.test.js +99 -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 +194 -0
- package/server/agent/tests/judgeService.test.js +251 -0
- package/server/agent/tests/lanDiscovery.test.js +324 -0
- package/server/agent/tests/lanInterfaces.test.js +510 -0
- package/server/agent/tests/lmStudioWakeup.test.js +278 -0
- package/server/agent/tests/locationTool.test.js +278 -0
- package/server/agent/tests/mcpClient.test.js +201 -0
- package/server/agent/tests/memoryRoute.test.js +173 -0
- package/server/agent/tests/memoryUiNormalization.test.js +75 -0
- package/server/agent/tests/modelCompatibility.test.js +148 -0
- package/server/agent/tests/modelManager.test.js +141 -0
- package/server/agent/tests/multilingualScenarios.test.js +69 -0
- package/server/agent/tests/musicControlUiBridge.test.js +113 -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 +101 -0
- package/server/agent/tests/offlineInput.test.js +217 -0
- package/server/agent/tests/openDomainPrompt.test.js +197 -0
- package/server/agent/tests/openDomainSearch.test.js +121 -0
- package/server/agent/tests/packagingGuard.test.js +57 -0
- package/server/agent/tests/presentation.test.js +61 -0
- package/server/agent/tests/processManager.test.js +144 -0
- package/server/agent/tests/providerPersistence.test.js +44 -0
- package/server/agent/tests/queryBenchmarks.test.js +83 -0
- package/server/agent/tests/queryDetails.test.js +126 -0
- package/server/agent/tests/quickActionsAndModals.test.js +180 -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/searchQueryPlanner.test.js +108 -0
- package/server/agent/tests/securityAuditRemediation.test.js +221 -0
- package/server/agent/tests/securityAuditRemediations2.test.js +154 -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 +267 -0
- package/server/agent/tests/sidebarControls.test.js +135 -0
- package/server/agent/tests/statefulMemory.test.js +277 -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 +197 -0
- package/server/agent/tests/textToolParsing.test.js +187 -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 +160 -0
- package/server/agent/tests/tools.test.js +259 -0
- package/server/agent/tests/unifiedEndpoints.test.js +159 -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 +595 -0
- package/server/agent/tests/webChatDocs.test.js +199 -0
- package/server/agent/tests/webChatInferenceControl.test.js +298 -0
- package/server/agent/tests/webChatInspection.test.js +551 -0
- package/server/agent/tests/webChatMcp.test.js +94 -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 +284 -0
- package/server/agent/tests/webSources.test.js +281 -0
- package/server/agent/textToolProtocol.js +173 -46
- package/server/agent/toolRouter.js +318 -44
- package/server/agent/tools.js +877 -96
- package/server/agent/ui.js +39 -34
- package/server/agent/vramService.js +35 -54
- package/server/agent/webSearch.js +441 -162
- package/server/agent/websearch/orchestrator.js +15 -5
- package/server/agent/websearch/resilience/cache.js +9 -2
- package/server/agent/websearch/security/ssrf.js +41 -10
- package/server/agent/youtubeAuth.js +5 -20
- package/server/apiRouter.js +5558 -4422
- package/server/eval/judgeRubrics.js +110 -0
- package/server/eval/judgeService.js +290 -0
- package/server/eval/runBenchmark.js +182 -0
- package/server/mcp/data/discography.js +2 -2
- package/server/mcp/examples/antigravity.example.json +17 -0
- package/server/mcp/examples/librechat.example.yaml +16 -0
- package/server/mcp/handler.js +8 -4
- package/server/mcp/security.js +220 -21
- package/server/mcp/server.js +2 -1
- package/server/mcp/tests/connection.test.js +162 -0
- package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
- package/server/mcp/tools.js +7 -1
- package/server/supabase_migration_prefix.sql +102 -0
- package/server/supabase_schema.sql +63 -0
- package/server/web-chat/core/connection.js +179 -218
- package/server/web-chat/core/conversationContext.js +9 -2
- package/server/web-chat/core/directCatalogEngine.js +52 -28
- package/server/web-chat/core/dramaKnowledge.js +33 -22
- package/server/web-chat/core/followUpEngine.js +735 -81
- package/server/web-chat/core/futureEventGuard.js +5 -1
- package/server/web-chat/core/musicControlAction.js +70 -0
- package/server/web-chat/core/security.js +32 -21
- package/server/web-chat/core/toolInspector.js +6 -6
- package/server/web-chat/core/urlGuard.js +45 -0
- package/server/web-chat/router.js +204 -157
- package/server/web-chat/routes/chatRoute.js +885 -488
- package/server/web-chat/routes/engineRoute.js +270 -0
- package/server/web-chat/routes/exportRoute.js +1 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
- package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
- package/server/web-chat/routes/mcpRoute.js +386 -0
- package/server/web-chat/routes/memoryRoute.js +253 -0
- package/server/web-chat/routes/providerRoute.js +49 -16
- package/server/web-chat/routes/staticRoute.js +306 -17
- package/server/web-chat/routes/statusRoute.js +194 -73
- package/server/web-chat/routes/suggestRoute.js +11 -3
- package/server/web-chat/routes/verifyRoute.js +26 -4
- package/server/web-chat/server.js +7 -6
- package/server/web-chat/ui/docsBuilder.js +3558 -0
- package/server/web-chat/ui/htmlBuilder.js +12 -0
- package/server/web-chat/ui/scripts/client.js +3401 -1302
- package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
- package/server/web-chat/ui/settingsBuilder.js +7870 -0
- package/server/web-chat/ui/styles/lan-card.css +165 -0
- package/server/web-chat/ui/styles/main.css +4289 -447
- package/server/web-chat/ui/sw.js +64 -0
- package/server/web-chat/ui/templates/body.html +407 -281
- package/src/data/awardsData.js +146 -146
- package/src/data/musicVideoData.js +6 -4
- package/src/lib/vram/estimator.js +195 -19
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
loadDynamicCatalog,
|
|
9
|
+
saveDynamicCatalog,
|
|
10
|
+
validateCatalogEntry,
|
|
11
|
+
addDynamicEntry,
|
|
12
|
+
getDynamicEntries,
|
|
13
|
+
clearDynamicCatalog,
|
|
14
|
+
getDynamicCatalogStats,
|
|
15
|
+
pruneExpiredEntries
|
|
16
|
+
} from '../dynamicCatalog.js';
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
isTrustedSource,
|
|
20
|
+
computeEventSimHash,
|
|
21
|
+
isDuplicateEvent,
|
|
22
|
+
extractStructuredEvent,
|
|
23
|
+
ingestRawEvent,
|
|
24
|
+
runIngestionScan
|
|
25
|
+
} from '../ingestionWorker.js';
|
|
26
|
+
|
|
27
|
+
import { executeTool } from '../tools.js';
|
|
28
|
+
import { routeCommand } from '../commands.js';
|
|
29
|
+
|
|
30
|
+
test('Dynamic Catalog: validation and CRUD operations', async (t) => {
|
|
31
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'kiku-dynamic-test-'));
|
|
32
|
+
const catalogPath = path.join(tmpDir, 'dynamic-catalog.json');
|
|
33
|
+
|
|
34
|
+
await t.test('validateCatalogEntry validates categories and required titles', () => {
|
|
35
|
+
assert.equal(validateCatalogEntry('brands', { brand: 'Lafang' }).valid, true);
|
|
36
|
+
assert.equal(validateCatalogEntry('invalid_cat', { title: 'Test' }).valid, false);
|
|
37
|
+
assert.equal(validateCatalogEntry('works', {}).valid, false);
|
|
38
|
+
assert.equal(validateCatalogEntry('timeline', { title: 'Milestone 2026' }).valid, true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await t.test('addDynamicEntry persists and assigns clean IDs and metadata', () => {
|
|
42
|
+
const entry = addDynamicEntry('brands', {
|
|
43
|
+
brand: 'TuTuCare',
|
|
44
|
+
brandNameEn: 'TuTuCare',
|
|
45
|
+
brandNameCn: '途途呵护',
|
|
46
|
+
year: 2024,
|
|
47
|
+
role: 'Brand Ambassador',
|
|
48
|
+
highlight: 'Pet and rabbit care ambassador'
|
|
49
|
+
}, { catalogPath, trustTier: 1, source: 'official_weibo' });
|
|
50
|
+
|
|
51
|
+
assert.ok(entry.id.startsWith('dyn-bra-'));
|
|
52
|
+
assert.equal(entry.brand, 'TuTuCare');
|
|
53
|
+
assert.equal(entry.trustTier, 1);
|
|
54
|
+
assert.equal(entry.year, 2024);
|
|
55
|
+
|
|
56
|
+
const brands = getDynamicEntries('brands', null, catalogPath);
|
|
57
|
+
assert.equal(brands.length, 1);
|
|
58
|
+
assert.equal(brands[0].id, entry.id);
|
|
59
|
+
|
|
60
|
+
const stats = getDynamicCatalogStats(catalogPath);
|
|
61
|
+
assert.equal(stats.totalEntries, 1);
|
|
62
|
+
assert.equal(stats.brandsCount, 1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await t.test('pruneExpiredEntries removes expired entries and retains valid ones', () => {
|
|
66
|
+
|
|
67
|
+
const eightDaysAgo = new Date(Date.now() - 8 * 24 * 3600 * 1000).toISOString();
|
|
68
|
+
addDynamicEntry('events', {
|
|
69
|
+
id: 'old-event-1',
|
|
70
|
+
title: 'Old Live Stream',
|
|
71
|
+
createdAt: eightDaysAgo
|
|
72
|
+
}, { catalogPath });
|
|
73
|
+
|
|
74
|
+
const statsBefore = getDynamicCatalogStats(catalogPath);
|
|
75
|
+
assert.equal(statsBefore.eventsCount, 1);
|
|
76
|
+
|
|
77
|
+
const pruned = pruneExpiredEntries({ catalogPath, ttlDays: 7 });
|
|
78
|
+
assert.equal(pruned.prunedCount, 1);
|
|
79
|
+
|
|
80
|
+
const statsAfter = getDynamicCatalogStats(catalogPath);
|
|
81
|
+
assert.equal(statsAfter.eventsCount, 0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await t.test('clearDynamicCatalog empties all collections', () => {
|
|
85
|
+
clearDynamicCatalog(catalogPath);
|
|
86
|
+
const stats = getDynamicCatalogStats(catalogPath);
|
|
87
|
+
assert.equal(stats.totalEntries, 0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('Ingestion Worker: trust tiers, deduplication, and extraction', async (t) => {
|
|
94
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'kiku-ingest-test-'));
|
|
95
|
+
const catalogPath = path.join(tmpDir, 'dynamic-catalog.json');
|
|
96
|
+
|
|
97
|
+
await t.test('isTrustedSource classifies official platforms and press', () => {
|
|
98
|
+
const douyin = isTrustedSource('douyin');
|
|
99
|
+
assert.equal(douyin.trusted, true);
|
|
100
|
+
assert.equal(douyin.tier, 1);
|
|
101
|
+
|
|
102
|
+
const weiboUrl = isTrustedSource('https://weibo.com/u/123456');
|
|
103
|
+
assert.equal(weiboUrl.trusted, true);
|
|
104
|
+
assert.equal(weiboUrl.tier, 1);
|
|
105
|
+
|
|
106
|
+
const press = isTrustedSource('https://www.sohu.com/a/123456');
|
|
107
|
+
assert.equal(press.trusted, true);
|
|
108
|
+
assert.equal(press.tier, 2);
|
|
109
|
+
|
|
110
|
+
const community = isTrustedSource('https://randomblog.com/post/123');
|
|
111
|
+
assert.equal(community.trusted, false);
|
|
112
|
+
assert.equal(community.tier, 3);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await t.test('isDuplicateEvent detects identical and near-duplicate entries via SimHash', () => {
|
|
116
|
+
const existing = [
|
|
117
|
+
{
|
|
118
|
+
id: 'item-1',
|
|
119
|
+
brand: 'Lafang',
|
|
120
|
+
year: 2026,
|
|
121
|
+
highlight: 'จวีจิ้งอี ไลฟ์สดแบรนด์ Lafang บน Douyin'
|
|
122
|
+
}
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
const exactMatch = { brand: 'Lafang', year: 2026 };
|
|
126
|
+
assert.equal(isDuplicateEvent(exactMatch, existing), true);
|
|
127
|
+
|
|
128
|
+
const simHashMatch = {
|
|
129
|
+
brand: 'Other',
|
|
130
|
+
year: 2026,
|
|
131
|
+
highlight: 'จวีจิ้งอี ไลฟ์สดแบรนด์ Lafang บน Douyin ในลุคนางฟ้า'
|
|
132
|
+
};
|
|
133
|
+
assert.equal(isDuplicateEvent(simHashMatch, existing), true);
|
|
134
|
+
|
|
135
|
+
const different = {
|
|
136
|
+
brand: 'FRED Paris',
|
|
137
|
+
year: 2024,
|
|
138
|
+
highlight: 'เครื่องประดับชั้นสูงจากฝรั่งเศส'
|
|
139
|
+
};
|
|
140
|
+
assert.equal(isDuplicateEvent(different, existing), false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await t.test('extractStructuredEvent correctly parses Thai date and brand entity', async () => {
|
|
144
|
+
const sample = 'คืนวันที่ 12 กันยายน จวีจิ้งอี ไลฟ์สดงานแบรนด์ Lafang (拉芳) บนแพลตฟอร์ม Douyin ด้วยลุคนางฟ้าดอกไม้';
|
|
145
|
+
const res = await extractStructuredEvent(sample);
|
|
146
|
+
|
|
147
|
+
assert.equal(res.category, 'brands');
|
|
148
|
+
assert.equal(res.year, 2026);
|
|
149
|
+
assert.equal(res.date, '2026-09-12');
|
|
150
|
+
assert.equal(res.brand, 'Lafang');
|
|
151
|
+
assert.equal(res.brandNameCn, '拉芳');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await t.test('ingestRawEvent rejects untrusted source and ingests trusted source', async () => {
|
|
155
|
+
const text = 'จวีจิ้งอี ร่วมงานอีเวนต์พิเศษ';
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
const rejected = await ingestRawEvent(text, {
|
|
159
|
+
source: 'https://unverified-blog.org',
|
|
160
|
+
catalogPath
|
|
161
|
+
});
|
|
162
|
+
assert.equal(rejected.status, 'rejected');
|
|
163
|
+
|
|
164
|
+
const success = await ingestRawEvent(text, {
|
|
165
|
+
source: 'douyin',
|
|
166
|
+
catalogPath
|
|
167
|
+
});
|
|
168
|
+
assert.equal(success.status, 'success');
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
const dup = await ingestRawEvent(text, {
|
|
172
|
+
source: 'douyin',
|
|
173
|
+
catalogPath
|
|
174
|
+
});
|
|
175
|
+
assert.equal(dup.status, 'duplicate');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await t.test('runIngestionScan scans and ingests verified candidates', async () => {
|
|
179
|
+
const mockFetcher = async () => [
|
|
180
|
+
{
|
|
181
|
+
title: 'Ju Jingyi attends Luxury Gala',
|
|
182
|
+
snippet: 'Official appearance at annual fashion gala',
|
|
183
|
+
url: 'https://weibo.com/detail/999'
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
title: 'Ju Jingyi fan forum post',
|
|
187
|
+
snippet: 'Unverified rumor from forum',
|
|
188
|
+
url: 'https://randomforum.xyz/topic/1'
|
|
189
|
+
}
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const result = await runIngestionScan({
|
|
193
|
+
fetcher: mockFetcher,
|
|
194
|
+
catalogPath,
|
|
195
|
+
minTrustTier: 2
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
assert.equal(result.totalScanned, 2);
|
|
199
|
+
assert.equal(result.ingestedCount, 1);
|
|
200
|
+
assert.equal(result.skippedUntrusted, 1);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('Core Tools: Dynamic Catalog Overlay integration and record_dynamic_event tool', async () => {
|
|
207
|
+
|
|
208
|
+
clearDynamicCatalog();
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
const recordRes = await executeTool('record_dynamic_event', {
|
|
212
|
+
category: 'brands',
|
|
213
|
+
entry: {
|
|
214
|
+
brand: 'TestDynamicBrand',
|
|
215
|
+
brandNameEn: 'TestDynamicBrand',
|
|
216
|
+
brandNameCn: '测试品牌',
|
|
217
|
+
year: 2026,
|
|
218
|
+
category: 'beauty',
|
|
219
|
+
highlight: 'Special Dynamic Brand Ambassador'
|
|
220
|
+
},
|
|
221
|
+
source: 'weibo',
|
|
222
|
+
trustTier: 1
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
assert.equal(recordRes.status, 'success');
|
|
226
|
+
assert.equal(recordRes.entry.brand, 'TestDynamicBrand');
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
const brandsRes = await executeTool('get_brand_endorsements', { query: 'TestDynamicBrand' });
|
|
230
|
+
assert.equal(brandsRes.status, 'success');
|
|
231
|
+
assert.ok(brandsRes.brands.some(b => b.brand.includes('TestDynamicBrand')));
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
await executeTool('record_dynamic_event', {
|
|
235
|
+
category: 'timeline',
|
|
236
|
+
entry: {
|
|
237
|
+
title: 'Dynamic Milestone 2026',
|
|
238
|
+
titleCn: '动态里程碑',
|
|
239
|
+
year: 2026,
|
|
240
|
+
summary: 'Special dynamic timeline entry test'
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const timelineRes = await executeTool('get_career_timeline', { year: 2026 });
|
|
245
|
+
assert.equal(timelineRes.status, 'success');
|
|
246
|
+
assert.ok(timelineRes.milestones.some(m => m.title.includes('Dynamic Milestone 2026')));
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
await executeTool('record_dynamic_event', {
|
|
250
|
+
category: 'gallery',
|
|
251
|
+
entry: {
|
|
252
|
+
titleTh: 'ภาพพิเศษ 2026',
|
|
253
|
+
titleCn: '动态写真',
|
|
254
|
+
category: 'fashion',
|
|
255
|
+
year: 2026,
|
|
256
|
+
image: 'https://example.com/dynamic-photo.jpg'
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const galleryRes = await executeTool('get_gallery_photos', { year: 2026 });
|
|
261
|
+
assert.equal(galleryRes.status, 'success');
|
|
262
|
+
assert.ok(galleryRes.photos.some(p => p.title.includes('ภาพพิเศษ 2026')));
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
clearDynamicCatalog();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('CLI: /ingest routeCommand handles status, add, and clear', async () => {
|
|
269
|
+
clearDynamicCatalog();
|
|
270
|
+
|
|
271
|
+
const statusRes = await routeCommand('/ingest status', {});
|
|
272
|
+
assert.equal(statusRes, 'HANDLED');
|
|
273
|
+
|
|
274
|
+
const addRes = await routeCommand('/ingest add จวีจิ้งอี ไลฟ์สดงานแบรนด์ Lafang บน Douyin', {});
|
|
275
|
+
assert.equal(addRes, 'HANDLED');
|
|
276
|
+
|
|
277
|
+
const stats = getDynamicCatalogStats();
|
|
278
|
+
assert.ok(stats.totalEntries >= 1);
|
|
279
|
+
|
|
280
|
+
const clearRes = await routeCommand('/ingest clear', {});
|
|
281
|
+
assert.equal(clearRes, 'HANDLED');
|
|
282
|
+
|
|
283
|
+
const statsCleared = getDynamicCatalogStats();
|
|
284
|
+
assert.equal(statsCleared.totalEntries, 0);
|
|
285
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { resolveQueryEntities, getContextualizedSearchQuery, detectPlatforms, detectPromotionIntent } from '../catalogEntityResolver.js';
|
|
4
|
+
import { DynamicToolRouter } from '../toolRouter.js';
|
|
5
|
+
import { createCatalogMatcher, executeTool } from '../tools.js';
|
|
6
|
+
|
|
7
|
+
test('catalogEntityResolver resolves entities across languages without rigid keywords', () => {
|
|
8
|
+
const prsrQuery = 'ค้นหาการโปรโมท PRSR บน bilibili';
|
|
9
|
+
const prsrRes = resolveQueryEntities(prsrQuery);
|
|
10
|
+
|
|
11
|
+
assert.equal(prsrRes.hasBrand, true);
|
|
12
|
+
assert.ok(prsrRes.primaryBrand);
|
|
13
|
+
assert.equal(prsrRes.primaryBrand.id, 'prsr');
|
|
14
|
+
assert.ok(prsrRes.platforms.includes('bilibili'));
|
|
15
|
+
assert.equal(prsrRes.isPromotion, true);
|
|
16
|
+
assert.ok(prsrRes.contextualChineseKeywords.includes('帕莎眼镜'));
|
|
17
|
+
|
|
18
|
+
const pashaQuery = 'ดูคลิปโฆษณา pasha ใน b站';
|
|
19
|
+
const pashaRes = resolveQueryEntities(pashaQuery);
|
|
20
|
+
assert.equal(pashaRes.hasBrand, true);
|
|
21
|
+
assert.equal(pashaRes.primaryBrand.id, 'prsr');
|
|
22
|
+
assert.ok(pashaRes.platforms.includes('bilibili'));
|
|
23
|
+
|
|
24
|
+
const qixingcaiQuery = 'ผลงานชีซิงไฉ่';
|
|
25
|
+
const qixingcaiRes = resolveQueryEntities(qixingcaiQuery);
|
|
26
|
+
assert.equal(qixingcaiRes.hasWork, true);
|
|
27
|
+
assert.equal(qixingcaiRes.primaryWork.nameCn, '七星彩');
|
|
28
|
+
|
|
29
|
+
const inBlossomQuery = 'เพลงประกอบฮวาเจียนลิ่ง';
|
|
30
|
+
const inBlossomRes = resolveQueryEntities(inBlossomQuery);
|
|
31
|
+
assert.equal(inBlossomRes.hasWork, true);
|
|
32
|
+
assert.equal(inBlossomRes.primaryWork.nameCn, '花间令');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('getContextualizedSearchQuery enriches Chinese queries with artist and brand aliases', () => {
|
|
36
|
+
const biliPrsr = getContextualizedSearchQuery('PRSR', 'bilibili');
|
|
37
|
+
assert.ok(biliPrsr.includes('鞠婧祎'), 'Must include artist name in Chinese');
|
|
38
|
+
assert.ok(biliPrsr.includes('帕莎'), 'Must include Chinese brand name');
|
|
39
|
+
|
|
40
|
+
const biliPrompt = getContextualizedSearchQuery('ค้นหาการโปรโมท PRSR บน bilibili', 'bilibili');
|
|
41
|
+
assert.ok(biliPrompt.includes('鞠婧祎'));
|
|
42
|
+
assert.ok(biliPrompt.includes('帕莎'));
|
|
43
|
+
assert.ok(biliPrompt.includes('PRSR'));
|
|
44
|
+
|
|
45
|
+
const dramaQuery = getContextualizedSearchQuery('In Blossom', 'bilibili');
|
|
46
|
+
assert.ok(dramaQuery.includes('鞠婧祎'));
|
|
47
|
+
assert.ok(dramaQuery.includes('花间令'));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('DynamicToolRouter routes brand and media tools simultaneously on cross-cutting queries', () => {
|
|
51
|
+
const router = new DynamicToolRouter();
|
|
52
|
+
|
|
53
|
+
const routed = router.routeToolsForQuery('ค้นหาการโปรโมท PRSR บน bilibili');
|
|
54
|
+
const toolNames = new Set(routed.map(t => t.function.name));
|
|
55
|
+
|
|
56
|
+
assert.ok(toolNames.has('get_brand_endorsements'), 'Must include get_brand_endorsements');
|
|
57
|
+
assert.ok(toolNames.has('search_bilibili'), 'Must include search_bilibili');
|
|
58
|
+
assert.ok(toolNames.has('get_media_links'), 'Must include get_media_links');
|
|
59
|
+
assert.ok(routed.length <= 15);
|
|
60
|
+
|
|
61
|
+
const fallback = router.routeToolsForQuery('สวัสดีครับ');
|
|
62
|
+
assert.ok(fallback.length <= 10, 'Fallback tools must not exceed 10');
|
|
63
|
+
assert.ok(fallback.some(t => t.function.name === 'global_search'));
|
|
64
|
+
assert.ok(fallback.some(t => t.function.name === 'get_brand_endorsements'));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('createCatalogMatcher handles promotional query framing without token ratio penalty', () => {
|
|
68
|
+
const matcher = createCatalogMatcher('ค้นหาการโปรโมท PRSR บน bilibili');
|
|
69
|
+
assert.equal(matcher('PRSR / Pasha', '帕莎眼镜', 'Global Brand Ambassador'), true);
|
|
70
|
+
|
|
71
|
+
const yunxiMatcher = createCatalogMatcher('เพลง Ost Ju Jingyi 2024 Yunxi');
|
|
72
|
+
assert.equal(yunxiMatcher('ตำนานหยุนซี Legend of Yunxi', 'Yunxi'), true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('get_brand_endorsements tool returns bilibiliSearchUrl and handles query filter', async () => {
|
|
76
|
+
const res = await executeTool('get_brand_endorsements', { query: 'PRSR' });
|
|
77
|
+
assert.equal(res.status, 'success');
|
|
78
|
+
assert.ok(res.count >= 1);
|
|
79
|
+
const prsr = res.brands[0];
|
|
80
|
+
assert.ok(prsr.brand.includes('PRSR') || prsr.brand.includes('Pasha'));
|
|
81
|
+
assert.ok(prsr.bilibiliSearchUrl, 'Must include bilibiliSearchUrl');
|
|
82
|
+
assert.ok(prsr.bilibiliSearchUrl.includes('search.bilibili.com'));
|
|
83
|
+
assert.ok(prsr.bilibiliSearchUrl.includes(encodeURIComponent('鞠婧祎')));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('get_media_links tool searches brand commercial links alongside works and music', async () => {
|
|
87
|
+
const res = await executeTool('get_media_links', { query: 'PRSR' });
|
|
88
|
+
assert.equal(res.status, 'success');
|
|
89
|
+
assert.ok(res.results.brands.length >= 1, 'Should find PRSR in brand media results');
|
|
90
|
+
assert.ok(res.results.brands[0].bilibiliSearchUrl);
|
|
91
|
+
assert.ok(res.results.brands[0].youtubeCommercialSearch);
|
|
92
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { sanitizeQuery } from '../sanitizer.js';
|
|
4
|
+
import { DynamicToolRouter } from '../toolRouter.js';
|
|
5
|
+
import { executeTool } from '../tools.js';
|
|
6
|
+
import { ContextCompactor } from '../contextCompactor.js';
|
|
7
|
+
import { KikuAgent } from '../agentCore.js';
|
|
8
|
+
import { handleVerifyRoute } from '../../web-chat/routes/verifyRoute.js';
|
|
9
|
+
import { applyFutureEventGuard } from '../../web-chat/core/futureEventGuard.js';
|
|
10
|
+
|
|
11
|
+
test('Edge Cases Suite: Sanitizer handles empty, whitespace, and extreme length strings gracefully', () => {
|
|
12
|
+
const emptyRes = sanitizeQuery('');
|
|
13
|
+
assert.equal(emptyRes.sanitized, '');
|
|
14
|
+
|
|
15
|
+
const spaceRes = sanitizeQuery(' \t\n ');
|
|
16
|
+
assert.equal(spaceRes.sanitized, '');
|
|
17
|
+
|
|
18
|
+
const longText = 'A'.repeat(40000);
|
|
19
|
+
const longRes = sanitizeQuery(longText);
|
|
20
|
+
assert.ok(longRes.sanitized.length <= 32768);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('Edge Cases Suite: DynamicToolRouter returns safe fallback when given empty or strange inputs', () => {
|
|
24
|
+
const router = new DynamicToolRouter();
|
|
25
|
+
const nullRouted = router.routeToolsForQuery(null);
|
|
26
|
+
assert.ok(nullRouted.length > 0);
|
|
27
|
+
|
|
28
|
+
const emptyRouted = router.routeToolsForQuery('');
|
|
29
|
+
assert.ok(emptyRouted.length > 0);
|
|
30
|
+
|
|
31
|
+
const weirdRouted = router.routeToolsForQuery('!@#$%^&*()_+~`|}{[]:;?><,./');
|
|
32
|
+
assert.ok(weirdRouted.length > 0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('Edge Cases Suite: Catalog tools return not_found status instead of throwing on unknown records', async () => {
|
|
36
|
+
const notFoundWork = await executeTool('search_works', { query: 'non_existent_drama_xyz_99999' });
|
|
37
|
+
assert.equal(notFoundWork.status, 'not_found');
|
|
38
|
+
assert.equal(notFoundWork.count, 0);
|
|
39
|
+
|
|
40
|
+
const notFoundSong = await executeTool('search_discography', { query: 'non_existent_song_xyz_99999' });
|
|
41
|
+
assert.equal(notFoundSong.status, 'not_found');
|
|
42
|
+
assert.equal(notFoundSong.count, 0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('Edge Cases Suite: ContextCompactor handles empty, malformed, or oversized history arrays', () => {
|
|
46
|
+
const compactor = new ContextCompactor({ maxTokens: 100, keepRecentMessages: 2 });
|
|
47
|
+
assert.deepEqual(compactor.compact([]), []);
|
|
48
|
+
assert.deepEqual(compactor.compact(null), null);
|
|
49
|
+
|
|
50
|
+
const singleMsg = [{ role: 'user', content: 'Hello' }];
|
|
51
|
+
assert.deepEqual(compactor.compact(singleMsg), singleMsg);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('Edge Cases Suite: KikuAgent immunizes vision limitation statements from refusal interceptor', async () => {
|
|
55
|
+
let callCount = 0;
|
|
56
|
+
const fakeClient = {
|
|
57
|
+
provider: 'lmstudio',
|
|
58
|
+
model: 'google/gemma-4-e4b',
|
|
59
|
+
chat: async () => {
|
|
60
|
+
callCount++;
|
|
61
|
+
return { role: 'assistant', content: 'ฉันเป็นระบบข้อความ ไม่สามารถดูรูปภาพได้โดยตรง จึงไม่มีข้อมูลของภาพนี้' };
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const agent = new KikuAgent({ client: fakeClient, maxIterations: 2 });
|
|
65
|
+
const result = await agent.run('ช่วยดูรูปนี้ให้หน่อย จวีจิ้งอี\n\n[USER ATTACHMENT: ผู้ใช้ได้แนบรูปภาพจำนวน 1 รูป: "my_photo.jpg" (image/jpeg)]');
|
|
66
|
+
assert.equal(callCount, 1);
|
|
67
|
+
assert.ok(result.includes('ไม่สามารถดูรูปภาพ'));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('Edge Cases Suite: KikuAgent provides graceful fallback for image attachments instead of raw failure', async () => {
|
|
71
|
+
const fakeClient = {
|
|
72
|
+
provider: 'lmstudio',
|
|
73
|
+
model: 'google/gemma-4-e4b',
|
|
74
|
+
chat: async () => ({ role: 'assistant', content: '' })
|
|
75
|
+
};
|
|
76
|
+
const agent = new KikuAgent({ client: fakeClient, maxIterations: 1 });
|
|
77
|
+
const result = await agent.run('ดูภาพนี้\n\n[USER ATTACHMENT: ผู้ใช้ได้แนบรูปภาพจำนวน 1 รูป: "unknown.png" (image/png)]');
|
|
78
|
+
assert.ok(!result.includes('ขออภัยค่ะ ไม่สามารถประมวลผลคำตอบได้'));
|
|
79
|
+
assert.ok(result.includes('ได้รับรูปภาพเรียบร้อยแล้วค่ะ'));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('Edge Cases Suite: KikuAgent does not intercept substantive negative answers as refusals', async () => {
|
|
83
|
+
let callCount = 0;
|
|
84
|
+
const fakeClient = {
|
|
85
|
+
provider: 'lmstudio',
|
|
86
|
+
model: 'google/gemma-4-e4b',
|
|
87
|
+
chat: async () => {
|
|
88
|
+
callCount++;
|
|
89
|
+
return {
|
|
90
|
+
role: 'assistant',
|
|
91
|
+
content: 'จากการตรวจสอบข้อมูลและประวัติการทำงานของจวีจิ้งอี ไม่พบว่ามีประวัติการกระทำความผิดกฎหมายหรือเรื่องอื้อฉาวใดๆ ในวงการบันเทิง เธอเป็นศิลปินและนักแสดงที่มีผลงานซีรีส์อย่าง In Blossom และ The Legend of White Snake อย่างต่อเนื่อง'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const agent = new KikuAgent({ client: fakeClient, maxIterations: 2 });
|
|
96
|
+
const result = await agent.run('จวีจิ้งอีเคยมีประวัติด้านลบหรือทำผิดกฎหมายอะไรไหม');
|
|
97
|
+
assert.equal(callCount, 1);
|
|
98
|
+
assert.ok(result.includes('ไม่พบว่ามีประวัติการกระทำความผิด'));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('Edge Cases Suite: handleVerifyRoute verifies natural language claim using token overlap', async () => {
|
|
102
|
+
let jsonResponse = null;
|
|
103
|
+
let status = null;
|
|
104
|
+
const payload = JSON.stringify({ claim: 'Ju Jingyi starred as Yang Caiwei in the 2024 historical drama In Blossom' });
|
|
105
|
+
const req = {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
url: '/api/verify',
|
|
108
|
+
headers: { 'content-type': 'application/json' },
|
|
109
|
+
on: (event, cb) => {
|
|
110
|
+
if (event === 'data') cb(Buffer.from(payload));
|
|
111
|
+
if (event === 'end') cb();
|
|
112
|
+
return req;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const res = {
|
|
116
|
+
writeHead: (s) => { status = s; },
|
|
117
|
+
end: (data) => { jsonResponse = JSON.parse(data); }
|
|
118
|
+
};
|
|
119
|
+
await handleVerifyRoute(req, res);
|
|
120
|
+
assert.equal(status, 200);
|
|
121
|
+
assert.equal(jsonResponse.verdict, 'PARTIALLY_VERIFIED');
|
|
122
|
+
assert.ok(jsonResponse.evidence.length > 0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('Edge Cases Suite: applyFutureEventGuard preserves confirmed 2026 dates when catalog tools are executed', () => {
|
|
126
|
+
const answer = 'ซีรีส์เรื่อง 《千香》 (The Eternal Fragrance) มีกำหนดออกอากาศในปี 2026';
|
|
127
|
+
const guarded = applyFutureEventGuard(answer, { tools: [{ name: 'search_works' }], now: new Date('2024-01-01') });
|
|
128
|
+
assert.ok(guarded.includes('2026'));
|
|
129
|
+
assert.ok(!guarded.includes('วันที่ในอนาคตที่ยังไม่ยืนยัน'));
|
|
130
|
+
});
|
|
131
|
+
|