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.
- package/README.md +300 -495
- package/package.json +12 -11
- package/public/fonts/GoogleSans-Bold.ttf +0 -0
- package/public/fonts/GoogleSans-Bold.woff2 +0 -0
- package/public/fonts/GoogleSans-BoldItalic.ttf +0 -0
- package/public/fonts/GoogleSans-Italic.ttf +0 -0
- package/public/fonts/GoogleSans-Latin-400.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-500.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-600.woff2 +0 -0
- package/public/fonts/GoogleSans-Latin-700.woff2 +0 -0
- package/public/fonts/GoogleSans-Medium.ttf +0 -0
- package/public/fonts/GoogleSans-Medium.woff2 +0 -0
- package/public/fonts/GoogleSans-Regular.ttf +0 -0
- package/public/fonts/GoogleSans-Regular.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-400.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-500.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-600.woff2 +0 -0
- package/public/fonts/GoogleSans-Thai-700.woff2 +0 -0
- package/public/fonts/NotoSansThai-Bold.woff2 +0 -0
- package/public/fonts/NotoSansThai-Regular.woff2 +0 -0
- package/public/fonts/google-sans-official.css +900 -0
- package/public/images/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
- package/public/images/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
- package/public/images/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
- package/public/images/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/public/images/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
- package/public/images/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
- package/public/images/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
- package/public/images/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
- package/public/images/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-399915.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-424041.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-588166.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-672198.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-700522.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-839722.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026825-460586.jpg +0 -0
- package/public/images/douyin-update/thumbs//346/212/226/351/237/263202694-689407.jpg +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-399915.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-424041.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-588166.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-672198.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-700522.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026820-839722.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/2632026825-460586.mp4 +0 -0
- package/public/images/douyin-update//346/212/226/351/237/263202694-689407.mp4 +0 -0
- package/public/images/general/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
- package/public/images/general/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
- package/public/images/general/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
- package/public/images/general/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
- package/public/images/general/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
- package/public/images/general/jujingyi_portrait.jpg +0 -0
- package/public/images/jujingyi_portrait.jpg +0 -0
- package/public/images/series/a-millenniums-wait/poster.jpg +0 -0
- package/public/images/series/beauty-of-resilience/poster.jpg +0 -0
- package/public/images/series/detective-samoyeds/poster.jpg +0 -0
- package/public/images/series/everyday/poster.jpg +0 -0
- package/public/images/series/fu-rong-ancient-painting/poster.jpg +0 -0
- package/public/images/series/in-a-class-of-her-own/poster.jpg +0 -0
- package/public/images/series/in-blossom/poster.jpg +0 -0
- package/public/images/series/legend-of-yunxi/poster.jpg +0 -0
- package/public/images/series/lets-fight/poster-2.jpg +0 -0
- package/public/images/series/lets-fight/poster.jpg +0 -0
- package/public/images/series/love-emergency/poster.jpg +0 -0
- package/public/images/series/love-under-the-full-moon/poster.jpg +0 -0
- package/public/images/series/money-bastion/poster.jpg +0 -0
- package/public/images/series/mr-swimmer/poster.jpg +0 -0
- package/public/images/series/novoland-castle-in-the-sky/poster.jpg +0 -0
- package/public/images/series/please-give-me-a-pair-of-wings/poster.jpg +0 -0
- package/public/images/series/rebirth-for-you/poster.jpg +0 -0
- package/public/images/series/sword-and-fairy-4/poster.jpg +0 -0
- package/public/images/series/the-blooms-at-ruyi-pavilion/poster.jpg +0 -0
- package/public/images/series/the-eternal-fragrance/poster-2.jpg +0 -0
- package/public/images/series/the-eternal-fragrance/poster.jpg +0 -0
- package/public/images/series/the-fallen-flowers/poster.jpg +0 -0
- package/public/images/series/the-legend-of-white-snake/poster.jpg +0 -0
- package/public/images/series/veil-of-shadows/poster.jpg +0 -0
- package/public/images/series/wanhua-sin/poster-2.jpg +0 -0
- package/public/images/series/wanhua-sin/poster.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj10yogj62u03s0npe02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj2pdvyj61li25jkjl02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj71177j62672w9e8302.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj7twpfj62192197rw02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj8jfu8j62c03401kx02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj9dhdsj622k2l77wh02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkja31s4j62bw3407r802.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjcf331j61402ey1kx02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjdx8gqj62602pihdt02.jpg +0 -0
- package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjfvxnzj62432n57wh02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixniv2yj627y4fxb2b02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixsvbdaj625k2vfu1102.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixxxdaoj62c03501l202.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixzeef9j62c0340npd02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy0vs6tj626t4rbhdu02.jpg +0 -0
- package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy21wwuj62bt34hx6q02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly4wr85j62z629y7wh02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly67oilj623u2t5npd02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly9qtw8j62c0340u0x02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyem4m4j63402c01kz02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlygkoq4j627s2yeavd02.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyjh0daj62bw340nm102.jpg +0 -0
- package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlylejlhj62bw3401kx02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiaa8b7fj627j2y67wh02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiagw3ezj61s02ebu0x02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiaivng3j61rv2ebx6p02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiajuixvj62bw340qv502.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaiapyartj62bw340hdt02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaibkwu4ij61vy2iq1kx02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaic1fvkpj61u92eo1ky02.jpg +0 -0
- package/public/images/weibo-update/today/0040jbadgy1igaic3cruhj62822ywb2902.jpg +0 -0
- package/public/manifest.json +25 -0
- package/public/mcp-logo-128.png +0 -0
- package/public/mcp-logo-64.png +0 -0
- package/public/mcp-logo.svg +16 -0
- package/server/agent/agentCore.js +542 -74
- package/server/agent/appPaths.js +1 -0
- package/server/agent/audioPlayer.js +4 -2
- package/server/agent/citations.js +294 -0
- package/server/agent/clarificationAnalytics.js +477 -0
- package/server/agent/clarifyingQuestions.js +1089 -0
- package/server/agent/cli.js +549 -125
- package/server/agent/commandFeatures.js +1 -0
- package/server/agent/commandRegistry.js +946 -879
- package/server/agent/commands.js +546 -272
- package/server/agent/config.js +581 -412
- package/server/agent/contextCompactor.js +2 -2
- package/server/agent/debate.js +235 -0
- package/server/agent/eventBus.js +3 -0
- package/server/agent/feedbackLearning.js +217 -0
- package/server/agent/llmClient.js +512 -394
- package/server/agent/mcpClient.js +182 -32
- package/server/agent/memory.js +283 -224
- package/server/agent/modelManager.js +79 -39
- package/server/agent/profiles.js +178 -0
- package/server/agent/publicTools.js +22 -0
- package/server/agent/queryDetails.js +68 -8
- package/server/agent/quizData.js +398 -384
- package/server/agent/research.js +121 -79
- package/server/agent/sanitizer.js +104 -13
- package/server/agent/session.js +319 -11
- package/server/agent/subagent.js +8 -3
- package/server/agent/suggestionTui.js +294 -0
- package/server/agent/suggestions.js +69 -3
- package/server/agent/telemetry.js +119 -0
- package/server/agent/terminalImage.js +146 -27
- package/server/agent/theme.js +17 -0
- package/server/agent/toolRouter.js +16 -1
- package/server/agent/tools.js +424 -225
- package/server/agent/ui.js +486 -64
- package/server/agent/version.js +2 -4
- package/server/agent/webSearch.js +111 -11
- package/server/agent/youtubeAuth.js +24 -6
- package/server/apiRouter.js +4222 -0
- package/server/mcp/data/discography.js +25 -16
- package/server/mcp/data/gallery.js +308 -20
- package/server/mcp/data/profile.js +74 -44
- package/server/mcp/data/timeline.js +1 -1
- package/server/mcp/data/works.js +51 -17
- package/server/mcp/dataAdapter.js +5 -3
- package/server/mcp/handler.js +5 -11
- package/server/mcp/prompts.js +86 -0
- package/server/mcp/security.js +18 -15
- package/server/mcp/server.js +7 -2
- package/server/mcp/tools.js +66 -2
- package/server/web-chat/core/browserLauncher.js +18 -0
- package/server/web-chat/core/connection.js +194 -0
- package/server/web-chat/core/conversationContext.js +82 -0
- package/server/web-chat/core/directCatalogEngine.js +196 -0
- package/server/web-chat/core/dramaKnowledge.js +142 -0
- package/server/web-chat/core/errorHandler.js +51 -0
- package/server/web-chat/core/followUpEngine.js +182 -0
- package/server/web-chat/core/futureEventGuard.js +80 -0
- package/server/web-chat/core/memorySnapshot.js +12 -0
- package/server/web-chat/core/security.js +146 -0
- package/server/web-chat/core/toolInspector.js +52 -0
- package/server/web-chat/router.js +120 -0
- package/server/web-chat/routes/chatRoute.js +397 -0
- package/server/web-chat/routes/clarifyConfigRoute.js +98 -0
- package/server/web-chat/routes/clarifyRoute.js +83 -0
- package/server/web-chat/routes/compareRoute.js +79 -0
- package/server/web-chat/routes/exportRoute.js +59 -0
- package/server/web-chat/routes/feedbackRoute.js +57 -0
- package/server/web-chat/routes/newsRoute.js +33 -0
- package/server/web-chat/routes/quizRoute.js +11 -0
- package/server/web-chat/routes/staticRoute.js +103 -0
- package/server/web-chat/routes/statusRoute.js +52 -0
- package/server/web-chat/routes/suggestRoute.js +77 -0
- package/server/web-chat/routes/timelineRoute.js +34 -0
- package/server/web-chat/routes/verifyRoute.js +57 -0
- package/server/web-chat/server.js +116 -0
- package/server/web-chat/ui/htmlBuilder.js +91 -0
- package/server/web-chat/ui/previews/agentBubble.preview.html +134 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +320 -0
- package/server/web-chat/ui/scripts/client.js +8397 -0
- package/server/web-chat/ui/scripts/kikuDb.js +177 -0
- package/server/web-chat/ui/scripts/suggestionsData.js +118 -0
- package/server/web-chat/ui/scripts/webchatI18n.js +291 -0
- package/server/web-chat/ui/styles/main.css +8568 -0
- package/server/web-chat/ui/templates/body.html +796 -0
- package/server/webChatHtml.js +1 -0
- package/server/webChatServer.js +6 -0
- package/src/data/discographyData.js +892 -866
- package/src/data/galleryData.js +1805 -561
- package/src/data/profileData.js +79 -2
- package/src/data/socialUpdatesData.js +106 -47
- package/src/data/timelineData.js +1 -1
- package/src/data/worksData.js +25 -4
- package/tokens.css +97 -0
- package/server/agent/updateManager.js +0 -456
package/server/agent/research.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ResearchSubagent } from './subagent.js';
|
|
2
2
|
import { executeTool } from './tools.js';
|
|
3
3
|
import { eventBus, Events } from './eventBus.js';
|
|
4
|
+
import { mapConcurrent } from './suggestions.js';
|
|
4
5
|
|
|
5
6
|
const CATALOG_TOOLS = Object.freeze([
|
|
6
7
|
'global_search', 'get_profile_and_fandom', 'search_works', 'get_work_detail',
|
|
@@ -78,13 +79,30 @@ export class ResearchOrchestrator {
|
|
|
78
79
|
cancel() {
|
|
79
80
|
if (!this.active) return false;
|
|
80
81
|
this.active.controller.abort();
|
|
82
|
+
this.active = null;
|
|
81
83
|
return true;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
async run(question, options = {}) {
|
|
85
87
|
const query = String(question || '').trim();
|
|
86
88
|
if (!query) throw new Error('A research question is required.');
|
|
87
|
-
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if (this.active && this.active.question === query && this.active.promise) {
|
|
92
|
+
try {
|
|
93
|
+
return await this.active.promise;
|
|
94
|
+
} catch (err) {
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if (this.active) {
|
|
101
|
+
try {
|
|
102
|
+
this.active.controller.abort();
|
|
103
|
+
} catch {}
|
|
104
|
+
this.active = null;
|
|
105
|
+
}
|
|
88
106
|
|
|
89
107
|
const controller = new AbortController();
|
|
90
108
|
const abortParent = () => controller.abort(options.signal?.reason);
|
|
@@ -96,6 +114,7 @@ export class ResearchOrchestrator {
|
|
|
96
114
|
startedAt: new Date().toISOString(),
|
|
97
115
|
controller,
|
|
98
116
|
workers: new Map(plan.map(task => [task.id, { id: task.id, label: task.label, state: 'pending', toolCalls: 0 }])),
|
|
117
|
+
promise: null,
|
|
99
118
|
};
|
|
100
119
|
const sharedToolCache = new Map();
|
|
101
120
|
const cachedExecute = async (name, args) => {
|
|
@@ -113,90 +132,113 @@ export class ResearchOrchestrator {
|
|
|
113
132
|
steps: plan.map(p => ({ id: p.id, label: p.label, state: 'pending', objective: p.objective }))
|
|
114
133
|
});
|
|
115
134
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
const isLocalProvider = Boolean(
|
|
136
|
+
this.client?.provider === 'lmstudio' ||
|
|
137
|
+
this.client?.provider === 'ollama' ||
|
|
138
|
+
this.client?.isLocal ||
|
|
139
|
+
this.client?.getProviderInfo?.()?.isLocal
|
|
140
|
+
);
|
|
141
|
+
const concurrency = isLocalProvider ? 1 : Math.min(this.maxWorkers, plan.length);
|
|
142
|
+
|
|
143
|
+
const executeJob = async () => {
|
|
144
|
+
try {
|
|
145
|
+
const settled = await mapConcurrent(plan, concurrency, async task => {
|
|
146
|
+
const status = job.workers.get(task.id);
|
|
147
|
+
status.state = 'running';
|
|
148
|
+
options.onProgress?.({ type: 'worker-start', id: task.id, label: task.label });
|
|
149
|
+
eventBus.emit(Events.AGENT_PLAN_STEP_UPDATE, {
|
|
150
|
+
planId: job.id,
|
|
151
|
+
stepId: task.id,
|
|
152
|
+
state: 'running',
|
|
153
|
+
label: task.label,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const worker = new ResearchSubagent({
|
|
157
|
+
client: this.client,
|
|
158
|
+
executeTool: cachedExecute,
|
|
159
|
+
allowedTools: task.tools,
|
|
160
|
+
maxIterations: this.maxIterations,
|
|
161
|
+
});
|
|
162
|
+
const result = await worker.run(task.objective, {
|
|
163
|
+
signal: controller.signal,
|
|
164
|
+
skillContext: options.skillContext,
|
|
165
|
+
onProgress: event => {
|
|
166
|
+
if (event.type === 'tool') status.toolCalls += 1;
|
|
167
|
+
options.onProgress?.({ ...event, workerId: task.id, label: task.label });
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
status.state = 'complete';
|
|
171
|
+
options.onProgress?.({ type: 'worker-complete', id: task.id, label: task.label });
|
|
172
|
+
eventBus.emit(Events.AGENT_PLAN_STEP_UPDATE, {
|
|
173
|
+
planId: job.id,
|
|
174
|
+
stepId: task.id,
|
|
175
|
+
state: 'complete',
|
|
176
|
+
label: task.label,
|
|
177
|
+
});
|
|
178
|
+
return { id: task.id, label: task.label, ...result };
|
|
126
179
|
});
|
|
127
180
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
181
|
+
const reports = settled.map((entry, index) => {
|
|
182
|
+
if (entry.status === 'fulfilled') return entry.value;
|
|
183
|
+
const task = plan[index];
|
|
184
|
+
const status = job.workers.get(task.id);
|
|
185
|
+
status.state = controller.signal.aborted ? 'cancelled' : 'failed';
|
|
186
|
+
status.error = entry.reason?.message || 'Unknown worker error';
|
|
187
|
+
return { id: task.id, label: task.label, answer: '', error: status.error, toolCalls: [] };
|
|
133
188
|
});
|
|
134
|
-
|
|
189
|
+
if (controller.signal.aborted) throw controller.signal.reason || new DOMException('Aborted', 'AbortError');
|
|
190
|
+
|
|
191
|
+
const usable = reports.filter(report => report.answer);
|
|
192
|
+
if (usable.length === 0) {
|
|
193
|
+
const { webExtract } = await import('./webSearch.js');
|
|
194
|
+
const fallbackRes = await webExtract(query);
|
|
195
|
+
if (fallbackRes?.text) {
|
|
196
|
+
usable.push({ label: 'Live Web Intelligence', answer: fallbackRes.text, sources: fallbackRes.sources || [] });
|
|
197
|
+
} else {
|
|
198
|
+
throw new Error('All research workers failed and no web intelligence was returned.');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const synthesisPrompt = options.mode === 'verify'
|
|
202
|
+
? 'Return a clear verdict: VERIFIED, UNVERIFIED, CONFLICTING, or FALSE. Explain briefly and cite only URLs present in the reports.'
|
|
203
|
+
: 'Synthesize a comprehensive, detailed, and polished answer with verified facts. Cite only URLs present in the reports.';
|
|
204
|
+
const synthesis = await this.client.chat({
|
|
205
|
+
messages: [
|
|
206
|
+
{ role: 'system', content: `You are the Kiku research editor. ${synthesisPrompt} Never invent a source, date, title, or URL. Match the language of the question. Do not reveal hidden reasoning.` },
|
|
207
|
+
{ role: 'user', content: JSON.stringify({ question: query, reports: usable.map(({ label, answer }) => ({ label, answer })) }) },
|
|
208
|
+
],
|
|
209
|
+
temperature: 0.1,
|
|
210
|
+
stream: false,
|
|
135
211
|
signal: controller.signal,
|
|
136
|
-
skillContext: options.skillContext,
|
|
137
|
-
onProgress: event => {
|
|
138
|
-
if (event.type === 'tool') status.toolCalls += 1;
|
|
139
|
-
options.onProgress?.({ ...event, workerId: task.id, label: task.label });
|
|
140
|
-
},
|
|
141
212
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
213
|
+
const answer = synthesis?.content || usable.map(report => `### ${report.label}\n${report.answer}`).join('\n\n');
|
|
214
|
+
const sources = uniqueUrls(usable.map(report => report.answer).join('\n'));
|
|
215
|
+
const result = {
|
|
216
|
+
id: job.id,
|
|
217
|
+
mode: options.mode || 'research',
|
|
218
|
+
question: query,
|
|
219
|
+
answer,
|
|
220
|
+
sources,
|
|
221
|
+
reports,
|
|
222
|
+
startedAt: job.startedAt,
|
|
223
|
+
completedAt: new Date().toISOString(),
|
|
224
|
+
};
|
|
225
|
+
this.lastResult = result;
|
|
226
|
+
options.onProgress?.({ type: 'complete', id: job.id, sources: sources.length });
|
|
227
|
+
eventBus.emit(Events.AGENT_PLAN_COMPLETE, {
|
|
145
228
|
planId: job.id,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
label: task.label,
|
|
229
|
+
sourcesCount: sources.length,
|
|
230
|
+
reportsCount: reports.length,
|
|
149
231
|
});
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (controller.signal.aborted) throw controller.signal.reason || new DOMException('Aborted', 'AbortError');
|
|
162
|
-
|
|
163
|
-
const usable = reports.filter(report => report.answer);
|
|
164
|
-
if (usable.length === 0) throw new Error('All research workers failed.');
|
|
165
|
-
const synthesisPrompt = options.mode === 'verify'
|
|
166
|
-
? 'Return a clear verdict: VERIFIED, UNVERIFIED, CONFLICTING, or FALSE. Explain briefly and cite only URLs present in the reports.'
|
|
167
|
-
: 'Synthesize a direct, polished answer. Separate verified facts, conflicts, and missing evidence. Cite only URLs present in the reports.';
|
|
168
|
-
const synthesis = await this.client.chat({
|
|
169
|
-
messages: [
|
|
170
|
-
{ role: 'system', content: `You are the Kiku research editor. ${synthesisPrompt} Never invent a source, date, title, or URL. Match the language of the question. Do not reveal hidden reasoning.` },
|
|
171
|
-
{ role: 'user', content: JSON.stringify({ question: query, reports: usable.map(({ label, answer }) => ({ label, answer })) }) },
|
|
172
|
-
],
|
|
173
|
-
temperature: 0.1,
|
|
174
|
-
stream: false,
|
|
175
|
-
signal: controller.signal,
|
|
176
|
-
});
|
|
177
|
-
const answer = synthesis?.content || usable.map(report => `### ${report.label}\n${report.answer}`).join('\n\n');
|
|
178
|
-
const sources = uniqueUrls(usable.map(report => report.answer).join('\n'));
|
|
179
|
-
const result = {
|
|
180
|
-
id: job.id,
|
|
181
|
-
mode: options.mode || 'research',
|
|
182
|
-
question: query,
|
|
183
|
-
answer,
|
|
184
|
-
sources,
|
|
185
|
-
reports,
|
|
186
|
-
startedAt: job.startedAt,
|
|
187
|
-
completedAt: new Date().toISOString(),
|
|
188
|
-
};
|
|
189
|
-
this.lastResult = result;
|
|
190
|
-
options.onProgress?.({ type: 'complete', id: job.id, sources: sources.length });
|
|
191
|
-
eventBus.emit(Events.AGENT_PLAN_COMPLETE, {
|
|
192
|
-
planId: job.id,
|
|
193
|
-
sourcesCount: sources.length,
|
|
194
|
-
reportsCount: reports.length,
|
|
195
|
-
});
|
|
196
|
-
return result;
|
|
197
|
-
} finally {
|
|
198
|
-
options.signal?.removeEventListener('abort', abortParent);
|
|
199
|
-
this.active = null;
|
|
200
|
-
}
|
|
232
|
+
return result;
|
|
233
|
+
} finally {
|
|
234
|
+
options.signal?.removeEventListener('abort', abortParent);
|
|
235
|
+
if (this.active?.id === job.id) {
|
|
236
|
+
this.active = null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
job.promise = executeJob();
|
|
242
|
+
return job.promise;
|
|
201
243
|
}
|
|
202
244
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { lookup as dnsLookup } from 'node:dns/promises';
|
|
4
3
|
|
|
5
4
|
const MAX_QUERY_LENGTH = 32768;
|
|
6
5
|
const MAX_PATH_LENGTH = 2048;
|
|
@@ -66,10 +65,24 @@ const BLOCKED_HOST_PATTERNS = [
|
|
|
66
65
|
const DANGEROUS_EXTENSIONS = new Set([
|
|
67
66
|
'.sh', '.bash', '.zsh', '.ps1', '.cmd', '.bat', '.exe', '.msi',
|
|
68
67
|
'.com', '.scr', '.pif', '.vbs', '.vbe', '.js', '.jse', '.wsf',
|
|
69
|
-
'.wsh', '.reg',
|
|
68
|
+
'.wsh', '.reg', '.pem', '.key', '.p12', '.pfx', '.der',
|
|
70
69
|
]);
|
|
71
70
|
|
|
72
71
|
|
|
72
|
+
const SENSITIVE_FILENAMES = [
|
|
73
|
+
/(^|[\\/])\.env(\.|$)/i,
|
|
74
|
+
/(^|[\\/])cookies\.txt$/i,
|
|
75
|
+
/(^|[\\/])\.(git|npmrc|netrc|ssh)([\\/]|$)/i,
|
|
76
|
+
/(^|[\\/])\.kiku_sessions([\\/]|$)/i,
|
|
77
|
+
/\.pem$/i,
|
|
78
|
+
/\.key$/i,
|
|
79
|
+
/\.p12$/i,
|
|
80
|
+
/\.pfx$/i,
|
|
81
|
+
/secret/i,
|
|
82
|
+
/credential/i,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
|
|
73
86
|
export function stripAnsi(str) {
|
|
74
87
|
if (typeof str !== 'string') return '';
|
|
75
88
|
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
|
|
@@ -138,6 +151,12 @@ export function sanitizeFilePath(filePath, projectRoot = process.cwd(), { blockD
|
|
|
138
151
|
return { safe: false, resolvedPath: resolved, reason: 'Path escapes project root (traversal detected)' };
|
|
139
152
|
}
|
|
140
153
|
|
|
154
|
+
for (const pattern of SENSITIVE_FILENAMES) {
|
|
155
|
+
if (pattern.test(resolved)) {
|
|
156
|
+
return { safe: false, resolvedPath: resolved, reason: 'Sensitive file path blocked (secrets/dotfiles)' };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
141
160
|
if (blockDangerousExtensions) {
|
|
142
161
|
const normalizedPath = resolved.replace(/[ .]+$/g, '');
|
|
143
162
|
const extension = path.extname(normalizedPath).toLowerCase();
|
|
@@ -150,6 +169,40 @@ export function sanitizeFilePath(filePath, projectRoot = process.cwd(), { blockD
|
|
|
150
169
|
}
|
|
151
170
|
|
|
152
171
|
|
|
172
|
+
function isPrivateIPv4(ip) {
|
|
173
|
+
const parts = ip.split('.').map(Number);
|
|
174
|
+
if (parts.length !== 4) return false;
|
|
175
|
+
const [a, b] = parts;
|
|
176
|
+
if (a === 10) return true;
|
|
177
|
+
if (a === 127) return true;
|
|
178
|
+
if (a === 0) return true;
|
|
179
|
+
if (a === 169 && b === 254) return true;
|
|
180
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
181
|
+
if (a === 192 && b === 168) return true;
|
|
182
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
183
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
184
|
+
if (a >= 224) return true;
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isPrivateIPv6(ip) {
|
|
189
|
+
const normalized = ip.toLowerCase();
|
|
190
|
+
if (normalized === '::' || normalized === '::1') return true;
|
|
191
|
+
if (normalized.startsWith('fc') || normalized.startsWith('fd')) return true;
|
|
192
|
+
if (normalized.startsWith('fe80') || normalized.startsWith('fe8') || normalized.startsWith('fe9') || normalized.startsWith('fea') || normalized.startsWith('feb')) return true;
|
|
193
|
+
if (normalized.startsWith('::ffff:')) {
|
|
194
|
+
const v4 = normalized.slice(7);
|
|
195
|
+
return isPrivateIPv4(v4);
|
|
196
|
+
}
|
|
197
|
+
if (normalized.startsWith('2001:db8:')) return true;
|
|
198
|
+
if (normalized.startsWith('::')) return true;
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function isIpAddress(hostname) {
|
|
203
|
+
return /^\d+\.\d+\.\d+\.\d+$/.test(hostname) || /^[0-9a-f:]+$/i.test(hostname);
|
|
204
|
+
}
|
|
205
|
+
|
|
153
206
|
export function sanitizeUrl(urlStr) {
|
|
154
207
|
if (typeof urlStr !== 'string' || !urlStr.trim()) {
|
|
155
208
|
return { safe: false, reason: 'URL is empty or not a string' };
|
|
@@ -173,6 +226,10 @@ export function sanitizeUrl(urlStr) {
|
|
|
173
226
|
return { safe: false, reason: `Unsafe protocol: ${url.protocol} (only http/https allowed)` };
|
|
174
227
|
}
|
|
175
228
|
|
|
229
|
+
if (url.username || url.password) {
|
|
230
|
+
return { safe: false, reason: 'Credentials embedded in URLs are not allowed' };
|
|
231
|
+
}
|
|
232
|
+
|
|
176
233
|
|
|
177
234
|
const hostname = url.hostname.replace(/^\[|\]$/g, '').replace(/\.$/, '').toLowerCase();
|
|
178
235
|
for (const pattern of BLOCKED_HOST_PATTERNS) {
|
|
@@ -181,7 +238,36 @@ export function sanitizeUrl(urlStr) {
|
|
|
181
238
|
}
|
|
182
239
|
}
|
|
183
240
|
|
|
184
|
-
|
|
241
|
+
if (!isIpAddress(hostname)) {
|
|
242
|
+
url.__pendingDnsCheck = true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { safe: true, url, hostname };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
export async function assertSafeUrlHostname(hostname) {
|
|
250
|
+
if (!hostname || typeof hostname !== 'string') return { safe: false, reason: 'Missing hostname' };
|
|
251
|
+
const clean = hostname.replace(/^\[|\]$/g, '').replace(/\.$/, '').toLowerCase();
|
|
252
|
+
|
|
253
|
+
if (isIpAddress(clean)) {
|
|
254
|
+
if (isPrivateIPv4(clean) || isPrivateIPv6(clean)) {
|
|
255
|
+
return { safe: false, reason: `Resolved host is private/internal: ${clean}` };
|
|
256
|
+
}
|
|
257
|
+
return { safe: true };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const addresses = await dnsLookup(clean, { all: true });
|
|
262
|
+
for (const { address } of addresses) {
|
|
263
|
+
if (isPrivateIPv4(address) || isPrivateIPv6(address)) {
|
|
264
|
+
return { safe: false, reason: `Host resolved to private/internal IP: ${address}` };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { safe: true };
|
|
268
|
+
} catch {
|
|
269
|
+
return { safe: false, reason: 'Unable to resolve hostname for SSRF check' };
|
|
270
|
+
}
|
|
185
271
|
}
|
|
186
272
|
|
|
187
273
|
|
|
@@ -244,7 +330,7 @@ export function sanitizeToolArgs(toolName, args) {
|
|
|
244
330
|
}
|
|
245
331
|
|
|
246
332
|
case 'fetch_api': {
|
|
247
|
-
|
|
333
|
+
|
|
248
334
|
const endpoint = sanitized.endpoint;
|
|
249
335
|
if (endpoint && typeof endpoint === 'string') {
|
|
250
336
|
const clean = stripAnsi(endpoint).trim();
|
|
@@ -253,6 +339,14 @@ export function sanitizeToolArgs(toolName, args) {
|
|
|
253
339
|
}
|
|
254
340
|
sanitized.endpoint = clean;
|
|
255
341
|
}
|
|
342
|
+
|
|
343
|
+
const baseUrl = sanitized.baseUrl;
|
|
344
|
+
if (baseUrl) {
|
|
345
|
+
const check = sanitizeUrl(baseUrl);
|
|
346
|
+
if (!check.safe) {
|
|
347
|
+
return { safe: false, sanitized: {}, warnings: [`API base URL rejected: ${check.reason}`] };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
256
350
|
break;
|
|
257
351
|
}
|
|
258
352
|
|
|
@@ -290,18 +384,15 @@ export function sanitizeResponse(response) {
|
|
|
290
384
|
|
|
291
385
|
|
|
292
386
|
const leakPatterns = [
|
|
293
|
-
/system\s*prompt\s*[:=]/
|
|
294
|
-
/you\s*are\s*(actually|really)\s+(a|an)\s+/
|
|
295
|
-
/my\s*instructions\s*(are|say|tell)/
|
|
296
|
-
/i\s*was\s*(told|instructed|programmed)\s+to/
|
|
387
|
+
/system\s*prompt\s*[:=]/gi,
|
|
388
|
+
/you\s*are\s*(actually|really)\s+(a|an)\s+/gi,
|
|
389
|
+
/my\s*instructions\s*(are|say|tell)/gi,
|
|
390
|
+
/i\s*was\s*(told|instructed|programmed)\s+to/gi,
|
|
297
391
|
];
|
|
298
392
|
|
|
299
393
|
let sanitized = response;
|
|
300
394
|
for (const pattern of leakPatterns) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
395
|
+
sanitized = sanitized.replace(pattern, '[system content]');
|
|
305
396
|
}
|
|
306
397
|
|
|
307
398
|
return sanitized;
|