kiku-agent 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -12
- package/package.json +5 -12
- package/server/agent/agentCore.js +319 -46
- package/server/agent/appPaths.js +31 -0
- package/server/agent/audioPlayer.js +339 -12
- package/server/agent/autoContext.js +193 -0
- package/server/agent/catalogEntityResolver.js +170 -28
- package/server/agent/clarifyingQuestions.js +137 -35
- package/server/agent/cli.js +177 -48
- package/server/agent/cloudAdapters.js +116 -0
- package/server/agent/commandRegistry.js +409 -80
- package/server/agent/commands.js +1056 -125
- package/server/agent/config.js +1088 -690
- package/server/agent/contextCompactor.js +213 -21
- package/server/agent/cpuMetrics.js +199 -0
- package/server/agent/engineService.js +591 -0
- package/server/agent/eval/AGENTS.md +228 -0
- package/server/agent/eval/analyze.py +684 -0
- package/server/agent/eval/gates.json +15 -0
- package/server/agent/eval/modules/analysis/compare.js +198 -0
- package/server/agent/eval/modules/analysis/gates.js +72 -0
- package/server/agent/eval/modules/analysis/judge.js +70 -0
- package/server/agent/eval/modules/core/cli.js +453 -0
- package/server/agent/eval/modules/core/config.js +60 -0
- package/server/agent/eval/modules/core/store.js +236 -0
- package/server/agent/eval/modules/evaluation/cases.js +689 -0
- package/server/agent/eval/modules/evaluation/runner.js +288 -0
- package/server/agent/eval/modules/evaluation/scoring.js +363 -0
- package/server/agent/eval/modules/evaluation/worker.js +122 -0
- package/server/agent/eval/modules/reporting/benchmark.js +265 -0
- package/server/agent/eval/modules/reporting/benchmarkPng.js +487 -0
- package/server/agent/eval/modules/reporting/dashboard.js +1406 -0
- package/server/agent/eval/modules/server/evalApi.js +1099 -0
- package/server/agent/eval/modules/ui/evalUi.js +4799 -0
- package/server/agent/eval/modules/utils/smokes.js +119 -0
- package/server/agent/eventBus.js +1 -0
- package/server/agent/ink/main.jsx +196 -191
- package/server/agent/jobManager.js +11 -1
- package/server/agent/lanDiscovery.js +721 -23
- package/server/agent/llmClient.js +317 -71
- package/server/agent/lmStudioWakeup.js +517 -65
- package/server/agent/locationService.js +797 -0
- package/server/agent/mcpQuizData.js +509 -0
- package/server/agent/memory/contextAllocator.js +19 -7
- package/server/agent/memory.js +3 -1
- package/server/agent/modelCompatibility.js +352 -0
- package/server/agent/modelDefaults.js +1 -0
- package/server/agent/modelManager.js +46 -2
- package/server/agent/newsWatch.js +9 -3
- package/server/agent/processManager.js +229 -0
- package/server/agent/prompts.js +174 -8
- package/server/agent/routingInterceptor.js +9 -1
- package/server/agent/sanitizer.js +41 -6
- package/server/agent/searchQueryPlanner.js +202 -0
- package/server/agent/session.js +59 -4
- package/server/agent/tests/agentBubbleSystem.test.js +65 -0
- package/server/agent/tests/agentFollowUps.test.js +253 -0
- package/server/agent/tests/agentMusicControl.test.js +245 -0
- package/server/agent/tests/apiRouter.test.js +108 -0
- package/server/agent/tests/architectureFixes.test.js +81 -0
- package/server/agent/tests/architectureRefactoring.test.js +194 -0
- package/server/agent/tests/autoContext.test.js +176 -0
- package/server/agent/tests/autoResearch.test.js +64 -0
- package/server/agent/tests/bilibiliEmbed.test.js +296 -0
- package/server/agent/tests/catalogSync.test.js +130 -0
- package/server/agent/tests/chatHardening.test.js +121 -0
- package/server/agent/tests/chineseWebSearchEscalation.test.js +62 -0
- package/server/agent/tests/citations.test.js +180 -0
- package/server/agent/tests/clarificationAnalytics.test.js +219 -0
- package/server/agent/tests/clarifyingQuestions.test.js +627 -0
- package/server/agent/tests/cliAutomation.test.js +158 -0
- package/server/agent/tests/cloudProviders.test.js +322 -0
- package/server/agent/tests/commandInput.test.js +24 -0
- package/server/agent/tests/commands.test.js +506 -0
- package/server/agent/tests/contextIntelligence.test.js +209 -0
- package/server/agent/tests/conversationSafety.test.js +53 -0
- package/server/agent/tests/cpuMetrics.test.js +101 -0
- package/server/agent/tests/decisionAndSearchEnhancements.test.js +167 -0
- package/server/agent/tests/dramaAnswerQuality.test.js +73 -0
- package/server/agent/tests/dynamicCatalogIngestion.test.js +285 -0
- package/server/agent/tests/dynamicRoutingAndSearch.test.js +92 -0
- package/server/agent/tests/edgeCasesScenarios.test.js +131 -0
- package/server/agent/tests/engineService.test.js +436 -0
- package/server/agent/tests/engineUnload.test.js +94 -0
- package/server/agent/tests/entertainmentMetrics.test.js +126 -0
- package/server/agent/tests/envBootstrap.test.js +87 -0
- package/server/agent/tests/envCommand.test.js +284 -0
- package/server/agent/tests/errorHandling.test.js +80 -0
- package/server/agent/tests/errors.test.js +13 -0
- package/server/agent/tests/evalAnalyze.test.js +54 -0
- package/server/agent/tests/evalCommand.test.js +54 -0
- package/server/agent/tests/evalCorpus.test.js +126 -0
- package/server/agent/tests/evalHarness.test.js +62 -0
- package/server/agent/tests/evalJudgeContext.test.js +38 -0
- package/server/agent/tests/evalMetrics.test.js +320 -0
- package/server/agent/tests/evalStore.test.js +190 -0
- package/server/agent/tests/evalStudio.test.js +179 -0
- package/server/agent/tests/feedbackLearning.test.js +186 -0
- package/server/agent/tests/fetchApi.test.js +99 -0
- package/server/agent/tests/firstRun.test.js +44 -0
- package/server/agent/tests/gemma4eOptimization.test.js +95 -0
- package/server/agent/tests/hardwareTiers.test.js +206 -0
- package/server/agent/tests/hybridWebSearch.test.js +194 -0
- package/server/agent/tests/judgeService.test.js +251 -0
- package/server/agent/tests/lanDiscovery.test.js +324 -0
- package/server/agent/tests/lanInterfaces.test.js +510 -0
- package/server/agent/tests/lmStudioWakeup.test.js +278 -0
- package/server/agent/tests/locationTool.test.js +278 -0
- package/server/agent/tests/mcpClient.test.js +201 -0
- package/server/agent/tests/memoryRoute.test.js +173 -0
- package/server/agent/tests/memoryUiNormalization.test.js +75 -0
- package/server/agent/tests/modelCompatibility.test.js +148 -0
- package/server/agent/tests/modelManager.test.js +141 -0
- package/server/agent/tests/multilingualScenarios.test.js +69 -0
- package/server/agent/tests/musicControlUiBridge.test.js +113 -0
- package/server/agent/tests/newCommands.test.js +347 -0
- package/server/agent/tests/newCommandsTui.test.js +141 -0
- package/server/agent/tests/newTools.test.js +113 -0
- package/server/agent/tests/newsIntelligence.test.js +178 -0
- package/server/agent/tests/nextGenArchitecture.test.js +101 -0
- package/server/agent/tests/offlineInput.test.js +217 -0
- package/server/agent/tests/openDomainPrompt.test.js +197 -0
- package/server/agent/tests/openDomainSearch.test.js +121 -0
- package/server/agent/tests/packagingGuard.test.js +57 -0
- package/server/agent/tests/presentation.test.js +61 -0
- package/server/agent/tests/processManager.test.js +144 -0
- package/server/agent/tests/providerPersistence.test.js +44 -0
- package/server/agent/tests/queryBenchmarks.test.js +83 -0
- package/server/agent/tests/queryDetails.test.js +126 -0
- package/server/agent/tests/quickActionsAndModals.test.js +180 -0
- package/server/agent/tests/realWorldValidation.test.js +391 -0
- package/server/agent/tests/resilienceAndConcurrency.test.js +117 -0
- package/server/agent/tests/runtimeWatchdog.test.js +32 -0
- package/server/agent/tests/sanitizer.test.js +37 -0
- package/server/agent/tests/scenariosData.js +378 -0
- package/server/agent/tests/schemaRouting.test.js +54 -0
- package/server/agent/tests/searchQueryPlanner.test.js +108 -0
- package/server/agent/tests/securityAuditRemediation.test.js +221 -0
- package/server/agent/tests/securityAuditRemediations2.test.js +154 -0
- package/server/agent/tests/securityHardening.test.js +146 -0
- package/server/agent/tests/securitySanitizer.test.js +38 -0
- package/server/agent/tests/sessionAndCopy.test.js +267 -0
- package/server/agent/tests/sidebarControls.test.js +135 -0
- package/server/agent/tests/statefulMemory.test.js +277 -0
- package/server/agent/tests/subagent.test.js +216 -0
- package/server/agent/tests/subagentProgress.test.js +109 -0
- package/server/agent/tests/subagentTextFallback.test.js +115 -0
- package/server/agent/tests/suggestions.test.js +199 -0
- package/server/agent/tests/suggestionsTui.test.js +251 -0
- package/server/agent/tests/telemetryAndProfiles.test.js +179 -0
- package/server/agent/tests/terminalImage.test.js +80 -0
- package/server/agent/tests/terminalMarkdown.test.js +166 -0
- package/server/agent/tests/terminalText.test.js +133 -0
- package/server/agent/tests/textProtocolLoop.test.js +197 -0
- package/server/agent/tests/textToolParsing.test.js +187 -0
- package/server/agent/tests/theme.test.js +26 -0
- package/server/agent/tests/toolRegistryParity.test.js +53 -0
- package/server/agent/tests/toolRoutingRegressions.test.js +160 -0
- package/server/agent/tests/tools.test.js +259 -0
- package/server/agent/tests/unifiedEndpoints.test.js +159 -0
- package/server/agent/tests/v110Features.test.js +262 -0
- package/server/agent/tests/versionBump.test.js +69 -0
- package/server/agent/tests/vramEstimator.test.js +595 -0
- package/server/agent/tests/webChatDocs.test.js +199 -0
- package/server/agent/tests/webChatInferenceControl.test.js +298 -0
- package/server/agent/tests/webChatInspection.test.js +551 -0
- package/server/agent/tests/webChatMcp.test.js +94 -0
- package/server/agent/tests/webChatMemory.test.js +112 -0
- package/server/agent/tests/webChatSecurity.test.js +169 -0
- package/server/agent/tests/webExtract.test.js +284 -0
- package/server/agent/tests/webSources.test.js +281 -0
- package/server/agent/textToolProtocol.js +173 -46
- package/server/agent/toolRouter.js +318 -44
- package/server/agent/tools.js +877 -96
- package/server/agent/ui.js +39 -34
- package/server/agent/vramService.js +35 -54
- package/server/agent/webSearch.js +441 -162
- package/server/agent/websearch/orchestrator.js +15 -5
- package/server/agent/websearch/resilience/cache.js +9 -2
- package/server/agent/websearch/security/ssrf.js +41 -10
- package/server/agent/youtubeAuth.js +5 -20
- package/server/apiRouter.js +5558 -4422
- package/server/eval/judgeRubrics.js +110 -0
- package/server/eval/judgeService.js +290 -0
- package/server/eval/runBenchmark.js +182 -0
- package/server/mcp/data/discography.js +2 -2
- package/server/mcp/examples/antigravity.example.json +17 -0
- package/server/mcp/examples/librechat.example.yaml +16 -0
- package/server/mcp/handler.js +8 -4
- package/server/mcp/security.js +220 -21
- package/server/mcp/server.js +2 -1
- package/server/mcp/tests/connection.test.js +162 -0
- package/server/mcp/tests/mcpKeyAuth.test.js +156 -0
- package/server/mcp/tools.js +7 -1
- package/server/supabase_migration_prefix.sql +102 -0
- package/server/supabase_schema.sql +63 -0
- package/server/web-chat/core/connection.js +179 -218
- package/server/web-chat/core/conversationContext.js +9 -2
- package/server/web-chat/core/directCatalogEngine.js +52 -28
- package/server/web-chat/core/dramaKnowledge.js +33 -22
- package/server/web-chat/core/followUpEngine.js +735 -81
- package/server/web-chat/core/futureEventGuard.js +5 -1
- package/server/web-chat/core/musicControlAction.js +70 -0
- package/server/web-chat/core/security.js +32 -21
- package/server/web-chat/core/toolInspector.js +6 -6
- package/server/web-chat/core/urlGuard.js +45 -0
- package/server/web-chat/router.js +204 -157
- package/server/web-chat/routes/chatRoute.js +885 -488
- package/server/web-chat/routes/engineRoute.js +270 -0
- package/server/web-chat/routes/exportRoute.js +1 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +17 -2
- package/server/web-chat/routes/lmStudioLoadRoute.js +172 -0
- package/server/web-chat/routes/mcpRoute.js +386 -0
- package/server/web-chat/routes/memoryRoute.js +253 -0
- package/server/web-chat/routes/providerRoute.js +49 -16
- package/server/web-chat/routes/staticRoute.js +306 -17
- package/server/web-chat/routes/statusRoute.js +194 -73
- package/server/web-chat/routes/suggestRoute.js +11 -3
- package/server/web-chat/routes/verifyRoute.js +26 -4
- package/server/web-chat/server.js +7 -6
- package/server/web-chat/ui/docsBuilder.js +3558 -0
- package/server/web-chat/ui/htmlBuilder.js +12 -0
- package/server/web-chat/ui/scripts/client.js +3401 -1302
- package/server/web-chat/ui/scripts/webchatI18n.js +155 -153
- package/server/web-chat/ui/settingsBuilder.js +7870 -0
- package/server/web-chat/ui/styles/lan-card.css +165 -0
- package/server/web-chat/ui/styles/main.css +4289 -447
- package/server/web-chat/ui/sw.js +64 -0
- package/server/web-chat/ui/templates/body.html +407 -281
- package/src/data/awardsData.js +146 -146
- package/src/data/musicVideoData.js +6 -4
- package/src/lib/vram/estimator.js +195 -19
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { COMMAND_NAMES } from '../commandRegistry.js';
|
|
5
|
+
import { handlers, routeCommand } from '../commands.js';
|
|
6
|
+
import { getThemeName } from '../theme.js';
|
|
7
|
+
|
|
8
|
+
const HIDDEN_LEGACY_HANDLERS = [];
|
|
9
|
+
|
|
10
|
+
test('every canonical slash command has exactly one handler', () => {
|
|
11
|
+
assert.equal(new Set(COMMAND_NAMES).size, COMMAND_NAMES.length);
|
|
12
|
+
for (const name of COMMAND_NAMES) {
|
|
13
|
+
assert.equal(typeof handlers[name], 'function', `${name} must have a handler`);
|
|
14
|
+
}
|
|
15
|
+
const hidden = Object.keys(handlers).filter(name => !COMMAND_NAMES.includes(name)).sort();
|
|
16
|
+
assert.deepEqual(hidden, HIDDEN_LEGACY_HANDLERS, 'hidden legacy handlers must stay minimal');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('/ads receives the router context and displays one sponsor card', async () => {
|
|
20
|
+
let calls = 0;
|
|
21
|
+
const result = await routeCommand('/ads', {
|
|
22
|
+
printAd() { calls += 1; },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
assert.equal(result, 'HANDLED');
|
|
26
|
+
assert.equal(calls, 1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('/gallery displays an image through the managed TUI callback and aliases work', async () => {
|
|
30
|
+
let calls = 0;
|
|
31
|
+
const ctx = {
|
|
32
|
+
showTerminalImage() { calls += 1; return true; },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
assert.equal(await routeCommand('/gallery', ctx), 'HANDLED');
|
|
36
|
+
assert.equal(await routeCommand('/image', ctx), 'HANDLED');
|
|
37
|
+
assert.equal(await routeCommand('/photo', ctx), 'HANDLED');
|
|
38
|
+
assert.equal(calls, 3);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('/quiz retries the current question after a wrong managed-TUI answer', async () => {
|
|
42
|
+
const originalFetch = global.fetch;
|
|
43
|
+
const originalRandom = Math.random;
|
|
44
|
+
const originalLog = console.log;
|
|
45
|
+
const prompts = [];
|
|
46
|
+
const output = [];
|
|
47
|
+
const preferences = {};
|
|
48
|
+
let claimBody = null;
|
|
49
|
+
|
|
50
|
+
global.fetch = async (_url, options = {}) => {
|
|
51
|
+
if (!options.method) {
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
json: async () => ({ data: { challengeToken: 'kiku_chal_test' } }),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
claimBody = JSON.parse(options.body);
|
|
58
|
+
return {
|
|
59
|
+
json: async () => ({ status: 'success', data: { apiKey: 'kiku_vip_sk_server_test' } }),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
Math.random = () => 0;
|
|
63
|
+
console.log = (...args) => output.push(args.join(' '));
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const result = await routeCommand('/quiz lang en', {
|
|
67
|
+
sub: 'lang',
|
|
68
|
+
extra: 'en',
|
|
69
|
+
readInput: async options => {
|
|
70
|
+
prompts.push(options);
|
|
71
|
+
return prompts.length === 1 ? 'a' : 'd';
|
|
72
|
+
},
|
|
73
|
+
agent: {
|
|
74
|
+
memory: {
|
|
75
|
+
preferences,
|
|
76
|
+
setPreference(key, value) { preferences[key] = value; },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
assert.equal(result, 'HANDLED');
|
|
82
|
+
assert.equal(prompts.length, 16);
|
|
83
|
+
assert.ok(prompts.every(prompt => prompt.prompt === 'Answer ❯ '));
|
|
84
|
+
assert.ok(prompts.every(prompt => prompt.history === false && prompt.cancelValue === 'q'));
|
|
85
|
+
assert.match(output.join('\n'), /Incorrect, try again!/);
|
|
86
|
+
assert.match(output.join('\n'), /Perfect Score!/);
|
|
87
|
+
assert.equal(preferences.vipKey, 'kiku_vip_sk_server_test');
|
|
88
|
+
assert.equal(claimBody.challengeToken, 'kiku_chal_test');
|
|
89
|
+
assert.equal(claimBody.answers.length, 15);
|
|
90
|
+
assert.ok(claimBody.answers.every(answer => answer.answerIndex === 0));
|
|
91
|
+
} finally {
|
|
92
|
+
global.fetch = originalFetch;
|
|
93
|
+
Math.random = originalRandom;
|
|
94
|
+
console.log = originalLog;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('/quiz can be cancelled through the managed TUI input row', async () => {
|
|
99
|
+
const originalFetch = global.fetch;
|
|
100
|
+
const originalLog = console.log;
|
|
101
|
+
const output = [];
|
|
102
|
+
|
|
103
|
+
global.fetch = async () => ({ ok: false });
|
|
104
|
+
console.log = (...args) => output.push(args.join(' '));
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = await routeCommand('/quiz lang en', {
|
|
108
|
+
sub: 'lang',
|
|
109
|
+
extra: 'en',
|
|
110
|
+
readInput: async () => 'q',
|
|
111
|
+
agent: { memory: { preferences: {} } },
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
assert.equal(result, 'HANDLED');
|
|
115
|
+
assert.match(output.join('\n'), /Quiz cancelled/);
|
|
116
|
+
} finally {
|
|
117
|
+
global.fetch = originalFetch;
|
|
118
|
+
console.log = originalLog;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('/skill list and learn commands update user skills', async () => {
|
|
123
|
+
const skills = [];
|
|
124
|
+
const preferences = { userSkills: skills, skillsEnabled: true };
|
|
125
|
+
const mockMemory = {
|
|
126
|
+
preferences,
|
|
127
|
+
addUserSkill(instruction) {
|
|
128
|
+
const skill = { id: 's_test', instruction, createdAt: Date.now() };
|
|
129
|
+
skills.push(skill);
|
|
130
|
+
return skill;
|
|
131
|
+
},
|
|
132
|
+
removeUserSkill(id) {
|
|
133
|
+
const idx = skills.findIndex(s => s.id === id);
|
|
134
|
+
if (idx !== -1) { skills.splice(idx, 1); return true; }
|
|
135
|
+
return false;
|
|
136
|
+
},
|
|
137
|
+
clearUserSkills() { skills.length = 0; },
|
|
138
|
+
setPreference(key, val) { preferences[key] = val; }
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const agent = { memory: mockMemory };
|
|
142
|
+
assert.equal(await routeCommand('/skill list', { agent }), 'HANDLED');
|
|
143
|
+
assert.equal(await routeCommand('/skill learn always be kind', { agent, sub: 'learn', extra: 'always be kind' }), 'HANDLED');
|
|
144
|
+
assert.equal(skills.length, 1);
|
|
145
|
+
assert.equal(await routeCommand('/skill off', { agent, sub: 'off' }), 'HANDLED');
|
|
146
|
+
assert.equal(preferences.skillsEnabled, false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('/subagent status returns gracefully when no previous run', async () => {
|
|
150
|
+
const agent = {};
|
|
151
|
+
assert.equal(await routeCommand('/subagent status', { agent, sub: 'status' }), 'HANDLED');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('/research status and sources return gracefully', async () => {
|
|
155
|
+
const agent = {
|
|
156
|
+
getResearchStatus() { return { state: 'idle', question: '', workers: [], lastResult: null }; }
|
|
157
|
+
};
|
|
158
|
+
assert.equal(await routeCommand('/research status', { agent, sub: 'status' }), 'HANDLED');
|
|
159
|
+
assert.equal(await routeCommand('/research sources', { agent, sub: 'sources' }), 'HANDLED');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('/clear resets TUI viewport without appending duplicate banners', async () => {
|
|
163
|
+
let ended = false;
|
|
164
|
+
const agent = { endSession: async () => { ended = true; } };
|
|
165
|
+
const mockTuiPrompt = {
|
|
166
|
+
viewportLines: ['line1', 'line2'],
|
|
167
|
+
scrollOffset: 5,
|
|
168
|
+
rendered: false,
|
|
169
|
+
addViewportLines(line) { this.viewportLines.push(line); },
|
|
170
|
+
render() { this.rendered = true; }
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const res = await routeCommand('/clear', { agent, tuiPrompt: mockTuiPrompt });
|
|
174
|
+
assert.equal(res, 'HANDLED');
|
|
175
|
+
assert.equal(ended, true);
|
|
176
|
+
assert.equal(mockTuiPrompt.scrollOffset, 0);
|
|
177
|
+
assert.ok(mockTuiPrompt.viewportLines.length > 0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('/gpu and /system return handled status and display hardware information', async () => {
|
|
181
|
+
assert.equal(await routeCommand('/gpu', {}), 'HANDLED');
|
|
182
|
+
assert.equal(await routeCommand('/vram', {}), 'HANDLED');
|
|
183
|
+
assert.equal(await routeCommand('/system', {}), 'HANDLED');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('/mcp status includes every connected tool command in one panel output', async () => {
|
|
187
|
+
const tools = Array.from({ length: 18 }, (_, index) => ({
|
|
188
|
+
function: {
|
|
189
|
+
name: `mcp_kiku_tool_${index + 1}`,
|
|
190
|
+
description: `Tool ${index + 1}`,
|
|
191
|
+
parameters: { type: 'object', properties: {} },
|
|
192
|
+
},
|
|
193
|
+
}));
|
|
194
|
+
const agent = {
|
|
195
|
+
mcpManager: {
|
|
196
|
+
getStatus() {
|
|
197
|
+
return {
|
|
198
|
+
activeServersCount: 1,
|
|
199
|
+
totalToolsCount: tools.length,
|
|
200
|
+
servers: [{ name: 'kiku', type: 'streamable-http', toolsCount: tools.length, resourcesCount: 5 }],
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
getToolsSchema() { return tools; },
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
const originalLog = console.log;
|
|
207
|
+
const output = [];
|
|
208
|
+
console.log = (...args) => output.push(args.join(' '));
|
|
209
|
+
try {
|
|
210
|
+
assert.equal(await routeCommand('/mcp', { agent }), 'HANDLED');
|
|
211
|
+
} finally {
|
|
212
|
+
console.log = originalLog;
|
|
213
|
+
}
|
|
214
|
+
const rendered = output.join('\n');
|
|
215
|
+
for (const tool of tools) assert.match(rendered, new RegExp(`@${tool.function.name}`));
|
|
216
|
+
assert.equal(output.length, 1, 'the panel should be emitted as one TUI batch');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('/update is no longer registered or routed', async () => {
|
|
220
|
+
const originalLog = console.log;
|
|
221
|
+
console.log = () => {};
|
|
222
|
+
try {
|
|
223
|
+
assert.equal(COMMAND_NAMES.includes('/update'), false);
|
|
224
|
+
assert.equal(await routeCommand('/update check', {}), 'UNKNOWN');
|
|
225
|
+
} finally {
|
|
226
|
+
console.log = originalLog;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('audioPlayer tracks playback offset and resumes from saved position', async () => {
|
|
231
|
+
const { audioPlayer } = await import('../audioPlayer.js');
|
|
232
|
+
const originalCreateStream = audioPlayer._createStream;
|
|
233
|
+
audioPlayer._createStream = async () => {};
|
|
234
|
+
try {
|
|
235
|
+
audioPlayer.isPlaying = true;
|
|
236
|
+
audioPlayer.isPaused = false;
|
|
237
|
+
audioPlayer.currentTrack = 'Test Track';
|
|
238
|
+
audioPlayer._playbackStartedAt = Date.now() - 35000;
|
|
239
|
+
audioPlayer._currentOffsetSeconds = 10;
|
|
240
|
+
|
|
241
|
+
const pauseMsg = audioPlayer.pause();
|
|
242
|
+
assert.ok(pauseMsg.includes('Paused: Test Track'));
|
|
243
|
+
assert.ok(pauseMsg.includes('0:45'));
|
|
244
|
+
assert.equal(audioPlayer.isPaused, true);
|
|
245
|
+
assert.equal(audioPlayer._playbackStartedAt, 0);
|
|
246
|
+
assert.ok(audioPlayer._currentOffsetSeconds >= 44 && audioPlayer._currentOffsetSeconds <= 46);
|
|
247
|
+
|
|
248
|
+
const resumeMsg = audioPlayer.resume();
|
|
249
|
+
assert.ok(resumeMsg.includes('Resumed: Test Track'));
|
|
250
|
+
assert.ok(resumeMsg.includes('from 0:45'));
|
|
251
|
+
assert.equal(audioPlayer.isPaused, false);
|
|
252
|
+
assert.ok(audioPlayer._playbackStartedAt > 0);
|
|
253
|
+
|
|
254
|
+
audioPlayer.stop();
|
|
255
|
+
assert.equal(audioPlayer._currentOffsetSeconds, 0);
|
|
256
|
+
assert.equal(audioPlayer._playbackStartedAt, 0);
|
|
257
|
+
} finally {
|
|
258
|
+
audioPlayer._createStream = originalCreateStream;
|
|
259
|
+
audioPlayer.stop();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('getCommandDetails returns structured metadata, subcommands, and examples', async () => {
|
|
264
|
+
const { getCommandDetails } = await import('../commandRegistry.js');
|
|
265
|
+
|
|
266
|
+
const suggestionDetails = getCommandDetails('/suggestion');
|
|
267
|
+
assert.ok(suggestionDetails, 'Should have details for /suggestion');
|
|
268
|
+
assert.ok(suggestionDetails.subcommands.length >= 4);
|
|
269
|
+
assert.ok(suggestionDetails.subcommands.some(s => s.cmd === 'list'));
|
|
270
|
+
assert.ok(suggestionDetails.subcommands.some(s => s.cmd.startsWith('run')));
|
|
271
|
+
assert.ok(suggestionDetails.subcommands.some(s => s.cmd.startsWith('topic')));
|
|
272
|
+
assert.ok(suggestionDetails.examples.length >= 3);
|
|
273
|
+
|
|
274
|
+
const mcpDetails = getCommandDetails('mcp');
|
|
275
|
+
assert.ok(mcpDetails, 'Should normalize command without leading slash');
|
|
276
|
+
assert.ok(mcpDetails.subcommands.some(s => s.cmd.startsWith('connect')));
|
|
277
|
+
assert.ok(mcpDetails.subcommands.some(s => s.cmd.startsWith('save')));
|
|
278
|
+
assert.ok(mcpDetails.subcommands.some(s => s.cmd.startsWith('remove')));
|
|
279
|
+
|
|
280
|
+
const providerDetails = getCommandDetails('/provider');
|
|
281
|
+
assert.ok(providerDetails);
|
|
282
|
+
assert.ok(providerDetails.subcommands.some(s => s.cmd.startsWith('use')));
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('/<cmd> help and /help <cmd> route to help handler with subcommands', async () => {
|
|
286
|
+
let printedCmd = null;
|
|
287
|
+
const ctx = {
|
|
288
|
+
printHelp(cmd) { printedCmd = cmd; },
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
assert.equal(await routeCommand('/help suggestion', ctx), 'HANDLED');
|
|
292
|
+
assert.equal(printedCmd, 'suggestion');
|
|
293
|
+
|
|
294
|
+
assert.equal(await routeCommand('/suggestion help', ctx), 'HANDLED');
|
|
295
|
+
assert.equal(printedCmd, '/suggestion');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('/cancel command aborts running query and returns handled', async () => {
|
|
299
|
+
let cancelled = false;
|
|
300
|
+
const ctx = {
|
|
301
|
+
cancelActiveQuery: () => {
|
|
302
|
+
cancelled = true;
|
|
303
|
+
return true;
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
assert.equal(await routeCommand('/cancel', ctx), 'HANDLED');
|
|
308
|
+
assert.equal(cancelled, true);
|
|
309
|
+
|
|
310
|
+
assert.equal(await routeCommand('/abort', ctx), 'HANDLED');
|
|
311
|
+
assert.equal(cancelled, true);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('/copy command routes to copyQueryDetails context method and aliases work', async () => {
|
|
315
|
+
let copiedRef = null;
|
|
316
|
+
const ctx = {
|
|
317
|
+
copyQueryDetails: (ref) => {
|
|
318
|
+
copiedRef = ref;
|
|
319
|
+
return true;
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
assert.equal(await routeCommand('/copy', ctx), 'HANDLED');
|
|
324
|
+
assert.equal(copiedRef, '');
|
|
325
|
+
|
|
326
|
+
assert.equal(await routeCommand('/copy thinking', ctx), 'HANDLED');
|
|
327
|
+
assert.equal(copiedRef, 'thinking');
|
|
328
|
+
|
|
329
|
+
assert.equal(await routeCommand('/cp Q2 answer', ctx), 'HANDLED');
|
|
330
|
+
assert.equal(copiedRef, 'Q2 answer');
|
|
331
|
+
|
|
332
|
+
assert.equal(await routeCommand('/clipboard tool 1', ctx), 'HANDLED');
|
|
333
|
+
assert.equal(copiedRef, 'tool 1');
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test('/stats renders an AI insight when a chat client is available', async () => {
|
|
337
|
+
const originalLog = console.log;
|
|
338
|
+
const output = [];
|
|
339
|
+
console.log = (...args) => output.push(args.join(' '));
|
|
340
|
+
const ctx = {
|
|
341
|
+
agent: {
|
|
342
|
+
memory: { preferences: { language: null } },
|
|
343
|
+
client: { chat: async () => ({ content: 'Dramas dominate the catalog.' }) },
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
try {
|
|
347
|
+
assert.equal(await routeCommand('/stats', ctx), 'HANDLED');
|
|
348
|
+
} finally {
|
|
349
|
+
console.log = originalLog;
|
|
350
|
+
}
|
|
351
|
+
const text = output.join('\n');
|
|
352
|
+
assert.match(text, /Database Overview/);
|
|
353
|
+
assert.match(text, /Dramas dominate the catalog/);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test('/stats falls back gracefully when no chat client is available', async () => {
|
|
357
|
+
const originalLog = console.log;
|
|
358
|
+
const output = [];
|
|
359
|
+
console.log = (...args) => output.push(args.join(' '));
|
|
360
|
+
try {
|
|
361
|
+
assert.equal(await routeCommand('/stats', {}), 'HANDLED');
|
|
362
|
+
} finally {
|
|
363
|
+
console.log = originalLog;
|
|
364
|
+
}
|
|
365
|
+
const text = output.join('\n');
|
|
366
|
+
assert.match(text, /Database Overview/);
|
|
367
|
+
assert.doesNotMatch(text, /STATS ERROR/);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('/theme next cycles to next theme and renders tuiPrompt if available', async () => {
|
|
371
|
+
let rendered = false;
|
|
372
|
+
const mockTuiPrompt = { render() { rendered = true; } };
|
|
373
|
+
const current = getThemeName();
|
|
374
|
+
assert.equal(await routeCommand('/theme next', { tuiPrompt: mockTuiPrompt }), 'HANDLED');
|
|
375
|
+
assert.equal(rendered, true);
|
|
376
|
+
assert.notEqual(getThemeName(), current);
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
assert.equal(await routeCommand('/theme prev', { tuiPrompt: mockTuiPrompt }), 'HANDLED');
|
|
380
|
+
assert.equal(getThemeName(), current);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test('all hotkey target commands are routeable and handled', async () => {
|
|
384
|
+
const dummyCtx = {
|
|
385
|
+
agent: {
|
|
386
|
+
session: {
|
|
387
|
+
setThinkingMode() {},
|
|
388
|
+
toggleThinkingMode() { return 'show'; },
|
|
389
|
+
exportToMarkdown: async () => ({ filePath: 'export.md' }),
|
|
390
|
+
},
|
|
391
|
+
getAllToolsSchema: () => [],
|
|
392
|
+
},
|
|
393
|
+
client: {},
|
|
394
|
+
printHelp: () => {},
|
|
395
|
+
printQueryDetails: () => {},
|
|
396
|
+
copyQueryDetails: () => ({ error: null }),
|
|
397
|
+
showTerminalImage: () => true,
|
|
398
|
+
lastQuery: 'test query',
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
assert.equal(await routeCommand('/help', dummyCtx), 'HANDLED');
|
|
403
|
+
assert.equal(await routeCommand('/details', dummyCtx), 'HANDLED');
|
|
404
|
+
assert.equal(await routeCommand('/details thinking', dummyCtx), 'HANDLED');
|
|
405
|
+
assert.equal(await routeCommand('/details tools', dummyCtx), 'HANDLED');
|
|
406
|
+
assert.equal(await routeCommand('/copy', dummyCtx), 'HANDLED');
|
|
407
|
+
assert.equal(await routeCommand('/gallery', dummyCtx), 'HANDLED');
|
|
408
|
+
assert.equal(await routeCommand('/theme next', { ...dummyCtx, tuiPrompt: { render() {} } }), 'HANDLED');
|
|
409
|
+
assert.equal(await routeCommand('/stats', dummyCtx), 'HANDLED');
|
|
410
|
+
assert.equal(await routeCommand('/provider', dummyCtx), 'HANDLED');
|
|
411
|
+
assert.equal(await routeCommand('/mcp list', dummyCtx), 'HANDLED');
|
|
412
|
+
assert.equal(await routeCommand('/thinking toggle', dummyCtx), 'HANDLED');
|
|
413
|
+
assert.equal(await routeCommand('/retry', dummyCtx), 'RETRY');
|
|
414
|
+
assert.equal(await routeCommand('/save', dummyCtx), 'HANDLED');
|
|
415
|
+
assert.equal(await routeCommand('/mcp-key', dummyCtx), 'HANDLED');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test('/quiz mcp completes 12 questions and claims MCP key', async () => {
|
|
419
|
+
const originalFetch = global.fetch;
|
|
420
|
+
const originalRandom = Math.random;
|
|
421
|
+
const originalLog = console.log;
|
|
422
|
+
const prompts = [];
|
|
423
|
+
const output = [];
|
|
424
|
+
const preferences = {};
|
|
425
|
+
let claimBody = null;
|
|
426
|
+
let challengeUrl = null;
|
|
427
|
+
let claimUrl = null;
|
|
428
|
+
|
|
429
|
+
global.fetch = async (url, options = {}) => {
|
|
430
|
+
if (!options.method) {
|
|
431
|
+
challengeUrl = url;
|
|
432
|
+
return {
|
|
433
|
+
ok: true,
|
|
434
|
+
json: async () => ({ data: { challengeToken: 'kiku_mcp_chal_test' } }),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
claimUrl = url;
|
|
438
|
+
claimBody = JSON.parse(options.body);
|
|
439
|
+
return {
|
|
440
|
+
json: async () => ({ status: 'success', data: { apiKey: 'kiku_mcp_sk_test_token' } }),
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
Math.random = () => 0;
|
|
444
|
+
console.log = (...args) => output.push(args.join(' '));
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
const result = await routeCommand('/quiz mcp', {
|
|
448
|
+
sub: 'mcp',
|
|
449
|
+
extra: '',
|
|
450
|
+
readInput: async options => {
|
|
451
|
+
prompts.push(options);
|
|
452
|
+
return 'd';
|
|
453
|
+
},
|
|
454
|
+
agent: {
|
|
455
|
+
memory: {
|
|
456
|
+
preferences,
|
|
457
|
+
setPreference(key, value) { preferences[key] = value; },
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
assert.equal(result, 'HANDLED');
|
|
463
|
+
assert.equal(prompts.length, 12);
|
|
464
|
+
assert.match(challengeUrl, /\/api\/v1\/mcp\/quiz\/challenge/);
|
|
465
|
+
assert.match(claimUrl, /\/api\/v1\/mcp\/keys\/claim/);
|
|
466
|
+
assert.equal(claimBody.challengeToken, 'kiku_mcp_chal_test');
|
|
467
|
+
assert.equal(claimBody.answers.length, 12);
|
|
468
|
+
assert.equal(preferences.mcpKey, 'kiku_mcp_sk_test_token');
|
|
469
|
+
} finally {
|
|
470
|
+
global.fetch = originalFetch;
|
|
471
|
+
Math.random = originalRandom;
|
|
472
|
+
console.log = originalLog;
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test('/mcp-key manages MCP API key preferences', async () => {
|
|
477
|
+
const originalLog = console.log;
|
|
478
|
+
const output = [];
|
|
479
|
+
console.log = (...args) => output.push(args.join(' '));
|
|
480
|
+
const preferences = {};
|
|
481
|
+
const mem = {
|
|
482
|
+
preferences,
|
|
483
|
+
setPreference(k, v) { preferences[k] = v; },
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
await routeCommand('/mcp-key', { sub: '', extra: '', agent: { memory: mem } });
|
|
488
|
+
assert.match(output.join('\n'), /No MCP API Key stored/);
|
|
489
|
+
|
|
490
|
+
await routeCommand('/mcp-key apply kiku_mcp_sk_my_key', { sub: 'apply', extra: 'kiku_mcp_sk_my_key', agent: { memory: mem } });
|
|
491
|
+
assert.equal(preferences.mcpKey, 'kiku_mcp_sk_my_key');
|
|
492
|
+
|
|
493
|
+
output.length = 0;
|
|
494
|
+
await routeCommand('/mcp-key config', { sub: 'config', extra: '', agent: { memory: mem } });
|
|
495
|
+
assert.match(output.join('\n'), /Claude Desktop & Cursor Config/);
|
|
496
|
+
assert.match(output.join('\n'), /kiku_mcp_sk_my_key/);
|
|
497
|
+
|
|
498
|
+
output.length = 0;
|
|
499
|
+
await routeCommand('/mcp-key clear', { sub: 'clear', extra: '', agent: { memory: mem } });
|
|
500
|
+
assert.equal(preferences.mcpKey, null);
|
|
501
|
+
assert.match(output.join('\n'), /MCP API Key cleared/);
|
|
502
|
+
} finally {
|
|
503
|
+
console.log = originalLog;
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|