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.
Files changed (232) hide show
  1. package/README.md +69 -12
  2. package/package.json +5 -12
  3. package/server/agent/agentCore.js +319 -46
  4. package/server/agent/appPaths.js +31 -0
  5. package/server/agent/audioPlayer.js +339 -12
  6. package/server/agent/autoContext.js +193 -0
  7. package/server/agent/catalogEntityResolver.js +170 -28
  8. package/server/agent/clarifyingQuestions.js +137 -35
  9. package/server/agent/cli.js +177 -48
  10. package/server/agent/cloudAdapters.js +116 -0
  11. package/server/agent/commandRegistry.js +409 -80
  12. package/server/agent/commands.js +1056 -125
  13. package/server/agent/config.js +1088 -690
  14. package/server/agent/contextCompactor.js +213 -21
  15. package/server/agent/cpuMetrics.js +199 -0
  16. package/server/agent/engineService.js +591 -0
  17. package/server/agent/eval/AGENTS.md +228 -0
  18. package/server/agent/eval/analyze.py +684 -0
  19. package/server/agent/eval/gates.json +15 -0
  20. package/server/agent/eval/modules/analysis/compare.js +198 -0
  21. package/server/agent/eval/modules/analysis/gates.js +72 -0
  22. package/server/agent/eval/modules/analysis/judge.js +70 -0
  23. package/server/agent/eval/modules/core/cli.js +453 -0
  24. package/server/agent/eval/modules/core/config.js +60 -0
  25. package/server/agent/eval/modules/core/store.js +236 -0
  26. package/server/agent/eval/modules/evaluation/cases.js +689 -0
  27. package/server/agent/eval/modules/evaluation/runner.js +288 -0
  28. package/server/agent/eval/modules/evaluation/scoring.js +363 -0
  29. package/server/agent/eval/modules/evaluation/worker.js +122 -0
  30. package/server/agent/eval/modules/reporting/benchmark.js +265 -0
  31. package/server/agent/eval/modules/reporting/benchmarkPng.js +487 -0
  32. package/server/agent/eval/modules/reporting/dashboard.js +1406 -0
  33. package/server/agent/eval/modules/server/evalApi.js +1099 -0
  34. package/server/agent/eval/modules/ui/evalUi.js +4799 -0
  35. package/server/agent/eval/modules/utils/smokes.js +119 -0
  36. package/server/agent/eventBus.js +1 -0
  37. package/server/agent/ink/main.jsx +196 -191
  38. package/server/agent/jobManager.js +11 -1
  39. package/server/agent/lanDiscovery.js +721 -23
  40. package/server/agent/llmClient.js +317 -71
  41. package/server/agent/lmStudioWakeup.js +517 -65
  42. package/server/agent/locationService.js +797 -0
  43. package/server/agent/mcpQuizData.js +509 -0
  44. package/server/agent/memory/contextAllocator.js +19 -7
  45. package/server/agent/memory.js +3 -1
  46. package/server/agent/modelCompatibility.js +352 -0
  47. package/server/agent/modelDefaults.js +1 -0
  48. package/server/agent/modelManager.js +46 -2
  49. package/server/agent/newsWatch.js +9 -3
  50. package/server/agent/processManager.js +229 -0
  51. package/server/agent/prompts.js +174 -8
  52. package/server/agent/routingInterceptor.js +9 -1
  53. package/server/agent/sanitizer.js +41 -6
  54. package/server/agent/searchQueryPlanner.js +202 -0
  55. package/server/agent/session.js +59 -4
  56. package/server/agent/tests/agentBubbleSystem.test.js +65 -0
  57. package/server/agent/tests/agentFollowUps.test.js +253 -0
  58. package/server/agent/tests/agentMusicControl.test.js +245 -0
  59. package/server/agent/tests/apiRouter.test.js +108 -0
  60. package/server/agent/tests/architectureFixes.test.js +81 -0
  61. package/server/agent/tests/architectureRefactoring.test.js +194 -0
  62. package/server/agent/tests/autoContext.test.js +176 -0
  63. package/server/agent/tests/autoResearch.test.js +64 -0
  64. package/server/agent/tests/bilibiliEmbed.test.js +296 -0
  65. package/server/agent/tests/catalogSync.test.js +130 -0
  66. package/server/agent/tests/chatHardening.test.js +121 -0
  67. package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -0
  68. package/server/agent/tests/citations.test.js +180 -0
  69. package/server/agent/tests/clarificationAnalytics.test.js +219 -0
  70. package/server/agent/tests/clarifyingQuestions.test.js +627 -0
  71. package/server/agent/tests/cliAutomation.test.js +158 -0
  72. package/server/agent/tests/cloudProviders.test.js +322 -0
  73. package/server/agent/tests/commandInput.test.js +24 -0
  74. package/server/agent/tests/commands.test.js +506 -0
  75. package/server/agent/tests/contextIntelligence.test.js +209 -0
  76. package/server/agent/tests/conversationSafety.test.js +53 -0
  77. package/server/agent/tests/cpuMetrics.test.js +101 -0
  78. package/server/agent/tests/decisionAndSearchEnhancements.test.js +167 -0
  79. package/server/agent/tests/dramaAnswerQuality.test.js +73 -0
  80. package/server/agent/tests/dynamicCatalogIngestion.test.js +285 -0
  81. package/server/agent/tests/dynamicRoutingAndSearch.test.js +92 -0
  82. package/server/agent/tests/edgeCasesScenarios.test.js +131 -0
  83. package/server/agent/tests/engineService.test.js +436 -0
  84. package/server/agent/tests/engineUnload.test.js +94 -0
  85. package/server/agent/tests/entertainmentMetrics.test.js +126 -0
  86. package/server/agent/tests/envBootstrap.test.js +87 -0
  87. package/server/agent/tests/envCommand.test.js +284 -0
  88. package/server/agent/tests/errorHandling.test.js +80 -0
  89. package/server/agent/tests/errors.test.js +13 -0
  90. package/server/agent/tests/evalAnalyze.test.js +54 -0
  91. package/server/agent/tests/evalCommand.test.js +54 -0
  92. package/server/agent/tests/evalCorpus.test.js +126 -0
  93. package/server/agent/tests/evalHarness.test.js +62 -0
  94. package/server/agent/tests/evalJudgeContext.test.js +38 -0
  95. package/server/agent/tests/evalMetrics.test.js +320 -0
  96. package/server/agent/tests/evalStore.test.js +190 -0
  97. package/server/agent/tests/evalStudio.test.js +179 -0
  98. package/server/agent/tests/feedbackLearning.test.js +186 -0
  99. package/server/agent/tests/fetchApi.test.js +99 -0
  100. package/server/agent/tests/firstRun.test.js +44 -0
  101. package/server/agent/tests/gemma4eOptimization.test.js +95 -0
  102. package/server/agent/tests/hardwareTiers.test.js +206 -0
  103. package/server/agent/tests/hybridWebSearch.test.js +194 -0
  104. package/server/agent/tests/judgeService.test.js +251 -0
  105. package/server/agent/tests/lanDiscovery.test.js +324 -0
  106. package/server/agent/tests/lanInterfaces.test.js +510 -0
  107. package/server/agent/tests/lmStudioWakeup.test.js +278 -0
  108. package/server/agent/tests/locationTool.test.js +278 -0
  109. package/server/agent/tests/mcpClient.test.js +201 -0
  110. package/server/agent/tests/memoryRoute.test.js +173 -0
  111. package/server/agent/tests/memoryUiNormalization.test.js +75 -0
  112. package/server/agent/tests/modelCompatibility.test.js +148 -0
  113. package/server/agent/tests/modelManager.test.js +141 -0
  114. package/server/agent/tests/multilingualScenarios.test.js +69 -0
  115. package/server/agent/tests/musicControlUiBridge.test.js +113 -0
  116. package/server/agent/tests/newCommands.test.js +347 -0
  117. package/server/agent/tests/newCommandsTui.test.js +141 -0
  118. package/server/agent/tests/newTools.test.js +113 -0
  119. package/server/agent/tests/newsIntelligence.test.js +178 -0
  120. package/server/agent/tests/nextGenArchitecture.test.js +101 -0
  121. package/server/agent/tests/offlineInput.test.js +217 -0
  122. package/server/agent/tests/openDomainPrompt.test.js +197 -0
  123. package/server/agent/tests/openDomainSearch.test.js +121 -0
  124. package/server/agent/tests/packagingGuard.test.js +57 -0
  125. package/server/agent/tests/presentation.test.js +61 -0
  126. package/server/agent/tests/processManager.test.js +144 -0
  127. package/server/agent/tests/providerPersistence.test.js +44 -0
  128. package/server/agent/tests/queryBenchmarks.test.js +83 -0
  129. package/server/agent/tests/queryDetails.test.js +126 -0
  130. package/server/agent/tests/quickActionsAndModals.test.js +180 -0
  131. package/server/agent/tests/realWorldValidation.test.js +391 -0
  132. package/server/agent/tests/resilienceAndConcurrency.test.js +117 -0
  133. package/server/agent/tests/runtimeWatchdog.test.js +32 -0
  134. package/server/agent/tests/sanitizer.test.js +37 -0
  135. package/server/agent/tests/scenariosData.js +378 -0
  136. package/server/agent/tests/schemaRouting.test.js +54 -0
  137. package/server/agent/tests/searchQueryPlanner.test.js +108 -0
  138. package/server/agent/tests/securityAuditRemediation.test.js +221 -0
  139. package/server/agent/tests/securityAuditRemediations2.test.js +154 -0
  140. package/server/agent/tests/securityHardening.test.js +146 -0
  141. package/server/agent/tests/securitySanitizer.test.js +38 -0
  142. package/server/agent/tests/sessionAndCopy.test.js +267 -0
  143. package/server/agent/tests/sidebarControls.test.js +135 -0
  144. package/server/agent/tests/statefulMemory.test.js +277 -0
  145. package/server/agent/tests/subagent.test.js +216 -0
  146. package/server/agent/tests/subagentProgress.test.js +109 -0
  147. package/server/agent/tests/subagentTextFallback.test.js +115 -0
  148. package/server/agent/tests/suggestions.test.js +199 -0
  149. package/server/agent/tests/suggestionsTui.test.js +251 -0
  150. package/server/agent/tests/telemetryAndProfiles.test.js +179 -0
  151. package/server/agent/tests/terminalImage.test.js +80 -0
  152. package/server/agent/tests/terminalMarkdown.test.js +166 -0
  153. package/server/agent/tests/terminalText.test.js +133 -0
  154. package/server/agent/tests/textProtocolLoop.test.js +197 -0
  155. package/server/agent/tests/textToolParsing.test.js +187 -0
  156. package/server/agent/tests/theme.test.js +26 -0
  157. package/server/agent/tests/toolRegistryParity.test.js +53 -0
  158. package/server/agent/tests/toolRoutingRegressions.test.js +160 -0
  159. package/server/agent/tests/tools.test.js +259 -0
  160. package/server/agent/tests/unifiedEndpoints.test.js +159 -0
  161. package/server/agent/tests/v110Features.test.js +262 -0
  162. package/server/agent/tests/versionBump.test.js +69 -0
  163. package/server/agent/tests/vramEstimator.test.js +595 -0
  164. package/server/agent/tests/webChatDocs.test.js +199 -0
  165. package/server/agent/tests/webChatInferenceControl.test.js +298 -0
  166. package/server/agent/tests/webChatInspection.test.js +551 -0
  167. package/server/agent/tests/webChatMcp.test.js +94 -0
  168. package/server/agent/tests/webChatMemory.test.js +112 -0
  169. package/server/agent/tests/webChatSecurity.test.js +169 -0
  170. package/server/agent/tests/webExtract.test.js +284 -0
  171. package/server/agent/tests/webSources.test.js +281 -0
  172. package/server/agent/textToolProtocol.js +173 -46
  173. package/server/agent/toolRouter.js +318 -44
  174. package/server/agent/tools.js +877 -96
  175. package/server/agent/ui.js +39 -34
  176. package/server/agent/vramService.js +35 -54
  177. package/server/agent/webSearch.js +441 -162
  178. package/server/agent/websearch/orchestrator.js +15 -5
  179. package/server/agent/websearch/resilience/cache.js +9 -2
  180. package/server/agent/websearch/security/ssrf.js +41 -10
  181. package/server/agent/youtubeAuth.js +5 -20
  182. package/server/apiRouter.js +5558 -4422
  183. package/server/eval/judgeRubrics.js +110 -0
  184. package/server/eval/judgeService.js +290 -0
  185. package/server/eval/runBenchmark.js +182 -0
  186. package/server/mcp/data/discography.js +2 -2
  187. package/server/mcp/examples/antigravity.example.json +17 -0
  188. package/server/mcp/examples/librechat.example.yaml +16 -0
  189. package/server/mcp/handler.js +8 -4
  190. package/server/mcp/security.js +220 -21
  191. package/server/mcp/server.js +2 -1
  192. package/server/mcp/tests/connection.test.js +162 -0
  193. package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
  194. package/server/mcp/tools.js +7 -1
  195. package/server/supabase_migration_prefix.sql +102 -0
  196. package/server/supabase_schema.sql +63 -0
  197. package/server/web-chat/core/connection.js +179 -218
  198. package/server/web-chat/core/conversationContext.js +9 -2
  199. package/server/web-chat/core/directCatalogEngine.js +52 -28
  200. package/server/web-chat/core/dramaKnowledge.js +33 -22
  201. package/server/web-chat/core/followUpEngine.js +735 -81
  202. package/server/web-chat/core/futureEventGuard.js +5 -1
  203. package/server/web-chat/core/musicControlAction.js +70 -0
  204. package/server/web-chat/core/security.js +32 -21
  205. package/server/web-chat/core/toolInspector.js +6 -6
  206. package/server/web-chat/core/urlGuard.js +45 -0
  207. package/server/web-chat/router.js +204 -157
  208. package/server/web-chat/routes/chatRoute.js +885 -488
  209. package/server/web-chat/routes/engineRoute.js +270 -0
  210. package/server/web-chat/routes/exportRoute.js +1 -1
  211. package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
  212. package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
  213. package/server/web-chat/routes/mcpRoute.js +386 -0
  214. package/server/web-chat/routes/memoryRoute.js +253 -0
  215. package/server/web-chat/routes/providerRoute.js +49 -16
  216. package/server/web-chat/routes/staticRoute.js +306 -17
  217. package/server/web-chat/routes/statusRoute.js +194 -73
  218. package/server/web-chat/routes/suggestRoute.js +11 -3
  219. package/server/web-chat/routes/verifyRoute.js +26 -4
  220. package/server/web-chat/server.js +7 -6
  221. package/server/web-chat/ui/docsBuilder.js +3558 -0
  222. package/server/web-chat/ui/htmlBuilder.js +12 -0
  223. package/server/web-chat/ui/scripts/client.js +3401 -1302
  224. package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
  225. package/server/web-chat/ui/settingsBuilder.js +7870 -0
  226. package/server/web-chat/ui/styles/lan-card.css +165 -0
  227. package/server/web-chat/ui/styles/main.css +4289 -447
  228. package/server/web-chat/ui/sw.js +64 -0
  229. package/server/web-chat/ui/templates/body.html +407 -281
  230. package/src/data/awardsData.js +146 -146
  231. package/src/data/musicVideoData.js +6 -4
  232. package/src/lib/vram/estimator.js +195 -19
