kiku-agent 1.0.11 → 1.2.0

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 (221) hide show
  1. package/README.md +300 -495
  2. package/package.json +12 -11
  3. package/public/fonts/GoogleSans-Bold.ttf +0 -0
  4. package/public/fonts/GoogleSans-Bold.woff2 +0 -0
  5. package/public/fonts/GoogleSans-BoldItalic.ttf +0 -0
  6. package/public/fonts/GoogleSans-Italic.ttf +0 -0
  7. package/public/fonts/GoogleSans-Latin-400.woff2 +0 -0
  8. package/public/fonts/GoogleSans-Latin-500.woff2 +0 -0
  9. package/public/fonts/GoogleSans-Latin-600.woff2 +0 -0
  10. package/public/fonts/GoogleSans-Latin-700.woff2 +0 -0
  11. package/public/fonts/GoogleSans-Medium.ttf +0 -0
  12. package/public/fonts/GoogleSans-Medium.woff2 +0 -0
  13. package/public/fonts/GoogleSans-Regular.ttf +0 -0
  14. package/public/fonts/GoogleSans-Regular.woff2 +0 -0
  15. package/public/fonts/GoogleSans-Thai-400.woff2 +0 -0
  16. package/public/fonts/GoogleSans-Thai-500.woff2 +0 -0
  17. package/public/fonts/GoogleSans-Thai-600.woff2 +0 -0
  18. package/public/fonts/GoogleSans-Thai-700.woff2 +0 -0
  19. package/public/fonts/NotoSansThai-Bold.woff2 +0 -0
  20. package/public/fonts/NotoSansThai-Regular.woff2 +0 -0
  21. package/public/fonts/google-sans-official.css +900 -0
  22. package/public/images/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
  23. package/public/images/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
  24. package/public/images/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
  25. package/public/images/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
  26. package/public/images/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
  27. package/public/images/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
  28. package/public/images/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
  29. package/public/images/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
  30. package/public/images/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
  31. package/public/images/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
  32. package/public/images/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
  33. package/public/images/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
  34. package/public/images/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
  35. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-399915.jpg +0 -0
  36. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-424041.jpg +0 -0
  37. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-588166.jpg +0 -0
  38. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-672198.jpg +0 -0
  39. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-700522.jpg +0 -0
  40. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-839722.jpg +0 -0
  41. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026825-460586.jpg +0 -0
  42. package/public/images/douyin-update/thumbs//346/212/226/351/237/263202694-689407.jpg +0 -0
  43. package/public/images/douyin-update//346/212/226/351/237/2632026820-399915.mp4 +0 -0
  44. package/public/images/douyin-update//346/212/226/351/237/2632026820-424041.mp4 +0 -0
  45. package/public/images/douyin-update//346/212/226/351/237/2632026820-588166.mp4 +0 -0
  46. package/public/images/douyin-update//346/212/226/351/237/2632026820-672198.mp4 +0 -0
  47. package/public/images/douyin-update//346/212/226/351/237/2632026820-700522.mp4 +0 -0
  48. package/public/images/douyin-update//346/212/226/351/237/2632026820-839722.mp4 +0 -0
  49. package/public/images/douyin-update//346/212/226/351/237/2632026825-460586.mp4 +0 -0
  50. package/public/images/douyin-update//346/212/226/351/237/263202694-689407.mp4 +0 -0
  51. package/public/images/general/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
  52. package/public/images/general/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
  53. package/public/images/general/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
  54. package/public/images/general/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
  55. package/public/images/general/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
  56. package/public/images/general/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
  57. package/public/images/general/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
  58. package/public/images/general/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
  59. package/public/images/general/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
  60. package/public/images/general/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
  61. package/public/images/general/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
  62. package/public/images/general/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
  63. package/public/images/general/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
  64. package/public/images/general/jujingyi_portrait.jpg +0 -0
  65. package/public/images/jujingyi_portrait.jpg +0 -0
  66. package/public/images/series/a-millenniums-wait/poster.jpg +0 -0
  67. package/public/images/series/beauty-of-resilience/poster.jpg +0 -0
  68. package/public/images/series/detective-samoyeds/poster.jpg +0 -0
  69. package/public/images/series/everyday/poster.jpg +0 -0
  70. package/public/images/series/fu-rong-ancient-painting/poster.jpg +0 -0
  71. package/public/images/series/in-a-class-of-her-own/poster.jpg +0 -0
  72. package/public/images/series/in-blossom/poster.jpg +0 -0
  73. package/public/images/series/legend-of-yunxi/poster.jpg +0 -0
  74. package/public/images/series/lets-fight/poster-2.jpg +0 -0
  75. package/public/images/series/lets-fight/poster.jpg +0 -0
  76. package/public/images/series/love-emergency/poster.jpg +0 -0
  77. package/public/images/series/love-under-the-full-moon/poster.jpg +0 -0
  78. package/public/images/series/money-bastion/poster.jpg +0 -0
  79. package/public/images/series/mr-swimmer/poster.jpg +0 -0
  80. package/public/images/series/novoland-castle-in-the-sky/poster.jpg +0 -0
  81. package/public/images/series/please-give-me-a-pair-of-wings/poster.jpg +0 -0
  82. package/public/images/series/rebirth-for-you/poster.jpg +0 -0
  83. package/public/images/series/sword-and-fairy-4/poster.jpg +0 -0
  84. package/public/images/series/the-blooms-at-ruyi-pavilion/poster.jpg +0 -0
  85. package/public/images/series/the-eternal-fragrance/poster-2.jpg +0 -0
  86. package/public/images/series/the-eternal-fragrance/poster.jpg +0 -0
  87. package/public/images/series/the-fallen-flowers/poster.jpg +0 -0
  88. package/public/images/series/the-legend-of-white-snake/poster.jpg +0 -0
  89. package/public/images/series/veil-of-shadows/poster.jpg +0 -0
  90. package/public/images/series/wanhua-sin/poster-2.jpg +0 -0
  91. package/public/images/series/wanhua-sin/poster.jpg +0 -0
  92. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj10yogj62u03s0npe02.jpg +0 -0
  93. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj2pdvyj61li25jkjl02.jpg +0 -0
  94. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj71177j62672w9e8302.jpg +0 -0
  95. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj7twpfj62192197rw02.jpg +0 -0
  96. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj8jfu8j62c03401kx02.jpg +0 -0
  97. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj9dhdsj622k2l77wh02.jpg +0 -0
  98. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkja31s4j62bw3407r802.jpg +0 -0
  99. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjcf331j61402ey1kx02.jpg +0 -0
  100. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjdx8gqj62602pihdt02.jpg +0 -0
  101. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjfvxnzj62432n57wh02.jpg +0 -0
  102. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixniv2yj627y4fxb2b02.jpg +0 -0
  103. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixsvbdaj625k2vfu1102.jpg +0 -0
  104. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixxxdaoj62c03501l202.jpg +0 -0
  105. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixzeef9j62c0340npd02.jpg +0 -0
  106. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy0vs6tj626t4rbhdu02.jpg +0 -0
  107. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy21wwuj62bt34hx6q02.jpg +0 -0
  108. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly4wr85j62z629y7wh02.jpg +0 -0
  109. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly67oilj623u2t5npd02.jpg +0 -0
  110. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly9qtw8j62c0340u0x02.jpg +0 -0
  111. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyem4m4j63402c01kz02.jpg +0 -0
  112. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlygkoq4j627s2yeavd02.jpg +0 -0
  113. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyjh0daj62bw340nm102.jpg +0 -0
  114. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlylejlhj62bw3401kx02.jpg +0 -0
  115. package/public/images/weibo-update/today/0040jbadgy1igaiaa8b7fj627j2y67wh02.jpg +0 -0
  116. package/public/images/weibo-update/today/0040jbadgy1igaiagw3ezj61s02ebu0x02.jpg +0 -0
  117. package/public/images/weibo-update/today/0040jbadgy1igaiaivng3j61rv2ebx6p02.jpg +0 -0
  118. package/public/images/weibo-update/today/0040jbadgy1igaiajuixvj62bw340qv502.jpg +0 -0
  119. package/public/images/weibo-update/today/0040jbadgy1igaiapyartj62bw340hdt02.jpg +0 -0
  120. package/public/images/weibo-update/today/0040jbadgy1igaibkwu4ij61vy2iq1kx02.jpg +0 -0
  121. package/public/images/weibo-update/today/0040jbadgy1igaic1fvkpj61u92eo1ky02.jpg +0 -0
  122. package/public/images/weibo-update/today/0040jbadgy1igaic3cruhj62822ywb2902.jpg +0 -0
  123. package/public/manifest.json +25 -0
  124. package/public/mcp-logo-128.png +0 -0
  125. package/public/mcp-logo-64.png +0 -0
  126. package/public/mcp-logo.svg +16 -0
  127. package/server/agent/agentCore.js +542 -74
  128. package/server/agent/appPaths.js +1 -0
  129. package/server/agent/audioPlayer.js +4 -2
  130. package/server/agent/citations.js +294 -0
  131. package/server/agent/clarificationAnalytics.js +477 -0
  132. package/server/agent/clarifyingQuestions.js +1089 -0
  133. package/server/agent/cli.js +549 -125
  134. package/server/agent/commandFeatures.js +1 -0
  135. package/server/agent/commandRegistry.js +946 -879
  136. package/server/agent/commands.js +546 -272
  137. package/server/agent/config.js +581 -412
  138. package/server/agent/contextCompactor.js +2 -2
  139. package/server/agent/debate.js +235 -0
  140. package/server/agent/eventBus.js +3 -0
  141. package/server/agent/feedbackLearning.js +217 -0
  142. package/server/agent/llmClient.js +512 -394
  143. package/server/agent/mcpClient.js +182 -32
  144. package/server/agent/memory.js +283 -224
  145. package/server/agent/modelManager.js +79 -39
  146. package/server/agent/profiles.js +178 -0
  147. package/server/agent/publicTools.js +22 -0
  148. package/server/agent/queryDetails.js +68 -8
  149. package/server/agent/quizData.js +398 -384
  150. package/server/agent/research.js +121 -79
  151. package/server/agent/sanitizer.js +104 -13
  152. package/server/agent/session.js +319 -11
  153. package/server/agent/subagent.js +8 -3
  154. package/server/agent/suggestionTui.js +294 -0
  155. package/server/agent/suggestions.js +69 -3
  156. package/server/agent/telemetry.js +119 -0
  157. package/server/agent/terminalImage.js +146 -27
  158. package/server/agent/theme.js +17 -0
  159. package/server/agent/toolRouter.js +16 -1
  160. package/server/agent/tools.js +424 -225
  161. package/server/agent/ui.js +486 -64
  162. package/server/agent/version.js +2 -4
  163. package/server/agent/webSearch.js +111 -11
  164. package/server/agent/youtubeAuth.js +24 -6
  165. package/server/apiRouter.js +4222 -0
  166. package/server/mcp/data/discography.js +25 -16
  167. package/server/mcp/data/gallery.js +308 -20
  168. package/server/mcp/data/profile.js +74 -44
  169. package/server/mcp/data/timeline.js +1 -1
  170. package/server/mcp/data/works.js +51 -17
  171. package/server/mcp/dataAdapter.js +5 -3
  172. package/server/mcp/handler.js +5 -11
  173. package/server/mcp/prompts.js +86 -0
  174. package/server/mcp/security.js +18 -15
  175. package/server/mcp/server.js +7 -2
  176. package/server/mcp/tools.js +66 -2
  177. package/server/web-chat/core/browserLauncher.js +18 -0
  178. package/server/web-chat/core/connection.js +194 -0
  179. package/server/web-chat/core/conversationContext.js +82 -0
  180. package/server/web-chat/core/directCatalogEngine.js +196 -0
  181. package/server/web-chat/core/dramaKnowledge.js +142 -0
  182. package/server/web-chat/core/errorHandler.js +51 -0
  183. package/server/web-chat/core/followUpEngine.js +182 -0
  184. package/server/web-chat/core/futureEventGuard.js +80 -0
  185. package/server/web-chat/core/memorySnapshot.js +12 -0
  186. package/server/web-chat/core/security.js +146 -0
  187. package/server/web-chat/core/toolInspector.js +52 -0
  188. package/server/web-chat/router.js +120 -0
  189. package/server/web-chat/routes/chatRoute.js +397 -0
  190. package/server/web-chat/routes/clarifyConfigRoute.js +98 -0
  191. package/server/web-chat/routes/clarifyRoute.js +83 -0
  192. package/server/web-chat/routes/compareRoute.js +79 -0
  193. package/server/web-chat/routes/exportRoute.js +59 -0
  194. package/server/web-chat/routes/feedbackRoute.js +57 -0
  195. package/server/web-chat/routes/newsRoute.js +33 -0
  196. package/server/web-chat/routes/quizRoute.js +11 -0
  197. package/server/web-chat/routes/staticRoute.js +103 -0
  198. package/server/web-chat/routes/statusRoute.js +52 -0
  199. package/server/web-chat/routes/suggestRoute.js +77 -0
  200. package/server/web-chat/routes/timelineRoute.js +34 -0
  201. package/server/web-chat/routes/verifyRoute.js +57 -0
  202. package/server/web-chat/server.js +116 -0
  203. package/server/web-chat/ui/htmlBuilder.js +91 -0
  204. package/server/web-chat/ui/previews/agentBubble.preview.html +134 -0
  205. package/server/web-chat/ui/scripts/agentBubbleSystem.js +320 -0
  206. package/server/web-chat/ui/scripts/client.js +8397 -0
  207. package/server/web-chat/ui/scripts/kikuDb.js +177 -0
  208. package/server/web-chat/ui/scripts/suggestionsData.js +118 -0
  209. package/server/web-chat/ui/scripts/webchatI18n.js +291 -0
  210. package/server/web-chat/ui/styles/main.css +8568 -0
  211. package/server/web-chat/ui/templates/body.html +796 -0
  212. package/server/webChatHtml.js +1 -0
  213. package/server/webChatServer.js +6 -0
  214. package/src/data/discographyData.js +892 -866
  215. package/src/data/galleryData.js +1805 -561
  216. package/src/data/profileData.js +79 -2
  217. package/src/data/socialUpdatesData.js +106 -47
  218. package/src/data/timelineData.js +1 -1
  219. package/src/data/worksData.js +25 -4
  220. package/tokens.css +97 -0
  221. package/server/agent/updateManager.js +0 -456
