kiku-agent 1.2.0 → 1.3.1
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 +459 -407
- package/package.json +32 -3
- package/server/agent/agentCore.js +635 -267
- package/server/agent/appPaths.js +2 -0
- package/server/agent/catalogEntityResolver.js +637 -0
- package/server/agent/citations.js +0 -21
- package/server/agent/clarificationAnalytics.js +1 -1
- package/server/agent/clarifyingQuestions.js +286 -216
- package/server/agent/cli.js +532 -73
- package/server/agent/commandFeatures.js +123 -24
- package/server/agent/commandRegistry.js +128 -62
- package/server/agent/commands.js +1410 -95
- package/server/agent/config.js +253 -163
- package/server/agent/contextCompactor.js +239 -25
- package/server/agent/dynamicCatalog.js +192 -0
- package/server/agent/envBootstrap.js +46 -0
- package/server/agent/envManager.js +317 -0
- package/server/agent/eventBus.js +3 -3
- package/server/agent/feedbackLearning.js +0 -8
- package/server/agent/gpuMetrics.js +188 -55
- package/server/agent/ingestionWorker.js +339 -0
- package/server/agent/ink/App.jsx +348 -0
- package/server/agent/ink/Panel.jsx +54 -0
- package/server/agent/ink/PromptInput.jsx +321 -0
- package/server/agent/ink/TaskPlanPanel.jsx +88 -0
- package/server/agent/ink/ThinkingPanel.jsx +68 -0
- package/server/agent/ink/ToolResult.jsx +133 -0
- package/server/agent/ink/main.jsx +195 -0
- package/server/agent/ink/useEventBus.js +218 -0
- package/server/agent/lanDiscovery.js +369 -0
- package/server/agent/llmClient.js +111 -39
- package/server/agent/lmStudioWakeup.js +144 -0
- package/server/agent/mcpClient.js +2 -2
- package/server/agent/memory/contextAllocator.js +214 -0
- package/server/agent/memory/hierarchicalStore.js +523 -0
- package/server/agent/memory/reflectionWorker.js +110 -0
- package/server/agent/memory.js +635 -494
- package/server/agent/modelConfig.js +487 -0
- package/server/agent/newsIntelligence.js +219 -0
- package/server/agent/newsWatch.js +85 -0
- package/server/agent/prompts.js +146 -0
- package/server/agent/publicTools.js +7 -0
- package/server/agent/queryDetails.js +62 -1
- package/server/agent/research.js +314 -35
- package/server/agent/routingInterceptor.js +77 -0
- package/server/agent/rumoredProjects.js +15 -0
- package/server/agent/sanitizer.js +117 -17
- package/server/agent/subagent.js +52 -9
- package/server/agent/suggestionTui.js +33 -13
- package/server/agent/suggestions.js +29 -13
- package/server/agent/telemetry/hardwareTiers.js +332 -0
- package/server/agent/telemetry/runtimeWatchdog.js +66 -0
- package/server/agent/telemetry/techPowerUpClient.js +682 -0
- package/server/agent/telemetry/techpowerup-mcp.json +22 -0
- package/server/agent/telemetry.js +0 -11
- package/server/agent/terminalImage.js +0 -1
- package/server/agent/textToolProtocol.js +190 -0
- package/server/agent/tokenTracker.js +220 -0
- package/server/agent/toolRouter.js +362 -216
- package/server/agent/tools.js +1155 -194
- package/server/agent/ui.js +144 -41
- package/server/agent/vramService.js +572 -0
- package/server/agent/webSearch.js +2279 -273
- package/server/agent/websearch/adapters/duckduckgo.js +97 -0
- package/server/agent/websearch/adapters/jina.js +77 -0
- package/server/agent/websearch/adapters/searxng.js +57 -0
- package/server/agent/websearch/core/canonical.js +89 -0
- package/server/agent/websearch/core/fusion.js +81 -0
- package/server/agent/websearch/core/types.js +29 -0
- package/server/agent/websearch/extract.js +605 -0
- package/server/agent/websearch/index.js +43 -0
- package/server/agent/websearch/orchestrator.js +266 -0
- package/server/agent/websearch/resilience/breaker.js +97 -0
- package/server/agent/websearch/resilience/cache.js +59 -0
- package/server/agent/websearch/resilience/limiter.js +93 -0
- package/server/agent/websearch/security/filter.js +36 -0
- package/server/agent/websearch/security/ssrf.js +289 -0
- package/server/agent/websearch/telemetry.js +71 -0
- package/server/agent/youtubeAuth.js +82 -73
- package/server/agent/youtubeTranscript.js +209 -0
- package/server/apiRouter.js +390 -190
- package/server/mcp/data/brands.js +5 -5
- package/server/mcp/data/discography.js +56 -45
- package/server/mcp/data/gallery.js +9 -0
- package/server/mcp/data/index.js +1 -0
- package/server/mcp/data/industryIndex.js +251 -0
- package/server/mcp/data/profile.js +5 -5
- package/server/mcp/data/timeline.js +2 -2
- package/server/mcp/data/works.js +2 -2
- package/server/mcp/handler.js +0 -2
- package/server/mcp/prompts.js +42 -2
- package/server/mcp/resources.js +5 -1
- package/server/mcp/security.js +25 -14
- package/server/mcp/server.js +92 -3
- package/server/mcp/tools.js +20 -11
- package/server/routes/agent.js +55 -0
- package/server/web-chat/core/connection.js +218 -194
- package/server/web-chat/core/conversationContext.js +1 -1
- package/server/web-chat/core/directCatalogEngine.js +190 -184
- package/server/web-chat/core/followUpEngine.js +88 -11
- package/server/web-chat/core/security.js +7 -4
- package/server/web-chat/router.js +54 -17
- package/server/web-chat/routes/bilibiliRoute.js +34 -0
- package/server/web-chat/routes/chatRoute.js +488 -397
- package/server/web-chat/routes/clarifyConfigRoute.js +1 -2
- package/server/web-chat/routes/exportRoute.js +2 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +90 -0
- package/server/web-chat/routes/newsRoute.js +36 -6
- package/server/web-chat/routes/providerRoute.js +87 -0
- package/server/web-chat/routes/staticRoute.js +61 -13
- package/server/web-chat/routes/statusRoute.js +23 -2
- package/server/web-chat/routes/vramRoute.js +22 -0
- package/server/web-chat/server.js +135 -106
- package/server/web-chat/ui/htmlBuilder.js +8 -8
- package/server/web-chat/ui/previews/modelSettings.preview.html +41 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +0 -5
- package/server/web-chat/ui/scripts/client.js +10526 -8397
- package/server/web-chat/ui/scripts/webchatI18n.js +190 -138
- package/server/web-chat/ui/styles/main.css +7471 -4246
- package/server/web-chat/ui/templates/body.html +305 -179
- package/src/data/brandsData.js +29 -6
- package/src/data/discographyData.js +918 -900
- package/src/data/galleryData.js +66 -0
- package/src/data/industryIndexData.js +292 -0
- package/src/data/musicVideoData.js +48 -5
- package/src/data/profileData.js +15 -1
- package/src/data/timelineData.js +4 -3
- package/src/data/worksData.js +536 -535
- package/src/lib/vram/estimator.js +464 -0
- package/src/lib/vram/modelRegistry.js +387 -0
- package/tokens.css +33 -2
package/server/agent/cli.js
CHANGED
|
@@ -5,11 +5,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { stdin as input } from 'node:process';
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
|
-
import './
|
|
8
|
+
import './envBootstrap.js';
|
|
9
9
|
import { KikuAgent } from './agentCore.js';
|
|
10
10
|
import { LLMClient, LMStudioClient } from './llmClient.js';
|
|
11
11
|
import { toolsSchema, toolHandlers, executeTool } from './tools.js';
|
|
12
|
-
import { config, CLOUD_PROVIDERS } from './config.js';
|
|
12
|
+
import { config, CLOUD_PROVIDERS, matchModelIdentifier } from './config.js';
|
|
13
13
|
import { audioPlayer } from './audioPlayer.js';
|
|
14
14
|
import { routeCommand } from './commands.js';
|
|
15
15
|
import { eventBus, Events } from './eventBus.js';
|
|
@@ -18,6 +18,9 @@ import { sanitizeQuery } from './sanitizer.js';
|
|
|
18
18
|
import { isAbortError, formatExecutionErrorCard } from './errors.js';
|
|
19
19
|
import { COMMAND_DEFINITIONS, COMMAND_CATEGORIES, SUB_COMMANDS, findCommandDefinition, getCommandDetails } from './commandRegistry.js';
|
|
20
20
|
import { ModelManager } from './modelManager.js';
|
|
21
|
+
import { runVramPreflight, probeLmStudioRuntime, getHardwareTarget } from './vramService.js';
|
|
22
|
+
import { resolveModelConfig } from './modelConfig.js';
|
|
23
|
+
import { estimateVramFootprint, classifyAllocation, formatGb } from '../../src/lib/vram/estimator.js';
|
|
21
24
|
import { PACKAGE_VERSION } from './version.js';
|
|
22
25
|
import { getProfile, listProfiles } from './profiles.js';
|
|
23
26
|
import { formatCitationsBlock } from './citations.js';
|
|
@@ -30,7 +33,7 @@ import {
|
|
|
30
33
|
parseDetailsRequest,
|
|
31
34
|
serializeDetailValue,
|
|
32
35
|
summarizeQueryDetails,
|
|
33
|
-
|
|
36
|
+
copyToClipboardAsync,
|
|
34
37
|
getCopyableContent,
|
|
35
38
|
} from './queryDetails.js';
|
|
36
39
|
|
|
@@ -39,7 +42,7 @@ import {
|
|
|
39
42
|
previousGraphemeBoundary, nextGraphemeBoundary, sliceLineForDisplay,
|
|
40
43
|
printSolidBannerRow, printSolidPanel, formatSolidPanelLines, formatTerminalMarkdown, formatMarkdownLines,
|
|
41
44
|
parseMarkdownTableRow, isMarkdownTableSeparator, expandMarkdownLinks,
|
|
42
|
-
formatRichCardLines, printRichCard, printRichStatusCard, formatStatusBadge, formatRichTable,
|
|
45
|
+
formatRichCardLines, printRichCard, printRichStatusCard, formatStatusBadge, formatRichTable, cleanTableCell,
|
|
43
46
|
printWelcomeHero, getWelcomeHeroLines, randomFanName, setupOutputInterception,
|
|
44
47
|
rawWrite, INPUT_PLACEHOLDER, UI_ICONS,
|
|
45
48
|
spinnerFrames, thinkingFrames, createProgressRunner,
|
|
@@ -136,6 +139,11 @@ export function parseCliArgs(customArgs = null) {
|
|
|
136
139
|
webChat: false,
|
|
137
140
|
port: null,
|
|
138
141
|
noOpen: false,
|
|
142
|
+
force: false,
|
|
143
|
+
vram: null,
|
|
144
|
+
eval: null,
|
|
145
|
+
hub: null,
|
|
146
|
+
portal: null,
|
|
139
147
|
};
|
|
140
148
|
|
|
141
149
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -157,6 +165,9 @@ export function parseCliArgs(customArgs = null) {
|
|
|
157
165
|
else if (arg === '--output' || arg === '-o') options.output = (args[++i] || 'text').toLowerCase();
|
|
158
166
|
else if (arg === '--plain') { options.plain = true; options.noColor = true; }
|
|
159
167
|
else if (arg === '--no-color') options.noColor = true;
|
|
168
|
+
else if (arg === '--cn-tools' || arg === '--cn' || arg === '--enable-cn-tools') {
|
|
169
|
+
process.env.KIKU_ENABLE_CN_WEB_TOOLS = 'true';
|
|
170
|
+
}
|
|
160
171
|
else if (arg === '--web-chat' || arg === '--web') options.webChat = true;
|
|
161
172
|
else if (arg.startsWith('--web-chat=')) {
|
|
162
173
|
options.webChat = true;
|
|
@@ -181,6 +192,19 @@ export function parseCliArgs(customArgs = null) {
|
|
|
181
192
|
}
|
|
182
193
|
else if (arg === '--no-open') options.noOpen = true;
|
|
183
194
|
else if (arg === '--open') options.noOpen = false;
|
|
195
|
+
else if (arg === '--force') options.force = true;
|
|
196
|
+
else if (arg === '--vram' || arg === '--gpu') {
|
|
197
|
+
options.vram = (args[i + 1] && !args[i + 1].startsWith('-')) ? args[++i] : 'live';
|
|
198
|
+
}
|
|
199
|
+
else if (arg === '--hub') {
|
|
200
|
+
options.hub = (args[i + 1] && !args[i + 1].startsWith('-')) ? args[++i] : true;
|
|
201
|
+
}
|
|
202
|
+
else if (arg === '--eval') {
|
|
203
|
+
options.eval = (args[i + 1] && !args[i + 1].startsWith('-')) ? args[++i] : 'ui';
|
|
204
|
+
}
|
|
205
|
+
else if (arg === '--portal') {
|
|
206
|
+
options.portal = (args[i + 1] && !args[i + 1].startsWith('-')) ? args[++i] : 'open';
|
|
207
|
+
}
|
|
184
208
|
else if (!arg.startsWith('-') && !options.query) { options.query = args.slice(i).join(' '); break; }
|
|
185
209
|
else if (arg.startsWith('-')) {
|
|
186
210
|
console.error(`Unknown option: ${arg}`);
|
|
@@ -431,7 +455,7 @@ function printQueryDetails(reference = '') {
|
|
|
431
455
|
});
|
|
432
456
|
}
|
|
433
457
|
|
|
434
|
-
function copyQueryDetails(reference = '', session = null) {
|
|
458
|
+
async function copyQueryDetails(reference = '', session = null) {
|
|
435
459
|
const request = parseDetailsRequest(reference);
|
|
436
460
|
if (request.error) {
|
|
437
461
|
printSolidPanel({
|
|
@@ -481,7 +505,7 @@ function copyQueryDetails(reference = '', session = null) {
|
|
|
481
505
|
return false;
|
|
482
506
|
}
|
|
483
507
|
|
|
484
|
-
const copied =
|
|
508
|
+
const copied = await copyToClipboardAsync(copyable.text);
|
|
485
509
|
const lineCount = copyable.text.split('\n').length;
|
|
486
510
|
const charCount = copyable.text.length.toLocaleString();
|
|
487
511
|
|
|
@@ -597,7 +621,7 @@ export function printHelp(specificCmd = null) {
|
|
|
597
621
|
helpLines.push(` ${C.emerald}${"-q, --query <text>".padEnd(28)}${C.reset} ${C.white}Run a one-shot query${C.reset}`);
|
|
598
622
|
helpLines.push(` ${C.emerald}${"-p, --print".padEnd(28)}${C.reset} ${C.white}Headless non-interactive print mode (clean text output)${C.reset}`);
|
|
599
623
|
helpLines.push(` ${C.emerald}${"-o, --output <format>".padEnd(28)}${C.reset} ${C.white}Output format: text (default) or json${C.reset}`);
|
|
600
|
-
helpLines.push(` ${C.emerald}${"-P, --provider <name>".padEnd(28)}${C.reset} ${C.white}Select provider (lmstudio, openai, gemini, groq, etc.)${C.reset}`);
|
|
624
|
+
helpLines.push(` ${C.emerald}${"-P, --provider <name>".padEnd(28)}${C.reset} ${C.white}Select provider (lmstudio, vllm, openai, gemini, groq, etc.)${C.reset}`);
|
|
601
625
|
helpLines.push(` ${C.emerald}${"--profile <name>".padEnd(28)}${C.reset} ${C.white}Load configuration profile (default, fast, deep-reasoning, cloud, gemini)${C.reset}`);
|
|
602
626
|
helpLines.push(` ${C.emerald}${"-m, --model <id>".padEnd(28)}${C.reset} ${C.white}Model ID override${C.reset}`);
|
|
603
627
|
helpLines.push(` ${C.emerald}${"-k, --api-key <key>".padEnd(28)}${C.reset} ${C.white}Provider API key override${C.reset}`);
|
|
@@ -607,7 +631,12 @@ export function printHelp(specificCmd = null) {
|
|
|
607
631
|
helpLines.push(` ${C.emerald}${"--thinking <mode>".padEnd(28)}${C.reset} ${C.white}Show or hide reasoning process (show | hide)${C.reset}`);
|
|
608
632
|
helpLines.push(` ${C.emerald}${"-t, --tools".padEnd(28)}${C.reset} ${C.white}List all registered tools${C.reset}`);
|
|
609
633
|
helpLines.push(` ${C.emerald}${"-s, --stats".padEnd(28)}${C.reset} ${C.white}Show system and catalog stats${C.reset}`);
|
|
634
|
+
helpLines.push(` ${C.emerald}${"--hub [filter]".padEnd(28)}${C.reset} ${C.white}Show Kiku Master Command Center hub${C.reset}`);
|
|
635
|
+
helpLines.push(` ${C.emerald}${"--portal [open|api|chat]".padEnd(28)}${C.reset} ${C.white}Open Fan Portal SPA, REST API docs, or Web Chat${C.reset}`);
|
|
636
|
+
helpLines.push(` ${C.emerald}${"--eval [ui|dash|val]".padEnd(28)}${C.reset} ${C.white}Eval Studio web UI, dashboard, or validation${C.reset}`);
|
|
637
|
+
helpLines.push(` ${C.emerald}${"--vram [live|compare|tier]".padEnd(28)}${C.reset} ${C.white}VRAM memory telemetry & hardware classification${C.reset}`);
|
|
610
638
|
helpLines.push(` ${C.emerald}${"--web-chat, --web".padEnd(28)}${C.reset} ${C.white}Start standalone web chatbot (default port 3300)${C.reset}`);
|
|
639
|
+
helpLines.push(` ${C.emerald}${"--cn-tools, --cn".padEnd(28)}${C.reset} ${C.white}Enable Chinese web tools (search_bilibili, Baidu)${C.reset}`);
|
|
611
640
|
helpLines.push(` ${C.emerald}${"--port <n>".padEnd(28)}${C.reset} ${C.white}Web chatbot port 1-65535 (or --web-chat=<port>)${C.reset}`);
|
|
612
641
|
helpLines.push(` ${C.emerald}${"--no-open, --open".padEnd(28)}${C.reset} ${C.white}Skip or force auto-open browser for --web-chat${C.reset}`);
|
|
613
642
|
helpLines.push(` ${C.emerald}${"-v, --verbose".padEnd(28)}${C.reset} ${C.white}Verbose logging and thinking${C.reset}`);
|
|
@@ -863,6 +892,22 @@ class InteractiveTuiPrompt {
|
|
|
863
892
|
if (resolve) resolve(result);
|
|
864
893
|
}
|
|
865
894
|
|
|
895
|
+
abortActiveRead(result = null) {
|
|
896
|
+
if (this.isDisposed || !this.activeResolve || this.isResolving) return false;
|
|
897
|
+
this.isResolving = true;
|
|
898
|
+
this._cleanupInput();
|
|
899
|
+
this.line = '';
|
|
900
|
+
this.cursor = 0;
|
|
901
|
+
this.ghostText = '';
|
|
902
|
+
this.popoverActive = false;
|
|
903
|
+
this.popoverItems = [];
|
|
904
|
+
const resolve = this.activeResolve;
|
|
905
|
+
this.activeResolve = null;
|
|
906
|
+
this.activeReject = null;
|
|
907
|
+
resolve(result);
|
|
908
|
+
return true;
|
|
909
|
+
}
|
|
910
|
+
|
|
866
911
|
fail(error) {
|
|
867
912
|
if (this.isResolving) return;
|
|
868
913
|
this.isResolving = true;
|
|
@@ -901,7 +946,7 @@ class InteractiveTuiPrompt {
|
|
|
901
946
|
this.popoverItems = [];
|
|
902
947
|
this.popoverIndex = 0;
|
|
903
948
|
if (!this.isConnected && this.line.length > 0) {
|
|
904
|
-
this.ghostText = ' (LM Studio offline -
|
|
949
|
+
this.ghostText = ' (LM Studio offline - will auto-connect once loaded)';
|
|
905
950
|
} else {
|
|
906
951
|
this.ghostText = '';
|
|
907
952
|
}
|
|
@@ -924,7 +969,7 @@ class InteractiveTuiPrompt {
|
|
|
924
969
|
this.screenBuffer.clear(C.bgDark);
|
|
925
970
|
|
|
926
971
|
|
|
927
|
-
const shortModel = (this.modelName || 'gemma-4-e4b').
|
|
972
|
+
const shortModel = String(this.modelName || 'gemma-4-e4b').split(/[/\\]/).pop();
|
|
928
973
|
const modelLabel = this.isConnected
|
|
929
974
|
? `${C.gold}✦ ${shortModel}${C.reset}`
|
|
930
975
|
: `${C.roseGlow}✦ OFFLINE${C.reset}`;
|
|
@@ -981,7 +1026,7 @@ class InteractiveTuiPrompt {
|
|
|
981
1026
|
} else if (musicStatus) {
|
|
982
1027
|
statusStyled = ` ${C.cyberCyan}🎵 ${musicStatus}${C.reset}`;
|
|
983
1028
|
} else if (!this.isConnected) {
|
|
984
|
-
statusStyled = ` ${C.roseGlow}○ OFFLINE${C.reset} ${C.slate}•
|
|
1029
|
+
statusStyled = ` ${C.roseGlow}○ OFFLINE${C.reset} ${C.slate}• Auto-detecting LM Studio... (${C.cyberCyan}/model reconnect${C.reset}${C.slate})${C.reset}`;
|
|
985
1030
|
} else {
|
|
986
1031
|
const hkHints = `${C.slate}F1${C.reset}${C.dim} Help${C.reset} ${C.slate}F3${C.reset}${C.dim} Think${C.reset} ${C.slate}F4${C.reset}${C.dim} Tools${C.reset} ${C.slate}F5${C.reset}${C.dim} Copy${C.reset} ${C.slate}F7${C.reset}${C.dim} Theme${C.reset}`;
|
|
987
1032
|
statusStyled = ` ${C.emerald}● READY${C.reset} ${C.slate}│${C.reset} ${hkHints}`;
|
|
@@ -1038,7 +1083,7 @@ class InteractiveTuiPrompt {
|
|
|
1038
1083
|
} else if (!this.line) {
|
|
1039
1084
|
const defaultPlaceholder = this.isConnected
|
|
1040
1085
|
? this.inputPlaceholder
|
|
1041
|
-
: '[AI Offline]
|
|
1086
|
+
: '[AI Offline] Load model in LM Studio to auto-connect (or /help)';
|
|
1042
1087
|
const placeholder = sliceLineForDisplay(defaultPlaceholder, 0, maxLineWidth).displayLine;
|
|
1043
1088
|
trailingStyled = `${C.dim}${C.slate}${placeholder}${C.reset}`;
|
|
1044
1089
|
}
|
|
@@ -1548,7 +1593,7 @@ async function executeQueryWithUI(agent, prompt, isThinkingVisible = false, sign
|
|
|
1548
1593
|
const maxWidth = Math.max(16, getFullWidth() - 8);
|
|
1549
1594
|
for (let index = 0; index < activeTableHeaders.length; index++) {
|
|
1550
1595
|
const header = activeTableHeaders[index] || `Column ${index + 1}`;
|
|
1551
|
-
const value = expandMarkdownLinks(cells[index] || '—');
|
|
1596
|
+
const value = expandMarkdownLinks(cleanTableCell(cells[index] || '—'));
|
|
1552
1597
|
const prefix = `${header}: `;
|
|
1553
1598
|
const wrapped = wrapText(prefix + value, maxWidth);
|
|
1554
1599
|
for (const line of wrapped) {
|
|
@@ -1757,6 +1802,28 @@ async function executeQueryWithUI(agent, prompt, isThinkingVisible = false, sign
|
|
|
1757
1802
|
}
|
|
1758
1803
|
},
|
|
1759
1804
|
|
|
1805
|
+
onSubagentProgress: (event) => {
|
|
1806
|
+
if (!event || !event.type) return;
|
|
1807
|
+
let line = null;
|
|
1808
|
+
if (event.type === 'start') {
|
|
1809
|
+
line = ` ${C.slate}↳ deep research • ${event.workers || '?'} workers${C.reset}`;
|
|
1810
|
+
} else if (event.type === 'iteration') {
|
|
1811
|
+
line = ` ${C.slate}↳ iteration ${event.iteration}/${event.maxIterations}${C.reset}`;
|
|
1812
|
+
} else if (event.type === 'tool') {
|
|
1813
|
+
line = ` ${C.slate}↳ ${C.cyberCyan}${event.name}${C.reset}`;
|
|
1814
|
+
} else if (event.type === 'worker-start') {
|
|
1815
|
+
line = ` ${C.slate}↳ ${C.bold}${C.white}${event.label || event.id || 'worker'}${C.reset} ${C.dim}started${C.reset}`;
|
|
1816
|
+
} else if (event.type === 'worker-complete') {
|
|
1817
|
+
line = ` ${C.emerald}↳ ${event.label || event.id || 'worker'} done${C.reset}`;
|
|
1818
|
+
}
|
|
1819
|
+
if (!line) return;
|
|
1820
|
+
if (globalTuiPrompt?.addViewportLines) {
|
|
1821
|
+
globalTuiPrompt.addViewportLines(line);
|
|
1822
|
+
} else {
|
|
1823
|
+
console.log(line);
|
|
1824
|
+
}
|
|
1825
|
+
},
|
|
1826
|
+
|
|
1760
1827
|
onTokenStart: () => {
|
|
1761
1828
|
if (globalTuiPrompt) {
|
|
1762
1829
|
globalTuiPrompt.agentState = 'Streaming…';
|
|
@@ -1783,6 +1850,11 @@ async function executeQueryWithUI(agent, prompt, isThinkingVisible = false, sign
|
|
|
1783
1850
|
}
|
|
1784
1851
|
},
|
|
1785
1852
|
|
|
1853
|
+
onNotice: (text) => {
|
|
1854
|
+
stopSpinner();
|
|
1855
|
+
console.log(`\n ${C.amber}▸ ${text}${C.reset}`);
|
|
1856
|
+
},
|
|
1857
|
+
|
|
1786
1858
|
onError: (_err) => {
|
|
1787
1859
|
stopSpinner();
|
|
1788
1860
|
},
|
|
@@ -1844,6 +1916,134 @@ async function readStdin() {
|
|
|
1844
1916
|
});
|
|
1845
1917
|
}
|
|
1846
1918
|
|
|
1919
|
+
function vramZoneTag(zone) {
|
|
1920
|
+
const color = zone === 'GREEN' ? C.emerald : zone === 'YELLOW' ? C.gold : zone === 'RED' ? C.roseGlow : C.slate;
|
|
1921
|
+
return `${color}[${zone}]${C.reset}`;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
/**
|
|
1925
|
+
* Startup "Resource Verification" panel: live VRAM usage plus the running
|
|
1926
|
+
* model's estimated footprint, so users immediately see whether the loaded
|
|
1927
|
+
* model is a heavy fit for their card. Informational only — never blocks.
|
|
1928
|
+
*/
|
|
1929
|
+
async function printResourceVerificationPanel(client) {
|
|
1930
|
+
if (!client || client.provider !== 'lmstudio') return;
|
|
1931
|
+
try {
|
|
1932
|
+
const [hw, runtime] = await Promise.all([
|
|
1933
|
+
getHardwareTarget(),
|
|
1934
|
+
probeLmStudioRuntime(client.baseUrl).catch(() => null),
|
|
1935
|
+
]);
|
|
1936
|
+
const config = await resolveModelConfig({ modelName: client.model, allowNetwork: false });
|
|
1937
|
+
const cfg = config?.config || null;
|
|
1938
|
+
const runtimeCtx = runtime?.loadedContextLength || cfg?.contextLength || null;
|
|
1939
|
+
|
|
1940
|
+
const estimate = estimateVramFootprint({
|
|
1941
|
+
modelId: client.model,
|
|
1942
|
+
quant: cfg?.quant || 'q4_k_m',
|
|
1943
|
+
paramCount: cfg?.paramCount,
|
|
1944
|
+
paramsB: cfg?.paramsB,
|
|
1945
|
+
layers: cfg?.layers,
|
|
1946
|
+
queryHeads: cfg?.queryHeads,
|
|
1947
|
+
kvHeads: cfg?.kvHeads,
|
|
1948
|
+
headDim: cfg?.headDim,
|
|
1949
|
+
hiddenSize: cfg?.hiddenSize,
|
|
1950
|
+
contextLength: runtimeCtx,
|
|
1951
|
+
});
|
|
1952
|
+
|
|
1953
|
+
const lines = [];
|
|
1954
|
+
let livePct = null;
|
|
1955
|
+
if (hw.available && hw.freeBytes !== null && hw.freeBytes !== undefined) {
|
|
1956
|
+
const usedBytes = Math.max(0, hw.totalBytes - hw.freeBytes);
|
|
1957
|
+
const live = classifyAllocation({ totalBytes: usedBytes, availableBytes: hw.totalBytes });
|
|
1958
|
+
livePct = live.usagePct;
|
|
1959
|
+
lines.push(`${C.gold}GPU (live):${C.reset} ${C.white}${hw.name}${C.reset} — ${C.bold}${live.usagePct}%${C.reset} ${C.dim}(${formatGb(usedBytes)} / ${formatGb(hw.totalBytes)} used)${C.reset} ${vramZoneTag(live.zone)}`);
|
|
1960
|
+
lines.push(`${C.gold}Free VRAM:${C.reset} ${C.emerald}${formatGb(hw.freeBytes)}${C.reset}`);
|
|
1961
|
+
} else if (hw.available) {
|
|
1962
|
+
lines.push(`${C.gold}GPU:${C.reset} ${C.white}${hw.name}${C.reset} ${C.dim}(total ${formatGb(hw.totalBytes)} — live usage unavailable)${C.reset}`);
|
|
1963
|
+
} else {
|
|
1964
|
+
lines.push(`${C.gold}GPU:${C.reset} ${C.dim}not detected — VRAM feasibility checks unavailable (KIKU_VRAM_TOTAL_GB can pin a budget)${C.reset}`);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
const quantLabel = String(runtime?.quantization || estimate.quant || 'q4_k_m').toUpperCase();
|
|
1968
|
+
const paramsLabel = estimate.paramCount ? `${(estimate.paramCount / 1e9).toFixed(1)}B` : '?';
|
|
1969
|
+
const sourceLabel = config?.source === 'gguf' ? 'gguf metadata' : config?.source === 'registry' ? 'registry approx' : config?.source || 'unknown';
|
|
1970
|
+
lines.push(`${C.gold}Model:${C.reset} ${C.white}${client.model}${C.reset} ${C.dim}(${quantLabel} • ${paramsLabel} params • ${sourceLabel})${C.reset}`);
|
|
1971
|
+
|
|
1972
|
+
if (estimate.totalBytes > 0 && runtimeCtx) {
|
|
1973
|
+
const estCls = hw.available ? classifyAllocation({ totalBytes: estimate.totalBytes, availableBytes: hw.totalBytes }) : null;
|
|
1974
|
+
lines.push(`${C.gold}Estimate:${C.reset} ${C.bold}${C.white}${formatGb(estimate.totalBytes)}${C.reset} @ ${runtimeCtx.toLocaleString('en-US')} ctx` +
|
|
1975
|
+
(estCls && estCls.zone !== 'UNKNOWN' ? ` ${vramZoneTag(estCls.zone)}` : ''));
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
let verdict = null;
|
|
1979
|
+
if (livePct !== null) {
|
|
1980
|
+
if (livePct <= 80) verdict = `${C.emerald}Resource load is comfortable (${livePct}% VRAM in use).${C.reset}`;
|
|
1981
|
+
else if (livePct <= 95) verdict = `${C.gold}This model is heavy for the card (${livePct}% VRAM in use) — expect slower prefill on long contexts.${C.reset}`;
|
|
1982
|
+
else verdict = `${C.roseGlow}VRAM is critically full (${livePct}%) — lower the context or switch to a smaller quant in LM Studio.${C.reset}`;
|
|
1983
|
+
} else if (estimate.totalBytes > 0 && hw.available) {
|
|
1984
|
+
const estCls = classifyAllocation({ totalBytes: estimate.totalBytes, availableBytes: hw.totalBytes });
|
|
1985
|
+
if (estCls.zone === 'RED') verdict = `${C.gold}Naive estimate exceeds the card — if it loads anyway, sliding-window caching is saving you; otherwise lower ctx/quant.${C.reset}`;
|
|
1986
|
+
else verdict = `${C.dim}Live usage unavailable — estimate says ${estCls.zone}.${C.reset}`;
|
|
1987
|
+
}
|
|
1988
|
+
if (verdict) lines.push(`${C.gold}Verdict:${C.reset} ${verdict}`);
|
|
1989
|
+
lines.push(`${C.dim}Details: /vram live • /vram compare • /vram ctx${C.reset}`);
|
|
1990
|
+
|
|
1991
|
+
printSolidPanel({ title: 'Resource Verification', lines, headerBg: '\x1b[48;2;48;54;70m', bg: '\x1b[48;2;36;40;52m' });
|
|
1992
|
+
} catch { /* the startup sequence must never break because of this panel */ }
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
/**
|
|
1996
|
+
* Pre-flight VRAM gate for one-shot runs against local GPU inference.
|
|
1997
|
+
* A RED verdict aborts (exit 4) before the first request is sent unless
|
|
1998
|
+
* --force is supplied — unless the model is already loaded in LM Studio, in
|
|
1999
|
+
* which case live VRAM is authoritative and the gate only warns. Best-effort:
|
|
2000
|
+
* probe failures never block execution.
|
|
2001
|
+
*/
|
|
2002
|
+
async function runOneShotPreflightGate(client, opts) {
|
|
2003
|
+
if (!client || client.provider !== 'lmstudio') return null;
|
|
2004
|
+
const runtime = await probeLmStudioRuntime(client.baseUrl).catch(() => null);
|
|
2005
|
+
const alreadyLoaded = Boolean(runtime?.reachable && runtime.loadedModel && matchModelIdentifier(client.model, runtime.loadedModel));
|
|
2006
|
+
let preflight = null;
|
|
2007
|
+
try {
|
|
2008
|
+
preflight = await runVramPreflight({
|
|
2009
|
+
model: client.model,
|
|
2010
|
+
force: Boolean(opts?.force),
|
|
2011
|
+
contextLength: runtime?.loadedContextLength || undefined,
|
|
2012
|
+
allowNetwork: false,
|
|
2013
|
+
});
|
|
2014
|
+
} catch {
|
|
2015
|
+
return null;
|
|
2016
|
+
}
|
|
2017
|
+
if (!preflight || preflight.verdict === 'UNKNOWN' || preflight.verdict === 'GREEN') return preflight;
|
|
2018
|
+
|
|
2019
|
+
if (preflight.verdict === 'YELLOW') {
|
|
2020
|
+
if (opts?.verbose) {
|
|
2021
|
+
console.log(` ${C.gold}[WARN] VRAM pre-flight: ${preflight.message} (${preflight.classification.usagePct}% of ${(preflight.hardware.totalBytes / 1024 ** 3).toFixed(1)} GB)${C.reset}`);
|
|
2022
|
+
for (const suggestion of preflight.remediation || []) {
|
|
2023
|
+
console.log(` ${C.dim}↳ ${suggestion.description}${C.reset}`);
|
|
2024
|
+
}
|
|
2025
|
+
console.log('');
|
|
2026
|
+
}
|
|
2027
|
+
return preflight;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
if (alreadyLoaded) {
|
|
2031
|
+
return preflight;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
console.error(`\n ${C.roseGlow}${C.bold}ABORT: VRAM pre-flight failed (RED ZONE).${C.reset}`);
|
|
2035
|
+
console.error(` ${C.white}Estimated footprint ${formatGb(preflight.estimate?.totalBytes)} vs ${formatGb(preflight.hardware?.totalBytes)} available on ${preflight.hardware?.name || 'device'}.${C.reset}`);
|
|
2036
|
+
for (const suggestion of preflight.remediation || []) {
|
|
2037
|
+
console.error(` ${C.amber}↳ ${suggestion.description}${C.reset}`);
|
|
2038
|
+
}
|
|
2039
|
+
if (opts?.force) {
|
|
2040
|
+
console.error(` ${C.gold}--force supplied — continuing despite OOM risk.${C.reset}\n`);
|
|
2041
|
+
return preflight;
|
|
2042
|
+
}
|
|
2043
|
+
console.error(` ${C.dim}Re-run with --force to override (CPU offloading / CUDA OOM risk).${C.reset}\n`);
|
|
2044
|
+
process.exit(4);
|
|
2045
|
+
}
|
|
2046
|
+
|
|
1847
2047
|
async function executeHeadlessQuery(opts, query) {
|
|
1848
2048
|
const startTime = Date.now();
|
|
1849
2049
|
|
|
@@ -1879,6 +2079,8 @@ async function executeHeadlessQuery(opts, query) {
|
|
|
1879
2079
|
throw new Error(modelResolution.error || `Cannot reach model server or no model found for ${client.providerName}`);
|
|
1880
2080
|
}
|
|
1881
2081
|
|
|
2082
|
+
await runOneShotPreflightGate(client, opts);
|
|
2083
|
+
|
|
1882
2084
|
agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
1883
2085
|
await agent.ready();
|
|
1884
2086
|
|
|
@@ -1958,7 +2160,7 @@ async function executeHeadlessQuery(opts, query) {
|
|
|
1958
2160
|
|
|
1959
2161
|
async function runWebChatStandalone(opts) {
|
|
1960
2162
|
const { startWebChatServer, stopWebChatServer, openBrowser } = await import('../webChatServer.js');
|
|
1961
|
-
const port = opts.port || Number(process.env.PORT_WEB_CHAT) ||
|
|
2163
|
+
const port = opts.port || Number(process.env.PORT_WEB_CHAT) || 3002;
|
|
1962
2164
|
|
|
1963
2165
|
const client = new LLMClient({
|
|
1964
2166
|
provider: opts.provider || config.activeProvider,
|
|
@@ -1970,7 +2172,7 @@ async function runWebChatStandalone(opts) {
|
|
|
1970
2172
|
|
|
1971
2173
|
const res = await startWebChatServer({ port, agent });
|
|
1972
2174
|
if (!opts.noOpen) {
|
|
1973
|
-
try { openBrowser(res.url); } catch {
|
|
2175
|
+
try { openBrowser(res.url); } catch { }
|
|
1974
2176
|
}
|
|
1975
2177
|
|
|
1976
2178
|
if (!opts.plain && !opts.noColor) printWelcomeHero();
|
|
@@ -1988,19 +2190,22 @@ async function runWebChatStandalone(opts) {
|
|
|
1988
2190
|
});
|
|
1989
2191
|
|
|
1990
2192
|
const shutdown = async () => {
|
|
1991
|
-
try { await stopWebChatServer(); } catch {
|
|
1992
|
-
try { await agent.endSession?.(); } catch {
|
|
2193
|
+
try { await stopWebChatServer(); } catch { }
|
|
2194
|
+
try { await agent.endSession?.(); } catch { }
|
|
1993
2195
|
console.log(`\n ${C.sakura}Web chatbot stopped • 再见!${C.reset}\n`);
|
|
1994
2196
|
process.exit(0);
|
|
1995
2197
|
};
|
|
1996
2198
|
process.once('SIGINT', shutdown);
|
|
1997
2199
|
process.once('SIGTERM', shutdown);
|
|
1998
2200
|
|
|
1999
|
-
// Keep the process alive until Ctrl+C.
|
|
2000
2201
|
await new Promise(() => {});
|
|
2001
2202
|
}
|
|
2002
2203
|
|
|
2003
2204
|
async function main() {
|
|
2205
|
+
if (process.argv[2] === 'kiku-mcp' || process.argv[2] === 'mcp-server') {
|
|
2206
|
+
await import('../mcp/stdio.js');
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2004
2209
|
const opts = parseCliArgs();
|
|
2005
2210
|
|
|
2006
2211
|
if (!opts.query && !process.stdin.isTTY) {
|
|
@@ -2052,7 +2257,59 @@ async function main() {
|
|
|
2052
2257
|
return;
|
|
2053
2258
|
}
|
|
2054
2259
|
|
|
2260
|
+
if (opts.vram) {
|
|
2261
|
+
const vramSub = typeof opts.vram === 'string' && opts.vram !== 'true' ? opts.vram : 'live';
|
|
2262
|
+
await routeCommand(`/vram ${vramSub}`.trim(), {});
|
|
2263
|
+
process.exit(0);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
if (opts.hub) {
|
|
2267
|
+
const hubFilter = typeof opts.hub === 'string' && opts.hub !== 'true' ? opts.hub : '';
|
|
2268
|
+
await routeCommand(`/hub ${hubFilter}`.trim(), {});
|
|
2269
|
+
process.exit(0);
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
if (opts.eval) {
|
|
2273
|
+
const evalSub = typeof opts.eval === 'string' && opts.eval !== 'true' ? opts.eval : 'ui';
|
|
2274
|
+
await routeCommand(`/eval ${evalSub}`.trim(), {});
|
|
2275
|
+
process.exit(0);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
if (opts.portal) {
|
|
2279
|
+
const portalSub = typeof opts.portal === 'string' && opts.portal !== 'true' ? opts.portal : 'open';
|
|
2280
|
+
await routeCommand(`/portal ${portalSub}`.trim(), {});
|
|
2281
|
+
process.exit(0);
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2055
2284
|
if (opts.query) {
|
|
2285
|
+
const trimmedQuery = opts.query.trim();
|
|
2286
|
+
if (trimmedQuery.startsWith('/')) {
|
|
2287
|
+
const client = new LLMClient({
|
|
2288
|
+
provider: opts.provider || config.activeProvider,
|
|
2289
|
+
baseUrl: opts.url,
|
|
2290
|
+
apiKey: opts.apiKey,
|
|
2291
|
+
model: opts.model,
|
|
2292
|
+
});
|
|
2293
|
+
const agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
2294
|
+
const parts = trimmedQuery.split(/\s+/);
|
|
2295
|
+
const sub = parts[1] || '';
|
|
2296
|
+
const extra = parts.slice(2).join(' ');
|
|
2297
|
+
const cmdCtx = {
|
|
2298
|
+
agent,
|
|
2299
|
+
client,
|
|
2300
|
+
thinkingMode: opts.thinking,
|
|
2301
|
+
setThinkingMode: () => {},
|
|
2302
|
+
theme: getTheme(),
|
|
2303
|
+
setTheme: () => {},
|
|
2304
|
+
audioPlayer,
|
|
2305
|
+
sub,
|
|
2306
|
+
extra,
|
|
2307
|
+
parts,
|
|
2308
|
+
};
|
|
2309
|
+
await routeCommand(trimmedQuery, cmdCtx);
|
|
2310
|
+
process.exit(0);
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2056
2313
|
if (opts.print || opts.output === 'json') {
|
|
2057
2314
|
await executeHeadlessQuery(opts, opts.query);
|
|
2058
2315
|
return;
|
|
@@ -2074,7 +2331,7 @@ async function main() {
|
|
|
2074
2331
|
if (modelResolution.usedFallback) {
|
|
2075
2332
|
console.log(` ${C.amber}Model ${modelResolution.requestedModel} is unavailable; using ${modelResolution.selectedModel}.${C.reset}\n`);
|
|
2076
2333
|
}
|
|
2077
|
-
|
|
2334
|
+
await runOneShotPreflightGate(client, opts);
|
|
2078
2335
|
const agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
2079
2336
|
await executeQueryWithUI(agent, opts.query, agent.session.thinkingMode === 'show', null, 1);
|
|
2080
2337
|
process.exit(0);
|
|
@@ -2200,7 +2457,7 @@ async function main() {
|
|
|
2200
2457
|
status: 'offline',
|
|
2201
2458
|
lines: [
|
|
2202
2459
|
'LM Studio is not running. Free-form chat typing is locked to prevent errors.',
|
|
2203
|
-
'Open LM Studio
|
|
2460
|
+
'Open LM Studio and load a model (e.g. google/gemma-4-e4b) — it will auto-connect once loaded.',
|
|
2204
2461
|
'Offline commands available: /model reconnect, /help, /provider, /doctor, /theme, /exit',
|
|
2205
2462
|
],
|
|
2206
2463
|
});
|
|
@@ -2231,7 +2488,7 @@ async function main() {
|
|
|
2231
2488
|
status: 'warning',
|
|
2232
2489
|
lines: [
|
|
2233
2490
|
modelResolution.error || 'No chat-capable model loaded',
|
|
2234
|
-
'Load a model in LM Studio
|
|
2491
|
+
'Load a model in LM Studio — the CLI will auto-connect as soon as it is loaded in VRAM.',
|
|
2235
2492
|
],
|
|
2236
2493
|
});
|
|
2237
2494
|
catalogOnlyMode = true;
|
|
@@ -2313,6 +2570,10 @@ async function main() {
|
|
|
2313
2570
|
});
|
|
2314
2571
|
}
|
|
2315
2572
|
|
|
2573
|
+
if (!catalogOnlyMode) {
|
|
2574
|
+
await printResourceVerificationPanel(client);
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2316
2577
|
if (agent.restoredMessageCount > 0) {
|
|
2317
2578
|
const s = agent.session;
|
|
2318
2579
|
console.log(`\n ${C.sakura}${C.bold}[SESSION RESTORED]${C.reset} ${C.dim}${s.id} — ${s.messages.length} messages${C.reset}\n`);
|
|
@@ -2348,13 +2609,74 @@ async function main() {
|
|
|
2348
2609
|
}
|
|
2349
2610
|
});
|
|
2350
2611
|
|
|
2612
|
+
let autoReconnectTimer = null;
|
|
2613
|
+
const stopAutoReconnectWatcher = () => {
|
|
2614
|
+
if (autoReconnectTimer) {
|
|
2615
|
+
clearInterval(autoReconnectTimer);
|
|
2616
|
+
autoReconnectTimer = null;
|
|
2617
|
+
}
|
|
2618
|
+
};
|
|
2619
|
+
|
|
2620
|
+
const startAutoReconnectWatcher = () => {
|
|
2621
|
+
if (autoReconnectTimer) return;
|
|
2622
|
+
autoReconnectTimer = setInterval(async () => {
|
|
2623
|
+
if (!catalogOnlyMode && tuiPrompt.isConnected) {
|
|
2624
|
+
stopAutoReconnectWatcher();
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
if (isProcessing) return;
|
|
2628
|
+
|
|
2629
|
+
try {
|
|
2630
|
+
const resolution = modelManager
|
|
2631
|
+
? await modelManager.resolve()
|
|
2632
|
+
: await client.checkConnection();
|
|
2633
|
+
|
|
2634
|
+
if (resolution?.connected && (resolution.selectedModel || resolution.activeLoadedModel)) {
|
|
2635
|
+
const detectedModel = resolution.selectedModel || resolution.activeLoadedModel;
|
|
2636
|
+
catalogOnlyMode = false;
|
|
2637
|
+
tuiPrompt.isConnected = true;
|
|
2638
|
+
tuiPrompt.modelName = detectedModel;
|
|
2639
|
+
tuiPrompt.render();
|
|
2640
|
+
stopAutoReconnectWatcher();
|
|
2641
|
+
|
|
2642
|
+
printRichCard({
|
|
2643
|
+
title: 'Model Auto-Connected',
|
|
2644
|
+
status: 'connected',
|
|
2645
|
+
lines: [
|
|
2646
|
+
`• Provider: ${client.providerName}${client.provider === 'lmstudio' ? ' (Local)' : ''} • Model: ${detectedModel}`,
|
|
2647
|
+
'• Auto-detected model in VRAM: chat is ready without requiring /model reconnect.',
|
|
2648
|
+
],
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
} catch {
|
|
2652
|
+
|
|
2653
|
+
}
|
|
2654
|
+
}, 2500);
|
|
2655
|
+
|
|
2656
|
+
if (autoReconnectTimer?.unref) {
|
|
2657
|
+
autoReconnectTimer.unref();
|
|
2658
|
+
}
|
|
2659
|
+
};
|
|
2660
|
+
|
|
2351
2661
|
const cmdCtx = {
|
|
2352
2662
|
tuiPrompt,
|
|
2353
2663
|
agent,
|
|
2354
2664
|
client,
|
|
2355
2665
|
baseUrl,
|
|
2356
2666
|
modelManager,
|
|
2357
|
-
catalogOnlyMode,
|
|
2667
|
+
get catalogOnlyMode() { return catalogOnlyMode; },
|
|
2668
|
+
set catalogOnlyMode(value) {
|
|
2669
|
+
catalogOnlyMode = Boolean(value);
|
|
2670
|
+
if (tuiPrompt) {
|
|
2671
|
+
tuiPrompt.isConnected = !catalogOnlyMode;
|
|
2672
|
+
tuiPrompt.render();
|
|
2673
|
+
}
|
|
2674
|
+
if (catalogOnlyMode) {
|
|
2675
|
+
startAutoReconnectWatcher();
|
|
2676
|
+
} else {
|
|
2677
|
+
stopAutoReconnectWatcher();
|
|
2678
|
+
}
|
|
2679
|
+
},
|
|
2358
2680
|
setCatalogOnlyMode: value => {
|
|
2359
2681
|
catalogOnlyMode = Boolean(value);
|
|
2360
2682
|
if (tuiPrompt) {
|
|
@@ -2407,6 +2729,10 @@ async function main() {
|
|
|
2407
2729
|
},
|
|
2408
2730
|
};
|
|
2409
2731
|
|
|
2732
|
+
if (catalogOnlyMode) {
|
|
2733
|
+
startAutoReconnectWatcher();
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2410
2736
|
const triggerQueueProcessing = () => {
|
|
2411
2737
|
if (isProcessing || queryQueue.length === 0) return;
|
|
2412
2738
|
isProcessing = true;
|
|
@@ -2420,6 +2746,18 @@ async function main() {
|
|
|
2420
2746
|
activeAbortController = new AbortController();
|
|
2421
2747
|
|
|
2422
2748
|
try {
|
|
2749
|
+
if (catalogOnlyMode && modelManager) {
|
|
2750
|
+
try {
|
|
2751
|
+
const autoProbe = await modelManager.resolve();
|
|
2752
|
+
if (autoProbe?.connected && (autoProbe.selectedModel || autoProbe.activeLoadedModel)) {
|
|
2753
|
+
cmdCtx.setCatalogOnlyMode(false);
|
|
2754
|
+
if (tuiPrompt) {
|
|
2755
|
+
tuiPrompt.modelName = autoProbe.selectedModel || autoProbe.activeLoadedModel;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
} catch {}
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2423
2761
|
if (catalogOnlyMode) {
|
|
2424
2762
|
if (nextItem.prompt.startsWith('@')) {
|
|
2425
2763
|
const targetTool = nextItem.prompt.slice(1).split(/\s+/)[0];
|
|
@@ -2480,6 +2818,48 @@ async function main() {
|
|
|
2480
2818
|
})();
|
|
2481
2819
|
};
|
|
2482
2820
|
|
|
2821
|
+
const IMMEDIATE_COMMAND_NAMES = new Set([
|
|
2822
|
+
'/help', '/?', '/menu', '/commands',
|
|
2823
|
+
'/cancel', '/abort', '/stop',
|
|
2824
|
+
'/version', '/ver', '/v',
|
|
2825
|
+
'/status', '/doctor', '/about', '/info',
|
|
2826
|
+
'/jobs', '/job',
|
|
2827
|
+
'/theme', '/themes',
|
|
2828
|
+
'/ping', '/health',
|
|
2829
|
+
'/system', '/sys', '/os',
|
|
2830
|
+
'/gpu', '/gpus',
|
|
2831
|
+
'/vram', '/resource',
|
|
2832
|
+
'/details', '/detail', '/d',
|
|
2833
|
+
'/copy', '/cp', '/clip', '/clipboard',
|
|
2834
|
+
'/prompt', '/persona', '/sysprompt',
|
|
2835
|
+
'/config', '/settings', '/conf',
|
|
2836
|
+
'/sources', '/src', '/srcs',
|
|
2837
|
+
'/queue',
|
|
2838
|
+
'/ads', '/sponsor', '/ad',
|
|
2839
|
+
]);
|
|
2840
|
+
|
|
2841
|
+
function isImmediateCommand(rawInput) {
|
|
2842
|
+
const trimmed = (rawInput || '').trim();
|
|
2843
|
+
if (!trimmed.startsWith('/')) return false;
|
|
2844
|
+
const parts = trimmed.split(/\s+/);
|
|
2845
|
+
const base = parts[0].toLowerCase();
|
|
2846
|
+
const sub = (parts[1] || '').toLowerCase();
|
|
2847
|
+
if (IMMEDIATE_COMMAND_NAMES.has(base)) return true;
|
|
2848
|
+
if (base === '/research' && ['status', 'sources', 'cancel', 'last', 'workers'].includes(sub)) return true;
|
|
2849
|
+
if (base === '/subagent' && ['status', 'last', 'tools'].includes(sub)) return true;
|
|
2850
|
+
if (base === '/debate' && ['status', 'last'].includes(sub)) return true;
|
|
2851
|
+
if (base === '/favorites' && ['list', 'show', 'export'].includes(sub)) return true;
|
|
2852
|
+
if (base === '/history' && (!sub || ['list', 'summary', 'search'].includes(sub))) return true;
|
|
2853
|
+
if (base === '/session' && (!sub || ['info', 'list', 'summary'].includes(sub))) return true;
|
|
2854
|
+
if (base === '/lang' && !sub) return true;
|
|
2855
|
+
if (base === '/thinking' && !sub) return true;
|
|
2856
|
+
if (base === '/model' && (!sub || sub === 'status' || sub === 'list' || sub === 'recommend')) return true;
|
|
2857
|
+
if (base === '/provider' && (!sub || sub === 'list' || sub === 'status' || sub === 'info')) return true;
|
|
2858
|
+
if (base === '/mcp' && (!sub || sub === 'status' || sub === 'list' || sub === 'tools')) return true;
|
|
2859
|
+
if (base === '/env' && (!sub || sub === 'get' || sub === 'path')) return true;
|
|
2860
|
+
return false;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2483
2863
|
while (true) {
|
|
2484
2864
|
let rawInput;
|
|
2485
2865
|
|
|
@@ -2502,10 +2882,38 @@ async function main() {
|
|
|
2502
2882
|
const lower = trimmed.toLowerCase();
|
|
2503
2883
|
if (lower === '/cancel' || lower === '/abort' || (lower === '/stop' && isProcessing)) {
|
|
2504
2884
|
cancelActiveQuery();
|
|
2885
|
+
printRichCard({
|
|
2886
|
+
title: 'Cancelled',
|
|
2887
|
+
status: 'info',
|
|
2888
|
+
lines: ['• Active operation cancelled by user.'],
|
|
2889
|
+
});
|
|
2505
2890
|
continue;
|
|
2506
2891
|
}
|
|
2507
2892
|
|
|
2508
2893
|
if (isProcessing) {
|
|
2894
|
+
if (isImmediateCommand(trimmed)) {
|
|
2895
|
+
const parts = trimmed.split(/\s+/);
|
|
2896
|
+
const sub = parts[1] || '';
|
|
2897
|
+
const extra = parts.slice(2).join(' ');
|
|
2898
|
+
try {
|
|
2899
|
+
await routeCommand(trimmed, {
|
|
2900
|
+
...cmdCtx,
|
|
2901
|
+
sub,
|
|
2902
|
+
extra,
|
|
2903
|
+
parts,
|
|
2904
|
+
});
|
|
2905
|
+
} catch (cmdErr) {
|
|
2906
|
+
if (!isAbortError(cmdErr)) {
|
|
2907
|
+
printRichCard({
|
|
2908
|
+
title: 'Command Error',
|
|
2909
|
+
status: 'error',
|
|
2910
|
+
lines: [cmdErr.message || String(cmdErr)],
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
continue;
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2509
2917
|
deferredCommands.push(trimmed);
|
|
2510
2918
|
printRichCard({
|
|
2511
2919
|
title: 'Command Queued',
|
|
@@ -2519,45 +2927,71 @@ async function main() {
|
|
|
2519
2927
|
const sub = parts[1] || '';
|
|
2520
2928
|
const extra = parts.slice(2).join(' ');
|
|
2521
2929
|
|
|
2522
|
-
|
|
2930
|
+
console.log(`\n ${C.dim}❯ ${trimmed}${C.reset}`);
|
|
2931
|
+
cmdCtx.setIsProcessing(true, `Running ${parts[0]}`);
|
|
2523
2932
|
activeAbortController = new AbortController();
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
if (!isAbortError(cmdErr)) {
|
|
2534
|
-
printRichCard({
|
|
2535
|
-
title: 'Command Error',
|
|
2536
|
-
status: 'error',
|
|
2537
|
-
lines: [cmdErr.message || String(cmdErr)],
|
|
2933
|
+
|
|
2934
|
+
(async () => {
|
|
2935
|
+
try {
|
|
2936
|
+
const result = await routeCommand(trimmed, {
|
|
2937
|
+
...cmdCtx,
|
|
2938
|
+
signal: activeAbortController.signal,
|
|
2939
|
+
sub,
|
|
2940
|
+
extra,
|
|
2941
|
+
parts,
|
|
2538
2942
|
});
|
|
2943
|
+
if (result === 'EXIT') {
|
|
2944
|
+
cancelActiveQuery();
|
|
2945
|
+
activeAbortController = null;
|
|
2946
|
+
cmdCtx.setIsProcessing(false);
|
|
2947
|
+
tuiPrompt.abortActiveRead(null);
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
if (result === 'RETRY' && cmdCtx.lastQuery) {
|
|
2951
|
+
const retryNumber = nextQueryNumber++;
|
|
2952
|
+
printSolidPanel({
|
|
2953
|
+
title: `${C.roseGlow}${C.bold}Question ${retryNumber} • Retry${C.reset}`,
|
|
2954
|
+
lines: wrapText(cmdCtx.lastQuery, getFullWidth() - 8)
|
|
2955
|
+
.map(wLine => `${C.cyberCyan}❯${C.reset} ${C.white}${wLine}${C.reset}`),
|
|
2956
|
+
bg: C.bgPanel
|
|
2957
|
+
});
|
|
2958
|
+
queryQueue.push({ id: 'q_' + Date.now(), number: retryNumber, prompt: cmdCtx.lastQuery, addedAt: Date.now() });
|
|
2959
|
+
triggerQueueProcessing();
|
|
2960
|
+
}
|
|
2961
|
+
} catch (cmdErr) {
|
|
2962
|
+
if (!isAbortError(cmdErr)) {
|
|
2963
|
+
printRichCard({
|
|
2964
|
+
title: 'Command Error',
|
|
2965
|
+
status: 'error',
|
|
2966
|
+
lines: [cmdErr.message || String(cmdErr)],
|
|
2967
|
+
});
|
|
2968
|
+
}
|
|
2969
|
+
} finally {
|
|
2970
|
+
activeAbortController = null;
|
|
2971
|
+
cmdCtx.setIsProcessing(false);
|
|
2972
|
+
if (deferredCommands.length > 0) {
|
|
2973
|
+
const nextCmd = deferredCommands.shift();
|
|
2974
|
+
printRichCard({
|
|
2975
|
+
title: 'Deferred Command Executed',
|
|
2976
|
+
status: 'info',
|
|
2977
|
+
lines: [nextCmd],
|
|
2978
|
+
});
|
|
2979
|
+
setTimeout(async () => {
|
|
2980
|
+
try {
|
|
2981
|
+
const defParts = nextCmd.split(/\s+/);
|
|
2982
|
+
await routeCommand(nextCmd, {
|
|
2983
|
+
...cmdCtx,
|
|
2984
|
+
parts: defParts,
|
|
2985
|
+
sub: defParts[1] || '',
|
|
2986
|
+
extra: defParts.slice(2).join(' '),
|
|
2987
|
+
});
|
|
2988
|
+
} catch (err) {
|
|
2989
|
+
printRichCard({ title: 'Deferred Command Error', status: 'error', lines: [err.message || String(err)] });
|
|
2990
|
+
}
|
|
2991
|
+
}, 10);
|
|
2992
|
+
}
|
|
2539
2993
|
}
|
|
2540
|
-
|
|
2541
|
-
} finally {
|
|
2542
|
-
activeAbortController = null;
|
|
2543
|
-
cmdCtx.setIsProcessing(false);
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
|
-
if (result === 'EXIT') {
|
|
2547
|
-
cancelActiveQuery();
|
|
2548
|
-
break;
|
|
2549
|
-
}
|
|
2550
|
-
if (result === 'RETRY' && cmdCtx.lastQuery) {
|
|
2551
|
-
const retryNumber = nextQueryNumber++;
|
|
2552
|
-
printSolidPanel({
|
|
2553
|
-
title: `${C.roseGlow}${C.bold}Question ${retryNumber} • Retry${C.reset}`,
|
|
2554
|
-
lines: wrapText(cmdCtx.lastQuery, getFullWidth() - 8)
|
|
2555
|
-
.map(wLine => `${C.cyberCyan}❯${C.reset} ${C.white}${wLine}${C.reset}`),
|
|
2556
|
-
bg: C.bgPanel
|
|
2557
|
-
});
|
|
2558
|
-
queryQueue.push({ id: 'q_' + Date.now(), number: retryNumber, prompt: cmdCtx.lastQuery, addedAt: Date.now() });
|
|
2559
|
-
triggerQueueProcessing();
|
|
2560
|
-
}
|
|
2994
|
+
})();
|
|
2561
2995
|
continue;
|
|
2562
2996
|
}
|
|
2563
2997
|
|
|
@@ -2612,22 +3046,46 @@ async function main() {
|
|
|
2612
3046
|
}
|
|
2613
3047
|
|
|
2614
3048
|
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
3049
|
+
|
|
3050
|
+
try {
|
|
3051
|
+
const autoProbe = modelManager
|
|
3052
|
+
? await modelManager.resolve()
|
|
3053
|
+
: await client.checkConnection();
|
|
3054
|
+
if (autoProbe?.connected && (autoProbe.selectedModel || autoProbe.activeLoadedModel)) {
|
|
3055
|
+
catalogOnlyMode = false;
|
|
3056
|
+
tuiPrompt.isConnected = true;
|
|
3057
|
+
tuiPrompt.modelName = autoProbe.selectedModel || autoProbe.activeLoadedModel;
|
|
3058
|
+
tuiPrompt.render();
|
|
3059
|
+
stopAutoReconnectWatcher();
|
|
3060
|
+
printRichCard({
|
|
3061
|
+
title: 'Model Auto-Connected',
|
|
3062
|
+
status: 'connected',
|
|
3063
|
+
lines: [
|
|
3064
|
+
`• Provider: ${client.providerName}${client.provider === 'lmstudio' ? ' (Local)' : ''} • Model: ${tuiPrompt.modelName}`,
|
|
3065
|
+
'• Loaded model detected in VRAM: chat typing unlocked.',
|
|
3066
|
+
],
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
} catch {}
|
|
3070
|
+
|
|
3071
|
+
if (catalogOnlyMode || !tuiPrompt.isConnected) {
|
|
3072
|
+
printSolidPanel({
|
|
3073
|
+
title: 'AI Chat Locked • LM Studio Offline',
|
|
3074
|
+
lines: [
|
|
3075
|
+
`${C.bold}${C.amber}LM Studio is not running or connected.${C.reset}`,
|
|
3076
|
+
`${C.white}Chat query typing is disabled until the AI server is active.${C.reset}`,
|
|
3077
|
+
'',
|
|
3078
|
+
`${C.bold}${C.white}How to unlock & connect:${C.reset}`,
|
|
3079
|
+
` 1. Open ${C.cyberCyan}LM Studio${C.reset} on your computer`,
|
|
3080
|
+
` 2. Load model ${C.gold}${opts.model || client.model || 'google/gemma-4-e4b'}${C.reset} and ensure the local server is running (port 1234)`,
|
|
3081
|
+
` 3. The agent will auto-connect automatically (or type ${C.cyberCyan}/model reconnect${C.reset})`,
|
|
3082
|
+
'',
|
|
3083
|
+
`${C.dim}Offline commands available: /model reconnect, /help, /provider, /doctor, /theme, /exit${C.reset}`,
|
|
3084
|
+
],
|
|
3085
|
+
bg: C.bgPanel,
|
|
3086
|
+
});
|
|
3087
|
+
continue;
|
|
3088
|
+
}
|
|
2631
3089
|
}
|
|
2632
3090
|
|
|
2633
3091
|
const { sanitized, warnings } = sanitizeQuery(trimmed);
|
|
@@ -2653,6 +3111,7 @@ async function main() {
|
|
|
2653
3111
|
triggerQueueProcessing();
|
|
2654
3112
|
}
|
|
2655
3113
|
|
|
3114
|
+
stopAutoReconnectWatcher();
|
|
2656
3115
|
if (globalTuiPrompt) {
|
|
2657
3116
|
globalTuiPrompt.dispose();
|
|
2658
3117
|
globalTuiPrompt = null;
|