@@ -0,0 +1,378 @@
1
+ export const TEST_SCENARIOS = Object.freeze([
2
+ {
3
+ id: 'drama_2026_veil_of_shadows',
4
+ category: 'works',
5
+ language: 'th',
6
+ query: 'จวีจิ้งอี มีผลงานซีรีส์เรื่องอะไรบ้างในปี 2026',
7
+ expectedTools: ['search_works', 'get_work_detail'],
8
+ expectedKeywords: ['Veil of Shadows', 'The Eternal Fragrance', 'Wanhua Sin', '2026']
9
+ },
10
+ {
11
+ id: 'drama_eternal_fragrance_role',
12
+ category: 'works',
13
+ language: 'th',
14
+ query: 'ซีรีส์ พันกลิ่นรัญจวน (The Eternal Fragrance / 千香) จวีจิ้งอี รับบทเป็นใคร คู่กับใคร',
15
+ expectedTools: ['search_works', 'get_work_detail', 'get_character_roles'],
16
+ expectedKeywords: ['The Eternal Fragrance', '千香', 'Song Song', 'Jiang Li']
17
+ },
18
+ {
19
+ id: 'drama_wanhua_sin',
20
+ category: 'works',
21
+ language: 'zh',
22
+ query: '鞠婧祎新剧《万华世界》(Wanhua Sin) 的题材和角色',
23
+ expectedTools: ['search_works', 'get_work_detail'],
24
+ expectedKeywords: ['Wanhua Sin', '万华世界']
25
+ },
26
+ {
27
+ id: 'drama_in_blossom_detail',
28
+ category: 'works',
29
+ language: 'th',
30
+ query: 'เล่าเรื่องย่อและบทบาทของจวีจิ้งอีในเรื่อง ฮวาเจียนลิ่ง (In Blossom) ให้ฟังหน่อย',
31
+ expectedTools: ['search_works', 'get_work_detail', 'get_character_roles'],
32
+ expectedKeywords: ['In Blossom', 'Yang Caiwei', 'Shangguan Zhi', 'ฮวาเจียนลิ่ง']
33
+ },
34
+ {
35
+ id: 'drama_legend_of_yunxi',
36
+ category: 'works',
37
+ language: 'zh',
38
+ query: '鞠婧祎在《芸汐传》中饰演什么角色?',
39
+ expectedTools: ['search_works', 'get_character_roles'],
40
+ expectedKeywords: ['韩芸汐', '芸汐传', 'Legend of Yun Xi']
41
+ },
42
+ {
43
+ id: 'drama_white_snake_en',
44
+ category: 'works',
45
+ language: 'en',
46
+ query: 'Tell me about Ju Jingyi role in The Legend of White Snake 2019',
47
+ expectedTools: ['search_works', 'get_work_detail'],
48
+ expectedKeywords: ['Bai Suzhen', 'White Snake', '2019']
49
+ },
50
+ {
51
+ id: 'drama_sword_fairy_4',
52
+ category: 'works',
53
+ language: 'th',
54
+ query: 'ซีรีส์ เซียนกระบี่พิชิตมาร 4 (Sword and Fairy 4) จวีจิ้งอี รับบทเป็นใคร',
55
+ expectedTools: ['search_works', 'get_character_roles'],
56
+ expectedKeywords: ['Han Lingsha', 'หานหลิงซา', 'Sword and Fairy 4']
57
+ },
58
+ {
59
+ id: 'drama_ruyi_pavilion',
60
+ category: 'works',
61
+ language: 'th',
62
+ query: 'ซีรีส์ กรุ่นรักกลิ่นบุปผา (The Blooms at Ruyi Pavilion) จวีจิ้งอี แสดงร่วมกับใครและรับบทอะไร',
63
+ expectedTools: ['search_works', 'get_work_detail'],
64
+ expectedKeywords: ['Fu Rong', 'Ruyi Pavilion', '如意芳霏']
65
+ },
66
+ {
67
+ id: 'drama_rebirth_for_you',
68
+ category: 'works',
69
+ language: 'th',
70
+ query: 'เจียหนานจ้วน (Rebirth For You) จวีจิ้งอี รับบทเป็นท่านหญิงชื่ออะไร',
71
+ expectedTools: ['search_works', 'get_character_roles'],
72
+ expectedKeywords: ['Jiang Baoning', 'Rebirth For You', '嘉南传']
73
+ },
74
+ {
75
+ id: 'drama_pair_of_wings',
76
+ category: 'works',
77
+ language: 'zh',
78
+ query: '民国悬疑剧《请赐我一双翅膀》鞠婧祎饰演的角色叫什么',
79
+ expectedTools: ['search_works', 'get_character_roles'],
80
+ expectedKeywords: ['林九歌', 'Lin Jiuge', 'Wings']
81
+ },
82
+ {
83
+ id: 'drama_love_full_moon',
84
+ category: 'works',
85
+ language: 'en',
86
+ query: 'What is the plot of modern fantasy romance drama Love Under the Full Moon starring Ju Jingyi?',
87
+ expectedTools: ['search_works', 'get_work_detail'],
88
+ expectedKeywords: ['Lei Chuxia', 'Full Moon', 'Love Under the Full Moon']
89
+ },
90
+ {
91
+ id: 'drama_novoland_castle',
92
+ category: 'works',
93
+ language: 'th',
94
+ query: 'จวีจิ้งอี เคยเล่นเรื่อง ตำนานอาณาจักรเหนือเวหา (Novoland: The Castle in the Sky) รับบทเป็นใคร',
95
+ expectedTools: ['search_works', 'get_character_roles'],
96
+ expectedKeywords: ['Xue Feishuang', 'Novoland', 'องค์หญิงหิมะ']
97
+ },
98
+ {
99
+ id: 'music_ruyi_ost',
100
+ category: 'music',
101
+ language: 'th',
102
+ query: 'เพลงประกอบซีรีส์ กรุ่นรักกลิ่นบุปผา (The Blooms at Ruyi Pavilion) มีเพลงอะไรบ้าง',
103
+ expectedTools: ['search_discography', 'get_media_links'],
104
+ expectedKeywords: ['Fu Rong', 'Ruyi Pavilion', '如意芳霏']
105
+ },
106
+ {
107
+ id: 'music_ginkgo_play',
108
+ category: 'music',
109
+ language: 'th',
110
+ query: 'เปิดเพลง Rain of Ginkgo (银杏树下的雨) ให้หน่อย',
111
+ expectedTools: ['play_music', 'search_discography'],
112
+ expectedKeywords: ['Rain of Ginkgo', '银杏树下的雨']
113
+ },
114
+ {
115
+ id: 'music_solo_singles_en',
116
+ category: 'music',
117
+ language: 'en',
118
+ query: 'What are the top solo singles released by Ju Jingyi?',
119
+ expectedTools: ['search_discography', 'get_music_videos'],
120
+ expectedKeywords: ['Every Day', 'Rain of Ginkgo', 'Don\'t Touch', 'Single']
121
+ },
122
+ {
123
+ id: 'music_luohua_chengni',
124
+ category: 'music',
125
+ language: 'zh',
126
+ query: '《落花成泥》是哪部电视剧的主题曲?',
127
+ expectedTools: ['search_discography', 'get_work_detail'],
128
+ expectedKeywords: ['芸汐传', 'Legend of Yun Xi', '落花成泥']
129
+ },
130
+ {
131
+ id: 'music_qiannian_deng_yihui',
132
+ category: 'music',
133
+ language: 'th',
134
+ query: 'เพลง พันปีรอคอยหนึ่งหน (千年等一回) จากเรื่อง นางพญางูขาว จวีจิ้งอี ร้องเวอร์ชันไหน',
135
+ expectedTools: ['search_discography', 'get_media_links'],
136
+ expectedKeywords: ['千年等一回', 'White Snake', 'OST']
137
+ },
138
+ {
139
+ id: 'music_dont_touch_mv',
140
+ category: 'music',
141
+ language: 'en',
142
+ query: 'Tell me about Ju Jingyi iconic dance track Don\'t Touch and its music video',
143
+ expectedTools: ['search_discography', 'get_music_videos'],
144
+ expectedKeywords: ['Don\'t Touch', 'Dance', 'MV']
145
+ },
146
+ {
147
+ id: 'music_past_perfection',
148
+ category: 'music',
149
+ language: 'th',
150
+ query: 'เพลง Past Perfection (过去完成时) ของจวีจิ้งอี ปล่อยออกมาปีไหนและมีความหมายอย่างไร',
151
+ expectedTools: ['search_discography', 'get_media_links'],
152
+ expectedKeywords: ['过去完成时', 'Past Perfection']
153
+ },
154
+ {
155
+ id: 'music_stage_performances',
156
+ category: 'music',
157
+ language: 'th',
158
+ query: 'ขอดูคลิปการแสดงและ Music Video สเตจงาน Gala ของจวีจิ้งอี',
159
+ expectedTools: ['get_music_videos', 'get_media_links'],
160
+ expectedKeywords: ['Official MV', 'Stage Performance']
161
+ },
162
+ {
163
+ id: 'brand_global_ambassador_2026',
164
+ category: 'brand',
165
+ language: 'th',
166
+ query: 'จวีจิ้งอี เป็นแบรนด์แอมบาสเดอร์ระดับโลก (Global Ambassador) ของแบรนด์ใดบ้าง',
167
+ expectedTools: ['get_brand_endorsements'],
168
+ expectedKeywords: ['Global', 'Ambassador', 'Brand']
169
+ },
170
+ {
171
+ id: 'brand_versace_fragrance',
172
+ category: 'brand',
173
+ language: 'en',
174
+ query: 'When did Ju Jingyi become the Global Fragrance Ambassador for Versace?',
175
+ expectedTools: ['get_brand_endorsements'],
176
+ expectedKeywords: ['Versace', 'Fragrance', 'Global Ambassador']
177
+ },
178
+ {
179
+ id: 'brand_makeup_forever',
180
+ category: 'brand',
181
+ language: 'zh',
182
+ query: '鞠婧祎代言的法国彩妆品牌 MAKE UP FOR EVER 是什么级别的代言?',
183
+ expectedTools: ['get_brand_endorsements'],
184
+ expectedKeywords: ['MAKE UP FOR EVER', 'Ambassador', 'Brand']
185
+ },
186
+ {
187
+ id: 'brand_beauty_fashion_en',
188
+ category: 'brand',
189
+ language: 'en',
190
+ query: 'Which cosmetics and luxury fashion brands does Ju Jingyi endorse?',
191
+ expectedTools: ['get_brand_endorsements'],
192
+ expectedKeywords: ['Ambassador', 'Brand']
193
+ },
194
+ {
195
+ id: 'brand_ysl_mistine',
196
+ category: 'brand',
197
+ language: 'th',
198
+ query: 'แบรนด์ความงามและเครื่องสำอางที่จวีจิ้งอีร่วมงาน เช่น YSL หรือ Mistine มีอะไรบ้าง',
199
+ expectedTools: ['get_brand_endorsements'],
200
+ expectedKeywords: ['Mistine', 'Beauty', 'Brand']
201
+ },
202
+ {
203
+ id: 'brand_fashion_week_magazines',
204
+ category: 'brand',
205
+ language: 'th',
206
+ query: 'นิตยสารแฟชั่นชั้นนำที่จวีจิ้งอีเคยขึ้นปก เช่น Harper\'s Bazaar, Elle, L\'Officiel',
207
+ expectedTools: ['get_gallery_photos', 'get_brand_endorsements'],
208
+ expectedKeywords: ['Fashion', 'Magazine', 'Cover']
209
+ },
210
+ {
211
+ id: 'awards_history_th',
212
+ category: 'awards',
213
+ language: 'th',
214
+ query: 'จวีจิ้งอี เคยได้รับรางวัลเกียรติยศอะไรบ้างตลอดเส้นทางอาชีพ',
215
+ expectedTools: ['get_awards'],
216
+ expectedKeywords: ['Award', 'Honor', 'Weibo', 'SNH48']
217
+ },
218
+ {
219
+ id: 'awards_snh48_hall_of_fame',
220
+ category: 'awards',
221
+ language: 'en',
222
+ query: 'How many times did Ju Jingyi win 1st place in SNH48 General Elections before entering the Hall of Fame?',
223
+ expectedTools: ['get_awards', 'get_career_timeline'],
224
+ expectedKeywords: ['SNH48', 'Hall of Fame', 'Senbatsu', '1st']
225
+ },
226
+ {
227
+ id: 'awards_weibo_night',
228
+ category: 'awards',
229
+ language: 'zh',
230
+ query: '鞠婧祎在微博之夜(Weibo Night)获得过哪些年度荣誉奖项?',
231
+ expectedTools: ['get_awards'],
232
+ expectedKeywords: ['微博之夜', 'Weibo Night', 'Award']
233
+ },
234
+ {
235
+ id: 'awards_music_starlight',
236
+ category: 'awards',
237
+ language: 'th',
238
+ query: 'จวีจิ้งอีเคยได้รับรางวัลด้านดนตรี เช่น รางวัล Tencent Starlight Awards หรือ รางวัลเพลงยอดเยี่ยม ERC Chinese Top Ten อะไรบ้าง',
239
+ expectedTools: ['get_awards'],
240
+ expectedKeywords: ['Starlight', 'ERC', 'OST', 'Singer']
241
+ },
242
+ {
243
+ id: 'profile_bio_fandom_th',
244
+ category: 'profile',
245
+ language: 'th',
246
+ query: 'จวีจิ้งอีเกิดวันที่เท่าไหร่ แฟนคลับชื่ออะไร และสโลแกนคืออะไร',
247
+ expectedTools: ['get_profile_and_fandom'],
248
+ expectedKeywords: ['18 มิถุนายน', 'Miju', '蜜橘', '鞠世无双']
249
+ },
250
+ {
251
+ id: 'profile_social_links_en',
252
+ category: 'profile',
253
+ language: 'en',
254
+ query: 'What are Ju Jingyi official social media accounts on Weibo and Instagram?',
255
+ expectedTools: ['get_profile_and_fandom', 'get_social_feed'],
256
+ expectedKeywords: ['weibo', 'instagram', 'social']
257
+ },
258
+ {
259
+ id: 'profile_hometown_specialties',
260
+ category: 'profile',
261
+ language: 'zh',
262
+ query: '鞠婧祎的出生地、特长和精通乐器有哪些?',
263
+ expectedTools: ['get_profile_and_fandom'],
264
+ expectedKeywords: ['四川遂宁', '小提琴', '古筝', 'Suining']
265
+ },
266
+ {
267
+ id: 'timeline_snh48_to_solo',
268
+ category: 'timeline',
269
+ language: 'th',
270
+ query: 'เส้นทางประวัติการทำงานของจวีจิ้งอี ตั้งแต่ SNH48 จนถึงศิลปินเดี่ยว',
271
+ expectedTools: ['get_career_timeline'],
272
+ expectedKeywords: ['2013', 'SNH48', 'Studio', 'Solo']
273
+ },
274
+ {
275
+ id: 'timeline_studio_independence',
276
+ category: 'timeline',
277
+ language: 'en',
278
+ query: 'When did Ju Jingyi establish her personal independent studio?',
279
+ expectedTools: ['get_career_timeline', 'get_profile_and_fandom'],
280
+ expectedKeywords: ['2017', 'Studio', 'Independence', 'Solo']
281
+ },
282
+ {
283
+ id: 'quotes_famous_sayings',
284
+ category: 'profile',
285
+ language: 'th',
286
+ query: 'ขอคำพูดหรือ Quote ให้กำลังใจเด่นๆ ของจวีจิ้งอี',
287
+ expectedTools: ['get_quotes'],
288
+ expectedKeywords: ['Quote', 'คำพูด']
289
+ },
290
+ {
291
+ id: 'verify_contract_dispute',
292
+ category: 'research',
293
+ language: 'th',
294
+ query: 'ตรวจสอบข้อเท็จจริงเกี่ยวกับข้อพิพาทสัญญาระหว่าง จวีจิ้งอี กับ Star48',
295
+ expectedTools: ['verify_claim', 'web_search', 'run_subagent'],
296
+ expectedKeywords: ['Star48', 'Siba', 'Contract', 'Dispute']
297
+ },
298
+ {
299
+ id: 'verify_4000_year_idol_origin',
300
+ category: 'research',
301
+ language: 'en',
302
+ query: 'Verify the truth behind the 4000-year idol nickname origin in Japanese media',
303
+ expectedTools: ['verify_claim', 'get_profile_and_fandom'],
304
+ expectedKeywords: ['4000-year', 'Japanese', 'Translation', 'Mistranslation']
305
+ },
306
+ {
307
+ id: 'research_legal_portrait_rights',
308
+ category: 'research',
309
+ language: 'zh',
310
+ query: '查证鞠婧祎肖像权维权和胜诉判决的相关公开信息',
311
+ expectedTools: ['verify_claim', 'web_search', 'run_deep_research'],
312
+ expectedKeywords: ['肖像权', '维权', '判决', 'Court']
313
+ },
314
+ {
315
+ id: 'research_deep_investigation_en',
316
+ category: 'research',
317
+ language: 'en',
318
+ query: 'Investigate the latest updates regarding Ju Jingyi upcoming drama premiere licenses in 2026',
319
+ expectedTools: ['run_deep_research', 'web_search', 'web_extract'],
320
+ expectedKeywords: ['License', 'Drama', 'Broadcast']
321
+ },
322
+ {
323
+ id: 'multilingual_mixed_thai_chinese',
324
+ category: 'works',
325
+ language: 'th',
326
+ query: 'ผลงานของ 鞠婧祎 (Ju Jingyi) เรื่อง ฮวาเจียนลิ่ง (花间令) ฉายทางแพลตฟอร์มไหนคะ',
327
+ expectedTools: ['search_works', 'get_work_detail'],
328
+ expectedKeywords: ['Youku', 'In Blossom', '花间令']
329
+ },
330
+ {
331
+ id: 'multilingual_pinyin_search',
332
+ category: 'works',
333
+ language: 'en',
334
+ query: 'Find the drama Hua Jian Ling and Yun Xi Zhuan starring Ju Jingyi',
335
+ expectedTools: ['search_works', 'global_search'],
336
+ expectedKeywords: ['In Blossom', 'Legend of Yun Xi']
337
+ },
338
+ {
339
+ id: 'reasoning_compare_dramas',
340
+ category: 'works',
341
+ language: 'th',
342
+ query: 'เปรียบเทียบบทบาทของจวีจิ้งอี ระหว่างเรื่อง ตำนานยุ่นซี (Legend of Yunxi) กับ ฮวาเจียนลิ่ง (In Blossom)',
343
+ expectedTools: ['search_works', 'get_work_detail', 'get_character_roles'],
344
+ expectedKeywords: ['Han Yunxi', 'Yang Caiwei', 'In Blossom', 'Yunxi']
345
+ },
346
+ {
347
+ id: 'reasoning_all_self_sung_osts',
348
+ category: 'music',
349
+ language: 'th',
350
+ query: 'รวมเพลง OST ทั้งหมดที่จวีจิ้งอีร้องให้กับซีรีส์ที่ตัวเองแสดงนำ',
351
+ expectedTools: ['search_discography', 'search_works'],
352
+ expectedKeywords: ['OST', 'Luohua Chengni', 'Fu Rong', 'Qiannian']
353
+ },
354
+ {
355
+ id: 'system_stats_check',
356
+ category: 'system',
357
+ language: 'th',
358
+ query: 'ดูสถิติระบบและจำนวนผลงานทั้งหมดของ Kiku',
359
+ expectedTools: ['get_system_stats'],
360
+ expectedKeywords: ['totalWorks', 'totalUniqueSongs']
361
+ },
362
+ {
363
+ id: 'system_skill_learn',
364
+ category: 'system',
365
+ language: 'th',
366
+ query: 'สอน Kiku ว่าให้ตอบภาษาไทยอย่างสุภาพและลงท้ายด้วยค่ะเสมอ',
367
+ expectedTools: ['manage_skill'],
368
+ expectedKeywords: ['skill', 'learn']
369
+ },
370
+ {
371
+ id: 'system_doctor_check',
372
+ category: 'system',
373
+ language: 'en',
374
+ query: 'Show system doctor status, LM Studio connectivity, and runtime paths',
375
+ expectedTools: ['get_system_stats'],
376
+ expectedKeywords: ['stats', 'system']
377
+ }
378
+ ]);
@@ -0,0 +1,54 @@
1
+ import { describe, it, beforeEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { RoutingInterceptor } from '../routingInterceptor.js';
4
+ import { compactToolsForPrompt } from '../contextCompactor.js';
5
+
6
+ describe('Sprint 3: Schema Routing & Misrouting Elimination', () => {
7
+ let interceptor;
8
+
9
+ beforeEach(() => {
10
+ interceptor = new RoutingInterceptor();
11
+ });
12
+
13
+ it('w13 regression: should prevent global_search on Han Lingsha costume query', () => {
14
+ const call = {
15
+ name: 'global_search',
16
+ params: { query: '仙剑四 韩菱纱 服装颜色' }
17
+ };
18
+ const result = interceptor.interceptToolCall(call, 0);
19
+
20
+ assert.equal(result.rerouted, true);
21
+ assert.equal(result.replacement.name, 'search_works');
22
+ });
23
+
24
+ it('m02 regression: should prevent global_search for View From The Wall Chinese title', () => {
25
+ const call = {
26
+ name: 'global_search',
27
+ params: { query: 'View From The Wall Chinese title' }
28
+ };
29
+ const result = interceptor.interceptToolCall(call, 0);
30
+
31
+ assert.equal(result.rerouted, true);
32
+ assert.equal(result.replacement.name, 'search_discography');
33
+ });
34
+
35
+ it('r15 regression: should detect and abort duplicate queries in multi-turn loop', () => {
36
+ const call = { name: 'search_works', params: { query: '七星彩' } };
37
+
38
+ const firstAttempt = interceptor.interceptToolCall(call, 0);
39
+ assert.equal(firstAttempt.valid, true);
40
+
41
+
42
+ const secondAttempt = interceptor.interceptToolCall(call, 1);
43
+ assert.equal(secondAttempt.aborted, true);
44
+ });
45
+
46
+ it('Dynamic Scoping: should isolate gallery tools for photoshoot queries', () => {
47
+ const query = 'According to the catalog, which jewelry collection from FRED was featured in PORT Magazine?';
48
+ const scoped = compactToolsForPrompt(query, { schemaStrategy: 'domain_grouped' });
49
+
50
+ const toolNames = scoped.map(t => t.schema?.name || t.function?.name || t.name);
51
+ assert.ok(toolNames.includes('get_gallery_photos'));
52
+ assert.equal(toolNames.includes('get_brand_endorsements'), false);
53
+ });
54
+ });
@@ -0,0 +1,108 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { appendDomainFilters, planSearchRequest, rerankSearchResults } from '../searchQueryPlanner.js';
5
+ import { prepareToolArguments, toolsSchema } from '../tools.js';
6
+
7
+ test('search planner normalizes conversational Thai and romanized Chinese entities', () => {
8
+ const plan = planSearchRequest({
9
+ keywords: 'ช่วยอปเดตขาวกองถาย Wan hua sin jie ปี 2026 หน่อย',
10
+ target_language: 'zh',
11
+ domain_filter: ['https://www.weibo.com/search', 'bilibili.com'],
12
+ }, { kind: 'chinese' });
13
+
14
+ assert.match(plan.keywords, /万花世界/);
15
+ assert.match(plan.keywords, /电视剧/);
16
+ assert.match(plan.keywords, /拍摄进度/);
17
+ assert.match(plan.keywords, /2026/);
18
+ assert.deepEqual(plan.domains, ['weibo.com', 'bilibili.com']);
19
+ });
20
+
21
+ test('search planner resolves a romanized performer name to native script and qualifier', () => {
22
+ const plan = planSearchRequest('latest update for Song Xinran', { kind: 'news' });
23
+ assert.match(plan.keywords, /宋昕冉/);
24
+ assert.match(plan.keywords, /SNH48/);
25
+ });
26
+
27
+ test('post-retrieval reranker promotes intent and domain matches', () => {
28
+ const plan = planSearchRequest({ keywords: 'Song Xinran event', domain_filter: ['weibo.com'] });
29
+ const ranked = rerankSearchResults([
30
+ { title: 'Unrelated sports result', snippet: 'football table', url: 'https://example.com/a' },
31
+ { title: '宋昕冉 SNH48 event update', snippet: 'latest official schedule', url: 'https://weibo.com/a' },
32
+ ], plan);
33
+ assert.match(ranked[0].title, /宋昕冉/);
34
+ assert.ok(ranked.length === 1 || ranked[0].relevanceScore > ranked.at(-1).relevanceScore);
35
+ });
36
+
37
+ test('structured search fields pass through tool argument sanitation while legacy query remains accepted', () => {
38
+ const structured = prepareToolArguments('web_search', {
39
+ keywords: '万花世界 拍摄进度',
40
+ target_language: 'zh',
41
+ domain_filter: ['weibo.com'],
42
+ }, toolsSchema);
43
+ assert.equal(structured.safe, true);
44
+ assert.deepEqual(structured.args.domain_filter, ['weibo.com']);
45
+
46
+ const legacy = prepareToolArguments('web_search', { query: 'Ju Jingyi news' }, toolsSchema);
47
+ assert.equal(legacy.safe, true);
48
+ assert.equal(legacy.args.query, 'Ju Jingyi news');
49
+ assert.match(appendDomainFilters('鞠婧祎', ['weibo.com']), /site:weibo\.com/);
50
+ });
51
+
52
+ test('planner parity: bilibili, youtube, encyclopedia, and music tools accept canonical keywords', () => {
53
+ const bili = prepareToolArguments('search_bilibili', {
54
+ keywords: '宋昕冉 SNH48 舞台',
55
+ target_language: 'zh',
56
+ }, toolsSchema);
57
+ assert.equal(bili.safe, true);
58
+ assert.equal(bili.args.keywords, '宋昕冉 SNH48 舞台');
59
+
60
+ const biliLegacy = prepareToolArguments('search_bilibili', { keyword: '鞠婧祎 舞台' }, toolsSchema);
61
+ assert.equal(biliLegacy.safe, true);
62
+ assert.equal(biliLegacy.args.keyword, '鞠婧祎 舞台');
63
+
64
+ const yt = prepareToolArguments('search_youtube', { keywords: 'Ju Jingyi FIND THE WAY' }, toolsSchema);
65
+ assert.equal(yt.safe, true);
66
+ assert.equal(yt.args.keywords, 'Ju Jingyi FIND THE WAY');
67
+
68
+ const ytLegacy = prepareToolArguments('search_youtube', { query: 'Ju Jingyi live stage' }, toolsSchema);
69
+ assert.equal(ytLegacy.safe, true);
70
+
71
+ const baike = prepareToolArguments('search_encyclopedia', {
72
+ keywords: '宋昕冉 SNH48',
73
+ target_language: 'zh',
74
+ }, toolsSchema);
75
+ assert.equal(baike.safe, true);
76
+
77
+ const baikeLegacy = prepareToolArguments('search_encyclopedia', { term: '鞠婧祎', language: 'zh' }, toolsSchema);
78
+ assert.equal(baikeLegacy.safe, true);
79
+
80
+ const music = prepareToolArguments('search_music_platforms', { keywords: 'Ju Jingyi 叹云兮' }, toolsSchema);
81
+ assert.equal(music.safe, true);
82
+
83
+ const musicLegacy = prepareToolArguments('search_music_platforms', { term: 'Ju Jingyi' }, toolsSchema);
84
+ assert.equal(musicLegacy.safe, true);
85
+ });
86
+
87
+ test('planner parity: handlers reject empty input without touching the network', async () => {
88
+ const { toolHandlers } = await import('../tools.js');
89
+ const bili = await toolHandlers.search_bilibili({});
90
+ assert.equal(bili.status, 'error');
91
+ assert.match(bili.message, /keyword parameter is required/);
92
+
93
+ const enc = await toolHandlers.search_encyclopedia({});
94
+ assert.equal(enc.status, 'error');
95
+
96
+ const music = await toolHandlers.search_music_platforms({});
97
+ assert.equal(music.status, 'error');
98
+
99
+ const yt = await toolHandlers.search_youtube({});
100
+ assert.equal(yt.status, 'error');
101
+ });
102
+
103
+ test('planner resolves performer queries to entity-native terms for encyclopedia-style lookup', () => {
104
+ const plan = planSearchRequest({ keywords: 'ค้นหาข้อมูลของ Song Xinran SNH48 ให้หน่อย' });
105
+ const native = plan.entities[0]?.native;
106
+ assert.ok(native && /宋昕冉|SNH48/.test(`${native} ${plan.keywords}`));
107
+ assert.match(plan.keywords, /宋昕冉/);
108
+ });