@@ -0,0 +1,477 @@
1
+
2
+
3
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
5
+
6
+ const DEFAULT_CONFIG = {
7
+ enabled: true,
8
+ confidenceThreshold: 0.65,
9
+ maxRounds: 2,
10
+ timeoutMs: 30000,
11
+ preferLLMQuestions: false,
12
+ trackedCategories: [
13
+ 'timeframe', 'scope', 'depth', 'comparison', 'media_type',
14
+ 'language', 'specific_work', 'aspect_focus', 'date_range', 'content_format'
15
+ ],
16
+ };
17
+
18
+ const ANALYTICS_RETENTION_DAYS = 30;
19
+
20
+ export class ClarificationAnalytics {
21
+ constructor(options = {}) {
22
+ this.storagePath = options.storagePath || join(process.cwd(), '.kiku_sessions', 'clarification_analytics.json');
23
+ this.stats = {
24
+ totalQueries: 0,
25
+ totalClarifications: 0,
26
+ totalSkipped: 0,
27
+ totalTimedOut: 0,
28
+ roundsHistogram: {},
29
+ categoryStats: {},
30
+ confidenceBuckets: {},
31
+ recentSessions: [],
32
+ dailyStats: {},
33
+ };
34
+ this._maxRecentSessions = options.maxRecentSessions || 100;
35
+ this._load();
36
+ }
37
+
38
+ _load() {
39
+ try {
40
+ if (existsSync(this.storagePath)) {
41
+ const data = JSON.parse(readFileSync(this.storagePath, 'utf-8'));
42
+ this.stats = { ...this.stats, ...data };
43
+ }
44
+ } catch {
45
+
46
+ }
47
+ }
48
+
49
+ _save() {
50
+ try {
51
+ const dir = dirname(this.storagePath);
52
+ if (!existsSync(dir)) {
53
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
54
+ }
55
+ writeFileSync(this.storagePath, JSON.stringify(this.stats, null, 2), { mode: 0o600 });
56
+ } catch {
57
+
58
+ }
59
+ }
60
+
61
+
62
+ recordClarificationStart(queryId, questions, confidenceScore) {
63
+ const today = new Date().toISOString().split('T')[0];
64
+
65
+ const session = {
66
+ queryId,
67
+ startedAt: Date.now(),
68
+ confidenceScore,
69
+ rounds: [{ round: 1, questions: questions.map(q => q.category) }],
70
+ totalQuestions: questions.length,
71
+ answered: false,
72
+ skipped: false,
73
+ timedOut: false,
74
+ finalRound: 1,
75
+ answerSummary: null,
76
+ };
77
+
78
+
79
+ this.stats.totalClarifications++;
80
+
81
+
82
+ if (!this.stats.dailyStats[today]) {
83
+ this.stats.dailyStats[today] = { asked: 0, answered: 0, skipped: 0 };
84
+ }
85
+ this.stats.dailyStats[today].asked++;
86
+
87
+
88
+ for (const q of questions) {
89
+ if (!this.stats.categoryStats[q.category]) {
90
+ this.stats.categoryStats[q.category] = { asked: 0, answered: 0, totalResponseTimeMs: 0 };
91
+ }
92
+ this.stats.categoryStats[q.category].asked++;
93
+ }
94
+
95
+
96
+ const bucket = this._getConfidenceBucket(confidenceScore);
97
+ this.stats.confidenceBuckets[bucket] = (this.stats.confidenceBuckets[bucket] || 0) + 1;
98
+
99
+
100
+ this.stats.recentSessions.unshift(session);
101
+ if (this.stats.recentSessions.length > this._maxRecentSessions) {
102
+ this.stats.recentSessions = this.stats.recentSessions.slice(0, this._maxRecentSessions);
103
+ }
104
+
105
+ this._save();
106
+ return session;
107
+ }
108
+
109
+
110
+ recordClarificationAnswer(queryId, answers, round, responseTimeMs) {
111
+ const session = this.stats.recentSessions.find(s => s.queryId === queryId);
112
+ if (!session) return;
113
+
114
+ session.answered = true;
115
+ session.answerSummary = answers;
116
+ session.finalRound = round;
117
+
118
+
119
+ if (round > 1) {
120
+ session.rounds.push({ round, questions: Object.keys(answers) });
121
+ session.totalQuestions += Object.keys(answers).length;
122
+ }
123
+
124
+
125
+ for (const [category] of Object.entries(answers)) {
126
+ if (this.stats.categoryStats[category]) {
127
+ this.stats.categoryStats[category].answered++;
128
+ this.stats.categoryStats[category].totalResponseTimeMs += responseTimeMs;
129
+ }
130
+ }
131
+
132
+
133
+ const today = new Date().toISOString().split('T')[0];
134
+ if (this.stats.dailyStats[today]) {
135
+ this.stats.dailyStats[today].answered++;
136
+ }
137
+
138
+
139
+ this.stats.roundsHistogram[round] = (this.stats.roundsHistogram[round] || 0) + 1;
140
+
141
+ this._save();
142
+ }
143
+
144
+
145
+ recordClarificationSkipped(queryId) {
146
+ const session = this.stats.recentSessions.find(s => s.queryId === queryId);
147
+ if (session) {
148
+ session.skipped = true;
149
+ session.answered = false;
150
+ }
151
+
152
+ this.stats.totalSkipped++;
153
+
154
+ const today = new Date().toISOString().split('T')[0];
155
+ if (this.stats.dailyStats[today]) {
156
+ this.stats.dailyStats[today].skipped++;
157
+ }
158
+
159
+ this._save();
160
+ }
161
+
162
+
163
+ recordClarificationTimeout(queryId) {
164
+ const session = this.stats.recentSessions.find(s => s.queryId === queryId);
165
+ if (session) {
166
+ session.timedOut = true;
167
+ }
168
+
169
+ this.stats.totalTimedOut++;
170
+ this._save();
171
+ }
172
+
173
+
174
+ getAnalytics(options = {}) {
175
+ const { days = 7 } = options;
176
+ const cutoffDate = new Date();
177
+ cutoffDate.setDate(cutoffDate.getDate() - days);
178
+ const cutoffStr = cutoffDate.toISOString().split('T')[0];
179
+
180
+
181
+ const periodStats = {};
182
+ for (const [date, stats] of Object.entries(this.stats.dailyStats)) {
183
+ if (date >= cutoffStr) {
184
+ periodStats[date] = stats;
185
+ }
186
+ }
187
+
188
+
189
+ let periodAsked = 0, periodAnswered = 0, periodSkipped = 0;
190
+ for (const stats of Object.values(periodStats)) {
191
+ periodAsked += stats.asked;
192
+ periodAnswered += stats.answered;
193
+ periodSkipped += stats.skipped;
194
+ }
195
+
196
+
197
+ const categoryEffectiveness = {};
198
+ for (const [category, stats] of Object.entries(this.stats.categoryStats)) {
199
+ const avgResponseTime = stats.answered > 0
200
+ ? Math.round(stats.totalResponseTimeMs / stats.answered)
201
+ : 0;
202
+ categoryEffectiveness[category] = {
203
+ asked: stats.asked,
204
+ answered: stats.answered,
205
+ skipRate: stats.asked > 0
206
+ ? ((stats.asked - stats.answered) / stats.asked * 100).toFixed(1) + '%'
207
+ : '0%',
208
+ avgResponseTimeMs: avgResponseTime,
209
+ };
210
+ }
211
+
212
+
213
+ const sessionsWithRounds = this.stats.recentSessions.filter(s => s.answered);
214
+ const avgRounds = sessionsWithRounds.length > 0
215
+ ? (sessionsWithRounds.reduce((sum, s) => sum + s.finalRound, 0) / sessionsWithRounds.length).toFixed(2)
216
+ : 0;
217
+
218
+ return {
219
+ summary: {
220
+ totalClarifications: this.stats.totalClarifications,
221
+ totalAnswered: this.stats.totalClarifications - this.stats.totalSkipped - this.stats.totalTimedOut,
222
+ totalSkipped: this.stats.totalSkipped,
223
+ totalTimedOut: this.stats.totalTimedOut,
224
+ overallSkipRate: this.stats.totalClarifications > 0
225
+ ? ((this.stats.totalSkipped / this.stats.totalClarifications) * 100).toFixed(1) + '%'
226
+ : '0%',
227
+ avgRoundsPerSession: parseFloat(avgRounds),
228
+ },
229
+ period: {
230
+ days,
231
+ asked: periodAsked,
232
+ answered: periodAnswered,
233
+ skipped: periodSkipped,
234
+ dailyBreakdown: periodStats,
235
+ },
236
+ categories: categoryEffectiveness,
237
+ roundsDistribution: this.stats.roundsHistogram,
238
+ confidenceBuckets: this.stats.confidenceBuckets,
239
+ recentSessions: this.stats.recentSessions.slice(0, 10),
240
+ };
241
+ }
242
+
243
+
244
+ getCategoryEffectiveness(category) {
245
+ const stats = this.stats.categoryStats[category];
246
+ if (!stats || stats.asked === 0) return 50;
247
+
248
+ const answerRate = stats.answered / stats.asked;
249
+ const avgResponseTime = stats.answered > 0
250
+ ? stats.totalResponseTimeMs / stats.answered
251
+ : 5000;
252
+
253
+
254
+ const responseScore = Math.max(0, 1 - (avgResponseTime / 10000));
255
+
256
+
257
+ return Math.round((answerRate * 0.7 + responseScore * 0.3) * 100);
258
+ }
259
+
260
+
261
+ getTopCategories(n = 5) {
262
+ const categories = Object.keys(this.stats.categoryStats);
263
+ const scored = categories.map(cat => ({
264
+ category: cat,
265
+ effectiveness: this.getCategoryEffectiveness(cat),
266
+ asked: this.stats.categoryStats[cat].asked,
267
+ }));
268
+ return scored.sort((a, b) => b.effectiveness - a.effectiveness).slice(0, n);
269
+ }
270
+
271
+
272
+ cleanup(olderThanDays = ANALYTICS_RETENTION_DAYS) {
273
+ const cutoffDate = new Date();
274
+ cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);
275
+ const cutoffStr = cutoffDate.toISOString().split('T')[0];
276
+
277
+
278
+ for (const date of Object.keys(this.stats.dailyStats)) {
279
+ if (date < cutoffStr) {
280
+ delete this.stats.dailyStats[date];
281
+ }
282
+ }
283
+
284
+
285
+ this.stats.recentSessions = this.stats.recentSessions.filter(
286
+ s => s.startedAt > cutoffDate.getTime()
287
+ );
288
+
289
+ this._save();
290
+ }
291
+
292
+ _getConfidenceBucket(score) {
293
+ if (score < 0.2) return '0-20';
294
+ if (score < 0.4) return '20-40';
295
+ if (score < 0.6) return '40-60';
296
+ if (score < 0.8) return '60-80';
297
+ return '80-100';
298
+ }
299
+ }
300
+
301
+ export class ClarificationConfig {
302
+ constructor(options = {}) {
303
+ this.storagePath = options.storagePath || join(process.cwd(), '.kiku_sessions', 'clarification_config.json');
304
+ this.config = { ...DEFAULT_CONFIG };
305
+ this._load();
306
+ }
307
+
308
+ _load() {
309
+ try {
310
+ if (existsSync(this.storagePath)) {
311
+ const data = JSON.parse(readFileSync(this.storagePath, 'utf-8'));
312
+ this.config = { ...DEFAULT_CONFIG, ...data };
313
+ }
314
+ } catch {
315
+
316
+ }
317
+ }
318
+
319
+ _save() {
320
+ try {
321
+ const dir = dirname(this.storagePath);
322
+ if (!existsSync(dir)) {
323
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
324
+ }
325
+ writeFileSync(this.storagePath, JSON.stringify(this.config, null, 2), { mode: 0o600 });
326
+ } catch {
327
+
328
+ }
329
+ }
330
+
331
+
332
+ getConfig() {
333
+ return { ...this.config };
334
+ }
335
+
336
+
337
+ updateConfig(updates) {
338
+
339
+ if (updates.confidenceThreshold !== undefined) {
340
+ const val = Number(updates.confidenceThreshold);
341
+ if (isNaN(val) || val < 0 || val > 1) {
342
+ throw new Error('confidenceThreshold must be between 0 and 1');
343
+ }
344
+ this.config.confidenceThreshold = val;
345
+ }
346
+
347
+ if (updates.maxRounds !== undefined) {
348
+ const val = Number(updates.maxRounds);
349
+ if (isNaN(val) || val < 1 || val > 5) {
350
+ throw new Error('maxRounds must be between 1 and 5');
351
+ }
352
+ this.config.maxRounds = val;
353
+ }
354
+
355
+ if (updates.timeoutMs !== undefined) {
356
+ const val = Number(updates.timeoutMs);
357
+ if (isNaN(val) || val < 5000 || val > 60000) {
358
+ throw new Error('timeoutMs must be between 5000 and 60000');
359
+ }
360
+ this.config.timeoutMs = val;
361
+ }
362
+
363
+ if (updates.enabled !== undefined) {
364
+ this.config.enabled = Boolean(updates.enabled);
365
+ }
366
+
367
+ if (updates.preferLLMQuestions !== undefined) {
368
+ this.config.preferLLMQuestions = Boolean(updates.preferLLMQuestions);
369
+ }
370
+
371
+ this._save();
372
+ return this.getConfig();
373
+ }
374
+
375
+
376
+ reset() {
377
+ this.config = { ...DEFAULT_CONFIG };
378
+ this._save();
379
+ return this.getConfig();
380
+ }
381
+
382
+
383
+ get(key) {
384
+ return this.config[key];
385
+ }
386
+
387
+
388
+ set(key, value) {
389
+ this.config[key] = value;
390
+ this._save();
391
+ }
392
+ }
393
+
394
+ export function formatAnalyticsForCLI(analytics, colors = {}) {
395
+ const C = colors;
396
+ const lines = [];
397
+
398
+ lines.push(`${C.gold || ''}┌─ Clarification Analytics ──────────────────────────┐${C.reset || ''}`);
399
+ lines.push(`${C.gold || ''}│${C.reset || ''}`);
400
+
401
+
402
+ const s = analytics.summary;
403
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.bold || ''}Summary${C.reset || ''}`);
404
+ lines.push(`${C.gold || ''}│${C.reset || ''} Total Clarifications: ${C.white || ''}${s.totalClarifications}${C.reset || ''}`);
405
+ lines.push(`${C.gold || ''}│${C.reset || ''} Answered: ${C.green || ''}${s.totalAnswered}${C.reset || ''} Skipped: ${C.yellow || ''}${s.totalSkipped}${C.reset || ''} Timeout: ${C.red || ''}${s.totalTimedOut}${C.reset || ''}`);
406
+ lines.push(`${C.gold || ''}│${C.reset || ''} Skip Rate: ${C.dim || ''}${s.overallSkipRate}${C.reset || ''} Avg Rounds: ${C.dim || ''}${s.avgRoundsPerSession}${C.reset || ''}`);
407
+ lines.push(`${C.gold || ''}│${C.reset || ''}`);
408
+
409
+
410
+ const topCats = Object.entries(analytics.categories)
411
+ .sort(([,a], [,b]) => b.asked - a.asked)
412
+ .slice(0, 5);
413
+
414
+ if (topCats.length > 0) {
415
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.bold || ''}Top Categories${C.reset || ''}`);
416
+ for (const [cat, stats] of topCats) {
417
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.dim || ''}${cat.padEnd(16)}${C.reset || ''} Asked: ${stats.asked} Answered: ${stats.answered} Skip: ${stats.skipRate}`);
418
+ }
419
+ lines.push(`${C.gold || ''}│${C.reset || ''}`);
420
+ }
421
+
422
+
423
+ const p = analytics.period;
424
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.bold || ''}Last ${p.days} Days${C.reset || ''}`);
425
+ lines.push(`${C.gold || ''}│${C.reset || ''} Asked: ${p.asked} Answered: ${p.answered} Skipped: ${p.skipped}`);
426
+ lines.push(`${C.gold || ''}│${C.reset || ''}`);
427
+
428
+ lines.push(`${C.gold || ''}└────────────────────────────────────────────────────┘${C.reset || ''}`);
429
+
430
+ return lines.join('\n');
431
+ }
432
+
433
+ export function formatConfigForCLI(config, colors = {}) {
434
+ const C = colors;
435
+ const lines = [];
436
+
437
+ lines.push(`${C.gold || ''}┌─ Clarification Settings ───────────────────────────┐${C.reset || ''}`);
438
+ lines.push(`${C.gold || ''}│${C.reset || ''}`);
439
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.bold || ''}Current Configuration${C.reset || ''}`);
440
+ lines.push(`${C.gold || ''}│${C.reset || ''}`);
441
+ lines.push(`${C.gold || ''}│${C.reset || ''} Enabled: ${config.enabled ? `${C.green || ''}✓ ON${C.reset || ''}` : `${C.red || ''}✗ OFF${C.reset || ''}`}`);
442
+ lines.push(`${C.gold || ''}│${C.reset || ''} Confidence Threshold: ${C.white || ''}${(config.confidenceThreshold * 100).toFixed(0)}%${C.reset || ''} ${C.dim || ''}(ask questions when confidence < this)${C.reset || ''}`);
443
+ lines.push(`${C.gold || ''}│${C.reset || ''} Max Rounds: ${C.white || ''}${config.maxRounds}${C.reset || ''}`);
444
+ lines.push(`${C.gold || ''}│${C.reset || ''} Timeout: ${C.white || ''}${(config.timeoutMs / 1000).toFixed(1)}s${C.reset || ''}`);
445
+ lines.push(`${C.gold || ''}│${C.reset || ''} LLM Questions: ${config.preferLLMQuestions ? `${C.green || ''}✓ ON${C.reset || ''}` : `${C.yellow || ''}✗ OFF${C.reset || ''}`}`);
446
+ lines.push(`${C.gold || ''}│${C.reset || ''}`);
447
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.dim || ''}Commands:${C.reset || ''}`);
448
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.dim || ''}/clarify config threshold <0-100>${C.reset || ''}`);
449
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.dim || ''}/clarify config rounds <1-5>${C.reset || ''}`);
450
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.dim || ''}/clarify config timeout <5-60>${C.reset || ''}`);
451
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.dim || ''}/clarify config on|off${C.reset || ''}`);
452
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.dim || ''}/clarify config llm on|off${C.reset || ''}`);
453
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.dim || ''}/clarify config reset${C.reset || ''}`);
454
+ lines.push(`${C.gold || ''}│${C.reset || ''} ${C.dim || ''}/clarify analytics${C.reset || ''}`);
455
+ lines.push(`${C.gold || ''}│${C.reset || ''}`);
456
+
457
+ lines.push(`${C.gold || ''}└────────────────────────────────────────────────────┘${C.reset || ''}`);
458
+
459
+ return lines.join('\n');
460
+ }
461
+
462
+ export function formatAnalyticsForAPI(analytics) {
463
+ return {
464
+ type: 'clarification_analytics',
465
+ data: analytics,
466
+ timestamp: Date.now(),
467
+ };
468
+ }
469
+
470
+ export default {
471
+ ClarificationAnalytics,
472
+ ClarificationConfig,
473
+ formatAnalyticsForCLI,
474
+ formatConfigForCLI,
475
+ formatAnalyticsForAPI,
476
+ DEFAULT_CONFIG,
477
+ };