kiku-agent 1.2.0 → 1.3.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 +459 -407
- package/package.json +27 -3
- package/server/agent/agentCore.js +635 -267
- package/server/agent/appPaths.js +2 -0
- package/server/agent/catalogEntityResolver.js +629 -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 +528 -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/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.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.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/youtubeAuth.js +82 -73
- package/server/agent/youtubeTranscript.js +209 -0
- package/server/apiRouter.js +383 -179
- 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/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,15 +2190,14 @@ 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
|
|
|
@@ -2052,7 +2253,59 @@ async function main() {
|
|
|
2052
2253
|
return;
|
|
2053
2254
|
}
|
|
2054
2255
|
|
|
2256
|
+
if (opts.vram) {
|
|
2257
|
+
const vramSub = typeof opts.vram === 'string' && opts.vram !== 'true' ? opts.vram : 'live';
|
|
2258
|
+
await routeCommand(`/vram ${vramSub}`.trim(), {});
|
|
2259
|
+
process.exit(0);
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
if (opts.hub) {
|
|
2263
|
+
const hubFilter = typeof opts.hub === 'string' && opts.hub !== 'true' ? opts.hub : '';
|
|
2264
|
+
await routeCommand(`/hub ${hubFilter}`.trim(), {});
|
|
2265
|
+
process.exit(0);
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
if (opts.eval) {
|
|
2269
|
+
const evalSub = typeof opts.eval === 'string' && opts.eval !== 'true' ? opts.eval : 'ui';
|
|
2270
|
+
await routeCommand(`/eval ${evalSub}`.trim(), {});
|
|
2271
|
+
process.exit(0);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
if (opts.portal) {
|
|
2275
|
+
const portalSub = typeof opts.portal === 'string' && opts.portal !== 'true' ? opts.portal : 'open';
|
|
2276
|
+
await routeCommand(`/portal ${portalSub}`.trim(), {});
|
|
2277
|
+
process.exit(0);
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2055
2280
|
if (opts.query) {
|
|
2281
|
+
const trimmedQuery = opts.query.trim();
|
|
2282
|
+
if (trimmedQuery.startsWith('/')) {
|
|
2283
|
+
const client = new LLMClient({
|
|
2284
|
+
provider: opts.provider || config.activeProvider,
|
|
2285
|
+
baseUrl: opts.url,
|
|
2286
|
+
apiKey: opts.apiKey,
|
|
2287
|
+
model: opts.model,
|
|
2288
|
+
});
|
|
2289
|
+
const agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
2290
|
+
const parts = trimmedQuery.split(/\s+/);
|
|
2291
|
+
const sub = parts[1] || '';
|
|
2292
|
+
const extra = parts.slice(2).join(' ');
|
|
2293
|
+
const cmdCtx = {
|
|
2294
|
+
agent,
|
|
2295
|
+
client,
|
|
2296
|
+
thinkingMode: opts.thinking,
|
|
2297
|
+
setThinkingMode: () => {},
|
|
2298
|
+
theme: getTheme(),
|
|
2299
|
+
setTheme: () => {},
|
|
2300
|
+
audioPlayer,
|
|
2301
|
+
sub,
|
|
2302
|
+
extra,
|
|
2303
|
+
parts,
|
|
2304
|
+
};
|
|
2305
|
+
await routeCommand(trimmedQuery, cmdCtx);
|
|
2306
|
+
process.exit(0);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2056
2309
|
if (opts.print || opts.output === 'json') {
|
|
2057
2310
|
await executeHeadlessQuery(opts, opts.query);
|
|
2058
2311
|
return;
|
|
@@ -2074,7 +2327,7 @@ async function main() {
|
|
|
2074
2327
|
if (modelResolution.usedFallback) {
|
|
2075
2328
|
console.log(` ${C.amber}Model ${modelResolution.requestedModel} is unavailable; using ${modelResolution.selectedModel}.${C.reset}\n`);
|
|
2076
2329
|
}
|
|
2077
|
-
|
|
2330
|
+
await runOneShotPreflightGate(client, opts);
|
|
2078
2331
|
const agent = new KikuAgent({ client, thinkingMode: opts.thinking });
|
|
2079
2332
|
await executeQueryWithUI(agent, opts.query, agent.session.thinkingMode === 'show', null, 1);
|
|
2080
2333
|
process.exit(0);
|
|
@@ -2200,7 +2453,7 @@ async function main() {
|
|
|
2200
2453
|
status: 'offline',
|
|
2201
2454
|
lines: [
|
|
2202
2455
|
'LM Studio is not running. Free-form chat typing is locked to prevent errors.',
|
|
2203
|
-
'Open LM Studio
|
|
2456
|
+
'Open LM Studio and load a model (e.g. google/gemma-4-e4b) — it will auto-connect once loaded.',
|
|
2204
2457
|
'Offline commands available: /model reconnect, /help, /provider, /doctor, /theme, /exit',
|
|
2205
2458
|
],
|
|
2206
2459
|
});
|
|
@@ -2231,7 +2484,7 @@ async function main() {
|
|
|
2231
2484
|
status: 'warning',
|
|
2232
2485
|
lines: [
|
|
2233
2486
|
modelResolution.error || 'No chat-capable model loaded',
|
|
2234
|
-
'Load a model in LM Studio
|
|
2487
|
+
'Load a model in LM Studio — the CLI will auto-connect as soon as it is loaded in VRAM.',
|
|
2235
2488
|
],
|
|
2236
2489
|
});
|
|
2237
2490
|
catalogOnlyMode = true;
|
|
@@ -2313,6 +2566,10 @@ async function main() {
|
|
|
2313
2566
|
});
|
|
2314
2567
|
}
|
|
2315
2568
|
|
|
2569
|
+
if (!catalogOnlyMode) {
|
|
2570
|
+
await printResourceVerificationPanel(client);
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2316
2573
|
if (agent.restoredMessageCount > 0) {
|
|
2317
2574
|
const s = agent.session;
|
|
2318
2575
|
console.log(`\n ${C.sakura}${C.bold}[SESSION RESTORED]${C.reset} ${C.dim}${s.id} — ${s.messages.length} messages${C.reset}\n`);
|
|
@@ -2348,13 +2605,74 @@ async function main() {
|
|
|
2348
2605
|
}
|
|
2349
2606
|
});
|
|
2350
2607
|
|
|
2608
|
+
let autoReconnectTimer = null;
|
|
2609
|
+
const stopAutoReconnectWatcher = () => {
|
|
2610
|
+
if (autoReconnectTimer) {
|
|
2611
|
+
clearInterval(autoReconnectTimer);
|
|
2612
|
+
autoReconnectTimer = null;
|
|
2613
|
+
}
|
|
2614
|
+
};
|
|
2615
|
+
|
|
2616
|
+
const startAutoReconnectWatcher = () => {
|
|
2617
|
+
if (autoReconnectTimer) return;
|
|
2618
|
+
autoReconnectTimer = setInterval(async () => {
|
|
2619
|
+
if (!catalogOnlyMode && tuiPrompt.isConnected) {
|
|
2620
|
+
stopAutoReconnectWatcher();
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
if (isProcessing) return;
|
|
2624
|
+
|
|
2625
|
+
try {
|
|
2626
|
+
const resolution = modelManager
|
|
2627
|
+
? await modelManager.resolve()
|
|
2628
|
+
: await client.checkConnection();
|
|
2629
|
+
|
|
2630
|
+
if (resolution?.connected && (resolution.selectedModel || resolution.activeLoadedModel)) {
|
|
2631
|
+
const detectedModel = resolution.selectedModel || resolution.activeLoadedModel;
|
|
2632
|
+
catalogOnlyMode = false;
|
|
2633
|
+
tuiPrompt.isConnected = true;
|
|
2634
|
+
tuiPrompt.modelName = detectedModel;
|
|
2635
|
+
tuiPrompt.render();
|
|
2636
|
+
stopAutoReconnectWatcher();
|
|
2637
|
+
|
|
2638
|
+
printRichCard({
|
|
2639
|
+
title: 'Model Auto-Connected',
|
|
2640
|
+
status: 'connected',
|
|
2641
|
+
lines: [
|
|
2642
|
+
`• Provider: ${client.providerName}${client.provider === 'lmstudio' ? ' (Local)' : ''} • Model: ${detectedModel}`,
|
|
2643
|
+
'• Auto-detected model in VRAM: chat is ready without requiring /model reconnect.',
|
|
2644
|
+
],
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
} catch {
|
|
2648
|
+
|
|
2649
|
+
}
|
|
2650
|
+
}, 2500);
|
|
2651
|
+
|
|
2652
|
+
if (autoReconnectTimer?.unref) {
|
|
2653
|
+
autoReconnectTimer.unref();
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
|
|
2351
2657
|
const cmdCtx = {
|
|
2352
2658
|
tuiPrompt,
|
|
2353
2659
|
agent,
|
|
2354
2660
|
client,
|
|
2355
2661
|
baseUrl,
|
|
2356
2662
|
modelManager,
|
|
2357
|
-
catalogOnlyMode,
|
|
2663
|
+
get catalogOnlyMode() { return catalogOnlyMode; },
|
|
2664
|
+
set catalogOnlyMode(value) {
|
|
2665
|
+
catalogOnlyMode = Boolean(value);
|
|
2666
|
+
if (tuiPrompt) {
|
|
2667
|
+
tuiPrompt.isConnected = !catalogOnlyMode;
|
|
2668
|
+
tuiPrompt.render();
|
|
2669
|
+
}
|
|
2670
|
+
if (catalogOnlyMode) {
|
|
2671
|
+
startAutoReconnectWatcher();
|
|
2672
|
+
} else {
|
|
2673
|
+
stopAutoReconnectWatcher();
|
|
2674
|
+
}
|
|
2675
|
+
},
|
|
2358
2676
|
setCatalogOnlyMode: value => {
|
|
2359
2677
|
catalogOnlyMode = Boolean(value);
|
|
2360
2678
|
if (tuiPrompt) {
|
|
@@ -2407,6 +2725,10 @@ async function main() {
|
|
|
2407
2725
|
},
|
|
2408
2726
|
};
|
|
2409
2727
|
|
|
2728
|
+
if (catalogOnlyMode) {
|
|
2729
|
+
startAutoReconnectWatcher();
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2410
2732
|
const triggerQueueProcessing = () => {
|
|
2411
2733
|
if (isProcessing || queryQueue.length === 0) return;
|
|
2412
2734
|
isProcessing = true;
|
|
@@ -2420,6 +2742,18 @@ async function main() {
|
|
|
2420
2742
|
activeAbortController = new AbortController();
|
|
2421
2743
|
|
|
2422
2744
|
try {
|
|
2745
|
+
if (catalogOnlyMode && modelManager) {
|
|
2746
|
+
try {
|
|
2747
|
+
const autoProbe = await modelManager.resolve();
|
|
2748
|
+
if (autoProbe?.connected && (autoProbe.selectedModel || autoProbe.activeLoadedModel)) {
|
|
2749
|
+
cmdCtx.setCatalogOnlyMode(false);
|
|
2750
|
+
if (tuiPrompt) {
|
|
2751
|
+
tuiPrompt.modelName = autoProbe.selectedModel || autoProbe.activeLoadedModel;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
} catch {}
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2423
2757
|
if (catalogOnlyMode) {
|
|
2424
2758
|
if (nextItem.prompt.startsWith('@')) {
|
|
2425
2759
|
const targetTool = nextItem.prompt.slice(1).split(/\s+/)[0];
|
|
@@ -2480,6 +2814,48 @@ async function main() {
|
|
|
2480
2814
|
})();
|
|
2481
2815
|
};
|
|
2482
2816
|
|
|
2817
|
+
const IMMEDIATE_COMMAND_NAMES = new Set([
|
|
2818
|
+
'/help', '/?', '/menu', '/commands',
|
|
2819
|
+
'/cancel', '/abort', '/stop',
|
|
2820
|
+
'/version', '/ver', '/v',
|
|
2821
|
+
'/status', '/doctor', '/about', '/info',
|
|
2822
|
+
'/jobs', '/job',
|
|
2823
|
+
'/theme', '/themes',
|
|
2824
|
+
'/ping', '/health',
|
|
2825
|
+
'/system', '/sys', '/os',
|
|
2826
|
+
'/gpu', '/gpus',
|
|
2827
|
+
'/vram', '/resource',
|
|
2828
|
+
'/details', '/detail', '/d',
|
|
2829
|
+
'/copy', '/cp', '/clip', '/clipboard',
|
|
2830
|
+
'/prompt', '/persona', '/sysprompt',
|
|
2831
|
+
'/config', '/settings', '/conf',
|
|
2832
|
+
'/sources', '/src', '/srcs',
|
|
2833
|
+
'/queue',
|
|
2834
|
+
'/ads', '/sponsor', '/ad',
|
|
2835
|
+
]);
|
|
2836
|
+
|
|
2837
|
+
function isImmediateCommand(rawInput) {
|
|
2838
|
+
const trimmed = (rawInput || '').trim();
|
|
2839
|
+
if (!trimmed.startsWith('/')) return false;
|
|
2840
|
+
const parts = trimmed.split(/\s+/);
|
|
2841
|
+
const base = parts[0].toLowerCase();
|
|
2842
|
+
const sub = (parts[1] || '').toLowerCase();
|
|
2843
|
+
if (IMMEDIATE_COMMAND_NAMES.has(base)) return true;
|
|
2844
|
+
if (base === '/research' && ['status', 'sources', 'cancel', 'last', 'workers'].includes(sub)) return true;
|
|
2845
|
+
if (base === '/subagent' && ['status', 'last', 'tools'].includes(sub)) return true;
|
|
2846
|
+
if (base === '/debate' && ['status', 'last'].includes(sub)) return true;
|
|
2847
|
+
if (base === '/favorites' && ['list', 'show', 'export'].includes(sub)) return true;
|
|
2848
|
+
if (base === '/history' && (!sub || ['list', 'summary', 'search'].includes(sub))) return true;
|
|
2849
|
+
if (base === '/session' && (!sub || ['info', 'list', 'summary'].includes(sub))) return true;
|
|
2850
|
+
if (base === '/lang' && !sub) return true;
|
|
2851
|
+
if (base === '/thinking' && !sub) return true;
|
|
2852
|
+
if (base === '/model' && (!sub || sub === 'status' || sub === 'list' || sub === 'recommend')) return true;
|
|
2853
|
+
if (base === '/provider' && (!sub || sub === 'list' || sub === 'status' || sub === 'info')) return true;
|
|
2854
|
+
if (base === '/mcp' && (!sub || sub === 'status' || sub === 'list' || sub === 'tools')) return true;
|
|
2855
|
+
if (base === '/env' && (!sub || sub === 'get' || sub === 'path')) return true;
|
|
2856
|
+
return false;
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2483
2859
|
while (true) {
|
|
2484
2860
|
let rawInput;
|
|
2485
2861
|
|
|
@@ -2502,10 +2878,38 @@ async function main() {
|
|
|
2502
2878
|
const lower = trimmed.toLowerCase();
|
|
2503
2879
|
if (lower === '/cancel' || lower === '/abort' || (lower === '/stop' && isProcessing)) {
|
|
2504
2880
|
cancelActiveQuery();
|
|
2881
|
+
printRichCard({
|
|
2882
|
+
title: 'Cancelled',
|
|
2883
|
+
status: 'info',
|
|
2884
|
+
lines: ['• Active operation cancelled by user.'],
|
|
2885
|
+
});
|
|
2505
2886
|
continue;
|
|
2506
2887
|
}
|
|
2507
2888
|
|
|
2508
2889
|
if (isProcessing) {
|
|
2890
|
+
if (isImmediateCommand(trimmed)) {
|
|
2891
|
+
const parts = trimmed.split(/\s+/);
|
|
2892
|
+
const sub = parts[1] || '';
|
|
2893
|
+
const extra = parts.slice(2).join(' ');
|
|
2894
|
+
try {
|
|
2895
|
+
await routeCommand(trimmed, {
|
|
2896
|
+
...cmdCtx,
|
|
2897
|
+
sub,
|
|
2898
|
+
extra,
|
|
2899
|
+
parts,
|
|
2900
|
+
});
|
|
2901
|
+
} catch (cmdErr) {
|
|
2902
|
+
if (!isAbortError(cmdErr)) {
|
|
2903
|
+
printRichCard({
|
|
2904
|
+
title: 'Command Error',
|
|
2905
|
+
status: 'error',
|
|
2906
|
+
lines: [cmdErr.message || String(cmdErr)],
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
continue;
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2509
2913
|
deferredCommands.push(trimmed);
|
|
2510
2914
|
printRichCard({
|
|
2511
2915
|
title: 'Command Queued',
|
|
@@ -2519,45 +2923,71 @@ async function main() {
|
|
|
2519
2923
|
const sub = parts[1] || '';
|
|
2520
2924
|
const extra = parts.slice(2).join(' ');
|
|
2521
2925
|
|
|
2522
|
-
|
|
2926
|
+
console.log(`\n ${C.dim}❯ ${trimmed}${C.reset}`);
|
|
2927
|
+
cmdCtx.setIsProcessing(true, `Running ${parts[0]}`);
|
|
2523
2928
|
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)],
|
|
2929
|
+
|
|
2930
|
+
(async () => {
|
|
2931
|
+
try {
|
|
2932
|
+
const result = await routeCommand(trimmed, {
|
|
2933
|
+
...cmdCtx,
|
|
2934
|
+
signal: activeAbortController.signal,
|
|
2935
|
+
sub,
|
|
2936
|
+
extra,
|
|
2937
|
+
parts,
|
|
2538
2938
|
});
|
|
2939
|
+
if (result === 'EXIT') {
|
|
2940
|
+
cancelActiveQuery();
|
|
2941
|
+
activeAbortController = null;
|
|
2942
|
+
cmdCtx.setIsProcessing(false);
|
|
2943
|
+
tuiPrompt.abortActiveRead(null);
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
if (result === 'RETRY' && cmdCtx.lastQuery) {
|
|
2947
|
+
const retryNumber = nextQueryNumber++;
|
|
2948
|
+
printSolidPanel({
|
|
2949
|
+
title: `${C.roseGlow}${C.bold}Question ${retryNumber} • Retry${C.reset}`,
|
|
2950
|
+
lines: wrapText(cmdCtx.lastQuery, getFullWidth() - 8)
|
|
2951
|
+
.map(wLine => `${C.cyberCyan}❯${C.reset} ${C.white}${wLine}${C.reset}`),
|
|
2952
|
+
bg: C.bgPanel
|
|
2953
|
+
});
|
|
2954
|
+
queryQueue.push({ id: 'q_' + Date.now(), number: retryNumber, prompt: cmdCtx.lastQuery, addedAt: Date.now() });
|
|
2955
|
+
triggerQueueProcessing();
|
|
2956
|
+
}
|
|
2957
|
+
} catch (cmdErr) {
|
|
2958
|
+
if (!isAbortError(cmdErr)) {
|
|
2959
|
+
printRichCard({
|
|
2960
|
+
title: 'Command Error',
|
|
2961
|
+
status: 'error',
|
|
2962
|
+
lines: [cmdErr.message || String(cmdErr)],
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2965
|
+
} finally {
|
|
2966
|
+
activeAbortController = null;
|
|
2967
|
+
cmdCtx.setIsProcessing(false);
|
|
2968
|
+
if (deferredCommands.length > 0) {
|
|
2969
|
+
const nextCmd = deferredCommands.shift();
|
|
2970
|
+
printRichCard({
|
|
2971
|
+
title: 'Deferred Command Executed',
|
|
2972
|
+
status: 'info',
|
|
2973
|
+
lines: [nextCmd],
|
|
2974
|
+
});
|
|
2975
|
+
setTimeout(async () => {
|
|
2976
|
+
try {
|
|
2977
|
+
const defParts = nextCmd.split(/\s+/);
|
|
2978
|
+
await routeCommand(nextCmd, {
|
|
2979
|
+
...cmdCtx,
|
|
2980
|
+
parts: defParts,
|
|
2981
|
+
sub: defParts[1] || '',
|
|
2982
|
+
extra: defParts.slice(2).join(' '),
|
|
2983
|
+
});
|
|
2984
|
+
} catch (err) {
|
|
2985
|
+
printRichCard({ title: 'Deferred Command Error', status: 'error', lines: [err.message || String(err)] });
|
|
2986
|
+
}
|
|
2987
|
+
}, 10);
|
|
2988
|
+
}
|
|
2539
2989
|
}
|
|
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
|
-
}
|
|
2990
|
+
})();
|
|
2561
2991
|
continue;
|
|
2562
2992
|
}
|
|
2563
2993
|
|
|
@@ -2612,22 +3042,46 @@ async function main() {
|
|
|
2612
3042
|
}
|
|
2613
3043
|
|
|
2614
3044
|
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
3045
|
+
|
|
3046
|
+
try {
|
|
3047
|
+
const autoProbe = modelManager
|
|
3048
|
+
? await modelManager.resolve()
|
|
3049
|
+
: await client.checkConnection();
|
|
3050
|
+
if (autoProbe?.connected && (autoProbe.selectedModel || autoProbe.activeLoadedModel)) {
|
|
3051
|
+
catalogOnlyMode = false;
|
|
3052
|
+
tuiPrompt.isConnected = true;
|
|
3053
|
+
tuiPrompt.modelName = autoProbe.selectedModel || autoProbe.activeLoadedModel;
|
|
3054
|
+
tuiPrompt.render();
|
|
3055
|
+
stopAutoReconnectWatcher();
|
|
3056
|
+
printRichCard({
|
|
3057
|
+
title: 'Model Auto-Connected',
|
|
3058
|
+
status: 'connected',
|
|
3059
|
+
lines: [
|
|
3060
|
+
`• Provider: ${client.providerName}${client.provider === 'lmstudio' ? ' (Local)' : ''} • Model: ${tuiPrompt.modelName}`,
|
|
3061
|
+
'• Loaded model detected in VRAM: chat typing unlocked.',
|
|
3062
|
+
],
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
3065
|
+
} catch {}
|
|
3066
|
+
|
|
3067
|
+
if (catalogOnlyMode || !tuiPrompt.isConnected) {
|
|
3068
|
+
printSolidPanel({
|
|
3069
|
+
title: 'AI Chat Locked • LM Studio Offline',
|
|
3070
|
+
lines: [
|
|
3071
|
+
`${C.bold}${C.amber}LM Studio is not running or connected.${C.reset}`,
|
|
3072
|
+
`${C.white}Chat query typing is disabled until the AI server is active.${C.reset}`,
|
|
3073
|
+
'',
|
|
3074
|
+
`${C.bold}${C.white}How to unlock & connect:${C.reset}`,
|
|
3075
|
+
` 1. Open ${C.cyberCyan}LM Studio${C.reset} on your computer`,
|
|
3076
|
+
` 2. Load model ${C.gold}${opts.model || client.model || 'google/gemma-4-e4b'}${C.reset} and ensure the local server is running (port 1234)`,
|
|
3077
|
+
` 3. The agent will auto-connect automatically (or type ${C.cyberCyan}/model reconnect${C.reset})`,
|
|
3078
|
+
'',
|
|
3079
|
+
`${C.dim}Offline commands available: /model reconnect, /help, /provider, /doctor, /theme, /exit${C.reset}`,
|
|
3080
|
+
],
|
|
3081
|
+
bg: C.bgPanel,
|
|
3082
|
+
});
|
|
3083
|
+
continue;
|
|
3084
|
+
}
|
|
2631
3085
|
}
|
|
2632
3086
|
|
|
2633
3087
|
const { sanitized, warnings } = sanitizeQuery(trimmed);
|
|
@@ -2653,6 +3107,7 @@ async function main() {
|
|
|
2653
3107
|
triggerQueueProcessing();
|
|
2654
3108
|
}
|
|
2655
3109
|
|
|
3110
|
+
stopAutoReconnectWatcher();
|
|
2656
3111
|
if (globalTuiPrompt) {
|
|
2657
3112
|
globalTuiPrompt.dispose();
|
|
2658
3113
|
globalTuiPrompt = null;
|