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,180 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { extractCitations, formatCitationsBlock, resolveCitationReference } from '../citations.js';
4
+ import { routeCommand } from '../commands.js';
5
+
6
+ test('extractCitations: correctly extracts and deduplicates works from search_works', () => {
7
+ const tools = [
8
+ {
9
+ name: 'search_works',
10
+ result: {
11
+ count: 2,
12
+ works: [
13
+ {
14
+ id: 'legend-of-yunxi',
15
+ titleTh: 'ตำนานหยุนซี มเหสียอดอัจฉริยะแห่งพิษ',
16
+ titleCn: '芸汐传',
17
+ category: 'ซีรีส์โบราณ',
18
+ year: 2018,
19
+ role: 'หานอวิ๋นซี (Han Yunxi)'
20
+ },
21
+ {
22
+ id: 'in-blossom',
23
+ titleTh: 'บุปผารักอลวน',
24
+ titleCn: '花间令',
25
+ category: 'ซีรีส์สืบสวนโบราณ',
26
+ year: 2024,
27
+ role: 'หยางไฉ่เวย / ซ่างกวนจื่อ'
28
+ }
29
+ ]
30
+ }
31
+ }
32
+ ];
33
+
34
+ const answer = 'จวีจิ้งอี มีผลงานซีรีส์ชื่อดังอย่าง ตำนานหยุนซี (芸汐传) รับบทเป็น หานอวิ๋นซี';
35
+ const citations = extractCitations(answer, tools);
36
+
37
+ assert.equal(citations.length, 1);
38
+ assert.equal(citations[0].id, 1);
39
+ assert.equal(citations[0].type, 'work');
40
+ assert.match(citations[0].title, /ตำนานหยุนซี/);
41
+ assert.equal(citations[0].year, 2018);
42
+ assert.equal(citations[0].verified, true);
43
+ });
44
+
45
+ test('extractCitations: extracts discography, timeline, and web search results', () => {
46
+ const tools = [
47
+ {
48
+ name: 'search_discography',
49
+ result: {
50
+ songs: [
51
+ {
52
+ title: 'Everyday',
53
+ titleTh: 'ทุกๆ วัน',
54
+ category: 'Single',
55
+ year: 2016
56
+ }
57
+ ]
58
+ }
59
+ },
60
+ {
61
+ name: 'get_career_timeline',
62
+ result: {
63
+ timeline: [
64
+ {
65
+ year: 2013,
66
+ event: 'เดบิวต์ในฐานะสมาชิกรุ่นที่ 2 ของ SNH48'
67
+ }
68
+ ]
69
+ }
70
+ },
71
+ {
72
+ name: 'web_search',
73
+ result: {
74
+ results: [
75
+ {
76
+ title: 'Ju Jingyi Studio Official Statement',
77
+ url: 'https://weibo.com/jujingyi/status/12345'
78
+ }
79
+ ]
80
+ }
81
+ }
82
+ ];
83
+
84
+ const answer = 'เธอเดบิวต์ในปี 2013 กับ SNH48 และมีเพลง Everyday รวมถึงแถลงการณ์อย่างเป็นทางการ';
85
+ const citations = extractCitations(answer, tools);
86
+
87
+ assert.equal(citations.length, 3);
88
+ assert.equal(citations[0].id, 1);
89
+ assert.equal(citations[0].type, 'song');
90
+ assert.equal(citations[1].id, 2);
91
+ assert.equal(citations[1].type, 'timeline');
92
+ assert.equal(citations[2].id, 3);
93
+ assert.equal(citations[2].type, 'web');
94
+ assert.equal(citations[2].source, 'weibo.com');
95
+ });
96
+
97
+ test('extractCitations: returns empty array for empty inputs or no matches', () => {
98
+ assert.deepEqual(extractCitations('', []), []);
99
+ assert.deepEqual(extractCitations('ข้อความทั่วไป', null), []);
100
+ });
101
+
102
+ test('formatCitationsBlock: formats citations in Thai, English, and Chinese', () => {
103
+ const citations = [
104
+ {
105
+ id: 1,
106
+ title: 'บุปผารักอลวน (花间令)',
107
+ category: 'ซีรีส์',
108
+ year: 2024,
109
+ source: 'catalog:works (search_works)',
110
+ verified: true
111
+ },
112
+ {
113
+ id: 2,
114
+ title: 'Weibo News',
115
+ url: 'https://weibo.com/test',
116
+ category: 'เว็บข่าว',
117
+ verified: true
118
+ }
119
+ ];
120
+
121
+ const thBlock = formatCitationsBlock(citations, { lang: 'th' });
122
+ assert.match(thBlock, /แหล่งข้อมูลและหลักฐานอ้างอิง/);
123
+ assert.match(thBlock, /\[1\] บุปผารักอลวน/);
124
+ assert.match(thBlock, /\[2024\]/);
125
+ assert.match(thBlock, /\[2\] Weibo News/);
126
+ assert.match(thBlock, /https:\/\/weibo.com\/test/);
127
+
128
+ const enBlock = formatCitationsBlock(citations, { lang: 'en' });
129
+ assert.match(enBlock, /Sources & Evidence Citations/);
130
+
131
+ const zhBlock = formatCitationsBlock(citations, { lang: 'zh' });
132
+ assert.match(zhBlock, /参考来源与证据引用/);
133
+
134
+ assert.equal(formatCitationsBlock([]), '');
135
+ });
136
+
137
+ test('resolveCitationReference: resolves bracket notation, hash, number, and entity name', () => {
138
+ const citations = [
139
+ { id: 1, title: 'ตำนานหยุนซี (芸汐传)', year: 2018 },
140
+ { id: 2, title: 'บุปผารักอลวน (In Blossom)', year: 2024 },
141
+ { id: 3, title: 'เพลง Everyday', year: 2016 }
142
+ ];
143
+
144
+ const res1 = resolveCitationReference('เล่าเรื่องย่อของ [1] ให้ฟังหน่อย', citations);
145
+ assert.equal(res1?.id, 1);
146
+ assert.equal(res1?.year, 2018);
147
+
148
+ const res2 = resolveCitationReference('ขอรายละเอียดของ #2 เพิ่มเติม', citations);
149
+ assert.equal(res2?.id, 2);
150
+
151
+ const res3 = resolveCitationReference('3', citations);
152
+ assert.equal(res3?.id, 3);
153
+
154
+ const resTitle = resolveCitationReference('บุปผารักอลวน มีกี่ตอน', citations);
155
+ assert.equal(resTitle?.id, 2);
156
+
157
+ const notFound = resolveCitationReference('ไม่มีในรายการ', citations);
158
+ assert.equal(notFound, null);
159
+ });
160
+
161
+ test('/sources command: displays active grounded citations when available', async () => {
162
+ const mockAgent = {
163
+ lastCitations: [
164
+ {
165
+ id: 1,
166
+ title: 'ตำนานหยุนซี',
167
+ category: 'ซีรีส์',
168
+ year: 2018,
169
+ tool: 'search_works'
170
+ }
171
+ ]
172
+ };
173
+
174
+ const status = await routeCommand('/sources', {
175
+ agent: mockAgent,
176
+ setIsProcessing: () => {}
177
+ });
178
+
179
+ assert.equal(status, 'HANDLED');
180
+ });
@@ -0,0 +1,219 @@
1
+ import { describe, it, beforeEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { ClarificationAnalytics, ClarificationConfig, formatAnalyticsForCLI, formatConfigForCLI } from '../clarificationAnalytics.js';
4
+ import { existsSync, unlinkSync, mkdirSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+
7
+ const TEST_DIR = join(process.cwd(), '.kiku_test_analytics');
8
+ const TEST_ANALYTICS_PATH = join(TEST_DIR, 'test_analytics.json');
9
+ const TEST_CONFIG_PATH = join(TEST_DIR, 'test_config.json');
10
+
11
+ beforeEach(() => {
12
+
13
+ try {
14
+ if (existsSync(TEST_ANALYTICS_PATH)) unlinkSync(TEST_ANALYTICS_PATH);
15
+ if (existsSync(TEST_CONFIG_PATH)) unlinkSync(TEST_CONFIG_PATH);
16
+ } catch {}
17
+ });
18
+
19
+ describe('ClarificationAnalytics', () => {
20
+ it('initializes with default stats', () => {
21
+ const analytics = new ClarificationAnalytics({ storagePath: TEST_ANALYTICS_PATH });
22
+ assert.equal(analytics.stats.totalClarifications, 0);
23
+ assert.equal(analytics.stats.totalSkipped, 0);
24
+ assert.equal(analytics.stats.totalTimedOut, 0);
25
+ });
26
+
27
+ it('records clarification start', () => {
28
+ const analytics = new ClarificationAnalytics({ storagePath: TEST_ANALYTICS_PATH });
29
+ const questions = [
30
+ { category: 'timeframe', question: 'When?', options: ['2026', '2025'] },
31
+ ];
32
+
33
+ const session = analytics.recordClarificationStart('q_test_1', questions, 0.45);
34
+ assert.equal(session.queryId, 'q_test_1');
35
+ assert.equal(session.totalQuestions, 1);
36
+ assert.equal(analytics.stats.totalClarifications, 1);
37
+ });
38
+
39
+ it('records clarification answer', () => {
40
+ const analytics = new ClarificationAnalytics({ storagePath: TEST_ANALYTICS_PATH });
41
+ const questions = [
42
+ { category: 'timeframe', question: 'When?', options: ['2026', '2025'] },
43
+ ];
44
+
45
+ analytics.recordClarificationStart('q_test_1', questions, 0.45);
46
+ analytics.recordClarificationAnswer('q_test_1', { timeframe: '2026' }, 1, 2500);
47
+
48
+ const session = analytics.stats.recentSessions.find(s => s.queryId === 'q_test_1');
49
+ assert.equal(session.answered, true);
50
+ assert.equal(session.answerSummary.timeframe, '2026');
51
+ assert.equal(analytics.stats.categoryStats.timeframe.answered, 1);
52
+ });
53
+
54
+ it('records clarification skip', () => {
55
+ const analytics = new ClarificationAnalytics({ storagePath: TEST_ANALYTICS_PATH });
56
+ const questions = [
57
+ { category: 'timeframe', question: 'When?', options: ['2026', '2025'] },
58
+ ];
59
+
60
+ analytics.recordClarificationStart('q_test_1', questions, 0.45);
61
+ analytics.recordClarificationSkipped('q_test_1');
62
+
63
+ assert.equal(analytics.stats.totalSkipped, 1);
64
+ const session = analytics.stats.recentSessions.find(s => s.queryId === 'q_test_1');
65
+ assert.equal(session.skipped, true);
66
+ });
67
+
68
+ it('records clarification timeout', () => {
69
+ const analytics = new ClarificationAnalytics({ storagePath: TEST_ANALYTICS_PATH });
70
+ const questions = [
71
+ { category: 'timeframe', question: 'When?', options: ['2026', '2025'] },
72
+ ];
73
+
74
+ analytics.recordClarificationStart('q_test_1', questions, 0.45);
75
+ analytics.recordClarificationTimeout('q_test_1');
76
+
77
+ assert.equal(analytics.stats.totalTimedOut, 1);
78
+ const session = analytics.stats.recentSessions.find(s => s.queryId === 'q_test_1');
79
+ assert.equal(session.timedOut, true);
80
+ });
81
+
82
+ it('gets analytics summary', () => {
83
+ const analytics = new ClarificationAnalytics({ storagePath: TEST_ANALYTICS_PATH });
84
+ const questions = [
85
+ { category: 'timeframe', question: 'When?', options: ['2026', '2025'] },
86
+ ];
87
+
88
+ analytics.recordClarificationStart('q_test_1', questions, 0.45);
89
+ analytics.recordClarificationAnswer('q_test_1', { timeframe: '2026' }, 1, 2500);
90
+
91
+ const summary = analytics.getAnalytics({ days: 7 });
92
+ assert.ok(summary.summary);
93
+ assert.equal(summary.summary.totalClarifications, 1);
94
+ assert.ok(summary.period);
95
+ assert.ok(summary.categories);
96
+ });
97
+
98
+ it('gets category effectiveness', () => {
99
+ const analytics = new ClarificationAnalytics({ storagePath: TEST_ANALYTICS_PATH });
100
+ const questions = [
101
+ { category: 'timeframe', question: 'When?', options: ['2026', '2025'] },
102
+ ];
103
+
104
+ analytics.recordClarificationStart('q_test_1', questions, 0.45);
105
+ analytics.recordClarificationAnswer('q_test_1', { timeframe: '2026' }, 1, 2000);
106
+
107
+ const effectiveness = analytics.getCategoryEffectiveness('timeframe');
108
+ assert.ok(effectiveness >= 0 && effectiveness <= 100);
109
+ });
110
+
111
+ it('gets top categories', () => {
112
+ const analytics = new ClarificationAnalytics({ storagePath: TEST_ANALYTICS_PATH });
113
+
114
+ analytics.recordClarificationStart('q_1', [{ category: 'timeframe', question: 'When?', options: [] }], 0.5);
115
+ analytics.recordClarificationStart('q_2', [{ category: 'scope', question: 'How much?', options: [] }], 0.5);
116
+
117
+ const top = analytics.getTopCategories(5);
118
+ assert.ok(Array.isArray(top));
119
+ assert.ok(top.length <= 5);
120
+ });
121
+ });
122
+
123
+ describe('ClarificationConfig', () => {
124
+ it('initializes with default config', () => {
125
+ const config = new ClarificationConfig({ storagePath: TEST_CONFIG_PATH });
126
+ const cfg = config.getConfig();
127
+ assert.equal(cfg.enabled, true);
128
+ assert.equal(cfg.confidenceThreshold, 0.65);
129
+ assert.equal(cfg.maxRounds, 2);
130
+ assert.equal(cfg.timeoutMs, 30000);
131
+ });
132
+
133
+ it('updates config', () => {
134
+ const config = new ClarificationConfig({ storagePath: TEST_CONFIG_PATH });
135
+ const newConfig = config.updateConfig({ confidenceThreshold: 0.8 });
136
+ assert.equal(newConfig.confidenceThreshold, 0.8);
137
+ });
138
+
139
+ it('validates confidence threshold', () => {
140
+ const config = new ClarificationConfig({ storagePath: TEST_CONFIG_PATH });
141
+ assert.throws(() => {
142
+ config.updateConfig({ confidenceThreshold: 1.5 });
143
+ });
144
+ assert.throws(() => {
145
+ config.updateConfig({ confidenceThreshold: -0.1 });
146
+ });
147
+ });
148
+
149
+ it('validates maxRounds', () => {
150
+ const config = new ClarificationConfig({ storagePath: TEST_CONFIG_PATH });
151
+ assert.throws(() => {
152
+ config.updateConfig({ maxRounds: 10 });
153
+ });
154
+ assert.throws(() => {
155
+ config.updateConfig({ maxRounds: 0 });
156
+ });
157
+ });
158
+
159
+ it('resets to defaults', () => {
160
+ const config = new ClarificationConfig({ storagePath: TEST_CONFIG_PATH });
161
+ config.updateConfig({ confidenceThreshold: 0.9 });
162
+ const resetConfig = config.reset();
163
+ assert.equal(resetConfig.confidenceThreshold, 0.65);
164
+ });
165
+
166
+ it('gets and sets individual values', () => {
167
+ const config = new ClarificationConfig({ storagePath: TEST_CONFIG_PATH });
168
+ config.set('enabled', false);
169
+ assert.equal(config.get('enabled'), false);
170
+ });
171
+ });
172
+
173
+ describe('formatAnalyticsForCLI', () => {
174
+ it('formats analytics for display', () => {
175
+ const analytics = {
176
+ summary: {
177
+ totalClarifications: 10,
178
+ totalAnswered: 8,
179
+ totalSkipped: 2,
180
+ totalTimedOut: 0,
181
+ overallSkipRate: '20%',
182
+ avgRoundsPerSession: 1.2,
183
+ },
184
+ period: {
185
+ days: 7,
186
+ asked: 5,
187
+ answered: 4,
188
+ skipped: 1,
189
+ },
190
+ categories: {
191
+ timeframe: { asked: 5, answered: 4, skipRate: '20%' },
192
+ },
193
+ };
194
+
195
+ const result = formatAnalyticsForCLI(analytics, { gold: '', reset: '', bold: '', white: '', dim: '', green: '', yellow: '', red: '' });
196
+ assert.ok(result.includes('Clarification Analytics'));
197
+ assert.ok(result.includes('10'));
198
+ assert.ok(result.includes('timeframe'));
199
+ });
200
+ });
201
+
202
+ describe('formatConfigForCLI', () => {
203
+ it('formats config for display', () => {
204
+ const config = {
205
+ enabled: true,
206
+ confidenceThreshold: 0.65,
207
+ maxRounds: 2,
208
+ timeoutMs: 30000,
209
+ preferLLMQuestions: true,
210
+ };
211
+
212
+ const result = formatConfigForCLI(config, { gold: '', reset: '', bold: '', white: '', dim: '', green: '', yellow: '' });
213
+ assert.ok(result.includes('Clarification Settings'));
214
+ assert.ok(result.includes('65%'));
215
+ assert.ok(result.includes('2'));
216
+ });
217
+ });
218
+
219
+ console.log('✅ All clarification analytics tests passed');